This is page 16 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/reactive/docs/guide/concept.md: -------------------------------------------------------------------------------- ```markdown 1 | # Core idea 2 | 3 | ## Observable 4 | 5 | Observable is the most important part of the reactive programming model. Its core concepts are: 6 | 7 | An observable object, literally means a subscribable object, **we create a subscribable object, each time we manipulate the attribute data of the object, we will automatically notify the subscriber**, @formily/reactive creates an observable object mainly It is created by ES Proxy, which can perfectly hijack data operations 8 | 9 | We mainly use the following APIs to create observable objects in @formily/reactive: 10 | 11 | - The observable function creates a deep observable object 12 | - The observable.deep function creates a deep hijacking observable object 13 | - The observable.shallow function creates shallow hijacked observable objects 14 | - The observable.computed function creates a cache calculator 15 | - The observable.box function creates observable objects with get/set methods 16 | - The observable.ref function creates a reference-level observable object 17 | - The define function defines the observable domain model, which can be combined with the observable function and its static attribute (such as observable.computed) function to complete the definition of the domain model 18 | - The model function defines an automatic observable domain model. It will wrap the getter setter attribute as a computed attribute, wrap the function as an action, and wrap other data attributes with observable (note that this is a deep hijacking) 19 | 20 | ## Reaction 21 | 22 | In the reactive programming model, reaction is equivalent to the subscriber of the subscribeable object. It receives a tracker function. When this function is executed, if there is a **read operation* on an attribute in the observable object inside the function. * (Dependency collection), then the current reaction will be bound to the attribute (dependency tracking), until the attribute has a **write operation\*\* in other places, it will trigger the tracker function to repeat execution, using a picture Means: 23 | 24 |  25 | 26 | You can see that from subscribing to dispatching subscriptions, it is actually a closed loop state machine. Each time the tracker function is executed, the dependencies are re-collected, and the tracker execution is re-triggered when the dependencies change. So, if we don't want to subscribe to the reaction anymore, we must manually dispose, otherwise there will be memory leaks. 27 | 28 | In @formily/reactive, we mainly use the following APIs to create reactions: 29 | 30 | - autorun creates an automatically executed responder 31 | - reaction creates a responder that can implement dirty checks 32 | - Tracker creates a dependency tracker that requires users to manually perform tracking 33 | 34 | ## Computed 35 | 36 | Computed is also a relatively important concept in the reactive programming model. In one sentence, **computed is a Reaction that can cache calculation results** 37 | 38 | Its caching strategy is: as long as the observable data that the computed function relies on changes, the function will re-execute the calculation, otherwise the cached result will always be read 39 | 40 | The requirement here is that the computed function must be a pure function. The internally dependent data is either observable data or external constant data. If it is external variable data (non-observable), then if the external variable data changes, the computed will not be re-executed computational. 41 | 42 | ## Batch 43 | 44 | As mentioned earlier, @formily/reactive is a reactive programming model based on Proxy hijacking. Therefore, any atomic operation will trigger the execution of Reaction, which is obviously a waste of computing resources. For example, we have a function for multiple observables. Property to operate: 45 | 46 | ```ts 47 | import { observable, autorun } from '@formily/reactive' 48 | const obs = observable({}) 49 | const handler = () => { 50 | obs.aa = 123 51 | obs.bb = 321 52 | } 53 | 54 | autorun(() => { 55 | console.log(obs.aa, obs.bb) 56 | }) 57 | 58 | handler() 59 | ``` 60 | 61 | This will execute 3 prints, autorun is executed once by default, plus the assignment of obs.aa is executed once, and the assignment of obs.bb is executed once. If there are more atomic operations, the number of executions will be more. Therefore, we recommend using batch mode To merge the updates: 62 | 63 | ```ts 64 | import { observable, autorun, batch } from '@formily/reactive' 65 | const obs = observable({}) 66 | const handler = () => { 67 | obs.aa = 123 68 | obs.bb = 321 69 | } 70 | 71 | autorun(() => { 72 | console.log(obs.aa, obs.bb) 73 | }) 74 | 75 | batch(() => { 76 | handler() 77 | }) 78 | ``` 79 | 80 | Of course, we can also use action for high-level packaging: 81 | 82 | ```ts 83 | import { observable, autorun, action } from '@formily/reactive' 84 | const obs = observable({}) 85 | const handler = action.bound(() => { 86 | obs.aa = 123 87 | obs.bb = 321 88 | }) 89 | 90 | autorun(() => { 91 | console.log(obs.aa, obs.bb) 92 | }) 93 | 94 | handler() 95 | ``` 96 | 97 | The final number of executions is 2 times, even if there are more operations inside the handler, it is still 2 times 98 | ``` -------------------------------------------------------------------------------- /packages/antd/src/form-collapse/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment, useMemo } from 'react' 2 | import { Collapse, Badge } from 'antd' 3 | import { model, markRaw } from '@formily/reactive' 4 | import { CollapseProps, CollapsePanelProps } from 'antd/lib/collapse' 5 | import { 6 | useField, 7 | observer, 8 | useFieldSchema, 9 | RecursionField, 10 | } from '@formily/react' 11 | import { Schema, SchemaKey } from '@formily/json-schema' 12 | import cls from 'classnames' 13 | import { usePrefixCls } from '../__builtins__' 14 | import { toArr } from '@formily/shared' 15 | 16 | type ActiveKeys = string | number | Array<string | number> 17 | 18 | type ActiveKey = string | number 19 | export interface IFormCollapse { 20 | activeKeys: ActiveKeys 21 | hasActiveKey(key: ActiveKey): boolean 22 | setActiveKeys(key: ActiveKeys): void 23 | addActiveKey(key: ActiveKey): void 24 | removeActiveKey(key: ActiveKey): void 25 | toggleActiveKey(key: ActiveKey): void 26 | } 27 | 28 | export interface IFormCollapseProps extends CollapseProps { 29 | formCollapse?: IFormCollapse 30 | } 31 | 32 | type ComposedFormCollapse = React.FC< 33 | React.PropsWithChildren<IFormCollapseProps> 34 | > & { 35 | CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>> 36 | createFormCollapse?: (defaultActiveKeys?: ActiveKeys) => IFormCollapse 37 | } 38 | 39 | const usePanels = () => { 40 | const collapseField = useField() 41 | const schema = useFieldSchema() 42 | const panels: { name: SchemaKey; props: any; schema: Schema }[] = [] 43 | schema.mapProperties((schema, name) => { 44 | const field = collapseField.query(collapseField.address.concat(name)).take() 45 | if (field?.display === 'none' || field?.display === 'hidden') return 46 | if (schema['x-component']?.indexOf('CollapsePanel') > -1) { 47 | const componentProps = field?.componentProps 48 | panels.push({ 49 | name, 50 | props: { 51 | ...componentProps, 52 | key: componentProps?.key || name, 53 | }, 54 | schema, 55 | }) 56 | } 57 | }) 58 | return panels 59 | } 60 | 61 | const createFormCollapse = (defaultActiveKeys?: ActiveKeys) => { 62 | const formCollapse = model({ 63 | activeKeys: defaultActiveKeys, 64 | setActiveKeys(keys: ActiveKeys) { 65 | formCollapse.activeKeys = keys 66 | }, 67 | hasActiveKey(key: ActiveKey) { 68 | if (Array.isArray(formCollapse.activeKeys)) { 69 | if (formCollapse.activeKeys.includes(key)) { 70 | return true 71 | } 72 | } else if (formCollapse.activeKeys == key) { 73 | return true 74 | } 75 | return false 76 | }, 77 | addActiveKey(key: ActiveKey) { 78 | if (formCollapse.hasActiveKey(key)) return 79 | formCollapse.activeKeys = toArr(formCollapse.activeKeys).concat(key) 80 | }, 81 | removeActiveKey(key: ActiveKey) { 82 | if (Array.isArray(formCollapse.activeKeys)) { 83 | formCollapse.activeKeys = formCollapse.activeKeys.filter( 84 | (item) => item != key 85 | ) 86 | } else { 87 | formCollapse.activeKeys = '' 88 | } 89 | }, 90 | toggleActiveKey(key: ActiveKey) { 91 | if (formCollapse.hasActiveKey(key)) { 92 | formCollapse.removeActiveKey(key) 93 | } else { 94 | formCollapse.addActiveKey(key) 95 | } 96 | }, 97 | }) 98 | return markRaw(formCollapse) 99 | } 100 | 101 | export const FormCollapse: ComposedFormCollapse = observer( 102 | ({ formCollapse, ...props }) => { 103 | const field = useField() 104 | const panels = usePanels() 105 | const prefixCls = usePrefixCls('formily-collapse', props) 106 | const _formCollapse = useMemo(() => { 107 | return formCollapse 108 | ? formCollapse 109 | : createFormCollapse(props.defaultActiveKey) 110 | }, []) 111 | 112 | const takeActiveKeys = () => { 113 | if (props.activeKey) return props.activeKey 114 | if (_formCollapse?.activeKeys) return _formCollapse?.activeKeys 115 | if (props.accordion) return panels[0]?.name 116 | return panels.map((item) => item.name) 117 | } 118 | 119 | const badgedHeader = (key: SchemaKey, props: any) => { 120 | const errors = field.form.queryFeedbacks({ 121 | type: 'error', 122 | address: `${field.address.concat(key)}.*`, 123 | }) 124 | if (errors.length) { 125 | return ( 126 | <Badge size="small" className="errors-badge" count={errors.length}> 127 | {props.header} 128 | </Badge> 129 | ) 130 | } 131 | return props.header 132 | } 133 | return ( 134 | <Collapse 135 | {...props} 136 | className={cls(prefixCls, props.className)} 137 | activeKey={takeActiveKeys()} 138 | onChange={(key) => { 139 | props?.onChange?.(key) 140 | _formCollapse?.setActiveKeys?.(key) 141 | }} 142 | > 143 | {panels.map(({ props, schema, name }, index) => ( 144 | <Collapse.Panel 145 | key={index} 146 | {...props} 147 | header={badgedHeader(name, props)} 148 | forceRender 149 | > 150 | <RecursionField schema={schema} name={name} /> 151 | </Collapse.Panel> 152 | ))} 153 | </Collapse> 154 | ) 155 | } 156 | ) 157 | 158 | const CollapsePanel: React.FC<React.PropsWithChildren<CollapsePanelProps>> = ({ 159 | children, 160 | }) => { 161 | return <Fragment>{children}</Fragment> 162 | } 163 | 164 | FormCollapse.CollapsePanel = CollapsePanel 165 | FormCollapse.createFormCollapse = createFormCollapse 166 | 167 | export default FormCollapse 168 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-collapse/json-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField :schema="schema" /> 4 | <Submit @submit="log">提交</Submit> 5 | </FormProvider> 6 | </template> 7 | 8 | <script> 9 | import { createForm } from '@formily/core' 10 | import { FormProvider, createSchemaField } from '@formily/vue' 11 | import { 12 | FormItem, 13 | FormButtonGroup, 14 | Submit, 15 | Input, 16 | ArrayCollapse, 17 | } from '@formily/element' 18 | import { Button } from 'element-ui' 19 | 20 | const SchemaField = createSchemaField({ 21 | components: { 22 | FormItem, 23 | Input, 24 | ArrayCollapse, 25 | }, 26 | }) 27 | 28 | export default { 29 | components: { 30 | FormProvider, 31 | FormButtonGroup, 32 | Button, 33 | Submit, 34 | ...SchemaField, 35 | }, 36 | 37 | data() { 38 | const form = createForm() 39 | const schema = { 40 | type: 'object', 41 | properties: { 42 | string_array: { 43 | type: 'array', 44 | 'x-component': 'ArrayCollapse', 45 | maxItems: 3, 46 | 'x-decorator': 'FormItem', 47 | items: { 48 | type: 'object', 49 | 'x-component': 'ArrayCollapse.Item', 50 | 'x-component-props': { 51 | title: '字符串数组', 52 | }, 53 | properties: { 54 | index: { 55 | type: 'void', 56 | 'x-component': 'ArrayCollapse.Index', 57 | }, 58 | input: { 59 | type: 'string', 60 | 'x-decorator': 'FormItem', 61 | title: 'Input', 62 | required: true, 63 | 'x-component': 'Input', 64 | }, 65 | remove: { 66 | type: 'void', 67 | 'x-component': 'ArrayCollapse.Remove', 68 | }, 69 | moveUp: { 70 | type: 'void', 71 | 'x-component': 'ArrayCollapse.MoveUp', 72 | }, 73 | moveDown: { 74 | type: 'void', 75 | 'x-component': 'ArrayCollapse.MoveDown', 76 | }, 77 | }, 78 | }, 79 | properties: { 80 | addition: { 81 | type: 'void', 82 | title: '添加条目', 83 | 'x-component': 'ArrayCollapse.Addition', 84 | }, 85 | }, 86 | }, 87 | array: { 88 | type: 'array', 89 | 'x-component': 'ArrayCollapse', 90 | maxItems: 3, 91 | 'x-decorator': 'FormItem', 92 | items: { 93 | type: 'object', 94 | 'x-component': 'ArrayCollapse.Item', 95 | 'x-component-props': { 96 | title: '对象数组', 97 | }, 98 | properties: { 99 | index: { 100 | type: 'void', 101 | 'x-component': 'ArrayCollapse.Index', 102 | }, 103 | input: { 104 | type: 'string', 105 | 'x-decorator': 'FormItem', 106 | title: 'Input', 107 | required: true, 108 | 'x-component': 'Input', 109 | }, 110 | remove: { 111 | type: 'void', 112 | 'x-component': 'ArrayCollapse.Remove', 113 | }, 114 | moveUp: { 115 | type: 'void', 116 | 'x-component': 'ArrayCollapse.MoveUp', 117 | }, 118 | moveDown: { 119 | type: 'void', 120 | 'x-component': 'ArrayCollapse.MoveDown', 121 | }, 122 | }, 123 | }, 124 | properties: { 125 | addition: { 126 | type: 'void', 127 | title: '添加条目', 128 | 'x-component': 'ArrayCollapse.Addition', 129 | }, 130 | }, 131 | }, 132 | array_unshift: { 133 | type: 'array', 134 | 'x-component': 'ArrayCollapse', 135 | maxItems: 3, 136 | 'x-decorator': 'FormItem', 137 | items: { 138 | type: 'object', 139 | 'x-component': 'ArrayCollapse.Item', 140 | 'x-component-props': { 141 | title: '对象数组', 142 | }, 143 | properties: { 144 | index: { 145 | type: 'void', 146 | 'x-component': 'ArrayCollapse.Index', 147 | }, 148 | input: { 149 | type: 'string', 150 | 'x-decorator': 'FormItem', 151 | title: 'Input', 152 | required: true, 153 | 'x-component': 'Input', 154 | }, 155 | remove: { 156 | type: 'void', 157 | 'x-component': 'ArrayCollapse.Remove', 158 | }, 159 | moveUp: { 160 | type: 'void', 161 | 'x-component': 'ArrayCollapse.MoveUp', 162 | }, 163 | moveDown: { 164 | type: 'void', 165 | 'x-component': 'ArrayCollapse.MoveDown', 166 | }, 167 | }, 168 | }, 169 | properties: { 170 | addition: { 171 | type: 'void', 172 | title: '添加条目(unshift)', 173 | 'x-component': 'ArrayCollapse.Addition', 174 | 'x-component-props': { 175 | method: 'unshift', 176 | }, 177 | }, 178 | }, 179 | }, 180 | }, 181 | } 182 | 183 | return { 184 | form, 185 | schema, 186 | } 187 | }, 188 | methods: { 189 | log(values) { 190 | console.log(values) 191 | }, 192 | }, 193 | } 194 | </script> 195 | 196 | <style lang="scss" scoped></style> 197 | ``` -------------------------------------------------------------------------------- /packages/element/src/array-items/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ArrayField } from '@formily/core' 2 | import { ISchema } from '@formily/json-schema' 3 | import { observer } from '@formily/reactive-vue' 4 | import { h, RecursionField, useField, useFieldSchema } from '@formily/vue' 5 | import { defineComponent } from 'vue-demi' 6 | import { SlickItem, SlickList } from 'vue-slicksort' 7 | import { ArrayBase } from '../array-base' 8 | import { stylePrefix } from '../__builtins__/configs' 9 | import { composeExport } from '../__builtins__/shared' 10 | 11 | const isAdditionComponent = (schema: ISchema) => { 12 | return schema['x-component']?.indexOf('Addition') > -1 13 | } 14 | 15 | export interface IArrayItemsItemProps { 16 | type?: 'card' | 'divide' 17 | } 18 | 19 | const ArrayItemsInner = observer( 20 | defineComponent({ 21 | name: 'FArrayItems', 22 | setup() { 23 | const fieldRef = useField<ArrayField>() 24 | const schemaRef = useFieldSchema() 25 | 26 | const prefixCls = `${stylePrefix}-array-items` 27 | const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value) 28 | 29 | return () => { 30 | const field = fieldRef.value 31 | const schema = schemaRef.value 32 | const dataSource = Array.isArray(field.value) ? field.value.slice() : [] 33 | 34 | const renderItems = () => { 35 | const items = dataSource?.map((item, index) => { 36 | const items = Array.isArray(schema.items) 37 | ? schema.items[index] || schema.items[0] 38 | : schema.items 39 | const key = getKey(item, index) 40 | return h( 41 | ArrayBase.Item, 42 | { 43 | key, 44 | props: { 45 | index, 46 | record: item, 47 | }, 48 | }, 49 | { 50 | default: () => 51 | h( 52 | SlickItem, 53 | { 54 | class: [`${prefixCls}-item-inner`], 55 | props: { 56 | index, 57 | }, 58 | key, 59 | }, 60 | { 61 | default: () => 62 | h( 63 | RecursionField, 64 | { 65 | props: { 66 | schema: items, 67 | name: index, 68 | }, 69 | }, 70 | {} 71 | ), 72 | } 73 | ), 74 | } 75 | ) 76 | }) 77 | 78 | return h( 79 | SlickList, 80 | { 81 | class: [`${prefixCls}-list`], 82 | props: { 83 | useDragHandle: true, 84 | lockAxis: 'y', 85 | helperClass: `${prefixCls}-sort-helper`, 86 | value: [], 87 | }, 88 | on: { 89 | 'sort-end': ({ oldIndex, newIndex }) => { 90 | if (Array.isArray(keyMap)) { 91 | keyMap.splice(newIndex, 0, keyMap.splice(oldIndex, 1)[0]) 92 | } 93 | field.move(oldIndex, newIndex) 94 | }, 95 | }, 96 | }, 97 | { default: () => items } 98 | ) 99 | } 100 | const renderAddition = () => { 101 | return schema.reduceProperties((addition, schema) => { 102 | if (isAdditionComponent(schema)) { 103 | return h( 104 | RecursionField, 105 | { 106 | props: { 107 | schema, 108 | name: 'addition', 109 | }, 110 | }, 111 | {} 112 | ) 113 | } 114 | return addition 115 | }, null) 116 | } 117 | 118 | return h( 119 | ArrayBase, 120 | { 121 | props: { 122 | keyMap, 123 | }, 124 | }, 125 | { 126 | default: () => 127 | h( 128 | 'div', 129 | { 130 | class: [prefixCls], 131 | on: { 132 | change: () => {}, 133 | }, 134 | }, 135 | { 136 | default: () => [renderItems(), renderAddition()], 137 | } 138 | ), 139 | } 140 | ) 141 | } 142 | }, 143 | }) 144 | ) 145 | 146 | const ArrayItemsItem = defineComponent<IArrayItemsItemProps>({ 147 | name: 'FArrayItemsItem', 148 | props: ['type'], 149 | setup(props, { attrs, slots }) { 150 | const prefixCls = `${stylePrefix}-array-items` 151 | 152 | return () => 153 | h( 154 | 'div', 155 | { 156 | class: [`${prefixCls}-${props.type || 'card'}`], 157 | attrs: { 158 | ...attrs, 159 | }, 160 | on: { 161 | change: () => {}, 162 | }, 163 | }, 164 | slots 165 | ) 166 | }, 167 | }) 168 | 169 | export const ArrayItems = composeExport(ArrayItemsInner, { 170 | Item: ArrayItemsItem, 171 | Index: ArrayBase.Index, 172 | SortHandle: ArrayBase.SortHandle, 173 | Addition: ArrayBase.Addition, 174 | Remove: ArrayBase.Remove, 175 | MoveDown: ArrayBase.MoveDown, 176 | MoveUp: ArrayBase.MoveUp, 177 | useArray: ArrayBase.useArray, 178 | useIndex: ArrayBase.useIndex, 179 | useRecord: ArrayBase.useRecord, 180 | }) 181 | 182 | export default ArrayItems 183 | ``` -------------------------------------------------------------------------------- /packages/next/src/form-collapse/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment, useMemo } from 'react' 2 | import { Collapse, Badge } from '@alifd/next' 3 | import { model, markRaw } from '@formily/reactive' 4 | import { 5 | CollapseProps, 6 | PanelProps as CollapsePanelProps, 7 | } from '@alifd/next/lib/collapse' 8 | import { 9 | useField, 10 | observer, 11 | useFieldSchema, 12 | RecursionField, 13 | } from '@formily/react' 14 | import { Schema, SchemaKey } from '@formily/json-schema' 15 | import { toArr } from '@formily/shared' 16 | import cls from 'classnames' 17 | import { usePrefixCls } from '../__builtins__' 18 | 19 | type ActiveKeys = string | number | Array<string | number> 20 | 21 | type ActiveKey = string | number 22 | 23 | export interface IFormCollapse { 24 | activeKeys: ActiveKeys 25 | hasActiveKey(key: ActiveKey): boolean 26 | setActiveKeys(key: ActiveKeys): void 27 | addActiveKey(key: ActiveKey): void 28 | removeActiveKey(key: ActiveKey): void 29 | toggleActiveKey(key: ActiveKey): void 30 | } 31 | 32 | export interface IFormCollapseProps extends CollapseProps { 33 | formCollapse?: IFormCollapse 34 | } 35 | 36 | type ComposedFormCollapse = React.FC< 37 | React.PropsWithChildren<IFormCollapseProps> 38 | > & { 39 | CollapsePanel?: React.FC<React.PropsWithChildren<CollapsePanelProps>> 40 | createFormCollapse?: ( 41 | defaultActiveKeys?: CollapseProps['expandedKeys'] 42 | ) => IFormCollapse 43 | } 44 | 45 | const usePanels = () => { 46 | const collapseField = useField() 47 | const schema = useFieldSchema() 48 | const panels: { name: SchemaKey; props: any; schema: Schema }[] = [] 49 | schema.mapProperties((schema, name) => { 50 | const field = collapseField.query(collapseField.address.concat(name)).take() 51 | if (field?.display === 'none' || field?.display === 'hidden') return 52 | if (schema['x-component']?.indexOf('CollapsePanel') > -1) { 53 | panels.push({ 54 | name, 55 | props: { 56 | ...schema?.['x-component-props'], 57 | title: schema?.['x-component-props']?.title || schema?.title, 58 | key: schema?.['x-component-props']?.key || name, 59 | }, 60 | schema, 61 | }) 62 | } 63 | }) 64 | return panels 65 | } 66 | 67 | const createFormCollapse = (defaultActiveKeys?: ActiveKeys) => { 68 | const formCollapse = model({ 69 | activeKeys: defaultActiveKeys, 70 | setActiveKeys(keys: ActiveKeys) { 71 | formCollapse.activeKeys = keys 72 | }, 73 | hasActiveKey(key: ActiveKey) { 74 | if (Array.isArray(formCollapse.activeKeys)) { 75 | if (formCollapse.activeKeys.includes(key)) { 76 | return true 77 | } 78 | } else if (formCollapse.activeKeys == key) { 79 | return true 80 | } 81 | return false 82 | }, 83 | addActiveKey(key: ActiveKey) { 84 | if (formCollapse.hasActiveKey(key)) return 85 | formCollapse.activeKeys = toArr(formCollapse.activeKeys).concat(key) 86 | }, 87 | removeActiveKey(key: ActiveKey) { 88 | if (Array.isArray(formCollapse.activeKeys)) { 89 | formCollapse.activeKeys = formCollapse.activeKeys.filter( 90 | (item) => item != key 91 | ) 92 | } else { 93 | formCollapse.activeKeys = [] 94 | } 95 | }, 96 | toggleActiveKey(key: ActiveKey) { 97 | if (formCollapse.hasActiveKey(key)) { 98 | formCollapse.removeActiveKey(key) 99 | } else { 100 | formCollapse.addActiveKey(key) 101 | } 102 | }, 103 | }) 104 | return markRaw(formCollapse) 105 | } 106 | 107 | export const FormCollapse: ComposedFormCollapse = observer( 108 | ({ formCollapse, ...props }) => { 109 | const field = useField() 110 | const panels = usePanels() 111 | const prefixCls = usePrefixCls('formily-collapse', props) 112 | const _formCollapse = useMemo(() => { 113 | return formCollapse 114 | ? formCollapse 115 | : createFormCollapse(props.defaultExpandedKeys) 116 | }, []) 117 | 118 | const takeExpandedKeys = () => { 119 | if (props.expandedKeys) return props.expandedKeys 120 | if (_formCollapse?.activeKeys) return _formCollapse?.activeKeys 121 | if (props.accordion) return panels[0]?.name 122 | return panels.map((item) => item.name) 123 | } 124 | 125 | const badgedHeader = (key: SchemaKey, props: any) => { 126 | const errors = field.form.queryFeedbacks({ 127 | type: 'error', 128 | address: `${field.address.concat(key)}.*`, 129 | }) 130 | if (errors.length) { 131 | return ( 132 | <Badge className="errors-badge" count={errors.length}> 133 | {props.title} 134 | </Badge> 135 | ) 136 | } 137 | return props.title 138 | } 139 | return ( 140 | <Collapse 141 | {...props} 142 | className={cls(prefixCls, props.className)} 143 | expandedKeys={takeExpandedKeys() as any} 144 | onExpand={(keys) => { 145 | props?.onExpand?.(keys) 146 | _formCollapse?.setActiveKeys?.(keys) 147 | }} 148 | > 149 | {panels.map(({ props, schema, name }, index) => ( 150 | <Collapse.Panel 151 | key={index} 152 | {...props} 153 | title={badgedHeader(name, props)} 154 | > 155 | <RecursionField schema={schema} name={name} /> 156 | </Collapse.Panel> 157 | ))} 158 | </Collapse> 159 | ) 160 | } 161 | ) 162 | 163 | const CollapsePanel: React.FC<React.PropsWithChildren<CollapsePanelProps>> = ({ 164 | children, 165 | }) => { 166 | return <Fragment>{children}</Fragment> 167 | } 168 | 169 | FormCollapse.CollapsePanel = CollapsePanel 170 | FormCollapse.createFormCollapse = createFormCollapse 171 | 172 | export default FormCollapse 173 | ``` -------------------------------------------------------------------------------- /packages/react/src/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import { 3 | Form, 4 | Field as FieldType, 5 | VoidField, 6 | ObjectField, 7 | GeneralField, 8 | IFieldFactoryProps, 9 | IVoidFieldFactoryProps, 10 | FormPatternTypes, 11 | FieldDisplayTypes, 12 | FieldValidator, 13 | } from '@formily/core' 14 | import { ReactFC } from '@formily/reactive-react' 15 | import { ISchema, Schema, SchemaKey } from '@formily/json-schema' 16 | import { FormPathPattern } from '@formily/shared' 17 | 18 | export type JSXComponent = 19 | | keyof JSX.IntrinsicElements 20 | | React.JSXElementConstructor<any> 21 | 22 | export type IProviderProps = { 23 | form: Form 24 | } 25 | 26 | export interface IFormSpyProps { 27 | children?: (form: Form) => ReactChild 28 | } 29 | 30 | export type RenderPropsChildren<Payload> = 31 | | ((field: Payload, form: Form) => React.ReactNode) 32 | | React.ReactNode 33 | 34 | export interface IFieldProps< 35 | D extends JSXComponent, 36 | C extends JSXComponent, 37 | Field = FieldType 38 | > extends IFieldFactoryProps<D, C> { 39 | children?: RenderPropsChildren<Field> 40 | decorator?: [] | [D] | [D, React.ComponentProps<D>] | any[] 41 | component?: [] | [C] | [C, React.ComponentProps<C>] | any[] 42 | } 43 | 44 | export interface IVoidFieldProps< 45 | D extends JSXComponent, 46 | C extends JSXComponent, 47 | Field = VoidField 48 | > extends IVoidFieldFactoryProps<D, C> { 49 | children?: RenderPropsChildren<Field> 50 | decorator?: [] | [D] | [D, React.ComponentProps<D>] | any[] 51 | component?: [] | [C] | [C, React.ComponentProps<C>] | any[] 52 | } 53 | 54 | export interface IComponentMapper<T extends JSXComponent> { 55 | (target: T): JSXComponent 56 | } 57 | 58 | export type IStateMapper<Props> = 59 | | { 60 | [key in keyof FieldType]?: keyof Props | boolean 61 | } 62 | | ((props: Props, field: GeneralField) => Props) 63 | 64 | export type SchemaReactComponents = Record<string, JSXComponent> 65 | 66 | export interface ISchemaFieldReactFactoryOptions< 67 | Components extends SchemaReactComponents = any 68 | > { 69 | components?: Components 70 | scope?: any 71 | } 72 | 73 | export interface ISchemaFieldOptionContext { 74 | components: SchemaReactComponents 75 | } 76 | 77 | export interface ISchemaFieldProps< 78 | Decorator extends JSXComponent = any, 79 | Component extends JSXComponent = any, 80 | InnerField = ObjectField<Decorator, Component> 81 | > extends Omit<IFieldFactoryProps<Decorator, Component, InnerField>, 'name'> { 82 | schema?: ISchema 83 | components?: { 84 | [key: string]: JSXComponent 85 | } 86 | scope?: any 87 | name?: SchemaKey 88 | children?: React.ReactNode 89 | } 90 | 91 | export interface ISchemaMapper { 92 | (schema: Schema, name: SchemaKey): Schema 93 | } 94 | 95 | export interface ISchemaFilter { 96 | (schema: Schema, name: SchemaKey): boolean 97 | } 98 | export interface IRecursionFieldProps { 99 | schema: ISchema 100 | name?: SchemaKey 101 | basePath?: FormPathPattern 102 | propsRecursion?: boolean 103 | onlyRenderProperties?: boolean 104 | onlyRenderSelf?: boolean 105 | mapProperties?: ISchemaMapper 106 | filterProperties?: ISchemaFilter 107 | } 108 | 109 | export type ObjectKey = string | number | boolean | symbol 110 | 111 | export type Path<T, Key extends keyof T = keyof T> = Key extends string 112 | ? T[Key] extends Record<string, any> 113 | ? 114 | | `${Key}.${Path<T[Key], Exclude<keyof T[Key], keyof Array<any>>> & 115 | string}` 116 | | `${Key}.${Exclude<keyof T[Key], keyof Array<any>> & string}` 117 | | Key 118 | : Key 119 | : never 120 | 121 | export type PathValue< 122 | T, 123 | P extends Path<T> 124 | > = P extends `${infer Key}.${infer Rest}` 125 | ? Key extends keyof T 126 | ? Rest extends Path<T[Key]> 127 | ? PathValue<T[Key], Rest> 128 | : never 129 | : never 130 | : P extends keyof T 131 | ? T[P] 132 | : never 133 | 134 | export type KeyOfReactComponent<T> = Exclude< 135 | keyof T, 136 | 'contextTypes' | 'displayName' | 'propTypes' | 'defaultProps' 137 | > 138 | 139 | export type ReactComponentPath< 140 | T, 141 | Key extends KeyOfReactComponent<T> = KeyOfReactComponent<T> 142 | > = Key extends string 143 | ? T[Key] extends Record<string, any> 144 | ? 145 | | `${Key}.${Exclude<KeyOfReactComponent<T[Key]>, keyof Array<any>> & 146 | string}` 147 | | Key 148 | : Key 149 | : never 150 | 151 | export type ReactComponentPropsByPathValue< 152 | T extends Record<string, any>, 153 | P extends ReactComponentPath<T> 154 | > = P extends `${infer Key}.${infer Rest}` 155 | ? Key extends keyof T 156 | ? Rest extends ReactComponentPath<T[Key]> 157 | ? ReactComponentPropsByPathValue<T[Key], Rest> 158 | : never 159 | : React.ComponentProps<T[P]> 160 | : P extends keyof T 161 | ? React.ComponentProps<T[P]> 162 | : never 163 | export interface ISchemaMarkupFieldProps< 164 | Components extends SchemaReactComponents, 165 | Decorator extends ReactComponentPath<Components>, 166 | Component extends ReactComponentPath<Components> 167 | > extends ISchema< 168 | Decorator, 169 | Component, 170 | ReactComponentPropsByPathValue<Components, Decorator>, 171 | ReactComponentPropsByPathValue<Components, Component>, 172 | FormPatternTypes, 173 | FieldDisplayTypes, 174 | FieldValidator, 175 | React.ReactNode, 176 | GeneralField 177 | > { 178 | children?: React.ReactNode 179 | } 180 | 181 | export type ISchemaTypeFieldProps< 182 | Components extends SchemaReactComponents, 183 | Decorator extends ReactComponentPath<Components>, 184 | Component extends ReactComponentPath<Components> 185 | > = ISchemaMarkupFieldProps<Components, Decorator, Component> 186 | 187 | export interface IExpressionScopeProps { 188 | value?: any 189 | } 190 | 191 | export interface IRecordScopeProps { 192 | getIndex?(): number 193 | getRecord(): any 194 | } 195 | 196 | export interface IRecordsScopeProps { 197 | getRecords(): any[] 198 | } 199 | 200 | export type ReactChild = React.ReactElement | string | number 201 | 202 | export { ReactFC } 203 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-grid/form.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaObjectField x-component="QueryForm"> 5 | <SchemaStringField 6 | name="input1" 7 | title="Input 1" 8 | x-component="Input" 9 | x-decorator="FormItem" 10 | /> 11 | <SchemaStringField 12 | name="input2" 13 | title="Input 2" 14 | x-component="Input" 15 | x-decorator="FormItem" 16 | /> 17 | 18 | <SchemaStringField 19 | name="select1" 20 | title="Select 1" 21 | x-component="Select" 22 | x-decorator="FormItem" 23 | /> 24 | <SchemaStringField 25 | name="select2" 26 | title="Select 2" 27 | x-component="Select" 28 | x-decorator="FormItem" 29 | /> 30 | <SchemaStringField 31 | name="date" 32 | title="DatePicker" 33 | x-component="DatePicker" 34 | x-decorator="FormItem" 35 | /> 36 | <SchemaStringField 37 | name="dateRange" 38 | title="DatePicker" 39 | x-component="DatePicker" 40 | x-decorator="FormItem" 41 | :x-decorator-props="{ 42 | gridSpan: 2, 43 | }" 44 | :x-component-props="{ 45 | type: 'daterange', 46 | }" 47 | /> 48 | <SchemaStringField 49 | name="select3" 50 | title="Select 3" 51 | x-component="Select" 52 | x-decorator="FormItem" 53 | /> 54 | </SchemaObjectField> 55 | </SchemaField> 56 | </FormProvider> 57 | </template> 58 | 59 | <script> 60 | import { defineComponent, ref, onUnmounted } from 'vue-demi' 61 | import { createForm } from '@formily/core' 62 | import { 63 | createSchemaField, 64 | FormProvider, 65 | FragmentComponent, 66 | } from '@formily/vue' 67 | import { autorun } from '@formily/reactive' 68 | import { observer } from '@formily/reactive-vue' 69 | import { 70 | Form, 71 | Input, 72 | Select, 73 | DatePicker, 74 | FormItem, 75 | FormGrid, 76 | Submit, 77 | Reset, 78 | FormButtonGroup, 79 | } from '@formily/element' 80 | 81 | const useCollapseGrid = (maxRows) => { 82 | const grid = FormGrid.createFormGrid({ 83 | maxColumns: 4, 84 | maxWidth: 240, 85 | maxRows: maxRows, 86 | shouldVisible: (node, grid) => { 87 | if (node.index === grid.childSize - 1) return true 88 | if (grid.maxRows === Infinity) return true 89 | return node.shadowRow < maxRows + 1 90 | }, 91 | }) 92 | 93 | const expanded = ref(false) 94 | const type = ref('') 95 | 96 | const takeType = (realRows, computeRows) => { 97 | if (realRows < maxRows + 1) return 'incomplete-wrap' 98 | if (computeRows > maxRows) return 'collapsible' 99 | return 'complete-wrap' 100 | } 101 | 102 | const dispose = autorun(() => { 103 | expanded.value = grid.maxRows === Infinity 104 | 105 | const realRows = grid.shadowRows 106 | const computeRows = grid.fullnessLastColumn 107 | ? grid.shadowRows - 1 108 | : grid.shadowRows 109 | 110 | type.value = takeType(realRows, computeRows) 111 | }) 112 | 113 | onUnmounted(dispose) 114 | 115 | const toggle = () => { 116 | if (grid.maxRows === Infinity) { 117 | grid.maxRows = maxRows 118 | } else { 119 | grid.maxRows = Infinity 120 | } 121 | } 122 | return { 123 | grid, 124 | expanded, 125 | toggle, 126 | type, 127 | } 128 | } 129 | 130 | const QueryForm = observer( 131 | defineComponent({ 132 | setup(props, { slots }) { 133 | const { grid, expanded, toggle, type } = useCollapseGrid(1) 134 | 135 | const renderActions = () => { 136 | return ( 137 | <FragmentComponent> 138 | <Submit onSubmit={console.log}>查询</Submit> 139 | <Reset>重置</Reset> 140 | </FragmentComponent> 141 | ) 142 | } 143 | 144 | const renderButtonGroup = () => { 145 | if (type.value === 'incomplete-wrap') { 146 | return ( 147 | <FormButtonGroup.FormItem> 148 | <FormButtonGroup>{renderActions()}</FormButtonGroup> 149 | </FormButtonGroup.FormItem> 150 | ) 151 | } 152 | if (type.value === 'collapsible') { 153 | return ( 154 | <FragmentComponent> 155 | <FormButtonGroup> 156 | <a 157 | href="" 158 | onClick={(e) => { 159 | e.preventDefault() 160 | toggle() 161 | }} 162 | > 163 | {expanded.value ? '收起' : '展开'} 164 | </a> 165 | </FormButtonGroup> 166 | <FormButtonGroup align="right">{renderActions()}</FormButtonGroup> 167 | </FragmentComponent> 168 | ) 169 | } 170 | return ( 171 | <FormButtonGroup 172 | align="right" 173 | style={{ display: 'flex', width: '100%' }} 174 | > 175 | {renderActions()} 176 | </FormButtonGroup> 177 | ) 178 | } 179 | 180 | return () => { 181 | return ( 182 | <Form {...props} layout="vertical" feedbackLayout="terse"> 183 | <FormGrid grid={grid}> 184 | {slots.default()} 185 | <FormGrid.GridColumn 186 | gridSpan={-1} 187 | style={{ display: 'flex', justifyContent: 'space-between' }} 188 | > 189 | {renderButtonGroup()} 190 | </FormGrid.GridColumn> 191 | </FormGrid> 192 | </Form> 193 | ) 194 | } 195 | }, 196 | }) 197 | ) 198 | 199 | const form = createForm() 200 | const fields = createSchemaField({ 201 | components: { 202 | QueryForm, 203 | Input, 204 | Select, 205 | DatePicker, 206 | FormItem, 207 | }, 208 | }) 209 | 210 | export default { 211 | components: { FormProvider, ...fields, Submit }, 212 | data() { 213 | return { 214 | form, 215 | } 216 | }, 217 | methods: { 218 | onSubmit(value) { 219 | console.log(value) 220 | }, 221 | }, 222 | } 223 | </script> 224 | ``` -------------------------------------------------------------------------------- /packages/json-schema/src/shared.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isFn, each, isPlainObj, isArr, toArr, FormPath } from '@formily/shared' 2 | import { isObservable, untracked } from '@formily/reactive' 3 | import { Schema } from './schema' 4 | import { ISchema } from './types' 5 | 6 | const REVA_ACTIONS_KEY = Symbol.for('__REVA_ACTIONS') 7 | 8 | export const SchemaNestedMap = { 9 | parent: true, 10 | root: true, 11 | properties: true, 12 | patternProperties: true, 13 | additionalProperties: true, 14 | items: true, 15 | additionalItems: true, 16 | 'x-linkages': true, 17 | 'x-reactions': true, 18 | } 19 | 20 | export const SchemaStateMap = { 21 | title: 'title', 22 | description: 'description', 23 | default: 'initialValue', 24 | enum: 'dataSource', 25 | readOnly: 'readOnly', 26 | writeOnly: 'editable', 27 | 'x-content': 'content', 28 | 'x-data': 'data', 29 | 'x-value': 'value', 30 | 'x-editable': 'editable', 31 | 'x-disabled': 'disabled', 32 | 'x-read-pretty': 'readPretty', 33 | 'x-read-only': 'readOnly', 34 | 'x-visible': 'visible', 35 | 'x-hidden': 'hidden', 36 | 'x-display': 'display', 37 | 'x-pattern': 'pattern', 38 | 'x-validator': 'validator', 39 | 'x-decorator': 'decoratorType', 40 | 'x-component': 'componentType', 41 | 'x-decorator-props': 'decoratorProps', 42 | 'x-component-props': 'componentProps', 43 | } 44 | 45 | export const SchemaValidatorMap = { 46 | required: true, 47 | format: true, 48 | maxItems: true, 49 | minItems: true, 50 | maxLength: true, 51 | minLength: true, 52 | maximum: true, 53 | minimum: true, 54 | exclusiveMaximum: true, 55 | exclusiveMinimum: true, 56 | pattern: true, 57 | const: true, 58 | multipleOf: true, 59 | maxProperties: true, 60 | minProperties: true, 61 | uniqueItems: true, 62 | } 63 | 64 | export const SchemaNormalKeys = Object.keys(SchemaStateMap) 65 | 66 | export const SchemaValidatorKeys = Object.keys(SchemaValidatorMap) 67 | 68 | export const hasOwnProperty = Object.prototype.hasOwnProperty 69 | 70 | export const traverse = ( 71 | target: any, 72 | visitor: (value: any, path: Array<string | number>) => void 73 | ) => { 74 | const seenObjects = [] 75 | const root = target 76 | const traverse = (target: any, path = []) => { 77 | if (isPlainObj(target)) { 78 | const seenIndex = seenObjects.indexOf(target) 79 | if (seenIndex > -1) { 80 | return 81 | } 82 | const addIndex = seenObjects.length 83 | seenObjects.push(target) 84 | if (isNoNeedCompileObject(target) && root !== target) { 85 | visitor(target, path) 86 | return 87 | } 88 | each(target, (value, key) => { 89 | traverse(value, path.concat(key)) 90 | }) 91 | seenObjects.splice(addIndex, 1) 92 | } else { 93 | visitor(target, path) 94 | } 95 | } 96 | traverse(target) 97 | } 98 | 99 | export const traverseSchema = ( 100 | schema: ISchema, 101 | visitor: (value: any, path: any[], omitCompile?: boolean) => void 102 | ) => { 103 | if (schema['x-validator'] !== undefined) { 104 | visitor( 105 | schema['x-validator'], 106 | ['x-validator'], 107 | schema['x-compile-omitted']?.includes('x-validator') 108 | ) 109 | } 110 | const seenObjects = [] 111 | const root = schema 112 | const traverse = (target: any, path = []) => { 113 | if ( 114 | path[0] === 'x-compile-omitted' || 115 | path[0] === 'x-validator' || 116 | path[0] === 'version' || 117 | path[0] === '_isJSONSchemaObject' 118 | ) 119 | return 120 | if (String(path[0]).indexOf('x-') == -1 && isFn(target)) return 121 | if (SchemaNestedMap[path[0]]) return 122 | if (schema['x-compile-omitted']?.indexOf(path[0]) > -1) { 123 | visitor(target, path, true) 124 | return 125 | } 126 | if (isPlainObj(target)) { 127 | if (path[0] === 'default' || path[0] === 'x-value') { 128 | visitor(target, path) 129 | return 130 | } 131 | const seenIndex = seenObjects.indexOf(target) 132 | if (seenIndex > -1) { 133 | return 134 | } 135 | const addIndex = seenObjects.length 136 | seenObjects.push(target) 137 | if (isNoNeedCompileObject(target) && root !== target) { 138 | visitor(target, path) 139 | return 140 | } 141 | each(target, (value, key) => { 142 | traverse(value, path.concat(key)) 143 | }) 144 | seenObjects.splice(addIndex, 1) 145 | } else { 146 | visitor(target, path) 147 | } 148 | } 149 | traverse(schema) 150 | } 151 | 152 | export const isNoNeedCompileObject = (source: any) => { 153 | if ('$$typeof' in source && '_owner' in source) { 154 | return true 155 | } 156 | if (source['_isAMomentObject']) { 157 | return true 158 | } 159 | if (Schema.isSchemaInstance(source)) { 160 | return true 161 | } 162 | if (source[REVA_ACTIONS_KEY]) { 163 | return true 164 | } 165 | if (isFn(source['toJS'])) { 166 | return true 167 | } 168 | if (isFn(source['toJSON'])) { 169 | return true 170 | } 171 | if (isObservable(source)) { 172 | return true 173 | } 174 | return false 175 | } 176 | 177 | export const createDataSource = (source: any[]) => { 178 | return toArr(source).map((item) => { 179 | if (typeof item === 'object') { 180 | return item 181 | } else { 182 | return { 183 | label: item, 184 | value: item, 185 | } 186 | } 187 | }) 188 | } 189 | 190 | export const patchStateFormSchema = ( 191 | targetState: any, 192 | pattern: any[], 193 | compiled: any 194 | ) => { 195 | untracked(() => { 196 | const path = FormPath.parse(pattern) 197 | const segments = path.segments 198 | const key = segments[0] 199 | const isEnum = key === 'enum' && isArr(compiled) 200 | const schemaMapKey = SchemaStateMap[key] 201 | if (schemaMapKey) { 202 | FormPath.setIn( 203 | targetState, 204 | [schemaMapKey].concat(segments.slice(1)), 205 | isEnum ? createDataSource(compiled) : compiled 206 | ) 207 | } else { 208 | const isValidatorKey = SchemaValidatorMap[key] 209 | if (isValidatorKey) { 210 | targetState['setValidatorRule']?.(key, compiled) 211 | } 212 | } 213 | }) 214 | } 215 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/DatePicker.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # DatePicker 2 | 3 | > 日期选择器 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | DatePicker, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="date" 27 | title="普通日期" 28 | x-decorator="FormItem" 29 | x-component="DatePicker" 30 | /> 31 | <SchemaField.String 32 | name="week" 33 | title="周选择" 34 | x-decorator="FormItem" 35 | x-component="DatePicker.WeekPicker" 36 | /> 37 | <SchemaField.String 38 | name="month" 39 | title="月选择" 40 | x-decorator="FormItem" 41 | x-component="DatePicker.MonthPicker" 42 | /> 43 | <SchemaField.String 44 | name="year" 45 | title="年选择" 46 | x-decorator="FormItem" 47 | x-component="DatePicker.YearPicker" 48 | /> 49 | <SchemaField.String 50 | name="[startDate,endDate]" 51 | title="日期范围" 52 | x-decorator="FormItem" 53 | x-component="DatePicker.RangePicker" 54 | x-component-props={{ 55 | showTime: true, 56 | }} 57 | /> 58 | <SchemaField.String 59 | name="range_month" 60 | title="月范围选择" 61 | x-decorator="FormItem" 62 | x-component="DatePicker.RangePicker" 63 | x-component-props={{ 64 | type: 'month', 65 | }} 66 | /> 67 | <SchemaField.String 68 | name="range_year" 69 | title="年范围选择" 70 | x-decorator="FormItem" 71 | x-component="DatePicker.RangePicker" 72 | x-component-props={{ 73 | type: 'year', 74 | }} 75 | /> 76 | </SchemaField> 77 | <FormButtonGroup> 78 | <Submit onSubmit={console.log}>提交</Submit> 79 | </FormButtonGroup> 80 | </FormProvider> 81 | ) 82 | ``` 83 | 84 | ## JSON Schema 案例 85 | 86 | ```tsx 87 | import React from 'react' 88 | import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/next' 89 | import { createForm } from '@formily/core' 90 | import { FormProvider, createSchemaField } from '@formily/react' 91 | 92 | const SchemaField = createSchemaField({ 93 | components: { 94 | DatePicker, 95 | FormItem, 96 | }, 97 | }) 98 | 99 | const form = createForm() 100 | 101 | const schema = { 102 | type: 'object', 103 | properties: { 104 | date: { 105 | title: '普通日期', 106 | 'x-decorator': 'FormItem', 107 | 'x-component': 'DatePicker', 108 | type: 'string', 109 | }, 110 | week: { 111 | title: '周选择', 112 | 'x-decorator': 'FormItem', 113 | 'x-component': 'DatePicker.WeekPicker', 114 | type: 'string', 115 | }, 116 | month: { 117 | title: '月选择', 118 | 'x-decorator': 'FormItem', 119 | 'x-component': 'DatePicker.MonthPicker', 120 | type: 'string', 121 | }, 122 | year: { 123 | title: '年选择', 124 | 'x-decorator': 'FormItem', 125 | 'x-component': 'DatePicker.YearPicker', 126 | type: 'string', 127 | }, 128 | '[startDate,endDate]': { 129 | title: '日期范围', 130 | 'x-decorator': 'FormItem', 131 | 'x-component': 'DatePicker.RangePicker', 132 | 'x-component-props': { 133 | showTime: true, 134 | }, 135 | type: 'string', 136 | }, 137 | range_month: { 138 | title: '月范围选择', 139 | 'x-decorator': 'FormItem', 140 | 'x-component': 'DatePicker.RangePicker', 141 | 'x-component-props': { 142 | type: 'month', 143 | }, 144 | type: 'string', 145 | }, 146 | range_year: { 147 | name: 'range_year', 148 | title: '年范围选择', 149 | 'x-decorator': 'FormItem', 150 | 'x-component': 'DatePicker.RangePicker', 151 | 'x-component-props': { 152 | type: 'year', 153 | }, 154 | type: 'string', 155 | }, 156 | }, 157 | } 158 | 159 | export default () => ( 160 | <FormProvider form={form}> 161 | <SchemaField schema={schema} /> 162 | <FormButtonGroup> 163 | <Submit onSubmit={console.log}>提交</Submit> 164 | </FormButtonGroup> 165 | </FormProvider> 166 | ) 167 | ``` 168 | 169 | ## 纯 JSX 案例 170 | 171 | ```tsx 172 | import React from 'react' 173 | import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/next' 174 | import { createForm } from '@formily/core' 175 | import { FormProvider, Field } from '@formily/react' 176 | 177 | const form = createForm() 178 | 179 | export default () => ( 180 | <FormProvider form={form}> 181 | <Field 182 | name="date" 183 | title="日期选择" 184 | decorator={[FormItem]} 185 | component={[DatePicker]} 186 | /> 187 | <Field 188 | name="week" 189 | title="周选择" 190 | decorator={[FormItem]} 191 | component={[DatePicker.WeekPicker]} 192 | /> 193 | <Field 194 | name="quarter" 195 | title="财年选择" 196 | decorator={[FormItem]} 197 | component={[DatePicker.MonthPicker]} 198 | /> 199 | <Field 200 | name="year" 201 | title="年选择" 202 | decorator={[FormItem]} 203 | component={[DatePicker.YearPicker]} 204 | /> 205 | <Field 206 | name="[startDate,endDate]" 207 | title="日期范围选择" 208 | decorator={[FormItem]} 209 | component={[DatePicker.RangePicker]} 210 | /> 211 | <Field 212 | name="range_month" 213 | title="月范围选择" 214 | decorator={[FormItem]} 215 | component={[ 216 | DatePicker.RangePicker, 217 | { 218 | type: 'month', 219 | }, 220 | ]} 221 | /> 222 | <Field 223 | name="range_year" 224 | title="年范围选择" 225 | decorator={[FormItem]} 226 | component={[ 227 | DatePicker.RangePicker, 228 | { 229 | type: 'year', 230 | }, 231 | ]} 232 | /> 233 | <FormButtonGroup> 234 | <Submit onSubmit={console.log}>提交</Submit> 235 | </FormButtonGroup> 236 | </FormProvider> 237 | ) 238 | ``` 239 | 240 | ## API 241 | 242 | 参考 https://fusion.design/pc/component/basic/date-picker 243 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/DatePicker2.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # DatePicker2 2 | 3 | > 日期选择器 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { DatePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | DatePicker2, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="date" 27 | title="普通日期" 28 | x-decorator="FormItem" 29 | x-component="DatePicker2" 30 | /> 31 | <SchemaField.String 32 | name="week" 33 | title="周选择" 34 | x-decorator="FormItem" 35 | x-component="DatePicker2.WeekPicker" 36 | /> 37 | <SchemaField.String 38 | name="month" 39 | title="月选择" 40 | x-decorator="FormItem" 41 | x-component="DatePicker2.MonthPicker" 42 | /> 43 | <SchemaField.String 44 | name="year" 45 | title="年选择" 46 | x-decorator="FormItem" 47 | x-component="DatePicker2.YearPicker" 48 | /> 49 | <SchemaField.String 50 | name="[startDate,endDate]" 51 | title="日期范围" 52 | x-decorator="FormItem" 53 | x-component="DatePicker2.RangePicker" 54 | x-component-props={{ 55 | showTime: true, 56 | }} 57 | /> 58 | <SchemaField.String 59 | name="range_month" 60 | title="月范围选择" 61 | x-decorator="FormItem" 62 | x-component="DatePicker2.RangePicker" 63 | x-component-props={{ 64 | mode: 'month', 65 | }} 66 | /> 67 | <SchemaField.String 68 | name="range_year" 69 | title="年范围选择" 70 | x-decorator="FormItem" 71 | x-component="DatePicker2.RangePicker" 72 | x-component-props={{ 73 | mode: 'year', 74 | }} 75 | /> 76 | </SchemaField> 77 | <FormButtonGroup> 78 | <Submit onSubmit={console.log}>提交</Submit> 79 | </FormButtonGroup> 80 | </FormProvider> 81 | ) 82 | ``` 83 | 84 | ## JSON Schema 案例 85 | 86 | ```tsx 87 | import React from 'react' 88 | import { DatePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' 89 | import { createForm } from '@formily/core' 90 | import { FormProvider, createSchemaField } from '@formily/react' 91 | 92 | const SchemaField = createSchemaField({ 93 | components: { 94 | DatePicker2, 95 | FormItem, 96 | }, 97 | }) 98 | 99 | const form = createForm() 100 | 101 | const schema = { 102 | type: 'object', 103 | properties: { 104 | date: { 105 | title: '普通日期', 106 | 'x-decorator': 'FormItem', 107 | 'x-component': 'DatePicker2', 108 | type: 'string', 109 | }, 110 | week: { 111 | title: '周选择', 112 | 'x-decorator': 'FormItem', 113 | 'x-component': 'DatePicker2.WeekPicker', 114 | type: 'string', 115 | }, 116 | month: { 117 | title: '月选择', 118 | 'x-decorator': 'FormItem', 119 | 'x-component': 'DatePicker2.MonthPicker', 120 | type: 'string', 121 | }, 122 | year: { 123 | title: '年选择', 124 | 'x-decorator': 'FormItem', 125 | 'x-component': 'DatePicker2.YearPicker', 126 | type: 'string', 127 | }, 128 | '[startDate,endDate]': { 129 | title: '日期范围', 130 | 'x-decorator': 'FormItem', 131 | 'x-component': 'DatePicker2.RangePicker', 132 | 'x-component-props': { 133 | showTime: true, 134 | }, 135 | type: 'string', 136 | }, 137 | range_month: { 138 | title: '月范围选择', 139 | 'x-decorator': 'FormItem', 140 | 'x-component': 'DatePicker2.RangePicker', 141 | 'x-component-props': { 142 | mode: 'month', 143 | }, 144 | type: 'string', 145 | }, 146 | range_year: { 147 | name: 'range_year', 148 | title: '年范围选择', 149 | 'x-decorator': 'FormItem', 150 | 'x-component': 'DatePicker2.RangePicker', 151 | 'x-component-props': { 152 | mode: 'year', 153 | }, 154 | type: 'string', 155 | }, 156 | }, 157 | } 158 | 159 | export default () => ( 160 | <FormProvider form={form}> 161 | <SchemaField schema={schema} /> 162 | <FormButtonGroup> 163 | <Submit onSubmit={console.log}>提交</Submit> 164 | </FormButtonGroup> 165 | </FormProvider> 166 | ) 167 | ``` 168 | 169 | ## 纯 JSX 案例 170 | 171 | ```tsx 172 | import React from 'react' 173 | import { DatePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' 174 | import { createForm } from '@formily/core' 175 | import { FormProvider, Field } from '@formily/react' 176 | 177 | const form = createForm() 178 | 179 | export default () => ( 180 | <FormProvider form={form}> 181 | <Field 182 | name="date" 183 | title="日期选择" 184 | decorator={[FormItem]} 185 | component={[DatePicker2]} 186 | /> 187 | <Field 188 | name="week" 189 | title="周选择" 190 | decorator={[FormItem]} 191 | component={[DatePicker2.WeekPicker]} 192 | /> 193 | <Field 194 | name="quarter" 195 | title="财年选择" 196 | decorator={[FormItem]} 197 | component={[DatePicker2.MonthPicker]} 198 | /> 199 | <Field 200 | name="year" 201 | title="年选择" 202 | decorator={[FormItem]} 203 | component={[DatePicker2.YearPicker]} 204 | /> 205 | <Field 206 | name="[startDate,endDate]" 207 | title="日期范围选择" 208 | decorator={[FormItem]} 209 | component={[DatePicker2.RangePicker]} 210 | /> 211 | <Field 212 | name="range_month" 213 | title="月范围选择" 214 | decorator={[FormItem]} 215 | component={[ 216 | DatePicker2.RangePicker, 217 | { 218 | mode: 'month', 219 | }, 220 | ]} 221 | /> 222 | <Field 223 | name="range_year" 224 | title="年范围选择" 225 | decorator={[FormItem]} 226 | component={[ 227 | DatePicker2.RangePicker, 228 | { 229 | mode: 'year', 230 | }, 231 | ]} 232 | /> 233 | <FormButtonGroup> 234 | <Submit onSubmit={console.log}>提交</Submit> 235 | </FormButtonGroup> 236 | </FormProvider> 237 | ) 238 | ``` 239 | 240 | ## API 241 | 242 | 参考 https://fusion.design/pc/component/basic/date-picker2 243 | ``` -------------------------------------------------------------------------------- /packages/next/src/upload/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useEffect } from 'react' 2 | import { Field } from '@formily/core' 3 | import { useField } from '@formily/react' 4 | import { reaction } from '@formily/reactive' 5 | import { Upload as NextUpload, Button, Icon } from '@alifd/next' 6 | import { 7 | UploadProps as NextUploadProps, 8 | CardProps, 9 | } from '@alifd/next/lib/upload' 10 | import { isArr, toArr } from '@formily/shared' 11 | import { UPLOAD_PLACEHOLDER } from './placeholder' 12 | 13 | type ExtendsUploadProps = NextUploadProps & { 14 | textContent?: React.ReactNode 15 | serviceErrorMessage?: string 16 | } 17 | 18 | type FileList = Parameters<ExtendsUploadProps['onChange']>[0] 19 | 20 | type ComposedUpload = React.FC<React.PropsWithChildren<IUploadProps>> & { 21 | Card?: React.FC<React.PropsWithChildren<ICardUploadProps>> 22 | Dragger?: React.FC<React.PropsWithChildren<IUploadProps>> 23 | } 24 | 25 | type IExtendsUploadProps = { 26 | value?: any[] 27 | serviceErrorMessage?: string 28 | onChange?: (...args: any) => void 29 | formatter?: (...args: any) => any 30 | } 31 | 32 | export type IUploadProps = ExtendsUploadProps & { serviceErrorMessage?: string } 33 | 34 | export type ICardUploadProps = CardProps & { serviceErrorMessage?: string } 35 | 36 | const testOpts = ( 37 | ext: RegExp, 38 | options: { exclude?: string[]; include?: string[] } 39 | ) => { 40 | if (options && isArr(options.include)) { 41 | return options.include.some((url) => ext.test(url)) 42 | } 43 | 44 | if (options && isArr(options.exclude)) { 45 | return !options.exclude.some((url) => ext.test(url)) 46 | } 47 | 48 | return true 49 | } 50 | 51 | const getImageByUrl = (url: string, options: any) => { 52 | for (let i = 0; i < UPLOAD_PLACEHOLDER.length; i++) { 53 | if ( 54 | UPLOAD_PLACEHOLDER[i].ext.test(url) && 55 | testOpts(UPLOAD_PLACEHOLDER[i].ext, options) 56 | ) { 57 | return UPLOAD_PLACEHOLDER[i].icon || url 58 | } 59 | } 60 | 61 | return url 62 | } 63 | 64 | const getURL = (target: any) => { 65 | return target?.['url'] || target?.['downloadURL'] || target?.['imgURL'] 66 | } 67 | 68 | const getThumbURL = (target: any) => { 69 | return ( 70 | target?.['thumbUrl'] || 71 | target?.['url'] || 72 | target?.['downloadURL'] || 73 | target?.['imgURL'] 74 | ) 75 | } 76 | 77 | const getSuccess = (target: any) => { 78 | return ( 79 | target?.success || 80 | target?.status === 'done' || 81 | target?.status === 'success' || 82 | target?.state === 'done' || 83 | target?.state === 'success' 84 | ) 85 | } 86 | 87 | const getErrorMessage = (target: any) => { 88 | return ( 89 | target?.errorMessage || 90 | target?.errMsg || 91 | target?.errorMsg || 92 | target?.message || 93 | (typeof target?.error === 'string' ? target.error : '') 94 | ) 95 | } 96 | 97 | const getState = (target: any) => { 98 | if (target?.success === false) return 'error' 99 | if (target?.failed === true) return 'error' 100 | if (target?.error) return 'error' 101 | return target?.state || target?.status 102 | } 103 | 104 | const normalizeFileList = (fileList: IUploadProps['value']) => { 105 | if (fileList && fileList.length) { 106 | return fileList.map(({ ...file }, index) => { 107 | delete file['originFileObj'] 108 | return { 109 | ...file, 110 | uid: file.uid || index, 111 | state: getState(file?.response) || getState(file), 112 | downloadURL: getURL(file) || getURL(file?.response), 113 | imgURL: getImageByUrl( 114 | getThumbURL(file) || getThumbURL(file?.response), 115 | { 116 | exclude: ['.png', '.jpg', '.jpeg', '.gif'], 117 | } 118 | ), 119 | } 120 | }) 121 | } 122 | return [] 123 | } 124 | 125 | const useValidator = (validator: (value: any) => string) => { 126 | const field = useField<Field>() 127 | useEffect(() => { 128 | const dispose = reaction( 129 | () => field.value, 130 | (value) => { 131 | const message = validator(value) 132 | field.setFeedback({ 133 | type: 'error', 134 | code: 'UploadError', 135 | messages: message ? [message] : [], 136 | }) 137 | } 138 | ) 139 | return () => { 140 | dispose() 141 | } 142 | }, []) 143 | } 144 | 145 | const useUploadValidator = (serviceErrorMessage = 'Upload Service Error') => { 146 | useValidator((value) => { 147 | const list = toArr(value) 148 | for (let i = 0; i < list.length; i++) { 149 | if (list[i]?.state === 'error') { 150 | return ( 151 | getErrorMessage(list[i]?.response) || 152 | getErrorMessage(list[i]) || 153 | serviceErrorMessage 154 | ) 155 | } 156 | } 157 | }) 158 | } 159 | 160 | function useUploadProps<T extends IExtendsUploadProps = IUploadProps>({ 161 | serviceErrorMessage, 162 | ...props 163 | }: T) { 164 | useUploadValidator(serviceErrorMessage) 165 | const onChange = (fileList: FileList) => { 166 | props.onChange?.(normalizeFileList([...fileList])) 167 | } 168 | 169 | const formatter = (res: any, file: any) => { 170 | const response = props?.formatter?.(res, file) as any 171 | return { 172 | ...res, 173 | success: getSuccess(res), 174 | ...response, 175 | } 176 | } 177 | return { 178 | ...props, 179 | value: normalizeFileList(props.value), 180 | onChange, 181 | formatter, 182 | } 183 | } 184 | 185 | const getPlaceholder = (props: IUploadProps) => { 186 | if (props.shape !== 'card') { 187 | return ( 188 | <Button> 189 | <Icon type="upload" /> 190 | {props.textContent} 191 | </Button> 192 | ) 193 | } 194 | return <Icon type="upload" style={{ fontSize: 20 }} /> 195 | } 196 | 197 | export const Upload: ComposedUpload = (props) => { 198 | return ( 199 | <NextUpload listType="text" {...useUploadProps(props)}> 200 | {props.children || getPlaceholder(props)} 201 | </NextUpload> 202 | ) 203 | } 204 | 205 | Upload.Dragger = (props) => { 206 | return <NextUpload.Dragger listType="text" {...useUploadProps(props)} /> 207 | } 208 | 209 | Upload.Card = (props) => { 210 | return <NextUpload.Card listType="card" {...useUploadProps(props)} /> 211 | } 212 | 213 | export default Upload 214 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/PreviewText.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # PreviewText 2 | 3 | > 阅读态组件,主要用来实现类 Input,类 DatePicker 这些组件的阅读态 4 | 5 | ## 简单用例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { PreviewText, FormItem, FormLayout } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | FormItem, 16 | PreviewText, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => { 23 | return ( 24 | <FormLayout labelCol={8} wrapperCol={16}> 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.String 28 | x-decorator="FormItem" 29 | title="文本预览" 30 | x-component="PreviewText.Input" 31 | default={'Hello world'} 32 | /> 33 | <SchemaField.String 34 | x-decorator="FormItem" 35 | title="选择项预览" 36 | x-component="PreviewText.Select" 37 | x-component-props={{ 38 | mode: 'multiple', 39 | }} 40 | default={['123', '222']} 41 | enum={[ 42 | { label: 'A111', value: '123' }, 43 | { label: 'A222', value: '222' }, 44 | ]} 45 | /> 46 | <SchemaField.String 47 | x-decorator="FormItem" 48 | title="日期预览" 49 | x-component="PreviewText.DatePicker" 50 | default={'2020-11-23 22:15:20'} 51 | /> 52 | <SchemaField.String 53 | x-decorator="FormItem" 54 | title="Cascader预览" 55 | x-component="PreviewText.Cascader" 56 | default={'yuhang'} 57 | enum={[ 58 | { 59 | label: '杭州', 60 | value: 'hangzhou', 61 | children: [ 62 | { 63 | label: '余杭', 64 | value: 'yuhang', 65 | }, 66 | ], 67 | }, 68 | ]} 69 | /> 70 | </SchemaField> 71 | </FormProvider> 72 | </FormLayout> 73 | ) 74 | } 75 | ``` 76 | 77 | ## 扩展阅读态 78 | 79 | ```tsx 80 | import React from 'react' 81 | import { 82 | PreviewText, 83 | FormItem, 84 | FormButtonGroup, 85 | FormLayout, 86 | } from '@formily/next' 87 | import { createForm } from '@formily/core' 88 | import { 89 | FormProvider, 90 | mapReadPretty, 91 | connect, 92 | createSchemaField, 93 | } from '@formily/react' 94 | import { Button, Input as NextInput } from '@alifd/next' 95 | 96 | const Input = connect(NextInput, mapReadPretty(PreviewText.Input)) 97 | 98 | const SchemaField = createSchemaField({ 99 | components: { 100 | Input, 101 | FormItem, 102 | PreviewText, 103 | }, 104 | }) 105 | 106 | const form = createForm() 107 | 108 | export default () => { 109 | return ( 110 | <PreviewText.Placeholder value="暂无数据"> 111 | <FormLayout labelCol={8} wrapperCol={16}> 112 | <FormProvider form={form}> 113 | <SchemaField> 114 | <SchemaField.Markup 115 | type="string" 116 | x-decorator="FormItem" 117 | title="文本预览" 118 | required 119 | x-component="Input" 120 | default={'Hello world'} 121 | /> 122 | <SchemaField.Markup 123 | type="string" 124 | x-decorator="FormItem" 125 | title="选择项预览" 126 | x-component="PreviewText.Select" 127 | x-component-props={{ 128 | mode: 'multiple', 129 | }} 130 | default={['123']} 131 | enum={[ 132 | { label: 'A111', value: '123' }, 133 | { label: 'A222', value: '222' }, 134 | ]} 135 | /> 136 | <SchemaField.Markup 137 | type="string" 138 | x-decorator="FormItem" 139 | title="日期预览" 140 | x-component="PreviewText.DatePicker" 141 | /> 142 | <SchemaField.Markup 143 | type="string" 144 | x-decorator="FormItem" 145 | title="Cascader预览" 146 | x-component="PreviewText.Cascader" 147 | default={'yuhang'} 148 | enum={[ 149 | { 150 | label: '杭州', 151 | value: 'hangzhou', 152 | children: [ 153 | { 154 | label: '余杭', 155 | value: 'yuhang', 156 | }, 157 | ], 158 | }, 159 | ]} 160 | /> 161 | </SchemaField> 162 | <FormButtonGroup.FormItem> 163 | <Button 164 | onClick={() => { 165 | form.setState((state) => { 166 | state.editable = !state.editable 167 | }) 168 | }} 169 | > 170 | 切换阅读态 171 | </Button> 172 | </FormButtonGroup.FormItem> 173 | </FormProvider> 174 | </FormLayout> 175 | </PreviewText.Placeholder> 176 | ) 177 | } 178 | ``` 179 | 180 | ## API 181 | 182 | ### PreviewText.Input 183 | 184 | 参考 https://fusion.design/pc/component/basic/input 185 | 186 | ### PreviewText.Select 187 | 188 | 参考 https://fusion.design/pc/component/basic/select 189 | 190 | ### PreviewText.TreeSelect 191 | 192 | 参考 https://fusion.design/pc/component/basic/tree-select 193 | 194 | ### PreviewText.Cascader 195 | 196 | 参考 https://fusion.design/pc/component/basic/cascader-select 197 | 198 | ### PreviewText.DatePicker 199 | 200 | 参考 https://fusion.design/pc/component/basic/date-picker 201 | 202 | ### PreviewText.DateRangePicker 203 | 204 | 参考 https://fusion.design/pc/component/basic/date-picker 205 | 206 | ### PreviewText.TimePicker 207 | 208 | 参考 https://fusion.design/pc/component/basic/time-picker 209 | 210 | ### PreviewText.NumberPicker 211 | 212 | 参考 https://fusion.design/pc/component/basic/number-picker 213 | 214 | ### PreviewText.Placeholder 215 | 216 | | 属性名 | 类型 | 描述 | 默认值 | 217 | | ------ | ------ | ---------- | ------ | 218 | | value | stirng | 缺省占位符 | N/A | 219 | 220 | ### PreviewText.usePlaceholder 221 | 222 | ```ts pure 223 | interface usePlaceholder { 224 | (): string 225 | } 226 | ``` 227 | ``` -------------------------------------------------------------------------------- /packages/next/src/form-drawer/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment, useLayoutEffect, useRef, useState } from 'react' 2 | import { createPortal } from 'react-dom' 3 | import { 4 | createForm, 5 | onFormSubmitSuccess, 6 | IFormProps, 7 | Form, 8 | } from '@formily/core' 9 | import { toJS } from '@formily/reactive' 10 | import { FormProvider, observer, Observer, ReactFC } from '@formily/react' 11 | import { 12 | isNum, 13 | isStr, 14 | isBool, 15 | isFn, 16 | applyMiddleware, 17 | IMiddleware, 18 | } from '@formily/shared' 19 | import { ConfigProvider, Drawer } from '@alifd/next' 20 | import { DrawerProps } from '@alifd/next/lib/drawer' 21 | import { 22 | usePrefixCls, 23 | loading, 24 | createPortalProvider, 25 | createPortalRoot, 26 | } from '../__builtins__' 27 | 28 | type FormDrawerRenderer = 29 | | React.ReactElement 30 | | ((form: Form) => React.ReactElement) 31 | 32 | type DrawerTitle = string | number | React.ReactElement 33 | 34 | const getContext: () => any = ConfigProvider['getContext'] 35 | 36 | const isDrawerTitle = (props: any): props is DrawerTitle => { 37 | return ( 38 | isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props) 39 | ) 40 | } 41 | 42 | const getDrawerProps = (props: any): IDrawerProps => { 43 | if (isDrawerTitle(props)) { 44 | return { 45 | title: props, 46 | } 47 | } else { 48 | return props 49 | } 50 | } 51 | 52 | export interface IDrawerProps extends DrawerProps { 53 | onClose?: (reason: string, e: React.MouseEvent) => void | boolean 54 | loadingText?: React.ReactNode 55 | } 56 | 57 | export interface IFormDrawer { 58 | forOpen(middleware: IMiddleware<IFormProps>): IFormDrawer 59 | open(props?: IFormProps): Promise<any> 60 | close(): void 61 | } 62 | 63 | export function FormDrawer( 64 | title: IDrawerProps, 65 | id: string, 66 | renderer: FormDrawerRenderer 67 | ): IFormDrawer 68 | export function FormDrawer( 69 | title: IDrawerProps, 70 | renderer: FormDrawerRenderer 71 | ): IFormDrawer 72 | export function FormDrawer( 73 | title: DrawerTitle, 74 | id: string, 75 | renderer: FormDrawerRenderer 76 | ): IFormDrawer 77 | export function FormDrawer( 78 | title: DrawerTitle, 79 | renderer: FormDrawerRenderer 80 | ): IFormDrawer 81 | export function FormDrawer(title: any, id: any, renderer?: any): IFormDrawer { 82 | if (isFn(id) || React.isValidElement(id)) { 83 | renderer = id 84 | id = 'form-drawer' 85 | } 86 | const env = { 87 | host: document.createElement('div'), 88 | form: null, 89 | promise: null, 90 | openMiddlewares: [], 91 | } 92 | const root = createPortalRoot(env.host, id) 93 | const props = getDrawerProps(title) 94 | const drawer = { 95 | width: '40%', 96 | ...props, 97 | onClose: (reason: string, e: any) => { 98 | if (props?.onClose?.(reason, e) !== false) { 99 | formDrawer.close() 100 | } 101 | }, 102 | afterClose() { 103 | props?.afterClose?.() 104 | root.unmount() 105 | }, 106 | } 107 | const DrawerContent = observer(() => { 108 | return <Fragment>{isFn(renderer) ? renderer(env.form) : renderer}</Fragment> 109 | }) 110 | const renderDrawer = (visible = true) => { 111 | return ( 112 | <ConfigProvider {...getContext()}> 113 | <Observer> 114 | {() => ( 115 | <Drawer {...drawer} visible={visible}> 116 | <FormProvider form={env.form}> 117 | <DrawerContent /> 118 | </FormProvider> 119 | </Drawer> 120 | )} 121 | </Observer> 122 | </ConfigProvider> 123 | ) 124 | } 125 | document.body.appendChild(env.host) 126 | const formDrawer = { 127 | forOpen: (middleware: IMiddleware<IFormProps>) => { 128 | if (isFn(middleware)) { 129 | env.openMiddlewares.push(middleware) 130 | } 131 | return formDrawer 132 | }, 133 | open: (props: IFormProps) => { 134 | if (env.promise) return env.promise 135 | env.promise = new Promise(async (resolve, reject) => { 136 | try { 137 | props = await loading(drawer.loadingText, () => 138 | applyMiddleware(props, env.openMiddlewares) 139 | ) 140 | env.form = 141 | env.form || 142 | createForm({ 143 | ...props, 144 | effects(form) { 145 | onFormSubmitSuccess(() => { 146 | resolve(toJS(form.values)) 147 | formDrawer.close() 148 | }) 149 | props?.effects?.(form) 150 | }, 151 | }) 152 | } catch (e) { 153 | reject(e) 154 | } 155 | root.render(() => renderDrawer(false)) 156 | setTimeout(() => { 157 | root.render(() => renderDrawer(true)) 158 | }, 16) 159 | }) 160 | return env.promise 161 | }, 162 | close: () => { 163 | if (!env.host) return 164 | root.render(() => renderDrawer(false)) 165 | }, 166 | } 167 | return formDrawer 168 | } 169 | 170 | const DrawerFooter: ReactFC = (props) => { 171 | const ref = useRef<HTMLDivElement>() 172 | const [footer, setFooter] = useState<HTMLDivElement>() 173 | const footerRef = useRef<HTMLDivElement>() 174 | const prefixCls = usePrefixCls('drawer') 175 | useLayoutEffect(() => { 176 | const content = ref.current?.closest(`.${prefixCls}`) 177 | if (content) { 178 | if (!footerRef.current) { 179 | footerRef.current = content.querySelector(`.${prefixCls}-footer`) 180 | const body = content.querySelector(`.${prefixCls}-body`) 181 | if (!footerRef.current && body) { 182 | footerRef.current = document.createElement('div') 183 | footerRef.current.classList.add(`${prefixCls}-footer`) 184 | footerRef.current.style.padding = '20px' 185 | footerRef.current.style.borderTop = '1px solid #eee' 186 | body.after(footerRef.current) 187 | } 188 | } 189 | setFooter(footerRef.current) 190 | } 191 | }) 192 | 193 | footerRef.current = footer 194 | 195 | return ( 196 | <div ref={ref} style={{ display: 'none' }}> 197 | {footer && createPortal(props.children, footer)} 198 | </div> 199 | ) 200 | } 201 | 202 | FormDrawer.Footer = DrawerFooter 203 | 204 | FormDrawer.Portal = createPortalProvider('form-drawer') 205 | 206 | export default FormDrawer 207 | ``` -------------------------------------------------------------------------------- /packages/next/src/editable/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useLayoutEffect, useRef, useState } from 'react' 2 | import { isVoidField, Field } from '@formily/core' 3 | import { useField, observer } from '@formily/react' 4 | import { Balloon } from '@alifd/next' 5 | import { BalloonProps as PopoverProps } from '@alifd/next/lib/balloon' 6 | import { BaseItem, IFormItemProps } from '../form-item' 7 | import { 8 | useClickAway, 9 | usePrefixCls, 10 | EditOutlinedIcon, 11 | CloseOutlinedIcon, 12 | MessageOutlinedIcon, 13 | } from '../__builtins__' 14 | /** 15 | * 默认Inline展示 16 | */ 17 | 18 | type IPopoverProps = PopoverProps 19 | 20 | type ComposedEditable = React.FC<React.PropsWithChildren<IFormItemProps>> & { 21 | Popover?: React.FC< 22 | React.PropsWithChildren<IPopoverProps & { title?: React.ReactNode }> 23 | > 24 | } 25 | 26 | const useParentPattern = () => { 27 | const field = useField<Field>() 28 | return field?.parent?.pattern || field?.form?.pattern 29 | } 30 | 31 | const useEditable = (): [boolean, (payload: boolean) => void] => { 32 | const pattern = useParentPattern() 33 | const field = useField<Field>() 34 | useLayoutEffect(() => { 35 | if (pattern === 'editable') { 36 | field.setPattern('readPretty') 37 | } 38 | }, [pattern]) 39 | return [ 40 | field.pattern === 'editable', 41 | (pyaload: boolean) => { 42 | if (pattern !== 'editable') return 43 | field.setPattern(pyaload ? 'editable' : 'readPretty') 44 | }, 45 | ] 46 | } 47 | 48 | const useFormItemProps = (): IFormItemProps => { 49 | const field = useField() 50 | if (isVoidField(field)) return {} 51 | if (!field) return {} 52 | const takeMessage = () => { 53 | if (field.selfErrors.length) return field.selfErrors 54 | if (field.selfWarnings.length) return field.selfWarnings 55 | if (field.selfSuccesses.length) return field.selfSuccesses 56 | } 57 | 58 | return { 59 | feedbackStatus: 60 | field.validateStatus === 'validating' ? 'pending' : field.validateStatus, 61 | feedbackText: takeMessage(), 62 | extra: field.description, 63 | } 64 | } 65 | 66 | export const Editable: ComposedEditable = observer((props) => { 67 | const [editable, setEditable] = useEditable() 68 | const pattern = useParentPattern() 69 | const itemProps = useFormItemProps() 70 | const field = useField<Field>() 71 | const basePrefixCls = usePrefixCls() 72 | const prefixCls = usePrefixCls('formily-editable') 73 | const ref = useRef<boolean>() 74 | const innerRef = useRef<HTMLDivElement>() 75 | const recover = () => { 76 | if (ref.current && !field?.errors?.length) { 77 | setEditable(false) 78 | } 79 | } 80 | const renderEditHelper = () => { 81 | if (editable) return 82 | return ( 83 | <BaseItem {...props} {...itemProps}> 84 | {pattern === 'editable' && ( 85 | <EditOutlinedIcon className={`${prefixCls}-edit-btn`} /> 86 | )} 87 | {pattern !== 'editable' && ( 88 | <MessageOutlinedIcon className={`${prefixCls}-edit-btn`} /> 89 | )} 90 | </BaseItem> 91 | ) 92 | } 93 | 94 | const renderCloseHelper = () => { 95 | if (!editable) return 96 | return ( 97 | <BaseItem {...props}> 98 | <CloseOutlinedIcon className={`${prefixCls}-close-btn`} /> 99 | </BaseItem> 100 | ) 101 | } 102 | 103 | useClickAway((e) => { 104 | const target = e.target as HTMLElement 105 | if (target?.closest(`.${basePrefixCls}-overlay-wrapper`)) return 106 | recover() 107 | }, innerRef) 108 | 109 | const onClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { 110 | const target = e.target as HTMLElement 111 | const close = innerRef.current.querySelector(`.${prefixCls}-close-btn`) 112 | if (target?.contains(close) || close?.contains(target)) { 113 | recover() 114 | } else if (!ref.current) { 115 | setTimeout(() => { 116 | setEditable(true) 117 | setTimeout(() => { 118 | innerRef.current.querySelector('input')?.focus() 119 | }) 120 | }) 121 | } 122 | } 123 | 124 | ref.current = editable 125 | 126 | return ( 127 | <div className={prefixCls} ref={innerRef} onClick={onClick}> 128 | <div className={`${prefixCls}-content`}> 129 | <BaseItem {...props} {...itemProps}> 130 | {props.children} 131 | </BaseItem> 132 | {renderEditHelper()} 133 | {renderCloseHelper()} 134 | </div> 135 | </div> 136 | ) 137 | }) 138 | 139 | Editable.Popover = observer((props) => { 140 | const field = useField<Field>() 141 | const pattern = useParentPattern() 142 | const [visible, setVisible] = useState(false) 143 | const prefixCls = usePrefixCls('formily-editable') 144 | const closePopover = async () => { 145 | try { 146 | await field.form.validate(`${field.address}.*`) 147 | } finally { 148 | const errors = field.form.queryFeedbacks({ 149 | type: 'error', 150 | address: `${field.address}.*`, 151 | }) 152 | if (errors?.length) return 153 | setVisible(false) 154 | } 155 | } 156 | const openPopover = () => { 157 | setVisible(true) 158 | } 159 | return ( 160 | <Balloon 161 | {...props} 162 | title={field.title} 163 | visible={visible} 164 | className={props.className} 165 | onVisibleChange={(visible) => { 166 | if (visible) { 167 | openPopover() 168 | } else { 169 | closePopover() 170 | } 171 | }} 172 | align="t" 173 | triggerType="click" 174 | closable={false} 175 | trigger={ 176 | <div style={{ display: 'inline-flex' }}> 177 | <BaseItem className={`${prefixCls}-trigger`}> 178 | <div className={`${prefixCls}-content`}> 179 | <span className={`${prefixCls}-preview`}> 180 | {props.title || field.title} 181 | </span> 182 | {pattern === 'editable' && ( 183 | <EditOutlinedIcon className={`${prefixCls}-edit-btn`} /> 184 | )} 185 | {pattern !== 'editable' && ( 186 | <MessageOutlinedIcon className={`${prefixCls}-edit-btn`} /> 187 | )} 188 | </div> 189 | </BaseItem> 190 | </div> 191 | } 192 | > 193 | {props.children} 194 | </Balloon> 195 | ) 196 | }) 197 | 198 | export default Editable 199 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/DatePicker.md: -------------------------------------------------------------------------------- ```markdown 1 | # DatePicker 2 | 3 | > Date Picker 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | DatePicker, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="date" 27 | title="normal date" 28 | x-decorator="FormItem" 29 | x-component="DatePicker" 30 | /> 31 | <SchemaField.String 32 | name="week" 33 | title="Week Selection" 34 | x-decorator="FormItem" 35 | x-component="DatePicker.WeekPicker" 36 | /> 37 | <SchemaField.String 38 | name="month" 39 | title="Month Selection" 40 | x-decorator="FormItem" 41 | x-component="DatePicker.MonthPicker" 42 | /> 43 | <SchemaField.String 44 | name="year" 45 | title="Year selection" 46 | x-decorator="FormItem" 47 | x-component="DatePicker.YearPicker" 48 | /> 49 | <SchemaField.String 50 | name="[startDate,endDate]" 51 | title="Date Range" 52 | x-decorator="FormItem" 53 | x-component="DatePicker.RangePicker" 54 | x-component-props={{ 55 | showTime: true, 56 | }} 57 | /> 58 | <SchemaField.String 59 | name="range_month" 60 | title="Month Range Selection" 61 | x-decorator="FormItem" 62 | x-component="DatePicker.RangePicker" 63 | x-component-props={{ 64 | type: 'month', 65 | }} 66 | /> 67 | <SchemaField.String 68 | name="range_year" 69 | title="Year range selection" 70 | x-decorator="FormItem" 71 | x-component="DatePicker.RangePicker" 72 | x-component-props={{ 73 | type: 'year', 74 | }} 75 | /> 76 | </SchemaField> 77 | <FormButtonGroup> 78 | <Submit onSubmit={console.log}>Submit</Submit> 79 | </FormButtonGroup> 80 | </FormProvider> 81 | ) 82 | ``` 83 | 84 | ## JSON Schema case 85 | 86 | ```tsx 87 | import React from 'react' 88 | import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/next' 89 | import { createForm } from '@formily/core' 90 | import { FormProvider, createSchemaField } from '@formily/react' 91 | 92 | const SchemaField = createSchemaField({ 93 | components: { 94 | DatePicker, 95 | FormItem, 96 | }, 97 | }) 98 | 99 | const form = createForm() 100 | 101 | const schema = { 102 | type: 'object', 103 | properties: { 104 | date: { 105 | title: 'Normal date', 106 | 'x-decorator': 'FormItem', 107 | 'x-component': 'DatePicker', 108 | type: 'string', 109 | }, 110 | week: { 111 | title: 'Week Selection', 112 | 'x-decorator': 'FormItem', 113 | 'x-component': 'DatePicker.WeekPicker', 114 | type: 'string', 115 | }, 116 | month: { 117 | title: 'Month Selection', 118 | 'x-decorator': 'FormItem', 119 | 'x-component': 'DatePicker.MonthPicker', 120 | type: 'string', 121 | }, 122 | year: { 123 | title: 'Year selection', 124 | 'x-decorator': 'FormItem', 125 | 'x-component': 'DatePicker.YearPicker', 126 | type: 'string', 127 | }, 128 | '[startDate,endDate]': { 129 | title: 'Date range', 130 | 'x-decorator': 'FormItem', 131 | 'x-component': 'DatePicker.RangePicker', 132 | 'x-component-props': { 133 | showTime: true, 134 | }, 135 | type: 'string', 136 | }, 137 | range_month: { 138 | title: 'Month Range Selection', 139 | 'x-decorator': 'FormItem', 140 | 'x-component': 'DatePicker.RangePicker', 141 | 'x-component-props': { 142 | type: 'month', 143 | }, 144 | type: 'string', 145 | }, 146 | range_year: { 147 | name: 'range_year', 148 | title: 'Year range selection', 149 | 'x-decorator': 'FormItem', 150 | 'x-component': 'DatePicker.RangePicker', 151 | 'x-component-props': { 152 | type: 'year', 153 | }, 154 | type: 'string', 155 | }, 156 | }, 157 | } 158 | 159 | export default () => ( 160 | <FormProvider form={form}> 161 | <SchemaField schema={schema} /> 162 | <FormButtonGroup> 163 | <Submit onSubmit={console.log}>Submit</Submit> 164 | </FormButtonGroup> 165 | </FormProvider> 166 | ) 167 | ``` 168 | 169 | ## Pure JSX case 170 | 171 | ```tsx 172 | import React from 'react' 173 | import { DatePicker, FormItem, FormButtonGroup, Submit } from '@formily/next' 174 | import { createForm } from '@formily/core' 175 | import { FormProvider, Field } from '@formily/react' 176 | 177 | const form = createForm() 178 | 179 | export default () => ( 180 | <FormProvider form={form}> 181 | <Field 182 | name="date" 183 | title="date selection" 184 | decorator={[FormItem]} 185 | component={[DatePicker]} 186 | /> 187 | <Field 188 | name="week" 189 | title="Week Selection" 190 | decorator={[FormItem]} 191 | component={[DatePicker.WeekPicker]} 192 | /> 193 | <Field 194 | name="quarter" 195 | title="Financial Year Selection" 196 | decorator={[FormItem]} 197 | component={[DatePicker.MonthPicker]} 198 | /> 199 | <Field 200 | name="year" 201 | title="Year selection" 202 | decorator={[FormItem]} 203 | component={[DatePicker.YearPicker]} 204 | /> 205 | <Field 206 | name="[startDate,endDate]" 207 | title="Date range selection" 208 | decorator={[FormItem]} 209 | component={[DatePicker.RangePicker]} 210 | /> 211 | <Field 212 | name="range_month" 213 | title="Month Range Selection" 214 | decorator={[FormItem]} 215 | component={[ 216 | DatePicker.RangePicker, 217 | { 218 | type: 'month', 219 | }, 220 | ]} 221 | /> 222 | <Field 223 | name="range_year" 224 | title="Year range selection" 225 | decorator={[FormItem]} 226 | component={[ 227 | DatePicker.RangePicker, 228 | { 229 | type: 'year', 230 | }, 231 | ]} 232 | /> 233 | <FormButtonGroup> 234 | <Submit onSubmit={console.log}>Submit</Submit> 235 | </FormButtonGroup> 236 | </FormProvider> 237 | ) 238 | ``` 239 | 240 | ## API 241 | 242 | Reference https://fusion.design/pc/component/basic/date-picker 243 | ``` -------------------------------------------------------------------------------- /packages/antd/src/editable/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useLayoutEffect, useRef, useState } from 'react' 2 | import { isVoidField, Field } from '@formily/core' 3 | import { useField, observer } from '@formily/react' 4 | import { Popover } from 'antd' 5 | import { EditOutlined, CloseOutlined, MessageOutlined } from '@ant-design/icons' 6 | import { BaseItem, IFormItemProps } from '../form-item' 7 | import { PopoverProps } from 'antd/lib/popover' 8 | import { useClickAway, usePrefixCls } from '../__builtins__' 9 | import cls from 'classnames' 10 | /** 11 | * 默认Inline展示 12 | */ 13 | 14 | type IPopoverProps = PopoverProps 15 | 16 | type ComposedEditable = React.FC<React.PropsWithChildren<IFormItemProps>> & { 17 | Popover?: React.FC< 18 | React.PropsWithChildren<IPopoverProps & { title?: React.ReactNode }> 19 | > 20 | } 21 | 22 | const useParentPattern = () => { 23 | const field = useField<Field>() 24 | return field?.parent?.pattern || field?.form?.pattern 25 | } 26 | 27 | const useEditable = (): [boolean, (payload: boolean) => void] => { 28 | const pattern = useParentPattern() 29 | const field = useField<Field>() 30 | useLayoutEffect(() => { 31 | if (pattern === 'editable') { 32 | return field.setPattern('readPretty') 33 | } 34 | }, [pattern]) 35 | return [ 36 | field.pattern === 'editable', 37 | (payload: boolean) => { 38 | if (pattern !== 'editable') return 39 | field.setPattern(payload ? 'editable' : 'readPretty') 40 | }, 41 | ] 42 | } 43 | 44 | const useFormItemProps = (): IFormItemProps => { 45 | const field = useField() 46 | if (isVoidField(field)) return {} 47 | if (!field) return {} 48 | const takeMessage = () => { 49 | if (field.selfErrors.length) return field.selfErrors 50 | if (field.selfWarnings.length) return field.selfWarnings 51 | if (field.selfSuccesses.length) return field.selfSuccesses 52 | } 53 | 54 | return { 55 | feedbackStatus: 56 | field.validateStatus === 'validating' ? 'pending' : field.validateStatus, 57 | feedbackText: takeMessage(), 58 | extra: field.description, 59 | } 60 | } 61 | 62 | export const Editable: ComposedEditable = observer((props) => { 63 | const [editable, setEditable] = useEditable() 64 | const pattern = useParentPattern() 65 | const itemProps = useFormItemProps() 66 | const field = useField<Field>() 67 | const basePrefixCls = usePrefixCls() 68 | const prefixCls = usePrefixCls('formily-editable') 69 | const ref = useRef<boolean>() 70 | const innerRef = useRef<HTMLDivElement>() 71 | const recover = () => { 72 | if (ref.current && !field?.errors?.length) { 73 | setEditable(false) 74 | } 75 | } 76 | const renderEditHelper = () => { 77 | if (editable) return 78 | return ( 79 | <BaseItem {...props} {...itemProps}> 80 | {pattern === 'editable' && ( 81 | <EditOutlined className={`${prefixCls}-edit-btn`} /> 82 | )} 83 | {pattern !== 'editable' && ( 84 | <MessageOutlined className={`${prefixCls}-edit-btn`} /> 85 | )} 86 | </BaseItem> 87 | ) 88 | } 89 | 90 | const renderCloseHelper = () => { 91 | if (!editable) return 92 | return ( 93 | <BaseItem {...props}> 94 | <CloseOutlined className={`${prefixCls}-close-btn`} /> 95 | </BaseItem> 96 | ) 97 | } 98 | 99 | useClickAway((e) => { 100 | const target = e.target as HTMLElement 101 | if (target?.closest(`.${basePrefixCls}-select-dropdown`)) return 102 | if (target?.closest(`.${basePrefixCls}-picker-dropdown`)) return 103 | if (target?.closest(`.${basePrefixCls}-cascader-menus`)) return 104 | recover() 105 | }, innerRef) 106 | 107 | const onClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { 108 | const target = e.target as HTMLElement 109 | const close = innerRef.current.querySelector(`.${prefixCls}-close-btn`) 110 | if (target?.contains(close) || close?.contains(target)) { 111 | recover() 112 | } else if (!ref.current) { 113 | setTimeout(() => { 114 | setEditable(true) 115 | setTimeout(() => { 116 | innerRef.current.querySelector('input')?.focus() 117 | }) 118 | }) 119 | } 120 | } 121 | 122 | ref.current = editable 123 | 124 | return ( 125 | <div className={prefixCls} ref={innerRef} onClick={onClick}> 126 | <div className={`${prefixCls}-content`}> 127 | <BaseItem {...props} {...itemProps}> 128 | {props.children} 129 | </BaseItem> 130 | {renderEditHelper()} 131 | {renderCloseHelper()} 132 | </div> 133 | </div> 134 | ) 135 | }) 136 | 137 | Editable.Popover = observer((props) => { 138 | const field = useField<Field>() 139 | const pattern = useParentPattern() 140 | const [visible, setVisible] = useState(false) 141 | const prefixCls = usePrefixCls('formily-editable') 142 | const closePopover = async () => { 143 | try { 144 | await field.form.validate(`${field.address}.*`) 145 | } finally { 146 | const errors = field.form.queryFeedbacks({ 147 | type: 'error', 148 | address: `${field.address}.*`, 149 | }) 150 | if (errors?.length) return 151 | setVisible(false) 152 | } 153 | } 154 | const openPopover = () => { 155 | setVisible(true) 156 | } 157 | return ( 158 | <Popover 159 | {...props} 160 | title={props.title || field.title} 161 | visible={visible} 162 | className={cls(prefixCls, props.className)} 163 | content={props.children} 164 | trigger="click" 165 | destroyTooltipOnHide 166 | onVisibleChange={(visible) => { 167 | if (visible) { 168 | openPopover() 169 | } else { 170 | closePopover() 171 | } 172 | }} 173 | > 174 | <div> 175 | <BaseItem className={`${prefixCls}-trigger`}> 176 | <div className={`${prefixCls}-content`}> 177 | <span className={`${prefixCls}-preview`}> 178 | {props.title || field.title} 179 | </span> 180 | {pattern === 'editable' && ( 181 | <EditOutlined className={`${prefixCls}-edit-btn`} /> 182 | )} 183 | {pattern !== 'editable' && ( 184 | <MessageOutlined className={`${prefixCls}-edit-btn`} /> 185 | )} 186 | </div> 187 | </BaseItem> 188 | </div> 189 | </Popover> 190 | ) 191 | }) 192 | 193 | export default Editable 194 | ```