This is page 21 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/path/src/__tests__/accessor.spec.ts: -------------------------------------------------------------------------------- ```typescript import { Path } from '../' const { getIn, setIn } = Path test('test getIn null parent', () => { const value = { aa: null } expect(getIn(value, 'aa')).toEqual(null) expect(getIn(value, 'aa.bb.cc')).toEqual(undefined) }) test('test getIn and setIn', () => { const value = { a: { b: { c: 2, d: 333 } } } expect(getIn(value, 'a.b.c')).toEqual(2) setIn(value, 'a.b.c', 1111) expect(getIn(value, 'a.b.c')).toEqual(1111) }) test('test getIn with destructor', () => { const value = { array: [{ aa: 123, bb: 321 }] } expect(getIn(value, 'array.0.[aa,bb]')).toEqual([123, 321]) }) test('test setIn auto create array', () => { const value = { array: null } setIn(value, 'array[0].bb[2]', 'hello world') expect(value).toEqual({ array: [ { bb: [undefined, undefined, 'hello world'], }, ], }) expect(getIn(undefined, 'aa.bb.cc')).toEqual(undefined) setIn(undefined, 'aa.bb.cc', 123) }) test('map', () => { const value = { map: new Map() } setIn(value, 'map.aa.bb.cc', 123) expect(getIn(value, 'map.aa.bb.cc')).toEqual(123) }) test('test setIn array properties', () => { const value = { array: [] } setIn(value, 'array.xxx', 'hello world') expect(value).toEqual({ array: [] }) }) test('test setIn dose not affect other items', () => { const value = { aa: [ { dd: [ { ee: '是', }, ], cc: '1111', }, ], } setIn(value, 'aa.1.dd.0.ee', '否') expect(value.aa[0]).toEqual({ dd: [ { ee: '是', }, ], cc: '1111', }) }) test('destruct getIn', () => { // getIn 通过解构表达式从扁平数据转为复合嵌套数据 const value = { a: { b: { c: 2, d: 333 } } } expect(getIn({ a: { b: { kk: 2, mm: 333 } } }, 'a.b.{c:kk,d:mm}')).toEqual({ c: 2, d: 333, }) expect( getIn( { kk: 2, mm: 333 }, `{ a : { b : { c : kk, d : mm } } }` ) ).toEqual(value) expect(getIn({ bb: undefined, dd: undefined }, `[{aa:bb,cc:dd}]`)).toEqual([]) expect( getIn( { kk: undefined, mm: undefined }, `{ a : { b : { c : kk, d : mm } } }` ) ).toEqual({}) }) test('destruct setIn', () => { const value = { a: { b: { c: 2, d: 333 } } } // setIn 从复杂嵌套结构中解构数据出来对其做赋值处理 expect( setIn( {}, `{ a : { b : { c, d } } }`, value ) ).toEqual({ c: 2, d: 333 }) expect( setIn( {}, ` [aa,bb] `, [123, 444] ) ).toEqual({ aa: 123, bb: 444 }) expect(setIn({}, 'aa.bb.ddd.[aa,bb]', [123, 444])).toEqual({ aa: { bb: { ddd: { aa: 123, bb: 444 } } }, }) expect(setIn({}, 'aa.bb.ddd.[{cc:aa,bb}]', [{ cc: 123, bb: 444 }])).toEqual({ aa: { bb: { ddd: { aa: 123, bb: 444 } } }, }) }) test('setIn with a.b.c.{aaa,bbb}', () => { expect(Path.setIn({}, 'a.b.c.{aaa,bbb}', { aaa: 123, bbb: 321 })).toEqual({ a: { b: { c: { aaa: 123, bbb: 321 } } }, }) }) test('getIn with a.b.c.{aaa,bbb}', () => { expect( Path.getIn({ a: { b: { c: { aaa: 123, bbb: 321 } } } }, 'a.b.c.{aaa,bbb}') ).toEqual({ aaa: 123, bbb: 321 }) }) test('setIn with a.b.c.{aaa,bbb} source has extra property', () => { expect( Path.setIn({ a: { b: { c: { kkk: 'ddd' } } } }, 'a.b.c.{aaa,bbb}', { aaa: 123, bbb: 321, }) ).toEqual({ a: { b: { c: { aaa: 123, bbb: 321, kkk: 'ddd' } } } }) }) test('getIn with a.b.c.{aaa,bbb} source has extra property', () => { expect( Path.getIn( { a: { b: { c: { aaa: 123, bbb: 321, kkk: 'ddd' } } } }, 'a.b.c.{aaa,bbb}' ) ).toEqual({ aaa: 123, bbb: 321 }) }) test('setIn with a.b.c.{aaa:ooo,bbb}', () => { expect( Path.setIn({ a: { b: { c: { kkk: 'ddd' } } } }, 'a.b.c.{aaa:ooo,bbb}', { aaa: 123, bbb: 321, }) ).toEqual({ a: { b: { c: { ooo: 123, bbb: 321, kkk: 'ddd' } } } }) }) test('getIn with a.b.c.{aaa:ooo,bbb}', () => { expect( Path.getIn( { a: { b: { c: { ooo: 123, bbb: 321, kkk: 'ddd' } } } }, 'a.b.c.{aaa:ooo,bbb}' ) ).toEqual({ aaa: 123, bbb: 321 }) }) test('setIn with a.b.c.[aaa,bbb]', () => { expect(Path.setIn({}, 'a.b.c.[aaa,bbb]', [123, 321])).toEqual({ a: { b: { c: { aaa: 123, bbb: 321 } } }, }) }) test('getIn with a.b.c.[aaa,bbb]', () => { expect( Path.getIn({ a: { b: { c: { aaa: 123, bbb: 321 } } } }, 'a.b.c.[aaa,bbb]') ).toEqual([123, 321]) }) test('setIn with a.b.c.[aaa,bbb] source has extra property', () => { expect( Path.setIn( { a: { b: { c: { kkk: 'ddd' } } } }, 'a.b.c.[aaa,bbb]', [123, 321] ) ).toEqual({ a: { b: { c: { aaa: 123, bbb: 321, kkk: 'ddd' } } } }) }) test('getIn with a.b.c.[aaa,bbb] source has extra property', () => { expect( Path.getIn( { a: { b: { c: { aaa: 123, bbb: 321, kkk: 'ddd' } } } }, 'a.b.c.[aaa,bbb]' ) ).toEqual([123, 321]) }) test('setIn with a.b.c.[{ddd,kkk:mmm},bbb]', () => { expect( Path.setIn({}, 'a.b.c.[{ddd,kkk:mmm},bbb]', [{ ddd: 123, kkk: 'hhh' }, 321]) ).toEqual({ a: { b: { c: { ddd: 123, bbb: 321, mmm: 'hhh' } } } }) }) test('getIn with a.b.c.[{ddd,kkk:mmm},bbb]', () => { expect( Path.getIn( { a: { b: { c: { ddd: 123, bbb: 321, mmm: 'hhh' } } } }, 'a.b.c.[{ddd,kkk:mmm},bbb]' ) ).toEqual([{ ddd: 123, kkk: 'hhh' }, 321]) }) test('setIn with a.b.c.{aaa:ooo,bbb:[ccc,ddd]}', () => { expect( Path.setIn( { a: { b: { c: { kkk: 'ddd' } } } }, 'a.b.c.{aaa:ooo,bbb:[ccc,ddd]}', { aaa: 123, bbb: [123, 321] } ) ).toEqual({ a: { b: { c: { ooo: 123, ccc: 123, ddd: 321, kkk: 'ddd' } } } }) }) test('getIn with a.b.c.{aaa:ooo,bbb:[ccc,ddd]}', () => { expect( Path.getIn( { a: { b: { c: { ooo: 123, ccc: 123, ddd: 321, kkk: 'ddd' } } } }, 'a.b.c.{aaa:ooo,bbb:[ccc,ddd]}' ) ).toEqual({ aaa: 123, bbb: [123, 321] }) }) test('existIn with a.b.c', () => { expect(Path.existIn({ a: { b: { c: 123123 } } }, 'a.b.c')).toEqual(true) expect(Path.existIn({ a: { b: { c: 123123 } } }, 'a.b.c.d')).toEqual(false) expect(Path.existIn({ a: 123 }, 'a.b.c.d')).toEqual(false) expect( Path.existIn( { a: { b: { c: { ooo: 123, ccc: 123, ddd: 321, kkk: 'ddd' } } } }, 'a.b.c.{aaa:ooo,bbb:[ccc,ddd]}' ) ).toEqual(true) expect( Path.existIn( { a: { b: { c: { ooo: 123, ccc: 123, kkk: 'ddd' } } } }, 'a.b.c.{aaa:ooo,bbb:[ccc,ddd]}' ) ).toEqual(false) expect(Path.existIn({ a: [{}] }, 'a.0')).toEqual(true) }) test('existIn with start Path', () => { expect(Path.existIn({ a: [{}] }, 'a.0', Path.parse('a'))).toEqual(false) expect(Path.existIn({ a: [{}] }, 'b.a.0', Path.parse('b'))).toEqual(true) }) test('deleteIn', () => { expect( Path.deleteIn({ a: { b: { c: { ooo: 123, ccc: 234 } } } }, 'a.b.c.ccc') ).toEqual({ a: { b: { c: { ooo: 123 } } } }) expect( Path.deleteIn({ a: { b: { c: { ooo: 123, ccc: 234 } } } }, null) ).toEqual({ a: { b: { c: { ooo: 123, ccc: 234 } } } }) expect( Path.deleteIn({ a: { b: { c: { ooo: 123, ccc: 234 } } } }, []) ).toEqual({ a: { b: { c: { ooo: 123, ccc: 234 } } } }) expect(Path.deleteIn({ a: { b: { c: 'c' } } }, 'a.b.c.ccc')).toEqual({ a: { b: { c: 'c' } }, }) expect(Path.deleteIn({ a: 1, b: 2 }, '{ a }')).toEqual({ b: 2 }) expect(Path.deleteIn([1, 2], '[0]')).toEqual([undefined, 2]) }) test('ensureIn', () => { expect(Path.parse('a.b').ensureIn({}, 'default')).toEqual('default') expect(Path.parse('a.b').ensureIn({ a: { b: 'value' } }, 'default')).toEqual( 'value' ) expect(Path.ensureIn({}, 'a.b.c', 'default')).toEqual('default') }) test('complex destructing', () => { expect( Path.setIn( {}, '{aa:{bb:{cc:destructor1,dd:[destructor2,destructor3],ee}}}', { aa: { bb: { cc: 123, dd: [333, 444], ee: 'abcde', }, }, } ) ).toEqual({ destructor1: 123, destructor2: 333, destructor3: 444, ee: 'abcde', }) expect( Path.getIn( { destructor1: 123, destructor2: 333, destructor3: 444, ee: 'abcde', }, '{aa:{bb:{cc:destructor1,dd:[destructor2,destructor3],ee}}}' ) ).toEqual({ aa: { bb: { cc: 123, dd: [333, 444], ee: 'abcde', }, }, }) }) test('test getIn with invalid value', () => { const value = { array: [null, undefined, { nil: null, undef: undefined }], nil: null, undef: undefined, } expect(getIn(value, 'array.0')).toBeNull() expect(getIn(value, 'array.1')).toBeUndefined() expect(getIn(value, 'array.2.nil')).toBeNull() expect(getIn(value, 'array.2.undef')).toBeUndefined() expect(getIn(value, 'nil')).toBeNull() expect(getIn(value, 'undef')).toBeUndefined() }) test('test setIn with invalid value', () => { const value = { a: 1, b: 2, array: [null, undefined, { nil: null, undef: undefined }], nil: null, undef: undefined, } setIn(value, 'a', null) setIn(value, 'b', undefined) // undefined 与 null 互转 setIn(value, 'array.0', undefined) setIn(value, 'array.1', null) setIn(value, 'array.2.nil', undefined) setIn(value, 'array.2.undef', null) setIn(value, 'nil', undefined) setIn(value, 'undef', null) expect(getIn(value, 'a')).toBeNull() expect(getIn(value, 'b')).toBeUndefined() expect(getIn(value, 'array.0')).toBeUndefined() expect(getIn(value, 'array.1')).toBeNull() expect(getIn(value, 'array.2.nil')).toBeUndefined() expect(getIn(value, 'array.2.undef')).toBeNull() expect(getIn(value, 'nil')).toBeUndefined() expect(getIn(value, 'undef')).toBeNull() }) test('path arguments', () => { const path = new Path('a.b.c') expect(new Path(path).segments).toEqual(['a', 'b', 'c']) const matchPath = Path.match('a.b.c') expect(new Path(matchPath).segments).toEqual(['a', 'b', 'c']) expect(new Path(undefined).segments).toEqual([]) }) test('path methods', () => { const path = Path.parse('a.b.c') expect(path.concat(Path.parse('d.e')).segments).toEqual([ 'a', 'b', 'c', 'd', 'e', ]) expect(Path.parse(['a', 'b', 'c']).toString()).toEqual('a.b.c') expect(Path.parse(['a', 'b', 'c']).length).toEqual(3) const matchPath = Path.parse('*') const regexPath = Path.parse(/.+/) expect(() => matchPath.concat('a')).toThrowError() expect(() => regexPath.concat('a')).toThrowError() expect(() => matchPath.slice()).toThrowError() expect(() => regexPath.slice()).toThrowError() expect(() => matchPath.pop()).toThrowError() expect(() => regexPath.pop()).toThrowError() expect(() => matchPath.splice(0, 1)).toThrowError() expect(() => regexPath.splice(0, 1)).toThrowError() expect(() => matchPath.forEach(() => {})).toThrowError() expect(() => regexPath.forEach(() => {})).toThrowError() expect(() => matchPath.map(() => {})).toThrowError() expect(() => regexPath.map(() => {})).toThrowError() expect(() => matchPath.reduce((p) => p, '')).toThrowError() expect(() => regexPath.reduce((p) => p, '')).toThrowError() expect(path.slice().segments).toEqual(['a', 'b', 'c']) expect(path.push('d').segments).toEqual(['a', 'b', 'c', 'd']) expect(path.pop().segments).toEqual(['a', 'b']) expect(path.splice(0, 1).segments).toEqual(['b', 'c']) let key = '' path.forEach((p) => (key += p + '_')) expect(key).toEqual('a_b_c_') expect(path.map((p) => p)).toEqual(['a', 'b', 'c']) expect(path.reduce((str, p) => str + p, '')).toEqual('abc') expect(path.parent().segments).toEqual(['a', 'b']) expect(() => Path.parse('*').includes('*')).toThrowError() expect(() => Path.parse('*').includes('*')).toThrowError() expect(() => Path.parse('a.b').includes('*')).toThrowError() expect(Path.parse('*').includes('a.b')).toBeTruthy() expect(Path.parse('a.b').includes('a.b')).toBeTruthy() expect(Path.parse('a.b').includes('a.c')).toBeFalsy() expect(Path.parse('a.b').includes('a.b.c')).toBeFalsy() expect(Path.parse('a.b.c').transform(/[a-z]/, (...result) => result)).toEqual( ['a', 'b', 'c'] ) expect(Path.parse('a.b.c').transform(/[a-b]/, (...result) => result)).toEqual( ['a', 'b'] ) expect(Path.parse('a.b.c').transform('', null)).toEqual('') expect(() => Path.parse('*').transform('', () => {})).toThrowError() expect(Path.transform('a.b.c', /[a-z]/, (...result) => result)).toEqual([ 'a', 'b', 'c', ]) expect(Path.parse('a.b.c').match('*')).toBeTruthy() expect(() => Path.parse('*').match('*')).toThrowError() expect(Path.match('*')('a.b.c')).toBeTruthy() expect(Path.match('a.b')('a.b.c')).toBeFalsy() const matcher = Path.match('a.b.c') expect(Path.parse(matcher).segments).toEqual(['a', 'b', 'c']) }) ``` -------------------------------------------------------------------------------- /packages/json-schema/src/schema.ts: -------------------------------------------------------------------------------- ```typescript import { ISchema, SchemaEnum, SchemaProperties, SchemaReaction, SchemaTypes, SchemaKey, ISchemaTransformerOptions, Slot, } from './types' import { IFieldFactoryProps } from '@formily/core' import { map, each, isFn, instOf, FormPath, isStr } from '@formily/shared' import { compile, silent, shallowCompile, registerCompiler } from './compiler' import { transformFieldProps } from './transformer' import { reducePatches, registerPatches, registerPolyfills, enablePolyfills, } from './patches' import { registerVoidComponents, registerTypeDefaultComponents, } from './polyfills' import { SchemaNestedMap } from './shared' export class Schema< Decorator = any, Component = any, DecoratorProps = any, ComponentProps = any, Pattern = any, Display = any, Validator = any, Message = any, ReactionField = any > implements ISchema { parent?: Schema root?: Schema name?: SchemaKey title?: Message description?: Message default?: any readOnly?: boolean writeOnly?: boolean type?: SchemaTypes enum?: SchemaEnum<Message> const?: any multipleOf?: number maximum?: number exclusiveMaximum?: number minimum?: number exclusiveMinimum?: number maxLength?: number minLength?: number pattern?: string | RegExp maxItems?: number minItems?: number uniqueItems?: boolean maxProperties?: number minProperties?: number required?: string[] | boolean | string format?: string /** nested json schema spec **/ definitions?: Record< string, Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > > properties?: Record< string, Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > > items?: | Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > | Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message >[] additionalItems?: Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > patternProperties?: Record< string, Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > > additionalProperties?: Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message >; //顺序描述 ['x-index']?: number; //交互模式 ['x-pattern']?: Pattern; //展示状态 ['x-display']?: Display; //校验器 ['x-validator']?: Validator; //装饰器 ['x-decorator']?: Decorator; //装饰器属性 ['x-decorator-props']?: DecoratorProps; //组件 ['x-component']?: Component; //组件属性 ['x-component-props']?: ComponentProps; ['x-reactions']?: SchemaReaction<ReactionField>[]; ['x-content']?: any; ['x-data']?: any; ['x-visible']?: boolean; ['x-hidden']?: boolean; ['x-disabled']?: boolean; ['x-editable']?: boolean; ['x-read-only']?: boolean; ['x-read-pretty']?: boolean; ['x-compile-omitted']?: string[]; ['x-slot-node']?: Slot; [key: `x-${string | number}` | symbol]: any _isJSONSchemaObject = true version = '2.0' constructor( json: ISchema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message >, parent?: Schema ) { if (parent) { this.parent = parent this.root = parent.root } else { this.root = this } return this.fromJSON(json) } addProperty = ( key: SchemaKey, schema: ISchema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > ) => { this.properties = this.properties || {} this.properties[key] = new Schema(schema, this) this.properties[key].name = key return this.properties[key] } removeProperty = (key: SchemaKey) => { const schema = this.properties[key] delete this.properties[key] return schema } setProperties = ( properties: SchemaProperties< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > ) => { for (const key in properties) { this.addProperty(key, properties[key]) } return this } addPatternProperty = ( key: SchemaKey, schema: ISchema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > ) => { if (!schema) return this.patternProperties = this.patternProperties || {} this.patternProperties[key] = new Schema(schema, this) this.patternProperties[key].name = key return this.patternProperties[key] } removePatternProperty = (key: SchemaKey) => { const schema = this.patternProperties[key] delete this.patternProperties[key] return schema } setPatternProperties = ( properties: SchemaProperties< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > ) => { if (!properties) return this for (const key in properties) { this.addPatternProperty(key, properties[key]) } return this } setAdditionalProperties = ( properties: ISchema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > ) => { if (!properties) return this.additionalProperties = new Schema(properties) return this.additionalProperties } setItems = ( schema: | ISchema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > | ISchema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message >[] ) => { if (!schema) return if (Array.isArray(schema)) { this.items = schema.map((item) => new Schema(item, this)) } else { this.items = new Schema(schema, this) } return this.items } setAdditionalItems = ( items: ISchema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > ) => { if (!items) return this.additionalItems = new Schema(items, this) return this.additionalItems } findDefinitions = (ref: string) => { if (!ref || !this.root || !isStr(ref)) return if (ref.indexOf('#/') !== 0) return return FormPath.getIn(this.root, ref.substring(2).split('/')) } mapProperties = <T>( callback?: ( schema: Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message >, key: SchemaKey, index: number ) => T ): T[] => { return Schema.getOrderProperties(this).map(({ schema, key }, index) => { return callback(schema, key, index) }) } mapPatternProperties = <T>( callback?: ( schema: Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message >, key: SchemaKey, index: number ) => T ): T[] => { return Schema.getOrderProperties(this, 'patternProperties').map( ({ schema, key }, index) => { return callback(schema, key, index) } ) } reduceProperties = <P, R>( callback?: ( buffer: P, schema: Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message >, key: SchemaKey, index: number ) => R, predicate?: P ): R => { let results: any = predicate Schema.getOrderProperties(this, 'properties').forEach( ({ schema, key }, index) => { results = callback(results, schema, key, index) } ) return results } reducePatternProperties = <P, R>( callback?: ( buffer: P, schema: Schema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message >, key: SchemaKey, index: number ) => R, predicate?: P ): R => { let results: any = predicate Schema.getOrderProperties(this, 'patternProperties').forEach( ({ schema, key }, index) => { results = callback(results, schema, key, index) } ) return results } compile = (scope?: any) => { const schema = new Schema({}, this.parent) each(this, (value, key) => { if (isFn(value) && !key.includes('x-')) return if (key === 'parent' || key === 'root') return if (!SchemaNestedMap[key]) { schema[key] = value ? compile(value, scope) : value } else { schema[key] = value ? shallowCompile(value, scope) : value } }) return schema } fromJSON = ( json: ISchema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > ) => { if (!json) return this if (Schema.isSchemaInstance(json)) return json each(reducePatches(json), (value, key) => { if (isFn(value) && !key.includes('x-')) return if (key === 'properties') { this.setProperties(value) } else if (key === 'patternProperties') { this.setPatternProperties(value) } else if (key === 'additionalProperties') { this.setAdditionalProperties(value) } else if (key === 'items') { this.setItems(value) } else if (key === 'additionalItems') { this.setAdditionalItems(value) } else if (key === '$ref') { this.fromJSON(this.findDefinitions(value)) } else { this[key] = value } }) return this } toJSON = ( recursion = true ): ISchema< Decorator, Component, DecoratorProps, ComponentProps, Pattern, Display, Validator, Message > => { const results = {} each(this, (value: any, key) => { if ( (isFn(value) && !key.includes('x-')) || key === 'parent' || key === 'root' ) return if (key === 'properties' || key === 'patternProperties') { if (!recursion) return results[key] = map(value, (item) => item?.toJSON?.()) } else if (key === 'additionalProperties' || key === 'additionalItems') { if (!recursion) return results[key] = value?.toJSON?.() } else if (key === 'items') { if (!recursion) return if (Array.isArray(value)) { results[key] = value.map((item) => item?.toJSON?.()) } else { results[key] = value?.toJSON?.() } } else { results[key] = value } }) return results } toFieldProps = ( options?: ISchemaTransformerOptions ): IFieldFactoryProps<any, any> => { return transformFieldProps(this, options) } static getOrderProperties = ( schema: ISchema = {}, propertiesName: keyof ISchema = 'properties' ) => { const orderProperties = [] const unorderProperties = [] for (const key in schema[propertiesName]) { const item = schema[propertiesName][key] const index = item['x-index'] if (!isNaN(index)) { orderProperties[index] = { schema: item, key } } else { unorderProperties.push({ schema: item, key }) } } return orderProperties.concat(unorderProperties).filter((item) => !!item) } static compile = (expression: any, scope?: any) => { return compile(expression, scope) } static shallowCompile = (expression: any, scope?: any) => { return shallowCompile(expression, scope) } static isSchemaInstance = (value: any): value is Schema => { return instOf(value, Schema) } static registerCompiler = registerCompiler static registerPatches = registerPatches static registerVoidComponents = registerVoidComponents static registerTypeDefaultComponents = registerTypeDefaultComponents static registerPolyfills = registerPolyfills static enablePolyfills = enablePolyfills static silent = silent } ``` -------------------------------------------------------------------------------- /packages/core/src/models/Field.ts: -------------------------------------------------------------------------------- ```typescript import { isValid, isEmpty, toArr, FormPathPattern, isArr, } from '@formily/shared' import { ValidatorTriggerType, parseValidatorDescriptions, } from '@formily/validator' import { define, observable, batch, toJS, action } from '@formily/reactive' import { JSXComponent, LifeCycleTypes, IFieldFeedback, FeedbackMessage, IFieldCaches, IFieldRequests, FieldValidator, FieldDataSource, ISearchFeedback, IFieldProps, IFieldResetOptions, IFieldState, IModelSetter, IModelGetter, } from '../types' import { updateFeedback, queryFeedbacks, allowAssignDefaultValue, queryFeedbackMessages, getValuesFromEvent, createReactions, createStateSetter, createStateGetter, isHTMLInputEvent, setValidatorRule, batchValidate, batchSubmit, batchReset, setValidating, setSubmitting, setLoading, validateSelf, modifySelf, getValidFieldDefaultValue, initializeStart, initializeEnd, createChildrenFeedbackFilter, createReaction, } from '../shared/internals' import { Form } from './Form' import { BaseField } from './BaseField' import { IFormFeedback } from '../types' export class Field< Decorator extends JSXComponent = any, Component extends JSXComponent = any, TextType = any, ValueType = any > extends BaseField<Decorator, Component, TextType> { displayName = 'Field' props: IFieldProps<Decorator, Component, TextType, ValueType> loading: boolean validating: boolean submitting: boolean active: boolean visited: boolean selfModified: boolean modified: boolean inputValue: ValueType inputValues: any[] dataSource: FieldDataSource validator: FieldValidator feedbacks: IFieldFeedback[] caches: IFieldCaches = {} requests: IFieldRequests = {} constructor( address: FormPathPattern, props: IFieldProps<Decorator, Component, TextType, ValueType>, form: Form, designable: boolean ) { super() this.form = form this.props = props this.designable = designable initializeStart() this.locate(address) this.initialize() this.makeObservable() this.makeReactive() this.onInit() initializeEnd() } protected initialize() { this.initialized = false this.loading = false this.validating = false this.submitting = false this.selfModified = false this.active = false this.visited = false this.mounted = false this.unmounted = false this.inputValues = [] this.inputValue = null this.feedbacks = [] this.title = this.props.title this.description = this.props.description this.display = this.props.display this.pattern = this.props.pattern this.editable = this.props.editable this.disabled = this.props.disabled this.readOnly = this.props.readOnly this.readPretty = this.props.readPretty this.visible = this.props.visible this.hidden = this.props.hidden this.dataSource = this.props.dataSource this.validator = this.props.validator this.required = this.props.required this.content = this.props.content this.initialValue = this.props.initialValue this.value = this.props.value this.data = this.props.data this.decorator = toArr(this.props.decorator) this.component = toArr(this.props.component) } protected makeObservable() { if (this.designable) return define(this, { path: observable.ref, title: observable.ref, description: observable.ref, dataSource: observable.ref, selfDisplay: observable.ref, selfPattern: observable.ref, loading: observable.ref, validating: observable.ref, submitting: observable.ref, selfModified: observable.ref, modified: observable.ref, active: observable.ref, visited: observable.ref, initialized: observable.ref, mounted: observable.ref, unmounted: observable.ref, inputValue: observable.ref, inputValues: observable.ref, decoratorType: observable.ref, componentType: observable.ref, content: observable.ref, feedbacks: observable.ref, decoratorProps: observable, componentProps: observable, validator: observable.shallow, data: observable.shallow, component: observable.computed, decorator: observable.computed, errors: observable.computed, warnings: observable.computed, successes: observable.computed, valid: observable.computed, invalid: observable.computed, selfErrors: observable.computed, selfWarnings: observable.computed, selfSuccesses: observable.computed, selfValid: observable.computed, selfInvalid: observable.computed, validateStatus: observable.computed, value: observable.computed, initialValue: observable.computed, display: observable.computed, pattern: observable.computed, required: observable.computed, hidden: observable.computed, visible: observable.computed, disabled: observable.computed, readOnly: observable.computed, readPretty: observable.computed, editable: observable.computed, indexes: observable.computed, setDisplay: action, setTitle: action, setDescription: action, setDataSource: action, setValue: action, setPattern: action, setInitialValue: action, setLoading: action, setValidating: action, setFeedback: action, setSelfErrors: action, setSelfWarnings: action, setSelfSuccesses: action, setValidator: action, setRequired: action, setComponent: action, setComponentProps: action, setDecorator: action, setDecoratorProps: action, setData: action, setContent: action, validate: action, reset: action, onInit: batch, onInput: batch, onMount: batch, onUnmount: batch, onFocus: batch, onBlur: batch, }) } protected makeReactive() { if (this.designable) return this.disposers.push( createReaction( () => this.value, (value) => { this.notify(LifeCycleTypes.ON_FIELD_VALUE_CHANGE) if (isValid(value)) { if (this.selfModified && !this.caches.inputting) { validateSelf(this) } if (!isEmpty(value) && this.display === 'none') { this.caches.value = toJS(value) this.form.deleteValuesIn(this.path) } } } ), createReaction( () => this.initialValue, () => { this.notify(LifeCycleTypes.ON_FIELD_INITIAL_VALUE_CHANGE) } ), createReaction( () => this.display, (display) => { const value = this.value if (display !== 'none') { if (value === undefined && this.caches.value !== undefined) { this.setValue(this.caches.value) this.caches.value = undefined } } else { this.caches.value = toJS(value) ?? toJS(this.initialValue) this.form.deleteValuesIn(this.path) } if (display === 'none' || display === 'hidden') { this.setFeedback({ type: 'error', messages: [], }) } } ), createReaction( () => this.pattern, (pattern) => { if (pattern !== 'editable') { this.setFeedback({ type: 'error', messages: [], }) } } ) ) createReactions(this) } get selfErrors(): FeedbackMessage { return queryFeedbackMessages(this, { type: 'error', }) } get errors(): IFormFeedback[] { return this.form.errors.filter(createChildrenFeedbackFilter(this)) } get selfWarnings(): FeedbackMessage { return queryFeedbackMessages(this, { type: 'warning', }) } get warnings(): IFormFeedback[] { return this.form.warnings.filter(createChildrenFeedbackFilter(this)) } get selfSuccesses(): FeedbackMessage { return queryFeedbackMessages(this, { type: 'success', }) } get successes(): IFormFeedback[] { return this.form.successes.filter(createChildrenFeedbackFilter(this)) } get selfValid() { return !this.selfErrors.length } get valid() { return !this.errors.length } get selfInvalid() { return !this.selfValid } get invalid() { return !this.valid } get value(): ValueType { return this.form.getValuesIn(this.path) } get initialValue(): ValueType { return this.form.getInitialValuesIn(this.path) } get required() { const validators = isArr(this.validator) ? this.validator : parseValidatorDescriptions(this.validator) return validators.some((desc) => !!desc?.['required']) } get validateStatus() { if (this.validating) return 'validating' if (this.selfInvalid) return 'error' if (this.selfWarnings.length) return 'warning' if (this.selfSuccesses.length) return 'success' } set required(required: boolean) { if (this.required === required) return this.setValidatorRule('required', required) } set value(value: ValueType) { this.setValue(value) } set initialValue(initialValue: ValueType) { this.setInitialValue(initialValue) } set selfErrors(messages: FeedbackMessage) { this.setFeedback({ type: 'error', code: 'EffectError', messages, }) } set selfWarnings(messages: FeedbackMessage) { this.setFeedback({ type: 'warning', code: 'EffectWarning', messages, }) } set selfSuccesses(messages: FeedbackMessage) { this.setFeedback({ type: 'success', code: 'EffectSuccess', messages, }) } setDataSource = (dataSource?: FieldDataSource) => { this.dataSource = dataSource } setFeedback = (feedback?: IFieldFeedback) => { updateFeedback(this, feedback) } setSelfErrors = (messages?: FeedbackMessage) => { this.selfErrors = messages } setSelfWarnings = (messages?: FeedbackMessage) => { this.selfWarnings = messages } setSelfSuccesses = (messages?: FeedbackMessage) => { this.selfSuccesses = messages } setValidator = (validator?: FieldValidator) => { this.validator = validator } setValidatorRule = (name: string, value: any) => { setValidatorRule(this, name, value) } setRequired = (required?: boolean) => { this.required = required } setValue = (value?: ValueType) => { if (this.destroyed) return if (!this.initialized) { if (this.display === 'none') { this.caches.value = value return } value = getValidFieldDefaultValue(value, this.initialValue) if (!allowAssignDefaultValue(this.value, value) && !this.designable) { return } } this.form.setValuesIn(this.path, value) } setInitialValue = (initialValue?: ValueType) => { if (this.destroyed) return if (!this.initialized) { if ( !allowAssignDefaultValue(this.initialValue, initialValue) && !this.designable ) { return } } this.form.setInitialValuesIn(this.path, initialValue) } setLoading = (loading?: boolean) => { setLoading(this, loading) } setValidating = (validating?: boolean) => { setValidating(this, validating) } setSubmitting = (submitting?: boolean) => { setSubmitting(this, submitting) } setState: IModelSetter<IFieldState> = createStateSetter(this) getState: IModelGetter<IFieldState> = createStateGetter(this) onInput = async (...args: any[]) => { const isHTMLInputEventFromSelf = (args: any[]) => isHTMLInputEvent(args[0]) && 'currentTarget' in args[0] ? args[0]?.target === args[0]?.currentTarget : true const getValues = (args: any[]) => { if (args[0]?.target) { if (!isHTMLInputEvent(args[0])) return args } return getValuesFromEvent(args) } if (!isHTMLInputEventFromSelf(args)) return const values = getValues(args) const value = values[0] this.caches.inputting = true this.inputValue = value this.inputValues = values this.value = value this.modify() this.notify(LifeCycleTypes.ON_FIELD_INPUT_VALUE_CHANGE) this.notify(LifeCycleTypes.ON_FORM_INPUT_CHANGE, this.form) await validateSelf(this, 'onInput') this.caches.inputting = false } onFocus = async (...args: any[]) => { if (args[0]?.target) { if (!isHTMLInputEvent(args[0], false)) return } this.active = true this.visited = true await validateSelf(this, 'onFocus') } onBlur = async (...args: any[]) => { if (args[0]?.target) { if (!isHTMLInputEvent(args[0], false)) return } this.active = false await validateSelf(this, 'onBlur') } validate = (triggerType?: ValidatorTriggerType) => { return batchValidate(this, `${this.address}.**`, triggerType) } submit = <T>(onSubmit?: (values: any) => Promise<T> | void): Promise<T> => { return batchSubmit(this, onSubmit) } reset = (options?: IFieldResetOptions) => { return batchReset(this, `${this.address}.**`, options) } queryFeedbacks = (search?: ISearchFeedback): IFieldFeedback[] => { return queryFeedbacks(this, search) } modify = () => modifySelf(this) } ``` -------------------------------------------------------------------------------- /packages/antd/src/form-item/index.tsx: -------------------------------------------------------------------------------- ```typescript import React, { useEffect, useRef, useContext, useState } from 'react' import cls from 'classnames' import { usePrefixCls, pickDataProps } from '../__builtins__' import { isVoidField } from '@formily/core' import { connect, mapProps } from '@formily/react' import { useFormLayout, FormLayoutShallowContext } from '../form-layout' import { isElement } from 'react-is' import { Tooltip, Popover, ConfigProvider } from 'antd' import { QuestionCircleOutlined, CloseCircleOutlined, CheckCircleOutlined, ExclamationCircleOutlined, } from '@ant-design/icons' export interface IFormItemProps { className?: string style?: React.CSSProperties prefixCls?: string label?: React.ReactNode colon?: boolean tooltip?: React.ReactNode | React.ComponentProps<typeof Tooltip> tooltipIcon?: React.ReactNode layout?: 'vertical' | 'horizontal' | 'inline' tooltipLayout?: 'icon' | 'text' labelStyle?: React.CSSProperties labelAlign?: 'left' | 'right' labelFor?: string labelWrap?: boolean labelWidth?: number | string wrapperWidth?: number | string labelCol?: number wrapperCol?: number wrapperAlign?: 'left' | 'right' wrapperWrap?: boolean wrapperStyle?: React.CSSProperties fullness?: boolean addonBefore?: React.ReactNode addonAfter?: React.ReactNode size?: 'small' | 'default' | 'large' inset?: boolean extra?: React.ReactNode feedbackText?: React.ReactNode feedbackLayout?: 'loose' | 'terse' | 'popover' | 'none' | (string & {}) feedbackStatus?: 'error' | 'warning' | 'success' | 'pending' | (string & {}) feedbackIcon?: React.ReactNode enableOutlineFeedback?: boolean getPopupContainer?: (node: HTMLElement) => HTMLElement asterisk?: boolean optionalMarkHidden?: boolean gridSpan?: number bordered?: boolean } type ComposeFormItem = React.FC<React.PropsWithChildren<IFormItemProps>> & { BaseItem?: React.FC<React.PropsWithChildren<IFormItemProps>> } const isTooltipProps = ( tooltip: React.ReactNode | React.ComponentProps<typeof Tooltip> ): tooltip is React.ComponentProps<typeof Tooltip> => { return !isElement(tooltip) } const useFormItemLayout = (props: IFormItemProps) => { const layout = useFormLayout() const layoutType = props.layout ?? layout.layout ?? 'horizontal' return { ...props, layout: layoutType, colon: props.colon ?? layout.colon, labelAlign: layoutType === 'vertical' ? props.labelAlign ?? 'left' : props.labelAlign ?? layout.labelAlign ?? 'right', labelWrap: props.labelWrap ?? layout.labelWrap, labelWidth: props.labelWidth ?? layout.labelWidth, wrapperWidth: props.wrapperWidth ?? layout.wrapperWidth, labelCol: props.labelCol ?? layout.labelCol, wrapperCol: props.wrapperCol ?? layout.wrapperCol, wrapperAlign: props.wrapperAlign ?? layout.wrapperAlign, wrapperWrap: props.wrapperWrap ?? layout.wrapperWrap, fullness: props.fullness ?? layout.fullness, size: props.size ?? layout.size, inset: props.inset ?? layout.inset, asterisk: props.asterisk, requiredMark: layout.requiredMark, optionalMarkHidden: props.optionalMarkHidden, bordered: props.bordered ?? layout.bordered, feedbackIcon: props.feedbackIcon, feedbackLayout: props.feedbackLayout ?? layout.feedbackLayout ?? 'loose', tooltipLayout: props.tooltipLayout ?? layout.tooltipLayout ?? 'icon', tooltipIcon: props.tooltipIcon ?? layout.tooltipIcon ?? ( <QuestionCircleOutlined /> ), } } function useOverflow< Container extends HTMLElement, Content extends HTMLElement >() { const [overflow, setOverflow] = useState(false) const containerRef = useRef<Container>() const contentRef = useRef<Content>() const layout = useFormLayout() const labelCol = JSON.stringify(layout.labelCol) useEffect(() => { requestAnimationFrame(() => { if (containerRef.current && contentRef.current) { const contentWidth = contentRef.current.getBoundingClientRect().width const containerWidth = containerRef.current.getBoundingClientRect().width if (contentWidth && containerWidth && containerWidth < contentWidth) { if (!overflow) setOverflow(true) } else { if (overflow) setOverflow(false) } } }) }, [labelCol]) return { overflow, containerRef, contentRef, } } const ICON_MAP = { error: <CloseCircleOutlined />, success: <CheckCircleOutlined />, warning: <ExclamationCircleOutlined />, } export const BaseItem: React.FC<React.PropsWithChildren<IFormItemProps>> = ({ children, ...props }) => { const [active, setActive] = useState(false) const formLayout = useFormItemLayout(props) const { locale } = useContext(ConfigProvider.ConfigContext) const { containerRef, contentRef, overflow } = useOverflow< HTMLDivElement, HTMLSpanElement >() const { label, style, layout, colon = true, addonBefore, addonAfter, asterisk, requiredMark = true, optionalMarkHidden = false, feedbackStatus, extra, feedbackText, fullness, feedbackLayout, feedbackIcon, enableOutlineFeedback = true, getPopupContainer, inset, bordered = true, labelWidth, wrapperWidth, labelCol, wrapperCol, labelAlign, wrapperAlign = 'left', size, labelWrap, wrapperWrap, tooltipLayout, tooltip, tooltipIcon, } = formLayout const labelStyle = { ...formLayout.labelStyle } const wrapperStyle = { ...formLayout.wrapperStyle } // 固定宽度 let enableCol = false if (labelWidth || wrapperWidth) { if (labelWidth) { labelStyle.width = labelWidth === 'auto' ? undefined : labelWidth labelStyle.maxWidth = labelWidth === 'auto' ? undefined : labelWidth } if (wrapperWidth) { wrapperStyle.width = wrapperWidth === 'auto' ? undefined : wrapperWidth wrapperStyle.maxWidth = wrapperWidth === 'auto' ? undefined : wrapperWidth } // 栅格模式 } if (labelCol || wrapperCol) { if (!labelStyle.width && !wrapperStyle.width && layout !== 'vertical') { enableCol = true } } const prefixCls = usePrefixCls('formily-item', props) const formatChildren = feedbackLayout === 'popover' ? ( <Popover autoAdjustOverflow placement="top" content={ <div className={cls({ [`${prefixCls}-${feedbackStatus}-help`]: !!feedbackStatus, [`${prefixCls}-help`]: true, })} > {ICON_MAP[feedbackStatus]} {feedbackText} </div> } visible={!!feedbackText} getPopupContainer={getPopupContainer} > {children} </Popover> ) : ( children ) const gridStyles: React.CSSProperties = {} const tooltipNode = isTooltipProps(tooltip) ? ( <Tooltip {...tooltip}></Tooltip> ) : ( tooltip ) const getOverflowTooltip = () => { if (overflow) { return ( <div> <div>{label}</div> <div>{tooltipNode}</div> </div> ) } return tooltipNode } const renderLabelText = () => { const labelChildren = ( <div className={`${prefixCls}-label-content`} ref={containerRef}> <span ref={contentRef}> {asterisk && requiredMark === true && ( <span className={`${prefixCls}-asterisk`}>{'*'}</span> )} <label htmlFor={props.labelFor}>{label}</label> {!asterisk && requiredMark === 'optional' && !optionalMarkHidden && ( <span className={`${prefixCls}-optional`}> {locale?.Form?.optional} </span> )} </span> </div> ) if ((tooltipLayout === 'text' && tooltip) || overflow) { return ( <Tooltip placement="top" align={{ offset: [0, 10] }} title={getOverflowTooltip()} > {labelChildren} </Tooltip> ) } return labelChildren } const renderTooltipIcon = () => { if (tooltip && tooltipLayout === 'icon' && !overflow) { return ( <span className={`${prefixCls}-label-tooltip-icon`}> <Tooltip placement="top" align={{ offset: [0, 2] }} title={tooltipNode} > {tooltipIcon} </Tooltip> </span> ) } } const renderLabel = () => { if (!label) return null return ( <div className={cls({ [`${prefixCls}-label`]: true, [`${prefixCls}-label-tooltip`]: (tooltip && tooltipLayout === 'text') || overflow, [`${prefixCls}-item-col-${labelCol}`]: enableCol && !!labelCol, })} style={labelStyle} > {renderLabelText()} {renderTooltipIcon()} {label !== ' ' && ( <span className={`${prefixCls}-colon`}>{colon ? ':' : ''}</span> )} </div> ) } return ( <div {...pickDataProps(props)} style={{ ...style, ...gridStyles, }} data-grid-span={props.gridSpan} className={cls({ [`${prefixCls}`]: true, [`${prefixCls}-layout-${layout}`]: true, [`${prefixCls}-${feedbackStatus}`]: enableOutlineFeedback && !!feedbackStatus, [`${prefixCls}-feedback-has-text`]: !!feedbackText, [`${prefixCls}-size-${size}`]: !!size, [`${prefixCls}-feedback-layout-${feedbackLayout}`]: !!feedbackLayout, [`${prefixCls}-fullness`]: !!fullness || !!inset || !!feedbackIcon, [`${prefixCls}-inset`]: !!inset, [`${prefixCls}-active`]: active, [`${prefixCls}-inset-active`]: !!inset && active, [`${prefixCls}-label-align-${labelAlign}`]: true, [`${prefixCls}-control-align-${wrapperAlign}`]: true, [`${prefixCls}-label-wrap`]: !!labelWrap, [`${prefixCls}-control-wrap`]: !!wrapperWrap, [`${prefixCls}-bordered-none`]: bordered === false || !!inset || !!feedbackIcon, [props.className]: !!props.className, })} onFocus={() => { if (feedbackIcon || inset) { setActive(true) } }} onBlur={() => { if (feedbackIcon || inset) { setActive(false) } }} > {renderLabel()} <div className={cls({ [`${prefixCls}-control`]: true, [`${prefixCls}-item-col-${wrapperCol}`]: enableCol && !!wrapperCol && label, })} > <div className={cls(`${prefixCls}-control-content`)}> {addonBefore && ( <div className={cls(`${prefixCls}-addon-before`)}> {addonBefore} </div> )} <div style={wrapperStyle} className={cls({ [`${prefixCls}-control-content-component`]: true, [`${prefixCls}-control-content-component-has-feedback-icon`]: !!feedbackIcon, })} > <FormLayoutShallowContext.Provider value={undefined}> {formatChildren} </FormLayoutShallowContext.Provider> {feedbackIcon && ( <div className={cls(`${prefixCls}-feedback-icon`)}> {feedbackIcon} </div> )} </div> {addonAfter && ( <div className={cls(`${prefixCls}-addon-after`)}>{addonAfter}</div> )} </div> {!!feedbackText && feedbackLayout !== 'popover' && feedbackLayout !== 'none' && ( <div className={cls({ [`${prefixCls}-${feedbackStatus}-help`]: !!feedbackStatus, [`${prefixCls}-help`]: true, [`${prefixCls}-help-enter`]: true, [`${prefixCls}-help-enter-active`]: true, })} > {feedbackText} </div> )} {extra && <div className={cls(`${prefixCls}-extra`)}>{extra}</div>} </div> </div> ) } // 适配 export const FormItem: ComposeFormItem = connect( BaseItem, mapProps((props, field) => { if (isVoidField(field)) return { label: field.title || props.label, asterisk: props.asterisk, extra: props.extra || field.description, } if (!field) return props const takeFeedbackStatus = () => { if (field.validating) return 'pending' return field.decoratorProps.feedbackStatus || field.validateStatus } const takeMessage = () => { const split = (messages: any[]) => { return messages.reduce((buf, text, index) => { if (!text) return buf return index < messages.length - 1 ? buf.concat([text, ', ']) : buf.concat([text]) }, []) } if (field.validating) return if (props.feedbackText) return props.feedbackText if (field.selfErrors.length) return split(field.selfErrors) if (field.selfWarnings.length) return split(field.selfWarnings) if (field.selfSuccesses.length) return split(field.selfSuccesses) } const takeAsterisk = () => { if (field.required && field.pattern !== 'readPretty') { return true } if ('asterisk' in props) { return props.asterisk } return false } return { label: props.label || field.title, feedbackStatus: takeFeedbackStatus(), feedbackText: takeMessage(), asterisk: takeAsterisk(), optionalMarkHidden: field.pattern === 'readPretty' && !('asterisk' in props), extra: props.extra || field.description, } }) ) FormItem.BaseItem = BaseItem export default FormItem ``` -------------------------------------------------------------------------------- /packages/next/docs/components/ArrayCards.md: -------------------------------------------------------------------------------- ```markdown # ArrayCards > Card list, it is more suitable to use ArrayCards for scenarios with a large number of fields in each row and more linkages > > Note: This component is only applicable to Schema scenarios ## Markup Schema example ```tsx import React from 'react' import { FormItem, Input, ArrayCards, FormButtonGroup, Submit, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="string_array" maxItems={3} x-decorator="FormItem" x-component="ArrayCards" x-component-props={{ title: 'String array', }} > <SchemaField.Void> <SchemaField.Void x-component="ArrayCards.Index" /> <SchemaField.String name="input" x-decorator="FormItem" title="Input" required x-component="Input" /> <SchemaField.Void x-component="ArrayCards.Remove" /> <SchemaField.Void x-component="ArrayCards.Copy" /> <SchemaField.Void x-component="ArrayCards.MoveUp" /> <SchemaField.Void x-component="ArrayCards.MoveDown" /> </SchemaField.Void> <SchemaField.Void x-component="ArrayCards.Addition" title="Add entry" /> </SchemaField.Array> <SchemaField.Array name="array" maxItems={3} x-decorator="FormItem" x-component="ArrayCards" x-component-props={{ title: 'Object array', }} > <SchemaField.Object> <SchemaField.Void x-component="ArrayCards.Index" /> <SchemaField.String name="input" x-decorator="FormItem" title="Input" required x-component="Input" /> <SchemaField.Void x-component="ArrayCards.Remove" /> <SchemaField.Void x-component="ArrayCards.MoveUp" /> <SchemaField.Void x-component="ArrayCards.MoveDown" /> </SchemaField.Object> <SchemaField.Void x-component="ArrayCards.Addition" title="Add entry" /> </SchemaField.Array> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## JSON Schema case ```tsx import React from 'react' import { FormItem, Input, ArrayCards, FormButtonGroup, Submit, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) const form = createForm() const schema = { type: 'object', properties: { string_array: { type: 'array', 'x-component': 'ArrayCards', maxItems: 3, 'x-decorator': 'FormItem', 'x-component-props': { title: 'String array', }, items: { type: 'void', properties: { index: { type: 'void', 'x-component': 'ArrayCards.Index', }, input: { type: 'string', 'x-decorator': 'FormItem', title: 'Input', required: true, 'x-component': 'Input', }, remove: { type: 'void', 'x-component': 'ArrayCards.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCards.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCards.MoveDown', }, }, }, properties: { addition: { type: 'void', title: 'Add entry', 'x-component': 'ArrayCards.Addition', }, }, }, array: { type: 'array', 'x-component': 'ArrayCards', maxItems: 3, 'x-decorator': 'FormItem', 'x-component-props': { title: 'Object array', }, items: { type: 'object', properties: { index: { type: 'void', 'x-component': 'ArrayCards.Index', }, input: { type: 'string', 'x-decorator': 'FormItem', title: 'Input', required: true, 'x-component': 'Input', }, remove: { type: 'void', 'x-component': 'ArrayCards.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCards.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCards.MoveDown', }, }, }, properties: { addition: { type: 'void', title: 'Add entry', 'x-component': 'ArrayCards.Addition', }, }, }, }, } export default () => { return ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## Effects linkage case ```tsx import React from 'react' import { FormItem, Input, ArrayCards, FormButtonGroup, Submit, } from '@formily/next' import { createForm, onFieldChange, onFieldReact } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) const form = createForm({ effects: () => { //Active linkage mode onFieldChange('array.*.aa', ['value'], (field, form) => { form.setFieldState(field.query('.bb'), (state) => { state.visible = field.value != '123' }) }) //Passive linkage mode onFieldReact('array.*.dd', (field) => { field.visible = field.query('.cc').get('value') != '123' }) }, }) export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="array" maxItems={3} x-component="ArrayCards" x-decorator="FormItem" x-component-props={{ title: 'Object array', }} > <SchemaField.Object> <SchemaField.Void x-component="ArrayCards.Index" /> <SchemaField.String name="aa" x-decorator="FormItem" title="AA" required description="AA hide BB when entering 123" x-component="Input" /> <SchemaField.String name="bb" x-decorator="FormItem" title="BB" required x-component="Input" /> <SchemaField.String name="cc" x-decorator="FormItem" title="CC" required description="Hide DD when CC enters 123" x-component="Input" /> <SchemaField.String name="dd" x-decorator="FormItem" title="DD" required x-component="Input" /> <SchemaField.Void x-component="ArrayCards.Remove" /> <SchemaField.Void x-component="ArrayCards.MoveUp" /> <SchemaField.Void x-component="ArrayCards.MoveDown" /> </SchemaField.Object> <SchemaField.Void x-component="ArrayCards.Addition" title="Add entry" /> </SchemaField.Array> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## JSON Schema linkage case ```tsx import React from 'react' import { FormItem, Input, ArrayCards, FormButtonGroup, Submit, } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) const form = createForm() const schema = { type: 'object', properties: { array: { type: 'array', 'x-component': 'ArrayCards', maxItems: 3, title: 'Object array', items: { type: 'object', properties: { index: { type: 'void', 'x-component': 'ArrayCards.Index', }, aa: { type: 'string', 'x-decorator': 'FormItem', title: 'AA', required: true, 'x-component': 'Input', description: 'Enter 123', }, bb: { type: 'string', title: 'BB', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-reactions': [ { dependencies: ['.aa'], when: "{{$deps[0] != '123'}}", fulfill: { schema: { title: 'BB', 'x-disabled': true, }, }, otherwise: { schema: { title: 'Changed', 'x-disabled': false, }, }, }, ], }, remove: { type: 'void', 'x-component': 'ArrayCards.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCards.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCards.MoveDown', }, }, }, properties: { addition: { type: 'void', title: 'Add entry', 'x-component': 'ArrayCards.Addition', }, }, }, }, } export default () => { return ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## API ### ArrayCards Extended attributes | Property name | Type | Description | Default value | | ------------- | ------------------------- | --------------- | ------------- | | onAdd | `(index: number) => void` | add method | | | onRemove | `(index: number) => void` | remove method | | | onCopy | `(index: number) => void` | copy method | | | onMoveUp | `(index: number) => void` | moveUp method | | | onMoveDown | `(index: number) => void` | moveDown method | | Other Reference https://fusion.design/pc/component/basic/card ### ArrayCards.Addition > Add button Extended attributes | Property name | Type | Description | Default value | | ------------- | -------------------- | ------------- | ------------- | | title | ReactText | Copywriting | | | method | `'push' \|'unshift'` | add method | `'push'` | | defaultValue | `any` | Default value | | Other references https://fusion.design/pc/component/basic/button Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective ### ArrayCards.Copy > Copy button Extended attributes | Property name | Type | Description | Default value | | ------------- | -------------------- | ----------- | ------------- | | title | ReactText | Copywriting | | | method | `'push' \|'unshift'` | add method | `'push'` | Other references https://fusion.design/pc/component/basic/button Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective ### ArrayCards.Remove > Delete button | Property name | Type | Description | Default value | | ------------- | --------- | ----------- | ------------- | | title | ReactText | Copywriting | | Other references https://ant.design/components/icon-cn/ Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective ### ArrayCards.MoveDown > Move down button | Property name | Type | Description | Default value | | ------------- | --------- | ----------- | ------------- | | title | ReactText | Copywriting | | Other references https://ant.design/components/icon-cn/ Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective ### ArrayCards.MoveUp > Move up button | Property name | Type | Description | Default value | | ------------- | --------- | ----------- | ------------- | | title | ReactText | Copywriting | | Other references https://ant.design/components/icon-cn/ Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective ### ArrayCards.Index > Index Renderer No attributes ### ArrayCards.useIndex > Read the React Hook of the current rendering row index ### ArrayCards.useRecord > Read the React Hook of the current rendering row ``` -------------------------------------------------------------------------------- /packages/validator/src/__tests__/validator.spec.ts: -------------------------------------------------------------------------------- ```typescript import { validate, registerValidateRules, registerValidateFormats, setValidateLanguage, registerValidateMessageTemplateEngine, } from '../index' registerValidateRules({ custom: (value) => (value === '123' ? 'custom error' : ''), customBool: () => false, customBool2: () => true, }) registerValidateFormats({ custom: /^[\u4e00-\u9fa5]+$/, }) const hasError = (results: any, message?: string) => { if (!message) { return expect(results?.error?.[0]).not.toBeUndefined() } return expect(results?.error?.[0]).toEqual(message) } const noError = (results: any) => { return expect(results?.error?.[0]).toBeUndefined() } test('empty string validate', async () => { const results = await validate('', { required: true }) expect(results).toEqual({ error: ['The field value is required'], success: [], warning: [], }) }) test('empty array validate', async () => { const results = await validate([], { required: true }) expect(results).toEqual({ error: ['The field value is required'], success: [], warning: [], }) noError(await validate([''], { required: true })) }) test('empty object validate', async () => { const results = await validate({}, { required: true }) expect(results).toEqual({ error: ['The field value is required'], success: [], warning: [], }) }) test('empty number validate', async () => { const results = await validate(0, { required: true }) expect(results).toEqual({ error: [], success: [], warning: [], }) }) test('multi validate', async () => { const results = await validate('', { required: true, validator() { return 'validate error' }, }) expect(results).toEqual({ error: ['The field value is required', 'validate error'], success: [], warning: [], }) }) test('message scope', async () => { const results = await validate( '', { required: true, validator() { return 'validate error {{name}}' }, }, { context: { name: 'scopeName', }, } ) expect(results).toEqual({ error: ['The field value is required', 'validate error scopeName'], success: [], warning: [], }) }) test('first validate', async () => { const results = await validate( '', { required: true, validator() { return 'validate error' }, }, { validateFirst: true, } ) expect(results).toEqual({ error: ['The field value is required'], success: [], warning: [], }) }) test('custom validate results', async () => { const results = await validate('', { validator() { return { type: 'error', message: 'validate error' } }, }) expect(results).toEqual({ error: ['validate error'], success: [], warning: [], }) }) test('exception validate', async () => { const results1 = await validate('', { validator() { throw new Error('validate error') }, }) expect(results1).toEqual({ error: ['validate error'], success: [], warning: [], }) const results2 = await validate('', { validator() { throw 'custom string' }, }) expect(results2).toEqual({ error: ['custom string'], success: [], warning: [], }) }) test('max/maxItems/maxLength/minItems/minLength/min/maximum/exclusiveMaximum/minimum/exclusiveMinimum/len', async () => { hasError(await validate(6, { max: 5 })) hasError(await validate(6, { maxLength: 5 })) hasError(await validate(6, { maxItems: 5 })) noError(await validate(5, { max: 5 })) noError(await validate(5, { maxLength: 5 })) noError(await validate(5, { maxItems: 5 })) hasError(await validate([1, 2, 3, 4, 5, 6], { max: 5 })) hasError(await validate([1, 2, 3, 4, 5, 6], { maxLength: 5 })) hasError(await validate([1, 2, 3, 4, 5, 6], { maxItems: 5 })) noError(await validate([1, 2, 3, 4, 5], { max: 5 })) noError(await validate([1, 2, 3, 4, 5], { maxLength: 5 })) noError(await validate([1, 2, 3, 4, 5], { maxItems: 5 })) hasError(await validate('123456', { max: 5 })) hasError(await validate('123456', { maxLength: 5 })) hasError(await validate('123456', { maxItems: 5 })) noError(await validate('12345', { max: 5 })) noError(await validate('12345', { maxLength: 5 })) noError(await validate('12345', { maxItems: 5 })) hasError(await validate(2, { min: 3 })) hasError(await validate(2, { minLength: 3 })) hasError(await validate(2, { minItems: 3 })) noError(await validate(3, { min: 3 })) noError(await validate(3, { minLength: 3 })) noError(await validate(3, { minItems: 3 })) hasError(await validate([1, 2], { min: 3 })) hasError(await validate([1, 2], { minLength: 3 })) hasError(await validate([1, 2], { minItems: 3 })) noError(await validate([1, 2, 3], { min: 3 })) noError(await validate([1, 2, 3], { minLength: 3 })) noError(await validate([1, 2, 3], { minItems: 3 })) hasError(await validate('12', { min: 3 })) hasError(await validate('12', { minLength: 3 })) hasError(await validate('12', { minItems: 3 })) noError(await validate('123', { min: 3 })) noError(await validate('123', { minLength: 3 })) noError(await validate('123', { minItems: 3 })) hasError(await validate(6, { maximum: 5 })) noError(await validate(5, { maximum: 5 })) hasError(await validate([1, 2, 3, 4, 5, 6], { maximum: 5 })) noError(await validate([1, 2, 3, 4, 5], { maximum: 5 })) hasError(await validate('123456', { maximum: 5 })) noError(await validate('12345', { maximum: 5 })) hasError(await validate(2, { minimum: 3 })) noError(await validate(3, { minimum: 3 })) hasError(await validate([1, 2], { minimum: 3 })) noError(await validate([1, 2, 3], { minimum: 3 })) hasError(await validate('12', { minimum: 3 })) noError(await validate('123', { minimum: 3 })) hasError(await validate(6, { exclusiveMaximum: 5 })) hasError(await validate(5, { exclusiveMaximum: 5 })) hasError(await validate([1, 2, 3, 4, 5, 6], { exclusiveMaximum: 5 })) hasError(await validate([1, 2, 3, 4, 5], { exclusiveMaximum: 5 })) hasError(await validate('123456', { exclusiveMaximum: 5 })) hasError(await validate('12345', { exclusiveMaximum: 5 })) hasError(await validate(2, { exclusiveMinimum: 3 })) hasError(await validate(3, { exclusiveMinimum: 3 })) hasError(await validate([1, 2], { exclusiveMinimum: 3 })) hasError(await validate([1, 2, 3], { exclusiveMinimum: 3 })) hasError(await validate('12', { exclusiveMinimum: 3 })) hasError(await validate('123', { exclusiveMinimum: 3 })) hasError(await validate('1234', { len: 3 })) hasError(await validate({ aa: 1, bb: 2, cc: 3 }, { maxProperties: 2 })) noError(await validate({ aa: 1, cc: 3 }, { maxProperties: 2 })) hasError(await validate({ aa: 1 }, { minProperties: 2 })) noError(await validate({ aa: 1, bb: 2, cc: 3 }, { minProperties: 2 })) noError(await validate({ aa: 1, cc: 3 }, { maxProperties: 2 })) }) test('const', async () => { noError(await validate('', { const: '123' })) noError(await validate('123', { const: '123' })) hasError(await validate('xxx', { const: '123' })) }) test('multipleOf', async () => { noError(await validate('', { multipleOf: 2 })) noError(await validate(4, { multipleOf: 2 })) hasError(await validate(3, { multipleOf: 2 })) }) test('uniqueItems', async () => { noError(await validate('', { uniqueItems: true })) noError(await validate(4, { uniqueItems: true })) hasError(await validate([1, 2], { uniqueItems: true })) hasError( await validate([{ label: '11', value: '11' }, { label: '11' }], { uniqueItems: true, }) ) noError(await validate([1, 1], { uniqueItems: true })) noError( await validate( [ { label: '11', value: '11' }, { label: '11', value: '11' }, ], { uniqueItems: true } ) ) }) test('pattern', async () => { hasError(await validate('aaa', { pattern: /^\d+$/ })) }) test('validator', async () => { hasError( await validate('aaa', { validator() { return false }, message: 'error', }), 'error' ) }) test('whitespace', async () => { hasError( await validate(' ', { whitespace: true, }) ) }) test('enum', async () => { hasError( await validate('11', { enum: ['22', '33'], }) ) noError( await validate('11', { enum: ['22', '33', '11'], }) ) }) test('filter trigger type(unmatch)', async () => { expect( await validate( '', { triggerType: 'onBlur', required: true, validator() { return 'validate error' }, }, { validateFirst: true, triggerType: 'onInput', } ) ).toEqual({ error: [], success: [], warning: [], }) }) test('filter trigger type(match first validate)', async () => { expect( await validate( '', { triggerType: 'onBlur', required: true, validator() { return 'validate error' }, }, { validateFirst: true, triggerType: 'onBlur', } ) ).toEqual({ error: ['The field value is required'], success: [], warning: [], }) }) test('filter trigger type(match multi validate)', async () => { expect( await validate( '', { triggerType: 'onBlur', required: true, validator() { return 'validate error' }, }, { triggerType: 'onBlur', } ) ).toEqual({ error: ['The field value is required', 'validate error'], success: [], warning: [], }) }) test('validate formats(date)', async () => { noError(await validate('', 'date')) hasError(await validate('2020-1', 'date')) hasError(await validate('2020-01- 11:23:33', 'date')) hasError(await validate('12/01/', 'date')) noError(await validate('2020-1-12', 'date')) noError(await validate('2020/1/12', 'date')) noError(await validate('2020-01-12', 'date')) noError(await validate('2020/01/12', 'date')) noError(await validate('12/01/2020', 'date')) noError(await validate('2020-01-12 11:23:33', 'date')) noError(await validate('2020/01/12 11:23:33', 'date')) noError(await validate('12/01/2020 11:23:33', 'date')) noError(await validate('12/1/2020 11:23:33', 'date')) }) test('validate formats(number)', async () => { noError(await validate('', 'number')) hasError(await validate('12323d', 'number')) noError(await validate('12323', 'number')) noError(await validate('12323.12', 'number')) noError(await validate('-12323.12', 'number')) noError(await validate('+12323.12', 'number')) }) test('validate formats(integer)', async () => { noError(await validate('', 'integer')) hasError(await validate('222.333', 'integer')) noError(await validate('12323', 'integer')) }) test('validate formats(phone)', async () => { noError(await validate('', 'phone')) hasError(await validate('222333', 'phone')) noError(await validate('15934567899', 'phone')) }) test('validate formats(money)', async () => { noError(await validate('$12', 'money')) hasError(await validate('$12.', 'money')) noError(await validate('$12.3', 'money')) }) test('validate custom validator', async () => { hasError(await validate('123', { custom: true })) noError(await validate('', { custom: true })) }) test('validate custom formats', async () => { hasError(await validate('aa asd', 'custom')) hasError(await validate('aa asd 中文', 'custom')) noError(await validate('中文', 'custom')) }) test('validate undefined format', async () => { expect( ( await validate('a', { required: false, pattern: '(\\d{3,4}-\\d{7,8}-\\d{4})|(4\\d{4,9})|(\\d{3,4}-\\d{7,8})', format: undefined, message: 'error', }) ).error ).toEqual(['error']) }) test('validator return boolean', async () => { hasError( await validate('123', { customBool: true, message: 'custom error', }), 'custom error' ) noError( await validate('123', { customBool2: true, message: 'custom error', }) ) }) test('language', async () => { setValidateLanguage('zh-CN') hasError( await validate('', { required: true, }), '该字段是必填字段' ) setValidateLanguage('en-US') hasError( await validate('', { required: true, }), 'The field value is required' ) }) test('validator template', async () => { registerValidateMessageTemplateEngine((message) => { if (typeof message !== 'string') return message return message.replace(/\<\<\s*([\w.]+)\s*\>\>/g, (_, $0) => { return { aa: 123 }[$0] }) }) hasError( await validate('', () => { return `<<aa>>=123` }), '123=123' ) }) test('validator template with format', async () => { registerValidateMessageTemplateEngine((message) => { if (typeof message !== 'string') return message return message.replace(/\<\<\s*([\w.]+)\s*\>\>/g, (_, $0) => { return { aa: 123 }[$0] }) }) hasError( await validate('', (value, rules, ctx, format) => { return `<<aa>>=123&${format('<<aa>>')}` }), '123=123&123' ) }) test('validator template with format and scope', async () => { registerValidateMessageTemplateEngine((message) => { if (typeof message !== 'string') return message return message.replace(/\<\<\s*([\w.]+)\s*\>\>/g, (_, $0) => { return { aa: 123 }[$0] }) }) const result = await validate( '', (value, rules, ctx, format) => { return `<<aa>>=123&${format('<<aa>>{{name}}')}` }, { context: { name: 'scopeName', }, } ) expect(result.error[0]).toEqual('123=123&123scopeName') }) test('validator order with format', async () => { hasError( await validate('', [ { required: true }, { format: 'url', }, ]), 'The field value is required' ) }) ``` -------------------------------------------------------------------------------- /docs/guide/advanced/controlled.md: -------------------------------------------------------------------------------- ```markdown # Form Controlled Formily 2.x has given up supporting controlled mode for form components and field components. Because the internal management state mode of the form itself is not a controlled mode, there will be many boundary problems in the process of changing the controlled mode to the uncontrolled mode. At the same time, the controlled mode will have a large number of dirty inspection processes, and the performance is very poor. Instead, the controlled mode itself can solve most of the problems. So Formily no longer supports the controlled mode, but if we insist on implementing ordinary React controlled, we can still support it. It can only achieve value control, not field-level control, which is the Field component we use. The properties will only take effect during the first rendering. Any changes to the properties in the future will not be automatically updated. If you want to update automatically, unless you recreate the Form instance (obviously this will lose all the previously maintained state). Therefore, we more recommend using [@formily/reactive](https://reactive.formilyjs.org) to achieve responsive control, which can achieve both value control and field-level control. ## Value Controlled Ordinary controlled mode, which will rely heavily on dirty checking to achieve data synchronization, and the number of component renderings will be very high. ```tsx import React, { useMemo, useState, useEffect, useRef } from 'react' import { createForm, onFormValuesChange } from '@formily/core' import { createSchemaField } from '@formily/react' import { Form, FormItem, Input } from '@formily/antd' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const MyForm = (props) => { const form = useMemo( () => createForm({ values: props.values, effects: () => { onFormValuesChange((form) => { props.onChange(form.values) }) }, }), [] ) const count = useRef(1) useEffect(() => { form.setValues(props.values, 'overwrite') }, [JSON.stringify(props.values)]) return ( <Form form={form}> <SchemaField> <SchemaField.String name="input" x-decorator="FormItem" x-component="Input" x-component-props={{ placeholder: 'controlled target' }} /> </SchemaField> Form component rendering times:{count.current++} </Form> ) } export default () => { const [values, setValues] = useState({ input: '' }) const count = useRef(1) return ( <> <FormItem> <Input value={values.input} placeholder="controller" onChange={(event) => { setValues({ ...values, input: event.target.value }) }} /> </FormItem> <MyForm values={values} onChange={(values) => { setValues({ ...values }) }} /> root component rendering times: {count.current++} </> ) } ``` ## Responsive Value Controlled Responsive control is mainly to use [@formily/reactive](https://reactive.formilyjs.org) to achieve responsive updates, we can easily achieve two-way binding, while the performance is full of normal controlled updates. ```tsx import React, { useMemo, useRef } from 'react' import { createForm } from '@formily/core' import { createSchemaField } from '@formily/react' import { Form, FormItem, Input } from '@formily/antd' import { observable } from '@formily/reactive' import { observer } from '@formily/reactive-react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const MyForm = (props) => { const count = useRef(1) const form = useMemo( () => createForm({ values: props.values, }), [] ) return ( <Form form={form}> <SchemaField> <SchemaField.String name="input" x-decorator="FormItem" x-component="Input" x-component-props={{ placeholder: 'controlled target' }} /> </SchemaField> Form component rendering times:{count.current++} </Form> ) } const Controller = observer((props) => { const count = useRef(1) return ( <FormItem> <Input value={props.values.input} placeholder="controller" onChange={(event) => { props.values.input = event.target.value }} /> Controller component rendering times:{count.current++} </FormItem> ) }) export default () => { const count = useRef(1) const values = useMemo(() => observable({ input: '', }) ) return ( <> <Controller values={values} /> <MyForm values={values} /> root component rendering times:{count.current++} </> ) } ``` ## Schema Controlled There will be a requirement for the form configuration scenario. The Schema of the form will change frequently. In fact, it is equivalent to frequently creating new forms. The state of the previous operation should be discarded. ```tsx import React, { useMemo, useState } from 'react' import { createForm } from '@formily/core' import { createSchemaField } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' import { Button, Space } from 'antd' const SchemaField = createSchemaField({ components: { Input, FormItem, Select, }, }) export default () => { const [current, setCurrent] = useState({}) const form = useMemo(() => createForm(), [current]) return ( <Form form={form} layout="vertical"> <Space style={{ marginBottom: 20 }}> <Button onClick={() => { setCurrent({ type: 'object', properties: { aa: { type: 'string', title: 'AA', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { placeholder: 'Input', }, }, }, }) }} > Schema1 </Button> <Button onClick={() => { setCurrent({ type: 'object', properties: { aa: { type: 'string', title: 'AA', 'x-decorator': 'FormItem', enum: [ { label: '111', value: '111', }, { label: '222', value: '222' }, ], 'x-component': 'Select', 'x-component-props': { placeholder: 'Select', }, }, bb: { type: 'string', title: 'BB', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }) }} > Schema2 </Button> </Space> <SchemaField schema={current} /> </Form> ) } ``` ## Schema fragment linkage (top level control) The most important thing for fragment linkage is to manually clean up the field model, otherwise the UI cannot be synchronized ```tsx import React, { useMemo, useRef } from 'react' import { createForm } from '@formily/core' import { createSchemaField, observer } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' const SchemaField = createSchemaField({ components: { Input, FormItem, Select, }, }) const DYNAMIC_INJECT_SCHEMA = { type_1: { type: 'void', properties: { aa: { type: 'string', title: 'AA', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { placeholder: 'Input', }, }, }, }, type_2: { type: 'void', properties: { aa: { type: 'string', title: 'AA', 'x-decorator': 'FormItem', enum: [ { label: '111', value: '111', }, { label: '222', value: '222' }, ], 'x-component': 'Select', 'x-component-props': { placeholder: 'Select', }, }, bb: { type: 'string', title: 'BB', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, } const App = observer(() => { const oldTypeRef = useRef() const form = useMemo(() => createForm(), []) const currentType = form.values.type const schema = { type: 'object', properties: { type: { type: 'string', title: 'Type', enum: [ { label: 'type 1', value: 'type_1' }, { label: 'type 2', value: 'type_2' }, ], 'x-decorator': 'FormItem', 'x-component': 'Select', }, container: DYNAMIC_INJECT_SCHEMA[currentType], }, } if (oldTypeRef.current !== currentType) { form.clearFormGraph('container.*') //Recycle field model } oldTypeRef.current = currentType return ( <Form form={form} layout="vertical"> <SchemaField schema={schema} /> </Form> ) }) export default App ``` ## Schema fragment linkage (custom component) ```tsx import React, { useMemo, useState, useEffect } from 'react' import { createForm } from '@formily/core' import { createSchemaField, RecursionField, useForm, useField, observer, } from '@formily/react' import { Form, FormItem, Input, Select } from '@formily/antd' const Custom = observer(() => { const field = useField() const form = useForm() const [schema, setSchema] = useState({}) useEffect(() => { form.clearFormGraph(`${field.address}.*`) //Recycle field model //Can be obtained asynchronously setSchema(DYNAMIC_INJECT_SCHEMA[form.values.type]) }, [form.values.type]) return ( <RecursionField basePath={field.address} schema={schema} onlyRenderProperties /> ) }) const SchemaField = createSchemaField({ components: { Input, FormItem, Select, Custom, }, }) const DYNAMIC_INJECT_SCHEMA = { type_1: { type: 'void', properties: { aa: { type: 'string', title: 'AA', 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { placeholder: 'Input', }, }, }, }, type_2: { type: 'void', properties: { aa: { type: 'string', title: 'AA', 'x-decorator': 'FormItem', enum: [ { label: '111', value: '111', }, { label: '222', value: '222' }, ], 'x-component': 'Select', 'x-component-props': { placeholder: 'Select', }, }, bb: { type: 'string', title: 'BB', 'x-decorator': 'FormItem', 'x-component': 'Input', }, }, }, } const App = observer(() => { const form = useMemo(() => createForm(), []) const schema = { type: 'object', properties: { type: { type: 'string', title: 'Type', enum: [ { label: 'type 1', value: 'type_1' }, { label: 'type 2', value: 'type_2' }, ], 'x-decorator': 'FormItem', 'x-component': 'Select', }, container: { type: 'object', 'x-component': 'Custom', }, }, } return ( <Form form={form} layout="vertical"> <SchemaField schema={schema} /> </Form> ) }) export default App ``` ## Field Level Control ### Best Practices It is recommended to use [@formily/reactive](https://reactive.formilyjs.org) to achieve responsive control. ```tsx import React from 'react' import { createForm } from '@formily/core' import { createSchemaField } from '@formily/react' import { Form, FormItem, Input } from '@formily/antd' import { observable } from '@formily/reactive' import { observer } from '@formily/reactive-react' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() const obs = observable({ input: '', }) const Controller = observer(() => { return ( <FormItem> <Input value={obs.input} placeholder="controller" onChange={(event) => { obs.input = event.target.value }} /> </FormItem> ) }) export default () => { return ( <> <Controller /> <Form form={form}> <SchemaField> <SchemaField.String name="input" x-decorator="FormItem" x-component="Input" x-component-props={{ placeholder: 'controlled target' }} x-reactions={(field) => { field.component[1].placeholder = obs.input || 'controlled target' }} /> </SchemaField> </Form> </> ) } ``` ### Anti-pattern It is not possible to update automatically when using traditional controlled mode. ```tsx import React, { useState } from 'react' import { createForm } from '@formily/core' import { createSchemaField } from '@formily/react' import { Form, FormItem, Input } from '@formily/antd' const SchemaField = createSchemaField({ components: { Input, FormItem, }, }) const form = createForm() export default () => { const [value, setValue] = useState('') return ( <> <FormItem> <Input value={value} placeholder="controller" onChange={(event) => { setValue(event.target.value) }} /> </FormItem> <Form form={form}> <SchemaField> <SchemaField.String name="input" x-decorator="FormItem" x-component="Input" x-component-props={{ placeholder: value || 'controlled target' }} /> </SchemaField> </Form> </> ) } ``` ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/ArrayCards.md: -------------------------------------------------------------------------------- ```markdown # ArrayCards > Card list, it is more suitable to use ArrayCards for scenarios with a large number of fields in each row and more linkages > > Note: This component is only applicable to Schema scenarios ## Markup Schema example ```tsx import React from 'react' import { FormItem, Input, ArrayCards, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) const form = createForm() export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="string_array" maxItems={3} x-decorator="FormItem" x-component="ArrayCards" x-component-props={{ title: 'String array', }} > <SchemaField.Void> <SchemaField.Void x-component="ArrayCards.Index" /> <SchemaField.String name="input" x-decorator="FormItem" title="Input" required x-component="Input" /> <SchemaField.Void x-component="ArrayCards.Remove" /> <SchemaField.Void x-component="ArrayCards.Copy" /> <SchemaField.Void x-component="ArrayCards.MoveUp" /> <SchemaField.Void x-component="ArrayCards.MoveDown" /> </SchemaField.Void> <SchemaField.Void x-component="ArrayCards.Addition" title="Add entry" /> </SchemaField.Array> <SchemaField.Array name="array" maxItems={3} x-decorator="FormItem" x-component="ArrayCards" x-component-props={{ title: 'Object array', }} > <SchemaField.Object> <SchemaField.Void x-component="ArrayCards.Index" /> <SchemaField.String name="input" x-decorator="FormItem" title="Input" required x-component="Input" /> <SchemaField.Void x-component="ArrayCards.Remove" /> <SchemaField.Void x-component="ArrayCards.MoveUp" /> <SchemaField.Void x-component="ArrayCards.MoveDown" /> </SchemaField.Object> <SchemaField.Void x-component="ArrayCards.Addition" title="Add entry" /> </SchemaField.Array> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## JSON Schema case ```tsx import React from 'react' import { FormItem, Input, ArrayCards, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) const form = createForm() const schema = { type: 'object', properties: { string_array: { type: 'array', 'x-component': 'ArrayCards', maxItems: 3, 'x-decorator': 'FormItem', 'x-component-props': { title: 'String array', }, items: { type: 'void', properties: { index: { type: 'void', 'x-component': 'ArrayCards.Index', }, input: { type: 'string', 'x-decorator': 'FormItem', title: 'Input', required: true, 'x-component': 'Input', }, remove: { type: 'void', 'x-component': 'ArrayCards.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCards.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCards.MoveDown', }, }, }, properties: { addition: { type: 'void', title: 'Add entry', 'x-component': 'ArrayCards.Addition', }, }, }, array: { type: 'array', 'x-component': 'ArrayCards', maxItems: 3, 'x-decorator': 'FormItem', 'x-component-props': { title: 'Object array', }, items: { type: 'object', properties: { index: { type: 'void', 'x-component': 'ArrayCards.Index', }, input: { type: 'string', 'x-decorator': 'FormItem', title: 'Input', required: true, 'x-component': 'Input', }, remove: { type: 'void', 'x-component': 'ArrayCards.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCards.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCards.MoveDown', }, }, }, properties: { addition: { type: 'void', title: 'Add entry', 'x-component': 'ArrayCards.Addition', }, }, }, }, } export default () => { return ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## Effects linkage case ```tsx import React from 'react' import { FormItem, Input, ArrayCards, FormButtonGroup, Submit, } from '@formily/antd' import { createForm, onFieldChange, onFieldReact } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) const form = createForm({ effects: () => { //Active linkage mode onFieldChange('array.*.aa', ['value'], (field, form) => { form.setFieldState(field.query('.bb'), (state) => { state.visible = field.value != '123' }) }) //Passive linkage mode onFieldReact('array.*.dd', (field) => { field.visible = field.query('.cc').get('value') != '123' }) }, }) export default () => { return ( <FormProvider form={form}> <SchemaField> <SchemaField.Array name="array" maxItems={3} x-component="ArrayCards" x-decorator="FormItem" x-component-props={{ title: 'Object array', }} > <SchemaField.Object> <SchemaField.Void x-component="ArrayCards.Index" /> <SchemaField.String name="aa" x-decorator="FormItem" title="AA" required description="AA hide BB when entering 123" x-component="Input" /> <SchemaField.String name="bb" x-decorator="FormItem" title="BB" required x-component="Input" /> <SchemaField.String name="cc" x-decorator="FormItem" title="CC" required description="Hide DD when CC enters 123" x-component="Input" /> <SchemaField.String name="dd" x-decorator="FormItem" title="DD" required x-component="Input" /> <SchemaField.Void x-component="ArrayCards.Remove" /> <SchemaField.Void x-component="ArrayCards.MoveUp" /> <SchemaField.Void x-component="ArrayCards.MoveDown" /> </SchemaField.Object> <SchemaField.Void x-component="ArrayCards.Addition" title="Add entry" /> </SchemaField.Array> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## JSON Schema linkage case ```tsx import React from 'react' import { FormItem, Input, ArrayCards, FormButtonGroup, Submit, } from '@formily/antd' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { FormItem, Input, ArrayCards, }, }) const form = createForm() const schema = { type: 'object', properties: { array: { type: 'array', 'x-component': 'ArrayCards', maxItems: 3, title: 'Object array', items: { type: 'object', properties: { index: { type: 'void', 'x-component': 'ArrayCards.Index', }, aa: { type: 'string', 'x-decorator': 'FormItem', title: 'AA', required: true, 'x-component': 'Input', description: 'Enter 123', }, bb: { type: 'string', title: 'BB', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-reactions': [ { dependencies: ['.aa'], when: "{{$deps[0] != '123'}}", fulfill: { schema: { title: 'BB', 'x-disabled': true, }, }, otherwise: { schema: { title: 'Changed', 'x-disabled': false, }, }, }, ], }, remove: { type: 'void', 'x-component': 'ArrayCards.Remove', }, moveUp: { type: 'void', 'x-component': 'ArrayCards.MoveUp', }, moveDown: { type: 'void', 'x-component': 'ArrayCards.MoveDown', }, }, }, properties: { addition: { type: 'void', title: 'Add entry', 'x-component': 'ArrayCards.Addition', }, }, }, }, } export default () => { return ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> ) } ``` ## API ### ArrayCards Extended attributes | Property name | Type | Description | Default value | | ------------- | ------------------------- | --------------- | ------------- | | onAdd | `(index: number) => void` | add method | | | onRemove | `(index: number) => void` | remove method | | | onCopy | `(index: number) => void` | copy method | | | onMoveUp | `(index: number) => void` | moveUp method | | | onMoveDown | `(index: number) => void` | moveDown method | | Other Reference https://ant.design/components/card-cn/ ### ArrayCards.Addition > Add button Extended attributes | Property name | Type | Description | Default value | | ------------- | -------------------- | ------------- | ------------- | | title | ReactText | Copywriting | | | method | `'push' \|'unshift'` | add method | `'push'` | | defaultValue | `any` | Default value | | Other references https://ant.design/components/button-cn/ Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective Note: You can disable default behavior with `onClick={e => e.preventDefault()}` in props. ### ArrayCards.Copy > Copy button Extended attributes | Property name | Type | Description | Default value | | ------------- | -------------------- | ----------- | ------------- | | title | ReactText | Copywriting | | | method | `'push' \|'unshift'` | Copy method | `'push'` | Other references https://ant.design/components/button-cn/ Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective Note: You can disable default behavior with `onClick={e => e.preventDefault()}` in props. ### ArrayCards.Remove > Delete button | Property name | Type | Description | Default value | | ------------- | --------- | ----------- | ------------- | | title | ReactText | Copywriting | | Other references https://ant.design/components/icon-cn/ Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective Note: You can disable default behavior with `onClick={e => e.preventDefault()}` in props. ### ArrayCards.MoveDown > Move down button | Property name | Type | Description | Default value | | ------------- | --------- | ----------- | ------------- | | title | ReactText | Copywriting | | Other references https://ant.design/components/icon-cn/ Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective Note: You can disable default behavior with `onClick={e => e.preventDefault()}` in props. ### ArrayCards.MoveUp > Move up button | Property name | Type | Description | Default value | | ------------- | --------- | ----------- | ------------- | | title | ReactText | Copywriting | | Other references https://ant.design/components/icon-cn/ Note: The title attribute can receive the title mapping in the Field model, that is, uploading the title in the Field is also effective Note: You can disable default behavior with `onClick={e => e.preventDefault()}` in props. ### ArrayCards.Index > Index Renderer No attributes ### ArrayCards.useIndex > Read the React Hook of the current rendering row index ### ArrayCards.useRecord > Read the React Hook of the current rendering row ```