This is page 5 of 35. Use http://codebase.md/alibaba/formily?page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/hooks/use-form-effects.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <Field name="input" :decorator="[FormItem]" :component="[Input, { placeholder: 'input' }]" /> <Field name="custom" :decorator="[FormItem]" :component="[Custom]" /> </FormProvider> </template> <script> import { defineComponent, h } from '@vue/composition-api' import { createForm, onFieldReact } from '@formily/core' import { FormProvider, Field, useFormEffects } from '@formily/vue' import { Form, Input } from 'ant-design-vue' import 'ant-design-vue/dist/antd.css' const Custom = defineComponent({ setup() { useFormEffects(() => { onFieldReact('custom.bb', (field) => { field.value = field.query('.aa').get('value') }) }) return () => h('div', {}, [ h( Field, { props: { name: 'aa', decorator: [Form.Item], component: [Input, { placeholder: 'aa' }], }, }, {} ), h( Field, { props: { name: 'bb', decorator: [Form.Item], component: [Input, { placeholder: 'bb' }], }, }, {} ), ]) }, }) export default { components: { FormProvider, Field, }, data() { const form = createForm({ effects() { onFieldReact('custom.aa', (field) => { field.value = field.query('input').get('value') }) }, }) return { FormItem: Form.Item, Input, Custom, form, } }, } </script> ``` -------------------------------------------------------------------------------- /packages/core/src/models/Graph.ts: -------------------------------------------------------------------------------- ```typescript import { define, batch } from '@formily/reactive' import { each, FormPath } from '@formily/shared' import { IFormGraph } from '../types' import { Form } from './Form' import { isFormState, isFieldState, isArrayFieldState, isObjectFieldState, } from '../shared/checkers' export class Graph { form: Form constructor(form: Form) { this.form = form define(this, { setGraph: batch, }) } getGraph = (): IFormGraph => { const graph = {} graph[''] = this.form.getState() each(this.form.fields, (field: any, identifier) => { graph[identifier] = field.getState() }) return graph } setGraph = (graph: IFormGraph) => { const form = this.form const createField = (identifier: string, state: any) => { const address = FormPath.parse(identifier) const name = address.segments[address.segments.length - 1] const basePath = address.parent() if (isFieldState(state)) { return this.form.createField({ name, basePath }) } else if (isArrayFieldState(state)) { return this.form.createArrayField({ name, basePath }) } else if (isObjectFieldState(state)) { return this.form.createObjectField({ name, basePath }) } else { return this.form.createVoidField({ name, basePath }) } } each(graph, (state, address) => { if (isFormState(state)) { form.setState(state) } else { const field = form.fields[address] if (field) { field.setState(state) } else { createField(address, state).setState(state) } } }) } } ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/app/components/filter.ts: -------------------------------------------------------------------------------- ```typescript // Helper functions for filtering export const defaultMatcher = (filterText, node) => { return node.name.toLowerCase().indexOf(filterText.toLowerCase()) !== -1 } export const findNode = (node, filter, matcher) => { return ( matcher(filter, node) || // i match (node.children && // or i have decendents and one of them match node.children.length && !!node.children.find((child) => findNode(child, filter, matcher))) ) } export const filterTree = (node, filter, matcher = defaultMatcher) => { // If im an exact match then all my children get to stay if (matcher(filter, node) || !node.children) { return node } // If not then only keep the ones that match or have matching descendants const filtered = node.children .filter((child) => findNode(child, filter, matcher)) .map((child) => filterTree(child, filter, matcher)) return Object.assign({}, node, { children: filtered }) } export const expandFilteredNodes = (node, filter, matcher = defaultMatcher) => { let children = node.children if (!children || children.length === 0) { return Object.assign({}, node, { toggled: false }) } const childrenWithMatches = node.children.filter((child) => findNode(child, filter, matcher) ) const shouldExpand = childrenWithMatches.length > 0 // If im going to expand, go through all the matches and see if thier children need to expand if (shouldExpand) { children = childrenWithMatches.map((child) => { return expandFilteredNodes(child, filter, matcher) }) } return Object.assign({}, node, { children: children, toggled: shouldExpand, }) } ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/shared/connect.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <Form layout="vertical"> <Field name="name" title="Name" required :decorator="[FormItem]" :component="[Input, { placeholder: 'Please Input' }]" /> <FormConsumer> <template #default="{ form }"> <div style="white-space: pre; margin-bottom: 16px"> {{ JSON.stringify(form.values, null, 2) }} </div> <Button type="primary" @click=" () => { form.submit(log) } " > Submit </Button> </template> </FormConsumer> </Form> </FormProvider> </template> <script> import { Form, Input, Button } from 'ant-design-vue' import { createForm, setValidateLanguage } from '@formily/core' import { FormProvider, FormConsumer, Field, connect, mapProps, } 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, } } ) ) export default { components: { FormProvider, FormConsumer, Field, Form, Button, }, data() { const form = createForm({ validateFirst: true }) return { FormItem, Input, form, } }, methods: { log(...args) { console.log(...args) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/shared/map-props.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <Form layout="vertical"> <Field name="name" title="Name" required :decorator="[FormItem]" :component="[Input, { placeholder: 'Please Input' }]" /> <FormConsumer> <template #default="{ form }"> <div style="white-space: pre; margin-bottom: 16px"> {{ JSON.stringify(form.values, null, 2) }} </div> <Button type="primary" @click=" () => { form.submit(log) } " > Submit </Button> </template> </FormConsumer> </Form> </FormProvider> </template> <script> import { Form, Input, Button } from 'ant-design-vue' import { createForm, setValidateLanguage } from '@formily/core' import { FormProvider, FormConsumer, Field, connect, mapProps, } 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, } } ) ) export default { components: { FormProvider, FormConsumer, Field, Form, Button, }, data() { const form = createForm({ validateFirst: true }) return { FormItem, Input, form, } }, methods: { log(...args) { console.log(...args) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/src/array-table/style.scss: -------------------------------------------------------------------------------- ```scss @import '../__builtins__/styles/common.scss'; $array-table-prefix-cls: '#{$formily-prefix}-array-table'; .#{$array-table-prefix-cls} { .#{$formily-prefix}-form-item:not(.#{$formily-prefix}-form-item-feedback-layout-popover) { margin-bottom: 0 !important; } &-status-select-dropdown { .#{$namespace}-badge { line-height: 1; } } &-pagination { display: flex; justify-content: center; margin-top: 8px; .#{$array-table-prefix-cls}-status-select.has-error { .#{$namespace}-input__inner { border-color: $--color-danger !important; } } } .#{$namespace}-table { .cell { overflow: visible; } .cell.el-tooltip { overflow: hidden; } &__fixed { box-shadow: 10px 0 10px -10px rgb(0 0 0 / 12%); } &__fixed-right { box-shadow: -10px 0 10px -10px rgb(0 0 0 / 12%); } } .#{$formily-prefix}-form-item-help { position: absolute; font-size: 12px; top: 100%; background: #fff; width: 100%; margin-top: 3px; padding: 3px; z-index: 2; border-radius: 3px; box-shadow: 0 0 10px #eee; } .#{$formily-prefix}-array-base-addition { margin-top: 8px; width: 100%; border: $--border-width-base dashed $--border-color-base; &:hover { background-color: $--color-white; border-color: $--border-color-hover; } &:active, &:focus { background-color: $--color-white; border-color: $--color-primary; } } .#{$formily-prefix}-form-item-feedback-layout-popover { margin-bottom: 0; } &-inner-asterisk { color: $--color-danger; font-weight: $--font-weight-primary; } } ``` -------------------------------------------------------------------------------- /packages/next/src/space/index.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { Box } from '@alifd/next' import { isNumberLike } from '@formily/shared' import { toArray, usePrefixCls } from '../__builtins__' import { useFormLayout } from '../form-layout' export interface ISpaceProps { prefix?: string className?: string style?: React.CSSProperties size?: number | 'small' | 'large' | 'middle' direction?: 'horizontal' | 'vertical' // No `stretch` since many components do not support that. align?: 'start' | 'end' | 'center' | 'baseline' wrap?: boolean } const spaceSize = { small: 8, middle: 16, large: 24, } export const Space: React.FC<React.PropsWithChildren<ISpaceProps>> = ({ direction = 'horizontal', size, align = 'start', ...props }) => { const layout = useFormLayout() const prefix = usePrefixCls('space', props) const getDirection = () => { if (direction === 'horizontal') { return 'row' } else { return 'column' } } const getAlign = () => { if (align === 'start') { return 'flex-start' } else if (align === 'end') { return 'flex-end' } else { return 'center' } } const _size = size ?? layout?.spaceGap ?? 8 const _align = getAlign() return ( <Box {...props} spacing={isNumberLike(_size) ? _size : spaceSize[_size] || 8} style={{ alignItems: _align, display: 'inline-flex', ...props.style, }} align={_align} direction={getDirection()} > {toArray(props.children, { keepEmpty: true }).map((child, index) => ( <div className={`${prefix}-item`} key={index}> {child} </div> ))} </Box> ) } export default Space ``` -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/react", "version": "2.3.7", "license": "MIT", "main": "lib", "module": "esm", "umd:main": "dist/formily.react.umd.production.js", "unpkg": "dist/formily.react.umd.production.js", "jsdelivr": "dist/formily.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/core": "2.3.7", "@formily/json-schema": "2.3.7", "@formily/reactive": "2.3.7", "@formily/reactive-react": "2.3.7", "@formily/shared": "2.3.7", "@formily/validator": "2.3.7", "hoist-non-react-statics": "^3.3.2" }, "publishConfig": { "access": "public" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" } ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/app/components/SearchBox.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import styled from 'styled-components' const SerachBox = styled.div` display: flex; align-items: center; height: 100%; .input-addon { padding: 0 5px; } .form-control { width: 50%; border: none; background: transparent; color: white; outline: none; } ` const SearchIcon = () => { return ( <svg t="1592193216787" className="icon" viewBox="0 0 1024 1024" version="1.1" p-id="3365" width="12" height="12" > <defs> <style type="text/css"></style> </defs> <path d="M976.738462 892.061538L712.861538 630.153846c53.169231-74.830769 80.738462-169.353846 66.953847-269.784615-23.630769-169.353846-161.476923-303.261538-332.8-319.015385C214.646154 17.723077 17.723077 214.646154 41.353846 448.984615c15.753846 169.353846 149.661538 309.169231 319.015385 332.8 100.430769 13.784615 194.953846-13.784615 269.784615-66.953846l261.907692 261.907693c11.815385 11.815385 29.538462 11.815385 41.353847 0l41.353846-41.353847c11.815385-11.815385 11.815385-31.507692 1.969231-43.323077zM157.538462 411.569231C157.538462 271.753846 271.753846 157.538462 411.569231 157.538462s254.030769 114.215385 254.030769 254.030769S551.384615 665.6 411.569231 665.6 157.538462 553.353846 157.538462 411.569231z" p-id="3366" fill="#9da5ab" ></path> </svg> ) } export default ({ onSearch }) => { return ( <SerachBox> <div className="input-addon"> <SearchIcon /> </div> <input className="form-control" onChange={onSearch} placeholder="Search the field..." type="text" /> </SerachBox> ) } ``` -------------------------------------------------------------------------------- /packages/antd/src/upload/placeholder.ts: -------------------------------------------------------------------------------- ```typescript export const UPLOAD_PLACEHOLDER = [ { ext: /\.docx/i, icon: '//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png', }, { ext: /\.pptx/i, icon: '//img.alicdn.com/tfs/TB1ItgWr_tYBeNjy1XdXXXXyVXa-200-200.png', }, { ext: /\.jpe?g/i, icon: '//img.alicdn.com/tfs/TB1wrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', }, { ext: /\.pdf/i, icon: '//img.alicdn.com/tfs/TB1GwD8r9BYBeNjy0FeXXbnmFXa-200-200.png', }, { ext: /\.png/i, icon: '//img.alicdn.com/tfs/TB1BHT5r9BYBeNjy0FeXXbnmFXa-200-200.png', }, { ext: /\.eps/i, icon: '//img.alicdn.com/tfs/TB1G_iGrVOWBuNjy0FiXXXFxVXa-200-200.png', }, { ext: /\.ai/i, icon: '//img.alicdn.com/tfs/TB1B2cVr_tYBeNjy1XdXXXXyVXa-200-200.png', }, { ext: /\.gif/i, icon: '//img.alicdn.com/tfs/TB1DTiGrVOWBuNjy0FiXXXFxVXa-200-200.png', }, { ext: /\.svg/i, icon: '//img.alicdn.com/tfs/TB1uUm9rY9YBuNjy0FgXXcxcXXa-200-200.png', }, { ext: /\.xlsx?/i, icon: '//img.alicdn.com/tfs/TB1any1r1OSBuNjy0FdXXbDnVXa-200-200.png', }, { ext: /\.psd?/i, icon: '//img.alicdn.com/tfs/TB1_nu1r1OSBuNjy0FdXXbDnVXa-200-200.png', }, { ext: /\.(wav|aif|aiff|au|mp1|mp2|mp3|ra|rm|ram|mid|rmi)/i, icon: '//img.alicdn.com/tfs/TB1jPvwr49YBuNjy0FfXXXIsVXa-200-200.png', }, { ext: /\.(avi|wmv|mpg|mpeg|vob|dat|3gp|mp4|mkv|rm|rmvb|mov|flv)/i, icon: '//img.alicdn.com/tfs/TB1FrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', }, { ext: /\.(zip|rar|arj|z|gz|iso|jar|ace|tar|uue|dmg|pkg|lzh|cab)/i, icon: '//img.alicdn.com/tfs/TB10jmfr29TBuNjy0FcXXbeiFXa-200-200.png', }, { ext: /\.[^.]+/i, icon: '//img.alicdn.com/tfs/TB10.R4r3mTBuNjy1XbXXaMrVXa-200-200.png', }, ] ``` -------------------------------------------------------------------------------- /packages/next/src/upload/placeholder.ts: -------------------------------------------------------------------------------- ```typescript export const UPLOAD_PLACEHOLDER = [ { ext: /\.docx/i, icon: '//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png', }, { ext: /\.pptx/i, icon: '//img.alicdn.com/tfs/TB1ItgWr_tYBeNjy1XdXXXXyVXa-200-200.png', }, { ext: /\.jpe?g/i, icon: '//img.alicdn.com/tfs/TB1wrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', }, { ext: /\.pdf/i, icon: '//img.alicdn.com/tfs/TB1GwD8r9BYBeNjy0FeXXbnmFXa-200-200.png', }, { ext: /\.png/i, icon: '//img.alicdn.com/tfs/TB1BHT5r9BYBeNjy0FeXXbnmFXa-200-200.png', }, { ext: /\.eps/i, icon: '//img.alicdn.com/tfs/TB1G_iGrVOWBuNjy0FiXXXFxVXa-200-200.png', }, { ext: /\.ai/i, icon: '//img.alicdn.com/tfs/TB1B2cVr_tYBeNjy1XdXXXXyVXa-200-200.png', }, { ext: /\.gif/i, icon: '//img.alicdn.com/tfs/TB1DTiGrVOWBuNjy0FiXXXFxVXa-200-200.png', }, { ext: /\.svg/i, icon: '//img.alicdn.com/tfs/TB1uUm9rY9YBuNjy0FgXXcxcXXa-200-200.png', }, { ext: /\.xlsx?/i, icon: '//img.alicdn.com/tfs/TB1any1r1OSBuNjy0FdXXbDnVXa-200-200.png', }, { ext: /\.psd?/i, icon: '//img.alicdn.com/tfs/TB1_nu1r1OSBuNjy0FdXXbDnVXa-200-200.png', }, { ext: /\.(wav|aif|aiff|au|mp1|mp2|mp3|ra|rm|ram|mid|rmi)/i, icon: '//img.alicdn.com/tfs/TB1jPvwr49YBuNjy0FfXXXIsVXa-200-200.png', }, { ext: /\.(avi|wmv|mpg|mpeg|vob|dat|3gp|mp4|mkv|rm|rmvb|mov|flv)/i, icon: '//img.alicdn.com/tfs/TB1FrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', }, { ext: /\.(zip|rar|arj|z|gz|iso|jar|ace|tar|uue|dmg|pkg|lzh|cab)/i, icon: '//img.alicdn.com/tfs/TB10jmfr29TBuNjy0FcXXbeiFXa-200-200.png', }, { ext: /\.[^.]+/i, icon: '//img.alicdn.com/tfs/TB10.R4r3mTBuNjy1XbXXaMrVXa-200-200.png', }, ] ``` -------------------------------------------------------------------------------- /packages/shared/src/isEmpty.ts: -------------------------------------------------------------------------------- ```typescript import { instOf } from './instanceof' const has = Object.prototype.hasOwnProperty const toString = Object.prototype.toString export const isUndef = (val: any) => val === undefined export const isValid = (val: any) => val !== undefined && val !== null export function isEmpty(val: any, strict = false): boolean { // Null and Undefined... if (val == null) { return true } // Booleans... if (typeof val === 'boolean') { return false } // Numbers... if (typeof val === 'number') { return false } // Strings... if (typeof val === 'string') { return val.length === 0 } // Functions... if (typeof val === 'function') { return val.length === 0 } // Arrays... if (Array.isArray(val)) { if (val.length === 0) { return true } for (let i = 0; i < val.length; i++) { if (strict) { if (val[i] !== undefined && val[i] !== null) { return false } } else { if ( val[i] !== undefined && val[i] !== null && val[i] !== '' && val[i] !== 0 ) { return false } } } return true } // Errors... if (instOf(val, 'Error')) { return val.message === '' } // Objects... if (val.toString === toString) { switch (val.toString()) { // Maps, Sets, Files and Errors... case '[object File]': case '[object Map]': case '[object Set]': { return val.size === 0 } // Plain objects... case '[object Object]': { for (const key in val) { if (has.call(val, key)) { return false } } return true } } } // Anything else... return false } ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/extension/manifest.json: -------------------------------------------------------------------------------- ```json { "version": "0.1.14", "name": "Formily DevTools", "short_name": "Formily DevTools", "description": "Formily DevTools for debugging application's state changes.", "homepage_url": "https://github.com/alibaba/formily", "manifest_version": 3, "action": { "default_icon": "img/logo/scalable.png", "default_title": "Formily DevTools", "default_popup": "popup.html" }, "commands": { "devtools-left": { "description": "DevTools window to left" }, "devtools-right": { "description": "DevTools window to right" }, "devtools-bottom": { "description": "DevTools window to bottom" }, "devtools-remote": { "description": "Remote DevTools" }, "_execute_action": { "suggested_key": { "default": "Ctrl+Shift+E" } } }, "icons": { "16": "img/logo/16x16.png", "48": "img/logo/48x48.png", "128": "img/logo/128x128.png" }, "background": { "service_worker": "js/background.bundle.js", "type": "module" }, "content_scripts": [ { "matches": ["<all_urls>"], "exclude_matches": ["*://www.google.com/*"], "js": ["js/content.bundle.js", "js/inject.bundle.js"], "run_at": "document_start", "all_frames": true } ], "devtools_page": "devtools.html", "web_accessible_resources": [ { "resources": ["js/backend.bundle.js"], "matches": ["<all_urls>"] } ], "externally_connectable": { "ids": ["*"] }, "host_permissions": ["file://*/*", "http://*/*", "https://*/*"], "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" }, "update_url": "https://clients2.google.com/service/update2/crx" } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/cascader/template.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form"> <Field name="address" title="地址选择" required :decorator="[FormItem]" :component="[ Cascader, { style: { width: '240px', }, }, ]" /> <Submit @submit="onSubmit">提交</Submit> </Form> </template> <script> import { createForm, onFieldReact } from '@formily/core' import { Field } from '@formily/vue' import { Form, FormItem, Cascader, Submit } from '@formily/element' import { action } from '@formily/reactive' import axios from 'axios' const transformAddress = (data = {}) => { return Object.entries(data).reduce((buf, [key, value]) => { if (typeof value === 'string') return buf.concat({ label: value, value: key, }) const { name, code, cities, districts } = value const _cities = transformAddress(cities) const _districts = transformAddress(districts) return buf.concat({ label: name, value: code, children: _cities.length ? _cities : _districts.length ? _districts : undefined, }) }, []) } const useAddress = (pattern) => { onFieldReact(pattern, (field) => { field.loading = true axios('//unpkg.com/china-location/dist/location.json') .then((res) => res.data) .then( action.bound((data) => { field.dataSource = transformAddress(data) field.loading = false }) ) }) } const form = createForm({ effects: () => { useAddress('address') }, }) export default { components: { Form, Field, Submit }, data() { return { FormItem, Cascader, form, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/next/src/array-items/main.scss: -------------------------------------------------------------------------------- ```scss @import '~@alifd/next/lib/core/index-noreset.scss'; $array-items-prefix-cls: '#{$css-prefix}formily-array-items'; .#{$array-items-prefix-cls} { .#{$css-prefix}form-item { margin-bottom: 0; } } // fix https://github.com/alibaba/formily/issues/2891 .#{$array-items-prefix-cls}-item { z-index: 100000; } .#{$array-items-prefix-cls}-sort-handler { cursor: move; color: #888 !important; } .#{$array-items-prefix-cls}-item-inner { margin-bottom: 10px; visibility: visible; } .#{$array-items-prefix-cls}-card { display: flex; border: 1px solid #eee; margin-bottom: 10px; padding: 3px 6px; background: #fff; justify-content: space-between; align-items: center; transition: all 0.35s; .#{$css-prefix}formily-item:not(.#{$css-prefix}formily-item-feedback-layout-popover) { margin-bottom: 0 !important; position: relative; .#{$css-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; } } } .#{$array-items-prefix-cls}-divide { display: flex; border-bottom: 1px solid #eee; margin-bottom: 10px; padding: 10px 0; background: #fff; justify-content: space-between; align-items: center; .#{$css-prefix}formily-item:not(.#{$css-prefix}formily-item-feedback-layout-popover) { margin-bottom: 0 !important; position: relative; .#{$css-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; } } } ``` -------------------------------------------------------------------------------- /packages/element/src/array-items/style.scss: -------------------------------------------------------------------------------- ```scss @import '../__builtins__/styles/common.scss'; $array-items-prefix-cls: '#{$formily-prefix}-array-items'; .#{$array-items-prefix-cls}-item-inner { visibility: visible; } .#{$array-items-prefix-cls} { .#{$formily-prefix}-array-base-addition { width: 100%; border: $--border-width-base dashed $--border-color-base; &:hover { background-color: $--color-white; border-color: $--border-color-hover; } &:active, &:focus { background-color: $--color-white; border-color: $--color-primary; } } } .#{$array-items-prefix-cls}-card { display: flex; border: 1px solid $--card-border-color; margin-bottom: 10px; padding: 3px 6px; background: $--color-white; justify-content: space-between; .#{$formily-prefix}-form-item:not(.#{$formily-prefix}-form-item-feedback-layout-popover) { margin-bottom: 0 !important; .#{$formily-prefix}-form-item-help { position: absolute; font-size: 12px; top: 100%; background: $--color-white; width: 100%; margin-top: 3px; padding: 3px; z-index: 1; border-radius: 3px; box-shadow: 0 0 10px $--border-color-base; } } } .#{$array-items-prefix-cls}-divide { display: flex; border-bottom: 1px solid $--card-border-color; padding: 10px 0; justify-content: space-between; .#{$formily-prefix}-form-item:not(.#{$formily-prefix}-form-item-feedback-layout-popover) { margin-bottom: 0 !important; .#{$formily-prefix}-form-item-help { position: absolute; font-size: 12px; top: 100%; background: $--color-white; width: 100%; margin-top: 3px; padding: 3px; z-index: 1; border-radius: 3px; box-shadow: 0 0 10px $--card-border-color; } } } ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/assets/img/loading.svg: -------------------------------------------------------------------------------- ``` <!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL --> <svg width="45" height="45" viewBox="0 0 45 45" xmlns="http://www.w3.org/2000/svg" stroke="#fff"> <g fill="none" fill-rule="evenodd" transform="translate(1 1)" stroke-width="2"> <circle cx="22" cy="22" r="6" stroke-opacity="0"> <animate attributeName="r" begin="1.5s" dur="3s" values="6;22" calcMode="linear" repeatCount="indefinite" /> <animate attributeName="stroke-opacity" begin="1.5s" dur="3s" values="1;0" calcMode="linear" repeatCount="indefinite" /> <animate attributeName="stroke-width" begin="1.5s" dur="3s" values="2;0" calcMode="linear" repeatCount="indefinite" /> </circle> <circle cx="22" cy="22" r="6" stroke-opacity="0"> <animate attributeName="r" begin="3s" dur="3s" values="6;22" calcMode="linear" repeatCount="indefinite" /> <animate attributeName="stroke-opacity" begin="3s" dur="3s" values="1;0" calcMode="linear" repeatCount="indefinite" /> <animate attributeName="stroke-width" begin="3s" dur="3s" values="2;0" calcMode="linear" repeatCount="indefinite" /> </circle> <circle cx="22" cy="22" r="8"> <animate attributeName="r" begin="0s" dur="1.5s" values="6;1;2;3;4;5;6" calcMode="linear" repeatCount="indefinite" /> </circle> </g> </svg> ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/config/webpack.base.ts: -------------------------------------------------------------------------------- ```typescript import path from 'path' import fs from 'fs-extra' const getEntry = (src) => { return [path.resolve(__dirname, '../src/extension/', src)] } // 先确保删除package目录,再创建新的 const packageDir = path.resolve(__dirname, '../package') if (fs.existsSync(packageDir)) { fs.removeSync(packageDir) } fs.ensureDirSync(packageDir) fs.copy(path.resolve(__dirname, '../assets'), packageDir) fs.copy( path.resolve(__dirname, '../src/extension/manifest.json'), path.resolve(__dirname, '../package/manifest.json') ) export default { mode: 'development', devtool: 'inline-source-map', // 嵌入到源文件中 entry: { popup: getEntry('./popup.tsx'), devtools: getEntry('./devtools.tsx'), devpanel: getEntry('./devpanel.tsx'), content: getEntry('./content.ts'), backend: getEntry('./backend.ts'), demo: getEntry('../app/demo.tsx'), inject: getEntry('./inject.ts'), background: getEntry('./background.ts'), }, output: { path: path.resolve(__dirname, '../package'), filename: 'js/[name].bundle.js', }, resolve: { modules: ['node_modules'], extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], }, module: { rules: [ { test: /\.tsx?$/, use: [ { loader: require.resolve('ts-loader'), options: { transpileOnly: true, }, }, ], }, { test: /\.css$/, use: [ { loader: require.resolve('style-loader'), options: { singleton: true, }, }, require.resolve('css-loader'), ], }, { test: /\.html?$/, loader: require.resolve('file-loader'), options: { name: '[name].[ext]', }, }, ], }, } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-tabs/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaArrayField name="string_array" x-decorator="FormItem" title="字符串数组" :maxItems="3" x-component="ArrayTabs" > <SchemaStringField x-decorator="FormItem" required x-component="Input" /> </SchemaArrayField> <SchemaArrayField name="array" x-decorator="FormItem" title="对象数组" :maxItems="3" x-component="ArrayTabs" > <SchemaObjectField> <SchemaStringField x-decorator="FormItem" title="AAA" name="aaa" required x-component="Input" /> <SchemaStringField x-decorator="FormItem" title="BBB" name="bbb" required x-component="Input" /> </SchemaObjectField> </SchemaArrayField> </SchemaField> <FormButtonGroup> <Submit @submit="log">提交</Submit> </FormButtonGroup> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormItem, FormButtonGroup, Submit, Input, Select, ArrayTabs, } from '@formily/element' import { Button } from 'element-ui' const SchemaField = createSchemaField({ components: { FormItem, Input, Select, ArrayTabs, }, }) export default { components: { FormProvider, FormButtonGroup, Button, Submit, ...SchemaField, }, data() { const form = createForm() return { form, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/next/src/form/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useMemo } from 'react' import { FormProvider, JSXComponent, useParentForm } from '@formily/react' import { FormLayout, IFormLayoutProps } from '../form-layout' import { ConfigProvider } from '@alifd/next' import { getValidateLocaleIOSCode, setValidateLanguage, Form as FormType, ObjectField, IFormFeedback, } from '@formily/core' 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 lang = (ConfigProvider as any).getContext()?.locale?.momentLocale ?? 'zh-CN' useMemo(() => { const validateLanguage = getValidateLocaleIOSCode(lang) setValidateLanguage(validateLanguage) }, [lang]) 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/next/src/array-cards/main.scss: -------------------------------------------------------------------------------- ```scss @import '~@alifd/next/lib/core/index-noreset.scss'; $array-cards-prefix-cls: '#{$css-prefix}formily-array-cards'; .#{$css-prefix}empty { display: flex; justify-content: center; align-items: center; &-image { display: flex; justify-content: center; align-items: center; transform: scale(0.8); .ant-empty-img-default-ellipse { fill-opacity: 0.8; fill: #f5f5f5; } .ant-empty-img-default-path-1 { fill: #aeb8c2; } .ant-empty-img-default-path-2 { fill: url(#linearGradient-1); } .ant-empty-img-default-path-3 { fill: #f5f5f7; } .ant-empty-img-default-path-4, .ant-empty-img-default-path-5 { fill: #dce0e6; } .ant-empty-img-default-g { fill: #fff; } .ant-empty-img-simple-ellipse { fill: #f5f5f5; } .ant-empty-img-simple-g { stroke: #d9d9d9; } .ant-empty-img-simple-path { fill: #fafafa; } .ant-empty-rtl { direction: rtl; } } } .#{$array-cards-prefix-cls}-remove { transition: all 0.25s ease-in-out; color: $color-text1-3; font-size: 16px; margin-left: 6px; &:hover { color: $color-text1-1; } } .#{$array-cards-prefix-cls}-addition { transition: all 0.25s ease-in-out; } .#{$array-cards-prefix-cls}-move-down { transition: all 0.25s ease-in-out; color: $color-text1-3; font-size: 16px; margin-left: 6px; &:hover { color: $color-text1-1; } } .#{$array-cards-prefix-cls}-move-up { transition: all 0.25s ease-in-out; color: $color-text1-3; font-size: 16px; margin-left: 6px; &:hover { color: $color-text1-1; } } .#{$array-cards-prefix-cls}-item { margin-bottom: 10px !important; } .next-card-extra { svg { margin-right: 6px; &:last-of-type { margin-right: 0; } } } ``` -------------------------------------------------------------------------------- /packages/element/src/form-button-group/index.ts: -------------------------------------------------------------------------------- ```typescript import { h } from '@formily/vue' import { defineComponent } from 'vue-demi' import { FormBaseItem } from '../form-item' import { Space, SpaceProps } from '../space' import { stylePrefix } from '../__builtins__/configs' export type FormButtonGroupProps = Omit<SpaceProps, 'align' | 'size'> & { align?: 'left' | 'right' | 'center' gutter?: number className?: string alignFormItem: boolean } export const FormButtonGroup = defineComponent<FormButtonGroupProps>({ name: 'FFormButtonGroup', props: { align: { type: String, default: 'left', }, gutter: { type: Number, default: 8, }, alignFormItem: { type: Boolean, default: false, }, }, setup(props, { slots, attrs }) { const prefixCls = `${stylePrefix}-form-button-group` return () => { if (props.alignFormItem) { return h( FormBaseItem, { style: { margin: 0, padding: 0, width: '100%', }, attrs: { colon: false, label: ' ', ...attrs, }, }, { default: () => h(Space, { props: { size: props.gutter } }, slots), } ) } else { return h( Space, { class: [prefixCls], style: { justifyContent: props.align === 'left' ? 'flex-start' : props.align === 'right' ? 'flex-end' : 'center', display: 'flex', }, props: { ...attrs, size: props.gutter, }, attrs, }, slots ) } } }, }) export default FormButtonGroup ``` -------------------------------------------------------------------------------- /packages/next/src/array-collapse/main.scss: -------------------------------------------------------------------------------- ```scss @import '~@alifd/next/lib/core/index-noreset.scss'; $array-collapse-prefix-cls: '#{$css-prefix}formily-array-collapse'; .#{$css-prefix}empty { display: flex; justify-content: center; align-items: center; &-image { display: flex; justify-content: center; align-items: center; transform: scale(0.8); .ant-empty-img-default-ellipse { fill-opacity: 0.8; fill: #f5f5f5; } .ant-empty-img-default-path-1 { fill: #aeb8c2; } .ant-empty-img-default-path-2 { fill: url(#linearGradient-1); } .ant-empty-img-default-path-3 { fill: #f5f5f7; } .ant-empty-img-default-path-4, .ant-empty-img-default-path-5 { fill: #dce0e6; } .ant-empty-img-default-g { fill: #fff; } .ant-empty-img-simple-ellipse { fill: #f5f5f5; } .ant-empty-img-simple-g { stroke: #d9d9d9; } .ant-empty-img-simple-path { fill: #fafafa; } .ant-empty-rtl { direction: rtl; } } } .#{$array-collapse-prefix-cls}-remove { transition: all 0.25s ease-in-out; color: $color-text1-3; font-size: 16px; margin-left: 6px; &:hover { color: $color-text1-1; } } .#{$array-collapse-prefix-cls}-addition { transition: all 0.25s ease-in-out; } .#{$array-collapse-prefix-cls}-move-down { transition: all 0.25s ease-in-out; color: $color-text1-3; font-size: 16px; margin-left: 6px; &:hover { color: $color-text1-1; } } .#{$array-collapse-prefix-cls}-move-up { transition: all 0.25s ease-in-out; color: $color-text1-3; font-size: 16px; margin-left: 6px; &:hover { color: $color-text1-1; } } .#{$array-collapse-prefix-cls}-item { margin-bottom: 10px !important; .#{$array-collapse-prefix-cls}-item-title { display: flex; justify-content: space-between; } } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-drawer/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <Button @click="handleOpen">点击打开表单</Button> </template> <script> import { FormDrawer, FormLayout, FormItem, Input } from '@formily/element' import { Button } from 'element-ui' import { createSchemaField } from '@formily/vue' const { SchemaField } = createSchemaField({ components: { FormItem, Input, }, }) // 抽屉表单组件 const DrawerForm = { data() { const schema = { type: 'object', properties: { aaa: { type: 'string', title: '输入框1', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, bbb: { type: 'string', title: '输入框2', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ccc: { type: 'string', title: '输入框3', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ddd: { type: 'string', title: '输入框4', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, } return { schema, } }, render(h) { return ( <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={this.schema} /> <FormDrawer.Footer> <span style={{ marginLeft: '4px' }}>扩展文案</span> </FormDrawer.Footer> </FormLayout> ) }, } export default { components: { Button }, data() { return {} }, methods: { handleOpen() { FormDrawer('抽屉表单', DrawerForm) .open({ initialValues: { aaa: '123', }, }) .then((values) => { console.log('values', values) }) .catch((e) => { console.log(e) }) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-dialog/template.vue: -------------------------------------------------------------------------------- ```vue <template> <Button @click="handleOpen">点击打开表单</Button> </template> <script> import { FormDialog, FormLayout, FormItem, Input } from '@formily/element' import { Button } from 'element-ui' import { Field } from '@formily/vue' export default { components: { Button }, data() { return {} }, methods: { handleOpen() { FormDialog('弹框表单', () => ( <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]} /> <FormDialog.Footer> <span style={{ marginLeft: '4px' }}>扩展文案</span> </FormDialog.Footer> </FormLayout> )) .forOpen((payload, next) => { setTimeout(() => { next({ initialValues: { aaa: '123', }, }) }, 1000) }) .forConfirm((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .forCancel((payload, next) => { setTimeout(() => { console.log(payload) next(payload) }, 1000) }) .open() .then(console.log) .catch(console.error) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/src/__builtins__/shared/transform-component.ts: -------------------------------------------------------------------------------- ```typescript import type { Component } from 'vue' import { merge } from '@formily/shared' import { h } from '@formily/vue' import { isVue2, defineComponent } from 'vue-demi' type ListenersTransformRules = Record<string, string> const noop = () => {} export const transformComponent = <T extends Record<string, any>>( tag: any, transformRules?: ListenersTransformRules, defaultProps?: Partial<T> ): Component<T> | any => { if (isVue2) { return defineComponent({ setup(props, { attrs, slots, listeners }) { return () => { const data = { attrs: { ...attrs, }, on: { ...listeners, }, } if (transformRules) { const transformListeners = transformRules Object.keys(transformListeners).forEach((extract) => { if (data.on !== undefined) { data.on[transformListeners[extract]] = listeners[extract] || noop } }) } if (defaultProps) { data.attrs = merge(defaultProps, data.attrs) } return h(tag, data, slots) } }, }) } else { return defineComponent({ setup(props, { attrs, slots }) { return () => { let data = { ...attrs, } if (transformRules) { const listeners = transformRules Object.keys(listeners).forEach((extract) => { const event = listeners[extract] data[`on${event[0].toUpperCase()}${event.slice(1)}`] = attrs[`on${extract[0].toUpperCase()}${extract.slice(1)}`] || noop }) } if (defaultProps) { data = merge(defaultProps, data) } return h(tag, data, slots) } }, }) } } ``` -------------------------------------------------------------------------------- /packages/antd/src/date-picker/index.tsx: -------------------------------------------------------------------------------- ```typescript import moment from 'moment' import { connect, mapProps, mapReadPretty } from '@formily/react' import { DatePicker as AntdDatePicker } from 'antd' import { DatePickerProps as AntdDatePickerProps, RangePickerProps, } from 'antd/lib/date-picker' import { PreviewText } from '../preview-text' import { formatMomentValue, momentable } from '../__builtins__' type DatePickerProps<PickerProps> = Exclude< PickerProps, 'value' | 'onChange' > & { value: string onChange: (value: string | string[]) => void } type ComposedDatePicker = React.FC< React.PropsWithChildren<AntdDatePickerProps> > & { RangePicker?: React.FC<React.PropsWithChildren<RangePickerProps>> } const mapDateFormat = function () { const getDefaultFormat = (props: DatePickerProps<AntdDatePickerProps>) => { if (props['picker'] === 'month') { return 'YYYY-MM' } else if (props['picker'] === 'quarter') { return 'YYYY-\\QQ' } else if (props['picker'] === 'year') { return 'YYYY' } else if (props['picker'] === 'week') { return 'gggg-wo' } return props['showTime'] ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD' } return (props: any) => { const format = props['format'] || getDefaultFormat(props) const onChange = props.onChange return { ...props, format: format, value: momentable(props.value, format === 'gggg-wo' ? 'gggg-ww' : format), onChange: (value: moment.Moment | moment.Moment[]) => { if (onChange) { onChange(formatMomentValue(value, format)) } }, } } } export const DatePicker: ComposedDatePicker = connect( AntdDatePicker, mapProps(mapDateFormat()), mapReadPretty(PreviewText.DatePicker) ) DatePicker.RangePicker = connect( AntdDatePicker.RangePicker, mapProps(mapDateFormat()), mapReadPretty(PreviewText.DateRangePicker) ) export default DatePicker ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-drawer/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <Button @click="handleOpen">点击打开表单</Button> </template> <script> import { FormDrawer, FormLayout, FormItem, Input } from '@formily/element' import { Button } from 'element-ui' import { createSchemaField } from '@formily/vue' const { SchemaField } = createSchemaField({ components: { FormItem, Input, }, }) // 抽屉表单组件 const DrawerForm = { props: ['form'], data() { const schema = { type: 'object', properties: { aaa: { type: 'string', title: '输入框1', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, bbb: { type: 'string', title: '输入框2', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ccc: { type: 'string', title: '输入框3', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, ddd: { type: 'string', title: '输入框4', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, } return { schema, } }, render() { return ( <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={this.schema} /> <FormDrawer.Footer> <span style={{ marginLeft: '4px' }}>扩展文案</span> </FormDrawer.Footer> </FormLayout> ) }, } export default { components: { Button }, data() { return {} }, methods: { handleOpen() { FormDrawer('抽屉表单', DrawerForm) .forOpen((props, next) => { setTimeout(() => { next() }, 1000) }) .open({ initialValues: { aaa: '123', }, }) .then(console.log) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/react/docs/api/shared/connect.md: -------------------------------------------------------------------------------- ```markdown # connect ## Description Mainly used for non-intrusive access to third-party component libraries Formily ## Signature ```ts interface IComponentMapper<T extends React.FC> { (target: T): React.FC } interface connect<T extends React.FC> { (target: T, ...args: IComponentMapper<T>[]): React.FC } ``` The first parameter passed in is the component to be connected, the following parameters are component mappers, each mapper is a function, usually we will use the built-in [mapProps](/api/shared/map- props) and [mapReadPretty](/api/shared/map-read-pretty) mapper ## Example ```tsx import React, { useMemo } from 'react' import { createForm } from '@formily/core' import { FormProvider, FormConsumer, Field, connect, mapProps, } from '@formily/react' import { Input, Form, Button } 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, } } ) ) export default () => { const form = useMemo(() => createForm({ validateFirst: true })) return ( <FormProvider form={form}> <Form layout="vertical"> <Field name="name" title="Name" required decorator={[FormItem]} component={[Input, { placeholder: 'Please Input' }]} /> <code> <pre> <FormConsumer> {(form) => JSON.stringify(form.values, null, 2)} </FormConsumer> </pre> </code> <Button type="primary" onClick={() => { form.submit(console.log) }} > Submit </Button> </Form> </FormProvider> ) } ``` ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-grid/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaVoidField x-component="FormGrid" :x-component-props="{ maxColumns: 3, minColumns: 2, }" > <SchemaStringField name="aaa" title="aaa" x-decorator="FormItem" :x-decorator-props="{ gridSpan: 2 }" x-component="Input" /> <SchemaStringField name="bbb" title="bbb" x-decorator="FormItem" x-component="Input" /> <SchemaStringField name="ccc" title="ccc" x-decorator="FormItem" x-component="Input" /> <SchemaStringField name="ddd" title="ddd" x-decorator="FormItem" x-component="Input" /> <SchemaStringField name="eee" title="eee" x-decorator="FormItem" x-component="Input" /> <SchemaStringField name="fff" title="fff" x-decorator="FormItem" x-component="Input" /> <SchemaStringField name="ggg" title="ggg" x-decorator="FormItem" x-component="Input" /> </SchemaVoidField> </SchemaField> <Submit @submit="onSubmit">提交</Submit> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { createSchemaField, FormProvider } from '@formily/vue' import { FormItem, Input, Submit, FormGrid } from '@formily/element' const form = createForm() const fields = createSchemaField({ components: { FormItem, Input, FormGrid, }, }) export default { components: { FormProvider, ...fields, Submit }, data() { return { form, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/react/docs/api/components/RecordsScope.md: -------------------------------------------------------------------------------- ```markdown --- order: 10 --- # RecordsScope ## Description Standard scoped injection component for injecting the following built-in variables: - `$records` current record list data ## Signature ```ts interface IRecordsScopeProps { getRecords(): any[] } type RecordsScope = React.FC<React.PropsWithChildren<IRecordsScopeProps>> ``` ## Usage Any auto-incrementing list extension component should use RecordsScope internally to pass record scope variables. Components that have implemented this convention include: All components of the ArrayX family in @formily/antd and @formily/next ## Custom component extension use case ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, createSchemaField, RecordsScope } from '@formily/react' import { Input } from 'antd' const form = createForm() const MyCustomComponent = (props) => { return ( <RecordsScope getRecords={() => props.records}> {props.children} </RecordsScope> ) } const SchemaField = createSchemaField({ components: { Input, MyCustomComponent, }, }) export default () => ( <FormProvider form={form}> <SchemaField schema={{ type: 'object', properties: { records: { type: 'void', 'x-component': 'MyCustomComponent', 'x-component-props': { records: [ { name: 'Name', code: 'Code', }, ], }, properties: { input: { type: 'string', 'x-component': 'Input', 'x-value': '{{`' + '${$records[0].name} ' + '${$records[0].code}' + '`}}', }, }, }, }, }} ></SchemaField> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/element/src/submit/index.ts: -------------------------------------------------------------------------------- ```typescript import { IFormFeedback } from '@formily/core' import { observer } from '@formily/reactive-vue' import { h, useParentForm } from '@formily/vue' import { defineComponent } from 'vue-demi' import type { Button as ElButtonProps } from 'element-ui' import { Button as ElButton } from 'element-ui' export interface ISubmitProps extends ElButtonProps { onClick?: (e: MouseEvent) => any onSubmit?: (values: any) => any onSubmitSuccess?: (payload: any) => void onSubmitFailed?: (feedbacks: IFormFeedback[]) => void } export const Submit = observer( defineComponent<ISubmitProps>({ name: 'FSubmit', props: ['onClick', 'onSubmit', 'onSubmitSuccess', 'onSubmitFailed'], setup(props, { attrs, slots, listeners }) { const formRef = useParentForm() return () => { const { onClick = listeners?.click, onSubmit = listeners?.submit, onSubmitSuccess = listeners?.submitSuccess, onSubmitFailed = listeners?.submitFailed, } = props const form = formRef?.value return h( ElButton, { attrs: { nativeType: listeners?.submit ? 'button' : 'submit', type: 'primary', ...attrs, loading: attrs.loading !== undefined ? attrs.loading : form?.submitting, }, on: { ...listeners, click: (e: any) => { if (onClick) { if (onClick(e) === false) return } if (onSubmit) { form ?.submit(onSubmit as (e: any) => void) .then(onSubmitSuccess as (e: any) => void) .catch(onSubmitFailed as (e: any) => void) } }, }, }, slots ) } }, }) ) export default Submit ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/cascader/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form"> <SchemaField> <SchemaStringField name="address" title="地址选择" required x-decorator="FormItem" x-component="Cascader" :x-component-props="{ style: { width: '240px', }, }" /> </SchemaField> <Submit @submit="onSubmit">提交</Submit> </Form> </template> <script> import { createForm, onFieldReact } from '@formily/core' import { createSchemaField } from '@formily/vue' import { Form, FormItem, Cascader, Submit } from '@formily/element' import { action } from '@formily/reactive' import axios from 'axios' const transformAddress = (data = {}) => { return Object.entries(data).reduce((buf, [key, value]) => { if (typeof value === 'string') return buf.concat({ label: value, value: key, }) const { name, code, cities, districts } = value const _cities = transformAddress(cities) const _districts = transformAddress(districts) return buf.concat({ label: name, value: code, children: _cities.length ? _cities : _districts.length ? _districts : undefined, }) }, []) } const useAddress = (pattern) => { onFieldReact(pattern, (field) => { field.loading = true axios('//unpkg.com/china-location/dist/location.json') .then((res) => res.data) .then( action.bound((data) => { field.dataSource = transformAddress(data) field.loading = false }) ) }) } const form = createForm({ effects: () => { useAddress('address') }, }) const fields = createSchemaField({ components: { FormItem, Cascader, }, }) export default { components: { Form, ...fields, Submit }, data() { return { form, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/upload/template.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form" :label-col="4" :wrapper-col="10"> <ArrayField name="upload" title="上传" :decorator="[FormItem]" :component="[ Upload, { action: 'https://formily-vue.free.beeceptor.com/file', textContent: '上传', }, ]" required /> <ArrayField name="upload2" title="卡片上传" :decorator="[FormItem]" :component="[ Upload, { listType: 'picture-card', action: 'https://formily-vue.free.beeceptor.com/file', }, ]" required /> <ArrayField name="upload3" title="拖拽上传" :decorator="[FormItem]" :component="[ Upload, { action: 'https://formily-vue.free.beeceptor.com/file', textContent: '将文件拖到此处,或者点击上传', drag: true, }, ]" required /> <ArrayField name="custom" title="自定义按钮" :decorator="[FormItem]" :component="[ Upload, { action: 'https://formily-vue.free.beeceptor.com/file', }, ]" required ><UploadButton /></ArrayField> <FormButtonGroup align-form-item> <Submit @submit="onSubmit">提交</Submit> </FormButtonGroup> </Form> </template> <script> import { createForm } from '@formily/core' import { ArrayField } from '@formily/vue' import { Form, FormItem, Upload, Submit, FormButtonGroup, } from '@formily/element' import { Button } from 'element-ui' const UploadButton = { functional: true, render(h) { return h(Button, {}, '上传图片') }, } const form = createForm() export default { components: { UploadButton, Form, ArrayField, Submit, FormButtonGroup }, data() { return { FormItem, Upload, form, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-grid/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField :schema="schema" /> <Submit @submit="onSubmit">提交</Submit> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { createSchemaField, FormProvider } from '@formily/vue' import { FormItem, Input, Submit, FormGrid } from '@formily/element' const schema = { type: 'object', properties: { grid: { type: 'void', 'x-component': 'FormGrid', 'x-component-props': { minColumns: [4, 6, 10], }, properties: { aaa: { type: 'string', title: 'AAA', 'x-decorator': 'FormItem', 'x-component': 'Input', }, bbb: { type: 'string', title: 'BBB', 'x-decorator': 'FormItem', 'x-component': 'Input', }, ccc: { type: 'string', title: 'CCC', 'x-decorator': 'FormItem', 'x-component': 'Input', }, ddd: { type: 'string', title: 'DDD', 'x-decorator': 'FormItem', 'x-component': 'Input', }, eee: { type: 'string', title: 'EEE', 'x-decorator': 'FormItem', 'x-component': 'Input', }, fff: { type: 'string', title: 'FFF', 'x-decorator': 'FormItem', 'x-component': 'Input', }, ggg: { type: 'string', title: 'GGG', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, }, } const form = createForm() const { SchemaField } = createSchemaField({ components: { FormItem, Input, FormGrid, }, }) export default { components: { FormProvider, SchemaField, Submit }, data() { return { form, schema, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/core/src/models/Heart.ts: -------------------------------------------------------------------------------- ```typescript import { isStr, isArr, Subscribable } from '@formily/shared' import { LifeCycle } from './LifeCycle' import { IHeartProps } from '../types' export class Heart<Payload = any, Context = any> extends Subscribable { lifecycles: LifeCycle<Payload>[] = [] outerLifecycles: Map<any, LifeCycle<Payload>[]> = new Map() context: Context constructor({ lifecycles, context }: IHeartProps<Context> = {}) { super() this.lifecycles = this.buildLifeCycles(lifecycles || []) this.context = context } buildLifeCycles = (lifecycles: LifeCycle[]) => { return lifecycles.reduce((buf, item) => { if (item instanceof LifeCycle) { return buf.concat(item) } else { if (isArr(item)) { return this.buildLifeCycles(item) } else if (typeof item === 'object') { this.context = item return buf } return buf } }, []) } addLifeCycles = (id: any, lifecycles: LifeCycle[] = []) => { const observers = this.buildLifeCycles(lifecycles) if (observers.length) { this.outerLifecycles.set(id, observers) } } hasLifeCycles = (id: any) => { return this.outerLifecycles.has(id) } removeLifeCycles = (id: any) => { this.outerLifecycles.delete(id) } setLifeCycles = (lifecycles: LifeCycle[] = []) => { this.lifecycles = this.buildLifeCycles(lifecycles) } publish = <P, C>(type: any, payload?: P, context?: C) => { if (isStr(type)) { this.lifecycles.forEach((lifecycle) => { lifecycle.notify(type, payload, context || this.context) }) this.outerLifecycles.forEach((lifecycles) => { lifecycles.forEach((lifecycle) => { lifecycle.notify(type, payload, context || this.context) }) }) this.notify({ type, payload, }) } } clear = () => { this.lifecycles = [] this.outerLifecycles.clear() this.unsubscribe() } } ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/tracker.spec.ts: -------------------------------------------------------------------------------- ```typescript import { Tracker, observable } from '../' test('base tracker', () => { const obs = observable<any>({}) const fn = jest.fn() const view = () => { fn(obs.value) } const scheduler = () => { tracker.track(view) } const tracker = new Tracker(scheduler) tracker.track(view) obs.value = 123 expect(fn).nthCalledWith(1, undefined) expect(fn).nthCalledWith(2, 123) tracker.dispose() }) test('nested tracker', () => { const obs = observable<any>({}) const fn = jest.fn() const view = () => { obs.value = obs.value || 321 fn(obs.value) } const scheduler = () => { tracker.track(view) } const tracker = new Tracker(scheduler) tracker.track(view) expect(fn).toBeCalledTimes(1) expect(fn).nthCalledWith(1, 321) obs.value = 123 expect(fn).toBeCalledTimes(2) expect(fn).nthCalledWith(2, 123) tracker.dispose() }) test('tracker recollect dependencies', () => { const obs = observable<any>({ aa: 'aaa', bb: 'bbb', cc: 'ccc', }) const fn = jest.fn() const view = () => { fn() if (obs.aa === 'aaa') { return obs.bb } return obs.cc } const scheduler = () => { tracker.track(view) } const tracker = new Tracker(scheduler) tracker.track(view) obs.aa = '111' obs.bb = '222' expect(fn).toBeCalledTimes(2) tracker.dispose() }) test('shared scheduler with multi tracker(mock react strict mode)', () => { const obs = observable<any>({}) const component = () => obs.value const render = () => { tracker1.track(component) tracker2.track(component) } const scheduler1 = jest.fn(() => { tracker2.track(component) }) const scheduler2 = jest.fn(() => { tracker1.track(component) }) const tracker1 = new Tracker(scheduler1, 'tracker1') const tracker2 = new Tracker(scheduler2, 'tracker2') render() obs.value = 123 expect(scheduler1).toBeCalledTimes(1) expect(scheduler2).toBeCalledTimes(0) }) ``` -------------------------------------------------------------------------------- /packages/antd/src/password/index.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { connect, mapReadPretty } from '@formily/react' import { Input } from 'antd' import { PasswordProps } from 'antd/lib/input' import { PasswordStrength } from './PasswordStrength' import { PreviewText } from '../preview-text' export interface IPasswordProps extends PasswordProps { checkStrength: boolean } export const Password = connect((props: IPasswordProps) => { const { value, className, checkStrength, ...others } = props const blockStyle: React.CSSProperties = { position: 'absolute', zIndex: 1, height: 8, top: 0, background: '#fff', width: 1, transform: 'translate(-50%, 0)', } return ( <span className={className}> <Input.Password {...others} value={value} /> {checkStrength && ( <PasswordStrength value={String(value)}> {(score) => { return ( <div style={{ background: '#e0e0e0', marginBottom: 3, position: 'relative', }} > <div style={{ ...blockStyle, left: '20%' }} /> <div style={{ ...blockStyle, left: '40%' }} /> <div style={{ ...blockStyle, left: '60%' }} /> <div style={{ ...blockStyle, left: '80%' }} /> <div style={{ position: 'relative', backgroundImage: '-webkit-linear-gradient(left, #ff5500, #ff9300)', transition: 'all 0.35s ease-in-out', height: 8, width: '100%', marginTop: 5, clipPath: `polygon(0 0,${score}% 0,${score}% 100%,0 100%)`, }} /> </div> ) }} </PasswordStrength> )} </span> ) }, mapReadPretty(PreviewText.Input)) export default Password ``` -------------------------------------------------------------------------------- /packages/react/docs/api/hooks/useField.md: -------------------------------------------------------------------------------- ```markdown # useField ## Description Mainly used in custom components to read current field properties, manipulate field status, etc. It can be used in the subtree of all Field components. Note that the one you get is [GeneralField](https://core.formilyjs.org/ api/models/field#generalfield), if you need to process different types of fields, please use [Type Checker](https://core.formilyjs.org/api/entry/form-checker) <Alert> Note: If you want to use useField in a custom component and respond to changes in the field model, you need to wrap the observer for the custom component </Alert> ## Signature ```ts interface useField { (): Field } ``` ## Example ```tsx import React, { useMemo } from 'react' import { createForm } from '@formily/core' import { FormProvider, FormConsumer, Field, useField, observer, } from '@formily/react' import { Input, Form, Button } from 'antd' // FormItem UI component const FormItem = observer(({ children }) => { const field = useField() return ( <Form.Item label={field.title} help={field.selfErrors?.length ? field.selfErrors : undefined} extra={field.description} validateStatus={field.validateStatus} > {children} </Form.Item> ) }) export default () => { const form = useMemo(() => createForm({ validateFirst: true })) return ( <FormProvider form={form}> <Form layout="vertical"> <Field name="name" title="Name" required decorator={[FormItem]} component={[Input, { placeholder: 'Please Input' }]} /> <code> <pre> <FormConsumer> {(form) => JSON.stringify(form.values, null, 2)} </FormConsumer> </pre> </code> <Button type="primary" onClick={() => { form.submit(console.log) }} > Submit </Button> </Form> </FormProvider> ) } ``` ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/preview-text/base.vue: -------------------------------------------------------------------------------- ```vue <template> <FormLayout :labelCol="6" :wrapperCol="10"> <FormProvider :form="form"> <SchemaField> <SchemaStringField x-decorator="FormItem" title="文本预览" x-component="PreviewText.Input" default="Hello world" /> <SchemaStringField x-decorator="FormItem" title="选择项预览" x-component="PreviewText.Select" :x-component-props="{ multiple: true, }" :default="['123', '222']" :enum="[ { label: 'A111', value: '123' }, { label: 'A222', value: '222', }, ]" /> <SchemaStringField x-decorator="FormItem" title="日期预览" x-component="PreviewText.DatePicker" default="2020-11-23 22:15:20" /> <SchemaStringField x-decorator="FormItem" title="时间预览" x-component="PreviewText.TimePicker" :default="['2020-11-23 22:15:20', '2020-11-23 23:15:20']" /> <SchemaStringField x-decorator="FormItem" title="Cascader预览" x-component="PreviewText.Cascader" :default="['hangzhou', 'yuhang']" :enum="[ { label: '杭州', value: 'hangzhou' }, { label: '余杭', value: 'yuhang' }, ]" /> </SchemaField> </FormProvider> </FormLayout> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormLayout, FormItem, PreviewText } from '@formily/element' const fields = createSchemaField({ components: { FormItem, PreviewText, }, }) export default { components: { FormProvider, FormLayout, ...fields, }, data() { const form = createForm() return { form, } }, methods: { log(v) { console.log(v) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-tabs/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField :schema="schema" /> <Submit @submit="log">提交</Submit> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormButtonGroup, Submit, FormItem, Space, Input, ArrayTabs, } from '@formily/element' import { Button } from 'element-ui' const SchemaField = createSchemaField({ components: { FormItem, Space, Input, ArrayTabs, }, }) export default { components: { FormProvider, FormButtonGroup, Button, Submit, ...SchemaField, }, data() { const form = createForm() const schema = { type: 'object', properties: { string_array: { type: 'array', title: '字符串数组', 'x-decorator': 'FormItem', maxItems: 3, 'x-component': 'ArrayTabs', items: { type: 'string', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, array: { type: 'array', title: '对象数组', 'x-decorator': 'FormItem', maxItems: 3, 'x-component': 'ArrayTabs', items: { type: 'object', properties: { aaa: { type: 'string', 'x-decorator': 'FormItem', title: 'AAA', required: true, 'x-component': 'Input', }, bbb: { type: 'string', 'x-decorator': 'FormItem', title: 'BBB', required: true, 'x-component': 'Input', }, }, }, }, }, } return { form, schema, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/hooks/use-field.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <Form layout="vertical"> <Field name="name" title="Name" required :decorator="[FormItem]" :component="[Input, { placeholder: 'Please Input' }]" /> <FormConsumer> <template #default="{ form }"> <div style="white-space: pre; margin-bottom: 16px"> {{ JSON.stringify(form.values, null, 2) }} </div> <Button type="primary" @click=" () => { form.submit(log) } " > Submit </Button> </template> </FormConsumer> </Form> </FormProvider> </template> <script> import { defineComponent, h } from '@vue/composition-api' import { Form, Input, Button } from 'ant-design-vue' import { createForm, setValidateLanguage } from '@formily/core' import { FormProvider, FormConsumer, Field, useField } from '@formily/vue' import { observer } from '@formily/reactive-vue' import 'ant-design-vue/dist/antd.css' setValidateLanguage('en') const FormItem = observer( defineComponent({ setup(props, { slots }) { const fieldRef = useField() return () => { const field = fieldRef.value return h( Form.Item, { props: { label: field.title, required: field.required, help: field.selfErrors?.length ? field.selfErrors : undefined, extra: field.description, validateStatus: field.validateStatus, }, }, slots?.default() ) } }, }) ) export default { components: { FormProvider, FormConsumer, Field, Form, Button, }, data() { const form = createForm({ validateFirst: true }) return { FormItem, Input, form, } }, methods: { log(...args) { console.log(...args) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/reactive/src/tree.ts: -------------------------------------------------------------------------------- ```typescript import { ObModelSymbol, ObModelNodeSymbol, RawNode } from './environment' import { raw as getRaw } from './externals' import { PropertyKey, IOperation } from './types' export class DataChange { node: DataNode key: PropertyKey object: object type: string value: any oldValue: any constructor(operation: IOperation, node: DataNode) { this.node = node this.key = operation.key this.type = operation.type this.object = operation.target this.value = operation.value this.oldValue = operation.oldValue } get path() { return this.node.path.concat(this.key) } } export class DataNode { target: any key: PropertyKey value: any constructor(target: any, key: PropertyKey, value: any) { this.target = target this.key = key this.value = value } get path() { if (!this.parent) return this.key ? [this.key] : [] return this.parent.path.concat(this.key) } get targetRaw() { return getRaw(this.target) } get parent() { if (!this.target) return return getDataNode(this.targetRaw) } isEqual(node: DataNode) { if (this.key) { return node.targetRaw === this.targetRaw && node.key === this.key } return node.value === this.value } contains(node: DataNode) { if (node === this) return true let parent = node.parent while (!!parent) { if (this.isEqual(parent)) return true parent = parent.parent } return false } } export const getDataNode = (raw: any) => { if (raw?.[ObModelNodeSymbol]) { return raw[ObModelNodeSymbol] } return RawNode.get(raw) } export const setDataNode = (raw: any, node: DataNode) => { if (raw?.[ObModelSymbol]) { raw[ObModelNodeSymbol] = node return } RawNode.set(raw, node) } export const buildDataTree = (target: any, key: PropertyKey, value: any) => { const raw = getRaw(value) const currentNode = getDataNode(raw) if (currentNode) return currentNode setDataNode(raw, new DataNode(target, key, value)) } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/editable/template.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <Field name="date" title="日期" :decorator="[Editable]" :component="[DatePicker]" /> <Field name="input" title="输入框" :decorator="[Editable]" :component="[Input]" /> <VoidField name="void" title="虚拟节点容器" :component="[Editable.Popover]" :reactions=" (field) => { field.title = field.query('.void.date2').get('value') || field.title } " > <Field name="date2" title="日期" :decorator="[FormItem]" :component="[DatePicker]" /> <Field name="input2" title="输入框" :decorator="[FormItem]" :component="[Input]" /> </VoidField> <ObjectField name="iobject" title="对象节点容器" :component="[Editable.Popover]" :reactions=" (field) => { field.title = (field.value && field.value.date) || field.title } " > <Field name="date" title="日期" :decorator="[FormItem]" :component="[DatePicker]" /> <Field name="input" title="输入框" :decorator="[FormItem]" :component="[Input]" /> </ObjectField> <FormButtonGroup> <Submit @submit="log">提交</Submit> </FormButtonGroup> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, Field, VoidField, ObjectField } from '@formily/vue' import { FormButtonGroup, FormItem, Submit, Input, DatePicker, Editable, } from '@formily/element' export default { components: { FormButtonGroup, FormProvider, Submit, Field, VoidField, ObjectField, }, data() { const form = createForm() return { FormItem, Input, DatePicker, Editable, form, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-item/inset.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form"> <SchemaField> <SchemaStringField name="input" title="Input" x-decorator="FormItem" x-component="Input" required :x-decorator-props="{ inset: true, }" /> <SchemaStringField name="Select" title="Select" x-decorator="FormItem" x-component="Select" required :x-decorator-props="{ inset: true, }" /> <SchemaStringField name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required :x-decorator-props="{ inset: true, }" /> <SchemaStringField name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required :x-decorator-props="{ inset: true, }" /> <SchemaStringField name="InputNumber" title="InputNumber" x-decorator="FormItem" x-component="InputNumber" required :x-decorator-props="{ inset: true, }" /> <SchemaBooleanField name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required :x-decorator-props="{ inset: true, }" /> </SchemaField> </Form> </template> <script> import { createForm } from '@formily/core' import { createSchemaField } from '@formily/vue' import { Form, FormItem, Input, Select, Cascader, DatePicker, Switch, InputNumber, } from '@formily/element' const form = createForm() const fields = createSchemaField({ components: { FormItem, Input, Select, Cascader, DatePicker, Switch, InputNumber, }, }) export default { components: { Form, ...fields }, data() { return { form, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Switch.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Switch > 开关组件 ## Markup Schema 案例 ```tsx import React from 'react' import { Switch, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Switch, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Boolean name="switch" title="开关" x-decorator="FormItem" x-component="Switch" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Switch, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Switch, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { switch: { type: 'boolean', title: '开关', 'x-decorator': 'FormItem', 'x-component': 'Switch', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Switch, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="switch" title="开关" decorator={[FormItem]} component={[Switch]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://ant.design/components/switch-cn/ ``` -------------------------------------------------------------------------------- /packages/core/src/shared/effective.ts: -------------------------------------------------------------------------------- ```typescript import { isFn, isValid } from '@formily/shared' import { LifeCycle, Form } from '../models' import { AnyFunction } from '../types' import { isForm } from './checkers' import { GlobalState } from './constants' export const createEffectHook = < F extends (payload: any, ...ctxs: any[]) => AnyFunction >( type: string, callback?: F ) => { return (...args: Parameters<ReturnType<F>>) => { if (GlobalState.effectStart) { GlobalState.lifecycles.push( new LifeCycle(type, (payload, ctx) => { if (isFn(callback)) { callback(payload, ctx, ...GlobalState.context)(...args) } }) ) } else { throw new Error( 'Effect hooks cannot be used in asynchronous function body' ) } } } export const createEffectContext = <T = any>(defaultValue?: T) => { let index: number return { provide(value?: T) { if (GlobalState.effectStart) { index = GlobalState.context.length GlobalState.context[index] = isValid(value) ? value : defaultValue } else { throw new Error( 'Provide method cannot be used in asynchronous function body' ) } }, consume(): T { if (!GlobalState.effectStart) { throw new Error( 'Consume method cannot be used in asynchronous function body' ) } return GlobalState.context[index] }, } } const FormEffectContext = createEffectContext<Form>() export const useEffectForm = FormEffectContext.consume export const runEffects = <Context>( context?: Context, ...args: ((context: Context) => void)[] ): LifeCycle[] => { GlobalState.lifecycles = [] GlobalState.context = [] GlobalState.effectStart = true GlobalState.effectEnd = false if (isForm(context)) { FormEffectContext.provide(context) } args.forEach((effects) => { if (isFn(effects)) { effects(context) } }) GlobalState.context = [] GlobalState.effectStart = false GlobalState.effectEnd = true return GlobalState.lifecycles } ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Switch.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Switch > 开关组件 ## Markup Schema 案例 ```tsx import React from 'react' import { Switch, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Switch, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Boolean name="switch" title="开关" x-decorator="FormItem" x-component="Switch" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Switch, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Switch, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { switch: { type: 'boolean', title: '开关', 'x-decorator': 'FormItem', 'x-component': 'Switch', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Switch, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="switch" title="开关" decorator={[FormItem]} component={[Switch]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://fusion.design/pc/component/basic/switch ``` -------------------------------------------------------------------------------- /packages/core/docs/guide/index.md: -------------------------------------------------------------------------------- ```markdown # Introduction ## Pure Core,No UI Because @formily/core exists as an independent package, its core meaning is to separate the domain model from the UI framework, and at the same time, it can bring the following two intuitive benefits to developers: 1. It is convenient for formily developers to release from the coupling relationship between UI and logic, and improve code maintainability; 2. Allow formily to have cross-terminal and cross-framework capabilities. Whether you are a React user, Vue user or Angular user, you can enjoy the efficiency improvement brought by formily's domain model. ## Ultra high performance With the help of @formily/reactive, @formily/core naturally gains the ability to track dependencies, update efficiently, and render on-demand. Whether it is under React or Vue/Angular, whether it is frequent field input or field linkage, it can give Users bring O(1) performance experience, developers do not need to care about performance optimization, only need to focus on business logic implementation. ## Domain Model If we break down the form question, we can actually break it down: - Data management issues - Field management issues - Calibration management issues - Linkage management issues The problems in these directions can actually be solved as domain-level problems. Each domain problem is actually a very complex problem. In Formily, all of them are solved by breakthroughs, so you only need to focus on business logic. That's it. ## Smart tips Because formily is a complete Typescript project, users can develop on VSCode or WebStorm to get the maximum intelligent prompt experience  ## Status observable Install [FormilyDevtools](https://chrome.google.com/webstore/detail/formily-devtools/kkocalmbfnplecdmbadaapgapdioecfm?hl=zh-CN) to observe the model status changes in real time and troubleshoot problems  ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-item/bordered-none.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form"> <SchemaField> <SchemaStringField name="input" title="Input" x-decorator="FormItem" x-component="Input" required :x-decorator-props="{ bordered: false, }" /> <SchemaStringField name="Select" title="Select" x-decorator="FormItem" x-component="Select" required :x-decorator-props="{ bordered: false, }" /> <SchemaStringField name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required :x-decorator-props="{ bordered: false, }" /> <SchemaStringField name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required :x-decorator-props="{ bordered: false, }" /> <SchemaStringField name="InputNumber" title="InputNumber" x-decorator="FormItem" x-component="InputNumber" required :x-decorator-props="{ bordered: false, }" /> <SchemaBooleanField name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required :x-decorator-props="{ bordered: false, }" /> </SchemaField> </Form> </template> <script> import { createForm } from '@formily/core' import { createSchemaField } from '@formily/vue' import { Form, FormItem, Input, Select, Cascader, DatePicker, Switch, InputNumber, } from '@formily/element' const form = createForm() const fields = createSchemaField({ components: { FormItem, Input, Select, Cascader, DatePicker, Switch, InputNumber, }, }) export default { components: { Form, ...fields }, data() { return { form, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/recursion-field-with-component.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaArrayField name="custom" x-component="ArrayItems"> <SchemaObjectField> <SchemaStringField name="input" x-component="Input" /> </SchemaObjectField> </SchemaArrayField> </SchemaField> </FormProvider> </template> <script> import { defineComponent, h } from '@vue/composition-api' // or "import { defineComponent, h } from 'vue'" if using vue3 import { Input, Button, Space } from 'ant-design-vue' import { createForm } from '@formily/core' import { FormProvider, createSchemaField, RecursionField, useField, useFieldSchema, } from '@formily/vue' import { observer } from '@formily/reactive-vue' import 'ant-design-vue/dist/antd.css' const ArrayItems = observer( defineComponent({ setup() { const fieldRef = useField() const schemaRef = useFieldSchema() return () => { const field = fieldRef.value const schema = schemaRef.value const items = field.value?.map((item, index) => { return h('div', { key: item.id, style: { marginBottom: '10px' } }, [ h(Space, [ // params of render function is different in vue3 h(RecursionField, { props: { schema: schema.items, name: index }, }), h(Button, { on: { click: () => field.remove(index) } }, [ 'Remove', ]), ]), ]) }) const button = h( Button, { on: { click: () => field.push({ id: Date.now() }) } }, ['Add'] ) return h('div', [items, button]) } }, }) ) const { SchemaField, SchemaStringField, SchemaArrayField, SchemaObjectField } = createSchemaField({ components: { ArrayItems, Input, }, }) export default { components: { FormProvider, SchemaField, SchemaStringField, SchemaArrayField, SchemaObjectField, }, data() { return { form: createForm(), } }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/select/markup-schema-async-search.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaStringField name="select" title="异步搜索选择框" x-decorator="FormItem" x-component="Select" :x-component-props="{ filterable: true, remote: true, style: { width: '240px', }, }" /> </SchemaField> <Submit @submit="log">提交</Submit> </FormProvider> </template> <script> import { createForm, onFieldInit, onFieldReact } from '@formily/core' import { action, observable } from '@formily/reactive' import { createSchemaField, FormProvider } from '@formily/vue' import { FormItem, Select, Submit } from '@formily/element' let timeout let currentValue function fetchData(value, callback) { if (timeout) { clearTimeout(timeout) timeout = null } currentValue = value function fake() { callback([ { label: 'AAA', value: 'aaa', }, { label: 'BBB', value: 'ccc', }, ]) } timeout = setTimeout(fake, 300) } const useAsyncDataSource = (pattern, service) => { const keyword = observable.ref('') onFieldInit(pattern, (field) => { field.setComponentProps({ remoteMethod: (value) => { keyword.value = value }, }) }) onFieldReact(pattern, (field) => { field.loading = true service({ field, keyword: keyword.value }).then( action.bound((data) => { field.dataSource = data field.loading = false }) ) }) } const form = createForm({ effects: () => { useAsyncDataSource('select', async ({ keyword }) => { if (!keyword) { return [] } return new Promise((resolve) => { fetchData(keyword, resolve) }) }) }, }) const fields = createSchemaField({ components: { FormItem, Select, }, }) export default { components: { FormProvider, ...fields, Submit }, data() { return { form, } }, methods: { log(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/next/docs/components/TimePicker.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # TimePicker > 时间选择器 ## Markup Schema 案例 ```tsx import React from 'react' import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="time" title="时间" x-decorator="FormItem" x-component="TimePicker" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { TimePicker, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { time: { title: '时间', 'x-decorator': 'FormItem', 'x-component': 'TimePicker', type: 'string', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="time" title="时间" decorator={[FormItem]} component={[TimePicker]} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API 参考 https://fusion.design/pc/component/basic/time-picker ``` -------------------------------------------------------------------------------- /packages/element/src/select/index.ts: -------------------------------------------------------------------------------- ```typescript import { connect, h, mapProps, mapReadPretty } from '@formily/vue' import { defineComponent } from 'vue-demi' import { PreviewText } from '../preview-text' import type { Option as ElOptionProps, Select as ElSelectProps, } from 'element-ui' import { Option as ElOption, Select as ElSelect } from 'element-ui' import { resolveComponent } from '../__builtins__' export type SelectProps = ElSelectProps & { options?: Array<ElOptionProps> } const SelectOption = defineComponent<SelectProps>({ name: 'FSelect', props: ['options'], setup(customProps, { attrs, slots, listeners }) { return () => { const options = customProps.options || [] const children = options.length !== 0 ? { default: () => options.map((option) => { if (typeof option === 'string') { return h( ElOption, { props: { value: option, label: option } }, { default: () => [ resolveComponent(slots?.option, { option }), ], } ) } else { return h( ElOption, { props: { ...option, }, }, { default: () => [ resolveComponent(slots?.option, { option, }), ], } ) } }), } : slots return h( ElSelect, { attrs: { ...attrs, }, on: listeners, }, children ) } }, }) export const Select = connect( SelectOption, mapProps({ dataSource: 'options', loading: true }), mapReadPretty(PreviewText.Select) ) export default Select ``` -------------------------------------------------------------------------------- /packages/next/package.json: -------------------------------------------------------------------------------- ```json { "name": "@formily/next", "version": "2.3.7", "license": "MIT", "main": "lib", "umd:main": "dist/formily.next.umd.production.js", "unpkg": "dist/formily.next.umd.production.js", "jsdelivr": "dist/formily.next.umd.production.js", "jsnext:main": "esm", "module": "esm", "sideEffects": [ "dist/*", "esm/*.js", "lib/*.js", "src/*.ts", "*.scss", "**/*/style.js" ], "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 create:style && npm run build:cjs && npm run build:esm && npm run build:umd && npm run build:style", "create:style": "ts-node create-style", "build:style": "ts-node build-style", "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": { "@alifd/next": "^1.19.0", "@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": { "@umijs/plugin-sass": "^1.1.1", "dumi": "^1.1.0-rc.8" }, "dependencies": { "@formily/core": "2.3.7", "@formily/grid": "2.3.7", "@formily/json-schema": "2.3.7", "@formily/react": "2.3.7", "@formily/reactive": "2.3.7", "@formily/reactive-react": "2.3.7", "@formily/shared": "2.3.7", "classnames": "^2.2.6", "react-sortable-hoc": "^1.11.0", "react-sticky-box": "^0.9.3" }, "publishConfig": { "access": "public" }, "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" } ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Switch.md: -------------------------------------------------------------------------------- ```markdown # Switch > Switch Components ## Markup Schema example ```tsx import React from 'react' import { Switch, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Switch, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Boolean name="switch" title="Switch" x-decorator="FormItem" x-component="Switch" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema case ```tsx import React from 'react' import { Switch, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Switch, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { switch: { type: 'boolean', title: 'Switch', 'x-decorator': 'FormItem', 'x-component': 'Switch', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Pure JSX case ```tsx import React from 'react' import { Switch, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="switch" title="Switch" decorator={[FormItem]} component={[Switch]} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API Reference https://ant.design/components/switch-cn/ ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/upload/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form" :label-col="4" :wrapper-col="10"> <SchemaField> <SchemaArrayField name="upload" title="上传" x-decorator="FormItem" x-component="Upload" :x-component-props="{ action: 'https://formily-vue.free.beeceptor.com/file', textContent: '上传', }" required /> <SchemaArrayField name="upload2" title="卡片上传" x-decorator="FormItem" x-component="Upload" :x-component-props="{ listType: 'picture-card', action: 'https://formily-vue.free.beeceptor.com/file', }" required /> <SchemaArrayField name="upload3" title="拖拽上传" x-decorator="FormItem" x-component="Upload" :x-component-props="{ action: 'https://formily-vue.free.beeceptor.com/file', textContent: '将文件拖到此处,或者点击上传', drag: true, }" required /> <SchemaArrayField name="custom" title="自定义按钮" x-decorator="FormItem" x-component="Upload" :x-component-props="{ action: 'https://formily-vue.free.beeceptor.com/file', }" required :x-content="UploadButton" /> </SchemaField> <FormButtonGroup align-form-item> <Submit @submit="onSubmit">提交</Submit> </FormButtonGroup> </Form> </template> <script> import { createForm } from '@formily/core' import { createSchemaField } from '@formily/vue' import { Form, FormItem, Upload, Submit, FormButtonGroup, } from '@formily/element' import { Button } from 'element-ui' const UploadButton = { functional: true, render(h) { return h(Button, {}, '上传图片') }, } const form = createForm() const fields = createSchemaField({ components: { FormItem, Upload, }, }) export default { components: { Form, ...fields, Submit, FormButtonGroup }, data() { return { UploadButton, form, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/cascader/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form"> <SchemaField :schema="schema" :scope="{ useAsyncDataSource, transformAddress }" /> <Submit @submit="onSubmit">提交</Submit> </Form> </template> <script> import { createForm } from '@formily/core' import { createSchemaField } from '@formily/vue' import { Form, FormItem, Cascader, Submit } from '@formily/element' import { action } from '@formily/reactive' import axios from 'axios' const transformAddress = (data = {}) => { return Object.entries(data).reduce((buf, [key, value]) => { if (typeof value === 'string') return buf.concat({ label: value, value: key, }) const { name, code, cities, districts } = value const _cities = transformAddress(cities) const _districts = transformAddress(districts) return buf.concat({ label: name, value: code, children: _cities.length ? _cities : _districts.length ? _districts : undefined, }) }, []) } const useAsyncDataSource = (url, transform) => { return (field) => { field.loading = true axios .get(url) .then((res) => res.data) .then( action.bound((data) => { field.dataSource = transform(data) field.loading = false }) ) } } const schema = { type: 'object', properties: { cascader: { type: 'string', title: '地址选择', 'x-decorator': 'FormItem', 'x-component': 'Cascader', 'x-component-props': { style: { width: '240px', }, }, 'x-reactions': [ '{{useAsyncDataSource("//unpkg.com/china-location/dist/location.json",transformAddress)}}', ], }, }, } const form = createForm() const { SchemaField } = createSchemaField({ components: { FormItem, Cascader, }, }) export default { components: { Form, SchemaField, Submit }, data() { return { useAsyncDataSource, transformAddress, form, schema, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ```