This is page 7 of 35. Use http://codebase.md/alibaba/formily?page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/next/__tests__/moment.spec.ts: -------------------------------------------------------------------------------- ```typescript import { momentable, formatMomentValue } from '../src/__builtins__/moment' import moment from 'moment' test('momentable is usable', () => { expect(moment.isMoment(momentable('2021-09-08'))).toBe(true) expect( momentable(['2021-09-08', '2021-12-29']).every((item) => moment.isMoment(item) ) ).toBe(true) expect(momentable(0)).toBe(0) }) test('formatMomentValue is usable', () => { expect(formatMomentValue('', 'YYYY-MM-DD', '~')).toBe('~') expect(formatMomentValue('2021-12-22 15:47:00', 'YYYY-MM-DD')).toBe( '2021-12-22' ) expect(formatMomentValue('2021-12-23 15:47:00', undefined)).toBe( '2021-12-23 15:47:00' ) expect(formatMomentValue('2021-12-21 15:47:00', (date: string) => date)).toBe( '2021-12-21 15:47:00' ) expect(formatMomentValue('12:11', 'HH:mm')).toBe('12:11') expect(formatMomentValue('12:11:11', 'HH:mm:ss')).toBe('12:11:11') expect(formatMomentValue(['12:11'], ['HH:mm'])).toEqual(['12:11']) expect(formatMomentValue(['12:11:11'], ['HH:mm:ss'])).toEqual(['12:11:11']) expect(formatMomentValue(1663155911097, 'YYYY-MM-DD HH:mm:ss')).toBe( moment(1663155911097).format('YYYY-MM-DD HH:mm:ss') ) expect(formatMomentValue([1663155911097], ['YYYY-MM-DD HH:mm:ss'])).toEqual([ moment(1663155911097).format('YYYY-MM-DD HH:mm:ss'), ]) expect( formatMomentValue('2022-09-15T09:56:26.000Z', 'YYYY-MM-DD HH:mm:ss') ).toBe(moment('2022-09-15T09:56:26.000Z').format('YYYY-MM-DD HH:mm:ss')) expect( formatMomentValue(['2022-09-15T09:56:26.000Z'], ['YYYY-MM-DD HH:mm:ss']) ).toEqual([moment('2022-09-15T09:56:26.000Z').format('YYYY-MM-DD HH:mm:ss')]) expect(formatMomentValue('2022-09-15 09:56:26', 'HH:mm:ss')).toBe('09:56:26') expect(formatMomentValue(['2022-09-15 09:56:26'], ['HH:mm:ss'])).toEqual([ '09:56:26', ]) expect( formatMomentValue( ['2021-12-21 15:47:00', '2021-12-29 15:47:00'], 'YYYY-MM-DD' ) ).toEqual(['2021-12-21', '2021-12-29']) expect( formatMomentValue( ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], (date: string) => date ) ).toEqual(['2021-12-21 16:47:00', '2021-12-29 18:47:00']) expect( formatMomentValue( ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], ['YYYY-MM-DD', (date: string) => date] ) ).toEqual(['2021-12-21', '2021-12-29 18:47:00']) expect( formatMomentValue( ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], ['YYYY-MM-DD', undefined] ) ).toEqual(['2021-12-21', '2021-12-29 18:47:00']) }) ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Radio.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Radio > 单选框 ## Markup Schema 案例 ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Number name="radio" title="单选" enum={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} x-decorator="FormItem" x-component="Radio.Group" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { radio: { type: 'number', title: '单选', enum: [ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="radio" title="单选" dataSource={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} decorator={FormItem} component={Radio.Group} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://ant.design/components/radio-cn/ ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-tab/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaVoidField type="void" x-component="FormTab" :x-component-props="{ formTab }" > <SchemaVoidField type="void" name="tab1" x-component="FormTab.TabPane" :x-component-props="{ label: 'A1' }" > <SchemaStringField name="aaa" x-decorator="FormItem" title="AAA" required x-component="Input" /> </SchemaVoidField> <SchemaVoidField name="tab2" x-component="FormTab.TabPane" :x-component-props="{ label: 'A2' }" > <SchemaStringField name="bbb" x-decorator="FormItem" title="BBB" required x-component="Input" /> </SchemaVoidField> <SchemaVoidField name="tab3" x-component="FormTab.TabPane" :x-component-props="{ label: 'A3' }" > <SchemaStringField name="ccc" x-decorator="FormItem" title="CCC" required x-component="Input" /> </SchemaVoidField> </SchemaVoidField> </SchemaField> <FormButtonGroup alignFormItem> <Button @click=" () => { form.query('tab3').take((field) => { field.visible = !field.visible }) } " > 显示/隐藏最后一个Tab </Button> <Button @click=" () => { formTab.setActiveKey('tab2') } " > 切换第二个Tab </Button> <Submit @submit="log">提交</Submit> </FormButtonGroup> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormItem, FormTab, FormButtonGroup, Submit, Input, } from '@formily/element' import { Button } from 'element-ui' const SchemaField = createSchemaField({ components: { FormItem, FormTab, Input, }, }) export default { components: { FormProvider, FormButtonGroup, Button, Submit, ...SchemaField, }, data() { const form = createForm() const formTab = FormTab.createFormTab() return { form, formTab, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Radio.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Radio > 单选框 ## Markup Schema 案例 ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Number name="radio" title="单选" enum={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} x-decorator="FormItem" x-component="Radio.Group" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { radio: { type: 'number', title: '单选', enum: [ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="radio" title="单选" dataSource={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} decorator={FormItem} component={Radio.Group} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://fusion.design/pc/component/basic/radio ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/react/observer.md: -------------------------------------------------------------------------------- ```markdown # observer ## observer ### Description In React, turn Function Component into Reaction, and dependencies will be collected every time the view is re-rendered, and dependency updates will be automatically re-rendered <Alert> Note: Only Function Component is supported </Alert> ### Signature ```ts interface IObserverOptions { forwardRef?: boolean //Whether to pass the reference transparently scheduler?: (updater: () => void) => void //The scheduler, you can manually control the timing of the update displayName?: string //displayName of the packaged component } interface observer<T extends React.FC> { (component: T, options?: IObserverOptions): T } ``` ### Example ```tsx /** * defaultShowCode: true */ import React from 'react' import { observable } from '@formily/reactive' import { observer } from '@formily/reactive-react' const obs = observable({ value: 'Hello world', }) export default observer(() => { return ( <div> <div> <input style={{ height: 28, padding: '0 8px', border: '2px solid #888', borderRadius: 3, }} value={obs.value} onChange={(e) => { obs.value = e.target.value }} /> </div> <div>{obs.value}</div> </div> ) }) ``` ## Observer ### Description Similar to Vue's responsive slot, it receives a Function RenderProps, as long as any responsive data consumed inside the Function, it will be automatically re-rendered as the data changes, and it is easier to achieve local accurate rendering ### Signature ```ts interface IObserverProps { children?: () => React.ReactElement } type Observer = React.FC<React.PropsWithChildren<IObserverProps>> ``` ### Example ```tsx /** * defaultShowCode: true */ import React from 'react' import { observable } from '@formily/reactive' import { Observer } from '@formily/reactive-react' const obs = observable({ value: 'Hello world', }) export default () => { return ( <div> <div> <Observer> {() => ( <input style={{ height: 28, padding: '0 8px', border: '2px solid #888', borderRadius: 3, }} value={obs.value} onChange={(e) => { obs.value = e.target.value }} /> )} </Observer> </div> <Observer>{() => <div>{obs.value}</div>}</Observer> </div> ) } ``` ``` -------------------------------------------------------------------------------- /packages/vue/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/vue", "version": "2.3.7", "license": "MIT", "main": "lib", "module": "esm", "umd:main": "dist/formily.vue.umd.production.js", "unpkg": "dist/formily.vue.umd.production.js", "jsdelivr": "dist/formily.vue.umd.production.js", "jsnext:main": "esm", "types": "type-artefacts/cur/index.d.ts", "engines": { "npm": ">=3.0.0" }, "scripts": { "postinstall": "node ./scripts/postinstall.js", "start": "vuepress dev docs", "build": "rimraf -rf lib esm dist type-artefacts && npm run build:cjs && npm run build:esm && npm run build:umd && npm run build:types", "build:cjs": "tsc --project tsconfig.build.json", "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", "build:umd": "rollup --config", "build:types": "npm run build:types-vue2 && npm run build:types-vue3 && rimraf type-artefacts/**/*.js type-artefacts/**/**/*.js", "build:types-vue2": "tsc --project tsconfig.types.json --outDir type-artefacts/v2", "build:types-vue3": "npx vue-demi-switch 3 vue3 && tsc --project tsconfig.types.json --outDir type-artefacts/v3 && tsc --project tsconfig.types.json --outDir type-artefacts/cur && npx vue-demi-switch 2", "build:docs": "vuepress build docs" }, "bin": { "formily-vue-fix": "bin/formily-vue-fix.js", "formily-vue-switch": "bin/formily-vue-switch.js" }, "devDependencies": { "@ant-design/icons": "^2.1.1", "@ant-design/icons-vue": "^2.0.0", "@vue/composition-api": "^1.0.0-rc.7", "@vuepress-dumi/vuepress-plugin-dumi-previewer": "0.3.3", "@vuepress-dumi/vuepress-theme-dumi": "0.3.3", "@vuepress/plugin-back-to-top": "^1.8.2", "@vuepress/plugin-medium-zoom": "^1.8.2", "ant-design-vue": "^1.7.3", "codesandbox": "^2.2.3", "core-js": "^2.4.0", "vue": "^2.6.12", "vue3": "npm:vue@3", "vuepress": "^1.8.2", "vuepress-plugin-typescript": "^0.3.1" }, "dependencies": { "@formily/core": "2.3.7", "@formily/json-schema": "2.3.7", "@formily/reactive": "2.3.7", "@formily/reactive-vue": "2.3.7", "@formily/shared": "2.3.7", "@formily/validator": "2.3.7", "fs-extra": "^10.0.0", "vue-demi": ">=0.13.6", "vue-frag": "^1.1.4" }, "peerDependencies": { "@vue/composition-api": "^1.0.0-beta.1", "vue": "^2.6.0 || >=3.0.0-rc.0" }, "peerDependenciesMeta": { "@vue/composition-api": { "optional": true } }, "publishConfig": { "access": "public" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" } ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Transfer.md: -------------------------------------------------------------------------------- ```markdown # Transfer > Shuttle Box ## Markup Schema example ```tsx import React from 'react' import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Transfer, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="transfer" title="shuttle box" x-decorator="FormItem" x-component="Transfer" enum={[ { label: 'Option 1', value: 'aaa' }, { label: 'Option 2', value: 'bbb' }, ]} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Transfer, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { transfer: { type: 'array', title: 'shuttle box', 'x-decorator': 'FormItem', 'x-component': 'Transfer', enum: [ { label: 'Option 1', value: 'aaa' }, { label: 'Option 2', value: 'bbb' }, ], }, }, } const renderTitle = (item) => item.title export default () => ( <FormProvider form={form}> <SchemaField schema={schema} scope={{ renderTitle }} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="transfer" title="shuttle box" dataSource={[ { label: 'Option 1', value: 'aaa' }, { label: 'Option 2', value: 'bbb' }, ]} decorator={[FormItem]} component={[Transfer]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://fusion.design/pc/component/basic/transfer ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/TimePicker.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # TimePicker > 时间选择器 ## Markup Schema 案例 ```tsx import React from 'react' import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="time" title="时间" required x-decorator="FormItem" x-component="TimePicker" /> <SchemaField.String name="[startTime,endTime]" title="时间范围" x-decorator="FormItem" x-component="TimePicker.RangePicker" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { time: { title: '时间', 'x-decorator': 'FormItem', 'x-component': 'TimePicker', type: 'string', }, '[startTime,endTime]': { title: '时间范围', 'x-decorator': 'FormItem', 'x-component': 'TimePicker.RangePicker', type: 'string', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="time" title="时间" decorator={[FormItem]} component={[TimePicker]} /> <Field name="[startTime,endTime]" title="时间范围" decorator={[FormItem]} component={[TimePicker.RangePicker]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://ant.design/components/time-picker-cn/ ``` -------------------------------------------------------------------------------- /packages/element/src/form-layout/useResponsiveFormLayout.ts: -------------------------------------------------------------------------------- ```typescript import { isArr, isValid } from '@formily/shared' import { onMounted, Ref, ref } from 'vue-demi' interface IProps { breakpoints?: number[] layout?: | 'vertical' | 'horizontal' | 'inline' | ('vertical' | 'horizontal' | 'inline')[] labelCol?: number | number[] wrapperCol?: number | number[] labelAlign?: 'right' | 'left' | ('right' | 'left')[] wrapperAlign?: 'right' | 'left' | ('right' | 'left')[] [props: string]: any } interface ICalcBreakpointIndex { (originalBreakpoints: number[], width: number): number } interface ICalculateProps { (target: Element, props: IProps): IProps } interface IUseResponsiveFormLayout { (props: IProps, root: Ref<Element>): { props: Ref<IProps> } } const calcBreakpointIndex: ICalcBreakpointIndex = (breakpoints, width) => { for (let i = 0; i < breakpoints.length; i++) { if (width <= breakpoints[i]) { return i } } } const calcFactor = <T>(value: T | T[], breakpointIndex: number): T => { if (Array.isArray(value)) { if (breakpointIndex === -1) return value[0] return value[breakpointIndex] ?? value[value.length - 1] } else { return value } } const factor = <T>(value: T | T[], breakpointIndex: number): T => isValid(value) ? calcFactor(value as any, breakpointIndex) : value const calculateProps: ICalculateProps = (target, props) => { const { clientWidth } = target const { breakpoints, layout, labelAlign, wrapperAlign, labelCol, wrapperCol, ...otherProps } = props const breakpointIndex = calcBreakpointIndex(breakpoints, clientWidth) return { layout: factor(layout, breakpointIndex), labelAlign: factor(labelAlign, breakpointIndex), wrapperAlign: factor(wrapperAlign, breakpointIndex), labelCol: factor(labelCol, breakpointIndex), wrapperCol: factor(wrapperCol, breakpointIndex), ...otherProps, } } export const useResponsiveFormLayout: IUseResponsiveFormLayout = ( props, root ) => { const { breakpoints } = props if (!isArr(breakpoints)) { return { props: ref(props) } } const layoutProps = ref<IProps>(props) const updateUI = () => { if (root.value) { layoutProps.value = calculateProps(root.value, props) } } onMounted(() => { const observer = () => { updateUI() } const resizeObserver = new ResizeObserver(observer) if (root.value) { resizeObserver.observe(root.value) } updateUI() return () => { resizeObserver.disconnect() } }) return { props: layoutProps, } } ``` -------------------------------------------------------------------------------- /packages/next/docs/components/TimePicker2.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # TimePicker2 > 时间选择器 ## Markup Schema 案例 ```tsx import React from 'react' import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker2, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="time" title="时间" x-decorator="FormItem" x-component="TimePicker2" /> <SchemaField.String name="[startTime,endTime]" title="时间范围" x-decorator="FormItem" x-component="TimePicker2.RangePicker" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker2, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { time: { title: '时间', 'x-decorator': 'FormItem', 'x-component': 'TimePicker2', type: 'string', }, '[startTime,endTime]': { title: '时间范围', 'x-decorator': 'FormItem', 'x-component': 'TimePicker2.RangePicker', type: 'string', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="time" title="时间" decorator={[FormItem]} component={[TimePicker2]} /> <Field name="[startTime,endTime]" title="时间范围" decorator={[FormItem]} component={[TimePicker2.RangePicker]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://fusion.design/pc/component/basic/time-picker2 ``` -------------------------------------------------------------------------------- /packages/benchmark/webpack.base.ts: -------------------------------------------------------------------------------- ```typescript import path from 'path' import fs from 'fs-extra' import { GlobSync } from 'glob' import MiniCssExtractPlugin from 'mini-css-extract-plugin' import autoprefixer from 'autoprefixer' //import { getThemeVariables } from 'antd/dist/theme' const getWorkspaceAlias = () => { const basePath = path.resolve(__dirname, '../../') const pkg = fs.readJSONSync(path.resolve(basePath, 'package.json')) || {} const results = {} const workspaces = pkg.workspaces if (Array.isArray(workspaces)) { workspaces.forEach((pattern) => { const { found } = new GlobSync(pattern, { cwd: basePath }) found.forEach((name) => { const pkg = fs.readJSONSync( path.resolve(basePath, name, './package.json') ) results[pkg.name] = path.resolve(basePath, name, './src') }) }) } return results } export default { mode: 'development', devtool: 'inline-source-map', // 嵌入到源文件中 stats: { entrypoints: false, children: false, }, entry: { index: path.resolve(__dirname, './src/index'), }, output: { path: path.resolve(__dirname, 'build'), filename: '[name].[hash].bundle.js', }, resolve: { modules: ['node_modules'], extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], alias: getWorkspaceAlias(), }, externals: { react: 'React', 'react-dom': 'ReactDOM', moment: 'moment', antd: 'antd', }, module: { rules: [ { test: /\.tsx?$/, use: [ { loader: require.resolve('ts-loader'), options: { transpileOnly: true, }, }, ], }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, require.resolve('css-loader')], }, { test: /\.less$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader' }, { loader: 'postcss-loader', options: { plugins: () => autoprefixer(), }, }, { loader: 'less-loader', options: { // modifyVars: getThemeVariables({ // dark: true, // 开启暗黑模式 // }), javascriptEnabled: true, }, }, ], }, { test: /\.(woff|woff2|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, use: ['url-loader'], }, { test: /\.html?$/, loader: require.resolve('file-loader'), options: { name: '[name].[ext]', }, }, ], }, } ``` -------------------------------------------------------------------------------- /packages/react/src/shared/connect.ts: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { isFn, isStr, FormPath, each, isValid } from '@formily/shared' import { isVoidField } from '@formily/core' import { observer, Observer } from '@formily/reactive-react' import { JSXComponent, IComponentMapper, IStateMapper } from '../types' import { useField } from '../hooks' import hoistNonReactStatics from 'hoist-non-react-statics' export function mapProps<T extends JSXComponent>( ...args: IStateMapper<React.ComponentProps<T>>[] ) { return (target: T) => { return observer( (props: any) => { const field = useField() const results = args.reduce( (props, mapper) => { if (isFn(mapper)) { props = Object.assign(props, mapper(props, field)) } else { each(mapper, (to, extract) => { const extractValue = FormPath.getIn(field, extract) const targetValue = isStr(to) ? to : (extract as any) const originalValue = FormPath.getIn(props, targetValue) if (extract === 'value') { if (to !== extract) { delete props.value } } if (isValid(originalValue) && !isValid(extractValue)) return FormPath.setIn(props, targetValue, extractValue) }) } return props }, { ...props } ) return React.createElement(target, results) }, { forwardRef: true, } ) } } export function mapReadPretty<T extends JSXComponent, C extends JSXComponent>( component: C, readPrettyProps?: React.ComponentProps<C> ) { return (target: T) => { return observer( (props) => { const field = useField() if (!isVoidField(field) && field?.pattern === 'readPretty') { return React.createElement(component, { ...readPrettyProps, ...props, }) } return React.createElement(target, props) }, { forwardRef: true, } ) } } export function connect<T extends JSXComponent>( target: T, ...args: IComponentMapper<T>[] ) { const Target = args.reduce((target, mapper) => { return mapper(target) }, target) const Destination = React.forwardRef( (props: Partial<React.ComponentProps<T>>, ref) => { return React.createElement(Target, { ...props, ref }) } ) if (target) hoistNonReactStatics(Destination, target as any) return Destination } export { observer, Observer } ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/collections-weakset.spec.ts: -------------------------------------------------------------------------------- ```typescript import { observable, autorun, raw } from '..' describe('WeakSet', () => { test('should be a proper JS WeakSet', () => { const weakSet = observable(new WeakSet()) expect(weakSet).toBeInstanceOf(WeakSet) expect(raw(weakSet)).toBeInstanceOf(WeakSet) }) test('should autorun mutations', () => { const handler = jest.fn() const value = {} const weakSet = observable(new WeakSet()) autorun(() => handler(weakSet.has(value))) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(false) weakSet.add(value) expect(handler).toBeCalledTimes(2) expect(handler).lastCalledWith(true) weakSet.delete(value) expect(handler).toBeCalledTimes(3) expect(handler).lastCalledWith(false) }) test('should not autorun custom property mutations', () => { const handler = jest.fn() const weakSet = observable(new WeakSet()) autorun(() => handler(weakSet['customProp'])) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(undefined) weakSet['customProp'] = 'Hello World' expect(handler).toBeCalledTimes(1) }) test('should not autorun non value changing mutations', () => { const handler = jest.fn() const value = {} const weakSet = observable(new WeakSet()) autorun(() => handler(weakSet.has(value))) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(false) weakSet.add(value) expect(handler).toBeCalledTimes(2) expect(handler).lastCalledWith(true) weakSet.add(value) expect(handler).toBeCalledTimes(2) weakSet.delete(value) expect(handler).toBeCalledTimes(3) expect(handler).lastCalledWith(false) weakSet.delete(value) expect(handler).toBeCalledTimes(3) }) test('should not autorun raw data', () => { const handler = jest.fn() const value = {} const weakSet = observable(new WeakSet()) autorun(() => handler(raw(weakSet).has(value))) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(false) weakSet.add(value) expect(handler).toBeCalledTimes(1) weakSet.delete(value) expect(handler).toBeCalledTimes(1) }) test('should not be triggered by raw mutations', () => { const handler = jest.fn() const value = {} const weakSet = observable(new WeakSet()) autorun(() => handler(weakSet.has(value))) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(false) raw(weakSet).add(value) expect(handler).toBeCalledTimes(1) raw(weakSet).delete(value) expect(handler).toBeCalledTimes(1) }) }) ``` -------------------------------------------------------------------------------- /packages/antd/src/array-tabs/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment, useState } from 'react' import { Tabs, Badge } from 'antd' import { ArrayField } from '@formily/core' import { useField, observer, useFieldSchema, RecursionField, ReactFC, } from '@formily/react' import { TabsProps } from 'antd/lib/tabs' interface IFeedbackBadgeProps { index: number } const FeedbackBadge: ReactFC<IFeedbackBadgeProps> = observer( (props) => { const field = useField<ArrayField>() const tab = `${field.title || 'Untitled'} ${props.index + 1}` const errors = field.errors.filter((error) => error.address.includes(`${field.address}.${props.index}`) ) if (errors.length) { return ( <Badge size="small" className="errors-badge" count={errors.length}> {tab} </Badge> ) } return <Fragment>{tab}</Fragment> }, { scheduler(request) { requestAnimationFrame(request) }, } ) export const ArrayTabs: React.FC<React.PropsWithChildren<TabsProps>> = observer( (props) => { const field = useField<ArrayField>() const schema = useFieldSchema() const [activeKey, setActiveKey] = useState('tab-0') const value = Array.isArray(field.value) ? field.value : [] const dataSource = value?.length ? value : [{}] const onEdit = (targetKey: any, type: 'add' | 'remove') => { if (type == 'add') { const id = dataSource.length if (field?.value?.length) { field.push(null) } else { field.push(null, null) } setActiveKey(`tab-${id}`) } else if (type == 'remove') { const index = Number(targetKey.match(/-(\d+)/)?.[1]) if (index - 1 > -1) { setActiveKey(`tab-${index - 1}`) } field.remove(index) } } return ( <Tabs {...props} activeKey={activeKey} onChange={(key) => { setActiveKey(key) }} type="editable-card" onEdit={onEdit} > {dataSource?.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] : schema.items const key = `tab-${index}` return ( <Tabs.TabPane key={key} forceRender closable={index !== 0} tab={<FeedbackBadge index={index} />} > <RecursionField schema={items} name={index} /> </Tabs.TabPane> ) })} </Tabs> ) }, { scheduler(request) { requestAnimationFrame(request) }, } ) export default ArrayTabs ``` -------------------------------------------------------------------------------- /docs/guide/scenes/query-list.md: -------------------------------------------------------------------------------- ```markdown # Query list Because Formily Schema can completely describe the UI, we can simply abstract out the QueryList/QueryForm/QueryTable components to combine to implement the query list component. The following is only the pseudo code, because the query list scenario usually involves a lot of business packaging. At present, Formily hasn't figured out how to consider both versatility and quick start of business, so it will not open up specific components for the time being. But you can take a look at the pseudo-code first. If these components are officially implemented, the usage will definitely be like this: ```tsx pure import React from 'react' import { Void, Object, Array, String } from './MySchemaField' export default () => ( <Void x-component="QueryList" x-component-props={{ service: (params) => fetchRecords(params), }} > <Object name="query" x-component="QueryForm"> <String name="name" x-component="Input" /> <String name="id" x-component="Input" /> </Object> <Void name="toolbar" x-component="QueryToolbar"></Void> <Array name="list" x-component="QueryTable"> <Object> <Void x-component="QueryTable.Column"> <String name="name" x-component="PreviewText" /> </Void> <Void x-component="QueryTable.Column"> <String name="id" x-component="PreviewText" /> </Void> </Object> </Array> </Void> ) ``` ## Ideas - QueryList - Mainly responsible for sending requests at the top level, and issuing query methods to QueryForm and QueryTable for consumption through React Context - Query parameters need to call `form.query('query')` to find the field of QueryForm, and then take out the value of the field to send the request - When you have finished querying the data, you need to call `form.query('list')` to find the QueryTable field, and then fill in the table data for the value of the field model - QueryTable - The idea is very similar to that of ArrayTable. The main thing is to parse the Schema subtree and assemble the Columns data needed by the Table by yourself. If you want to support column merging and row merging, you need to parse more complex data - Based on props.value for rendering Table structure - Rely on RecursionField to render the internal data of the Table Column - Rely on the query method passed down from the context to achieve paging query - QueryForm - There is no special logic, the main thing is to combine Form+FormGrid to realize a query form layout - Realize query form query by relying on the query method passed down from the context ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/TimePicker.md: -------------------------------------------------------------------------------- ```markdown # TimePicker > Time Picker ## Markup Schema example ```tsx import React from 'react' import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="time" title="time" required x-decorator="FormItem" x-component="TimePicker" /> <SchemaField.String name="[startTime,endTime]" title="time range" x-decorator="FormItem" x-component="TimePicker.RangePicker" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { time: { title: 'Time', 'x-decorator': 'FormItem', 'x-component': 'TimePicker', type: 'string', }, '[startTime,endTime]': { title: 'Time Range', 'x-decorator': 'FormItem', 'x-component': 'TimePicker.RangePicker', type: 'string', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="time" title="time" decorator={[FormItem]} component={[TimePicker]} /> <Field name="[startTime,endTime]" title="time range" decorator={[FormItem]} component={[TimePicker.RangePicker]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://ant.design/components/time-picker-cn/ ``` -------------------------------------------------------------------------------- /packages/next/docs/components/TimePicker2.md: -------------------------------------------------------------------------------- ```markdown # TimePicker2 > Time 选择器 ## Markup Schema Example ```tsx import React from 'react' import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker2, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="time" title="time" x-decorator="FormItem" x-component="TimePicker2" /> <SchemaField.String name="[startTime,endTime]" title="Time Range" x-decorator="FormItem" x-component="TimePicker2.RangePicker" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema Case ```tsx import React from 'react' import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker2, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { time: { title: 'Time', 'x-decorator': 'FormItem', 'x-component': 'TimePicker2', type: 'string', }, '[startTime,endTime]': { title: 'Time Range', 'x-decorator': 'FormItem', 'x-component': 'TimePicker2.RangePicker', type: 'string', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="time" title="Time" decorator={[FormItem]} component={[TimePicker2]} /> <Field name="[startTime,endTime]" title="Time Range" decorator={[FormItem]} component={[TimePicker2.RangePicker]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://fusion.design/pc/component/basic/time-picker2 ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-step/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaVoidField x-component="FormStep" :x-component-props="{ formStep }"> <SchemaVoidField x-component="FormStep.StepPane" :x-component-props="{ title: '第一步' }" > <SchemaStringField name="aaa" x-decorator="FormItem" required x-component="Input" /> </SchemaVoidField> <SchemaVoidField x-component="FormStep.StepPane" :x-component-props="{ title: '第二步' }" > <SchemaStringField name="bbb" x-decorator="FormItem" required x-component="Input" /> </SchemaVoidField> <SchemaVoidField type="void" x-component="FormStep.StepPane" :x-component-props="{ title: '第三步' }" > <SchemaStringField name="ccc" x-decorator="FormItem" required x-component="Input" /> </SchemaVoidField> </SchemaVoidField> </SchemaField> <FormConsumer> <template #default> <FormButtonGroup> <Button :disabled="!formStep.allowBack" @click=" () => { formStep.back() } " > 上一步 </Button> <Button :disabled="!formStep.allowNext" @click=" () => { formStep.next() } " > 下一步 </Button> <Submit :disabled="formStep.allowNext" @submit="log">提交</Submit> </FormButtonGroup> </template> </FormConsumer> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, FormConsumer, createSchemaField } from '@formily/vue' import { FormItem, FormStep, FormButtonGroup, Submit, Input, } from '@formily/element' import { Button } from 'element-ui' import Template from '../editable/template.vue' const SchemaField = createSchemaField({ components: { FormItem, FormStep, Input, }, }) const formStep = FormStep.createFormStep() export default { components: { FormConsumer, FormProvider, FormButtonGroup, Button, Submit, Template, ...SchemaField, }, data() { const form = createForm() return { form, formStep, } }, methods: { log() { this.formStep.submit(console.log) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/antd/__tests__/moment.spec.ts: -------------------------------------------------------------------------------- ```typescript import moment from 'moment' import { formatMomentValue, momentable } from '../src/__builtins__/moment' test('momentable is usable', () => { expect(moment.isMoment(momentable('2021-09-08'))).toBe(true) expect( momentable(['2021-09-08', '2021-12-29']).every((item) => moment.isMoment(item) ) ).toBe(true) expect(momentable(0)).toBe(0) }) test('formatMomentValue is usable', () => { expect(formatMomentValue('', 'YYYY-MM-DD', '~')).toBe('~') expect(formatMomentValue('2021-12-21 15:47:00', 'YYYY-MM-DD')).toBe( '2021-12-21' ) expect(formatMomentValue('2021-12-21 15:47:00', undefined)).toBe( '2021-12-21 15:47:00' ) expect(formatMomentValue('2021-12-21 15:47:00', (date: string) => date)).toBe( '2021-12-21 15:47:00' ) expect(formatMomentValue('12:11', 'HH:mm')).toBe('12:11') expect(formatMomentValue('12:11:11', 'HH:mm:ss')).toBe('12:11:11') expect(formatMomentValue(['12:11'], ['HH:mm'])).toEqual(['12:11']) expect(formatMomentValue(['12:11:11'], ['HH:mm:ss'])).toEqual(['12:11:11']) expect(formatMomentValue(1663155911097, 'YYYY-MM-DD HH:mm:ss')).toBe( moment(1663155911097).format('YYYY-MM-DD HH:mm:ss') ) expect(formatMomentValue([1663155911097], ['YYYY-MM-DD HH:mm:ss'])).toEqual([ moment(1663155911097).format('YYYY-MM-DD HH:mm:ss'), ]) expect( formatMomentValue('2022-09-15T09:56:26.000Z', 'YYYY-MM-DD HH:mm:ss') ).toBe(moment('2022-09-15T09:56:26.000Z').format('YYYY-MM-DD HH:mm:ss')) expect( formatMomentValue(['2022-09-15T09:56:26.000Z'], ['YYYY-MM-DD HH:mm:ss']) ).toEqual([moment('2022-09-15T09:56:26.000Z').format('YYYY-MM-DD HH:mm:ss')]) expect(formatMomentValue('2022-09-15 09:56:26', 'HH:mm:ss')).toBe('09:56:26') expect(formatMomentValue(['2022-09-15 09:56:26'], ['HH:mm:ss'])).toEqual([ '09:56:26', ]) expect( formatMomentValue( ['2021-12-21 15:47:00', '2021-12-29 15:47:00'], 'YYYY-MM-DD' ) ).toEqual(['2021-12-21', '2021-12-29']) expect( formatMomentValue( ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], (date: string) => date ) ).toEqual(['2021-12-21 16:47:00', '2021-12-29 18:47:00']) expect( formatMomentValue( ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], ['YYYY-MM-DD', (date: string) => date] ) ).toEqual(['2021-12-21', '2021-12-29 18:47:00']) expect( formatMomentValue( ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], ['YYYY-MM-DD', undefined] ) ).toEqual(['2021-12-21', '2021-12-29 18:47:00']) expect(formatMomentValue([undefined], 'YYYY-MM-DD', 'placeholder')).toEqual([ 'placeholder', ]) }) ``` -------------------------------------------------------------------------------- /packages/element/src/form/index.ts: -------------------------------------------------------------------------------- ```typescript import { Form as FormType, IFormFeedback } from '@formily/core' import { FormProvider as _FormProvider, h, useForm } from '@formily/vue' import { Component, VNode } from 'vue' import { defineComponent } from 'vue-demi' import { FormLayout, FormLayoutProps } from '../form-layout' import { PreviewText } from '../preview-text' const FormProvider = _FormProvider as unknown as Component export interface FormProps extends FormLayoutProps { form?: FormType component?: Component previewTextPlaceholder: string | (() => VNode) onAutoSubmit?: (values: any) => any onAutoSubmitFailed?: (feedbacks: IFormFeedback[]) => void } export const Form = defineComponent<FormProps>({ name: 'FForm', props: [ 'form', 'component', 'previewTextPlaceholder', 'onAutoSubmit', 'onAutoSubmitFailed', ], setup(props, { attrs, slots, listeners }) { const top = useForm() return () => { const { form, component = 'form', onAutoSubmit = listeners?.autoSubmit, onAutoSubmitFailed = listeners?.autoSubmitFailed, previewTextPlaceholder = slots?.previewTextPlaceholder, } = props const renderContent = (form: FormType) => { return h( PreviewText.Placeholder, { props: { value: previewTextPlaceholder, }, }, { default: () => [ h( FormLayout, { attrs: { ...attrs, }, }, { default: () => [ h( component, { on: { submit: (e: Event) => { e?.stopPropagation?.() e?.preventDefault?.() form .submit(onAutoSubmit as (e: any) => void) .catch(onAutoSubmitFailed as (e: any) => void) }, }, }, slots ), ], } ), ], } ) } if (form) { return h( FormProvider, { props: { form } }, { default: () => renderContent(form), } ) } if (!top.value) throw new Error('must pass form instance by createForm') return renderContent(top.value) } }, }) export default Form ``` -------------------------------------------------------------------------------- /packages/next/src/form-item/scss/variable.scss: -------------------------------------------------------------------------------- ```scss //// /// @module form: 表单 /// @tag Form /// @category component /// @family data-entry /// @varPrefix $form- /// @classPrefix {prefix}-form /// @order {"size/bounding":10,"size/item":11,"size/label":12,"size/help":13,"size/border":14,"statement/help":10,"statement/label":11,"statement/normal":12,"statement/border":13} //// @charset "UTF-8"; // form variables // -------------------------------------------------- $form-item-cls: '#{$css-prefix}formily-item'; $input-border-color: $color-line1-3 !default; /// label padding (r) /// @namespace size/bounding $form-label-padding-r: $s-3 !default; /// margin (b) /// @namespace size/item $form-item-m-margin-b: $s-4 !default; /// margin (b) /// @namespace size/item $form-item-l-margin-b: $s-5 !default; /// margin (b) /// @namespace size/item $form-item-s-margin-b: $s-3 !default; /// margin (r) /// @namespace size/item $form-inline-l-item-margin-r: $s-6 !default; /// margin (r) /// @namespace size/item $form-inline-m-item-margin-r: $s-5 !default; /// margin (r) /// @namespace size/item $form-inline-s-item-margin-r: $s-4 !default; /// margin (t) /// @namespace size/help $form-help-margin-top: $s-1 !default; /// text /// @namespace size/help $form-help-font-size: $font-size-caption !default; /// text /// @namespace statement/help $form-help-color: $color-text1-2 !default; /// error text /// @namespace statement/help $form-error-color: $color-error-3 !default; /// warning text /// @namespace statement/help $form-warning-color: $color-warning-3 !default; $form-success-color: $color-success-3 !default; /// margin (b) /// @type length /// @namespace size/label $form-top-label-margin-b: 2px !default; /// text /// @namespace statement/label $form-label-color: $color-text1-3 !default; /// padding /// @namespace size/bounding $input-l-padding: $s-3 !default; /// padding(l) /// @namespace size/label $input-l-label-padding-left: $s-3 !default; /// padding(r) /// @namespace size/label $input-l-icon-padding-right: $s-2 !default; // medium // -------------------------------------------------- /// padding /// @namespace size/bounding $input-m-padding: $s-2 !default; /// padding(l) /// @namespace size/label $input-m-label-padding-left: $s-2 !default; /// padding(r) /// @namespace size/label $input-m-icon-padding-right: $s-2 !default; // small // -------------------------------------------------- /// padding /// @namespace size/bounding $input-s-padding: $s-1 !default; /// padding(l) /// @namespace size/label $input-s-label-padding-left: $s-2 !default; /// padding(r) /// @namespace size/label $input-s-icon-padding-right: $s-1 !default; ``` -------------------------------------------------------------------------------- /packages/antd/src/form-layout/useResponsiveFormLayout.ts: -------------------------------------------------------------------------------- ```typescript import { useRef, useState, useEffect } from 'react' import { isArr, isValid } from '@formily/shared' interface IProps { breakpoints?: number[] layout?: | 'vertical' | 'horizontal' | 'inline' | ('vertical' | 'horizontal' | 'inline')[] labelCol?: number | number[] wrapperCol?: number | number[] labelAlign?: 'right' | 'left' | ('right' | 'left')[] wrapperAlign?: 'right' | 'left' | ('right' | 'left')[] [props: string]: any } interface ICalcBreakpointIndex { (originalBreakpoints: number[], width: number): number } interface ICalculateProps { (target: HTMLElement, props: IProps): IProps } interface IUseResponsiveFormLayout { (props: IProps): { ref: React.MutableRefObject<HTMLDivElement> props: any } } const calcBreakpointIndex: ICalcBreakpointIndex = (breakpoints, width) => { for (let i = 0; i < breakpoints.length; i++) { if (width <= breakpoints[i]) { return i } } } const calcFactor = <T>(value: T | T[], breakpointIndex: number): T => { if (Array.isArray(value)) { if (breakpointIndex === -1) return value[0] return value[breakpointIndex] ?? value[value.length - 1] } else { return value } } const factor = <T>(value: T | T[], breakpointIndex: number): T => isValid(value) ? calcFactor(value as any, breakpointIndex) : value const calculateProps: ICalculateProps = (target, props) => { const { clientWidth } = target const { breakpoints, layout, labelAlign, wrapperAlign, labelCol, wrapperCol, ...otherProps } = props const breakpointIndex = calcBreakpointIndex(breakpoints, clientWidth) return { layout: factor(layout, breakpointIndex), labelAlign: factor(labelAlign, breakpointIndex), wrapperAlign: factor(wrapperAlign, breakpointIndex), labelCol: factor(labelCol, breakpointIndex), wrapperCol: factor(wrapperCol, breakpointIndex), ...otherProps, } } export const useResponsiveFormLayout: IUseResponsiveFormLayout = (props) => { const ref = useRef<HTMLDivElement>(null) const { breakpoints } = props if (!isArr(breakpoints)) { return { ref, props } } const [layoutProps, setLayout] = useState<any>(props) const updateUI = () => { if (ref.current) { setLayout(calculateProps(ref.current, props)) } } useEffect(() => { const observer = () => { updateUI() } const resizeObserver = new ResizeObserver(observer) if (ref.current) { resizeObserver.observe(ref.current) } updateUI() return () => { resizeObserver.disconnect() } }, []) return { ref, props: layoutProps, } } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-collapse/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form" :label-col="6" :wrapper-col="10"> <SchemaField> <SchemaVoidField type="void" title="折叠面板" x-decorator="FormItem" x-component="FormCollapse" :x-component-props="{ formCollapse }" > <SchemaVoidField type="void" name="tab1" x-component="FormCollapse.Item" :x-component-props="{ title: 'A1' }" > <SchemaStringField name="aaa" x-decorator="FormItem" title="AAA" required x-component="Input" /> </SchemaVoidField> <SchemaVoidField name="tab2" x-component="FormCollapse.Item" :x-component-props="{ title: 'A2' }" > <SchemaStringField name="bbb" x-decorator="FormItem" title="BBB" required x-component="Input" /> </SchemaVoidField> <SchemaVoidField name="tab3" x-component="FormCollapse.Item" :x-component-props="{ title: 'A3' }" > <SchemaStringField name="ccc" x-decorator="FormItem" title="CCC" required x-component="Input" /> </SchemaVoidField> </SchemaVoidField> </SchemaField> <FormButtonGroup alignFormItem> <Button @click=" () => { form.query('tab3').take((field) => { field.visible = !field.visible }) } " > 显示/隐藏最后一个Tab </Button> <Button @click=" () => { formCollapse.toggleActiveKey('tab2') } " > 切换第二个Tab </Button> <Submit @submit="log">提交</Submit> </FormButtonGroup> </Form> </template> <script> import { createForm } from '@formily/core' import { createSchemaField } from '@formily/vue' import { FormItem, FormCollapse, FormButtonGroup, Submit, Input, Form, } from '@formily/element' import { Button } from 'element-ui' const SchemaField = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) export default { components: { Form, FormButtonGroup, Button, Submit, ...SchemaField, }, data() { const form = createForm() const formCollapse = FormCollapse.createFormCollapse() return { form, formCollapse, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/next/src/form-layout/useResponsiveFormLayout.ts: -------------------------------------------------------------------------------- ```typescript import { useRef, useState, useEffect } from 'react' import { isArr, isValid } from '@formily/shared' interface IProps { breakpoints?: number[] layout?: | 'vertical' | 'horizontal' | 'inline' | ('vertical' | 'horizontal' | 'inline')[] labelCol?: number | number[] wrapperCol?: number | number[] labelAlign?: 'right' | 'left' | ('right' | 'left')[] wrapperAlign?: 'right' | 'left' | ('right' | 'left')[] [props: string]: any } interface ICalcBreakpointIndex { (originalBreakpoints: number[], width: number): number } interface ICalculateProps { (target: HTMLElement, props: IProps): IProps } interface IUseResponsiveFormLayout { (props: IProps): { ref: React.MutableRefObject<HTMLDivElement> props: any } } const calcBreakpointIndex: ICalcBreakpointIndex = (breakpoints, width) => { for (let i = 0; i < breakpoints.length; i++) { if (width <= breakpoints[i]) { return i } } } const calcFactor = <T>(value: T | T[], breakpointIndex: number): T => { if (Array.isArray(value)) { if (breakpointIndex === -1) return value[0] return value[breakpointIndex] ?? value[value.length - 1] } else { return value } } const factor = <T>(value: T | T[], breakpointIndex: number): T => isValid(value) ? calcFactor(value as any, breakpointIndex) : value const calculateProps: ICalculateProps = (target, props) => { const { clientWidth } = target const { breakpoints, layout, labelAlign, wrapperAlign, labelCol, wrapperCol, ...otherProps } = props const breakpointIndex = calcBreakpointIndex(breakpoints, clientWidth) return { layout: factor(layout, breakpointIndex), labelAlign: factor(labelAlign, breakpointIndex), wrapperAlign: factor(wrapperAlign, breakpointIndex), labelCol: factor(labelCol, breakpointIndex), wrapperCol: factor(wrapperCol, breakpointIndex), ...otherProps, } } export const useResponsiveFormLayout: IUseResponsiveFormLayout = (props) => { const ref = useRef<HTMLDivElement>(null) const { breakpoints } = props if (!isArr(breakpoints)) { return { ref, props } } const [layoutProps, setLayout] = useState<IProps>(props) const updateUI = () => { if (ref.current) { setLayout(calculateProps(ref.current, props)) } } useEffect(() => { const observer = () => { updateUI() } const resizeObserver = new ResizeObserver(observer) if (ref.current) { resizeObserver.observe(ref.current) } updateUI() return () => { resizeObserver.disconnect() } }, []) return { ref, props: layoutProps, } } ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Radio.md: -------------------------------------------------------------------------------- ```markdown # Radio > Single selection box ## Markup Schema example ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Number name="radio" title="single choice" enum={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} x-decorator="FormItem" x-component="Radio.Group" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { radio: { type: 'number', title: 'Single selection', enum: [ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="radio" title="single choice" dataSource={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} decorator={FormItem} component={Radio.Group} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://ant.design/components/radio-cn/ ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-cards/effects-markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaArrayField name="array" :maxItems="3" x-component="ArrayCards" x-decorator="FormItem" :x-component-props="{ title: '对象数组', }" > <SchemaObjectField> <SchemaVoidField x-component="ArrayCards.Index" /> <SchemaStringField name="aa" x-decorator="FormItem" title="AA" required description="AA输入123时隐藏BB" x-component="Input" /> <SchemaStringField name="bb" x-decorator="FormItem" title="BB" required x-component="Input" /> <SchemaStringField name="cc" x-decorator="FormItem" title="CC" required description="CC输入123时隐藏DD" x-component="Input" /> <SchemaStringField name="dd" x-decorator="FormItem" title="DD" required x-component="Input" /> <SchemaVoidField x-component="ArrayCards.Remove" /> <SchemaVoidField x-component="ArrayCards.MoveUp" /> <SchemaVoidField x-component="ArrayCards.MoveDown" /> </SchemaObjectField> <SchemaVoidField x-component="ArrayCards.Addition" title="添加条目" /> </SchemaArrayField> </SchemaField> <Submit @submit="log">提交</Submit> </FormProvider> </template> <script> import { createForm, onFieldChange, onFieldReact } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormItem, FormButtonGroup, Submit, Input, ArrayCards, } from '@formily/element' import { Button } from 'element-ui' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) export default { components: { FormProvider, FormButtonGroup, Button, Submit, ...SchemaField, }, data() { const form = createForm({ effects: () => { //主动联动模式 onFieldChange('array.*.aa', ['value'], (field, form) => { form.setFieldState(field.query('.bb'), (state) => { state.visible = field.value != '123' }) }) //被动联动模式 onFieldReact('array.*.dd', (field) => { field.visible = field.query('.cc').get('value') != '123' }) }, }) return { form, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/vue/docs/.vuepress/components/createCodeSandBox.js: -------------------------------------------------------------------------------- ```javascript import { getParameters } from 'codesandbox/lib/api/define' const CodeSandBoxHTML = '<div id="app"></div>' const CodeSandBoxJS = ` import Vue from 'vue' import Antd from 'ant-design-vue'; import 'ant-design-vue/dist/antd.css'; import App from './App.vue' Vue.config.productionTip = false; Vue.use(Antd); new Vue({ render: h => h(App), }).$mount('#app')` const createForm = ({ method, action, data }) => { const form = document.createElement('form') // 构造 form form.style.display = 'none' // 设置为不显示 form.target = '_blank' // 指向 iframe // 构造 formdata Object.keys(data).forEach((key) => { const input = document.createElement('input') // 创建 input input.name = key // 设置 name input.value = data[key] // 设置 value form.appendChild(input) }) form.method = method // 设置方法 form.action = action // 设置地址 document.body.appendChild(form) // 对该 form 执行提交 form.submit() document.body.removeChild(form) } export function createCodeSandBox(codeStr) { const parameters = getParameters({ files: { 'sandbox.config.json': { content: { template: 'node', infiniteLoopProtection: true, hardReloadOnChange: false, view: 'browser', container: { port: 8080, node: '14', }, }, }, 'package.json': { content: { scripts: { serve: 'vue-cli-service serve', build: 'vue-cli-service build', lint: 'vue-cli-service lint', }, dependencies: { '@formily/core': 'latest', '@formily/vue': 'latest', 'core-js': '^3.6.5', 'ant-design-vue': 'latest', 'vue-demi': 'latest', vue: '^2.6.11', }, devDependencies: { '@vue/cli-plugin-babel': '~4.5.0', '@vue/cli-service': '~4.5.0', '@vue/composition-api': 'latest', 'vue-template-compiler': '^2.6.11', }, babel: { presets: ['@vue/cli-plugin-babel/preset'], }, vue: { devServer: { host: '0.0.0.0', disableHostCheck: true, // 必须 }, }, }, }, 'src/App.vue': { content: codeStr, }, 'src/main.js': { content: CodeSandBoxJS, }, 'public/index.html': { content: CodeSandBoxHTML, }, }, }) createForm({ method: 'post', action: 'https://codesandbox.io/api/v1/sandboxes/define', data: { parameters, query: 'file=/src/App.vue', }, }) } ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Radio.md: -------------------------------------------------------------------------------- ```markdown # Radio > Single selection box ## Markup Schema example ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Number name="radio" title="single choice" enum={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} x-decorator="FormItem" x-component="Radio.Group" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { radio: { type: 'number', title: 'Single selection', enum: [ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="radio" title="single choice" dataSource={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} decorator={FormItem} component={Radio.Group} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://fusion.design/pc/component/basic/radio ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Transfer.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Transfer > 穿梭框 ## Markup Schema 案例 ```tsx import React from 'react' import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Transfer, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="transfer" title="穿梭框" x-decorator="FormItem" x-component="Transfer" enum={[ { title: '选项1', key: 1 }, { title: '选项2', key: 2 }, ]} x-component-props={{ render: (item) => item.title, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Transfer, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { transfer: { type: 'array', title: '穿梭框', 'x-decorator': 'FormItem', 'x-component': 'Transfer', enum: [ { title: '选项1', key: 1 }, { title: '选项2', key: 2 }, ], 'x-component-props': { render: '{{renderTitle}}', }, }, }, } const renderTitle = (item) => item.title export default () => ( <FormProvider form={form}> <SchemaField schema={schema} scope={{ renderTitle }} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="transfer" title="穿梭框" dataSource={[ { title: '选项1', key: 1 }, { title: '选项2', key: 2 }, ]} decorator={[FormItem]} component={[ Transfer, { render: (item) => item.title, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://ant.design/components/transfer-cn/ ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Password.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Password > 密码输入框 ## Markup Schema 案例 ```tsx import React from 'react' import { Password, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Password, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String name="input" title="输入框" x-decorator="FormItem" x-component="Password" required x-component-props={{ checkStrength: true, }} /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Password, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Password, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: '输入框', 'x-decorator': 'FormItem', 'x-component': 'Password', 'x-component-props': { checkStrength: true, }, }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Password, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <Field name="input" title="输入框" required decorator={[FormItem]} component={[ Password, { checkStrength: true, }, ]} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API 参考 https://ant.design/components/input-cn/ ``` -------------------------------------------------------------------------------- /packages/core/src/effects/onFormEffects.ts: -------------------------------------------------------------------------------- ```typescript import { isFn } from '@formily/shared' import { autorun, batch } from '@formily/reactive' import { Form } from '../models' import { LifeCycleTypes } from '../types' import { createEffectHook } from '../shared/effective' function createFormEffect(type: LifeCycleTypes) { return createEffectHook( type, (form: Form) => (callback: (form: Form) => void) => { batch(() => { callback(form) }) } ) } export const onFormInit = createFormEffect(LifeCycleTypes.ON_FORM_INIT) export const onFormMount = createFormEffect(LifeCycleTypes.ON_FORM_MOUNT) export const onFormUnmount = createFormEffect(LifeCycleTypes.ON_FORM_UNMOUNT) export const onFormValuesChange = createFormEffect( LifeCycleTypes.ON_FORM_VALUES_CHANGE ) export const onFormInitialValuesChange = createFormEffect( LifeCycleTypes.ON_FORM_INITIAL_VALUES_CHANGE ) export const onFormInputChange = createFormEffect( LifeCycleTypes.ON_FORM_INPUT_CHANGE ) export const onFormSubmit = createFormEffect(LifeCycleTypes.ON_FORM_SUBMIT) export const onFormReset = createFormEffect(LifeCycleTypes.ON_FORM_RESET) export const onFormSubmitStart = createFormEffect( LifeCycleTypes.ON_FORM_SUBMIT_START ) export const onFormSubmitEnd = createFormEffect( LifeCycleTypes.ON_FORM_SUBMIT_END ) export const onFormSubmitSuccess = createFormEffect( LifeCycleTypes.ON_FORM_SUBMIT_SUCCESS ) export const onFormSubmitFailed = createFormEffect( LifeCycleTypes.ON_FORM_SUBMIT_FAILED ) export const onFormSubmitValidateStart = createFormEffect( LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_START ) export const onFormSubmitValidateSuccess = createFormEffect( LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_SUCCESS ) export const onFormSubmitValidateFailed = createFormEffect( LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_FAILED ) export const onFormSubmitValidateEnd = createFormEffect( LifeCycleTypes.ON_FORM_SUBMIT_VALIDATE_END ) export const onFormValidateStart = createFormEffect( LifeCycleTypes.ON_FORM_VALIDATE_START ) export const onFormValidateSuccess = createFormEffect( LifeCycleTypes.ON_FORM_VALIDATE_SUCCESS ) export const onFormValidateFailed = createFormEffect( LifeCycleTypes.ON_FORM_VALIDATE_FAILED ) export const onFormValidateEnd = createFormEffect( LifeCycleTypes.ON_FORM_VALIDATE_END ) export const onFormGraphChange = createFormEffect( LifeCycleTypes.ON_FORM_GRAPH_CHANGE ) export const onFormLoading = createFormEffect(LifeCycleTypes.ON_FORM_LOADING) export function onFormReact(callback?: (form: Form) => void) { let dispose = null onFormInit((form) => { dispose = autorun(() => { if (isFn(callback)) callback(form) }) }) onFormUnmount(() => { dispose() }) } ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Password.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Password > 密码输入框 ## Markup Schema 案例 ```tsx import React from 'react' import { Password, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Password, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String name="input" title="输入框" x-decorator="FormItem" x-component="Password" required x-component-props={{ checkStrength: true, }} /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Password, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Password, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: '输入框', 'x-decorator': 'FormItem', 'x-component': 'Password', 'x-component-props': { checkStrength: true, }, }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Password, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <Field name="input" title="输入框" required decorator={[FormItem]} component={[ Password, { checkStrength: true, }, ]} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API 参考 https://fusion.design/pc/component/basic/input ``` -------------------------------------------------------------------------------- /packages/next/__tests__/sideEffects.spec.ts: -------------------------------------------------------------------------------- ```typescript import SideEffectsFlagPlugin from 'webpack/lib/optimize/SideEffectsFlagPlugin' // eslint-disable-next-line @typescript-eslint/no-var-requires const { sideEffects, name: baseName } = require('../package.json') test('sideEffects should be controlled manually', () => { // if config in pkg.json changed, please ensure it is covered by jest. expect(sideEffects).toStrictEqual([ 'dist/*', 'esm/*.js', 'lib/*.js', 'src/*.ts', '*.scss', '**/*/style.js', ]) }) test('dist/*', () => { // eg. import "@formily/next/dist/next.css" expect( SideEffectsFlagPlugin.moduleHasSideEffects('dist/next.css', 'dist/*') ).toBeTruthy() expect( SideEffectsFlagPlugin.moduleHasSideEffects( 'dist/formily.next.umd.production.js', 'dist/*' ) ).toBeTruthy() expect( SideEffectsFlagPlugin.moduleHasSideEffects( 'dist/formily.next.umd.production.js', 'dist/*' ) ).toBeTruthy() }) test('esm/*.js & lib/*.js', () => { // expected to be truthy // eg. import Formilynext from "@formily/next/esm/index" expect( SideEffectsFlagPlugin.moduleHasSideEffects('esm/index.js', 'esm/*.js') ).toBeTruthy() expect( SideEffectsFlagPlugin.moduleHasSideEffects('lib/index.js', 'lib/*.js') ).toBeTruthy() // expected to be falsy // eg. import Input from "@formily/next/esm/input/index" => will be compiled to __webpack_require__("./node_modules/@formily/next/esm/input/index.js") // It should be removed by webpack if not used after imported. expect( SideEffectsFlagPlugin.moduleHasSideEffects('esm/input/index.js', 'esm/*.js') ).toBeFalsy() expect( SideEffectsFlagPlugin.moduleHasSideEffects( 'esm/array-base/index.js', 'esm/*.js' ) ).toBeFalsy() expect( SideEffectsFlagPlugin.moduleHasSideEffects('lib/input/index.js', 'lib/*.js') ).toBeFalsy() }) test('*.scss', () => { // eg. import "@formily/next/lib/input/style.scss" expect( SideEffectsFlagPlugin.moduleHasSideEffects( `${baseName}/lib/input/style.scss`, '*.scss' ) ).toBeTruthy() }) test('**/*/style.js', () => { // eg. import "@formily/next/lib/input/style" will be compiled to __webpack_require__("./node_modules/@formily/next/lib/input/style.js") // so we can match the `*style.js` only, not `**/*/style*` may be cause someting mismatch like `@formily/next/lib/xxx-style/index.js` const modulePathArr = [ 'lib/input/style.js', `${baseName}/lib/input/style.js`, `./node_modules/${baseName}/style.js`, ] modulePathArr.forEach((modulePath) => { const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects( modulePath, '**/*/style.js' ) expect(hasSideEffects).toBeTruthy() }) }) ``` -------------------------------------------------------------------------------- /packages/antd/__tests__/sideEffects.spec.ts: -------------------------------------------------------------------------------- ```typescript import SideEffectsFlagPlugin from 'webpack/lib/optimize/SideEffectsFlagPlugin' // eslint-disable-next-line @typescript-eslint/no-var-requires const { sideEffects, name: baseName } = require('../package.json') test('sideEffects should be controlled manually', () => { // if config in pkg.json changed, please ensure it is covered by jest. expect(sideEffects).toStrictEqual([ 'dist/*', 'esm/*.js', 'lib/*.js', 'src/*.ts', '*.less', '**/*/style.js', ]) }) test('dist/*', () => { // eg. import "@formily/antd/dist/antd.css" expect( SideEffectsFlagPlugin.moduleHasSideEffects('dist/antd.css', 'dist/*') ).toBeTruthy() expect( SideEffectsFlagPlugin.moduleHasSideEffects( 'dist/formily.antd.umd.development.js', 'dist/*' ) ).toBeTruthy() expect( SideEffectsFlagPlugin.moduleHasSideEffects( 'dist/formily.antd.umd.production.js', 'dist/*' ) ).toBeTruthy() }) test('esm/*.js & lib/*.js', () => { // expected to be truthy // eg. import FormilyAntd from "@formily/antd/esm/index" expect( SideEffectsFlagPlugin.moduleHasSideEffects('esm/index.js', 'esm/*.js') ).toBeTruthy() expect( SideEffectsFlagPlugin.moduleHasSideEffects('lib/index.js', 'lib/*.js') ).toBeTruthy() // expected to be falsy // eg. import Input from "@formily/antd/esm/input/index" => will be compiled to __webpack_require__("./node_modules/@formily/antd/esm/input/index.js") // It should be removed by webpack if not used after imported. expect( SideEffectsFlagPlugin.moduleHasSideEffects('esm/input/index.js', 'esm/*.js') ).toBeFalsy() expect( SideEffectsFlagPlugin.moduleHasSideEffects( 'esm/array-base/index.js', 'esm/*.js' ) ).toBeFalsy() expect( SideEffectsFlagPlugin.moduleHasSideEffects('lib/input/index.js', 'lib/*.js') ).toBeFalsy() }) test('*.less', () => { // eg. import "@formily/antd/lib/input/style.less" expect( SideEffectsFlagPlugin.moduleHasSideEffects( `${baseName}/lib/input/style.less`, '*.less' ) ).toBeTruthy() }) test('**/*/style.js', () => { // eg. import "@formily/antd/lib/input/style" will be compiled to __webpack_require__("./node_modules/@formily/antd/lib/input/style.js") // so we can match the `*style.js` only, not `**/*/style*` may be cause someting mismatch like `@formily/antd/lib/xxx-style/index.js` const modulePathArr = [ 'lib/input/style.js', `${baseName}/lib/input/style.js`, `./node_modules/${baseName}/style.js`, ] modulePathArr.forEach((modulePath) => { const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects( modulePath, '**/*/style.js' ) expect(hasSideEffects).toBeTruthy() }) }) ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/internals.spec.ts: -------------------------------------------------------------------------------- ```typescript import { getValuesFromEvent, matchFeedback, patchFieldStates, deserialize, isHTMLInputEvent, } from '../shared/internals' import { createForm } from '../' import { attach } from './shared' test('getValuesFromEvent', () => { expect(getValuesFromEvent([{ target: { value: 123 } }])).toEqual([123]) expect(getValuesFromEvent([{ target: { checked: true } }])).toEqual([true]) expect(getValuesFromEvent([{ target: {} }])).toEqual([undefined]) expect(getValuesFromEvent([{ target: null }])).toEqual([{ target: null }]) expect(getValuesFromEvent([123])).toEqual([123]) expect(getValuesFromEvent([null])).toEqual([null]) }) test('empty', () => { expect(matchFeedback()).toBeFalsy() }) test('patchFieldStates', () => { const fields = {} patchFieldStates(fields, [{ type: 'update', address: 'aaa', payload: null }]) patchFieldStates(fields, [ { type: 'update3' as any, address: 'aaa', payload: null }, ]) expect(fields).toEqual({}) }) test('patchFieldStates should be sequence', () => { const form = attach(createForm()) attach( form.createArrayField({ name: 'array', }) ) attach( form.createField({ name: 'input', basePath: 'array.0', }) ) attach( form.createField({ name: 'input', basePath: 'array.1', }) ) const before = Object.keys(form.fields) form.fields['array'].move(1, 0) const after = Object.keys(form.fields) expect(after).toEqual(before) const form2 = attach(createForm()) attach( form2.createField({ name: 'field1', title: 'Field 1', }) ) attach( form2.createField({ name: 'field2', title: 'Field 1', }) ) patchFieldStates(form2.fields, [ { type: 'update', address: 'field2', oldAddress: 'field1', payload: form2.field1, }, { type: 'update', address: 'field1', oldAddress: 'field2', payload: form2.field2, }, ]) expect(Object.keys(form2.fields)).toEqual(['field1', 'field2']) }) test('deserialize', () => { expect(deserialize(null, null)).toBeUndefined() expect( deserialize( {}, { parent: null, } ) ).toEqual({}) }) test('isHTMLInputEvent', () => { expect(isHTMLInputEvent({ target: { checked: true } })).toBeTruthy() expect(isHTMLInputEvent({ target: { value: 123 } })).toBeTruthy() expect( isHTMLInputEvent({ target: { tagName: 'INPUT', value: null } }) ).toBeTruthy() expect(isHTMLInputEvent({ target: { tagName: 'INPUT' } })).toBeFalsy() expect(isHTMLInputEvent({ target: { tagName: 'DIV' } })).toBeFalsy() expect(isHTMLInputEvent({ target: {}, stopPropagation() {} })).toBeFalsy() expect(isHTMLInputEvent({})).toBeFalsy() }) ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Password.md: -------------------------------------------------------------------------------- ```markdown # Password > Password input box ## Markup Schema example ```tsx import React from 'react' import { Password, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Password, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String name="input" title="input box" x-decorator="FormItem" x-component="Password" required x-component-props={{ checkStrength: true, }} /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Password, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Password, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Password', 'x-component-props': { checkStrength: true, }, }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Password, FormItem, FormLayout, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <Field name="input" title="input box" required decorator={[FormItem]} component={[ Password, { checkStrength: true, }, ]} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API Reference https://ant.design/components/input-cn/ ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Transfer.md: -------------------------------------------------------------------------------- ```markdown # Transfer > Shuttle Box ## Markup Schema example ```tsx import React from 'react' import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Transfer, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="transfer" title="shuttle box" x-decorator="FormItem" x-component="Transfer" enum={[ { title: 'Option 1', key: 1 }, { title: 'Option 2', key: 2 }, ]} x-component-props={{ render: (item) => item.title, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Transfer, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { transfer: { type: 'array', title: 'shuttle box', 'x-decorator': 'FormItem', 'x-component': 'Transfer', enum: [ { title: 'Option 1', key: 1 }, { title: 'Option 2', key: 2 }, ], 'x-component-props': { render: '{{renderTitle}}', }, }, }, } const renderTitle = (item) => item.title export default () => ( <FormProvider form={form}> <SchemaField schema={schema} scope={{ renderTitle }} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="transfer" title="shuttle box" dataSource={[ { title: 'Option 1', key: 1 }, { title: 'Option 2', key: 2 }, ]} decorator={[FormItem]} component={[ Transfer, { render: (item) => item.title, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://ant.design/components/transfer-cn/ ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/collections-weakmap.spec.ts: -------------------------------------------------------------------------------- ```typescript import { observable, autorun, raw } from '..' describe('WeakMap', () => { test('should be a proper JS WeakMap', () => { const weakMap = observable(new WeakMap()) expect(weakMap).toBeInstanceOf(WeakMap) expect(raw(weakMap)).toBeInstanceOf(WeakMap) }) test('should autorun mutations', () => { const handler = jest.fn() const key = {} const weakMap = observable(new WeakMap()) autorun(() => handler(weakMap.get(key))) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(undefined) weakMap.set(key, 'value') expect(handler).toBeCalledTimes(2) expect(handler).lastCalledWith('value') weakMap.set(key, 'value2') expect(handler).toBeCalledTimes(3) expect(handler).lastCalledWith('value2') weakMap.delete(key) expect(handler).toBeCalledTimes(4) expect(handler).lastCalledWith(undefined) }) test('should not autorun custom property mutations', () => { const handler = jest.fn() const weakMap = observable(new WeakMap()) autorun(() => handler(weakMap['customProp'])) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(undefined) weakMap['customProp'] = 'Hello World' expect(handler).toBeCalledTimes(1) }) test('should not autorun non value changing mutations', () => { const handler = jest.fn() const key = {} const weakMap = observable(new WeakMap()) autorun(() => handler(weakMap.get(key))) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(undefined) weakMap.set(key, 'value') expect(handler).toBeCalledTimes(2) expect(handler).lastCalledWith('value') weakMap.set(key, 'value') expect(handler).toBeCalledTimes(2) weakMap.delete(key) expect(handler).toBeCalledTimes(3) expect(handler).lastCalledWith(undefined) weakMap.delete(key) expect(handler).toBeCalledTimes(3) }) test('should not autorun raw data', () => { const handler = jest.fn() const key = {} const weakMap = observable(new WeakMap()) autorun(() => handler(raw(weakMap).get(key))) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(undefined) weakMap.set(key, 'Hello') expect(handler).toBeCalledTimes(1) weakMap.delete(key) expect(handler).toBeCalledTimes(1) }) test('should not be triggered by raw mutations', () => { const handler = jest.fn() const key = {} const weakMap = observable(new WeakMap()) autorun(() => handler(weakMap.get(key))) expect(handler).toBeCalledTimes(1) expect(handler).lastCalledWith(undefined) raw(weakMap).set(key, 'Hello') expect(handler).toBeCalledTimes(1) raw(weakMap).delete(key) expect(handler).toBeCalledTimes(1) }) }) ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Password.md: -------------------------------------------------------------------------------- ```markdown # Password > Password input box ## Markup Schema example ```tsx import React from 'react' import { Password, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Password, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String name="input" title="input box" x-decorator="FormItem" x-component="Password" required x-component-props={{ checkStrength: true, }} /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Password, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Password, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Password', 'x-component-props': { checkStrength: true, }, }, }, } export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Password, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <Field name="input" title="input box" required decorator={[FormItem]} component={[ Password, { checkStrength: true, }, ]} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API Reference https://fusion.design/pc/component/basic/input ``` -------------------------------------------------------------------------------- /packages/json-schema/src/__tests__/server-validate.spec.ts: -------------------------------------------------------------------------------- ```typescript import { createForm, Form } from '@formily/core' import { ISchema, Schema, SchemaKey } from '../' // 这是schema const schemaJson = { type: 'object', title: 'xxx配置', properties: { string: { type: 'string', title: 'string', maxLength: 5, required: true, }, number: { type: 'number', title: 'number', required: true, }, url: { type: 'string', title: 'url', format: 'url', }, arr: { type: 'array', title: 'array', maxItems: 2, required: true, items: { type: 'object', properties: { string: { type: 'string', title: 'string', required: true, }, }, }, }, }, } // 这是需要校验的数据 const schemaData = { string: '123456', // 超过5个字 // number 字段不存在 url: 'xxxxx', // 不合法的url arr: [ { string: '1', }, { string: '2', }, { // 数组超出2项 string: '', // 没有填 }, ], } function recursiveField( form: Form, schema: ISchema, basePath?: string, name?: SchemaKey ) { const fieldSchema = new Schema(schema) const fieldProps = fieldSchema.toFieldProps() function recursiveProperties(propBasePath?: string) { fieldSchema.mapProperties((propSchema, propName) => { recursiveField(form, propSchema, propBasePath, propName) }) } if (name === undefined || name === null) { recursiveProperties(basePath) return } if (schema.type === 'object') { const field = form.createObjectField({ ...fieldProps, name, basePath, }) recursiveProperties(field.address.toString()) } else if (schema.type === 'array') { const field = form.createArrayField({ ...fieldProps, name, basePath, }) const fieldAddress = field.address.toString() const fieldValues = form.getValuesIn(fieldAddress) fieldValues.forEach((value: any, index: number) => { if (schema.items) { const itemsSchema = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items recursiveField(form, itemsSchema as ISchema, fieldAddress, index) } }) } else if (schema.type === 'void') { const field = form.createVoidField({ ...fieldProps, name, basePath, }) recursiveProperties(field.address.toString()) } else { form.createField({ ...fieldProps, name, basePath, }) } } test('server validate', async () => { const form = createForm({ values: schemaData, }) recursiveField(form, schemaJson) let errors: any[] try { await form.validate() } catch (e) { errors = e } expect(errors).not.toBeUndefined() }) ``` -------------------------------------------------------------------------------- /packages/core/src/shared/checkers.ts: -------------------------------------------------------------------------------- ```typescript import { isFn } from '@formily/shared' import { DataField, JSXComponent } from '..' import { Form, Field, ArrayField, ObjectField, VoidField, Query, } from '../models' import { IFormState, IFieldState, IVoidFieldState, GeneralField, IGeneralFieldState, } from '../types' export const isForm = (node: any): node is Form => { return node instanceof Form } export const isGeneralField = (node: any): node is GeneralField => { return node instanceof Field || node instanceof VoidField } export const isField = < Decorator extends JSXComponent = any, Component extends JSXComponent = any, TextType = any, ValueType = any >( node: any ): node is Field<Decorator, Component, TextType, ValueType> => { return node instanceof Field } export const isArrayField = < Decorator extends JSXComponent = any, Component extends JSXComponent = any >( node: any ): node is ArrayField<Decorator, Component> => { return node instanceof ArrayField } export const isObjectField = < Decorator extends JSXComponent = any, Component extends JSXComponent = any >( node: any ): node is ObjectField<Decorator, Component> => { return node instanceof ObjectField } export const isVoidField = <Decorator = any, Component = any, TextType = any>( node: any ): node is VoidField<Decorator, Component, TextType> => { return node instanceof VoidField } export const isFormState = <T extends Record<any, any> = any>( state: any ): state is IFormState<T> => { if (isFn(state?.initialize)) return false return state?.displayName === 'Form' } export const isFieldState = (state: any): state is IFieldState => { if (isFn(state?.initialize)) return false return state?.displayName === 'Field' } export const isGeneralFieldState = (node: any): node is IGeneralFieldState => { if (isFn(node?.initialize)) return false return node?.displayName?.indexOf('Field') > -1 } export const isArrayFieldState = (state: any): state is IFieldState => { if (isFn(state?.initialize)) return false return state?.displayName === 'ArrayField' } export const isDataField = (node: any): node is DataField => { return isField(node) || isArrayField(node) || isObjectField(node) } export const isDataFieldState = (node: any) => { return ( isFieldState(node) || isObjectFieldState(node) || isArrayFieldState(node) ) } export const isObjectFieldState = (state: any): state is IFieldState => { if (isFn(state?.initialize)) return false return state?.displayName === 'ObjectField' } export const isVoidFieldState = (state: any): state is IVoidFieldState => { if (isFn(state?.initialize)) return false return state?.displayName === 'VoidField' } export const isQuery = (query: any): query is Query => { return query && query instanceof Query } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-tab/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField :schema="schema" :scope="{ formTab }" /> <FormButtonGroup alignFormItem> <Button @click=" () => { form.query('tab3').take((field) => { field.visible = !field.visible }) } " > 显示/隐藏最后一个Tab </Button> <Button @click=" () => { formTab.setActiveKey('tab2') } " > 切换第二个Tab </Button> <Submit @submit="log">提交</Submit> </FormButtonGroup> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormItem, FormTab, FormButtonGroup, Submit, Input, } from '@formily/element' import { Button } from 'element-ui' const { SchemaField } = createSchemaField({ components: { FormItem, FormTab, Input, }, }) const schema = { type: 'object', properties: { collapse: { type: 'void', 'x-component': 'FormTab', 'x-component-props': { formTab: '{{formTab}}', }, properties: { tab1: { type: 'void', 'x-component': 'FormTab.TabPane', 'x-component-props': { label: 'A1', }, properties: { aaa: { type: 'string', title: 'AAA', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, tab2: { type: 'void', 'x-component': 'FormTab.TabPane', 'x-component-props': { label: 'A2', }, properties: { bbb: { type: 'string', title: 'BBB', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, tab3: { type: 'void', 'x-component': 'FormTab.TabPane', 'x-component-props': { label: 'A3', }, properties: { ccc: { type: 'string', title: 'CCC', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, }, }, }, } export default { components: { FormProvider, FormButtonGroup, Button, Submit, SchemaField, }, data() { const form = createForm() const formTab = FormTab.createFormTab() return { schema, form, formTab, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ```