This is page 10 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/form-layout/useResponsiveFormLayout.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { useRef, useState, useEffect } from 'react' 2 | import { isArr, isValid } from '@formily/shared' 3 | 4 | interface IProps { 5 | breakpoints?: number[] 6 | layout?: 7 | | 'vertical' 8 | | 'horizontal' 9 | | 'inline' 10 | | ('vertical' | 'horizontal' | 'inline')[] 11 | labelCol?: number | number[] 12 | wrapperCol?: number | number[] 13 | labelAlign?: 'right' | 'left' | ('right' | 'left')[] 14 | wrapperAlign?: 'right' | 'left' | ('right' | 'left')[] 15 | [props: string]: any 16 | } 17 | 18 | interface ICalcBreakpointIndex { 19 | (originalBreakpoints: number[], width: number): number 20 | } 21 | 22 | interface ICalculateProps { 23 | (target: HTMLElement, props: IProps): IProps 24 | } 25 | 26 | interface IUseResponsiveFormLayout { 27 | (props: IProps): { 28 | ref: React.MutableRefObject<HTMLDivElement> 29 | props: any 30 | } 31 | } 32 | 33 | const calcBreakpointIndex: ICalcBreakpointIndex = (breakpoints, width) => { 34 | for (let i = 0; i < breakpoints.length; i++) { 35 | if (width <= breakpoints[i]) { 36 | return i 37 | } 38 | } 39 | } 40 | 41 | const calcFactor = <T>(value: T | T[], breakpointIndex: number): T => { 42 | if (Array.isArray(value)) { 43 | if (breakpointIndex === -1) return value[0] 44 | return value[breakpointIndex] ?? value[value.length - 1] 45 | } else { 46 | return value 47 | } 48 | } 49 | 50 | const factor = <T>(value: T | T[], breakpointIndex: number): T => 51 | isValid(value) ? calcFactor(value as any, breakpointIndex) : value 52 | 53 | const calculateProps: ICalculateProps = (target, props) => { 54 | const { clientWidth } = target 55 | const { 56 | breakpoints, 57 | layout, 58 | labelAlign, 59 | wrapperAlign, 60 | labelCol, 61 | wrapperCol, 62 | ...otherProps 63 | } = props 64 | const breakpointIndex = calcBreakpointIndex(breakpoints, clientWidth) 65 | 66 | return { 67 | layout: factor(layout, breakpointIndex), 68 | labelAlign: factor(labelAlign, breakpointIndex), 69 | wrapperAlign: factor(wrapperAlign, breakpointIndex), 70 | labelCol: factor(labelCol, breakpointIndex), 71 | wrapperCol: factor(wrapperCol, breakpointIndex), 72 | ...otherProps, 73 | } 74 | } 75 | 76 | export const useResponsiveFormLayout: IUseResponsiveFormLayout = (props) => { 77 | const ref = useRef<HTMLDivElement>(null) 78 | const { breakpoints } = props 79 | if (!isArr(breakpoints)) { 80 | return { ref, props } 81 | } 82 | const [layoutProps, setLayout] = useState<any>(props) 83 | 84 | const updateUI = () => { 85 | if (ref.current) { 86 | setLayout(calculateProps(ref.current, props)) 87 | } 88 | } 89 | 90 | useEffect(() => { 91 | const observer = () => { 92 | updateUI() 93 | } 94 | const resizeObserver = new ResizeObserver(observer) 95 | if (ref.current) { 96 | resizeObserver.observe(ref.current) 97 | } 98 | updateUI() 99 | return () => { 100 | resizeObserver.disconnect() 101 | } 102 | }, []) 103 | 104 | return { 105 | ref, 106 | props: layoutProps, 107 | } 108 | } 109 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-collapse/markup-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <Form :form="form" :label-col="6" :wrapper-col="10"> 3 | <SchemaField> 4 | <SchemaVoidField 5 | type="void" 6 | title="折叠面板" 7 | x-decorator="FormItem" 8 | x-component="FormCollapse" 9 | :x-component-props="{ formCollapse }" 10 | > 11 | <SchemaVoidField 12 | type="void" 13 | name="tab1" 14 | x-component="FormCollapse.Item" 15 | :x-component-props="{ title: 'A1' }" 16 | > 17 | <SchemaStringField 18 | name="aaa" 19 | x-decorator="FormItem" 20 | title="AAA" 21 | required 22 | x-component="Input" 23 | /> 24 | </SchemaVoidField> 25 | <SchemaVoidField 26 | name="tab2" 27 | x-component="FormCollapse.Item" 28 | :x-component-props="{ title: 'A2' }" 29 | > 30 | <SchemaStringField 31 | name="bbb" 32 | x-decorator="FormItem" 33 | title="BBB" 34 | required 35 | x-component="Input" 36 | /> 37 | </SchemaVoidField> 38 | <SchemaVoidField 39 | name="tab3" 40 | x-component="FormCollapse.Item" 41 | :x-component-props="{ title: 'A3' }" 42 | > 43 | <SchemaStringField 44 | name="ccc" 45 | x-decorator="FormItem" 46 | title="CCC" 47 | required 48 | x-component="Input" 49 | /> 50 | </SchemaVoidField> 51 | </SchemaVoidField> 52 | </SchemaField> 53 | <FormButtonGroup alignFormItem> 54 | <Button 55 | @click=" 56 | () => { 57 | form.query('tab3').take((field) => { 58 | field.visible = !field.visible 59 | }) 60 | } 61 | " 62 | > 63 | 显示/隐藏最后一个Tab 64 | </Button> 65 | <Button 66 | @click=" 67 | () => { 68 | formCollapse.toggleActiveKey('tab2') 69 | } 70 | " 71 | > 72 | 切换第二个Tab 73 | </Button> 74 | <Submit @submit="log">提交</Submit> 75 | </FormButtonGroup> 76 | </Form> 77 | </template> 78 | 79 | <script> 80 | import { createForm } from '@formily/core' 81 | import { createSchemaField } from '@formily/vue' 82 | import { 83 | FormItem, 84 | FormCollapse, 85 | FormButtonGroup, 86 | Submit, 87 | Input, 88 | Form, 89 | } from '@formily/element' 90 | import { Button } from 'element-ui' 91 | 92 | const SchemaField = createSchemaField({ 93 | components: { 94 | FormItem, 95 | FormCollapse, 96 | Input, 97 | }, 98 | }) 99 | 100 | export default { 101 | components: { 102 | Form, 103 | FormButtonGroup, 104 | Button, 105 | Submit, 106 | ...SchemaField, 107 | }, 108 | 109 | data() { 110 | const form = createForm() 111 | const formCollapse = FormCollapse.createFormCollapse() 112 | 113 | return { 114 | form, 115 | formCollapse, 116 | } 117 | }, 118 | methods: { 119 | log(values) { 120 | console.log(values) 121 | }, 122 | }, 123 | } 124 | </script> 125 | 126 | <style lang="scss" scoped></style> 127 | ``` -------------------------------------------------------------------------------- /packages/next/src/form-layout/useResponsiveFormLayout.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { useRef, useState, useEffect } from 'react' 2 | import { isArr, isValid } from '@formily/shared' 3 | 4 | interface IProps { 5 | breakpoints?: number[] 6 | layout?: 7 | | 'vertical' 8 | | 'horizontal' 9 | | 'inline' 10 | | ('vertical' | 'horizontal' | 'inline')[] 11 | labelCol?: number | number[] 12 | wrapperCol?: number | number[] 13 | labelAlign?: 'right' | 'left' | ('right' | 'left')[] 14 | wrapperAlign?: 'right' | 'left' | ('right' | 'left')[] 15 | [props: string]: any 16 | } 17 | 18 | interface ICalcBreakpointIndex { 19 | (originalBreakpoints: number[], width: number): number 20 | } 21 | 22 | interface ICalculateProps { 23 | (target: HTMLElement, props: IProps): IProps 24 | } 25 | 26 | interface IUseResponsiveFormLayout { 27 | (props: IProps): { 28 | ref: React.MutableRefObject<HTMLDivElement> 29 | props: any 30 | } 31 | } 32 | 33 | const calcBreakpointIndex: ICalcBreakpointIndex = (breakpoints, width) => { 34 | for (let i = 0; i < breakpoints.length; i++) { 35 | if (width <= breakpoints[i]) { 36 | return i 37 | } 38 | } 39 | } 40 | 41 | const calcFactor = <T>(value: T | T[], breakpointIndex: number): T => { 42 | if (Array.isArray(value)) { 43 | if (breakpointIndex === -1) return value[0] 44 | return value[breakpointIndex] ?? value[value.length - 1] 45 | } else { 46 | return value 47 | } 48 | } 49 | 50 | const factor = <T>(value: T | T[], breakpointIndex: number): T => 51 | isValid(value) ? calcFactor(value as any, breakpointIndex) : value 52 | 53 | const calculateProps: ICalculateProps = (target, props) => { 54 | const { clientWidth } = target 55 | const { 56 | breakpoints, 57 | layout, 58 | labelAlign, 59 | wrapperAlign, 60 | labelCol, 61 | wrapperCol, 62 | ...otherProps 63 | } = props 64 | const breakpointIndex = calcBreakpointIndex(breakpoints, clientWidth) 65 | 66 | return { 67 | layout: factor(layout, breakpointIndex), 68 | labelAlign: factor(labelAlign, breakpointIndex), 69 | wrapperAlign: factor(wrapperAlign, breakpointIndex), 70 | labelCol: factor(labelCol, breakpointIndex), 71 | wrapperCol: factor(wrapperCol, breakpointIndex), 72 | ...otherProps, 73 | } 74 | } 75 | 76 | export const useResponsiveFormLayout: IUseResponsiveFormLayout = (props) => { 77 | const ref = useRef<HTMLDivElement>(null) 78 | const { breakpoints } = props 79 | if (!isArr(breakpoints)) { 80 | return { ref, props } 81 | } 82 | const [layoutProps, setLayout] = useState<IProps>(props) 83 | 84 | const updateUI = () => { 85 | if (ref.current) { 86 | setLayout(calculateProps(ref.current, props)) 87 | } 88 | } 89 | 90 | useEffect(() => { 91 | const observer = () => { 92 | updateUI() 93 | } 94 | const resizeObserver = new ResizeObserver(observer) 95 | if (ref.current) { 96 | resizeObserver.observe(ref.current) 97 | } 98 | updateUI() 99 | return () => { 100 | resizeObserver.disconnect() 101 | } 102 | }, []) 103 | 104 | return { 105 | ref, 106 | props: layoutProps, 107 | } 108 | } 109 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Radio.md: -------------------------------------------------------------------------------- ```markdown 1 | # Radio 2 | 3 | > Single selection box 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | Radio, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Number 26 | name="radio" 27 | title="single choice" 28 | enum={[ 29 | { 30 | label: 'Option 1', 31 | value: 1, 32 | }, 33 | { 34 | label: 'Option 2', 35 | value: 2, 36 | }, 37 | ]} 38 | x-decorator="FormItem" 39 | x-component="Radio.Group" 40 | /> 41 | </SchemaField> 42 | <FormButtonGroup> 43 | <Submit onSubmit={console.log}>Submit</Submit> 44 | </FormButtonGroup> 45 | </FormProvider> 46 | ) 47 | ``` 48 | 49 | ## JSON Schema case 50 | 51 | ```tsx 52 | import React from 'react' 53 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' 54 | import { createForm } from '@formily/core' 55 | import { FormProvider, createSchemaField } from '@formily/react' 56 | 57 | const SchemaField = createSchemaField({ 58 | components: { 59 | Radio, 60 | FormItem, 61 | }, 62 | }) 63 | 64 | const form = createForm() 65 | 66 | const schema = { 67 | type: 'object', 68 | properties: { 69 | radio: { 70 | type: 'number', 71 | title: 'Single selection', 72 | enum: [ 73 | { 74 | label: 'Option 1', 75 | value: 1, 76 | }, 77 | { 78 | label: 'Option 2', 79 | value: 2, 80 | }, 81 | ], 82 | 'x-decorator': 'FormItem', 83 | 'x-component': 'Radio.Group', 84 | }, 85 | }, 86 | } 87 | 88 | export default () => ( 89 | <FormProvider form={form}> 90 | <SchemaField schema={schema} /> 91 | <FormButtonGroup> 92 | <Submit onSubmit={console.log}>Submit</Submit> 93 | </FormButtonGroup> 94 | </FormProvider> 95 | ) 96 | ``` 97 | 98 | ## Pure JSX case 99 | 100 | ```tsx 101 | import React from 'react' 102 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' 103 | import { createForm } from '@formily/core' 104 | import { FormProvider, Field } from '@formily/react' 105 | 106 | const form = createForm() 107 | 108 | export default () => ( 109 | <FormProvider form={form}> 110 | <Field 111 | name="radio" 112 | title="single choice" 113 | dataSource={[ 114 | { 115 | label: 'Option 1', 116 | value: 1, 117 | }, 118 | { 119 | label: 'Option 2', 120 | value: 2, 121 | }, 122 | ]} 123 | decorator={FormItem} 124 | component={Radio.Group} 125 | /> 126 | <FormButtonGroup> 127 | <Submit onSubmit={console.log}>Submit</Submit> 128 | </FormButtonGroup> 129 | </FormProvider> 130 | ) 131 | ``` 132 | 133 | ## API 134 | 135 | Reference https://ant.design/components/radio-cn/ 136 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-cards/effects-markup-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaArrayField 5 | name="array" 6 | :maxItems="3" 7 | x-component="ArrayCards" 8 | x-decorator="FormItem" 9 | :x-component-props="{ 10 | title: '对象数组', 11 | }" 12 | > 13 | <SchemaObjectField> 14 | <SchemaVoidField x-component="ArrayCards.Index" /> 15 | <SchemaStringField 16 | name="aa" 17 | x-decorator="FormItem" 18 | title="AA" 19 | required 20 | description="AA输入123时隐藏BB" 21 | x-component="Input" 22 | /> 23 | <SchemaStringField 24 | name="bb" 25 | x-decorator="FormItem" 26 | title="BB" 27 | required 28 | x-component="Input" 29 | /> 30 | <SchemaStringField 31 | name="cc" 32 | x-decorator="FormItem" 33 | title="CC" 34 | required 35 | description="CC输入123时隐藏DD" 36 | x-component="Input" 37 | /> 38 | <SchemaStringField 39 | name="dd" 40 | x-decorator="FormItem" 41 | title="DD" 42 | required 43 | x-component="Input" 44 | /> 45 | <SchemaVoidField x-component="ArrayCards.Remove" /> 46 | <SchemaVoidField x-component="ArrayCards.MoveUp" /> 47 | <SchemaVoidField x-component="ArrayCards.MoveDown" /> 48 | </SchemaObjectField> 49 | <SchemaVoidField x-component="ArrayCards.Addition" title="添加条目" /> 50 | </SchemaArrayField> 51 | </SchemaField> 52 | <Submit @submit="log">提交</Submit> 53 | </FormProvider> 54 | </template> 55 | 56 | <script> 57 | import { createForm, onFieldChange, onFieldReact } from '@formily/core' 58 | import { FormProvider, createSchemaField } from '@formily/vue' 59 | import { 60 | FormItem, 61 | FormButtonGroup, 62 | Submit, 63 | Input, 64 | ArrayCards, 65 | } from '@formily/element' 66 | import { Button } from 'element-ui' 67 | 68 | const SchemaField = createSchemaField({ 69 | components: { 70 | FormItem, 71 | Input, 72 | ArrayCards, 73 | }, 74 | }) 75 | 76 | export default { 77 | components: { 78 | FormProvider, 79 | FormButtonGroup, 80 | Button, 81 | Submit, 82 | ...SchemaField, 83 | }, 84 | 85 | data() { 86 | const form = createForm({ 87 | effects: () => { 88 | //主动联动模式 89 | onFieldChange('array.*.aa', ['value'], (field, form) => { 90 | form.setFieldState(field.query('.bb'), (state) => { 91 | state.visible = field.value != '123' 92 | }) 93 | }) 94 | //被动联动模式 95 | onFieldReact('array.*.dd', (field) => { 96 | field.visible = field.query('.cc').get('value') != '123' 97 | }) 98 | }, 99 | }) 100 | 101 | return { 102 | form, 103 | } 104 | }, 105 | methods: { 106 | log(values) { 107 | console.log(values) 108 | }, 109 | }, 110 | } 111 | </script> 112 | 113 | <style lang="scss" scoped></style> 114 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/.vuepress/components/createCodeSandBox.js: -------------------------------------------------------------------------------- ```javascript 1 | import { getParameters } from 'codesandbox/lib/api/define' 2 | 3 | const CodeSandBoxHTML = '<div id="app"></div>' 4 | const CodeSandBoxJS = ` 5 | import Vue from 'vue' 6 | import Antd from 'ant-design-vue'; 7 | import 'ant-design-vue/dist/antd.css'; 8 | import App from './App.vue' 9 | 10 | Vue.config.productionTip = false; 11 | 12 | Vue.use(Antd); 13 | 14 | 15 | new Vue({ 16 | render: h => h(App), 17 | }).$mount('#app')` 18 | 19 | const createForm = ({ method, action, data }) => { 20 | const form = document.createElement('form') // 构造 form 21 | form.style.display = 'none' // 设置为不显示 22 | form.target = '_blank' // 指向 iframe 23 | 24 | // 构造 formdata 25 | Object.keys(data).forEach((key) => { 26 | const input = document.createElement('input') // 创建 input 27 | 28 | input.name = key // 设置 name 29 | input.value = data[key] // 设置 value 30 | 31 | form.appendChild(input) 32 | }) 33 | 34 | form.method = method // 设置方法 35 | form.action = action // 设置地址 36 | 37 | document.body.appendChild(form) 38 | 39 | // 对该 form 执行提交 40 | form.submit() 41 | 42 | document.body.removeChild(form) 43 | } 44 | 45 | export function createCodeSandBox(codeStr) { 46 | const parameters = getParameters({ 47 | files: { 48 | 'sandbox.config.json': { 49 | content: { 50 | template: 'node', 51 | infiniteLoopProtection: true, 52 | hardReloadOnChange: false, 53 | view: 'browser', 54 | container: { 55 | port: 8080, 56 | node: '14', 57 | }, 58 | }, 59 | }, 60 | 'package.json': { 61 | content: { 62 | scripts: { 63 | serve: 'vue-cli-service serve', 64 | build: 'vue-cli-service build', 65 | lint: 'vue-cli-service lint', 66 | }, 67 | dependencies: { 68 | '@formily/core': 'latest', 69 | '@formily/vue': 'latest', 70 | 'core-js': '^3.6.5', 71 | 'ant-design-vue': 'latest', 72 | 'vue-demi': 'latest', 73 | vue: '^2.6.11', 74 | }, 75 | devDependencies: { 76 | '@vue/cli-plugin-babel': '~4.5.0', 77 | '@vue/cli-service': '~4.5.0', 78 | '@vue/composition-api': 'latest', 79 | 'vue-template-compiler': '^2.6.11', 80 | }, 81 | babel: { 82 | presets: ['@vue/cli-plugin-babel/preset'], 83 | }, 84 | vue: { 85 | devServer: { 86 | host: '0.0.0.0', 87 | disableHostCheck: true, // 必须 88 | }, 89 | }, 90 | }, 91 | }, 92 | 'src/App.vue': { 93 | content: codeStr, 94 | }, 95 | 'src/main.js': { 96 | content: CodeSandBoxJS, 97 | }, 98 | 'public/index.html': { 99 | content: CodeSandBoxHTML, 100 | }, 101 | }, 102 | }) 103 | 104 | createForm({ 105 | method: 'post', 106 | action: 'https://codesandbox.io/api/v1/sandboxes/define', 107 | data: { 108 | parameters, 109 | query: 'file=/src/App.vue', 110 | }, 111 | }) 112 | } 113 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Radio.md: -------------------------------------------------------------------------------- ```markdown 1 | # Radio 2 | 3 | > Single selection box 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Radio, 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 | Radio, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Number 26 | name="radio" 27 | title="single choice" 28 | enum={[ 29 | { 30 | label: 'Option 1', 31 | value: 1, 32 | }, 33 | { 34 | label: 'Option 2', 35 | value: 2, 36 | }, 37 | ]} 38 | x-decorator="FormItem" 39 | x-component="Radio.Group" 40 | /> 41 | </SchemaField> 42 | <FormButtonGroup> 43 | <Submit onSubmit={console.log}>Submit</Submit> 44 | </FormButtonGroup> 45 | </FormProvider> 46 | ) 47 | ``` 48 | 49 | ## JSON Schema case 50 | 51 | ```tsx 52 | import React from 'react' 53 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' 54 | import { createForm } from '@formily/core' 55 | import { FormProvider, createSchemaField } from '@formily/react' 56 | 57 | const SchemaField = createSchemaField({ 58 | components: { 59 | Radio, 60 | FormItem, 61 | }, 62 | }) 63 | 64 | const form = createForm() 65 | 66 | const schema = { 67 | type: 'object', 68 | properties: { 69 | radio: { 70 | type: 'number', 71 | title: 'Single selection', 72 | enum: [ 73 | { 74 | label: 'Option 1', 75 | value: 1, 76 | }, 77 | { 78 | label: 'Option 2', 79 | value: 2, 80 | }, 81 | ], 82 | 'x-decorator': 'FormItem', 83 | 'x-component': 'Radio.Group', 84 | }, 85 | }, 86 | } 87 | 88 | export default () => ( 89 | <FormProvider form={form}> 90 | <SchemaField schema={schema} /> 91 | <FormButtonGroup> 92 | <Submit onSubmit={console.log}>Submit</Submit> 93 | </FormButtonGroup> 94 | </FormProvider> 95 | ) 96 | ``` 97 | 98 | ## Pure JSX case 99 | 100 | ```tsx 101 | import React from 'react' 102 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' 103 | import { createForm } from '@formily/core' 104 | import { FormProvider, Field } from '@formily/react' 105 | 106 | const form = createForm() 107 | 108 | export default () => ( 109 | <FormProvider form={form}> 110 | <Field 111 | name="radio" 112 | title="single choice" 113 | dataSource={[ 114 | { 115 | label: 'Option 1', 116 | value: 1, 117 | }, 118 | { 119 | label: 'Option 2', 120 | value: 2, 121 | }, 122 | ]} 123 | decorator={FormItem} 124 | component={Radio.Group} 125 | /> 126 | <FormButtonGroup> 127 | <Submit onSubmit={console.log}>Submit</Submit> 128 | </FormButtonGroup> 129 | </FormProvider> 130 | ) 131 | ``` 132 | 133 | ## API 134 | 135 | Reference https://fusion.design/pc/component/basic/radio 136 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Transfer.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Transfer 2 | 3 | > 穿梭框 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | Transfer, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Array 26 | name="transfer" 27 | title="穿梭框" 28 | x-decorator="FormItem" 29 | x-component="Transfer" 30 | enum={[ 31 | { title: '选项1', key: 1 }, 32 | { title: '选项2', key: 2 }, 33 | ]} 34 | x-component-props={{ 35 | render: (item) => item.title, 36 | }} 37 | /> 38 | </SchemaField> 39 | <FormButtonGroup> 40 | <Submit onSubmit={console.log}>提交</Submit> 41 | </FormButtonGroup> 42 | </FormProvider> 43 | ) 44 | ``` 45 | 46 | ## JSON Schema 案例 47 | 48 | ```tsx 49 | import React from 'react' 50 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' 51 | import { createForm } from '@formily/core' 52 | import { FormProvider, createSchemaField } from '@formily/react' 53 | 54 | const SchemaField = createSchemaField({ 55 | components: { 56 | Transfer, 57 | FormItem, 58 | }, 59 | }) 60 | 61 | const form = createForm() 62 | 63 | const schema = { 64 | type: 'object', 65 | properties: { 66 | transfer: { 67 | type: 'array', 68 | title: '穿梭框', 69 | 'x-decorator': 'FormItem', 70 | 'x-component': 'Transfer', 71 | enum: [ 72 | { title: '选项1', key: 1 }, 73 | { title: '选项2', key: 2 }, 74 | ], 75 | 'x-component-props': { 76 | render: '{{renderTitle}}', 77 | }, 78 | }, 79 | }, 80 | } 81 | 82 | const renderTitle = (item) => item.title 83 | 84 | export default () => ( 85 | <FormProvider form={form}> 86 | <SchemaField schema={schema} scope={{ renderTitle }} /> 87 | <FormButtonGroup> 88 | <Submit onSubmit={console.log}>提交</Submit> 89 | </FormButtonGroup> 90 | </FormProvider> 91 | ) 92 | ``` 93 | 94 | ## 纯 JSX 案例 95 | 96 | ```tsx 97 | import React from 'react' 98 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' 99 | import { createForm } from '@formily/core' 100 | import { FormProvider, Field } from '@formily/react' 101 | 102 | const form = createForm() 103 | 104 | export default () => ( 105 | <FormProvider form={form}> 106 | <Field 107 | name="transfer" 108 | title="穿梭框" 109 | dataSource={[ 110 | { title: '选项1', key: 1 }, 111 | { title: '选项2', key: 2 }, 112 | ]} 113 | decorator={[FormItem]} 114 | component={[ 115 | Transfer, 116 | { 117 | render: (item) => item.title, 118 | }, 119 | ]} 120 | /> 121 | <FormButtonGroup> 122 | <Submit onSubmit={console.log}>提交</Submit> 123 | </FormButtonGroup> 124 | </FormProvider> 125 | ) 126 | ``` 127 | 128 | ## API 129 | 130 | 参考 https://ant.design/components/transfer-cn/ 131 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Password.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Password 2 | 3 | > 密码输入框 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | Password, 11 | FormItem, 12 | FormLayout, 13 | FormButtonGroup, 14 | Submit, 15 | } from '@formily/antd' 16 | import { createForm } from '@formily/core' 17 | import { FormProvider, createSchemaField } from '@formily/react' 18 | 19 | const SchemaField = createSchemaField({ 20 | components: { 21 | Password, 22 | FormItem, 23 | }, 24 | }) 25 | 26 | const form = createForm() 27 | 28 | export default () => ( 29 | <FormProvider form={form}> 30 | <FormLayout labelCol={6} wrapperCol={10}> 31 | <SchemaField> 32 | <SchemaField.String 33 | name="input" 34 | title="输入框" 35 | x-decorator="FormItem" 36 | x-component="Password" 37 | required 38 | x-component-props={{ 39 | checkStrength: true, 40 | }} 41 | /> 42 | </SchemaField> 43 | <FormButtonGroup.FormItem> 44 | <Submit onSubmit={console.log}>提交</Submit> 45 | </FormButtonGroup.FormItem> 46 | </FormLayout> 47 | </FormProvider> 48 | ) 49 | ``` 50 | 51 | ## JSON Schema 案例 52 | 53 | ```tsx 54 | import React from 'react' 55 | import { 56 | Password, 57 | FormItem, 58 | FormLayout, 59 | FormButtonGroup, 60 | Submit, 61 | } from '@formily/antd' 62 | import { createForm } from '@formily/core' 63 | import { FormProvider, createSchemaField } from '@formily/react' 64 | 65 | const SchemaField = createSchemaField({ 66 | components: { 67 | Password, 68 | FormItem, 69 | }, 70 | }) 71 | 72 | const form = createForm() 73 | 74 | const schema = { 75 | type: 'object', 76 | properties: { 77 | input: { 78 | type: 'string', 79 | title: '输入框', 80 | 'x-decorator': 'FormItem', 81 | 'x-component': 'Password', 82 | 'x-component-props': { 83 | checkStrength: true, 84 | }, 85 | }, 86 | }, 87 | } 88 | 89 | export default () => ( 90 | <FormProvider form={form}> 91 | <FormLayout labelCol={6} wrapperCol={10}> 92 | <SchemaField schema={schema} /> 93 | <FormButtonGroup.FormItem> 94 | <Submit onSubmit={console.log}>提交</Submit> 95 | </FormButtonGroup.FormItem> 96 | </FormLayout> 97 | </FormProvider> 98 | ) 99 | ``` 100 | 101 | ## 纯 JSX 案例 102 | 103 | ```tsx 104 | import React from 'react' 105 | import { 106 | Password, 107 | FormItem, 108 | FormLayout, 109 | FormButtonGroup, 110 | Submit, 111 | } from '@formily/antd' 112 | import { createForm } from '@formily/core' 113 | import { FormProvider, Field } from '@formily/react' 114 | const form = createForm() 115 | 116 | export default () => ( 117 | <FormProvider form={form}> 118 | <FormLayout labelCol={6} wrapperCol={10}> 119 | <Field 120 | name="input" 121 | title="输入框" 122 | required 123 | decorator={[FormItem]} 124 | component={[ 125 | Password, 126 | { 127 | checkStrength: true, 128 | }, 129 | ]} 130 | /> 131 | <FormButtonGroup.FormItem> 132 | <Submit onSubmit={console.log}>提交</Submit> 133 | </FormButtonGroup.FormItem> 134 | </FormLayout> 135 | </FormProvider> 136 | ) 137 | ``` 138 | 139 | ## API 140 | 141 | 参考 https://ant.design/components/input-cn/ 142 | ``` -------------------------------------------------------------------------------- /packages/core/src/effects/onFormEffects.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isFn } from '@formily/shared' 2 | import { autorun, batch } from '@formily/reactive' 3 | import { Form } from '../models' 4 | import { LifeCycleTypes } from '../types' 5 | import { createEffectHook } from '../shared/effective' 6 | 7 | function createFormEffect(type: LifeCycleTypes) { 8 | return createEffectHook( 9 | type, 10 | (form: Form) => (callback: (form: Form) => void) => { 11 | batch(() => { 12 | callback(form) 13 | }) 14 | } 15 | ) 16 | } 17 | 18 | export const onFormInit = createFormEffect(LifeCycleTypes.ON_FORM_INIT) 19 | export const onFormMount = createFormEffect(LifeCycleTypes.ON_FORM_MOUNT) 20 | export const onFormUnmount = createFormEffect(LifeCycleTypes.ON_FORM_UNMOUNT) 21 | export const onFormValuesChange = createFormEffect( 22 | LifeCycleTypes.ON_FORM_VALUES_CHANGE 23 | ) 24 | export const onFormInitialValuesChange = createFormEffect( 25 | LifeCycleTypes.ON_FORM_INITIAL_VALUES_CHANGE 26 | ) 27 | export const onFormInputChange = createFormEffect( 28 | LifeCycleTypes.ON_FORM_INPUT_CHANGE 29 | ) 30 | export const onFormSubmit = createFormEffect(LifeCycleTypes.ON_FORM_SUBMIT) 31 | export const onFormReset = createFormEffect(LifeCycleTypes.ON_FORM_RESET) 32 | export const onFormSubmitStart = createFormEffect( 33 | LifeCycleTypes.ON_FORM_SUBMIT_START 34 | ) 35 | export const onFormSubmitEnd = createFormEffect( 36 | LifeCycleTypes.ON_FORM_SUBMIT_END 37 | ) 38 | export const onFormSubmitSuccess = createFormEffect( 39 | LifeCycleTypes.ON_FORM_SUBMIT_SUCCESS 40 | ) 41 | export const onFormSubmitFailed = createFormEffect( 42 | LifeCycleTypes.ON_FORM_SUBMIT_FAILED 43 | ) 44 | export const onFormSubmitValidateStart = createFormEffect( 45 | LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_START 46 | ) 47 | export const onFormSubmitValidateSuccess = createFormEffect( 48 | LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_SUCCESS 49 | ) 50 | export const onFormSubmitValidateFailed = createFormEffect( 51 | LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_FAILED 52 | ) 53 | export const onFormSubmitValidateEnd = createFormEffect( 54 | LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_END 55 | ) 56 | export const onFormValidateStart = createFormEffect( 57 | LifeCycleTypes.ON_FORM_VALIDATE_START 58 | ) 59 | export const onFormValidateSuccess = createFormEffect( 60 | LifeCycleTypes.ON_FORM_VALIDATE_SUCCESS 61 | ) 62 | export const onFormValidateFailed = createFormEffect( 63 | LifeCycleTypes.ON_FORM_VALIDATE_FAILED 64 | ) 65 | export const onFormValidateEnd = createFormEffect( 66 | LifeCycleTypes.ON_FORM_VALIDATE_END 67 | ) 68 | export const onFormGraphChange = createFormEffect( 69 | LifeCycleTypes.ON_FORM_GRAPH_CHANGE 70 | ) 71 | export const onFormLoading = createFormEffect(LifeCycleTypes.ON_FORM_LOADING) 72 | export function onFormReact(callback?: (form: Form) => void) { 73 | let dispose = null 74 | onFormInit((form) => { 75 | dispose = autorun(() => { 76 | if (isFn(callback)) callback(form) 77 | }) 78 | }) 79 | onFormUnmount(() => { 80 | dispose() 81 | }) 82 | } 83 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Password.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Password 2 | 3 | > 密码输入框 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | Password, 11 | FormItem, 12 | FormButtonGroup, 13 | Submit, 14 | FormLayout, 15 | } from '@formily/next' 16 | import { createForm } from '@formily/core' 17 | import { FormProvider, createSchemaField } from '@formily/react' 18 | 19 | const SchemaField = createSchemaField({ 20 | components: { 21 | Password, 22 | FormItem, 23 | }, 24 | }) 25 | 26 | const form = createForm() 27 | 28 | export default () => ( 29 | <FormProvider form={form}> 30 | <FormLayout labelCol={6} wrapperCol={10}> 31 | <SchemaField> 32 | <SchemaField.String 33 | name="input" 34 | title="输入框" 35 | x-decorator="FormItem" 36 | x-component="Password" 37 | required 38 | x-component-props={{ 39 | checkStrength: true, 40 | }} 41 | /> 42 | </SchemaField> 43 | <FormButtonGroup.FormItem> 44 | <Submit onSubmit={console.log}>提交</Submit> 45 | </FormButtonGroup.FormItem> 46 | </FormLayout> 47 | </FormProvider> 48 | ) 49 | ``` 50 | 51 | ## JSON Schema 案例 52 | 53 | ```tsx 54 | import React from 'react' 55 | import { 56 | Password, 57 | FormItem, 58 | FormButtonGroup, 59 | Submit, 60 | FormLayout, 61 | } from '@formily/next' 62 | import { createForm } from '@formily/core' 63 | import { FormProvider, createSchemaField } from '@formily/react' 64 | const SchemaField = createSchemaField({ 65 | components: { 66 | Password, 67 | FormItem, 68 | }, 69 | }) 70 | 71 | const form = createForm() 72 | 73 | const schema = { 74 | type: 'object', 75 | properties: { 76 | input: { 77 | type: 'string', 78 | title: '输入框', 79 | 'x-decorator': 'FormItem', 80 | 'x-component': 'Password', 81 | 'x-component-props': { 82 | checkStrength: true, 83 | }, 84 | }, 85 | }, 86 | } 87 | 88 | export default () => ( 89 | <FormProvider form={form}> 90 | <FormLayout labelCol={6} wrapperCol={10}> 91 | <SchemaField schema={schema} /> 92 | <FormButtonGroup.FormItem> 93 | <Submit onSubmit={console.log}>提交</Submit> 94 | </FormButtonGroup.FormItem> 95 | </FormLayout> 96 | </FormProvider> 97 | ) 98 | ``` 99 | 100 | ## 纯 JSX 案例 101 | 102 | ```tsx 103 | import React from 'react' 104 | import { 105 | Password, 106 | FormItem, 107 | FormButtonGroup, 108 | Submit, 109 | FormLayout, 110 | } from '@formily/next' 111 | import { createForm } from '@formily/core' 112 | import { FormProvider, Field } from '@formily/react' 113 | const form = createForm() 114 | 115 | export default () => ( 116 | <FormProvider form={form}> 117 | <FormLayout labelCol={6} wrapperCol={10}> 118 | <Field 119 | name="input" 120 | title="输入框" 121 | required 122 | decorator={[FormItem]} 123 | component={[ 124 | Password, 125 | { 126 | checkStrength: true, 127 | }, 128 | ]} 129 | /> 130 | <FormButtonGroup.FormItem> 131 | <Submit onSubmit={console.log}>提交</Submit> 132 | </FormButtonGroup.FormItem> 133 | </FormLayout> 134 | </FormProvider> 135 | ) 136 | ``` 137 | 138 | ## API 139 | 140 | 参考 https://fusion.design/pc/component/basic/input 141 | ``` -------------------------------------------------------------------------------- /packages/next/__tests__/sideEffects.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import SideEffectsFlagPlugin from 'webpack/lib/optimize/SideEffectsFlagPlugin' 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { sideEffects, name: baseName } = require('../package.json') 4 | 5 | test('sideEffects should be controlled manually', () => { 6 | // if config in pkg.json changed, please ensure it is covered by jest. 7 | expect(sideEffects).toStrictEqual([ 8 | 'dist/*', 9 | 'esm/*.js', 10 | 'lib/*.js', 11 | 'src/*.ts', 12 | '*.scss', 13 | '**/*/style.js', 14 | ]) 15 | }) 16 | 17 | test('dist/*', () => { 18 | // eg. import "@formily/next/dist/next.css" 19 | expect( 20 | SideEffectsFlagPlugin.moduleHasSideEffects('dist/next.css', 'dist/*') 21 | ).toBeTruthy() 22 | expect( 23 | SideEffectsFlagPlugin.moduleHasSideEffects( 24 | 'dist/formily.next.umd.production.js', 25 | 'dist/*' 26 | ) 27 | ).toBeTruthy() 28 | expect( 29 | SideEffectsFlagPlugin.moduleHasSideEffects( 30 | 'dist/formily.next.umd.production.js', 31 | 'dist/*' 32 | ) 33 | ).toBeTruthy() 34 | }) 35 | 36 | test('esm/*.js & lib/*.js', () => { 37 | // expected to be truthy 38 | // eg. import Formilynext from "@formily/next/esm/index" 39 | expect( 40 | SideEffectsFlagPlugin.moduleHasSideEffects('esm/index.js', 'esm/*.js') 41 | ).toBeTruthy() 42 | expect( 43 | SideEffectsFlagPlugin.moduleHasSideEffects('lib/index.js', 'lib/*.js') 44 | ).toBeTruthy() 45 | 46 | // expected to be falsy 47 | // eg. import Input from "@formily/next/esm/input/index" => will be compiled to __webpack_require__("./node_modules/@formily/next/esm/input/index.js") 48 | // It should be removed by webpack if not used after imported. 49 | expect( 50 | SideEffectsFlagPlugin.moduleHasSideEffects('esm/input/index.js', 'esm/*.js') 51 | ).toBeFalsy() 52 | expect( 53 | SideEffectsFlagPlugin.moduleHasSideEffects( 54 | 'esm/array-base/index.js', 55 | 'esm/*.js' 56 | ) 57 | ).toBeFalsy() 58 | expect( 59 | SideEffectsFlagPlugin.moduleHasSideEffects('lib/input/index.js', 'lib/*.js') 60 | ).toBeFalsy() 61 | }) 62 | 63 | test('*.scss', () => { 64 | // eg. import "@formily/next/lib/input/style.scss" 65 | expect( 66 | SideEffectsFlagPlugin.moduleHasSideEffects( 67 | `${baseName}/lib/input/style.scss`, 68 | '*.scss' 69 | ) 70 | ).toBeTruthy() 71 | }) 72 | 73 | test('**/*/style.js', () => { 74 | // eg. import "@formily/next/lib/input/style" will be compiled to __webpack_require__("./node_modules/@formily/next/lib/input/style.js") 75 | // so we can match the `*style.js` only, not `**/*/style*` may be cause someting mismatch like `@formily/next/lib/xxx-style/index.js` 76 | const modulePathArr = [ 77 | 'lib/input/style.js', 78 | `${baseName}/lib/input/style.js`, 79 | `./node_modules/${baseName}/style.js`, 80 | ] 81 | 82 | modulePathArr.forEach((modulePath) => { 83 | const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects( 84 | modulePath, 85 | '**/*/style.js' 86 | ) 87 | expect(hasSideEffects).toBeTruthy() 88 | }) 89 | }) 90 | ``` -------------------------------------------------------------------------------- /packages/antd/__tests__/sideEffects.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import SideEffectsFlagPlugin from 'webpack/lib/optimize/SideEffectsFlagPlugin' 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { sideEffects, name: baseName } = require('../package.json') 4 | 5 | test('sideEffects should be controlled manually', () => { 6 | // if config in pkg.json changed, please ensure it is covered by jest. 7 | expect(sideEffects).toStrictEqual([ 8 | 'dist/*', 9 | 'esm/*.js', 10 | 'lib/*.js', 11 | 'src/*.ts', 12 | '*.less', 13 | '**/*/style.js', 14 | ]) 15 | }) 16 | 17 | test('dist/*', () => { 18 | // eg. import "@formily/antd/dist/antd.css" 19 | expect( 20 | SideEffectsFlagPlugin.moduleHasSideEffects('dist/antd.css', 'dist/*') 21 | ).toBeTruthy() 22 | expect( 23 | SideEffectsFlagPlugin.moduleHasSideEffects( 24 | 'dist/formily.antd.umd.development.js', 25 | 'dist/*' 26 | ) 27 | ).toBeTruthy() 28 | expect( 29 | SideEffectsFlagPlugin.moduleHasSideEffects( 30 | 'dist/formily.antd.umd.production.js', 31 | 'dist/*' 32 | ) 33 | ).toBeTruthy() 34 | }) 35 | 36 | test('esm/*.js & lib/*.js', () => { 37 | // expected to be truthy 38 | // eg. import FormilyAntd from "@formily/antd/esm/index" 39 | expect( 40 | SideEffectsFlagPlugin.moduleHasSideEffects('esm/index.js', 'esm/*.js') 41 | ).toBeTruthy() 42 | expect( 43 | SideEffectsFlagPlugin.moduleHasSideEffects('lib/index.js', 'lib/*.js') 44 | ).toBeTruthy() 45 | 46 | // expected to be falsy 47 | // eg. import Input from "@formily/antd/esm/input/index" => will be compiled to __webpack_require__("./node_modules/@formily/antd/esm/input/index.js") 48 | // It should be removed by webpack if not used after imported. 49 | expect( 50 | SideEffectsFlagPlugin.moduleHasSideEffects('esm/input/index.js', 'esm/*.js') 51 | ).toBeFalsy() 52 | expect( 53 | SideEffectsFlagPlugin.moduleHasSideEffects( 54 | 'esm/array-base/index.js', 55 | 'esm/*.js' 56 | ) 57 | ).toBeFalsy() 58 | expect( 59 | SideEffectsFlagPlugin.moduleHasSideEffects('lib/input/index.js', 'lib/*.js') 60 | ).toBeFalsy() 61 | }) 62 | 63 | test('*.less', () => { 64 | // eg. import "@formily/antd/lib/input/style.less" 65 | expect( 66 | SideEffectsFlagPlugin.moduleHasSideEffects( 67 | `${baseName}/lib/input/style.less`, 68 | '*.less' 69 | ) 70 | ).toBeTruthy() 71 | }) 72 | 73 | test('**/*/style.js', () => { 74 | // eg. import "@formily/antd/lib/input/style" will be compiled to __webpack_require__("./node_modules/@formily/antd/lib/input/style.js") 75 | // so we can match the `*style.js` only, not `**/*/style*` may be cause someting mismatch like `@formily/antd/lib/xxx-style/index.js` 76 | const modulePathArr = [ 77 | 'lib/input/style.js', 78 | `${baseName}/lib/input/style.js`, 79 | `./node_modules/${baseName}/style.js`, 80 | ] 81 | 82 | modulePathArr.forEach((modulePath) => { 83 | const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects( 84 | modulePath, 85 | '**/*/style.js' 86 | ) 87 | expect(hasSideEffects).toBeTruthy() 88 | }) 89 | }) 90 | ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/internals.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | getValuesFromEvent, 3 | matchFeedback, 4 | patchFieldStates, 5 | deserialize, 6 | isHTMLInputEvent, 7 | } from '../shared/internals' 8 | import { createForm } from '../' 9 | import { attach } from './shared' 10 | 11 | test('getValuesFromEvent', () => { 12 | expect(getValuesFromEvent([{ target: { value: 123 } }])).toEqual([123]) 13 | expect(getValuesFromEvent([{ target: { checked: true } }])).toEqual([true]) 14 | expect(getValuesFromEvent([{ target: {} }])).toEqual([undefined]) 15 | expect(getValuesFromEvent([{ target: null }])).toEqual([{ target: null }]) 16 | expect(getValuesFromEvent([123])).toEqual([123]) 17 | expect(getValuesFromEvent([null])).toEqual([null]) 18 | }) 19 | 20 | test('empty', () => { 21 | expect(matchFeedback()).toBeFalsy() 22 | }) 23 | 24 | test('patchFieldStates', () => { 25 | const fields = {} 26 | patchFieldStates(fields, [{ type: 'update', address: 'aaa', payload: null }]) 27 | patchFieldStates(fields, [ 28 | { type: 'update3' as any, address: 'aaa', payload: null }, 29 | ]) 30 | expect(fields).toEqual({}) 31 | }) 32 | 33 | test('patchFieldStates should be sequence', () => { 34 | const form = attach(createForm()) 35 | attach( 36 | form.createArrayField({ 37 | name: 'array', 38 | }) 39 | ) 40 | attach( 41 | form.createField({ 42 | name: 'input', 43 | basePath: 'array.0', 44 | }) 45 | ) 46 | attach( 47 | form.createField({ 48 | name: 'input', 49 | basePath: 'array.1', 50 | }) 51 | ) 52 | const before = Object.keys(form.fields) 53 | form.fields['array'].move(1, 0) 54 | const after = Object.keys(form.fields) 55 | expect(after).toEqual(before) 56 | 57 | const form2 = attach(createForm()) 58 | attach( 59 | form2.createField({ 60 | name: 'field1', 61 | title: 'Field 1', 62 | }) 63 | ) 64 | attach( 65 | form2.createField({ 66 | name: 'field2', 67 | title: 'Field 1', 68 | }) 69 | ) 70 | 71 | patchFieldStates(form2.fields, [ 72 | { 73 | type: 'update', 74 | address: 'field2', 75 | oldAddress: 'field1', 76 | payload: form2.field1, 77 | }, 78 | { 79 | type: 'update', 80 | address: 'field1', 81 | oldAddress: 'field2', 82 | payload: form2.field2, 83 | }, 84 | ]) 85 | 86 | expect(Object.keys(form2.fields)).toEqual(['field1', 'field2']) 87 | }) 88 | 89 | test('deserialize', () => { 90 | expect(deserialize(null, null)).toBeUndefined() 91 | expect( 92 | deserialize( 93 | {}, 94 | { 95 | parent: null, 96 | } 97 | ) 98 | ).toEqual({}) 99 | }) 100 | 101 | test('isHTMLInputEvent', () => { 102 | expect(isHTMLInputEvent({ target: { checked: true } })).toBeTruthy() 103 | expect(isHTMLInputEvent({ target: { value: 123 } })).toBeTruthy() 104 | expect( 105 | isHTMLInputEvent({ target: { tagName: 'INPUT', value: null } }) 106 | ).toBeTruthy() 107 | expect(isHTMLInputEvent({ target: { tagName: 'INPUT' } })).toBeFalsy() 108 | expect(isHTMLInputEvent({ target: { tagName: 'DIV' } })).toBeFalsy() 109 | expect(isHTMLInputEvent({ target: {}, stopPropagation() {} })).toBeFalsy() 110 | expect(isHTMLInputEvent({})).toBeFalsy() 111 | }) 112 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Password.md: -------------------------------------------------------------------------------- ```markdown 1 | # Password 2 | 3 | > Password input box 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | Password, 11 | FormItem, 12 | FormLayout, 13 | FormButtonGroup, 14 | Submit, 15 | } from '@formily/antd' 16 | import { createForm } from '@formily/core' 17 | import { FormProvider, createSchemaField } from '@formily/react' 18 | 19 | const SchemaField = createSchemaField({ 20 | components: { 21 | Password, 22 | FormItem, 23 | }, 24 | }) 25 | 26 | const form = createForm() 27 | 28 | export default () => ( 29 | <FormProvider form={form}> 30 | <FormLayout labelCol={6} wrapperCol={10}> 31 | <SchemaField> 32 | <SchemaField.String 33 | name="input" 34 | title="input box" 35 | x-decorator="FormItem" 36 | x-component="Password" 37 | required 38 | x-component-props={{ 39 | checkStrength: true, 40 | }} 41 | /> 42 | </SchemaField> 43 | <FormButtonGroup.FormItem> 44 | <Submit onSubmit={console.log}>Submit</Submit> 45 | </FormButtonGroup.FormItem> 46 | </FormLayout> 47 | </FormProvider> 48 | ) 49 | ``` 50 | 51 | ## JSON Schema case 52 | 53 | ```tsx 54 | import React from 'react' 55 | import { 56 | Password, 57 | FormItem, 58 | FormLayout, 59 | FormButtonGroup, 60 | Submit, 61 | } from '@formily/antd' 62 | import { createForm } from '@formily/core' 63 | import { FormProvider, createSchemaField } from '@formily/react' 64 | 65 | const SchemaField = createSchemaField({ 66 | components: { 67 | Password, 68 | FormItem, 69 | }, 70 | }) 71 | 72 | const form = createForm() 73 | 74 | const schema = { 75 | type: 'object', 76 | properties: { 77 | input: { 78 | type: 'string', 79 | title: 'input box', 80 | 'x-decorator': 'FormItem', 81 | 'x-component': 'Password', 82 | 'x-component-props': { 83 | checkStrength: true, 84 | }, 85 | }, 86 | }, 87 | } 88 | 89 | export default () => ( 90 | <FormProvider form={form}> 91 | <FormLayout labelCol={6} wrapperCol={10}> 92 | <SchemaField schema={schema} /> 93 | <FormButtonGroup.FormItem> 94 | <Submit onSubmit={console.log}>Submit</Submit> 95 | </FormButtonGroup.FormItem> 96 | </FormLayout> 97 | </FormProvider> 98 | ) 99 | ``` 100 | 101 | ## Pure JSX case 102 | 103 | ```tsx 104 | import React from 'react' 105 | import { 106 | Password, 107 | FormItem, 108 | FormLayout, 109 | FormButtonGroup, 110 | Submit, 111 | } from '@formily/antd' 112 | import { createForm } from '@formily/core' 113 | import { FormProvider, Field } from '@formily/react' 114 | const form = createForm() 115 | 116 | export default () => ( 117 | <FormProvider form={form}> 118 | <FormLayout labelCol={6} wrapperCol={10}> 119 | <Field 120 | name="input" 121 | title="input box" 122 | required 123 | decorator={[FormItem]} 124 | component={[ 125 | Password, 126 | { 127 | checkStrength: true, 128 | }, 129 | ]} 130 | /> 131 | <FormButtonGroup.FormItem> 132 | <Submit onSubmit={console.log}>Submit</Submit> 133 | </FormButtonGroup.FormItem> 134 | </FormLayout> 135 | </FormProvider> 136 | ) 137 | ``` 138 | 139 | ## API 140 | 141 | Reference https://ant.design/components/input-cn/ 142 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Transfer.md: -------------------------------------------------------------------------------- ```markdown 1 | # Transfer 2 | 3 | > Shuttle Box 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | Transfer, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Array 26 | name="transfer" 27 | title="shuttle box" 28 | x-decorator="FormItem" 29 | x-component="Transfer" 30 | enum={[ 31 | { title: 'Option 1', key: 1 }, 32 | { title: 'Option 2', key: 2 }, 33 | ]} 34 | x-component-props={{ 35 | render: (item) => item.title, 36 | }} 37 | /> 38 | </SchemaField> 39 | <FormButtonGroup> 40 | <Submit onSubmit={console.log}>Submit</Submit> 41 | </FormButtonGroup> 42 | </FormProvider> 43 | ) 44 | ``` 45 | 46 | ## JSON Schema case 47 | 48 | ```tsx 49 | import React from 'react' 50 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' 51 | import { createForm } from '@formily/core' 52 | import { FormProvider, createSchemaField } from '@formily/react' 53 | 54 | const SchemaField = createSchemaField({ 55 | components: { 56 | Transfer, 57 | FormItem, 58 | }, 59 | }) 60 | 61 | const form = createForm() 62 | 63 | const schema = { 64 | type: 'object', 65 | properties: { 66 | transfer: { 67 | type: 'array', 68 | title: 'shuttle box', 69 | 'x-decorator': 'FormItem', 70 | 'x-component': 'Transfer', 71 | enum: [ 72 | { title: 'Option 1', key: 1 }, 73 | { title: 'Option 2', key: 2 }, 74 | ], 75 | 'x-component-props': { 76 | render: '{{renderTitle}}', 77 | }, 78 | }, 79 | }, 80 | } 81 | 82 | const renderTitle = (item) => item.title 83 | 84 | export default () => ( 85 | <FormProvider form={form}> 86 | <SchemaField schema={schema} scope={{ renderTitle }} /> 87 | <FormButtonGroup> 88 | <Submit onSubmit={console.log}>Submit</Submit> 89 | </FormButtonGroup> 90 | </FormProvider> 91 | ) 92 | ``` 93 | 94 | ## Pure JSX case 95 | 96 | ```tsx 97 | import React from 'react' 98 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' 99 | import { createForm } from '@formily/core' 100 | import { FormProvider, Field } from '@formily/react' 101 | 102 | const form = createForm() 103 | 104 | export default () => ( 105 | <FormProvider form={form}> 106 | <Field 107 | name="transfer" 108 | title="shuttle box" 109 | dataSource={[ 110 | { title: 'Option 1', key: 1 }, 111 | { title: 'Option 2', key: 2 }, 112 | ]} 113 | decorator={[FormItem]} 114 | component={[ 115 | Transfer, 116 | { 117 | render: (item) => item.title, 118 | }, 119 | ]} 120 | /> 121 | <FormButtonGroup> 122 | <Submit onSubmit={console.log}>Submit</Submit> 123 | </FormButtonGroup> 124 | </FormProvider> 125 | ) 126 | ``` 127 | 128 | ## API 129 | 130 | Reference https://ant.design/components/transfer-cn/ 131 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/collections-weakmap.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { observable, autorun, raw } from '..' 2 | 3 | describe('WeakMap', () => { 4 | test('should be a proper JS WeakMap', () => { 5 | const weakMap = observable(new WeakMap()) 6 | expect(weakMap).toBeInstanceOf(WeakMap) 7 | expect(raw(weakMap)).toBeInstanceOf(WeakMap) 8 | }) 9 | 10 | test('should autorun mutations', () => { 11 | const handler = jest.fn() 12 | const key = {} 13 | const weakMap = observable(new WeakMap()) 14 | autorun(() => handler(weakMap.get(key))) 15 | 16 | expect(handler).toBeCalledTimes(1) 17 | expect(handler).lastCalledWith(undefined) 18 | weakMap.set(key, 'value') 19 | expect(handler).toBeCalledTimes(2) 20 | expect(handler).lastCalledWith('value') 21 | weakMap.set(key, 'value2') 22 | expect(handler).toBeCalledTimes(3) 23 | expect(handler).lastCalledWith('value2') 24 | weakMap.delete(key) 25 | expect(handler).toBeCalledTimes(4) 26 | expect(handler).lastCalledWith(undefined) 27 | }) 28 | 29 | test('should not autorun custom property mutations', () => { 30 | const handler = jest.fn() 31 | const weakMap = observable(new WeakMap()) 32 | autorun(() => handler(weakMap['customProp'])) 33 | 34 | expect(handler).toBeCalledTimes(1) 35 | expect(handler).lastCalledWith(undefined) 36 | weakMap['customProp'] = 'Hello World' 37 | expect(handler).toBeCalledTimes(1) 38 | }) 39 | 40 | test('should not autorun non value changing mutations', () => { 41 | const handler = jest.fn() 42 | const key = {} 43 | const weakMap = observable(new WeakMap()) 44 | autorun(() => handler(weakMap.get(key))) 45 | 46 | expect(handler).toBeCalledTimes(1) 47 | expect(handler).lastCalledWith(undefined) 48 | weakMap.set(key, 'value') 49 | expect(handler).toBeCalledTimes(2) 50 | expect(handler).lastCalledWith('value') 51 | weakMap.set(key, 'value') 52 | expect(handler).toBeCalledTimes(2) 53 | weakMap.delete(key) 54 | expect(handler).toBeCalledTimes(3) 55 | expect(handler).lastCalledWith(undefined) 56 | weakMap.delete(key) 57 | expect(handler).toBeCalledTimes(3) 58 | }) 59 | 60 | test('should not autorun raw data', () => { 61 | const handler = jest.fn() 62 | const key = {} 63 | const weakMap = observable(new WeakMap()) 64 | autorun(() => handler(raw(weakMap).get(key))) 65 | 66 | expect(handler).toBeCalledTimes(1) 67 | expect(handler).lastCalledWith(undefined) 68 | weakMap.set(key, 'Hello') 69 | expect(handler).toBeCalledTimes(1) 70 | weakMap.delete(key) 71 | expect(handler).toBeCalledTimes(1) 72 | }) 73 | 74 | test('should not be triggered by raw mutations', () => { 75 | const handler = jest.fn() 76 | const key = {} 77 | const weakMap = observable(new WeakMap()) 78 | autorun(() => handler(weakMap.get(key))) 79 | 80 | expect(handler).toBeCalledTimes(1) 81 | expect(handler).lastCalledWith(undefined) 82 | raw(weakMap).set(key, 'Hello') 83 | expect(handler).toBeCalledTimes(1) 84 | raw(weakMap).delete(key) 85 | expect(handler).toBeCalledTimes(1) 86 | }) 87 | }) 88 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Password.md: -------------------------------------------------------------------------------- ```markdown 1 | # Password 2 | 3 | > Password input box 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { 10 | Password, 11 | FormItem, 12 | FormButtonGroup, 13 | Submit, 14 | FormLayout, 15 | } from '@formily/next' 16 | import { createForm } from '@formily/core' 17 | import { FormProvider, createSchemaField } from '@formily/react' 18 | 19 | const SchemaField = createSchemaField({ 20 | components: { 21 | Password, 22 | FormItem, 23 | }, 24 | }) 25 | 26 | const form = createForm() 27 | 28 | export default () => ( 29 | <FormProvider form={form}> 30 | <FormLayout labelCol={6} wrapperCol={10}> 31 | <SchemaField> 32 | <SchemaField.String 33 | name="input" 34 | title="input box" 35 | x-decorator="FormItem" 36 | x-component="Password" 37 | required 38 | x-component-props={{ 39 | checkStrength: true, 40 | }} 41 | /> 42 | </SchemaField> 43 | <FormButtonGroup.FormItem> 44 | <Submit onSubmit={console.log}>Submit</Submit> 45 | </FormButtonGroup.FormItem> 46 | </FormLayout> 47 | </FormProvider> 48 | ) 49 | ``` 50 | 51 | ## JSON Schema case 52 | 53 | ```tsx 54 | import React from 'react' 55 | import { 56 | Password, 57 | FormItem, 58 | FormButtonGroup, 59 | Submit, 60 | FormLayout, 61 | } from '@formily/next' 62 | import { createForm } from '@formily/core' 63 | import { FormProvider, createSchemaField } from '@formily/react' 64 | const SchemaField = createSchemaField({ 65 | components: { 66 | Password, 67 | FormItem, 68 | }, 69 | }) 70 | 71 | const form = createForm() 72 | 73 | const schema = { 74 | type: 'object', 75 | properties: { 76 | input: { 77 | type: 'string', 78 | title: 'input box', 79 | 'x-decorator': 'FormItem', 80 | 'x-component': 'Password', 81 | 'x-component-props': { 82 | checkStrength: true, 83 | }, 84 | }, 85 | }, 86 | } 87 | 88 | export default () => ( 89 | <FormProvider form={form}> 90 | <FormLayout labelCol={6} wrapperCol={10}> 91 | <SchemaField schema={schema} /> 92 | <FormButtonGroup.FormItem> 93 | <Submit onSubmit={console.log}>Submit</Submit> 94 | </FormButtonGroup.FormItem> 95 | </FormLayout> 96 | </FormProvider> 97 | ) 98 | ``` 99 | 100 | ## Pure JSX case 101 | 102 | ```tsx 103 | import React from 'react' 104 | import { 105 | Password, 106 | FormItem, 107 | FormButtonGroup, 108 | Submit, 109 | FormLayout, 110 | } from '@formily/next' 111 | import { createForm } from '@formily/core' 112 | import { FormProvider, Field } from '@formily/react' 113 | const form = createForm() 114 | 115 | export default () => ( 116 | <FormProvider form={form}> 117 | <FormLayout labelCol={6} wrapperCol={10}> 118 | <Field 119 | name="input" 120 | title="input box" 121 | required 122 | decorator={[FormItem]} 123 | component={[ 124 | Password, 125 | { 126 | checkStrength: true, 127 | }, 128 | ]} 129 | /> 130 | <FormButtonGroup.FormItem> 131 | <Submit onSubmit={console.log}>Submit</Submit> 132 | </FormButtonGroup.FormItem> 133 | </FormLayout> 134 | </FormProvider> 135 | ) 136 | ``` 137 | 138 | ## API 139 | 140 | Reference https://fusion.design/pc/component/basic/input 141 | ``` -------------------------------------------------------------------------------- /packages/json-schema/src/__tests__/server-validate.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { createForm, Form } from '@formily/core' 2 | import { ISchema, Schema, SchemaKey } from '../' 3 | 4 | // 这是schema 5 | const schemaJson = { 6 | type: 'object', 7 | title: 'xxx配置', 8 | properties: { 9 | string: { 10 | type: 'string', 11 | title: 'string', 12 | maxLength: 5, 13 | required: true, 14 | }, 15 | number: { 16 | type: 'number', 17 | title: 'number', 18 | required: true, 19 | }, 20 | url: { 21 | type: 'string', 22 | title: 'url', 23 | format: 'url', 24 | }, 25 | arr: { 26 | type: 'array', 27 | title: 'array', 28 | maxItems: 2, 29 | required: true, 30 | items: { 31 | type: 'object', 32 | properties: { 33 | string: { 34 | type: 'string', 35 | title: 'string', 36 | required: true, 37 | }, 38 | }, 39 | }, 40 | }, 41 | }, 42 | } 43 | // 这是需要校验的数据 44 | const schemaData = { 45 | string: '123456', // 超过5个字 46 | // number 字段不存在 47 | url: 'xxxxx', // 不合法的url 48 | arr: [ 49 | { 50 | string: '1', 51 | }, 52 | { 53 | string: '2', 54 | }, 55 | { 56 | // 数组超出2项 57 | string: '', // 没有填 58 | }, 59 | ], 60 | } 61 | 62 | function recursiveField( 63 | form: Form, 64 | schema: ISchema, 65 | basePath?: string, 66 | name?: SchemaKey 67 | ) { 68 | const fieldSchema = new Schema(schema) 69 | const fieldProps = fieldSchema.toFieldProps() 70 | 71 | function recursiveProperties(propBasePath?: string) { 72 | fieldSchema.mapProperties((propSchema, propName) => { 73 | recursiveField(form, propSchema, propBasePath, propName) 74 | }) 75 | } 76 | 77 | if (name === undefined || name === null) { 78 | recursiveProperties(basePath) 79 | return 80 | } 81 | 82 | if (schema.type === 'object') { 83 | const field = form.createObjectField({ 84 | ...fieldProps, 85 | name, 86 | basePath, 87 | }) 88 | 89 | recursiveProperties(field.address.toString()) 90 | } else if (schema.type === 'array') { 91 | const field = form.createArrayField({ 92 | ...fieldProps, 93 | name, 94 | basePath, 95 | }) 96 | 97 | const fieldAddress = field.address.toString() 98 | const fieldValues = form.getValuesIn(fieldAddress) 99 | fieldValues.forEach((value: any, index: number) => { 100 | if (schema.items) { 101 | const itemsSchema = Array.isArray(schema.items) 102 | ? schema.items[index] || schema.items[0] 103 | : schema.items 104 | 105 | recursiveField(form, itemsSchema as ISchema, fieldAddress, index) 106 | } 107 | }) 108 | } else if (schema.type === 'void') { 109 | const field = form.createVoidField({ 110 | ...fieldProps, 111 | name, 112 | basePath, 113 | }) 114 | 115 | recursiveProperties(field.address.toString()) 116 | } else { 117 | form.createField({ 118 | ...fieldProps, 119 | name, 120 | basePath, 121 | }) 122 | } 123 | } 124 | test('server validate', async () => { 125 | const form = createForm({ 126 | values: schemaData, 127 | }) 128 | recursiveField(form, schemaJson) 129 | let errors: any[] 130 | try { 131 | await form.validate() 132 | } catch (e) { 133 | errors = e 134 | } 135 | expect(errors).not.toBeUndefined() 136 | }) 137 | ``` -------------------------------------------------------------------------------- /packages/core/src/shared/checkers.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isFn } from '@formily/shared' 2 | import { DataField, JSXComponent } from '..' 3 | import { 4 | Form, 5 | Field, 6 | ArrayField, 7 | ObjectField, 8 | VoidField, 9 | Query, 10 | } from '../models' 11 | import { 12 | IFormState, 13 | IFieldState, 14 | IVoidFieldState, 15 | GeneralField, 16 | IGeneralFieldState, 17 | } from '../types' 18 | 19 | export const isForm = (node: any): node is Form => { 20 | return node instanceof Form 21 | } 22 | 23 | export const isGeneralField = (node: any): node is GeneralField => { 24 | return node instanceof Field || node instanceof VoidField 25 | } 26 | 27 | export const isField = < 28 | Decorator extends JSXComponent = any, 29 | Component extends JSXComponent = any, 30 | TextType = any, 31 | ValueType = any 32 | >( 33 | node: any 34 | ): node is Field<Decorator, Component, TextType, ValueType> => { 35 | return node instanceof Field 36 | } 37 | 38 | export const isArrayField = < 39 | Decorator extends JSXComponent = any, 40 | Component extends JSXComponent = any 41 | >( 42 | node: any 43 | ): node is ArrayField<Decorator, Component> => { 44 | return node instanceof ArrayField 45 | } 46 | 47 | export const isObjectField = < 48 | Decorator extends JSXComponent = any, 49 | Component extends JSXComponent = any 50 | >( 51 | node: any 52 | ): node is ObjectField<Decorator, Component> => { 53 | return node instanceof ObjectField 54 | } 55 | 56 | export const isVoidField = <Decorator = any, Component = any, TextType = any>( 57 | node: any 58 | ): node is VoidField<Decorator, Component, TextType> => { 59 | return node instanceof VoidField 60 | } 61 | 62 | export const isFormState = <T extends Record<any, any> = any>( 63 | state: any 64 | ): state is IFormState<T> => { 65 | if (isFn(state?.initialize)) return false 66 | return state?.displayName === 'Form' 67 | } 68 | 69 | export const isFieldState = (state: any): state is IFieldState => { 70 | if (isFn(state?.initialize)) return false 71 | return state?.displayName === 'Field' 72 | } 73 | 74 | export const isGeneralFieldState = (node: any): node is IGeneralFieldState => { 75 | if (isFn(node?.initialize)) return false 76 | return node?.displayName?.indexOf('Field') > -1 77 | } 78 | 79 | export const isArrayFieldState = (state: any): state is IFieldState => { 80 | if (isFn(state?.initialize)) return false 81 | return state?.displayName === 'ArrayField' 82 | } 83 | 84 | export const isDataField = (node: any): node is DataField => { 85 | return isField(node) || isArrayField(node) || isObjectField(node) 86 | } 87 | 88 | export const isDataFieldState = (node: any) => { 89 | return ( 90 | isFieldState(node) || isObjectFieldState(node) || isArrayFieldState(node) 91 | ) 92 | } 93 | 94 | export const isObjectFieldState = (state: any): state is IFieldState => { 95 | if (isFn(state?.initialize)) return false 96 | return state?.displayName === 'ObjectField' 97 | } 98 | 99 | export const isVoidFieldState = (state: any): state is IVoidFieldState => { 100 | if (isFn(state?.initialize)) return false 101 | return state?.displayName === 'VoidField' 102 | } 103 | 104 | export const isQuery = (query: any): query is Query => { 105 | return query && query instanceof Query 106 | } 107 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-tab/json-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField :schema="schema" :scope="{ formTab }" /> 4 | <FormButtonGroup alignFormItem> 5 | <Button 6 | @click=" 7 | () => { 8 | form.query('tab3').take((field) => { 9 | field.visible = !field.visible 10 | }) 11 | } 12 | " 13 | > 14 | 显示/隐藏最后一个Tab 15 | </Button> 16 | <Button 17 | @click=" 18 | () => { 19 | formTab.setActiveKey('tab2') 20 | } 21 | " 22 | > 23 | 切换第二个Tab 24 | </Button> 25 | <Submit @submit="log">提交</Submit> 26 | </FormButtonGroup> 27 | </FormProvider> 28 | </template> 29 | 30 | <script> 31 | import { createForm } from '@formily/core' 32 | import { FormProvider, createSchemaField } from '@formily/vue' 33 | import { 34 | FormItem, 35 | FormTab, 36 | FormButtonGroup, 37 | Submit, 38 | Input, 39 | } from '@formily/element' 40 | import { Button } from 'element-ui' 41 | 42 | const { SchemaField } = createSchemaField({ 43 | components: { 44 | FormItem, 45 | FormTab, 46 | Input, 47 | }, 48 | }) 49 | 50 | const schema = { 51 | type: 'object', 52 | properties: { 53 | collapse: { 54 | type: 'void', 55 | 'x-component': 'FormTab', 56 | 'x-component-props': { 57 | formTab: '{{formTab}}', 58 | }, 59 | properties: { 60 | tab1: { 61 | type: 'void', 62 | 'x-component': 'FormTab.TabPane', 63 | 'x-component-props': { 64 | label: 'A1', 65 | }, 66 | properties: { 67 | aaa: { 68 | type: 'string', 69 | title: 'AAA', 70 | 'x-decorator': 'FormItem', 71 | required: true, 72 | 'x-component': 'Input', 73 | }, 74 | }, 75 | }, 76 | tab2: { 77 | type: 'void', 78 | 'x-component': 'FormTab.TabPane', 79 | 'x-component-props': { 80 | label: 'A2', 81 | }, 82 | properties: { 83 | bbb: { 84 | type: 'string', 85 | title: 'BBB', 86 | 'x-decorator': 'FormItem', 87 | required: true, 88 | 'x-component': 'Input', 89 | }, 90 | }, 91 | }, 92 | tab3: { 93 | type: 'void', 94 | 'x-component': 'FormTab.TabPane', 95 | 'x-component-props': { 96 | label: 'A3', 97 | }, 98 | properties: { 99 | ccc: { 100 | type: 'string', 101 | title: 'CCC', 102 | 'x-decorator': 'FormItem', 103 | required: true, 104 | 'x-component': 'Input', 105 | }, 106 | }, 107 | }, 108 | }, 109 | }, 110 | }, 111 | } 112 | 113 | export default { 114 | components: { 115 | FormProvider, 116 | FormButtonGroup, 117 | Button, 118 | Submit, 119 | SchemaField, 120 | }, 121 | 122 | data() { 123 | const form = createForm() 124 | const formTab = FormTab.createFormTab() 125 | 126 | return { 127 | schema, 128 | form, 129 | formTab, 130 | } 131 | }, 132 | methods: { 133 | log(values) { 134 | console.log(values) 135 | }, 136 | }, 137 | } 138 | </script> 139 | 140 | <style lang="scss" scoped></style> 141 | ``` -------------------------------------------------------------------------------- /packages/next/src/__builtins__/empty.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | 3 | export const Empty = () => { 4 | return ( 5 | <div className="next-empty"> 6 | <div className="next-empty-image"> 7 | <svg 8 | className="ant-empty-img-default" 9 | width="184" 10 | height="120" 11 | viewBox="0 0 184 152" 12 | xmlns="http://www.w3.org/2000/svg" 13 | > 14 | <g fill="none" fillRule="evenodd"> 15 | <g transform="translate(24 31.67)"> 16 | <ellipse 17 | className="ant-empty-img-default-ellipse" 18 | cx="67.797" 19 | cy="106.89" 20 | rx="67.797" 21 | ry="12.668" 22 | ></ellipse> 23 | <path 24 | className="ant-empty-img-default-path-1" 25 | d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z" 26 | ></path> 27 | <path 28 | className="ant-empty-img-default-path-2" 29 | d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z" 30 | transform="translate(13.56)" 31 | ></path> 32 | <path 33 | className="ant-empty-img-default-path-3" 34 | d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z" 35 | ></path> 36 | <path 37 | className="ant-empty-img-default-path-4" 38 | d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z" 39 | ></path> 40 | </g> 41 | <path 42 | className="ant-empty-img-default-path-5" 43 | d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z" 44 | ></path> 45 | <g 46 | className="ant-empty-img-default-g" 47 | transform="translate(149.65 15.383)" 48 | > 49 | <ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815"></ellipse> 50 | <path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"></path> 51 | </g> 52 | </g> 53 | </svg> 54 | </div> 55 | </div> 56 | ) 57 | } 58 | ``` -------------------------------------------------------------------------------- /packages/next/src/date-picker/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import moment from 'moment' 2 | import { connect, mapProps, mapReadPretty } from '@formily/react' 3 | import { DatePicker as NextDatePicker } from '@alifd/next' 4 | import { 5 | DatePickerProps as NextDatePickerProps, 6 | MonthPickerProps, 7 | YearPickerProps, 8 | RangePickerProps, 9 | } from '@alifd/next/lib/date-picker' 10 | import { PreviewText } from '../preview-text' 11 | import { 12 | formatMomentValue, 13 | momentable, 14 | mapSize, 15 | mapStatus, 16 | } from '../__builtins__' 17 | 18 | type DatePickerProps<PickerProps> = Exclude< 19 | PickerProps, 20 | 'value' | 'onChange' 21 | > & { 22 | value: string 23 | onChange: (value: string | string[]) => void 24 | } 25 | 26 | type ComposedDatePicker = React.FC< 27 | React.PropsWithChildren<NextDatePickerProps> 28 | > & { 29 | RangePicker?: React.FC<React.PropsWithChildren<RangePickerProps>> 30 | MonthPicker?: React.FC<React.PropsWithChildren<MonthPickerProps>> 31 | YearPicker?: React.FC<React.PropsWithChildren<YearPickerProps>> 32 | WeekPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> 33 | } 34 | 35 | const mapDateFormat = function (type?: 'month' | 'year' | 'week') { 36 | const getDefaultFormat = (props: DatePickerProps<NextDatePickerProps>) => { 37 | const _type = props['type'] || type 38 | if (_type === 'month') { 39 | return 'YYYY-MM' 40 | } else if (_type === 'year') { 41 | return 'YYYY' 42 | } else if (_type === 'week') { 43 | return 'YYYY-wo' 44 | } 45 | return 'YYYY-MM-DD' 46 | } 47 | 48 | return (props: any) => { 49 | const dateFormat = props['format'] || getDefaultFormat(props) 50 | 51 | let valueFormat = dateFormat 52 | if (props.showTime) { 53 | const timeFormat = props.showTime.format || 'HH:mm:ss' 54 | valueFormat = `${valueFormat} ${timeFormat}` 55 | } 56 | 57 | const onChange = props.onChange 58 | return { 59 | ...props, 60 | format: dateFormat, 61 | value: momentable( 62 | props.value, 63 | valueFormat === 'YYYY-wo' ? 'YYYY-w' : valueFormat 64 | ), 65 | onChange: (value: moment.Moment | moment.Moment[]) => { 66 | if (onChange) { 67 | onChange(formatMomentValue(value, valueFormat)) 68 | } 69 | }, 70 | } 71 | } 72 | } 73 | 74 | export const DatePicker: ComposedDatePicker = connect( 75 | NextDatePicker, 76 | mapProps(mapDateFormat(), mapSize, mapStatus), 77 | mapReadPretty(PreviewText.DatePicker) 78 | ) 79 | 80 | DatePicker.RangePicker = connect( 81 | NextDatePicker.RangePicker, 82 | mapProps(mapDateFormat(), mapSize, mapStatus), 83 | mapReadPretty(PreviewText.DateRangePicker) 84 | ) 85 | 86 | DatePicker.YearPicker = connect( 87 | NextDatePicker.YearPicker, 88 | mapProps(mapDateFormat('year'), mapSize, mapStatus), 89 | mapReadPretty(PreviewText.DatePicker) 90 | ) 91 | 92 | DatePicker.MonthPicker = connect( 93 | NextDatePicker.MonthPicker, 94 | mapProps(mapDateFormat('month'), mapSize, mapStatus), 95 | mapReadPretty(PreviewText.DatePicker) 96 | ) 97 | 98 | DatePicker.WeekPicker = connect( 99 | NextDatePicker.WeekPicker, 100 | mapProps(mapDateFormat('week'), mapSize, mapStatus), 101 | mapReadPretty(PreviewText.DatePicker) 102 | ) 103 | 104 | export default DatePicker 105 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-collapse/effects-markup-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaArrayField 5 | name="array" 6 | :maxItems="3" 7 | x-component="ArrayCollapse" 8 | x-decorator="FormItem" 9 | :x-component-props="{ 10 | title: '对象数组', 11 | }" 12 | > 13 | <SchemaObjectField 14 | x-component="ArrayCollapse.Item" 15 | x-decorator="FormItem" 16 | :x-component-props="{ 17 | title: '对象数组', 18 | }" 19 | > 20 | <SchemaVoidField x-component="ArrayCollapse.Index" /> 21 | <SchemaStringField 22 | name="aa" 23 | x-decorator="FormItem" 24 | title="AA" 25 | required 26 | description="AA输入123时隐藏BB" 27 | x-component="Input" 28 | /> 29 | <SchemaStringField 30 | name="bb" 31 | x-decorator="FormItem" 32 | title="BB" 33 | required 34 | x-component="Input" 35 | /> 36 | <SchemaStringField 37 | name="cc" 38 | x-decorator="FormItem" 39 | title="CC" 40 | required 41 | description="CC输入123时隐藏DD" 42 | x-component="Input" 43 | /> 44 | <SchemaStringField 45 | name="dd" 46 | x-decorator="FormItem" 47 | title="DD" 48 | required 49 | x-component="Input" 50 | /> 51 | <SchemaVoidField x-component="ArrayCollapse.Remove" /> 52 | <SchemaVoidField x-component="ArrayCollapse.MoveUp" /> 53 | <SchemaVoidField x-component="ArrayCollapse.MoveDown" /> 54 | </SchemaObjectField> 55 | <SchemaVoidField 56 | x-component="ArrayCollapse.Addition" 57 | title="添加条目" 58 | /> 59 | </SchemaArrayField> 60 | </SchemaField> 61 | <Submit @submit="log">提交</Submit> 62 | </FormProvider> 63 | </template> 64 | 65 | <script> 66 | import { createForm, onFieldChange, onFieldReact } from '@formily/core' 67 | import { FormProvider, createSchemaField } from '@formily/vue' 68 | import { 69 | FormItem, 70 | FormButtonGroup, 71 | Submit, 72 | Input, 73 | ArrayCollapse, 74 | } from '@formily/element' 75 | import { Button } from 'element-ui' 76 | 77 | const SchemaField = createSchemaField({ 78 | components: { 79 | FormItem, 80 | Input, 81 | ArrayCollapse, 82 | }, 83 | }) 84 | 85 | export default { 86 | components: { 87 | FormProvider, 88 | FormButtonGroup, 89 | Button, 90 | Submit, 91 | ...SchemaField, 92 | }, 93 | 94 | data() { 95 | const form = createForm({ 96 | effects: () => { 97 | //主动联动模式 98 | onFieldChange('array.*.aa', ['value'], (field, form) => { 99 | form.setFieldState(field.query('.bb'), (state) => { 100 | state.visible = field.value != '123' 101 | }) 102 | }) 103 | //被动联动模式 104 | onFieldReact('array.*.dd', (field) => { 105 | field.visible = field.query('.cc').get('value') != '123' 106 | }) 107 | }, 108 | }) 109 | 110 | return { 111 | form, 112 | } 113 | }, 114 | methods: { 115 | log(values) { 116 | console.log(values) 117 | }, 118 | }, 119 | } 120 | </script> 121 | 122 | <style lang="scss" scoped></style> 123 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-cards/effects-json-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField :schema="schema" /> 4 | <Submit @submit="log">提交</Submit> 5 | </FormProvider> 6 | </template> 7 | 8 | <script> 9 | import { createForm } from '@formily/core' 10 | import { FormProvider, createSchemaField } from '@formily/vue' 11 | import { 12 | FormItem, 13 | FormButtonGroup, 14 | Submit, 15 | Input, 16 | ArrayCards, 17 | } from '@formily/element' 18 | import { Button } from 'element-ui' 19 | 20 | const SchemaField = createSchemaField({ 21 | components: { 22 | FormItem, 23 | Input, 24 | ArrayCards, 25 | }, 26 | }) 27 | 28 | export default { 29 | components: { 30 | FormProvider, 31 | FormButtonGroup, 32 | Button, 33 | Submit, 34 | ...SchemaField, 35 | }, 36 | 37 | data() { 38 | const form = createForm() 39 | const schema = { 40 | type: 'object', 41 | properties: { 42 | array: { 43 | type: 'array', 44 | 'x-component': 'ArrayCards', 45 | maxItems: 3, 46 | title: '对象数组', 47 | items: { 48 | type: 'object', 49 | properties: { 50 | index: { 51 | type: 'void', 52 | 'x-component': 'ArrayCards.Index', 53 | }, 54 | aa: { 55 | type: 'string', 56 | 'x-decorator': 'FormItem', 57 | title: 'AA', 58 | required: true, 59 | 'x-component': 'Input', 60 | description: '输入123', 61 | }, 62 | bb: { 63 | type: 'string', 64 | title: 'BB', 65 | required: true, 66 | 'x-decorator': 'FormItem', 67 | 'x-component': 'Input', 68 | 'x-reactions': [ 69 | { 70 | dependencies: ['.aa'], 71 | when: "{{$deps[0] != '123'}}", 72 | fulfill: { 73 | schema: { 74 | title: 'BB', 75 | 'x-disabled': true, 76 | }, 77 | }, 78 | otherwise: { 79 | schema: { 80 | title: 'Changed', 81 | 'x-disabled': false, 82 | }, 83 | }, 84 | }, 85 | ], 86 | }, 87 | remove: { 88 | type: 'void', 89 | 'x-component': 'ArrayCards.Remove', 90 | }, 91 | moveUp: { 92 | type: 'void', 93 | 'x-component': 'ArrayCards.MoveUp', 94 | }, 95 | moveDown: { 96 | type: 'void', 97 | 'x-component': 'ArrayCards.MoveDown', 98 | }, 99 | }, 100 | }, 101 | properties: { 102 | addition: { 103 | type: 'void', 104 | title: '添加条目', 105 | 'x-component': 'ArrayCards.Addition', 106 | }, 107 | }, 108 | }, 109 | }, 110 | } 111 | 112 | return { 113 | form, 114 | schema, 115 | } 116 | }, 117 | methods: { 118 | log(values) { 119 | console.log(values) 120 | }, 121 | }, 122 | } 123 | </script> 124 | 125 | <style lang="scss" scoped></style> 126 | ``` -------------------------------------------------------------------------------- /packages/validator/src/registry.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | FormPath, 3 | each, 4 | lowerCase, 5 | globalThisPolyfill, 6 | merge as deepmerge, 7 | isFn, 8 | isStr, 9 | } from '@formily/shared' 10 | import { 11 | ValidatorFunctionResponse, 12 | ValidatorFunction, 13 | IRegistryFormats, 14 | IRegistryLocaleMessages, 15 | IRegistryLocales, 16 | IRegistryRules, 17 | } from './types' 18 | 19 | const getIn = FormPath.getIn 20 | 21 | const self: any = globalThisPolyfill 22 | 23 | const defaultLanguage = 'en' 24 | 25 | const getBrowserlanguage = () => { 26 | /* istanbul ignore next */ 27 | if (!self.navigator) { 28 | return defaultLanguage 29 | } 30 | return ( 31 | self.navigator.browserlanguage || self.navigator.language || defaultLanguage 32 | ) 33 | } 34 | 35 | const registry = { 36 | locales: { 37 | messages: {}, 38 | language: getBrowserlanguage(), 39 | }, 40 | formats: {}, 41 | rules: {}, 42 | template: null, 43 | } 44 | 45 | const getISOCode = (language: string) => { 46 | let isoCode = registry.locales.language 47 | if (registry.locales.messages[language]) { 48 | return language 49 | } 50 | const lang = lowerCase(language) 51 | each( 52 | registry.locales.messages, 53 | (messages: IRegistryLocaleMessages, key: string) => { 54 | const target = lowerCase(key) 55 | if (target.indexOf(lang) > -1 || lang.indexOf(target) > -1) { 56 | isoCode = key 57 | return false 58 | } 59 | } 60 | ) 61 | return isoCode 62 | } 63 | 64 | export const getValidateLocaleIOSCode = getISOCode 65 | 66 | export const setValidateLanguage = (lang: string) => { 67 | registry.locales.language = lang || defaultLanguage 68 | } 69 | 70 | export const getValidateLanguage = () => registry.locales.language 71 | 72 | export const getLocaleByPath = ( 73 | path: string, 74 | lang: string = registry.locales.language 75 | ) => getIn(registry.locales.messages, `${getISOCode(lang)}.${path}`) 76 | 77 | export const getValidateLocale = (path: string) => { 78 | const message = getLocaleByPath(path) 79 | return ( 80 | message || 81 | getLocaleByPath('pattern') || 82 | getLocaleByPath('pattern', defaultLanguage) 83 | ) 84 | } 85 | 86 | export const getValidateMessageTemplateEngine = () => registry.template 87 | 88 | export const getValidateFormats = (key?: string) => 89 | key ? registry.formats[key] : registry.formats 90 | 91 | export const getValidateRules = <T>( 92 | key?: T 93 | ): T extends string 94 | ? ValidatorFunction 95 | : { [key: string]: ValidatorFunction } => 96 | key ? registry.rules[key as any] : registry.rules 97 | 98 | export const registerValidateLocale = (locale: IRegistryLocales) => { 99 | registry.locales.messages = deepmerge(registry.locales.messages, locale) 100 | } 101 | 102 | export const registerValidateRules = (rules: IRegistryRules) => { 103 | each(rules, (rule, key) => { 104 | if (isFn(rule)) { 105 | registry.rules[key] = rule 106 | } 107 | }) 108 | } 109 | 110 | export const registerValidateFormats = (formats: IRegistryFormats) => { 111 | each(formats, (pattern, key) => { 112 | if (isStr(pattern) || pattern instanceof RegExp) { 113 | registry.formats[key] = new RegExp(pattern) 114 | } 115 | }) 116 | } 117 | 118 | export const registerValidateMessageTemplateEngine = ( 119 | template: (message: ValidatorFunctionResponse, context: any) => any 120 | ) => { 121 | registry.template = template 122 | } 123 | ``` -------------------------------------------------------------------------------- /packages/core/docs/api/entry/FormValidatorRegistry.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | order: 6 3 | --- 4 | 5 | # Form Validator Registry 6 | 7 | ## setValidateLanguage 8 | 9 | #### Description 10 | 11 | Set the built-in verification rule language 12 | 13 | #### Signature 14 | 15 | ```ts 16 | interface setValidateLanguage { 17 | (language: string): void 18 | } 19 | ``` 20 | 21 | #### Example 22 | 23 | ```ts 24 | import { setValidateLanguage } from '@formily/core' 25 | 26 | setValidateLanguage('en-US') 27 | 28 | setValidateLanguage('zh-CN') 29 | ``` 30 | 31 | ## registerValidateFormats 32 | 33 | #### Description 34 | 35 | Register general regular rules, the current built-in regular library reference: [formats.ts](https://github.com/alibaba/formily/blob/master/packages/validator/src/formats.ts) 36 | 37 | #### Signature 38 | 39 | ```ts 40 | interface registerValidateFormats { 41 | (rules: { [key: string]: RegExp }): void 42 | } 43 | ``` 44 | 45 | #### Example 46 | 47 | ```ts 48 | import { registerValidateFormats } from '@formily/core' 49 | 50 | registerValidateFormats({ 51 | integer: /^[+-]?\d+$/, 52 | }) 53 | ``` 54 | 55 | ## registerValidateLocale 56 | 57 | #### Description 58 | 59 | Global registration verification language package, the current built-in language package reference: [locale.ts](https://github.com/alibaba/formily/blob/master/packages/validator/src/locale.ts) 60 | 61 | #### Signature 62 | 63 | ```ts 64 | interface registerValidateLocale { 65 | (locales: { 66 | [key: string]: { 67 | key: string 68 | } 69 | }): void 70 | } 71 | ``` 72 | 73 | #### Example 74 | 75 | ```ts 76 | import { registerValidateLocale } from '@formily/core' 77 | 78 | registerValidateLocale({ 79 | ja: { 80 | required: 'このProjectは mustです', 81 | }, 82 | }) 83 | ``` 84 | 85 | ## registerValidateMessageTemplateEngine 86 | 87 | #### Description 88 | 89 | Globally register the verification message template engine. When we return the verification message in the validator, we can perform conversion based on the template engine syntax 90 | 91 | #### Signature 92 | 93 | ```ts 94 | interface registerValidateMessageTemplateEngine { 95 | (template: (message: ValidatorFunctionResponse, context: any) => any): void 96 | } 97 | ``` 98 | 99 | #### Example 100 | 101 | ```ts 102 | import { registerValidateMessageTemplateEngine } from '@formily/core' 103 | 104 | registerValidateMessageTemplateEngine((message, context) => { 105 | return message.replace(/\<\%\s*([\w.]+)\s*\%\>/g, (_, $0) => { 106 | return FormPath.getIn(context, $0) 107 | }) 108 | }) 109 | ``` 110 | 111 | ## registerValidateRules 112 | 113 | #### Description 114 | 115 | Register general verification rules, the current built-in rule library reference: [rules.ts](https://github.com/alibaba/formily/blob/master/packages/validator/src/rules.ts) 116 | 117 | #### Signature 118 | 119 | ```ts 120 | interface registerValidateRules { 121 | (rules: { 122 | [key: string]: ( 123 | value: any, 124 | rule: ValidatorRules, 125 | ctx: Context 126 | ) => ValidateResult | Promise<ValidateResult> 127 | }): void 128 | } 129 | ``` 130 | 131 | #### Example 132 | 133 | ```ts 134 | import { registerValidateRules } from '@formily/core' 135 | 136 | registerValidateRules({ 137 | custom(value) { 138 | return value > 100 ? 'error' : '' 139 | }, 140 | }) 141 | ``` 142 | 143 | ## getValidateLocaleIOSCode 144 | 145 | #### Description 146 | 147 | Get the built-in ISO Code 148 | 149 | #### Signature 150 | 151 | ```ts 152 | interface getValidateLocaleIOSCode { 153 | (language: string): string | undefined 154 | } 155 | ``` 156 | 157 | #### Example 158 | 159 | ```ts 160 | import { getValidateLocaleIOSCode } from '@formily/core' 161 | 162 | getValidateLocaleIOSCode('en') 163 | 164 | // ==> en_US 165 | ``` 166 | ``` -------------------------------------------------------------------------------- /packages/next/src/date-picker2/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import moment from 'moment' 2 | import { connect, mapProps, mapReadPretty } from '@formily/react' 3 | import { DatePicker2 as NextDatePicker } from '@alifd/next' 4 | import { 5 | DatePickerProps as NextDatePickerProps, 6 | RangePickerProps, 7 | } from '@alifd/next/lib/date-picker2' 8 | import { PreviewText } from '../preview-text' 9 | import { 10 | formatMomentValue, 11 | momentable, 12 | mapSize, 13 | mapStatus, 14 | } from '../__builtins__' 15 | 16 | type DatePickerProps<PickerProps> = Exclude< 17 | PickerProps, 18 | 'value' | 'onChange' 19 | > & { 20 | value: string 21 | onChange: (value: string | string[]) => void 22 | } 23 | 24 | type ComposedDatePicker = React.FC< 25 | React.PropsWithChildren<NextDatePickerProps> 26 | > & { 27 | RangePicker?: React.FC<React.PropsWithChildren<RangePickerProps>> 28 | MonthPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> 29 | YearPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> 30 | WeekPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> 31 | QuarterPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> 32 | } 33 | 34 | const mapDateFormat = function (type?: 'month' | 'year' | 'week' | 'quarter') { 35 | const getDefaultFormat = (props: DatePickerProps<NextDatePickerProps>) => { 36 | const _type = props['type'] || type 37 | if (_type === 'month') { 38 | return 'YYYY-MM' 39 | } else if (_type === 'quarter') { 40 | return 'YYYY-\\QQ' 41 | } else if (_type === 'year') { 42 | return 'YYYY' 43 | } else if (_type === 'week') { 44 | return 'YYYY-wo' 45 | } 46 | return props['showTime'] ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD' 47 | } 48 | 49 | return (props: any) => { 50 | const format = props['format'] || getDefaultFormat(props) 51 | const onChange = props.onChange 52 | return { 53 | ...props, 54 | format, 55 | value: momentable(props.value, format === 'YYYY-wo' ? 'YYYY-w' : format), 56 | onChange: (value: moment.Moment | moment.Moment[]) => { 57 | if (onChange) { 58 | onChange(formatMomentValue(value, format)) 59 | } 60 | }, 61 | } 62 | } 63 | } 64 | 65 | export const DatePicker2: ComposedDatePicker = connect( 66 | NextDatePicker, 67 | mapProps(mapDateFormat(), mapSize, mapStatus), 68 | mapReadPretty(PreviewText.DatePicker) 69 | ) 70 | 71 | DatePicker2.RangePicker = connect( 72 | NextDatePicker.RangePicker, 73 | mapProps(mapDateFormat(), mapSize, mapStatus), 74 | mapReadPretty(PreviewText.DateRangePicker) 75 | ) 76 | 77 | DatePicker2.YearPicker = connect( 78 | NextDatePicker.YearPicker, 79 | mapProps(mapDateFormat('year'), mapSize, mapStatus), 80 | mapReadPretty(PreviewText.DatePicker) 81 | ) 82 | 83 | DatePicker2.MonthPicker = connect( 84 | NextDatePicker.MonthPicker, 85 | mapProps(mapDateFormat('month'), mapSize, mapStatus), 86 | mapReadPretty(PreviewText.DatePicker) 87 | ) 88 | 89 | DatePicker2.WeekPicker = connect( 90 | NextDatePicker.WeekPicker, 91 | mapProps(mapDateFormat('week'), mapSize, mapStatus), 92 | mapReadPretty(PreviewText.DatePicker) 93 | ) 94 | 95 | DatePicker2.QuarterPicker = connect( 96 | NextDatePicker.QuarterPicker, 97 | mapProps(mapDateFormat('quarter'), mapSize, mapStatus), 98 | mapReadPretty(PreviewText.DatePicker) 99 | ) 100 | 101 | export default DatePicker2 102 | ```