This is page 5 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/recursion-field.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaObjectField 5 | name="custom" 6 | x-component="Custom" 7 | :x-component-props="{ 8 | schema: { 9 | type: 'object', 10 | properties: { 11 | input: { 12 | type: 'string', 13 | 'x-component': 'Input', 14 | }, 15 | }, 16 | }, 17 | }" 18 | /> 19 | </SchemaField> 20 | </FormProvider> 21 | </template> 22 | 23 | <script> 24 | import { Input } from 'ant-design-vue' 25 | import { createForm } from '@formily/core' 26 | import { FormProvider, createSchemaField, RecursionField } from '@formily/vue' 27 | import 'ant-design-vue/dist/antd.css' 28 | 29 | // functional component in vue2 30 | const Custom = { 31 | functional: true, 32 | render(h, { props }) { 33 | return h(RecursionField, { 34 | props: { 35 | name: props.name, 36 | schema: props.schema, 37 | onlyRenderProperties: true, 38 | }, 39 | }) 40 | }, 41 | } 42 | 43 | const { SchemaField, SchemaObjectField } = createSchemaField({ 44 | components: { 45 | Custom, 46 | Input, 47 | }, 48 | }) 49 | 50 | export default { 51 | components: { FormProvider, SchemaField, SchemaObjectField }, 52 | data() { 53 | return { 54 | form: createForm(), 55 | } 56 | }, 57 | } 58 | </script> 59 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/time-picker/json-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <Form :form="form"> 3 | <SchemaField :schema="schema" /> 4 | <Submit @submit="onSubmit">提交</Submit> 5 | </Form> 6 | </template> 7 | 8 | <script> 9 | import { createForm } from '@formily/core' 10 | import { createSchemaField } from '@formily/vue' 11 | import { Form, FormItem, TimePicker, Submit } from '@formily/element' 12 | 13 | const schema = { 14 | type: 'object', 15 | properties: { 16 | time: { 17 | type: 'string', 18 | title: '时间', 19 | 'x-decorator': 'FormItem', 20 | 'x-component': 'TimePicker', 21 | 'x-component-props': { 22 | style: { 23 | width: '240px', 24 | }, 25 | }, 26 | }, 27 | '[startTime,endTime]': { 28 | title: '时间范围', 29 | 'x-decorator': 'FormItem', 30 | 'x-component': 'TimePicker', 31 | 'x-component-props': { 32 | isRange: true, 33 | style: { 34 | width: '240px', 35 | }, 36 | }, 37 | type: 'string', 38 | }, 39 | }, 40 | } 41 | 42 | const form = createForm() 43 | const { SchemaField } = createSchemaField({ 44 | components: { 45 | FormItem, 46 | TimePicker, 47 | }, 48 | }) 49 | 50 | export default { 51 | components: { Form, SchemaField, Submit }, 52 | data() { 53 | return { 54 | form, 55 | schema, 56 | } 57 | }, 58 | methods: { 59 | onSubmit(value) { 60 | console.log(value) 61 | }, 62 | }, 63 | } 64 | </script> 65 | ``` -------------------------------------------------------------------------------- /packages/benchmark/webpack.dev.ts: -------------------------------------------------------------------------------- ```typescript 1 | import baseConfig from './webpack.base' 2 | import HtmlWebpackPlugin from 'html-webpack-plugin' 3 | import MiniCssExtractPlugin from 'mini-css-extract-plugin' 4 | import webpack from 'webpack' 5 | import path from 'path' 6 | 7 | const PORT = 3000 8 | 9 | const createPages = (pages) => { 10 | return pages.map(({ filename, template, chunk }) => { 11 | return new HtmlWebpackPlugin({ 12 | filename, 13 | template, 14 | inject: 'body', 15 | chunks: chunk, 16 | }) 17 | }) 18 | } 19 | 20 | for (const key in baseConfig.entry) { 21 | if (Array.isArray(baseConfig.entry[key])) { 22 | baseConfig.entry[key].push( 23 | require.resolve('webpack/hot/dev-server'), 24 | `${require.resolve('webpack-dev-server/client')}?http://localhost:${PORT}` 25 | ) 26 | } 27 | } 28 | 29 | export default { 30 | ...baseConfig, 31 | plugins: [ 32 | new MiniCssExtractPlugin({ 33 | filename: '[name].[hash].css', 34 | chunkFilename: '[id].[hash].css', 35 | }), 36 | ...createPages([ 37 | { 38 | filename: 'index.html', 39 | template: path.resolve(__dirname, './template.ejs'), 40 | chunk: ['index'], 41 | }, 42 | ]), 43 | new webpack.HotModuleReplacementPlugin(), 44 | // new BundleAnalyzerPlugin() 45 | ], 46 | devServer: { 47 | host: '127.0.0.1', 48 | open: true, 49 | port: PORT, 50 | }, 51 | } 52 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-button-group.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <FormLayout :labelCol="6" :wrapperCol="10"> 4 | <SchemaField> 5 | <SchemaStringField 6 | required 7 | title="输入框" 8 | x-decorator="FormItem" 9 | x-component="Input" 10 | /> 11 | <SchemaStringField 12 | required 13 | title="输入框" 14 | x-decorator="FormItem" 15 | x-component="Input" 16 | /> 17 | </SchemaField> 18 | <FormButtonGroup align-form-item> 19 | <Submit @submit="log">提交</Submit> 20 | <Reset>重置</Reset> 21 | </FormButtonGroup> 22 | </FormLayout> 23 | </FormProvider> 24 | </template> 25 | 26 | <script> 27 | import { createForm } from '@formily/core' 28 | import { FormProvider, createSchemaField } from '@formily/vue' 29 | import { 30 | FormLayout, 31 | Submit, 32 | Reset, 33 | FormButtonGroup, 34 | FormItem, 35 | Input, 36 | Select, 37 | } from '@formily/element' 38 | 39 | const fields = createSchemaField({ components: { FormItem, Input, Select } }) 40 | 41 | export default { 42 | components: { 43 | FormProvider, 44 | FormLayout, 45 | Submit, 46 | Reset, 47 | FormButtonGroup, 48 | ...fields, 49 | }, 50 | data() { 51 | const form = createForm() 52 | return { 53 | form, 54 | } 55 | }, 56 | methods: { 57 | log(v) { 58 | console.log(v) 59 | }, 60 | }, 61 | } 62 | </script> 63 | ``` -------------------------------------------------------------------------------- /packages/reactive-test-cases-for-react18/webpack.dev.ts: -------------------------------------------------------------------------------- ```typescript 1 | import baseConfig from './webpack.base' 2 | import HtmlWebpackPlugin from 'html-webpack-plugin' 3 | import MiniCssExtractPlugin from 'mini-css-extract-plugin' 4 | import webpack from 'webpack' 5 | import path from 'path' 6 | 7 | const PORT = 3000 8 | 9 | const createPages = (pages) => { 10 | return pages.map(({ filename, template, chunk }) => { 11 | return new HtmlWebpackPlugin({ 12 | filename, 13 | template, 14 | inject: 'body', 15 | chunks: chunk, 16 | }) 17 | }) 18 | } 19 | 20 | for (const key in baseConfig.entry) { 21 | if (Array.isArray(baseConfig.entry[key])) { 22 | baseConfig.entry[key].push( 23 | require.resolve('webpack/hot/dev-server'), 24 | `${require.resolve('webpack-dev-server/client')}?http://localhost:${PORT}` 25 | ) 26 | } 27 | } 28 | 29 | export default { 30 | ...baseConfig, 31 | plugins: [ 32 | new MiniCssExtractPlugin({ 33 | filename: '[name].[hash].css', 34 | chunkFilename: '[id].[hash].css', 35 | }), 36 | ...createPages([ 37 | { 38 | filename: 'index.html', 39 | template: path.resolve(__dirname, './template.ejs'), 40 | chunk: ['index'], 41 | }, 42 | ]), 43 | new webpack.HotModuleReplacementPlugin(), 44 | // new BundleAnalyzerPlugin() 45 | ], 46 | devServer: { 47 | host: '127.0.0.1', 48 | open: true, 49 | port: PORT, 50 | }, 51 | } 52 | ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/app/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useState } from 'react' 2 | import { LeftPanel } from './components/LeftPanel' 3 | import { RightPanel } from './components/RightPanel' 4 | import styled from 'styled-components' 5 | 6 | export default styled(({ className, dataSource }) => { 7 | const [selected, select] = useState({ 8 | current: 0, 9 | key: '', 10 | }) 11 | return ( 12 | <div className={className}> 13 | <LeftPanel 14 | dataSource={dataSource} 15 | onSelect={(info) => { 16 | select(info) 17 | if (chrome && chrome.devtools && chrome.devtools.inspectedWindow) { 18 | chrome.devtools.inspectedWindow.eval( 19 | `window.__FORMILY_DEV_TOOLS_HOOK__.setVm("${info.key}","${ 20 | dataSource[info.current][''].id 21 | }")` 22 | ) 23 | } 24 | }} 25 | /> 26 | <RightPanel 27 | dataSource={ 28 | selected 29 | ? (dataSource && 30 | dataSource[selected.current] && 31 | dataSource[selected.current][selected.key]) || 32 | {} 33 | : {} 34 | } 35 | /> 36 | </div> 37 | ) 38 | })` 39 | display: flex; 40 | position: absolute; 41 | top: 0; 42 | bottom: 0; 43 | left: 0; 44 | right: 0; 45 | overflow: hidden; 46 | color: #36d4c7; 47 | background: #282c34; 48 | ` 49 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/annotations/box.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ProxyRaw, RawProxy } from '../environment' 2 | import { createAnnotation } from '../internals' 3 | import { buildDataTree } from '../tree' 4 | import { 5 | bindTargetKeyWithCurrentReaction, 6 | runReactionsFromTargetKey, 7 | } from '../reaction' 8 | 9 | export interface IBox { 10 | <T>(target: T): { get(): T; set(value: T): void } 11 | } 12 | 13 | export const box: IBox = createAnnotation(({ target, key, value }) => { 14 | const store = { 15 | value: target ? target[key] : value, 16 | } 17 | 18 | const proxy = { 19 | set, 20 | get, 21 | } 22 | 23 | ProxyRaw.set(proxy, store) 24 | RawProxy.set(store, proxy) 25 | 26 | buildDataTree(target, key, store) 27 | 28 | function get() { 29 | bindTargetKeyWithCurrentReaction({ 30 | target: store, 31 | key, 32 | type: 'get', 33 | }) 34 | return store.value 35 | } 36 | 37 | function set(value: any) { 38 | const oldValue = store.value 39 | store.value = value 40 | if (oldValue !== value) { 41 | runReactionsFromTargetKey({ 42 | target: store, 43 | key, 44 | type: 'set', 45 | oldValue, 46 | value, 47 | }) 48 | } 49 | } 50 | 51 | if (target) { 52 | Object.defineProperty(target, key, { 53 | value: proxy, 54 | enumerable: true, 55 | configurable: false, 56 | writable: false, 57 | }) 58 | return target 59 | } 60 | return proxy 61 | }) 62 | ``` -------------------------------------------------------------------------------- /packages/react/docs/api/hooks/useFieldSchema.md: -------------------------------------------------------------------------------- ```markdown 1 | # useFieldSchema 2 | 3 | ## Description 4 | 5 | Mainly read the Schema information of the current field in the custom component, this hook can only be used in the subtree of SchemaField or RecursionField 6 | 7 | ## Signature 8 | 9 | ```ts 10 | interface useFieldSchema { 11 | (): Schema 12 | } 13 | ``` 14 | 15 | Schema Reference [Schema](/api/shared/schema) 16 | 17 | ## Example 18 | 19 | ```tsx 20 | import React from 'react' 21 | import { createForm } from '@formily/core' 22 | import { FormProvider, createSchemaField, useFieldSchema } from '@formily/react' 23 | 24 | const form = createForm() 25 | 26 | const Custom = () => { 27 | const schema = useFieldSchema() 28 | return ( 29 | <code> 30 | <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre> 31 | </code> 32 | ) 33 | } 34 | 35 | const SchemaField = createSchemaField({ 36 | components: { 37 | Custom, 38 | }, 39 | }) 40 | 41 | export default () => ( 42 | <FormProvider form={form}> 43 | <SchemaField> 44 | <SchemaField.Object 45 | name="custom" 46 | x-component="Custom" 47 | x-component-props={{ 48 | schema: { 49 | type: 'object', 50 | properties: { 51 | input: { 52 | type: 'string', 53 | 'x-component': 'Custom', 54 | }, 55 | }, 56 | }, 57 | }} 58 | /> 59 | </SchemaField> 60 | </FormProvider> 61 | ) 62 | ``` 63 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/expression-scope.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField :scope="{ $outerScope: 'outer scope value' }"> 4 | <SchemaVoidField x-component="Container"> 5 | <SchemaVoidField 6 | name="div" 7 | x-component="Text" 8 | :x-component-props="{ text: `{{$innerScope + ' ' + $outerScope}}` }" 9 | /> 10 | </SchemaVoidField> 11 | </SchemaField> 12 | </FormProvider> 13 | </template> 14 | 15 | <script> 16 | import { defineComponent } from '@vue/composition-api' 17 | import { createForm } from '@formily/core' 18 | import { 19 | FormProvider, 20 | h, 21 | createSchemaField, 22 | ExpressionScope, 23 | } from '@formily/vue' 24 | 25 | const Container = defineComponent({ 26 | setup(_props, { slots }) { 27 | return () => 28 | h( 29 | ExpressionScope, 30 | { 31 | props: { value: { $innerScope: 'inner scope value' } }, 32 | }, 33 | slots 34 | ) 35 | }, 36 | }) 37 | const Text = defineComponent({ 38 | props: ['text'], 39 | setup(props) { 40 | return () => h('div', {}, { default: () => props.text }) 41 | }, 42 | }) 43 | const SchemaField = createSchemaField({ 44 | components: { Container, Text }, 45 | }) 46 | 47 | export default { 48 | components: { FormProvider, ...SchemaField }, 49 | data() { 50 | return { 51 | Text, 52 | form: createForm(), 53 | } 54 | }, 55 | } 56 | </script> 57 | ``` -------------------------------------------------------------------------------- /packages/json-schema/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@formily/json-schema", 3 | "version": "2.3.7", 4 | "license": "MIT", 5 | "main": "lib", 6 | "module": "esm", 7 | "umd:main": "dist/formily.json-schema.umd.production.js", 8 | "unpkg": "dist/formily.json-schema.umd.production.js", 9 | "jsdelivr": "dist/formily.json-schema.umd.production.js", 10 | "jsnext:main": "esm", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/alibaba/formily.git" 14 | }, 15 | "types": "esm/index.d.ts", 16 | "bugs": { 17 | "url": "https://github.com/alibaba/formily/issues" 18 | }, 19 | "homepage": "https://github.com/alibaba/formily#readme", 20 | "engines": { 21 | "npm": ">=3.0.0" 22 | }, 23 | "scripts": { 24 | "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", 25 | "build:cjs": "tsc --project tsconfig.build.json", 26 | "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", 27 | "build:umd": "rollup --config" 28 | }, 29 | "peerDependencies": { 30 | "typescript": ">4.1.5" 31 | }, 32 | "dependencies": { 33 | "@formily/core": "2.3.7", 34 | "@formily/reactive": "2.3.7", 35 | "@formily/shared": "2.3.7" 36 | }, 37 | "publishConfig": { 38 | "access": "public" 39 | }, 40 | "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" 41 | } 42 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/observe.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { IOperation } from './types' 2 | import { ObserverListeners } from './environment' 3 | import { raw as getRaw } from './externals' 4 | import { isFn } from './checkers' 5 | import { DataChange, getDataNode } from './tree' 6 | 7 | export const observe = ( 8 | target: object, 9 | observer?: (change: DataChange) => void, 10 | deep = true 11 | ) => { 12 | const addListener = (target: any) => { 13 | const raw = getRaw(target) 14 | const node = getDataNode(raw) 15 | 16 | const listener = (operation: IOperation) => { 17 | const targetRaw = getRaw(operation.target) 18 | const targetNode = getDataNode(targetRaw) 19 | if (deep) { 20 | if (node.contains(targetNode)) { 21 | observer(new DataChange(operation, targetNode)) 22 | return 23 | } 24 | } 25 | if ( 26 | node === targetNode || 27 | (node.targetRaw === targetRaw && node.key === operation.key) 28 | ) { 29 | observer(new DataChange(operation, targetNode)) 30 | } 31 | } 32 | 33 | if (node && isFn(observer)) { 34 | ObserverListeners.add(listener) 35 | } 36 | return () => { 37 | ObserverListeners.delete(listener) 38 | } 39 | } 40 | if (target && typeof target !== 'object') 41 | throw Error(`Can not observe ${typeof target} type.`) 42 | return addListener(target) 43 | } 44 | ``` -------------------------------------------------------------------------------- /packages/element/src/date-picker/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { transformComponent } from '../__builtins__/shared' 2 | import { connect, mapProps, mapReadPretty } from '@formily/vue' 3 | 4 | import type { DatePicker as ElDatePickerProps } from 'element-ui' 5 | import { DatePicker as ElDatePicker } from 'element-ui' 6 | import { PreviewText } from '../preview-text' 7 | 8 | export type DatePickerProps = ElDatePickerProps 9 | 10 | const TransformElDatePicker = transformComponent<DatePickerProps>( 11 | ElDatePicker, 12 | { 13 | change: 'input', 14 | } 15 | ) 16 | 17 | const getDefaultFormat = (props, formatType = 'format') => { 18 | const type = props.type 19 | 20 | if (type === 'week' && formatType === 'format') { 21 | return 'yyyy-WW' 22 | } else if (type === 'month') { 23 | return 'yyyy-MM' 24 | } else if (type === 'year') { 25 | return 'yyyy' 26 | } else if (type === 'datetime' || type === 'datetimerange') { 27 | return 'yyyy-MM-dd HH:mm:ss' 28 | } 29 | 30 | return 'yyyy-MM-dd' 31 | } 32 | 33 | export const DatePicker = connect( 34 | TransformElDatePicker, 35 | mapProps({ readOnly: 'readonly' }, (props) => { 36 | return { 37 | ...props, 38 | format: props.format || getDefaultFormat(props), 39 | valueFormat: props.valueFormat || getDefaultFormat(props, 'valueFormat'), 40 | } 41 | }), 42 | mapReadPretty(PreviewText.DatePicker) 43 | ) 44 | 45 | export default DatePicker 46 | ``` -------------------------------------------------------------------------------- /packages/antd/src/array-table/style.less: -------------------------------------------------------------------------------- ``` 1 | @root-entry-name: 'default'; 2 | @import (reference) '~antd/es/style/themes/index.less'; 3 | 4 | @array-table-prefix-cls: ~'@{ant-prefix}-formily-array-table'; 5 | 6 | .@{array-table-prefix-cls} { 7 | .@{array-table-prefix-cls}-pagination { 8 | display: flex; 9 | justify-content: center; 10 | 11 | .@{array-table-prefix-cls}-status-select.has-error { 12 | .@{ant-prefix}-select-selector { 13 | border-color: @error-color !important; 14 | } 15 | } 16 | } 17 | 18 | .@{ant-prefix}-table { 19 | table { 20 | overflow: hidden; 21 | } 22 | 23 | td { 24 | visibility: visible; 25 | 26 | .@{ant-prefix}-formily-item:not(.@{ant-prefix}-formily-item-feedback-layout-popover) { 27 | margin-bottom: 0 !important; 28 | 29 | .@{ant-prefix}-formily-item-help { 30 | position: absolute; 31 | font-size: 12px; 32 | top: 100%; 33 | background: #fff; 34 | width: 100%; 35 | margin-top: 3px; 36 | padding: 3px; 37 | z-index: 1; 38 | border-radius: 3px; 39 | box-shadow: 0 0 10px #eee; 40 | animation: none; 41 | transform: translateY(0); 42 | opacity: 1; 43 | } 44 | } 45 | } 46 | } 47 | 48 | .@{array-table-prefix-cls}-sort-helper { 49 | background: #fff; 50 | border: 1px solid #eee; 51 | z-index: 10; 52 | } 53 | } 54 | ``` -------------------------------------------------------------------------------- /packages/antd/src/submit/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import { Button } from 'antd' 3 | import { ButtonProps } from 'antd/lib/button' 4 | import { IFormFeedback } from '@formily/core' 5 | import { useParentForm, observer } from '@formily/react' 6 | 7 | export interface ISubmitProps extends ButtonProps { 8 | onClick?: (e: React.MouseEvent<Element, MouseEvent>) => any 9 | onSubmit?: (values: any) => any 10 | onSubmitSuccess?: (payload: any) => void 11 | onSubmitFailed?: (feedbacks: IFormFeedback[]) => void 12 | } 13 | 14 | export const Submit: React.FC<React.PropsWithChildren<ISubmitProps>> = observer( 15 | ({ onSubmit, onSubmitFailed, onSubmitSuccess, ...props }: ISubmitProps) => { 16 | const form = useParentForm() 17 | return ( 18 | <Button 19 | htmlType={onSubmit ? 'button' : 'submit'} 20 | type="primary" 21 | {...props} 22 | loading={props.loading !== undefined ? props.loading : form.submitting} 23 | onClick={(e) => { 24 | if (props.onClick) { 25 | if (props.onClick(e) === false) return 26 | } 27 | if (onSubmit) { 28 | form.submit(onSubmit).then(onSubmitSuccess).catch(onSubmitFailed) 29 | } 30 | }} 31 | > 32 | {props.children} 33 | </Button> 34 | ) 35 | }, 36 | { 37 | forwardRef: true, 38 | } 39 | ) 40 | 41 | export default Submit 42 | ``` -------------------------------------------------------------------------------- /packages/vue/src/utils/getFieldProps.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const getFieldProps = () => ({ 2 | name: {}, 3 | title: {}, 4 | description: {}, 5 | value: {}, 6 | initialValue: {}, 7 | basePath: {}, 8 | decorator: Array, 9 | component: Array, 10 | display: String, 11 | pattern: String, 12 | required: { type: Boolean, default: undefined }, 13 | validateFirst: { type: Boolean, default: undefined }, 14 | hidden: { type: Boolean, default: undefined }, 15 | visible: { type: Boolean, default: undefined }, 16 | editable: { type: Boolean, default: undefined }, 17 | disabled: { type: Boolean, default: undefined }, 18 | readOnly: { type: Boolean, default: undefined }, 19 | readPretty: { type: Boolean, default: undefined }, 20 | dataSource: {}, 21 | validator: {}, 22 | reactions: [Array, Function], 23 | }) 24 | 25 | export const getVoidFieldProps = () => ({ 26 | name: {}, 27 | title: {}, 28 | description: {}, 29 | basePath: {}, 30 | decorator: Array, 31 | component: Array, 32 | display: String, 33 | pattern: String, 34 | hidden: { type: Boolean, default: undefined }, 35 | visible: { type: Boolean, default: undefined }, 36 | editable: { type: Boolean, default: undefined }, 37 | disabled: { type: Boolean, default: undefined }, 38 | readOnly: { type: Boolean, default: undefined }, 39 | readPretty: { type: Boolean, default: undefined }, 40 | reactions: [Array, Function], 41 | }) 42 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/submit/loading.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaStringField 5 | required 6 | name="input1" 7 | title="输入框" 8 | x-decorator="FormItem" 9 | x-component="Input" 10 | /> 11 | <SchemaStringField 12 | required 13 | title="输入框" 14 | name="input2" 15 | x-decorator="FormItem" 16 | x-component="Input" 17 | /> 18 | </SchemaField> 19 | <FormButtonGroup align-form-item> 20 | <Submit @submit="handleSubmit">提交</Submit> 21 | </FormButtonGroup> 22 | </FormProvider> 23 | </template> 24 | 25 | <script> 26 | import { createForm } from '@formily/core' 27 | import { FormProvider, createSchemaField } from '@formily/vue' 28 | import { 29 | FormLayout, 30 | Submit, 31 | FormButtonGroup, 32 | FormItem, 33 | Input, 34 | } from '@formily/element' 35 | 36 | const fields = createSchemaField({ components: { FormItem, Input } }) 37 | 38 | export default { 39 | components: { 40 | FormProvider, 41 | FormLayout, 42 | Submit, 43 | FormButtonGroup, 44 | ...fields, 45 | }, 46 | data() { 47 | const form = createForm() 48 | return { 49 | form, 50 | } 51 | }, 52 | methods: { 53 | handleSubmit(values) { 54 | return new Promise((resolve) => { 55 | setTimeout(() => { 56 | console.log(values) 57 | resolve() 58 | }, 2000) 59 | }) 60 | }, 61 | }, 62 | } 63 | </script> 64 | ``` -------------------------------------------------------------------------------- /packages/next/src/__builtins__/moment.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isArr, isEmpty, isFn } from '@formily/shared' 2 | import Moment from 'moment' 3 | 4 | const moment = (date: any, format?: string) => { 5 | return Moment(date?.toDate ? date.toDate() : date, format) 6 | } 7 | 8 | export const momentable = (value: any, format?: string) => { 9 | return Array.isArray(value) 10 | ? value.map((val) => moment(val, format)) 11 | : value 12 | ? moment(value, format) 13 | : value 14 | } 15 | 16 | export const formatMomentValue = ( 17 | value: any, 18 | format: any, 19 | placeholder?: string 20 | ): string | string[] => { 21 | const formatDate = (date: any, format: any, i = 0) => { 22 | if (!date) return placeholder 23 | const TIME_REG = /^(?:[01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/ 24 | let _format = format 25 | if (isArr(format)) { 26 | _format = format[i] 27 | } 28 | if (isFn(_format)) { 29 | return _format(date) 30 | } 31 | if (isEmpty(_format)) { 32 | return date 33 | } 34 | // moment '19:55:22' 下需要传入第二个参数 35 | if (TIME_REG.test(date)) { 36 | return moment(date, _format).format(_format) 37 | } 38 | return moment(date).format(_format) 39 | } 40 | if (isArr(value)) { 41 | return value.map((val, index) => { 42 | return formatDate(val, format, index) 43 | }) 44 | } else { 45 | return value ? formatDate(value, format) : value || placeholder 46 | } 47 | } 48 | ``` -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@formily/shared", 3 | "version": "2.3.7", 4 | "license": "MIT", 5 | "main": "lib", 6 | "module": "esm", 7 | "umd:main": "dist/formily.shared.umd.production.js", 8 | "unpkg": "dist/formily.shared.umd.production.js", 9 | "jsdelivr": "dist/formily.shared.umd.production.js", 10 | "jsnext:main": "esm", 11 | "types": "esm/index.d.ts", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/alibaba/formily.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/alibaba/formily/issues" 18 | }, 19 | "homepage": "https://github.com/alibaba/formily#readme", 20 | "engines": { 21 | "npm": ">=3.0.0" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283", 27 | "scripts": { 28 | "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", 29 | "build:cjs": "tsc --project tsconfig.build.json", 30 | "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", 31 | "build:umd": "rollup --config" 32 | }, 33 | "dependencies": { 34 | "@formily/path": "2.3.7", 35 | "camel-case": "^4.1.1", 36 | "lower-case": "^2.0.1", 37 | "no-case": "^3.0.4", 38 | "param-case": "^3.0.4", 39 | "pascal-case": "^3.1.1", 40 | "upper-case": "^2.0.1" 41 | } 42 | } 43 | ``` -------------------------------------------------------------------------------- /packages/next/src/submit/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import { Button } from '@alifd/next' 3 | import { ButtonProps } from '@alifd/next/lib/button' 4 | import { IFormFeedback } from '@formily/core' 5 | import { useParentForm, observer } from '@formily/react' 6 | 7 | interface ISubmitProps extends ButtonProps { 8 | onClick?: (e: React.MouseEvent<Element, MouseEvent>) => any 9 | onSubmit?: (values: any) => any 10 | onSubmitSuccess?: (payload: any) => void 11 | onSubmitFailed?: (feedbacks: IFormFeedback[]) => void 12 | } 13 | 14 | export const Submit: React.FC<React.PropsWithChildren<ISubmitProps>> = observer( 15 | ({ onSubmit, onSubmitFailed, onSubmitSuccess, ...props }: ISubmitProps) => { 16 | const form = useParentForm() 17 | return ( 18 | <Button 19 | htmlType={onSubmit ? 'button' : 'submit'} 20 | type="primary" 21 | {...props} 22 | loading={props.loading !== undefined ? props.loading : form.submitting} 23 | onClick={(e) => { 24 | if (props.onClick) { 25 | if (props.onClick(e) === false) return 26 | } 27 | if (onSubmit) { 28 | form.submit(onSubmit).then(onSubmitSuccess).catch(onSubmitFailed) 29 | } 30 | }} 31 | > 32 | {props.children} 33 | </Button> 34 | ) 35 | }, 36 | { 37 | forwardRef: true, 38 | } 39 | ) 40 | 41 | export default Submit 42 | ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/markRaw.md: -------------------------------------------------------------------------------- ```markdown 1 | # markRaw 2 | 3 | ## Description 4 | 5 | Mark any object or class prototype as never being hijacked by observable, priority is higher than markObservable 6 | 7 | Note: If you mark an object that is already observable with markRaw, then toJS will not convert it into a normal object 8 | 9 | ## Signature 10 | 11 | ```ts 12 | interface markRaw<T> { 13 | (target: T): T 14 | } 15 | ``` 16 | 17 | ## Example 18 | 19 | ```ts 20 | import { observable, autorun, markRaw } from '@formily/reactive' 21 | 22 | class A { 23 | property = '' 24 | } 25 | 26 | const a = observable(new A()) 27 | 28 | autorun(() => { 29 | console.log(a.property) //It will be triggered when the property changes, because the A instance is a normal object 30 | }) 31 | 32 | a.property = 123 33 | 34 | //-------------------------------------------- 35 | 36 | const b = observable(markRaw(new A())) //instance-level mark, only valid for the current instance 37 | 38 | autorun(() => { 39 | console.log(b.property) //will not be triggered when the property changes, because it has been marked raw 40 | }) 41 | 42 | b.property = 123 43 | 44 | //-------------------------------------------- 45 | 46 | markRaw(A) //Class-level mark, then all instances will take effect 47 | 48 | const c = observable(new A()) 49 | 50 | autorun(() => { 51 | console.log(c.property) //will not be triggered when the property changes, because it has been marked raw 52 | }) 53 | 54 | c.property = 123 55 | ``` 56 | ``` -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@formily/core", 3 | "version": "2.3.7", 4 | "license": "MIT", 5 | "main": "lib", 6 | "module": "esm", 7 | "umd:main": "dist/formily.core.umd.production.js", 8 | "unpkg": "dist/formily.core.umd.production.js", 9 | "jsdelivr": "dist/formily.core.umd.production.js", 10 | "jsnext:main": "esm", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/alibaba/formily.git" 14 | }, 15 | "types": "esm/index.d.ts", 16 | "bugs": { 17 | "url": "https://github.com/alibaba/formily/issues" 18 | }, 19 | "homepage": "https://github.com/alibaba/formily#readme", 20 | "engines": { 21 | "npm": ">=3.0.0" 22 | }, 23 | "scripts": { 24 | "start": "dumi dev", 25 | "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", 26 | "build:cjs": "tsc --project tsconfig.build.json", 27 | "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", 28 | "build:umd": "rollup --config", 29 | "build:docs": "dumi build" 30 | }, 31 | "devDependencies": { 32 | "dumi": "^1.1.0-rc.8" 33 | }, 34 | "dependencies": { 35 | "@formily/reactive": "2.3.7", 36 | "@formily/shared": "2.3.7", 37 | "@formily/validator": "2.3.7" 38 | }, 39 | "publishConfig": { 40 | "access": "public" 41 | }, 42 | "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" 43 | } 44 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/void-field.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <Space> 4 | <VoidField name="layout"> 5 | <Field name="input" :component="[Input]" /> 6 | </VoidField> 7 | <FormConsumer> 8 | <template #default="{ form }"> 9 | <Space> 10 | <Button 11 | @click=" 12 | () => { 13 | form 14 | .query('layout') 15 | .take() 16 | .setState((state) => { 17 | state.visible = !state.visible 18 | }) 19 | } 20 | " 21 | > 22 | {{ form.query('layout').get('visible') ? 'Hide' : 'Show' }} 23 | </Button> 24 | <div>{{ JSON.stringify(form.values, null, 2) }}</div> 25 | </Space> 26 | </template> 27 | </FormConsumer> 28 | </Space> 29 | </FormProvider> 30 | </template> 31 | 32 | <script> 33 | import { Input, Space, Button } from 'ant-design-vue' 34 | import { createForm } from '@formily/core' 35 | import { FormProvider, Field, FormConsumer, VoidField } from '@formily/vue' 36 | import 'ant-design-vue/dist/antd.css' 37 | 38 | export default { 39 | components: { FormProvider, Field, FormConsumer, VoidField, Space, Button }, 40 | data() { 41 | return { 42 | Input, 43 | form: createForm(), 44 | } 45 | }, 46 | } 47 | </script> 48 | ``` -------------------------------------------------------------------------------- /packages/react/docs/api/hooks/useExpressionScope.md: -------------------------------------------------------------------------------- ```markdown 1 | # useExpressionScope 2 | 3 | ## Description 4 | 5 | The expression scope is mainly read in the custom component. The sources of the expression scope are: 6 | 7 | - createSchemaField top-level delivery 8 | - SchemaField component attribute delivery 9 | - ExpressionScope/RecordScope/RecordsScope are issued inside custom components 10 | 11 | ## Signature 12 | 13 | ```ts 14 | interface useExpressionScope { 15 | (): any 16 | } 17 | ``` 18 | 19 | ## Example 20 | 21 | ```tsx 22 | import React from 'react' 23 | import { createForm } from '@formily/core' 24 | import { 25 | FormProvider, 26 | createSchemaField, 27 | useExpressionScope, 28 | RecordScope, 29 | } from '@formily/react' 30 | 31 | const form = createForm() 32 | 33 | const Custom = () => { 34 | const scope = useExpressionScope() 35 | return ( 36 | <code> 37 | <pre>{JSON.stringify(scope, null, 2)}</pre> 38 | </code> 39 | ) 40 | } 41 | 42 | const SchemaField = createSchemaField({ 43 | components: { 44 | Custom, 45 | }, 46 | scope: { 47 | topScope: { 48 | aa: 123, 49 | }, 50 | }, 51 | }) 52 | 53 | export default () => ( 54 | <FormProvider form={form}> 55 | <RecordScope 56 | getRecord={() => ({ name: 'Record Name', code: 'Record Code' })} 57 | getIndex={() => 2} 58 | > 59 | <SchemaField scope={{ propsScope: { bb: 321 } }}> 60 | <SchemaField.String name="custom" x-component="Custom" /> 61 | </SchemaField> 62 | </RecordScope> 63 | </FormProvider> 64 | ) 65 | ``` 66 | ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/vue/observer.md: -------------------------------------------------------------------------------- ```markdown 1 | # observer 2 | 3 | ## describe 4 | 5 | In Vue, the component rendering method is changed to Reaction, and dependencies are collected every time the view is re-rendered, and dependencies are updated automatically to re-render. 6 | 7 | ### Signature 8 | 9 | ```ts 10 | interface IObserverOptions { 11 | scheduler?: (updater: () => void) => void //The scheduler, you can manually control the timing of the update 12 | name?: string //name of the packaged component 13 | } 14 | 15 | interface observer<T extends VueComponent> { 16 | (component: T, options?: IObserverOptions): T 17 | } 18 | ``` 19 | 20 | ## Example 21 | 22 | ```html 23 | <template> 24 | <div> 25 | <div> 26 | <input 27 | :style="{ 28 | height: 28, 29 | padding: '0 8px', 30 | border: '2px solid #888', 31 | borderRadius: 3, 32 | }" 33 | :value="obs.value" 34 | @input="(e) => { 35 | obs.value = e.target.value 36 | }" 37 | /> 38 | </div> 39 | <div>{{obs.value}}</div> 40 | </div> 41 | </template> 42 | 43 | <script> 44 | import { observable } from '@formily/reactive' 45 | import { observer } from '@formily/reactive-vue' 46 | 47 | export default observer({ 48 | data() { 49 | // can coexist with vue's response system 50 | const obs = observable({ 51 | value: 'Hello world', 52 | }) 53 | return { 54 | obs, 55 | } 56 | }, 57 | }) 58 | </script> 59 | ``` 60 | ``` -------------------------------------------------------------------------------- /packages/antd/src/time-picker/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import moment from 'moment' 2 | import { connect, mapProps, mapReadPretty } from '@formily/react' 3 | import { TimePicker as AntdTimePicker } from 'antd' 4 | import { 5 | TimePickerProps as AntdTimePickerProps, 6 | TimeRangePickerProps, 7 | } from 'antd/lib/time-picker' 8 | import { PreviewText } from '../preview-text' 9 | import { formatMomentValue, momentable } from '../__builtins__' 10 | 11 | type ComposedTimePicker = React.FC< 12 | React.PropsWithChildren<AntdTimePickerProps> 13 | > & { 14 | RangePicker?: React.FC<React.PropsWithChildren<TimeRangePickerProps>> 15 | } 16 | 17 | const mapTimeFormat = function () { 18 | return (props: any) => { 19 | const format = props['format'] || 'HH:mm:ss' 20 | const onChange = props.onChange 21 | return { 22 | ...props, 23 | format, 24 | value: momentable(props.value, format), 25 | onChange: (value: moment.Moment | moment.Moment[]) => { 26 | if (onChange) { 27 | onChange(formatMomentValue(value, format)) 28 | } 29 | }, 30 | } 31 | } 32 | } 33 | 34 | export const TimePicker: ComposedTimePicker = connect( 35 | AntdTimePicker, 36 | mapProps(mapTimeFormat()), 37 | mapReadPretty(PreviewText.TimePicker) 38 | ) 39 | 40 | TimePicker.RangePicker = connect( 41 | AntdTimePicker.RangePicker, 42 | mapProps(mapTimeFormat()), 43 | mapReadPretty(PreviewText.TimeRangePicker) 44 | ) 45 | 46 | export default TimePicker 47 | ``` -------------------------------------------------------------------------------- /packages/element/src/__builtins__/shared/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { onMounted, ref } from 'vue-demi' 2 | 3 | export function isValidElement(element) { 4 | return ( 5 | isVueOptions(element) || 6 | (element && 7 | typeof element === 'object' && 8 | 'componentOptions' in element && 9 | 'context' in element && 10 | element.tag !== undefined) 11 | ) // remove text node 12 | } 13 | 14 | export function isVnode(element: any): boolean { 15 | return ( 16 | element && 17 | typeof element === 'object' && 18 | 'componentOptions' in element && 19 | 'context' in element && 20 | element.tag !== undefined 21 | ) 22 | } 23 | 24 | export function isVueOptions(options) { 25 | return ( 26 | options && 27 | (typeof options.template === 'string' || 28 | typeof options.render === 'function') 29 | ) 30 | } 31 | 32 | export function composeExport<T0 extends {}, T1 extends {}>( 33 | s0: T0, 34 | s1: T1 35 | ): T0 & T1 { 36 | return Object.assign(s0, s1) 37 | } 38 | 39 | /** 40 | * 处理 vue 2.6 和 2.7 的 ref 兼容问题 41 | * composition-api 不支持 setup ref 42 | * @param refs 43 | * @returns 44 | */ 45 | export function useCompatRef(refs?: { 46 | [key: string]: Vue | Element | Vue[] | Element[] 47 | }) { 48 | const elRef = ref(null) 49 | const elRefBinder = Math.random().toString(36).slice(-8) 50 | 51 | onMounted(() => { 52 | if (refs) { 53 | elRef.value = refs[elRefBinder] 54 | } 55 | }) 56 | 57 | return { 58 | elRef, 59 | elRefBinder: refs ? elRefBinder : elRef, 60 | } 61 | } 62 | ``` -------------------------------------------------------------------------------- /packages/next/src/editable/main.scss: -------------------------------------------------------------------------------- ```scss 1 | @import '~@alifd/next/lib/core/index-noreset.scss'; 2 | 3 | $editable-prefix-cls: '#{$css-prefix}formily-editable'; 4 | $editable-popover-prefix-cls: '#{$css-prefix}formily-editable-popover'; 5 | 6 | .#{$editable-prefix-cls} { 7 | cursor: pointer; 8 | display: inline-flex; 9 | align-items: center; 10 | 11 | .#{$css-prefix}form-text { 12 | .#{$css-prefix}tag { 13 | transition: none !important; 14 | } 15 | 16 | .#{$css-prefix}tag:last-child { 17 | margin-right: 0 !important; 18 | } 19 | } 20 | 21 | &-content { 22 | display: flex; 23 | align-items: center; 24 | 25 | > * { 26 | margin-right: 3px; 27 | 28 | &:last-child { 29 | margin-right: 0; 30 | } 31 | } 32 | } 33 | 34 | .#{$editable-prefix-cls}-edit-btn, 35 | .#{$editable-prefix-cls}-close-btn { 36 | transition: all 0.25s ease-in-out; 37 | color: #aaa; 38 | font-size: 12px; 39 | 40 | &:hover { 41 | color: $color-text1-1; 42 | } 43 | } 44 | 45 | .#{$css-prefix}form-text { 46 | display: flex; 47 | align-items: center; 48 | } 49 | 50 | .#{$editable-prefix-cls}-preview { 51 | white-space: nowrap; 52 | text-overflow: ellipsis; 53 | overflow: hidden; 54 | word-break: break-all; 55 | max-width: 100px; 56 | display: block; 57 | } 58 | } 59 | 60 | .#{$editable-prefix-cls} { 61 | &-trigger { 62 | cursor: pointer; 63 | display: inline-flex !important; 64 | outline: none; 65 | } 66 | 67 | &-edit-btn { 68 | color: #aaa; 69 | } 70 | } 71 | ``` -------------------------------------------------------------------------------- /packages/benchmark/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@formily/benchmark", 3 | "version": "2.3.7", 4 | "license": "MIT", 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/alibaba/formily.git" 9 | }, 10 | "types": "esm/index.d.ts", 11 | "bugs": { 12 | "url": "https://github.com/alibaba/formily/issues" 13 | }, 14 | "homepage": "https://github.com/alibaba/formily#readme", 15 | "engines": { 16 | "npm": ">=3.0.0" 17 | }, 18 | "scripts": { 19 | "start": "webpack-dev-server --config webpack.dev.ts" 20 | }, 21 | "devDependencies": { 22 | "css-loader": "^5.0.0", 23 | "file-loader": "^5.0.2", 24 | "html-webpack-plugin": "^3.2.0", 25 | "mini-css-extract-plugin": "^1.6.0", 26 | "postcss": "^8.4.31", 27 | "postcss-less": "^4.0.0", 28 | "postcss-loader": "^3.x", 29 | "raw-loader": "^4.0.0", 30 | "style-loader": "^1.1.3", 31 | "ts-loader": "^7.0.4", 32 | "webpack": "^4.41.5", 33 | "webpack-bundle-analyzer": "^3.9.0", 34 | "webpack-cli": "^3.3.10", 35 | "webpack-dev-server": "^3.10.1" 36 | }, 37 | "resolutions": { 38 | "react": "next", 39 | "react-dom": "next", 40 | "react-is": "next" 41 | }, 42 | "dependencies": { 43 | "@formily/reactive": "2.3.7", 44 | "@formily/reactive-react": "2.3.7", 45 | "react": "next", 46 | "react-dom": "next", 47 | "react-is": "next" 48 | }, 49 | "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" 50 | } 51 | ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/extension/devpanel.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useEffect, useState } from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from '../app' 4 | 5 | const backgroundPageConnection = chrome.runtime.connect({ 6 | name: '@formily-devtools-panel-script', 7 | }) 8 | 9 | backgroundPageConnection.postMessage({ 10 | name: 'init', 11 | tabId: chrome.devtools.inspectedWindow.tabId, 12 | }) 13 | 14 | chrome.devtools.inspectedWindow.eval( 15 | 'window.__FORMILY_DEV_TOOLS_HOOK__.openDevtools()' 16 | ) 17 | 18 | const Devtools = () => { 19 | const [state, setState] = useState([]) 20 | useEffect(() => { 21 | let store = {} 22 | const update = () => { 23 | setState( 24 | Object.keys(store).map((key) => { 25 | return store[key] 26 | }) 27 | ) 28 | } 29 | chrome.devtools.inspectedWindow.eval( 30 | 'window.__FORMILY_DEV_TOOLS_HOOK__.update()' 31 | ) 32 | backgroundPageConnection.onMessage.addListener(({ type, id, graph }) => { 33 | if (type === 'init') { 34 | store = {} 35 | chrome.devtools.inspectedWindow.eval( 36 | 'window.__FORMILY_DEV_TOOLS_HOOK__.openDevtools()' 37 | ) 38 | } else if (type !== 'uninstall') { 39 | store[id] = JSON.parse(graph) 40 | } else { 41 | delete store[id] 42 | } 43 | update() 44 | }) 45 | }, []) 46 | return <App dataSource={state} /> 47 | } 48 | 49 | ReactDOM.render(<Devtools />, document.getElementById('root')) 50 | ``` -------------------------------------------------------------------------------- /packages/reactive/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@formily/reactive", 3 | "version": "2.3.7", 4 | "license": "MIT", 5 | "main": "lib", 6 | "module": "esm", 7 | "umd:main": "dist/formily.reactive.umd.production.js", 8 | "unpkg": "dist/formily.reactive.umd.production.js", 9 | "jsdelivr": "dist/formily.reactive.umd.production.js", 10 | "jsnext:main": "esm", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/alibaba/formily.git" 14 | }, 15 | "types": "esm/index.d.ts", 16 | "bugs": { 17 | "url": "https://github.com/alibaba/formily/issues" 18 | }, 19 | "homepage": "https://github.com/alibaba/formily#readme", 20 | "engines": { 21 | "npm": ">=3.0.0" 22 | }, 23 | "scripts": { 24 | "start": "dumi dev", 25 | "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", 26 | "build:cjs": "tsc --project tsconfig.build.json", 27 | "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", 28 | "build:umd": "rollup --config", 29 | "build:docs": "dumi build", 30 | "benchmark": "ts-node ./benchmark" 31 | }, 32 | "devDependencies": { 33 | "@vue/reactivity": "^3.0.11", 34 | "benny": "^3.6.15", 35 | "dumi": "^1.1.0-rc.8", 36 | "lodash": "^4.17.21", 37 | "mobx": "^6.3.0" 38 | }, 39 | "publishConfig": { 40 | "access": "public" 41 | }, 42 | "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" 43 | } 44 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/annotations/ref.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ObModelSymbol } from '../environment' 2 | import { createAnnotation } from '../internals' 3 | import { buildDataTree } from '../tree' 4 | import { 5 | bindTargetKeyWithCurrentReaction, 6 | runReactionsFromTargetKey, 7 | } from '../reaction' 8 | 9 | export interface IRef { 10 | <T>(target: T): { value: T } 11 | } 12 | 13 | export const ref: IRef = createAnnotation(({ target, key, value }) => { 14 | const store = { 15 | value: target ? target[key] : value, 16 | } 17 | 18 | const proxy = {} 19 | 20 | const context = target ? target : store 21 | const property = target ? key : 'value' 22 | 23 | function get() { 24 | bindTargetKeyWithCurrentReaction({ 25 | target: context, 26 | key: property, 27 | type: 'get', 28 | }) 29 | return store.value 30 | } 31 | 32 | function set(value: any) { 33 | const oldValue = store.value 34 | store.value = value 35 | if (oldValue !== value) { 36 | runReactionsFromTargetKey({ 37 | target: context, 38 | key: property, 39 | type: 'set', 40 | oldValue, 41 | value, 42 | }) 43 | } 44 | } 45 | if (target) { 46 | Object.defineProperty(target, key, { 47 | get, 48 | set, 49 | enumerable: true, 50 | }) 51 | return target 52 | } else { 53 | Object.defineProperty(proxy, 'value', { 54 | set, 55 | get, 56 | }) 57 | buildDataTree(target, key, store) 58 | proxy[ObModelSymbol] = store 59 | } 60 | return proxy 61 | }) 62 | ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/graph.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { createForm } from '../' 2 | import { isVoidField } from '../shared/checkers' 3 | import { attach } from './shared' 4 | 5 | test('getGraph/setGraph', () => { 6 | const form = attach(createForm()) 7 | attach( 8 | form.createField({ 9 | name: 'normal', 10 | }) 11 | ) 12 | attach( 13 | form.createArrayField({ 14 | name: 'array', 15 | }) 16 | ) 17 | attach( 18 | form.createObjectField({ 19 | name: 'object', 20 | }) 21 | ) 22 | attach( 23 | form.createVoidField({ 24 | name: 'void', 25 | }) 26 | ) 27 | form.query('normal').take((field) => { 28 | if (isVoidField(field)) return 29 | field.selfErrors = ['error'] 30 | }) 31 | const graph = form.getFormGraph() 32 | form.clearFormGraph() 33 | form.setFormGraph(graph) 34 | const graph2 = form.getFormGraph() 35 | expect(graph).toEqual(graph2) 36 | form.setFormGraph({ 37 | object: { 38 | value: 123, 39 | }, 40 | }) 41 | expect(form.query('object').get('value')).toEqual(123) 42 | }) 43 | 44 | test('clearFormGraph', () => { 45 | const form = attach(createForm()) 46 | attach( 47 | form.createField({ 48 | name: 'normal', 49 | }) 50 | ) 51 | attach( 52 | form.createArrayField({ 53 | name: 'array', 54 | }) 55 | ) 56 | attach( 57 | form.createObjectField({ 58 | name: 'object', 59 | }) 60 | ) 61 | form.clearFormGraph('normal') 62 | expect(form.fields['normal']).toBeUndefined() 63 | expect(form.fields['array']).not.toBeUndefined() 64 | }) 65 | ``` -------------------------------------------------------------------------------- /packages/element/src/el-form/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Form } from '@formily/core' 2 | import { FormProvider as _FormProvider, createForm } from '@formily/vue' 3 | import type { Form as _ElFormProps } from 'element-ui' 4 | import type { FunctionalComponentOptions, Component } from 'vue' 5 | import { Form as ElFormComponent } from 'element-ui' 6 | 7 | const FormProvider = _FormProvider as unknown as Component 8 | 9 | export type ElFormProps = _ElFormProps & { 10 | form?: Form 11 | component: Component 12 | onAutoSubmit?: (values: any) => any 13 | } 14 | 15 | export const ElForm: FunctionalComponentOptions<ElFormProps> = { 16 | functional: true, 17 | render(h, context) { 18 | const { 19 | form = createForm({}), 20 | component = ElFormComponent, 21 | onAutoSubmit = context.listeners?.autoSubmit, 22 | ...props 23 | } = context.props 24 | const submitHandler = ( 25 | Array.isArray(onAutoSubmit) ? onAutoSubmit[0] : onAutoSubmit 26 | ) as (values: any) => any 27 | return h(FormProvider, { props: { form } }, [ 28 | h( 29 | component, 30 | { 31 | ...context.data, 32 | props, 33 | nativeOn: { 34 | submit: (e: Event) => { 35 | e?.stopPropagation?.() 36 | e?.preventDefault?.() 37 | form.submit(submitHandler) 38 | }, 39 | }, 40 | }, 41 | context.children 42 | ), 43 | ]) 44 | }, 45 | } 46 | 47 | export default ElForm 48 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/hooks/use-field-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaObjectField 5 | name="custom" 6 | x-component="Custom" 7 | :x-component-props="{ 8 | schema: { 9 | type: 'object', 10 | properties: { 11 | input: { 12 | type: 'string', 13 | 'x-component': 'Custom', 14 | }, 15 | }, 16 | }, 17 | }" 18 | /> 19 | </SchemaField> 20 | </FormProvider> 21 | </template> 22 | 23 | <script> 24 | import { defineComponent, h } from '@vue/composition-api' 25 | import { createForm } from '@formily/core' 26 | import { FormProvider, createSchemaField, useFieldSchema } from '@formily/vue' 27 | import 'ant-design-vue/dist/antd.css' 28 | 29 | const Custom = defineComponent({ 30 | setup() { 31 | const schemaRef = useFieldSchema() 32 | return () => { 33 | const schema = schemaRef.value 34 | return h( 35 | 'div', 36 | { 37 | style: { whiteSpace: 'pre' }, 38 | }, 39 | [JSON.stringify(schema.toJSON(), null, 4)] 40 | ) 41 | } 42 | }, 43 | }) 44 | 45 | const { SchemaField, SchemaObjectField } = createSchemaField({ 46 | components: { 47 | Custom, 48 | }, 49 | }) 50 | 51 | export default { 52 | components: { FormProvider, SchemaField, SchemaObjectField }, 53 | data() { 54 | const form = createForm({ validateFirst: true }) 55 | return { 56 | form, 57 | } 58 | }, 59 | } 60 | </script> 61 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <Form 3 | :form="form" 4 | :label-col="6" 5 | :wrapper-col="10" 6 | @autoSubmit="log" 7 | @autoSubmitFailed="log" 8 | > 9 | <SchemaField> 10 | <SchemaStringField 11 | name="input" 12 | title="输入框" 13 | x-decorator="FormItem" 14 | x-component="Input" 15 | :required="true" 16 | /> 17 | <SchemaStringField 18 | name="select" 19 | title="选择框" 20 | x-decorator="FormItem" 21 | x-component="Select" 22 | :enum="[ 23 | { 24 | label: '选项1', 25 | value: 1, 26 | }, 27 | { 28 | label: '选项2', 29 | value: 2, 30 | }, 31 | ]" 32 | :required="true" 33 | /> 34 | </SchemaField> 35 | <FormButtonGroup alignFormItem> 36 | <Submit>提交</Submit> 37 | </FormButtonGroup> 38 | </Form> 39 | </template> 40 | 41 | <script> 42 | import { createForm } from '@formily/core' 43 | import { createSchemaField } from '@formily/vue' 44 | import { 45 | Form, 46 | Input, 47 | Select, 48 | FormItem, 49 | FormButtonGroup, 50 | Submit, 51 | } from '@formily/element' 52 | 53 | const form = createForm() 54 | const fields = createSchemaField({ components: { Input, Select, FormItem } }) 55 | 56 | export default { 57 | components: { FormButtonGroup, Submit, Form, ...fields }, 58 | data() { 59 | return { 60 | form, 61 | } 62 | }, 63 | 64 | methods: { 65 | log(value) { 66 | console.log(value) 67 | }, 68 | }, 69 | } 70 | </script> 71 | ``` -------------------------------------------------------------------------------- /packages/next/src/time-picker2/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import moment from 'moment' 2 | import { connect, mapProps, mapReadPretty } from '@formily/react' 3 | import { TimePicker2 as NextTimePicker2 } from '@alifd/next' 4 | import { 5 | TimePickerProps, 6 | RangePickerProps, 7 | } from '@alifd/next/types/time-picker2' 8 | import { PreviewText } from '../preview-text' 9 | import { 10 | formatMomentValue, 11 | momentable, 12 | mapSize, 13 | mapStatus, 14 | } from '../__builtins__' 15 | 16 | type ComposedTimePicker = React.FC<React.PropsWithChildren<TimePickerProps>> & { 17 | RangePicker?: React.FC<React.PropsWithChildren<RangePickerProps>> 18 | } 19 | 20 | const mapTimeFormat = function () { 21 | return (props: any) => { 22 | const format = props['format'] || 'HH:mm:ss' 23 | const onChange = props.onChange 24 | return { 25 | ...props, 26 | format, 27 | value: momentable(props.value, format), 28 | onChange: (value: moment.Moment | moment.Moment[]) => { 29 | if (onChange) { 30 | onChange(formatMomentValue(value, format)) 31 | } 32 | }, 33 | } 34 | } 35 | } 36 | 37 | export const TimePicker2: ComposedTimePicker = connect( 38 | NextTimePicker2, 39 | mapProps(mapTimeFormat(), mapSize, mapStatus), 40 | mapReadPretty(PreviewText.TimePicker2) 41 | ) 42 | 43 | TimePicker2.RangePicker = connect( 44 | NextTimePicker2.RangePicker, 45 | mapProps(mapTimeFormat(), mapSize, mapStatus), 46 | mapReadPretty(PreviewText.TimeRangePicker2) 47 | ) 48 | 49 | export default TimePicker2 50 | ``` -------------------------------------------------------------------------------- /packages/shared/src/subscribable.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isFn, Subscriber, Subscription } from './checkers' 2 | import { each } from './array' 3 | 4 | export class Subscribable<Payload = any> { 5 | subscribers: { 6 | index?: number 7 | [key: number]: Subscriber<Payload> 8 | } = { 9 | index: 0, 10 | } 11 | 12 | subscription: Subscription<Payload> 13 | 14 | subscribe = (callback?: Subscriber<Payload>): number => { 15 | if (isFn(callback)) { 16 | const index: number = this.subscribers.index + 1 17 | this.subscribers[index] = callback 18 | this.subscribers.index++ 19 | return index 20 | } 21 | } 22 | 23 | unsubscribe = (index?: number) => { 24 | if (this.subscribers[index]) { 25 | delete this.subscribers[index] 26 | } else if (!index) { 27 | this.subscribers = { 28 | index: 0, 29 | } 30 | } 31 | } 32 | 33 | notify = (payload?: Payload, silent?: boolean) => { 34 | if (this.subscription) { 35 | if (this.subscription && isFn(this.subscription.notify)) { 36 | if (this.subscription.notify.call(this, payload) === false) { 37 | return 38 | } 39 | } 40 | } 41 | if (silent) return 42 | const filter = (payload: Payload) => { 43 | if (this.subscription && isFn(this.subscription.filter)) { 44 | return this.subscription.filter.call(this, payload) 45 | } 46 | return payload 47 | } 48 | each(this.subscribers, (callback: any) => { 49 | if (isFn(callback)) callback(filter(payload)) 50 | }) 51 | } 52 | } 53 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/index.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <Space> 4 | <Field 5 | name="price" 6 | title="价格" 7 | :initialValue="5.2" 8 | :decorator="[FormItem]" 9 | :component="[ 10 | InputNumber, 11 | { 12 | placeholder: '请输入', 13 | style: { 14 | width: 100, 15 | }, 16 | }, 17 | ]" 18 | /> 19 | <FormItem>×</FormItem> 20 | <Field 21 | name="count" 22 | title="数量" 23 | :initialValue="100" 24 | :decorator="[FormItem]" 25 | :component="[ 26 | InputNumber, 27 | { 28 | placeholder: '请输入', 29 | style: { 30 | width: 100, 31 | }, 32 | }, 33 | ]" 34 | /> 35 | <FormConsumer> 36 | <template #default="{ form }"> 37 | <FormItem> 38 | = {{ `${form.values.price * form.values.count} 元` }}</FormItem 39 | > 40 | </template> 41 | </FormConsumer> 42 | </Space> 43 | </FormProvider> 44 | </template> 45 | 46 | <script> 47 | import { createForm } from '@formily/core' 48 | import { InputNumber, FormItem, Space } from '@formily/element' 49 | import { FormProvider, FormConsumer, Field } from '@formily/vue' 50 | 51 | const form = createForm() 52 | 53 | export default { 54 | components: { FormProvider, FormConsumer, Field, FormItem, Space }, 55 | data() { 56 | return { 57 | form, 58 | InputNumber, 59 | FormItem, 60 | } 61 | }, 62 | } 63 | </script> 64 | ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/markObservable.md: -------------------------------------------------------------------------------- ```markdown 1 | # markObservable 2 | 3 | ## Description 4 | 5 | Mark any object or class prototype as being hijacked by observable. React Node and objects with toJSON/toJS methods will be automatically bypassed in @formily/reactive. In special scenarios, we may hope that the object should be hijacked, so you can use it markObservable mark 6 | 7 | ## Signature 8 | 9 | ```ts 10 | interface markObservable<T> { 11 | (target: T): T 12 | } 13 | ``` 14 | 15 | ## Example 16 | 17 | ```ts 18 | import { observable, autorun, markObservable } from '@formily/reactive' 19 | 20 | class A { 21 | property = '' 22 | 23 | toJSON() {} 24 | } 25 | 26 | const a = observable(new A()) 27 | 28 | autorun(() => { 29 | console.log(a.property) //will not be triggered when the property changes, because there is a toJSON method in the A instance 30 | }) 31 | 32 | a.property = 123 33 | 34 | //-------------------------------------------- 35 | 36 | const b = observable(markObservable(new A())) //instance-level mark, only valid for the current instance 37 | 38 | autorun(() => { 39 | console.log(b.property) //Can be triggered when the property changes, because it has been marked as observable 40 | }) 41 | 42 | b.property = 123 43 | 44 | //-------------------------------------------- 45 | 46 | markObservable(A) //Class-level mark, then all instances will take effect 47 | 48 | const c = observable(new A()) 49 | 50 | autorun(() => { 51 | console.log(c.property) //Can be triggered when the property changes, because it has been marked as observable 52 | }) 53 | 54 | c.property = 123 55 | ``` 56 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/model.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isFn } from './checkers' 2 | import { buildDataTree } from './tree' 3 | import { observable } from './observable' 4 | import { getObservableMaker } from './internals' 5 | import { isObservable, isAnnotation, isSupportObservable } from './externals' 6 | import { Annotations } from './types' 7 | import { action } from './action' 8 | import { ObModelSymbol } from './environment' 9 | 10 | export function define<Target extends object = any>( 11 | target: Target, 12 | annotations?: Annotations<Target> 13 | ): Target { 14 | if (isObservable(target)) return target 15 | if (!isSupportObservable(target)) return target 16 | target[ObModelSymbol] = target 17 | buildDataTree(undefined, undefined, target) 18 | for (const key in annotations) { 19 | const annotation = annotations[key] 20 | if (isAnnotation(annotation)) { 21 | getObservableMaker(annotation)({ 22 | target, 23 | key, 24 | }) 25 | } 26 | } 27 | return target 28 | } 29 | 30 | export function model<Target extends object = any>(target: Target): Target { 31 | const annotations = Object.keys(target || {}).reduce((buf, key) => { 32 | const descriptor = Object.getOwnPropertyDescriptor(target, key) 33 | if (descriptor && descriptor.get) { 34 | buf[key] = observable.computed 35 | } else if (isFn(target[key])) { 36 | buf[key] = action 37 | } else { 38 | buf[key] = observable 39 | } 40 | return buf 41 | }, {}) 42 | return define(target, annotations) 43 | } 44 | ``` -------------------------------------------------------------------------------- /packages/core/src/shared/externals.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { FormPath } from '@formily/shared' 2 | import { Form } from '../models' 3 | import { IFormProps } from '../types' 4 | import { 5 | getValidateLocaleIOSCode, 6 | getLocaleByPath, 7 | setValidateLanguage, 8 | registerValidateFormats, 9 | registerValidateLocale, 10 | registerValidateMessageTemplateEngine, 11 | registerValidateRules, 12 | } from '@formily/validator' 13 | import { 14 | createEffectHook, 15 | createEffectContext, 16 | useEffectForm, 17 | } from './effective' 18 | import { 19 | isArrayField, 20 | isArrayFieldState, 21 | isDataField, 22 | isDataFieldState, 23 | isField, 24 | isFieldState, 25 | isForm, 26 | isFormState, 27 | isGeneralField, 28 | isGeneralFieldState, 29 | isObjectField, 30 | isObjectFieldState, 31 | isQuery, 32 | isVoidField, 33 | isVoidFieldState, 34 | } from './checkers' 35 | 36 | const createForm = <T extends object = any>(options?: IFormProps<T>) => { 37 | return new Form(options) 38 | } 39 | 40 | export { 41 | FormPath, 42 | createForm, 43 | isArrayField, 44 | isArrayFieldState, 45 | isDataField, 46 | isDataFieldState, 47 | isField, 48 | isFieldState, 49 | isForm, 50 | isFormState, 51 | isGeneralField, 52 | isGeneralFieldState, 53 | isObjectField, 54 | isObjectFieldState, 55 | isQuery, 56 | isVoidField, 57 | isVoidFieldState, 58 | getValidateLocaleIOSCode, 59 | getLocaleByPath, 60 | setValidateLanguage, 61 | registerValidateFormats, 62 | registerValidateLocale, 63 | registerValidateMessageTemplateEngine, 64 | registerValidateRules, 65 | createEffectHook, 66 | createEffectContext, 67 | useEffectForm, 68 | } 69 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/shared/map-read-pretty.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <Form layout="vertical"> 4 | <Field 5 | name="name" 6 | title="Name" 7 | required 8 | initialValue="Hello world" 9 | :decorator="[FormItem]" 10 | :component="[Input, { placeholder: 'Please Input' }]" 11 | /> 12 | </Form> 13 | </FormProvider> 14 | </template> 15 | 16 | <script> 17 | import { Form, Input as AntdInput } from 'ant-design-vue' 18 | import { createForm, setValidateLanguage } from '@formily/core' 19 | import { 20 | FormProvider, 21 | Field, 22 | connect, 23 | mapProps, 24 | mapReadPretty, 25 | } from '@formily/vue' 26 | import 'ant-design-vue/dist/antd.css' 27 | 28 | setValidateLanguage('en') 29 | 30 | const FormItem = connect( 31 | Form.Item, 32 | mapProps( 33 | { 34 | title: 'label', 35 | description: 'extra', 36 | required: true, 37 | validateStatus: true, 38 | }, 39 | (props, field) => { 40 | return { 41 | ...props, 42 | help: field.selfErrors?.length ? field.selfErrors : undefined, 43 | } 44 | } 45 | ) 46 | ) 47 | 48 | const Input = connect( 49 | AntdInput, 50 | mapReadPretty({ 51 | props: ['value'], 52 | // you need import "h" from "vue" in vue3 53 | render(h) { 54 | return h('div', [this.value]) 55 | }, 56 | }) 57 | ) 58 | 59 | export default { 60 | components: { 61 | FormProvider, 62 | Field, 63 | Form, 64 | }, 65 | data() { 66 | const form = createForm({ validateFirst: true, readPretty: true }) 67 | return { 68 | FormItem, 69 | Input, 70 | form, 71 | } 72 | }, 73 | } 74 | </script> 75 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-layout/json-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField :schema="schema" /> 4 | </FormProvider> 5 | </template> 6 | 7 | <script> 8 | import { createForm } from '@formily/core' 9 | import { createSchemaField, FormProvider } from '@formily/vue' 10 | import { FormItem, FormLayout, Input, Select, Submit } from '@formily/element' 11 | 12 | const schema = { 13 | type: 'object', 14 | properties: { 15 | layout: { 16 | type: 'void', 17 | 'x-component': 'FormLayout', 18 | 'x-component-props': { 19 | labelCol: 6, 20 | wrapperCol: 10, 21 | layout: 'vertical', 22 | }, 23 | properties: { 24 | input: { 25 | type: 'string', 26 | title: '输入框', 27 | required: true, 28 | 'x-decorator': 'FormItem', 29 | 'x-decorator-props': { 30 | tooltip: '123', 31 | }, 32 | 'x-component': 'Input', 33 | }, 34 | select: { 35 | type: 'string', 36 | title: '选择框', 37 | required: true, 38 | 'x-decorator': 'FormItem', 39 | 'x-component': 'Select', 40 | }, 41 | }, 42 | }, 43 | }, 44 | } 45 | 46 | const form = createForm() 47 | const fields = createSchemaField({ 48 | components: { 49 | FormLayout, 50 | FormItem, 51 | Input, 52 | Select, 53 | }, 54 | }) 55 | 56 | export default { 57 | components: { FormProvider, ...fields, Submit }, 58 | data() { 59 | return { 60 | form, 61 | schema, 62 | } 63 | }, 64 | methods: { 65 | onSubmit(value) { 66 | console.log(value) 67 | }, 68 | }, 69 | } 70 | </script> 71 | ``` -------------------------------------------------------------------------------- /packages/react/src/__tests__/form.spec.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import { render } from '@testing-library/react' 3 | import { createForm } from '@formily/core' 4 | import { FormProvider, ObjectField, VoidField, Field } from '../' 5 | import { FormConsumer } from '../components' 6 | import { useParentForm } from '../hooks' 7 | 8 | test('render form', () => { 9 | const form = createForm() 10 | render( 11 | <FormProvider form={form}> 12 | <FormConsumer>{(form) => `${form.mounted}`}</FormConsumer> 13 | <FormConsumer /> 14 | </FormProvider> 15 | ) 16 | expect(form.mounted).toBeTruthy() 17 | }) 18 | 19 | const DisplayParentForm: React.FC< 20 | React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>> 21 | > = (props) => { 22 | return <div {...props}>{useParentForm()?.displayName}</div> 23 | } 24 | 25 | test('useParentForm', () => { 26 | const form = createForm() 27 | const { queryByTestId } = render( 28 | <FormProvider form={form}> 29 | <ObjectField name="aa"> 30 | <Field name="bb"> 31 | <DisplayParentForm data-testid="111" /> 32 | </Field> 33 | </ObjectField> 34 | <VoidField name="cc"> 35 | <Field name="dd"> 36 | <DisplayParentForm data-testid="222" /> 37 | </Field> 38 | </VoidField> 39 | <DisplayParentForm data-testid="333" /> 40 | </FormProvider> 41 | ) 42 | 43 | expect(queryByTestId('111').textContent).toBe('ObjectField') 44 | expect(queryByTestId('222').textContent).toBe('Form') 45 | expect(queryByTestId('333').textContent).toBe('Form') 46 | }) 47 | ``` -------------------------------------------------------------------------------- /packages/reactive-react/src/observer.ts: -------------------------------------------------------------------------------- ```typescript 1 | import React, { forwardRef, memo, Fragment } from 'react' 2 | import hoistNonReactStatics from 'hoist-non-react-statics' 3 | import { useObserver } from './hooks/useObserver' 4 | import { IObserverOptions, IObserverProps, ReactFC } from './types' 5 | 6 | export function observer< 7 | P, 8 | Options extends IObserverOptions = IObserverOptions 9 | >( 10 | component: ReactFC<P>, 11 | options?: Options 12 | ): React.MemoExoticComponent< 13 | ReactFC< 14 | Options extends { forwardRef: true } 15 | ? P & { 16 | ref?: 'ref' extends keyof P ? P['ref'] : React.RefAttributes<any> 17 | } 18 | : React.PropsWithoutRef<P> 19 | > 20 | > { 21 | const realOptions = { 22 | forwardRef: false, 23 | ...options, 24 | } 25 | 26 | const wrappedComponent = realOptions.forwardRef 27 | ? forwardRef((props: any, ref: any) => { 28 | return useObserver(() => component({ ...props, ref }), realOptions) 29 | }) 30 | : (props: any) => { 31 | return useObserver(() => component(props), realOptions) 32 | } 33 | 34 | const memoComponent = memo(wrappedComponent) 35 | 36 | hoistNonReactStatics(memoComponent, component) 37 | 38 | if (realOptions.displayName) { 39 | memoComponent.displayName = realOptions.displayName 40 | } 41 | 42 | return memoComponent 43 | } 44 | 45 | export const Observer = observer((props: IObserverProps) => { 46 | const children = 47 | typeof props.children === 'function' ? props.children() : props.children 48 | return React.createElement(Fragment, {}, children) 49 | }) 50 | ``` -------------------------------------------------------------------------------- /packages/vue/src/components/Field.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isVue2, h as _h } from 'vue-demi' 2 | import ReactiveField from './ReactiveField' 3 | import { getRawComponent } from '../utils/getRawComponent' 4 | 5 | import type { IFieldProps, DefineComponent } from '../types' 6 | import { getFieldProps } from '../utils/getFieldProps' 7 | 8 | let Field: DefineComponent<IFieldProps> 9 | 10 | /* istanbul ignore else */ 11 | if (isVue2) { 12 | Field = { 13 | functional: true, 14 | name: 'Field', 15 | props: getFieldProps(), 16 | render(h, context) { 17 | const props = context.props as IFieldProps 18 | const attrs = context.data.attrs 19 | const componentData = { 20 | ...context.data, 21 | props: { 22 | fieldType: 'Field', 23 | fieldProps: { 24 | ...attrs, 25 | ...props, 26 | ...getRawComponent(props), 27 | }, 28 | }, 29 | } 30 | return _h(ReactiveField, componentData, context.children) 31 | }, 32 | } as unknown as DefineComponent<IFieldProps> 33 | } else { 34 | Field = { 35 | name: 'Field', 36 | props: getFieldProps(), 37 | setup(props: IFieldProps, context) { 38 | return () => { 39 | const componentData = { 40 | fieldType: 'Field', 41 | fieldProps: { 42 | ...props, 43 | ...getRawComponent(props), 44 | }, 45 | } as Record<string, unknown> 46 | return _h(ReactiveField, componentData, context.slots) 47 | } 48 | }, 49 | } as unknown as DefineComponent<IFieldProps> 50 | } 51 | 52 | export default Field 53 | ``` -------------------------------------------------------------------------------- /packages/next/src/array-base/main.scss: -------------------------------------------------------------------------------- ```scss 1 | @import '~@alifd/next/lib/core/index-noreset.scss'; 2 | 3 | $array-base-prefix-cls: '#{$css-prefix}formily-array-base'; 4 | 5 | .#{$array-base-prefix-cls}-remove, 6 | .#{$array-base-prefix-cls}-copy { 7 | transition: all 0.25s ease-in-out; 8 | color: $color-text1-3; 9 | font-size: 16px; 10 | 11 | &:hover { 12 | color: $color-text1-1; 13 | } 14 | 15 | &-disabled { 16 | color: $color-text1-1; 17 | cursor: not-allowed !important; 18 | &:hover { 19 | color: $color-text1-1; 20 | } 21 | } 22 | 23 | .#{$css-prefix}formily-icon { 24 | font-size: 16px; 25 | } 26 | } 27 | 28 | .#{$array-base-prefix-cls}-addition { 29 | transition: all 0.25s ease-in-out; 30 | } 31 | 32 | .#{$array-base-prefix-cls}-move-down { 33 | transition: all 0.25s ease-in-out; 34 | color: $color-text1-3; 35 | font-size: 16px; 36 | 37 | &:hover { 38 | color: $color-text1-1; 39 | } 40 | 41 | &-disabled { 42 | color: $color-text1-1; 43 | cursor: not-allowed !important; 44 | &:hover { 45 | color: $color-text1-1; 46 | } 47 | } 48 | 49 | .#{$css-prefix}formily-icon { 50 | font-size: 16px; 51 | } 52 | } 53 | 54 | .#{$array-base-prefix-cls}-move-up { 55 | transition: all 0.25s ease-in-out; 56 | color: $color-text1-3; 57 | font-size: 16px; 58 | 59 | &:hover { 60 | color: $color-text1-1; 61 | } 62 | 63 | &-disabled { 64 | color: $color-text1-1; 65 | cursor: not-allowed !important; 66 | &:hover { 67 | color: $color-text1-1; 68 | } 69 | } 70 | 71 | .#{$css-prefix}formily-icon { 72 | font-size: 16px; 73 | } 74 | } 75 | 76 | .#{$array-base-prefix-cls}-sort-handle { 77 | cursor: move; 78 | color: #888 !important; 79 | } 80 | ``` -------------------------------------------------------------------------------- /packages/core/src/models/LifeCycle.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isFn, isStr, each } from '@formily/shared' 2 | import { LifeCycleHandler, LifeCyclePayload } from '../types' 3 | 4 | type LifeCycleParams<Payload> = Array< 5 | | string 6 | | LifeCycleHandler<Payload> 7 | | { [key: string]: LifeCycleHandler<Payload> } 8 | > 9 | export class LifeCycle<Payload = any> { 10 | private listener: LifeCyclePayload<Payload> 11 | 12 | constructor(...params: LifeCycleParams<Payload>) { 13 | this.listener = this.buildListener(params) 14 | } 15 | buildListener = (params: any[]) => { 16 | return function (payload: { type: string; payload: Payload }, ctx: any) { 17 | for (let index = 0; index < params.length; index++) { 18 | let item = params[index] 19 | if (isFn(item)) { 20 | item.call(this, payload, ctx) 21 | } else if (isStr(item) && isFn(params[index + 1])) { 22 | if (item === payload.type) { 23 | params[index + 1].call(this, payload.payload, ctx) 24 | } 25 | index++ 26 | } else { 27 | each<any, any>(item, (handler, type) => { 28 | if (isFn(handler) && isStr(type)) { 29 | if (type === payload.type) { 30 | handler.call(this, payload.payload, ctx) 31 | return false 32 | } 33 | } 34 | }) 35 | } 36 | } 37 | } 38 | } 39 | 40 | notify = <Payload>(type: any, payload?: Payload, ctx?: any) => { 41 | if (isStr(type)) { 42 | this.listener.call(ctx, { type, payload }, ctx) 43 | } 44 | } 45 | } 46 | ``` -------------------------------------------------------------------------------- /packages/path/src/__tests__/share.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isAssignable, isEqual } from '../shared' 2 | 3 | test('isAssignable', () => { 4 | expect(isAssignable({})).toBeTruthy() 5 | expect(isAssignable(() => {})).toBeTruthy() 6 | 7 | expect(isAssignable(1)).toBeFalsy() 8 | expect(isAssignable('str')).toBeFalsy() 9 | }) 10 | 11 | test('isEqual', () => { 12 | const sameObj = {} 13 | const sameArray = [] 14 | expect(isEqual('string', 'string')).toBeTruthy() 15 | expect(isEqual(123, 123)).toBeTruthy() 16 | expect(isEqual(undefined, undefined)).toBeTruthy() 17 | expect(isEqual(null, null)).toBeTruthy() 18 | 19 | expect(isEqual(sameObj, sameObj)).toBeTruthy() 20 | expect(isEqual(sameArray, sameArray)).toBeTruthy() 21 | 22 | expect(isEqual([1, '123'], [1, '123'])).toBeTruthy() 23 | expect( 24 | isEqual([1, '123', { a: 1, b: 2 }], [1, '123', { a: 1, b: 2 }]) 25 | ).toBeTruthy() 26 | expect( 27 | isEqual([1, '123', { a: 1, b: 2 }], [1, '123', { a: 1, b: 3 }]) 28 | ).toBeFalsy() 29 | expect(isEqual([1, '123'], [1, '234'])).toBeFalsy() 30 | expect(isEqual([], [1])).toBeFalsy() 31 | expect(isEqual([], {})).toBeFalsy() 32 | 33 | expect(isEqual({ a: [1, 2, 3] }, { a: [1, 2, 3] })).toBeTruthy() 34 | expect(isEqual({ a: [1, 2, 3] }, { a: [1, 2, 4] })).toBeFalsy() 35 | expect(isEqual({ a: 1 }, { a: 11 })).toBeFalsy() 36 | expect(isEqual({ a: 1 }, { a: 1, b: 2 })).toBeFalsy() 37 | 38 | const b = { age: '234' } 39 | // @ts-ignore 40 | Object.prototype.name = '123' 41 | expect(isEqual({ name: '123' }, b)).toBeFalsy() 42 | 43 | expect(isEqual(NaN, NaN)).toBeTruthy() 44 | }) 45 | ``` -------------------------------------------------------------------------------- /packages/reactive-vue/src/hooks/useObserver.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Tracker } from '@formily/reactive' 2 | import { getCurrentInstance, onBeforeUnmount, isVue3 } from 'vue-demi' 3 | import { IObserverOptions } from '../types' 4 | 5 | /* istanbul ignore next */ 6 | export const useObserver = (options?: IObserverOptions) => { 7 | if (isVue3) { 8 | const vm = getCurrentInstance() 9 | let tracker: Tracker = null 10 | const disposeTracker = () => { 11 | if (tracker) { 12 | tracker.dispose() 13 | tracker = null 14 | } 15 | } 16 | const vmUpdate = () => { 17 | vm?.proxy?.$forceUpdate() 18 | } 19 | 20 | onBeforeUnmount(disposeTracker) 21 | 22 | Object.defineProperty(vm, 'effect', { 23 | get() { 24 | // https://github.com/alibaba/formily/issues/2655 25 | return vm['_updateEffect'] || {} 26 | }, 27 | set(newValue) { 28 | vm['_updateEffectRun'] = newValue.run 29 | disposeTracker() 30 | const newTracker = () => { 31 | tracker = new Tracker(() => { 32 | if (options?.scheduler && typeof options.scheduler === 'function') { 33 | options.scheduler(vmUpdate) 34 | } else { 35 | vmUpdate() 36 | } 37 | }) 38 | } 39 | 40 | const update = function () { 41 | let refn = null 42 | tracker?.track(() => { 43 | refn = vm['_updateEffectRun'].call(newValue) 44 | }) 45 | return refn 46 | } 47 | newTracker() 48 | newValue.run = update 49 | vm['_updateEffect'] = newValue 50 | }, 51 | }) 52 | } 53 | } 54 | ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/heart.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Heart, LifeCycle } from '../models' 2 | 3 | test('buildLifecycles', () => { 4 | const heart = new Heart({ 5 | lifecycles: [{} as any, [{}], 123], 6 | }) 7 | expect(heart.lifecycles.length).toEqual(0) 8 | }) 9 | 10 | test('clear heart', () => { 11 | const handler = jest.fn() 12 | const heart = new Heart({ 13 | lifecycles: [new LifeCycle('event', handler)], 14 | }) 15 | heart.publish('event') 16 | expect(handler).toBeCalledTimes(1) 17 | heart.clear() 18 | heart.publish('event') 19 | expect(handler).toBeCalledTimes(1) 20 | heart.publish({}) 21 | }) 22 | 23 | test('set lifecycles', () => { 24 | const handler = jest.fn() 25 | const heart = new Heart() 26 | heart.setLifeCycles([new LifeCycle('event', handler)]) 27 | heart.publish('event') 28 | expect(handler).toBeCalledTimes(1) 29 | heart.setLifeCycles() 30 | }) 31 | 32 | test('add/remove lifecycle', () => { 33 | const handler = jest.fn() 34 | const heart = new Heart() 35 | heart.addLifeCycles('xxx', [new LifeCycle('event', handler)]) 36 | heart.addLifeCycles('yyy') 37 | heart.publish('event') 38 | expect(handler).toBeCalledTimes(1) 39 | heart.removeLifeCycles('xxx') 40 | heart.publish('event') 41 | expect(handler).toBeCalledTimes(1) 42 | }) 43 | 44 | test('add/clear lifecycle', () => { 45 | const handler = jest.fn() 46 | const heart = new Heart() 47 | heart.addLifeCycles('xxx', [new LifeCycle('event', handler)]) 48 | heart.addLifeCycles('yyy') 49 | heart.publish('event') 50 | expect(handler).toBeCalledTimes(1) 51 | heart.clear() 52 | heart.publish('event') 53 | expect(handler).toBeCalledTimes(1) 54 | }) 55 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/array.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const toArray = (value: any) => { 2 | return Array.isArray(value) 3 | ? value 4 | : value !== undefined && value !== null 5 | ? [value] 6 | : [] 7 | } 8 | 9 | export class ArraySet<T> { 10 | value: T[] 11 | forEachIndex = 0 12 | constructor(value: T[] = []) { 13 | this.value = value 14 | } 15 | 16 | add(item: T) { 17 | if (!this.has(item)) { 18 | this.value.push(item) 19 | } 20 | } 21 | 22 | has(item: T) { 23 | return this.value.indexOf(item) > -1 24 | } 25 | 26 | delete(item: T) { 27 | const len = this.value.length 28 | if (len === 0) return 29 | if (len === 1 && this.value[0] === item) { 30 | this.value = [] 31 | return 32 | } 33 | const findIndex = this.value.indexOf(item) 34 | if (findIndex > -1) { 35 | this.value.splice(findIndex, 1) 36 | if (findIndex <= this.forEachIndex) { 37 | this.forEachIndex -= 1 38 | } 39 | } 40 | } 41 | 42 | forEach(callback: (value: T) => void) { 43 | if (this.value.length === 0) return 44 | this.forEachIndex = 0 45 | for (; this.forEachIndex < this.value.length; this.forEachIndex++) { 46 | callback(this.value[this.forEachIndex]) 47 | } 48 | } 49 | 50 | batchDelete(callback: (value: T) => void) { 51 | if (this.value.length === 0) return 52 | this.forEachIndex = 0 53 | for (; this.forEachIndex < this.value.length; this.forEachIndex++) { 54 | const value = this.value[this.forEachIndex] 55 | this.value.splice(this.forEachIndex, 1) 56 | this.forEachIndex-- 57 | callback(value) 58 | } 59 | } 60 | 61 | clear() { 62 | this.value.length = 0 63 | } 64 | } 65 | ``` -------------------------------------------------------------------------------- /packages/json-schema/src/__tests__/traverse.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { traverse, traverseSchema } from '../shared' 2 | import { FormPath } from '@formily/shared' 3 | 4 | test('traverseSchema', () => { 5 | const visited = [] 6 | const omitted = [] 7 | traverseSchema( 8 | { 9 | type: 'string', 10 | title: '{{aa}}', 11 | required: true, 12 | 'x-validator': 'phone', 13 | 'x-compile-omitted': ['title'], 14 | default: { 15 | input: 123, 16 | }, 17 | }, 18 | (value, path, omitCompile) => { 19 | if (omitCompile) { 20 | omitted.push(value) 21 | } else { 22 | visited.push(path) 23 | } 24 | } 25 | ) 26 | expect(visited).toEqual([ 27 | ['x-validator'], 28 | ['type'], 29 | ['required'], 30 | ['default'], 31 | ]) 32 | expect(omitted).toEqual(['{{aa}}']) 33 | }) 34 | 35 | test('traverse circular reference', () => { 36 | // eslint-disable-next-line 37 | var a = { 38 | dd: { 39 | mm: null, 40 | }, 41 | bb: { 42 | cc: { 43 | dd: 123, 44 | }, 45 | }, 46 | kk: { 47 | toJS() {}, 48 | }, 49 | } 50 | a.dd.mm = a 51 | traverse(a, () => {}) 52 | traverseSchema(a as any, () => {}) 53 | }) 54 | 55 | test('traverse none circular reference', () => { 56 | // eslint-disable-next-line 57 | var dd = { 58 | mm: null, 59 | } 60 | let a = { 61 | dd, 62 | bb: { 63 | dd, 64 | }, 65 | } 66 | const paths = [] 67 | traverse(a, (value, path) => { 68 | paths.push(path) 69 | }) 70 | traverseSchema(a, () => {}) 71 | expect( 72 | paths.some((path) => FormPath.parse(path).includes('dd.mm')) 73 | ).toBeTruthy() 74 | expect( 75 | paths.some((path) => FormPath.parse(path).includes('bb.dd.mm')) 76 | ).toBeTruthy() 77 | }) 78 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/observable.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { observable } from '../' 2 | import { contains } from '../externals' 3 | 4 | test('array mutation', () => { 5 | const arr = observable([1, 2, 3, 4]) 6 | arr.splice(2, 1) 7 | expect(arr).toEqual([1, 2, 4]) 8 | }) 9 | 10 | test('observable contains', () => { 11 | const subElement = { cc: 333 } 12 | const element = { aa: subElement } 13 | const arr = observable<any[]>([element, 2, 3, 4]) 14 | expect(contains(arr, arr[0])).toBe(true) 15 | expect(contains(arr, arr[0].aa)).toBe(true) 16 | expect(contains(arr, element)).toBe(true) 17 | expect(contains(arr, subElement)).toBe(true) 18 | expect(contains(element, subElement)).toBe(true) 19 | expect(contains(element, arr[0].aa)).toBe(true) 20 | expect(contains(arr[0], subElement)).toBe(true) 21 | 22 | const obj = observable<any>({}) 23 | const other = { bb: 321 } 24 | expect(contains(obj, obj.other)).toBe(false) 25 | obj.other = other 26 | obj.arr = arr 27 | 28 | expect(contains(obj, obj.other)).toBe(true) 29 | expect(contains(obj, other)).toBe(true) 30 | 31 | expect(contains(obj, obj.arr)).toBe(true) 32 | expect(contains(obj, arr)).toBe(true) 33 | }) 34 | 35 | test('observable __proto__', () => { 36 | const observableArr = observable([] as any[]) 37 | // @ts-ignore 38 | observableArr.__proto__ = Object.create(Array.prototype) 39 | observableArr[0] = {} 40 | expect(observableArr).toEqual([{}]) 41 | 42 | const observableObj = observable({} as any) 43 | // @ts-ignore 44 | observableObj.__proto__ = Object.create(Object.prototype) 45 | observableObj.aa = {} 46 | expect(observableObj).toEqual({ aa: {} }) 47 | }) 48 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/object-field.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <ObjectField name="object"> 4 | <template #default="{ field }"> 5 | <div 6 | v-for="key in Object.keys(field.value || {})" 7 | :key="key" 8 | :style="{ marginBottom: '10px' }" 9 | > 10 | <Space> 11 | <Field :name="key" :component="[Input, { placeholder: key }]" /> 12 | <Button @click="field.removeProperty(key)"> Remove </Button> 13 | </Space> 14 | </div> 15 | <Space> 16 | <Field 17 | name="propertyName" 18 | basePath="" 19 | required 20 | :component="[Input, { placeholder: 'Property Name' }]" 21 | /> 22 | <Button @click="addPropertyToField(field)"> Add </Button> 23 | </Space> 24 | </template> 25 | </ObjectField> 26 | </FormProvider> 27 | </template> 28 | 29 | <script> 30 | import { Input, Space, Button } from 'ant-design-vue' 31 | import { createForm } from '@formily/core' 32 | import { FormProvider, ObjectField, Field } from '@formily/vue' 33 | import 'ant-design-vue/dist/antd.css' 34 | 35 | export default { 36 | components: { FormProvider, ObjectField, Field, Space, Button }, 37 | data() { 38 | return { 39 | Input, 40 | form: createForm(), 41 | } 42 | }, 43 | methods: { 44 | addPropertyToField(field) { 45 | const name = this.form.values.propertyName 46 | if (name && !this.form.existValuesIn(`object.${name}`)) { 47 | field.addProperty(name, '') 48 | this.form.deleteValuesIn('propertyName') 49 | } 50 | }, 51 | }, 52 | } 53 | </script> 54 | ``` -------------------------------------------------------------------------------- /packages/vue/src/components/VoidField.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isVue2, h as _h } from 'vue-demi' 2 | import ReactiveField from './ReactiveField' 3 | import { getRawComponent } from '../utils/getRawComponent' 4 | 5 | import type { IVoidFieldProps, DefineComponent } from '../types' 6 | import { getVoidFieldProps } from '../utils/getFieldProps' 7 | 8 | let VoidField: DefineComponent<IVoidFieldProps> 9 | 10 | /* istanbul ignore else */ 11 | if (isVue2) { 12 | VoidField = { 13 | functional: true, 14 | name: 'VoidField', 15 | props: getVoidFieldProps(), 16 | render(h, context) { 17 | const props = context.props as IVoidFieldProps 18 | const attrs = context.data.attrs 19 | const componentData = { 20 | ...context.data, 21 | props: { 22 | fieldType: 'VoidField', 23 | fieldProps: { 24 | ...attrs, 25 | ...props, 26 | ...getRawComponent(props), 27 | }, 28 | }, 29 | } 30 | return _h(ReactiveField, componentData, context.children) 31 | }, 32 | } as unknown as DefineComponent<IVoidFieldProps> 33 | } else { 34 | VoidField = { 35 | name: 'VoidField', 36 | props: getVoidFieldProps(), 37 | setup(props: IVoidFieldProps, context) { 38 | return () => { 39 | const componentData = { 40 | fieldType: 'VoidField', 41 | fieldProps: { 42 | ...props, 43 | ...getRawComponent(props), 44 | }, 45 | } as Record<string, unknown> 46 | return _h(ReactiveField, componentData, context.slots) 47 | } 48 | }, 49 | } as unknown as DefineComponent<IVoidFieldProps> 50 | } 51 | 52 | export default VoidField 53 | ``` -------------------------------------------------------------------------------- /packages/vue/src/components/ArrayField.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isVue2, h as _h } from 'vue-demi' 2 | import ReactiveField from './ReactiveField' 3 | import { getRawComponent } from '../utils/getRawComponent' 4 | 5 | import type { IArrayFieldProps, DefineComponent } from '../types' 6 | import { getFieldProps } from '../utils/getFieldProps' 7 | 8 | let ArrayField: DefineComponent<IArrayFieldProps> 9 | 10 | /* istanbul ignore else */ 11 | if (isVue2) { 12 | ArrayField = { 13 | functional: true, 14 | name: 'ArrayField', 15 | props: getFieldProps(), 16 | render(h, context) { 17 | const props = context.props as IArrayFieldProps 18 | const attrs = context.data.attrs 19 | const componentData = { 20 | ...context.data, 21 | props: { 22 | fieldType: 'ArrayField', 23 | fieldProps: { 24 | ...attrs, 25 | ...props, 26 | ...getRawComponent(props), 27 | }, 28 | }, 29 | } 30 | return _h(ReactiveField, componentData, context.children) 31 | }, 32 | } as unknown as DefineComponent<IArrayFieldProps> 33 | } else { 34 | ArrayField = { 35 | name: 'ArrayField', 36 | props: getFieldProps(), 37 | setup(props: IArrayFieldProps, context) { 38 | return () => { 39 | const componentData = { 40 | fieldType: 'ArrayField', 41 | fieldProps: { 42 | ...props, 43 | ...getRawComponent(props), 44 | }, 45 | } as Record<string, unknown> 46 | return _h(ReactiveField, componentData, context.slots) 47 | } 48 | }, 49 | } as unknown as DefineComponent<IArrayFieldProps> 50 | } 51 | 52 | export default ArrayField 53 | ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/lifecycle.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { LifeCycle } from '../models' 2 | 3 | test('create lifecycle', () => { 4 | const handler1 = jest.fn() 5 | const lifecycle1 = new LifeCycle(handler1) 6 | lifecycle1.notify('event1') 7 | expect(handler1).toBeCalledTimes(1) 8 | expect(handler1).toBeCalledWith( 9 | { 10 | type: 'event1', 11 | payload: undefined, 12 | }, 13 | undefined 14 | ) 15 | lifecycle1.notify('event11', 'payload1') 16 | expect(handler1).toBeCalledTimes(2) 17 | expect(handler1).toBeCalledWith( 18 | { 19 | type: 'event11', 20 | payload: 'payload1', 21 | }, 22 | undefined 23 | ) 24 | const context: any = {} 25 | lifecycle1.notify('event12', 'payload11', context) 26 | expect(handler1).toBeCalledTimes(3) 27 | expect(handler1).toBeCalledWith( 28 | { 29 | type: 'event12', 30 | payload: 'payload11', 31 | }, 32 | context 33 | ) 34 | 35 | const handler2 = jest.fn() 36 | const lifecycle2 = new LifeCycle('event2', handler2) 37 | lifecycle2.notify('event1') 38 | expect(handler2).not.toBeCalled() 39 | lifecycle2.notify('event2') 40 | expect(handler2).toBeCalledTimes(1) 41 | 42 | const handler31 = jest.fn() 43 | const handler32 = jest.fn() 44 | const lifecycle3 = new LifeCycle({ 45 | event31: handler31, 46 | event32: handler32, 47 | }) 48 | lifecycle3.notify('event3') 49 | expect(handler31).not.toBeCalled() 50 | expect(handler32).not.toBeCalled() 51 | lifecycle3.notify('event31') 52 | expect(handler31).toBeCalledTimes(1) 53 | expect(handler32).not.toBeCalled() 54 | lifecycle3.notify('event32') 55 | expect(handler31).toBeCalledTimes(1) 56 | expect(handler32).toBeCalledTimes(1) 57 | }) 58 | ``` -------------------------------------------------------------------------------- /packages/vue/src/components/ObjectField.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isVue2, h as _h } from 'vue-demi' 2 | import ReactiveField from './ReactiveField' 3 | import { getRawComponent } from '../utils/getRawComponent' 4 | 5 | import type { IObjectFieldProps, DefineComponent } from '../types' 6 | import { getFieldProps } from '../utils/getFieldProps' 7 | 8 | let ObjectField: DefineComponent<IObjectFieldProps> 9 | 10 | /* istanbul ignore else */ 11 | if (isVue2) { 12 | ObjectField = { 13 | functional: true, 14 | name: 'ObjectField', 15 | props: getFieldProps(), 16 | render(h, context) { 17 | const props = context.props as IObjectFieldProps 18 | const attrs = context.data.attrs 19 | const componentData = { 20 | ...context.data, 21 | props: { 22 | fieldType: 'ObjectField', 23 | fieldProps: { 24 | ...attrs, 25 | ...props, 26 | ...getRawComponent(props), 27 | }, 28 | }, 29 | } 30 | return _h(ReactiveField, componentData, context.children) 31 | }, 32 | } as unknown as DefineComponent<IObjectFieldProps> 33 | } else { 34 | ObjectField = { 35 | name: 'ObjectField', 36 | props: getFieldProps(), 37 | setup(props: IObjectFieldProps, context) { 38 | return () => { 39 | const componentData = { 40 | fieldType: 'ObjectField', 41 | fieldProps: { 42 | ...props, 43 | ...getRawComponent(props), 44 | }, 45 | } as Record<string, unknown> 46 | return _h(ReactiveField, componentData, context.slots) 47 | } 48 | }, 49 | } as unknown as DefineComponent<IObjectFieldProps> 50 | } 51 | 52 | export default ObjectField 53 | ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/define.md: -------------------------------------------------------------------------------- ```markdown 1 | # define 2 | 3 | ## Description 4 | 5 | Manually define the domain model, you can specify the responsive behavior of specific attributes, or you can specify a method as batch mode 6 | 7 | ## Signature 8 | 9 | ```ts 10 | interface define<Target extends object> { 11 | ( 12 | target: Target, 13 | annotations?: { 14 | [key: string]: (...args: any[]) => any 15 | } 16 | ): Target 17 | } 18 | ``` 19 | 20 | ## Annotations 21 | 22 | All Annotations currently supported are: 23 | 24 | - observable/observable.deep defines deep hijacking responsive properties 25 | - observable.box defines get/set container 26 | - observable.computed defines calculated properties 27 | - observable.ref defines reference hijacking responsive attributes 28 | - observable.shallow defines shallow hijacking responsive properties 29 | - action/batch defines the batch processing method 30 | 31 | ## Example 32 | 33 | ```ts 34 | import { define, observable, action, autorun } from '@formily/reactive' 35 | 36 | class DomainModel { 37 | deep = { aa: 1 } 38 | shallow = {} 39 | box = 0 40 | ref = '' 41 | 42 | constructor() { 43 | define(this, { 44 | deep: observable, 45 | shallow: observable.shallow, 46 | box: observable.box, 47 | ref: observable.ref, 48 | computed: observable.computed, 49 | action, 50 | }) 51 | } 52 | 53 | get computed() { 54 | return this.deep.aa + this.box.get() 55 | } 56 | action(aa, box) { 57 | this.deep.aa = aa 58 | this.box.set(box) 59 | } 60 | } 61 | 62 | const model = new DomainModel() 63 | 64 | autorun(() => { 65 | console.log(model.computed) 66 | }) 67 | 68 | model.action(1, 2) 69 | model.action(1, 2) //Repeat calls will not respond repeatedly 70 | model.action(3, 4) 71 | ``` 72 | ``` -------------------------------------------------------------------------------- /packages/antd/src/form/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import { Form as FormType, ObjectField, IFormFeedback } from '@formily/core' 3 | import { useParentForm, FormProvider, JSXComponent } from '@formily/react' 4 | import { FormLayout, IFormLayoutProps } from '../form-layout' 5 | import { PreviewText } from '../preview-text' 6 | export interface FormProps extends IFormLayoutProps { 7 | form?: FormType 8 | component?: JSXComponent 9 | onAutoSubmit?: (values: any) => any 10 | onAutoSubmitFailed?: (feedbacks: IFormFeedback[]) => void 11 | previewTextPlaceholder?: React.ReactNode 12 | } 13 | 14 | export const Form: React.FC<React.PropsWithChildren<FormProps>> = ({ 15 | form, 16 | component = 'form', 17 | onAutoSubmit, 18 | onAutoSubmitFailed, 19 | previewTextPlaceholder, 20 | ...props 21 | }) => { 22 | const top = useParentForm() 23 | const renderContent = (form: FormType | ObjectField) => ( 24 | <PreviewText.Placeholder value={previewTextPlaceholder}> 25 | <FormLayout {...props}> 26 | {React.createElement( 27 | component, 28 | { 29 | onSubmit(e: React.FormEvent) { 30 | e?.stopPropagation?.() 31 | e?.preventDefault?.() 32 | form.submit(onAutoSubmit).catch(onAutoSubmitFailed) 33 | }, 34 | }, 35 | props.children 36 | )} 37 | </FormLayout> 38 | </PreviewText.Placeholder> 39 | ) 40 | if (form) 41 | return <FormProvider form={form}>{renderContent(form)}</FormProvider> 42 | if (!top) throw new Error('must pass form instance by createForm') 43 | return renderContent(top) 44 | } 45 | 46 | export default Form 47 | ``` -------------------------------------------------------------------------------- /packages/reactive-vue/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@formily/reactive-vue", 3 | "version": "2.3.7", 4 | "license": "MIT", 5 | "main": "lib", 6 | "module": "esm", 7 | "umd:main": "dist/formily.reactive-vue.umd.production.js", 8 | "unpkg": "dist/formily.reactive-vue.umd.production.js", 9 | "jsdelivr": "dist/formily.reactive-vue.umd.production.js", 10 | "jsnext:main": "esm", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/alibaba/formily.git" 14 | }, 15 | "types": "esm/index.d.ts", 16 | "bugs": { 17 | "url": "https://github.com/alibaba/formily/issues" 18 | }, 19 | "homepage": "https://github.com/alibaba/formily#readme", 20 | "engines": { 21 | "npm": ">=3.0.0" 22 | }, 23 | "scripts": { 24 | "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", 25 | "build:cjs": "tsc --project tsconfig.build.json", 26 | "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", 27 | "build:umd": "rollup --config" 28 | }, 29 | "devDependencies": { 30 | "@vue/composition-api": "^1.0.0-rc.7", 31 | "@vue/test-utils": "1.0.0-beta.22", 32 | "core-js": "^2.4.0", 33 | "vue": "^2.6.12" 34 | }, 35 | "dependencies": { 36 | "@formily/reactive": "2.3.7", 37 | "vue-demi": ">=0.13.6" 38 | }, 39 | "peerDependencies": { 40 | "@vue/composition-api": "^1.0.0-beta.1", 41 | "vue": "^2.6.0 || >=3.0.0-rc.0" 42 | }, 43 | "peerDependenciesMeta": { 44 | "@vue/composition-api": { 45 | "optional": true 46 | } 47 | }, 48 | "publishConfig": { 49 | "access": "public" 50 | }, 51 | "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" 52 | } 53 | ```