This is page 9 of 35. Use http://codebase.md/alibaba/formily?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /packages/validator/src/formats.ts: -------------------------------------------------------------------------------- ```typescript export default { url: new RegExp( // protocol identifier '^(?:(?:(?:https?|ftp|rtmp):)?//)' + // user:pass authentication '(?:\\S+(?::\\S*)?@)?' + '(?:' + // IP address exclusion - private & local networks // Reference: https://www.arin.net/knowledge/address_filters.html // filter 10.*.*.* and 127.*.*.* addresses '(?!(?:10|127)(?:\\.\\d{1,3}){3})' + // filter 169.254.*.* and 192.168.*.* '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' + // filter 172.16.0.0 - 172.31.255.255 // TODO: add test to validate that it invalidates address in 16-31 range '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' + // IP address dotted notation octets // excludes loopback network 0.0.0.0 // excludes reserved space >= 224.0.0.0 // excludes network & broadcast addresses // (first & last IP address of each class) // filter 1. part for 1-223 '(?:22[0-3]|2[01]\\d|[1-9]\\d?|1\\d\\d)' + // filter 2. and 3. part for 0-255 '(?:\\.(?:25[0-5]|2[0-4]\\d|1?\\d{1,2})){2}' + // filter 4. part for 1-254 '(?:\\.(?:25[0-4]|2[0-4]\\d|1\\d\\d|[1-9]\\d?))' + '|' + // host name '(?:(?:[a-z\\u00a1-\\uffff0-9_]-*)*[a-z\\u00a1-\\uffff0-9_]+)' + // domain name '(?:\\.(?:[a-z\\u00a1-\\uffff0-9_]-*)*[a-z\\u00a1-\\uffff0-9_]+)*' + // TLD identifier '(?:\\.(?:[a-z\\u00a1-\\uffff_]{2,}))' + ')' + // port number '(?::\\d{2,5})?' + // resource path '(?:/?\\S*)?$' ), email: /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, ipv6: /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/, ipv4: /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/, number: /^[+-]?\d+(\.\d+)?$/, integer: /^[+-]?\d+$/, qq: /^(\+?[1-9]\d*|0)$/, phone: /^\d{3}-\d{8}$|^\d{4}-\d{7}$|^\d{11}$/, idcard: /^\d{15}$|^\d{17}(\d|x|X)$/, money: /^([\u0024\u00A2\u00A3\u00A4\u20AC\u00A5\u20B1\u20B9\uFFE5]\s*)(\d+,?)+(\.\d+)?\s*$/, zh: /^[\u4e00-\u9fa5]+$/, date: /^[0-9]+[./-][0-9]+[./-][0-9]+\s*(?:[0-9]+\s*:\s*[0-9]+\s*:\s*[0-9]+)?$/, zip: /^[0-9]{6}$/, } ``` -------------------------------------------------------------------------------- /packages/core/src/models/VoidField.ts: -------------------------------------------------------------------------------- ```typescript import { toArr, FormPathPattern } from '@formily/shared' import { define, observable, batch, action } from '@formily/reactive' import { createReactions, createStateSetter, createStateGetter, initializeStart, initializeEnd, } from '../shared/internals' import { IModelSetter, IModelGetter, IVoidFieldProps, IVoidFieldState, } from '../types' import { Form } from './Form' import { BaseField } from './BaseField' export class VoidField< Decorator = any, Component = any, TextType = any > extends BaseField<Decorator, Component, TextType> { displayName: 'VoidField' = 'VoidField' props: IVoidFieldProps<Decorator, Component> constructor( address: FormPathPattern, props: IVoidFieldProps<Decorator, Component>, form: Form, designable: boolean ) { super() this.form = form this.props = props this.designable = designable initializeStart() this.locate(address) this.initialize() this.makeObservable() this.makeReactive() this.onInit() initializeEnd() } protected initialize() { this.mounted = false this.unmounted = false this.initialized = false this.title = this.props.title this.description = this.props.description this.pattern = this.props.pattern this.display = this.props.display this.hidden = this.props.hidden this.editable = this.props.editable this.disabled = this.props.disabled this.readOnly = this.props.readOnly this.readPretty = this.props.readPretty this.visible = this.props.visible this.content = this.props.content this.data = this.props.data this.decorator = toArr(this.props.decorator) this.component = toArr(this.props.component) } protected makeObservable() { if (this.designable) return define(this, { path: observable.ref, title: observable.ref, description: observable.ref, selfDisplay: observable.ref, selfPattern: observable.ref, initialized: observable.ref, mounted: observable.ref, unmounted: observable.ref, decoratorType: observable.ref, componentType: observable.ref, content: observable.ref, data: observable.shallow, decoratorProps: observable, componentProps: observable, display: observable.computed, pattern: observable.computed, hidden: observable.computed, visible: observable.computed, disabled: observable.computed, readOnly: observable.computed, readPretty: observable.computed, editable: observable.computed, component: observable.computed, decorator: observable.computed, indexes: observable.computed, setTitle: action, setDescription: action, setDisplay: action, setPattern: action, setComponent: action, setComponentProps: action, setDecorator: action, setDecoratorProps: action, setData: action, setContent: action, onInit: batch, onMount: batch, onUnmount: batch, }) } protected makeReactive() { if (this.designable) return createReactions(this) } setState: IModelSetter<IVoidFieldState> = createStateSetter(this) getState: IModelGetter<IVoidFieldState> = createStateGetter(this) } ``` -------------------------------------------------------------------------------- /packages/react/src/components/ReactiveField.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment, useContext } from 'react' import { toJS } from '@formily/reactive' import { observer } from '@formily/reactive-react' import { FormPath, isFn } from '@formily/shared' import { isVoidField, GeneralField, Form } from '@formily/core' import { SchemaComponentsContext } from '../shared' import { RenderPropsChildren } from '../types' interface IReactiveFieldProps { field: GeneralField children?: RenderPropsChildren<GeneralField> } const mergeChildren = ( children: RenderPropsChildren<GeneralField>, content: React.ReactNode ) => { if (!children && !content) return if (isFn(children)) return return ( <Fragment> {children} {content} </Fragment> ) } const isValidComponent = (target: any) => target && (typeof target === 'object' || typeof target === 'function') const renderChildren = ( children: RenderPropsChildren<GeneralField>, field?: GeneralField, form?: Form ) => (isFn(children) ? children(field, form) : children) const ReactiveInternal: React.FC<IReactiveFieldProps> = (props) => { const components = useContext(SchemaComponentsContext) if (!props.field) { return <Fragment>{renderChildren(props.children)}</Fragment> } const field = props.field const content = mergeChildren( renderChildren(props.children, field, field.form), field.content ?? field.componentProps.children ) if (field.display !== 'visible') return null const getComponent = (target: any) => { return isValidComponent(target) ? target : FormPath.getIn(components, target) ?? target } const renderDecorator = (children: React.ReactNode) => { if (!field.decoratorType) { return <Fragment>{children}</Fragment> } return React.createElement( getComponent(field.decoratorType), toJS(field.decoratorProps), children ) } const renderComponent = () => { if (!field.componentType) return content const value = !isVoidField(field) ? field.value : undefined const onChange = !isVoidField(field) ? (...args: any[]) => { field.onInput(...args) field.componentProps?.onChange?.(...args) } : field.componentProps?.onChange const onFocus = !isVoidField(field) ? (...args: any[]) => { field.onFocus(...args) field.componentProps?.onFocus?.(...args) } : field.componentProps?.onFocus const onBlur = !isVoidField(field) ? (...args: any[]) => { field.onBlur(...args) field.componentProps?.onBlur?.(...args) } : field.componentProps?.onBlur const disabled = !isVoidField(field) ? field.pattern === 'disabled' || field.pattern === 'readPretty' : undefined const readOnly = !isVoidField(field) ? field.pattern === 'readOnly' : undefined return React.createElement( getComponent(field.componentType), { disabled, readOnly, ...toJS(field.componentProps), value, onChange, onFocus, onBlur, }, content ) } return renderDecorator(renderComponent()) } ReactiveInternal.displayName = 'ReactiveField' export const ReactiveField = observer(ReactiveInternal, { forwardRef: true, }) ``` -------------------------------------------------------------------------------- /packages/path/src/types.ts: -------------------------------------------------------------------------------- ```typescript import { Path } from './index' interface INode { type?: string after?: Node depth?: number } export type Node = | IdentifierNode | WildcardOperatorNode | GroupExpressionNode | RangeExpressionNode | DestructorExpressionNode | ObjectPatternNode | ArrayPatternNode | DotOperatorNode | ExpandOperatorNode | INode export type IdentifierNode = { type: 'Identifier' value: string arrayIndex?: boolean } & INode export type IgnoreExpressionNode = { type: 'IgnoreExpression' value: string } & INode export type DotOperatorNode = { type: 'DotOperator' } & INode export type WildcardOperatorNode = { type: 'WildcardOperator' filter?: GroupExpressionNode | RangeExpressionNode optional?: boolean } & INode export type ExpandOperatorNode = { type: 'ExpandOperator' } & INode export type GroupExpressionNode = { type: 'GroupExpression' value: Node[] isExclude?: boolean } & INode export type RangeExpressionNode = { type: 'RangeExpression' start?: IdentifierNode end?: IdentifierNode } & INode export type DestructorExpressionNode = { type: 'DestructorExpression' value?: ObjectPatternNode | ArrayPatternNode source?: string } & INode export type ObjectPatternNode = { type: 'ObjectPattern' properties: ObjectPatternPropertyNode[] } & INode export type ObjectPatternPropertyNode = { type: 'ObjectPatternProperty' key: IdentifierNode value?: ObjectPatternNode[] | ArrayPatternNode[] | IdentifierNode } & INode export type ArrayPatternNode = { type: 'ArrayPattern' elements: ObjectPatternNode[] | ArrayPatternNode[] | IdentifierNode[] } & INode export type DestructorRule = { key?: string | number path?: Array<number | string> } export type MatcherFunction = ((path: Segments) => boolean) & { path: Path } export type Pattern = | string | number | Path | Segments | MatcherFunction | RegExp export type DestructorRules = DestructorRule[] export type Segments = Array<string | number> export const isType = <T>(type: string) => (obj: any): obj is T => { return obj && obj.type === type } export const isIdentifier = isType<IdentifierNode>('Identifier') export const isIgnoreExpression = isType<IgnoreExpressionNode>('IgnoreExpression') export const isDotOperator = isType<DotOperatorNode>('DotOperator') export const isWildcardOperator = isType<WildcardOperatorNode>('WildcardOperator') export const isExpandOperator = isType<ExpandOperatorNode>('ExpandOperator') export const isGroupExpression = isType<GroupExpressionNode>('GroupExpression') export const isRangeExpression = isType<RangeExpressionNode>('RangeExpression') export const isDestructorExpression = isType<DestructorExpressionNode>( 'DestructorExpression' ) export const isObjectPattern = isType<ObjectPatternNode>('ObjectPattern') export const isObjectPatternProperty = isType<ObjectPatternPropertyNode>( 'ObjectPatternProperty' ) export const isArrayPattern = isType<ArrayPatternNode>('ArrayPattern') export type KeyType = string | number | symbol export type IAccessors = { get?: (source: any, key: KeyType) => any set?: (source: any, key: KeyType, value: any) => any has?: (source: any, key: KeyType) => boolean delete?: (source: any, key: KeyType) => any } export type IRegistry = { accessors?: IAccessors } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-item/size.vue: -------------------------------------------------------------------------------- ```vue <template> <Form :form="form"> <SchemaField> <SchemaStringField name="size" title="Radio.Group" x-decorator="FormItem" x-component="Radio.Group" :enum="[ { value: 'small', label: 'Small' }, { value: 'default', label: 'Default' }, { value: 'large', label: 'Large' }, ]" /> <SchemaVoidField name="sizeWrap" x-component="Div"> <SchemaStringField name="input" title="Input" x-decorator="FormItem" x-component="Input" required /> <SchemaStringField name="select1" title="Multiple Select" x-decorator="FormItem" x-component="Select" :enum="[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]" :x-component-props="{ multiple: true, placeholder: '请选择', }" required /> <SchemaStringField name="select2" title="Select" x-decorator="FormItem" x-component="Select" :enum="[ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, ]" :x-component-props="{ placeholder: '请选择', }" required /> <SchemaStringField name="Cascader" title="Cascader" x-decorator="FormItem" x-component="Cascader" required /> <SchemaStringField name="DatePicker" title="DatePicker" x-decorator="FormItem" x-component="DatePicker" required /> <SchemaStringField name="InputNumber" title="InputNumber" x-decorator="FormItem" x-component="InputNumber" required /> <SchemaBooleanField name="Switch" title="Switch" x-decorator="FormItem" x-component="Switch" required /> </SchemaVoidField> </SchemaField> </Form> </template> <script> import { createForm, onFieldChange } from '@formily/core' import { createSchemaField } from '@formily/vue' import { Form, FormItem, Input, Select, Cascader, DatePicker, Switch, InputNumber, Radio, } from '@formily/element' const Div = { functional: true, render(h, context) { return h('div', context.data, context.children) }, } const form = createForm({ values: { size: 'default', }, effects: () => { onFieldChange('size', ['value'], (field, form) => { form.setFieldState('sizeWrap.*', (state) => { if (state.decorator[1]) { state.decorator[1].size = field.value } }) }) }, }) const fields = createSchemaField({ components: { FormItem, Input, Select, Cascader, DatePicker, Switch, InputNumber, Radio, Div, }, }) export default { components: { Form, ...fields }, data() { return { form, } }, methods: { onSubmit(value) { console.log(value) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/element/src/form-item/grid.scss: -------------------------------------------------------------------------------- ```scss .#{$form-item-prefix}-item-col-24 { -webkit-box-flex: 0; -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .#{$form-item-prefix}-item-col-23 { -webkit-box-flex: 0; -ms-flex: 0 0 95.83333333%; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .#{$form-item-prefix}-item-col-22 { -webkit-box-flex: 0; -ms-flex: 0 0 91.66666667%; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .#{$form-item-prefix}-item-col-21 { -webkit-box-flex: 0; -ms-flex: 0 0 87.5%; flex: 0 0 87.5%; max-width: 87.5%; } .#{$form-item-prefix}-item-col-20 { -webkit-box-flex: 0; -ms-flex: 0 0 83.33333333%; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .#{$form-item-prefix}-item-col-19 { -webkit-box-flex: 0; -ms-flex: 0 0 79.16666667%; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .#{$form-item-prefix}-item-col-18 { -webkit-box-flex: 0; -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .#{$form-item-prefix}-item-col-17 { -webkit-box-flex: 0; -ms-flex: 0 0 70.83333333%; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .#{$form-item-prefix}-item-col-16 { -webkit-box-flex: 0; -ms-flex: 0 0 66.66666667%; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .#{$form-item-prefix}-item-col-15 { -webkit-box-flex: 0; -ms-flex: 0 0 62.5%; flex: 0 0 62.5%; max-width: 62.5%; } .#{$form-item-prefix}-item-col-14 { -webkit-box-flex: 0; -ms-flex: 0 0 58.33333333%; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .#{$form-item-prefix}-item-col-13 { -webkit-box-flex: 0; -ms-flex: 0 0 54.16666667%; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .#{$form-item-prefix}-item-col-12 { -webkit-box-flex: 0; -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .#{$form-item-prefix}-item-col-11 { -webkit-box-flex: 0; -ms-flex: 0 0 45.83333333%; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .#{$form-item-prefix}-item-col-10 { -webkit-box-flex: 0; -ms-flex: 0 0 41.66666667%; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .#{$form-item-prefix}-item-col-9 { -webkit-box-flex: 0; -ms-flex: 0 0 37.5%; flex: 0 0 37.5%; max-width: 37.5%; } .#{$form-item-prefix}-item-col-8 { -webkit-box-flex: 0; -ms-flex: 0 0 33.33333333%; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .#{$form-item-prefix}-item-col-7 { -webkit-box-flex: 0; -ms-flex: 0 0 29.16666667%; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .#{$form-item-prefix}-item-col-6 { -webkit-box-flex: 0; -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .#{$form-item-prefix}-item-col-5 { -webkit-box-flex: 0; -ms-flex: 0 0 20.83333333%; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .#{$form-item-prefix}-item-col-4 { -webkit-box-flex: 0; -ms-flex: 0 0 16.66666667%; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .#{$form-item-prefix}-item-col-3 { -webkit-box-flex: 0; -ms-flex: 0 0 12.5%; flex: 0 0 12.5%; max-width: 12.5%; } .#{$form-item-prefix}-item-col-2 { -webkit-box-flex: 0; -ms-flex: 0 0 8.33333333%; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .#{$form-item-prefix}-item-col-1 { -webkit-box-flex: 0; -ms-flex: 0 0 4.16666667%; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .#{$form-item-prefix}-item-col-0 { display: none; } ``` -------------------------------------------------------------------------------- /packages/core/src/models/ArrayField.ts: -------------------------------------------------------------------------------- ```typescript import { isArr, move } from '@formily/shared' import { action, reaction } from '@formily/reactive' import { spliceArrayState, exchangeArrayState, cleanupArrayChildren, } from '../shared/internals' import { Field } from './Field' import { Form } from './Form' import { JSXComponent, IFieldProps, FormPathPattern } from '../types' export class ArrayField< Decorator extends JSXComponent = any, Component extends JSXComponent = any > extends Field<Decorator, Component, any, any[]> { displayName = 'ArrayField' constructor( address: FormPathPattern, props: IFieldProps<Decorator, Component>, form: Form, designable: boolean ) { super(address, props, form, designable) this.makeAutoCleanable() } protected makeAutoCleanable() { this.disposers.push( reaction( () => this.value?.length, (newLength, oldLength) => { if (oldLength && !newLength) { cleanupArrayChildren(this, 0) } else if (newLength < oldLength) { cleanupArrayChildren(this, newLength) } } ) ) } push = (...items: any[]) => { return action(() => { if (!isArr(this.value)) { this.value = [] } this.value.push(...items) return this.onInput(this.value) }) } pop = () => { if (!isArr(this.value)) return return action(() => { const index = this.value.length - 1 spliceArrayState(this, { startIndex: index, deleteCount: 1, }) this.value.pop() return this.onInput(this.value) }) } insert = (index: number, ...items: any[]) => { return action(() => { if (!isArr(this.value)) { this.value = [] } if (items.length === 0) { return } spliceArrayState(this, { startIndex: index, insertCount: items.length, }) this.value.splice(index, 0, ...items) return this.onInput(this.value) }) } remove = (index: number) => { if (!isArr(this.value)) return return action(() => { spliceArrayState(this, { startIndex: index, deleteCount: 1, }) this.value.splice(index, 1) return this.onInput(this.value) }) } shift = () => { if (!isArr(this.value)) return return action(() => { this.value.shift() return this.onInput(this.value) }) } unshift = (...items: any[]) => { return action(() => { if (!isArr(this.value)) { this.value = [] } spliceArrayState(this, { startIndex: 0, insertCount: items.length, }) this.value.unshift(...items) return this.onInput(this.value) }) } move = (fromIndex: number, toIndex: number) => { if (!isArr(this.value)) return if (fromIndex === toIndex) return return action(() => { move(this.value, fromIndex, toIndex) exchangeArrayState(this, { fromIndex, toIndex, }) return this.onInput(this.value) }) } moveUp = (index: number) => { if (!isArr(this.value)) return return this.move(index, index - 1 < 0 ? this.value.length - 1 : index - 1) } moveDown = (index: number) => { if (!isArr(this.value)) return return this.move(index, index + 1 >= this.value.length ? 0 : index + 1) } } ``` -------------------------------------------------------------------------------- /packages/reactive/src/externals.ts: -------------------------------------------------------------------------------- ```typescript import { isValid, isFn, isMap, isWeakMap, isSet, isWeakSet, isPlainObj, isArr, } from './checkers' import { ProxyRaw, MakeObModelSymbol, DependencyCollected, ObModelSymbol, } from './environment' import { getDataNode } from './tree' import { Annotation } from './types' const RAW_TYPE = Symbol('RAW_TYPE') const OBSERVABLE_TYPE = Symbol('OBSERVABLE_TYPE') const hasOwnProperty = Object.prototype.hasOwnProperty export const isObservable = (target: any) => { return ProxyRaw.has(target) || !!target?.[ObModelSymbol] } export const isAnnotation = (target: any): target is Annotation => { return target && !!target[MakeObModelSymbol] } export const isSupportObservable = (target: any) => { if (!isValid(target)) return false if (isArr(target)) return true if (isPlainObj(target)) { if (target[RAW_TYPE]) { return false } if (target[OBSERVABLE_TYPE]) { return true } if ('$$typeof' in target && '_owner' in target) { return false } if (target['_isAMomentObject']) { return false } if (target['_isJSONSchemaObject']) { return false } if (isFn(target['toJS'])) { return false } if (isFn(target['toJSON'])) { return false } return true } if (isMap(target) || isWeakMap(target) || isSet(target) || isWeakSet(target)) return true return false } export const markRaw = <T>(target: T): T => { if (!target) return if (isFn(target)) { target.prototype[RAW_TYPE] = true } else { target[RAW_TYPE] = true } return target } export const markObservable = <T>(target: T): T => { if (!target) return if (isFn(target)) { target.prototype[OBSERVABLE_TYPE] = true } else { target[OBSERVABLE_TYPE] = true } return target } export const raw = <T>(target: T): T => { if (target?.[ObModelSymbol]) return target[ObModelSymbol] return ProxyRaw.get(target as any) || target } export const toJS = <T>(values: T): T => { const visited = new WeakSet<any>() const _toJS: typeof toJS = (values: any) => { if (visited.has(values)) { return values } if (values && values[RAW_TYPE]) return values if (isArr(values)) { if (isObservable(values)) { visited.add(values) const res: any = [] values.forEach((item: any) => { res.push(_toJS(item)) }) visited.delete(values) return res } } else if (isPlainObj(values)) { if (isObservable(values)) { visited.add(values) const res: any = {} for (const key in values) { if (hasOwnProperty.call(values, key)) { res[key] = _toJS(values[key]) } } visited.delete(values) return res } } return values } return _toJS(values) } export const contains = (target: any, property: any) => { const targetRaw = raw(target) const propertyRaw = raw(property) if (targetRaw === propertyRaw) return true const targetNode = getDataNode(targetRaw) const propertyNode = getDataNode(propertyRaw) if (!targetNode) return false if (!propertyNode) return false return targetNode.contains(propertyNode) } export const hasCollected = (callback?: () => void) => { DependencyCollected.value = false callback?.() return DependencyCollected.value } ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/ArrayTabs.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # ArrayTabs > 自增选项卡,对于纵向空间要求较高的场景可以考虑使用该组件 > > 注意:该组件只适用于 Schema 场景,交互上请避免跨 Tab 联动 ## Markup Schema 案例 ```tsx import React from 'react' import { FormItem, Input, ArrayTabs, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayTabs, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="string_array" x-decorator="FormItem" title="字符串数组" maxItems={3} x-component="ArrayTabs" > <SchemaField.String x-decorator="FormItem" required x-component="Input" /> </SchemaField.Array> <SchemaField.Array name="array" x-decorator="FormItem" title="对象数组" maxItems={3} x-component="ArrayTabs" > <SchemaField.Object> <SchemaField.String x-decorator="FormItem" title="AAA" name="aaa" required x-component="Input" /> <SchemaField.String x-decorator="FormItem" title="BBB" name="bbb" required x-component="Input" /> </SchemaField.Object> </SchemaField.Array> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## JSON Schema 案例 ```tsx import React from 'react' import { FormItem, Input, ArrayTabs, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayTabs, }, }) const form = createForm() const schema = { type: 'object', properties: { string_array: { type: 'array', title: '字符串数组', 'x-decorator': 'FormItem', maxItems: 3, 'x-component': 'ArrayTabs', items: { type: 'string', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, array: { type: 'array', title: '对象数组', 'x-decorator': 'FormItem', maxItems: 3, 'x-component': 'ArrayTabs', items: { type: 'object', properties: { aaa: { type: 'string', 'x-decorator': 'FormItem', title: 'AAA', required: true, 'x-component': 'Input', }, bbb: { type: 'string', 'x-decorator': 'FormItem', title: 'BBB', required: true, 'x-component': 'Input', }, }, }, }, }, } export default () => { return ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## API ### ArrayTabs 参考 https://ant.design/components/tabs-cn/ ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-cards/json-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField :schema="schema" /> <Submit @submit="log">提交</Submit> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { FormItem, FormButtonGroup, Submit, Input, ArrayCards, } from '@formily/element' import { Button } from 'element-ui' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) export default { components: { FormProvider, FormButtonGroup, Button, Submit, ...SchemaField, }, data() { const form = createForm() const schema = { type: 'object', properties: { string_array: { type: 'array', 'x-component': 'ArrayCards', maxItems: 3, 'x-decorator': 'FormItem', 'x-component-props': { title: '字符串数组', }, items: { type: 'void', properties: { index: { type: 'void', 'x-component': 'ArrayCards.Index', }, input: { type: 'string', 'x-decorator': 'FormItem', title: 'Input', required: true, 'x-component': 'Input', }, remove: { type: 'void', 'x-component': 'ArrayCards.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCards.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCards.MoveDown', }, }, }, properties: { addition: { type: 'void', title: '添加条目', 'x-component': 'ArrayCards.Addition', }, }, }, array: { type: 'array', 'x-component': 'ArrayCards', maxItems: 3, 'x-decorator': 'FormItem', 'x-component-props': { title: '对象数组', }, items: { type: 'object', properties: { index: { type: 'void', 'x-component': 'ArrayCards.Index', }, input: { type: 'string', 'x-decorator': 'FormItem', title: 'Input', required: true, 'x-component': 'Input', }, remove: { type: 'void', 'x-component': 'ArrayCards.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCards.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCards.MoveDown', }, }, }, properties: { addition: { type: 'void', title: '添加条目', 'x-component': 'ArrayCards.Addition', }, }, }, }, } return { form, schema, } }, methods: { log(values) { console.log(values) }, }, } </script> <style lang="scss" scoped></style> ``` -------------------------------------------------------------------------------- /packages/json-schema/src/__tests__/patches.spec.ts: -------------------------------------------------------------------------------- ```typescript import { Schema } from '../schema' import { registerTypeDefaultComponents, registerVoidComponents, } from '../polyfills' registerVoidComponents(['MyCard']) registerTypeDefaultComponents({ string: 'Input', }) Schema.enablePolyfills(['1.0']) test('v1 polyfill', () => { const schema = new Schema({ type: 'string', editable: true, } as any) expect(schema['x-editable']).toEqual(true) const schema1 = new Schema({ type: 'string', visible: true, } as any) expect(schema1['x-visible']).toEqual(true) const schema2 = new Schema({ type: 'string', display: false, } as any) expect(schema2['x-display']).toEqual('hidden') expect(schema2['x-display']).toEqual('hidden') const schema3 = new Schema({ type: 'string', 'x-linkages': [ { type: 'value:visible', condition: '{{$value == 123}}', }, ], } as any) expect(schema3['x-reactions']).toEqual([ { when: '{{$self.value == 123}}', fulfill: { state: { visible: true, }, }, otherwise: { state: { visible: false, }, }, }, ]) const schema4 = new Schema({ type: 'string', 'x-linkages': [ { type: 'value:schema', target: 'xxx', condition: '{{$value == 123}}', schema: { title: 'xxx', }, otherwise: { title: '123', }, }, ], } as any) expect(schema4['x-reactions']).toEqual([ { when: '{{$self.value == 123}}', target: 'xxx', fulfill: { schema: { version: '1.0', title: 'xxx', 'x-decorator': 'FormItem', }, }, otherwise: { schema: { version: '1.0', title: '123', 'x-decorator': 'FormItem', }, }, }, ]) const schema5 = new Schema({ type: 'string', 'x-linkages': [ { type: 'value:state', target: 'xxx', condition: '{{$value == 123}}', state: { title: 'xxx', }, otherwise: { title: '123', }, }, ], } as any) expect(schema5['x-reactions']).toEqual([ { when: '{{$self.value == 123}}', target: 'xxx', fulfill: { state: { title: 'xxx', }, }, otherwise: { state: { title: '123', }, }, }, ]) const schema6 = new Schema({ type: 'string', 'x-props': { labelCol: 3, wrapperCol: 4, }, 'x-linkages': [ { type: 'value:visible', condition: null, }, ], } as any) expect(schema6['x-component']).toEqual('Input') expect(schema6['x-decorator']).toEqual('FormItem') expect(schema6['x-decorator-props']).toEqual({ labelCol: 3, wrapperCol: 4, }) const schema7 = new Schema({ type: 'object', 'x-component': 'MyCard', 'x-linkages': {}, } as any) expect(schema7.type === 'void').toBeTruthy() new Schema({ type: 'object', 'x-component': 'MyCard', 'x-linkages': [null], } as any) new Schema({ type: 'object', 'x-component': 'MyCard', 'x-linkages': [{}], } as any) const schema8 = new Schema({ type: 'string', 'x-rules': ['phone'], } as any) expect(schema8['x-validator']).toEqual(['phone']) }) ``` -------------------------------------------------------------------------------- /packages/antd/src/form-tab/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment, useMemo } from 'react' import { Tabs, Badge } from 'antd' import { model, markRaw } from '@formily/reactive' import { TabPaneProps, TabsProps } from 'antd/lib/tabs' import { useField, ReactFC, observer, useFieldSchema, RecursionField, } from '@formily/react' import { Schema, SchemaKey } from '@formily/json-schema' import cls from 'classnames' import { usePrefixCls } from '../__builtins__' export interface IFormTab { activeKey: string setActiveKey(key: string): void } export interface IFormTabProps extends TabsProps { formTab?: IFormTab } export interface IFormTabPaneProps extends TabPaneProps { key: string | number } interface IFeedbackBadgeProps { name: SchemaKey tab: React.ReactNode } type ComposedFormTab = React.FC<React.PropsWithChildren<IFormTabProps>> & { TabPane: React.FC<React.PropsWithChildren<IFormTabPaneProps>> createFormTab: (defaultActiveKey?: string) => IFormTab } const useTabs = () => { const tabsField = useField() const schema = useFieldSchema() const tabs: { name: SchemaKey; props: any; schema: Schema }[] = [] schema.mapProperties((schema, name) => { const field = tabsField.query(tabsField.address.concat(name)).take() if (field?.display === 'none' || field?.display === 'hidden') return if (schema['x-component']?.indexOf('TabPane') > -1) { tabs.push({ name, props: { key: schema?.['x-component-props']?.key || name, ...schema?.['x-component-props'], }, schema, }) } }) return tabs } const FeedbackBadge: ReactFC<IFeedbackBadgeProps> = observer((props) => { const field = useField() const errors = field.form.queryFeedbacks({ type: 'error', address: `${field.address.concat(props.name)}.*`, }) if (errors.length) { return ( <Badge size="small" className="errors-badge" count={errors.length}> {props.tab} </Badge> ) } return <Fragment>{props.tab}</Fragment> }) const createFormTab = (defaultActiveKey?: string) => { const formTab = model({ activeKey: defaultActiveKey, setActiveKey(key: string) { formTab.activeKey = key }, }) return markRaw(formTab) } export const FormTab: ComposedFormTab = observer( ({ formTab, ...props }: IFormTabProps) => { const tabs = useTabs() const _formTab = useMemo(() => { return formTab ? formTab : createFormTab() }, []) const prefixCls = usePrefixCls('formily-tab', props) const activeKey = props.activeKey || _formTab?.activeKey return ( <Tabs {...props} className={cls(prefixCls, props.className)} activeKey={activeKey} onChange={(key) => { props.onChange?.(key) _formTab?.setActiveKey?.(key) }} > {tabs.map(({ props, schema, name }, key) => ( <Tabs.TabPane key={key} {...props} tab={<FeedbackBadge name={name} tab={props.tab} />} forceRender > <RecursionField schema={schema} name={name} /> </Tabs.TabPane> ))} </Tabs> ) } ) as unknown as ComposedFormTab const TabPane: React.FC<React.PropsWithChildren<IFormTabPaneProps>> = ({ children, }) => { return <Fragment>{children}</Fragment> } FormTab.TabPane = TabPane FormTab.createFormTab = createFormTab export default FormTab ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-table/markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaArrayField name="array" x-decorator="FormItem" x-component="ArrayTable" :x-component-props="{ pagination: { pageSize: 10 }, }" > <SchemaObjectField> <SchemaVoidField x-component="ArrayTable.Column" :x-component-props="{ width: 80, title: 'Index' }" ><SchemaVoidField x-decorator="FormItem" x-component="ArrayTable.Index" /> </SchemaVoidField> <SchemaVoidField x-component="ArrayTable.Column" :x-component-props="{ prop: 'a1', title: 'A1', width: 200 }" > <SchemaStringField x-decorator="Editable" name="a1" :required="true" x-component="Input" /> </SchemaVoidField> <SchemaVoidField x-component="ArrayTable.Column" :x-component-props="{ title: 'A2', width: 200 }" > <SchemaStringField x-decorator="FormItem" name="a2" :required="true" x-component="Input" /> </SchemaVoidField> <SchemaVoidField x-component="ArrayTable.Column" :x-component-props="{ title: 'A3' }" > <SchemaStringField name="a3" :required="true" x-decorator="FormItem" x-component="Input" /> </SchemaVoidField> <SchemaVoidField x-component="ArrayTable.Column" :x-component-props="{ title: 'Operations', prop: 'operations', width: 200, fixed: 'right', }" > <SchemaVoidField x-component="FormItem"> <SchemaVoidField x-component="ArrayTable.Remove" /> <SchemaVoidField x-component="ArrayTable.MoveUp" /> <SchemaVoidField x-component="ArrayTable.MoveDown" /> </SchemaVoidField> </SchemaVoidField> </SchemaObjectField> <SchemaVoidField x-component="ArrayTable.Addition" title="添加条目" /> </SchemaArrayField> </SchemaField> <Submit @submit="log">提交</Submit> <Button @click=" () => { form.setInitialValues({ array: range(100000), }) } " > 加载10W条超大数据 </Button> <Alert :style="{ marginTop: '10px' }" title="注意:开启formily插件的页面,因为后台有数据通信,会占用浏览器算力,最好在无痕模式(无formily插件)下测试" type="warning" /> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { Submit, FormItem, ArrayTable, Input, Editable } from '@formily/element' import { Button, Alert } from 'element-ui' const fields = createSchemaField({ components: { FormItem, ArrayTable, Input, Editable, }, }) export default { components: { FormProvider, Submit, Button, Alert, ...fields }, data() { const form = createForm() return { form, } }, methods: { log(...v) { console.log(...v) }, range(count) { return Array.from(new Array(count)).map((_, key) => ({ aaa: key, })) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/next/src/form-layout/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { createContext, useContext } from 'react' import { useResponsiveFormLayout } from './useResponsiveFormLayout' import { usePrefixCls } from '../__builtins__' import cls from 'classnames' export interface IFormLayoutProps { prefix?: string className?: string style?: React.CSSProperties colon?: boolean labelAlign?: 'right' | 'left' | ('right' | 'left')[] wrapperAlign?: 'right' | 'left' | ('right' | 'left')[] labelWrap?: boolean labelWidth?: number wrapperWidth?: number wrapperWrap?: boolean labelCol?: number | number[] wrapperCol?: number | number[] fullness?: boolean size?: 'small' | 'default' | 'large' layout?: | 'vertical' | 'horizontal' | 'inline' | ('vertical' | 'horizontal' | 'inline')[] direction?: 'rtl' | 'ltr' inset?: boolean shallow?: boolean tooltipLayout?: 'icon' | 'text' tooltipIcon?: React.ReactNode feedbackLayout?: 'loose' | 'terse' | 'popover' | 'none' bordered?: boolean breakpoints?: number[] gridColumnGap?: number gridRowGap?: number spaceGap?: number } export interface IFormLayoutContext extends Omit< IFormLayoutProps, 'labelAlign' | 'wrapperAlign' | 'layout' | 'labelCol' | 'wrapperCol' > { labelAlign?: 'right' | 'left' wrapperAlign?: 'right' | 'left' layout?: 'vertical' | 'horizontal' | 'inline' labelCol?: number wrapperCol?: number } export const FormLayoutDeepContext = createContext<IFormLayoutContext>(null) export const FormLayoutShallowContext = createContext<IFormLayoutContext>(null) export const useFormDeepLayout = () => useContext(FormLayoutDeepContext) export const useFormShallowLayout = () => useContext(FormLayoutShallowContext) export const useFormLayout = () => ({ ...useFormDeepLayout(), ...useFormShallowLayout(), }) export const FormLayout: React.FC<React.PropsWithChildren<IFormLayoutProps>> & { useFormLayout: () => IFormLayoutContext useFormDeepLayout: () => IFormLayoutContext useFormShallowLayout: () => IFormLayoutContext } = ({ shallow = true, children, prefix, className, style, ...otherProps }) => { const { ref, props } = useResponsiveFormLayout(otherProps) const deepLayout = useFormDeepLayout() const formPrefixCls = usePrefixCls('form', { prefix }) const layoutPrefixCls = usePrefixCls('formily-layout', { prefix }) const layoutClassName = cls( layoutPrefixCls, { [`${formPrefixCls}-${props.layout}`]: true, [`${formPrefixCls}-rtl`]: props.direction === 'rtl', [`${formPrefixCls}-${props.size}`]: props.size, }, className ) const renderChildren = () => { const newDeepLayout = { ...deepLayout, } if (!shallow) { Object.assign(newDeepLayout, props) } else { if (props.size) { newDeepLayout.size = props.size } if (props.colon) { newDeepLayout.colon = props.colon } } return ( <FormLayoutDeepContext.Provider value={newDeepLayout}> <FormLayoutShallowContext.Provider value={shallow ? props : undefined}> {children} </FormLayoutShallowContext.Provider> </FormLayoutDeepContext.Provider> ) } return ( <div ref={ref} className={layoutClassName} style={style}> {renderChildren()} </div> ) } FormLayout.useFormDeepLayout = useFormDeepLayout FormLayout.useFormShallowLayout = useFormShallowLayout FormLayout.useFormLayout = useFormLayout export default FormLayout ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Submit.md: -------------------------------------------------------------------------------- ```markdown # Submit > Submit button ## Ordinary submission ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="input box" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="input2" title="input box" default="123" required x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Prevent Duplicate Submission (Loading) ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="input box" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="input2" title="input box" default="123" required x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={(values) => { return new Promise((resolve) => { setTimeout(() => { console.log(values) resolve() }, 2000) }) }} onSubmitFailed={console.log} > submit </Submit> </FormButtonGroup> </FormProvider> ) ``` ## API For button-related API properties, we can refer to https://ant.design/components/button-cn/, and the rest are the unique API properties of the Submit component | Property name | Type | Description | Default value | | --------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------------- | | onClick | `(event: MouseEvent) => void \| boolean` | Click event, if it returns false, it can block submission | - | | onSubmit | `(values: any) => Promise<any> \| any` | Submit event callback | - | | onSubmitSuccess | (payload: any) => void | Submit successful response event | - | | onSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Submit verification failure event callback | - | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Submit.md: -------------------------------------------------------------------------------- ```markdown # Submit > Submit button ## Ordinary submission ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="input box" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="input2" title="input box" default="123" required x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) ``` ## Prevent Duplicate Submission (Loading) ```tsx import React from 'react' import { Input, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="input" title="input box" required x-decorator="FormItem" x-component="Input" /> <SchemaField.String name="input2" title="input box" default="123" required x-decorator="FormItem" x-component="Input" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={(values) => { return new Promise((resolve) => { setTimeout(() => { console.log(values) resolve() }, 2000) }) }} onSubmitFailed={console.log} > submit </Submit> </FormButtonGroup> </FormProvider> ) ``` ## API For button-related API properties, we can refer to https://fusion.design/pc/component/basic/button, and the rest are the unique API properties of the Submit component | Property name | Type | Description | Default value | | --------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------------- | | onClick | `(event: MouseEvent) => void \| boolean` | Click event, if it returns false, it can block submission | - | | onSubmit | `(values: any) => Promise<any> \| any` | Submit event callback | - | | onSubmitSuccess | (payload: any) => void | Submit successful response event | - | | onSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | Submit verification failure event callback | - | ``` -------------------------------------------------------------------------------- /packages/react/docs/api/components/ArrayField.zh-CN.md: -------------------------------------------------------------------------------- ```markdown --- order: 1 --- # ArrayField ## 描述 作为@formily/core 的 [createArrayField](https://core.formilyjs.org/zh-CN/api/models/form#createarrayfield) React 实现,它是专门用于将 ViewModel 与输入控件做绑定的桥接组件,ArrayField 组件属性参考[IFieldFactoryProps](https://core.formilyjs.org/zh-CN/api/models/form#ifieldfactoryprops) <Alert> 我们在使用 ArrayField 组件的时候,一定要记得传name属性。同时要使用render props形式来组织子组件 </Alert> ## 签名 ```ts type ArrayField = React.FC<React.PropsWithChildren<IFieldFactoryProps>> ``` ## 自定义组件用例 ```tsx import React from 'react' import { createForm, ArrayField as ArrayFieldType } from '@formily/core' import { FormProvider, Field, ArrayField, useField, observer, } from '@formily/react' import { Input, Button, Space } from 'antd' const form = createForm() const ArrayComponent = observer(() => { const field = useField<ArrayFieldType>() return ( <> <div> {field.value?.map((item, index) => ( <div key={index} style={{ display: 'flex-block', marginBottom: 10 }}> <Space> <Field name={index} component={[Input]} /> <Button onClick={() => { field.remove(index) }} > Remove </Button> <Button onClick={() => { field.moveUp(index) }} > Move Up </Button> <Button onClick={() => { field.moveDown(index) }} > Move Down </Button> </Space> </div> ))} </div> <Button onClick={() => { field.push('') }} > Add </Button> </> ) }) export default () => ( <FormProvider form={form}> <ArrayField name="array" component={[ArrayComponent]} /> </FormProvider> ) ``` ## RenderProps 用例 ```tsx import React from 'react' import { createForm } from '@formily/core' import { FormProvider, Field, ArrayField } from '@formily/react' import { Input, Button, Space } from 'antd' const form = createForm() export default () => ( <FormProvider form={form}> <ArrayField name="array"> {(field) => { return ( <> <div> {field.value?.map((item, index) => ( <div key={index} style={{ display: 'flex-block', marginBottom: 10 }} > <Space> <Field name={index} component={[Input]} /> <Button onClick={() => { field.remove(index) }} > Remove </Button> <Button onClick={() => { field.moveUp(index) }} > Move Up </Button> <Button onClick={() => { field.moveDown(index) }} > Move Down </Button> </Space> </div> ))} </div> <Button onClick={() => field.push('')}>Add</Button> </> ) }} </ArrayField> </FormProvider> ) ``` ``` -------------------------------------------------------------------------------- /packages/antd/src/form-layout/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { createContext, useContext } from 'react' import { useResponsiveFormLayout } from './useResponsiveFormLayout' import { usePrefixCls } from '../__builtins__' import cls from 'classnames' export interface IFormLayoutProps { prefixCls?: string className?: string style?: React.CSSProperties colon?: boolean requiredMark?: boolean | 'optional' labelAlign?: 'right' | 'left' | ('right' | 'left')[] wrapperAlign?: 'right' | 'left' | ('right' | 'left')[] labelWrap?: boolean labelWidth?: number wrapperWidth?: number wrapperWrap?: boolean labelCol?: number | number[] wrapperCol?: number | number[] fullness?: boolean size?: 'small' | 'default' | 'large' layout?: | 'vertical' | 'horizontal' | 'inline' | ('vertical' | 'horizontal' | 'inline')[] direction?: 'rtl' | 'ltr' inset?: boolean shallow?: boolean tooltipLayout?: 'icon' | 'text' tooltipIcon?: React.ReactNode feedbackLayout?: 'loose' | 'terse' | 'popover' | 'none' bordered?: boolean breakpoints?: number[] spaceGap?: number gridColumnGap?: number gridRowGap?: number } export interface IFormLayoutContext extends Omit< IFormLayoutProps, 'labelAlign' | 'wrapperAlign' | 'layout' | 'labelCol' | 'wrapperCol' > { labelAlign?: 'right' | 'left' wrapperAlign?: 'right' | 'left' layout?: 'vertical' | 'horizontal' | 'inline' labelCol?: number wrapperCol?: number } export const FormLayoutDeepContext = createContext<IFormLayoutContext>(null) export const FormLayoutShallowContext = createContext<IFormLayoutContext>(null) export const useFormDeepLayout = () => useContext(FormLayoutDeepContext) export const useFormShallowLayout = () => useContext(FormLayoutShallowContext) export const useFormLayout = () => ({ ...useFormDeepLayout(), ...useFormShallowLayout(), }) export const FormLayout: React.FC<React.PropsWithChildren<IFormLayoutProps>> & { useFormLayout: () => IFormLayoutContext useFormDeepLayout: () => IFormLayoutContext useFormShallowLayout: () => IFormLayoutContext } = ({ shallow = true, children, prefixCls, className, style, ...otherProps }) => { const { ref, props } = useResponsiveFormLayout(otherProps) const deepLayout = useFormDeepLayout() const formPrefixCls = usePrefixCls('form', { prefixCls }) const layoutPrefixCls = usePrefixCls('formily-layout', { prefixCls }) const layoutClassName = cls( layoutPrefixCls, { [`${formPrefixCls}-${props.layout}`]: true, [`${formPrefixCls}-rtl`]: props.direction === 'rtl', [`${formPrefixCls}-${props.size}`]: props.size, }, className ) const renderChildren = () => { const newDeepLayout = { ...deepLayout, } if (!shallow) { Object.assign(newDeepLayout, props) } else { if (props.size) { newDeepLayout.size = props.size } if (props.colon) { newDeepLayout.colon = props.colon } } return ( <FormLayoutDeepContext.Provider value={newDeepLayout}> <FormLayoutShallowContext.Provider value={shallow ? props : undefined}> {children} </FormLayoutShallowContext.Provider> </FormLayoutDeepContext.Provider> ) } return ( <div ref={ref} className={layoutClassName} style={style}> {renderChildren()} </div> ) } FormLayout.useFormDeepLayout = useFormDeepLayout FormLayout.useFormShallowLayout = useFormShallowLayout FormLayout.useFormLayout = useFormLayout export default FormLayout ``` -------------------------------------------------------------------------------- /packages/next/src/form-tab/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { Fragment, useMemo } from 'react' import { Tab as Tabs, Badge } from '@alifd/next' import { model, markRaw } from '@formily/reactive' import { isValid } from '@formily/shared' import { ItemProps as TabPaneProps, TabProps as TabsProps, } from '@alifd/next/lib/tab' import { useField, observer, ReactFC, useFieldSchema, RecursionField, } from '@formily/react' import { Schema, SchemaKey } from '@formily/json-schema' import cls from 'classnames' import { usePrefixCls } from '../__builtins__' export interface IFormTab { activeKey: SchemaKey setActiveKey(key: SchemaKey): void } export interface IFormTabProps extends TabsProps { formTab?: IFormTab } export interface IFormTabPaneProps extends TabPaneProps { key: SchemaKey } interface IFeedbackBadgeProps { name: SchemaKey tab: React.ReactNode } type ComposedFormTab = React.FC<React.PropsWithChildren<IFormTabProps>> & { TabPane: React.FC<React.PropsWithChildren<IFormTabPaneProps>> createFormTab: (defaultActiveKey?: React.ReactText) => IFormTab } const useTabs = () => { const tabsField = useField() const schema = useFieldSchema() const tabs: { name: SchemaKey; props: any; schema: Schema }[] = [] schema.mapProperties((schema, name) => { const field = tabsField.query(tabsField.address.concat(name)).take() if (field?.display === 'none' || field?.display === 'hidden') return if (schema['x-component']?.indexOf('TabPane') > -1) { tabs.push({ name, props: { key: schema?.['x-component-props']?.key || name, ...schema?.['x-component-props'], }, schema, }) } }) return tabs } const createFormTab = (defaultActiveKey?: string) => { const formTab = model({ activeKey: defaultActiveKey, setActiveKey(key: string) { formTab.activeKey = key }, }) return markRaw(formTab) } const FeedbackBadge: ReactFC<IFeedbackBadgeProps> = observer((props) => { const field = useField() const errors = field.form.queryFeedbacks({ type: 'error', address: `${field.address.concat(props.name)}.*`, }) if (errors.length) { return ( <Badge className="errors-badge" count={errors.length}> {props.tab} </Badge> ) } return <Fragment>{props.tab}</Fragment> }) export const FormTab: ComposedFormTab = observer( ({ formTab, ...props }: IFormTabProps) => { const tabs = useTabs() const _formTab = useMemo(() => { return formTab ? formTab : createFormTab() }, []) const prefixCls = usePrefixCls('formily-tab', props) const activeKey = props.activeKey || _formTab?.activeKey return ( <Tabs {...props} {...(isValid(activeKey) && { activeKey })} className={cls(prefixCls, props.className)} onChange={(key) => { props.onChange?.(key) _formTab?.setActiveKey?.(key) }} lazyLoad={false} > {tabs.map(({ props, schema, name }, key) => ( <Tabs.Item key={key} {...props} tab={<FeedbackBadge name={name} tab={props.tab} />} > <RecursionField schema={schema} name={name} /> </Tabs.Item> ))} </Tabs> ) } ) as unknown as ComposedFormTab const TabPane: React.FC<React.PropsWithChildren<IFormTabPaneProps>> = ({ children, }) => { return <Fragment>{children}</Fragment> } FormTab.TabPane = TabPane FormTab.createFormTab = createFormTab export default FormTab ``` -------------------------------------------------------------------------------- /packages/core/docs/api/entry/FormHooksAPI.md: -------------------------------------------------------------------------------- ```markdown --- order: 3 --- # Form Hooks API ## createEffectHook #### Description Create a custom hook listener #### Signature ```ts interface createEffectHook { ( type: string, callback?: ( payload: any, form: Form, ...ctx: any[] //user-injected context ) => (...args: any[]) => void //High-level callbacks are used to process the encapsulation of the listener and help users achieve parameter customization capabilities ) } ``` #### Example ```tsx import React, { useMemo, useState } from 'react' import { createForm, createEffectHook } from '@formily/core' import { ActionResponse } from './ActionResponse' const onCustomEvent = createEffectHook( 'custom-event', (payload, form) => (listener) => { listener(payload, form) } ) export default () => { const [response, setResponse] = useState('') const form = useMemo( () => createForm({ effects() { onCustomEvent((payload, form) => { setResponse(payload + 'Form:' + form.id) }) }, }), [] ) return ( <ActionResponse response={response}> <button onClick={() => { form.notify('custom-event', 'This is Custom Event') }} > Notify </button> </ActionResponse> ) } ``` ## createEffectContext #### Description In the effects function, if we abstract a lot of fine-grained hooks, we need to pass it layer by layer if we want to read the top-level context data in hooks, which is obviously very inefficient, so formily provides createEffectContext to help users quickly obtain context data #### Signature ```ts interface createEffectContext<T> { (defaultValue: T): { provide(value: T): void consume(): T } } ``` #### Example ```tsx import React, { useMemo, useState } from 'react' import { createForm, onFormSubmit, createEffectContext } from '@formily/core' import { ActionResponse } from './ActionResponse' const { provide, consume } = createEffectContext() const useMyHook = () => { const setResponse = consume() onFormSubmit(() => { setResponse('Context communication succeeded') }) } export default () => { const [response, setResponse] = useState('') const form = useMemo( () => createForm({ effects() { provide(setResponse) useMyHook() }, }), [] ) return ( <ActionResponse response={response}> <button onClick={() => { form.submit() }} > submit </button> </ActionResponse> ) } ``` ## useEffectForm #### Description useEffectForm is actually a convenient usage of EffectContext, because most scene users will read Form instances, so there is no need to manually define an EffectFormContext #### Signature ```ts interface useEffectForm { (): Form } ``` #### Example ```tsx import React, { useMemo, useState } from 'react' import { createForm, useEffectForm, createEffectContext } from '@formily/core' import { ActionResponse } from './ActionResponse' const { provide, consume } = createEffectContext() const useMyHook = () => { const form = useEffectForm() const setResponse = consume() setResponse('Communication successful:' + form.id) } export default () => { const [response, setResponse] = useState('') useMemo( () => createForm({ effects() { provide(setResponse) useMyHook() }, }), [] ) return <ActionResponse response={response} /> } ``` ``` -------------------------------------------------------------------------------- /packages/antd/src/form-item/grid.less: -------------------------------------------------------------------------------- ``` .@{form-item-cls} { .@{form-item-cls}-item-col-24 { -webkit-box-flex: 0; -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .@{form-item-cls}-item-col-23 { -webkit-box-flex: 0; -ms-flex: 0 0 95.83333333%; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .@{form-item-cls}-item-col-22 { -webkit-box-flex: 0; -ms-flex: 0 0 91.66666667%; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .@{form-item-cls}-item-col-21 { -webkit-box-flex: 0; -ms-flex: 0 0 87.5%; flex: 0 0 87.5%; max-width: 87.5%; } .@{form-item-cls}-item-col-20 { -webkit-box-flex: 0; -ms-flex: 0 0 83.33333333%; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .@{form-item-cls}-item-col-19 { -webkit-box-flex: 0; -ms-flex: 0 0 79.16666667%; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .@{form-item-cls}-item-col-18 { -webkit-box-flex: 0; -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .@{form-item-cls}-item-col-17 { -webkit-box-flex: 0; -ms-flex: 0 0 70.83333333%; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .@{form-item-cls}-item-col-16 { -webkit-box-flex: 0; -ms-flex: 0 0 66.66666667%; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .@{form-item-cls}-item-col-15 { -webkit-box-flex: 0; -ms-flex: 0 0 62.5%; flex: 0 0 62.5%; max-width: 62.5%; } .@{form-item-cls}-item-col-14 { -webkit-box-flex: 0; -ms-flex: 0 0 58.33333333%; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .@{form-item-cls}-item-col-13 { -webkit-box-flex: 0; -ms-flex: 0 0 54.16666667%; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .@{form-item-cls}-item-col-12 { -webkit-box-flex: 0; -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .@{form-item-cls}-item-col-11 { -webkit-box-flex: 0; -ms-flex: 0 0 45.83333333%; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .@{form-item-cls}-item-col-10 { -webkit-box-flex: 0; -ms-flex: 0 0 41.66666667%; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .@{form-item-cls}-item-col-9 { -webkit-box-flex: 0; -ms-flex: 0 0 37.5%; flex: 0 0 37.5%; max-width: 37.5%; } .@{form-item-cls}-item-col-8 { -webkit-box-flex: 0; -ms-flex: 0 0 33.33333333%; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .@{form-item-cls}-item-col-7 { -webkit-box-flex: 0; -ms-flex: 0 0 29.16666667%; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .@{form-item-cls}-item-col-6 { -webkit-box-flex: 0; -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .@{form-item-cls}-item-col-5 { -webkit-box-flex: 0; -ms-flex: 0 0 20.83333333%; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .@{form-item-cls}-item-col-4 { -webkit-box-flex: 0; -ms-flex: 0 0 16.66666667%; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .@{form-item-cls}-item-col-3 { -webkit-box-flex: 0; -ms-flex: 0 0 12.5%; flex: 0 0 12.5%; max-width: 12.5%; } .@{form-item-cls}-item-col-2 { -webkit-box-flex: 0; -ms-flex: 0 0 8.33333333%; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .@{form-item-cls}-item-col-1 { -webkit-box-flex: 0; -ms-flex: 0 0 4.16666667%; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .@{form-item-cls}-item-col-0 { display: none; } } ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/ArrayTabs.md: -------------------------------------------------------------------------------- ```markdown # ArrayTabs > Self-increasing tab, you can consider using this component for scenarios with high vertical space requirements > > Note: This component is only applicable to Schema scenarios, please avoid cross-tab linkage in interaction ## Markup Schema example ```tsx import React from 'react' import { FormItem, Input, ArrayTabs, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayTabs, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="string_array" x-decorator="FormItem" title="string array" maxItems={3} x-component="ArrayTabs" > <SchemaField.String x-decorator="FormItem" required x-component="Input" /> </SchemaField.Array> <SchemaField.Array name="array" x-decorator="FormItem" title="Object array" maxItems={3} x-component="ArrayTabs" > <SchemaField.Object> <SchemaField.String x-decorator="FormItem" title="AAA" name="aaa" required x-component="Input" /> <SchemaField.String x-decorator="FormItem" title="BBB" name="bbb" required x-component="Input" /> </SchemaField.Object> </SchemaField.Array> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## JSON Schema case ```tsx import React from 'react' import { FormItem, Input, ArrayTabs, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayTabs, }, }) const form = createForm() const schema = { type: 'object', properties: { string_array: { type: 'array', title: 'String array', 'x-decorator': 'FormItem', maxItems: 3, 'x-component': 'ArrayTabs', items: { type: 'string', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, array: { type: 'array', title: 'Object array', 'x-decorator': 'FormItem', maxItems: 3, 'x-component': 'ArrayTabs', items: { type: 'object', properties: { aaa: { type: 'string', 'x-decorator': 'FormItem', title: 'AAA', required: true, 'x-component': 'Input', }, bbb: { type: 'string', 'x-decorator': 'FormItem', title: 'BBB', required: true, 'x-component': 'Input', }, }, }, }, }, } export default () => { return ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## API ### ArrayTabs Reference https://ant.design/components/tabs-cn/ ``` -------------------------------------------------------------------------------- /packages/reactive-vue/src/observer/observerInVue2.ts: -------------------------------------------------------------------------------- ```typescript // https://github.com/mobxjs/mobx-vue/blob/master/src/observer.ts /** * @author Kuitos * @homepage https://github.com/kuitos/ * @since 2018-05-22 16:39 */ import { Tracker, batch } from '@formily/reactive' import collectDataForVue from './collectData' import { Vue2 as Vue } from 'vue-demi' import { IObserverOptions } from '../types' const noop = () => {} const disposerSymbol = Symbol('disposerSymbol') function observer(Component: any, observerOptions?: IObserverOptions): any { const name = observerOptions?.name || (Component as any).name || (Component as any)._componentTag || (Component.constructor && Component.constructor.name) || '<component>' const originalOptions = typeof Component === 'object' ? Component : (Component as any).options // To not mutate the original component options, we need to construct a new one const dataDefinition = originalOptions.data const options = { name, ...originalOptions, data(vm: any) { return collectDataForVue(vm || this, dataDefinition) }, // overrider the cached constructor to avoid extending skip // @see https://github.com/vuejs/vue/blob/6cc070063bd211229dff5108c99f7d11b6778550/src/core/global-api/extend.js#L24 _Ctor: {}, } // we couldn't use the Component as super class when Component was a VueClass, that will invoke the lifecycle twice after we called Component.extend const superProto = typeof Component === 'function' && Object.getPrototypeOf(Component.prototype) const Super = superProto instanceof (Vue as any) ? superProto.constructor : Vue const ExtendedComponent = Super.extend(options) const { $mount, $destroy } = ExtendedComponent.prototype ExtendedComponent.prototype.$mount = function (this: any, ...args: any[]) { let mounted = false this[disposerSymbol] = noop let nativeRenderOfVue: any const reactiveRender = () => { batch(() => { tracker.track(() => { if (!mounted) { $mount.apply(this, args) mounted = true nativeRenderOfVue = this._watcher.getter // rewrite the native render method of vue with our reactive tracker render // thus if component updated by vue watcher, we could re track and collect dependencies by @formily/reactive this._watcher.getter = reactiveRender } else { nativeRenderOfVue.call(this, this) } }) }) return this } reactiveRender.$vm = this const tracker = new Tracker(() => { if ( reactiveRender.$vm._isBeingDestroyed || reactiveRender.$vm._isDestroyed ) { return tracker.dispose() } if ( observerOptions?.scheduler && typeof observerOptions.scheduler === 'function' ) { observerOptions.scheduler(reactiveRender) } else { reactiveRender() } }) this[disposerSymbol] = tracker.dispose return reactiveRender() } ExtendedComponent.prototype.$destroy = function (this: any) { ;(this as any)[disposerSymbol]() $destroy.apply(this) } const extendedComponentNamePropertyDescriptor = Object.getOwnPropertyDescriptor(ExtendedComponent, 'name') || {} if (extendedComponentNamePropertyDescriptor.configurable === true) { Object.defineProperty(ExtendedComponent, 'name', { writable: false, value: name, enumerable: false, configurable: false, }) } return ExtendedComponent } export { observer, observer as Observer } ``` -------------------------------------------------------------------------------- /packages/next/src/form-item/grid.scss: -------------------------------------------------------------------------------- ```scss .#{$form-item-cls} { .#{$form-item-cls}-item-col-24 { -webkit-box-flex: 0; -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .#{$form-item-cls}-item-col-23 { -webkit-box-flex: 0; -ms-flex: 0 0 95.83333333%; flex: 0 0 95.83333333%; max-width: 95.83333333%; } .#{$form-item-cls}-item-col-22 { -webkit-box-flex: 0; -ms-flex: 0 0 91.66666667%; flex: 0 0 91.66666667%; max-width: 91.66666667%; } .#{$form-item-cls}-item-col-21 { -webkit-box-flex: 0; -ms-flex: 0 0 87.5%; flex: 0 0 87.5%; max-width: 87.5%; } .#{$form-item-cls}-item-col-20 { -webkit-box-flex: 0; -ms-flex: 0 0 83.33333333%; flex: 0 0 83.33333333%; max-width: 83.33333333%; } .#{$form-item-cls}-item-col-19 { -webkit-box-flex: 0; -ms-flex: 0 0 79.16666667%; flex: 0 0 79.16666667%; max-width: 79.16666667%; } .#{$form-item-cls}-item-col-18 { -webkit-box-flex: 0; -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .#{$form-item-cls}-item-col-17 { -webkit-box-flex: 0; -ms-flex: 0 0 70.83333333%; flex: 0 0 70.83333333%; max-width: 70.83333333%; } .#{$form-item-cls}-item-col-16 { -webkit-box-flex: 0; -ms-flex: 0 0 66.66666667%; flex: 0 0 66.66666667%; max-width: 66.66666667%; } .#{$form-item-cls}-item-col-15 { -webkit-box-flex: 0; -ms-flex: 0 0 62.5%; flex: 0 0 62.5%; max-width: 62.5%; } .#{$form-item-cls}-item-col-14 { -webkit-box-flex: 0; -ms-flex: 0 0 58.33333333%; flex: 0 0 58.33333333%; max-width: 58.33333333%; } .#{$form-item-cls}-item-col-13 { -webkit-box-flex: 0; -ms-flex: 0 0 54.16666667%; flex: 0 0 54.16666667%; max-width: 54.16666667%; } .#{$form-item-cls}-item-col-12 { -webkit-box-flex: 0; -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .#{$form-item-cls}-item-col-11 { -webkit-box-flex: 0; -ms-flex: 0 0 45.83333333%; flex: 0 0 45.83333333%; max-width: 45.83333333%; } .#{$form-item-cls}-item-col-10 { -webkit-box-flex: 0; -ms-flex: 0 0 41.66666667%; flex: 0 0 41.66666667%; max-width: 41.66666667%; } .#{$form-item-cls}-item-col-9 { -webkit-box-flex: 0; -ms-flex: 0 0 37.5%; flex: 0 0 37.5%; max-width: 37.5%; } .#{$form-item-cls}-item-col-8 { -webkit-box-flex: 0; -ms-flex: 0 0 33.33333333%; flex: 0 0 33.33333333%; max-width: 33.33333333%; } .#{$form-item-cls}-item-col-7 { -webkit-box-flex: 0; -ms-flex: 0 0 29.16666667%; flex: 0 0 29.16666667%; max-width: 29.16666667%; } .#{$form-item-cls}-item-col-6 { -webkit-box-flex: 0; -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .#{$form-item-cls}-item-col-5 { -webkit-box-flex: 0; -ms-flex: 0 0 20.83333333%; flex: 0 0 20.83333333%; max-width: 20.83333333%; } .#{$form-item-cls}-item-col-4 { -webkit-box-flex: 0; -ms-flex: 0 0 16.66666667%; flex: 0 0 16.66666667%; max-width: 16.66666667%; } .#{$form-item-cls}-item-col-3 { -webkit-box-flex: 0; -ms-flex: 0 0 12.5%; flex: 0 0 12.5%; max-width: 12.5%; } .#{$form-item-cls}-item-col-2 { -webkit-box-flex: 0; -ms-flex: 0 0 8.33333333%; flex: 0 0 8.33333333%; max-width: 8.33333333%; } .#{$form-item-cls}-item-col-1 { -webkit-box-flex: 0; -ms-flex: 0 0 4.16666667%; flex: 0 0 4.16666667%; max-width: 4.16666667%; } .#{$form-item-cls}-item-col-0 { display: none; } } ``` -------------------------------------------------------------------------------- /packages/json-schema/src/polyfills/SPECIFICATION_1_0.ts: -------------------------------------------------------------------------------- ```typescript import { registerPolyfills } from '../patches' import { toArr, isArr, isStr, lowerCase, isValid } from '@formily/shared' import { ISchema } from '../types' const VOID_COMPONENTS = [ 'card', 'block', 'grid-col', 'grid-row', 'grid', 'layout', 'step', 'tab', 'text-box', ] const TYPE_DEFAULT_COMPONENTS = {} const transformCondition = (condition: string) => { if (isStr(condition)) { return condition.replace(/\$value/, '$self.value') } } const transformXLinkage = (linkages: any[]) => { if (isArr(linkages)) { return linkages.reduce((buf, item) => { if (!item) return buf if (item.type === 'value:visible') { return buf.concat({ target: item.target, when: transformCondition(item.condition), fulfill: { state: { visible: true, }, }, otherwise: { state: { visible: false, }, }, }) } else if (item.type === 'value:schema') { return buf.concat({ target: item.target, when: transformCondition(item.condition), fulfill: { schema: SpecificationV1Polyfill({ version: '1.0', ...item.schema }), }, otherwise: { schema: SpecificationV1Polyfill({ version: '1.0', ...item.otherwise, }), }, }) } else if (item.type === 'value:state') { return buf.concat({ target: item.target, when: transformCondition(item.condition), fulfill: { state: item.state, }, otherwise: { state: item.otherwise, }, }) } }, []) } return [] } const SpecificationV1Polyfill = (schema: ISchema) => { if (isValid(schema['editable'])) { schema['x-editable'] = schema['x-editable'] || schema['editable'] delete schema['editable'] } if (isValid(schema['visible'])) { schema['x-visible'] = schema['x-visible'] || schema['visible'] delete schema['visible'] } if (isValid(schema['display'])) { schema['x-display'] = schema['x-display'] || (schema['display'] ? 'visible' : 'hidden') delete schema['display'] } if (isValid(schema['x-props'])) { schema['x-decorator-props'] = schema['x-decorator-props'] || schema['x-props'] delete schema['display'] } if (schema['x-linkages']) { schema['x-reactions'] = toArr(schema['x-reactions']).concat( transformXLinkage(schema['x-linkages']) ) delete schema['x-linkages'] } if (schema['x-component']) { if ( VOID_COMPONENTS.some( (component) => lowerCase(component) === lowerCase(schema['x-component']) ) ) { schema['type'] = 'void' } } else { if (TYPE_DEFAULT_COMPONENTS[schema['type']]) { schema['x-component'] = TYPE_DEFAULT_COMPONENTS[schema['type']] } } if ( !schema['x-decorator'] && schema['type'] !== 'void' && schema['type'] !== 'object' ) { schema['x-decorator'] = schema['x-decorator'] || 'FormItem' } if (schema['x-rules']) { schema['x-validator'] = [] .concat(schema['x-validator'] || []) .concat(schema['x-rules']) } return schema } registerPolyfills('1.0', SpecificationV1Polyfill) export const registerVoidComponents = (components: string[]) => { VOID_COMPONENTS.push(...components) } export const registerTypeDefaultComponents = (maps: Record<string, string>) => { Object.assign(TYPE_DEFAULT_COMPONENTS, maps) } ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- ```markdown --- title: Formily - Alibaba unified front-end form solution order: 10 hero: title: Alibaba Formily desc: Alibaba Unified Front-end Form Solution actions: - text: Introduction link: /guide - text: Quick start link: /guide/quick-start features: - icon: https://img.alicdn.com/imgextra/i2/O1CN016i72sH1c5wh1kyy9U_!!6000000003550-55-tps-800-800.svg title: Easier to Use desc: Out of the box, rich cases - icon: https://img.alicdn.com/imgextra/i1/O1CN01bHdrZJ1rEOESvXEi5_!!6000000005599-55-tps-800-800.svg title: More Efficient desc: Fool writing, ultra-high performance - icon: https://img.alicdn.com/imgextra/i3/O1CN01xlETZk1G0WSQT6Xii_!!6000000000560-55-tps-800-800.svg title: More Professional desc: Complete, flexible and elegant footer: Open-source MIT Licensed | Copyright © 2019-present<br />Powered by self --- ```tsx /** * inline: true */ import React from 'react' import { Section } from './site/Section' import './site/styles.less' export default () => ( <Section title="Fool Writing, Ultra-high Performance" style={{ marginTop: 40 }} titleStyle={{ paddingBottom: 100, fontWeight: 'bold' }} > <iframe className="codesandbox" src="https://codesandbox.io/embed/formilyyaliceshi-vbu4w?fontsize=12&module=%2FApp.tsx&theme=dark" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" ></iframe> </Section> ) ``` ```tsx /** * inline: true */ import React from 'react' import { Section } from './site/Section' import './site/styles.less' export default () => ( <Section title="Form Builder,Efficient Development" style={{ marginTop: 140, fontWeight: 'bold' }} titleStyle={{ paddingBottom: 140 }} scale={1.2} > <a href="//designable-antd.formilyjs.org" target="_blank" rel="noreferrer"> <img src="//img.alicdn.com/imgextra/i2/O1CN01eI9FLz22tZek2jv7E_!!6000000007178-2-tps-3683-2272.png" /> </a> </Section> ) ``` ```tsx /** * inline: true */ import React from 'react' import { Section } from './site/Section' import './site/styles.less' export default () => ( <Section title="Pure Core, More Extensibility" style={{ marginTop: 140 }} titleStyle={{ paddingBottom: 100, fontWeight: 'bold' }} > <a href="//core.formilyjs.org" target="_blank" rel="noreferrer"> <img src="//img.alicdn.com/imgextra/i4/O1CN019qbf1b1ChnTfT9x3X_!!6000000000113-55-tps-1939-1199.svg" /> </a> </Section> ) ``` ```tsx /** * inline: true */ import React from 'react' import { Section } from './site/Section' import { Contributors } from './site/Contributors' import './site/styles.less' export default () => ( <Section title="Active Community & Genius People" style={{ marginTop: 100 }} titleStyle={{ paddingBottom: 140, fontWeight: 'bold' }} > <Contributors /> </Section> ) ``` ```tsx /** * inline: true */ import React from 'react' import { Section } from './site/Section' import { QrCode, QrCodeGroup } from './site/QrCode' import './site/styles.less' export default () => ( <Section title="High-Quality Community Group" style={{ marginTop: 140 }} titleStyle={{ paddingBottom: 20, fontWeight: 'bold' }} > <QrCodeGroup> <QrCode link="//img.alicdn.com/imgextra/i1/O1CN011zlc5b1uu1BDUpNg1_!!6000000006096-2-tps-978-1380.png" /> </QrCodeGroup> </Section> ) ``` ``` -------------------------------------------------------------------------------- /packages/vue/src/shared/connect.ts: -------------------------------------------------------------------------------- ```typescript import { isVue2, markRaw, defineComponent, getCurrentInstance } from 'vue-demi' import { isFn, isStr, FormPath, each, isValid } from '@formily/shared' import { isVoidField, GeneralField } from '@formily/core' import { observer } from '@formily/reactive-vue' import { useField } from '../hooks/useField' import h from './h' import type { VueComponent, IComponentMapper, IStateMapper, VueComponentProps, } from '../types' export function mapProps<T extends VueComponent = VueComponent>( ...args: IStateMapper<VueComponentProps<T>>[] ) { const transform = (input: VueComponentProps<T>, field: GeneralField) => args.reduce((props, mapper) => { if (isFn(mapper)) { props = Object.assign(props, mapper(props, field)) } else { each(mapper, (to, extract) => { const extractValue = FormPath.getIn(field, extract) const targetValue = isStr(to) ? to : extract const originalValue = FormPath.getIn(props, targetValue) if (extract === 'value') { if (to !== extract) { delete props['value'] } } if (isValid(originalValue) && !isValid(extractValue)) return FormPath.setIn(props, targetValue, extractValue) }) } return props }, input) return (target: T) => { return observer( defineComponent({ name: target.name ? `Connected${target.name}` : `ConnectedComponent`, setup(props, { attrs, slots, listeners }: any) { const fieldRef = useField() return () => { const newAttrs = fieldRef.value ? transform({ ...attrs } as VueComponentProps<T>, fieldRef.value) : { ...attrs } return h( target, { attrs: newAttrs, on: listeners, }, slots ) } }, }) ) } } export function mapReadPretty<T extends VueComponent, C extends VueComponent>( component: C, readPrettyProps?: Record<string, any> ) { return (target: T) => { return observer( defineComponent({ name: target.name ? `Read${target.name}` : `ReadComponent`, setup(props, { attrs, slots, listeners }: Record<string, any>) { const fieldRef = useField() return () => { const field = fieldRef.value return h( field && !isVoidField(field) && field.pattern === 'readPretty' ? component : target, { attrs: { ...readPrettyProps, ...attrs, }, on: listeners, }, slots ) } }, }) ) } } export function connect<T extends VueComponent>( target: T, ...args: IComponentMapper[] ): T { const Component = args.reduce((target: VueComponent, mapper) => { return mapper(target) }, target) /* istanbul ignore else */ if (isVue2) { const functionalComponent = defineComponent({ functional: true, name: target.name, render(h, context) { return h(Component, context.data, context.children) }, }) return markRaw(functionalComponent) as T } else { const functionalComponent = defineComponent({ name: target.name, setup(props, { attrs, slots }) { return () => { return h(Component, { props, attrs }, slots) } }, }) return markRaw(functionalComponent) as T } } ``` -------------------------------------------------------------------------------- /packages/path/src/destructor.ts: -------------------------------------------------------------------------------- ```typescript import { Segments, Node, DestructorRules, isArrayPattern, isObjectPattern, isIdentifier, isDestructorExpression, } from './types' import { isNum } from './shared' type Mutators = { getIn: (segments: Segments, source: any) => any setIn: (segments: Segments, source: any, value: any) => void deleteIn?: (segments: Segments, source: any) => any existIn?: (segments: Segments, source: any, start: number) => boolean } const DestructorCache = new Map() const isValid = (val: any) => val !== undefined && val !== null export const getDestructor = (source: string) => { return DestructorCache.get(source) } export const setDestructor = (source: string, rules: DestructorRules) => { DestructorCache.set(source, rules) } export const parseDestructorRules = (node: Node): DestructorRules => { const rules = [] if (isObjectPattern(node)) { let index = 0 node.properties.forEach((child) => { rules[index] = { path: [], } rules[index].key = child.key.value rules[index].path.push(child.key.value) if (isIdentifier(child.value)) { rules[index].key = child.value.value } const basePath = rules[index].path const childRules = parseDestructorRules(child.value as Node) let k = index childRules.forEach((rule) => { if (rules[k]) { rules[k].key = rule.key rules[k].path = basePath.concat(rule.path) } else { rules[k] = { key: rule.key, path: basePath.concat(rule.path), } } k++ }) if (k > index) { index = k } else { index++ } }) return rules } else if (isArrayPattern(node)) { let index = 0 node.elements.forEach((child, key) => { rules[index] = { path: [], } rules[index].key = key rules[index].path.push(key) if (isIdentifier(child)) { rules[index].key = child.value } const basePath = rules[index].path const childRules = parseDestructorRules(child as Node) let k = index childRules.forEach((rule) => { if (rules[k]) { rules[k].key = rule.key rules[k].path = basePath.concat(rule.path) } else { rules[k] = { key: rule.key, path: basePath.concat(rule.path), } } k++ }) if (k > index) { index = k } else { index++ } }) return rules } if (isDestructorExpression(node)) { return parseDestructorRules(node.value) } return rules } export const setInByDestructor = ( source: any, rules: DestructorRules, value: any, mutators: Mutators ) => { rules.forEach(({ key, path }) => { mutators.setIn([key], source, mutators.getIn(path, value)) }) } export const getInByDestructor = ( source: any, rules: DestructorRules, mutators: Mutators ) => { let response = {} if (rules.length) { if (isNum(rules[0].path[0])) { response = [] } } source = isValid(source) ? source : {} rules.forEach(({ key, path }) => { mutators.setIn(path, response, source[key]) }) return response } export const deleteInByDestructor = ( source: any, rules: DestructorRules, mutators: Mutators ) => { rules.forEach(({ key }) => { mutators.deleteIn([key], source) }) } export const existInByDestructor = ( source: any, rules: DestructorRules, start: number, mutators: Mutators ) => { return rules.every(({ key }) => { return mutators.existIn([key], source, start) }) } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/array-table/effects-markup-schema.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaBooleanField name="hideFirstColumn" x-decorator="FormItem" x-component="Switch" title="隐藏A2" /> <SchemaArrayField name="array" x-decorator="FormItem" x-component="ArrayTable" > <SchemaObjectField> <SchemaVoidField name="column1" x-component="ArrayTable.Column" :x-component-props="{ width: 80, title: 'Index' }" ><SchemaVoidField x-component="ArrayTable.Index" /> </SchemaVoidField> <SchemaVoidField name="column2" x-component="ArrayTable.Column" :x-component-props="{ title: '显隐->A2', width: 100, }" > <SchemaBooleanField name="a1" x-decorator="FormItem" x-component="Switch" /> </SchemaVoidField> <SchemaVoidField x-component="ArrayTable.Column" name="column3" :x-component-props="{ title: 'A2', width: 200 }" > <SchemaStringField x-decorator="FormItem" name="a2" x-component="Input" /> </SchemaVoidField> <SchemaVoidField name="column4" x-component="ArrayTable.Column" :x-component-props="{ title: 'A3' }" > <SchemaStringField name="a3" x-decorator="FormItem" x-component="Input" /> </SchemaVoidField> <SchemaVoidField name="column5" x-component="ArrayTable.Column" :x-component-props="{ title: 'Operations', prop: 'operations', width: 200, fixed: 'right', }" > <SchemaVoidField x-component="FormItem"> <SchemaVoidField x-component="ArrayTable.Remove" /> <SchemaVoidField x-component="ArrayTable.MoveUp" /> <SchemaVoidField x-component="ArrayTable.MoveDown" /> </SchemaVoidField> </SchemaVoidField> </SchemaObjectField> <SchemaVoidField x-component="ArrayTable.Addition" title="添加条目" /> </SchemaArrayField> </SchemaField> <Submit @submit="log">提交</Submit> </FormProvider> </template> <script> import { createForm, onFieldChange, onFieldReact } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/vue' import { Submit, FormItem, ArrayTable, Input, Editable, Switch, } from '@formily/element' const fields = createSchemaField({ components: { FormItem, ArrayTable, Input, Editable, Switch, }, }) export default { components: { FormProvider, Submit, ...fields }, data() { const form = createForm({ effects: () => { //主动联动模式 onFieldChange('hideFirstColumn', ['value'], (field) => { field.query('array.column3').take((target) => { console.log('target', target) target.visible = !field.value }) field.query('array.*.a2').take((target) => { target.visible = !field.value }) }) //被动联动模式 onFieldReact('array.*.a2', (field) => { field.visible = !field.query('.a1').get('value') }) }, }) return { form, } }, methods: { log(...v) { console.log(...v) }, }, } </script> ``` -------------------------------------------------------------------------------- /packages/reactive/src/internals.ts: -------------------------------------------------------------------------------- ```typescript import { isFn, isCollectionType, isNormalType } from './checkers' import { RawProxy, ProxyRaw, MakeObModelSymbol, RawShallowProxy, } from './environment' import { baseHandlers, collectionHandlers } from './handlers' import { buildDataTree, getDataNode } from './tree' import { isSupportObservable } from './externals' import { PropertyKey, IVisitor, BoundaryFunction } from './types' const createNormalProxy = (target: any, shallow?: boolean) => { const proxy = new Proxy(target, baseHandlers) ProxyRaw.set(proxy, target) if (shallow) { RawShallowProxy.set(target, proxy) } else { RawProxy.set(target, proxy) } return proxy } const createCollectionProxy = (target: any, shallow?: boolean) => { const proxy = new Proxy(target, collectionHandlers) ProxyRaw.set(proxy, target) if (shallow) { RawShallowProxy.set(target, proxy) } else { RawProxy.set(target, proxy) } return proxy } const createShallowProxy = (target: any) => { if (isNormalType(target)) return createNormalProxy(target, true) if (isCollectionType(target)) return createCollectionProxy(target, true) // never reach return target } export const createObservable = ( target: any, key?: PropertyKey, value?: any, shallow?: boolean ) => { if (typeof value !== 'object') return value const raw = ProxyRaw.get(value) if (!!raw) { const node = getDataNode(raw) if (!node.target) node.target = target node.key = key return value } if (!isSupportObservable(value)) return value if (target) { const parentRaw = ProxyRaw.get(target) || target const isShallowParent = RawShallowProxy.get(parentRaw) if (isShallowParent) return value } buildDataTree(target, key, value) if (shallow) return createShallowProxy(value) if (isNormalType(value)) return createNormalProxy(value) if (isCollectionType(value)) return createCollectionProxy(value) // never reach return value } export const createAnnotation = <T extends (visitor: IVisitor) => any>( maker: T ) => { const annotation = (target: any): ReturnType<T> => { return maker({ value: target }) } if (isFn(maker)) { annotation[MakeObModelSymbol] = maker } return annotation } export const getObservableMaker = (target: any) => { if (target[MakeObModelSymbol]) { if (!target[MakeObModelSymbol][MakeObModelSymbol]) { return target[MakeObModelSymbol] } return getObservableMaker(target[MakeObModelSymbol]) } } export const createBoundaryFunction = ( start: (...args: any) => void, end: (...args: any) => void ) => { function boundary<F extends (...args: any) => any>(fn?: F): ReturnType<F> { let results: ReturnType<F> try { start() if (isFn(fn)) { results = fn() } } finally { end() } return results } boundary.bound = createBindFunction(boundary) return boundary } export const createBindFunction = <Boundary extends BoundaryFunction>( boundary: Boundary ) => { function bind<F extends (...args: any[]) => any>( callback?: F, context?: any ): F { return ((...args: any[]) => boundary(() => callback.apply(context, args))) as any } return bind } export const createBoundaryAnnotation = ( start: (...args: any) => void, end: (...args: any) => void ) => { const boundary = createBoundaryFunction(start, end) const annotation = createAnnotation(({ target, key }) => { target[key] = boundary.bound(target[key], target) return target }) boundary[MakeObModelSymbol] = annotation boundary.bound[MakeObModelSymbol] = annotation return boundary } ``` -------------------------------------------------------------------------------- /packages/antd/src/__builtins__/sort.tsx: -------------------------------------------------------------------------------- ```typescript import { DndContext, DragEndEvent, DragStartEvent } from '@dnd-kit/core' import { SortableContext, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable' import { ReactFC } from '@formily/reactive-react' import React, { createContext, useContext, useMemo } from 'react' export interface ISortableContainerProps { list: any[] start?: number accessibility?: { container?: Element } onSortStart?: (event: DragStartEvent) => void onSortEnd?: (event: { oldIndex: number; newIndex: number }) => void } export function SortableContainer<T extends React.HTMLAttributes<HTMLElement>>( Component: ReactFC<T> ): ReactFC<ISortableContainerProps & T> { return ({ list, start = 0, accessibility, onSortStart, onSortEnd, ...props }) => { const _onSortEnd = (event: DragEndEvent) => { const { active, over } = event const oldIndex = (active.id as number) - 1 const newIndex = (over?.id as number) - 1 onSortEnd?.({ oldIndex, newIndex, }) } return ( <DndContext accessibility={accessibility} onDragStart={onSortStart} onDragEnd={_onSortEnd} > <SortableContext items={list.map((_, index) => index + start + 1)} strategy={verticalListSortingStrategy} > <Component {...(props as unknown as T)}>{props.children}</Component> </SortableContext> </DndContext> ) } } export const useSortableItem = () => { return useContext(SortableItemContext) } export const SortableItemContext = createContext< Partial<ReturnType<typeof useSortable>> >({}) export interface ISortableElementProps { index?: number lockAxis?: 'x' | 'y' } export function SortableElement<T extends React.HTMLAttributes<HTMLElement>>( Component: ReactFC<T> ): ReactFC<T & ISortableElementProps> { return ({ index = 0, lockAxis, ...props }) => { const sortable = useSortable({ id: index + 1, }) const { setNodeRef, transform, transition, isDragging } = sortable if (transform) { switch (lockAxis) { case 'x': transform.y = 0 break case 'y': transform.x = 0 break default: break } } const style = useMemo(() => { const itemStyle: React.CSSProperties = { position: 'relative', touchAction: 'none', zIndex: 1, transform: `translate3d(${transform?.x || 0}px, ${ transform?.y || 0 }px, 0)`, transition: `${transform ? 'all 200ms ease' : ''}`, } const dragStyle: React.CSSProperties = { transition, opacity: '0.8', transform: `translate3d(${transform?.x || 0}px, ${ transform?.y || 0 }px, 0)`, } const computedStyle = isDragging ? { ...itemStyle, ...dragStyle, ...props.style, } : { ...itemStyle, ...props.style, } return computedStyle }, [isDragging, transform, transition, props.style]) return ( <SortableItemContext.Provider value={sortable}> {Component({ ...props, style, ref: setNodeRef, } as unknown as T)} </SortableItemContext.Provider> ) } } export function SortableHandle<T extends React.HTMLAttributes<HTMLElement>>( Component: ReactFC<T> ): ReactFC<T> { return (props: T) => { const { attributes, listeners } = useSortableItem() return <Component {...props} {...attributes} {...listeners} /> } } ```