This is page 4 of 35. Use http://codebase.md/alibaba/formily?page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /devtools/chrome-extension/src/app/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useState } from 'react' import { LeftPanel } from './components/LeftPanel' import { RightPanel } from './components/RightPanel' import styled from 'styled-components' export default styled(({ className, dataSource }) => { const [selected, select] = useState({ current: 0, key: '', }) return ( <div className={className}> <LeftPanel dataSource={dataSource} onSelect={(info) => { select(info) if (chrome && chrome.devtools && chrome.devtools.inspectedWindow) { chrome.devtools.inspectedWindow.eval( `window.__FORMILY_DEV_TOOLS_HOOK__.setVm("${info.key}","${ dataSource[info.current][''].id }")` ) } }} /> <RightPanel dataSource={ selected ? (dataSource && dataSource[selected.current] && dataSource[selected.current][selected.key]) || {} : {} } /> </div> ) })` display: flex; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow: hidden; color: #36d4c7; background: #282c34; ` ``` -------------------------------------------------------------------------------- /packages/reactive/src/annotations/box.ts: -------------------------------------------------------------------------------- ```typescript import { ProxyRaw, RawProxy } from '../environment' import { createAnnotation } from '../internals' import { buildDataTree } from '../tree' import { bindTargetKeyWithCurrentReaction, runReactionsFromTargetKey, } from '../reaction' export interface IBox { <T>(target: T): { get(): T; set(value: T): void } } export const box: IBox = createAnnotation(({ target, key, value }) => { const store = { value: target ? target[key] : value, } const proxy = { set, get, } ProxyRaw.set(proxy, store) RawProxy.set(store, proxy) buildDataTree(target, key, store) function get() { bindTargetKeyWithCurrentReaction({ target: store, key, type: 'get', }) return store.value } function set(value: any) { const oldValue = store.value store.value = value if (oldValue !== value) { runReactionsFromTargetKey({ target: store, key, type: 'set', oldValue, value, }) } } if (target) { Object.defineProperty(target, key, { value: proxy, enumerable: true, configurable: false, writable: false, }) return target } return proxy }) ``` -------------------------------------------------------------------------------- /packages/react/docs/api/hooks/useFieldSchema.md: -------------------------------------------------------------------------------- ```markdown # useFieldSchema ## Description 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 ## Signature ```ts interface useFieldSchema { (): Schema } ``` Schema Reference [Schema](/api/shared/schema) ## Example ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, createSchemaField, useFieldSchema } from '@formily/react' const form = createForm() const Custom = () => { const schema = useFieldSchema() return ( <code> <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre> </code> ) } const SchemaField = createSchemaField({ components: { Custom, }, }) export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Object name="custom" x-component="Custom" x-component-props={{ schema: { type: 'object', properties: { input: { type: 'string', 'x-component': 'Custom', }, }, }, }} /> </SchemaField> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/expression-scope.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField :scope="{ $outerScope: 'outer scope value' }"> <SchemaVoidField x-component="Container"> <SchemaVoidField name="div" x-component="Text" :x-component-props="{ text: `{{$innerScope + ' ' + $outerScope}}` }" /> </SchemaVoidField> </SchemaField> </FormProvider> </template> <script> import { defineComponent } from '@vue/composition-api' import { createForm } from '@formily/core' import { FormProvider, h, createSchemaField, ExpressionScope, } from '@formily/vue' const Container = defineComponent({ setup(_props, { slots }) { return () => h( ExpressionScope, { props: { value: { $innerScope: 'inner scope value' } }, }, slots ) }, }) const Text = defineComponent({ props: ['text'], setup(props) { return () => h('div', {}, { default: () => props.text }) }, }) const SchemaField = createSchemaField({ components: { Container, Text }, }) export default { components: { FormProvider, ...SchemaField }, data() { return { Text, form: createForm(), } }, } </script> ``` -------------------------------------------------------------------------------- /packages/json-schema/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/json-schema", "version": "2.3.7", "license": "MIT", "main": "lib", "module": "esm", "umd:main": "dist/formily.json-schema.umd.production.js", "unpkg": "dist/formily.json-schema.umd.production.js", "jsdelivr": "dist/formily.json-schema.umd.production.js", "jsnext:main": "esm", "repository": { "type": "git", "url": "git+https://github.com/alibaba/formily.git" }, "types": "esm/index.d.ts", "bugs": { "url": "https://github.com/alibaba/formily/issues" }, "homepage": "https://github.com/alibaba/formily#readme", "engines": { "npm": ">=3.0.0" }, "scripts": { "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", "build:cjs": "tsc --project tsconfig.build.json", "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", "build:umd": "rollup --config" }, "peerDependencies": { "typescript": ">4.1.5" }, "dependencies": { "@formily/core": "2.3.7", "@formily/reactive": "2.3.7", "@formily/shared": "2.3.7" }, "publishConfig": { "access": "public" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" } ``` -------------------------------------------------------------------------------- /packages/reactive/src/observe.ts: -------------------------------------------------------------------------------- ```typescript import { IOperation } from './types' import { ObserverListeners } from './environment' import { raw as getRaw } from './externals' import { isFn } from './checkers' import { DataChange, getDataNode } from './tree' export const observe = ( target: object, observer?: (change: DataChange) => void, deep = true ) => { const addListener = (target: any) => { const raw = getRaw(target) const node = getDataNode(raw) const listener = (operation: IOperation) => { const targetRaw = getRaw(operation.target) const targetNode = getDataNode(targetRaw) if (deep) { if (node.contains(targetNode)) { observer(new DataChange(operation, targetNode)) return } } if ( node === targetNode || (node.targetRaw === targetRaw && node.key === operation.key) ) { observer(new DataChange(operation, targetNode)) } } if (node && isFn(observer)) { ObserverListeners.add(listener) } return () => { ObserverListeners.delete(listener) } } if (target && typeof target !== 'object') throw Error(`Can not observe ${typeof target} type.`) return addListener(target) } ``` -------------------------------------------------------------------------------- /packages/element/src/date-picker/index.ts: -------------------------------------------------------------------------------- ```typescript import { transformComponent } from '../__builtins__/shared' import { connect, mapProps, mapReadPretty } from '@formily/vue' import type { DatePicker as ElDatePickerProps } from 'element-ui' import { DatePicker as ElDatePicker } from 'element-ui' import { PreviewText } from '../preview-text' export type DatePickerProps = ElDatePickerProps const TransformElDatePicker = transformComponent<DatePickerProps>( ElDatePicker, { change: 'input', } ) const getDefaultFormat = (props, formatType = 'format') => { const type = props.type if (type === 'week' && formatType === 'format') { return 'yyyy-WW' } else if (type === 'month') { return 'yyyy-MM' } else if (type === 'year') { return 'yyyy' } else if (type === 'datetime' || type === 'datetimerange') { return 'yyyy-MM-dd HH:mm:ss' } return 'yyyy-MM-dd' } export const DatePicker = connect( TransformElDatePicker, mapProps({ readOnly: 'readonly' }, (props) => { return { ...props, format: props.format || getDefaultFormat(props), valueFormat: props.valueFormat || getDefaultFormat(props, 'valueFormat'), } }), mapReadPretty(PreviewText.DatePicker) ) export default DatePicker ``` -------------------------------------------------------------------------------- /packages/antd/src/array-table/style.less: -------------------------------------------------------------------------------- ``` @root-entry-name: 'default'; @import (reference) '~antd/es/style/themes/index.less'; @array-table-prefix-cls: ~'@{ant-prefix}-formily-array-table'; .@{array-table-prefix-cls} { .@{array-table-prefix-cls}-pagination { display: flex; justify-content: center; .@{array-table-prefix-cls}-status-select.has-error { .@{ant-prefix}-select-selector { border-color: @error-color !important; } } } .@{ant-prefix}-table { table { overflow: hidden; } td { visibility: visible; .@{ant-prefix}-formily-item:not(.@{ant-prefix}-formily-item-feedback-layout-popover) { margin-bottom: 0 !important; .@{ant-prefix}-formily-item-help { position: absolute; font-size: 12px; top: 100%; background: #fff; width: 100%; margin-top: 3px; padding: 3px; z-index: 1; border-radius: 3px; box-shadow: 0 0 10px #eee; animation: none; transform: translateY(0); opacity: 1; } } } } .@{array-table-prefix-cls}-sort-helper { background: #fff; border: 1px solid #eee; z-index: 10; } } ``` -------------------------------------------------------------------------------- /packages/antd/src/submit/index.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { Button } from 'antd' import { ButtonProps } from 'antd/lib/button' import { IFormFeedback } from '@formily/core' import { useParentForm, observer } from '@formily/react' export interface ISubmitProps extends ButtonProps { onClick?: (e: React.MouseEvent<Element, MouseEvent>) => any onSubmit?: (values: any) => any onSubmitSuccess?: (payload: any) => void onSubmitFailed?: (feedbacks: IFormFeedback[]) => void } export const Submit: React.FC<React.PropsWithChildren<ISubmitProps>> = observer( ({ onSubmit, onSubmitFailed, onSubmitSuccess, ...props }: ISubmitProps) => { const form = useParentForm() return ( <Button htmlType={onSubmit ? 'button' : 'submit'} type="primary" {...props} loading={props.loading !== undefined ? props.loading : form.submitting} onClick={(e) => { if (props.onClick) { if (props.onClick(e) === false) return } if (onSubmit) { form.submit(onSubmit).then(onSubmitSuccess).catch(onSubmitFailed) } }} > {props.children} </Button> ) }, { forwardRef: true, } ) export default Submit ``` -------------------------------------------------------------------------------- /packages/vue/src/utils/getFieldProps.ts: -------------------------------------------------------------------------------- ```typescript export const getFieldProps = () => ({ name: {}, title: {}, description: {}, value: {}, initialValue: {}, basePath: {}, decorator: Array, component: Array, display: String, pattern: String, required: { type: Boolean, default: undefined }, validateFirst: { type: Boolean, default: undefined }, hidden: { type: Boolean, default: undefined }, visible: { type: Boolean, default: undefined }, editable: { type: Boolean, default: undefined }, disabled: { type: Boolean, default: undefined }, readOnly: { type: Boolean, default: undefined }, readPretty: { type: Boolean, default: undefined }, dataSource: {}, validator: {}, reactions: [Array, Function], }) export const getVoidFieldProps = () => ({ name: {}, title: {}, description: {}, basePath: {}, decorator: Array, component: Array, display: String, pattern: String, hidden: { type: Boolean, default: undefined }, visible: { type: Boolean, default: undefined }, editable: { type: Boolean, default: undefined }, disabled: { type: Boolean, default: undefined }, readOnly: { type: Boolean, default: undefined }, readPretty: { type: Boolean, default: undefined }, reactions: [Array, Function], }) ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/submit/loading.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaStringField required name="input1" title="输入框" x-decorator="FormItem" x-component="Input" /> <SchemaStringField required title="输入框" name="input2" x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormButtonGroup align-form-item> <Submit @submit="handleSubmit">提交</Submit> </FormButtonGroup> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormLayout, Submit, FormButtonGroup, FormItem, Input, } from '@formily/element' const fields = createSchemaField({ components: { FormItem, Input } }) export default { components: { FormProvider, FormLayout, Submit, FormButtonGroup, ...fields, }, data() { const form = createForm() return { form, } }, methods: { handleSubmit(values) { return new Promise((resolve) => { setTimeout(() => { console.log(values) resolve() }, 2000) }) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/next/src/__builtins__/moment.ts: -------------------------------------------------------------------------------- ```typescript import { isArr, isEmpty, isFn } from '@formily/shared' import Moment from 'moment' const moment = (date: any, format?: string) => { return Moment(date?.toDate ? date.toDate() : date, format) } export const momentable = (value: any, format?: string) => { return Array.isArray(value) ? value.map((val) => moment(val, format)) : value ? moment(value, format) : value } export const formatMomentValue = ( value: any, format: any, placeholder?: string ): string | string[] => { const formatDate = (date: any, format: any, i = 0) => { if (!date) return placeholder const TIME_REG = /^(?:[01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/ let _format = format if (isArr(format)) { _format = format[i] } if (isFn(_format)) { return _format(date) } if (isEmpty(_format)) { return date } // moment '19:55:22' 下需要传入第二个参数 if (TIME_REG.test(date)) { return moment(date, _format).format(_format) } return moment(date).format(_format) } if (isArr(value)) { return value.map((val, index) => { return formatDate(val, format, index) }) } else { return value ? formatDate(value, format) : value || placeholder } } ``` -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/shared", "version": "2.3.7", "license": "MIT", "main": "lib", "module": "esm", "umd:main": "dist/formily.shared.umd.production.js", "unpkg": "dist/formily.shared.umd.production.js", "jsdelivr": "dist/formily.shared.umd.production.js", "jsnext:main": "esm", "types": "esm/index.d.ts", "repository": { "type": "git", "url": "git+https://github.com/alibaba/formily.git" }, "bugs": { "url": "https://github.com/alibaba/formily/issues" }, "homepage": "https://github.com/alibaba/formily#readme", "engines": { "npm": ">=3.0.0" }, "publishConfig": { "access": "public" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283", "scripts": { "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", "build:cjs": "tsc --project tsconfig.build.json", "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", "build:umd": "rollup --config" }, "dependencies": { "@formily/path": "2.3.7", "camel-case": "^4.1.1", "lower-case": "^2.0.1", "no-case": "^3.0.4", "param-case": "^3.0.4", "pascal-case": "^3.1.1", "upper-case": "^2.0.1" } } ``` -------------------------------------------------------------------------------- /packages/next/src/submit/index.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { Button } from '@alifd/next' import { ButtonProps } from '@alifd/next/lib/button' import { IFormFeedback } from '@formily/core' import { useParentForm, observer } from '@formily/react' interface ISubmitProps extends ButtonProps { onClick?: (e: React.MouseEvent<Element, MouseEvent>) => any onSubmit?: (values: any) => any onSubmitSuccess?: (payload: any) => void onSubmitFailed?: (feedbacks: IFormFeedback[]) => void } export const Submit: React.FC<React.PropsWithChildren<ISubmitProps>> = observer( ({ onSubmit, onSubmitFailed, onSubmitSuccess, ...props }: ISubmitProps) => { const form = useParentForm() return ( <Button htmlType={onSubmit ? 'button' : 'submit'} type="primary" {...props} loading={props.loading !== undefined ? props.loading : form.submitting} onClick={(e) => { if (props.onClick) { if (props.onClick(e) === false) return } if (onSubmit) { form.submit(onSubmit).then(onSubmitSuccess).catch(onSubmitFailed) } }} > {props.children} </Button> ) }, { forwardRef: true, } ) export default Submit ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/markRaw.md: -------------------------------------------------------------------------------- ```markdown # markRaw ## Description Mark any object or class prototype as never being hijacked by observable, priority is higher than markObservable Note: If you mark an object that is already observable with markRaw, then toJS will not convert it into a normal object ## Signature ```ts interface markRaw<T> { (target: T): T } ``` ## Example ```ts import { observable, autorun, markRaw } from '@formily/reactive' class A { property = '' } const a = observable(new A()) autorun(() => { console.log(a.property) //It will be triggered when the property changes, because the A instance is a normal object }) a.property = 123 //-------------------------------------------- const b = observable(markRaw(new A())) //instance-level mark, only valid for the current instance autorun(() => { console.log(b.property) //will not be triggered when the property changes, because it has been marked raw }) b.property = 123 //-------------------------------------------- markRaw(A) //Class-level mark, then all instances will take effect const c = observable(new A()) autorun(() => { console.log(c.property) //will not be triggered when the property changes, because it has been marked raw }) c.property = 123 ``` ``` -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/core", "version": "2.3.7", "license": "MIT", "main": "lib", "module": "esm", "umd:main": "dist/formily.core.umd.production.js", "unpkg": "dist/formily.core.umd.production.js", "jsdelivr": "dist/formily.core.umd.production.js", "jsnext:main": "esm", "repository": { "type": "git", "url": "git+https://github.com/alibaba/formily.git" }, "types": "esm/index.d.ts", "bugs": { "url": "https://github.com/alibaba/formily/issues" }, "homepage": "https://github.com/alibaba/formily#readme", "engines": { "npm": ">=3.0.0" }, "scripts": { "start": "dumi dev", "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", "build:cjs": "tsc --project tsconfig.build.json", "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", "build:umd": "rollup --config", "build:docs": "dumi build" }, "devDependencies": { "dumi": "^1.1.0-rc.8" }, "dependencies": { "@formily/reactive": "2.3.7", "@formily/shared": "2.3.7", "@formily/validator": "2.3.7" }, "publishConfig": { "access": "public" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" } ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/void-field.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <Space> <VoidField name="layout"> <Field name="input" :component="[Input]" /> </VoidField> <FormConsumer> <template #default="{ form }"> <Space> <Button @click=" () => { form .query('layout') .take() .setState((state) => { state.visible = !state.visible }) } " > {{ form.query('layout').get('visible') ? 'Hide' : 'Show' }} </Button> <div>{{ JSON.stringify(form.values, null, 2) }}</div> </Space> </template> </FormConsumer> </Space> </FormProvider> </template> <script> import { Input, Space, Button } from 'ant-design-vue' import { createForm } from '@formily/core' import { FormProvider, Field, FormConsumer, VoidField } from '@formily/vue' import 'ant-design-vue/dist/antd.css' export default { components: { FormProvider, Field, FormConsumer, VoidField, Space, Button }, data() { return { Input, form: createForm(), } }, } </script> ``` -------------------------------------------------------------------------------- /packages/react/docs/api/hooks/useExpressionScope.md: -------------------------------------------------------------------------------- ```markdown # useExpressionScope ## Description The expression scope is mainly read in the custom component. The sources of the expression scope are: - createSchemaField top-level delivery - SchemaField component attribute delivery - ExpressionScope/RecordScope/RecordsScope are issued inside custom components ## Signature ```ts interface useExpressionScope { (): any } ``` ## Example ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, createSchemaField, useExpressionScope, RecordScope, } from '@formily/react' const form = createForm() const Custom = () => { const scope = useExpressionScope() return ( <code> <pre>{JSON.stringify(scope, null, 2)}</pre> </code> ) } const SchemaField = createSchemaField({ components: { Custom, }, scope: { topScope: { aa: 123, }, }, }) export default () => ( <FormProvider form={form}> <RecordScope getRecord={() => ({ name: 'Record Name', code: 'Record Code' })} getIndex={() => 2} > <SchemaField scope={{ propsScope: { bb: 321 } }}> <SchemaField.String name="custom" x-component="Custom" /> </SchemaField> </RecordScope> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/vue/observer.md: -------------------------------------------------------------------------------- ```markdown # observer ## describe 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. ### Signature ```ts interface IObserverOptions { scheduler?: (updater: () => void) => void //The scheduler, you can manually control the timing of the update name?: string //name of the packaged component } interface observer<T extends VueComponent> { (component: T, options?: IObserverOptions): T } ``` ## Example ```html <template> <div> <div> <input :style="{ height: 28, padding: '0 8px', border: '2px solid #888', borderRadius: 3, }" :value="obs.value" @input="(e) => { obs.value = e.target.value }" /> </div> <div>{{obs.value}}</div> </div> </template> <script> import { observable } from '@formily/reactive' import { observer } from '@formily/reactive-vue' export default observer({ data() { // can coexist with vue's response system const obs = observable({ value: 'Hello world', }) return { obs, } }, }) </script> ``` ``` -------------------------------------------------------------------------------- /packages/antd/src/time-picker/index.tsx: -------------------------------------------------------------------------------- ```typescript import moment from 'moment' import { connect, mapProps, mapReadPretty } from '@formily/react' import { TimePicker as AntdTimePicker } from 'antd' import { TimePickerProps as AntdTimePickerProps, TimeRangePickerProps, } from 'antd/lib/time-picker' import { PreviewText } from '../preview-text' import { formatMomentValue, momentable } from '../__builtins__' type ComposedTimePicker = React.FC< React.PropsWithChildren<AntdTimePickerProps> > & { RangePicker?: React.FC<React.PropsWithChildren<TimeRangePickerProps>> } const mapTimeFormat = function () { return (props: any) => { const format = props['format'] || 'HH:mm:ss' const onChange = props.onChange return { ...props, format, value: momentable(props.value, format), onChange: (value: moment.Moment | moment.Moment[]) => { if (onChange) { onChange(formatMomentValue(value, format)) } }, } } } export const TimePicker: ComposedTimePicker = connect( AntdTimePicker, mapProps(mapTimeFormat()), mapReadPretty(PreviewText.TimePicker) ) TimePicker.RangePicker = connect( AntdTimePicker.RangePicker, mapProps(mapTimeFormat()), mapReadPretty(PreviewText.TimeRangePicker) ) export default TimePicker ``` -------------------------------------------------------------------------------- /packages/element/src/__builtins__/shared/utils.ts: -------------------------------------------------------------------------------- ```typescript import { onMounted, ref } from 'vue-demi' export function isValidElement(element) { return ( isVueOptions(element) || (element && typeof element === 'object' && 'componentOptions' in element && 'context' in element && element.tag !== undefined) ) // remove text node } export function isVnode(element: any): boolean { return ( element && typeof element === 'object' && 'componentOptions' in element && 'context' in element && element.tag !== undefined ) } export function isVueOptions(options) { return ( options && (typeof options.template === 'string' || typeof options.render === 'function') ) } export function composeExport<T0 extends {}, T1 extends {}>( s0: T0, s1: T1 ): T0 & T1 { return Object.assign(s0, s1) } /** * 处理 vue 2.6 和 2.7 的 ref 兼容问题 * composition-api 不支持 setup ref * @param refs * @returns */ export function useCompatRef(refs?: { [key: string]: Vue | Element | Vue[] | Element[] }) { const elRef = ref(null) const elRefBinder = Math.random().toString(36).slice(-8) onMounted(() => { if (refs) { elRef.value = refs[elRefBinder] } }) return { elRef, elRefBinder: refs ? elRefBinder : elRef, } } ``` -------------------------------------------------------------------------------- /packages/next/src/editable/main.scss: -------------------------------------------------------------------------------- ```scss @import '~@alifd/next/lib/core/index-noreset.scss'; $editable-prefix-cls: '#{$css-prefix}formily-editable'; $editable-popover-prefix-cls: '#{$css-prefix}formily-editable-popover'; .#{$editable-prefix-cls} { cursor: pointer; display: inline-flex; align-items: center; .#{$css-prefix}form-text { .#{$css-prefix}tag { transition: none !important; } .#{$css-prefix}tag:last-child { margin-right: 0 !important; } } &-content { display: flex; align-items: center; > * { margin-right: 3px; &:last-child { margin-right: 0; } } } .#{$editable-prefix-cls}-edit-btn, .#{$editable-prefix-cls}-close-btn { transition: all 0.25s ease-in-out; color: #aaa; font-size: 12px; &:hover { color: $color-text1-1; } } .#{$css-prefix}form-text { display: flex; align-items: center; } .#{$editable-prefix-cls}-preview { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; word-break: break-all; max-width: 100px; display: block; } } .#{$editable-prefix-cls} { &-trigger { cursor: pointer; display: inline-flex !important; outline: none; } &-edit-btn { color: #aaa; } } ``` -------------------------------------------------------------------------------- /packages/benchmark/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/benchmark", "version": "2.3.7", "license": "MIT", "private": true, "repository": { "type": "git", "url": "git+https://github.com/alibaba/formily.git" }, "types": "esm/index.d.ts", "bugs": { "url": "https://github.com/alibaba/formily/issues" }, "homepage": "https://github.com/alibaba/formily#readme", "engines": { "npm": ">=3.0.0" }, "scripts": { "start": "webpack-dev-server --config webpack.dev.ts" }, "devDependencies": { "css-loader": "^5.0.0", "file-loader": "^5.0.2", "html-webpack-plugin": "^3.2.0", "mini-css-extract-plugin": "^1.6.0", "postcss": "^8.4.31", "postcss-less": "^4.0.0", "postcss-loader": "^3.x", "raw-loader": "^4.0.0", "style-loader": "^1.1.3", "ts-loader": "^7.0.4", "webpack": "^4.41.5", "webpack-bundle-analyzer": "^3.9.0", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.10.1" }, "resolutions": { "react": "next", "react-dom": "next", "react-is": "next" }, "dependencies": { "@formily/reactive": "2.3.7", "@formily/reactive-react": "2.3.7", "react": "next", "react-dom": "next", "react-is": "next" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" } ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/extension/devpanel.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useEffect, useState } from 'react' import ReactDOM from 'react-dom' import App from '../app' const backgroundPageConnection = chrome.runtime.connect({ name: '@formily-devtools-panel-script', }) backgroundPageConnection.postMessage({ name: 'init', tabId: chrome.devtools.inspectedWindow.tabId, }) chrome.devtools.inspectedWindow.eval( 'window.__FORMILY_DEV_TOOLS_HOOK__.openDevtools()' ) const Devtools = () => { const [state, setState] = useState([]) useEffect(() => { let store = {} const update = () => { setState( Object.keys(store).map((key) => { return store[key] }) ) } chrome.devtools.inspectedWindow.eval( 'window.__FORMILY_DEV_TOOLS_HOOK__.update()' ) backgroundPageConnection.onMessage.addListener(({ type, id, graph }) => { if (type === 'init') { store = {} chrome.devtools.inspectedWindow.eval( 'window.__FORMILY_DEV_TOOLS_HOOK__.openDevtools()' ) } else if (type !== 'uninstall') { store[id] = JSON.parse(graph) } else { delete store[id] } update() }) }, []) return <App dataSource={state} /> } ReactDOM.render(<Devtools />, document.getElementById('root')) ``` -------------------------------------------------------------------------------- /packages/reactive/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/reactive", "version": "2.3.7", "license": "MIT", "main": "lib", "module": "esm", "umd:main": "dist/formily.reactive.umd.production.js", "unpkg": "dist/formily.reactive.umd.production.js", "jsdelivr": "dist/formily.reactive.umd.production.js", "jsnext:main": "esm", "repository": { "type": "git", "url": "git+https://github.com/alibaba/formily.git" }, "types": "esm/index.d.ts", "bugs": { "url": "https://github.com/alibaba/formily/issues" }, "homepage": "https://github.com/alibaba/formily#readme", "engines": { "npm": ">=3.0.0" }, "scripts": { "start": "dumi dev", "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", "build:cjs": "tsc --project tsconfig.build.json", "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", "build:umd": "rollup --config", "build:docs": "dumi build", "benchmark": "ts-node ./benchmark" }, "devDependencies": { "@vue/reactivity": "^3.0.11", "benny": "^3.6.15", "dumi": "^1.1.0-rc.8", "lodash": "^4.17.21", "mobx": "^6.3.0" }, "publishConfig": { "access": "public" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" } ``` -------------------------------------------------------------------------------- /packages/reactive/src/annotations/ref.ts: -------------------------------------------------------------------------------- ```typescript import { ObModelSymbol } from '../environment' import { createAnnotation } from '../internals' import { buildDataTree } from '../tree' import { bindTargetKeyWithCurrentReaction, runReactionsFromTargetKey, } from '../reaction' export interface IRef { <T>(target: T): { value: T } } export const ref: IRef = createAnnotation(({ target, key, value }) => { const store = { value: target ? target[key] : value, } const proxy = {} const context = target ? target : store const property = target ? key : 'value' function get() { bindTargetKeyWithCurrentReaction({ target: context, key: property, type: 'get', }) return store.value } function set(value: any) { const oldValue = store.value store.value = value if (oldValue !== value) { runReactionsFromTargetKey({ target: context, key: property, type: 'set', oldValue, value, }) } } if (target) { Object.defineProperty(target, key, { get, set, enumerable: true, }) return target } else { Object.defineProperty(proxy, 'value', { set, get, }) buildDataTree(target, key, store) proxy[ObModelSymbol] = store } return proxy }) ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/graph.spec.ts: -------------------------------------------------------------------------------- ```typescript import { createForm } from '../' import { isVoidField } from '../shared/checkers' import { attach } from './shared' test('getGraph/setGraph', () => { const form = attach(createForm()) attach( form.createField({ name: 'normal', }) ) attach( form.createArrayField({ name: 'array', }) ) attach( form.createObjectField({ name: 'object', }) ) attach( form.createVoidField({ name: 'void', }) ) form.query('normal').take((field) => { if (isVoidField(field)) return field.selfErrors = ['error'] }) const graph = form.getFormGraph() form.clearFormGraph() form.setFormGraph(graph) const graph2 = form.getFormGraph() expect(graph).toEqual(graph2) form.setFormGraph({ object: { value: 123, }, }) expect(form.query('object').get('value')).toEqual(123) }) test('clearFormGraph', () => { const form = attach(createForm()) attach( form.createField({ name: 'normal', }) ) attach( form.createArrayField({ name: 'array', }) ) attach( form.createObjectField({ name: 'object', }) ) form.clearFormGraph('normal') expect(form.fields['normal']).toBeUndefined() expect(form.fields['array']).not.toBeUndefined() }) ``` -------------------------------------------------------------------------------- /packages/element/src/el-form/index.ts: -------------------------------------------------------------------------------- ```typescript import { Form } from '@formily/core' import { FormProvider as _FormProvider, createForm } from '@formily/vue' import type { Form as _ElFormProps } from 'element-ui' import type { FunctionalComponentOptions, Component } from 'vue' import { Form as ElFormComponent } from 'element-ui' const FormProvider = _FormProvider as unknown as Component export type ElFormProps = _ElFormProps & { form?: Form component: Component onAutoSubmit?: (values: any) => any } export const ElForm: FunctionalComponentOptions<ElFormProps> = { functional: true, render(h, context) { const { form = createForm({}), component = ElFormComponent, onAutoSubmit = context.listeners?.autoSubmit, ...props } = context.props const submitHandler = ( Array.isArray(onAutoSubmit) ? onAutoSubmit[0] : onAutoSubmit ) as (values: any) => any return h(FormProvider, { props: { form } }, [ h( component, { ...context.data, props, nativeOn: { submit: (e: Event) => { e?.stopPropagation?.() e?.preventDefault?.() form.submit(submitHandler) }, }, }, context.children ), ]) }, } export default ElForm ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/hooks/use-field-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaObjectField name="custom" x-component="Custom" :x-component-props="{ schema: { type: 'object', properties: { input: { type: 'string', 'x-component': 'Custom', }, }, }, }" /> </SchemaField> </FormProvider> </template> <script> import { defineComponent, h } from '@vue/composition-api' import { createForm } from '@formily/core' import { FormProvider, createSchemaField, useFieldSchema } from '@formily/vue' import 'ant-design-vue/dist/antd.css' const Custom = defineComponent({ setup() { const schemaRef = useFieldSchema() return () => { const schema = schemaRef.value return h( 'div', { style: { whiteSpace: 'pre' }, }, [JSON.stringify(schema.toJSON(), null, 4)] ) } }, }) const { SchemaField, SchemaObjectField } = createSchemaField({ components: { Custom, }, }) export default { components: { FormProvider, SchemaField, SchemaObjectField }, data() { const form = createForm({ validateFirst: true }) return { form, } }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form" :label-col="6" :wrapper-col="10" @autoSubmit="log" @autoSubmitFailed="log" > <SchemaField> <SchemaStringField name="input" title="输入框" x-decorator="FormItem" x-component="Input" :required="true" /> <SchemaStringField name="select" title="选择框" x-decorator="FormItem" x-component="Select" :enum="[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]" :required="true" /> </SchemaField> <FormButtonGroup alignFormItem> <Submit>提交</Submit> </FormButtonGroup> </Form> </template> <script> import { createForm } from '@formily/core' import { createSchemaField } from '@formily/vue' import { Form, Input, Select, FormItem, FormButtonGroup, Submit, } from '@formily/element' const form = createForm() const fields = createSchemaField({ components: { Input, Select, FormItem } }) export default { components: { FormButtonGroup, Submit, Form, ...fields }, data() { return { form, } }, methods: { log(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/next/src/time-picker2/index.tsx: -------------------------------------------------------------------------------- ```typescript import moment from 'moment' import { connect, mapProps, mapReadPretty } from '@formily/react' import { TimePicker2 as NextTimePicker2 } from '@alifd/next' import { TimePickerProps, RangePickerProps, } from '@alifd/next/types/time-picker2' import { PreviewText } from '../preview-text' import { formatMomentValue, momentable, mapSize, mapStatus, } from '../__builtins__' type ComposedTimePicker = React.FC<React.PropsWithChildren<TimePickerProps>> & { RangePicker?: React.FC<React.PropsWithChildren<RangePickerProps>> } const mapTimeFormat = function () { return (props: any) => { const format = props['format'] || 'HH:mm:ss' const onChange = props.onChange return { ...props, format, value: momentable(props.value, format), onChange: (value: moment.Moment | moment.Moment[]) => { if (onChange) { onChange(formatMomentValue(value, format)) } }, } } } export const TimePicker2: ComposedTimePicker = connect( NextTimePicker2, mapProps(mapTimeFormat(), mapSize, mapStatus), mapReadPretty(PreviewText.TimePicker2) ) TimePicker2.RangePicker = connect( NextTimePicker2.RangePicker, mapProps(mapTimeFormat(), mapSize, mapStatus), mapReadPretty(PreviewText.TimeRangePicker2) ) export default TimePicker2 ``` -------------------------------------------------------------------------------- /packages/shared/src/subscribable.ts: -------------------------------------------------------------------------------- ```typescript import { isFn, Subscriber, Subscription } from './checkers' import { each } from './array' export class Subscribable<Payload = any> { subscribers: { index?: number [key: number]: Subscriber<Payload> } = { index: 0, } subscription: Subscription<Payload> subscribe = (callback?: Subscriber<Payload>): number => { if (isFn(callback)) { const index: number = this.subscribers.index + 1 this.subscribers[index] = callback this.subscribers.index++ return index } } unsubscribe = (index?: number) => { if (this.subscribers[index]) { delete this.subscribers[index] } else if (!index) { this.subscribers = { index: 0, } } } notify = (payload?: Payload, silent?: boolean) => { if (this.subscription) { if (this.subscription && isFn(this.subscription.notify)) { if (this.subscription.notify.call(this, payload) === false) { return } } } if (silent) return const filter = (payload: Payload) => { if (this.subscription && isFn(this.subscription.filter)) { return this.subscription.filter.call(this, payload) } return payload } each(this.subscribers, (callback: any) => { if (isFn(callback)) callback(filter(payload)) }) } } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/index.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <Space> <Field name="price" title="价格" :initialValue="5.2" :decorator="[FormItem]" :component="[ InputNumber, { placeholder: '请输入', style: { width: 100, }, }, ]" /> <FormItem>×</FormItem> <Field name="count" title="数量" :initialValue="100" :decorator="[FormItem]" :component="[ InputNumber, { placeholder: '请输入', style: { width: 100, }, }, ]" /> <FormConsumer> <template #default="{ form }"> <FormItem> = {{ `${form.values.price * form.values.count} 元` }}</FormItem > </template> </FormConsumer> </Space> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { InputNumber, FormItem, Space } from '@formily/element' import { FormProvider, FormConsumer, Field } from '@formily/vue' const form = createForm() export default { components: { FormProvider, FormConsumer, Field, FormItem, Space }, data() { return { form, InputNumber, FormItem, } }, } </script> ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/markObservable.md: -------------------------------------------------------------------------------- ```markdown # markObservable ## Description 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 ## Signature ```ts interface markObservable<T> { (target: T): T } ``` ## Example ```ts import { observable, autorun, markObservable } from '@formily/reactive' class A { property = '' toJSON() {} } const a = observable(new A()) autorun(() => { console.log(a.property) //will not be triggered when the property changes, because there is a toJSON method in the A instance }) a.property = 123 //-------------------------------------------- const b = observable(markObservable(new A())) //instance-level mark, only valid for the current instance autorun(() => { console.log(b.property) //Can be triggered when the property changes, because it has been marked as observable }) b.property = 123 //-------------------------------------------- markObservable(A) //Class-level mark, then all instances will take effect const c = observable(new A()) autorun(() => { console.log(c.property) //Can be triggered when the property changes, because it has been marked as observable }) c.property = 123 ``` ``` -------------------------------------------------------------------------------- /packages/reactive/src/model.ts: -------------------------------------------------------------------------------- ```typescript import { isFn } from './checkers' import { buildDataTree } from './tree' import { observable } from './observable' import { getObservableMaker } from './internals' import { isObservable, isAnnotation, isSupportObservable } from './externals' import { Annotations } from './types' import { action } from './action' import { ObModelSymbol } from './environment' export function define<Target extends object = any>( target: Target, annotations?: Annotations<Target> ): Target { if (isObservable(target)) return target if (!isSupportObservable(target)) return target target[ObModelSymbol] = target buildDataTree(undefined, undefined, target) for (const key in annotations) { const annotation = annotations[key] if (isAnnotation(annotation)) { getObservableMaker(annotation)({ target, key, }) } } return target } export function model<Target extends object = any>(target: Target): Target { const annotations = Object.keys(target || {}).reduce((buf, key) => { const descriptor = Object.getOwnPropertyDescriptor(target, key) if (descriptor && descriptor.get) { buf[key] = observable.computed } else if (isFn(target[key])) { buf[key] = action } else { buf[key] = observable } return buf }, {}) return define(target, annotations) } ``` -------------------------------------------------------------------------------- /packages/core/src/shared/externals.ts: -------------------------------------------------------------------------------- ```typescript import { FormPath } from '@formily/shared' import { Form } from '../models' import { IFormProps } from '../types' import { getValidateLocaleIOSCode, getLocaleByPath, setValidateLanguage, registerValidateFormats, registerValidateLocale, registerValidateMessageTemplateEngine, registerValidateRules, } from '@formily/validator' import { createEffectHook, createEffectContext, useEffectForm, } from './effective' import { isArrayField, isArrayFieldState, isDataField, isDataFieldState, isField, isFieldState, isForm, isFormState, isGeneralField, isGeneralFieldState, isObjectField, isObjectFieldState, isQuery, isVoidField, isVoidFieldState, } from './checkers' const createForm = <T extends object = any>(options?: IFormProps<T>) => { return new Form(options) } export { FormPath, createForm, isArrayField, isArrayFieldState, isDataField, isDataFieldState, isField, isFieldState, isForm, isFormState, isGeneralField, isGeneralFieldState, isObjectField, isObjectFieldState, isQuery, isVoidField, isVoidFieldState, getValidateLocaleIOSCode, getLocaleByPath, setValidateLanguage, registerValidateFormats, registerValidateLocale, registerValidateMessageTemplateEngine, registerValidateRules, createEffectHook, createEffectContext, useEffectForm, } ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/shared/map-read-pretty.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <Form layout="vertical"> <Field name="name" title="Name" required initialValue="Hello world" :decorator="[FormItem]" :component="[Input, { placeholder: 'Please Input' }]" /> </Form> </FormProvider> </template> <script> import { Form, Input as AntdInput } from 'ant-design-vue' import { createForm, setValidateLanguage } from '@formily/core' import { FormProvider, Field, connect, mapProps, mapReadPretty, } from '@formily/vue' import 'ant-design-vue/dist/antd.css' setValidateLanguage('en') const FormItem = connect( Form.Item, mapProps( { title: 'label', description: 'extra', required: true, validateStatus: true, }, (props, field) => { return { ...props, help: field.selfErrors?.length ? field.selfErrors : undefined, } } ) ) const Input = connect( AntdInput, mapReadPretty({ props: ['value'], // you need import "h" from "vue" in vue3 render(h) { return h('div', [this.value]) }, }) ) export default { components: { FormProvider, Field, Form, }, data() { const form = createForm({ validateFirst: true, readPretty: true }) return { FormItem, Input, form, } }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-layout/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField :schema="schema" /> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { createSchemaField, FormProvider } from '@formily/vue' import { FormItem, FormLayout, Input, Select, Submit } from '@formily/element' const schema = { type: 'object', properties: { layout: { type: 'void', 'x-component': 'FormLayout', 'x-component-props': { labelCol: 6, wrapperCol: 10, layout: 'vertical', }, properties: { input: { type: 'string', title: '输入框', required: true, 'x-decorator': 'FormItem', 'x-decorator-props': { tooltip: '123', }, 'x-component': 'Input', }, select: { type: 'string', title: '选择框', required: true, 'x-decorator': 'FormItem', 'x-component': 'Select', }, }, }, }, } const form = createForm() const fields = createSchemaField({ components: { FormLayout, FormItem, Input, Select, }, }) export default { components: { FormProvider, ...fields, Submit }, data() { return { form, schema, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/react/src/__tests__/form.spec.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { render } from '@testing-library/react' import { createForm } from '@formily/core' import { FormProvider, ObjectField, VoidField, Field } from '../' import { FormConsumer } from '../components' import { useParentForm } from '../hooks' test('render form', () => { const form = createForm() render( <FormProvider form={form}> <FormConsumer>{(form) => `${form.mounted}`}</FormConsumer> <FormConsumer /> </FormProvider> ) expect(form.mounted).toBeTruthy() }) const DisplayParentForm: React.FC< React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>> > = (props) => { return <div {...props}>{useParentForm()?.displayName}</div> } test('useParentForm', () => { const form = createForm() const { queryByTestId } = render( <FormProvider form={form}> <ObjectField name="aa"> <Field name="bb"> <DisplayParentForm data-testid="111" /> </Field> </ObjectField> <VoidField name="cc"> <Field name="dd"> <DisplayParentForm data-testid="222" /> </Field> </VoidField> <DisplayParentForm data-testid="333" /> </FormProvider> ) expect(queryByTestId('111').textContent).toBe('ObjectField') expect(queryByTestId('222').textContent).toBe('Form') expect(queryByTestId('333').textContent).toBe('Form') }) ``` -------------------------------------------------------------------------------- /packages/reactive-react/src/observer.ts: -------------------------------------------------------------------------------- ```typescript import React, { forwardRef, memo, Fragment } from 'react' import hoistNonReactStatics from 'hoist-non-react-statics' import { useObserver } from './hooks/useObserver' import { IObserverOptions, IObserverProps, ReactFC } from './types' export function observer< P, Options extends IObserverOptions = IObserverOptions >( component: ReactFC<P>, options?: Options ): React.MemoExoticComponent< ReactFC< Options extends { forwardRef: true } ? P & { ref?: 'ref' extends keyof P ? P['ref'] : React.RefAttributes<any> } : React.PropsWithoutRef<P> > > { const realOptions = { forwardRef: false, ...options, } const wrappedComponent = realOptions.forwardRef ? forwardRef((props: any, ref: any) => { return useObserver(() => component({ ...props, ref }), realOptions) }) : (props: any) => { return useObserver(() => component(props), realOptions) } const memoComponent = memo(wrappedComponent) hoistNonReactStatics(memoComponent, component) if (realOptions.displayName) { memoComponent.displayName = realOptions.displayName } return memoComponent } export const Observer = observer((props: IObserverProps) => { const children = typeof props.children === 'function' ? props.children() : props.children return React.createElement(Fragment, {}, children) }) ``` -------------------------------------------------------------------------------- /packages/vue/src/components/Field.ts: -------------------------------------------------------------------------------- ```typescript import { isVue2, h as _h } from 'vue-demi' import ReactiveField from './ReactiveField' import { getRawComponent } from '../utils/getRawComponent' import type { IFieldProps, DefineComponent } from '../types' import { getFieldProps } from '../utils/getFieldProps' let Field: DefineComponent<IFieldProps> /* istanbul ignore else */ if (isVue2) { Field = { functional: true, name: 'Field', props: getFieldProps(), render(h, context) { const props = context.props as IFieldProps const attrs = context.data.attrs const componentData = { ...context.data, props: { fieldType: 'Field', fieldProps: { ...attrs, ...props, ...getRawComponent(props), }, }, } return _h(ReactiveField, componentData, context.children) }, } as unknown as DefineComponent<IFieldProps> } else { Field = { name: 'Field', props: getFieldProps(), setup(props: IFieldProps, context) { return () => { const componentData = { fieldType: 'Field', fieldProps: { ...props, ...getRawComponent(props), }, } as Record<string, unknown> return _h(ReactiveField, componentData, context.slots) } }, } as unknown as DefineComponent<IFieldProps> } export default Field ``` -------------------------------------------------------------------------------- /packages/next/src/array-base/main.scss: -------------------------------------------------------------------------------- ```scss @import '~@alifd/next/lib/core/index-noreset.scss'; $array-base-prefix-cls: '#{$css-prefix}formily-array-base'; .#{$array-base-prefix-cls}-remove, .#{$array-base-prefix-cls}-copy { transition: all 0.25s ease-in-out; color: $color-text1-3; font-size: 16px; &:hover { color: $color-text1-1; } &-disabled { color: $color-text1-1; cursor: not-allowed !important; &:hover { color: $color-text1-1; } } .#{$css-prefix}formily-icon { font-size: 16px; } } .#{$array-base-prefix-cls}-addition { transition: all 0.25s ease-in-out; } .#{$array-base-prefix-cls}-move-down { transition: all 0.25s ease-in-out; color: $color-text1-3; font-size: 16px; &:hover { color: $color-text1-1; } &-disabled { color: $color-text1-1; cursor: not-allowed !important; &:hover { color: $color-text1-1; } } .#{$css-prefix}formily-icon { font-size: 16px; } } .#{$array-base-prefix-cls}-move-up { transition: all 0.25s ease-in-out; color: $color-text1-3; font-size: 16px; &:hover { color: $color-text1-1; } &-disabled { color: $color-text1-1; cursor: not-allowed !important; &:hover { color: $color-text1-1; } } .#{$css-prefix}formily-icon { font-size: 16px; } } .#{$array-base-prefix-cls}-sort-handle { cursor: move; color: #888 !important; } ``` -------------------------------------------------------------------------------- /packages/core/src/models/LifeCycle.ts: -------------------------------------------------------------------------------- ```typescript import { isFn, isStr, each } from '@formily/shared' import { LifeCycleHandler, LifeCyclePayload } from '../types' type LifeCycleParams<Payload> = Array< | string | LifeCycleHandler<Payload> | { [key: string]: LifeCycleHandler<Payload> } > export class LifeCycle<Payload = any> { private listener: LifeCyclePayload<Payload> constructor(...params: LifeCycleParams<Payload>) { this.listener = this.buildListener(params) } buildListener = (params: any[]) => { return function (payload: { type: string; payload: Payload }, ctx: any) { for (let index = 0; index < params.length; index++) { let item = params[index] if (isFn(item)) { item.call(this, payload, ctx) } else if (isStr(item) && isFn(params[index + 1])) { if (item === payload.type) { params[index + 1].call(this, payload.payload, ctx) } index++ } else { each<any, any>(item, (handler, type) => { if (isFn(handler) && isStr(type)) { if (type === payload.type) { handler.call(this, payload.payload, ctx) return false } } }) } } } } notify = <Payload>(type: any, payload?: Payload, ctx?: any) => { if (isStr(type)) { this.listener.call(ctx, { type, payload }, ctx) } } } ``` -------------------------------------------------------------------------------- /packages/path/src/__tests__/share.spec.ts: -------------------------------------------------------------------------------- ```typescript import { isAssignable, isEqual } from '../shared' test('isAssignable', () => { expect(isAssignable({})).toBeTruthy() expect(isAssignable(() => {})).toBeTruthy() expect(isAssignable(1)).toBeFalsy() expect(isAssignable('str')).toBeFalsy() }) test('isEqual', () => { const sameObj = {} const sameArray = [] expect(isEqual('string', 'string')).toBeTruthy() expect(isEqual(123, 123)).toBeTruthy() expect(isEqual(undefined, undefined)).toBeTruthy() expect(isEqual(null, null)).toBeTruthy() expect(isEqual(sameObj, sameObj)).toBeTruthy() expect(isEqual(sameArray, sameArray)).toBeTruthy() expect(isEqual([1, '123'], [1, '123'])).toBeTruthy() expect( isEqual([1, '123', { a: 1, b: 2 }], [1, '123', { a: 1, b: 2 }]) ).toBeTruthy() expect( isEqual([1, '123', { a: 1, b: 2 }], [1, '123', { a: 1, b: 3 }]) ).toBeFalsy() expect(isEqual([1, '123'], [1, '234'])).toBeFalsy() expect(isEqual([], [1])).toBeFalsy() expect(isEqual([], {})).toBeFalsy() expect(isEqual({ a: [1, 2, 3] }, { a: [1, 2, 3] })).toBeTruthy() expect(isEqual({ a: [1, 2, 3] }, { a: [1, 2, 4] })).toBeFalsy() expect(isEqual({ a: 1 }, { a: 11 })).toBeFalsy() expect(isEqual({ a: 1 }, { a: 1, b: 2 })).toBeFalsy() const b = { age: '234' } // @ts-ignore Object.prototype.name = '123' expect(isEqual({ name: '123' }, b)).toBeFalsy() expect(isEqual(NaN, NaN)).toBeTruthy() }) ``` -------------------------------------------------------------------------------- /packages/reactive-vue/src/hooks/useObserver.ts: -------------------------------------------------------------------------------- ```typescript import { Tracker } from '@formily/reactive' import { getCurrentInstance, onBeforeUnmount, isVue3 } from 'vue-demi' import { IObserverOptions } from '../types' /* istanbul ignore next */ export const useObserver = (options?: IObserverOptions) => { if (isVue3) { const vm = getCurrentInstance() let tracker: Tracker = null const disposeTracker = () => { if (tracker) { tracker.dispose() tracker = null } } const vmUpdate = () => { vm?.proxy?.$forceUpdate() } onBeforeUnmount(disposeTracker) Object.defineProperty(vm, 'effect', { get() { // https://github.com/alibaba/formily/issues/2655 return vm['_updateEffect'] || {} }, set(newValue) { vm['_updateEffectRun'] = newValue.run disposeTracker() const newTracker = () => { tracker = new Tracker(() => { if (options?.scheduler && typeof options.scheduler === 'function') { options.scheduler(vmUpdate) } else { vmUpdate() } }) } const update = function () { let refn = null tracker?.track(() => { refn = vm['_updateEffectRun'].call(newValue) }) return refn } newTracker() newValue.run = update vm['_updateEffect'] = newValue }, }) } } ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/heart.spec.ts: -------------------------------------------------------------------------------- ```typescript import { Heart, LifeCycle } from '../models' test('buildLifecycles', () => { const heart = new Heart({ lifecycles: [{} as any, [{}], 123], }) expect(heart.lifecycles.length).toEqual(0) }) test('clear heart', () => { const handler = jest.fn() const heart = new Heart({ lifecycles: [new LifeCycle('event', handler)], }) heart.publish('event') expect(handler).toBeCalledTimes(1) heart.clear() heart.publish('event') expect(handler).toBeCalledTimes(1) heart.publish({}) }) test('set lifecycles', () => { const handler = jest.fn() const heart = new Heart() heart.setLifeCycles([new LifeCycle('event', handler)]) heart.publish('event') expect(handler).toBeCalledTimes(1) heart.setLifeCycles() }) test('add/remove lifecycle', () => { const handler = jest.fn() const heart = new Heart() heart.addLifeCycles('xxx', [new LifeCycle('event', handler)]) heart.addLifeCycles('yyy') heart.publish('event') expect(handler).toBeCalledTimes(1) heart.removeLifeCycles('xxx') heart.publish('event') expect(handler).toBeCalledTimes(1) }) test('add/clear lifecycle', () => { const handler = jest.fn() const heart = new Heart() heart.addLifeCycles('xxx', [new LifeCycle('event', handler)]) heart.addLifeCycles('yyy') heart.publish('event') expect(handler).toBeCalledTimes(1) heart.clear() heart.publish('event') expect(handler).toBeCalledTimes(1) }) ``` -------------------------------------------------------------------------------- /packages/reactive/src/array.ts: -------------------------------------------------------------------------------- ```typescript export const toArray = (value: any) => { return Array.isArray(value) ? value : value !== undefined && value !== null ? [value] : [] } export class ArraySet<T> { value: T[] forEachIndex = 0 constructor(value: T[] = []) { this.value = value } add(item: T) { if (!this.has(item)) { this.value.push(item) } } has(item: T) { return this.value.indexOf(item) > -1 } delete(item: T) { const len = this.value.length if (len === 0) return if (len === 1 && this.value[0] === item) { this.value = [] return } const findIndex = this.value.indexOf(item) if (findIndex > -1) { this.value.splice(findIndex, 1) if (findIndex <= this.forEachIndex) { this.forEachIndex -= 1 } } } forEach(callback: (value: T) => void) { if (this.value.length === 0) return this.forEachIndex = 0 for (; this.forEachIndex < this.value.length; this.forEachIndex++) { callback(this.value[this.forEachIndex]) } } batchDelete(callback: (value: T) => void) { if (this.value.length === 0) return this.forEachIndex = 0 for (; this.forEachIndex < this.value.length; this.forEachIndex++) { const value = this.value[this.forEachIndex] this.value.splice(this.forEachIndex, 1) this.forEachIndex-- callback(value) } } clear() { this.value.length = 0 } } ``` -------------------------------------------------------------------------------- /packages/json-schema/src/__tests__/traverse.spec.ts: -------------------------------------------------------------------------------- ```typescript import { traverse, traverseSchema } from '../shared' import { FormPath } from '@formily/shared' test('traverseSchema', () => { const visited = [] const omitted = [] traverseSchema( { type: 'string', title: '{{aa}}', required: true, 'x-validator': 'phone', 'x-compile-omitted': ['title'], default: { input: 123, }, }, (value, path, omitCompile) => { if (omitCompile) { omitted.push(value) } else { visited.push(path) } } ) expect(visited).toEqual([ ['x-validator'], ['type'], ['required'], ['default'], ]) expect(omitted).toEqual(['{{aa}}']) }) test('traverse circular reference', () => { // eslint-disable-next-line var a = { dd: { mm: null, }, bb: { cc: { dd: 123, }, }, kk: { toJS() {}, }, } a.dd.mm = a traverse(a, () => {}) traverseSchema(a as any, () => {}) }) test('traverse none circular reference', () => { // eslint-disable-next-line var dd = { mm: null, } let a = { dd, bb: { dd, }, } const paths = [] traverse(a, (value, path) => { paths.push(path) }) traverseSchema(a, () => {}) expect( paths.some((path) => FormPath.parse(path).includes('dd.mm')) ).toBeTruthy() expect( paths.some((path) => FormPath.parse(path).includes('bb.dd.mm')) ).toBeTruthy() }) ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/observable.spec.ts: -------------------------------------------------------------------------------- ```typescript import { observable } from '../' import { contains } from '../externals' test('array mutation', () => { const arr = observable([1, 2, 3, 4]) arr.splice(2, 1) expect(arr).toEqual([1, 2, 4]) }) test('observable contains', () => { const subElement = { cc: 333 } const element = { aa: subElement } const arr = observable<any[]>([element, 2, 3, 4]) expect(contains(arr, arr[0])).toBe(true) expect(contains(arr, arr[0].aa)).toBe(true) expect(contains(arr, element)).toBe(true) expect(contains(arr, subElement)).toBe(true) expect(contains(element, subElement)).toBe(true) expect(contains(element, arr[0].aa)).toBe(true) expect(contains(arr[0], subElement)).toBe(true) const obj = observable<any>({}) const other = { bb: 321 } expect(contains(obj, obj.other)).toBe(false) obj.other = other obj.arr = arr expect(contains(obj, obj.other)).toBe(true) expect(contains(obj, other)).toBe(true) expect(contains(obj, obj.arr)).toBe(true) expect(contains(obj, arr)).toBe(true) }) test('observable __proto__', () => { const observableArr = observable([] as any[]) // @ts-ignore observableArr.__proto__ = Object.create(Array.prototype) observableArr[0] = {} expect(observableArr).toEqual([{}]) const observableObj = observable({} as any) // @ts-ignore observableObj.__proto__ = Object.create(Object.prototype) observableObj.aa = {} expect(observableObj).toEqual({ aa: {} }) }) ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/object-field.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <ObjectField name="object"> <template #default="{ field }"> <div v-for="key in Object.keys(field.value || {})" :key="key" :style="{ marginBottom: '10px' }" > <Space> <Field :name="key" :component="[Input, { placeholder: key }]" /> <Button @click="field.removeProperty(key)"> Remove </Button> </Space> </div> <Space> <Field name="propertyName" basePath="" required :component="[Input, { placeholder: 'Property Name' }]" /> <Button @click="addPropertyToField(field)"> Add </Button> </Space> </template> </ObjectField> </FormProvider> </template> <script> import { Input, Space, Button } from 'ant-design-vue' import { createForm } from '@formily/core' import { FormProvider, ObjectField, Field } from '@formily/vue' import 'ant-design-vue/dist/antd.css' export default { components: { FormProvider, ObjectField, Field, Space, Button }, data() { return { Input, form: createForm(), } }, methods: { addPropertyToField(field) { const name = this.form.values.propertyName if (name && !this.form.existValuesIn(`object.${name}`)) { field.addProperty(name, '') this.form.deleteValuesIn('propertyName') } }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/vue/src/components/VoidField.ts: -------------------------------------------------------------------------------- ```typescript import { isVue2, h as _h } from 'vue-demi' import ReactiveField from './ReactiveField' import { getRawComponent } from '../utils/getRawComponent' import type { IVoidFieldProps, DefineComponent } from '../types' import { getVoidFieldProps } from '../utils/getFieldProps' let VoidField: DefineComponent<IVoidFieldProps> /* istanbul ignore else */ if (isVue2) { VoidField = { functional: true, name: 'VoidField', props: getVoidFieldProps(), render(h, context) { const props = context.props as IVoidFieldProps const attrs = context.data.attrs const componentData = { ...context.data, props: { fieldType: 'VoidField', fieldProps: { ...attrs, ...props, ...getRawComponent(props), }, }, } return _h(ReactiveField, componentData, context.children) }, } as unknown as DefineComponent<IVoidFieldProps> } else { VoidField = { name: 'VoidField', props: getVoidFieldProps(), setup(props: IVoidFieldProps, context) { return () => { const componentData = { fieldType: 'VoidField', fieldProps: { ...props, ...getRawComponent(props), }, } as Record<string, unknown> return _h(ReactiveField, componentData, context.slots) } }, } as unknown as DefineComponent<IVoidFieldProps> } export default VoidField ``` -------------------------------------------------------------------------------- /packages/vue/src/components/ArrayField.ts: -------------------------------------------------------------------------------- ```typescript import { isVue2, h as _h } from 'vue-demi' import ReactiveField from './ReactiveField' import { getRawComponent } from '../utils/getRawComponent' import type { IArrayFieldProps, DefineComponent } from '../types' import { getFieldProps } from '../utils/getFieldProps' let ArrayField: DefineComponent<IArrayFieldProps> /* istanbul ignore else */ if (isVue2) { ArrayField = { functional: true, name: 'ArrayField', props: getFieldProps(), render(h, context) { const props = context.props as IArrayFieldProps const attrs = context.data.attrs const componentData = { ...context.data, props: { fieldType: 'ArrayField', fieldProps: { ...attrs, ...props, ...getRawComponent(props), }, }, } return _h(ReactiveField, componentData, context.children) }, } as unknown as DefineComponent<IArrayFieldProps> } else { ArrayField = { name: 'ArrayField', props: getFieldProps(), setup(props: IArrayFieldProps, context) { return () => { const componentData = { fieldType: 'ArrayField', fieldProps: { ...props, ...getRawComponent(props), }, } as Record<string, unknown> return _h(ReactiveField, componentData, context.slots) } }, } as unknown as DefineComponent<IArrayFieldProps> } export default ArrayField ``` -------------------------------------------------------------------------------- /packages/core/src/__tests__/lifecycle.spec.ts: -------------------------------------------------------------------------------- ```typescript import { LifeCycle } from '../models' test('create lifecycle', () => { const handler1 = jest.fn() const lifecycle1 = new LifeCycle(handler1) lifecycle1.notify('event1') expect(handler1).toBeCalledTimes(1) expect(handler1).toBeCalledWith( { type: 'event1', payload: undefined, }, undefined ) lifecycle1.notify('event11', 'payload1') expect(handler1).toBeCalledTimes(2) expect(handler1).toBeCalledWith( { type: 'event11', payload: 'payload1', }, undefined ) const context: any = {} lifecycle1.notify('event12', 'payload11', context) expect(handler1).toBeCalledTimes(3) expect(handler1).toBeCalledWith( { type: 'event12', payload: 'payload11', }, context ) const handler2 = jest.fn() const lifecycle2 = new LifeCycle('event2', handler2) lifecycle2.notify('event1') expect(handler2).not.toBeCalled() lifecycle2.notify('event2') expect(handler2).toBeCalledTimes(1) const handler31 = jest.fn() const handler32 = jest.fn() const lifecycle3 = new LifeCycle({ event31: handler31, event32: handler32, }) lifecycle3.notify('event3') expect(handler31).not.toBeCalled() expect(handler32).not.toBeCalled() lifecycle3.notify('event31') expect(handler31).toBeCalledTimes(1) expect(handler32).not.toBeCalled() lifecycle3.notify('event32') expect(handler31).toBeCalledTimes(1) expect(handler32).toBeCalledTimes(1) }) ``` -------------------------------------------------------------------------------- /packages/vue/src/components/ObjectField.ts: -------------------------------------------------------------------------------- ```typescript import { isVue2, h as _h } from 'vue-demi' import ReactiveField from './ReactiveField' import { getRawComponent } from '../utils/getRawComponent' import type { IObjectFieldProps, DefineComponent } from '../types' import { getFieldProps } from '../utils/getFieldProps' let ObjectField: DefineComponent<IObjectFieldProps> /* istanbul ignore else */ if (isVue2) { ObjectField = { functional: true, name: 'ObjectField', props: getFieldProps(), render(h, context) { const props = context.props as IObjectFieldProps const attrs = context.data.attrs const componentData = { ...context.data, props: { fieldType: 'ObjectField', fieldProps: { ...attrs, ...props, ...getRawComponent(props), }, }, } return _h(ReactiveField, componentData, context.children) }, } as unknown as DefineComponent<IObjectFieldProps> } else { ObjectField = { name: 'ObjectField', props: getFieldProps(), setup(props: IObjectFieldProps, context) { return () => { const componentData = { fieldType: 'ObjectField', fieldProps: { ...props, ...getRawComponent(props), }, } as Record<string, unknown> return _h(ReactiveField, componentData, context.slots) } }, } as unknown as DefineComponent<IObjectFieldProps> } export default ObjectField ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/define.md: -------------------------------------------------------------------------------- ```markdown # define ## Description Manually define the domain model, you can specify the responsive behavior of specific attributes, or you can specify a method as batch mode ## Signature ```ts interface define<Target extends object> { ( target: Target, annotations?: { [key: string]: (...args: any[]) => any } ): Target } ``` ## Annotations All Annotations currently supported are: - observable/observable.deep defines deep hijacking responsive properties - observable.box defines get/set container - observable.computed defines calculated properties - observable.ref defines reference hijacking responsive attributes - observable.shallow defines shallow hijacking responsive properties - action/batch defines the batch processing method ## Example ```ts import { define, observable, action, autorun } from '@formily/reactive' class DomainModel { deep = { aa: 1 } shallow = {} box = 0 ref = '' constructor() { define(this, { deep: observable, shallow: observable.shallow, box: observable.box, ref: observable.ref, computed: observable.computed, action, }) } get computed() { return this.deep.aa + this.box.get() } action(aa, box) { this.deep.aa = aa this.box.set(box) } } const model = new DomainModel() autorun(() => { console.log(model.computed) }) model.action(1, 2) model.action(1, 2) //Repeat calls will not respond repeatedly model.action(3, 4) ``` ``` -------------------------------------------------------------------------------- /packages/antd/src/form/index.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { Form as FormType, ObjectField, IFormFeedback } from '@formily/core' import { useParentForm, FormProvider, JSXComponent } from '@formily/react' import { FormLayout, IFormLayoutProps } from '../form-layout' import { PreviewText } from '../preview-text' export interface FormProps extends IFormLayoutProps { form?: FormType component?: JSXComponent onAutoSubmit?: (values: any) => any onAutoSubmitFailed?: (feedbacks: IFormFeedback[]) => void previewTextPlaceholder?: React.ReactNode } export const Form: React.FC<React.PropsWithChildren<FormProps>> = ({ form, component = 'form', onAutoSubmit, onAutoSubmitFailed, previewTextPlaceholder, ...props }) => { const top = useParentForm() const renderContent = (form: FormType | ObjectField) => ( <PreviewText.Placeholder value={previewTextPlaceholder}> <FormLayout {...props}> {React.createElement( component, { onSubmit(e: React.FormEvent) { e?.stopPropagation?.() e?.preventDefault?.() form.submit(onAutoSubmit).catch(onAutoSubmitFailed) }, }, props.children )} </FormLayout> </PreviewText.Placeholder> ) if (form) return <FormProvider form={form}>{renderContent(form)}</FormProvider> if (!top) throw new Error('must pass form instance by createForm') return renderContent(top) } export default Form ``` -------------------------------------------------------------------------------- /packages/reactive-vue/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/reactive-vue", "version": "2.3.7", "license": "MIT", "main": "lib", "module": "esm", "umd:main": "dist/formily.reactive-vue.umd.production.js", "unpkg": "dist/formily.reactive-vue.umd.production.js", "jsdelivr": "dist/formily.reactive-vue.umd.production.js", "jsnext:main": "esm", "repository": { "type": "git", "url": "git+https://github.com/alibaba/formily.git" }, "types": "esm/index.d.ts", "bugs": { "url": "https://github.com/alibaba/formily/issues" }, "homepage": "https://github.com/alibaba/formily#readme", "engines": { "npm": ">=3.0.0" }, "scripts": { "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", "build:cjs": "tsc --project tsconfig.build.json", "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", "build:umd": "rollup --config" }, "devDependencies": { "@vue/composition-api": "^1.0.0-rc.7", "@vue/test-utils": "1.0.0-beta.22", "core-js": "^2.4.0", "vue": "^2.6.12" }, "dependencies": { "@formily/reactive": "2.3.7", "vue-demi": ">=0.13.6" }, "peerDependencies": { "@vue/composition-api": "^1.0.0-beta.1", "vue": "^2.6.0 || >=3.0.0-rc.0" }, "peerDependenciesMeta": { "@vue/composition-api": { "optional": true } }, "publishConfig": { "access": "public" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" } ``` -------------------------------------------------------------------------------- /scripts/build-style/copy.ts: -------------------------------------------------------------------------------- ```typescript /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { copy, readFile, writeFile, existsSync } from 'fs-extra' import glob from 'glob' export type CopyBaseOptions = Record<'esStr' | 'libStr', string> const importLibToEs = async ({ libStr, esStr, filename, }: CopyBaseOptions & { filename: string }) => { if (!existsSync(filename)) { return Promise.resolve() } const fileContent: string = (await readFile(filename)).toString() return writeFile( filename, fileContent.replace(new RegExp(libStr, 'g'), esStr) ) } export const runCopy = ({ resolveForItem, ...lastOpts }: CopyBaseOptions & { resolveForItem?: (filename: string) => unknown }) => { return new Promise((resolve, reject) => { glob(`./src/**/*`, (err, files) => { if (err) { return reject(err) } const all = [] as Promise<unknown>[] for (let i = 0; i < files.length; i += 1) { const filename = files[i] resolveForItem?.(filename) if (/\.(less|scss)$/.test(filename)) { all.push(copy(filename, filename.replace(/src\//, 'esm/'))) all.push(copy(filename, filename.replace(/src\//, 'lib/'))) continue } if (/\/style.ts$/.test(filename)) { importLibToEs({ ...lastOpts, filename: filename.replace(/src\//, 'esm/').replace(/\.ts$/, '.js'), }) continue } } }) }) } ``` -------------------------------------------------------------------------------- /packages/vue/src/__tests__/expression.scope.spec.ts: -------------------------------------------------------------------------------- ```typescript import { render } from '@testing-library/vue' import { createForm } from '@formily/core' import { FormProvider, ExpressionScope, createSchemaField, h } from '..' import { defineComponent } from '@vue/composition-api' test('expression scope', async () => { const Container = defineComponent({ setup(_props, { slots }) { return () => h( ExpressionScope, { props: { value: { $innerScope: 'inner scope value' } }, }, slots ) }, }) const Input = defineComponent({ props: ['text'], setup(props) { return () => h( 'div', { attrs: { 'data-testid': 'test-input' } }, { default: () => props.text } ) }, }) const SchemaField = createSchemaField({ components: { Container, Input }, }) const form = createForm() const { getByTestId } = render({ components: { ...SchemaField, FormProvider }, data() { return { form } }, template: `<FormProvider :form="form"> <SchemaField :scope="{ $outerScope: 'outer scope value' }"> <SchemaVoidField x-component="Container"> <SchemaVoidField name="div" x-component="Input" :x-component-props='{ text: "{{$innerScope + $outerScope}}"}' /> </SchemaVoidField> </SchemaField> </FormProvider>`, }) expect(getByTestId('test-input').textContent).toBe( 'inner scope valueouter scope value' ) }) ``` -------------------------------------------------------------------------------- /packages/react/docs/api/shared/context.md: -------------------------------------------------------------------------------- ```markdown # context ## Description All React Context of @formily/react is convenient for users to do more complex personalized customization. We can consume these contexts through useContext ## FormContext #### Description Form context, you can get the current Form instance #### Signature ```ts import { Form } from '@formily/core' const FormContext = createContext<Form>(null) ``` ## FieldContext #### Description Field context, you can get the current field instance #### Signature ```ts import { GeneralField } from '@formily/core' const FieldContext = createContext<GeneralField>(null) ``` ## SchemaMarkupContext #### Description Schema tag context, mainly used to collect Schema tags written in JSX Markup, and then convert them into standard JSON Schema #### Signature ```ts SchemaMarkupContext = createContext<Schema>(null) ``` ## SchemaContext #### Description Field Schema context, mainly used to obtain the Schema information of the current field #### Signature ```ts const SchemaContext = createContext<Schema>(null) ``` ## SchemaExpressionScopeContext #### Description Schema expression scope context #### Signature ```ts export const SchemaExpressionScopeContext = createContext<any>(null) ``` ## SchemaOptionsContext #### Description Schema global parameter context, mainly used to obtain the parameters passed in from createSchemaField #### Signature ```ts const SchemaOptionsContext = createContext<ISchemaFieldFactoryOptions>(null) ``` ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-drawer/template.vue: -------------------------------------------------------------------------------- ```vue <template> <Button @click="handleOpen">点击打开表单</Button> </template> <script> import { FormDrawer, FormLayout, FormItem, Input } from '@formily/element' import { Button } from 'element-ui' import { Field } from '@formily/vue' export default { components: { Button }, data() { return {} }, methods: { handleOpen() { FormDrawer('抽屉表单', () => ( <FormLayout labelCol={6} wrapperCol={10}> <Field name="aaa" required title="输入框1" decorator={[FormItem]} component={[Input]} /> <Field name="bbb" required title="输入框2" decorator={[FormItem]} component={[Input]} /> <Field name="ccc" required title="输入框3" decorator={[FormItem]} component={[Input]} /> <Field name="ddd" required title="输入框4" decorator={[FormItem]} component={[Input]} /> <FormDrawer.Footer> <span style={{ marginLeft: '4px' }}>扩展文案</span> </FormDrawer.Footer> </FormLayout> )) .open({ initialValues: { aaa: '123', }, }) .then((values) => { console.log('values', values) }) .catch((e) => { console.log(e) }) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/src/reset/index.ts: -------------------------------------------------------------------------------- ```typescript import { IFieldResetOptions } from '@formily/core' import { observer } from '@formily/reactive-vue' import { h, useParentForm } from '@formily/vue' import { defineComponent } from 'vue-demi' import type { Button as IElButton } from 'element-ui' import { Button as ElButton } from 'element-ui' export type ResetProps = IFieldResetOptions & IElButton export const Reset = observer( defineComponent<ResetProps>({ name: 'FReset', props: { forceClear: { type: Boolean, default: false, }, validate: { type: Boolean, default: false, }, }, setup(props, context) { const formRef = useParentForm() const { listeners, slots } = context return () => { const form = formRef?.value return h( ElButton, { attrs: context.attrs, on: { ...listeners, click: (e: any) => { if (listeners?.click) { if (listeners.click(e) === false) return } form ?.reset('*', { forceClear: props.forceClear, validate: props.validate, }) .then(listeners.resetValidateSuccess as (e: any) => void) .catch(listeners.resetValidateFailed as (e: any) => void) }, }, }, slots ) } }, }) ) export default Reset ``` -------------------------------------------------------------------------------- /packages/shared/src/checkers.ts: -------------------------------------------------------------------------------- ```typescript const toString = Object.prototype.toString const isType = <T>(type: string | string[]) => (obj: unknown): obj is T => getType(obj) === `[object ${type}]` export const getType = (obj: any) => toString.call(obj) export const isFn = (val: any): val is Function => typeof val === 'function' export const isArr = Array.isArray export const isPlainObj = isType<object>('Object') export const isStr = isType<string>('String') export const isBool = isType<boolean>('Boolean') export const isNum = isType<number>('Number') export const isMap = (val: any): val is Map<any, any> => val && val instanceof Map export const isSet = (val: any): val is Set<any> => val && val instanceof Set export const isWeakMap = (val: any): val is WeakMap<any, any> => val && val instanceof WeakMap export const isWeakSet = (val: any): val is WeakSet<any> => val && val instanceof WeakSet export const isNumberLike = (index: any): index is number => isNum(index) || /^\d+$/.test(index) export const isObj = (val: unknown): val is object => typeof val === 'object' export const isRegExp = isType<RegExp>('RegExp') export const isReactElement = (obj: any): boolean => obj && obj['$$typeof'] && obj['_owner'] export const isHTMLElement = (target: any): target is EventTarget => { return Object.prototype.toString.call(target).indexOf('HTML') > -1 } export type Subscriber<S> = (payload: S) => void export interface Subscription<S> { notify?: (payload: S) => void | boolean filter?: (payload: S) => any } ``` -------------------------------------------------------------------------------- /packages/react/docs/api/shared/mapReadPretty.md: -------------------------------------------------------------------------------- ```markdown # mapReadPretty ## Description Because most third-party components do not support the reading state, if you want to quickly support the reading state, you can use the mapReadPretty function to map a reading state component ## Signature ```ts interface mapReadPretty<Target extends React.FC> { (component: Target, readPrettyProps?: React.ComponentProps<Target>): React.FC } ``` ## Example ```tsx import React, { useMemo } from 'react' import { createForm } from '@formily/core' import { FormProvider, Field, connect, mapProps, mapReadPretty, } from '@formily/react' import { Input as AntdInput, Form } from 'antd' // FormItem UI component const FormItem = connect( Form.Item, mapProps( { title: 'label', description: 'extra', required: true, validateStatus: true, }, (props, field) => { return { ...props, help: field.selfErrors?.length ? field.selfErrors : undefined, } } ) ) const Input = connect( AntdInput, mapReadPretty(({ value }) => <div>{value}</div>) ) export default () => { const form = useMemo(() => createForm({ validateFirst: true, readPretty: true }) ) return ( <FormProvider form={form}> <Form layout="vertical"> <Field name="name" title="Name" required initialValue="Hello world" decorator={[FormItem]} component={[Input, { placeholder: 'Please Input' }]} /> </Form> </FormProvider> ) } ``` ``` -------------------------------------------------------------------------------- /packages/react/docs/api/hooks/useFormEffects.md: -------------------------------------------------------------------------------- ```markdown # useFormEffects ## Description Mainly inject side-effect logic into the current [Form](https://core.formilyjs.org/api/models/form) instance in the custom component to implement some more complex scenario-based components <Alert> Note: It is invalid to monitor onFormInit in the effects function, because the Form has already been initialized when rendering to the current component, and the effects function will only be executed once, so if you want to rely on the data of useState, please use the reference data of useRef. </Alert> ## Signature ```ts interface useFormEffects { (form: Form): void } ``` ## Example ```tsx import React from 'react' import { createForm, onFieldReact } from '@formily/core' import { FormProvider, Field, useFormEffects } from '@formily/react' import { Input, Form } from 'antd' const form = createForm({ effects() { onFieldReact('custom.aa', (field) => { field.value = field.query('input').get('value') }) }, }) const Custom = () => { useFormEffects(() => { onFieldReact('custom.bb', (field) => { field.value = field.query('.aa').get('value') }) }) return ( <div> <Field name="aa" decorator={[Form.Item]} component={[Input]} /> <Field name="bb" decorator={[Form.Item]} component={[Input]} /> </div> ) } export default () => ( <FormProvider form={form}> <Field name="input" decorator={[Form.Item]} component={[Input]} /> <Field name="custom" decorator={[Form.Item]} component={[Custom]} /> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/array-field.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <ArrayField name="array"> <template #default="{ field }"> <div v-for="(item, index) in field.value || []" :key="`${item.id}-${index}`" :style="{ marginBottom: '10px' }" > <Space> <Field :name="`${index}.value`" :component="[Input]" /> <Button @click=" () => { field.remove(index) } " > Remove </Button> <Button @click=" () => { field.moveUp(index) } " > Move Up </Button> <Button @click=" () => { field.moveDown(index) } " > Move Down </Button> </Space> </div> <Button @click="() => field.push({ id: Date.now(), value: '' })"> Add </Button> </template> </ArrayField> </FormProvider> </template> <script> import { Input, Space, Button } from 'ant-design-vue' import { createForm } from '@formily/core' import { FormProvider, ArrayField, Field } from '@formily/vue' import 'ant-design-vue/dist/antd.css' export default { components: { FormProvider, ArrayField, Field, Space, Button }, data() { return { Input, form: createForm(), } }, } </script> ``` -------------------------------------------------------------------------------- /packages/core/src/models/ObjectField.ts: -------------------------------------------------------------------------------- ```typescript import { reaction } from '@formily/reactive' import { cleanupObjectChildren } from '../shared/internals' import { JSXComponent, IFieldProps, FormPathPattern } from '../types' import { Field } from './Field' import { Form } from './Form' export class ObjectField< Decorator extends JSXComponent = any, Component extends JSXComponent = any > extends Field<Decorator, Component, any, Record<string, any>> { displayName = 'ObjectField' private additionalProperties: string[] = [] constructor( address: FormPathPattern, props: IFieldProps<Decorator, Component>, form: Form, designable: boolean ) { super(address, props, form, designable) this.makeAutoCleanable() } protected makeAutoCleanable() { this.disposers.push( reaction( () => Object.keys(this.value || {}), (newKeys) => { const filterKeys = this.additionalProperties.filter( (key) => !newKeys.includes(key) ) cleanupObjectChildren(this, filterKeys) } ) ) } addProperty = (key: string, value: any) => { this.form.setValuesIn(this.path.concat(key), value) this.additionalProperties.push(key) return this.onInput(this.value) } removeProperty = (key: string) => { this.form.deleteValuesIn(this.path.concat(key)) this.additionalProperties.splice(this.additionalProperties.indexOf(key), 1) return this.onInput(this.value) } existProperty = (key: string) => { return this.form.existValuesIn(this.path.concat(key)) } } ``` -------------------------------------------------------------------------------- /packages/shared/src/clone.ts: -------------------------------------------------------------------------------- ```typescript import { isFn, isPlainObj } from './checkers' export const shallowClone = (values: any) => { if (Array.isArray(values)) { return values.slice(0) } else if (isPlainObj(values)) { if ('$$typeof' in values && '_owner' in values) { return values } if (values['_isBigNumber']) { return values } if (values['_isAMomentObject']) { return values } if (values['_isJSONSchemaObject']) { return values } if (isFn(values['toJS'])) { return values } if (isFn(values['toJSON'])) { return values } return { ...values, } } else if (typeof values === 'object') { return new values.constructor(values) } return values } export const clone = (values: any) => { if (Array.isArray(values)) { const res = [] values.forEach((item) => { res.push(clone(item)) }) return res } else if (isPlainObj(values)) { if ('$$typeof' in values && '_owner' in values) { return values } if (values['_isBigNumber']) { return values } if (values['_isAMomentObject']) { return values } if (values['_isJSONSchemaObject']) { return values } if (isFn(values['toJS'])) { return values['toJS']() } if (isFn(values['toJSON'])) { return values['toJSON']() } const res = {} for (const key in values) { if (Object.hasOwnProperty.call(values, key)) { res[key] = clone(values[key]) } } return res } else { return values } } ``` -------------------------------------------------------------------------------- /packages/reactive-react/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/reactive-react", "version": "2.3.7", "license": "MIT", "main": "lib", "module": "esm", "umd:main": "dist/formily.reactive-react.umd.production.js", "unpkg": "dist/formily.reactive-react.umd.production.js", "jsdelivr": "dist/formily.reactive-react.umd.production.js", "jsnext:main": "esm", "repository": { "type": "git", "url": "git+https://github.com/alibaba/formily.git" }, "types": "esm/index.d.ts", "bugs": { "url": "https://github.com/alibaba/formily/issues" }, "homepage": "https://github.com/alibaba/formily#readme", "engines": { "npm": ">=3.0.0" }, "scripts": { "start": "dumi dev", "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", "build:cjs": "tsc --project tsconfig.build.json", "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", "build:umd": "rollup --config", "build:docs": "dumi build" }, "peerDependencies": { "@types/react": ">=16.8.0", "@types/react-dom": ">=16.8.0", "react": ">=16.8.0", "react-dom": ">=16.8.0", "react-is": ">=16.8.0" }, "peerDependenciesMeta": { "@types/react": { "optional": true }, "@types/react-dom": { "optional": true } }, "devDependencies": { "dumi": "^1.1.0-rc.8" }, "dependencies": { "@formily/reactive": "2.3.7", "hoist-non-react-statics": "^3.3.2" }, "publishConfig": { "access": "public" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" } ``` -------------------------------------------------------------------------------- /packages/antd/src/array-base/style.less: -------------------------------------------------------------------------------- ``` @root-entry-name: 'default'; @import (reference) '~antd/es/style/themes/index.less'; @array-base-prefix-cls: ~'@{ant-prefix}-formily-array-base'; .@{array-base-prefix-cls}-remove, .@{array-base-prefix-cls}-copy { transition: all 0.25s ease-in-out; color: @text-color; font-size: 14px; margin-left: 6px; padding: 0; border: none; width: auto; height: auto; &:hover { color: @primary-5; } &-disabled { color: @disabled-color; cursor: not-allowed !important; &:hover { color: @disabled-color; } } } .@{array-base-prefix-cls}-sort-handle { cursor: move; color: #888 !important; // overrid iconfont.less .anticon[tabindex] cursor &.anticon[tabindex] { cursor: move; } } .@{array-base-prefix-cls}-addition { transition: all 0.25s ease-in-out; } .@{array-base-prefix-cls}-move-down { transition: all 0.25s ease-in-out; color: @text-color; font-size: 14px; margin-left: 6px; padding: 0; border: none; width: auto; height: auto; &:hover { color: @primary-5; } &-disabled { color: @disabled-color; cursor: not-allowed !important; &:hover { color: @disabled-color; } } } .@{array-base-prefix-cls}-move-up { transition: all 0.25s ease-in-out; color: @text-color; font-size: 14px; margin-left: 6px; padding: 0; border: none; width: auto; height: auto; &:hover { color: @primary-5; } &-disabled { color: @disabled-color; cursor: not-allowed !important; &:hover { color: @disabled-color; } } } ``` -------------------------------------------------------------------------------- /packages/antd/src/__builtins__/hooks/useClickAway.ts: -------------------------------------------------------------------------------- ```typescript import { useRef, useEffect, MutableRefObject } from 'react' const defaultEvent = 'click' type EventType = MouseEvent | TouchEvent type BasicTarget<T = HTMLElement> = | (() => T | null) | T | null | MutableRefObject<T | null | undefined> type TargetElement = HTMLElement | Element | Document | Window function getTargetElement( target?: BasicTarget<TargetElement>, defaultElement?: TargetElement ): TargetElement | undefined | null { if (!target) { return defaultElement } let targetElement: TargetElement | undefined | null if (typeof target === 'function') { targetElement = target() } else if ('current' in target) { targetElement = target.current } else { targetElement = target } return targetElement } export const useClickAway = ( onClickAway: (event: EventType) => void, target: BasicTarget | BasicTarget[], eventName: string = defaultEvent ) => { const onClickAwayRef = useRef(onClickAway) onClickAwayRef.current = onClickAway useEffect(() => { const handler = (event: any) => { const targets = Array.isArray(target) ? target : [target] if ( targets.some((targetItem) => { const targetElement = getTargetElement(targetItem) as HTMLElement return !targetElement || targetElement?.contains(event.target) }) ) { return } onClickAwayRef.current(event) } document.addEventListener(eventName, handler) return () => { document.removeEventListener(eventName, handler) } }, [target, eventName]) } ``` -------------------------------------------------------------------------------- /packages/antd/src/array-items/style.less: -------------------------------------------------------------------------------- ``` @root-entry-name: 'default'; @import (reference) '~antd/es/style/themes/index.less'; @array-items-prefix-cls: ~'@{ant-prefix}-formily-array-items'; .@{array-items-prefix-cls}-item-inner { visibility: visible; } // fix https://github.com/alibaba/formily/issues/2891 .@{array-items-prefix-cls}-item { z-index: 100000; } .@{array-items-prefix-cls}-card { display: flex; border: 1px solid @border-color-split; margin-bottom: 10px; padding: 3px 6px; background: @card-background; justify-content: space-between; color: @text-color; .@{ant-prefix}-formily-item:not(.@{ant-prefix}-formily-item-feedback-layout-popover) { margin-bottom: 0 !important; .@{ant-prefix}-formily-item-help { position: absolute; font-size: 12px; top: 100%; background: @card-background; width: 100%; margin-top: 3px; padding: 3px; z-index: 1; border-radius: 3px; box-shadow: 0 0 10px @border-color-split; } } } .@{array-items-prefix-cls}-divide { display: flex; border-bottom: 1px solid @border-color-split; padding: 10px 0; justify-content: space-between; .@{ant-prefix}-formily-item:not(.@{ant-prefix}-formily-item-feedback-layout-popover) { margin-bottom: 0 !important; .@{ant-prefix}-formily-item-help { position: absolute; font-size: 12px; top: 100%; background: @card-background; width: 100%; margin-top: 3px; padding: 3px; z-index: 1; border-radius: 3px; box-shadow: 0 0 10px @border-color-split; } } } ``` -------------------------------------------------------------------------------- /packages/next/src/__builtins__/hooks/useClickAway.ts: -------------------------------------------------------------------------------- ```typescript import { useRef, useEffect, MutableRefObject } from 'react' const defaultEvent = 'click' type EventType = MouseEvent | TouchEvent type BasicTarget<T = HTMLElement> = | (() => T | null) | T | null | MutableRefObject<T | null | undefined> type TargetElement = HTMLElement | Element | Document | Window function getTargetElement( target?: BasicTarget<TargetElement>, defaultElement?: TargetElement ): TargetElement | undefined | null { if (!target) { return defaultElement } let targetElement: TargetElement | undefined | null if (typeof target === 'function') { targetElement = target() } else if ('current' in target) { targetElement = target.current } else { targetElement = target } return targetElement } export const useClickAway = ( onClickAway: (event: EventType) => void, target: BasicTarget | BasicTarget[], eventName: string = defaultEvent ) => { const onClickAwayRef = useRef(onClickAway) onClickAwayRef.current = onClickAway useEffect(() => { const handler = (event: any) => { const targets = Array.isArray(target) ? target : [target] if ( targets.some((targetItem) => { const targetElement = getTargetElement(targetItem) as HTMLElement return !targetElement || targetElement?.contains(event.target) }) ) { return } onClickAwayRef.current(event) } document.addEventListener(eventName, handler) return () => { document.removeEventListener(eventName, handler) } }, [target, eventName]) } ``` -------------------------------------------------------------------------------- /packages/antd/src/form-item/animation.less: -------------------------------------------------------------------------------- ``` @-webkit-keyframes antShowHelpIn { 0% { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } to { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } } .@{form-item-cls}-help-appear, .@{form-item-cls}-help-enter { -webkit-animation-duration: 0.3s; animation-duration: 0.3s; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-play-state: paused; animation-play-state: paused; } .@{form-item-cls}-help-appear.@{form-item-cls}-help-appear-active, .@{form-item-cls}-help-enter.@{form-item-cls}-help-enter-active { -webkit-animation-name: antShowHelpIn; animation-name: antShowHelpIn; -webkit-animation-play-state: running; animation-play-state: running; } .@{form-item-cls}-help-appear, .@{form-item-cls}-help-enter { opacity: 0; } .@{form-item-cls}-help-appear, .@{form-item-cls}-help-enter { -webkit-animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); } @keyframes antShowHelpIn { 0% { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } to { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } } @-webkit-keyframes antShowHelpOut { to { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } } @keyframes antShowHelpOut { to { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } } ``` -------------------------------------------------------------------------------- /packages/next/src/form-item/animation.scss: -------------------------------------------------------------------------------- ```scss @-webkit-keyframes antShowHelpIn { 0% { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } to { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } } .#{$form-item-cls}-help-appear, .#{$form-item-cls}-help-enter { -webkit-animation-duration: 0.3s; animation-duration: 0.3s; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-play-state: paused; animation-play-state: paused; } .#{$form-item-cls}-help-appear.#{$form-item-cls}-help-appear-active, .#{$form-item-cls}-help-enter.#{$form-item-cls}-help-enter-active { -webkit-animation-name: antShowHelpIn; animation-name: antShowHelpIn; -webkit-animation-play-state: running; animation-play-state: running; } .#{$form-item-cls}-help-appear, .#{$form-item-cls}-help-enter { opacity: 0; } .#{$form-item-cls}-help-appear, .#{$form-item-cls}-help-enter { -webkit-animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); } @keyframes antShowHelpIn { 0% { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } to { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } } @-webkit-keyframes antShowHelpOut { to { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } } @keyframes antShowHelpOut { to { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } } ``` -------------------------------------------------------------------------------- /packages/reactive/docs/index.md: -------------------------------------------------------------------------------- ```markdown --- title: Formily-Alibaba unified front-end form solution order: 10 hero: title: Reactive Library desc: DDD-oriented Responsive State Management Solution actions: - text: Home Site link: //formilyjs.org - text: Document link: /guide features: - icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg title: High Performance desc: Efficient update, Demand rendering - icon: https://img.alicdn.com/imgextra/i2/O1CN01YqmcpN1tDalwgyHBH_!!6000000005868-55-tps-800-800.svg title: Zero Dependencies desc: Cross Device,Cross Framework - icon: https://img.alicdn.com/imgextra/i4/O1CN01u6jHgs1ZMwXpjAYnh_!!6000000003181-55-tps-800-800.svg title: Smart Tips desc: Embrace Typescript footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by self --- ## Installation ```bash $ npm install --save @formily/reactive ``` ## Quick start ```tsx /** * defaultShowCode: true */ import React from 'react' import { observable } from '@formily/reactive' import { observer } from '@formily/reactive-react' const obs = observable({ value: 'Hello world', }) export default observer(() => { return ( <div> <div> <input style={{ height: 28, padding: '0 8px', border: '2px solid #888', borderRadius: 3, }} value={obs.value} onChange={(e) => { obs.value = e.target.value }} /> </div> <div>{obs.value}</div> </div> ) }) ``` ``` -------------------------------------------------------------------------------- /docs/guide/scenes/VerifyCode.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useState } from 'react' import { Input, Button } from 'antd' interface IVerifyCodeProps { value?: any onChange?: (value: any) => void readyPost?: boolean phoneNumber?: number style?: React.CSSProperties } export const VerifyCode: React.FC<React.PropsWithChildren<IVerifyCodeProps>> = ({ value, onChange, readyPost, phoneNumber, ...props }) => { const [lastTime, setLastTime] = useState(0) const counting = (time = 20) => { if (time < 0) return setLastTime(time) setTimeout(() => { counting(time - 1) }, 1000) } return ( <div style={{ display: 'inline-flex', width: '100%', alignItems: 'center' }} > <Input {...props} style={{ marginRight: 5, ...props.style }} value={value} onChange={onChange} /> <div style={{ flexShrink: 0, color: '#999', width: 100, height: 35, display: 'flex', alignItems: 'center', justifyContent: 'center', }} > {lastTime === 0 && ( <Button disabled={!readyPost} block onClick={() => { if (phoneNumber) { console.log(`post code by phone number ${phoneNumber}`) } counting() }} > 发送验证码 </Button> )} {lastTime > 0 && <span>剩余{lastTime}秒</span>} </div> </div> ) } ``` -------------------------------------------------------------------------------- /packages/antd/src/__builtins__/portal.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment } from 'react' import { createPortal } from 'react-dom' import { observable } from '@formily/reactive' import { Observer } from '@formily/react' import { render as reactRender, unmount as reactUnmount } from './render' export interface IPortalProps { id?: string | symbol } const PortalMap = observable(new Map<string | symbol, React.ReactNode>()) export const createPortalProvider = (id: string | symbol) => { const Portal = (props: React.PropsWithChildren<IPortalProps>) => { const portalId = props.id ?? id if (portalId && !PortalMap.has(portalId)) { PortalMap.set(portalId, null) } return ( <Fragment> {props.children} <Observer> {() => { if (!portalId) return null const portal = PortalMap.get(portalId) if (portal) return createPortal(portal, document.body) return null }} </Observer> </Fragment> ) } return Portal } export function createPortalRoot<T extends React.ReactNode>( host: HTMLElement, id: string ) { function render(renderer?: () => T) { if (PortalMap.has(id)) { PortalMap.set(id, renderer?.()) } else if (host) { reactRender(<Fragment>{renderer?.()}</Fragment>, host) } } function unmount() { if (PortalMap.has(id)) { PortalMap.set(id, null) } if (host) { const unmountResult = reactUnmount(host) if (unmountResult && host.parentNode) { host.parentNode?.removeChild(host) } } } return { render, unmount, } } ``` -------------------------------------------------------------------------------- /packages/next/src/__builtins__/portal.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment } from 'react' import { createPortal } from 'react-dom' import { observable } from '@formily/reactive' import { Observer } from '@formily/react' import { render as reactRender, unmount as reactUnmount } from './render' export interface IPortalProps { id?: string | symbol } const PortalMap = observable(new Map<string | symbol, React.ReactNode>()) export const createPortalProvider = (id: string | symbol) => { const Portal = (props: React.PropsWithChildren<IPortalProps>) => { const portalId = props.id ?? id if (portalId && !PortalMap.has(portalId)) { PortalMap.set(portalId, null) } return ( <Fragment> {props.children} <Observer> {() => { if (!portalId) return null const portal = PortalMap.get(portalId) if (portal) return createPortal(portal, document.body) return null }} </Observer> </Fragment> ) } return Portal } export function createPortalRoot<T extends React.ReactNode>( host: HTMLElement, id: string ) { function render(renderer?: () => T) { if (PortalMap.has(id)) { PortalMap.set(id, renderer?.()) } else if (host) { reactRender(<Fragment>{renderer?.()}</Fragment>, host) } } function unmount() { if (PortalMap.has(id)) { PortalMap.set(id, null) } if (host) { const unmountResult = reactUnmount(host) if (unmountResult && host.parentNode) { host.parentNode?.removeChild(host) } } } return { render, unmount, } } ``` -------------------------------------------------------------------------------- /packages/element/src/form-item/animation.scss: -------------------------------------------------------------------------------- ```scss @-webkit-keyframes antShowHelpIn { 0% { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } to { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } } .#{$form-item-prefix}-help-appear, .#{$form-item-prefix}-help-enter { -webkit-animation-duration: 0.3s; animation-duration: 0.3s; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-play-state: paused; animation-play-state: paused; } .#{$form-item-prefix}-help-appear.#{$form-item-prefix}-help-appear-active, .#{$form-item-prefix}-help-enter.#{$form-item-prefix}-help-enter-active { -webkit-animation-name: antShowHelpIn; animation-name: antShowHelpIn; -webkit-animation-play-state: running; animation-play-state: running; } .#{$form-item-prefix}-help-appear, .#{$form-item-prefix}-help-enter { opacity: 0; } .#{$form-item-prefix}-help-appear, .#{$form-item-prefix}-help-enter { -webkit-animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); } @keyframes antShowHelpIn { 0% { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } to { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } } @-webkit-keyframes antShowHelpOut { to { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } } @keyframes antShowHelpOut { to { -webkit-transform: translateY(-5px); transform: translateY(-5px); opacity: 0; } } ```