This is page 14 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/reactive/src/reaction.ts: -------------------------------------------------------------------------------- ```typescript import { isFn } from './checkers' import { ArraySet } from './array' import { IOperation, ReactionsMap, Reaction, PropertyKey } from './types' import { ReactionStack, PendingScopeReactions, BatchEndpoints, DependencyCollected, RawReactionsMap, PendingReactions, BatchCount, UntrackCount, BatchScope, ObserverListeners, } from './environment' const ITERATION_KEY = Symbol('iteration key') const addRawReactionsMap = ( target: any, key: PropertyKey, reaction: Reaction ) => { const reactionsMap = RawReactionsMap.get(target) if (reactionsMap) { const reactions = reactionsMap.get(key) if (reactions) { reactions.add(reaction) } else { reactionsMap.set(key, new ArraySet([reaction])) } return reactionsMap } else { const reactionsMap: ReactionsMap = new Map([ [key, new ArraySet([reaction])], ]) RawReactionsMap.set(target, reactionsMap) return reactionsMap } } const addReactionsMapToReaction = ( reaction: Reaction, reactionsMap: ReactionsMap ) => { const bindSet = reaction._reactionsSet if (bindSet) { bindSet.add(reactionsMap) } else { reaction._reactionsSet = new ArraySet([reactionsMap]) } return bindSet } const getReactionsFromTargetKey = (target: any, key: PropertyKey) => { const reactionsMap = RawReactionsMap.get(target) const reactions = [] if (reactionsMap) { const map = reactionsMap.get(key) if (map) { map.forEach((reaction) => { if (reactions.indexOf(reaction) === -1) { reactions.push(reaction) } }) } } return reactions } const runReactions = (target: any, key: PropertyKey) => { const reactions = getReactionsFromTargetKey(target, key) const prevUntrackCount = UntrackCount.value UntrackCount.value = 0 for (let i = 0, len = reactions.length; i < len; i++) { const reaction = reactions[i] if (reaction._isComputed) { reaction._scheduler(reaction) } else if (isScopeBatching()) { PendingScopeReactions.add(reaction) } else if (isBatching()) { PendingReactions.add(reaction) } else { // never reach if (isFn(reaction._scheduler)) { reaction._scheduler(reaction) } else { reaction() } } } UntrackCount.value = prevUntrackCount } const notifyObservers = (operation: IOperation) => { ObserverListeners.forEach((fn) => fn(operation)) } export const bindTargetKeyWithCurrentReaction = (operation: IOperation) => { let { key, type, target } = operation if (type === 'iterate') { key = ITERATION_KEY } const reactionLen = ReactionStack.length if (reactionLen === 0) return const current = ReactionStack[reactionLen - 1] if (isUntracking()) return if (current) { DependencyCollected.value = true addReactionsMapToReaction(current, addRawReactionsMap(target, key, current)) } } export const bindComputedReactions = (reaction: Reaction) => { if (isFn(reaction)) { const current = ReactionStack[ReactionStack.length - 1] if (current) { const computes = current._computesSet if (computes) { computes.add(reaction) } else { current._computesSet = new ArraySet([reaction]) } } } } export const runReactionsFromTargetKey = (operation: IOperation) => { let { key, type, target, oldTarget } = operation batchStart() notifyObservers(operation) if (type === 'clear') { oldTarget.forEach((_: any, key: PropertyKey) => { runReactions(target, key) }) } else { runReactions(target, key) } if (type === 'add' || type === 'delete' || type === 'clear') { const newKey = Array.isArray(target) ? 'length' : ITERATION_KEY runReactions(target, newKey) } batchEnd() } export const hasRunningReaction = () => { return ReactionStack.length > 0 } export const releaseBindingReactions = (reaction: Reaction) => { reaction._reactionsSet?.forEach((reactionsMap) => { reactionsMap.forEach((reactions) => { reactions.delete(reaction) }) }) PendingReactions.delete(reaction) PendingScopeReactions.delete(reaction) delete reaction._reactionsSet } export const suspendComputedReactions = (current: Reaction) => { current._computesSet?.forEach((reaction) => { const reactions = getReactionsFromTargetKey( reaction._context, reaction._property ) if (reactions.length === 0) { disposeBindingReactions(reaction) reaction._dirty = true } }) } export const disposeBindingReactions = (reaction: Reaction) => { reaction._disposed = true releaseBindingReactions(reaction) suspendComputedReactions(reaction) } export const batchStart = () => { BatchCount.value++ } export const batchEnd = () => { BatchCount.value-- if (BatchCount.value === 0) { const prevUntrackCount = UntrackCount.value UntrackCount.value = 0 executePendingReactions() executeBatchEndpoints() UntrackCount.value = prevUntrackCount } } export const batchScopeStart = () => { BatchScope.value = true } export const batchScopeEnd = () => { const prevUntrackCount = UntrackCount.value BatchScope.value = false UntrackCount.value = 0 PendingScopeReactions.batchDelete((reaction) => { if (isFn(reaction._scheduler)) { reaction._scheduler(reaction) } else { reaction() } }) UntrackCount.value = prevUntrackCount } export const untrackStart = () => { UntrackCount.value++ } export const untrackEnd = () => { UntrackCount.value-- } export const isBatching = () => BatchCount.value > 0 export const isScopeBatching = () => BatchScope.value export const isUntracking = () => UntrackCount.value > 0 export const executePendingReactions = () => { PendingReactions.batchDelete((reaction) => { if (isFn(reaction._scheduler)) { reaction._scheduler(reaction) } else { reaction() } }) } export const executeBatchEndpoints = () => { BatchEndpoints.batchDelete((callback) => { callback() }) } export const hasDepsChange = (newDeps: any[], oldDeps: any[]) => { if (newDeps === oldDeps) return false if (newDeps.length !== oldDeps.length) return true if (newDeps.some((value, index) => value !== oldDeps[index])) return true return false } export const disposeEffects = (reaction: Reaction) => { if (reaction._effects) { try { batchStart() reaction._effects.queue.forEach((item) => { if (!item || !item.dispose) return item.dispose() }) } finally { batchEnd() } } } ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-item/feedback.vue: -------------------------------------------------------------------------------- ```vue <template> <FormProvider :form="form"> <SchemaField> <SchemaVoidField x-component="Title" x-content="表单状态: " /> <SchemaStringField title="错误状态(feedbackStatus=error)" x-decorator="FormItem" x-component="Input" description="description" :x-decorator-props="{ feedbackStatus: 'error', }" /> <SchemaStringField title="警告状态(feedbackStatus=warning)" x-decorator="FormItem" x-component="Input" description="description" :x-decorator-props="{ feedbackStatus: 'warning', }" /> <SchemaStringField title="成功状态(feedbackStatus=success)" x-decorator="FormItem" x-component="Input" description="description" :x-decorator-props="{ feedbackStatus: 'success', }" /> <SchemaStringField title="加载状态(feedbackStatus=pending)" x-decorator="FormItem" x-component="Input" description="description" :x-decorator-props="{ feedbackStatus: 'pending', }" /> <SchemaVoidField x-component="Title" x-content="反馈信息的布局: " /> <SchemaStringField title="紧凑模式required" x-decorator="FormItem" x-component="Input" :required="true" :x-decorator-props="{ feedbackLayout: 'terse', }" /> <SchemaStringField title="紧凑模式有feedback(feedbackLayout=terse)" x-decorator="FormItem" x-component="Input" :x-decorator-props="{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'terse', }" /> <SchemaStringField title="紧凑模式无feedback(feedbackLayout=terse)" x-decorator="FormItem" x-component="Input" :x-decorator-props="{ feedbackLayout: 'terse', }" /> <SchemaStringField title="松散模式(feedbackLayout=loose)" x-decorator="FormItem" x-component="Input" :x-decorator-props="{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'loose', }" /> <SchemaStringField title="弹出模式(feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" :x-decorator-props="{ feedbackStatus: 'warning', feedbackText: 'warning message', feedbackLayout: 'popover', }" /> <SchemaStringField title="弹出模式(feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" :x-decorator-props="{ feedbackStatus: 'error', feedbackText: 'error message', feedbackLayout: 'popover', }" /> <SchemaStringField title="弹出模式(feedbackLayout=popover)" x-decorator="FormItem" x-component="Input" :x-decorator-props="{ feedbackStatus: 'success', feedbackText: 'success message', feedbackLayout: 'popover', }" /> <SchemaVoidField x-component="Title" x-content="组件的适配情况: " /> <SchemaVoidField x-component="FormLayout" :x-component-props="{ labelCol: 6, wrapperCol: 10, }" > <SchemaStringField title="Select" x-decorator="FormItem" x-component="Select" :x-decorator-props="{ feedbackStatus: 'success', feedbackIcon: SuccessIcon, }" /> <SchemaStringField title="DatePicker" x-decorator="FormItem" x-component="DatePicker" :x-decorator-props="{ feedbackStatus: 'success', feedbackIcon: SuccessIcon, }" /> <SchemaStringField title="DateRangePicker" x-decorator="FormItem" x-component="DatePicker" :x-decorator-props="{ feedbackStatus: 'success', feedbackIcon: SuccessIcon, }" :x-component-props="{ type: 'daterange', }" /> <SchemaStringField title="YearPicker" x-decorator="FormItem" x-component="DatePicker" :x-decorator-props="{ feedbackStatus: 'success', feedbackIcon: SuccessIcon, }" :x-component-props="{ type: 'year', }" /> <SchemaStringField title="MonthPicker" x-decorator="FormItem" x-component="DatePicker" :x-decorator-props="{ feedbackStatus: 'success', feedbackIcon: SuccessIcon, }" :x-component-props="{ type: 'month', }" /> <SchemaStringField title="TimePicker" x-decorator="FormItem" x-component="TimePicker" :x-decorator-props="{ feedbackStatus: 'success', feedbackIcon: SuccessIcon, }" /> <SchemaStringField title="InputNumber" x-decorator="FormItem" x-component="InputNumber" :x-decorator-props="{ feedbackStatus: 'success', feedbackIcon: SuccessIcon, }" /> <SchemaStringField title="Cascader" x-decorator="FormItem" x-component="Cascader" :x-decorator-props="{ feedbackStatus: 'success', feedbackIcon: SuccessIcon, }" /> </SchemaVoidField> </SchemaField> </FormProvider> </template> <script> import { createForm } from '@formily/core' import { createSchemaField, FormProvider } from '@formily/vue' import { FormItem, InputNumber, Input, Cascader, Select, DatePicker, FormLayout, TimePicker, } from '@formily/element' const SuccessIcon = { functional: true, render(h) { return h('i', { class: 'el-icon-circle-check', style: { color: '#8AE65C' }, }) }, } const Title = { functional: true, render(h, context) { return h('p', context.data, context.children) }, } const fields = createSchemaField({ components: { Title, FormItem, InputNumber, Input, Cascader, Select, DatePicker, FormLayout, TimePicker, }, }) export default { components: { FormProvider, ...fields }, data() { const form = createForm() return { form, SuccessIcon, } }, } </script> ``` -------------------------------------------------------------------------------- /packages/json-schema/src/transformer.ts: -------------------------------------------------------------------------------- ```typescript import { untracked, autorun, observable } from '@formily/reactive' import { isArr, isStr, toArr, each, isFn, isPlainObj, reduce, lazyMerge, } from '@formily/shared' import { Schema } from './schema' import { ISchema, ISchemaTransformerOptions, IFieldStateSetterOptions, SchemaReaction, } from './types' import { onFieldInit, onFieldMount, onFieldUnmount, onFieldValueChange, onFieldInputValueChange, onFieldInitialValueChange, onFieldValidateStart, onFieldValidateEnd, onFieldValidateFailed, onFieldValidateSuccess, IFieldFactoryProps, Field, } from '@formily/core' import { patchCompile, patchSchemaCompile, shallowCompile } from './compiler' const FieldEffects = { onFieldInit, onFieldMount, onFieldUnmount, onFieldValueChange, onFieldInputValueChange, onFieldInitialValueChange, onFieldValidateStart, onFieldValidateEnd, onFieldValidateFailed, onFieldValidateSuccess, } const DefaultFieldEffects = ['onFieldInit', 'onFieldValueChange'] const getDependencyValue = ( field: Field, pattern: string, property?: string ) => { const [target, path] = String(pattern).split(/\s*#\s*/) return field.query(target).getIn(path || property || 'value') } const getDependencies = ( field: Field, dependencies: | Array<string | { name?: string; source?: string; property?: string }> | object ) => { if (isArr(dependencies)) { const results = [] dependencies.forEach((pattern) => { if (isStr(pattern)) { results.push(getDependencyValue(field, pattern)) } else if (isPlainObj(pattern)) { if (pattern.name && pattern.source) { results[pattern.name] = getDependencyValue( field, pattern.source, pattern.property ) } } }) return results } else if (isPlainObj(dependencies)) { return reduce( dependencies, (buf, pattern, key) => { buf[key] = getDependencyValue(field, pattern) return buf }, {} ) } return [] } const setSchemaFieldState = ( options: IFieldStateSetterOptions, demand = false ) => { const { request, target, runner, field, scope } = options || {} if (!request) return if (target) { if (request.state) { field.form.setFieldState(target, (state) => patchCompile( state, request.state, lazyMerge(scope, { $target: state, }) ) ) } if (request.schema) { field.form.setFieldState(target, (state) => patchSchemaCompile( state, request.schema, lazyMerge(scope, { $target: state, }), demand ) ) } if (isStr(runner) && runner) { field.form.setFieldState(target, (state) => { shallowCompile( `{{function(){${runner}}}}`, lazyMerge(scope, { $target: state, }) )() }) } } else { if (request.state) { field.setState((state) => patchCompile(state, request.state, scope)) } if (request.schema) { field.setState((state) => patchSchemaCompile(state, request.schema, scope, demand) ) } if (isStr(runner) && runner) { shallowCompile(`{{function(){${runner}}}}`, scope)() } } } const getBaseScope = ( field: Field, options: ISchemaTransformerOptions = {} ) => { const $observable = (target: any, deps?: any[]) => autorun.memo(() => observable(target), deps) const $props = (props: any) => field.setComponentProps(props) const $effect = autorun.effect const $memo = autorun.memo const $self = field const $form = field.form const $values = field.form.values return lazyMerge( { get $lookup() { return options?.scope?.$record ?? $values }, get $records() { return field.records }, get $record() { const record = field.record if (typeof record === 'object') { return lazyMerge(record, { get $lookup() { return options?.scope?.$record ?? $values }, get $index() { return field.index }, }) } return record }, get $index() { return field.index }, }, options.scope, { $form, $self, $observable, $effect, $memo, $props, $values, } ) } const getBaseReactions = (schema: ISchema, options: ISchemaTransformerOptions) => (field: Field) => { setSchemaFieldState( { field, request: { schema }, scope: getBaseScope(field, options), }, true ) } const getUserReactions = ( schema: ISchema, options: ISchemaTransformerOptions ) => { const reactions: SchemaReaction[] = toArr(schema['x-reactions']) return reactions.map((unCompiled) => { return (field: Field) => { const baseScope = getBaseScope(field, options) const reaction = shallowCompile(unCompiled, baseScope) if (!reaction) return if (isFn(reaction)) { return reaction(field, baseScope) } const { when, fulfill, otherwise, target, effects } = reaction const run = () => { const $deps = getDependencies(field, reaction.dependencies) const $dependencies = $deps const scope = lazyMerge(baseScope, { $target: null, $deps, $dependencies, }) const compiledWhen = shallowCompile(when, scope) const condition = when ? compiledWhen : true const request = condition ? fulfill : otherwise const runner = request?.run setSchemaFieldState({ field, target, request, runner, scope, }) } if (target) { reaction.effects = effects?.length ? effects : DefaultFieldEffects } if (reaction.effects) { autorun.memo(() => { untracked(() => { each(reaction.effects, (type) => { if (FieldEffects[type]) { FieldEffects[type](field.address, run) } }) }) }, []) } else { run() } } }) } export const transformFieldProps = ( schema: Schema, options: ISchemaTransformerOptions ): IFieldFactoryProps<any, any> => { return { name: schema.name, reactions: [getBaseReactions(schema, options)].concat( getUserReactions(schema, options) ), } } ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/PreviewText.md: -------------------------------------------------------------------------------- ```markdown # PreviewText > Reading state components, mainly used to implement the reading state of these components of class Input and DatePicker ## Simple use case ```tsx import React from 'react' import { PreviewText, FormItem, FormLayout } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, PreviewText, }, }) const form = createForm() export default () => { return ( <FormLayout labelCol={6} wrapperCol={10}> <FormProvider form={form}> <SchemaField> <SchemaField.String x-decorator="FormItem" title="text preview" x-component="PreviewText.Input" default={'Hello world'} /> <SchemaField.String x-decorator="FormItem" title="Select item preview" x-component="PreviewText.Select" x-component-props={{ mode: 'multiple', }} default={['123', '222']} enum={[ { label: 'A111', value: '123' }, { label: 'A222', value: '222' }, ]} /> <SchemaField.String x-decorator="FormItem" title="TreeSelect preview" x-component="PreviewText.TreeSelect" x-component-props={{ multiple: true, }} default={['123', '222']} enum={[ { label: 'A111', value: '123' }, { label: 'A222', value: '222' }, ]} /> <SchemaField.String x-decorator="FormItem" title="TreeSelect(treeData)preview" x-component="PreviewText.TreeSelect" x-component-props={{ multiple: true, treeNodeLabelProp: 'name', treeData: [ { name: 'A111', value: '123' }, { name: 'A222', value: '222' }, ], }} default={['123', '222']} /> <SchemaField.String x-decorator="FormItem" title="date preview" x-component="PreviewText.DatePicker" default={'2020-11-23 22:15:20'} /> <SchemaField.String x-decorator="FormItem" title="Cascader Preview" x-component="PreviewText.Cascader" default={'yuhang'} enum={[ { label: 'Hangzhou', value: 'hangzhou', children: [ { label: 'Yuhang', value: 'yuhang', }, ], }, ]} /> </SchemaField> </FormProvider> </FormLayout> ) } ``` ## Extended reading mode ```tsx import React from 'react' import { PreviewText, FormItem, FormLayout, FormButtonGroup, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, mapReadPretty, connect, createSchemaField, } from '@formily/react' import { Button, Input as AntdInput } from 'antd' const Input = connect(AntdInput, mapReadPretty(PreviewText.Input)) const SchemaField = createSchemaField({ components: { Input, FormItem, PreviewText, }, }) const form = createForm() export default () => { return ( <PreviewText.Placeholder value="No data currently available"> <FormLayout labelCol={6} wrapperCol={10}> <FormProvider form={form}> <SchemaField> <SchemaField.Markup type="string" x-decorator="FormItem" title="text preview" required x-component="Input" default={'Hello world'} /> <SchemaField.Markup type="string" x-decorator="FormItem" title="Select item preview" x-component="PreviewText.Select" x-component-props={{ mode: 'multiple', }} default={['123']} enum={[ { label: 'A111', value: '123' }, { label: 'A222', value: '222' }, ]} /> <SchemaField.Markup type="string" x-decorator="FormItem" title="date preview" x-component="PreviewText.DatePicker" /> <SchemaField.Markup type="string" x-decorator="FormItem" title="Cascader Preview" x-component="PreviewText.Cascader" default={'yuhang'} enum={[ { label: 'Hangzhou', value: 'hangzhou', children: [ { label: 'Yuhang', value: 'yuhang', }, ], }, ]} /> </SchemaField> <FormButtonGroup.FormItem> <Button onClick={() => { form.setState((state) => { state.editable = !state.editable }) }} > Switch reading mode </Button> </FormButtonGroup.FormItem> </FormProvider> </FormLayout> </PreviewText.Placeholder> ) } ``` ## API ### PreviewText.Input Reference https://ant.design/components/input-cn/ ### PreviewText.Select Reference https://ant.design/components/select-cn/ ### PreviewText.TreeSelect Reference https://ant.design/components/tree-select-cn/ ### PreviewText.Cascader Reference https://ant.design/components/cascader-cn/ ### PreviewText.DatePicker Reference https://ant.design/components/date-picker-cn/ ### PreviewText.DateRangePicker Reference https://ant.design/components/date-picker-cn/ ### PreviewText.TimePicker Reference https://ant.design/components/time-picker-cn/ ### PreviewText.TimeRangePicker Reference https://ant.design/components/time-picker-cn/ ### PreviewText.NumberPicker 参考 https://ant.design/components/input-number-cn/ ### PreviewText.Placeholder | Property name | Type | Description | Default value | | ------------- | ------ | ------------------- | ------------- | | value | stirng | Default placeholder | N/A | ### PreviewText.usePlaceholder ```ts pure interface usePlaceholder { (): string } ``` ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormCollapse.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # FormCollapse > 折叠面板,通常用在布局空间要求较高的表单场景 > > 注意:只能用在 Schema 场景 ## Markup Schema 案例 ```tsx import React from 'react' import { FormCollapse, FormLayout, FormItem, Input, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from 'antd' const SchemaField = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) const form = createForm() const formCollapse = FormCollapse.createFormCollapse() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.Void title="折叠面板" x-decorator="FormItem" x-component="FormCollapse" x-component-props={{ formCollapse, }} > <SchemaField.Void name="panel1" x-component="FormCollapse.CollapsePanel" x-component-props={{ header: 'A1' }} > <SchemaField.String name="aaa" title="AAA" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> <SchemaField.Void name="panel2" x-component="FormCollapse.CollapsePanel" x-component-props={{ header: 'A2' }} > <SchemaField.String name="bbb" title="BBB" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> <SchemaField.Void name="panel3" x-component="FormCollapse.CollapsePanel" x-component-props={{ header: 'A3' }} > <SchemaField.String name="ccc" title="CCC" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> </SchemaField.Void> </SchemaField> <FormButtonGroup.FormItem> <Button onClick={() => { form.query('panel3').take((field) => { field.visible = !field.visible }) }} > 显示/隐藏最后一个Tab </Button> <Button onClick={() => { formCollapse.toggleActiveKey('panel2') }} > 切换第二个Tab </Button> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## JSON Schema 案例 ```tsx import React from 'react' import { FormCollapse, FormItem, FormLayout, Input, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from 'antd' const SchemaField = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) const form = createForm() const formCollapse = FormCollapse.createFormCollapse() const schema = { type: 'object', properties: { collapse: { type: 'void', title: '折叠面板', 'x-decorator': 'FormItem', 'x-component': 'FormCollapse', 'x-component-props': { formCollapse: '{{formCollapse}}', }, properties: { panel1: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { header: 'A1', }, properties: { aaa: { type: 'string', title: 'AAA', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, panel2: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { header: 'A2', }, properties: { bbb: { type: 'string', title: 'BBB', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, panel3: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { header: 'A3', }, properties: { ccc: { type: 'string', title: 'CCC', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, }, }, }, } export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} scope={{ formCollapse }} /> <FormButtonGroup.FormItem> <Button onClick={() => { form.query('panel3').take((field) => { field.visible = !field.visible }) }} > 显示/隐藏最后一个Tab </Button> <Button onClick={() => { formCollapse.toggleActiveKey('panel2') }} > 切换第二个Tab </Button> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## API ### FormCollapse | 属性名 | 类型 | 描述 | 默认值 | | ------------ | ------------- | ---------------------------------------------------------- | ------ | | formCollapse | IFormCollapse | 传入通过 createFormCollapse/useFormCollapse 创建出来的模型 | | 其余参考 https://ant.design/components/collapse-cn/ ### FormCollapse.CollapsePanel 参考 https://ant.design/components/collapse-cn/ ### FormCollapse.createFormCollapse ```ts pure type ActiveKey = string | number type ActiveKeys = string | number | Array<string | number> interface createFormCollapse { (defaultActiveKeys?: ActiveKeys): IFormCollpase } interface IFormCollapse { //激活主键列表 activeKeys: ActiveKeys //是否存在该激活主键 hasActiveKey(key: ActiveKey): boolean //设置激活主键列表 setActiveKeys(keys: ActiveKeys): void //添加激活主键 addActiveKey(key: ActiveKey): void //删除激活主键 removeActiveKey(key: ActiveKey): void //开关切换激活主键 toggleActiveKey(key: ActiveKey): void } ``` ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormCollapse.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # FormCollapse > 折叠面板,通常用在布局空间要求较高的表单场景 > > 注意:只能用在 Schema 场景 ## Markup Schema 案例 ```tsx import React from 'react' import { FormCollapse, FormItem, Input, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from '@alifd/next' const SchemaField = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) const form = createForm() const formCollapse = FormCollapse.createFormCollapse() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.Void title="折叠面板" x-decorator="FormItem" x-component="FormCollapse" x-component-props={{ formCollapse, }} > <SchemaField.Void name="panel1" x-component="FormCollapse.CollapsePanel" x-component-props={{ title: 'A1' }} > <SchemaField.String name="aaa" title="AAA" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> <SchemaField.Void name="panel2" x-component="FormCollapse.CollapsePanel" x-component-props={{ title: 'A2' }} > <SchemaField.String name="bbb" title="BBB" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> <SchemaField.Void name="panel3" x-component="FormCollapse.CollapsePanel" x-component-props={{ title: 'A3' }} > <SchemaField.String name="ccc" title="CCC" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> </SchemaField.Void> </SchemaField> <FormButtonGroup.FormItem> <Button onClick={() => { form.query('panel3').take((field) => { field.visible = !field.visible }) }} > 显示/隐藏最后一个Tab </Button> <Button onClick={() => { formCollapse.toggleActiveKey('panel2') }} > 切换第二个Tab </Button> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## JSON Schema 案例 ```tsx import React from 'react' import { FormCollapse, FormItem, Input, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from '@alifd/next' const SchemaField = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) const form = createForm() const formCollapse = FormCollapse.createFormCollapse() const schema = { type: 'object', properties: { collapse: { type: 'void', title: '折叠面板', 'x-decorator': 'FormItem', 'x-component': 'FormCollapse', 'x-component-props': { formCollapse: '{{formCollapse}}', }, properties: { panel1: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { title: 'A1', }, properties: { aaa: { type: 'string', title: 'AAA', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, panel2: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { title: 'A2', }, properties: { bbb: { type: 'string', title: 'BBB', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, panel3: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { title: 'A3', }, properties: { ccc: { type: 'string', title: 'CCC', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, }, }, }, } export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} scope={{ formCollapse }} /> <FormButtonGroup.FormItem> <Button onClick={() => { form.query('panel3').take((field) => { field.visible = !field.visible }) }} > 显示/隐藏最后一个Tab </Button> <Button onClick={() => { formCollapse.toggleActiveKey('panel2') }} > 切换第二个Tab </Button> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## API ### FormCollapse | 属性名 | 类型 | 描述 | 默认值 | | ------------ | ------------- | ---------------------------------------------------------- | ------ | | formCollapse | IFormCollapse | 传入通过 createFormCollapse/useFormCollapse 创建出来的模型 | | 其余参考 https://fusion.design/pc/component/basic/collapse ### FormCollapse.CollapsePanel 参考 https://fusion.design/pc/component/basic/collapse ### FormCollapse.createFormCollapse ```ts pure type ActiveKey = string | number type ActiveKeys = string | number | Array<string | number> interface createFormCollapse { (defaultActiveKeys?: ActiveKeys): IFormCollpase } interface IFormCollapse { //激活主键列表 activeKeys: ActiveKeys //是否存在该激活主键 hasActiveKey(key: ActiveKey): boolean //设置激活主键列表 setActiveKeys(keys: ActiveKeys): void //添加激活主键 addActiveKey(key: ActiveKey): void //删除激活主键 removeActiveKey(key: ActiveKey): void //开关切换激活主键 toggleActiveKey(key: ActiveKey): void } ``` ``` -------------------------------------------------------------------------------- /docs/guide/quick-start.md: -------------------------------------------------------------------------------- ```markdown # Quick Start ## Install Dependencies ### Install the Core Library To use Formily, you must use [@formily/core](https://core.formilyjs.org), which is responsible for managing the status of the form, form verification, linkage, and so on. ```bash $ npm install --save @formily/core ``` ### Install UI Bridge Library The kernel alone is not enough. We also need a UI library to access kernel data to achieve the final form interaction effect. For users of different frameworks, we have different bridge libraries. **React users** ```bash $ npm install --save @formily/react ``` **Vue users** ```bash $ npm install --save @formily/vue ``` ### Install component library To quickly implement beautiful forms, we usually need to use industry-leading component libraries, such as [Ant Design](https://ant.design) and [Alibaba Fusion](https://fusion.design). However, these excellent component libraries are not fully covered in some scenes of the form. For example, the detailed preview state is not supported by Ant Design, and some scene-based components are not supported, so Formily is in On top of this, @formily/antd and @formily/next are encapsulated to ensure that users can use it out of the box. **Ant Design users** ```bash $ npm install --save antd moment @formily/antd ``` **Alibaba Fusion users** ```bash $ npm install --save @alifd/next moment @formily/next ``` ## Import Dependencies Use ES Module import syntax to import dependencies ```ts import React from 'react' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' import { FormItem, Input } from '@formily/antd' ``` ## Example ```tsx /** * defaultShowCode: true */ import React from 'react' import { createForm } from '@formily/core' import { FormProvider, FormConsumer, Field } from '@formily/react' import { FormItem, FormLayout, Input, FormButtonGroup, Submit, } from '@formily/antd' const form = createForm() export default () => { return ( <FormProvider form={form}> <FormLayout layout="vertical"> <Field name="input" title="Input box" required initialValue="Hello world" decorator={[FormItem]} component={[Input]} /> </FormLayout> <FormConsumer> {() => ( <div style={{ marginBottom: 20, padding: 5, border: '1px dashed #666', }} > Real-time response:{form.values.input} </div> )} </FormConsumer> <FormButtonGroup> <Submit onSubmit={console.log}>submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` From the above examples, we can learn a lot: - [createForm](https://core.formilyjs.org/api/entry/create-form) is used to create the core domain model of the form, which is the standard ViewModel as the [MVVM](https://core.formilyjs.org/guide/mvvm) design pattern. - The [FormProvider](https://react.formilyjs.org/api/components/form-provider) component is used as the entrance to the view layer bridge form model. It has only one parameter, which is to receive the Form instance created by createForm and pass the Form instance to the child component in the form of context. - The [FormLayout](https://antd.formilyjs.org/components/form-layout) component is a component used to control the style of [FormItem](https://antd.formilyjs.org/components/form-item) in batches. Here we specify the layout as top and bottom layout, that is, the label is on the top and the component is on the bottom. - The [Field](https://react.formilyjs.org/api/components/field) component is a component used to undertake common fields. - The name attribute identifies the path of the field in the final submitted data of the form. - Title attribute, which identifies the title of the field - If the decorator is specified as FormItem, then the title attribute will be received as the label by default in the FormItem component. - If specified as a custom component, the consumer of the title will be taken over by the custom component. - If decorator is not specified, then the title will not be displayed on the UI. - Required attribute, a shorthand for required verification, which identifies that the field is required - If the decorator is specified as FormItem, then an asterisk prompt will automatically appear, and there will be corresponding status feedback if the verification fails. These are the default processing done inside the FormItem. - If the decorator is specified as a custom component, the corresponding UI style needs to be implemented by the custom component implementer. - If decorator is not specified, then required will just block submission, and there will be no UI feedback for verification failure. - InitialValue property, which represents the default value of the field - Decorator attribute, representing the UI decorator of the field, usually we will specify it as FormItem - Note that the decorator attribute is passed in the form of an array, the first parameter represents the specified component type, and the second parameter represents the specified component attribute. - The component attribute, which represents the input control of the field, can be Input or Select, etc. - Note that the component property is passed in the form of an array, the first parameter represents the specified component type, and the second parameter represents the specified component property. - The [FormConsumer](https://react.formilyjs.org/api/components/form-consumer) component exists as a responder of a responsive model. Its core is a render props mode. In the callback function as children, all dependencies are automatically collected. If the dependencies change, it will be re-rendered. With the help of FormConsumer, we can Conveniently realize the needs of various calculations and summaries. - The [FormButtonGroup](https://antd.formilyjs.org/components/form-button-group) component exists as a form button group container and is mainly responsible for the layout of the buttons. - The [Submit](https://antd.formilyjs.org/components/submit) component exists as an action trigger for form submission. In fact, we can also directly use the form.submit method to submit. But the advantage of using Submit is that there is no need to write the onClick event handler on the Button component every time, and it also handles the loading state of the Form. If the onSubmit method returns a Promise and the Promise is pending, the button will automatically enter the loading state. ``` -------------------------------------------------------------------------------- /packages/shared/src/array.ts: -------------------------------------------------------------------------------- ```typescript import { isArr, isObj, isStr } from './checkers' type EachArrayIterator<T> = (currentValue: T, key: number) => void | boolean type EachStringIterator = (currentValue: string, key: number) => void | boolean type EachObjectIterator<T = any> = ( currentValue: T, key: string ) => void | boolean type MapArrayIterator<TItem, TResult> = ( currentValue: TItem, key: number ) => TResult type MapStringIterator<TResult> = (currentValue: string, key: number) => TResult type MapObjectIterator<TItem, TResult> = ( currentValue: TItem, key: string ) => TResult type MemoArrayIterator<T, U> = ( previousValue: U, currentValue: T, key: number ) => U type MemoStringIterator<T> = ( previousValue: T, currentValue: string, key: number ) => T type MemoObjectIterator<TValue, TResult> = ( previousValue: TResult, currentValue: TValue, key: string ) => TResult export const toArr = (val: any): any[] => (isArr(val) ? val : val ? [val] : []) export function each( val: string, iterator: EachStringIterator, revert?: boolean ): void export function each<T>( val: T[], iterator: EachArrayIterator<T>, revert?: boolean ): void export function each<T extends {}, TValue extends T[keyof T]>( val: T, iterator: EachObjectIterator<TValue>, revert?: boolean ): void export function each(val: any, iterator: any, revert?: boolean): void { if (isArr(val) || isStr(val)) { if (revert) { for (let i: number = val.length - 1; i >= 0; i--) { if (iterator(val[i], i) === false) { return } } } else { for (let i = 0; i < val.length; i++) { if (iterator(val[i], i) === false) { return } } } } else if (isObj(val)) { let key: string for (key in val) { if (Object.hasOwnProperty.call(val, key)) { if (iterator(val[key], key) === false) { return } } } } } export function map<T>( val: string, iterator: MapStringIterator<T>, revert?: boolean ): T[] export function map<TItem, TResult>( val: TItem[], iterator: MapArrayIterator<TItem, TResult>, revert?: boolean ): TResult[] export function map<T extends {}, TResult>( val: T, iterator: MapObjectIterator<T[keyof T], TResult>, revert?: boolean ): Record<keyof T, TResult> export function map(val: any, iterator: any, revert?: any): any { const res = isArr(val) || isStr(val) ? [] : {} each( val, (item, key) => { const value = iterator(item, key) if (isArr(res)) { ;(res as any).push(value) } else { res[key] = value } }, revert ) return res } export function reduce<T, U>( val: T[], iterator: MemoArrayIterator<T, U>, accumulator?: U, revert?: boolean ): U export function reduce<T>( val: string, iterator: MemoStringIterator<T>, accumulator?: T, revert?: boolean ): T export function reduce<T extends {}, TValue extends T[keyof T], TResult = any>( val: T, iterator: MemoObjectIterator<TValue, TResult>, accumulator?: TResult, revert?: boolean ): TResult export function reduce( val: any, iterator: any, accumulator?: any, revert?: boolean ): any { let result = accumulator each( val, (item, key) => { result = iterator(result, item, key) }, revert ) return result } export function every<T extends string>( val: T, iterator: EachStringIterator, revert?: boolean ): boolean export function every<T>( val: T[], iterator: EachArrayIterator<T>, revert?: boolean ): boolean export function every<T extends {}>( val: T, iterator: EachObjectIterator, revert?: boolean ): boolean export function every(val: any, iterator: any, revert?: boolean): boolean { let res = true each( val, (item, key) => { if (!iterator(item, key)) { res = false return false } }, revert ) return res } export function some<T extends string>( val: T, iterator: EachStringIterator, revert?: boolean ): boolean export function some<T>( val: T[], iterator: EachArrayIterator<T>, revert?: boolean ): boolean export function some<T extends {}>( val: T, iterator: EachObjectIterator, revert?: boolean ): boolean export function some(val: any, iterator: any, revert?: boolean): boolean { let res = false each( val, (item, key) => { if (iterator(item, key)) { res = true return false } }, revert ) return res } export function findIndex<T extends string>( val: T, iterator: EachStringIterator, revert?: boolean ): number export function findIndex<T>( val: T[], iterator: EachArrayIterator<T>, revert?: boolean ): number export function findIndex<T extends {}>( val: T, iterator: EachObjectIterator, revert?: boolean ): keyof T export function findIndex( val: any, iterator: any, revert?: boolean ): string | number { let res: number | string = -1 each( val, (item, key) => { if (iterator(item, key)) { res = key return false } }, revert ) return res } export function find<T extends string>( val: T, iterator: EachStringIterator, revert?: boolean ): any export function find<T>( val: T[], iterator: EachArrayIterator<T>, revert?: boolean ): T export function find<T extends {}>( val: T, iterator: EachObjectIterator, revert?: boolean ): T[keyof T] export function find(val: any, iterator: any, revert?: boolean): any { let res: any each( val, (item, key) => { if (iterator(item, key)) { res = item return false } }, revert ) return res } export function includes<T extends string>( val: T, searchElement: string, revert?: boolean ): boolean export function includes<T>( val: T[], searchElement: T, revert?: boolean ): boolean export function includes(val: any, searchElement: any, revert?: boolean) { if (isStr(val)) return val.includes(searchElement) return some(val, (item) => item === searchElement, revert) } export function move<T extends any>( array: T[], fromIndex: number, toIndex: number ) { if (fromIndex === toIndex) return array if ( toIndex < 0 || fromIndex < 0 || toIndex > array.length - 1 || fromIndex > array.length - 1 ) { return array } if (fromIndex < toIndex) { const fromItem = array[fromIndex] for (let index = fromIndex; index < toIndex; index++) { array[index] = array[index + 1] } array[toIndex] = fromItem } else { const fromItem = array[fromIndex] for (let index = fromIndex; index > toIndex; index--) { array[index] = array[index - 1] } array[toIndex] = fromItem } return array } ``` -------------------------------------------------------------------------------- /devtools/chrome-extension/src/app/components/FieldTree.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useState, useEffect, useRef } from 'react' import styled from 'styled-components' import { FormPath, isObj } from '@formily/shared' import { Treebeard, decorators } from 'react-treebeard' import * as filters from './filter' import SearchBox from './SearchBox' const createTree = (dataSource: any, cursor?: any) => { const tree: any = {} const getParentPath = (key: string) => { let parentPath: FormPath = FormPath.parse(key) let i = 0 while (true) { parentPath = parentPath.parent() if (dataSource[parentPath.toString()]) { return parentPath } if (i > parentPath.segments.length) return parentPath i++ } } const findParent = (key: string): any => { const parentPath = getParentPath(key) const _findParent = (node: any) => { if (FormPath.parse(node.path).match(parentPath)) { return node } else { for (let i = 0; i < node?.children?.length; i++) { const parent = _findParent(node.children[i]) if (parent) { return parent } } } } return _findParent(tree) } Object.keys(dataSource || {}).forEach((key) => { if (key == '') { tree.name = 'Form' tree.path = key tree.toggled = true tree.data = dataSource[key] if (cursor && cursor.current && cursor.current.path === key) { tree.active = true cursor.current = tree } } else { const node: any = { name: key, path: key, toggled: true, data: dataSource[key], } if (cursor && cursor.current && cursor.current.path === key) { node.active = true cursor.current = node } const parent = findParent(key) if (parent) { node.name = (node.path || '').slice( parent && parent.path ? parent.path.length + 1 : 0 ) parent.children = parent.children || [] parent.children.push(node) } } }) return tree } const theme = { tree: { base: { listStyle: 'none', margin: 0, padding: 0, color: '#9DA5AB', fontFamily: 'lucida grande ,tahoma,verdana,arial,sans-serif', fontSize: '8px', background: 'none', marginBottom: '50px', }, node: { base: { position: 'relative', background: 'none', }, link: { cursor: 'pointer', position: 'relative', padding: '0px 5px', display: 'block', }, activeLink: { background: '#3D424A', }, toggle: { base: { position: 'relative', display: 'inline-block', verticalAlign: 'top', marginLeft: '-5px', height: '22px', width: '20px', zIndex: 2, }, wrapper: { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)', width: 4, height: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', }, height: 6, width: 4, arrow: { fill: '#9DA5AB', strokeWidth: 0, }, }, header: { base: { display: 'inline-block', verticalAlign: 'top', color: '#9DA5AB', }, connector: { width: '2px', height: '12px', borderLeft: 'solid 2px black', borderBottom: 'solid 2px black', position: 'absolute', top: '0px', left: '-21px', }, title: { lineHeight: '24px', verticalAlign: 'middle', }, }, subtree: { listStyle: 'none', paddingLeft: '19px', }, loading: { color: '#E2C089', }, }, }, } const Header = (props) => { const { node, style, customStyles } = props const title = node.data?.title ? node.data.title : '' return ( <div className="node-header" style={style.base} onClick={() => { node.toggled = false }} > <div style={ node.selected ? { ...style.title, ...customStyles.header.title } : style.title } > <span style={{ zIndex: 1, position: 'relative', fontSize: 12, }} > {node.name} </span> <span style={{ zIndex: 1, position: 'absolute', right: 12 }}> {isObj(title) ? ((title as any).title ?? '') : title} </span> <div className={`highlight ${node.active ? 'active' : ''}`} style={{ transition: '.15s all ease-in' }} ></div> </div> </div> ) } const ToolBar = styled.div` border-bottom: 1px solid #3d424a; height: 20px; padding: 10px 10px; padding: 5px; overflow: auto; position: sticky; top: 0; background: #282c34; z-index: 100; ` export const FieldTree = styled(({ className, dataSource, onSelect }) => { const allDataRef = useRef(createTree(dataSource)) const cursor = useRef(allDataRef.current) const [keyword, setKeyword] = useState('') const searchTimer = useRef(null) const [data, setData] = useState(allDataRef.current) const filterData = () => { if (!keyword) return data const finded = filters.filterTree(data, keyword) return filters.expandFilteredNodes(finded, keyword) } const onToggle = (node: any, toggled: boolean) => { cursor.current.active = false node.active = true if (node.children && node.children.length) { node.toggled = toggled } cursor.current = node setData(data) if (onSelect) { onSelect(node) } } const onSearch = ({ target: { value } }) => { clearTimeout(searchTimer.current) searchTimer.current = setTimeout(() => { setKeyword(value.trim()) }, 100) } useEffect(() => { allDataRef.current = createTree(dataSource, cursor) setData(allDataRef.current) }, [dataSource]) return ( <div className={className}> <ToolBar> <SearchBox onSearch={onSearch} /> </ToolBar> <Treebeard data={filterData()} onToggle={onToggle} decorators={{ ...decorators, Header, }} style={theme} /> </div> ) })` position: relative; overflow: auto; height: calc(100% - 40px); user-select: none; .highlight { position: absolute; top: 0; right: 0; left: -100%; height: 100%; z-index: 0; &.active { background: #3d424a; } } .node-header:hover .highlight { background: #3d424a; } ` ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Editable.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # Editable > 局部编辑器,对于一些空间要求较高的表单区域可以使用该组件 > > Editable 组件相当于是 FormItem 组件的变体,所以通常放在 decorator 中 ## Markup Schema 案例 ```tsx import React from 'react' import { Input, DatePicker, Editable, FormItem, FormButtonGroup, Submit, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { DatePicker, Editable, Input, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.String name="date" title="日期" x-decorator="Editable" x-component="DatePicker" /> <SchemaField.String name="input" title="输入框" x-decorator="Editable" x-component="Input" /> <SchemaField.Void name="void" title="虚拟节点容器" x-component="Editable.Popover" x-reactions={(field) => { field.title = field.query('.void.date2').get('value') || field.title }} > <SchemaField.String name="date2" title="日期" x-decorator="FormItem" x-component="DatePicker" x-component-props={{ followTrigger: true, }} /> <SchemaField.String name="input2" title="输入框" x-decorator="FormItem" x-component="Input" /> </SchemaField.Void> <SchemaField.Object name="iobject" title="对象节点容器" x-component="Editable.Popover" x-reactions={(field) => { field.title = field.value?.date || field.title }} > <SchemaField.String name="date" title="日期" x-decorator="FormItem" x-component="DatePicker" x-component-props={{ followTrigger: true, }} /> <SchemaField.String name="input" title="输入框" x-decorator="FormItem" x-component="Input" /> </SchemaField.Object> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Input, DatePicker, Editable, FormItem, FormButtonGroup, Submit, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { DatePicker, Editable, Input, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { date: { type: 'string', title: '日期', 'x-decorator': 'Editable', 'x-component': 'DatePicker', }, input: { type: 'string', title: '输入框', 'x-decorator': 'Editable', 'x-component': 'Input', }, void: { type: 'void', title: '虚拟节点容器', 'x-component': 'Editable.Popover', 'x-reactions': "{{(field) => field.title = field.query('.void.date2').get('value') || field.title}}", properties: { date2: { type: 'string', title: '日期', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', 'x-component-props': { followTrigger: true, }, }, input2: { type: 'string', title: '输入框', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, iobject: { type: 'object', title: '对象节点容器', 'x-component': 'Editable.Popover', 'x-reactions': '{{(field) => field.title = field.value && field.value.date || field.title}}', properties: { date: { type: 'string', title: '日期', 'x-decorator': 'FormItem', 'x-component': 'DatePicker', 'x-component-props': { followTrigger: true, }, }, input: { type: 'string', title: '输入框', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Input, DatePicker, Editable, FormItem, FormButtonGroup, Submit, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field, VoidField, ObjectField } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="date" title="日期" decorator={[Editable]} component={[DatePicker]} /> <Field name="input" title="输入框" decorator={[Editable]} component={[Input]} /> <VoidField name="void" title="虚拟节点容器" reactions={(field) => { field.title = field.query('.void.date2').get('value') || field.title }} component={[Editable.Popover]} > <Field name="date2" title="日期" decorator={[FormItem]} component={[ DatePicker, { followTrigger: true, }, ]} /> <Field name="input2" title="输入框" decorator={[FormItem]} component={[Input]} /> </VoidField> <ObjectField name="iobject" title="对象节点容器" component={[ Editable.Popover, { renderPreview: (field) => { return field.value?.date }, }, ]} > <Field name="date" title="日期" decorator={[FormItem]} component={[ DatePicker, { followTrigger: true, }, ]} /> <Field name="input" title="输入框" decorator={[FormItem]} component={[Input]} /> </ObjectField> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup> </FormProvider> ) ``` ## API ### Editable > 内联编辑 参考 https://fusion.design/pc/component/basic/form 中的 FormItem 属性 ### Editable.Popover > 浮层编辑 | 属性名 | 类型 | 描述 | 默认值 | | ------------- | --------------------------------- | ---------- | ------ | | renderPreview | `(field:GeneralField)=>ReactNode` | 预览渲染器 | | 注意:如果在 Popover 内部有 Select/DatePicker 之类的浮层组件,需要在浮层组件上配置 followTrigger=true 其余参考 https://fusion.design/pc/component/basic/balloon ``` -------------------------------------------------------------------------------- /packages/shared/src/merge.ts: -------------------------------------------------------------------------------- ```typescript import { isFn, isPlainObj } from './checkers' import { isEmpty, isValid } from './isEmpty' function defaultIsMergeableObject(value: any) { return isNonNullObject(value) && !isSpecial(value) } function isNonNullObject(value: any) { // TODO: value !== null && typeof value === 'object' return Boolean(value) && typeof value === 'object' } function isSpecial(value: any) { // TODO: use isComplexObject() if ('$$typeof' in value && '_owner' in value) { return true } if (value._isAMomentObject) { return true } if (value._isJSONSchemaObject) { return true } if (isFn(value.toJS)) { return true } if (isFn(value.toJSON)) { return true } return !isPlainObj(value) } function emptyTarget(val: any) { return Array.isArray(val) ? [] : {} } // @ts-ignore function cloneUnlessOtherwiseSpecified(value: any, options: Options) { if (options.clone !== false && options.isMergeableObject?.(value)) { return deepmerge(emptyTarget(value), value, options) } return value } function defaultArrayMerge(target: any, source: any, options: Options) { return target.concat(source).map(function (element: any) { return cloneUnlessOtherwiseSpecified(element, options) }) } function getMergeFunction(key: string, options: Options) { if (!options.customMerge) { return deepmerge } const customMerge = options.customMerge(key) return typeof customMerge === 'function' ? customMerge : deepmerge } function getEnumerableOwnPropertySymbols(target: any): any { return Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(target).filter(function (symbol) { return target.propertyIsEnumerable(symbol) }) : [] } function getKeys(target: any) { if (!isValid(target)) return [] return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target)) } function propertyIsOnObject(object: any, property: any) { /* istanbul ignore next */ try { return property in object } catch (_) { return false } } // Protects from prototype poisoning and unexpected merging up the prototype chain. function propertyIsUnsafe(target: any, key: PropertyKey) { return ( propertyIsOnObject(target, key) && // Properties are safe to merge if they don't exist in the target yet, !( Object.hasOwnProperty.call(target, key) && // unsafe if they exist up the prototype chain, Object.propertyIsEnumerable.call(target, key) ) ) // and also unsafe if they're nonenumerable. } function mergeObject(target: any, source: any, options: Options) { const destination = options.assign ? target || {} : {} if (!options.isMergeableObject(target)) return target if (!options.assign) { getKeys(target).forEach(function (key) { destination[key] = cloneUnlessOtherwiseSpecified(target[key], options) }) } getKeys(source).forEach(function (key) { /* istanbul ignore next */ if (propertyIsUnsafe(target, key)) { return } if (isEmpty(target[key])) { destination[key] = source[key] } else if ( propertyIsOnObject(target, key) && // @ts-ignore options.isMergeableObject(source[key]) ) { destination[key] = getMergeFunction(key, options)( target[key], source[key], options ) } else { destination[key] = cloneUnlessOtherwiseSpecified(source[key], options) } }) return destination } interface Options { arrayMerge?(target: any[], source: any[], options?: Options): any[] clone?: boolean assign?: boolean customMerge?: ( key: string, options?: Options ) => ((x: any, y: any) => any) | undefined isMergeableObject?(value: object): boolean cloneUnlessOtherwiseSpecified?: (value: any, options: Options) => any } // @ts-ignore function deepmerge(target: any, source: any, options?: Options) { options = options || {} options.arrayMerge = options.arrayMerge || defaultArrayMerge options.isMergeableObject = options.isMergeableObject || defaultIsMergeableObject // cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge() // implementations can use it. The caller may not replace it. options.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified const sourceIsArray = Array.isArray(source) const targetIsArray = Array.isArray(target) const sourceAndTargetTypesMatch = sourceIsArray === targetIsArray if (!sourceAndTargetTypesMatch) { return cloneUnlessOtherwiseSpecified(source, options) } else if (sourceIsArray) { return options.arrayMerge(target, source, options) } else { return mergeObject(target, source, options) } } export const lazyMerge = <T extends object | Function>( target: T, ...args: T[] ): any => { const _lazyMerge = <T extends object | Function>( target: T, source: T ): {} => { if (!isValid(source)) return target if (!isValid(target)) return source const isTargetObject = typeof target === 'object' const isSourceObject = typeof source === 'object' const isTargetFn = typeof target === 'function' const isSourceFn = typeof source === 'function' if (!isTargetObject && !isTargetFn) return source if (!isSourceObject && !isSourceFn) return target const getTarget = () => (isTargetFn ? target() : target) const getSource = () => (isSourceFn ? source() : source) const set = (_: object, key: PropertyKey, value: any) => { const source = getSource() const target = getTarget() if (key in source) { // @ts-ignore source[key] = value } else if (key in target) { // @ts-ignore target[key] = value } else { source[key] = value } return true } const get = (_: object, key: PropertyKey) => { const source = getSource() // @ts-ignore if (key in source) { return source[key] } // @ts-ignore return getTarget()[key] } const ownKeys = () => { const source = getSource() const target = getTarget() const keys = Object.keys(target) for (const key in source) { if (!(key in target)) { keys.push(key) } } return keys } const getOwnPropertyDescriptor = (_: object, key: PropertyKey) => ({ value: get(_, key), enumerable: true, configurable: true, }) const has = (_: object, key: PropertyKey) => { if (key in getSource() || key in getTarget()) return true return false } const getPrototypeOf = () => Object.getPrototypeOf({}) return new Proxy(Object.create(null), { set, get, ownKeys, getPrototypeOf, getOwnPropertyDescriptor, has, }) as any } return args.reduce<{}>((buf, arg) => _lazyMerge(buf, arg), target) } export const merge = deepmerge ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormCollapse.md: -------------------------------------------------------------------------------- ```markdown # FormCollapse > Folding panel, usually used in form scenes with high layout space requirements > > Note: Can only be used in Schema scenarios ## Markup Schema example ```tsx import React from 'react' import { FormCollapse, FormLayout, FormItem, Input, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from 'antd' const SchemaField = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) const form = createForm() const formCollapse = FormCollapse.createFormCollapse() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.Void title="Folding Panel" x-decorator="FormItem" x-component="FormCollapse" x-component-props={{ formCollapse, }} > <SchemaField.Void name="panel1" x-component="FormCollapse.CollapsePanel" x-component-props={{ header: 'A1' }} > <SchemaField.String name="aaa" title="AAA" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> <SchemaField.Void name="panel2" x-component="FormCollapse.CollapsePanel" x-component-props={{ header: 'A2' }} > <SchemaField.String name="bbb" title="BBB" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> <SchemaField.Void name="panel3" x-component="FormCollapse.CollapsePanel" x-component-props={{ header: 'A3' }} > <SchemaField.String name="ccc" title="CCC" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> </SchemaField.Void> </SchemaField> <FormButtonGroup.FormItem> <Button onClick={() => { form.query('panel3').take((field) => { field.visible = !field.visible }) }} > Show/hide the last tab </Button> <Button onClick={() => { formCollapse.toggleActiveKey('panel2') }} > Switch to the second Tab </Button> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## JSON Schema case ```tsx import React from 'react' import { FormCollapse, FormItem, FormLayout, Input, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from 'antd' const SchemaField = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) const form = createForm() const formCollapse = FormCollapse.createFormCollapse() const schema = { type: 'object', properties: { collapse: { type: 'void', title: 'Folding Panel', 'x-decorator': 'FormItem', 'x-component': 'FormCollapse', 'x-component-props': { formCollapse: '{{formCollapse}}', }, properties: { panel1: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { header: 'A1', }, properties: { aaa: { type: 'string', title: 'AAA', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, panel2: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { header: 'A2', }, properties: { bbb: { type: 'string', title: 'BBB', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, panel3: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { header: 'A3', }, properties: { ccc: { type: 'string', title: 'CCC', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, }, }, }, } export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} scope={{ formCollapse }} /> <FormButtonGroup.FormItem> <Button onClick={() => { form.query('panel3').take((field) => { field.visible = !field.visible }) }} > Show/hide the last tab </Button> <Button onClick={() => { formCollapse.toggleActiveKey('panel2') }} > Switch to the second Tab </Button> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## API ### FormCollapse | Property name | Type | Description | Default value | | ------------- | ------------- | --------------------------------------------------------------- | ------------- | | formCollapse | IFormCollapse | Pass in the model created by createFormCollapse/useFormCollapse | | Other references https://ant.design/components/collapse-cn/ ### FormCollapse.CollapsePanel Reference https://ant.design/components/collapse-cn/ ### FormCollapse.createFormCollapse ```ts pure type ActiveKey = string | number type ActiveKeys = string | number | Array<string | number> interface createFormCollapse { (defaultActiveKeys?: ActiveKeys): IFormCollpase } interface IFormCollapse { //Activate the primary key list activeKeys: ActiveKeys //Does the activation key exist? hasActiveKey(key: ActiveKey): boolean //Set the list of active primary keys setActiveKeys(keys: ActiveKeys): void //Add activation key addActiveKey(key: ActiveKey): void //Delete the active primary key removeActiveKey(key: ActiveKey): void //Switch to activate the main key toggleActiveKey(key: ActiveKey): void } ``` ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormCollapse.md: -------------------------------------------------------------------------------- ```markdown # FormCollapse > Folding panel, usually used in form scenes with high layout space requirements > > Note: Can only be used in Schema scenarios ## Markup Schema example ```tsx import React from 'react' import { FormCollapse, FormItem, Input, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from '@alifd/next' const SchemaField = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) const form = createForm() const formCollapse = FormCollapse.createFormCollapse() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.Void title="Folding Panel" x-decorator="FormItem" x-component="FormCollapse" x-component-props={{ formCollapse, }} > <SchemaField.Void name="panel1" x-component="FormCollapse.CollapsePanel" x-component-props={{ title: 'A1' }} > <SchemaField.String name="aaa" title="AAA" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> <SchemaField.Void name="panel2" x-component="FormCollapse.CollapsePanel" x-component-props={{ title: 'A2' }} > <SchemaField.String name="bbb" title="BBB" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> <SchemaField.Void name="panel3" x-component="FormCollapse.CollapsePanel" x-component-props={{ title: 'A3' }} > <SchemaField.String name="ccc" title="CCC" x-decorator="FormItem" required x-component="Input" /> </SchemaField.Void> </SchemaField.Void> </SchemaField> <FormButtonGroup.FormItem> <Button onClick={() => { form.query('panel3').take((field) => { field.visible = !field.visible }) }} > Show/hide the last tab </Button> <Button onClick={() => { formCollapse.toggleActiveKey('panel2') }} > Switch to the second Tab </Button> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## JSON Schema case ```tsx import React from 'react' import { FormCollapse, FormItem, Input, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' import { Button } from '@alifd/next' const SchemaField = createSchemaField({ components: { FormItem, FormCollapse, Input, }, }) const form = createForm() const formCollapse = FormCollapse.createFormCollapse() const schema = { type: 'object', properties: { collapse: { type: 'void', title: 'Folding Panel', 'x-decorator': 'FormItem', 'x-component': 'FormCollapse', 'x-component-props': { formCollapse: '{{formCollapse}}', }, properties: { panel1: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { title: 'A1', }, properties: { aaa: { type: 'string', title: 'AAA', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, panel2: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { title: 'A2', }, properties: { bbb: { type: 'string', title: 'BBB', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, panel3: { type: 'void', 'x-component': 'FormCollapse.CollapsePanel', 'x-component-props': { title: 'A3', }, properties: { ccc: { type: 'string', title: 'CCC', 'x-decorator': 'FormItem', required: true, 'x-component': 'Input', }, }, }, }, }, }, } export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField schema={schema} scope={{ formCollapse }} /> <FormButtonGroup.FormItem> <Button onClick={() => { form.query('panel3').take((field) => { field.visible = !field.visible }) }} > Show/hide the last tab </Button> <Button onClick={() => { formCollapse.toggleActiveKey('panel2') }} > Switch to the second Tab </Button> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## API ### FormCollapse | Property name | Type | Description | Default value | | ------------- | ------------- | --------------------------------------------------------------- | ------------- | | formCollapse | IFormCollapse | Pass in the model created by createFormCollapse/useFormCollapse | | Other references https://fusion.design/pc/component/basic/collapse ### FormCollapse.CollapsePanel Reference https://fusion.design/pc/component/basic/collapse ### FormCollapse.createFormCollapse ```ts pure type ActiveKey = string | number type ActiveKeys = string | number | Array<string | number> interface createFormCollapse { (defaultActiveKeys?: ActiveKeys): IFormCollpase } interface IFormCollapse { //Activate the primary key list activeKeys: ActiveKeys //Does the activation key exist? hasActiveKey(key: ActiveKey): boolean //Set the list of active primary keys setActiveKeys(keys: ActiveKeys): void //Add activation key addActiveKey(key: ActiveKey): void //Delete the active primary key removeActiveKey(key: ActiveKey): void //Switch to activate the main key toggleActiveKey(key: ActiveKey): void } ``` ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormLayout.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # FormLayout > 区块级布局批量控制组件,借助该组件,我们可以轻松的控制被 FormLayout 圈住的所有 FormItem 组件的布局模式 ## Markup Schema 案例 ```tsx import React from 'react' import { Input, Select, FormItem, FormLayout } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, Select, FormItem, FormLayout, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Void x-component="FormLayout" x-component-props={{ labelCol: 6, wrapperCol: 10, }} > <SchemaField.String name="input" title="输入框" x-decorator="FormItem" x-decorator-props={{ tooltip: <div>123</div>, }} x-component="Input" required /> <SchemaField.String name="select" title="选择框" x-decorator="FormItem" x-component="Select" required /> </SchemaField.Void> </SchemaField> </FormProvider> ) ``` ## JSON Schema 案例 ```tsx import React from 'react' import { Input, Select, FormItem, FormLayout } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Input, Select, FormItem, FormLayout, }, }) const schema = { type: 'object', properties: { layout: { type: 'void', 'x-component': 'FormLayout', 'x-component-props': { labelCol: 6, wrapperCol: 10, layout: 'vertical', }, properties: { input: { type: 'string', title: '输入框', required: true, 'x-decorator': 'FormItem', 'x-decorator-props': { tooltip: <div>123</div>, }, 'x-component': 'Input', }, select: { type: 'string', title: '选择框', required: true, 'x-decorator': 'FormItem', 'x-component': 'Select', }, }, }, }, } const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> </FormProvider> ) ``` ## 纯 JSX 案例 ```tsx import React from 'react' import { Input, Select, FormItem, FormButtonGroup, Submit, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <FormLayout breakpoints={[680]} layout={['vertical', 'horizontal']} labelAlign={['left', 'right']} labelCol={[24, 6]} wrapperCol={[24, 10]} > <Field name="input" required title="输入框" decorator={[FormItem]} component={[Input]} /> <Field name="select" required title="选择框" decorator={[FormItem]} component={[Select]} /> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) ``` ## API | 属性名 | 类型 | 描述 | 默认值 | | -------------- | ------------------------------------------------------------------------------------- | ----------------------- | ---------- | | style | CSSProperties | 样式 | - | | className | string | 类名 | - | | colon | boolean | 是否有冒号 | true | | labelAlign | `'right' \| 'left' \| ('right' \| 'left')[]` | 标签内容对齐 | - | | wrapperAlign | `'right' \| 'left' \| ('right' \| 'left')[]` | 组件容器内容对齐 | - | | labelWrap | boolean | 标签内容换行 | false | | labelWidth | number | 标签宽度(px) | - | | wrapperWidth | number | 组件容器宽度(px) | - | | wrapperWrap | boolean | 组件容器换行 | false | | labelCol | `number \| number[]` | 标签宽度(24 column) | - | | wrapperCol | `number \| number[]` | 组件容器宽度(24 column) | - | | fullness | boolean | 组件容器宽度 100% | false | | size | `'small' \| 'default' \| 'large'` | 组件尺寸 | default | | layout | `'vertical' \| 'horizontal' \| 'inline' \|('vertical' \| 'horizontal' \| 'inline')[]` | 布局模式 | horizontal | | direction | `'rtl' \| 'ltr'` | 方向(暂不支持) | ltr | | inset | boolean | 内联布局 | false | | shallow | boolean | 上下文浅层传递 | true | | feedbackLayout | `'loose' \| 'terse' \| 'popover' \| 'none'` | 反馈布局 | true | | tooltipLayout | `"icon" \| "text"` | 问号提示布局 | `"icon"` | | tooltipIcon | ReactNode | 问号提示图标 | - | | bordered | boolean | 是否有边框 | true | | breakpoints | number[] | 容器尺寸断点 | - | | gridColumnGap | number | 网格布局列间距 | 8 | | gridRowGap | number | 网格布局行间距 | 4 | | spaceGap | number | 弹性间距 | 8 | ``` -------------------------------------------------------------------------------- /packages/element/src/array-cards/index.ts: -------------------------------------------------------------------------------- ```typescript import { ArrayField } from '@formily/core' import { ISchema } from '@formily/json-schema' import { observer } from '@formily/reactive-vue' import { h, RecursionField, useField, useFieldSchema } from '@formily/vue' import type { Card as CardProps } from 'element-ui' import { Card, Empty, Row } from 'element-ui' import { defineComponent } from 'vue-demi' import { ArrayBase } from '../array-base' import { stylePrefix } from '../__builtins__/configs' import { composeExport } from '../__builtins__/shared' const isAdditionComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Addition') > -1 } const isIndexComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Index') > -1 } const isRemoveComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Remove') > -1 } const isMoveUpComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveUp') > -1 } const isMoveDownComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveDown') > -1 } const isOperationComponent = (schema: ISchema) => { return ( isAdditionComponent(schema) || isRemoveComponent(schema) || isMoveDownComponent(schema) || isMoveUpComponent(schema) ) } const ArrayCardsInner = observer( defineComponent<CardProps>({ name: 'FArrayCards', props: [], setup(props, { attrs }) { const fieldRef = useField<ArrayField>() const schemaRef = useFieldSchema() const prefixCls = `${stylePrefix}-array-cards` const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value) return () => { const field = fieldRef.value const schema = schemaRef.value const dataSource = Array.isArray(field.value) ? field.value : [] if (!schema) throw new Error('can not found schema object') const renderItems = () => { return dataSource?.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items const title = h( 'span', {}, { default: () => [ h( RecursionField, { props: { schema: items, name: index, filterProperties: (schema) => { if (!isIndexComponent(schema)) return false return true }, onlyRenderProperties: true, }, }, {} ), attrs.title || field.title, ], } ) const extra = h( 'span', {}, { default: () => [ h( RecursionField, { props: { schema: items, name: index, filterProperties: (schema) => { if (!isOperationComponent(schema)) return false return true }, onlyRenderProperties: true, }, }, {} ), attrs.extra, ], } ) const content = h( RecursionField, { props: { schema: items, name: index, filterProperties: (schema) => { if (isIndexComponent(schema)) return false if (isOperationComponent(schema)) return false return true }, }, }, {} ) return h( ArrayBase.Item, { key: getKey(item, index), props: { index, record: item, }, }, { default: () => h( Card, { class: [`${prefixCls}-item`], attrs: { shadow: 'never', ...attrs, }, }, { default: () => [content], header: () => h( Row, { props: { type: 'flex', justify: 'space-between', }, }, { default: () => [title, extra], } ), } ), } ) }) } const renderAddition = () => { return schema.reduceProperties((addition, schema) => { if (isAdditionComponent(schema)) { return h( RecursionField, { props: { schema, name: 'addition', }, }, {} ) } return addition }, null) } const renderEmpty = () => { if (dataSource?.length) return return h( Card, { class: [`${prefixCls}-item`], attrs: { shadow: 'never', ...attrs, header: attrs.title || field.title, }, }, { default: () => h( Empty, { props: { description: 'No Data', imageSize: 100 } }, {} ), } ) } return h( 'div', { class: [prefixCls], }, { default: () => h( ArrayBase, { props: { keyMap, }, }, { default: () => [ renderEmpty(), renderItems(), renderAddition(), ], } ), } ) } }, }) ) export const ArrayCards = composeExport(ArrayCardsInner, { Index: ArrayBase.Index, SortHandle: ArrayBase.SortHandle, Addition: ArrayBase.Addition, Remove: ArrayBase.Remove, MoveDown: ArrayBase.MoveDown, MoveUp: ArrayBase.MoveUp, useArray: ArrayBase.useArray, useIndex: ArrayBase.useIndex, useRecord: ArrayBase.useRecord, }) export default ArrayCards ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormButtonGroup.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # FormButtonGroup > 表单按钮组布局组件 ## 普通案例 ```tsx import React from 'react' import { FormButtonGroup, Submit, Reset, FormItem, Input, FormLayout, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> <Reset>重置</Reset> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## 吸底案例 ```tsx import React from 'react' import { FormButtonGroup, Submit, Reset, FormItem, FormLayout, Input, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> </SchemaField> <FormButtonGroup.Sticky> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> <Reset>重置</Reset> </FormButtonGroup.FormItem> </FormButtonGroup.Sticky> </FormLayout> </FormProvider> ) } ``` ## 吸底居中案例 ```tsx import React from 'react' import { FormButtonGroup, Submit, Reset, FormItem, FormLayout, Input, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> </SchemaField> <FormButtonGroup.Sticky align="center"> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> <Reset>重置</Reset> </FormButtonGroup> </FormButtonGroup.Sticky> </FormLayout> </FormProvider> ) } ``` ## API ### FormButtonGroup > 该组件主要用来处理按钮组间隙 | 属性名 | 类型 | 描述 | 默认值 | | ------ | --------------------------- | -------- | -------- | | gutter | number | 间隙大小 | 8px | | align | `'left'\|'center'\|'right'` | 对齐方式 | `'left'` | ### FormButtonGroup.FormItem > 该组件主要用来处理按钮组与主表单 FormItem 对齐问题 参考 [FormItem](/components/form-item) 属性 ### FormButtonGroup.Sticky > 该组件主要用来处理按钮组浮动定位问题 | 属性名 | 类型 | 描述 | 默认值 | | ------ | --------------------------- | -------- | -------- | | align | `'left'\|'center'\|'right'` | 对齐方式 | `'left'` | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormButtonGroup.zh-CN.md: -------------------------------------------------------------------------------- ```markdown # FormButtonGroup > 表单按钮组布局组件 ## 普通案例 ```tsx import React from 'react' import { FormButtonGroup, Submit, Reset, FormItem, Input, FormLayout, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> </SchemaField> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> <Reset>重置</Reset> </FormButtonGroup.FormItem> </FormLayout> </FormProvider> ) } ``` ## 吸底案例 ```tsx import React from 'react' import { FormButtonGroup, Submit, Reset, FormItem, FormLayout, Input, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> </SchemaField> <FormButtonGroup.Sticky> <FormButtonGroup.FormItem> <Submit onSubmit={console.log}>提交</Submit> <Reset>重置</Reset> </FormButtonGroup.FormItem> </FormButtonGroup.Sticky> </FormLayout> </FormProvider> ) } ``` ## 吸底居中案例 ```tsx import React from 'react' import { FormButtonGroup, Submit, Reset, FormItem, FormLayout, Input, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <FormLayout labelCol={6} wrapperCol={10}> <SchemaField> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> <SchemaField.String title="输入框" x-decorator="FormItem" required x-component="Input" /> </SchemaField> <FormButtonGroup.Sticky align="center"> <FormButtonGroup> <Submit onSubmit={console.log}>提交</Submit> <Reset>重置</Reset> </FormButtonGroup> </FormButtonGroup.Sticky> </FormLayout> </FormProvider> ) } ``` ## API ### FormButtonGroup > 该组件主要用来处理按钮组间隙 | 属性名 | 类型 | 描述 | 默认值 | | ------ | --------------------------- | -------- | -------- | | gutter | number | 间隙大小 | 8px | | align | `'left'\|'center'\|'right'` | 对齐方式 | `'left'` | ### FormButtonGroup.FormItem > 该组件主要用来处理按钮组与主表单 FormItem 对齐问题 参考 [FormItem](/components/form-item) 属性 ### FormButtonGroup.Sticky > 该组件主要用来处理按钮组浮动定位问题 | 属性名 | 类型 | 描述 | 默认值 | | ------ | --------------------------- | -------- | -------- | | align | `'left'\|'center'\|'right'` | 对齐方式 | `'left'` | ``` -------------------------------------------------------------------------------- /packages/next/src/array-cards/index.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react' import { Card } from '@alifd/next' import { CardProps } from '@alifd/next/lib/card' import { ArrayField } from '@formily/core' import { useField, observer, useFieldSchema, RecursionField, } from '@formily/react' import { ISchema } from '@formily/json-schema' import { usePrefixCls } from '../__builtins__' import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from '../array-base' import cls from 'classnames' type ComposedArrayCards = React.FC< React.PropsWithChildren<CardProps & IArrayBaseProps> > & ArrayBaseMixins const isAdditionComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Addition') > -1 } const isIndexComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Index') > -1 } const isRemoveComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Remove') > -1 } const isCopyComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('Copy') > -1 } const isMoveUpComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveUp') > -1 } const isMoveDownComponent = (schema: ISchema) => { return schema['x-component']?.indexOf?.('MoveDown') > -1 } const isOperationComponent = (schema: ISchema) => { return ( isAdditionComponent(schema) || isRemoveComponent(schema) || isCopyComponent(schema) || isMoveDownComponent(schema) || isMoveUpComponent(schema) ) } const Empty = () => { return ( <div className="next-empty"> <div className="next-empty-image"> <svg className="ant-empty-img-default" width="184" height="152" viewBox="0 0 184 152" xmlns="http://www.w3.org/2000/svg" > <g fill="none" fillRule="evenodd"> <g transform="translate(24 31.67)"> <ellipse className="ant-empty-img-default-ellipse" cx="67.797" cy="106.89" rx="67.797" ry="12.668" ></ellipse> <path className="ant-empty-img-default-path-1" d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z" ></path> <path className="ant-empty-img-default-path-2" d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z" transform="translate(13.56)" ></path> <path className="ant-empty-img-default-path-3" d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z" ></path> <path className="ant-empty-img-default-path-4" d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z" ></path> </g> <path className="ant-empty-img-default-path-5" d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z" ></path> <g className="ant-empty-img-default-g" transform="translate(149.65 15.383)" > <ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815"></ellipse> <path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"></path> </g> </g> </svg> </div> </div> ) } export const ArrayCards: ComposedArrayCards = observer((props) => { const field = useField<ArrayField>() const schema = useFieldSchema() const dataSource = Array.isArray(field.value) ? field.value : [] const prefixCls = usePrefixCls('formily-array-cards', props) const { onAdd, onCopy, onRemove, onMoveDown, onMoveUp } = props const renderItems = () => { return dataSource?.map((item, index) => { const items = Array.isArray(schema.items) ? schema.items[index] || schema.items[0] : schema.items const title = ( <span> <RecursionField schema={items} name={index} filterProperties={(schema) => { if (!isIndexComponent(schema)) return false return true }} onlyRenderProperties /> {props.title || field.title} </span> ) const extra = ( <span> <RecursionField schema={items} name={index} filterProperties={(schema) => { if (!isOperationComponent(schema)) return false return true }} onlyRenderProperties /> {props.extra} </span> ) const content = ( <RecursionField schema={items} name={index} filterProperties={(schema) => { if (isIndexComponent(schema)) return false if (isOperationComponent(schema)) return false return true }} /> ) return ( <ArrayBase.Item key={index} index={index} record={() => field.value?.[index]} > <Card contentHeight="auto" {...props} onChange={() => {}} className={cls(`${prefixCls}-item`, props.className)} title={title} extra={extra} > {content} </Card> </ArrayBase.Item> ) }) } const renderAddition = () => { return schema.reduceProperties((addition, schema, key) => { if (isAdditionComponent(schema)) { return <RecursionField schema={schema} name={key} /> } return addition }, null) } const renderEmpty = () => { if (dataSource?.length) return return ( <Card contentHeight="auto" {...props} className={cls(`${prefixCls}-item`, props.className)} title={props.title || field.title} onChange={() => {}} > <Empty /> </Card> ) } return ( <ArrayBase onAdd={onAdd} onCopy={onCopy} onRemove={onRemove} onMoveUp={onMoveUp} onMoveDown={onMoveDown} > {renderEmpty()} {renderItems()} {renderAddition()} </ArrayBase> ) }) ArrayCards.displayName = 'ArrayCards' ArrayBase.mixin(ArrayCards) export default ArrayCards ```