This is page 8 of 35. Use http://codebase.md/alibaba/formily?page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/next/src/__builtins__/empty.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' export const Empty = () => { return ( <div className="next-empty"> <div className="next-empty-image"> <svg className="ant-empty-img-default" width="184" height="120" viewBox="0 0 184 152" xmlns="http://www.w3.org/2000/svg" > <g fill="none" fillRule="evenodd"> <g transform="translate(24 31.67)"> <ellipse className="ant-empty-img-default-ellipse" cx="67.797" cy="106.89" rx="67.797" ry="12.668" ></ellipse> <path className="ant-empty-img-default-path-1" d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z" ></path> <path className="ant-empty-img-default-path-2" d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z" transform="translate(13.56)" ></path> <path className="ant-empty-img-default-path-3" d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z" ></path> <path className="ant-empty-img-default-path-4" d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z" ></path> </g> <path className="ant-empty-img-default-path-5" d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z" ></path> <g className="ant-empty-img-default-g" transform="translate(149.65 15.383)" > <ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815"></ellipse> <path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"></path> </g> </g> </svg> </div> </div> ) } ``` -------------------------------------------------------------------------------- /packages/next/src/date-picker/index.tsx: -------------------------------------------------------------------------------- ```typescript import moment from 'moment' import { connect, mapProps, mapReadPretty } from '@formily/react' import { DatePicker as NextDatePicker } from '@alifd/next' import { DatePickerProps as NextDatePickerProps, MonthPickerProps, YearPickerProps, RangePickerProps, } from '@alifd/next/lib/date-picker' import { PreviewText } from '../preview-text' import { formatMomentValue, momentable, mapSize, mapStatus, } from '../__builtins__' type DatePickerProps<PickerProps> = Exclude< PickerProps, 'value' | 'onChange' > & { value: string onChange: (value: string | string[]) => void } type ComposedDatePicker = React.FC< React.PropsWithChildren<NextDatePickerProps> > & { RangePicker?: React.FC<React.PropsWithChildren<RangePickerProps>> MonthPicker?: React.FC<React.PropsWithChildren<MonthPickerProps>> YearPicker?: React.FC<React.PropsWithChildren<YearPickerProps>> WeekPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> } const mapDateFormat = function (type?: 'month' | 'year' | 'week') { const getDefaultFormat = (props: DatePickerProps<NextDatePickerProps>) => { const _type = props['type'] || type if (_type === 'month') { return 'YYYY-MM' } else if (_type === 'year') { return 'YYYY' } else if (_type === 'week') { return 'YYYY-wo' } return 'YYYY-MM-DD' } return (props: any) => { const dateFormat = props['format'] || getDefaultFormat(props) let valueFormat = dateFormat if (props.showTime) { const timeFormat = props.showTime.format || 'HH:mm:ss' valueFormat = `${valueFormat} ${timeFormat}` } const onChange = props.onChange return { ...props, format: dateFormat, value: momentable( props.value, valueFormat === 'YYYY-wo' ? 'YYYY-w' : valueFormat ), onChange: (value: moment.Moment | moment.Moment[]) => { if (onChange) { onChange(formatMomentValue(value, valueFormat)) } }, } } } export const DatePicker: ComposedDatePicker = connect( NextDatePicker, mapProps(mapDateFormat(), mapSize, mapStatus), mapReadPretty(PreviewText.DatePicker) ) DatePicker.RangePicker = connect( NextDatePicker.RangePicker, mapProps(mapDateFormat(), mapSize, mapStatus), mapReadPretty(PreviewText.DateRangePicker) ) DatePicker.YearPicker = connect( NextDatePicker.YearPicker, mapProps(mapDateFormat('year'), mapSize, mapStatus), mapReadPretty(PreviewText.DatePicker) ) DatePicker.MonthPicker = connect( NextDatePicker.MonthPicker, mapProps(mapDateFormat('month'), mapSize, mapStatus), mapReadPretty(PreviewText.DatePicker) ) DatePicker.WeekPicker = connect( NextDatePicker.WeekPicker, mapProps(mapDateFormat('week'), mapSize, mapStatus), mapReadPretty(PreviewText.DatePicker) ) export default DatePicker ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-collapse/effects-markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaArrayField name="array" :maxItems="3" x-component="ArrayCollapse" x-decorator="FormItem" :x-component-props="{ title: '对象数组', }" > <SchemaObjectField x-component="ArrayCollapse.Item" x-decorator="FormItem" :x-component-props="{ title: '对象数组', }" > <SchemaVoidField x-component="ArrayCollapse.Index" /> <SchemaStringField name="aa" x-decorator="FormItem" title="AA" required description="AA输入123时隐藏BB" x-component="Input" /> <SchemaStringField name="bb" x-decorator="FormItem" title="BB" required x-component="Input" /> <SchemaStringField name="cc" x-decorator="FormItem" title="CC" required description="CC输入123时隐藏DD" x-component="Input" /> <SchemaStringField name="dd" x-decorator="FormItem" title="DD" required x-component="Input" /> <SchemaVoidField x-component="ArrayCollapse.Remove" /> <SchemaVoidField x-component="ArrayCollapse.MoveUp" /> <SchemaVoidField x-component="ArrayCollapse.MoveDown" /> </SchemaObjectField> <SchemaVoidField x-component="ArrayCollapse.Addition" title="添加条目" /> </SchemaArrayField> </SchemaField> <Submit @submit="log">提交</Submit> </FormProvider> </template> <script> import { createForm, onFieldChange, onFieldReact } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormItem, FormButtonGroup, Submit, Input, ArrayCollapse, } from '@formily/element' import { Button } from 'element-ui' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCollapse, }, }) export default { components: { FormProvider, FormButtonGroup, Button, Submit, ...SchemaField, }, data() { const form = createForm({ effects: () => { //主动联动模式 onFieldChange('array.*.aa', ['value'], (field, form) => { form.setFieldState(field.query('.bb'), (state) => { state.visible = field.value != '123' }) }) //被动联动模式 onFieldReact('array.*.dd', (field) => { field.visible = field.query('.cc').get('value') != '123' }) }, }) return { form, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-cards/effects-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 { FormItem, FormButtonGroup, Submit, Input, ArrayCards, } from '@formily/element' import { Button } from 'element-ui' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) export default { components: { FormProvider, FormButtonGroup, Button, Submit, ...SchemaField, }, data() { const form = createForm() const schema = { type: 'object', properties: { array: { type: 'array', 'x-component': 'ArrayCards', maxItems: 3, title: '对象数组', items: { type: 'object', properties: { index: { type: 'void', 'x-component': 'ArrayCards.Index', }, aa: { type: 'string', 'x-decorator': 'FormItem', title: 'AA', required: true, 'x-component': 'Input', description: '输入123', }, bb: { type: 'string', title: 'BB', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-reactions': [ { dependencies: ['.aa'], when: "{{$deps[0] != '123'}}", fulfill: { schema: { title: 'BB', 'x-disabled': true, }, }, otherwise: { schema: { title: 'Changed', 'x-disabled': false, }, }, }, ], }, remove: { type: 'void', 'x-component': 'ArrayCards.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCards.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCards.MoveDown', }, }, }, properties: { addition: { type: 'void', title: '添加条目', 'x-component': 'ArrayCards.Addition', }, }, }, }, } return { form, schema, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/validator/src/registry.ts: -------------------------------------------------------------------------------- ```typescript import { FormPath, each, lowerCase, globalThisPolyfill, merge as deepmerge, isFn, isStr, } from '@formily/shared' import { ValidatorFunctionResponse, ValidatorFunction, IRegistryFormats, IRegistryLocaleMessages, IRegistryLocales, IRegistryRules, } from './types' const getIn = FormPath.getIn const self: any = globalThisPolyfill const defaultLanguage = 'en' const getBrowserlanguage = () => { /* istanbul ignore next */ if (!self.navigator) { return defaultLanguage } return ( self.navigator.browserlanguage || self.navigator.language || defaultLanguage ) } const registry = { locales: { messages: {}, language: getBrowserlanguage(), }, formats: {}, rules: {}, template: null, } const getISOCode = (language: string) => { let isoCode = registry.locales.language if (registry.locales.messages[language]) { return language } const lang = lowerCase(language) each( registry.locales.messages, (messages: IRegistryLocaleMessages, key: string) => { const target = lowerCase(key) if (target.indexOf(lang) > -1 || lang.indexOf(target) > -1) { isoCode = key return false } } ) return isoCode } export const getValidateLocaleIOSCode = getISOCode export const setValidateLanguage = (lang: string) => { registry.locales.language = lang || defaultLanguage } export const getValidateLanguage = () => registry.locales.language export const getLocaleByPath = ( path: string, lang: string = registry.locales.language ) => getIn(registry.locales.messages, `${getISOCode(lang)}.${path}`) export const getValidateLocale = (path: string) => { const message = getLocaleByPath(path) return ( message || getLocaleByPath('pattern') || getLocaleByPath('pattern', defaultLanguage) ) } export const getValidateMessageTemplateEngine = () => registry.template export const getValidateFormats = (key?: string) => key ? registry.formats[key] : registry.formats export const getValidateRules = <T>( key?: T ): T extends string ? ValidatorFunction : { [key: string]: ValidatorFunction } => key ? registry.rules[key as any] : registry.rules export const registerValidateLocale = (locale: IRegistryLocales) => { registry.locales.messages = deepmerge(registry.locales.messages, locale) } export const registerValidateRules = (rules: IRegistryRules) => { each(rules, (rule, key) => { if (isFn(rule)) { registry.rules[key] = rule } }) } export const registerValidateFormats = (formats: IRegistryFormats) => { each(formats, (pattern, key) => { if (isStr(pattern) || pattern instanceof RegExp) { registry.formats[key] = new RegExp(pattern) } }) } export const registerValidateMessageTemplateEngine = ( template: (message: ValidatorFunctionResponse, context: any) => any ) => { registry.template = template } ``` -------------------------------------------------------------------------------- /packages/core/docs/api/entry/FormValidatorRegistry.md: -------------------------------------------------------------------------------- ```markdown --- order: 6 --- # Form Validator Registry ## setValidateLanguage #### Description Set the built-in verification rule language #### Signature ```ts interface setValidateLanguage { (language: string): void } ``` #### Example ```ts import { setValidateLanguage } from '@formily/core' setValidateLanguage('en-US') setValidateLanguage('zh-CN') ``` ## registerValidateFormats #### Description Register general regular rules, the current built-in regular library reference: [formats.ts](https://github.com/alibaba/formily/blob/master/packages/validator/src/formats.ts) #### Signature ```ts interface registerValidateFormats { (rules: { [key: string]: RegExp }): void } ``` #### Example ```ts import { registerValidateFormats } from '@formily/core' registerValidateFormats({ integer: /^[+-]?\d+$/, }) ``` ## registerValidateLocale #### Description Global registration verification language package, the current built-in language package reference: [locale.ts](https://github.com/alibaba/formily/blob/master/packages/validator/src/locale.ts) #### Signature ```ts interface registerValidateLocale { (locales: { [key: string]: { key: string } }): void } ``` #### Example ```ts import { registerValidateLocale } from '@formily/core' registerValidateLocale({ ja: { required: 'このProjectは mustです', }, }) ``` ## registerValidateMessageTemplateEngine #### Description Globally register the verification message template engine. When we return the verification message in the validator, we can perform conversion based on the template engine syntax #### Signature ```ts interface registerValidateMessageTemplateEngine { (template: (message: ValidatorFunctionResponse, context: any) => any): void } ``` #### Example ```ts import { registerValidateMessageTemplateEngine } from '@formily/core' registerValidateMessageTemplateEngine((message, context) => { return message.replace(/\<\%\s*([\w.]+)\s*\%\>/g, (_, $0) => { return FormPath.getIn(context, $0) }) }) ``` ## registerValidateRules #### Description Register general verification rules, the current built-in rule library reference: [rules.ts](https://github.com/alibaba/formily/blob/master/packages/validator/src/rules.ts) #### Signature ```ts interface registerValidateRules { (rules: { [key: string]: ( value: any, rule: ValidatorRules, ctx: Context ) => ValidateResult | Promise<ValidateResult> }): void } ``` #### Example ```ts import { registerValidateRules } from '@formily/core' registerValidateRules({ custom(value) { return value > 100 ? 'error' : '' }, }) ``` ## getValidateLocaleIOSCode #### Description Get the built-in ISO Code #### Signature ```ts interface getValidateLocaleIOSCode { (language: string): string | undefined } ``` #### Example ```ts import { getValidateLocaleIOSCode } from '@formily/core' getValidateLocaleIOSCode('en') // ==> en_US ``` ``` -------------------------------------------------------------------------------- /packages/next/src/date-picker2/index.tsx: -------------------------------------------------------------------------------- ```typescript import moment from 'moment' import { connect, mapProps, mapReadPretty } from '@formily/react' import { DatePicker2 as NextDatePicker } from '@alifd/next' import { DatePickerProps as NextDatePickerProps, RangePickerProps, } from '@alifd/next/lib/date-picker2' import { PreviewText } from '../preview-text' import { formatMomentValue, momentable, mapSize, mapStatus, } from '../__builtins__' type DatePickerProps<PickerProps> = Exclude< PickerProps, 'value' | 'onChange' > & { value: string onChange: (value: string | string[]) => void } type ComposedDatePicker = React.FC< React.PropsWithChildren<NextDatePickerProps> > & { RangePicker?: React.FC<React.PropsWithChildren<RangePickerProps>> MonthPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> YearPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> WeekPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> QuarterPicker?: React.FC<React.PropsWithChildren<NextDatePickerProps>> } const mapDateFormat = function (type?: 'month' | 'year' | 'week' | 'quarter') { const getDefaultFormat = (props: DatePickerProps<NextDatePickerProps>) => { const _type = props['type'] || type if (_type === 'month') { return 'YYYY-MM' } else if (_type === 'quarter') { return 'YYYY-\\QQ' } else if (_type === 'year') { return 'YYYY' } else if (_type === 'week') { return 'YYYY-wo' } return props['showTime'] ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD' } return (props: any) => { const format = props['format'] || getDefaultFormat(props) const onChange = props.onChange return { ...props, format, value: momentable(props.value, format === 'YYYY-wo' ? 'YYYY-w' : format), onChange: (value: moment.Moment | moment.Moment[]) => { if (onChange) { onChange(formatMomentValue(value, format)) } }, } } } export const DatePicker2: ComposedDatePicker = connect( NextDatePicker, mapProps(mapDateFormat(), mapSize, mapStatus), mapReadPretty(PreviewText.DatePicker) ) DatePicker2.RangePicker = connect( NextDatePicker.RangePicker, mapProps(mapDateFormat(), mapSize, mapStatus), mapReadPretty(PreviewText.DateRangePicker) ) DatePicker2.YearPicker = connect( NextDatePicker.YearPicker, mapProps(mapDateFormat('year'), mapSize, mapStatus), mapReadPretty(PreviewText.DatePicker) ) DatePicker2.MonthPicker = connect( NextDatePicker.MonthPicker, mapProps(mapDateFormat('month'), mapSize, mapStatus), mapReadPretty(PreviewText.DatePicker) ) DatePicker2.WeekPicker = connect( NextDatePicker.WeekPicker, mapProps(mapDateFormat('week'), mapSize, mapStatus), mapReadPretty(PreviewText.DatePicker) ) DatePicker2.QuarterPicker = connect( NextDatePicker.QuarterPicker, mapProps(mapDateFormat('quarter'), mapSize, mapStatus), mapReadPretty(PreviewText.DatePicker) ) export default DatePicker2 ``` -------------------------------------------------------------------------------- /packages/core/src/models/Query.ts: -------------------------------------------------------------------------------- ```typescript import { FormPath, isFn, each, FormPathPattern } from '@formily/shared' import { buildDataPath } from '../shared/internals' import { GeneralField, IGeneralFieldState, IQueryProps } from '../types' import { Form } from './Form' const output = ( field: GeneralField, taker: (field: GeneralField, address: FormPath) => any ) => { if (!field) return if (isFn(taker)) { return taker(field, field.address) } return field } const takeMatchPattern = (form: Form, pattern: FormPath) => { const identifier = pattern.toString() const indexIdentifier = form.indexes[identifier] const absoluteField = form.fields[identifier] const indexField = form.fields[indexIdentifier] if (absoluteField) { return identifier } else if (indexField) { return indexIdentifier } } export class Query { private pattern: FormPath private addresses: string[] = [] private form: Form constructor(props: IQueryProps) { this.pattern = FormPath.parse(props.pattern, props.base) this.form = props.form if (!this.pattern.isMatchPattern) { const matched = takeMatchPattern( this.form, this.pattern.haveRelativePattern ? buildDataPath(props.form.fields, this.pattern) : this.pattern ) if (matched) { this.addresses = [matched] } } else { each(this.form.fields, (field, address) => { if (!field) { delete this.form.fields[address] return } if (field.match(this.pattern)) { this.addresses.push(address) } }) } } take(): GeneralField | undefined take<Result>( getter: (field: GeneralField, address: FormPath) => Result ): Result take(taker?: any): any { return output(this.form.fields[this.addresses[0]], taker) } map(): GeneralField[] map<Result>( iterator?: (field: GeneralField, address: FormPath) => Result ): Result[] map(iterator?: any): any { return this.addresses.map((address) => output(this.form.fields[address], iterator) ) } forEach<Result>( iterator: (field: GeneralField, address: FormPath) => Result ) { return this.addresses.forEach((address) => output(this.form.fields[address], iterator) ) } reduce<Result>( reducer: (value: Result, field: GeneralField, address: FormPath) => Result, initial?: Result ): Result { return this.addresses.reduce( (value, address) => output(this.form.fields[address], (field, address) => reducer(value, field, address) ), initial ) } get<K extends keyof IGeneralFieldState>(key: K): IGeneralFieldState[K] { const results: any = this.take() if (results) { return results[key] } } getIn(pattern?: FormPathPattern) { return FormPath.getIn(this.take(), pattern) } value() { return this.get('value') } initialValue() { return this.get('initialValue') } } ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Checkbox.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Checkbox > 复选框 ## Markup Schema 案例 ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Checkbox, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Boolean name="single" title="是否确认" x-decorator="FormItem" x-component="Checkbox" /> <SchemaField.String name="multiple" title="复选" enum={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} x-decorator="FormItem" x-component="Checkbox.Group" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Checkbox, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { single: { type: 'boolean', title: '是否确认', 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, multiple: { type: 'array', title: '复选', enum: [ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ], 'x-decorator': 'FormItem', 'x-component': 'Checkbox.Group', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="single" title="是否确认" decorator={[FormItem]} component={[Checkbox]} /> <Field name="multiple" title="复选" dataSource={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} decorator={[FormItem]} component={[Checkbox.Group]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://ant.design/components/checkbox-cn/ ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Checkbox.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Checkbox > 复选框 ## Markup Schema 案例 ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Checkbox, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Boolean name="single" title="是否确认" x-decorator="FormItem" x-component="Checkbox" /> <SchemaField.String name="multiple" title="复选" enum={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} x-decorator="FormItem" x-component="Checkbox.Group" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Checkbox, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { single: { type: 'boolean', title: '是否确认', 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, multiple: { type: 'array', title: '复选', enum: [ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ], 'x-decorator': 'FormItem', 'x-component': 'Checkbox.Group', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="single" title="是否确认" decorator={[FormItem]} component={[Checkbox]} /> <Field name="multiple" title="复选" dataSource={[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]} decorator={[FormItem]} component={[Checkbox.Group]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://fusion.design/pc/component/basic/checkbox ``` -------------------------------------------------------------------------------- /packages/element/docs/.vuepress/components/createCodeSandBox.js: -------------------------------------------------------------------------------- ```javascript import { getParameters } from 'codesandbox/lib/api/define' const CodeSandBoxHTML = '<div id="app"></div>' const CodeSandBoxJS = ` import Vue from 'vue' import App from './App.vue' import Element from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.config.productionTip = false Vue.use(Element, { size: 'small' }); new Vue({ render: h => h(App), }).$mount('#app')` const createForm = ({ method, action, data }) => { const form = document.createElement('form') // 构造 form form.style.display = 'none' // 设置为不显示 form.target = '_blank' // 指向 iframe // 构造 formdata Object.keys(data).forEach((key) => { const input = document.createElement('input') // 创建 input input.name = key // 设置 name input.value = data[key] // 设置 value form.appendChild(input) }) form.method = method // 设置方法 form.action = action // 设置地址 document.body.appendChild(form) // 对该 form 执行提交 form.submit() document.body.removeChild(form) } export function createCodeSandBox(codeStr) { const parameters = getParameters({ files: { 'sandbox.config.json': { content: { template: 'node', infiniteLoopProtection: true, hardReloadOnChange: false, view: 'browser', container: { port: 8080, node: '14', }, }, }, 'package.json': { content: { scripts: { serve: 'vue-cli-service serve', build: 'vue-cli-service build', lint: 'vue-cli-service lint', }, dependencies: { '@formily/core': 'latest', '@formily/vue': 'latest', '@formily/element': 'latest', axios: '^0.21.1', 'core-js': '^3.6.5', 'element-ui': 'latest', 'vue-demi': 'latest', vue: '^2.6.11', }, devDependencies: { '@vue/cli-plugin-babel': '~4.5.0', '@vue/cli-service': '~4.5.0', '@vue/composition-api': 'latest', 'vue-template-compiler': '^2.6.11', sass: '^1.34.1', 'sass-loader': '^8.0.2', }, babel: { presets: [ [ '@vue/babel-preset-jsx', { vModel: false, compositionAPI: true, }, ], ], }, vue: { devServer: { host: '0.0.0.0', disableHostCheck: true, // 必须 }, }, }, }, 'src/App.vue': { content: codeStr, }, 'src/main.js': { content: CodeSandBoxJS, }, 'public/index.html': { content: CodeSandBoxHTML, }, }, }) createForm({ method: 'post', action: 'https://codesandbox.io/api/v1/sandboxes/define', data: { parameters, query: 'file=/src/App.vue', }, }) } ``` -------------------------------------------------------------------------------- /packages/next/src/form-grid/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useLayoutEffect, useRef, useMemo, useContext } from 'react' import { markRaw } from '@formily/reactive' import { observer } from '@formily/react' import { Grid, IGridOptions } from '@formily/grid' import { usePrefixCls, pickDataProps } from '../__builtins__' import { useFormLayout } from '../form-layout' import cls from 'classnames' const FormGridContext = React.createContext<Grid<HTMLElement>>(null) export interface IFormGridProps extends IGridOptions { grid?: Grid<HTMLElement> prefix?: string className?: string style?: React.CSSProperties } export interface IGridColumnProps { gridSpan?: number style?: React.CSSProperties className?: string } type ComposedFormGrid = React.FC<React.PropsWithChildren<IFormGridProps>> & { GridColumn: React.FC<React.PropsWithChildren<IGridColumnProps>> useFormGrid: () => Grid<HTMLElement> createFormGrid: (props: IFormGridProps) => Grid<HTMLElement> /** * @deprecated */ useGridSpan: (gridSpan: number) => number /** * @deprecated */ useGridColumn: (gridSpan: number) => number } export const createFormGrid = (props: IFormGridProps) => { return markRaw(new Grid(props)) } export const useFormGrid = () => useContext(FormGridContext) /** * @deprecated */ export const useGridSpan = (gridSpan = 1) => { return gridSpan } /** * @deprecated */ export const useGridColumn = (gridSpan = 1) => { return gridSpan } export const FormGrid: ComposedFormGrid = observer( ({ children, className, style, ...props }: React.PropsWithChildren<IFormGridProps>) => { const layout = useFormLayout() const options = { columnGap: layout?.gridColumnGap ?? 8, rowGap: layout?.gridRowGap ?? 4, ...props, } const grid = useMemo( () => markRaw(options?.grid ? options.grid : new Grid(options)), [Grid.id(options)] ) const ref = useRef<HTMLDivElement>() const prefixCls = usePrefixCls('formily-grid', props) const dataProps = pickDataProps(props) useLayoutEffect(() => { return grid.connect(ref.current) }, [grid]) return ( <FormGridContext.Provider value={grid}> <div {...dataProps} className={cls(`${prefixCls}-layout`, className)} style={{ ...style, gridTemplateColumns: grid.templateColumns, gap: grid.gap, }} ref={ref} > {children} </div> </FormGridContext.Provider> ) }, { forwardRef: true, } ) as any export const GridColumn: React.FC<React.PropsWithChildren<IGridColumnProps>> = observer(({ gridSpan = 1, children, ...props }) => { return ( <div {...props} data-grid-span={gridSpan}> {children} </div> ) }) FormGrid.createFormGrid = createFormGrid FormGrid.useFormGrid = useFormGrid FormGrid.useGridSpan = useGridSpan FormGrid.useGridColumn = useGridColumn FormGrid.GridColumn = GridColumn export default FormGrid ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/extension/backend.ts: -------------------------------------------------------------------------------- ```typescript //inject content script const serializeObject = (obj: any) => { const seens = new WeakMap() const serialize = (obj: any) => { if (Array.isArray(obj)) { return obj.map(serialize) } else if (typeof obj === 'function') { return `f ${obj.displayName || obj.name}(){ }` } else if (typeof obj === 'object') { if (seens.get(obj)) return '#CircularReference' if (!obj) return obj if ('$$typeof' in obj && '_owner' in obj) { seens.set(obj, true) return '#ReactNode' } else if (obj.toJS) { seens.set(obj, true) return obj.toJS() } else if (obj.toJSON) { seens.set(obj, true) return obj.toJSON() } else { seens.set(obj, true) const result = {} for (let key in obj) { result[key] = serialize(obj[key]) } seens.set(obj, false) return result } } return obj } return serialize(obj) } const send = ({ type, id, form, }: { type: string id?: string | number form?: any }) => { const graph = serializeObject(form?.getFormGraph()) window.postMessage( { source: '@formily-devtools-inject-script', type, id, graph: form && JSON.stringify(graph, (key, value) => { if (typeof value === 'symbol') { return value.toString() } return value }), }, '*' ) } send({ type: 'init', }) interface IIdleDeadline { didTimeout: boolean timeRemaining: () => DOMHighResTimeStamp } const HOOK = { hasFormilyInstance: false, hasOpenDevtools: false, store: {}, openDevtools() { this.hasOpenDevtools = true }, closeDevtools() { this.hasOpenDevtools = false }, setVm(fieldId: string, formId: string) { if (fieldId) { globalThis.$vm = this.store[formId].fields[fieldId] } else { globalThis.$vm = this.store[formId] } }, inject(id: number, form: any) { this.hasFormilyInstance = true this.store[id] = form send({ type: 'install', id, form, }) let timer = null const task = () => { globalThis.requestIdleCallback((deadline: IIdleDeadline) => { if (this.store[id]) { if (deadline.timeRemaining() < 16) { task() } else { send({ type: 'update', id, form, }) } } }) } form.subscribe(() => { if (!this.hasOpenDevtools) return clearTimeout(timer) timer = setTimeout(task, 300) }) }, update() { const keys = Object.keys(this.store || {}) keys.forEach((id) => { send({ type: 'update', id, form: this.store[id], }) }) }, unmount(id: number) { delete this.store[id] send({ type: 'uninstall', id, }) }, } globalThis.__FORMILY_DEV_TOOLS_HOOK__ = HOOK globalThis.__UFORM_DEV_TOOLS_HOOK__ = HOOK ``` -------------------------------------------------------------------------------- /packages/element/src/radio/index.ts: -------------------------------------------------------------------------------- ```typescript import { connect, h, mapProps, mapReadPretty } from '@formily/vue' import type { Radio as ElRadioProps, RadioGroup as ElRadioGroupProps, } from 'element-ui' import { Radio as ElRadio, RadioButton, RadioGroup as ElRadioGroup, } from 'element-ui' import { defineComponent, PropType } from 'vue-demi' import { PreviewText } from '../preview-text' import { composeExport, resolveComponent, SlotTypes, transformComponent, } from '../__builtins__/shared' export type RadioGroupProps = ElRadioGroupProps & { value: any options?: ( | (Omit<ElRadioProps, 'value'> & { value: ElRadioProps['label'] label: SlotTypes }) | string )[] optionType: 'default' | 'button' } export type RadioProps = ElRadioProps const TransformElRadioGroup = transformComponent(ElRadioGroup, { change: 'input', uselessChange:'change' }) const RadioGroupOption = defineComponent<RadioGroupProps>({ name: 'FRadioGroup', props: { options: { type: Array as PropType<RadioGroupProps['options']>, default: () => [], }, optionType: { type: String as PropType<RadioGroupProps['optionType']>, default: 'default', }, }, setup(customProps, { attrs, slots, listeners }) { return () => { const options = customProps.options || [] const OptionType = customProps.optionType === 'button' ? RadioButton : ElRadio const children = options.length !== 0 ? { default: () => options.map((option) => { if (typeof option === 'string') { return h( OptionType, { props: { label: option } }, { default: () => [ resolveComponent(slots?.option ?? option, { option }), ], } ) } else { return h( OptionType, { props: { ...option, value: undefined, label: option.value, }, }, { default: () => [ resolveComponent(slots?.option ?? option.label, { option, }), ], } ) } }), } : slots return h( TransformElRadioGroup, { attrs: { ...attrs, }, on: listeners, }, children ) } }, }) const RadioGroup = connect( RadioGroupOption, mapProps({ dataSource: 'options' }), mapReadPretty(PreviewText.Select) ) export const Radio = composeExport(ElRadio, { Group: RadioGroup, }) export default Radio ``` -------------------------------------------------------------------------------- /packages/antd/src/form-grid/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useLayoutEffect, useRef, useMemo, useContext } from 'react' import { markRaw } from '@formily/reactive' import { observer } from '@formily/react' import { Grid, IGridOptions } from '@formily/grid' import { usePrefixCls, pickDataProps } from '../__builtins__' import { useFormLayout } from '../form-layout' import cls from 'classnames' const FormGridContext = React.createContext<Grid<HTMLElement>>(null) export interface IFormGridProps extends IGridOptions { grid?: Grid<HTMLElement> prefixCls?: string className?: string style?: React.CSSProperties } export interface IGridColumnProps { gridSpan?: number style?: React.CSSProperties className?: string } type ComposedFormGrid = React.FC<React.PropsWithChildren<IFormGridProps>> & { GridColumn: React.FC<React.PropsWithChildren<IGridColumnProps>> useFormGrid: () => Grid<HTMLElement> createFormGrid: (props: IFormGridProps) => Grid<HTMLElement> /** * @deprecated */ useGridSpan: (gridSpan: number) => number /** * @deprecated */ useGridColumn: (gridSpan: number) => number } export const createFormGrid = (props: IFormGridProps) => { return markRaw(new Grid(props)) } export const useFormGrid = () => useContext(FormGridContext) /** * @deprecated */ export const useGridSpan = (gridSpan = 1) => { return gridSpan } /** * @deprecated */ export const useGridColumn = (gridSpan = 1) => { return gridSpan } export const FormGrid: ComposedFormGrid = observer( ({ children, className, style, ...props }: React.PropsWithChildren<IFormGridProps>) => { const layout = useFormLayout() const options = { columnGap: layout?.gridColumnGap ?? 8, rowGap: layout?.gridRowGap ?? 4, ...props, } const grid = useMemo( () => markRaw(options?.grid ? options.grid : new Grid(options)), [Grid.id(options)] ) const ref = useRef<HTMLDivElement>() const prefixCls = usePrefixCls('formily-grid', props) const dataProps = pickDataProps(props) useLayoutEffect(() => { return grid.connect(ref.current) }, [grid]) return ( <FormGridContext.Provider value={grid}> <div {...dataProps} className={cls(`${prefixCls}-layout`, className)} style={{ ...style, gridTemplateColumns: grid.templateColumns, gap: grid.gap, }} ref={ref} > {children} </div> </FormGridContext.Provider> ) }, { forwardRef: true, } ) as any export const GridColumn: React.FC<React.PropsWithChildren<IGridColumnProps>> = observer(({ gridSpan = 1, children, ...props }) => { return ( <div {...props} style={props.style} data-grid-span={gridSpan}> {children} </div> ) }) FormGrid.createFormGrid = createFormGrid FormGrid.useGridSpan = useGridSpan FormGrid.useGridColumn = useGridColumn FormGrid.useFormGrid = useFormGrid FormGrid.GridColumn = GridColumn export default FormGrid ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-step/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField :schema="schema" :scope="{ formStep }" /> <FormConsumer> <template #default> <FormButtonGroup> <Button :disabled="!formStep.allowBack" @click=" () => { formStep.back() } " > 上一步 </Button> <Button :disabled="!formStep.allowNext" @click=" () => { formStep.next() } " > 下一步 </Button> <Submit :disabled="formStep.allowNext" @submit="log">提交</Submit> </FormButtonGroup> </template> </FormConsumer> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField, FormConsumer } from '@formily/vue' import { FormItem, FormStep, FormButtonGroup, Submit, Input, } from '@formily/element' import { Button } from 'element-ui' const { SchemaField } = createSchemaField({ components: { FormItem, FormStep, Input, }, }) const schema = { type: 'object', properties: { collapse: { type: 'void', 'x-component': 'FormStep', 'x-component-props': { formStep: '{{formStep}}', }, properties: { step1: { type: 'void', 'x-component': 'FormStep.StepPane', 'x-component-props': { title: '第一步', }, properties: { aaa: { type: 'string', title: 'AAA', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, step2: { type: 'void', 'x-component': 'FormStep.StepPane', 'x-component-props': { title: '第二步', }, properties: { bbb: { type: 'string', title: 'AAA', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, step3: { type: 'void', 'x-component': 'FormStep.StepPane', 'x-component-props': { title: '第三步', }, properties: { ccc: { type: 'string', title: 'AAA', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, }, }, }, } export default { components: { FormProvider, FormConsumer, FormButtonGroup, Button, Submit, SchemaField, }, data() { const form = createForm() const formStep = FormStep.createFormStep() return { schema, form, formStep, } }, methods: { log() { this.formStep.submit(console.log) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/space/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <FormLayout :labelCol="6" :wrapperCol="16"> <SchemaField :schema="schema" /> <FormButtonGroup alignFormItem> <Submit onSubmit="log">提交</Submit> </FormButtonGroup> </FormLayout> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { createSchemaField, FormProvider } from '@formily/vue' import { FormButtonGroup, FormLayout, FormItem, Input, Submit, Space, } from '@formily/element' const { SchemaField } = createSchemaField({ components: { FormLayout, FormItem, Input, Space, }, }) export default { components: { FormProvider, FormButtonGroup, FormLayout, SchemaField, Submit, }, data() { const schema = { type: 'object', properties: { name: { type: 'void', title: '姓名', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { firstName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, lastName: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Input', required: true, }, }, }, texts: { type: 'void', title: '文本串联', 'x-decorator': 'FormItem', 'x-decorator-props': { asterisk: true, feedbackLayout: 'none', }, 'x-component': 'Space', properties: { aa: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: '单位', }, 'x-component': 'Input', required: true, }, bb: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: '单位', }, 'x-component': 'Input', required: true, }, cc: { type: 'string', 'x-decorator': 'FormItem', 'x-decorator-props': { addonAfter: '单位', }, 'x-component': 'Input', required: true, }, }, }, textarea: { type: 'string', title: '文本框', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': { style: { width: 400, }, }, required: true, }, }, } const form = createForm() return { form, schema, } }, methods: { logs(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-collapse/effects-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 { FormItem, FormButtonGroup, Submit, Input, ArrayCollapse, } from '@formily/element' import { Button } from 'element-ui' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCollapse, }, }) export default { components: { FormProvider, FormButtonGroup, Button, Submit, ...SchemaField, }, data() { const form = createForm() const schema = { type: 'object', properties: { array: { type: 'array', 'x-component': 'ArrayCollapse', maxItems: 3, title: '对象数组', items: { type: 'object', 'x-component': 'ArrayCollapse.Item', 'x-component-props': { header: '对象数组', }, properties: { index: { type: 'void', 'x-component': 'ArrayCollapse.Index', }, aa: { type: 'string', 'x-decorator': 'FormItem', title: 'AA', required: true, 'x-component': 'Input', description: '输入123', }, bb: { type: 'string', title: 'BB', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-reactions': [ { dependencies: ['.aa'], when: "{{$deps[0] != '123'}}", fulfill: { schema: { title: 'BB', 'x-disabled': true, }, }, otherwise: { schema: { title: 'Changed', 'x-disabled': false, }, }, }, ], }, remove: { type: 'void', 'x-component': 'ArrayCollapse.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCollapse.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCollapse.MoveDown', }, }, }, properties: { addition: { type: 'void', title: '添加条目', 'x-component': 'ArrayCollapse.Addition', }, }, }, }, } return { form, schema, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/externals.spec.ts: -------------------------------------------------------------------------------- ```typescript import { createForm } from '..' import { isArrayField, isArrayFieldState, isDataField, isDataFieldState, isField, isFieldState, isForm, isFormState, isGeneralField, isGeneralFieldState, isObjectField, isObjectFieldState, isQuery, isVoidField, isVoidFieldState, createEffectHook, } from '../shared/externals' import { attach } from './shared' test('type checkers', () => { const form = attach(createForm()) const normal = attach( form.createField({ name: 'normal', }) ) const array = attach( form.createArrayField({ name: 'array', }) ) const object = attach( form.createObjectField({ name: 'object', }) ) const void_ = attach( form.createVoidField({ name: 'void', }) ) expect(isField(normal)).toBeTruthy() expect(isFieldState(normal.getState())).toBeTruthy() expect(isFieldState(null)).toBeFalsy() expect(isFieldState({})).toBeFalsy() expect(isFieldState(normal)).toBeFalsy() expect(isArrayField(array)).toBeTruthy() expect(isArrayFieldState(array.getState())).toBeTruthy() expect(isArrayFieldState(null)).toBeFalsy() expect(isArrayFieldState({})).toBeFalsy() expect(isArrayFieldState(array)).toBeFalsy() expect(isObjectField(object)).toBeTruthy() expect(isObjectFieldState(object.getState())).toBeTruthy() expect(isObjectFieldState(null)).toBeFalsy() expect(isObjectFieldState({})).toBeFalsy() expect(isObjectFieldState(object)).toBeFalsy() expect(isVoidField(void_)).toBeTruthy() expect(isVoidFieldState(void_.getState())).toBeTruthy() expect(isVoidFieldState(null)).toBeFalsy() expect(isVoidFieldState({})).toBeFalsy() expect(isVoidFieldState(void_)).toBeFalsy() expect(isDataField(void_)).toBeFalsy() expect(isDataFieldState(void_.getState())).toBeFalsy() expect(isDataField(normal)).toBeTruthy() expect(isDataFieldState(normal.getState())).toBeTruthy() expect(isGeneralField(normal)).toBeTruthy() expect(isGeneralField(array)).toBeTruthy() expect(isGeneralField(object)).toBeTruthy() expect(isGeneralField(void_)).toBeTruthy() expect(isGeneralFieldState(normal.getState())).toBeTruthy() expect(isGeneralFieldState(array.getState())).toBeTruthy() expect(isGeneralFieldState(object.getState())).toBeTruthy() expect(isGeneralFieldState(void_.getState())).toBeTruthy() expect(isGeneralFieldState(null)).toBeFalsy() expect(isGeneralFieldState({})).toBeFalsy() expect(isGeneralFieldState(void_)).toBeFalsy() expect(isForm(form)).toBeTruthy() expect(isFormState(form.getState())).toBeTruthy() expect(isFormState({})).toBeFalsy() expect(isFormState(form)).toBeFalsy() expect(isFormState(null)).toBeFalsy() expect(isQuery(form.query('*'))).toBeTruthy() }) test('createEffectHook', () => { try { createEffectHook('xxx')() } catch {} const form = attach( createForm({ effects() { createEffectHook('xxx')() createEffectHook('yyy', () => () => {})() }, }) ) form.notify('xxx') form.notify('yyy') }) ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Checkbox.md: -------------------------------------------------------------------------------- ```markdown # Checkbox > Checkbox ## Markup Schema example ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Checkbox, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Boolean name="single" title="Are you sure" x-decorator="FormItem" x-component="Checkbox" /> <SchemaField.String name="multiple" title="Check" enum={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} x-decorator="FormItem" x-component="Checkbox.Group" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Checkbox, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { single: { type: 'boolean', title: 'Are you sure?', 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, multiple: { type: 'array', title: 'Check', enum: [ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ], 'x-decorator': 'FormItem', 'x-component': 'Checkbox.Group', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="single" title="Are you sure" decorator={[FormItem]} component={[Checkbox]} /> <Field name="multiple" title="Check" dataSource={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} decorator={[FormItem]} component={[Checkbox.Group]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://ant.design/components/checkbox-cn/ ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Input.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Input > 文本输入框 ## Markup Schema 案例 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="输入框" x-decorator="FormItem" x-component="Input" required x-component-props={{ style: { width: 240, }, }} /> <SchemaField.String name="textarea" title="文本框" x-decorator="FormItem" required x-component="Input.TextArea" x-component-props={{ style: { width: 400, }, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: '输入框', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { style: { width: 240, }, }, }, textarea: { type: 'string', title: '输入框', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': { style: { width: 240, }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="input" title="输入框" required decorator={[FormItem]} component={[ Input, { style: { width: 240, }, }, ]} /> <Field name="textarea" title="文本框" required decorator={[FormItem]} component={[ Input.TextArea, { style: { width: 400, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://ant.design/components/input-cn/ ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Checkbox.md: -------------------------------------------------------------------------------- ```markdown # Checkbox > Checkbox ## Markup Schema example ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Checkbox, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Boolean name="single" title="Are you sure" x-decorator="FormItem" x-component="Checkbox" /> <SchemaField.String name="multiple" title="Check" enum={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} x-decorator="FormItem" x-component="Checkbox.Group" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Checkbox, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { single: { type: 'boolean', title: 'Are you sure?', 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, multiple: { type: 'array', title: 'Check', enum: [ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ], 'x-decorator': 'FormItem', 'x-component': 'Checkbox.Group', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Checkbox, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="single" title="Are you sure" decorator={[FormItem]} component={[Checkbox]} /> <Field name="multiple" title="Check" dataSource={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} decorator={[FormItem]} component={[Checkbox.Group]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://fusion.design/pc/component/basic/checkbox ``` -------------------------------------------------------------------------------- /packages/react/docs/api/components/RecordScope.md: -------------------------------------------------------------------------------- ```markdown --- order: 9 --- # RecordScope ## Description Standard scoped injection component for injecting the following built-in variables: - `$record` current record data - `$record.$lookup` The parent record of the current record, you can always look up - `$record.$index` the index of the current record - `$index` The current record index, equivalent to `$record.$index`, considering that if the record data is not an object, it needs to be read independently - `$lookup` The parent record of the current record, equivalent to `$record.$lookup`, considering that if the record data is not an object, it needs to be read independently ## Signature ```ts interface IRecordScopeProps { getRecord(): any getIndex?(): number } type RecordScope = React.FC<React.PropsWithChildren<IRecordScopeProps>> ``` ## Usage Any auto-increment list extension component should use RecordScope internally to pass record scope variables. Components that have implemented this convention include: All components of the ArrayX family in @formily/antd and @formily/next ## Custom component extension use case ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, createSchemaField, RecordScope } from '@formily/react' import { Input } from 'antd' const form = createForm() const MyCustomComponent = (props) => { return ( <RecordScope getRecord={() => props.record} getIndex={() => props.index}> {props.children} </RecordScope> ) } const SchemaField = createSchemaField({ components: { Input, MyCustomComponent, }, }) export default () => ( <FormProvider form={form}> <SchemaField schema={{ type: 'object', properties: { lookup: { type: 'void', 'x-component': 'MyCustomComponent', 'x-component-props': { record: { name: 'Lookup Name', code: 'Lookup Code', }, index: 1, }, properties: { record: { type: 'void', 'x-component': 'MyCustomComponent', 'x-component-props': { record: { name: 'Name', code: 'Code', }, index: 0, }, properties: { input: { type: 'string', 'x-component': 'Input', 'x-value': '{{`' + '${$record.name} ' + '${$record.code} ' + '${$record.$index} ' + '${$record.$lookup.name} ' + '${$record.$lookup.code} ' + '${$index} ' + '${$lookup.name} ' + '${$lookup.code} ' + '`}}', }, }, }, }, }, }, }} ></SchemaField> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Input.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Input > 文本输入框 ## Markup Schema 案例 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="输入框" x-decorator="FormItem" x-component="Input" required x-component-props={{ style: { width: 240, }, }} /> <SchemaField.String name="textarea" title="文本框" x-decorator="FormItem" required x-component="Input.TextArea" x-component-props={{ style: { width: 400, }, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: '输入框', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { style: { width: 240, }, }, }, textarea: { type: 'string', title: '输入框', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': { style: { width: 240, }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="input" title="输入框" required decorator={[FormItem]} component={[ Input, { style: { width: 240, }, }, ]} /> <Field name="textarea" title="文本框" required decorator={[FormItem]} component={[ Input.TextArea, { style: { width: 400, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://fusion.design/pc/component/basic/input ``` -------------------------------------------------------------------------------- /docs/guide/advanced/build.md: -------------------------------------------------------------------------------- ```markdown # Pack on Demand ## Based on Umi Development #### Install `babel-plugin-import` ```shell npm install babel-plugin-import --save-dev ``` or ```shell yarn add babel-plugin-import --dev ``` #### Plugin Configuration Modify `.umirc.js` or `.umirc.ts` ```js export default { extraBabelPlugins: [ [ 'babel-plugin-import', { libraryName: 'antd', libraryDirectory: 'es', style: true }, 'antd', ], [ 'babel-plugin-import', { libraryName: '@formily/antd', libraryDirectory: 'esm', style: true }, '@formily/antd', ], ], } ``` ## Based on Create-react-app Development First, we need to customize the default configuration of `create-react-app`, here we use [react-app-rewired](https://github.com/timarney/react-app-rewired) (A community solution for custom configuration of `create-react-app`) Introduce `react-app-rewired` and modify the startup configuration in `package.json`. Due to the new [[email protected]](https://github.com/timarney/react-app-rewired#alternatives) version, you also need to install [customize-cra](https://github.com/arackaf/customize-cra). ```shell $ npm install react-app-rewired customize-cra --save-dev ``` or ```shell $ yarn add react-app-rewired customize-cra --dev ``` modify `package.json` ```diff "scripts": { - "start": "react-scripts start", + "start": "react-app-rewired start", - "build": "react-scripts build", + "build": "react-app-rewired build", - "test": "react-scripts test", + "test": "react-app-rewired test", } ``` Then create a `config-overrides.js` in the project root directory to modify the default configuration. ```js module.exports = function override(config, env) { // do stuff with the webpack config... return config } ``` #### Install babel-plugin-import ```shell npm install babel-plugin-import --save-dev ``` or ```shell yarn add babel-plugin-import --dev ``` modify `config-overrides.js` ```diff + const { override, fixBabelImports } = require('customize-cra'); - module.exports = function override(config, env) { - // do stuff with the webpack config... - return config; - }; + module.exports = override( + fixBabelImports('antd', { + libraryName: 'antd', + libraryDirectory: 'es', + style: true + }), + fixBabelImports('@formily/antd', { + libraryName: '@formily/antd', + libraryDirectory: 'esm', + style: true + }), + ); ``` ## Use in Webpack #### Install babel-plugin-import ```shell npm install babel-plugin-import --save-dev ``` or ```shell yarn add babel-plugin-import --dev ``` Modify `.babelrc` or babel-loader ```json { "plugins": [ [ "import", { "libraryName": "antd", "libraryDirectory": "es", "style": true }, "antd" ], [ "import", { "libraryName": "@formily/antd", "libraryDirectory": "esm", "style": true }, "@formily/antd" ] ] } ``` For more configuration, please refer to [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-collapse/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <FormLayout :label-col="6" :wrapper-col="10"> <SchemaField :schema="schema" :scope="{ formCollapse }" /> <FormButtonGroup alignFormItem> <Button @click=" () => { form.query('tab3').take((field) => { field.visible = !field.visible }) } " > 显示/隐藏最后一个Tab </Button> <Button @click=" () => { formCollapse.toggleActiveKey('tab2') } " > 切换第二个Tab </Button> <Submit @submit="log">提交</Submit> </FormButtonGroup> </FormLayout> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormItem, FormCollapse, FormButtonGroup, Form, FormLayout, Submit, Input, } from '@formily/element' import { Button } from 'element-ui' const { SchemaField } = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) const schema = { type: 'object', properties: { collapse: { type: 'void', title: '折叠面板', 'x-decorator': 'FormItem', 'x-component': 'FormCollapse', 'x-component-props': { formCollapse: '{{formCollapse}}', }, properties: { tab1: { type: 'void', 'x-component': 'FormCollapse.Item', 'x-component-props': { title: 'A1', }, properties: { aaa: { type: 'string', title: 'AAA', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, tab2: { type: 'void', 'x-component': 'FormCollapse.Item', 'x-component-props': { title: 'A2', }, properties: { bbb: { type: 'string', title: 'BBB', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, tab3: { type: 'void', 'x-component': 'FormCollapse.Item', 'x-component-props': { title: 'A3', }, properties: { ccc: { type: 'string', title: 'CCC', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, }, }, }, } export default { components: { Form, FormButtonGroup, Button, Submit, SchemaField, FormProvider, FormLayout, }, data() { const form = createForm() const formCollapse = FormCollapse.createFormCollapse() return { schema, form, formCollapse, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/space/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <FormLayout :labelCol="6" :wrapperCol="16"> <SchemaField> <SchemaVoidField x-component="FormLayout" :x-component-props="{ labelCol: 6, wrapperCol: 10, }" > <SchemaVoidField title="姓名" x-decorator="FormItem" :x-decorator-props="{ asterisk: true, feedbackLayout: 'none', }" x-component="Space" > <SchemaStringField name="firstName" x-decorator="FormItem" x-component="Input" :required="true" /> <SchemaStringField name="lastName" x-decorator="FormItem" x-component="Input" :required="true" /> </SchemaVoidField> <SchemaVoidField title="文本串联" x-decorator="FormItem" :x-decorator-props="{ asterisk: true, feedbackLayout: 'none', }" x-component="Space" > <SchemaStringField name="aa" x-decorator="FormItem" x-component="Input" :x-decorator-props="{ addonAfter: '单位', }" :required="true" /> <SchemaStringField name="bb" x-decorator="FormItem" x-component="Input" :x-decorator-props="{ addonAfter: '单位', }" :required="true" /> <SchemaStringField name="cc" x-decorator="FormItem" x-component="Input" :x-decorator-props="{ addonAfter: '单位', }" :required="true" /> </SchemaVoidField> <SchemaStringField name="textarea" title="文本框" x-decorator="FormItem" :required="true" x-component="Input" :x-component-props="{ style: { width: 400, }, type: 'textarea', }" /> </SchemaVoidField> </SchemaField> <FormButtonGroup alignFormItem> <Submit onSubmit="log">提交</Submit> </FormButtonGroup> </FormLayout> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { createSchemaField, FormProvider } from '@formily/vue' import { FormLayout, FormItem, Input, Space, FormButtonGroup, Submit, } from '@formily/element' const fields = createSchemaField({ components: { FormItem, FormLayout, Input, Space }, }) export default { components: { FormProvider, FormLayout, FormButtonGroup, Submit, ...fields }, data() { const form = createForm() return { form, } }, methods: { log(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/react/docs/api/shared/observer.md: -------------------------------------------------------------------------------- ```markdown # observer ## observer ### Description The observer is a [HOC](https://reactjs.bootcss.com/docs/higher-order-components.html), which is used to add reactive features to react functional components. ### When to use When a component uses an [observable](https://reactive.formilyjs.org/api/observable) object inside, and you want the component to respond to changes in the observable object. ### API definition ```ts interface IObserverOptions { // Do you need observers to use forwardRef to pass ref attributes forwardRef?: boolean scheduler?: (updater: () => void) => void displayName?: string } function observer<P, Options extends IObserverOptions>( component: React.FunctionComponent<P>, options?: Options ): React.MemoExoticComponent< React.FunctionComponent< Options extends { forwardRef: true } ? React.PropsWithRef<P> : React.PropsWithoutRef<P> > > ``` ### Example ```tsx /** * defaultShowCode: true */ import React from 'react' import { observable } from '@formily/reactive' import { observer } from '@formily/reactive-react' const obs = observable({ value: 'Hello world', }) export default observer(() => { return ( <div> <div> <input style={{ height: 28, padding: '0 8px', border: '2px solid #888', borderRadius: 3, }} value={obs.value} onChange={(e) => { obs.value = e.target.value }} /> </div> <div>{obs.value}</div> </div> ) }) ``` ### Note `observer` can only receive callable function components, and does not support packaged components such as `React.forwardRef` | `React.memo`. ## Observer ### Description Similar to Vue's responsive slot, it receives a Function RenderProps, as long as any responsive data consumed inside the Function, it will be automatically re-rendered as the data changes, and it is easier to achieve local accurate rendering In fact, the function of this API is basically the same as that of FormConsumer, except that FormConsumer reveals the form instance of the current context in the RenderProps parameter. ### Signature ```ts interface IObserverProps { children?: () => React.ReactElement } type Observer = React.FC<React.PropsWithChildren<IObserverProps>> ``` ### Example ```tsx /** * defaultShowCode: true */ import React from 'react' import { observable } from '@formily/reactive' import { Observer } from '@formily/react' const obs = observable({ value: 'Hello world', }) export default () => { return ( <div> <div> <Observer> {() => ( <input style={{ height: 28, padding: '0 8px', border: '2px solid #888', borderRadius: 3, }} value={obs.value} onChange={(e) => { obs.value = e.target.value }} /> )} </Observer> </div> <Observer>{() => <div>{obs.value}</div>}</Observer> </div> ) } ``` ``` -------------------------------------------------------------------------------- /packages/react/src/__tests__/expression.spec.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { render, waitFor } from '@testing-library/react' import { createForm } from '@formily/core' import { FormProvider, ExpressionScope, createSchemaField, useField, Field, } from '..' test('expression scope', async () => { const Container = (props) => { return ( <ExpressionScope value={{ $innerScope: 'this is inner scope value' }}> {props.children} </ExpressionScope> ) } const Input = (props) => <div data-testid="test-input">{props.value}</div> const SchemaField = createSchemaField({ components: { Container, Input, }, }) const form = createForm() const { getByTestId } = render( <FormProvider form={form}> <SchemaField scope={{ $outerScope: 'this is outer scope value' }}> <SchemaField.Void x-component="Container"> <SchemaField.String name="input" x-component="Input" x-value="{{$innerScope + ' ' + $outerScope}}" /> </SchemaField.Void> </SchemaField> </FormProvider> ) expect(getByTestId('test-input').textContent).toBe( 'this is inner scope value this is outer scope value' ) }) test('x-compile-omitted', async () => { const form = createForm() const SchemaField = createSchemaField({ components: { Input: (props) => ( <div data-testid="input"> {props.aa} {useField().title} {props.extra} </div> ), }, }) const { queryByTestId } = render( <FormProvider form={form}> <SchemaField> <SchemaField.String name="target" x-compile-omitted={['x-component-props']} title="{{123 + '321'}}" x-component-props={{ aa: '{{fake}}', extra: 'extra', }} x-component="Input" /> <SchemaField.String name="btn" x-component="Button" /> </SchemaField> </FormProvider> ) await waitFor(() => { expect(queryByTestId('input')?.textContent).toBe('{{fake}}123321extra') }) }) test('field hidden & visible', async () => { const form = createForm({ initialValues: { empty: null } }) const { findByTestId } = render( <FormProvider form={form}> <div data-testid="testid"> <Field name="empty" component={['input']} /> </div> </FormProvider> ) await findByTestId('testid') // expect(form.fields.empty.hidden).toBe(false) expect(form.fields.empty.value).toBe(null) form.fields.empty.hidden = true expect(form.fields.empty.hidden).toBe(true) expect(form.fields.empty.value).toBe(null) form.fields.empty.hidden = false expect(form.fields.empty.hidden).toBe(false) expect(form.fields.empty.value).toBe(null) // expect(form.fields.empty.visible).toBe(true) expect(form.fields.empty.value).toBe(null) form.fields.empty.visible = false expect(form.fields.empty.visible).toBe(false) expect(form.fields.empty.value).toBe(undefined) form.fields.empty.visible = true expect(form.fields.empty.visible).toBe(true) expect(form.fields.empty.value).toBe(null) }) ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Submit.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Submit > 提交按钮 ## 普通提交 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="输入框" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="input2" title="输入框" default="123" required x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 防重复提交(Loading) ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="输入框" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="input2" title="输入框" default="123" required x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={(values) => { return new Promise((resolve) => { setTimeout(() => { console.log(values) resolve() }, 2000) }) }} onSubmitFailed={console.log} > 提交 </Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 按钮相关的 API 属性,我们参考 https://ant.design/components/button-cn/ 即可,剩下是 Submit 组件独有的 API 属性 | 属性名 | 类型 | 描述 | 默认值 | | --------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------- | ------ | | onClick | `(event: MouseEvent) => void \| boolean` | 点击事件,如果返回 false 可以阻塞提交 | - | | onSubmit | `(values: any) => Promise<any> \| any` | 提交事件回调 | - | | onSubmitSuccess | (payload: any) => void | 提交成功响应事件 | - | | onSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/zh-CN/api/models/form#iformfeedback)[]) => void | 提交校验失败事件回调 | - | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Submit.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Submit > 提交按钮 ## 普通提交 ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="输入框" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="input2" title="输入框" default="123" required x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 防重复提交(Loading) ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="输入框" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="input2" title="输入框" default="123" required x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={(values) => { return new Promise((resolve) => { setTimeout(() => { console.log(values) resolve() }, 2000) }) }} onSubmitFailed={console.log} > 提交 </Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 按钮相关的 API 属性,我们参考 https://fusion.design/pc/component/basic/button 即可,剩下是 Submit 组件独有的 API 属性 | 属性名 | 类型 | 描述 | 默认值 | | --------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------- | ------ | | onClick | `(event: MouseEvent) => void \| boolean` | 点击事件,如果返回 false 可以阻塞提交 | - | | onSubmit | `(values: any) => Promise<any> \| any` | 提交事件回调 | - | | onSubmitSuccess | (payload: any) => void | 提交成功响应事件 | - | | onSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/zh-CN/api/models/form#iformfeedback)[]) => void | 提交校验失败事件回调 | - | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Input.md: -------------------------------------------------------------------------------- ```markdown # Input > Text input box ## Markup Schema example ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="input box" x-decorator="FormItem" x-component="Input" required x-component-props={{ style: { width: 240, }, }} /> <SchemaField.String name="textarea" title="text box" x-decorator="FormItem" required x-component="Input.TextArea" x-component-props={{ style: { width: 400, }, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { style: { width: 240, }, }, }, textarea: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': { style: { width: 240, }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="input" title="input box" required decorator={[FormItem]} component={[ Input, { style: { width: 240, }, }, ]} /> <Field name="textarea" title="text box" required decorator={[FormItem]} component={[ Input.TextArea, { style: { width: 400, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://ant.design/components/input-cn/ ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Input.md: -------------------------------------------------------------------------------- ```markdown # Input > Text input box ## Markup Schema example ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="input box" x-decorator="FormItem" x-component="Input" required x-component-props={{ style: { width: 240, }, }} /> <SchemaField.String name="textarea" title="text box" x-decorator="FormItem" required x-component="Input.TextArea" x-component-props={{ style: { width: 400, }, }} /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { input: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { style: { width: 240, }, }, }, textarea: { type: 'string', title: 'input box', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': { style: { width: 240, }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="input" title="input box" required decorator={[FormItem]} component={[ Input, { style: { width: 240, }, }, ]} /> <Field name="textarea" title="text box" required decorator={[FormItem]} component={[ Input.TextArea, { style: { width: 400, }, }, ]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://fusion.design/pc/component/basic/input ``` -------------------------------------------------------------------------------- /packages/vue/docs/.vuepress/config.js: -------------------------------------------------------------------------------- ```javascript const path = require('path') module.exports = { title: 'Formily Vue', dest: './doc-site', theme: '@vuepress-dumi/dumi', head: [ [ 'link', { rel: 'icon', href: '//img.alicdn.com/imgextra/i3/O1CN01XtT3Tv1Wd1b5hNVKy_!!6000000002810-55-tps-360-360.svg', }, ], ], themeConfig: { logo: '//img.alicdn.com/imgextra/i2/O1CN01Kq3OHU1fph6LGqjIz_!!6000000004056-55-tps-1141-150.svg', nav: [ { text: '指南', link: '/guide/', }, { text: 'API', link: '/api/components/field', }, { text: 'Q&A', link: '/questions/', }, { text: '主站', link: 'https://formilyjs.org', }, { text: 'GITHUB', link: 'https://github.com/alibaba/formily', }, ], sidebar: { '/guide/': ['', 'architecture', 'concept'], '/api/': [ { title: 'Components', children: [ '/api/components/field', '/api/components/array-field', '/api/components/object-field', '/api/components/void-field', '/api/components/schema-field', '/api/components/schema-field-with-schema', '/api/components/recursion-field', '/api/components/recursion-field-with-component', '/api/components/form-provider', '/api/components/form-consumer', '/api/components/expression-scope', ], }, { title: 'Hooks', children: [ '/api/hooks/use-field', '/api/hooks/use-field-schema', '/api/hooks/use-form', '/api/hooks/use-form-effects', '/api/hooks/use-parent-form', ], }, { title: 'Shared', children: [ '/api/shared/connect', '/api/shared/injections', '/api/shared/map-props', '/api/shared/map-read-pretty', '/api/shared/observer', '/api/shared/schema', ], }, ], }, lastUpdated: 'Last Updated', smoothScroll: true, }, plugins: [ 'vuepress-plugin-typescript', '@vuepress/back-to-top', '@vuepress/last-updated', '@vuepress-dumi/dumi-previewer', [ '@vuepress/medium-zoom', { selector: '.content__default :not(a) > img', }, ], ], configureWebpack: (config, isServer) => { return { resolve: { alias: { '@formily/vue': path.resolve(__dirname, '../../src'), '@formily/json-schema': path.resolve( __dirname, '../../../json-schema/src' ), '@formily/path': path.resolve(__dirname, '../../../path/src'), '@formily/reactive-vue': path.resolve( __dirname, '../../../reactive-vue/src' ), '@formily/element': path.resolve(__dirname, '../../../element/src'), vue: path.resolve( __dirname, '../../../../node_modules/vue/dist/vue.runtime.esm.js' ), }, }, } }, } ``` -------------------------------------------------------------------------------- /packages/json-schema/src/compiler.ts: -------------------------------------------------------------------------------- ```typescript import { isArr, isFn, isPlainObj, isStr, reduce, FormPath, } from '@formily/shared' import { IGeneralFieldState } from '@formily/core' import { untracked, hasCollected } from '@formily/reactive' import { traverse, traverseSchema, isNoNeedCompileObject, hasOwnProperty, patchStateFormSchema, } from './shared' import { ISchema } from './types' const ExpRE = /^\s*\{\{([\s\S]*)\}\}\s*$/ const Registry = { silent: false, compile(expression: string, scope = {}) { if (Registry.silent) { try { return new Function('$root', `with($root) { return (${expression}); }`)( scope ) } catch {} } else { return new Function('$root', `with($root) { return (${expression}); }`)( scope ) } }, } export const silent = (value = true) => { Registry.silent = !!value } export const registerCompiler = ( compiler: (expression: string, scope: any) => any ) => { if (isFn(compiler)) { Registry.compile = compiler } } export const shallowCompile = <Source = any, Scope = any>( source: Source, scope?: Scope ) => { if (isStr(source)) { const matched = source.match(ExpRE) if (!matched) return source return Registry.compile(matched[1], scope) } return source } export const compile = <Source = any, Scope = any>( source: Source, scope?: Scope ): any => { const seenObjects = [] const compile = (source: any) => { if (isStr(source)) { return shallowCompile(source, scope) } else if (isArr(source)) { return source.map((value: any) => compile(value)) } else if (isPlainObj(source)) { if (isNoNeedCompileObject(source)) return source const seenIndex = seenObjects.indexOf(source) if (seenIndex > -1) { return source } const addIndex = seenObjects.length seenObjects.push(source) const results = reduce( source, (buf, value, key) => { buf[key] = compile(value) return buf }, {} ) seenObjects.splice(addIndex, 1) return results } return source } return compile(source) } export const patchCompile = ( targetState: IGeneralFieldState, sourceState: any, scope: any ) => { traverse(sourceState, (value, pattern) => { const compiled = compile(value, scope) if (compiled === undefined) return const path = FormPath.parse(pattern) const key = path.segments[0] if (hasOwnProperty.call(targetState, key)) { untracked(() => FormPath.setIn(targetState, path, compiled)) } }) } export const patchSchemaCompile = ( targetState: IGeneralFieldState, sourceSchema: ISchema, scope: any, demand = false ) => { traverseSchema(sourceSchema, (value, path, omitCompile) => { let compiled = value let collected = hasCollected(() => { if (!omitCompile) { compiled = compile(value, scope) } }) if (compiled === undefined) return if (demand) { if (collected || !targetState.initialized) { patchStateFormSchema(targetState, path, compiled) } } else { patchStateFormSchema(targetState, path, compiled) } }) } ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/externals.spec.ts: -------------------------------------------------------------------------------- ```typescript import { isObservable, isSupportObservable, markObservable, markRaw, observable, toJS, } from '..' test('is support observable', () => { const obs = observable<any>({ aa: 111 }) class Class {} expect(isSupportObservable(obs)).toBe(true) expect(isSupportObservable(new Class())).toBe(true) expect(isSupportObservable(null)).toBe(false) expect(isSupportObservable([])).toBe(true) expect(isSupportObservable({})).toBe(true) expect(isSupportObservable({ $$typeof: {}, _owner: {} })).toBe(false) expect(isSupportObservable({ _isAMomentObject: {} })).toBe(false) expect(isSupportObservable({ _isJSONSchemaObject: {} })).toBe(false) expect(isSupportObservable({ toJS: () => {} })).toBe(false) expect(isSupportObservable({ toJSON: () => {} })).toBe(false) expect(isSupportObservable(new Map())).toBe(true) expect(isSupportObservable(new WeakMap())).toBe(true) expect(isSupportObservable(new Set())).toBe(true) expect(isSupportObservable(new WeakSet())).toBe(true) }) describe('mark operation', () => { test('plain object should be observable', () => { const obs = observable<any>({ aa: 111 }) expect(isObservable(obs)).toBe(true) }) test('class instance should be observable', () => { class Class {} const obs = observable<any>(new Class()) const obs2 = observable<any>(new Class()) expect(isObservable(obs)).toBe(true) expect(isObservable(obs2)).toBe(true) }) test('object with toJS function should NOT be observable', () => { const obs = observable<any>({ aa: 111, toJS: () => {} }) expect(isObservable(obs)).toBe(false) }) test('plain object marked as raw should NOT be observable', () => { const obs = observable<any>(markRaw({ aa: 111 })) expect(isObservable(obs)).toBe(false) }) test('class marked as raw instance should NOT be observable', () => { class Class {} markRaw(Class) const obs = observable<any>(new Class()) const obs2 = observable<any>(new Class()) expect(isObservable(obs)).toBe(false) expect(isObservable(obs2)).toBe(false) }) test('object with toJS function marked as observable should be observable', () => { const obs = observable<any>(markObservable({ aa: 111, toJS: () => {} })) expect(isObservable(obs)).toBe(true) }) test('plain object marked as raw and observable should NOT be observable', () => { const obs = observable<any>(markRaw(markObservable({ aa: 111 }))) expect(isObservable(obs)).toBe(false) }) test('plain object marked as observable and raw should NOT be observable', () => { const obs = observable<any>(markObservable(markRaw({ aa: 111 }))) expect(isObservable(obs)).toBe(false) }) test('function marked as observable should NOT be observable', () => { const obs = observable<any>(markObservable(() => {})) expect(isObservable(obs)).toBe(false) }) }) test('recursive references tojs', () => { const obj: any = { aa: 111 } obj.obj = obj const obs = observable<any>(obj) obs.obs = obs expect(toJS(obs)).toBeTruthy() const arrObs = observable([{ aa: 1 }, { bb: 2 }, { cc: 3 }]) expect(toJS(arrObs)).toEqual([{ aa: 1 }, { bb: 2 }, { cc: 3 }]) }) ``` -------------------------------------------------------------------------------- /packages/element/src/upload/index.ts: -------------------------------------------------------------------------------- ```typescript import { Field } from '@formily/core' import { connect, Fragment, h, mapProps, useField } from '@formily/vue' import { defineComponent } from 'vue-demi' import type { ElUpload as ElUploadProps, ElUploadInternalFileDetail, } from 'element-ui/types/upload' import { Button as ElButton, Upload as ElUpload } from 'element-ui' export type UploadProps = ElUploadProps & { textContent?: String errorAdaptor?: (error?: ErrorEvent) => String } const UploadWrapper = defineComponent<UploadProps>({ name: 'FUpload', props: { textContent: { type: String, default: '', }, errorAdaptor: { type: Function, default(error?: ErrorEvent) { return error?.message || '' }, }, }, setup(curProps: UploadProps, { slots, attrs, listeners, emit }) { return () => { const fieldRef = useField<Field>() const setFeedBack = (error?: ErrorEvent) => { const message = curProps.errorAdaptor(error) fieldRef.value.setFeedback({ type: 'error', code: 'UploadError', messages: message ? [message] : [], }) } const props = { ...attrs, onChange( file: ElUploadInternalFileDetail, fileList: ElUploadInternalFileDetail[] ) { ;(attrs.onChange as Function)?.(file, fileList) setFeedBack() emit('change', fileList) }, onRemove( file: ElUploadInternalFileDetail, fileList: ElUploadInternalFileDetail[] ) { ;(attrs.onRemove as Function)?.(file, fileList) setFeedBack() emit('change', fileList) }, onError( error: ErrorEvent, file: ElUploadInternalFileDetail, fileList: ElUploadInternalFileDetail[] ) { ;(attrs.onError as Function)?.(error, file, fileList) setTimeout(() => { setFeedBack(error) }, 0) }, } const children = { ...slots, } if (!slots.default) { children.default = () => { const listType = attrs.listType const drag = attrs.drag if (drag) { return h( Fragment, {}, { default: () => [ h('i', { staticClass: 'el-icon-upload' }, {}), h( 'div', { staticClass: 'el-upload__text' }, { default: () => [curProps.textContent] } ), ], } ) } if (listType === 'picture-card') { return h( 'i', { staticClass: 'el-icon-plus', }, {} ) } return h( ElButton, { props: { icon: 'el-icon-upload2' } }, { default: () => [curProps.textContent] } ) } } return h(ElUpload, { attrs: props, on: listeners }, children) } }, }) export const Upload = connect( UploadWrapper, mapProps({ readOnly: 'readonly', value: 'fileList' }) ) export default Upload ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/observe.spec.ts: -------------------------------------------------------------------------------- ```typescript import { observable, observe } from '../' test('deep observe', () => { const obs = observable<any>({ aa: { bb: { cc: [11, 22, 33], }, }, ee: observable([]), }) const handler = jest.fn() observe(obs, handler) obs.dd = 123 obs.aa.bb.cc.push(44) expect(obs.aa.bb.cc).toEqual([11, 22, 33, 44]) expect(handler).toHaveBeenCalledTimes(2) delete obs.aa expect(handler).toHaveBeenCalledTimes(3) // Are these expected behaviors? obs.ee.push(11) expect(handler).toHaveBeenCalledTimes(3) obs.ee = [] expect(handler).toHaveBeenCalledTimes(4) obs.ee.push(11) expect(handler).toHaveBeenCalledTimes(5) }) test('shallow observe', () => { const obs = observable<any>({ aa: { bb: { cc: [11, 22, 33], }, }, }) const handler = jest.fn() observe(obs, handler, false) obs.dd = 123 obs.aa.bb.cc.push(44) expect(obs.aa.bb.cc).toEqual([11, 22, 33, 44]) expect(handler).toHaveBeenCalledTimes(1) delete obs.aa expect(handler).toHaveBeenCalledTimes(2) }) test('root replace observe', () => { const obs = observable<any>({ aa: { bb: { cc: [11, 22, 33], }, }, }) const handler1 = jest.fn() const handler = jest.fn() observe(obs, handler1) observe(obs.aa, handler) obs.aa = { mm: 123, } expect(handler1).toBeCalledTimes(1) expect(handler).toBeCalledTimes(1) obs.aa = { bb: { cc: [11, 22, 33], }, } obs.aa.bb.cc.push(44) expect(handler1).toBeCalledTimes(3) expect(handler).toBeCalledTimes(3) }) test('dispose observe', () => { const obs = observable<any>({ aa: { bb: { cc: [11, 22, 33], }, }, }) const handler = jest.fn() const dispose = observe(obs, handler) obs.kk = 123 expect(handler).toBeCalledTimes(1) dispose() obs.aa = 123 expect(handler).toBeCalledTimes(1) }) test('dispose observe', () => { const obs = observable<any>({ aa: { bb: { cc: [11, 22, 33], }, }, }) const handler = jest.fn() const dispose = observe(obs.aa, handler) obs.kk = 111 expect(handler).toBeCalledTimes(0) obs.aa = { mm: 222 } expect(handler).toBeCalledTimes(1) obs.aa = { mm: 222 } expect(handler).toBeCalledTimes(2) obs.aa = { mm: '111' } expect(handler).toBeCalledTimes(3) obs.aa = { mm: 333 } expect(handler).toBeCalledTimes(4) dispose() obs.aa = { mm: 444 } expect(handler).toBeCalledTimes(4) }) test('array delete', () => { const array = observable([{ value: 1 }, { value: 2 }]) const fn = jest.fn() const dispose = observe(array, (change) => { if (change.type === 'set' && change.key === 'value') { fn(change.path?.join('.')) } }) array[0].value = 3 expect(fn.mock.calls[0][0]).toBe('0.value') array.splice(0, 1) array[0].value = 3 expect(fn.mock.calls[1][0]).toBe('0.value') dispose() }) test('observe dynamic tree', () => { const handler = jest.fn() const tree = observable<any>({}) const childTree = observable({}) tree.children = childTree observe(tree, handler) tree.children.aa = 123 expect(handler).toBeCalledTimes(1) }) test('invalid target', () => { expect(() => observe(function () {})).toThrowError() }) ```