This is page 10 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/vue/src/types/index.ts: -------------------------------------------------------------------------------- ```typescript import { Component } from 'vue' import * as VueDemi from 'vue-demi' import { Form, IFieldFactoryProps, IVoidFieldFactoryProps, GeneralField, Field, ObjectField, FormPatternTypes, FieldDisplayTypes, FieldValidator, } from '@formily/core' import type { FormPathPattern } from '@formily/shared' import type { ISchema, Schema, SchemaKey } from '@formily/json-schema' class Helper<Props> { Return = VueDemi.defineComponent({} as { props: Record<keyof Props, any> }) } export type DefineComponent<Props> = Helper<Props>['Return'] export type VueComponent = Component export type VueComponentOptionsWithProps = { props: unknown } export type VueComponentProps<T extends VueComponent> = T extends VueComponentOptionsWithProps ? T['props'] : T export interface IProviderProps { form: Form } export type IFieldProps< D extends VueComponent = VueComponent, C extends VueComponent = VueComponent > = IFieldFactoryProps<D, C> export type IVoidFieldProps< D extends VueComponent = VueComponent, C extends VueComponent = VueComponent > = IVoidFieldFactoryProps<D, C> export type IArrayFieldProps = IFieldProps export type IObjectFieldProps = IFieldProps export interface IReactiveFieldProps { fieldType: 'Field' | 'ArrayField' | 'ObjectField' | 'VoidField' fieldProps: IFieldProps | IVoidFieldProps } export interface IComponentMapper<T extends VueComponent = any> { (target: T): VueComponent } export type IStateMapper<Props> = | { [key in keyof Field]?: keyof Props | boolean } | ((props: Props, field: GeneralField) => Props) export type SchemaVueComponents = Record<string, VueComponent> export interface ISchemaFieldVueFactoryOptions< Components extends SchemaVueComponents = any > { components?: Components scope?: any } export interface ISchemaFieldProps extends Omit<IRecursionFieldProps, 'name' | 'schema'> { schema?: ISchema components?: { [key: string]: VueComponent } scope?: any name?: SchemaKey } export interface ISchemaMapper { (schema: Schema, name: SchemaKey): Schema } export interface ISchemaFilter { (schema: Schema, name: SchemaKey): boolean } export interface IRecursionFieldProps { schema: Schema name?: SchemaKey basePath?: FormPathPattern onlyRenderProperties?: boolean onlyRenderSelf?: boolean mapProperties?: ISchemaMapper filterProperties?: ISchemaFilter } export type ObjectKey = string | number | boolean | symbol export type KeyOfComponents<T> = keyof T export type ComponentPath< T, Key extends KeyOfComponents<T> = KeyOfComponents<T> > = Key extends string ? Key : never export type ComponentPropsByPathValue< T extends SchemaVueComponents, P extends ComponentPath<T> > = P extends keyof T ? VueComponentProps<T[P]> : never export type ISchemaMarkupFieldProps< Components extends SchemaVueComponents = SchemaVueComponents, Decorator extends ComponentPath<Components> = ComponentPath<Components>, Component extends ComponentPath<Components> = ComponentPath<Components> > = ISchema< Decorator, Component, ComponentPropsByPathValue<Components, Decorator>, ComponentPropsByPathValue<Components, Component>, FormPatternTypes, FieldDisplayTypes, FieldValidator, string, GeneralField > export type ISchemaTypeFieldProps< Components extends SchemaVueComponents = SchemaVueComponents, Decorator extends ComponentPath<Components> = ComponentPath<Components>, Component extends ComponentPath<Components> = ComponentPath<Components> > = Omit<ISchemaMarkupFieldProps<Components, Decorator, Component>, 'type'> export type IExpressionScopeProps = { value: any } ``` -------------------------------------------------------------------------------- /packages/core/docs/guide/mvvm.md: -------------------------------------------------------------------------------- ```markdown # MVVM ## OOP architecture **MVVM** (**Model–view–viewmodel**) is an OOP software architecture model. Its core is to separate the logic and view of our application to improve code maintainability and application robustness. We can use a picture to describe:  To explain, the View (view layer) is responsible for maintaining the UI structure and style, and is responsible for data binding with the ViewModel (view model). The data binding relationship here is two-way, that is, the ViewModel (view model) data occurs. Changes will trigger the update of the View (view layer), and at the same time changes in the data of the view layer will trigger the changes of the ViewModel (view model). Model is more biased towards the actual business data processing model. Both ViewModel and Model are congested models, and both are injected with business logic from different fields. For example, the business logic of ViewModel is more biased towards the domain logic of the view interaction layer, while the business logic of Model is more biased towards the processing logic of business data. So, what should the Formily solution be positioned in MVVM? Obviously, Formily provides two tiers of View and ViewModel capabilities. View is @formily/react @formily/vue, which is specifically used to bridge communication with @formily/core. Therefore, @formily/core is positioned at the ViewModel layer. , Where is the Model layer? Of course it is our actual business code layer, this layer formily will not manage, so at this layer, whether users maintain a Model in OOP mode or maintain a series of business logic function sets in FP mode, formily Don't care. Therefore, this also makes formily's intrusion into the business very low, because formily's goal is to reduce the cost of users designing ViewModels, allowing users to focus more on the realization of business logic. ## FP architecture Remember before the React team used the simplest expression **UI = fn(State)** to express the entire React system? Such a functional UI is very simple and clear. Will it conflict with the MVVM model? There is no conflict, because in the MVVM mode, the relationship between View and ViewModel is actually approximately equal to **UI = fn(State)**, because ViewModel is a congestion model injected with logic, which is related to **fn(State) ** can achieve the same goal, but it is a more OOP expression, but **fn(State)** is a more functional expression, the state exists as an anemia model, through one function after another, Immutable updates to the anemia model are finally reflected in the UI. Therefore, from the perspective of separation of logic and data, functional expression is clearer, but functional expression requires all data to be Immutable. Therefore, in scenarios with high performance requirements, the benefits of using a functional model will not be too great, of course, this is only the case in the js language. On the contrary, the MVVM model requires more data for Reactive data, that is, a responsive data model that can manipulate data by reference, so that data changes can be accurately monitored, and finally reflected on the UI. Therefore, in the form scenario, the performance advantage of the MVVM mode will be better. The most important thing is that most of the GUI products that have survived for decades almost all use MVVM coincidentally. It seems that in the front-end field, the function The type system will be more academic. In terms of the actual benefits to the business, MVVM is still the first choice. ``` -------------------------------------------------------------------------------- /packages/react/docs/api/components/ArrayField.md: -------------------------------------------------------------------------------- ```markdown --- order: 1 --- # ArrayField ## Description As @formily/core's [createArrayField](https://core.formilyjs.org/api/models/form#createarrayfield) React implementation, it is a bridge component specifically used to bind ViewModel and input controls, ArrayField component Property reference [IFieldFactoryProps](https://core.formilyjs.org/api/models/form#ifieldfactoryprops) <Alert> When we use the ArrayField component, we must remember to pass the name attribute. At the same time, use render props to organize sub-components </Alert> ## Signature ```ts type ArrayField = React.FC<React.PropsWithChildren<IFieldFactoryProps>> ``` ## Custom component use case ```tsx import React from 'react' import { createForm, ArrayField as ArrayFieldType } from '@formily/core' import { FormProvider, Field, ArrayField, useField, observer, } from '@formily/react' import { Input, Button, Space } from 'antd' const form = createForm() const ArrayComponent = observer(() => { const field = useField<ArrayFieldType>() return ( <> <div> {field.value?.map((item, index) => ( <div key={index} style={{ display: 'flex-block', marginBottom: 10 }}> <Space> <Field name={index} component={[Input]} /> <Button onClick={() => { field.remove(index) }} > Remove </Button> <Button onClick={() => { field.moveUp(index) }} > Move Up </Button> <Button onClick={() => { field.moveDown(index) }} > Move Down </Button> </Space> </div> ))} </div> <Button onClick={() => { field.push('') }} > Add </Button> </> ) }) export default () => ( <FormProvider form={form}> <ArrayField name="array" component={[ArrayComponent]} /> </FormProvider> ) ``` ## RenderProps use cases ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, Field, ArrayField } from '@formily/react' import { Input, Button, Space } from 'antd' const form = createForm() export default () => ( <FormProvider form={form}> <ArrayField name="array"> {(field) => { return ( <> <div> {field.value?.map((item, index) => ( <div key={index} style={{ display: 'flex-block', marginBottom: 10 }} > <Space> <Field name={index} component={[Input]} /> <Button onClick={() => { field.remove(index) }} > Remove </Button> <Button onClick={() => { field.moveUp(index) }} > Move Up </Button> <Button onClick={() => { field.moveDown(index) }} > Move Down </Button> </Space> </div> ))} </div> <Button onClick={() => field.push('')}>Add</Button> </> ) }} </ArrayField> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/next/src/form-button-group/index.tsx: -------------------------------------------------------------------------------- ```typescript /** * 1. FormItem网格布局 * 2. 居中,居右,居左布局 * 3. 行内布局 * 4. 吸底布局 */ import React, { useRef, useLayoutEffect, useState } from 'react' import StickyBox from 'react-sticky-box' import { ReactFC } from '@formily/react' import { Space, ISpaceProps } from '../space' import { BaseItem, IFormItemProps } from '../form-item' import { usePrefixCls } from '../__builtins__' import cls from 'classnames' interface IStickyProps extends React.ComponentProps<typeof StickyBox> { align?: React.CSSProperties['textAlign'] } type IFormButtonGroupProps = Omit<ISpaceProps, 'align' | 'size'> & { align?: React.CSSProperties['textAlign'] gutter?: number } type ComposedButtonGroup = ReactFC<IFormButtonGroupProps> & { Sticky: ReactFC<IStickyProps> FormItem: ReactFC< IFormItemProps & { gutter?: number } > } function getInheritedBackgroundColor(el: HTMLElement) { // get default style for current browser let defaultStyle = getDefaultBackground() // typically "rgba(0, 0, 0, 0)" // get computed color for el let backgroundColor = window.getComputedStyle(el).backgroundColor // if we got a real value, return it if (backgroundColor != defaultStyle) return backgroundColor // if we've reached the top parent el without getting an explicit color, return default if (!el.parentElement) return defaultStyle // otherwise, recurse and try again on parent element return getInheritedBackgroundColor(el.parentElement) } function getDefaultBackground() { // have to add to the document in order to use getComputedStyle let div = document.createElement('div') document.head.appendChild(div) let bg = window.getComputedStyle(div).backgroundColor document.head.removeChild(div) return bg } export const FormButtonGroup: ComposedButtonGroup = ({ align = 'left', gutter, ...props }) => { const prefixCls = usePrefixCls('formily-button-group') return ( <Space {...props} size={gutter} className={cls(prefixCls, props.className)} style={{ ...props.style, justifyContent: align === 'left' ? 'flex-start' : align === 'right' ? 'flex-end' : 'center', display: 'flex', }} > {props.children} </Space> ) } FormButtonGroup.FormItem = ({ gutter, ...props }) => { return ( <BaseItem {...props} label=" " style={{ margin: 0, padding: 0, ...props.style, width: '100%', }} colon={false} > {props.children?.['length'] ? ( <Space size={gutter}>{props.children}</Space> ) : ( props.children )} </BaseItem> ) } FormButtonGroup.Sticky = ({ align = 'left', ...props }) => { const ref = useRef() const [color, setColor] = useState('transparent') const prefixCls = usePrefixCls('formily-button-group') useLayoutEffect(() => { if (ref.current) { const computed = getInheritedBackgroundColor(ref.current) if (computed !== color) { setColor(computed) } } }) return ( <StickyBox {...props} className={cls(`${prefixCls}-sticky`, props.className)} style={{ backgroundColor: color, ...props.style, }} bottom > <div ref={ref} className={`${prefixCls}-sticky-inner`} style={{ ...props.style, justifyContent: align === 'left' ? 'flex-start' : align === 'right' ? 'flex-end' : 'center', }} > {props.children} </div> </StickyBox> ) } export default FormButtonGroup ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Form.md: -------------------------------------------------------------------------------- ```markdown # Form > The combination of FormProvider + FormLayout + form tags can help us quickly implement forms that are submitted with carriage return and can be laid out in batches ## Use Cases ```tsx import React from 'react' import { Input, Select, Form, FormItem, FormGrid, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { Field } from '@formily/react' const form = createForm() export default () => ( <Form form={form} layout="vertical" feedbackLayout="terse" onAutoSubmit={console.log} onAutoSubmitFailed={console.log} > <FormGrid maxColumns={4}> <Field name="aa" title="select box" decorator={[FormItem]} component={[Select]} dataSource={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} /> <Field name="bb" title="input box" required decorator={[FormItem]} component={[Input]} /> <Field name="cc" title="input box" decorator={[FormItem]} component={[Input]} /> <Field name="dd" title="input box" decorator={[FormItem]} component={[Input]} /> <Field name="ee" title="input box" decorator={[FormItem]} component={[Input]} /> <FormButtonGroup.FormItem> <Submit>Query</Submit> </FormButtonGroup.FormItem> </FormGrid> </Form> ) ``` <Alert style="margin-top:20px"> Note: To realize the carriage return submission, we cannot pass the onSubmit event to it when using the Submit component, otherwise the carriage return submission will become invalid. The purpose of this is to prevent users from writing onSubmit event listeners in multiple places at the same time, and processing logic If they are inconsistent, it is difficult to locate the problem when submitting. </Alert> ## API For layout-related API properties, we can refer to [FormLayout](./form-layout), and the rest are the unique API properties of the Form component | Property name | Type | Description | Default value | | ---------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ------------- | | form | [Form](https://core.formilyjs.org/api/models/form) | Form example | - | | component | string | Rendering component, can be specified as custom component rendering | `form` | | previewTextPlaceholder | ReactNode | Preview State Placeholder | `N/A` | | onAutoSubmit | `(values:any)=>any` | Carriage return submit event callback | - | | onAutoSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Carriage return submission verification failure event callback | - | ``` -------------------------------------------------------------------------------- /packages/shared/src/compare.ts: -------------------------------------------------------------------------------- ```typescript import { isArr } from './checkers' import { instOf } from './instanceof' const isArray = isArr const keyList = Object.keys const hasProp = Object.prototype.hasOwnProperty /* eslint-disable */ function equal(a: any, b: any) { // fast-deep-equal index.js 2.0.1 if (a === b) { return true } if (a && b && typeof a === 'object' && typeof b === 'object') { const arrA = isArray(a) const arrB = isArray(b) let i: number let length: number let key: string | number if (arrA && arrB) { length = a.length if (length !== b.length) { return false } for (i = length; i-- !== 0; ) { if (!equal(a[i], b[i])) { return false } } return true } if (arrA !== arrB) { return false } const momentA = a && a._isAMomentObject const momentB = b && b._isAMomentObject if (momentA !== momentB) return false if (momentA && momentB) return a.isSame(b) const immutableA = a && a.toJS const immutableB = b && b.toJS if (immutableA !== immutableB) return false if (immutableA) return a.is ? a.is(b) : a === b const dateA = instOf(a, 'Date') const dateB = instOf(b, 'Date') if (dateA !== dateB) { return false } if (dateA && dateB) { return a.getTime() === b.getTime() } const regexpA = instOf(a, 'RegExp') const regexpB = instOf(b, 'RegExp') if (regexpA !== regexpB) { return false } if (regexpA && regexpB) { return a.toString() === b.toString() } const urlA = instOf(a, 'URL') const urlB = instOf(b, 'URL') if (urlA !== urlB) { return false } if (urlA && urlB) { return a.href === b.href } const schemaA = a && a.toJSON const schemaB = b && b.toJSON if (schemaA !== schemaB) return false if (schemaA && schemaB) return equal(a.toJSON(), b.toJSON()) const keys = keyList(a) length = keys.length if (length !== keyList(b).length) { return false } for (i = length; i-- !== 0; ) { if (!hasProp.call(b, keys[i])) { return false } } // end fast-deep-equal // Custom handling for React for (i = length; i-- !== 0; ) { key = keys[i] if (key === '_owner' && a.$$typeof) { // React-specific: avoid traversing React elements' _owner. // _owner contains circular references // and is not needed when comparing the actual elements (and not their owners) // .$$typeof and ._store on just reasonable markers of a react element continue } else { // all other properties should be traversed as usual if (!equal(a[key], b[key])) { return false } } } // fast-deep-equal index.js 2.0.1 return true } return a !== a && b !== b } // end fast-deep-equal export const isEqual = function exportedEqual(a: any, b: any) { try { return equal(a, b) } catch (error) { /* istanbul ignore next */ if ( (error.message && error.message.match(/stack|recursion/i)) || error.number === -2146828260 ) { // warn on circular references, don't crash // browsers give this different errors name and messages: // chrome/safari: "RangeError", "Maximum call stack size exceeded" // firefox: "InternalError", too much recursion" // edge: "Error", "Out of stack space" console.warn( 'Warning: react-fast-compare does not handle circular references.', error.name, error.message ) return false } // some other error. we should definitely know about these /* istanbul ignore next */ throw error } } ``` -------------------------------------------------------------------------------- /packages/antd/src/form-button-group/index.tsx: -------------------------------------------------------------------------------- ```typescript /** * 1. FormItem网格布局 * 2. 居中,居右,居左布局 * 3. 行内布局 * 4. 吸底布局 */ import React, { useRef, useLayoutEffect, useState } from 'react' import { ReactFC } from '@formily/react' import { Space } from 'antd' import { SpaceProps } from 'antd/lib/space' import { BaseItem, IFormItemProps } from '../form-item' import { usePrefixCls } from '../__builtins__' import StickyBox from 'react-sticky-box' import cls from 'classnames' interface IStickyProps extends React.ComponentProps<typeof StickyBox> { align?: React.CSSProperties['textAlign'] } type IFormButtonGroupProps = Omit<SpaceProps, 'align' | 'size'> & { align?: React.CSSProperties['textAlign'] gutter?: number } type ComposedButtonGroup = ReactFC<IFormButtonGroupProps> & { Sticky: ReactFC<React.PropsWithChildren<IStickyProps>> FormItem: ReactFC< IFormItemProps & { gutter?: number } > } function getInheritedBackgroundColor(el: HTMLElement) { // get default style for current browser const defaultStyle = getDefaultBackground() // typically "rgba(0, 0, 0, 0)" // get computed color for el const backgroundColor = window.getComputedStyle(el).backgroundColor // if we got a real value, return it if (backgroundColor != defaultStyle) return backgroundColor // if we've reached the top parent el without getting an explicit color, return default if (!el.parentElement) return defaultStyle // otherwise, recurse and try again on parent element return getInheritedBackgroundColor(el.parentElement) } function getDefaultBackground() { // have to add to the document in order to use getComputedStyle let div = document.createElement('div') document.head.appendChild(div) let bg = window.getComputedStyle(div).backgroundColor document.head.removeChild(div) return bg } export const FormButtonGroup: ComposedButtonGroup = ({ align = 'left', gutter, ...props }) => { const prefixCls = usePrefixCls('formily-button-group') return ( <Space {...props} size={gutter} className={cls(prefixCls, props.className)} style={{ ...props.style, justifyContent: align === 'left' ? 'flex-start' : align === 'right' ? 'flex-end' : 'center', display: 'flex', }} > {props.children} </Space> ) } FormButtonGroup.FormItem = ({ gutter, ...props }) => { return ( <BaseItem {...props} label=" " style={{ margin: 0, padding: 0, ...props.style, width: '100%', }} colon={false} > {props.children?.['length'] ? ( <Space size={gutter}>{props.children}</Space> ) : ( props.children )} </BaseItem> ) } FormButtonGroup.Sticky = ({ align = 'left', ...props }) => { const ref = useRef() const [color, setColor] = useState('transparent') const prefixCls = usePrefixCls('formily-button-group') useLayoutEffect(() => { if (ref.current) { const computed = getInheritedBackgroundColor(ref.current) if (computed !== color) { setColor(computed) } } }) return ( <StickyBox {...props} className={cls(`${prefixCls}-sticky`, props.className)} style={{ backgroundColor: color, ...props.style, }} bottom > <div ref={ref} className={`${prefixCls}-sticky-inner`} style={{ ...props.style, justifyContent: align === 'left' ? 'flex-start' : align === 'right' ? 'flex-end' : 'center', }} > {props.children} </div> </StickyBox> ) } export default FormButtonGroup ``` -------------------------------------------------------------------------------- /packages/reactive/src/annotations/computed.ts: -------------------------------------------------------------------------------- ```typescript import { ObModelSymbol, ReactionStack } from '../environment' import { createAnnotation } from '../internals' import { buildDataTree } from '../tree' import { isFn } from '../checkers' import { bindTargetKeyWithCurrentReaction, runReactionsFromTargetKey, bindComputedReactions, hasRunningReaction, isUntracking, batchStart, batchEnd, releaseBindingReactions, } from '../reaction' interface IValue<T = any> { value?: T } export interface IComputed { <T>(compute: () => T): IValue<T> <T>(compute: { get?: () => T; set?: (value: T) => void }): IValue<T> } const getDescriptor = Object.getOwnPropertyDescriptor const getProto = Object.getPrototypeOf const ClassDescriptorSymbol = Symbol('ClassDescriptorSymbol') function getPropertyDescriptor(obj: any, key: PropertyKey) { if (!obj) return return getDescriptor(obj, key) || getPropertyDescriptor(getProto(obj), key) } function getPropertyDescriptorCache(obj: any, key: PropertyKey) { const constructor = obj.constructor if (constructor === Object || constructor === Array) return getPropertyDescriptor(obj, key) const cache = constructor[ClassDescriptorSymbol] || {} const descriptor = cache[key] if (descriptor) return descriptor const newDesc = getPropertyDescriptor(obj, key) constructor[ClassDescriptorSymbol] = cache cache[key] = newDesc return newDesc } function getPrototypeDescriptor( target: any, key: PropertyKey, value: any ): PropertyDescriptor { if (!target) { if (value) { if (isFn(value)) { return { get: value } } else { return value } } return {} } const descriptor = getPropertyDescriptorCache(target, key) if (descriptor) { return descriptor } return {} } export const computed: IComputed = createAnnotation( ({ target, key, value }) => { const store: IValue = {} const proxy = {} const context = target ? target : store const property = target ? key : 'value' const descriptor = getPrototypeDescriptor(target, property, value) function compute() { store.value = descriptor.get?.call(context) } function reaction() { if (ReactionStack.indexOf(reaction) === -1) { releaseBindingReactions(reaction) try { ReactionStack.push(reaction) compute() } finally { ReactionStack.pop() } } } reaction._name = 'ComputedReaction' reaction._scheduler = () => { reaction._dirty = true runReactionsFromTargetKey({ target: context, key: property, value: store.value, type: 'set', }) } reaction._isComputed = true reaction._dirty = true reaction._context = context reaction._property = property function get() { if (hasRunningReaction()) { bindComputedReactions(reaction) } if (!isUntracking()) { //如果允许untracked过程中收集依赖,那么永远不会存在绑定,因为_dirty已经设置为false if (reaction._dirty) { reaction() reaction._dirty = false } } else { compute() } bindTargetKeyWithCurrentReaction({ target: context, key: property, type: 'get', }) return store.value } function set(value: any) { try { batchStart() descriptor.set?.call(context, value) } finally { batchEnd() } } if (target) { Object.defineProperty(target, key, { get, set, enumerable: true, }) return target } else { Object.defineProperty(proxy, 'value', { set, get, }) buildDataTree(target, key, store) proxy[ObModelSymbol] = store } return proxy } ) ``` -------------------------------------------------------------------------------- /packages/react/docs/api/components/RecursionField.md: -------------------------------------------------------------------------------- ```markdown --- order: 5 --- # RecursionField ## Description The recursive rendering component is mainly based on [JSON-Schema](/api/shared/schema) for recursive rendering. It is the core rendering component inside the [SchemaField](/api/components/schema-field) component. Of course, it can It is used separately from SchemaField. When we use it, it is mainly used in custom components to implement custom components with recursive rendering capabilities. ## Signature ```ts interface IRecursionFieldProps { schema: ISchema //Field schema name?: string //Path name basePath?: FormPathPattern //base path propsRecursion?: boolean //Whether to recursiveliy pass mapProperties and filterProperties onlyRenderProperties?: boolean //Whether to only render properties onlyRenderSelf?: boolean //Whether to only render itself without rendering properties mapProperties?: (schema: Schema, name: string) => Schema //schema properties mapper, mainly used to rewrite the schema filterProperties?: (schema: Schema, name: string) => boolean //schema properties filter, the filtered schema nodes will not be rendered } type RecursionField = React.FC<React.PropsWithChildren<IRecursionFieldProps>> ``` ## Example ### Simple recursion ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, createSchemaField, RecursionField } from '@formily/react' import { Input } from 'antd' const form = createForm() const Custom = (props) => { return <RecursionField schema={props.schema} onlyRenderProperties /> } const SchemaField = createSchemaField({ components: { Custom, Input, }, }) export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Object name="custom" x-component="Custom" x-component-props={{ schema: { type: 'object', properties: { input: { type: 'string', 'x-component': 'Input', }, }, }, }} /> </SchemaField> </FormProvider> ) ``` We can read independent schema objects from component properties and pass them to RecursionField for rendering ### Incremental list recursion ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, createSchemaField, RecursionField, useField, useFieldSchema, observer, } from '@formily/react' import { Input, Space, Button } from 'antd' const form = createForm() const ArrayItems = observer((props) => { const field = useField() const schema = useFieldSchema() return ( <div> {props.value?.map((item, index) => { return ( <div key={index} style={{ marginBottom: 10 }}> <Space> <RecursionField schema={schema.items} name={index} /> <Button onClick={() => { field.remove(index) }} > Remove </Button> </Space> </div> ) })} <Button onClick={() => { field.push({}) }} > Add </Button> </div> ) }) const SchemaField = createSchemaField({ components: { ArrayItems, Input, }, }) export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="custom" x-component="ArrayItems"> <SchemaField.Object> <SchemaField.String name="input" x-component="Input" /> </SchemaField.Object> </SchemaField.Array> </SchemaField> </FormProvider> ) ``` Use [useField](/api/hooks/useField) and [useFieldSchema](/api/shared/use-field-schema) to get the field instance and field schema in the current field context ``` -------------------------------------------------------------------------------- /packages/react/docs/api/components/ObjectField.zh-CN.md: -------------------------------------------------------------------------------- ```markdown --- order: 2 --- # ObjectField ## 描述 作为@formily/core 的 [createObjectField](https://core.formilyjs.org/zh-CN/api/models/form#createobjectfield) React 实现,它是专门用于将 ViewModel 与输入控件做绑定的桥接组件,ObjectField 组件属性参考[IFieldFactoryProps](https://core.formilyjs.org/zh-CN/api/models/form#ifieldfactoryprops) <Alert> 我们在使用 ObjectField 组件的时候,一定要记得传name属性。同时要使用render props形式来组织子组件 </Alert> ## 签名 ```ts type ObjectField = React.FC<React.PropsWithChildren<IFieldFactoryProps>> ``` ## 自定义组件用例 ```tsx import React from 'react' import { createForm, ObjectField as ObjectFieldType } from '@formily/core' import { FormProvider, Field, ObjectField, useField, observer, } from '@formily/react' import { Input, Button, Space } from 'antd' const form = createForm() const ObjectComponent = observer(() => { const field = useField<ObjectFieldType>() return ( <> <div> {Object.keys(field.value || {}).map((key) => ( <div key={key} style={{ display: 'flex-block', marginBottom: 10 }}> <Space> <Field name={key} component={[Input, { placeholder: key }]} /> <Button onClick={() => { field.removeProperty(key) }} > Remove </Button> </Space> </div> ))} </div> <Space> <Field name="propertyName" basePath={''} required component={[Input, { placeholder: 'Property Name' }]} /> <Button onClick={() => { const name = form.values.propertyName if (name && !form.existValuesIn(`${field.path}.${name}`)) { field.addProperty(name, '') form.deleteValuesIn('propertyName') } }} > Add </Button> </Space> </> ) }) export default () => ( <FormProvider form={form}> <ObjectField name="object" component={[ObjectComponent]} /> </FormProvider> ) ``` ## RenderProps 用例 ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, Field, ObjectField } from '@formily/react' import { Input, Button, Space } from 'antd' const form = createForm() export default () => ( <FormProvider form={form}> <ObjectField name="object"> {(field) => { return ( <> <div> {Object.keys(field.value || {}).map((key) => ( <div key={key} style={{ display: 'flex-block', marginBottom: 10 }} > <Space> <Field name={key} component={[Input, { placeholder: key }]} /> <Button onClick={() => { field.removeProperty(key) }} > Remove </Button> </Space> </div> ))} </div> <Space> <Field name="propertyName" basePath={''} required component={[Input, { placeholder: 'Property Name' }]} /> <Button onClick={() => { const name = form.values.propertyName if (name && !form.existValuesIn(`${field.path}.${name}`)) { field.addProperty(name, '') form.deleteValuesIn('propertyName') } }} > Add </Button> </Space> </> ) }} </ObjectField> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/antd/src/array-items/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useRef } from 'react' import { ArrayField } from '@formily/core' import { useField, observer, useFieldSchema, RecursionField, } from '@formily/react' import cls from 'classnames' import { ISchema } from '@formily/json-schema' import { usePrefixCls, SortableContainer, SortableElement, } from '../__builtins__' import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from '../array-base' type ComposedArrayItems = React.FC< React.PropsWithChildren< React.HTMLAttributes<HTMLDivElement> & IArrayBaseProps > > & ArrayBaseMixins & { Item?: React.FC< React.HTMLAttributes<HTMLDivElement> & { type?: 'card' | 'divide' } > } const SortableItem = SortableElement( (props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => { const prefixCls = usePrefixCls('formily-array-items') return ( <div {...props} className={cls(`${prefixCls}-item`, props.className)}> {props.children} </div> ) } ) const SortableList = SortableContainer( (props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => { const prefixCls = usePrefixCls('formily-array-items') return ( <div {...props} className={cls(`${prefixCls}-list`, props.className)}> {props.children} </div> ) } ) const isAdditionComponent = (schema: ISchema) => { return schema['x-component']?.indexOf('Addition') > -1 } const useAddition = () => { const schema = useFieldSchema() return schema.reduceProperties((addition, schema, key) => { if (isAdditionComponent(schema)) { return <RecursionField schema={schema} name={key} /> } return addition }, null) } export const ArrayItems: ComposedArrayItems = observer((props) => { const field = useField<ArrayField>() const prefixCls = usePrefixCls('formily-array-items') const ref = useRef<HTMLDivElement>(null) const schema = useFieldSchema() const addition = useAddition() const dataSource = Array.isArray(field.value) ? field.value : [] const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props if (!schema) throw new Error('can not found schema object') return ( <ArrayBase onAdd={onAdd} onCopy={onCopy} onRemove={onRemove} onMoveUp={onMoveUp} onMoveDown={onMoveDown} > <div {...props} ref={ref} onChange={() => {}} className={cls(prefixCls, props.className)} > <SortableList list={dataSource.slice()} className={`${prefixCls}-sort-helper`} onSortEnd={({ oldIndex, newIndex }) => { field.move(oldIndex, newIndex) }} > {dataSource?.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items return ( <ArrayBase.Item key={index} index={index} record={() => field.value?.[index]} > <SortableItem key={`item-${index}`} lockAxis="y" index={index}> <div className={`${prefixCls}-item-inner`}> <RecursionField schema={items} name={index} /> </div> </SortableItem> </ArrayBase.Item> ) })} </SortableList> {addition} </div> </ArrayBase> ) }) ArrayItems.displayName = 'ArrayItems' ArrayItems.Item = (props) => { const prefixCls = usePrefixCls('formily-array-items') return ( <div {...props} onChange={() => {}} className={cls(`${prefixCls}-${props.type || 'card'}`, props.className)} > {props.children} </div> ) } ArrayBase.mixin(ArrayItems) export default ArrayItems ``` -------------------------------------------------------------------------------- /scripts/rollup.base.js: -------------------------------------------------------------------------------- ```javascript import path from 'path' import typescript from 'rollup-plugin-typescript2' import resolve from 'rollup-plugin-node-resolve' import commonjs from '@rollup/plugin-commonjs' import externalGlobals from 'rollup-plugin-external-globals' import injectProcessEnv from 'rollup-plugin-inject-process-env' import dts from 'rollup-plugin-dts' import { terser } from 'rollup-plugin-terser' const presets = () => { const externals = { antd: 'antd', vue: 'Vue', react: 'React', moment: 'moment', 'react-is': 'ReactIs', '@alifd/next': 'Next', 'mobx-react-lite': 'mobxReactLite', 'react-dom': 'ReactDOM', 'element-ui': 'Element', '@ant-design/icons': 'icons', '@vue/composition-api': 'VueCompositionAPI', '@formily/reactive-react': 'Formily.ReactiveReact', '@formily/reactive-vue': 'Formily.ReactiveVue', '@formily/reactive': 'Formily.Reactive', '@formily/path': 'Formily.Path', '@formily/shared': 'Formily.Shared', '@formily/validator': 'Formily.Validator', '@formily/core': 'Formily.Core', '@formily/json-schema': 'Formily.JSONSchema', '@formily/react': 'Formily.React', '@formily/vue': 'Formily.Vue', 'vue-demi': 'VueDemi' } return [ typescript({ tsconfig: './tsconfig.build.json', tsconfigOverride: { compilerOptions: { module: 'ESNext', declaration: false, }, }, }), resolve(), commonjs(), externalGlobals(externals, { exclude: ['**/*.{less,sass,scss}'], }), ] } const createEnvPlugin = (env) => { return injectProcessEnv( { NODE_ENV: env, }, { exclude: '**/*.{css,less,sass,scss}', verbose: false, } ) } const inputFilePath = path.join(process.cwd(), 'src/index.ts') const noUIDtsPackages = [ 'formily.core', 'formily.validator', 'formily.shared', 'formily.path', 'formily.json-schema', 'formily.reactive', ] export const removeImportStyleFromInputFilePlugin = () => ({ name: 'remove-import-style-from-input-file', transform(code, id) { // 样式由 build:style 进行打包,所以要删除入口文件上的 `import './style'` if (inputFilePath === id) { return code.replace(`import './style';`, '') } return code }, }) export default (filename, targetName, ...plugins) => { const base = [ { input: 'src/index.ts', output: { format: 'umd', file: `dist/${filename}.umd.development.js`, name: targetName, sourcemap: true, amd: { id: filename, }, globals: { '@formily/json-schema': 'Formily.JSONSchema', }, }, external: ['react', 'react-dom', 'react-is', '@formily/json-schema'], plugins: [...presets(), ...plugins, createEnvPlugin('development')], }, { input: 'src/index.ts', output: { format: 'umd', file: `dist/${filename}.umd.production.js`, name: targetName, sourcemap: true, amd: { id: filename, }, globals: { '@formily/json-schema': 'Formily.JSONSchema', }, }, external: ['react', 'react-dom', 'react-is', '@formily/json-schema'], plugins: [ ...presets(), terser(), ...plugins, createEnvPlugin('production'), ], }, ] if (noUIDtsPackages.includes(filename)) { base.push({ input: 'esm/index.d.ts', output: { format: 'es', file: `dist/${filename}.d.ts`, }, plugins: [dts(), ...plugins], }) base.push({ input: 'esm/index.d.ts', output: { format: 'es', file: `dist/${filename}.all.d.ts`, }, plugins: [ dts({ respectExternal: true, }), ...plugins, ], }) } return base } ``` -------------------------------------------------------------------------------- /packages/reactive/src/autorun.ts: -------------------------------------------------------------------------------- ```typescript import { batchEnd, batchStart, disposeBindingReactions, releaseBindingReactions, disposeEffects, hasDepsChange, } from './reaction' import { isFn } from './checkers' import { ReactionStack } from './environment' import { Reaction, IReactionOptions, Dispose } from './types' import { toArray } from './array' interface IValue { currentValue?: any oldValue?: any } export const autorun = (tracker: Reaction, name = 'AutoRun') => { const reaction: Reaction = () => { if (!isFn(tracker)) return if (reaction._boundary > 0) return if (ReactionStack.indexOf(reaction) === -1) { releaseBindingReactions(reaction) try { batchStart() ReactionStack.push(reaction) tracker() } finally { ReactionStack.pop() reaction._boundary++ batchEnd() reaction._boundary = 0 reaction._memos.cursor = 0 reaction._effects.cursor = 0 } } } const cleanRefs = () => { reaction._memos = { queue: [], cursor: 0, } reaction._effects = { queue: [], cursor: 0, } } reaction._boundary = 0 reaction._name = name cleanRefs() reaction() return () => { disposeBindingReactions(reaction) disposeEffects(reaction) cleanRefs() } } autorun.memo = <T>(callback: () => T, dependencies?: any[]): T => { if (!isFn(callback)) return const current = ReactionStack[ReactionStack.length - 1] if (!current || !current._memos) throw new Error('autorun.memo must used in autorun function body.') const deps = toArray(dependencies || []) const id = current._memos.cursor++ const old = current._memos.queue[id] if (!old || hasDepsChange(deps, old.deps)) { const value = callback() current._memos.queue[id] = { value, deps, } return value } return old.value } autorun.effect = (callback: () => void | Dispose, dependencies?: any[]) => { if (!isFn(callback)) return const current = ReactionStack[ReactionStack.length - 1] if (!current || !current._effects) throw new Error('autorun.effect must used in autorun function body.') const effects = current._effects const deps = toArray(dependencies || [{}]) const id = effects.cursor++ const old = effects.queue[id] if (!old || hasDepsChange(deps, old.deps)) { Promise.resolve(0).then(() => { if (current._disposed) return const dispose = callback() if (isFn(dispose)) { effects.queue[id].dispose = dispose } }) effects.queue[id] = { deps, } } } export const reaction = <T>( tracker: () => T, subscriber?: (value: T, oldValue: T) => void, options?: IReactionOptions<T> ) => { const realOptions = { name: 'Reaction', ...options, } const value: IValue = {} const dirtyCheck = () => { if (isFn(realOptions.equals)) return !realOptions.equals(value.oldValue, value.currentValue) return value.oldValue !== value.currentValue } const fireAction = () => { try { //如果untrack的话,会导致用户如果在scheduler里同步调用setState影响下次React渲染的依赖收集 batchStart() if (isFn(subscriber)) subscriber(value.currentValue, value.oldValue) } finally { batchEnd() } } const reaction: Reaction = () => { if (ReactionStack.indexOf(reaction) === -1) { releaseBindingReactions(reaction) try { ReactionStack.push(reaction) value.currentValue = tracker() } finally { ReactionStack.pop() } } } reaction._scheduler = (looping) => { looping() if (dirtyCheck()) fireAction() value.oldValue = value.currentValue } reaction._name = realOptions.name reaction() value.oldValue = value.currentValue if (realOptions.fireImmediately) { fireAction() } return () => { disposeBindingReactions(reaction) } } ``` -------------------------------------------------------------------------------- /packages/antd/src/password/PasswordStrength.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment } from 'react' import { ReactFC } from '@formily/react' import { isFn } from '@formily/shared' type ReactRenderPropsChildren<T = any> = | React.ReactNode | ((props: T) => React.ReactElement) interface IPasswordStrengthProps { value?: React.ReactText children?: ReactRenderPropsChildren<number> } const isNum = function (c) { return c >= 48 && c <= 57 } const isLower = function (c) { return c >= 97 && c <= 122 } const isUpper = function (c) { return c >= 65 && c <= 90 } const isSymbol = function (c) { return !(isLower(c) || isUpper(c) || isNum(c)) } const isLetter = function (c) { return isLower(c) || isUpper(c) } const getStrength = (val) => { if (!val) return 0 let num = 0 let lower = 0 let upper = 0 let symbol = 0 let MNS = 0 let rep = 0 let repC = 0 let consecutive = 0 let sequential = 0 const len = () => num + lower + upper + symbol const callme = () => { let re = num > 0 ? 1 : 0 re += lower > 0 ? 1 : 0 re += upper > 0 ? 1 : 0 re += symbol > 0 ? 1 : 0 if (re > 2 && len() >= 8) { return re + 1 } else { return 0 } } for (let i = 0; i < val.length; i++) { const c = val.charCodeAt(i) if (isNum(c)) { num++ if (i !== 0 && i !== val.length - 1) { MNS++ } if (i > 0 && isNum(val.charCodeAt(i - 1))) { consecutive++ } } else if (isLower(c)) { lower++ if (i > 0 && isLower(val.charCodeAt(i - 1))) { consecutive++ } } else if (isUpper(c)) { upper++ if (i > 0 && isUpper(val.charCodeAt(i - 1))) { consecutive++ } } else { symbol++ if (i !== 0 && i !== val.length - 1) { MNS++ } } let exists = false for (let j = 0; j < val.length; j++) { if (val[i] === val[j] && i !== j) { exists = true repC += Math.abs(val.length / (j - i)) } } if (exists) { rep++ const unique = val.length - rep repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC) } if (i > 1) { const last1 = val.charCodeAt(i - 1) const last2 = val.charCodeAt(i - 2) if (isLetter(c)) { if (isLetter(last1) && isLetter(last2)) { const v = val.toLowerCase() const vi = v.charCodeAt(i) const vi1 = v.charCodeAt(i - 1) const vi2 = v.charCodeAt(i - 2) if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) { sequential++ } } } else if (isNum(c)) { if (isNum(last1) && isNum(last2)) { if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { sequential++ } } } else { if (isSymbol(last1) && isSymbol(last2)) { if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { sequential++ } } } } } let sum = 0 const length = len() sum += 4 * length if (lower > 0) { sum += 2 * (length - lower) } if (upper > 0) { sum += 2 * (length - upper) } if (num !== length) { sum += 4 * num } sum += 6 * symbol sum += 2 * MNS sum += 2 * callme() if (length === lower + upper) { sum -= length } if (length === num) { sum -= num } sum -= repC sum -= 2 * consecutive sum -= 3 * sequential sum = sum < 0 ? 0 : sum sum = sum > 100 ? 100 : sum if (sum >= 80) { return 100 } else if (sum >= 60) { return 80 } else if (sum >= 40) { return 60 } else if (sum >= 20) { return 40 } else { return 20 } } export const PasswordStrength: ReactFC<IPasswordStrengthProps> = (props) => { if (isFn(props.children)) { return props.children(getStrength(String(props.value))) } else { return <Fragment>{props.children}</Fragment> } } ``` -------------------------------------------------------------------------------- /packages/next/src/password/PasswordStrength.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment } from 'react' import { ReactFC } from '@formily/react' import { isFn } from '@formily/shared' type ReactRenderPropsChildren<T = any> = | React.ReactNode | ((props: T) => React.ReactElement) interface IPasswordStrengthProps { value?: React.ReactText children?: ReactRenderPropsChildren<number> } const isNum = function (c) { return c >= 48 && c <= 57 } const isLower = function (c) { return c >= 97 && c <= 122 } const isUpper = function (c) { return c >= 65 && c <= 90 } const isSymbol = function (c) { return !(isLower(c) || isUpper(c) || isNum(c)) } const isLetter = function (c) { return isLower(c) || isUpper(c) } const getStrength = (val) => { if (!val) return 0 let num = 0 let lower = 0 let upper = 0 let symbol = 0 let MNS = 0 let rep = 0 let repC = 0 let consecutive = 0 let sequential = 0 const len = () => num + lower + upper + symbol const callme = () => { let re = num > 0 ? 1 : 0 re += lower > 0 ? 1 : 0 re += upper > 0 ? 1 : 0 re += symbol > 0 ? 1 : 0 if (re > 2 && len() >= 8) { return re + 1 } else { return 0 } } for (let i = 0; i < val.length; i++) { const c = val.charCodeAt(i) if (isNum(c)) { num++ if (i !== 0 && i !== val.length - 1) { MNS++ } if (i > 0 && isNum(val.charCodeAt(i - 1))) { consecutive++ } } else if (isLower(c)) { lower++ if (i > 0 && isLower(val.charCodeAt(i - 1))) { consecutive++ } } else if (isUpper(c)) { upper++ if (i > 0 && isUpper(val.charCodeAt(i - 1))) { consecutive++ } } else { symbol++ if (i !== 0 && i !== val.length - 1) { MNS++ } } let exists = false for (let j = 0; j < val.length; j++) { if (val[i] === val[j] && i !== j) { exists = true repC += Math.abs(val.length / (j - i)) } } if (exists) { rep++ const unique = val.length - rep repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC) } if (i > 1) { const last1 = val.charCodeAt(i - 1) const last2 = val.charCodeAt(i - 2) if (isLetter(c)) { if (isLetter(last1) && isLetter(last2)) { const v = val.toLowerCase() const vi = v.charCodeAt(i) const vi1 = v.charCodeAt(i - 1) const vi2 = v.charCodeAt(i - 2) if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) { sequential++ } } } else if (isNum(c)) { if (isNum(last1) && isNum(last2)) { if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { sequential++ } } } else { if (isSymbol(last1) && isSymbol(last2)) { if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) { sequential++ } } } } } let sum = 0 const length = len() sum += 4 * length if (lower > 0) { sum += 2 * (length - lower) } if (upper > 0) { sum += 2 * (length - upper) } if (num !== length) { sum += 4 * num } sum += 6 * symbol sum += 2 * MNS sum += 2 * callme() if (length === lower + upper) { sum -= length } if (length === num) { sum -= num } sum -= repC sum -= 2 * consecutive sum -= 3 * sequential sum = sum < 0 ? 0 : sum sum = sum > 100 ? 100 : sum if (sum >= 80) { return 100 } else if (sum >= 60) { return 80 } else if (sum >= 40) { return 60 } else if (sum >= 20) { return 40 } else { return 20 } } export const PasswordStrength: ReactFC<IPasswordStrengthProps> = (props) => { if (isFn(props.children)) { return props.children(getStrength(String(props.value))) } else { return <Fragment>{props.children}</Fragment> } } ``` -------------------------------------------------------------------------------- /packages/react/docs/index.md: -------------------------------------------------------------------------------- ```markdown --- title: Formily-Alibaba unified front-end form solution order: 10 hero: title: React Library desc: Alibaba Unified Form Solution actions: - text: Home Site link: //formilyjs.org - text: Development Guide link: /guide features: - icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg title: Ultra High Performance desc: Dependency tracking, efficient update, on-demand rendering - icon: https://img.alicdn.com/imgextra/i2/O1CN016i72sH1c5wh1kyy9U_!!6000000003550-55-tps-800-800.svg title: Out Of The Box desc: The component status is automatically bound, and the access cost is extremely low - icon: https://img.alicdn.com/imgextra/i3/O1CN01JHzg8U1FZV5Mvt012_!!6000000000501-55-tps-800-800.svg title: JSON Schema Driver desc: Standard JSON-Schema - icon: https://img.alicdn.com/imgextra/i3/O1CN0194OqFF1ui6mMT4g7O_!!6000000006070-55-tps-800-800.svg title: Scene Reuse desc: Based on protocol-driven, abstract scene components - icon: https://img.alicdn.com/imgextra/i4/O1CN018vDmpl2186xdLu6KI_!!6000000006939-55-tps-800-800.svg title: Debugging Friendly desc: Natural docking with Formily DevTools - icon: https://img.alicdn.com/imgextra/i4/O1CN01u6jHgs1ZMwXpjAYnh_!!6000000003181-55-tps-800-800.svg title: Smart Tips desc: Embrace Typescript footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by self --- ## Installation ```bash $ npm install --save @formily/core @formily/react ``` ## Quick start ```tsx /** * defaultShowCode: true */ import React, { useMemo } from 'react' import { createForm, setValidateLanguage } from '@formily/core' import { FormProvider, FormConsumer, Field, useField, observer, } from '@formily/react' import { Input, Form } from 'antd' // FormItem UI component const FormItem = observer(({ children }) => { const field = useField() return ( <Form.Item label={field.title} help={field.selfErrors?.length ? field.selfErrors : undefined} extra={field.description} validateStatus={field.validateStatus} > {children} </Form.Item> ) }) /* * The above logic has been implemented in @formily/antd, and there is no need to rewrite it in actual use */ //Switch the built-in check internationalization copy to English setValidateLanguage('en') export default () => { const form = useMemo(() => createForm({ validateFirst: true })) const createPasswordEqualValidate = (equalName) => (field) => { if ( form.values.confirm_password && field.value && form.values[equalName] !== field.value ) { field.selfErrors = ['Password does not match Confirm Password.'] } else { field.selfErrors = [] } } return ( <FormProvider form={form}> <Form layout="vertical"> <Field name="name" title="Name" required decorator={[FormItem]} component={[Input, { placeholder: 'Please Input' }]} /> <Field name="password" title="Password" required decorator={[FormItem]} component={[Input, { type: 'password', placeholder: 'Please Input' }]} reactions={createPasswordEqualValidate('confirm_password')} /> <Field name="confirm_password" title="Confirm Password" required decorator={[FormItem]} component={[Input, { type: 'password', placeholder: 'Please Input' }]} reactions={createPasswordEqualValidate('password')} /> <code> <pre> <FormConsumer> {(form) => JSON.stringify(form.values, null, 2)} </FormConsumer> </pre> </code> </Form> </FormProvider> ) } ``` ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/index.md: -------------------------------------------------------------------------------- ```markdown # Ant Design ## Introduction @formily/antd is a professional component library for form scenarios based on Ant Design encapsulation. It has the following characteristics: - Only Formily 2.x is supported - Most components are not backward compatible - Unfortunately, many components of 1.x have inherent flaws in the API design. This is also because the form scheme has been explored, so there will be version breaks. - Richer component system - Layout components - FormLayout - FormItem - FormGrid - FormButtonGroup - Space - Submit - Reset - Input controls - Input - Password - Select - TreeSelect - DatePicker - TimePicker - NumberPicker - Transfer - Cascader - Radio - Checkbox - Upload - Switch - Scene components - ArrayCards - ArrayItems - ArrayTable - ArrayTabs - FormCollapse - FormStep - FormTab - FormDialog - FormDrawer - Editable - Reading state component - PreviewText - Theme customization ability - Completely abandon the 1.x styled-components solution, follow the style system of the component library, it is more convenient to customize the theme - Support secondary packaging - All components can be repackaged, and the 1.x component system cannot be repackaged, so providing this capability makes it more convenient for users to do business customization - Support reading mode - Although 1.x also supports reading mode, 2.x provides a separate PreviewText component, users can make reading mode encapsulation based on it, which is more flexible - Type is more friendly - Each component has an extremely complete type definition, and users can feel an unprecedented intelligent reminder experience during the actual development process - More complete layout control capabilities - 1.x's layout capabilities have basically converged to FormMegaLayout. This time, we directly removed Mega. Mega is a standard component and is completely internalized into FormLayout and FormItem components. At the same time, MegaLayout's grid layout capabilities are placed in FormGrid components. In, it also provides smarter layout capabilities. - More elegant and easy-to-use APIs, such as: - FormStep in the past has many problems. First, the type is not friendly. Second, the API is too hidden. To control the forward and backwards, you need to understand a bunch of private events. In the new version of FormStep, users only need to pay attention to the FormStep Reactive Model. You can create a Reactive Model through createFormStep and pass it to the FormStep component to quickly communicate. Similarly, FormTab/FormCollapse is the same communication mode. - Pop-up forms, drawer forms, presumably in the past, users had to write a lot of code on these two scenarios almost every time. This time, an extremely simple API is directly provided for users to use, which maximizes development efficiency. ## Installation ```bash $ npm install --save antd moment $ npm install --save @formily/core @formily/react @formily/antd ``` ## Q/A Q: I want to package a set of component libraries by myself, what should I do? Answer: If it is an open source component library, you can directly participate in the project co-construction and provide PR. If it is a private component library in the enterprise, you can refer to the source code. The source code does not have too much complicated logic. Question: Why do components such as ArrayCards/ArrayTable/FormStep only support Schema mode and not pure JSX mode? Answer: This is the core advantage of Schema mode. With the help of protocols, we can do scene-based abstraction. On the contrary, pure JSX mode is limited by the unparseability of JSX. It is difficult for us to achieve UI-level scene-based abstraction. It's just an abstract hook. ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-table/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField :schema="schema" /> <Submit @submit="log">提交</Submit> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { Submit, FormItem, ArrayTable, Input, Editable } from '@formily/element' const fields = createSchemaField({ components: { FormItem, ArrayTable, Input, Editable, }, }) export default { components: { FormProvider, Submit, ...fields }, data() { const form = createForm() const schema = { type: 'object', properties: { array: { type: 'array', 'x-decorator': 'FormItem', 'x-component': 'ArrayTable', items: { type: 'object', properties: { column1: { type: 'void', 'x-component': 'ArrayTable.Column', 'x-component-props': { width: 80, title: 'Index', align: 'center', }, properties: { index: { type: 'void', 'x-component': 'ArrayTable.Index', }, }, }, column2: { type: 'void', 'x-component': 'ArrayTable.Column', 'x-component-props': { width: 200, title: 'A1' }, properties: { a1: { type: 'string', 'x-decorator': 'Editable', 'x-component': 'Input', }, }, }, column3: { type: 'void', 'x-component': 'ArrayTable.Column', 'x-component-props': { width: 200, title: 'A2' }, properties: { a2: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, column4: { type: 'void', 'x-component': 'ArrayTable.Column', 'x-component-props': { title: 'A3' }, properties: { a3: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, column5: { type: 'void', 'x-component': 'ArrayTable.Column', 'x-component-props': { title: 'Operations', prop: 'operations', width: 200, fixed: 'right', }, properties: { item: { type: 'void', 'x-component': 'FormItem', properties: { remove: { type: 'void', 'x-component': 'ArrayTable.Remove', }, moveDown: { type: 'void', 'x-component': 'ArrayTable.MoveDown', }, moveUp: { type: 'void', 'x-component': 'ArrayTable.MoveUp', }, }, }, }, }, }, }, properties: { add: { type: 'void', 'x-component': 'ArrayTable.Addition', title: '添加条目', }, }, }, }, } return { form, schema, } }, methods: { log(...v) { console.log(...v) }, }, } </script> ``` -------------------------------------------------------------------------------- /docs/guide/learn-formily.md: -------------------------------------------------------------------------------- ```markdown # How to learn Formily ## Study Suggestion To describe Formily in one sentence, it is an MVVM form solution that abstracts the form domain model. Therefore, if you want to use Formily in depth, you must learn and understand what Formily's domain model is like and what problems does it solve. After understanding the domain model, it is actually how to consume the view layer of this domain model. This layer only needs to look at the documentation of the specific components. ## About the documentation Because Formily’s learning costs are still relatively high, if you want to quickly understand the full picture of Formily, the most important thing is to read the documentation. It's just how to look at the document and where it will be more important. Below we give different document learning routes for different users. ### Entry-level user - Introduction, because you need to understand Formily's core ideas and whether it is suitable for your business scenario. - Quick start, learn how to use Formily in practice from the simplest example. - Component documentation/core library documentation, because Formily has already encapsulated most of the out-of-the-box components for you. If you encounter component-related problems, you can just check the component documentation just like looking up a dictionary. - Scenario case, starting from the specific scenario, see what is the best practice in this scenario. ### Advanced users - Digest the core concepts carefully and have a deeper understanding of Formily. - Advanced guide, mainly to learn more advanced usage methods, such as custom components, from simple custom components to super complex custom components. - Read component documents/core library documents at any time to deepen memory - For the details and best practices of custom component development, it is recommended to look directly at the source code of @formily/antd or @formily/next, because this is the boilerplate code and is closely related to the actual business scenario. ### Source code co-builder - Contribution guide, understand the most basic contribution posture. - Read the document, if you find that the document is defective, you can submit a PR to fix it. - Read the unit test to understand the implementation details corresponding to each test case. If you find that there are missing test cases, you can submit a PR. - Read the source code, if you find a bug in the source code, you can raise a PR. <Alert type="error"> Pay attention to modify the source code, you must bring unit tests </Alert> ## About the question If you encounter problems during the development process, it is recommended to use the search function at the top of the document to quickly search for the content of the document and solve it quickly. If you can’t find it, I recommend you to ask questions in the [forum](https://github.com/alibaba/formily/discussions). It is convenient to record. If you encounter a very urgent problem, you can help solve it in the Dingding group @白玄. **It is not recommended to ask various basic questions directly without reading the document, which is very inefficient** ## About the bug If you find behaviors that do not meet expectations during the development process and can be reproduced in the smallest case, you can submit an [issue](https://github.com/alibaba/formily/issues) to Formily It is strongly not recommended to record the problem in the issue, which will disrupt the information flow of Issue. At the same time, **be sure to bring the smallest reproducible link address when mentioning Issue**, so that developers can quickly locate the problem and fix it quickly, instead of Find bugs in a bunch of codes. ## About Feature Request If during the development process you find that some of Formily's designs are not good, or can be improved better, you can submit your own ideas in the [forum](https://github.com/alibaba/formily/discussions) ``` -------------------------------------------------------------------------------- /packages/validator/src/parser.ts: -------------------------------------------------------------------------------- ```typescript import { isArr, isBool, isFn, isStr } from '@formily/shared' import { ValidatorDescription, ValidatorFunction, ValidatorParsedFunction, Validator, IValidatorRules, isValidateResult, IValidatorOptions, } from './types' import { getValidateRules, getValidateLocale } from './registry' import { render } from './template' const getRuleMessage = (rule: IValidatorRules, type: string) => { if (rule.format) { return rule.message || getValidateLocale(rule.format) } return rule.message || getValidateLocale(type) } export const parseValidatorDescription = ( description: ValidatorDescription ): IValidatorRules => { if (!description) return {} let rules: IValidatorRules = {} if (isStr(description)) { rules.format = description } else if (isFn(description)) { rules.validator = description } else { rules = Object.assign(rules, description) } return rules } export const parseValidatorDescriptions = <Context = any>( validator: Validator<Context> ): IValidatorRules[] => { if (!validator) return [] const array = isArr(validator) ? validator : [validator] return array.map((description) => { return parseValidatorDescription(description) }) } export const parseValidatorRules = ( rules: IValidatorRules = {} ): ValidatorParsedFunction[] => { const getRulesKeys = (): string[] => { const keys = [] if ('required' in rules) { keys.push('required') } for (let key in rules) { if (key === 'required' || key === 'validator') continue keys.push(key) } if ('validator' in rules) { keys.push('validator') } return keys } const getContext = (context: any, value: any) => { return { ...rules, ...context, value, } } const createValidate = (callback: ValidatorFunction, message: string) => async (value: any, context: any) => { const context_ = getContext(context, value) try { const results = await callback( value, { ...rules, message }, context_, (message: string, scope: any) => { return render( { type: 'error', message, }, Object.assign(context_, scope) )?.message } ) if (isBool(results)) { if (!results) { return render( { type: 'error', message, }, context_ ) } return { type: 'error', message: undefined, } } else if (results) { if (isValidateResult(results)) { return render(results, context_) } return render( { type: 'error', message: results, }, context_ ) } return { type: 'error', message: undefined, } } catch (e) { return { type: 'error', message: e?.message || e, } } } return getRulesKeys().reduce((buf, key) => { const callback = getValidateRules(key) if (callback) { const validator = createValidate(callback, getRuleMessage(rules, key)) return buf.concat(validator) } return buf }, []) } export const parseValidator = <Context = any>( validator: Validator<Context>, options: IValidatorOptions = {} ) => { if (!validator) return [] const array = isArr(validator) ? validator : [validator] return array.reduce<ValidatorParsedFunction<Context>[]>( (buf, description) => { const rules = parseValidatorDescription(description) const triggerType = rules.triggerType ?? 'onInput' if (options?.triggerType && options.triggerType !== triggerType) return buf return rules ? buf.concat(parseValidatorRules(rules)) : buf }, [] ) } ``` -------------------------------------------------------------------------------- /docs/guide/contribution.md: -------------------------------------------------------------------------------- ```markdown # Contribution Guide ## Why become a contributor? Welcome to our community!**Formily** It is the only official open-source form framework announced by Alibaba. Its functions and quality are guaranteed. It has a large number of community users. Participating in contributions can make **Formily** stronger and allow more developers to enjoy a better experience of developing forms. we are very grateful to any people who initiated **Pull Request** for this project. ## What can I contribute? - Add&Update features - Add/Update unit test cases - Fix the existing issue - Documentation improvements - Other ## How to contribute? #### Pull Repository - Original repository: https://github.com/alibaba/formily - Target repository: fork to your own github  #### Pull Branch The original branch is alibaba/formily master, The branch after pulling should be quirkyshop/formily master > Note: The recommended branch name is [feat]-[name], [feat] is the type of this branch. Featdoc[other] is optional, and [name] is the name, just customize it. eg. unittest-core (meaning: add single test to the core) #### Submit Code The code style follows 2 spaces and no semicolons. Please do not include any console-related methods and debuggers in the code unless it is explained. After the development is completed, submit a pull request to the repository you forked. > Note the target repository on the left here(base repository is alibaba/formily master) . And then the doc-wiki of the current branch own repository on the right. #### PR Specification Reference documents: https://github.com/alibaba/formily/blob/master/.github/GIT_COMMIT_SPECIFIC.md - PR name: format: `<type>(<scope>): <subject>` For example: `feat(core): add unit test` - PR content: List the content of this change - PR requirements: the added feat content, as far as possible, make clear comments. And the corresponding single test coverage should be covered as much 关注梁帅抽大奖 possible. - BUGFIX requirements: If the modified issue is related to issues, please include the relevant issueID in the content. #### Review&Merge The review phase will enter a multi-review process,`@janryWang` is responsible for reviewing whether this change is merged, and other people will also participate in the discussion. The discussion will be stored in the PR of github, and the DingTalk group will also receive corresponding notifications. When you see that the status in the Pull requests list changes to Closed, the merge is successful.  #### Synchronize source repository changes to repository after fork ``` # First, add "upstream" to your branch, that is, the source repository $ git remote add upstream https://github.com/alibaba/formily.git # Get the latest changes to the source repository $ git fetch upstream # Synchronize the changes of the source repository to the local branch $ git pull upstream master [The current local target branch, if not filled in, the current branch will be] ``` #### Project Development ```bash $ cd formily $ yarn install # Install overall project dependencies $ yarn build # Build all projects $ yarn test # Perform unit tests ``` #### Development Document Main project document ```bash $ yarn start ``` Core project documentation ```bash $ yarn workspace @formily/core start ``` React project documentation ```bash $ yarn workspace @formily/react start ``` Vue project documentation ```bash $ yarn workspace @formily/vue start ``` Antd project documentation ```bash $ yarn workspace @formily/antd start ``` Fusion project documentation ```bash $ yarn workspace @formily/next start ``` Reactive project documentation ```bash $ yarn workspace @formily/reactive start ``` ``` -------------------------------------------------------------------------------- /packages/next/src/array-items/index.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { ArrayField } from '@formily/core' import { useField, observer, useFieldSchema, RecursionField, } from '@formily/react' import cls from 'classnames' import { SortableContainer, SortableElement, SortableContainerProps, SortableElementProps, } from 'react-sortable-hoc' import { ISchema } from '@formily/json-schema' import { usePrefixCls } from '../__builtins__' import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from '../array-base' type ComposedArrayItems = React.FC< React.PropsWithChildren< React.HTMLAttributes<HTMLDivElement> & IArrayBaseProps > > & ArrayBaseMixins & { Item?: React.FC< React.HTMLAttributes<HTMLDivElement> & { type?: 'card' | 'divide' } > } const SortableItem: React.FC< React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>> & SortableElementProps > = SortableElement( (props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => { const prefixCls = usePrefixCls('formily-array-items') return ( <div {...props} className={cls(`${prefixCls}-item`, props.className)}> {props.children} </div> ) } ) as any const SortableList: React.FC< React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>> & SortableContainerProps > = SortableContainer( (props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => { const prefixCls = usePrefixCls('formily-array-items') return ( <div {...props} className={cls(`${prefixCls}-list`, props.className)}> {props.children} </div> ) } ) as any const isAdditionComponent = (schema: ISchema) => { return schema['x-component']?.indexOf('Addition') > -1 } const useAddition = () => { const schema = useFieldSchema() return schema.reduceProperties((addition, schema, key) => { if (isAdditionComponent(schema)) { return <RecursionField schema={schema} name={key} /> } return addition }, null) } export const ArrayItems: ComposedArrayItems = observer((props) => { const field = useField<ArrayField>() const prefixCls = usePrefixCls('formily-array-items') const schema = useFieldSchema() const addition = useAddition() const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props const dataSource = Array.isArray(field.value) ? field.value : [] return ( <ArrayBase onAdd={onAdd} onCopy={onCopy} onRemove={onRemove} onMoveUp={onMoveUp} onMoveDown={onMoveDown} > <div {...props} onChange={() => {}} className={cls(prefixCls, props.className)} > <SortableList useDragHandle lockAxis="y" helperClass={`${prefixCls}-sort-helper`} onSortEnd={({ oldIndex, newIndex }) => { field.move(oldIndex, newIndex) }} > {dataSource?.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items return ( <ArrayBase.Item key={index} index={index} record={() => field.value?.[index]} > <SortableItem key={`item-${index}`} index={index}> <div className={`${prefixCls}-item-inner`}> <RecursionField schema={items} name={index} /> </div> </SortableItem> </ArrayBase.Item> ) })} </SortableList> {addition} </div> </ArrayBase> ) }) ArrayItems.displayName = 'ArrayItems' ArrayItems.Item = (props) => { const prefixCls = usePrefixCls('formily-array-items') return ( <div {...props} onChange={() => {}} className={cls(`${prefixCls}-${props.type || 'card'}`, props.className)} > {props.children} </div> ) } ArrayBase.mixin(ArrayItems) export default ArrayItems ``` -------------------------------------------------------------------------------- /packages/react/docs/api/components/ObjectField.md: -------------------------------------------------------------------------------- ```markdown --- order: 2 --- # ObjectField ## Description As @formily/core's [createObjectField](https://core.formilyjs.org/api/models/form#createobjectfield) React implementation, it is a bridge component specifically used to bind ViewModel and input controls, ObjectField component Property reference [IFieldFactoryProps](https://core.formilyjs.org/api/models/form#ifieldfactoryprops) <Alert> When we use the ObjectField component, we must remember to pass the name attribute. At the same time, use render props to organize sub-components </Alert> ## Signature ```ts type ObjectField = React.FC<React.PropsWithChildren<IFieldFactoryProps>> ``` ## Custom component use case ```tsx import React from 'react' import { createForm, ObjectField as ObjectFieldType } from '@formily/core' import { FormProvider, Field, ObjectField, useField, observer, } from '@formily/react' import { Input, Button, Space } from 'antd' const form = createForm() const ObjectComponent = observer(() => { const field = useField<ObjectFieldType>() return ( <> <div> {Object.keys(field.value || {}).map((key) => ( <div key={key} style={{ display: 'flex-block', marginBottom: 10 }}> <Space> <Field name={key} component={[Input, { placeholder: key }]} /> <Button onClick={() => { field.removeProperty(key) }} > Remove </Button> </Space> </div> ))} </div> <Space> <Field name="propertyName" basePath={''} required component={[Input, { placeholder: 'Property Name' }]} /> <Button onClick={() => { const name = form.values.propertyName if (name && !form.existValuesIn(`${field.path}.${name}`)) { field.addProperty(name, '') form.deleteValuesIn('propertyName') } }} > Add </Button> </Space> </> ) }) export default () => ( <FormProvider form={form}> <ObjectField name="object" component={[ObjectComponent]} /> </FormProvider> ) ``` ## RenderProps use cases ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, Field, ObjectField } from '@formily/react' import { Input, Button, Space } from 'antd' const form = createForm() export default () => ( <FormProvider form={form}> <ObjectField name="object"> {(field) => { return ( <> <div> {Object.keys(field.value || {}).map((key) => ( <div key={key} style={{ display: 'flex-block', marginBottom: 10 }} > <Space> <Field name={key} component={[Input, { placeholder: key }]} /> <Button onClick={() => { field.removeProperty(key) }} > Remove </Button> </Space> </div> ))} </div> <Space> <Field name="propertyName" basePath={''} required component={[Input, { placeholder: 'Property Name' }]} /> <Button onClick={() => { const name = form.values.propertyName if (name && !form.existValuesIn(`${field.path}.${name}`)) { field.addProperty(name, '') form.deleteValuesIn('propertyName') } }} > Add </Button> </Space> </> ) }} </ObjectField> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/vue/src/__tests__/form.spec.ts: -------------------------------------------------------------------------------- ```typescript import Vue from 'vue' import { render, fireEvent } from '@testing-library/vue' import { mount } from '@vue/test-utils' import { createForm } from '@formily/core' import { FormProvider, FormConsumer, Field, ObjectField, VoidField, } from '../vue2-components' import { defineComponent } from 'vue-demi' import { useParentForm, useField } from '../hooks' import { h } from 'vue-demi' Vue.component('FormProvider', FormProvider) Vue.component('FormConsumer', FormConsumer) Vue.component('ObjectField', ObjectField) Vue.component('VoidField', VoidField) Vue.component('Field', Field) const Input = defineComponent({ props: ['value'], setup(props, { attrs, listeners }) { const fieldRef = useField() return () => { const field = fieldRef.value return h('input', { class: 'test-input', attrs: { ...attrs, value: props.value, 'data-testid': field.path.toString(), }, on: { ...listeners, input: listeners.change, }, }) } }, }) test('render form', () => { const form = createForm() render({ data() { return { form } }, template: `<FormProvider :form="form"> <FormConsumer> <template #default="{ form }"> {{ form.mounted }} </template> </FormConsumer> <FormConsumer /> </FormProvider>`, }) expect(form.mounted).toBeTruthy() }) const DisplayParentForm = defineComponent({ setup() { const form = useParentForm() return () => h('div', [form.value.displayName]) }, }) test('useParentForm', () => { const { queryByTestId } = render({ components: { DisplayParentForm, }, data() { const form = createForm() return { form } }, template: `<FormProvider :form="form"> <ObjectField name="aa"> <Field name="bb"> <DisplayParentForm data-testid="111" /> </Field> </ObjectField> <VoidField name="cc"> <Field name="dd"> <DisplayParentForm data-testid="222" /> </Field> </VoidField> <DisplayParentForm data-testid="333" /> </FormProvider>`, }) expect(queryByTestId('111').textContent).toBe('ObjectField') expect(queryByTestId('222').textContent).toBe('Form') expect(queryByTestId('333').textContent).toBe('Form') }) test('useInjectionCleaner', async () => { const form = createForm() const { getByTestId } = render({ name: 'TestComponent', setup() { return { form, Input, } }, template: `<FormProvider :form="form"> <Field name="parent"> <FormProvider :form="form"> <Field name="inner" :component="[Input]" /> </FormProvider> <Field name="outer" :component="[Input]" /> </Field> </FormProvider>`, }) expect(form.mounted).toBeTruthy() expect(form.query('inner').take().mounted).toBeTruthy() expect(form.query('parent.outer').take().mounted).toBeTruthy() await fireEvent.update(getByTestId('parent.outer'), '123') expect(form.getValuesIn('parent.outer')).toBe('123') await fireEvent.update(getByTestId('inner'), '123') expect(form.getValuesIn('inner')).toBe('123') }) test('FormConsumer', async () => { const form = createForm({ values: { a: 'abc', }, }) const wrapper = mount({ data() { return { form, Input } }, template: `<FormProvider :form="form"> <Field name="a" :component="[Input]" /> <FormConsumer ref="consumer"> <template #default="{ form }"> <div class="consumer">{{JSON.stringify(form.values)}}</div> </template> </FormConsumer> </FormProvider>`, }) expect(form.getValuesIn('a')).toBe('abc') expect(wrapper.find('.consumer').text()).toBe('{"a":"abc"}') form.setDisplay('none') expect(form.getValuesIn('a')).toBeUndefined() const $consumer = wrapper.vm.$refs.consumer as Vue $consumer.$forceUpdate() expect(wrapper.find('.consumer').text()).toBe('{}') }) ``` -------------------------------------------------------------------------------- /packages/element/src/form-grid/index.ts: -------------------------------------------------------------------------------- ```typescript import { Grid, IGridOptions } from '@formily/grid' import { markRaw } from '@formily/reactive' import { observer } from '@formily/reactive-vue' import { h } from '@formily/vue' import { computed, defineComponent, inject, InjectionKey, onMounted, PropType, provide, ref, Ref, watchEffect, } from 'vue-demi' import { useFormLayout } from '../form-layout' import { stylePrefix } from '../__builtins__/configs' import { composeExport } from '../__builtins__/shared' export interface IFormGridProps extends IGridOptions { grid?: Grid<HTMLElement> prefixCls?: string className?: string style?: React.CSSProperties } const FormGridSymbol: InjectionKey<Ref<Grid<HTMLElement>>> = Symbol('FormGridContext') interface GridColumnProps { gridSpan: number } export const createFormGrid = (props: IFormGridProps): Grid<HTMLElement> => { return markRaw(new Grid(props)) } export const useFormGrid = (): Ref<Grid<HTMLElement>> => inject(FormGridSymbol) /** * @deprecated */ const useGridSpan = (gridSpan: number) => { return gridSpan } /** * @deprecated */ export const useGridColumn = (gridSpan = 1) => { return gridSpan } const FormGridInner = observer( defineComponent({ name: 'FFormGrid', props: { columnGap: { type: Number, }, rowGap: { type: Number, }, minColumns: { type: [Number, Array], }, minWidth: { type: [Number, Array], }, maxColumns: { type: [Number, Array], }, maxWidth: { type: [Number, Array], }, breakpoints: { type: Array, }, colWrap: { type: Boolean, default: true, }, strictAutoFit: { type: Boolean, default: false, }, shouldVisible: { type: Function as PropType<IGridOptions['shouldVisible']>, default() { return () => true }, }, grid: { type: Object as PropType<Grid<HTMLElement>>, }, }, setup(props: IFormGridProps) { const layout = useFormLayout() const gridInstance = computed(() => { const newProps: IFormGridProps = {} Object.keys(props).forEach((key) => { if (typeof props[key] !== 'undefined') { newProps[key] = props[key] } }) const options = { columnGap: layout.value?.gridColumnGap ?? 8, rowGap: layout.value?.gridRowGap ?? 4, ...newProps, } return markRaw(options?.grid ? options.grid : new Grid(options)) }) const prefixCls = `${stylePrefix}-form-grid` const root = ref(null) provide(FormGridSymbol, gridInstance) onMounted(() => { watchEffect((onInvalidate) => { const dispose = gridInstance.value.connect(root.value) onInvalidate(() => { dispose() }) }) }) return { prefixCls, root, gridInstance, } }, render() { const { prefixCls, gridInstance } = this return h( 'div', { attrs: { class: `${prefixCls}`, }, style: { gridTemplateColumns: gridInstance.templateColumns, gap: gridInstance.gap, }, ref: 'root', }, { default: () => this.$slots.default, } ) }, }) ) as any const FormGridColumn = observer( defineComponent({ name: 'FFormGridColumn', props: { gridSpan: { type: Number, default: 1, }, }, setup(props: GridColumnProps, { slots }) { return () => { return h( 'div', { attrs: { 'data-grid-span': props.gridSpan, }, }, slots ) } }, }) ) export const FormGrid = composeExport(FormGridInner, { GridColumn: FormGridColumn, useGridSpan, useFormGrid, createFormGrid, }) export default FormGrid ``` -------------------------------------------------------------------------------- /docs/guide/advanced/business-logic.md: -------------------------------------------------------------------------------- ```markdown # Manage Business Logic In the previous document, we can actually find that Formily has provided the ability to describe the logic locally, that is, the x-reactions/reactions property of the field component. And in Schema, x-reactions can pass both functions and a structured object. Of course, there are also effects inherited from Formily 1.x, So to summarize, the ways to describe logic in Formily 2.x are: - Effects or reactions property in pure JSX mode - Effects or structured x-reactions property in Schema mode - Effects or functional x-reactions property in Schema mode With so many ways of describing logic, how should we choose? What scenarios are best practices? First, we need to understand the positioning of effects and reactions. First of all, reactions are responders used on specific field properties. They will be executed repeatedly based on the data changes that the function depends on. Its biggest advantage is that it is simple, straightforward and easy to understand, such as: ```tsx pure /* eslint-disable */ <Field name="A" reactions={(field) => { /**specific logic implementation**/ }} /> ``` Then, effects are used to implement the side-effect isolation logic management model. Its biggest advantage is that it can make the view code easier to maintain in a scenario with a large number of fields. At the same time, it also has the ability to process fields in batches. For example, we declare x-reactions in the field properties of A, B, C. If the x-reactions logic of these three fields are exactly the same, then we only need to write this in effects: ```ts onFieldReact('*(A,B,C)', (field) => { //...logic }) ``` Another advantage of using effects is that a series of reusable logic plug-ins can be implemented, which can be very convenient logic pluggable, and at the same time can do some things like global monitoring. In this way, do we not need to define the logic locally? No, the premise of the above writing is that for a large number of fields, if the view layer is full of reactions, it looks uncomfortable, so it is a better strategy to consider extracting logic from unified maintenance. On the contrary, if the number of fields is small and the logic is relatively simple, it is also good to write reactions directly on the field attributes, which is clear. At the same time, because JSON Schema can be consumed by the configuration system, we need to logically configure a specific field on the configuration interface. So we still need to support local definition logic capabilities, and also need to support structured description logic, such as: ```json { "x-reactions": { "dependencies": ["aa"], "fulfill": { "state": { "visible": "{{$deps[0] == '123'}}" } } } } ``` This can well solve the linkage requirements of most configuration scenarios. However, there is another scenario, that is, our linkage process is asynchronous, the logic is very complicated, or there is a large amount of data processing, then we can only consider open up the ability to describe functional states, such as: ```json { "x-reactions": "{{(field)=>{/**specific logic implementation**/}}}" } ``` This is very similar to a low-code configuration. Of course, we can also register a series of general logic functions in the context scope: ```json { "x-reactions": "{{customFunction}}" } ``` In conclusion, the way we manage business logic has the following priorities: - Pure source mode - The number of fields is huge and the logic is complex, and the logic defined in effects is preferred. - The number of fields is small, the logic is simple, and the logic defined in reactions is preferred - Schema mode - There is no asynchronous logic, structured reactions are preferred to define logic. - There is asynchronous logic, or a large number of calculations, the functional state reactions are preferred to define logic. For how to play with effects in effects, we mainly look at the [@formily/core](https://core.formilyjs.org) document. ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-grid/native.vue: -------------------------------------------------------------------------------- ```vue <template> <div> <p>maxColumns 3 + minColumns 2</p> <FormGrid :maxColumns="3" :minColumns="2" :columnGap="4"> <FormGridColumn :gridSpan="4"> <Cell>1</Cell> </FormGridColumn> <FormGridColumn> <Cell>2</Cell> </FormGridColumn> <FormGridColumn> <Cell>3</Cell> </FormGridColumn> <FormGridColumn> <Cell>4</Cell> </FormGridColumn> <FormGridColumn> <Cell>5</Cell> </FormGridColumn> <FormGridColumn> <Cell>6</Cell> </FormGridColumn> </FormGrid> <p>maxColumns 3</p> <FormGrid :maxColumns="3" :columnGap="4"> <FormGridColumn :gridSpan="2"> <Cell>1</Cell> </FormGridColumn> <FormGridColumn> <Cell>2</Cell> </FormGridColumn> <FormGridColumn> <Cell>3</Cell> </FormGridColumn> <FormGridColumn> <Cell>4</Cell> </FormGridColumn> <FormGridColumn> <Cell>5</Cell> </FormGridColumn> <FormGridColumn> <Cell>6</Cell> </FormGridColumn> </FormGrid> <p>minColumns 2</p> <FormGrid :minColumns="2" :columnGap="4"> <FormGridColumn :gridSpan="2"> <Cell>1</Cell> </FormGridColumn> <FormGridColumn> <Cell>2</Cell> </FormGridColumn> <FormGridColumn> <Cell>3</Cell> </FormGridColumn> <FormGridColumn> <Cell>4</Cell> </FormGridColumn> <FormGridColumn> <Cell>5</Cell> </FormGridColumn> <FormGridColumn> <Cell>6</Cell> </FormGridColumn> </FormGrid> <p>Null</p> <FormGrid :columnGap="4"> <FormGridColumn :gridSpan="2"> <Cell>1</Cell> </FormGridColumn> <FormGridColumn> <Cell>2</Cell> </FormGridColumn> <FormGridColumn> <Cell>3</Cell> </FormGridColumn> <FormGridColumn> <Cell>4</Cell> </FormGridColumn> <FormGridColumn> <Cell>5</Cell> </FormGridColumn> <FormGridColumn> <Cell>6</Cell> </FormGridColumn> </FormGrid> <p>minWidth 150 +maxColumns 3</p> <FormGrid :minWidth="150" :maxColumns="3" :columnGap="4"> <FormGridColumn :gridSpan="2"> <Cell>1</Cell> </FormGridColumn> <FormGridColumn> <Cell>2</Cell> </FormGridColumn> <FormGridColumn> <Cell>3</Cell> </FormGridColumn> <FormGridColumn> <Cell>4</Cell> </FormGridColumn> <FormGridColumn> <Cell>5</Cell> </FormGridColumn> <FormGridColumn> <Cell>6</Cell> </FormGridColumn> </FormGrid> <p>maxWidth 120+minColumns 2</p> <FormGrid :maxWidth="120" :minColumns="2" :columnGap="4"> <FormGridColumn :gridSpan="2"> <Cell>1</Cell> </FormGridColumn> <FormGridColumn> <Cell>2</Cell> </FormGridColumn> <FormGridColumn> <Cell>3</Cell> </FormGridColumn> <FormGridColumn> <Cell>4</Cell> </FormGridColumn> <FormGridColumn> <Cell>5</Cell> </FormGridColumn> <FormGridColumn> <Cell>6</Cell> </FormGridColumn> </FormGrid> <p>maxWidth 120 + gridSpan -1</p> <FormGrid :maxWidth="120" :columnGap="4"> <FormGridColumn :gridSpan="2"> <Cell>1</Cell> </FormGridColumn> <FormGridColumn> <Cell>2</Cell> </FormGridColumn> <FormGridColumn :gridSpan="-1"> <Cell>3</Cell> </FormGridColumn> </FormGrid> </div> </template> <script> import { FormGrid } from '@formily/element' const Cell = { functional: true, render(h, context) { return h( 'div', { style: { backgroundColor: '#AAA', color: '#FFF', height: '30px', display: 'flex', alignItems: 'center', padding: '0 10px', }, }, context.children ) }, } export default { components: { FormGrid, FormGridColumn: FormGrid.GridColumn, Cell }, } </script> ``` -------------------------------------------------------------------------------- /packages/benchmark/src/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useMemo, useState } from 'react' import ReactDOM from 'react-dom' import { createForm } from '@formily/core' import { Field, createSchemaField } from '@formily/react' import { Input, Form, FormItem } from '@formily/antd' import { Form as AntdForm, Input as AntdInput } from 'antd' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const PureAntdInput = () => { return ( <> {Array.from({ length: 2000 }).map((_, i) => { return <AntdInput key={i} /> })} </> ) } const PureAntd = () => { return ( <AntdForm> <h1>Please pay attention to the performance of the form input</h1> {Array.from({ length: 2000 }).map((_, i) => { return ( <AntdForm.Item key={i} name={`name_${i}`} required label={`name ${i + 1}`} > <AntdInput /> </AntdForm.Item> ) })} </AntdForm> ) } const PureJSX = () => { const form = useMemo(() => createForm(), []) return ( <Form form={form}> <h1>Please pay attention to the performance of the form input</h1> {Array.from({ length: 2000 }).map((_, i) => { return ( <Field key={i} name={`name_${i}`} title={`name ${i + 1}`} required decorator={[FormItem]} component={[Input, { placeholder: 'Please Input' }]} /> ) })} </Form> ) } const PureJSONSchema = () => { const form = useMemo(() => createForm(), []) const schema = { type: 'object', properties: {}, } Array.from({ length: 2000 }).forEach((_, i) => { schema.properties[`name_${i}`] = { type: 'string', title: `name ${i + 1}`, 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { placeholder: 'Please Input', }, } }) return ( <Form form={form}> <h1>Please pay attention to the performance of the form input</h1> <SchemaField schema={schema} /> </Form> ) } const PureMarkupSchema = () => { const form = useMemo(() => createForm(), []) return ( <Form form={form}> <h1>Please pay attention to the performance of the form input</h1> <SchemaField> {Array.from({ length: 2000 }).map((_, i) => { return ( <SchemaField.String key={i} name={`name_${i}`} title={`name ${i + 1}`} required x-decorator="FormItem" x-component="Input" x-component-props={{ placeholder: 'Please Input', }} /> ) })} </SchemaField> </Form> ) } const App = () => { const [visibleAntd, setVisibleAntd] = useState(false) const [visibleJSX, setVisibleJSX] = useState(false) const [visibleMarkupSchema, setVisibleMarkupSchema] = useState(false) const [visibleJSONSchema, setVisibleJSONSchema] = useState(false) const [visibleAntdInput, setVisibleAntdInput] = useState(false) return ( <div> <button onClick={() => { setVisibleJSX(!visibleJSX) }} > Show JSX </button> <button onClick={() => { setVisibleMarkupSchema(!visibleMarkupSchema) }} > Show Markup Schema </button> <button onClick={() => { setVisibleJSONSchema(!visibleJSONSchema) }} > Show JSON Schema </button> <button onClick={() => { setVisibleAntd(!visibleAntd) }} > Show Antd </button> <button onClick={() => { setVisibleAntdInput(!visibleAntdInput) }} > Show Antd Input </button> {visibleJSX && <PureJSX />} {visibleMarkupSchema && <PureMarkupSchema />} {visibleJSONSchema && <PureJSONSchema />} {visibleAntd && <PureAntd />} {visibleAntdInput && <PureAntdInput />} </div> ) } ReactDOM.render(<App />, document.getElementById('root')) ``` -------------------------------------------------------------------------------- /packages/element/src/form-layout/index.ts: -------------------------------------------------------------------------------- ```typescript import { h } from '@formily/vue' import { defineComponent, inject, InjectionKey, provide, Ref, ref, watch, } from 'vue-demi' import { stylePrefix } from '../__builtins__/configs' import { useCompatRef } from '../__builtins__/shared' import { useResponsiveFormLayout } from './useResponsiveFormLayout' export type FormLayoutProps = { className?: string colon?: boolean labelAlign?: 'right' | 'left' | ('right' | 'left')[] wrapperAlign?: 'right' | 'left' | ('right' | 'left')[] labelWrap?: boolean labelWidth?: number wrapperWidth?: number wrapperWrap?: boolean labelCol?: number | number[] wrapperCol?: number | number[] fullness?: boolean size?: 'small' | 'default' | 'large' layout?: | 'vertical' | 'horizontal' | 'inline' | ('vertical' | 'horizontal' | 'inline')[] direction?: 'rtl' | 'ltr' shallow?: boolean feedbackLayout?: 'loose' | 'terse' | 'popover' tooltipLayout?: 'icon' | 'text' bordered?: boolean breakpoints?: number[] inset?: boolean spaceGap?: number gridColumnGap?: number gridRowGap?: number } export const FormLayoutDeepContext: InjectionKey<Ref<FormLayoutProps>> = Symbol( 'FormLayoutDeepContext' ) export const FormLayoutShallowContext: InjectionKey<Ref<FormLayoutProps>> = Symbol('FormLayoutShallowContext') export const useFormDeepLayout = (): Ref<FormLayoutProps> => inject(FormLayoutDeepContext, ref({})) export const useFormShallowLayout = (): Ref<FormLayoutProps> => inject(FormLayoutShallowContext, ref({})) export const useFormLayout = (): Ref<FormLayoutProps> => { const shallowLayout = useFormShallowLayout() const deepLayout = useFormDeepLayout() const formLayout = ref({ ...deepLayout.value, ...shallowLayout.value, }) watch( [shallowLayout, deepLayout], () => { formLayout.value = { ...deepLayout.value, ...shallowLayout.value, } }, { deep: true, } ) return formLayout } export const FormLayout = defineComponent<FormLayoutProps>({ name: 'FFormLayout', props: { className: {}, colon: { default: true }, labelAlign: {}, wrapperAlign: {}, labelWrap: { default: false }, labelWidth: {}, wrapperWidth: {}, wrapperWrap: { default: false }, labelCol: {}, wrapperCol: {}, fullness: { default: false }, size: { default: 'default' }, layout: { default: 'horizontal' }, direction: { default: 'ltr' }, shallow: { default: true }, feedbackLayout: {}, tooltipLayout: {}, bordered: { default: true }, inset: { default: false }, breakpoints: {}, spaceGap: {}, gridColumnGap: {}, gridRowGap: {}, }, setup(customProps, { slots, refs }) { const { elRef: root, elRefBinder } = useCompatRef(refs) const { props } = useResponsiveFormLayout(customProps, root) const deepLayout = useFormDeepLayout() const newDeepLayout = ref({ ...deepLayout, }) const shallowProps = ref({}) watch( [props, deepLayout], () => { shallowProps.value = props.value.shallow ? props.value : undefined if (!props.value.shallow) { Object.assign(newDeepLayout.value, props.value) } else { if (props.value.size) { newDeepLayout.value.size = props.value.size } if (props.value.colon) { newDeepLayout.value.colon = props.value.colon } } }, { deep: true, immediate: true } ) provide(FormLayoutDeepContext, newDeepLayout) provide(FormLayoutShallowContext, shallowProps) const formPrefixCls = `${stylePrefix}-form` return () => { const classNames = { [`${formPrefixCls}-${props.value.layout}`]: true, [`${formPrefixCls}-rtl`]: props.value.direction === 'rtl', [`${formPrefixCls}-${props.value.size}`]: props.value.size !== undefined, [`${props.value.className}`]: props.value.className !== undefined, } return h( 'div', { ref: elRefBinder, class: classNames, }, slots ) } }, }) export default FormLayout ``` -------------------------------------------------------------------------------- /packages/element/src/array-tabs/index.ts: -------------------------------------------------------------------------------- ```typescript import { ArrayField } from '@formily/core' import { observer } from '@formily/reactive-vue' import { h, RecursionField, useField, useFieldSchema } from '@formily/vue' import { Badge, TabPane, Tabs } from 'element-ui' import { defineComponent, ref } from 'vue-demi' import { stylePrefix } from '../__builtins__/configs' import type { Tabs as TabsProps } from 'element-ui' export const ArrayTabs = observer( defineComponent<TabsProps>({ name: 'ArrayTabs', props: [], setup(props, { attrs, listeners }) { const fieldRef = useField<ArrayField>() const schemaRef = useFieldSchema() const prefixCls = `${stylePrefix}-array-tabs` const activeKey = ref('tab-0') return () => { const field = fieldRef.value const schema = schemaRef.value 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) } activeKey.value = `tab-${id}` } else if (type == 'remove') { const index = targetKey.match(/-(\d+)/)?.[1] field.remove(Number(index)) if (activeKey.value === targetKey) { activeKey.value = `tab-${index - 1}` } } } const badgedTab = (index: number) => { const tab = `${field.title || 'Untitled'} ${index + 1}` const path = field.address.concat(index) const errors = field.form.queryFeedbacks({ type: 'error', address: `${path}.**`, }) if (errors.length) { return h( 'span', {}, { default: () => [ h( Badge, { class: [`${prefixCls}-errors-badge`], props: { value: errors.length, }, }, { default: () => [tab], } ), ], } ) } return h( 'span', {}, { default: () => [tab], } ) } const renderItems = () => dataSource?.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] : schema.items const key = `tab-${index}` return h( TabPane, { key, attrs: { closable: index !== 0, name: key, }, }, { default: () => h( RecursionField, { props: { schema: items, name: index, }, }, {} ), label: () => [badgedTab(index)], } ) }) return h( Tabs, { class: [prefixCls], attrs: { ...attrs, type: 'card', value: activeKey.value, addable: true, }, on: { ...listeners, input: (key) => { activeKey.value = key }, 'tab-remove': (target) => { onEdit(target, 'remove') listeners?.['tab-remove']?.(target) }, 'tab-add': () => { onEdit(null, 'add') listeners?.['tab-add']?.() }, }, }, { default: () => [renderItems()], } ) } }, }) ) export default ArrayTabs ``` -------------------------------------------------------------------------------- /packages/next/docs/components/index.md: -------------------------------------------------------------------------------- ```markdown # Alibaba Fusion ## Introduction @formily/next is a professional component library for form scenarios based on Fusion Design encapsulation. It has the following characteristics: - Only Formily 2.x is supported - Most components are not backward compatible - Unfortunately, many components of 1.x have inherent flaws in the API design. This is also because the form scheme has been explored, so there will be version breaks. - Richer component system - Layout components - FormLayout - FormItem - FormGrid - FormButtonGroup - Space - Submit - Reset - Input controls - Input - Password - Select - TreeSelect - DatePicker - TimePicker - NumberPicker - Transfer - Cascader - Radio - Checkbox - Upload - Switch - Scene components - ArrayCards - ArrayItems - ArrayTable - FormCollapse - FormStep - FormTab - FormDialog - FormDrawer - Editable - LogicDiagram - Reading state component - PreviewText - Theme customization ability - Completely abandon the 1.x styled-components solution, follow the style system of the component library, it is more convenient to customize the theme - Support secondary packaging - All components can be repackaged, and the 1.x component system cannot be repackaged, so providing this capability makes it more convenient for users to do business customization - Support reading mode - Although 1.x also supports reading mode, 2.x provides a separate PreviewText component, users can make reading mode encapsulation based on it, which is more flexible - Type is more friendly - Each component has an extremely complete type definition, and users can feel an unprecedented intelligent reminder experience during the actual development process - More complete layout control capabilities - 1.x's layout capabilities have basically converged to FormMegaLayout. This time, we directly removed Mega. Mega is a standard component and is completely internalized into FormLayout and FormItem components. At the same time, MegaLayout's grid layout capabilities are placed in FormGrid components. In, it also provides smarter layout capabilities. - More elegant and easy-to-use APIs, such as: - FormStep in the past has many problems. First, the type is not friendly. Second, the API is too hidden. To control the forward and backwards, you need to understand a bunch of private events. In the new version of FormStep, users only need to pay attention to the FormStep Reactive Model. You can create a Reactive Model through createFormStep and pass it to the FormStep component to quickly communicate. Similarly, FormTab/FormCollapse is the same communication mode. - Pop-up forms, drawer forms, presumably in the past, users had to write a lot of code on these two scenarios almost every time. This time, an extremely simple API is directly provided for users to use, which maximizes development efficiency. ## Note Because Fusion is built on Sass, if you use Webpack configuration, please use the following two Sass tools ``` "sass": "^1.32.11", "sass-loader": "^8.0.2" ``` ## Installation ```bash $ npm install --save @alifd/next moment $ npm install --save @formily/next @formily/react ``` ## Q/A Q: I want to package a set of component libraries by myself, what should I do? Answer: If it is an open source component library, you can directly participate in the project co-construction and provide PR. If it is a private component library in the enterprise, you can refer to the source code. The source code does not have too much complicated logic. Question: Why do components such as ArrayCards/ArrayTable/FormStep only support Schema mode and not pure JSX mode? Answer: This is the core advantage of Schema mode. With the help of protocols, we can do scene-based abstraction. On the contrary, pure JSX mode is limited by the unparseability of JSX. It is difficult for us to achieve UI-level scene-based abstraction. It's just an abstract hook. Q: Why is there no ArrayTabs component? Answer: Because Fusion's Tab component does not support the ability to add Tabs, the ArrayTabs component is temporarily not supported. ``` -------------------------------------------------------------------------------- /packages/antd/src/form-step/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment } from 'react' import { define, observable, action, markRaw, model } from '@formily/reactive' import { Steps } from 'antd' import cls from 'classnames' import { StepsProps, StepProps } from 'antd/lib/steps' import { Form, VoidField } from '@formily/core' import { connect, useField, observer, useFieldSchema, RecursionField, } from '@formily/react' import { Schema, SchemaKey } from '@formily/json-schema' import { usePrefixCls } from '../__builtins__' export interface IFormStep { connect: (steps: SchemaStep[], field: VoidField) => void current: number allowNext: boolean allowBack: boolean setCurrent(key: number): void submit: Form['submit'] next(): void back(): void } export interface IFormStepProps extends StepsProps { formStep?: IFormStep } type ComposedFormStep = React.FC<React.PropsWithChildren<IFormStepProps>> & { StepPane: React.FC<React.PropsWithChildren<StepProps>> createFormStep: (defaultCurrent?: number) => IFormStep } type SchemaStep = { name: SchemaKey props: any schema: Schema } type FormStepEnv = { form: Form field: VoidField steps: SchemaStep[] } const parseSteps = (schema: Schema) => { const steps: SchemaStep[] = [] schema.mapProperties((schema, name) => { if (schema['x-component']?.indexOf('StepPane') > -1) { steps.push({ name, props: schema['x-component-props'], schema, }) } }) return steps } const createFormStep = (defaultCurrent = 0): IFormStep => { const env: FormStepEnv = define( { form: null, field: null, steps: [], }, { form: observable.ref, field: observable.ref, steps: observable.shallow, } ) const setDisplay = action.bound((target: number) => { const currentStep = env.steps[target] env.steps.forEach(({ name }) => { env.form.query(`${env.field.address}.${name}`).take((field) => { if (name === currentStep.name) { field.setDisplay('visible') } else { field.setDisplay('hidden') } }) }) }) const next = action.bound(() => { if (formStep.allowNext) { formStep.setCurrent(formStep.current + 1) } }) const back = action.bound(() => { if (formStep.allowBack) { formStep.setCurrent(formStep.current - 1) } }) const formStep: IFormStep = model({ connect(steps, field) { env.steps = steps env.form = field?.form env.field = field }, current: defaultCurrent, setCurrent(key: number) { setDisplay(key) formStep.current = key }, get allowNext() { return formStep.current < env.steps.length - 1 }, get allowBack() { return formStep.current > 0 }, async next() { try { await env.form.validate() if (env.form.valid) { next() } } catch {} }, async back() { back() }, async submit(onSubmit) { return env.form?.submit?.(onSubmit) }, }) return markRaw(formStep) } export const FormStep = connect( observer(({ formStep, className, ...props }: IFormStepProps) => { const field = useField<VoidField>() const prefixCls = usePrefixCls('formily-step', props) const schema = useFieldSchema() const steps = parseSteps(schema) const current = props.current || formStep?.current || 0 formStep?.connect?.(steps, field) return ( <div className={cls(prefixCls, className)}> <Steps {...props} style={{ marginBottom: 10, ...props.style }} current={current} > {steps.map(({ props }, key) => { return <Steps.Step {...props} key={key} /> })} </Steps> {steps.map(({ name, schema }, key) => { if (key !== current) return return <RecursionField key={key} name={name} schema={schema} /> })} </div> ) }) ) as unknown as ComposedFormStep const StepPane: React.FC<React.PropsWithChildren<StepProps>> = ({ children, }) => { return <Fragment>{children}</Fragment> } FormStep.StepPane = StepPane FormStep.createFormStep = createFormStep export default FormStep ```