This is page 17 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/antd/src/upload/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useEffect } from 'react' 2 | import { Field } from '@formily/core' 3 | import { connect, mapProps, useField } from '@formily/react' 4 | import { Upload as AntdUpload, Button } from 'antd' 5 | import { 6 | UploadChangeParam, 7 | UploadProps as AntdUploadProps, 8 | DraggerProps as AntdDraggerProps, 9 | } from 'antd/lib/upload' 10 | import { InboxOutlined, UploadOutlined } from '@ant-design/icons' 11 | import { reaction } from '@formily/reactive' 12 | import { UploadFile } from 'antd/lib/upload/interface' 13 | import { isArr, toArr } from '@formily/shared' 14 | import { UPLOAD_PLACEHOLDER } from './placeholder' 15 | import { usePrefixCls } from '../__builtins__' 16 | 17 | export type IUploadProps = Omit<AntdUploadProps, 'onChange'> & { 18 | textContent?: React.ReactNode 19 | onChange?: (fileList: UploadFile[]) => void 20 | serviceErrorMessage?: string 21 | } 22 | 23 | export type IDraggerUploadProps = Omit<AntdDraggerProps, 'onChange'> & { 24 | textContent?: React.ReactNode 25 | onChange?: (fileList: UploadFile[]) => void 26 | serviceErrorMessage?: string 27 | } 28 | 29 | type ComposedUpload = React.FC<React.PropsWithChildren<IUploadProps>> & { 30 | Dragger?: React.FC<React.PropsWithChildren<IDraggerUploadProps>> 31 | } 32 | 33 | type IExtendsUploadProps = { 34 | fileList?: any[] 35 | serviceErrorMessage?: string 36 | onChange?: (...args: any) => void 37 | } 38 | 39 | const testOpts = ( 40 | ext: RegExp, 41 | options: { exclude?: string[]; include?: string[] } 42 | ) => { 43 | if (options && isArr(options.include)) { 44 | return options.include.some((url) => ext.test(url)) 45 | } 46 | 47 | if (options && isArr(options.exclude)) { 48 | return !options.exclude.some((url) => ext.test(url)) 49 | } 50 | 51 | return true 52 | } 53 | 54 | const getImageByUrl = (url: string, options: any) => { 55 | for (let i = 0; i < UPLOAD_PLACEHOLDER.length; i++) { 56 | if ( 57 | UPLOAD_PLACEHOLDER[i].ext.test(url) && 58 | testOpts(UPLOAD_PLACEHOLDER[i].ext, options) 59 | ) { 60 | return UPLOAD_PLACEHOLDER[i].icon || url 61 | } 62 | } 63 | 64 | return url 65 | } 66 | 67 | const getURL = (target: any) => { 68 | return target?.['url'] || target?.['downloadURL'] || target?.['imgURL'] 69 | } 70 | const getThumbURL = (target: any) => { 71 | return ( 72 | target?.['thumbUrl'] || 73 | target?.['url'] || 74 | target?.['downloadURL'] || 75 | target?.['imgURL'] 76 | ) 77 | } 78 | 79 | const getErrorMessage = (target: any) => { 80 | return ( 81 | target?.errorMessage || 82 | target?.errMsg || 83 | target?.errorMsg || 84 | target?.message || 85 | (typeof target?.error === 'string' ? target.error : '') 86 | ) 87 | } 88 | 89 | const getState = (target: any) => { 90 | if (target?.success === false) return 'error' 91 | if (target?.failed === true) return 'error' 92 | if (target?.error) return 'error' 93 | return target?.state || target?.status 94 | } 95 | 96 | const normalizeFileList = (fileList: UploadFile[]) => { 97 | if (fileList && fileList.length) { 98 | return fileList.map((file, index) => { 99 | return { 100 | ...file, 101 | uid: file.uid || `${index}`, 102 | status: getState(file.response) || getState(file), 103 | url: getURL(file) || getURL(file?.response), 104 | thumbUrl: getImageByUrl( 105 | getThumbURL(file) || getThumbURL(file?.response), 106 | { 107 | exclude: ['.png', '.jpg', '.jpeg', '.gif'], 108 | } 109 | ), 110 | } 111 | }) 112 | } 113 | return [] 114 | } 115 | 116 | const useValidator = (validator: (value: any) => string) => { 117 | const field = useField<Field>() 118 | useEffect(() => { 119 | const dispose = reaction( 120 | () => field.value, 121 | (value) => { 122 | const message = validator(value) 123 | field.setFeedback({ 124 | type: 'error', 125 | code: 'UploadError', 126 | messages: message ? [message] : [], 127 | }) 128 | } 129 | ) 130 | return () => { 131 | dispose() 132 | } 133 | }, []) 134 | } 135 | 136 | const useUploadValidator = (serviceErrorMessage = 'Upload Service Error') => { 137 | useValidator((value) => { 138 | const list = toArr(value) 139 | for (let i = 0; i < list.length; i++) { 140 | if (list[i]?.status === 'error') { 141 | return ( 142 | getErrorMessage(list[i]?.response) || 143 | getErrorMessage(list[i]) || 144 | serviceErrorMessage 145 | ) 146 | } 147 | } 148 | }) 149 | } 150 | 151 | function useUploadProps<T extends IExtendsUploadProps = IUploadProps>({ 152 | serviceErrorMessage, 153 | ...props 154 | }: T) { 155 | useUploadValidator(serviceErrorMessage) 156 | const onChange = (param: UploadChangeParam<UploadFile>) => { 157 | props.onChange?.(normalizeFileList([...param.fileList])) 158 | } 159 | return { 160 | ...props, 161 | fileList: normalizeFileList(props.fileList), 162 | onChange, 163 | } 164 | } 165 | 166 | const getPlaceholder = (props: IUploadProps) => { 167 | if (props.listType !== 'picture-card') { 168 | return ( 169 | <Button> 170 | <UploadOutlined /> 171 | {props.textContent} 172 | </Button> 173 | ) 174 | } 175 | return <UploadOutlined style={{ fontSize: 20 }} /> 176 | } 177 | 178 | export const Upload: ComposedUpload = connect( 179 | (props: React.PropsWithChildren<IUploadProps>) => { 180 | return ( 181 | <AntdUpload {...useUploadProps(props)}> 182 | {props.children || getPlaceholder(props)} 183 | </AntdUpload> 184 | ) 185 | }, 186 | mapProps({ 187 | value: 'fileList', 188 | }) 189 | ) 190 | 191 | const Dragger = connect( 192 | (props: React.PropsWithChildren<IDraggerUploadProps>) => { 193 | return ( 194 | <div className={usePrefixCls('upload-dragger')}> 195 | <AntdUpload.Dragger {...useUploadProps(props)}> 196 | {props.children || ( 197 | <React.Fragment> 198 | <p className="ant-upload-drag-icon"> 199 | <InboxOutlined /> 200 | </p> 201 | {props.textContent && ( 202 | <p className="ant-upload-text">{props.textContent}</p> 203 | )} 204 | </React.Fragment> 205 | )} 206 | </AntdUpload.Dragger> 207 | </div> 208 | ) 209 | }, 210 | mapProps({ 211 | value: 'fileList', 212 | }) 213 | ) 214 | 215 | Upload.Dragger = Dragger 216 | 217 | export default Upload 218 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/DatePicker2.md: -------------------------------------------------------------------------------- ```markdown 1 | # DatePicker2 2 | 3 | > Date Picker 4 | 5 | ## Markup Schema example 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="normal date" 28 | x-decorator="FormItem" 29 | x-component="DatePicker2" 30 | /> 31 | <SchemaField.String 32 | name="week" 33 | title="Week Selection" 34 | x-decorator="FormItem" 35 | x-component="DatePicker2.WeekPicker" 36 | /> 37 | <SchemaField.String 38 | name="month" 39 | title="Month Selection" 40 | x-decorator="FormItem" 41 | x-component="DatePicker2.MonthPicker" 42 | /> 43 | <SchemaField.String 44 | name="year" 45 | title="Year selection" 46 | x-decorator="FormItem" 47 | x-component="DatePicker2.YearPicker" 48 | /> 49 | <SchemaField.String 50 | name="[startDate,endDate]" 51 | title="Date Range" 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="Month Range Selection" 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="Year range selection" 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</Submit> 79 | </FormButtonGroup> 80 | </FormProvider> 81 | ) 82 | ``` 83 | 84 | ## JSON Schema case 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: 'Normal date', 106 | 'x-decorator': 'FormItem', 107 | 'x-component': 'DatePicker2', 108 | type: 'string', 109 | }, 110 | week: { 111 | title: 'Week Selection', 112 | 'x-decorator': 'FormItem', 113 | 'x-component': 'DatePicker2.WeekPicker', 114 | type: 'string', 115 | }, 116 | month: { 117 | title: 'Month Selection', 118 | 'x-decorator': 'FormItem', 119 | 'x-component': 'DatePicker2.MonthPicker', 120 | type: 'string', 121 | }, 122 | year: { 123 | title: 'Year selection', 124 | 'x-decorator': 'FormItem', 125 | 'x-component': 'DatePicker2.YearPicker', 126 | type: 'string', 127 | }, 128 | '[startDate,endDate]': { 129 | title: 'Date range', 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: 'Month Range Selection', 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: 'Year range selection', 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</Submit> 164 | </FormButtonGroup> 165 | </FormProvider> 166 | ) 167 | ``` 168 | 169 | ## Pure JSX case 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="date selection" 184 | decorator={[FormItem]} 185 | component={[DatePicker2]} 186 | /> 187 | <Field 188 | name="week" 189 | title="Week Selection" 190 | decorator={[FormItem]} 191 | component={[DatePicker2.WeekPicker]} 192 | /> 193 | <Field 194 | name="quarter" 195 | title="Financial Year Selection" 196 | decorator={[FormItem]} 197 | component={[DatePicker2.MonthPicker]} 198 | /> 199 | <Field 200 | name="year" 201 | title="Year selection" 202 | decorator={[FormItem]} 203 | component={[DatePicker2.YearPicker]} 204 | /> 205 | <Field 206 | name="[startDate,endDate]" 207 | title="Date range selection" 208 | decorator={[FormItem]} 209 | component={[DatePicker2.RangePicker]} 210 | /> 211 | <Field 212 | name="range_month" 213 | title="Month Range Selection" 214 | decorator={[FormItem]} 215 | component={[ 216 | DatePicker2.RangePicker, 217 | { 218 | mode: 'month', 219 | }, 220 | ]} 221 | /> 222 | <Field 223 | name="range_year" 224 | title="Year range selection" 225 | decorator={[FormItem]} 226 | component={[ 227 | DatePicker2.RangePicker, 228 | { 229 | mode: '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-picker2 243 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormTab.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormTab 2 | 3 | > 选项卡表单 4 | > 5 | > 注意:该组件只适用于 Schema 场景 6 | 7 | ## Markup Schema 案例 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | FormTab, 13 | FormItem, 14 | Input, 15 | FormButtonGroup, 16 | Submit, 17 | } from '@formily/antd' 18 | import { createForm } from '@formily/core' 19 | import { FormProvider, createSchemaField } from '@formily/react' 20 | import { Button } from 'antd' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | FormItem, 25 | FormTab, 26 | Input, 27 | }, 28 | }) 29 | 30 | const form = createForm() 31 | const formTab = FormTab.createFormTab() 32 | 33 | export default () => { 34 | return ( 35 | <FormProvider form={form}> 36 | <SchemaField> 37 | <SchemaField.Void 38 | type="void" 39 | x-component="FormTab" 40 | x-component-props={{ formTab }} 41 | > 42 | <SchemaField.Void 43 | type="void" 44 | name="tab1" 45 | x-component="FormTab.TabPane" 46 | x-component-props={{ tab: 'A1' }} 47 | > 48 | <SchemaField.String 49 | name="aaa" 50 | x-decorator="FormItem" 51 | title="AAA" 52 | required 53 | x-component="Input" 54 | /> 55 | </SchemaField.Void> 56 | <SchemaField.Void 57 | name="tab2" 58 | x-component="FormTab.TabPane" 59 | x-component-props={{ tab: 'A2' }} 60 | > 61 | <SchemaField.String 62 | name="bbb" 63 | x-decorator="FormItem" 64 | title="BBB" 65 | required 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | <SchemaField.Void 70 | name="tab3" 71 | x-component="FormTab.TabPane" 72 | x-component-props={{ tab: 'A3' }} 73 | > 74 | <SchemaField.String 75 | name="ccc" 76 | x-decorator="FormItem" 77 | title="CCC" 78 | required 79 | x-component="Input" 80 | /> 81 | </SchemaField.Void> 82 | </SchemaField.Void> 83 | </SchemaField> 84 | <FormButtonGroup.FormItem> 85 | <Button 86 | onClick={() => { 87 | form.query('tab3').take((field) => { 88 | field.visible = !field.visible 89 | }) 90 | }} 91 | > 92 | 显示/隐藏最后一个Tab 93 | </Button> 94 | <Button 95 | onClick={() => { 96 | formTab.setActiveKey('tab2') 97 | }} 98 | > 99 | 切换第二个Tab 100 | </Button> 101 | <Submit onSubmit={console.log}>提交</Submit> 102 | </FormButtonGroup.FormItem> 103 | </FormProvider> 104 | ) 105 | } 106 | ``` 107 | 108 | ## JSON Schema 案例 109 | 110 | ```tsx 111 | import React from 'react' 112 | import { 113 | FormTab, 114 | FormItem, 115 | Input, 116 | FormButtonGroup, 117 | Submit, 118 | } from '@formily/antd' 119 | import { createForm } from '@formily/core' 120 | import { FormProvider, createSchemaField } from '@formily/react' 121 | import { Button } from 'antd' 122 | 123 | const SchemaField = createSchemaField({ 124 | components: { 125 | FormItem, 126 | FormTab, 127 | Input, 128 | }, 129 | }) 130 | 131 | const form = createForm() 132 | const formTab = FormTab.createFormTab() 133 | 134 | const schema = { 135 | type: 'object', 136 | properties: { 137 | collapse: { 138 | type: 'void', 139 | 'x-component': 'FormTab', 140 | 'x-component-props': { 141 | formTab: '{{formTab}}', 142 | }, 143 | properties: { 144 | tab1: { 145 | type: 'void', 146 | 'x-component': 'FormTab.TabPane', 147 | 'x-component-props': { 148 | tab: 'A1', 149 | }, 150 | properties: { 151 | aaa: { 152 | type: 'string', 153 | title: 'AAA', 154 | 'x-decorator': 'FormItem', 155 | required: true, 156 | 'x-component': 'Input', 157 | }, 158 | }, 159 | }, 160 | tab2: { 161 | type: 'void', 162 | 'x-component': 'FormTab.TabPane', 163 | 'x-component-props': { 164 | tab: 'A2', 165 | }, 166 | properties: { 167 | bbb: { 168 | type: 'string', 169 | title: 'BBB', 170 | 'x-decorator': 'FormItem', 171 | required: true, 172 | 'x-component': 'Input', 173 | }, 174 | }, 175 | }, 176 | tab3: { 177 | type: 'void', 178 | 'x-component': 'FormTab.TabPane', 179 | 'x-component-props': { 180 | tab: 'A3', 181 | }, 182 | properties: { 183 | ccc: { 184 | type: 'string', 185 | title: 'CCC', 186 | 'x-decorator': 'FormItem', 187 | required: true, 188 | 'x-component': 'Input', 189 | }, 190 | }, 191 | }, 192 | }, 193 | }, 194 | }, 195 | } 196 | 197 | export default () => { 198 | return ( 199 | <FormProvider form={form}> 200 | <SchemaField schema={schema} scope={{ formTab }} /> 201 | <FormButtonGroup.FormItem> 202 | <Button 203 | onClick={() => { 204 | form.query('tab3').take((field) => { 205 | field.visible = !field.visible 206 | }) 207 | }} 208 | > 209 | 显示/隐藏最后一个Tab 210 | </Button> 211 | <Button 212 | onClick={() => { 213 | formTab.setActiveKey('tab2') 214 | }} 215 | > 216 | 切换第二个Tab 217 | </Button> 218 | <Submit onSubmit={console.log}>提交</Submit> 219 | </FormButtonGroup.FormItem> 220 | </FormProvider> 221 | ) 222 | } 223 | ``` 224 | 225 | ## API 226 | 227 | ### FormTab 228 | 229 | | 属性名 | 类型 | 描述 | 默认值 | 230 | | ------- | -------- | ------------------------------------------------ | ------ | 231 | | formTab | IFormTab | 传入通过 createFormTab/useFormTab 创建出来的模型 | | 232 | 233 | 其余参考 https://ant.design/components/tabs-cn/ 234 | 235 | ### FormTab.TabPane 236 | 237 | 参考 https://ant.design/components/tabs-cn/ 238 | 239 | ### FormTab.createFormTab 240 | 241 | ```ts pure 242 | type ActiveKey = string | number 243 | 244 | interface createFormTab { 245 | (defaultActiveKey?: ActiveKey): IFormTab 246 | } 247 | 248 | interface IFormTab { 249 | //激活主键 250 | activeKey: ActiveKey 251 | //设置激活主键 252 | setActiveKey(key: ActiveKey): void 253 | } 254 | ``` 255 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormTab.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormTab 2 | 3 | > 选项卡表单 4 | > 5 | > 注意:该组件只适用于 Schema 场景 6 | 7 | ## Markup Schema 案例 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | FormTab, 13 | FormItem, 14 | Input, 15 | FormButtonGroup, 16 | Submit, 17 | } from '@formily/next' 18 | import { createForm } from '@formily/core' 19 | import { FormProvider, createSchemaField } from '@formily/react' 20 | import { Button } from '@alifd/next' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | FormItem, 25 | FormTab, 26 | Input, 27 | }, 28 | }) 29 | 30 | const form = createForm() 31 | const formTab = FormTab.createFormTab() 32 | 33 | export default () => { 34 | return ( 35 | <FormProvider form={form}> 36 | <SchemaField> 37 | <SchemaField.Void 38 | type="void" 39 | x-component="FormTab" 40 | x-component-props={{ formTab }} 41 | > 42 | <SchemaField.Void 43 | type="void" 44 | name="tab1" 45 | x-component="FormTab.TabPane" 46 | x-component-props={{ tab: 'A1' }} 47 | > 48 | <SchemaField.String 49 | name="aaa" 50 | x-decorator="FormItem" 51 | title="AAA" 52 | required 53 | x-component="Input" 54 | /> 55 | </SchemaField.Void> 56 | <SchemaField.Void 57 | name="tab2" 58 | x-component="FormTab.TabPane" 59 | x-component-props={{ tab: 'A2' }} 60 | > 61 | <SchemaField.String 62 | name="bbb" 63 | x-decorator="FormItem" 64 | title="BBB" 65 | required 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | <SchemaField.Void 70 | name="tab3" 71 | x-component="FormTab.TabPane" 72 | x-component-props={{ tab: 'A3' }} 73 | > 74 | <SchemaField.String 75 | name="ccc" 76 | x-decorator="FormItem" 77 | title="CCC" 78 | required 79 | x-component="Input" 80 | /> 81 | </SchemaField.Void> 82 | </SchemaField.Void> 83 | </SchemaField> 84 | <FormButtonGroup.FormItem> 85 | <Button 86 | onClick={() => { 87 | form.query('tab3').take((field) => { 88 | field.visible = !field.visible 89 | }) 90 | }} 91 | > 92 | 显示/隐藏最后一个Tab 93 | </Button> 94 | <Button 95 | onClick={() => { 96 | formTab.setActiveKey('tab2') 97 | }} 98 | > 99 | 切换第二个Tab 100 | </Button> 101 | <Submit onSubmit={console.log}>提交</Submit> 102 | </FormButtonGroup.FormItem> 103 | </FormProvider> 104 | ) 105 | } 106 | ``` 107 | 108 | ## JSON Schema 案例 109 | 110 | ```tsx 111 | import React from 'react' 112 | import { 113 | FormTab, 114 | FormItem, 115 | Input, 116 | FormButtonGroup, 117 | Submit, 118 | } from '@formily/next' 119 | import { createForm } from '@formily/core' 120 | import { FormProvider, createSchemaField } from '@formily/react' 121 | import { Button } from '@alifd/next' 122 | 123 | const SchemaField = createSchemaField({ 124 | components: { 125 | FormItem, 126 | FormTab, 127 | Input, 128 | }, 129 | }) 130 | 131 | const form = createForm() 132 | const formTab = FormTab.createFormTab() 133 | 134 | const schema = { 135 | type: 'object', 136 | properties: { 137 | collapse: { 138 | type: 'void', 139 | 'x-component': 'FormTab', 140 | 'x-component-props': { 141 | formTab: '{{formTab}}', 142 | }, 143 | properties: { 144 | tab1: { 145 | type: 'void', 146 | 'x-component': 'FormTab.TabPane', 147 | 'x-component-props': { 148 | tab: 'A1', 149 | }, 150 | properties: { 151 | aaa: { 152 | type: 'string', 153 | title: 'AAA', 154 | 'x-decorator': 'FormItem', 155 | required: true, 156 | 'x-component': 'Input', 157 | }, 158 | }, 159 | }, 160 | tab2: { 161 | type: 'void', 162 | 'x-component': 'FormTab.TabPane', 163 | 'x-component-props': { 164 | tab: 'A2', 165 | }, 166 | properties: { 167 | bbb: { 168 | type: 'string', 169 | title: 'BBB', 170 | 'x-decorator': 'FormItem', 171 | required: true, 172 | 'x-component': 'Input', 173 | }, 174 | }, 175 | }, 176 | tab3: { 177 | type: 'void', 178 | 'x-component': 'FormTab.TabPane', 179 | 'x-component-props': { 180 | tab: 'A3', 181 | }, 182 | properties: { 183 | ccc: { 184 | type: 'string', 185 | title: 'CCC', 186 | 'x-decorator': 'FormItem', 187 | required: true, 188 | 'x-component': 'Input', 189 | }, 190 | }, 191 | }, 192 | }, 193 | }, 194 | }, 195 | } 196 | 197 | export default () => { 198 | return ( 199 | <FormProvider form={form}> 200 | <SchemaField schema={schema} scope={{ formTab }} /> 201 | <FormButtonGroup.FormItem> 202 | <Button 203 | onClick={() => { 204 | form.query('tab3').take((field) => { 205 | field.visible = !field.visible 206 | }) 207 | }} 208 | > 209 | 显示/隐藏最后一个Tab 210 | </Button> 211 | <Button 212 | onClick={() => { 213 | formTab.setActiveKey('tab2') 214 | }} 215 | > 216 | 切换第二个Tab 217 | </Button> 218 | <Submit onSubmit={console.log}>提交</Submit> 219 | </FormButtonGroup.FormItem> 220 | </FormProvider> 221 | ) 222 | } 223 | ``` 224 | 225 | ## API 226 | 227 | ### FormTab 228 | 229 | | 属性名 | 类型 | 描述 | 默认值 | 230 | | ------- | -------- | ------------------------------------------------ | ------ | 231 | | formTab | IFormTab | 传入通过 createFormTab/useFormTab 创建出来的模型 | | 232 | 233 | 其余参考 https://fusion.design/pc/component/basic/tab 234 | 235 | ### FormTab.TabPane 236 | 237 | 参考 https://fusion.design/pc/component/basic/tab 的 Item 属性 238 | 239 | ### FormTab.createFormTab 240 | 241 | ```ts pure 242 | type ActiveKey = string | number 243 | 244 | interface createFormTab { 245 | (defaultActiveKey?: ActiveKey): IFormTab 246 | } 247 | 248 | interface IFormTab { 249 | //激活主键 250 | activeKey: ActiveKey 251 | //设置激活主键 252 | setActiveKey(key: ActiveKey): void 253 | } 254 | ``` 255 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Cascader.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Cascader 2 | 3 | > 联级选择器 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm, onFieldReact, FormPathPattern } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | import { action } from '@formily/reactive' 13 | 14 | const SchemaField = createSchemaField({ 15 | components: { 16 | Cascader, 17 | FormItem, 18 | }, 19 | }) 20 | 21 | const useAddress = (pattern: FormPathPattern) => { 22 | const transform = (data = {}) => { 23 | return Object.entries(data).reduce((buf, [key, value]) => { 24 | if (typeof value === 'string') 25 | return buf.concat({ 26 | label: value, 27 | value: key, 28 | }) 29 | const { name, code, cities, districts } = value 30 | const _cities = transform(cities) 31 | const _districts = transform(districts) 32 | return buf.concat({ 33 | label: name, 34 | value: code, 35 | children: _cities.length 36 | ? _cities 37 | : _districts.length 38 | ? _districts 39 | : undefined, 40 | }) 41 | }, []) 42 | } 43 | onFieldReact(pattern, (field) => { 44 | field.loading = true 45 | fetch('//unpkg.com/china-location/dist/location.json') 46 | .then((res) => res.json()) 47 | .then( 48 | action.bound((data) => { 49 | field.dataSource = transform(data) 50 | field.loading = false 51 | }) 52 | ) 53 | }) 54 | } 55 | 56 | const form = createForm({ 57 | effects: () => { 58 | useAddress('address') 59 | }, 60 | }) 61 | 62 | export default () => ( 63 | <FormProvider form={form}> 64 | <SchemaField> 65 | <SchemaField.String 66 | name="address" 67 | title="地址选择" 68 | x-decorator="FormItem" 69 | x-component="Cascader" 70 | x-component-props={{ 71 | style: { 72 | width: 240, 73 | }, 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 { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next' 89 | import { createForm } from '@formily/core' 90 | import { FormProvider, createSchemaField } from '@formily/react' 91 | import { action } from '@formily/reactive' 92 | 93 | const SchemaField = createSchemaField({ 94 | components: { 95 | Cascader, 96 | FormItem, 97 | }, 98 | }) 99 | 100 | const transformAddress = (data = {}) => { 101 | return Object.entries(data).reduce((buf, [key, value]) => { 102 | if (typeof value === 'string') 103 | return buf.concat({ 104 | label: value, 105 | value: key, 106 | }) 107 | const { name, code, cities, districts } = value 108 | const _cities = transformAddress(cities) 109 | const _districts = transformAddress(districts) 110 | return buf.concat({ 111 | label: name, 112 | value: code, 113 | children: _cities.length 114 | ? _cities 115 | : _districts.length 116 | ? _districts 117 | : undefined, 118 | }) 119 | }, []) 120 | } 121 | 122 | const useAsyncDataSource = 123 | (url: string, transform: (data: any) => any) => (field) => { 124 | field.loading = true 125 | fetch(url) 126 | .then((res) => res.json()) 127 | .then( 128 | action.bound((data) => { 129 | field.dataSource = transform(data) 130 | field.loading = false 131 | }) 132 | ) 133 | } 134 | 135 | const form = createForm() 136 | 137 | const schema = { 138 | type: 'object', 139 | properties: { 140 | address: { 141 | type: 'string', 142 | title: '地址选择', 143 | 'x-decorator': 'FormItem', 144 | 'x-component': 'Cascader', 145 | 'x-component-props': { 146 | style: { 147 | width: 240, 148 | }, 149 | }, 150 | 'x-reactions': [ 151 | '{{useAsyncDataSource("//unpkg.com/china-location/dist/location.json",transformAddress)}}', 152 | ], 153 | }, 154 | }, 155 | } 156 | 157 | export default () => ( 158 | <FormProvider form={form}> 159 | <SchemaField 160 | schema={schema} 161 | scope={{ useAsyncDataSource, transformAddress }} 162 | /> 163 | <FormButtonGroup> 164 | <Submit onSubmit={console.log}>提交</Submit> 165 | </FormButtonGroup> 166 | </FormProvider> 167 | ) 168 | ``` 169 | 170 | ## 纯 JSX 案例 171 | 172 | ```tsx 173 | import React from 'react' 174 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next' 175 | import { createForm, onFieldReact, FormPathPattern } from '@formily/core' 176 | import { FormProvider, Field } from '@formily/react' 177 | import { action } from '@formily/reactive' 178 | 179 | const useAddress = (pattern: FormPathPattern) => { 180 | const transform = (data = {}) => { 181 | return Object.entries(data).reduce((buf, [key, value]) => { 182 | if (typeof value === 'string') 183 | return buf.concat({ 184 | label: value, 185 | value: key, 186 | }) 187 | const { name, code, cities, districts } = value 188 | const _cities = transform(cities) 189 | const _districts = transform(districts) 190 | return buf.concat({ 191 | label: name, 192 | value: code, 193 | children: _cities.length 194 | ? _cities 195 | : _districts.length 196 | ? _districts 197 | : undefined, 198 | }) 199 | }, []) 200 | } 201 | onFieldReact(pattern, (field) => { 202 | field.loading = true 203 | fetch('//unpkg.com/china-location/dist/location.json') 204 | .then((res) => res.json()) 205 | .then( 206 | action.bound((data) => { 207 | field.dataSource = transform(data) 208 | field.loading = false 209 | }) 210 | ) 211 | }) 212 | } 213 | 214 | const form = createForm({ 215 | effects: () => { 216 | useAddress('address') 217 | }, 218 | }) 219 | 220 | export default () => ( 221 | <FormProvider form={form}> 222 | <Field 223 | name="address" 224 | title="地址选择" 225 | decorator={[FormItem]} 226 | component={[ 227 | Cascader, 228 | { 229 | style: { 230 | width: 240, 231 | }, 232 | }, 233 | ]} 234 | /> 235 | <FormButtonGroup> 236 | <Submit onSubmit={console.log}>提交</Submit> 237 | </FormButtonGroup> 238 | </FormProvider> 239 | ) 240 | ``` 241 | 242 | ## API 243 | 244 | 参考 https://fusion.design/pc/component/basic/cascader-select 245 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Cascader.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Cascader 2 | 3 | > 联级选择器 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm, onFieldReact, FormPathPattern } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | import { action } from '@formily/reactive' 13 | 14 | const SchemaField = createSchemaField({ 15 | components: { 16 | Cascader, 17 | FormItem, 18 | }, 19 | }) 20 | 21 | const useAddress = (pattern: FormPathPattern) => { 22 | const transform = (data = {}) => { 23 | return Object.entries(data).reduce((buf, [key, value]) => { 24 | if (typeof value === 'string') 25 | return buf.concat({ 26 | label: value, 27 | value: key, 28 | }) 29 | const { name, code, cities, districts } = value 30 | const _cities = transform(cities) 31 | const _districts = transform(districts) 32 | return buf.concat({ 33 | label: name, 34 | value: code, 35 | children: _cities.length 36 | ? _cities 37 | : _districts.length 38 | ? _districts 39 | : undefined, 40 | }) 41 | }, []) 42 | } 43 | onFieldReact(pattern, (field) => { 44 | field.loading = true 45 | fetch('//unpkg.com/china-location/dist/location.json') 46 | .then((res) => res.json()) 47 | .then( 48 | action.bound((data) => { 49 | field.dataSource = transform(data) 50 | field.loading = false 51 | }) 52 | ) 53 | }) 54 | } 55 | 56 | const form = createForm({ 57 | effects: () => { 58 | useAddress('address') 59 | }, 60 | }) 61 | 62 | export default () => ( 63 | <FormProvider form={form}> 64 | <SchemaField> 65 | <SchemaField.String 66 | name="address" 67 | title="地址选择" 68 | required 69 | x-decorator="FormItem" 70 | x-component="Cascader" 71 | x-component-props={{ 72 | style: { 73 | width: 240, 74 | }, 75 | }} 76 | /> 77 | </SchemaField> 78 | <FormButtonGroup> 79 | <Submit onSubmit={console.log}>提交</Submit> 80 | </FormButtonGroup> 81 | </FormProvider> 82 | ) 83 | ``` 84 | 85 | ## JSON Schema 案例 86 | 87 | ```tsx 88 | import React from 'react' 89 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd' 90 | import { createForm } from '@formily/core' 91 | import { FormProvider, createSchemaField } from '@formily/react' 92 | import { action } from '@formily/reactive' 93 | 94 | const SchemaField = createSchemaField({ 95 | components: { 96 | Cascader, 97 | FormItem, 98 | }, 99 | }) 100 | 101 | const transformAddress = (data = {}) => { 102 | return Object.entries(data).reduce((buf, [key, value]) => { 103 | if (typeof value === 'string') 104 | return buf.concat({ 105 | label: value, 106 | value: key, 107 | }) 108 | const { name, code, cities, districts } = value 109 | const _cities = transformAddress(cities) 110 | const _districts = transformAddress(districts) 111 | return buf.concat({ 112 | label: name, 113 | value: code, 114 | children: _cities.length 115 | ? _cities 116 | : _districts.length 117 | ? _districts 118 | : undefined, 119 | }) 120 | }, []) 121 | } 122 | 123 | const useAsyncDataSource = 124 | (url: string, transform: (data: any) => any) => (field) => { 125 | field.loading = true 126 | fetch(url) 127 | .then((res) => res.json()) 128 | .then( 129 | action.bound((data) => { 130 | field.dataSource = transform(data) 131 | field.loading = false 132 | }) 133 | ) 134 | } 135 | 136 | const form = createForm() 137 | 138 | const schema = { 139 | type: 'object', 140 | properties: { 141 | address: { 142 | type: 'string', 143 | title: '地址选择', 144 | 'x-decorator': 'FormItem', 145 | 'x-component': 'Cascader', 146 | 'x-component-props': { 147 | style: { 148 | width: 240, 149 | }, 150 | }, 151 | 'x-reactions': [ 152 | '{{useAsyncDataSource("//unpkg.com/china-location/dist/location.json",transformAddress)}}', 153 | ], 154 | }, 155 | }, 156 | } 157 | 158 | export default () => ( 159 | <FormProvider form={form}> 160 | <SchemaField 161 | schema={schema} 162 | scope={{ useAsyncDataSource, transformAddress }} 163 | /> 164 | <FormButtonGroup> 165 | <Submit onSubmit={console.log}>提交</Submit> 166 | </FormButtonGroup> 167 | </FormProvider> 168 | ) 169 | ``` 170 | 171 | ## 纯 JSX 案例 172 | 173 | ```tsx 174 | import React from 'react' 175 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd' 176 | import { createForm, onFieldReact, FormPathPattern } from '@formily/core' 177 | import { FormProvider, Field } from '@formily/react' 178 | import { action } from '@formily/reactive' 179 | 180 | const useAddress = (pattern: FormPathPattern) => { 181 | const transform = (data = {}) => { 182 | return Object.entries(data).reduce((buf, [key, value]) => { 183 | if (typeof value === 'string') 184 | return buf.concat({ 185 | label: value, 186 | value: key, 187 | }) 188 | const { name, code, cities, districts } = value 189 | const _cities = transform(cities) 190 | const _districts = transform(districts) 191 | return buf.concat({ 192 | label: name, 193 | value: code, 194 | children: _cities.length 195 | ? _cities 196 | : _districts.length 197 | ? _districts 198 | : undefined, 199 | }) 200 | }, []) 201 | } 202 | onFieldReact(pattern, (field) => { 203 | field.loading = true 204 | fetch('//unpkg.com/china-location/dist/location.json') 205 | .then((res) => res.json()) 206 | .then( 207 | action.bound((data) => { 208 | field.dataSource = transform(data) 209 | field.loading = false 210 | }) 211 | ) 212 | }) 213 | } 214 | 215 | const form = createForm({ 216 | effects: () => { 217 | useAddress('address') 218 | }, 219 | }) 220 | 221 | export default () => ( 222 | <FormProvider form={form}> 223 | <Field 224 | name="address" 225 | title="地址选择" 226 | decorator={[FormItem]} 227 | component={[ 228 | Cascader, 229 | { 230 | style: { 231 | width: 240, 232 | }, 233 | }, 234 | ]} 235 | /> 236 | <FormButtonGroup> 237 | <Submit onSubmit={console.log}>提交</Submit> 238 | </FormButtonGroup> 239 | </FormProvider> 240 | ) 241 | ``` 242 | 243 | ## API 244 | 245 | 参考 https://ant.design/components/cascader-cn/ 246 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Cascader.md: -------------------------------------------------------------------------------- ```markdown 1 | # Cascader 2 | 3 | > Cascade selector 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm, onFieldReact, FormPathPattern } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | import { action } from '@formily/reactive' 13 | 14 | const SchemaField = createSchemaField({ 15 | components: { 16 | Cascader, 17 | FormItem, 18 | }, 19 | }) 20 | 21 | const useAddress = (pattern: FormPathPattern) => { 22 | const transform = (data = {}) => { 23 | return Object.entries(data).reduce((buf, [key, value]) => { 24 | if (typeof value === 'string') 25 | return buf.concat({ 26 | label: value, 27 | value: key, 28 | }) 29 | const { name, code, cities, districts } = value 30 | const _cities = transform(cities) 31 | const _districts = transform(districts) 32 | return buf.concat({ 33 | label: name, 34 | value: code, 35 | children: _cities.length 36 | ? _cities 37 | : _districts.length 38 | ? _districts 39 | : undefined, 40 | }) 41 | }, []) 42 | } 43 | onFieldReact(pattern, (field) => { 44 | field.loading = true 45 | fetch('//unpkg.com/china-location/dist/location.json') 46 | .then((res) => res.json()) 47 | .then( 48 | action.bound((data) => { 49 | field.dataSource = transform(data) 50 | field.loading = false 51 | }) 52 | ) 53 | }) 54 | } 55 | 56 | const form = createForm({ 57 | effects: () => { 58 | useAddress('address') 59 | }, 60 | }) 61 | 62 | export default () => ( 63 | <FormProvider form={form}> 64 | <SchemaField> 65 | <SchemaField.String 66 | name="address" 67 | title="Address Selection" 68 | x-decorator="FormItem" 69 | x-component="Cascader" 70 | x-component-props={{ 71 | style: { 72 | width: 240, 73 | }, 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 { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next' 89 | import { createForm } from '@formily/core' 90 | import { FormProvider, createSchemaField } from '@formily/react' 91 | import { action } from '@formily/reactive' 92 | 93 | const SchemaField = createSchemaField({ 94 | components: { 95 | Cascader, 96 | FormItem, 97 | }, 98 | }) 99 | 100 | const transformAddress = (data = {}) => { 101 | return Object.entries(data).reduce((buf, [key, value]) => { 102 | if (typeof value === 'string') 103 | return buf.concat({ 104 | label: value, 105 | value: key, 106 | }) 107 | const { name, code, cities, districts } = value 108 | const _cities = transformAddress(cities) 109 | const _districts = transformAddress(districts) 110 | return buf.concat({ 111 | label: name, 112 | value: code, 113 | children: _cities.length 114 | ? _cities 115 | : _districts.length 116 | ? _districts 117 | : undefined, 118 | }) 119 | }, []) 120 | } 121 | 122 | const useAsyncDataSource = 123 | (url: string, transform: (data: any) => any) => (field) => { 124 | field.loading = true 125 | fetch(url) 126 | .then((res) => res.json()) 127 | .then( 128 | action.bound((data) => { 129 | field.dataSource = transform(data) 130 | field.loading = false 131 | }) 132 | ) 133 | } 134 | 135 | const form = createForm() 136 | 137 | const schema = { 138 | type: 'object', 139 | properties: { 140 | address: { 141 | type: 'string', 142 | title: 'Address Selection', 143 | 'x-decorator': 'FormItem', 144 | 'x-component': 'Cascader', 145 | 'x-component-props': { 146 | style: { 147 | width: 240, 148 | }, 149 | }, 150 | 'x-reactions': [ 151 | '{{useAsyncDataSource("//unpkg.com/china-location/dist/location.json",transformAddress)}}', 152 | ], 153 | }, 154 | }, 155 | } 156 | 157 | export default () => ( 158 | <FormProvider form={form}> 159 | <SchemaField 160 | schema={schema} 161 | scope={{ useAsyncDataSource, transformAddress }} 162 | /> 163 | <FormButtonGroup> 164 | <Submit onSubmit={console.log}>Submit</Submit> 165 | </FormButtonGroup> 166 | </FormProvider> 167 | ) 168 | ``` 169 | 170 | ## Pure JSX case 171 | 172 | ```tsx 173 | import React from 'react' 174 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/next' 175 | import { createForm, onFieldReact, FormPathPattern } from '@formily/core' 176 | import { FormProvider, Field } from '@formily/react' 177 | import { action } from '@formily/reactive' 178 | 179 | const useAddress = (pattern: FormPathPattern) => { 180 | const transform = (data = {}) => { 181 | return Object.entries(data).reduce((buf, [key, value]) => { 182 | if (typeof value === 'string') 183 | return buf.concat({ 184 | label: value, 185 | value: key, 186 | }) 187 | const { name, code, cities, districts } = value 188 | const _cities = transform(cities) 189 | const _districts = transform(districts) 190 | return buf.concat({ 191 | label: name, 192 | value: code, 193 | children: _cities.length 194 | ? _cities 195 | : _districts.length 196 | ? _districts 197 | : undefined, 198 | }) 199 | }, []) 200 | } 201 | onFieldReact(pattern, (field) => { 202 | field.loading = true 203 | fetch('//unpkg.com/china-location/dist/location.json') 204 | .then((res) => res.json()) 205 | .then( 206 | action.bound((data) => { 207 | field.dataSource = transform(data) 208 | field.loading = false 209 | }) 210 | ) 211 | }) 212 | } 213 | 214 | const form = createForm({ 215 | effects: () => { 216 | useAddress('address') 217 | }, 218 | }) 219 | 220 | export default () => ( 221 | <FormProvider form={form}> 222 | <Field 223 | name="address" 224 | title="Address Selection" 225 | decorator={[FormItem]} 226 | component={[ 227 | Cascader, 228 | { 229 | style: { 230 | width: 240, 231 | }, 232 | }, 233 | ]} 234 | /> 235 | <FormButtonGroup> 236 | <Submit onSubmit={console.log}>Submit</Submit> 237 | </FormButtonGroup> 238 | </FormProvider> 239 | ) 240 | ``` 241 | 242 | ## API 243 | 244 | Reference https://fusion.design/pc/component/basic/cascader-select 245 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Cascader.md: -------------------------------------------------------------------------------- ```markdown 1 | # Cascader 2 | 3 | > Cascade selector 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm, onFieldReact, FormPathPattern } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | import { action } from '@formily/reactive' 13 | 14 | const SchemaField = createSchemaField({ 15 | components: { 16 | Cascader, 17 | FormItem, 18 | }, 19 | }) 20 | 21 | const useAddress = (pattern: FormPathPattern) => { 22 | const transform = (data = {}) => { 23 | return Object.entries(data).reduce((buf, [key, value]) => { 24 | if (typeof value === 'string') 25 | return buf.concat({ 26 | label: value, 27 | value: key, 28 | }) 29 | const { name, code, cities, districts } = value 30 | const _cities = transform(cities) 31 | const _districts = transform(districts) 32 | return buf.concat({ 33 | label: name, 34 | value: code, 35 | children: _cities.length 36 | ? _cities 37 | : _districts.length 38 | ? _districts 39 | : undefined, 40 | }) 41 | }, []) 42 | } 43 | onFieldReact(pattern, (field) => { 44 | field.loading = true 45 | fetch('//unpkg.com/china-location/dist/location.json') 46 | .then((res) => res.json()) 47 | .then( 48 | action.bound((data) => { 49 | field.dataSource = transform(data) 50 | field.loading = false 51 | }) 52 | ) 53 | }) 54 | } 55 | 56 | const form = createForm({ 57 | effects: () => { 58 | useAddress('address') 59 | }, 60 | }) 61 | 62 | export default () => ( 63 | <FormProvider form={form}> 64 | <SchemaField> 65 | <SchemaField.String 66 | name="address" 67 | title="Address Selection" 68 | required 69 | x-decorator="FormItem" 70 | x-component="Cascader" 71 | x-component-props={{ 72 | style: { 73 | width: 240, 74 | }, 75 | }} 76 | /> 77 | </SchemaField> 78 | <FormButtonGroup> 79 | <Submit onSubmit={console.log}>Submit</Submit> 80 | </FormButtonGroup> 81 | </FormProvider> 82 | ) 83 | ``` 84 | 85 | ## JSON Schema case 86 | 87 | ```tsx 88 | import React from 'react' 89 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd' 90 | import { createForm } from '@formily/core' 91 | import { FormProvider, createSchemaField } from '@formily/react' 92 | import { action } from '@formily/reactive' 93 | 94 | const SchemaField = createSchemaField({ 95 | components: { 96 | Cascader, 97 | FormItem, 98 | }, 99 | }) 100 | 101 | const transformAddress = (data = {}) => { 102 | return Object.entries(data).reduce((buf, [key, value]) => { 103 | if (typeof value === 'string') 104 | return buf.concat({ 105 | label: value, 106 | value: key, 107 | }) 108 | const { name, code, cities, districts } = value 109 | const _cities = transformAddress(cities) 110 | const _districts = transformAddress(districts) 111 | return buf.concat({ 112 | label: name, 113 | value: code, 114 | children: _cities.length 115 | ? _cities 116 | : _districts.length 117 | ? _districts 118 | : undefined, 119 | }) 120 | }, []) 121 | } 122 | 123 | const useAsyncDataSource = 124 | (url: string, transform: (data: any) => any) => (field) => { 125 | field.loading = true 126 | fetch(url) 127 | .then((res) => res.json()) 128 | .then( 129 | action.bound((data) => { 130 | field.dataSource = transform(data) 131 | field.loading = false 132 | }) 133 | ) 134 | } 135 | 136 | const form = createForm() 137 | 138 | const schema = { 139 | type: 'object', 140 | properties: { 141 | address: { 142 | type: 'string', 143 | title: 'Address Selection', 144 | 'x-decorator': 'FormItem', 145 | 'x-component': 'Cascader', 146 | 'x-component-props': { 147 | style: { 148 | width: 240, 149 | }, 150 | }, 151 | 'x-reactions': [ 152 | '{{useAsyncDataSource("//unpkg.com/china-location/dist/location.json",transformAddress)}}', 153 | ], 154 | }, 155 | }, 156 | } 157 | 158 | export default () => ( 159 | <FormProvider form={form}> 160 | <SchemaField 161 | schema={schema} 162 | scope={{ useAsyncDataSource, transformAddress }} 163 | /> 164 | <FormButtonGroup> 165 | <Submit onSubmit={console.log}>Submit</Submit> 166 | </FormButtonGroup> 167 | </FormProvider> 168 | ) 169 | ``` 170 | 171 | ## Pure JSX case 172 | 173 | ```tsx 174 | import React from 'react' 175 | import { Cascader, FormItem, FormButtonGroup, Submit } from '@formily/antd' 176 | import { createForm, onFieldReact, FormPathPattern } from '@formily/core' 177 | import { FormProvider, Field } from '@formily/react' 178 | import { action } from '@formily/reactive' 179 | 180 | const useAddress = (pattern: FormPathPattern) => { 181 | const transform = (data = {}) => { 182 | return Object.entries(data).reduce((buf, [key, value]) => { 183 | if (typeof value === 'string') 184 | return buf.concat({ 185 | label: value, 186 | value: key, 187 | }) 188 | const { name, code, cities, districts } = value 189 | const _cities = transform(cities) 190 | const _districts = transform(districts) 191 | return buf.concat({ 192 | label: name, 193 | value: code, 194 | children: _cities.length 195 | ? _cities 196 | : _districts.length 197 | ? _districts 198 | : undefined, 199 | }) 200 | }, []) 201 | } 202 | onFieldReact(pattern, (field) => { 203 | field.loading = true 204 | fetch('//unpkg.com/china-location/dist/location.json') 205 | .then((res) => res.json()) 206 | .then( 207 | action.bound((data) => { 208 | field.dataSource = transform(data) 209 | field.loading = false 210 | }) 211 | ) 212 | }) 213 | } 214 | 215 | const form = createForm({ 216 | effects: () => { 217 | useAddress('address') 218 | }, 219 | }) 220 | 221 | export default () => ( 222 | <FormProvider form={form}> 223 | <Field 224 | name="address" 225 | title="Address Selection" 226 | decorator={[FormItem]} 227 | component={[ 228 | Cascader, 229 | { 230 | style: { 231 | width: 240, 232 | }, 233 | }, 234 | ]} 235 | /> 236 | <FormButtonGroup> 237 | <Submit onSubmit={console.log}>Submit</Submit> 238 | </FormButtonGroup> 239 | </FormProvider> 240 | ) 241 | ``` 242 | 243 | ## API 244 | 245 | Reference https://ant.design/components/cascader-cn/ 246 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/PreviewText.md: -------------------------------------------------------------------------------- ```markdown 1 | # PreviewText 2 | 3 | > Reading state components, mainly used to implement the reading state of these components of class Input and DatePicker 4 | 5 | ## Simple use case 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="text preview" 30 | x-component="PreviewText.Input" 31 | default={'Hello world'} 32 | /> 33 | <SchemaField.String 34 | x-decorator="FormItem" 35 | title="Select item preview" 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="date preview" 49 | x-component="PreviewText.DatePicker" 50 | default={'2020-11-23 22:15:20'} 51 | /> 52 | <SchemaField.String 53 | x-decorator="FormItem" 54 | title="Cascader Preview" 55 | x-component="PreviewText.Cascader" 56 | default={'yuhang'} 57 | enum={[ 58 | { 59 | label: 'Hangzhou', 60 | value: 'hangzhou', 61 | children: [ 62 | { 63 | label: 'Yuhang', 64 | value: 'yuhang', 65 | }, 66 | ], 67 | }, 68 | ]} 69 | /> 70 | </SchemaField> 71 | </FormProvider> 72 | </FormLayout> 73 | ) 74 | } 75 | ``` 76 | 77 | ## Extended reading mode 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="No data currently available"> 111 | <FormLayout labelCol={8} wrapperCol={16}> 112 | <FormProvider form={form}> 113 | <SchemaField> 114 | <SchemaField.Markup 115 | type="string" 116 | x-decorator="FormItem" 117 | title="text preview" 118 | required 119 | x-component="Input" 120 | default={'Hello world'} 121 | /> 122 | <SchemaField.Markup 123 | type="string" 124 | x-decorator="FormItem" 125 | title="Select item preview" 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="date preview" 140 | x-component="PreviewText.DatePicker" 141 | /> 142 | <SchemaField.Markup 143 | type="string" 144 | x-decorator="FormItem" 145 | title="Cascader Preview" 146 | x-component="PreviewText.Cascader" 147 | default={'yuhang'} 148 | enum={[ 149 | { 150 | label: 'Hangzhou', 151 | value: 'hangzhou', 152 | children: [ 153 | { 154 | label: 'Yuhang', 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 | Switch reading mode 171 | </Button> 172 | </FormButtonGroup.FormItem> 173 | </FormProvider> 174 | </FormLayout> 175 | </PreviewText.Placeholder> 176 | ) 177 | } 178 | ``` 179 | 180 | ## API 181 | 182 | ### PreviewText.Input 183 | 184 | Reference https://fusion.design/pc/component/basic/input 185 | 186 | ### PreviewText.Select 187 | 188 | Reference https://fusion.design/pc/component/basic/select 189 | 190 | ### PreviewText.TreeSelect 191 | 192 | Reference https://fusion.design/pc/component/basic/tree-select 193 | 194 | ### PreviewText.Cascader 195 | 196 | Reference https://fusion.design/pc/component/basic/cascader-select 197 | 198 | ### PreviewText.DatePicker 199 | 200 | Reference https://fusion.design/pc/component/basic/date-picker 201 | 202 | ### PreviewText.DateRangePicker 203 | 204 | Reference https://fusion.design/pc/component/basic/date-picker 205 | 206 | ### PreviewText.TimePicker 207 | 208 | Reference https://fusion.design/pc/component/basic/time-picker 209 | 210 | ### PreviewText.NumberPicker 211 | 212 | Reference https://fusion.design/pc/component/basic/number-picker 213 | 214 | ### PreviewText.Placeholder 215 | 216 | | Property name | Type | Description | Default value | 217 | | ------------- | ------ | ------------------- | ------------- | 218 | | value | stirng | Default placeholder | N/A | 219 | 220 | ### PreviewText.usePlaceholder 221 | 222 | ```ts pure 223 | interface usePlaceholder { 224 | (): string 225 | } 226 | ``` 227 | ``` -------------------------------------------------------------------------------- /packages/json-schema/src/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | IGeneralFieldState, 3 | GeneralField, 4 | FormPathPattern, 5 | } from '@formily/core' 6 | export type SchemaEnum<Message> = Array< 7 | | string 8 | | number 9 | | boolean 10 | | { label?: Message; value?: any; [key: string]: any } 11 | | { key?: any; title?: Message; [key: string]: any } 12 | > 13 | 14 | export type SchemaTypes = 15 | | 'string' 16 | | 'object' 17 | | 'array' 18 | | 'number' 19 | | 'boolean' 20 | | 'void' 21 | | 'date' 22 | | 'datetime' 23 | | (string & {}) 24 | 25 | export type SchemaProperties< 26 | Decorator, 27 | Component, 28 | DecoratorProps, 29 | ComponentProps, 30 | Pattern, 31 | Display, 32 | Validator, 33 | Message 34 | > = Record< 35 | string, 36 | ISchema< 37 | Decorator, 38 | Component, 39 | DecoratorProps, 40 | ComponentProps, 41 | Pattern, 42 | Display, 43 | Validator, 44 | Message 45 | > 46 | > 47 | 48 | export type SchemaPatch = (schema: ISchema) => ISchema 49 | 50 | export type SchemaKey = string | number 51 | 52 | export type SchemaEffectTypes = 53 | | 'onFieldInit' 54 | | 'onFieldMount' 55 | | 'onFieldUnmount' 56 | | 'onFieldValueChange' 57 | | 'onFieldInputValueChange' 58 | | 'onFieldInitialValueChange' 59 | | 'onFieldValidateStart' 60 | | 'onFieldValidateEnd' 61 | | 'onFieldValidateFailed' 62 | | 'onFieldValidateSuccess' 63 | 64 | export type SchemaReaction<Field = any> = 65 | | { 66 | dependencies?: 67 | | Array< 68 | | string 69 | | { 70 | name?: string 71 | type?: string 72 | source?: string 73 | property?: string 74 | } 75 | > 76 | | Record<string, string> 77 | when?: string | boolean 78 | target?: string 79 | effects?: (SchemaEffectTypes | (string & {}))[] 80 | fulfill?: { 81 | state?: Stringify<IGeneralFieldState> 82 | schema?: ISchema 83 | run?: string 84 | } 85 | otherwise?: { 86 | state?: Stringify<IGeneralFieldState> 87 | schema?: ISchema 88 | run?: string 89 | } 90 | [key: string]: any 91 | } 92 | | ((field: Field, scope: IScopeContext) => void) 93 | 94 | export type SchemaReactions<Field = any> = 95 | | SchemaReaction<Field> 96 | | SchemaReaction<Field>[] 97 | 98 | export type SchemaItems< 99 | Decorator, 100 | Component, 101 | DecoratorProps, 102 | ComponentProps, 103 | Pattern, 104 | Display, 105 | Validator, 106 | Message 107 | > = 108 | | ISchema< 109 | Decorator, 110 | Component, 111 | DecoratorProps, 112 | ComponentProps, 113 | Pattern, 114 | Display, 115 | Validator, 116 | Message 117 | > 118 | | ISchema< 119 | Decorator, 120 | Component, 121 | DecoratorProps, 122 | ComponentProps, 123 | Pattern, 124 | Display, 125 | Validator, 126 | Message 127 | >[] 128 | 129 | export type SchemaComponents = Record<string, any> 130 | 131 | export interface ISchemaFieldUpdateRequest { 132 | state?: Stringify<IGeneralFieldState> 133 | schema?: ISchema 134 | run?: string 135 | } 136 | 137 | export interface IScopeContext { 138 | [key: string]: any 139 | } 140 | 141 | export interface IFieldStateSetterOptions { 142 | field: GeneralField 143 | target?: FormPathPattern 144 | request: ISchemaFieldUpdateRequest 145 | runner?: string 146 | scope?: IScopeContext 147 | } 148 | 149 | export interface ISchemaTransformerOptions { 150 | scope?: IScopeContext 151 | } 152 | 153 | export type Slot = { 154 | target: string 155 | isRenderProp?: boolean 156 | } 157 | 158 | export type Stringify<P extends { [key: string]: any }> = { 159 | /** 160 | * Use `string & {}` instead of string to keep Literal Type for ISchema#component and ISchema#decorator 161 | */ 162 | [key in keyof P]?: P[key] | (string & {}) 163 | } 164 | 165 | export type ISchema< 166 | Decorator = any, 167 | Component = any, 168 | DecoratorProps = any, 169 | ComponentProps = any, 170 | Pattern = any, 171 | Display = any, 172 | Validator = any, 173 | Message = any, 174 | ReactionField = any 175 | > = Stringify<{ 176 | version?: string 177 | name?: SchemaKey 178 | title?: Message 179 | description?: Message 180 | default?: any 181 | readOnly?: boolean 182 | writeOnly?: boolean 183 | type?: SchemaTypes 184 | enum?: SchemaEnum<Message> 185 | const?: any 186 | multipleOf?: number 187 | maximum?: number 188 | exclusiveMaximum?: number 189 | minimum?: number 190 | exclusiveMinimum?: number 191 | maxLength?: number 192 | minLength?: number 193 | pattern?: string | RegExp 194 | maxItems?: number 195 | minItems?: number 196 | uniqueItems?: boolean 197 | maxProperties?: number 198 | minProperties?: number 199 | required?: string[] | boolean | string 200 | format?: string 201 | $ref?: string 202 | $namespace?: string 203 | /** nested json schema spec **/ 204 | definitions?: SchemaProperties< 205 | Decorator, 206 | Component, 207 | DecoratorProps, 208 | ComponentProps, 209 | Pattern, 210 | Display, 211 | Validator, 212 | Message 213 | > 214 | properties?: SchemaProperties< 215 | Decorator, 216 | Component, 217 | DecoratorProps, 218 | ComponentProps, 219 | Pattern, 220 | Display, 221 | Validator, 222 | Message 223 | > 224 | items?: SchemaItems< 225 | Decorator, 226 | Component, 227 | DecoratorProps, 228 | ComponentProps, 229 | Pattern, 230 | Display, 231 | Validator, 232 | Message 233 | > 234 | additionalItems?: ISchema< 235 | Decorator, 236 | Component, 237 | DecoratorProps, 238 | ComponentProps, 239 | Pattern, 240 | Display, 241 | Validator, 242 | Message 243 | > 244 | patternProperties?: SchemaProperties< 245 | Decorator, 246 | Component, 247 | DecoratorProps, 248 | ComponentProps, 249 | Pattern, 250 | Display, 251 | Validator, 252 | Message 253 | > 254 | additionalProperties?: ISchema< 255 | Decorator, 256 | Component, 257 | DecoratorProps, 258 | ComponentProps, 259 | Pattern, 260 | Display, 261 | Validator, 262 | Message 263 | > 264 | 265 | ['x-value']?: any 266 | 267 | //顺序描述 268 | ['x-index']?: number 269 | //交互模式 270 | ['x-pattern']?: Pattern 271 | //展示状态 272 | ['x-display']?: Display 273 | //校验器 274 | ['x-validator']?: Validator 275 | //装饰器 276 | ['x-decorator']?: Decorator | (string & {}) | ((...args: any[]) => any) 277 | //装饰器属性 278 | ['x-decorator-props']?: DecoratorProps 279 | //组件 280 | ['x-component']?: Component | (string & {}) | ((...args: any[]) => any) 281 | //组件属性 282 | ['x-component-props']?: ComponentProps 283 | //组件响应器 284 | ['x-reactions']?: SchemaReactions<ReactionField> 285 | //内容 286 | ['x-content']?: any 287 | 288 | ['x-data']?: any 289 | 290 | ['x-visible']?: boolean 291 | 292 | ['x-hidden']?: boolean 293 | 294 | ['x-disabled']?: boolean 295 | 296 | ['x-editable']?: boolean 297 | 298 | ['x-read-only']?: boolean 299 | 300 | ['x-read-pretty']?: boolean 301 | 302 | ['x-compile-omitted']?: string[] 303 | 304 | [key: `x-${string | number}` | symbol]: any 305 | }> 306 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-items/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 | FormButtonGroup, 13 | Submit, 14 | FormItem, 15 | Space, 16 | Input, 17 | Select, 18 | DatePicker, 19 | ArrayItems, 20 | } from '@formily/element' 21 | import { Button } from 'element-ui' 22 | 23 | const SchemaField = createSchemaField({ 24 | components: { 25 | FormItem, 26 | Space, 27 | Input, 28 | Select, 29 | DatePicker, 30 | ArrayItems, 31 | }, 32 | }) 33 | 34 | export default { 35 | components: { 36 | FormProvider, 37 | FormButtonGroup, 38 | Button, 39 | Submit, 40 | ...SchemaField, 41 | }, 42 | 43 | data() { 44 | const form = createForm() 45 | const schema = { 46 | type: 'object', 47 | properties: { 48 | string_array: { 49 | type: 'array', 50 | 'x-component': 'ArrayItems', 51 | 'x-decorator': 'FormItem', 52 | title: '字符串数组', 53 | items: { 54 | type: 'void', 55 | 'x-component': 'Space', 56 | properties: { 57 | sort: { 58 | type: 'void', 59 | 'x-decorator': 'FormItem', 60 | 'x-component': 'ArrayItems.SortHandle', 61 | }, 62 | input: { 63 | type: 'string', 64 | 'x-decorator': 'FormItem', 65 | 'x-component': 'Input', 66 | }, 67 | remove: { 68 | type: 'void', 69 | 'x-decorator': 'FormItem', 70 | 'x-component': 'ArrayItems.Remove', 71 | }, 72 | }, 73 | }, 74 | properties: { 75 | add: { 76 | type: 'void', 77 | title: '添加条目', 78 | 'x-component': 'ArrayItems.Addition', 79 | }, 80 | }, 81 | }, 82 | array: { 83 | type: 'array', 84 | 'x-component': 'ArrayItems', 85 | 'x-decorator': 'FormItem', 86 | title: '对象数组', 87 | items: { 88 | type: 'object', 89 | properties: { 90 | space: { 91 | type: 'void', 92 | 'x-component': 'Space', 93 | properties: { 94 | sort: { 95 | type: 'void', 96 | 'x-decorator': 'FormItem', 97 | 'x-component': 'ArrayItems.SortHandle', 98 | }, 99 | date: { 100 | type: 'string', 101 | title: '日期', 102 | 'x-decorator': 'FormItem', 103 | 'x-component': 'DatePicker', 104 | 'x-component-props': { 105 | type: 'daterange', 106 | style: { 107 | width: '250px', 108 | }, 109 | }, 110 | }, 111 | input: { 112 | type: 'string', 113 | title: '输入框', 114 | 'x-decorator': 'FormItem', 115 | 'x-component': 'Input', 116 | }, 117 | select: { 118 | type: 'string', 119 | title: '下拉框', 120 | enum: [ 121 | { label: '选项1', value: 1 }, 122 | { label: '选项2', value: 2 }, 123 | ], 124 | 'x-decorator': 'FormItem', 125 | 'x-component': 'Select', 126 | 'x-component-props': { 127 | style: { 128 | width: '250px', 129 | }, 130 | }, 131 | }, 132 | remove: { 133 | type: 'void', 134 | 'x-decorator': 'FormItem', 135 | 'x-component': 'ArrayItems.Remove', 136 | }, 137 | }, 138 | }, 139 | }, 140 | }, 141 | properties: { 142 | add: { 143 | type: 'void', 144 | title: '添加条目', 145 | 'x-component': 'ArrayItems.Addition', 146 | }, 147 | }, 148 | }, 149 | array2: { 150 | type: 'array', 151 | 'x-component': 'ArrayItems', 152 | 'x-decorator': 'FormItem', 153 | 'x-component-props': { style: { width: '600px' } }, 154 | title: '对象数组', 155 | items: { 156 | type: 'object', 157 | 'x-decorator': 'ArrayItems.Item', 158 | properties: { 159 | space: { 160 | type: 'void', 161 | 'x-component': 'Space', 162 | properties: { 163 | sort: { 164 | type: 'void', 165 | 'x-decorator': 'FormItem', 166 | 'x-component': 'ArrayItems.SortHandle', 167 | }, 168 | date: { 169 | type: 'string', 170 | title: '日期', 171 | 'x-decorator': 'FormItem', 172 | 'x-component': 'DatePicker', 173 | 'x-component-props': { 174 | type: 'daterange', 175 | style: { 176 | width: '250px', 177 | }, 178 | }, 179 | }, 180 | input: { 181 | type: 'string', 182 | title: '输入框', 183 | 'x-decorator': 'FormItem', 184 | 'x-component': 'Input', 185 | }, 186 | remove: { 187 | type: 'void', 188 | 'x-decorator': 'FormItem', 189 | 'x-component': 'ArrayItems.Remove', 190 | }, 191 | }, 192 | }, 193 | }, 194 | }, 195 | properties: { 196 | add: { 197 | type: 'void', 198 | title: '添加条目', 199 | 'x-component': 'ArrayItems.Addition', 200 | }, 201 | }, 202 | }, 203 | }, 204 | } 205 | 206 | return { 207 | form, 208 | schema, 209 | } 210 | }, 211 | methods: { 212 | log(values) { 213 | console.log(values) 214 | }, 215 | }, 216 | } 217 | </script> 218 | 219 | <style lang="scss" scoped></style> 220 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormTab.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormTab 2 | 3 | > Tab form 4 | > 5 | > Note: This component is only applicable to Schema scenarios 6 | 7 | ## Markup Schema example 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | FormTab, 13 | FormItem, 14 | Input, 15 | FormButtonGroup, 16 | Submit, 17 | } from '@formily/antd' 18 | import { createForm } from '@formily/core' 19 | import { FormProvider, createSchemaField } from '@formily/react' 20 | import { Button } from 'antd' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | FormItem, 25 | FormTab, 26 | Input, 27 | }, 28 | }) 29 | 30 | const form = createForm() 31 | const formTab = FormTab.createFormTab() 32 | 33 | export default () => { 34 | return ( 35 | <FormProvider form={form}> 36 | <SchemaField> 37 | <SchemaField.Void 38 | type="void" 39 | x-component="FormTab" 40 | x-component-props={{ formTab }} 41 | > 42 | <SchemaField.Void 43 | type="void" 44 | name="tab1" 45 | x-component="FormTab.TabPane" 46 | x-component-props={{ tab: 'A1' }} 47 | > 48 | <SchemaField.String 49 | name="aaa" 50 | x-decorator="FormItem" 51 | title="AAA" 52 | required 53 | x-component="Input" 54 | /> 55 | </SchemaField.Void> 56 | <SchemaField.Void 57 | name="tab2" 58 | x-component="FormTab.TabPane" 59 | x-component-props={{ tab: 'A2' }} 60 | > 61 | <SchemaField.String 62 | name="bbb" 63 | x-decorator="FormItem" 64 | title="BBB" 65 | required 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | <SchemaField.Void 70 | name="tab3" 71 | x-component="FormTab.TabPane" 72 | x-component-props={{ tab: 'A3' }} 73 | > 74 | <SchemaField.String 75 | name="ccc" 76 | x-decorator="FormItem" 77 | title="CCC" 78 | required 79 | x-component="Input" 80 | /> 81 | </SchemaField.Void> 82 | </SchemaField.Void> 83 | </SchemaField> 84 | <FormButtonGroup.FormItem> 85 | <Button 86 | onClick={() => { 87 | form.query('tab3').take((field) => { 88 | field.visible = !field.visible 89 | }) 90 | }} 91 | > 92 | Show/hide the last tab 93 | </Button> 94 | <Button 95 | onClick={() => { 96 | formTab.setActiveKey('tab2') 97 | }} 98 | > 99 | Switch to the second Tab 100 | </Button> 101 | <Submit onSubmit={console.log}>Submit</Submit> 102 | </FormButtonGroup.FormItem> 103 | </FormProvider> 104 | ) 105 | } 106 | ``` 107 | 108 | ## JSON Schema case 109 | 110 | ```tsx 111 | import React from 'react' 112 | import { 113 | FormTab, 114 | FormItem, 115 | Input, 116 | FormButtonGroup, 117 | Submit, 118 | } from '@formily/antd' 119 | import { createForm } from '@formily/core' 120 | import { FormProvider, createSchemaField } from '@formily/react' 121 | import { Button } from 'antd' 122 | 123 | const SchemaField = createSchemaField({ 124 | components: { 125 | FormItem, 126 | FormTab, 127 | Input, 128 | }, 129 | }) 130 | 131 | const form = createForm() 132 | const formTab = FormTab.createFormTab() 133 | 134 | const schema = { 135 | type: 'object', 136 | properties: { 137 | collapse: { 138 | type: 'void', 139 | 'x-component': 'FormTab', 140 | 'x-component-props': { 141 | formTab: '{{formTab}}', 142 | }, 143 | properties: { 144 | tab1: { 145 | type: 'void', 146 | 'x-component': 'FormTab.TabPane', 147 | 'x-component-props': { 148 | tab: 'A1', 149 | }, 150 | properties: { 151 | aaa: { 152 | type: 'string', 153 | title: 'AAA', 154 | 'x-decorator': 'FormItem', 155 | required: true, 156 | 'x-component': 'Input', 157 | }, 158 | }, 159 | }, 160 | tab2: { 161 | type: 'void', 162 | 'x-component': 'FormTab.TabPane', 163 | 'x-component-props': { 164 | tab: 'A2', 165 | }, 166 | properties: { 167 | bbb: { 168 | type: 'string', 169 | title: 'BBB', 170 | 'x-decorator': 'FormItem', 171 | required: true, 172 | 'x-component': 'Input', 173 | }, 174 | }, 175 | }, 176 | tab3: { 177 | type: 'void', 178 | 'x-component': 'FormTab.TabPane', 179 | 'x-component-props': { 180 | tab: 'A3', 181 | }, 182 | properties: { 183 | ccc: { 184 | type: 'string', 185 | title: 'CCC', 186 | 'x-decorator': 'FormItem', 187 | required: true, 188 | 'x-component': 'Input', 189 | }, 190 | }, 191 | }, 192 | }, 193 | }, 194 | }, 195 | } 196 | 197 | export default () => { 198 | return ( 199 | <FormProvider form={form}> 200 | <SchemaField schema={schema} scope={{ formTab }} /> 201 | <FormButtonGroup.FormItem> 202 | <Button 203 | onClick={() => { 204 | form.query('tab3').take((field) => { 205 | field.visible = !field.visible 206 | }) 207 | }} 208 | > 209 | Show/hide the last tab 210 | </Button> 211 | <Button 212 | onClick={() => { 213 | formTab.setActiveKey('tab2') 214 | }} 215 | > 216 | Switch to the second Tab 217 | </Button> 218 | <Submit onSubmit={console.log}>Submit</Submit> 219 | </FormButtonGroup.FormItem> 220 | </FormProvider> 221 | ) 222 | } 223 | ``` 224 | 225 | ## API 226 | 227 | ### FormTab 228 | 229 | | Property name | Type | Description | Default value | 230 | | ------------- | -------- | ----------------------------------------------------- | ------------- | 231 | | formTab | IFormTab | Pass in the model created by createFormTab/useFormTab | | 232 | 233 | Other references https://ant.design/components/tabs-cn/ 234 | 235 | ### FormTab.TabPane 236 | 237 | Reference https://ant.design/components/tabs-cn/ 238 | 239 | ### FormTab.createFormTab 240 | 241 | ```ts pure 242 | type ActiveKey = string | number 243 | 244 | interface createFormTab { 245 | (defaultActiveKey?: ActiveKey): IFormTab 246 | } 247 | 248 | interface IFormTab { 249 | //Activate the primary key 250 | activeKey: ActiveKey 251 | //Set the activation key 252 | setActiveKey(key: ActiveKey): void 253 | } 254 | ``` 255 | ``` -------------------------------------------------------------------------------- /packages/path/src/matcher.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | Segments, 3 | Node, 4 | isIdentifier, 5 | isExpandOperator, 6 | isWildcardOperator, 7 | isGroupExpression, 8 | isRangeExpression, 9 | isIgnoreExpression, 10 | isDotOperator, 11 | isDestructorExpression, 12 | IdentifierNode, 13 | IgnoreExpressionNode, 14 | DestructorExpressionNode, 15 | ExpandOperatorNode, 16 | WildcardOperatorNode, 17 | GroupExpressionNode, 18 | RangeExpressionNode, 19 | } from './types' 20 | import { isEqual, toArr, isSegmentEqual } from './shared' 21 | export interface IRecord { 22 | score: number 23 | } 24 | 25 | export class Matcher { 26 | private tree: Node 27 | 28 | private stack: Node[] 29 | 30 | private record: IRecord 31 | 32 | private excluding: boolean 33 | 34 | private wildcards: WildcardOperatorNode[] 35 | 36 | private path: Segments 37 | 38 | constructor(tree: Node, record?: any) { 39 | this.tree = tree 40 | this.stack = [] 41 | this.excluding = false 42 | this.wildcards = [] 43 | this.record = record 44 | } 45 | 46 | next(node: Node, pos: number) { 47 | // const isOverToken = pos > this.path.length 48 | if (node.after) { 49 | // if (isOverToken) { 50 | // return false 51 | // } 52 | return this.matchNode(node.after, pos) 53 | } 54 | 55 | if (isWildcardOperator(node) && !node.filter) { 56 | if (this.excluding) { 57 | return false 58 | } else { 59 | if (pos === 0 || node.optional) return true 60 | return !!this.take(pos) 61 | } 62 | } 63 | 64 | const isLastToken = pos === this.path.length - 1 65 | if (isLastToken) { 66 | return !!this.take(pos) 67 | } else { 68 | const wildcard = this.wildcards.pop() 69 | if (wildcard && wildcard.after) { 70 | return this.next(wildcard, pos) 71 | } 72 | } 73 | 74 | return false 75 | } 76 | 77 | shot() { 78 | if (this.record?.score >= 0) { 79 | this.record.score++ 80 | } 81 | } 82 | 83 | take(pos: number) { 84 | return String(this.path[pos] ?? '') 85 | } 86 | 87 | matchExcludeIdentifier(matched: boolean, node: Node, pos: number) { 88 | const isLastToken = pos === this.path.length - 1 89 | const isContainToken = pos < this.path.length 90 | if (!node.after) { 91 | this.excluding = false 92 | } 93 | if (matched) { 94 | if (node.after) { 95 | return this.next(node, pos) 96 | } 97 | if (isLastToken) { 98 | return false 99 | } 100 | } 101 | if (isLastToken) { 102 | return true 103 | } 104 | return isContainToken 105 | } 106 | 107 | matchIdentifier(node: IdentifierNode, pos: number) { 108 | const current = this.take(pos) 109 | let matched = false 110 | if (isExpandOperator(node.after)) { 111 | if (current.indexOf(node.value) === 0) { 112 | this.shot() 113 | matched = true 114 | } 115 | if (this.excluding) { 116 | return this.matchExcludeIdentifier(matched, node.after, pos) 117 | } else { 118 | return matched && this.next(node.after, pos) 119 | } 120 | } else if (current === node.value) { 121 | this.shot() 122 | matched = true 123 | } 124 | if (this.excluding) { 125 | return this.matchExcludeIdentifier(matched, node, pos) 126 | } else { 127 | return matched && this.next(node, pos) 128 | } 129 | } 130 | 131 | matchIgnoreExpression(node: IgnoreExpressionNode, pos: number) { 132 | return isEqual(node.value, this.take(pos)) && this.next(node, pos) 133 | } 134 | 135 | matchDestructorExpression(node: DestructorExpressionNode, pos: number) { 136 | return isEqual(node.source, this.take(pos)) && this.next(node, pos) 137 | } 138 | 139 | matchExpandOperator(node: ExpandOperatorNode, pos: number) { 140 | return this.next(node, pos) 141 | } 142 | 143 | matchWildcardOperator(node: WildcardOperatorNode, pos: number) { 144 | let matched = false 145 | if (node.filter) { 146 | this.stack.push(node) 147 | matched = this.matchNode(node.filter, pos) 148 | this.stack.pop() 149 | } else { 150 | matched = this.next(node, pos) 151 | } 152 | return matched 153 | } 154 | 155 | matchGroupExpression(node: GroupExpressionNode, pos: number) { 156 | let excluding = false 157 | if (node.isExclude) { 158 | excluding = !this.excluding 159 | } 160 | return toArr(node.value)[excluding ? 'every' : 'some']((item) => { 161 | this.wildcards = this.stack.slice() as WildcardOperatorNode[] 162 | this.excluding = excluding 163 | return this.matchNode(item, pos) 164 | }) 165 | } 166 | 167 | matchRangeExpression(node: RangeExpressionNode, pos: number) { 168 | const current = Number(this.take(pos)) 169 | if (node.start) { 170 | if (node.end) { 171 | return ( 172 | current >= Number(node.start.value) && 173 | current <= Number(node.end.value) 174 | ) 175 | } else { 176 | return current >= Number(node.start.value) 177 | } 178 | } else { 179 | if (node.end) { 180 | return current <= Number(node.end.value) 181 | } else { 182 | this.wildcards = this.stack.slice() as WildcardOperatorNode[] 183 | return this.next(node, pos) 184 | } 185 | } 186 | } 187 | 188 | matchNode(node: Node, pos = 0) { 189 | if (isDotOperator(node)) { 190 | return this.next(node, pos + 1) 191 | } else if (isIdentifier(node)) { 192 | return this.matchIdentifier(node, pos) 193 | } else if (isIgnoreExpression(node)) { 194 | return this.matchIgnoreExpression(node, pos) 195 | } else if (isDestructorExpression(node)) { 196 | return this.matchDestructorExpression(node, pos) 197 | } else if (isExpandOperator(node)) { 198 | return this.matchExpandOperator(node, pos) 199 | } else if (isWildcardOperator(node)) { 200 | return this.matchWildcardOperator(node, pos) 201 | } else if (isGroupExpression(node)) { 202 | return this.matchGroupExpression(node, pos) 203 | } else if (isRangeExpression(node)) { 204 | return this.matchRangeExpression(node, pos) 205 | } 206 | return false 207 | } 208 | 209 | match(path: Segments) { 210 | this.path = path 211 | return { matched: this.matchNode(this.tree), record: this.record } 212 | } 213 | 214 | static matchSegments(source: Segments, target: Segments, record?: any) { 215 | if (source.length !== target.length) return { matched: false, record } 216 | const match = (pos = 0) => { 217 | const current = isSegmentEqual(source[pos], target[pos]) 218 | if (record?.score >= 0) { 219 | record.score++ 220 | } 221 | return current && (pos < source.length - 1 ? match(pos + 1) : true) 222 | } 223 | return { matched: match(), record } 224 | } 225 | } 226 | ``` -------------------------------------------------------------------------------- /packages/vue/src/components/RecursionField.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { inject, provide, watch, shallowRef, computed, markRaw } from 'vue-demi' 2 | import { GeneralField } from '@formily/core' 3 | import { isFn, isValid, lazyMerge } from '@formily/shared' 4 | import { Schema } from '@formily/json-schema' 5 | import { 6 | SchemaSymbol, 7 | SchemaOptionsSymbol, 8 | SchemaExpressionScopeSymbol, 9 | } from '../shared' 10 | import { useField } from '../hooks' 11 | import ObjectField from './ObjectField' 12 | import ArrayField from './ArrayField' 13 | import Field from './Field' 14 | import VoidField from './VoidField' 15 | import { h } from '../shared/h' 16 | 17 | import type { IRecursionFieldProps, DefineComponent } from '../types' 18 | 19 | const resolveEmptySlot = (slots: Record<any, (...args: any[]) => any[]>) => { 20 | return Object.keys(slots).length 21 | ? h('div', { style: 'display:contents;' }, slots) 22 | : undefined 23 | } 24 | 25 | const RecursionField = { 26 | name: 'RecursionField', 27 | inheritAttrs: false, 28 | props: { 29 | schema: { 30 | required: true, 31 | }, 32 | name: [String, Number], 33 | basePath: {}, 34 | onlyRenderProperties: { 35 | type: Boolean, 36 | default: undefined, 37 | }, 38 | onlyRenderSelf: { 39 | type: Boolean, 40 | default: undefined, 41 | }, 42 | mapProperties: {}, 43 | filterProperties: {}, 44 | }, 45 | setup(props: IRecursionFieldProps) { 46 | const parentRef = useField() 47 | const optionsRef = inject(SchemaOptionsSymbol) 48 | const scopeRef = inject(SchemaExpressionScopeSymbol) 49 | const createSchema = (schemaProp: IRecursionFieldProps['schema']) => 50 | markRaw(new Schema(schemaProp)) 51 | const fieldSchemaRef = computed(() => createSchema(props.schema)) 52 | 53 | const getPropsFromSchema = (schema: Schema) => 54 | schema?.toFieldProps?.({ 55 | ...optionsRef.value, 56 | get scope() { 57 | return lazyMerge(optionsRef.value.scope, scopeRef.value) 58 | }, 59 | }) 60 | const fieldPropsRef = shallowRef(getPropsFromSchema(fieldSchemaRef.value)) 61 | 62 | watch([fieldSchemaRef, optionsRef], () => { 63 | fieldPropsRef.value = getPropsFromSchema(fieldSchemaRef.value) 64 | }) 65 | 66 | const getBasePath = () => { 67 | if (props.onlyRenderProperties) { 68 | return props.basePath ?? parentRef?.value?.address.concat(props.name) 69 | } 70 | return props.basePath ?? parentRef?.value?.address 71 | } 72 | 73 | provide(SchemaSymbol, fieldSchemaRef) 74 | 75 | return () => { 76 | const basePath = getBasePath() 77 | const fieldProps = fieldPropsRef.value 78 | 79 | const generateSlotsByProperties = (scoped = false) => { 80 | if (props.onlyRenderSelf) return {} 81 | const properties = Schema.getOrderProperties(fieldSchemaRef.value) 82 | if (!properties.length) return {} 83 | const renderMap: Record<string, ((field?: GeneralField) => unknown)[]> = 84 | {} 85 | const setRender = ( 86 | key: string, 87 | value: (field?: GeneralField) => unknown 88 | ) => { 89 | if (!renderMap[key]) { 90 | renderMap[key] = [] 91 | } 92 | renderMap[key].push(value) 93 | } 94 | properties.forEach(({ schema: item, key: name }, index) => { 95 | let schema: Schema = item 96 | if (isFn(props.mapProperties)) { 97 | const mapped = props.mapProperties(item, name) 98 | if (mapped) { 99 | schema = mapped 100 | } 101 | } 102 | if (isFn(props.filterProperties)) { 103 | if (props.filterProperties(schema, name) === false) { 104 | return null 105 | } 106 | } 107 | setRender(schema['x-slot'] ?? 'default', (field?: GeneralField) => 108 | h( 109 | RecursionField, 110 | { 111 | key: `${index}-${name}`, 112 | attrs: { 113 | schema, 114 | name, 115 | basePath: field?.address ?? basePath, 116 | }, 117 | slot: schema['x-slot'], 118 | }, 119 | {} 120 | ) 121 | ) 122 | }) 123 | const slots = {} 124 | Object.keys(renderMap).forEach((key) => { 125 | const renderFns = renderMap[key] 126 | slots[key] = scoped 127 | ? ({ field }) => renderFns.map((fn) => fn(field)) 128 | : () => renderFns.map((fn) => fn()) 129 | }) 130 | return slots 131 | } 132 | 133 | const render = () => { 134 | if (!isValid(props.name)) 135 | return resolveEmptySlot(generateSlotsByProperties()) 136 | if (fieldSchemaRef.value.type === 'object') { 137 | if (props.onlyRenderProperties) 138 | return resolveEmptySlot(generateSlotsByProperties()) 139 | return h( 140 | ObjectField, 141 | { 142 | attrs: { 143 | ...fieldProps, 144 | name: props.name, 145 | basePath: basePath, 146 | }, 147 | }, 148 | generateSlotsByProperties(true) 149 | ) 150 | } else if (fieldSchemaRef.value.type === 'array') { 151 | return h( 152 | ArrayField, 153 | { 154 | attrs: { 155 | ...fieldProps, 156 | name: props.name, 157 | basePath: basePath, 158 | }, 159 | }, 160 | {} 161 | ) 162 | } else if (fieldSchemaRef.value.type === 'void') { 163 | if (props.onlyRenderProperties) 164 | return resolveEmptySlot(generateSlotsByProperties()) 165 | const slots = generateSlotsByProperties(true) 166 | return h( 167 | VoidField, 168 | { 169 | attrs: { 170 | ...fieldProps, 171 | name: props.name, 172 | basePath: basePath, 173 | }, 174 | }, 175 | slots 176 | ) 177 | } 178 | 179 | return h( 180 | Field, 181 | { 182 | attrs: { 183 | ...fieldProps, 184 | name: props.name, 185 | basePath: basePath, 186 | }, 187 | }, 188 | {} 189 | ) 190 | } 191 | 192 | if (!fieldSchemaRef.value) return 193 | 194 | return render() 195 | } 196 | }, 197 | } as unknown as DefineComponent<IRecursionFieldProps> 198 | 199 | export default RecursionField 200 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormTab.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormTab 2 | 3 | > Tab form 4 | > 5 | > Note: This component is only applicable to Schema scenarios 6 | 7 | ## Markup Schema example 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | FormTab, 13 | FormItem, 14 | Input, 15 | FormButtonGroup, 16 | Submit, 17 | } from '@formily/next' 18 | import { createForm } from '@formily/core' 19 | import { FormProvider, createSchemaField } from '@formily/react' 20 | import { Button } from '@alifd/next' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | FormItem, 25 | FormTab, 26 | Input, 27 | }, 28 | }) 29 | 30 | const form = createForm() 31 | const formTab = FormTab.createFormTab() 32 | 33 | export default () => { 34 | return ( 35 | <FormProvider form={form}> 36 | <SchemaField> 37 | <SchemaField.Void 38 | type="void" 39 | x-component="FormTab" 40 | x-component-props={{ formTab }} 41 | > 42 | <SchemaField.Void 43 | type="void" 44 | name="tab1" 45 | x-component="FormTab.TabPane" 46 | x-component-props={{ tab: 'A1' }} 47 | > 48 | <SchemaField.String 49 | name="aaa" 50 | x-decorator="FormItem" 51 | title="AAA" 52 | required 53 | x-component="Input" 54 | /> 55 | </SchemaField.Void> 56 | <SchemaField.Void 57 | name="tab2" 58 | x-component="FormTab.TabPane" 59 | x-component-props={{ tab: 'A2' }} 60 | > 61 | <SchemaField.String 62 | name="bbb" 63 | x-decorator="FormItem" 64 | title="BBB" 65 | required 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | <SchemaField.Void 70 | name="tab3" 71 | x-component="FormTab.TabPane" 72 | x-component-props={{ tab: 'A3' }} 73 | > 74 | <SchemaField.String 75 | name="ccc" 76 | x-decorator="FormItem" 77 | title="CCC" 78 | required 79 | x-component="Input" 80 | /> 81 | </SchemaField.Void> 82 | </SchemaField.Void> 83 | </SchemaField> 84 | <FormButtonGroup.FormItem> 85 | <Button 86 | onClick={() => { 87 | form.query('tab3').take((field) => { 88 | field.visible = !field.visible 89 | }) 90 | }} 91 | > 92 | Show/hide the last tab 93 | </Button> 94 | <Button 95 | onClick={() => { 96 | formTab.setActiveKey('tab2') 97 | }} 98 | > 99 | Switch to the second Tab 100 | </Button> 101 | <Submit onSubmit={console.log}>Submit</Submit> 102 | </FormButtonGroup.FormItem> 103 | </FormProvider> 104 | ) 105 | } 106 | ``` 107 | 108 | ## JSON Schema case 109 | 110 | ```tsx 111 | import React from 'react' 112 | import { 113 | FormTab, 114 | FormItem, 115 | Input, 116 | FormButtonGroup, 117 | Submit, 118 | } from '@formily/next' 119 | import { createForm } from '@formily/core' 120 | import { FormProvider, createSchemaField } from '@formily/react' 121 | import { Button } from '@alifd/next' 122 | 123 | const SchemaField = createSchemaField({ 124 | components: { 125 | FormItem, 126 | FormTab, 127 | Input, 128 | }, 129 | }) 130 | 131 | const form = createForm() 132 | const formTab = FormTab.createFormTab() 133 | 134 | const schema = { 135 | type: 'object', 136 | properties: { 137 | collapse: { 138 | type: 'void', 139 | 'x-component': 'FormTab', 140 | 'x-component-props': { 141 | formTab: '{{formTab}}', 142 | }, 143 | properties: { 144 | tab1: { 145 | type: 'void', 146 | 'x-component': 'FormTab.TabPane', 147 | 'x-component-props': { 148 | tab: 'A1', 149 | }, 150 | properties: { 151 | aaa: { 152 | type: 'string', 153 | title: 'AAA', 154 | 'x-decorator': 'FormItem', 155 | required: true, 156 | 'x-component': 'Input', 157 | }, 158 | }, 159 | }, 160 | tab2: { 161 | type: 'void', 162 | 'x-component': 'FormTab.TabPane', 163 | 'x-component-props': { 164 | tab: 'A2', 165 | }, 166 | properties: { 167 | bbb: { 168 | type: 'string', 169 | title: 'BBB', 170 | 'x-decorator': 'FormItem', 171 | required: true, 172 | 'x-component': 'Input', 173 | }, 174 | }, 175 | }, 176 | tab3: { 177 | type: 'void', 178 | 'x-component': 'FormTab.TabPane', 179 | 'x-component-props': { 180 | tab: 'A3', 181 | }, 182 | properties: { 183 | ccc: { 184 | type: 'string', 185 | title: 'CCC', 186 | 'x-decorator': 'FormItem', 187 | required: true, 188 | 'x-component': 'Input', 189 | }, 190 | }, 191 | }, 192 | }, 193 | }, 194 | }, 195 | } 196 | 197 | export default () => { 198 | return ( 199 | <FormProvider form={form}> 200 | <SchemaField schema={schema} scope={{ formTab }} /> 201 | <FormButtonGroup.FormItem> 202 | <Button 203 | onClick={() => { 204 | form.query('tab3').take((field) => { 205 | field.visible = !field.visible 206 | }) 207 | }} 208 | > 209 | Show/hide the last tab 210 | </Button> 211 | <Button 212 | onClick={() => { 213 | formTab.setActiveKey('tab2') 214 | }} 215 | > 216 | Switch to the second Tab 217 | </Button> 218 | <Submit onSubmit={console.log}>Submit</Submit> 219 | </FormButtonGroup.FormItem> 220 | </FormProvider> 221 | ) 222 | } 223 | ``` 224 | 225 | ## API 226 | 227 | ### FormTab 228 | 229 | | Property name | Type | Description | Default value | 230 | | ------------- | -------- | ----------------------------------------------------- | ------------- | 231 | | formTab | IFormTab | Pass in the model created by createFormTab/useFormTab | | 232 | 233 | Other references https://fusion.design/pc/component/basic/tab 234 | 235 | ### FormTab.TabPane 236 | 237 | Refer to the Item property of https://fusion.design/pc/component/basic/tab 238 | 239 | ### FormTab.createFormTab 240 | 241 | ```ts pure 242 | type ActiveKey = string | number 243 | 244 | interface createFormTab { 245 | (defaultActiveKey?: ActiveKey): IFormTab 246 | } 247 | 248 | interface IFormTab { 249 | //Activate the primary key 250 | activeKey: ActiveKey 251 | //Set the activation key 252 | setActiveKey(key: ActiveKey): void 253 | } 254 | ``` 255 | ```