This is page 6 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /scripts/build-style/copy.ts: -------------------------------------------------------------------------------- ```typescript 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | import { copy, readFile, writeFile, existsSync } from 'fs-extra' 3 | import glob from 'glob' 4 | 5 | export type CopyBaseOptions = Record<'esStr' | 'libStr', string> 6 | 7 | const importLibToEs = async ({ 8 | libStr, 9 | esStr, 10 | filename, 11 | }: CopyBaseOptions & { filename: string }) => { 12 | if (!existsSync(filename)) { 13 | return Promise.resolve() 14 | } 15 | 16 | const fileContent: string = (await readFile(filename)).toString() 17 | 18 | return writeFile( 19 | filename, 20 | fileContent.replace(new RegExp(libStr, 'g'), esStr) 21 | ) 22 | } 23 | 24 | export const runCopy = ({ 25 | resolveForItem, 26 | ...lastOpts 27 | }: CopyBaseOptions & { resolveForItem?: (filename: string) => unknown }) => { 28 | return new Promise((resolve, reject) => { 29 | glob(`./src/**/*`, (err, files) => { 30 | if (err) { 31 | return reject(err) 32 | } 33 | 34 | const all = [] as Promise<unknown>[] 35 | 36 | for (let i = 0; i < files.length; i += 1) { 37 | const filename = files[i] 38 | 39 | resolveForItem?.(filename) 40 | 41 | if (/\.(less|scss)$/.test(filename)) { 42 | all.push(copy(filename, filename.replace(/src\//, 'esm/'))) 43 | all.push(copy(filename, filename.replace(/src\//, 'lib/'))) 44 | 45 | continue 46 | } 47 | 48 | if (/\/style.ts$/.test(filename)) { 49 | importLibToEs({ 50 | ...lastOpts, 51 | filename: filename.replace(/src\//, 'esm/').replace(/\.ts$/, '.js'), 52 | }) 53 | 54 | continue 55 | } 56 | } 57 | }) 58 | }) 59 | } 60 | ``` -------------------------------------------------------------------------------- /packages/vue/src/__tests__/expression.scope.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { render } from '@testing-library/vue' 2 | import { createForm } from '@formily/core' 3 | import { FormProvider, ExpressionScope, createSchemaField, h } from '..' 4 | import { defineComponent } from '@vue/composition-api' 5 | 6 | test('expression scope', async () => { 7 | const Container = defineComponent({ 8 | setup(_props, { slots }) { 9 | return () => 10 | h( 11 | ExpressionScope, 12 | { 13 | props: { value: { $innerScope: 'inner scope value' } }, 14 | }, 15 | slots 16 | ) 17 | }, 18 | }) 19 | const Input = defineComponent({ 20 | props: ['text'], 21 | setup(props) { 22 | return () => 23 | h( 24 | 'div', 25 | { attrs: { 'data-testid': 'test-input' } }, 26 | { default: () => props.text } 27 | ) 28 | }, 29 | }) 30 | const SchemaField = createSchemaField({ 31 | components: { Container, Input }, 32 | }) 33 | const form = createForm() 34 | const { getByTestId } = render({ 35 | components: { ...SchemaField, FormProvider }, 36 | data() { 37 | return { form } 38 | }, 39 | template: `<FormProvider :form="form"> 40 | <SchemaField :scope="{ $outerScope: 'outer scope value' }"> 41 | <SchemaVoidField x-component="Container"> 42 | <SchemaVoidField 43 | name="div" 44 | x-component="Input" 45 | :x-component-props='{ text: "{{$innerScope + $outerScope}}"}' 46 | /> 47 | </SchemaVoidField> 48 | </SchemaField> 49 | </FormProvider>`, 50 | }) 51 | 52 | expect(getByTestId('test-input').textContent).toBe( 53 | 'inner scope valueouter scope value' 54 | ) 55 | }) 56 | ``` -------------------------------------------------------------------------------- /packages/react/docs/api/shared/context.md: -------------------------------------------------------------------------------- ```markdown 1 | # context 2 | 3 | ## Description 4 | 5 | All React Context of @formily/react is convenient for users to do more complex personalized customization. We can consume these contexts through useContext 6 | 7 | ## FormContext 8 | 9 | #### Description 10 | 11 | Form context, you can get the current Form instance 12 | 13 | #### Signature 14 | 15 | ```ts 16 | import { Form } from '@formily/core' 17 | 18 | const FormContext = createContext<Form>(null) 19 | ``` 20 | 21 | ## FieldContext 22 | 23 | #### Description 24 | 25 | Field context, you can get the current field instance 26 | 27 | #### Signature 28 | 29 | ```ts 30 | import { GeneralField } from '@formily/core' 31 | 32 | const FieldContext = createContext<GeneralField>(null) 33 | ``` 34 | 35 | ## SchemaMarkupContext 36 | 37 | #### Description 38 | 39 | Schema tag context, mainly used to collect Schema tags written in JSX Markup, and then convert them into standard JSON Schema 40 | 41 | #### Signature 42 | 43 | ```ts 44 | SchemaMarkupContext = createContext<Schema>(null) 45 | ``` 46 | 47 | ## SchemaContext 48 | 49 | #### Description 50 | 51 | Field Schema context, mainly used to obtain the Schema information of the current field 52 | 53 | #### Signature 54 | 55 | ```ts 56 | const SchemaContext = createContext<Schema>(null) 57 | ``` 58 | 59 | ## SchemaExpressionScopeContext 60 | 61 | #### Description 62 | 63 | Schema expression scope context 64 | 65 | #### Signature 66 | 67 | ```ts 68 | export const SchemaExpressionScopeContext = createContext<any>(null) 69 | ``` 70 | 71 | ## SchemaOptionsContext 72 | 73 | #### Description 74 | 75 | Schema global parameter context, mainly used to obtain the parameters passed in from createSchemaField 76 | 77 | #### Signature 78 | 79 | ```ts 80 | const SchemaOptionsContext = createContext<ISchemaFieldFactoryOptions>(null) 81 | ``` 82 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-drawer/template.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <Button @click="handleOpen">点击打开表单</Button> 3 | </template> 4 | 5 | <script> 6 | import { FormDrawer, FormLayout, FormItem, Input } from '@formily/element' 7 | import { Button } from 'element-ui' 8 | import { Field } from '@formily/vue' 9 | 10 | export default { 11 | components: { Button }, 12 | data() { 13 | return {} 14 | }, 15 | methods: { 16 | handleOpen() { 17 | FormDrawer('抽屉表单', () => ( 18 | <FormLayout labelCol={6} wrapperCol={10}> 19 | <Field 20 | name="aaa" 21 | required 22 | title="输入框1" 23 | decorator={[FormItem]} 24 | component={[Input]} 25 | /> 26 | <Field 27 | name="bbb" 28 | required 29 | title="输入框2" 30 | decorator={[FormItem]} 31 | component={[Input]} 32 | /> 33 | <Field 34 | name="ccc" 35 | required 36 | title="输入框3" 37 | decorator={[FormItem]} 38 | component={[Input]} 39 | /> 40 | <Field 41 | name="ddd" 42 | required 43 | title="输入框4" 44 | decorator={[FormItem]} 45 | component={[Input]} 46 | /> 47 | <FormDrawer.Footer> 48 | <span style={{ marginLeft: '4px' }}>扩展文案</span> 49 | </FormDrawer.Footer> 50 | </FormLayout> 51 | )) 52 | .open({ 53 | initialValues: { 54 | aaa: '123', 55 | }, 56 | }) 57 | .then((values) => { 58 | console.log('values', values) 59 | }) 60 | .catch((e) => { 61 | console.log(e) 62 | }) 63 | }, 64 | }, 65 | } 66 | </script> 67 | ``` -------------------------------------------------------------------------------- /packages/element/src/reset/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { IFieldResetOptions } from '@formily/core' 2 | import { observer } from '@formily/reactive-vue' 3 | import { h, useParentForm } from '@formily/vue' 4 | import { defineComponent } from 'vue-demi' 5 | 6 | import type { Button as IElButton } from 'element-ui' 7 | import { Button as ElButton } from 'element-ui' 8 | 9 | export type ResetProps = IFieldResetOptions & IElButton 10 | 11 | export const Reset = observer( 12 | defineComponent<ResetProps>({ 13 | name: 'FReset', 14 | props: { 15 | forceClear: { 16 | type: Boolean, 17 | default: false, 18 | }, 19 | validate: { 20 | type: Boolean, 21 | default: false, 22 | }, 23 | }, 24 | setup(props, context) { 25 | const formRef = useParentForm() 26 | const { listeners, slots } = context 27 | return () => { 28 | const form = formRef?.value 29 | return h( 30 | ElButton, 31 | { 32 | attrs: context.attrs, 33 | on: { 34 | ...listeners, 35 | click: (e: any) => { 36 | if (listeners?.click) { 37 | if (listeners.click(e) === false) return 38 | } 39 | form 40 | ?.reset('*', { 41 | forceClear: props.forceClear, 42 | validate: props.validate, 43 | }) 44 | .then(listeners.resetValidateSuccess as (e: any) => void) 45 | .catch(listeners.resetValidateFailed as (e: any) => void) 46 | }, 47 | }, 48 | }, 49 | slots 50 | ) 51 | } 52 | }, 53 | }) 54 | ) 55 | 56 | export default Reset 57 | ``` -------------------------------------------------------------------------------- /packages/shared/src/checkers.ts: -------------------------------------------------------------------------------- ```typescript 1 | const toString = Object.prototype.toString 2 | const isType = 3 | <T>(type: string | string[]) => 4 | (obj: unknown): obj is T => 5 | getType(obj) === `[object ${type}]` 6 | export const getType = (obj: any) => toString.call(obj) 7 | export const isFn = (val: any): val is Function => typeof val === 'function' 8 | export const isArr = Array.isArray 9 | export const isPlainObj = isType<object>('Object') 10 | export const isStr = isType<string>('String') 11 | export const isBool = isType<boolean>('Boolean') 12 | export const isNum = isType<number>('Number') 13 | export const isMap = (val: any): val is Map<any, any> => 14 | val && val instanceof Map 15 | export const isSet = (val: any): val is Set<any> => val && val instanceof Set 16 | export const isWeakMap = (val: any): val is WeakMap<any, any> => 17 | val && val instanceof WeakMap 18 | export const isWeakSet = (val: any): val is WeakSet<any> => 19 | val && val instanceof WeakSet 20 | export const isNumberLike = (index: any): index is number => 21 | isNum(index) || /^\d+$/.test(index) 22 | export const isObj = (val: unknown): val is object => typeof val === 'object' 23 | export const isRegExp = isType<RegExp>('RegExp') 24 | export const isReactElement = (obj: any): boolean => 25 | obj && obj['$$typeof'] && obj['_owner'] 26 | export const isHTMLElement = (target: any): target is EventTarget => { 27 | return Object.prototype.toString.call(target).indexOf('HTML') > -1 28 | } 29 | 30 | export type Subscriber<S> = (payload: S) => void 31 | 32 | export interface Subscription<S> { 33 | notify?: (payload: S) => void | boolean 34 | filter?: (payload: S) => any 35 | } 36 | ``` -------------------------------------------------------------------------------- /packages/react/docs/api/shared/mapReadPretty.md: -------------------------------------------------------------------------------- ```markdown 1 | # mapReadPretty 2 | 3 | ## Description 4 | 5 | Because most third-party components do not support the reading state, if you want to quickly support the reading state, you can use the mapReadPretty function to map a reading state component 6 | 7 | ## Signature 8 | 9 | ```ts 10 | interface mapReadPretty<Target extends React.FC> { 11 | (component: Target, readPrettyProps?: React.ComponentProps<Target>): React.FC 12 | } 13 | ``` 14 | 15 | ## Example 16 | 17 | ```tsx 18 | import React, { useMemo } from 'react' 19 | import { createForm } from '@formily/core' 20 | import { 21 | FormProvider, 22 | Field, 23 | connect, 24 | mapProps, 25 | mapReadPretty, 26 | } from '@formily/react' 27 | import { Input as AntdInput, Form } from 'antd' 28 | 29 | // FormItem UI component 30 | const FormItem = connect( 31 | Form.Item, 32 | mapProps( 33 | { 34 | title: 'label', 35 | description: 'extra', 36 | required: true, 37 | validateStatus: true, 38 | }, 39 | (props, field) => { 40 | return { 41 | ...props, 42 | help: field.selfErrors?.length ? field.selfErrors : undefined, 43 | } 44 | } 45 | ) 46 | ) 47 | 48 | const Input = connect( 49 | AntdInput, 50 | mapReadPretty(({ value }) => <div>{value}</div>) 51 | ) 52 | 53 | export default () => { 54 | const form = useMemo(() => 55 | createForm({ validateFirst: true, readPretty: true }) 56 | ) 57 | return ( 58 | <FormProvider form={form}> 59 | <Form layout="vertical"> 60 | <Field 61 | name="name" 62 | title="Name" 63 | required 64 | initialValue="Hello world" 65 | decorator={[FormItem]} 66 | component={[Input, { placeholder: 'Please Input' }]} 67 | /> 68 | </Form> 69 | </FormProvider> 70 | ) 71 | } 72 | ``` 73 | ``` -------------------------------------------------------------------------------- /packages/react/docs/api/hooks/useFormEffects.md: -------------------------------------------------------------------------------- ```markdown 1 | # useFormEffects 2 | 3 | ## Description 4 | 5 | Mainly inject side-effect logic into the current [Form](https://core.formilyjs.org/api/models/form) instance in the custom component to implement some more complex scenario-based components 6 | 7 | <Alert> 8 | Note: It is invalid to monitor onFormInit in the effects function, because the Form has already been initialized when rendering to the current component, and the effects function will only be executed once, so if you want to rely on the data of useState, please use the reference data of useRef. 9 | </Alert> 10 | 11 | ## Signature 12 | 13 | ```ts 14 | interface useFormEffects { 15 | (form: Form): void 16 | } 17 | ``` 18 | 19 | ## Example 20 | 21 | ```tsx 22 | import React from 'react' 23 | import { createForm, onFieldReact } from '@formily/core' 24 | import { FormProvider, Field, useFormEffects } from '@formily/react' 25 | import { Input, Form } from 'antd' 26 | 27 | const form = createForm({ 28 | effects() { 29 | onFieldReact('custom.aa', (field) => { 30 | field.value = field.query('input').get('value') 31 | }) 32 | }, 33 | }) 34 | 35 | const Custom = () => { 36 | useFormEffects(() => { 37 | onFieldReact('custom.bb', (field) => { 38 | field.value = field.query('.aa').get('value') 39 | }) 40 | }) 41 | return ( 42 | <div> 43 | <Field name="aa" decorator={[Form.Item]} component={[Input]} /> 44 | <Field name="bb" decorator={[Form.Item]} component={[Input]} /> 45 | </div> 46 | ) 47 | } 48 | 49 | export default () => ( 50 | <FormProvider form={form}> 51 | <Field name="input" decorator={[Form.Item]} component={[Input]} /> 52 | <Field name="custom" decorator={[Form.Item]} component={[Custom]} /> 53 | </FormProvider> 54 | ) 55 | ``` 56 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/components/array-field.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <ArrayField name="array"> 4 | <template #default="{ field }"> 5 | <div 6 | v-for="(item, index) in field.value || []" 7 | :key="`${item.id}-${index}`" 8 | :style="{ marginBottom: '10px' }" 9 | > 10 | <Space> 11 | <Field :name="`${index}.value`" :component="[Input]" /> 12 | <Button 13 | @click=" 14 | () => { 15 | field.remove(index) 16 | } 17 | " 18 | > 19 | Remove 20 | </Button> 21 | <Button 22 | @click=" 23 | () => { 24 | field.moveUp(index) 25 | } 26 | " 27 | > 28 | Move Up 29 | </Button> 30 | <Button 31 | @click=" 32 | () => { 33 | field.moveDown(index) 34 | } 35 | " 36 | > 37 | Move Down 38 | </Button> 39 | </Space> 40 | </div> 41 | <Button @click="() => field.push({ id: Date.now(), value: '' })"> 42 | Add 43 | </Button> 44 | </template> 45 | </ArrayField> 46 | </FormProvider> 47 | </template> 48 | 49 | <script> 50 | import { Input, Space, Button } from 'ant-design-vue' 51 | import { createForm } from '@formily/core' 52 | import { FormProvider, ArrayField, Field } from '@formily/vue' 53 | import 'ant-design-vue/dist/antd.css' 54 | 55 | export default { 56 | components: { FormProvider, ArrayField, Field, Space, Button }, 57 | data() { 58 | return { 59 | Input, 60 | form: createForm(), 61 | } 62 | }, 63 | } 64 | </script> 65 | ``` -------------------------------------------------------------------------------- /packages/core/src/models/ObjectField.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { reaction } from '@formily/reactive' 2 | import { cleanupObjectChildren } from '../shared/internals' 3 | import { JSXComponent, IFieldProps, FormPathPattern } from '../types' 4 | import { Field } from './Field' 5 | import { Form } from './Form' 6 | 7 | export class ObjectField< 8 | Decorator extends JSXComponent = any, 9 | Component extends JSXComponent = any 10 | > extends Field<Decorator, Component, any, Record<string, any>> { 11 | displayName = 'ObjectField' 12 | private additionalProperties: string[] = [] 13 | constructor( 14 | address: FormPathPattern, 15 | props: IFieldProps<Decorator, Component>, 16 | form: Form, 17 | designable: boolean 18 | ) { 19 | super(address, props, form, designable) 20 | this.makeAutoCleanable() 21 | } 22 | 23 | protected makeAutoCleanable() { 24 | this.disposers.push( 25 | reaction( 26 | () => Object.keys(this.value || {}), 27 | (newKeys) => { 28 | const filterKeys = this.additionalProperties.filter( 29 | (key) => !newKeys.includes(key) 30 | ) 31 | cleanupObjectChildren(this, filterKeys) 32 | } 33 | ) 34 | ) 35 | } 36 | 37 | addProperty = (key: string, value: any) => { 38 | this.form.setValuesIn(this.path.concat(key), value) 39 | this.additionalProperties.push(key) 40 | return this.onInput(this.value) 41 | } 42 | 43 | removeProperty = (key: string) => { 44 | this.form.deleteValuesIn(this.path.concat(key)) 45 | this.additionalProperties.splice(this.additionalProperties.indexOf(key), 1) 46 | return this.onInput(this.value) 47 | } 48 | 49 | existProperty = (key: string) => { 50 | return this.form.existValuesIn(this.path.concat(key)) 51 | } 52 | } 53 | ``` -------------------------------------------------------------------------------- /packages/shared/src/clone.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isFn, isPlainObj } from './checkers' 2 | 3 | export const shallowClone = (values: any) => { 4 | if (Array.isArray(values)) { 5 | return values.slice(0) 6 | } else if (isPlainObj(values)) { 7 | if ('$$typeof' in values && '_owner' in values) { 8 | return values 9 | } 10 | if (values['_isBigNumber']) { 11 | return values 12 | } 13 | if (values['_isAMomentObject']) { 14 | return values 15 | } 16 | if (values['_isJSONSchemaObject']) { 17 | return values 18 | } 19 | if (isFn(values['toJS'])) { 20 | return values 21 | } 22 | if (isFn(values['toJSON'])) { 23 | return values 24 | } 25 | return { 26 | ...values, 27 | } 28 | } else if (typeof values === 'object') { 29 | return new values.constructor(values) 30 | } 31 | return values 32 | } 33 | 34 | export const clone = (values: any) => { 35 | if (Array.isArray(values)) { 36 | const res = [] 37 | values.forEach((item) => { 38 | res.push(clone(item)) 39 | }) 40 | return res 41 | } else if (isPlainObj(values)) { 42 | if ('$$typeof' in values && '_owner' in values) { 43 | return values 44 | } 45 | if (values['_isBigNumber']) { 46 | return values 47 | } 48 | if (values['_isAMomentObject']) { 49 | return values 50 | } 51 | if (values['_isJSONSchemaObject']) { 52 | return values 53 | } 54 | if (isFn(values['toJS'])) { 55 | return values['toJS']() 56 | } 57 | if (isFn(values['toJSON'])) { 58 | return values['toJSON']() 59 | } 60 | const res = {} 61 | for (const key in values) { 62 | if (Object.hasOwnProperty.call(values, key)) { 63 | res[key] = clone(values[key]) 64 | } 65 | } 66 | return res 67 | } else { 68 | return values 69 | } 70 | } 71 | ``` -------------------------------------------------------------------------------- /packages/reactive-react/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@formily/reactive-react", 3 | "version": "2.3.7", 4 | "license": "MIT", 5 | "main": "lib", 6 | "module": "esm", 7 | "umd:main": "dist/formily.reactive-react.umd.production.js", 8 | "unpkg": "dist/formily.reactive-react.umd.production.js", 9 | "jsdelivr": "dist/formily.reactive-react.umd.production.js", 10 | "jsnext:main": "esm", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/alibaba/formily.git" 14 | }, 15 | "types": "esm/index.d.ts", 16 | "bugs": { 17 | "url": "https://github.com/alibaba/formily/issues" 18 | }, 19 | "homepage": "https://github.com/alibaba/formily#readme", 20 | "engines": { 21 | "npm": ">=3.0.0" 22 | }, 23 | "scripts": { 24 | "start": "dumi dev", 25 | "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", 26 | "build:cjs": "tsc --project tsconfig.build.json", 27 | "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", 28 | "build:umd": "rollup --config", 29 | "build:docs": "dumi build" 30 | }, 31 | "peerDependencies": { 32 | "@types/react": ">=16.8.0", 33 | "@types/react-dom": ">=16.8.0", 34 | "react": ">=16.8.0", 35 | "react-dom": ">=16.8.0", 36 | "react-is": ">=16.8.0" 37 | }, 38 | "peerDependenciesMeta": { 39 | "@types/react": { 40 | "optional": true 41 | }, 42 | "@types/react-dom": { 43 | "optional": true 44 | } 45 | }, 46 | "devDependencies": { 47 | "dumi": "^1.1.0-rc.8" 48 | }, 49 | "dependencies": { 50 | "@formily/reactive": "2.3.7", 51 | "hoist-non-react-statics": "^3.3.2" 52 | }, 53 | "publishConfig": { 54 | "access": "public" 55 | }, 56 | "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" 57 | } 58 | ``` -------------------------------------------------------------------------------- /packages/antd/src/array-base/style.less: -------------------------------------------------------------------------------- ``` 1 | @root-entry-name: 'default'; 2 | @import (reference) '~antd/es/style/themes/index.less'; 3 | 4 | @array-base-prefix-cls: ~'@{ant-prefix}-formily-array-base'; 5 | 6 | .@{array-base-prefix-cls}-remove, 7 | .@{array-base-prefix-cls}-copy { 8 | transition: all 0.25s ease-in-out; 9 | color: @text-color; 10 | font-size: 14px; 11 | margin-left: 6px; 12 | padding: 0; 13 | border: none; 14 | width: auto; 15 | height: auto; 16 | 17 | &:hover { 18 | color: @primary-5; 19 | } 20 | 21 | &-disabled { 22 | color: @disabled-color; 23 | cursor: not-allowed !important; 24 | &:hover { 25 | color: @disabled-color; 26 | } 27 | } 28 | } 29 | 30 | .@{array-base-prefix-cls}-sort-handle { 31 | cursor: move; 32 | color: #888 !important; 33 | // overrid iconfont.less .anticon[tabindex] cursor 34 | &.anticon[tabindex] { 35 | cursor: move; 36 | } 37 | } 38 | 39 | .@{array-base-prefix-cls}-addition { 40 | transition: all 0.25s ease-in-out; 41 | } 42 | 43 | .@{array-base-prefix-cls}-move-down { 44 | transition: all 0.25s ease-in-out; 45 | color: @text-color; 46 | font-size: 14px; 47 | margin-left: 6px; 48 | padding: 0; 49 | border: none; 50 | width: auto; 51 | height: auto; 52 | 53 | &:hover { 54 | color: @primary-5; 55 | } 56 | 57 | &-disabled { 58 | color: @disabled-color; 59 | cursor: not-allowed !important; 60 | &:hover { 61 | color: @disabled-color; 62 | } 63 | } 64 | } 65 | 66 | .@{array-base-prefix-cls}-move-up { 67 | transition: all 0.25s ease-in-out; 68 | color: @text-color; 69 | font-size: 14px; 70 | margin-left: 6px; 71 | padding: 0; 72 | border: none; 73 | width: auto; 74 | height: auto; 75 | 76 | &:hover { 77 | color: @primary-5; 78 | } 79 | 80 | &-disabled { 81 | color: @disabled-color; 82 | cursor: not-allowed !important; 83 | &:hover { 84 | color: @disabled-color; 85 | } 86 | } 87 | } 88 | ``` -------------------------------------------------------------------------------- /packages/antd/src/__builtins__/hooks/useClickAway.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { useRef, useEffect, MutableRefObject } from 'react' 2 | 3 | const defaultEvent = 'click' 4 | 5 | type EventType = MouseEvent | TouchEvent 6 | 7 | type BasicTarget<T = HTMLElement> = 8 | | (() => T | null) 9 | | T 10 | | null 11 | | MutableRefObject<T | null | undefined> 12 | 13 | type TargetElement = HTMLElement | Element | Document | Window 14 | 15 | function getTargetElement( 16 | target?: BasicTarget<TargetElement>, 17 | defaultElement?: TargetElement 18 | ): TargetElement | undefined | null { 19 | if (!target) { 20 | return defaultElement 21 | } 22 | 23 | let targetElement: TargetElement | undefined | null 24 | 25 | if (typeof target === 'function') { 26 | targetElement = target() 27 | } else if ('current' in target) { 28 | targetElement = target.current 29 | } else { 30 | targetElement = target 31 | } 32 | 33 | return targetElement 34 | } 35 | 36 | export const useClickAway = ( 37 | onClickAway: (event: EventType) => void, 38 | target: BasicTarget | BasicTarget[], 39 | eventName: string = defaultEvent 40 | ) => { 41 | const onClickAwayRef = useRef(onClickAway) 42 | onClickAwayRef.current = onClickAway 43 | 44 | useEffect(() => { 45 | const handler = (event: any) => { 46 | const targets = Array.isArray(target) ? target : [target] 47 | if ( 48 | targets.some((targetItem) => { 49 | const targetElement = getTargetElement(targetItem) as HTMLElement 50 | return !targetElement || targetElement?.contains(event.target) 51 | }) 52 | ) { 53 | return 54 | } 55 | onClickAwayRef.current(event) 56 | } 57 | 58 | document.addEventListener(eventName, handler) 59 | 60 | return () => { 61 | document.removeEventListener(eventName, handler) 62 | } 63 | }, [target, eventName]) 64 | } 65 | ``` -------------------------------------------------------------------------------- /packages/antd/src/array-items/style.less: -------------------------------------------------------------------------------- ``` 1 | @root-entry-name: 'default'; 2 | @import (reference) '~antd/es/style/themes/index.less'; 3 | 4 | @array-items-prefix-cls: ~'@{ant-prefix}-formily-array-items'; 5 | 6 | .@{array-items-prefix-cls}-item-inner { 7 | visibility: visible; 8 | } 9 | 10 | // fix https://github.com/alibaba/formily/issues/2891 11 | .@{array-items-prefix-cls}-item { 12 | z-index: 100000; 13 | } 14 | 15 | .@{array-items-prefix-cls}-card { 16 | display: flex; 17 | border: 1px solid @border-color-split; 18 | margin-bottom: 10px; 19 | padding: 3px 6px; 20 | background: @card-background; 21 | justify-content: space-between; 22 | color: @text-color; 23 | 24 | .@{ant-prefix}-formily-item:not(.@{ant-prefix}-formily-item-feedback-layout-popover) { 25 | margin-bottom: 0 !important; 26 | 27 | .@{ant-prefix}-formily-item-help { 28 | position: absolute; 29 | font-size: 12px; 30 | top: 100%; 31 | background: @card-background; 32 | width: 100%; 33 | margin-top: 3px; 34 | padding: 3px; 35 | z-index: 1; 36 | border-radius: 3px; 37 | box-shadow: 0 0 10px @border-color-split; 38 | } 39 | } 40 | } 41 | 42 | .@{array-items-prefix-cls}-divide { 43 | display: flex; 44 | border-bottom: 1px solid @border-color-split; 45 | padding: 10px 0; 46 | justify-content: space-between; 47 | 48 | .@{ant-prefix}-formily-item:not(.@{ant-prefix}-formily-item-feedback-layout-popover) { 49 | margin-bottom: 0 !important; 50 | 51 | .@{ant-prefix}-formily-item-help { 52 | position: absolute; 53 | font-size: 12px; 54 | top: 100%; 55 | background: @card-background; 56 | width: 100%; 57 | margin-top: 3px; 58 | padding: 3px; 59 | z-index: 1; 60 | border-radius: 3px; 61 | box-shadow: 0 0 10px @border-color-split; 62 | } 63 | } 64 | } 65 | ``` -------------------------------------------------------------------------------- /packages/next/src/__builtins__/hooks/useClickAway.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { useRef, useEffect, MutableRefObject } from 'react' 2 | 3 | const defaultEvent = 'click' 4 | 5 | type EventType = MouseEvent | TouchEvent 6 | 7 | type BasicTarget<T = HTMLElement> = 8 | | (() => T | null) 9 | | T 10 | | null 11 | | MutableRefObject<T | null | undefined> 12 | 13 | type TargetElement = HTMLElement | Element | Document | Window 14 | 15 | function getTargetElement( 16 | target?: BasicTarget<TargetElement>, 17 | defaultElement?: TargetElement 18 | ): TargetElement | undefined | null { 19 | if (!target) { 20 | return defaultElement 21 | } 22 | 23 | let targetElement: TargetElement | undefined | null 24 | 25 | if (typeof target === 'function') { 26 | targetElement = target() 27 | } else if ('current' in target) { 28 | targetElement = target.current 29 | } else { 30 | targetElement = target 31 | } 32 | 33 | return targetElement 34 | } 35 | 36 | export const useClickAway = ( 37 | onClickAway: (event: EventType) => void, 38 | target: BasicTarget | BasicTarget[], 39 | eventName: string = defaultEvent 40 | ) => { 41 | const onClickAwayRef = useRef(onClickAway) 42 | onClickAwayRef.current = onClickAway 43 | 44 | useEffect(() => { 45 | const handler = (event: any) => { 46 | const targets = Array.isArray(target) ? target : [target] 47 | if ( 48 | targets.some((targetItem) => { 49 | const targetElement = getTargetElement(targetItem) as HTMLElement 50 | return !targetElement || targetElement?.contains(event.target) 51 | }) 52 | ) { 53 | return 54 | } 55 | onClickAwayRef.current(event) 56 | } 57 | 58 | document.addEventListener(eventName, handler) 59 | 60 | return () => { 61 | document.removeEventListener(eventName, handler) 62 | } 63 | }, [target, eventName]) 64 | } 65 | ``` -------------------------------------------------------------------------------- /packages/antd/src/form-item/animation.less: -------------------------------------------------------------------------------- ``` 1 | @-webkit-keyframes antShowHelpIn { 2 | 0% { 3 | -webkit-transform: translateY(-5px); 4 | transform: translateY(-5px); 5 | opacity: 0; 6 | } 7 | 8 | to { 9 | -webkit-transform: translateY(0); 10 | transform: translateY(0); 11 | opacity: 1; 12 | } 13 | } 14 | 15 | .@{form-item-cls}-help-appear, 16 | .@{form-item-cls}-help-enter { 17 | -webkit-animation-duration: 0.3s; 18 | animation-duration: 0.3s; 19 | -webkit-animation-fill-mode: both; 20 | animation-fill-mode: both; 21 | -webkit-animation-play-state: paused; 22 | animation-play-state: paused; 23 | } 24 | 25 | .@{form-item-cls}-help-appear.@{form-item-cls}-help-appear-active, 26 | .@{form-item-cls}-help-enter.@{form-item-cls}-help-enter-active { 27 | -webkit-animation-name: antShowHelpIn; 28 | animation-name: antShowHelpIn; 29 | -webkit-animation-play-state: running; 30 | animation-play-state: running; 31 | } 32 | 33 | .@{form-item-cls}-help-appear, 34 | .@{form-item-cls}-help-enter { 35 | opacity: 0; 36 | } 37 | 38 | .@{form-item-cls}-help-appear, 39 | .@{form-item-cls}-help-enter { 40 | -webkit-animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); 41 | animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); 42 | } 43 | 44 | @keyframes antShowHelpIn { 45 | 0% { 46 | -webkit-transform: translateY(-5px); 47 | transform: translateY(-5px); 48 | opacity: 0; 49 | } 50 | 51 | to { 52 | -webkit-transform: translateY(0); 53 | transform: translateY(0); 54 | opacity: 1; 55 | } 56 | } 57 | 58 | @-webkit-keyframes antShowHelpOut { 59 | to { 60 | -webkit-transform: translateY(-5px); 61 | transform: translateY(-5px); 62 | opacity: 0; 63 | } 64 | } 65 | 66 | @keyframes antShowHelpOut { 67 | to { 68 | -webkit-transform: translateY(-5px); 69 | transform: translateY(-5px); 70 | opacity: 0; 71 | } 72 | } 73 | ``` -------------------------------------------------------------------------------- /packages/next/src/form-item/animation.scss: -------------------------------------------------------------------------------- ```scss 1 | @-webkit-keyframes antShowHelpIn { 2 | 0% { 3 | -webkit-transform: translateY(-5px); 4 | transform: translateY(-5px); 5 | opacity: 0; 6 | } 7 | 8 | to { 9 | -webkit-transform: translateY(0); 10 | transform: translateY(0); 11 | opacity: 1; 12 | } 13 | } 14 | 15 | .#{$form-item-cls}-help-appear, 16 | .#{$form-item-cls}-help-enter { 17 | -webkit-animation-duration: 0.3s; 18 | animation-duration: 0.3s; 19 | -webkit-animation-fill-mode: both; 20 | animation-fill-mode: both; 21 | -webkit-animation-play-state: paused; 22 | animation-play-state: paused; 23 | } 24 | 25 | .#{$form-item-cls}-help-appear.#{$form-item-cls}-help-appear-active, 26 | .#{$form-item-cls}-help-enter.#{$form-item-cls}-help-enter-active { 27 | -webkit-animation-name: antShowHelpIn; 28 | animation-name: antShowHelpIn; 29 | -webkit-animation-play-state: running; 30 | animation-play-state: running; 31 | } 32 | 33 | .#{$form-item-cls}-help-appear, 34 | .#{$form-item-cls}-help-enter { 35 | opacity: 0; 36 | } 37 | 38 | .#{$form-item-cls}-help-appear, 39 | .#{$form-item-cls}-help-enter { 40 | -webkit-animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); 41 | animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); 42 | } 43 | 44 | @keyframes antShowHelpIn { 45 | 0% { 46 | -webkit-transform: translateY(-5px); 47 | transform: translateY(-5px); 48 | opacity: 0; 49 | } 50 | 51 | to { 52 | -webkit-transform: translateY(0); 53 | transform: translateY(0); 54 | opacity: 1; 55 | } 56 | } 57 | 58 | @-webkit-keyframes antShowHelpOut { 59 | to { 60 | -webkit-transform: translateY(-5px); 61 | transform: translateY(-5px); 62 | opacity: 0; 63 | } 64 | } 65 | 66 | @keyframes antShowHelpOut { 67 | to { 68 | -webkit-transform: translateY(-5px); 69 | transform: translateY(-5px); 70 | opacity: 0; 71 | } 72 | } 73 | ``` -------------------------------------------------------------------------------- /packages/reactive/docs/index.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: Formily-Alibaba unified front-end form solution 3 | order: 10 4 | hero: 5 | title: Reactive Library 6 | desc: DDD-oriented Responsive State Management Solution 7 | actions: 8 | - text: Home Site 9 | link: //formilyjs.org 10 | - text: Document 11 | link: /guide 12 | features: 13 | - icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg 14 | title: High Performance 15 | desc: Efficient update, Demand rendering 16 | - icon: https://img.alicdn.com/imgextra/i2/O1CN01YqmcpN1tDalwgyHBH_!!6000000005868-55-tps-800-800.svg 17 | title: Zero Dependencies 18 | desc: Cross Device,Cross Framework 19 | - icon: https://img.alicdn.com/imgextra/i4/O1CN01u6jHgs1ZMwXpjAYnh_!!6000000003181-55-tps-800-800.svg 20 | title: Smart Tips 21 | desc: Embrace Typescript 22 | footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by self 23 | --- 24 | 25 | ## Installation 26 | 27 | ```bash 28 | $ npm install --save @formily/reactive 29 | 30 | ``` 31 | 32 | ## Quick start 33 | 34 | ```tsx 35 | /** 36 | * defaultShowCode: true 37 | */ 38 | import React from 'react' 39 | import { observable } from '@formily/reactive' 40 | import { observer } from '@formily/reactive-react' 41 | 42 | const obs = observable({ 43 | value: 'Hello world', 44 | }) 45 | 46 | export default observer(() => { 47 | return ( 48 | <div> 49 | <div> 50 | <input 51 | style={{ 52 | height: 28, 53 | padding: '0 8px', 54 | border: '2px solid #888', 55 | borderRadius: 3, 56 | }} 57 | value={obs.value} 58 | onChange={(e) => { 59 | obs.value = e.target.value 60 | }} 61 | /> 62 | </div> 63 | <div>{obs.value}</div> 64 | </div> 65 | ) 66 | }) 67 | ``` 68 | ``` -------------------------------------------------------------------------------- /docs/guide/scenes/VerifyCode.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useState } from 'react' 2 | import { Input, Button } from 'antd' 3 | 4 | interface IVerifyCodeProps { 5 | value?: any 6 | onChange?: (value: any) => void 7 | readyPost?: boolean 8 | phoneNumber?: number 9 | style?: React.CSSProperties 10 | } 11 | 12 | export const VerifyCode: React.FC<React.PropsWithChildren<IVerifyCodeProps>> = 13 | ({ value, onChange, readyPost, phoneNumber, ...props }) => { 14 | const [lastTime, setLastTime] = useState(0) 15 | 16 | const counting = (time = 20) => { 17 | if (time < 0) return 18 | setLastTime(time) 19 | setTimeout(() => { 20 | counting(time - 1) 21 | }, 1000) 22 | } 23 | 24 | return ( 25 | <div 26 | style={{ display: 'inline-flex', width: '100%', alignItems: 'center' }} 27 | > 28 | <Input 29 | {...props} 30 | style={{ marginRight: 5, ...props.style }} 31 | value={value} 32 | onChange={onChange} 33 | /> 34 | <div 35 | style={{ 36 | flexShrink: 0, 37 | color: '#999', 38 | width: 100, 39 | height: 35, 40 | display: 'flex', 41 | alignItems: 'center', 42 | justifyContent: 'center', 43 | }} 44 | > 45 | {lastTime === 0 && ( 46 | <Button 47 | disabled={!readyPost} 48 | block 49 | onClick={() => { 50 | if (phoneNumber) { 51 | console.log(`post code by phone number ${phoneNumber}`) 52 | } 53 | counting() 54 | }} 55 | > 56 | 发送验证码 57 | </Button> 58 | )} 59 | {lastTime > 0 && <span>剩余{lastTime}秒</span>} 60 | </div> 61 | </div> 62 | ) 63 | } 64 | ``` -------------------------------------------------------------------------------- /packages/antd/src/__builtins__/portal.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment } from 'react' 2 | import { createPortal } from 'react-dom' 3 | import { observable } from '@formily/reactive' 4 | import { Observer } from '@formily/react' 5 | import { render as reactRender, unmount as reactUnmount } from './render' 6 | export interface IPortalProps { 7 | id?: string | symbol 8 | } 9 | 10 | const PortalMap = observable(new Map<string | symbol, React.ReactNode>()) 11 | 12 | export const createPortalProvider = (id: string | symbol) => { 13 | const Portal = (props: React.PropsWithChildren<IPortalProps>) => { 14 | const portalId = props.id ?? id 15 | if (portalId && !PortalMap.has(portalId)) { 16 | PortalMap.set(portalId, null) 17 | } 18 | 19 | return ( 20 | <Fragment> 21 | {props.children} 22 | <Observer> 23 | {() => { 24 | if (!portalId) return null 25 | const portal = PortalMap.get(portalId) 26 | if (portal) return createPortal(portal, document.body) 27 | return null 28 | }} 29 | </Observer> 30 | </Fragment> 31 | ) 32 | } 33 | return Portal 34 | } 35 | 36 | export function createPortalRoot<T extends React.ReactNode>( 37 | host: HTMLElement, 38 | id: string 39 | ) { 40 | function render(renderer?: () => T) { 41 | if (PortalMap.has(id)) { 42 | PortalMap.set(id, renderer?.()) 43 | } else if (host) { 44 | reactRender(<Fragment>{renderer?.()}</Fragment>, host) 45 | } 46 | } 47 | 48 | function unmount() { 49 | if (PortalMap.has(id)) { 50 | PortalMap.set(id, null) 51 | } 52 | if (host) { 53 | const unmountResult = reactUnmount(host) 54 | if (unmountResult && host.parentNode) { 55 | host.parentNode?.removeChild(host) 56 | } 57 | } 58 | } 59 | 60 | return { 61 | render, 62 | unmount, 63 | } 64 | } 65 | ``` -------------------------------------------------------------------------------- /packages/next/src/__builtins__/portal.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment } from 'react' 2 | import { createPortal } from 'react-dom' 3 | import { observable } from '@formily/reactive' 4 | import { Observer } from '@formily/react' 5 | import { render as reactRender, unmount as reactUnmount } from './render' 6 | export interface IPortalProps { 7 | id?: string | symbol 8 | } 9 | 10 | const PortalMap = observable(new Map<string | symbol, React.ReactNode>()) 11 | 12 | export const createPortalProvider = (id: string | symbol) => { 13 | const Portal = (props: React.PropsWithChildren<IPortalProps>) => { 14 | const portalId = props.id ?? id 15 | if (portalId && !PortalMap.has(portalId)) { 16 | PortalMap.set(portalId, null) 17 | } 18 | 19 | return ( 20 | <Fragment> 21 | {props.children} 22 | <Observer> 23 | {() => { 24 | if (!portalId) return null 25 | const portal = PortalMap.get(portalId) 26 | if (portal) return createPortal(portal, document.body) 27 | return null 28 | }} 29 | </Observer> 30 | </Fragment> 31 | ) 32 | } 33 | return Portal 34 | } 35 | 36 | export function createPortalRoot<T extends React.ReactNode>( 37 | host: HTMLElement, 38 | id: string 39 | ) { 40 | function render(renderer?: () => T) { 41 | if (PortalMap.has(id)) { 42 | PortalMap.set(id, renderer?.()) 43 | } else if (host) { 44 | reactRender(<Fragment>{renderer?.()}</Fragment>, host) 45 | } 46 | } 47 | 48 | function unmount() { 49 | if (PortalMap.has(id)) { 50 | PortalMap.set(id, null) 51 | } 52 | if (host) { 53 | const unmountResult = reactUnmount(host) 54 | if (unmountResult && host.parentNode) { 55 | host.parentNode?.removeChild(host) 56 | } 57 | } 58 | } 59 | 60 | return { 61 | render, 62 | unmount, 63 | } 64 | } 65 | ``` -------------------------------------------------------------------------------- /packages/element/src/form-item/animation.scss: -------------------------------------------------------------------------------- ```scss 1 | @-webkit-keyframes antShowHelpIn { 2 | 0% { 3 | -webkit-transform: translateY(-5px); 4 | transform: translateY(-5px); 5 | opacity: 0; 6 | } 7 | 8 | to { 9 | -webkit-transform: translateY(0); 10 | transform: translateY(0); 11 | opacity: 1; 12 | } 13 | } 14 | 15 | .#{$form-item-prefix}-help-appear, 16 | .#{$form-item-prefix}-help-enter { 17 | -webkit-animation-duration: 0.3s; 18 | animation-duration: 0.3s; 19 | -webkit-animation-fill-mode: both; 20 | animation-fill-mode: both; 21 | -webkit-animation-play-state: paused; 22 | animation-play-state: paused; 23 | } 24 | 25 | .#{$form-item-prefix}-help-appear.#{$form-item-prefix}-help-appear-active, 26 | .#{$form-item-prefix}-help-enter.#{$form-item-prefix}-help-enter-active { 27 | -webkit-animation-name: antShowHelpIn; 28 | animation-name: antShowHelpIn; 29 | -webkit-animation-play-state: running; 30 | animation-play-state: running; 31 | } 32 | 33 | .#{$form-item-prefix}-help-appear, 34 | .#{$form-item-prefix}-help-enter { 35 | opacity: 0; 36 | } 37 | 38 | .#{$form-item-prefix}-help-appear, 39 | .#{$form-item-prefix}-help-enter { 40 | -webkit-animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); 41 | animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); 42 | } 43 | 44 | @keyframes antShowHelpIn { 45 | 0% { 46 | -webkit-transform: translateY(-5px); 47 | transform: translateY(-5px); 48 | opacity: 0; 49 | } 50 | 51 | to { 52 | -webkit-transform: translateY(0); 53 | transform: translateY(0); 54 | opacity: 1; 55 | } 56 | } 57 | 58 | @-webkit-keyframes antShowHelpOut { 59 | to { 60 | -webkit-transform: translateY(-5px); 61 | transform: translateY(-5px); 62 | opacity: 0; 63 | } 64 | } 65 | 66 | @keyframes antShowHelpOut { 67 | to { 68 | -webkit-transform: translateY(-5px); 69 | transform: translateY(-5px); 70 | opacity: 0; 71 | } 72 | } 73 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/hooks/use-form-effects.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <Field 4 | name="input" 5 | :decorator="[FormItem]" 6 | :component="[Input, { placeholder: 'input' }]" 7 | /> 8 | <Field name="custom" :decorator="[FormItem]" :component="[Custom]" /> 9 | </FormProvider> 10 | </template> 11 | 12 | <script> 13 | import { defineComponent, h } from '@vue/composition-api' 14 | import { createForm, onFieldReact } from '@formily/core' 15 | import { FormProvider, Field, useFormEffects } from '@formily/vue' 16 | import { Form, Input } from 'ant-design-vue' 17 | import 'ant-design-vue/dist/antd.css' 18 | 19 | const Custom = defineComponent({ 20 | setup() { 21 | useFormEffects(() => { 22 | onFieldReact('custom.bb', (field) => { 23 | field.value = field.query('.aa').get('value') 24 | }) 25 | }) 26 | return () => 27 | h('div', {}, [ 28 | h( 29 | Field, 30 | { 31 | props: { 32 | name: 'aa', 33 | decorator: [Form.Item], 34 | component: [Input, { placeholder: 'aa' }], 35 | }, 36 | }, 37 | {} 38 | ), 39 | h( 40 | Field, 41 | { 42 | props: { 43 | name: 'bb', 44 | decorator: [Form.Item], 45 | component: [Input, { placeholder: 'bb' }], 46 | }, 47 | }, 48 | {} 49 | ), 50 | ]) 51 | }, 52 | }) 53 | 54 | export default { 55 | components: { 56 | FormProvider, 57 | Field, 58 | }, 59 | data() { 60 | const form = createForm({ 61 | effects() { 62 | onFieldReact('custom.aa', (field) => { 63 | field.value = field.query('input').get('value') 64 | }) 65 | }, 66 | }) 67 | return { 68 | FormItem: Form.Item, 69 | Input, 70 | Custom, 71 | form, 72 | } 73 | }, 74 | } 75 | </script> 76 | ``` -------------------------------------------------------------------------------- /packages/core/src/models/Graph.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { define, batch } from '@formily/reactive' 2 | import { each, FormPath } from '@formily/shared' 3 | import { IFormGraph } from '../types' 4 | import { Form } from './Form' 5 | import { 6 | isFormState, 7 | isFieldState, 8 | isArrayFieldState, 9 | isObjectFieldState, 10 | } from '../shared/checkers' 11 | 12 | export class Graph { 13 | form: Form 14 | 15 | constructor(form: Form) { 16 | this.form = form 17 | define(this, { 18 | setGraph: batch, 19 | }) 20 | } 21 | 22 | getGraph = (): IFormGraph => { 23 | const graph = {} 24 | graph[''] = this.form.getState() 25 | each(this.form.fields, (field: any, identifier) => { 26 | graph[identifier] = field.getState() 27 | }) 28 | return graph 29 | } 30 | 31 | setGraph = (graph: IFormGraph) => { 32 | const form = this.form 33 | const createField = (identifier: string, state: any) => { 34 | const address = FormPath.parse(identifier) 35 | const name = address.segments[address.segments.length - 1] 36 | const basePath = address.parent() 37 | if (isFieldState(state)) { 38 | return this.form.createField({ name, basePath }) 39 | } else if (isArrayFieldState(state)) { 40 | return this.form.createArrayField({ name, basePath }) 41 | } else if (isObjectFieldState(state)) { 42 | return this.form.createObjectField({ name, basePath }) 43 | } else { 44 | return this.form.createVoidField({ name, basePath }) 45 | } 46 | } 47 | each(graph, (state, address) => { 48 | if (isFormState(state)) { 49 | form.setState(state) 50 | } else { 51 | const field = form.fields[address] 52 | if (field) { 53 | field.setState(state) 54 | } else { 55 | createField(address, state).setState(state) 56 | } 57 | } 58 | }) 59 | } 60 | } 61 | ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/app/components/filter.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Helper functions for filtering 2 | export const defaultMatcher = (filterText, node) => { 3 | return node.name.toLowerCase().indexOf(filterText.toLowerCase()) !== -1 4 | } 5 | 6 | export const findNode = (node, filter, matcher) => { 7 | return ( 8 | matcher(filter, node) || // i match 9 | (node.children && // or i have decendents and one of them match 10 | node.children.length && 11 | !!node.children.find((child) => findNode(child, filter, matcher))) 12 | ) 13 | } 14 | 15 | export const filterTree = (node, filter, matcher = defaultMatcher) => { 16 | // If im an exact match then all my children get to stay 17 | if (matcher(filter, node) || !node.children) { 18 | return node 19 | } 20 | // If not then only keep the ones that match or have matching descendants 21 | const filtered = node.children 22 | .filter((child) => findNode(child, filter, matcher)) 23 | .map((child) => filterTree(child, filter, matcher)) 24 | return Object.assign({}, node, { children: filtered }) 25 | } 26 | 27 | export const expandFilteredNodes = (node, filter, matcher = defaultMatcher) => { 28 | let children = node.children 29 | if (!children || children.length === 0) { 30 | return Object.assign({}, node, { toggled: false }) 31 | } 32 | const childrenWithMatches = node.children.filter((child) => 33 | findNode(child, filter, matcher) 34 | ) 35 | const shouldExpand = childrenWithMatches.length > 0 36 | // If im going to expand, go through all the matches and see if thier children need to expand 37 | if (shouldExpand) { 38 | children = childrenWithMatches.map((child) => { 39 | return expandFilteredNodes(child, filter, matcher) 40 | }) 41 | } 42 | return Object.assign({}, node, { 43 | children: children, 44 | toggled: shouldExpand, 45 | }) 46 | } 47 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/shared/connect.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <Form layout="vertical"> 4 | <Field 5 | name="name" 6 | title="Name" 7 | required 8 | :decorator="[FormItem]" 9 | :component="[Input, { placeholder: 'Please Input' }]" 10 | /> 11 | <FormConsumer> 12 | <template #default="{ form }"> 13 | <div style="white-space: pre; margin-bottom: 16px"> 14 | {{ JSON.stringify(form.values, null, 2) }} 15 | </div> 16 | <Button 17 | type="primary" 18 | @click=" 19 | () => { 20 | form.submit(log) 21 | } 22 | " 23 | > 24 | Submit 25 | </Button> 26 | </template> 27 | </FormConsumer> 28 | </Form> 29 | </FormProvider> 30 | </template> 31 | 32 | <script> 33 | import { Form, Input, Button } from 'ant-design-vue' 34 | import { createForm, setValidateLanguage } from '@formily/core' 35 | import { 36 | FormProvider, 37 | FormConsumer, 38 | Field, 39 | connect, 40 | mapProps, 41 | } from '@formily/vue' 42 | import 'ant-design-vue/dist/antd.css' 43 | 44 | setValidateLanguage('en') 45 | 46 | const FormItem = connect( 47 | Form.Item, 48 | mapProps( 49 | { 50 | title: 'label', 51 | description: 'extra', 52 | required: true, 53 | validateStatus: true, 54 | }, 55 | (props, field) => { 56 | return { 57 | ...props, 58 | help: field.selfErrors?.length ? field.selfErrors : undefined, 59 | } 60 | } 61 | ) 62 | ) 63 | 64 | export default { 65 | components: { 66 | FormProvider, 67 | FormConsumer, 68 | Field, 69 | Form, 70 | Button, 71 | }, 72 | data() { 73 | const form = createForm({ validateFirst: true }) 74 | return { 75 | FormItem, 76 | Input, 77 | form, 78 | } 79 | }, 80 | methods: { 81 | log(...args) { 82 | console.log(...args) 83 | }, 84 | }, 85 | } 86 | </script> 87 | ``` -------------------------------------------------------------------------------- /packages/vue/docs/demos/api/shared/map-props.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <Form layout="vertical"> 4 | <Field 5 | name="name" 6 | title="Name" 7 | required 8 | :decorator="[FormItem]" 9 | :component="[Input, { placeholder: 'Please Input' }]" 10 | /> 11 | <FormConsumer> 12 | <template #default="{ form }"> 13 | <div style="white-space: pre; margin-bottom: 16px"> 14 | {{ JSON.stringify(form.values, null, 2) }} 15 | </div> 16 | <Button 17 | type="primary" 18 | @click=" 19 | () => { 20 | form.submit(log) 21 | } 22 | " 23 | > 24 | Submit 25 | </Button> 26 | </template> 27 | </FormConsumer> 28 | </Form> 29 | </FormProvider> 30 | </template> 31 | 32 | <script> 33 | import { Form, Input, Button } from 'ant-design-vue' 34 | import { createForm, setValidateLanguage } from '@formily/core' 35 | import { 36 | FormProvider, 37 | FormConsumer, 38 | Field, 39 | connect, 40 | mapProps, 41 | } from '@formily/vue' 42 | import 'ant-design-vue/dist/antd.css' 43 | 44 | setValidateLanguage('en') 45 | 46 | const FormItem = connect( 47 | Form.Item, 48 | mapProps( 49 | { 50 | title: 'label', 51 | description: 'extra', 52 | required: true, 53 | validateStatus: true, 54 | }, 55 | (props, field) => { 56 | return { 57 | ...props, 58 | help: field.selfErrors?.length ? field.selfErrors : undefined, 59 | } 60 | } 61 | ) 62 | ) 63 | 64 | export default { 65 | components: { 66 | FormProvider, 67 | FormConsumer, 68 | Field, 69 | Form, 70 | Button, 71 | }, 72 | data() { 73 | const form = createForm({ validateFirst: true }) 74 | return { 75 | FormItem, 76 | Input, 77 | form, 78 | } 79 | }, 80 | methods: { 81 | log(...args) { 82 | console.log(...args) 83 | }, 84 | }, 85 | } 86 | </script> 87 | ``` -------------------------------------------------------------------------------- /packages/element/src/array-table/style.scss: -------------------------------------------------------------------------------- ```scss 1 | @import '../__builtins__/styles/common.scss'; 2 | 3 | $array-table-prefix-cls: '#{$formily-prefix}-array-table'; 4 | 5 | .#{$array-table-prefix-cls} { 6 | .#{$formily-prefix}-form-item:not(.#{$formily-prefix}-form-item-feedback-layout-popover) { 7 | margin-bottom: 0 !important; 8 | } 9 | 10 | &-status-select-dropdown { 11 | .#{$namespace}-badge { 12 | line-height: 1; 13 | } 14 | } 15 | 16 | &-pagination { 17 | display: flex; 18 | justify-content: center; 19 | margin-top: 8px; 20 | 21 | .#{$array-table-prefix-cls}-status-select.has-error { 22 | .#{$namespace}-input__inner { 23 | border-color: $--color-danger !important; 24 | } 25 | } 26 | } 27 | 28 | .#{$namespace}-table { 29 | .cell { 30 | overflow: visible; 31 | } 32 | 33 | .cell.el-tooltip { 34 | overflow: hidden; 35 | } 36 | 37 | &__fixed { 38 | box-shadow: 10px 0 10px -10px rgb(0 0 0 / 12%); 39 | } 40 | 41 | &__fixed-right { 42 | box-shadow: -10px 0 10px -10px rgb(0 0 0 / 12%); 43 | } 44 | } 45 | 46 | .#{$formily-prefix}-form-item-help { 47 | position: absolute; 48 | font-size: 12px; 49 | top: 100%; 50 | background: #fff; 51 | width: 100%; 52 | margin-top: 3px; 53 | padding: 3px; 54 | z-index: 2; 55 | border-radius: 3px; 56 | box-shadow: 0 0 10px #eee; 57 | } 58 | 59 | .#{$formily-prefix}-array-base-addition { 60 | margin-top: 8px; 61 | width: 100%; 62 | border: $--border-width-base dashed $--border-color-base; 63 | 64 | &:hover { 65 | background-color: $--color-white; 66 | border-color: $--border-color-hover; 67 | } 68 | 69 | &:active, 70 | &:focus { 71 | background-color: $--color-white; 72 | border-color: $--color-primary; 73 | } 74 | } 75 | 76 | .#{$formily-prefix}-form-item-feedback-layout-popover { 77 | margin-bottom: 0; 78 | } 79 | 80 | &-inner-asterisk { 81 | color: $--color-danger; 82 | font-weight: $--font-weight-primary; 83 | } 84 | } 85 | ``` -------------------------------------------------------------------------------- /packages/next/src/space/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import { Box } from '@alifd/next' 3 | import { isNumberLike } from '@formily/shared' 4 | import { toArray, usePrefixCls } from '../__builtins__' 5 | import { useFormLayout } from '../form-layout' 6 | export interface ISpaceProps { 7 | prefix?: string 8 | className?: string 9 | style?: React.CSSProperties 10 | size?: number | 'small' | 'large' | 'middle' 11 | direction?: 'horizontal' | 'vertical' 12 | // No `stretch` since many components do not support that. 13 | align?: 'start' | 'end' | 'center' | 'baseline' 14 | wrap?: boolean 15 | } 16 | 17 | const spaceSize = { 18 | small: 8, 19 | middle: 16, 20 | large: 24, 21 | } 22 | 23 | export const Space: React.FC<React.PropsWithChildren<ISpaceProps>> = ({ 24 | direction = 'horizontal', 25 | size, 26 | align = 'start', 27 | ...props 28 | }) => { 29 | const layout = useFormLayout() 30 | const prefix = usePrefixCls('space', props) 31 | const getDirection = () => { 32 | if (direction === 'horizontal') { 33 | return 'row' 34 | } else { 35 | return 'column' 36 | } 37 | } 38 | const getAlign = () => { 39 | if (align === 'start') { 40 | return 'flex-start' 41 | } else if (align === 'end') { 42 | return 'flex-end' 43 | } else { 44 | return 'center' 45 | } 46 | } 47 | const _size = size ?? layout?.spaceGap ?? 8 48 | const _align = getAlign() 49 | return ( 50 | <Box 51 | {...props} 52 | spacing={isNumberLike(_size) ? _size : spaceSize[_size] || 8} 53 | style={{ 54 | alignItems: _align, 55 | display: 'inline-flex', 56 | ...props.style, 57 | }} 58 | align={_align} 59 | direction={getDirection()} 60 | > 61 | {toArray(props.children, { keepEmpty: true }).map((child, index) => ( 62 | <div className={`${prefix}-item`} key={index}> 63 | {child} 64 | </div> 65 | ))} 66 | </Box> 67 | ) 68 | } 69 | 70 | export default Space 71 | ``` -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@formily/react", 3 | "version": "2.3.7", 4 | "license": "MIT", 5 | "main": "lib", 6 | "module": "esm", 7 | "umd:main": "dist/formily.react.umd.production.js", 8 | "unpkg": "dist/formily.react.umd.production.js", 9 | "jsdelivr": "dist/formily.react.umd.production.js", 10 | "jsnext:main": "esm", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/alibaba/formily.git" 14 | }, 15 | "types": "esm/index.d.ts", 16 | "bugs": { 17 | "url": "https://github.com/alibaba/formily/issues" 18 | }, 19 | "homepage": "https://github.com/alibaba/formily#readme", 20 | "engines": { 21 | "npm": ">=3.0.0" 22 | }, 23 | "scripts": { 24 | "start": "dumi dev", 25 | "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm && npm run build:umd", 26 | "build:cjs": "tsc --project tsconfig.build.json", 27 | "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", 28 | "build:umd": "rollup --config", 29 | "build:docs": "dumi build" 30 | }, 31 | "peerDependencies": { 32 | "@types/react": ">=16.8.0", 33 | "@types/react-dom": ">=16.8.0", 34 | "react": ">=16.8.0", 35 | "react-dom": ">=16.8.0", 36 | "react-is": ">=16.8.0" 37 | }, 38 | "peerDependenciesMeta": { 39 | "@types/react": { 40 | "optional": true 41 | }, 42 | "@types/react-dom": { 43 | "optional": true 44 | } 45 | }, 46 | "devDependencies": { 47 | "dumi": "^1.1.0-rc.8" 48 | }, 49 | "dependencies": { 50 | "@formily/core": "2.3.7", 51 | "@formily/json-schema": "2.3.7", 52 | "@formily/reactive": "2.3.7", 53 | "@formily/reactive-react": "2.3.7", 54 | "@formily/shared": "2.3.7", 55 | "@formily/validator": "2.3.7", 56 | "hoist-non-react-statics": "^3.3.2" 57 | }, 58 | "publishConfig": { 59 | "access": "public" 60 | }, 61 | "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" 62 | } 63 | ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/app/components/SearchBox.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const SerachBox = styled.div` 5 | display: flex; 6 | align-items: center; 7 | height: 100%; 8 | .input-addon { 9 | padding: 0 5px; 10 | } 11 | .form-control { 12 | width: 50%; 13 | border: none; 14 | background: transparent; 15 | color: white; 16 | outline: none; 17 | } 18 | ` 19 | 20 | const SearchIcon = () => { 21 | return ( 22 | <svg 23 | t="1592193216787" 24 | className="icon" 25 | viewBox="0 0 1024 1024" 26 | version="1.1" 27 | p-id="3365" 28 | width="12" 29 | height="12" 30 | > 31 | <defs> 32 | <style type="text/css"></style> 33 | </defs> 34 | <path 35 | 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" 36 | p-id="3366" 37 | fill="#9da5ab" 38 | ></path> 39 | </svg> 40 | ) 41 | } 42 | 43 | export default ({ onSearch }) => { 44 | return ( 45 | <SerachBox> 46 | <div className="input-addon"> 47 | <SearchIcon /> 48 | </div> 49 | <input 50 | className="form-control" 51 | onChange={onSearch} 52 | placeholder="Search the field..." 53 | type="text" 54 | /> 55 | </SerachBox> 56 | ) 57 | } 58 | ``` -------------------------------------------------------------------------------- /packages/antd/src/upload/placeholder.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const UPLOAD_PLACEHOLDER = [ 2 | { 3 | ext: /\.docx/i, 4 | icon: '//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png', 5 | }, 6 | { 7 | ext: /\.pptx/i, 8 | icon: '//img.alicdn.com/tfs/TB1ItgWr_tYBeNjy1XdXXXXyVXa-200-200.png', 9 | }, 10 | { 11 | ext: /\.jpe?g/i, 12 | icon: '//img.alicdn.com/tfs/TB1wrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', 13 | }, 14 | { 15 | ext: /\.pdf/i, 16 | icon: '//img.alicdn.com/tfs/TB1GwD8r9BYBeNjy0FeXXbnmFXa-200-200.png', 17 | }, 18 | { 19 | ext: /\.png/i, 20 | icon: '//img.alicdn.com/tfs/TB1BHT5r9BYBeNjy0FeXXbnmFXa-200-200.png', 21 | }, 22 | { 23 | ext: /\.eps/i, 24 | icon: '//img.alicdn.com/tfs/TB1G_iGrVOWBuNjy0FiXXXFxVXa-200-200.png', 25 | }, 26 | { 27 | ext: /\.ai/i, 28 | icon: '//img.alicdn.com/tfs/TB1B2cVr_tYBeNjy1XdXXXXyVXa-200-200.png', 29 | }, 30 | { 31 | ext: /\.gif/i, 32 | icon: '//img.alicdn.com/tfs/TB1DTiGrVOWBuNjy0FiXXXFxVXa-200-200.png', 33 | }, 34 | { 35 | ext: /\.svg/i, 36 | icon: '//img.alicdn.com/tfs/TB1uUm9rY9YBuNjy0FgXXcxcXXa-200-200.png', 37 | }, 38 | { 39 | ext: /\.xlsx?/i, 40 | icon: '//img.alicdn.com/tfs/TB1any1r1OSBuNjy0FdXXbDnVXa-200-200.png', 41 | }, 42 | { 43 | ext: /\.psd?/i, 44 | icon: '//img.alicdn.com/tfs/TB1_nu1r1OSBuNjy0FdXXbDnVXa-200-200.png', 45 | }, 46 | { 47 | ext: /\.(wav|aif|aiff|au|mp1|mp2|mp3|ra|rm|ram|mid|rmi)/i, 48 | icon: '//img.alicdn.com/tfs/TB1jPvwr49YBuNjy0FfXXXIsVXa-200-200.png', 49 | }, 50 | { 51 | ext: /\.(avi|wmv|mpg|mpeg|vob|dat|3gp|mp4|mkv|rm|rmvb|mov|flv)/i, 52 | icon: '//img.alicdn.com/tfs/TB1FrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', 53 | }, 54 | { 55 | ext: /\.(zip|rar|arj|z|gz|iso|jar|ace|tar|uue|dmg|pkg|lzh|cab)/i, 56 | icon: '//img.alicdn.com/tfs/TB10jmfr29TBuNjy0FcXXbeiFXa-200-200.png', 57 | }, 58 | { 59 | ext: /\.[^.]+/i, 60 | icon: '//img.alicdn.com/tfs/TB10.R4r3mTBuNjy1XbXXaMrVXa-200-200.png', 61 | }, 62 | ] 63 | ``` -------------------------------------------------------------------------------- /packages/next/src/upload/placeholder.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const UPLOAD_PLACEHOLDER = [ 2 | { 3 | ext: /\.docx/i, 4 | icon: '//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png', 5 | }, 6 | { 7 | ext: /\.pptx/i, 8 | icon: '//img.alicdn.com/tfs/TB1ItgWr_tYBeNjy1XdXXXXyVXa-200-200.png', 9 | }, 10 | { 11 | ext: /\.jpe?g/i, 12 | icon: '//img.alicdn.com/tfs/TB1wrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', 13 | }, 14 | { 15 | ext: /\.pdf/i, 16 | icon: '//img.alicdn.com/tfs/TB1GwD8r9BYBeNjy0FeXXbnmFXa-200-200.png', 17 | }, 18 | { 19 | ext: /\.png/i, 20 | icon: '//img.alicdn.com/tfs/TB1BHT5r9BYBeNjy0FeXXbnmFXa-200-200.png', 21 | }, 22 | { 23 | ext: /\.eps/i, 24 | icon: '//img.alicdn.com/tfs/TB1G_iGrVOWBuNjy0FiXXXFxVXa-200-200.png', 25 | }, 26 | { 27 | ext: /\.ai/i, 28 | icon: '//img.alicdn.com/tfs/TB1B2cVr_tYBeNjy1XdXXXXyVXa-200-200.png', 29 | }, 30 | { 31 | ext: /\.gif/i, 32 | icon: '//img.alicdn.com/tfs/TB1DTiGrVOWBuNjy0FiXXXFxVXa-200-200.png', 33 | }, 34 | { 35 | ext: /\.svg/i, 36 | icon: '//img.alicdn.com/tfs/TB1uUm9rY9YBuNjy0FgXXcxcXXa-200-200.png', 37 | }, 38 | { 39 | ext: /\.xlsx?/i, 40 | icon: '//img.alicdn.com/tfs/TB1any1r1OSBuNjy0FdXXbDnVXa-200-200.png', 41 | }, 42 | { 43 | ext: /\.psd?/i, 44 | icon: '//img.alicdn.com/tfs/TB1_nu1r1OSBuNjy0FdXXbDnVXa-200-200.png', 45 | }, 46 | { 47 | ext: /\.(wav|aif|aiff|au|mp1|mp2|mp3|ra|rm|ram|mid|rmi)/i, 48 | icon: '//img.alicdn.com/tfs/TB1jPvwr49YBuNjy0FfXXXIsVXa-200-200.png', 49 | }, 50 | { 51 | ext: /\.(avi|wmv|mpg|mpeg|vob|dat|3gp|mp4|mkv|rm|rmvb|mov|flv)/i, 52 | icon: '//img.alicdn.com/tfs/TB1FrT5r9BYBeNjy0FeXXbnmFXa-200-200.png', 53 | }, 54 | { 55 | ext: /\.(zip|rar|arj|z|gz|iso|jar|ace|tar|uue|dmg|pkg|lzh|cab)/i, 56 | icon: '//img.alicdn.com/tfs/TB10jmfr29TBuNjy0FcXXbeiFXa-200-200.png', 57 | }, 58 | { 59 | ext: /\.[^.]+/i, 60 | icon: '//img.alicdn.com/tfs/TB10.R4r3mTBuNjy1XbXXaMrVXa-200-200.png', 61 | }, 62 | ] 63 | ``` -------------------------------------------------------------------------------- /packages/shared/src/isEmpty.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { instOf } from './instanceof' 2 | const has = Object.prototype.hasOwnProperty 3 | 4 | const toString = Object.prototype.toString 5 | 6 | export const isUndef = (val: any) => val === undefined 7 | 8 | export const isValid = (val: any) => val !== undefined && val !== null 9 | 10 | export function isEmpty(val: any, strict = false): boolean { 11 | // Null and Undefined... 12 | if (val == null) { 13 | return true 14 | } 15 | 16 | // Booleans... 17 | if (typeof val === 'boolean') { 18 | return false 19 | } 20 | 21 | // Numbers... 22 | if (typeof val === 'number') { 23 | return false 24 | } 25 | 26 | // Strings... 27 | if (typeof val === 'string') { 28 | return val.length === 0 29 | } 30 | 31 | // Functions... 32 | if (typeof val === 'function') { 33 | return val.length === 0 34 | } 35 | 36 | // Arrays... 37 | if (Array.isArray(val)) { 38 | if (val.length === 0) { 39 | return true 40 | } 41 | for (let i = 0; i < val.length; i++) { 42 | if (strict) { 43 | if (val[i] !== undefined && val[i] !== null) { 44 | return false 45 | } 46 | } else { 47 | if ( 48 | val[i] !== undefined && 49 | val[i] !== null && 50 | val[i] !== '' && 51 | val[i] !== 0 52 | ) { 53 | return false 54 | } 55 | } 56 | } 57 | return true 58 | } 59 | 60 | // Errors... 61 | if (instOf(val, 'Error')) { 62 | return val.message === '' 63 | } 64 | 65 | // Objects... 66 | if (val.toString === toString) { 67 | switch (val.toString()) { 68 | // Maps, Sets, Files and Errors... 69 | case '[object File]': 70 | case '[object Map]': 71 | case '[object Set]': { 72 | return val.size === 0 73 | } 74 | 75 | // Plain objects... 76 | case '[object Object]': { 77 | for (const key in val) { 78 | if (has.call(val, key)) { 79 | return false 80 | } 81 | } 82 | 83 | return true 84 | } 85 | } 86 | } 87 | 88 | // Anything else... 89 | return false 90 | } 91 | ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/extension/manifest.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "version": "0.1.14", 3 | "name": "Formily DevTools", 4 | "short_name": "Formily DevTools", 5 | "description": "Formily DevTools for debugging application's state changes.", 6 | "homepage_url": "https://github.com/alibaba/formily", 7 | "manifest_version": 3, 8 | "action": { 9 | "default_icon": "img/logo/scalable.png", 10 | "default_title": "Formily DevTools", 11 | "default_popup": "popup.html" 12 | }, 13 | "commands": { 14 | "devtools-left": { 15 | "description": "DevTools window to left" 16 | }, 17 | "devtools-right": { 18 | "description": "DevTools window to right" 19 | }, 20 | "devtools-bottom": { 21 | "description": "DevTools window to bottom" 22 | }, 23 | "devtools-remote": { 24 | "description": "Remote DevTools" 25 | }, 26 | "_execute_action": { 27 | "suggested_key": { 28 | "default": "Ctrl+Shift+E" 29 | } 30 | } 31 | }, 32 | "icons": { 33 | "16": "img/logo/16x16.png", 34 | "48": "img/logo/48x48.png", 35 | "128": "img/logo/128x128.png" 36 | }, 37 | "background": { 38 | "service_worker": "js/background.bundle.js", 39 | "type": "module" 40 | }, 41 | "content_scripts": [ 42 | { 43 | "matches": ["<all_urls>"], 44 | "exclude_matches": ["*://www.google.com/*"], 45 | "js": ["js/content.bundle.js", "js/inject.bundle.js"], 46 | "run_at": "document_start", 47 | "all_frames": true 48 | } 49 | ], 50 | "devtools_page": "devtools.html", 51 | "web_accessible_resources": [ 52 | { 53 | "resources": ["js/backend.bundle.js"], 54 | "matches": ["<all_urls>"] 55 | } 56 | ], 57 | "externally_connectable": { 58 | "ids": ["*"] 59 | }, 60 | "host_permissions": ["file://*/*", "http://*/*", "https://*/*"], 61 | "content_security_policy": { 62 | "extension_pages": "script-src 'self'; object-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" 63 | }, 64 | "update_url": "https://clients2.google.com/service/update2/crx" 65 | } 66 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/cascader/template.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <Form :form="form"> 3 | <Field 4 | name="address" 5 | title="地址选择" 6 | required 7 | :decorator="[FormItem]" 8 | :component="[ 9 | Cascader, 10 | { 11 | style: { 12 | width: '240px', 13 | }, 14 | }, 15 | ]" 16 | /> 17 | 18 | <Submit @submit="onSubmit">提交</Submit> 19 | </Form> 20 | </template> 21 | 22 | <script> 23 | import { createForm, onFieldReact } from '@formily/core' 24 | import { Field } from '@formily/vue' 25 | import { Form, FormItem, Cascader, Submit } from '@formily/element' 26 | import { action } from '@formily/reactive' 27 | import axios from 'axios' 28 | 29 | const transformAddress = (data = {}) => { 30 | return Object.entries(data).reduce((buf, [key, value]) => { 31 | if (typeof value === 'string') 32 | return buf.concat({ 33 | label: value, 34 | value: key, 35 | }) 36 | const { name, code, cities, districts } = value 37 | const _cities = transformAddress(cities) 38 | const _districts = transformAddress(districts) 39 | return buf.concat({ 40 | label: name, 41 | value: code, 42 | children: _cities.length 43 | ? _cities 44 | : _districts.length 45 | ? _districts 46 | : undefined, 47 | }) 48 | }, []) 49 | } 50 | 51 | const useAddress = (pattern) => { 52 | onFieldReact(pattern, (field) => { 53 | field.loading = true 54 | axios('//unpkg.com/china-location/dist/location.json') 55 | .then((res) => res.data) 56 | .then( 57 | action.bound((data) => { 58 | field.dataSource = transformAddress(data) 59 | field.loading = false 60 | }) 61 | ) 62 | }) 63 | } 64 | 65 | const form = createForm({ 66 | effects: () => { 67 | useAddress('address') 68 | }, 69 | }) 70 | 71 | export default { 72 | components: { Form, Field, Submit }, 73 | data() { 74 | return { 75 | FormItem, 76 | Cascader, 77 | form, 78 | } 79 | }, 80 | methods: { 81 | onSubmit(value) { 82 | console.log(value) 83 | }, 84 | }, 85 | } 86 | </script> 87 | ``` -------------------------------------------------------------------------------- /packages/next/src/array-items/main.scss: -------------------------------------------------------------------------------- ```scss 1 | @import '~@alifd/next/lib/core/index-noreset.scss'; 2 | 3 | $array-items-prefix-cls: '#{$css-prefix}formily-array-items'; 4 | 5 | .#{$array-items-prefix-cls} { 6 | .#{$css-prefix}form-item { 7 | margin-bottom: 0; 8 | } 9 | } 10 | 11 | // fix https://github.com/alibaba/formily/issues/2891 12 | .#{$array-items-prefix-cls}-item { 13 | z-index: 100000; 14 | } 15 | 16 | .#{$array-items-prefix-cls}-sort-handler { 17 | cursor: move; 18 | color: #888 !important; 19 | } 20 | 21 | .#{$array-items-prefix-cls}-item-inner { 22 | margin-bottom: 10px; 23 | visibility: visible; 24 | } 25 | 26 | .#{$array-items-prefix-cls}-card { 27 | display: flex; 28 | border: 1px solid #eee; 29 | margin-bottom: 10px; 30 | padding: 3px 6px; 31 | background: #fff; 32 | justify-content: space-between; 33 | align-items: center; 34 | 35 | transition: all 0.35s; 36 | 37 | .#{$css-prefix}formily-item:not(.#{$css-prefix}formily-item-feedback-layout-popover) { 38 | margin-bottom: 0 !important; 39 | position: relative; 40 | 41 | .#{$css-prefix}formily-item-help { 42 | position: absolute; 43 | font-size: 12px; 44 | top: 100%; 45 | background: #fff; 46 | width: 100%; 47 | margin-top: 3px; 48 | padding: 3px; 49 | z-index: 1; 50 | border-radius: 3px; 51 | box-shadow: 0 0 10px #eee; 52 | } 53 | } 54 | } 55 | 56 | .#{$array-items-prefix-cls}-divide { 57 | display: flex; 58 | border-bottom: 1px solid #eee; 59 | margin-bottom: 10px; 60 | padding: 10px 0; 61 | background: #fff; 62 | justify-content: space-between; 63 | align-items: center; 64 | 65 | .#{$css-prefix}formily-item:not(.#{$css-prefix}formily-item-feedback-layout-popover) { 66 | margin-bottom: 0 !important; 67 | position: relative; 68 | 69 | .#{$css-prefix}formily-item-help { 70 | position: absolute; 71 | font-size: 12px; 72 | top: 100%; 73 | background: #fff; 74 | width: 100%; 75 | margin-top: 3px; 76 | padding: 3px; 77 | z-index: 1; 78 | border-radius: 3px; 79 | box-shadow: 0 0 10px #eee; 80 | } 81 | } 82 | } 83 | ``` -------------------------------------------------------------------------------- /packages/element/src/array-items/style.scss: -------------------------------------------------------------------------------- ```scss 1 | @import '../__builtins__/styles/common.scss'; 2 | 3 | $array-items-prefix-cls: '#{$formily-prefix}-array-items'; 4 | 5 | .#{$array-items-prefix-cls}-item-inner { 6 | visibility: visible; 7 | } 8 | 9 | .#{$array-items-prefix-cls} { 10 | .#{$formily-prefix}-array-base-addition { 11 | width: 100%; 12 | border: $--border-width-base dashed $--border-color-base; 13 | 14 | &:hover { 15 | background-color: $--color-white; 16 | border-color: $--border-color-hover; 17 | } 18 | 19 | &:active, 20 | &:focus { 21 | background-color: $--color-white; 22 | border-color: $--color-primary; 23 | } 24 | } 25 | } 26 | 27 | .#{$array-items-prefix-cls}-card { 28 | display: flex; 29 | border: 1px solid $--card-border-color; 30 | margin-bottom: 10px; 31 | padding: 3px 6px; 32 | background: $--color-white; 33 | justify-content: space-between; 34 | 35 | .#{$formily-prefix}-form-item:not(.#{$formily-prefix}-form-item-feedback-layout-popover) { 36 | margin-bottom: 0 !important; 37 | 38 | .#{$formily-prefix}-form-item-help { 39 | position: absolute; 40 | font-size: 12px; 41 | top: 100%; 42 | background: $--color-white; 43 | width: 100%; 44 | margin-top: 3px; 45 | padding: 3px; 46 | z-index: 1; 47 | border-radius: 3px; 48 | box-shadow: 0 0 10px $--border-color-base; 49 | } 50 | } 51 | } 52 | 53 | .#{$array-items-prefix-cls}-divide { 54 | display: flex; 55 | border-bottom: 1px solid $--card-border-color; 56 | padding: 10px 0; 57 | justify-content: space-between; 58 | 59 | .#{$formily-prefix}-form-item:not(.#{$formily-prefix}-form-item-feedback-layout-popover) { 60 | margin-bottom: 0 !important; 61 | 62 | .#{$formily-prefix}-form-item-help { 63 | position: absolute; 64 | font-size: 12px; 65 | top: 100%; 66 | background: $--color-white; 67 | width: 100%; 68 | margin-top: 3px; 69 | padding: 3px; 70 | z-index: 1; 71 | border-radius: 3px; 72 | box-shadow: 0 0 10px $--card-border-color; 73 | } 74 | } 75 | } 76 | ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/assets/img/loading.svg: -------------------------------------------------------------------------------- ``` 1 | <!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL --> 2 | <svg width="45" height="45" viewBox="0 0 45 45" xmlns="http://www.w3.org/2000/svg" stroke="#fff"> 3 | <g fill="none" fill-rule="evenodd" transform="translate(1 1)" stroke-width="2"> 4 | <circle cx="22" cy="22" r="6" stroke-opacity="0"> 5 | <animate attributeName="r" 6 | begin="1.5s" dur="3s" 7 | values="6;22" 8 | calcMode="linear" 9 | repeatCount="indefinite" /> 10 | <animate attributeName="stroke-opacity" 11 | begin="1.5s" dur="3s" 12 | values="1;0" calcMode="linear" 13 | repeatCount="indefinite" /> 14 | <animate attributeName="stroke-width" 15 | begin="1.5s" dur="3s" 16 | values="2;0" calcMode="linear" 17 | repeatCount="indefinite" /> 18 | </circle> 19 | <circle cx="22" cy="22" r="6" stroke-opacity="0"> 20 | <animate attributeName="r" 21 | begin="3s" dur="3s" 22 | values="6;22" 23 | calcMode="linear" 24 | repeatCount="indefinite" /> 25 | <animate attributeName="stroke-opacity" 26 | begin="3s" dur="3s" 27 | values="1;0" calcMode="linear" 28 | repeatCount="indefinite" /> 29 | <animate attributeName="stroke-width" 30 | begin="3s" dur="3s" 31 | values="2;0" calcMode="linear" 32 | repeatCount="indefinite" /> 33 | </circle> 34 | <circle cx="22" cy="22" r="8"> 35 | <animate attributeName="r" 36 | begin="0s" dur="1.5s" 37 | values="6;1;2;3;4;5;6" 38 | calcMode="linear" 39 | repeatCount="indefinite" /> 40 | </circle> 41 | </g> 42 | </svg> ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/config/webpack.base.ts: -------------------------------------------------------------------------------- ```typescript 1 | import path from 'path' 2 | import fs from 'fs-extra' 3 | 4 | const getEntry = (src) => { 5 | return [path.resolve(__dirname, '../src/extension/', src)] 6 | } 7 | 8 | // 先确保删除package目录,再创建新的 9 | const packageDir = path.resolve(__dirname, '../package') 10 | if (fs.existsSync(packageDir)) { 11 | fs.removeSync(packageDir) 12 | } 13 | fs.ensureDirSync(packageDir) 14 | 15 | fs.copy(path.resolve(__dirname, '../assets'), packageDir) 16 | 17 | fs.copy( 18 | path.resolve(__dirname, '../src/extension/manifest.json'), 19 | path.resolve(__dirname, '../package/manifest.json') 20 | ) 21 | 22 | export default { 23 | mode: 'development', 24 | devtool: 'inline-source-map', // 嵌入到源文件中 25 | entry: { 26 | popup: getEntry('./popup.tsx'), 27 | devtools: getEntry('./devtools.tsx'), 28 | devpanel: getEntry('./devpanel.tsx'), 29 | content: getEntry('./content.ts'), 30 | backend: getEntry('./backend.ts'), 31 | demo: getEntry('../app/demo.tsx'), 32 | inject: getEntry('./inject.ts'), 33 | background: getEntry('./background.ts'), 34 | }, 35 | output: { 36 | path: path.resolve(__dirname, '../package'), 37 | filename: 'js/[name].bundle.js', 38 | }, 39 | resolve: { 40 | modules: ['node_modules'], 41 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], 42 | }, 43 | module: { 44 | rules: [ 45 | { 46 | test: /\.tsx?$/, 47 | use: [ 48 | { 49 | loader: require.resolve('ts-loader'), 50 | options: { 51 | transpileOnly: true, 52 | }, 53 | }, 54 | ], 55 | }, 56 | { 57 | test: /\.css$/, 58 | use: [ 59 | { 60 | loader: require.resolve('style-loader'), 61 | options: { 62 | singleton: true, 63 | }, 64 | }, 65 | require.resolve('css-loader'), 66 | ], 67 | }, 68 | { 69 | test: /\.html?$/, 70 | loader: require.resolve('file-loader'), 71 | options: { 72 | name: '[name].[ext]', 73 | }, 74 | }, 75 | ], 76 | }, 77 | } 78 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-tabs/markup-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaArrayField 5 | name="string_array" 6 | x-decorator="FormItem" 7 | title="字符串数组" 8 | :maxItems="3" 9 | x-component="ArrayTabs" 10 | > 11 | <SchemaStringField 12 | x-decorator="FormItem" 13 | required 14 | x-component="Input" 15 | /> 16 | </SchemaArrayField> 17 | <SchemaArrayField 18 | name="array" 19 | x-decorator="FormItem" 20 | title="对象数组" 21 | :maxItems="3" 22 | x-component="ArrayTabs" 23 | > 24 | <SchemaObjectField> 25 | <SchemaStringField 26 | x-decorator="FormItem" 27 | title="AAA" 28 | name="aaa" 29 | required 30 | x-component="Input" 31 | /> 32 | <SchemaStringField 33 | x-decorator="FormItem" 34 | title="BBB" 35 | name="bbb" 36 | required 37 | x-component="Input" 38 | /> 39 | </SchemaObjectField> 40 | </SchemaArrayField> 41 | </SchemaField> 42 | <FormButtonGroup> 43 | <Submit @submit="log">提交</Submit> 44 | </FormButtonGroup> 45 | </FormProvider> 46 | </template> 47 | 48 | <script> 49 | import { createForm } from '@formily/core' 50 | import { FormProvider, createSchemaField } from '@formily/vue' 51 | import { 52 | FormItem, 53 | FormButtonGroup, 54 | Submit, 55 | Input, 56 | Select, 57 | ArrayTabs, 58 | } from '@formily/element' 59 | import { Button } from 'element-ui' 60 | 61 | const SchemaField = createSchemaField({ 62 | components: { 63 | FormItem, 64 | Input, 65 | Select, 66 | ArrayTabs, 67 | }, 68 | }) 69 | 70 | export default { 71 | components: { 72 | FormProvider, 73 | FormButtonGroup, 74 | Button, 75 | Submit, 76 | ...SchemaField, 77 | }, 78 | 79 | data() { 80 | const form = createForm() 81 | 82 | return { 83 | form, 84 | } 85 | }, 86 | methods: { 87 | log(values) { 88 | console.log(values) 89 | }, 90 | }, 91 | } 92 | </script> 93 | 94 | <style lang="scss" scoped></style> 95 | ``` -------------------------------------------------------------------------------- /packages/next/src/form/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { useMemo } from 'react' 2 | import { FormProvider, JSXComponent, useParentForm } from '@formily/react' 3 | import { FormLayout, IFormLayoutProps } from '../form-layout' 4 | import { ConfigProvider } from '@alifd/next' 5 | import { 6 | getValidateLocaleIOSCode, 7 | setValidateLanguage, 8 | Form as FormType, 9 | ObjectField, 10 | IFormFeedback, 11 | } from '@formily/core' 12 | import { PreviewText } from '../preview-text' 13 | export interface FormProps extends IFormLayoutProps { 14 | form?: FormType 15 | component?: JSXComponent 16 | onAutoSubmit?: (values: any) => any 17 | onAutoSubmitFailed?: (feedbacks: IFormFeedback[]) => void 18 | previewTextPlaceholder?: React.ReactNode 19 | } 20 | 21 | export const Form: React.FC<React.PropsWithChildren<FormProps>> = ({ 22 | form, 23 | component = 'form', 24 | onAutoSubmit, 25 | onAutoSubmitFailed, 26 | previewTextPlaceholder, 27 | ...props 28 | }) => { 29 | const top = useParentForm() 30 | const lang = 31 | (ConfigProvider as any).getContext()?.locale?.momentLocale ?? 'zh-CN' 32 | useMemo(() => { 33 | const validateLanguage = getValidateLocaleIOSCode(lang) 34 | setValidateLanguage(validateLanguage) 35 | }, [lang]) 36 | 37 | const renderContent = (form: FormType | ObjectField) => ( 38 | <PreviewText.Placeholder value={previewTextPlaceholder}> 39 | <FormLayout {...props}> 40 | {React.createElement( 41 | component, 42 | { 43 | onSubmit(e: React.FormEvent) { 44 | e?.stopPropagation?.() 45 | e?.preventDefault?.() 46 | form.submit(onAutoSubmit).catch(onAutoSubmitFailed) 47 | }, 48 | }, 49 | props.children 50 | )} 51 | </FormLayout> 52 | </PreviewText.Placeholder> 53 | ) 54 | 55 | if (form) 56 | return <FormProvider form={form}>{renderContent(form)}</FormProvider> 57 | if (!top) throw new Error('must pass form instance by createForm') 58 | return renderContent(top) 59 | } 60 | 61 | export default Form 62 | ``` -------------------------------------------------------------------------------- /packages/next/src/array-cards/main.scss: -------------------------------------------------------------------------------- ```scss 1 | @import '~@alifd/next/lib/core/index-noreset.scss'; 2 | 3 | $array-cards-prefix-cls: '#{$css-prefix}formily-array-cards'; 4 | 5 | .#{$css-prefix}empty { 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | 10 | &-image { 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | transform: scale(0.8); 15 | 16 | .ant-empty-img-default-ellipse { 17 | fill-opacity: 0.8; 18 | fill: #f5f5f5; 19 | } 20 | 21 | .ant-empty-img-default-path-1 { 22 | fill: #aeb8c2; 23 | } 24 | 25 | .ant-empty-img-default-path-2 { 26 | fill: url(#linearGradient-1); 27 | } 28 | 29 | .ant-empty-img-default-path-3 { 30 | fill: #f5f5f7; 31 | } 32 | 33 | .ant-empty-img-default-path-4, 34 | .ant-empty-img-default-path-5 { 35 | fill: #dce0e6; 36 | } 37 | 38 | .ant-empty-img-default-g { 39 | fill: #fff; 40 | } 41 | 42 | .ant-empty-img-simple-ellipse { 43 | fill: #f5f5f5; 44 | } 45 | 46 | .ant-empty-img-simple-g { 47 | stroke: #d9d9d9; 48 | } 49 | 50 | .ant-empty-img-simple-path { 51 | fill: #fafafa; 52 | } 53 | 54 | .ant-empty-rtl { 55 | direction: rtl; 56 | } 57 | } 58 | } 59 | 60 | .#{$array-cards-prefix-cls}-remove { 61 | transition: all 0.25s ease-in-out; 62 | color: $color-text1-3; 63 | font-size: 16px; 64 | margin-left: 6px; 65 | 66 | &:hover { 67 | color: $color-text1-1; 68 | } 69 | } 70 | 71 | .#{$array-cards-prefix-cls}-addition { 72 | transition: all 0.25s ease-in-out; 73 | } 74 | 75 | .#{$array-cards-prefix-cls}-move-down { 76 | transition: all 0.25s ease-in-out; 77 | color: $color-text1-3; 78 | font-size: 16px; 79 | margin-left: 6px; 80 | 81 | &:hover { 82 | color: $color-text1-1; 83 | } 84 | } 85 | 86 | .#{$array-cards-prefix-cls}-move-up { 87 | transition: all 0.25s ease-in-out; 88 | color: $color-text1-3; 89 | font-size: 16px; 90 | margin-left: 6px; 91 | 92 | &:hover { 93 | color: $color-text1-1; 94 | } 95 | } 96 | 97 | .#{$array-cards-prefix-cls}-item { 98 | margin-bottom: 10px !important; 99 | } 100 | 101 | .next-card-extra { 102 | svg { 103 | margin-right: 6px; 104 | &:last-of-type { 105 | margin-right: 0; 106 | } 107 | } 108 | } 109 | ``` -------------------------------------------------------------------------------- /packages/element/src/form-button-group/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { h } from '@formily/vue' 2 | import { defineComponent } from 'vue-demi' 3 | import { FormBaseItem } from '../form-item' 4 | import { Space, SpaceProps } from '../space' 5 | import { stylePrefix } from '../__builtins__/configs' 6 | 7 | export type FormButtonGroupProps = Omit<SpaceProps, 'align' | 'size'> & { 8 | align?: 'left' | 'right' | 'center' 9 | gutter?: number 10 | className?: string 11 | alignFormItem: boolean 12 | } 13 | 14 | export const FormButtonGroup = defineComponent<FormButtonGroupProps>({ 15 | name: 'FFormButtonGroup', 16 | props: { 17 | align: { 18 | type: String, 19 | default: 'left', 20 | }, 21 | gutter: { 22 | type: Number, 23 | default: 8, 24 | }, 25 | alignFormItem: { 26 | type: Boolean, 27 | default: false, 28 | }, 29 | }, 30 | setup(props, { slots, attrs }) { 31 | const prefixCls = `${stylePrefix}-form-button-group` 32 | return () => { 33 | if (props.alignFormItem) { 34 | return h( 35 | FormBaseItem, 36 | { 37 | style: { 38 | margin: 0, 39 | padding: 0, 40 | width: '100%', 41 | }, 42 | attrs: { 43 | colon: false, 44 | label: ' ', 45 | ...attrs, 46 | }, 47 | }, 48 | { 49 | default: () => h(Space, { props: { size: props.gutter } }, slots), 50 | } 51 | ) 52 | } else { 53 | return h( 54 | Space, 55 | { 56 | class: [prefixCls], 57 | style: { 58 | justifyContent: 59 | props.align === 'left' 60 | ? 'flex-start' 61 | : props.align === 'right' 62 | ? 'flex-end' 63 | : 'center', 64 | display: 'flex', 65 | }, 66 | props: { 67 | ...attrs, 68 | size: props.gutter, 69 | }, 70 | attrs, 71 | }, 72 | slots 73 | ) 74 | } 75 | } 76 | }, 77 | }) 78 | 79 | export default FormButtonGroup 80 | ``` -------------------------------------------------------------------------------- /packages/next/src/array-collapse/main.scss: -------------------------------------------------------------------------------- ```scss 1 | @import '~@alifd/next/lib/core/index-noreset.scss'; 2 | 3 | $array-collapse-prefix-cls: '#{$css-prefix}formily-array-collapse'; 4 | 5 | .#{$css-prefix}empty { 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | 10 | &-image { 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | transform: scale(0.8); 15 | 16 | .ant-empty-img-default-ellipse { 17 | fill-opacity: 0.8; 18 | fill: #f5f5f5; 19 | } 20 | 21 | .ant-empty-img-default-path-1 { 22 | fill: #aeb8c2; 23 | } 24 | 25 | .ant-empty-img-default-path-2 { 26 | fill: url(#linearGradient-1); 27 | } 28 | 29 | .ant-empty-img-default-path-3 { 30 | fill: #f5f5f7; 31 | } 32 | 33 | .ant-empty-img-default-path-4, 34 | .ant-empty-img-default-path-5 { 35 | fill: #dce0e6; 36 | } 37 | 38 | .ant-empty-img-default-g { 39 | fill: #fff; 40 | } 41 | 42 | .ant-empty-img-simple-ellipse { 43 | fill: #f5f5f5; 44 | } 45 | 46 | .ant-empty-img-simple-g { 47 | stroke: #d9d9d9; 48 | } 49 | 50 | .ant-empty-img-simple-path { 51 | fill: #fafafa; 52 | } 53 | 54 | .ant-empty-rtl { 55 | direction: rtl; 56 | } 57 | } 58 | } 59 | 60 | .#{$array-collapse-prefix-cls}-remove { 61 | transition: all 0.25s ease-in-out; 62 | color: $color-text1-3; 63 | font-size: 16px; 64 | margin-left: 6px; 65 | 66 | &:hover { 67 | color: $color-text1-1; 68 | } 69 | } 70 | 71 | .#{$array-collapse-prefix-cls}-addition { 72 | transition: all 0.25s ease-in-out; 73 | } 74 | 75 | .#{$array-collapse-prefix-cls}-move-down { 76 | transition: all 0.25s ease-in-out; 77 | color: $color-text1-3; 78 | font-size: 16px; 79 | margin-left: 6px; 80 | 81 | &:hover { 82 | color: $color-text1-1; 83 | } 84 | } 85 | 86 | .#{$array-collapse-prefix-cls}-move-up { 87 | transition: all 0.25s ease-in-out; 88 | color: $color-text1-3; 89 | font-size: 16px; 90 | margin-left: 6px; 91 | 92 | &:hover { 93 | color: $color-text1-1; 94 | } 95 | } 96 | 97 | .#{$array-collapse-prefix-cls}-item { 98 | margin-bottom: 10px !important; 99 | .#{$array-collapse-prefix-cls}-item-title { 100 | display: flex; 101 | justify-content: space-between; 102 | } 103 | } 104 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-drawer/json-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <Button @click="handleOpen">点击打开表单</Button> 3 | </template> 4 | 5 | <script> 6 | import { FormDrawer, FormLayout, FormItem, Input } from '@formily/element' 7 | import { Button } from 'element-ui' 8 | import { createSchemaField } from '@formily/vue' 9 | const { SchemaField } = createSchemaField({ 10 | components: { 11 | FormItem, 12 | Input, 13 | }, 14 | }) 15 | 16 | // 抽屉表单组件 17 | const DrawerForm = { 18 | data() { 19 | const schema = { 20 | type: 'object', 21 | properties: { 22 | aaa: { 23 | type: 'string', 24 | title: '输入框1', 25 | required: true, 26 | 'x-decorator': 'FormItem', 27 | 'x-component': 'Input', 28 | }, 29 | bbb: { 30 | type: 'string', 31 | title: '输入框2', 32 | required: true, 33 | 'x-decorator': 'FormItem', 34 | 'x-component': 'Input', 35 | }, 36 | ccc: { 37 | type: 'string', 38 | title: '输入框3', 39 | required: true, 40 | 'x-decorator': 'FormItem', 41 | 'x-component': 'Input', 42 | }, 43 | ddd: { 44 | type: 'string', 45 | title: '输入框4', 46 | required: true, 47 | 'x-decorator': 'FormItem', 48 | 'x-component': 'Input', 49 | }, 50 | }, 51 | } 52 | return { 53 | schema, 54 | } 55 | }, 56 | render(h) { 57 | return ( 58 | <FormLayout labelCol={6} wrapperCol={10}> 59 | <SchemaField schema={this.schema} /> 60 | <FormDrawer.Footer> 61 | <span style={{ marginLeft: '4px' }}>扩展文案</span> 62 | </FormDrawer.Footer> 63 | </FormLayout> 64 | ) 65 | }, 66 | } 67 | 68 | export default { 69 | components: { Button }, 70 | data() { 71 | return {} 72 | }, 73 | methods: { 74 | handleOpen() { 75 | FormDrawer('抽屉表单', DrawerForm) 76 | .open({ 77 | initialValues: { 78 | aaa: '123', 79 | }, 80 | }) 81 | .then((values) => { 82 | console.log('values', values) 83 | }) 84 | .catch((e) => { 85 | console.log(e) 86 | }) 87 | }, 88 | }, 89 | } 90 | </script> 91 | ```