This is page 19 of 52. Use http://codebase.md/alibaba/formily?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .all-contributorsrc ├── .codecov.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows │ ├── check-pr-title.yml │ ├── ci.yml │ ├── commitlint.yml │ ├── issue-open-check.yml │ ├── package-size.yml │ └── pr-welcome.yml ├── .gitignore ├── .prettierrc.js ├── .umirc.js ├── .vscode │ └── cspell.json ├── .yarnrc ├── CHANGELOG.md ├── commitlint.config.js ├── devtools │ ├── .eslintrc │ └── chrome-extension │ ├── .npmignore │ ├── assets │ │ └── img │ │ ├── loading.svg │ │ └── logo │ │ ├── 128x128.png │ │ ├── 16x16.png │ │ ├── 38x38.png │ │ ├── 48x48.png │ │ ├── error.png │ │ ├── gray.png │ │ └── scalable.png │ ├── config │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── LICENSE.md │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── FieldTree.tsx │ │ │ │ ├── filter.ts │ │ │ │ ├── LeftPanel.tsx │ │ │ │ ├── RightPanel.tsx │ │ │ │ ├── SearchBox.tsx │ │ │ │ └── Tabs.tsx │ │ │ ├── demo.tsx │ │ │ └── index.tsx │ │ └── extension │ │ ├── backend.ts │ │ ├── background.ts │ │ ├── content.ts │ │ ├── devpanel.tsx │ │ ├── devtools.tsx │ │ ├── inject.ts │ │ ├── manifest.json │ │ ├── popup.tsx │ │ └── views │ │ ├── devpanel.ejs │ │ ├── devtools.ejs │ │ └── popup.ejs │ ├── tsconfig.build.json │ └── tsconfig.json ├── docs │ ├── functions │ │ ├── contributors.ts │ │ └── npm-search.ts │ ├── guide │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── async.zh-CN.md │ │ │ ├── build.md │ │ │ ├── build.zh-CN.md │ │ │ ├── business-logic.md │ │ │ ├── business-logic.zh-CN.md │ │ │ ├── calculator.md │ │ │ ├── calculator.zh-CN.md │ │ │ ├── controlled.md │ │ │ ├── controlled.zh-CN.md │ │ │ ├── custom.md │ │ │ ├── custom.zh-CN.md │ │ │ ├── destructor.md │ │ │ ├── destructor.zh-CN.md │ │ │ ├── input.less │ │ │ ├── layout.md │ │ │ ├── layout.zh-CN.md │ │ │ ├── linkages.md │ │ │ ├── linkages.zh-CN.md │ │ │ ├── validate.md │ │ │ └── validate.zh-CN.md │ │ ├── contribution.md │ │ ├── contribution.zh-CN.md │ │ ├── form-builder.md │ │ ├── form-builder.zh-CN.md │ │ ├── index.md │ │ ├── index.zh-CN.md │ │ ├── issue-helper.md │ │ ├── issue-helper.zh-CN.md │ │ ├── learn-formily.md │ │ ├── learn-formily.zh-CN.md │ │ ├── quick-start.md │ │ ├── quick-start.zh-CN.md │ │ ├── scenes │ │ │ ├── dialog-drawer.md │ │ │ ├── dialog-drawer.zh-CN.md │ │ │ ├── edit-detail.md │ │ │ ├── edit-detail.zh-CN.md │ │ │ ├── index.less │ │ │ ├── login-register.md │ │ │ ├── login-register.zh-CN.md │ │ │ ├── more.md │ │ │ ├── more.zh-CN.md │ │ │ ├── query-list.md │ │ │ ├── query-list.zh-CN.md │ │ │ ├── step-form.md │ │ │ ├── step-form.zh-CN.md │ │ │ ├── tab-form.md │ │ │ ├── tab-form.zh-CN.md │ │ │ └── VerifyCode.tsx │ │ ├── upgrade.md │ │ └── upgrade.zh-CN.md │ ├── index.md │ ├── index.zh-CN.md │ └── site │ ├── Contributors.less │ ├── Contributors.tsx │ ├── QrCode.less │ ├── QrCode.tsx │ ├── Section.less │ ├── Section.tsx │ └── styles.less ├── global.config.ts ├── jest.config.js ├── lerna.json ├── LICENSE.md ├── package.json ├── packages │ ├── .eslintrc │ ├── antd │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── ArrayTabs.md │ │ │ │ ├── ArrayTabs.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── sort.tsx │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── style.less │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── style.less │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── benchmark │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ └── index.tsx │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── core │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── entry │ │ │ │ │ ├── ActionResponse.less │ │ │ │ │ ├── ActionResponse.tsx │ │ │ │ │ ├── createForm.md │ │ │ │ │ ├── createForm.zh-CN.md │ │ │ │ │ ├── FieldEffectHooks.md │ │ │ │ │ ├── FieldEffectHooks.zh-CN.md │ │ │ │ │ ├── FormChecker.md │ │ │ │ │ ├── FormChecker.zh-CN.md │ │ │ │ │ ├── FormEffectHooks.md │ │ │ │ │ ├── FormEffectHooks.zh-CN.md │ │ │ │ │ ├── FormHooksAPI.md │ │ │ │ │ ├── FormHooksAPI.zh-CN.md │ │ │ │ │ ├── FormPath.md │ │ │ │ │ ├── FormPath.zh-CN.md │ │ │ │ │ ├── FormValidatorRegistry.md │ │ │ │ │ └── FormValidatorRegistry.zh-CN.md │ │ │ │ └── models │ │ │ │ ├── ArrayField.md │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ ├── Field.md │ │ │ │ ├── Field.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── ObjectField.md │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ ├── Query.md │ │ │ │ ├── Query.zh-CN.md │ │ │ │ ├── VoidField.md │ │ │ │ └── VoidField.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── field.md │ │ │ │ ├── field.zh-CN.md │ │ │ │ ├── form.md │ │ │ │ ├── form.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── mvvm.md │ │ │ │ ├── mvvm.zh-CN.md │ │ │ │ ├── values.md │ │ │ │ └── values.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── array.spec.ts │ │ │ │ ├── effects.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── field.spec.ts │ │ │ │ ├── form.spec.ts │ │ │ │ ├── graph.spec.ts │ │ │ │ ├── heart.spec.ts │ │ │ │ ├── internals.spec.ts │ │ │ │ ├── lifecycle.spec.ts │ │ │ │ ├── object.spec.ts │ │ │ │ ├── shared.ts │ │ │ │ └── void.spec.ts │ │ │ ├── effects │ │ │ │ ├── index.ts │ │ │ │ ├── onFieldEffects.ts │ │ │ │ └── onFormEffects.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── ArrayField.ts │ │ │ │ ├── BaseField.ts │ │ │ │ ├── Field.ts │ │ │ │ ├── Form.ts │ │ │ │ ├── Graph.ts │ │ │ │ ├── Heart.ts │ │ │ │ ├── index.ts │ │ │ │ ├── LifeCycle.ts │ │ │ │ ├── ObjectField.ts │ │ │ │ ├── Query.ts │ │ │ │ ├── types.ts │ │ │ │ └── VoidField.ts │ │ │ ├── shared │ │ │ │ ├── checkers.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── effective.ts │ │ │ │ ├── externals.ts │ │ │ │ └── internals.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── element │ │ ├── .npmignore │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── .vuepress │ │ │ │ ├── components │ │ │ │ │ ├── createCodeSandBox.js │ │ │ │ │ ├── dumi-previewer.vue │ │ │ │ │ └── highlight.js │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── styles │ │ │ │ │ └── index.styl │ │ │ │ └── util.js │ │ │ ├── demos │ │ │ │ ├── guide │ │ │ │ │ ├── array-cards │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-collapse │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-items │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-table │ │ │ │ │ │ ├── effects-json-schema.vue │ │ │ │ │ │ ├── effects-markup-schema.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── array-tabs │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── cascader │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── checkbox │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── date-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── editable │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-button-group.vue │ │ │ │ │ ├── form-collapse │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-dialog │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-drawer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-grid │ │ │ │ │ │ ├── form.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── native.vue │ │ │ │ │ ├── form-item │ │ │ │ │ │ ├── bordered-none.vue │ │ │ │ │ │ ├── common.vue │ │ │ │ │ │ ├── feedback.vue │ │ │ │ │ │ ├── inset.vue │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ ├── size.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-layout │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── form-step │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form-tab │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ └── markup-schema.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── input │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── input-number │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── password │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── preview-text │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── extend.vue │ │ │ │ │ ├── radio │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── reset │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ ├── force.vue │ │ │ │ │ │ └── validate.vue │ │ │ │ │ ├── select │ │ │ │ │ │ ├── json-schema-async.vue │ │ │ │ │ │ ├── json-schema-sync.vue │ │ │ │ │ │ ├── markup-schema-async-search.vue │ │ │ │ │ │ ├── markup-schema-async.vue │ │ │ │ │ │ ├── markup-schema-sync.vue │ │ │ │ │ │ ├── template-async.vue │ │ │ │ │ │ └── template-sync.vue │ │ │ │ │ ├── space │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── submit │ │ │ │ │ │ ├── base.vue │ │ │ │ │ │ └── loading.vue │ │ │ │ │ ├── switch │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── time-picker │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ ├── transfer │ │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ │ └── template.vue │ │ │ │ │ └── upload │ │ │ │ │ ├── json-schema.vue │ │ │ │ │ ├── markup-schema.vue │ │ │ │ │ └── template.vue │ │ │ │ └── index.vue │ │ │ ├── guide │ │ │ │ ├── array-cards.md │ │ │ │ ├── array-collapse.md │ │ │ │ ├── array-items.md │ │ │ │ ├── array-table.md │ │ │ │ ├── array-tabs.md │ │ │ │ ├── cascader.md │ │ │ │ ├── checkbox.md │ │ │ │ ├── date-picker.md │ │ │ │ ├── editable.md │ │ │ │ ├── form-button-group.md │ │ │ │ ├── form-collapse.md │ │ │ │ ├── form-dialog.md │ │ │ │ ├── form-drawer.md │ │ │ │ ├── form-grid.md │ │ │ │ ├── form-item.md │ │ │ │ ├── form-layout.md │ │ │ │ ├── form-step.md │ │ │ │ ├── form-tab.md │ │ │ │ ├── form.md │ │ │ │ ├── index.md │ │ │ │ ├── input-number.md │ │ │ │ ├── input.md │ │ │ │ ├── password.md │ │ │ │ ├── preview-text.md │ │ │ │ ├── radio.md │ │ │ │ ├── reset.md │ │ │ │ ├── select.md │ │ │ │ ├── space.md │ │ │ │ ├── submit.md │ │ │ │ ├── switch.md │ │ │ │ ├── time-picker.md │ │ │ │ ├── transfer.md │ │ │ │ └── upload.md │ │ │ └── README.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── configs │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shared │ │ │ │ │ ├── create-context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── loading.ts │ │ │ │ │ ├── portal.ts │ │ │ │ │ ├── resolve-component.ts │ │ │ │ │ ├── transform-component.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── styles │ │ │ │ └── common.scss │ │ │ ├── array-base │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── array-tabs │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── el-form │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── el-form-item │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── var.scss │ │ │ ├── form-layout │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── input-number │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── space │ │ │ │ ├── index.ts │ │ │ │ ├── style.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.ts │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.ts │ │ │ └── style.ts │ │ ├── transformer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── grid │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── index.ts │ │ │ └── observer.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── json-schema │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── schema.spec.ts.snap │ │ │ │ ├── compiler.spec.ts │ │ │ │ ├── patches.spec.ts │ │ │ │ ├── schema.spec.ts │ │ │ │ ├── server-validate.spec.ts │ │ │ │ ├── shared.spec.ts │ │ │ │ ├── transformer.spec.ts │ │ │ │ └── traverse.spec.ts │ │ │ ├── compiler.ts │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── patches.ts │ │ │ ├── polyfills │ │ │ │ ├── index.ts │ │ │ │ └── SPECIFICATION_1_0.ts │ │ │ ├── schema.ts │ │ │ ├── shared.ts │ │ │ ├── transformer.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── next │ │ ├── __tests__ │ │ │ ├── moment.spec.ts │ │ │ └── sideEffects.spec.ts │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── build-style.ts │ │ ├── create-style.ts │ │ ├── docs │ │ │ ├── components │ │ │ │ ├── ArrayCards.md │ │ │ │ ├── ArrayCards.zh-CN.md │ │ │ │ ├── ArrayCollapse.md │ │ │ │ ├── ArrayCollapse.zh-CN.md │ │ │ │ ├── ArrayItems.md │ │ │ │ ├── ArrayItems.zh-CN.md │ │ │ │ ├── ArrayTable.md │ │ │ │ ├── ArrayTable.zh-CN.md │ │ │ │ ├── Cascader.md │ │ │ │ ├── Cascader.zh-CN.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Checkbox.zh-CN.md │ │ │ │ ├── DatePicker.md │ │ │ │ ├── DatePicker.zh-CN.md │ │ │ │ ├── DatePicker2.md │ │ │ │ ├── DatePicker2.zh-CN.md │ │ │ │ ├── Editable.md │ │ │ │ ├── Editable.zh-CN.md │ │ │ │ ├── Form.md │ │ │ │ ├── Form.zh-CN.md │ │ │ │ ├── FormButtonGroup.md │ │ │ │ ├── FormButtonGroup.zh-CN.md │ │ │ │ ├── FormCollapse.md │ │ │ │ ├── FormCollapse.zh-CN.md │ │ │ │ ├── FormDialog.md │ │ │ │ ├── FormDialog.zh-CN.md │ │ │ │ ├── FormDrawer.md │ │ │ │ ├── FormDrawer.zh-CN.md │ │ │ │ ├── FormGrid.md │ │ │ │ ├── FormGrid.zh-CN.md │ │ │ │ ├── FormItem.md │ │ │ │ ├── FormItem.zh-CN.md │ │ │ │ ├── FormLayout.md │ │ │ │ ├── FormLayout.zh-CN.md │ │ │ │ ├── FormStep.md │ │ │ │ ├── FormStep.zh-CN.md │ │ │ │ ├── FormTab.md │ │ │ │ ├── FormTab.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── Input.md │ │ │ │ ├── Input.zh-CN.md │ │ │ │ ├── NumberPicker.md │ │ │ │ ├── NumberPicker.zh-CN.md │ │ │ │ ├── Password.md │ │ │ │ ├── Password.zh-CN.md │ │ │ │ ├── PreviewText.md │ │ │ │ ├── PreviewText.zh-CN.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Radio.zh-CN.md │ │ │ │ ├── Reset.md │ │ │ │ ├── Reset.zh-CN.md │ │ │ │ ├── Select.md │ │ │ │ ├── Select.zh-CN.md │ │ │ │ ├── SelectTable.md │ │ │ │ ├── SelectTable.zh-CN.md │ │ │ │ ├── Space.md │ │ │ │ ├── Space.zh-CN.md │ │ │ │ ├── Submit.md │ │ │ │ ├── Submit.zh-CN.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Switch.zh-CN.md │ │ │ │ ├── TimePicker.md │ │ │ │ ├── TimePicker.zh-CN.md │ │ │ │ ├── TimePicker2.md │ │ │ │ ├── TimePicker2.zh-CN.md │ │ │ │ ├── Transfer.md │ │ │ │ ├── Transfer.zh-CN.md │ │ │ │ ├── TreeSelect.md │ │ │ │ ├── TreeSelect.zh-CN.md │ │ │ │ ├── Upload.md │ │ │ │ └── Upload.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LESENCE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __builtins__ │ │ │ │ ├── empty.tsx │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useClickAway.ts │ │ │ │ │ └── usePrefixCls.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── loading.ts │ │ │ │ ├── mapSize.ts │ │ │ │ ├── mapStatus.ts │ │ │ │ ├── moment.ts │ │ │ │ ├── pickDataProps.ts │ │ │ │ ├── portal.tsx │ │ │ │ ├── render.ts │ │ │ │ └── toArray.ts │ │ │ ├── array-base │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-cards │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-collapse │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-items │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── array-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── cascader │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── checkbox │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── date-picker2 │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── editable │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-button-group │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-collapse │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-dialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-drawer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-grid │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── form-item │ │ │ │ ├── animation.scss │ │ │ │ ├── grid.scss │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── scss │ │ │ │ │ └── variable.scss │ │ │ │ └── style.ts │ │ │ ├── form-layout │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ └── useResponsiveFormLayout.ts │ │ │ ├── form-step │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── form-tab │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── index.ts │ │ │ ├── input │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── main.scss │ │ │ ├── number-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── password │ │ │ │ ├── index.tsx │ │ │ │ ├── PasswordStrength.tsx │ │ │ │ └── style.ts │ │ │ ├── preview-text │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── radio │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── reset │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── select-table │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ ├── style.ts │ │ │ │ ├── useCheckSlackly.tsx │ │ │ │ ├── useFilterOptions.tsx │ │ │ │ ├── useFlatOptions.tsx │ │ │ │ ├── useSize.tsx │ │ │ │ ├── useTitleAddon.tsx │ │ │ │ └── utils.ts │ │ │ ├── space │ │ │ │ ├── index.tsx │ │ │ │ ├── main.scss │ │ │ │ └── style.ts │ │ │ ├── style.ts │ │ │ ├── submit │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── switch │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── time-picker2 │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── transfer │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ ├── tree-select │ │ │ │ ├── index.tsx │ │ │ │ └── style.ts │ │ │ └── upload │ │ │ ├── index.tsx │ │ │ ├── main.scss │ │ │ ├── placeholder.ts │ │ │ └── style.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── path │ │ ├── .npmignore │ │ ├── benchmark.ts │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── accessor.spec.ts │ │ │ │ ├── basic.spec.ts │ │ │ │ ├── match.spec.ts │ │ │ │ ├── parser.spec.ts │ │ │ │ └── share.spec.ts │ │ │ ├── contexts.ts │ │ │ ├── destructor.ts │ │ │ ├── index.ts │ │ │ ├── matcher.ts │ │ │ ├── parser.ts │ │ │ ├── shared.ts │ │ │ ├── tokenizer.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── ArrayField.md │ │ │ │ │ ├── ArrayField.zh-CN.md │ │ │ │ │ ├── ExpressionScope.md │ │ │ │ │ ├── ExpressionScope.zh-CN.md │ │ │ │ │ ├── Field.md │ │ │ │ │ ├── Field.zh-CN.md │ │ │ │ │ ├── FormConsumer.md │ │ │ │ │ ├── FormConsumer.zh-CN.md │ │ │ │ │ ├── FormProvider.md │ │ │ │ │ ├── FormProvider.zh-CN.md │ │ │ │ │ ├── ObjectField.md │ │ │ │ │ ├── ObjectField.zh-CN.md │ │ │ │ │ ├── RecordScope.md │ │ │ │ │ ├── RecordScope.zh-CN.md │ │ │ │ │ ├── RecordsScope.md │ │ │ │ │ ├── RecordsScope.zh-CN.md │ │ │ │ │ ├── RecursionField.md │ │ │ │ │ ├── RecursionField.zh-CN.md │ │ │ │ │ ├── SchemaField.md │ │ │ │ │ ├── SchemaField.zh-CN.md │ │ │ │ │ ├── VoidField.md │ │ │ │ │ └── VoidField.zh-CN.md │ │ │ │ ├── hooks │ │ │ │ │ ├── useExpressionScope.md │ │ │ │ │ ├── useExpressionScope.zh-CN.md │ │ │ │ │ ├── useField.md │ │ │ │ │ ├── useField.zh-CN.md │ │ │ │ │ ├── useFieldSchema.md │ │ │ │ │ ├── useFieldSchema.zh-CN.md │ │ │ │ │ ├── useForm.md │ │ │ │ │ ├── useForm.zh-CN.md │ │ │ │ │ ├── useFormEffects.md │ │ │ │ │ ├── useFormEffects.zh-CN.md │ │ │ │ │ ├── useParentForm.md │ │ │ │ │ └── useParentForm.zh-CN.md │ │ │ │ └── shared │ │ │ │ ├── connect.md │ │ │ │ ├── connect.zh-CN.md │ │ │ │ ├── context.md │ │ │ │ ├── context.zh-CN.md │ │ │ │ ├── mapProps.md │ │ │ │ ├── mapProps.zh-CN.md │ │ │ │ ├── mapReadPretty.md │ │ │ │ ├── mapReadPretty.zh-CN.md │ │ │ │ ├── observer.md │ │ │ │ ├── observer.zh-CN.md │ │ │ │ ├── Schema.md │ │ │ │ └── Schema.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── architecture.md │ │ │ │ ├── architecture.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── expression.spec.tsx │ │ │ │ ├── field.spec.tsx │ │ │ │ ├── form.spec.tsx │ │ │ │ ├── schema.json.spec.tsx │ │ │ │ ├── schema.markup.spec.tsx │ │ │ │ └── shared.tsx │ │ │ ├── components │ │ │ │ ├── ArrayField.tsx │ │ │ │ ├── ExpressionScope.tsx │ │ │ │ ├── Field.tsx │ │ │ │ ├── FormConsumer.tsx │ │ │ │ ├── FormProvider.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ObjectField.tsx │ │ │ │ ├── ReactiveField.tsx │ │ │ │ ├── RecordScope.tsx │ │ │ │ ├── RecordsScope.tsx │ │ │ │ ├── RecursionField.tsx │ │ │ │ ├── SchemaField.tsx │ │ │ │ └── VoidField.tsx │ │ │ ├── global.d.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useAttach.ts │ │ │ │ ├── useExpressionScope.ts │ │ │ │ ├── useField.ts │ │ │ │ ├── useFieldSchema.ts │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormEffects.ts │ │ │ │ └── useParentForm.ts │ │ │ ├── index.ts │ │ │ ├── shared │ │ │ │ ├── connect.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ └── render.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── benchmark.ts │ │ ├── docs │ │ │ ├── api │ │ │ │ ├── action.md │ │ │ │ ├── action.zh-CN.md │ │ │ │ ├── autorun.md │ │ │ │ ├── autorun.zh-CN.md │ │ │ │ ├── batch.md │ │ │ │ ├── batch.zh-CN.md │ │ │ │ ├── define.md │ │ │ │ ├── define.zh-CN.md │ │ │ │ ├── hasCollected.md │ │ │ │ ├── hasCollected.zh-CN.md │ │ │ │ ├── markObservable.md │ │ │ │ ├── markObservable.zh-CN.md │ │ │ │ ├── markRaw.md │ │ │ │ ├── markRaw.zh-CN.md │ │ │ │ ├── model.md │ │ │ │ ├── model.zh-CN.md │ │ │ │ ├── observable.md │ │ │ │ ├── observable.zh-CN.md │ │ │ │ ├── observe.md │ │ │ │ ├── observe.zh-CN.md │ │ │ │ ├── raw.md │ │ │ │ ├── raw.zh-CN.md │ │ │ │ ├── react │ │ │ │ │ ├── observer.md │ │ │ │ │ └── observer.zh-CN.md │ │ │ │ ├── reaction.md │ │ │ │ ├── reaction.zh-CN.md │ │ │ │ ├── toJS.md │ │ │ │ ├── toJS.zh-CN.md │ │ │ │ ├── tracker.md │ │ │ │ ├── tracker.zh-CN.md │ │ │ │ ├── typeChecker.md │ │ │ │ ├── typeChecker.zh-CN.md │ │ │ │ ├── untracked.md │ │ │ │ ├── untracked.zh-CN.md │ │ │ │ └── vue │ │ │ │ ├── observer.md │ │ │ │ └── observer.zh-CN.md │ │ │ ├── guide │ │ │ │ ├── best-practice.md │ │ │ │ ├── best-practice.zh-CN.md │ │ │ │ ├── concept.md │ │ │ │ ├── concept.zh-CN.md │ │ │ │ ├── index.md │ │ │ │ └── index.zh-CN.md │ │ │ ├── index.md │ │ │ └── index.zh-CN.md │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── action.spec.ts │ │ │ │ ├── annotations.spec.ts │ │ │ │ ├── array.spec.ts │ │ │ │ ├── autorun.spec.ts │ │ │ │ ├── batch.spec.ts │ │ │ │ ├── collections-map.spec.ts │ │ │ │ ├── collections-set.spec.ts │ │ │ │ ├── collections-weakmap.spec.ts │ │ │ │ ├── collections-weakset.spec.ts │ │ │ │ ├── define.spec.ts │ │ │ │ ├── externals.spec.ts │ │ │ │ ├── hasCollected.spec.ts │ │ │ │ ├── observable.spec.ts │ │ │ │ ├── observe.spec.ts │ │ │ │ ├── tracker.spec.ts │ │ │ │ └── untracked.spec.ts │ │ │ ├── action.ts │ │ │ ├── annotations │ │ │ │ ├── box.ts │ │ │ │ ├── computed.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── ref.ts │ │ │ │ └── shallow.ts │ │ │ ├── array.ts │ │ │ ├── autorun.ts │ │ │ ├── batch.ts │ │ │ ├── checkers.ts │ │ │ ├── environment.ts │ │ │ ├── externals.ts │ │ │ ├── global.d.ts │ │ │ ├── handlers.ts │ │ │ ├── index.ts │ │ │ ├── internals.ts │ │ │ ├── model.ts │ │ │ ├── observable.ts │ │ │ ├── observe.ts │ │ │ ├── reaction.ts │ │ │ ├── tracker.ts │ │ │ ├── tree.ts │ │ │ ├── types.ts │ │ │ └── untracked.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-react │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ ├── useCompatEffect.ts │ │ │ │ ├── useCompatFactory.ts │ │ │ │ ├── useDidUpdate.ts │ │ │ │ ├── useForceUpdate.ts │ │ │ │ ├── useLayoutEffect.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer.ts │ │ │ ├── shared │ │ │ │ ├── gc.ts │ │ │ │ ├── global.ts │ │ │ │ ├── immediate.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── reactive-test-cases-for-react18 │ │ ├── .npmignore │ │ ├── .umirc.js │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.js │ │ │ └── MySlowList.js │ │ ├── template.ejs │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── webpack.base.ts │ │ ├── webpack.dev.ts │ │ └── webpack.prod.ts │ ├── reactive-vue │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── observer.spec.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useObserver.ts │ │ │ ├── index.ts │ │ │ ├── observer │ │ │ │ ├── collectData.ts │ │ │ │ ├── index.ts │ │ │ │ ├── observerInVue2.ts │ │ │ │ └── observerInVue3.ts │ │ │ └── types.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── shared │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── array.ts │ │ │ ├── case.ts │ │ │ ├── checkers.ts │ │ │ ├── clone.ts │ │ │ ├── compare.ts │ │ │ ├── defaults.ts │ │ │ ├── deprecate.ts │ │ │ ├── global.ts │ │ │ ├── index.ts │ │ │ ├── instanceof.ts │ │ │ ├── isEmpty.ts │ │ │ ├── merge.ts │ │ │ ├── middleware.ts │ │ │ ├── path.ts │ │ │ ├── string.ts │ │ │ ├── subscribable.ts │ │ │ └── uid.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── validator │ │ ├── .npmignore │ │ ├── LICENSE.md │ │ ├── package.json │ │ ├── README.md │ │ ├── rollup.config.js │ │ ├── src │ │ │ ├── __tests__ │ │ │ │ ├── parser.spec.ts │ │ │ │ ├── registry.spec.ts │ │ │ │ └── validator.spec.ts │ │ │ ├── formats.ts │ │ │ ├── index.ts │ │ │ ├── locale.ts │ │ │ ├── parser.ts │ │ │ ├── registry.ts │ │ │ ├── rules.ts │ │ │ ├── template.ts │ │ │ ├── types.ts │ │ │ └── validator.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── vue │ ├── .npmignore │ ├── bin │ │ ├── formily-vue-fix.js │ │ └── formily-vue-switch.js │ ├── docs │ │ ├── .vuepress │ │ │ ├── components │ │ │ │ ├── createCodeSandBox.js │ │ │ │ ├── dumi-previewer.vue │ │ │ │ └── highlight.js │ │ │ ├── config.js │ │ │ ├── enhanceApp.js │ │ │ └── styles │ │ │ └── index.styl │ │ ├── api │ │ │ ├── components │ │ │ │ ├── array-field.md │ │ │ │ ├── expression-scope.md │ │ │ │ ├── field.md │ │ │ │ ├── form-consumer.md │ │ │ │ ├── form-provider.md │ │ │ │ ├── object-field.md │ │ │ │ ├── recursion-field-with-component.md │ │ │ │ ├── recursion-field.md │ │ │ │ ├── schema-field-with-schema.md │ │ │ │ ├── schema-field.md │ │ │ │ └── void-field.md │ │ │ ├── hooks │ │ │ │ ├── use-field-schema.md │ │ │ │ ├── use-field.md │ │ │ │ ├── use-form-effects.md │ │ │ │ ├── use-form.md │ │ │ │ └── use-parent-form.md │ │ │ └── shared │ │ │ ├── connect.md │ │ │ ├── injections.md │ │ │ ├── map-props.md │ │ │ ├── map-read-pretty.md │ │ │ ├── observer.md │ │ │ └── schema.md │ │ ├── demos │ │ │ ├── api │ │ │ │ ├── components │ │ │ │ │ ├── array-field.vue │ │ │ │ │ ├── expression-scope.vue │ │ │ │ │ ├── field.vue │ │ │ │ │ ├── form-consumer.vue │ │ │ │ │ ├── form-provider.vue │ │ │ │ │ ├── object-field.vue │ │ │ │ │ ├── recursion-field-with-component.vue │ │ │ │ │ ├── recursion-field.vue │ │ │ │ │ ├── schema-field-with-schema.vue │ │ │ │ │ ├── schema-field.vue │ │ │ │ │ └── void-field.vue │ │ │ │ ├── hooks │ │ │ │ │ ├── use-field-schema.vue │ │ │ │ │ ├── use-field.vue │ │ │ │ │ ├── use-form-effects.vue │ │ │ │ │ ├── use-form.vue │ │ │ │ │ └── use-parent-form.vue │ │ │ │ └── shared │ │ │ │ ├── connect.vue │ │ │ │ ├── map-props.vue │ │ │ │ ├── map-read-pretty.vue │ │ │ │ └── observer.vue │ │ │ ├── index.vue │ │ │ └── questions │ │ │ ├── default-slot.vue │ │ │ ├── events.vue │ │ │ ├── named-slot.vue │ │ │ └── scoped-slot.vue │ │ ├── guide │ │ │ ├── architecture.md │ │ │ ├── concept.md │ │ │ └── README.md │ │ ├── questions │ │ │ └── README.md │ │ └── README.md │ ├── package.json │ ├── README.md │ ├── rollup.config.js │ ├── scripts │ │ ├── postinstall.js │ │ ├── switch-cli.js │ │ └── utils.js │ ├── src │ │ ├── __tests__ │ │ │ ├── expression.scope.spec.ts │ │ │ ├── field.spec.ts │ │ │ ├── form.spec.ts │ │ │ ├── schema.json.spec.ts │ │ │ ├── schema.markup.spec.ts │ │ │ ├── shared.spec.ts │ │ │ └── utils.spec.ts │ │ ├── components │ │ │ ├── ArrayField.ts │ │ │ ├── ExpressionScope.ts │ │ │ ├── Field.ts │ │ │ ├── FormConsumer.ts │ │ │ ├── FormProvider.ts │ │ │ ├── index.ts │ │ │ ├── ObjectField.ts │ │ │ ├── ReactiveField.ts │ │ │ ├── RecursionField.ts │ │ │ ├── SchemaField.ts │ │ │ └── VoidField.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAttach.ts │ │ │ ├── useField.ts │ │ │ ├── useFieldSchema.ts │ │ │ ├── useForm.ts │ │ │ ├── useFormEffects.ts │ │ │ ├── useInjectionCleaner.ts │ │ │ └── useParentForm.ts │ │ ├── index.ts │ │ ├── shared │ │ │ ├── connect.ts │ │ │ ├── context.ts │ │ │ ├── createForm.ts │ │ │ ├── fragment.ts │ │ │ ├── h.ts │ │ │ └── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── formatVNodeData.ts │ │ │ ├── getFieldProps.ts │ │ │ ├── getRawComponent.ts │ │ │ └── resolveSchemaProps.ts │ │ └── vue2-components.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── tsconfig.types.json ├── README.md ├── README.zh-cn.md ├── scripts │ ├── build-style │ │ ├── buildAllStyles.ts │ │ ├── copy.ts │ │ ├── helper.ts │ │ └── index.ts │ └── rollup.base.js ├── tsconfig.build.json ├── tsconfig.jest.json ├── tsconfig.json └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "root", 3 | "private": true, 4 | "devEngines": { 5 | "node": "8.x || 9.x || 10.x || 11.x" 6 | }, 7 | "workspaces": [ 8 | "packages/*", 9 | "devtools/*" 10 | ], 11 | "scripts": { 12 | "build": "rimraf -rf packages/*/{lib,dist,esm} && lerna run build", 13 | "build:docs": "dumi build", 14 | "start": "dumi dev", 15 | "test": "jest --coverage", 16 | "test:reactive": "jest packages/reactive/", 17 | "test:validator": "jest packages/validator/", 18 | "test:core": "jest packages/core/", 19 | "test:core:watch": "npm run test:core --- --watch", 20 | "test:schema": "jest packages/json-schema/", 21 | "test:schema:watch": "npm run test:schema --- --watch", 22 | "test:react": "jest packages/react/", 23 | "test:shared": "jest packages/shared/", 24 | "test:path": "jest packages/path/", 25 | "test:react:watch": "npm run test:react --- --watch", 26 | "test:vue": "jest packages/vue/", 27 | "test:vue:watch": "npm run test:vue --- --watch", 28 | "test:reactive-vue": "jest packages/reactive-vue/", 29 | "test:reactive-vue:watch": "npm run test:reactive-vue --- --watch", 30 | "test:antd": "jest packages/antd/", 31 | "test:next": "jest packages/next/", 32 | "test:watch": "jest --watch", 33 | "test:prod": "jest --coverage --silent", 34 | "preversion": "yarn install --ignore-engines && git add -A && npm run build && npm run lint && npm run test", 35 | "version:alpha": "lerna version prerelease --preid alpha", 36 | "version:beta": "lerna version prerelease --preid beta", 37 | "version:rc": "lerna version prerelease --preid rc", 38 | "version:patch": "lerna version patch", 39 | "version:minor": "lerna version minor", 40 | "version:preminor": "lerna version preminor --preid beta", 41 | "version:major": "lerna version major", 42 | "release:force": "lerna publish from-package --yes", 43 | "lint": "eslint ." 44 | }, 45 | "resolutions": { 46 | "@types/react": "^18.0.0", 47 | "@types/react-dom": "^18.0.0", 48 | "@mapbox/hast-util-to-jsx": "~1.0.0", 49 | "yargs": "^16.x", 50 | "commander": "^6.x", 51 | "ttypescript": "1.5.15" 52 | }, 53 | "devDependencies": { 54 | "@alifd/next": "^1.19.1", 55 | "@commitlint/cli": "^14.1.0", 56 | "@commitlint/config-conventional": "^14.1.0", 57 | "@commitlint/prompt-cli": "^14.1.0", 58 | "@netlify/functions": "^0.7.2", 59 | "@rollup/plugin-commonjs": "^17.0.0", 60 | "@testing-library/jest-dom": "^5.0.0", 61 | "@testing-library/react": "^11.2.3", 62 | "@testing-library/vue": "^5.6.2", 63 | "@types/fs-extra": "^8.1.0", 64 | "@types/hoist-non-react-statics": "^3.3.1", 65 | "@types/jest": "^24.0.18", 66 | "@types/node": "^12.6.8", 67 | "@types/react": "^18.0.0", 68 | "@types/react-dom": "^18.0.0", 69 | "@types/react-is": "^18.3.0", 70 | "@typescript-eslint/eslint-plugin": "^4.9.1", 71 | "@typescript-eslint/parser": "^4.8.2", 72 | "@umijs/plugin-sass": "^1.1.1", 73 | "@vue/test-utils": "1.0.0-beta.22", 74 | "antd": "^4.0.0", 75 | "axios": "^1.6.0", 76 | "chalk": "^2.4.2", 77 | "chokidar": "^2.1.2", 78 | "concurrently": "^4.1.0", 79 | "conventional-commit-types": "^2.2.0", 80 | "cool-path": "^1.0.6", 81 | "cross-env": "^5.2.1", 82 | "css-loader": "^5.0.0", 83 | "cz-conventional-changelog": "^2.1.0", 84 | "dumi": "^1.1.53", 85 | "escape-string-regexp": "^4.0.0", 86 | "eslint": "^7.14.0", 87 | "eslint-config-prettier": "^7.0.0", 88 | "eslint-plugin-import": "^2.13.0", 89 | "eslint-plugin-markdown": "^2.0.1", 90 | "eslint-plugin-node": "^11.1.0", 91 | "eslint-plugin-prettier": "^3.1.0", 92 | "eslint-plugin-promise": "^4.0.0", 93 | "eslint-plugin-react": "^7.14.2", 94 | "eslint-plugin-react-hooks": "^4.2.0", 95 | "eslint-plugin-vue": "^7.0.1", 96 | "execa": "^5.0.0", 97 | "file-loader": "^5.0.2", 98 | "findup": "^0.1.5", 99 | "fs-extra": "^7.0.1", 100 | "ghooks": "^2.0.4", 101 | "glob": "^7.1.3", 102 | "html-webpack-plugin": "^3.2.0", 103 | "immutable": "^4.0.0-rc.12", 104 | "istanbul-api": "^2.1.1", 105 | "istanbul-lib-coverage": "^2.0.3", 106 | "jest": "^26.0.0", 107 | "jest-codemods": "^0.19.1", 108 | "jest-dom": "^3.1.2", 109 | "jest-localstorage-mock": "^2.3.0", 110 | "jest-styled-components": "6.3.3", 111 | "jest-watch-lerna-packages": "^1.1.0", 112 | "lerna": "^4.0.0", 113 | "less": "^4.1.1", 114 | "less-loader": "^5.0.0", 115 | "less-plugin-npm-import": "^2.1.0", 116 | "lint-staged": "^8.2.1", 117 | "mfetch": "^0.2.27", 118 | "mobx": "^6.0.4", 119 | "mobx-react-lite": "^3.1.6", 120 | "onchange": "^5.2.0", 121 | "opencollective": "^1.0.3", 122 | "opencollective-postinstall": "^2.0.2", 123 | "param-case": "^3.0.4", 124 | "postcss": "^8.0.0", 125 | "prettier": "^2.2.1", 126 | "pretty-format": "^24.0.0", 127 | "pretty-quick": "^3.1.0", 128 | "querystring": "^0.2.1", 129 | "raw-loader": "^4.0.0", 130 | "react": "^18.0.0", 131 | "react-dom": "^18.0.0", 132 | "react-mde": "^11.5.0", 133 | "react-test-renderer": "^16.11.0", 134 | "rimraf": "^3.0.0", 135 | "rollup": "^2.37.1", 136 | "rollup-plugin-dts": "^2.0.0", 137 | "rollup-plugin-external-globals": "^0.6.1", 138 | "rollup-plugin-inject-process-env": "^1.3.1", 139 | "rollup-plugin-node-resolve": "^5.2.0", 140 | "rollup-plugin-postcss": "^4.0.0", 141 | "rollup-plugin-terser": "^7.0.2", 142 | "rollup-plugin-typescript2": "^0.35.0", 143 | "semver": "^7.3.5", 144 | "semver-regex": "^3.1.3", 145 | "showdown": "^1.9.1", 146 | "staged-git-files": "^1.1.2", 147 | "string-similarity": "^4.0.4", 148 | "style-loader": "^1.1.3", 149 | "styled-components": "^5.0.0", 150 | "ts-import-plugin": "1.6.1", 151 | "ts-jest": "^26.0.0", 152 | "ts-loader": "^7.0.4", 153 | "ts-node": "^9.1.1", 154 | "typescript": "^4.1.5", 155 | "vue-eslint-parser": "^7.1.1", 156 | "webpack": "^4.41.5", 157 | "webpack-cli": "^3.3.10", 158 | "webpack-dev-server": "^3.10.1", 159 | "yup": "^1.4.0" 160 | }, 161 | "repository": { 162 | "type": "git", 163 | "url": "git+https://github.com/alibaba/formily.git" 164 | }, 165 | "config": { 166 | "ghooks": { 167 | "pre-commit": "lint-staged", 168 | "commit-msg": "commitlint --edit" 169 | } 170 | }, 171 | "lint-staged": { 172 | "*.{ts,tsx,js}": [ 173 | "eslint --ext .ts,.tsx,.js", 174 | "pretty-quick --staged", 175 | "git add" 176 | ], 177 | "*.md": [ 178 | "pretty-quick --staged", 179 | "git add" 180 | ] 181 | }, 182 | "collective": { 183 | "type": "opencollective", 184 | "url": "https://opencollective.com/formily" 185 | }, 186 | "dependencies": { 187 | "@ant-design/icons": "^4.0.2" 188 | }, 189 | "packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 190 | } 191 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Editable.md: -------------------------------------------------------------------------------- ```markdown 1 | # Editable 2 | 3 | > Partial editor, you can use this component for some form areas with high space requirements 4 | > 5 | > Editable component is equivalent to a variant of FormItem component, so it is usually placed in decorator 6 | 7 | ## Markup Schema example 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | Input, 13 | DatePicker, 14 | Editable, 15 | FormItem, 16 | FormButtonGroup, 17 | Submit, 18 | } from '@formily/antd' 19 | import { createForm } from '@formily/core' 20 | import { FormProvider, createSchemaField } from '@formily/react' 21 | 22 | const SchemaField = createSchemaField({ 23 | components: { 24 | DatePicker, 25 | Editable, 26 | Input, 27 | FormItem, 28 | }, 29 | }) 30 | 31 | const form = createForm() 32 | 33 | export default () => ( 34 | <FormProvider form={form}> 35 | <SchemaField> 36 | <SchemaField.String 37 | name="date" 38 | title="date" 39 | x-decorator="Editable" 40 | x-component="DatePicker" 41 | /> 42 | <SchemaField.String 43 | name="input" 44 | title="input box" 45 | x-decorator="Editable" 46 | x-component="Input" 47 | /> 48 | <SchemaField.Void 49 | name="void" 50 | title="Virtual Node Container" 51 | x-component="Editable.Popover" 52 | x-reactions={(field) => { 53 | field.title = field.query('.void.date2').get('value') || field.title 54 | }} 55 | > 56 | <SchemaField.String 57 | name="date2" 58 | title="date" 59 | x-decorator="FormItem" 60 | x-component="DatePicker" 61 | /> 62 | <SchemaField.String 63 | name="input2" 64 | title="input box" 65 | x-decorator="FormItem" 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | <SchemaField.Object 70 | name="iobject" 71 | title="Object node container" 72 | x-component="Editable.Popover" 73 | x-reactions={(field) => { 74 | field.title = field.value?.date || field.title 75 | }} 76 | > 77 | <SchemaField.String 78 | name="date" 79 | title="date" 80 | x-decorator="FormItem" 81 | x-component="DatePicker" 82 | /> 83 | <SchemaField.String 84 | name="input" 85 | title="input box" 86 | x-decorator="FormItem" 87 | x-component="Input" 88 | /> 89 | </SchemaField.Object> 90 | </SchemaField> 91 | <FormButtonGroup> 92 | <Submit onSubmit={console.log}>Submit</Submit> 93 | </FormButtonGroup> 94 | </FormProvider> 95 | ) 96 | ``` 97 | 98 | ## JSON Schema case 99 | 100 | ```tsx 101 | import React from 'react' 102 | import { 103 | Input, 104 | DatePicker, 105 | Editable, 106 | FormItem, 107 | FormButtonGroup, 108 | Submit, 109 | } from '@formily/antd' 110 | import { createForm } from '@formily/core' 111 | import { FormProvider, createSchemaField } from '@formily/react' 112 | 113 | const SchemaField = createSchemaField({ 114 | components: { 115 | DatePicker, 116 | Editable, 117 | Input, 118 | FormItem, 119 | }, 120 | }) 121 | 122 | const form = createForm() 123 | 124 | const schema = { 125 | type: 'object', 126 | properties: { 127 | date: { 128 | type: 'string', 129 | title: 'Date', 130 | 'x-decorator': 'Editable', 131 | 'x-component': 'DatePicker', 132 | }, 133 | input: { 134 | type: 'string', 135 | title: 'input box', 136 | 'x-decorator': 'Editable', 137 | 'x-component': 'Input', 138 | }, 139 | void: { 140 | type: 'void', 141 | title: 'Virtual Node Container', 142 | 'x-component': 'Editable.Popover', 143 | 'x-reactions': 144 | "{{(field) => field.title = field.query('.void.date2').get('value') || field.title}}", 145 | properties: { 146 | date2: { 147 | type: 'string', 148 | title: 'Date', 149 | 'x-decorator': 'FormItem', 150 | 'x-component': 'DatePicker', 151 | }, 152 | input2: { 153 | type: 'string', 154 | title: 'input box', 155 | 'x-decorator': 'FormItem', 156 | 'x-component': 'Input', 157 | }, 158 | }, 159 | }, 160 | iobject: { 161 | type: 'object', 162 | title: 'Object node container', 163 | 'x-component': 'Editable.Popover', 164 | 'x-reactions': 165 | '{{(field) => field.title = field.value && field.value.date || field.title}}', 166 | properties: { 167 | date: { 168 | type: 'string', 169 | title: 'Date', 170 | 'x-decorator': 'FormItem', 171 | 'x-component': 'DatePicker', 172 | }, 173 | input: { 174 | type: 'string', 175 | title: 'input box', 176 | 'x-decorator': 'FormItem', 177 | 'x-component': 'Input', 178 | }, 179 | }, 180 | }, 181 | }, 182 | } 183 | 184 | export default () => ( 185 | <FormProvider form={form}> 186 | <SchemaField schema={schema} /> 187 | <FormButtonGroup> 188 | <Submit onSubmit={console.log}>Submit</Submit> 189 | </FormButtonGroup> 190 | </FormProvider> 191 | ) 192 | ``` 193 | 194 | ## Pure JSX case 195 | 196 | ```tsx 197 | import React from 'react' 198 | import { 199 | Input, 200 | DatePicker, 201 | Editable, 202 | FormItem, 203 | FormButtonGroup, 204 | Submit, 205 | } from '@formily/antd' 206 | import { createForm } from '@formily/core' 207 | import { FormProvider, Field, VoidField, ObjectField } from '@formily/react' 208 | 209 | const form = createForm() 210 | 211 | export default () => ( 212 | <FormProvider form={form}> 213 | <Field 214 | name="date" 215 | title="date" 216 | decorator={[Editable]} 217 | component={[DatePicker]} 218 | /> 219 | <Field 220 | name="input" 221 | title="input box" 222 | decorator={[Editable]} 223 | component={[Input]} 224 | /> 225 | <VoidField 226 | name="void" 227 | title="Virtual Node Container" 228 | reactions={(field) => { 229 | field.title = field.query('.void.date2').get('value') || field.title 230 | }} 231 | component={[Editable.Popover]} 232 | > 233 | <Field 234 | name="date2" 235 | title="date" 236 | decorator={[FormItem]} 237 | component={[DatePicker]} 238 | /> 239 | <Field 240 | name="input2" 241 | title="input box" 242 | decorator={[FormItem]} 243 | component={[Input]} 244 | /> 245 | </VoidField> 246 | <ObjectField 247 | name="iobject" 248 | title="Object node container" 249 | reactions={(field) => { 250 | field.title = field.value?.date || field.title 251 | }} 252 | component={[Editable.Popover]} 253 | > 254 | <Field 255 | name="date" 256 | title="date" 257 | decorator={[FormItem]} 258 | component={[DatePicker]} 259 | /> 260 | <Field 261 | name="input" 262 | title="input box" 263 | decorator={[FormItem]} 264 | component={[Input]} 265 | /> 266 | </ObjectField> 267 | 268 | <FormButtonGroup> 269 | <Submit onSubmit={console.log}>Submit</Submit> 270 | </FormButtonGroup> 271 | </FormProvider> 272 | ) 273 | ``` 274 | 275 | ## API 276 | 277 | ### Editable 278 | 279 | > Inline editing 280 | 281 | Refer to the FormItem property in https://ant.design/components/form-cn/ 282 | 283 | ### Editable.Popover 284 | 285 | > Floating layer editing 286 | 287 | Reference https://ant.design/components/popover-cn/ 288 | ``` -------------------------------------------------------------------------------- /packages/path/src/tokens.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | bracketContext, 3 | parenContext, 4 | bracketArrayContext, 5 | bracketDContext, 6 | braceContext, 7 | destructorContext, 8 | } from './contexts' 9 | 10 | interface ITokenProps { 11 | expectNext?: (next?: Token) => boolean 12 | expectPrev?: (prev?: Token) => boolean 13 | updateContext?: (prev?: Token) => void 14 | } 15 | 16 | export type Token = ITokenProps & { 17 | flag: string 18 | } 19 | 20 | const TokenType = (flag: string, props?: ITokenProps): Token => { 21 | return { 22 | flag, 23 | ...props, 24 | } 25 | } 26 | 27 | export const nameTok = TokenType('name', { 28 | expectNext(next) { 29 | if (this.includesContext(destructorContext)) { 30 | return ( 31 | next === nameTok || 32 | next === commaTok || 33 | next === bracketRTok || 34 | next === braceRTok || 35 | next === colonTok 36 | ) 37 | } 38 | return ( 39 | next === dotTok || 40 | next === commaTok || 41 | next === eofTok || 42 | next === bracketRTok || 43 | next === parenRTok || 44 | next === colonTok || 45 | next === expandTok || 46 | next === bracketLTok 47 | ) 48 | }, 49 | }) 50 | export const starTok = TokenType('*', { 51 | expectNext(next) { 52 | return ( 53 | next === dotTok || 54 | next === parenLTok || 55 | next === bracketLTok || 56 | next === eofTok || 57 | next === commaTok || 58 | next === parenRTok 59 | ) 60 | }, 61 | }) 62 | export const dbStarTok = TokenType('**', { 63 | expectNext(next) { 64 | return ( 65 | next === dotTok || 66 | next === bracketLTok || 67 | next === eofTok || 68 | next === commaTok || 69 | next === parenRTok 70 | ) 71 | }, 72 | }) 73 | export const dotTok = TokenType('.', { 74 | expectNext(next) { 75 | return ( 76 | next === dotTok || 77 | next === nameTok || 78 | next === bracketDLTok || 79 | next === starTok || 80 | next === dbStarTok || 81 | next === bracketLTok || 82 | next === braceLTok || 83 | next === eofTok 84 | ) 85 | }, 86 | expectPrev(prev) { 87 | return ( 88 | prev === dotTok || 89 | prev === nameTok || 90 | prev === bracketDRTok || 91 | prev === starTok || 92 | prev === parenRTok || 93 | prev === bracketRTok || 94 | prev === expandTok || 95 | prev === braceRTok 96 | ) 97 | }, 98 | }) 99 | export const bangTok = TokenType('!', { 100 | expectNext(next) { 101 | return next === nameTok || next === bracketDLTok 102 | }, 103 | }) 104 | export const colonTok = TokenType(':', { 105 | expectNext(next) { 106 | if (this.includesContext(destructorContext)) { 107 | return next === nameTok || next === braceLTok || next === bracketLTok 108 | } 109 | return next === nameTok || next === bracketDLTok || next === bracketRTok 110 | }, 111 | }) 112 | 113 | export const braceLTok = TokenType('{', { 114 | expectNext(next) { 115 | return next === nameTok 116 | }, 117 | expectPrev(prev) { 118 | if (this.includesContext(destructorContext)) { 119 | return prev === colonTok || prev === commaTok || prev === bracketLTok 120 | } 121 | return prev === dotTok || prev === colonTok || prev === parenLTok 122 | }, 123 | updateContext() { 124 | this.state.context.push(braceContext) 125 | }, 126 | }) 127 | 128 | export const braceRTok = TokenType('}', { 129 | expectNext(next) { 130 | if (this.includesContext(destructorContext)) { 131 | return ( 132 | next === commaTok || 133 | next === braceRTok || 134 | next === eofTok || 135 | next === bracketRTok 136 | ) 137 | } 138 | return next === dotTok || next === eofTok || next === commaTok 139 | }, 140 | expectPrev(prev) { 141 | return prev === nameTok || prev === braceRTok || prev === bracketRTok 142 | }, 143 | updateContext() { 144 | this.state.context.pop(braceContext) 145 | }, 146 | }) 147 | 148 | export const bracketLTok = TokenType('[', { 149 | expectNext(next) { 150 | if (this.includesContext(destructorContext)) { 151 | return ( 152 | next === nameTok || 153 | next === bracketLTok || 154 | next === braceLTok || 155 | next === bracketRTok 156 | ) 157 | } 158 | return ( 159 | next === nameTok || 160 | next === bracketDLTok || 161 | next === colonTok || 162 | next === bracketLTok || 163 | next === ignoreTok || 164 | next === bracketRTok 165 | ) 166 | }, 167 | expectPrev(prev) { 168 | if (this.includesContext(destructorContext)) { 169 | return prev === colonTok || prev === commaTok || prev === bracketLTok 170 | } 171 | return ( 172 | prev === starTok || 173 | prev === bracketLTok || 174 | prev === dotTok || 175 | prev === nameTok || 176 | prev === parenLTok || 177 | // never reach 178 | prev == commaTok 179 | ) 180 | }, 181 | updateContext() { 182 | this.state.context.push(bracketContext) 183 | }, 184 | }) 185 | 186 | export const bracketRTok = TokenType(']', { 187 | expectNext(next) { 188 | if (this.includesContext(destructorContext)) { 189 | return ( 190 | next === commaTok || 191 | next === braceRTok || 192 | next === bracketRTok || 193 | next === eofTok 194 | ) 195 | } 196 | return ( 197 | next === dotTok || 198 | next === eofTok || 199 | next === commaTok || 200 | next === parenRTok || 201 | next === bracketRTok 202 | ) 203 | }, 204 | updateContext() { 205 | if (this.includesContext(bracketArrayContext)) return 206 | if (!this.includesContext(bracketContext)) throw this.unexpect() 207 | this.state.context.pop() 208 | }, 209 | }) 210 | 211 | export const bracketDLTok = TokenType('[[', { 212 | updateContext() { 213 | this.state.context.push(bracketDContext) 214 | }, 215 | }) 216 | 217 | export const bracketDRTok = TokenType(']]', { 218 | updateContext() { 219 | if (this.curContext() !== bracketDContext) throw this.unexpect() 220 | this.state.context.pop() 221 | }, 222 | }) 223 | 224 | export const parenLTok = TokenType('(', { 225 | expectNext(next) { 226 | return ( 227 | next === nameTok || 228 | next === bracketDLTok || 229 | next === braceLTok || 230 | next === bangTok || 231 | next === bracketLTok 232 | ) 233 | }, 234 | expectPrev(prev) { 235 | return prev === starTok 236 | }, 237 | updateContext() { 238 | this.state.context.push(parenContext) 239 | }, 240 | }) 241 | export const parenRTok = TokenType(')', { 242 | expectNext(next) { 243 | return ( 244 | next === dotTok || 245 | next === eofTok || 246 | next === commaTok || 247 | next === parenRTok 248 | ) 249 | }, 250 | updateContext() { 251 | if (this.curContext() !== parenContext) throw this.unexpect() 252 | this.state.context.pop() 253 | }, 254 | }) 255 | 256 | export const commaTok = TokenType(',', { 257 | expectNext(next) { 258 | return ( 259 | next === nameTok || 260 | next === bracketDLTok || 261 | next === bracketLTok || 262 | next === braceLTok 263 | ) 264 | }, 265 | }) 266 | export const ignoreTok = TokenType('ignore', { 267 | expectNext(next) { 268 | return next === bracketDRTok 269 | }, 270 | expectPrev(prev) { 271 | return prev == bracketDLTok 272 | }, 273 | }) 274 | 275 | export const expandTok = TokenType('expandTok', { 276 | expectNext(next) { 277 | return ( 278 | next === dotTok || 279 | next === eofTok || 280 | next === commaTok || 281 | next === parenRTok 282 | ) 283 | }, 284 | }) 285 | 286 | export const eofTok = TokenType('eof') 287 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormStep.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormStep 2 | 3 | > Step-by-step form components 4 | > 5 | > Note: This component can only be used in Schema scenarios 6 | 7 | ## Markup Schema example 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { FormStep, FormItem, Input, FormButtonGroup } from '@formily/antd' 12 | import { createForm } from '@formily/core' 13 | import { FormProvider, FormConsumer, createSchemaField } from '@formily/react' 14 | import { Button } from 'antd' 15 | 16 | const SchemaField = createSchemaField({ 17 | components: { 18 | FormItem, 19 | FormStep, 20 | Input, 21 | }, 22 | }) 23 | 24 | const form = createForm() 25 | const formStep = FormStep.createFormStep() 26 | 27 | export default () => { 28 | return ( 29 | <FormProvider form={form}> 30 | <SchemaField> 31 | <SchemaField.Void 32 | x-component="FormStep" 33 | x-component-props={{ formStep }} 34 | > 35 | <SchemaField.Void 36 | x-component="FormStep.StepPane" 37 | x-component-props={{ title: 'First Step' }} 38 | > 39 | <SchemaField.String 40 | name="aaa" 41 | x-decorator="FormItem" 42 | required 43 | x-component="Input" 44 | /> 45 | </SchemaField.Void> 46 | <SchemaField.Void 47 | x-component="FormStep.StepPane" 48 | x-component-props={{ title: 'Second Step' }} 49 | > 50 | <SchemaField.String 51 | name="bbb" 52 | x-decorator="FormItem" 53 | required 54 | x-component="Input" 55 | /> 56 | </SchemaField.Void> 57 | <SchemaField.Void 58 | type="void" 59 | x-component="FormStep.StepPane" 60 | x-component-props={{ title: 'Step 3' }} 61 | > 62 | <SchemaField.String 63 | name="ccc" 64 | x-decorator="FormItem" 65 | required 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | </SchemaField.Void> 70 | </SchemaField> 71 | <FormConsumer> 72 | {() => ( 73 | <FormButtonGroup> 74 | <Button 75 | disabled={!formStep.allowBack} 76 | onClick={() => { 77 | formStep.back() 78 | }} 79 | > 80 | Previous 81 | </Button> 82 | <Button 83 | disabled={!formStep.allowNext} 84 | onClick={() => { 85 | formStep.next() 86 | }} 87 | > 88 | Next step 89 | </Button> 90 | <Button 91 | disabled={formStep.allowNext} 92 | onClick={() => { 93 | formStep.submit(console.log) 94 | }} 95 | > 96 | submit 97 | </Button> 98 | </FormButtonGroup> 99 | )} 100 | </FormConsumer> 101 | </FormProvider> 102 | ) 103 | } 104 | ``` 105 | 106 | ## JSON Schema case 107 | 108 | ```tsx 109 | import React from 'react' 110 | import { FormStep, FormItem, Input, FormButtonGroup } from '@formily/antd' 111 | import { createForm } from '@formily/core' 112 | import { FormProvider, FormConsumer, createSchemaField } from '@formily/react' 113 | import { Button } from 'antd' 114 | 115 | const SchemaField = createSchemaField({ 116 | components: { 117 | FormItem, 118 | FormStep, 119 | Input, 120 | }, 121 | }) 122 | 123 | const form = createForm() 124 | const formStep = FormStep.createFormStep() 125 | 126 | const schema = { 127 | type: 'object', 128 | properties: { 129 | step: { 130 | type: 'void', 131 | 'x-component': 'FormStep', 132 | 'x-component-props': { 133 | formStep: '{{formStep}}', 134 | }, 135 | properties: { 136 | step1: { 137 | type: 'void', 138 | 'x-component': 'FormStep.StepPane', 139 | 'x-component-props': { 140 | title: 'First Step', 141 | }, 142 | properties: { 143 | aaa: { 144 | type: 'string', 145 | title: 'AAA', 146 | required: true, 147 | 'x-decorator': 'FormItem', 148 | 'x-component': 'Input', 149 | }, 150 | }, 151 | }, 152 | step2: { 153 | type: 'void', 154 | 'x-component': 'FormStep.StepPane', 155 | 'x-component-props': { 156 | title: 'Second Step', 157 | }, 158 | properties: { 159 | bbb: { 160 | type: 'string', 161 | title: 'AAA', 162 | required: true, 163 | 'x-decorator': 'FormItem', 164 | 'x-component': 'Input', 165 | }, 166 | }, 167 | }, 168 | step3: { 169 | type: 'void', 170 | 'x-component': 'FormStep.StepPane', 171 | 'x-component-props': { 172 | title: 'The third step', 173 | }, 174 | properties: { 175 | ccc: { 176 | type: 'string', 177 | title: 'AAA', 178 | required: true, 179 | 'x-decorator': 'FormItem', 180 | 'x-component': 'Input', 181 | }, 182 | }, 183 | }, 184 | }, 185 | }, 186 | }, 187 | } 188 | 189 | export default () => { 190 | return ( 191 | <FormProvider form={form}> 192 | <SchemaField schema={schema} scope={{ formStep }} /> 193 | <FormConsumer> 194 | {() => ( 195 | <FormButtonGroup> 196 | <Button 197 | disabled={!formStep.allowBack} 198 | onClick={() => { 199 | formStep.back() 200 | }} 201 | > 202 | Previous 203 | </Button> 204 | <Button 205 | disabled={!formStep.allowNext} 206 | onClick={() => { 207 | formStep.next() 208 | }} 209 | > 210 | Next step 211 | </Button> 212 | <Button 213 | disabled={formStep.allowNext} 214 | onClick={() => { 215 | formStep.submit(console.log) 216 | }} 217 | > 218 | submit 219 | </Button> 220 | </FormButtonGroup> 221 | )} 222 | </FormConsumer> 223 | </FormProvider> 224 | ) 225 | } 226 | ``` 227 | 228 | ## API 229 | 230 | ### FormStep 231 | 232 | | Property name | Type | Description | Default value | 233 | | ------------- | --------- | ------------------------------------------------------- | ------------- | 234 | | formStep | IFormStep | Pass in the model created by createFormStep/useFormStep | | 235 | 236 | Other references https://ant.design/components/steps-cn/ 237 | 238 | ### FormStep.StepPane 239 | 240 | Refer to https://ant.design/components/steps-cn/ Steps.Step properties 241 | 242 | ### FormStep.createFormStep 243 | 244 | ```ts pure 245 | import { Form } from '@formily/core' 246 | 247 | interface createFormStep { 248 | (current?: number): IFormStep 249 | } 250 | 251 | interface IFormTab { 252 | //Current index 253 | current: number 254 | //Whether to allow backwards 255 | allowNext: boolean 256 | //Whether to allow forward 257 | allowBack: boolean 258 | //Set the current index 259 | setCurrent(key: number): void 260 | //submit Form 261 | submit: Form['submit'] 262 | //backward 263 | next(): void 264 | //forward 265 | back(): void 266 | } 267 | ``` 268 | ``` -------------------------------------------------------------------------------- /packages/react/docs/api/components/SchemaField.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | order: 4 3 | --- 4 | 5 | # SchemaField 6 | 7 | ## Description 8 | 9 | The SchemaField component is a component specially used to parse [JSON-Schema](/api/shared/schema) dynamically rendering forms. 10 | When using the SchemaField component, you need to create a SchemaField component through the createSchemaField factory function. 11 | 12 | ## Signature 13 | 14 | ```ts 15 | //SchemaField component and its static properties 16 | type ComposeSchemaField = React.FC< 17 | React.PropsWithChildren<ISchemaFieldProps> 18 | > & { 19 | Markup: React.FC<React.PropsWithChildren<ISchema>> 20 | String: React.FC<React.PropsWithChildren<Omit<ISchema, 'type'>>> 21 | Object: React.FC<React.PropsWithChildren<Omit<ISchema, 'type'>>> 22 | Array: React.FC<React.PropsWithChildren<Omit<ISchema, 'type'>>> 23 | Date: React.FC<React.PropsWithChildren<Omit<ISchema, 'type'>>> 24 | DateTime: React.FC<React.PropsWithChildren<Omit<ISchema, 'type'>>> 25 | Boolean: React.FC<React.PropsWithChildren<Omit<ISchema, 'type'>>> 26 | Number: React.FC<React.PropsWithChildren<Omit<ISchema, 'type'>>> 27 | Void: React.FC<React.PropsWithChildren<Omit<ISchema, 'type'>>> 28 | } 29 | 30 | //Factory function parameter attributes 31 | interface ISchemaFieldFactoryProps { 32 | components?: { 33 | [key: string]: React.FC //Component list 34 | } 35 | scope?: any //Global scope, used to implement protocol expression variable injection 36 | } 37 | 38 | //SchemaField attribute 39 | interface ISchemaFieldProps extends IFieldFactoryProps { 40 | schema?: ISchema //Field schema 41 | scope?: any //Protocol expression scope 42 | name?: string //Field name 43 | components?: { 44 | [key: string]: React.FC //Partial component list, note: the components passed here cannot enjoy smart prompts 45 | } 46 | } 47 | 48 | //Factory function 49 | interface createSchemaField { 50 | (props: ISchemaFieldFactoryProps): ComposeSchemaField 51 | } 52 | ``` 53 | 54 | IFieldFactoryProps reference [IFieldFactoryProps](https://core.formilyjs.org/api/models/form#ifieldfactoryprops) 55 | 56 | ISchema Reference [ISchema](/api/shared/schema#ischema) 57 | 58 | ## Markup Schema Use Case 59 | 60 | ```tsx 61 | import React from 'react' 62 | import { createForm } from '@formily/core' 63 | import { FormProvider, createSchemaField } from '@formily/react' 64 | import { Input, Select } from 'antd' 65 | 66 | const form = createForm() 67 | 68 | const SchemaField = createSchemaField({ 69 | components: { 70 | Input, 71 | }, 72 | }) 73 | 74 | export default () => ( 75 | <FormProvider form={form}> 76 | <SchemaField 77 | components={{ 78 | Select, 79 | }} 80 | > 81 | <SchemaField.String name="input" x-component="Input" /> 82 | <SchemaField.String 83 | name="select" 84 | x-component="Select" 85 | x-component-props={{ 86 | style: { 87 | width: 200, 88 | marginTop: 20, 89 | }, 90 | }} 91 | /> 92 | </SchemaField> 93 | </FormProvider> 94 | ) 95 | ``` 96 | 97 | ## JSON Schema Use Case 98 | 99 | ```tsx 100 | import React from 'react' 101 | import { createForm } from '@formily/core' 102 | import { FormProvider, createSchemaField } from '@formily/react' 103 | import { Input, Select } from 'antd' 104 | 105 | const form = createForm() 106 | 107 | const SchemaField = createSchemaField({ 108 | components: { 109 | Input, 110 | }, 111 | }) 112 | 113 | export default () => ( 114 | <FormProvider form={form}> 115 | <SchemaField 116 | components={{ 117 | Select, 118 | }} 119 | schema={{ 120 | type: 'object', 121 | properties: { 122 | input: { 123 | type: 'string', 124 | 'x-component': 'Input', 125 | }, 126 | select: { 127 | type: 'string', 128 | 'x-component': 'Select', 129 | 'x-component-props': { 130 | style: { 131 | width: 200, 132 | marginTop: 20, 133 | }, 134 | }, 135 | }, 136 | }, 137 | }} 138 | ></SchemaField> 139 | </FormProvider> 140 | ) 141 | ``` 142 | 143 | ## JSON Schema ReactNode Prop Use Case (x-slot-node) 144 | 145 | Reference [Slot](https://react.formilyjs.org/api/shared/schema#slot) 146 | 147 | ```tsx 148 | import React from 'react' 149 | import { createForm } from '@formily/core' 150 | import { FormProvider, createSchemaField } from '@formily/react' 151 | import { Input, Tag } from 'antd' 152 | import { CheckCircleTwoTone, CloseCircleOutlined } from '@ant-design/icons' 153 | 154 | const form = createForm() 155 | 156 | const SchemaField = createSchemaField({ 157 | components: { 158 | Input, 159 | CheckCircleTwoTone, 160 | CloseCircleOutlined, 161 | }, 162 | }) 163 | 164 | export default () => ( 165 | <FormProvider form={form}> 166 | <SchemaField 167 | components={{ 168 | Tag, 169 | }} 170 | schema={{ 171 | type: 'object', 172 | properties: { 173 | tag: { 174 | 'x-slot-node': { 175 | target: 'input.x-component-props.prefix', 176 | }, 177 | 'x-component': 'Tag', 178 | 'x-component-props': { 179 | children: 'www.', 180 | }, 181 | }, 182 | tag2: { 183 | 'x-slot-node': { 184 | target: 'input.x-component-props.suffix', 185 | }, 186 | 'x-component': 'Tag', 187 | 'x-component-props': { 188 | children: '.com', 189 | }, 190 | }, 191 | icon: { 192 | 'x-slot-node': { 193 | target: 'input.x-component-props.addonAfter', 194 | }, 195 | 'x-component': 196 | '{{$form.values.input?.length > 5 ? "CheckCircleTwoTone" : "CloseCircleOutlined"}}', 197 | }, 198 | input: { 199 | type: 'string', 200 | 'x-component': 'Input', 201 | 'x-component-props': {}, 202 | }, 203 | }, 204 | }} 205 | ></SchemaField> 206 | </FormProvider> 207 | ) 208 | ``` 209 | 210 | ## JSON Schema Render Prop Use Case (x-slot-node & isRenderProp) 211 | 212 | Reference [Slot](https://react.formilyjs.org/api/shared/schema#slot) 213 | 214 | ```tsx 215 | import React from 'react' 216 | import { createForm } from '@formily/core' 217 | import { FormProvider, createSchemaField } from '@formily/react' 218 | import { Rate } from 'antd' 219 | import { DollarOutlined } from '@ant-design/icons' 220 | 221 | const form = createForm() 222 | 223 | const SchemaField = createSchemaField({ 224 | components: { 225 | DollarOutlined, 226 | }, 227 | }) 228 | 229 | export default () => ( 230 | <FormProvider form={form}> 231 | <SchemaField 232 | components={{ 233 | Rate, 234 | }} 235 | schema={{ 236 | type: 'object', 237 | properties: { 238 | icon: { 239 | 'x-slot-node': { 240 | target: 'rate.x-component-props.character', 241 | isRenderProp: true, 242 | }, 243 | 'x-component': 'DollarOutlined', 244 | 'x-component-props': { 245 | rotate: '{{ $slotArgs[0].value * 45 }}', 246 | style: { 247 | fontSize: '50px', 248 | }, 249 | }, 250 | }, 251 | rate: { 252 | 'x-component': 'Rate', 253 | 'x-component-props': { 254 | defaultValue: 3, 255 | }, 256 | }, 257 | }, 258 | }} 259 | ></SchemaField> 260 | </FormProvider> 261 | ) 262 | ``` 263 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormStep.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormStep 2 | 3 | > Step-by-step form components 4 | > 5 | > Note: This component can only be used in Schema scenarios 6 | 7 | ## Markup Schema example 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { FormStep, FormItem, Input, FormButtonGroup } from '@formily/next' 12 | import { createForm } from '@formily/core' 13 | import { FormProvider, FormConsumer, createSchemaField } from '@formily/react' 14 | import { Button } from '@alifd/next' 15 | 16 | const SchemaField = createSchemaField({ 17 | components: { 18 | FormItem, 19 | FormStep, 20 | Input, 21 | }, 22 | }) 23 | 24 | const form = createForm() 25 | const formStep = FormStep.createFormStep() 26 | 27 | export default () => { 28 | return ( 29 | <FormProvider form={form}> 30 | <SchemaField> 31 | <SchemaField.Void 32 | x-component="FormStep" 33 | x-component-props={{ formStep }} 34 | > 35 | <SchemaField.Void 36 | x-component="FormStep.StepPane" 37 | x-component-props={{ title: 'First Step' }} 38 | > 39 | <SchemaField.String 40 | name="aaa" 41 | x-decorator="FormItem" 42 | required 43 | x-component="Input" 44 | /> 45 | </SchemaField.Void> 46 | <SchemaField.Void 47 | x-component="FormStep.StepPane" 48 | x-component-props={{ title: 'Second Step' }} 49 | > 50 | <SchemaField.String 51 | name="bbb" 52 | x-decorator="FormItem" 53 | required 54 | x-component="Input" 55 | /> 56 | </SchemaField.Void> 57 | <SchemaField.Void 58 | type="void" 59 | x-component="FormStep.StepPane" 60 | x-component-props={{ title: 'Step 3' }} 61 | > 62 | <SchemaField.String 63 | name="ccc" 64 | x-decorator="FormItem" 65 | required 66 | x-component="Input" 67 | /> 68 | </SchemaField.Void> 69 | </SchemaField.Void> 70 | </SchemaField> 71 | <FormConsumer> 72 | {() => ( 73 | <FormButtonGroup> 74 | <Button 75 | disabled={!formStep.allowBack} 76 | onClick={() => { 77 | formStep.back() 78 | }} 79 | > 80 | Previous 81 | </Button> 82 | <Button 83 | disabled={!formStep.allowNext} 84 | onClick={() => { 85 | formStep.next() 86 | }} 87 | > 88 | Next step 89 | </Button> 90 | <Button 91 | disabled={formStep.allowNext} 92 | onClick={() => { 93 | formStep.submit(console.log) 94 | }} 95 | > 96 | submit 97 | </Button> 98 | </FormButtonGroup> 99 | )} 100 | </FormConsumer> 101 | </FormProvider> 102 | ) 103 | } 104 | ``` 105 | 106 | ## JSON Schema case 107 | 108 | ```tsx 109 | import React from 'react' 110 | import { FormStep, FormItem, Input, FormButtonGroup } from '@formily/next' 111 | import { createForm } from '@formily/core' 112 | import { FormProvider, FormConsumer, createSchemaField } from '@formily/react' 113 | import { Button } from '@alifd/next' 114 | 115 | const SchemaField = createSchemaField({ 116 | components: { 117 | FormItem, 118 | FormStep, 119 | Input, 120 | }, 121 | }) 122 | 123 | const form = createForm() 124 | const formStep = FormStep.createFormStep() 125 | 126 | const schema = { 127 | type: 'object', 128 | properties: { 129 | step: { 130 | type: 'void', 131 | 'x-component': 'FormStep', 132 | 'x-component-props': { 133 | formStep: '{{formStep}}', 134 | }, 135 | properties: { 136 | step1: { 137 | type: 'void', 138 | 'x-component': 'FormStep.StepPane', 139 | 'x-component-props': { 140 | title: 'First Step', 141 | }, 142 | properties: { 143 | aaa: { 144 | type: 'string', 145 | title: 'AAA', 146 | required: true, 147 | 'x-decorator': 'FormItem', 148 | 'x-component': 'Input', 149 | }, 150 | }, 151 | }, 152 | step2: { 153 | type: 'void', 154 | 'x-component': 'FormStep.StepPane', 155 | 'x-component-props': { 156 | title: 'Second Step', 157 | }, 158 | properties: { 159 | bbb: { 160 | type: 'string', 161 | title: 'AAA', 162 | required: true, 163 | 'x-decorator': 'FormItem', 164 | 'x-component': 'Input', 165 | }, 166 | }, 167 | }, 168 | step3: { 169 | type: 'void', 170 | 'x-component': 'FormStep.StepPane', 171 | 'x-component-props': { 172 | title: 'The third step', 173 | }, 174 | properties: { 175 | ccc: { 176 | type: 'string', 177 | title: 'AAA', 178 | required: true, 179 | 'x-decorator': 'FormItem', 180 | 'x-component': 'Input', 181 | }, 182 | }, 183 | }, 184 | }, 185 | }, 186 | }, 187 | } 188 | 189 | export default () => { 190 | return ( 191 | <FormProvider form={form}> 192 | <SchemaField schema={schema} scope={{ formStep }} /> 193 | <FormConsumer> 194 | {() => ( 195 | <FormButtonGroup> 196 | <Button 197 | disabled={!formStep.allowBack} 198 | onClick={() => { 199 | formStep.back() 200 | }} 201 | > 202 | Previous 203 | </Button> 204 | <Button 205 | disabled={!formStep.allowNext} 206 | onClick={() => { 207 | formStep.next() 208 | }} 209 | > 210 | Next step 211 | </Button> 212 | <Button 213 | disabled={formStep.allowNext} 214 | onClick={() => { 215 | formStep.submit(console.log) 216 | }} 217 | > 218 | submit 219 | </Button> 220 | </FormButtonGroup> 221 | )} 222 | </FormConsumer> 223 | </FormProvider> 224 | ) 225 | } 226 | ``` 227 | 228 | ## API 229 | 230 | ### FormStep 231 | 232 | | Property name | Type | Description | Default value | 233 | | ------------- | --------- | ------------------------------------------------------- | ------------- | 234 | | formStep | IFormStep | Pass in the model created by createFormStep/useFormStep | | 235 | 236 | Other references https://fusion.design/pc/component/basic/step 237 | 238 | ### FormStep.StepPane 239 | 240 | Refer to https://fusion.design/pc/component/basic/step Steps.Step properties 241 | 242 | ### FormStep.createFormStep 243 | 244 | ```ts pure 245 | import { Form } from '@formily/core' 246 | 247 | interface createFormStep { 248 | (current?: number): IFormStep 249 | } 250 | 251 | interface IFormTab { 252 | //Current index 253 | current: number 254 | //Whether to allow backwards 255 | allowNext: boolean 256 | //Whether to allow forward 257 | allowBack: boolean 258 | //Set the current index 259 | setCurrent(key: number): void 260 | //submit Form 261 | submit: Form['submit'] 262 | //backward 263 | next(): void 264 | //forward 265 | back(): void 266 | } 267 | ``` 268 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/reaction.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isFn } from './checkers' 2 | import { ArraySet } from './array' 3 | import { IOperation, ReactionsMap, Reaction, PropertyKey } from './types' 4 | import { 5 | ReactionStack, 6 | PendingScopeReactions, 7 | BatchEndpoints, 8 | DependencyCollected, 9 | RawReactionsMap, 10 | PendingReactions, 11 | BatchCount, 12 | UntrackCount, 13 | BatchScope, 14 | ObserverListeners, 15 | } from './environment' 16 | 17 | const ITERATION_KEY = Symbol('iteration key') 18 | 19 | const addRawReactionsMap = ( 20 | target: any, 21 | key: PropertyKey, 22 | reaction: Reaction 23 | ) => { 24 | const reactionsMap = RawReactionsMap.get(target) 25 | if (reactionsMap) { 26 | const reactions = reactionsMap.get(key) 27 | if (reactions) { 28 | reactions.add(reaction) 29 | } else { 30 | reactionsMap.set(key, new ArraySet([reaction])) 31 | } 32 | return reactionsMap 33 | } else { 34 | const reactionsMap: ReactionsMap = new Map([ 35 | [key, new ArraySet([reaction])], 36 | ]) 37 | RawReactionsMap.set(target, reactionsMap) 38 | return reactionsMap 39 | } 40 | } 41 | 42 | const addReactionsMapToReaction = ( 43 | reaction: Reaction, 44 | reactionsMap: ReactionsMap 45 | ) => { 46 | const bindSet = reaction._reactionsSet 47 | if (bindSet) { 48 | bindSet.add(reactionsMap) 49 | } else { 50 | reaction._reactionsSet = new ArraySet([reactionsMap]) 51 | } 52 | return bindSet 53 | } 54 | 55 | const getReactionsFromTargetKey = (target: any, key: PropertyKey) => { 56 | const reactionsMap = RawReactionsMap.get(target) 57 | const reactions = [] 58 | if (reactionsMap) { 59 | const map = reactionsMap.get(key) 60 | if (map) { 61 | map.forEach((reaction) => { 62 | if (reactions.indexOf(reaction) === -1) { 63 | reactions.push(reaction) 64 | } 65 | }) 66 | } 67 | } 68 | return reactions 69 | } 70 | 71 | const runReactions = (target: any, key: PropertyKey) => { 72 | const reactions = getReactionsFromTargetKey(target, key) 73 | const prevUntrackCount = UntrackCount.value 74 | UntrackCount.value = 0 75 | for (let i = 0, len = reactions.length; i < len; i++) { 76 | const reaction = reactions[i] 77 | if (reaction._isComputed) { 78 | reaction._scheduler(reaction) 79 | } else if (isScopeBatching()) { 80 | PendingScopeReactions.add(reaction) 81 | } else if (isBatching()) { 82 | PendingReactions.add(reaction) 83 | } else { 84 | // never reach 85 | if (isFn(reaction._scheduler)) { 86 | reaction._scheduler(reaction) 87 | } else { 88 | reaction() 89 | } 90 | } 91 | } 92 | UntrackCount.value = prevUntrackCount 93 | } 94 | 95 | const notifyObservers = (operation: IOperation) => { 96 | ObserverListeners.forEach((fn) => fn(operation)) 97 | } 98 | 99 | export const bindTargetKeyWithCurrentReaction = (operation: IOperation) => { 100 | let { key, type, target } = operation 101 | if (type === 'iterate') { 102 | key = ITERATION_KEY 103 | } 104 | const reactionLen = ReactionStack.length 105 | if (reactionLen === 0) return 106 | const current = ReactionStack[reactionLen - 1] 107 | if (isUntracking()) return 108 | if (current) { 109 | DependencyCollected.value = true 110 | addReactionsMapToReaction(current, addRawReactionsMap(target, key, current)) 111 | } 112 | } 113 | 114 | export const bindComputedReactions = (reaction: Reaction) => { 115 | if (isFn(reaction)) { 116 | const current = ReactionStack[ReactionStack.length - 1] 117 | if (current) { 118 | const computes = current._computesSet 119 | if (computes) { 120 | computes.add(reaction) 121 | } else { 122 | current._computesSet = new ArraySet([reaction]) 123 | } 124 | } 125 | } 126 | } 127 | 128 | export const runReactionsFromTargetKey = (operation: IOperation) => { 129 | let { key, type, target, oldTarget } = operation 130 | batchStart() 131 | notifyObservers(operation) 132 | if (type === 'clear') { 133 | oldTarget.forEach((_: any, key: PropertyKey) => { 134 | runReactions(target, key) 135 | }) 136 | } else { 137 | runReactions(target, key) 138 | } 139 | if (type === 'add' || type === 'delete' || type === 'clear') { 140 | const newKey = Array.isArray(target) ? 'length' : ITERATION_KEY 141 | runReactions(target, newKey) 142 | } 143 | batchEnd() 144 | } 145 | 146 | export const hasRunningReaction = () => { 147 | return ReactionStack.length > 0 148 | } 149 | 150 | export const releaseBindingReactions = (reaction: Reaction) => { 151 | reaction._reactionsSet?.forEach((reactionsMap) => { 152 | reactionsMap.forEach((reactions) => { 153 | reactions.delete(reaction) 154 | }) 155 | }) 156 | PendingReactions.delete(reaction) 157 | PendingScopeReactions.delete(reaction) 158 | delete reaction._reactionsSet 159 | } 160 | 161 | export const suspendComputedReactions = (current: Reaction) => { 162 | current._computesSet?.forEach((reaction) => { 163 | const reactions = getReactionsFromTargetKey( 164 | reaction._context, 165 | reaction._property 166 | ) 167 | if (reactions.length === 0) { 168 | disposeBindingReactions(reaction) 169 | reaction._dirty = true 170 | } 171 | }) 172 | } 173 | 174 | export const disposeBindingReactions = (reaction: Reaction) => { 175 | reaction._disposed = true 176 | releaseBindingReactions(reaction) 177 | suspendComputedReactions(reaction) 178 | } 179 | 180 | export const batchStart = () => { 181 | BatchCount.value++ 182 | } 183 | 184 | export const batchEnd = () => { 185 | BatchCount.value-- 186 | if (BatchCount.value === 0) { 187 | const prevUntrackCount = UntrackCount.value 188 | UntrackCount.value = 0 189 | executePendingReactions() 190 | executeBatchEndpoints() 191 | UntrackCount.value = prevUntrackCount 192 | } 193 | } 194 | 195 | export const batchScopeStart = () => { 196 | BatchScope.value = true 197 | } 198 | 199 | export const batchScopeEnd = () => { 200 | const prevUntrackCount = UntrackCount.value 201 | BatchScope.value = false 202 | UntrackCount.value = 0 203 | PendingScopeReactions.batchDelete((reaction) => { 204 | if (isFn(reaction._scheduler)) { 205 | reaction._scheduler(reaction) 206 | } else { 207 | reaction() 208 | } 209 | }) 210 | UntrackCount.value = prevUntrackCount 211 | } 212 | 213 | export const untrackStart = () => { 214 | UntrackCount.value++ 215 | } 216 | 217 | export const untrackEnd = () => { 218 | UntrackCount.value-- 219 | } 220 | 221 | export const isBatching = () => BatchCount.value > 0 222 | 223 | export const isScopeBatching = () => BatchScope.value 224 | 225 | export const isUntracking = () => UntrackCount.value > 0 226 | 227 | export const executePendingReactions = () => { 228 | PendingReactions.batchDelete((reaction) => { 229 | if (isFn(reaction._scheduler)) { 230 | reaction._scheduler(reaction) 231 | } else { 232 | reaction() 233 | } 234 | }) 235 | } 236 | 237 | export const executeBatchEndpoints = () => { 238 | BatchEndpoints.batchDelete((callback) => { 239 | callback() 240 | }) 241 | } 242 | 243 | export const hasDepsChange = (newDeps: any[], oldDeps: any[]) => { 244 | if (newDeps === oldDeps) return false 245 | if (newDeps.length !== oldDeps.length) return true 246 | if (newDeps.some((value, index) => value !== oldDeps[index])) return true 247 | return false 248 | } 249 | 250 | export const disposeEffects = (reaction: Reaction) => { 251 | if (reaction._effects) { 252 | try { 253 | batchStart() 254 | reaction._effects.queue.forEach((item) => { 255 | if (!item || !item.dispose) return 256 | item.dispose() 257 | }) 258 | } finally { 259 | batchEnd() 260 | } 261 | } 262 | } 263 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-item/feedback.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaVoidField x-component="Title" x-content="表单状态: " /> 5 | <SchemaStringField 6 | title="错误状态(feedbackStatus=error)" 7 | x-decorator="FormItem" 8 | x-component="Input" 9 | description="description" 10 | :x-decorator-props="{ 11 | feedbackStatus: 'error', 12 | }" 13 | /> 14 | <SchemaStringField 15 | title="警告状态(feedbackStatus=warning)" 16 | x-decorator="FormItem" 17 | x-component="Input" 18 | description="description" 19 | :x-decorator-props="{ 20 | feedbackStatus: 'warning', 21 | }" 22 | /> 23 | <SchemaStringField 24 | title="成功状态(feedbackStatus=success)" 25 | x-decorator="FormItem" 26 | x-component="Input" 27 | description="description" 28 | :x-decorator-props="{ 29 | feedbackStatus: 'success', 30 | }" 31 | /> 32 | <SchemaStringField 33 | title="加载状态(feedbackStatus=pending)" 34 | x-decorator="FormItem" 35 | x-component="Input" 36 | description="description" 37 | :x-decorator-props="{ 38 | feedbackStatus: 'pending', 39 | }" 40 | /> 41 | <SchemaVoidField x-component="Title" x-content="反馈信息的布局: " /> 42 | <SchemaStringField 43 | title="紧凑模式required" 44 | x-decorator="FormItem" 45 | x-component="Input" 46 | :required="true" 47 | :x-decorator-props="{ 48 | feedbackLayout: 'terse', 49 | }" 50 | /> 51 | <SchemaStringField 52 | title="紧凑模式有feedback(feedbackLayout=terse)" 53 | x-decorator="FormItem" 54 | x-component="Input" 55 | :x-decorator-props="{ 56 | feedbackStatus: 'error', 57 | feedbackText: 'error message', 58 | feedbackLayout: 'terse', 59 | }" 60 | /> 61 | <SchemaStringField 62 | title="紧凑模式无feedback(feedbackLayout=terse)" 63 | x-decorator="FormItem" 64 | x-component="Input" 65 | :x-decorator-props="{ 66 | feedbackLayout: 'terse', 67 | }" 68 | /> 69 | <SchemaStringField 70 | title="松散模式(feedbackLayout=loose)" 71 | x-decorator="FormItem" 72 | x-component="Input" 73 | :x-decorator-props="{ 74 | feedbackStatus: 'error', 75 | feedbackText: 'error message', 76 | feedbackLayout: 'loose', 77 | }" 78 | /> 79 | <SchemaStringField 80 | title="弹出模式(feedbackLayout=popover)" 81 | x-decorator="FormItem" 82 | x-component="Input" 83 | :x-decorator-props="{ 84 | feedbackStatus: 'warning', 85 | feedbackText: 'warning message', 86 | feedbackLayout: 'popover', 87 | }" 88 | /> 89 | <SchemaStringField 90 | title="弹出模式(feedbackLayout=popover)" 91 | x-decorator="FormItem" 92 | x-component="Input" 93 | :x-decorator-props="{ 94 | feedbackStatus: 'error', 95 | feedbackText: 'error message', 96 | feedbackLayout: 'popover', 97 | }" 98 | /> 99 | <SchemaStringField 100 | title="弹出模式(feedbackLayout=popover)" 101 | x-decorator="FormItem" 102 | x-component="Input" 103 | :x-decorator-props="{ 104 | feedbackStatus: 'success', 105 | feedbackText: 'success message', 106 | feedbackLayout: 'popover', 107 | }" 108 | /> 109 | <SchemaVoidField x-component="Title" x-content="组件的适配情况: " /> 110 | <SchemaVoidField 111 | x-component="FormLayout" 112 | :x-component-props="{ 113 | labelCol: 6, 114 | wrapperCol: 10, 115 | }" 116 | > 117 | <SchemaStringField 118 | title="Select" 119 | x-decorator="FormItem" 120 | x-component="Select" 121 | :x-decorator-props="{ 122 | feedbackStatus: 'success', 123 | feedbackIcon: SuccessIcon, 124 | }" 125 | /> 126 | <SchemaStringField 127 | title="DatePicker" 128 | x-decorator="FormItem" 129 | x-component="DatePicker" 130 | :x-decorator-props="{ 131 | feedbackStatus: 'success', 132 | feedbackIcon: SuccessIcon, 133 | }" 134 | /> 135 | <SchemaStringField 136 | title="DateRangePicker" 137 | x-decorator="FormItem" 138 | x-component="DatePicker" 139 | :x-decorator-props="{ 140 | feedbackStatus: 'success', 141 | feedbackIcon: SuccessIcon, 142 | }" 143 | :x-component-props="{ 144 | type: 'daterange', 145 | }" 146 | /> 147 | <SchemaStringField 148 | title="YearPicker" 149 | x-decorator="FormItem" 150 | x-component="DatePicker" 151 | :x-decorator-props="{ 152 | feedbackStatus: 'success', 153 | feedbackIcon: SuccessIcon, 154 | }" 155 | :x-component-props="{ 156 | type: 'year', 157 | }" 158 | /> 159 | <SchemaStringField 160 | title="MonthPicker" 161 | x-decorator="FormItem" 162 | x-component="DatePicker" 163 | :x-decorator-props="{ 164 | feedbackStatus: 'success', 165 | feedbackIcon: SuccessIcon, 166 | }" 167 | :x-component-props="{ 168 | type: 'month', 169 | }" 170 | /> 171 | <SchemaStringField 172 | title="TimePicker" 173 | x-decorator="FormItem" 174 | x-component="TimePicker" 175 | :x-decorator-props="{ 176 | feedbackStatus: 'success', 177 | feedbackIcon: SuccessIcon, 178 | }" 179 | /> 180 | <SchemaStringField 181 | title="InputNumber" 182 | x-decorator="FormItem" 183 | x-component="InputNumber" 184 | :x-decorator-props="{ 185 | feedbackStatus: 'success', 186 | feedbackIcon: SuccessIcon, 187 | }" 188 | /> 189 | <SchemaStringField 190 | title="Cascader" 191 | x-decorator="FormItem" 192 | x-component="Cascader" 193 | :x-decorator-props="{ 194 | feedbackStatus: 'success', 195 | feedbackIcon: SuccessIcon, 196 | }" 197 | /> 198 | </SchemaVoidField> 199 | </SchemaField> 200 | </FormProvider> 201 | </template> 202 | 203 | <script> 204 | import { createForm } from '@formily/core' 205 | import { createSchemaField, FormProvider } from '@formily/vue' 206 | import { 207 | FormItem, 208 | InputNumber, 209 | Input, 210 | Cascader, 211 | Select, 212 | DatePicker, 213 | FormLayout, 214 | TimePicker, 215 | } from '@formily/element' 216 | 217 | const SuccessIcon = { 218 | functional: true, 219 | render(h) { 220 | return h('i', { 221 | class: 'el-icon-circle-check', 222 | style: { color: '#8AE65C' }, 223 | }) 224 | }, 225 | } 226 | 227 | const Title = { 228 | functional: true, 229 | render(h, context) { 230 | return h('p', context.data, context.children) 231 | }, 232 | } 233 | 234 | const fields = createSchemaField({ 235 | components: { 236 | Title, 237 | FormItem, 238 | InputNumber, 239 | Input, 240 | Cascader, 241 | Select, 242 | DatePicker, 243 | FormLayout, 244 | TimePicker, 245 | }, 246 | }) 247 | 248 | export default { 249 | components: { FormProvider, ...fields }, 250 | data() { 251 | const form = createForm() 252 | return { 253 | form, 254 | SuccessIcon, 255 | } 256 | }, 257 | } 258 | </script> 259 | ``` -------------------------------------------------------------------------------- /packages/json-schema/src/transformer.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { untracked, autorun, observable } from '@formily/reactive' 2 | import { 3 | isArr, 4 | isStr, 5 | toArr, 6 | each, 7 | isFn, 8 | isPlainObj, 9 | reduce, 10 | lazyMerge, 11 | } from '@formily/shared' 12 | import { Schema } from './schema' 13 | import { 14 | ISchema, 15 | ISchemaTransformerOptions, 16 | IFieldStateSetterOptions, 17 | SchemaReaction, 18 | } from './types' 19 | import { 20 | onFieldInit, 21 | onFieldMount, 22 | onFieldUnmount, 23 | onFieldValueChange, 24 | onFieldInputValueChange, 25 | onFieldInitialValueChange, 26 | onFieldValidateStart, 27 | onFieldValidateEnd, 28 | onFieldValidateFailed, 29 | onFieldValidateSuccess, 30 | IFieldFactoryProps, 31 | Field, 32 | } from '@formily/core' 33 | import { patchCompile, patchSchemaCompile, shallowCompile } from './compiler' 34 | 35 | const FieldEffects = { 36 | onFieldInit, 37 | onFieldMount, 38 | onFieldUnmount, 39 | onFieldValueChange, 40 | onFieldInputValueChange, 41 | onFieldInitialValueChange, 42 | onFieldValidateStart, 43 | onFieldValidateEnd, 44 | onFieldValidateFailed, 45 | onFieldValidateSuccess, 46 | } 47 | 48 | const DefaultFieldEffects = ['onFieldInit', 'onFieldValueChange'] 49 | 50 | const getDependencyValue = ( 51 | field: Field, 52 | pattern: string, 53 | property?: string 54 | ) => { 55 | const [target, path] = String(pattern).split(/\s*#\s*/) 56 | return field.query(target).getIn(path || property || 'value') 57 | } 58 | 59 | const getDependencies = ( 60 | field: Field, 61 | dependencies: 62 | | Array<string | { name?: string; source?: string; property?: string }> 63 | | object 64 | ) => { 65 | if (isArr(dependencies)) { 66 | const results = [] 67 | dependencies.forEach((pattern) => { 68 | if (isStr(pattern)) { 69 | results.push(getDependencyValue(field, pattern)) 70 | } else if (isPlainObj(pattern)) { 71 | if (pattern.name && pattern.source) { 72 | results[pattern.name] = getDependencyValue( 73 | field, 74 | pattern.source, 75 | pattern.property 76 | ) 77 | } 78 | } 79 | }) 80 | return results 81 | } else if (isPlainObj(dependencies)) { 82 | return reduce( 83 | dependencies, 84 | (buf, pattern, key) => { 85 | buf[key] = getDependencyValue(field, pattern) 86 | return buf 87 | }, 88 | {} 89 | ) 90 | } 91 | return [] 92 | } 93 | 94 | const setSchemaFieldState = ( 95 | options: IFieldStateSetterOptions, 96 | demand = false 97 | ) => { 98 | const { request, target, runner, field, scope } = options || {} 99 | if (!request) return 100 | if (target) { 101 | if (request.state) { 102 | field.form.setFieldState(target, (state) => 103 | patchCompile( 104 | state, 105 | request.state, 106 | lazyMerge(scope, { 107 | $target: state, 108 | }) 109 | ) 110 | ) 111 | } 112 | if (request.schema) { 113 | field.form.setFieldState(target, (state) => 114 | patchSchemaCompile( 115 | state, 116 | request.schema, 117 | lazyMerge(scope, { 118 | $target: state, 119 | }), 120 | demand 121 | ) 122 | ) 123 | } 124 | if (isStr(runner) && runner) { 125 | field.form.setFieldState(target, (state) => { 126 | shallowCompile( 127 | `{{function(){${runner}}}}`, 128 | lazyMerge(scope, { 129 | $target: state, 130 | }) 131 | )() 132 | }) 133 | } 134 | } else { 135 | if (request.state) { 136 | field.setState((state) => patchCompile(state, request.state, scope)) 137 | } 138 | if (request.schema) { 139 | field.setState((state) => 140 | patchSchemaCompile(state, request.schema, scope, demand) 141 | ) 142 | } 143 | if (isStr(runner) && runner) { 144 | shallowCompile(`{{function(){${runner}}}}`, scope)() 145 | } 146 | } 147 | } 148 | 149 | const getBaseScope = ( 150 | field: Field, 151 | options: ISchemaTransformerOptions = {} 152 | ) => { 153 | const $observable = (target: any, deps?: any[]) => 154 | autorun.memo(() => observable(target), deps) 155 | const $props = (props: any) => field.setComponentProps(props) 156 | const $effect = autorun.effect 157 | const $memo = autorun.memo 158 | const $self = field 159 | const $form = field.form 160 | const $values = field.form.values 161 | return lazyMerge( 162 | { 163 | get $lookup() { 164 | return options?.scope?.$record ?? $values 165 | }, 166 | get $records() { 167 | return field.records 168 | }, 169 | get $record() { 170 | const record = field.record 171 | if (typeof record === 'object') { 172 | return lazyMerge(record, { 173 | get $lookup() { 174 | return options?.scope?.$record ?? $values 175 | }, 176 | get $index() { 177 | return field.index 178 | }, 179 | }) 180 | } 181 | return record 182 | }, 183 | get $index() { 184 | return field.index 185 | }, 186 | }, 187 | options.scope, 188 | { 189 | $form, 190 | $self, 191 | $observable, 192 | $effect, 193 | $memo, 194 | $props, 195 | $values, 196 | } 197 | ) 198 | } 199 | 200 | const getBaseReactions = 201 | (schema: ISchema, options: ISchemaTransformerOptions) => (field: Field) => { 202 | setSchemaFieldState( 203 | { 204 | field, 205 | request: { schema }, 206 | scope: getBaseScope(field, options), 207 | }, 208 | true 209 | ) 210 | } 211 | 212 | const getUserReactions = ( 213 | schema: ISchema, 214 | options: ISchemaTransformerOptions 215 | ) => { 216 | const reactions: SchemaReaction[] = toArr(schema['x-reactions']) 217 | return reactions.map((unCompiled) => { 218 | return (field: Field) => { 219 | const baseScope = getBaseScope(field, options) 220 | const reaction = shallowCompile(unCompiled, baseScope) 221 | if (!reaction) return 222 | if (isFn(reaction)) { 223 | return reaction(field, baseScope) 224 | } 225 | const { when, fulfill, otherwise, target, effects } = reaction 226 | const run = () => { 227 | const $deps = getDependencies(field, reaction.dependencies) 228 | const $dependencies = $deps 229 | const scope = lazyMerge(baseScope, { 230 | $target: null, 231 | $deps, 232 | $dependencies, 233 | }) 234 | const compiledWhen = shallowCompile(when, scope) 235 | const condition = when ? compiledWhen : true 236 | const request = condition ? fulfill : otherwise 237 | const runner = request?.run 238 | setSchemaFieldState({ 239 | field, 240 | target, 241 | request, 242 | runner, 243 | scope, 244 | }) 245 | } 246 | 247 | if (target) { 248 | reaction.effects = effects?.length ? effects : DefaultFieldEffects 249 | } 250 | if (reaction.effects) { 251 | autorun.memo(() => { 252 | untracked(() => { 253 | each(reaction.effects, (type) => { 254 | if (FieldEffects[type]) { 255 | FieldEffects[type](field.address, run) 256 | } 257 | }) 258 | }) 259 | }, []) 260 | } else { 261 | run() 262 | } 263 | } 264 | }) 265 | } 266 | 267 | export const transformFieldProps = ( 268 | schema: Schema, 269 | options: ISchemaTransformerOptions 270 | ): IFieldFactoryProps<any, any> => { 271 | return { 272 | name: schema.name, 273 | reactions: [getBaseReactions(schema, options)].concat( 274 | getUserReactions(schema, options) 275 | ), 276 | } 277 | } 278 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/PreviewText.md: -------------------------------------------------------------------------------- ```markdown 1 | # PreviewText 2 | 3 | > Reading state components, mainly used to implement the reading state of these components of class Input and DatePicker 4 | 5 | ## Simple use case 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { PreviewText, FormItem, FormLayout } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | FormItem, 16 | PreviewText, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => { 23 | return ( 24 | <FormLayout labelCol={6} wrapperCol={10}> 25 | <FormProvider form={form}> 26 | <SchemaField> 27 | <SchemaField.String 28 | x-decorator="FormItem" 29 | title="text preview" 30 | x-component="PreviewText.Input" 31 | default={'Hello world'} 32 | /> 33 | <SchemaField.String 34 | x-decorator="FormItem" 35 | title="Select item preview" 36 | x-component="PreviewText.Select" 37 | x-component-props={{ 38 | mode: 'multiple', 39 | }} 40 | default={['123', '222']} 41 | enum={[ 42 | { label: 'A111', value: '123' }, 43 | { label: 'A222', value: '222' }, 44 | ]} 45 | /> 46 | 47 | <SchemaField.String 48 | x-decorator="FormItem" 49 | title="TreeSelect preview" 50 | x-component="PreviewText.TreeSelect" 51 | x-component-props={{ 52 | multiple: true, 53 | }} 54 | default={['123', '222']} 55 | enum={[ 56 | { label: 'A111', value: '123' }, 57 | { label: 'A222', value: '222' }, 58 | ]} 59 | /> 60 | <SchemaField.String 61 | x-decorator="FormItem" 62 | title="TreeSelect(treeData)preview" 63 | x-component="PreviewText.TreeSelect" 64 | x-component-props={{ 65 | multiple: true, 66 | treeNodeLabelProp: 'name', 67 | treeData: [ 68 | { name: 'A111', value: '123' }, 69 | { name: 'A222', value: '222' }, 70 | ], 71 | }} 72 | default={['123', '222']} 73 | /> 74 | <SchemaField.String 75 | x-decorator="FormItem" 76 | title="date preview" 77 | x-component="PreviewText.DatePicker" 78 | default={'2020-11-23 22:15:20'} 79 | /> 80 | <SchemaField.String 81 | x-decorator="FormItem" 82 | title="Cascader Preview" 83 | x-component="PreviewText.Cascader" 84 | default={'yuhang'} 85 | enum={[ 86 | { 87 | label: 'Hangzhou', 88 | value: 'hangzhou', 89 | children: [ 90 | { 91 | label: 'Yuhang', 92 | value: 'yuhang', 93 | }, 94 | ], 95 | }, 96 | ]} 97 | /> 98 | </SchemaField> 99 | </FormProvider> 100 | </FormLayout> 101 | ) 102 | } 103 | ``` 104 | 105 | ## Extended reading mode 106 | 107 | ```tsx 108 | import React from 'react' 109 | import { 110 | PreviewText, 111 | FormItem, 112 | FormLayout, 113 | FormButtonGroup, 114 | } from '@formily/antd' 115 | import { createForm } from '@formily/core' 116 | import { 117 | FormProvider, 118 | mapReadPretty, 119 | connect, 120 | createSchemaField, 121 | } from '@formily/react' 122 | import { Button, Input as AntdInput } from 'antd' 123 | 124 | const Input = connect(AntdInput, mapReadPretty(PreviewText.Input)) 125 | 126 | const SchemaField = createSchemaField({ 127 | components: { 128 | Input, 129 | FormItem, 130 | PreviewText, 131 | }, 132 | }) 133 | 134 | const form = createForm() 135 | 136 | export default () => { 137 | return ( 138 | <PreviewText.Placeholder value="No data currently available"> 139 | <FormLayout labelCol={6} wrapperCol={10}> 140 | <FormProvider form={form}> 141 | <SchemaField> 142 | <SchemaField.Markup 143 | type="string" 144 | x-decorator="FormItem" 145 | title="text preview" 146 | required 147 | x-component="Input" 148 | default={'Hello world'} 149 | /> 150 | <SchemaField.Markup 151 | type="string" 152 | x-decorator="FormItem" 153 | title="Select item preview" 154 | x-component="PreviewText.Select" 155 | x-component-props={{ 156 | mode: 'multiple', 157 | }} 158 | default={['123']} 159 | enum={[ 160 | { label: 'A111', value: '123' }, 161 | { label: 'A222', value: '222' }, 162 | ]} 163 | /> 164 | <SchemaField.Markup 165 | type="string" 166 | x-decorator="FormItem" 167 | title="date preview" 168 | x-component="PreviewText.DatePicker" 169 | /> 170 | <SchemaField.Markup 171 | type="string" 172 | x-decorator="FormItem" 173 | title="Cascader Preview" 174 | x-component="PreviewText.Cascader" 175 | default={'yuhang'} 176 | enum={[ 177 | { 178 | label: 'Hangzhou', 179 | value: 'hangzhou', 180 | children: [ 181 | { 182 | label: 'Yuhang', 183 | value: 'yuhang', 184 | }, 185 | ], 186 | }, 187 | ]} 188 | /> 189 | </SchemaField> 190 | <FormButtonGroup.FormItem> 191 | <Button 192 | onClick={() => { 193 | form.setState((state) => { 194 | state.editable = !state.editable 195 | }) 196 | }} 197 | > 198 | Switch reading mode 199 | </Button> 200 | </FormButtonGroup.FormItem> 201 | </FormProvider> 202 | </FormLayout> 203 | </PreviewText.Placeholder> 204 | ) 205 | } 206 | ``` 207 | 208 | ## API 209 | 210 | ### PreviewText.Input 211 | 212 | Reference https://ant.design/components/input-cn/ 213 | 214 | ### PreviewText.Select 215 | 216 | Reference https://ant.design/components/select-cn/ 217 | 218 | ### PreviewText.TreeSelect 219 | 220 | Reference https://ant.design/components/tree-select-cn/ 221 | 222 | ### PreviewText.Cascader 223 | 224 | Reference https://ant.design/components/cascader-cn/ 225 | 226 | ### PreviewText.DatePicker 227 | 228 | Reference https://ant.design/components/date-picker-cn/ 229 | 230 | ### PreviewText.DateRangePicker 231 | 232 | Reference https://ant.design/components/date-picker-cn/ 233 | 234 | ### PreviewText.TimePicker 235 | 236 | Reference https://ant.design/components/time-picker-cn/ 237 | 238 | ### PreviewText.TimeRangePicker 239 | 240 | Reference https://ant.design/components/time-picker-cn/ 241 | 242 | ### PreviewText.NumberPicker 243 | 244 | 参考 https://ant.design/components/input-number-cn/ 245 | 246 | ### PreviewText.Placeholder 247 | 248 | | Property name | Type | Description | Default value | 249 | | ------------- | ------ | ------------------- | ------------- | 250 | | value | stirng | Default placeholder | N/A | 251 | 252 | ### PreviewText.usePlaceholder 253 | 254 | ```ts pure 255 | interface usePlaceholder { 256 | (): string 257 | } 258 | ``` 259 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/FormCollapse.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormCollapse 2 | 3 | > 折叠面板,通常用在布局空间要求较高的表单场景 4 | > 5 | > 注意:只能用在 Schema 场景 6 | 7 | ## Markup Schema 案例 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | FormCollapse, 13 | FormLayout, 14 | FormItem, 15 | Input, 16 | FormButtonGroup, 17 | Submit, 18 | } from '@formily/antd' 19 | import { createForm } from '@formily/core' 20 | import { FormProvider, createSchemaField } from '@formily/react' 21 | import { Button } from 'antd' 22 | 23 | const SchemaField = createSchemaField({ 24 | components: { 25 | FormItem, 26 | FormCollapse, 27 | Input, 28 | }, 29 | }) 30 | 31 | const form = createForm() 32 | const formCollapse = FormCollapse.createFormCollapse() 33 | export default () => { 34 | return ( 35 | <FormProvider form={form}> 36 | <FormLayout labelCol={6} wrapperCol={10}> 37 | <SchemaField> 38 | <SchemaField.Void 39 | title="折叠面板" 40 | x-decorator="FormItem" 41 | x-component="FormCollapse" 42 | x-component-props={{ 43 | formCollapse, 44 | }} 45 | > 46 | <SchemaField.Void 47 | name="panel1" 48 | x-component="FormCollapse.CollapsePanel" 49 | x-component-props={{ header: 'A1' }} 50 | > 51 | <SchemaField.String 52 | name="aaa" 53 | title="AAA" 54 | x-decorator="FormItem" 55 | required 56 | x-component="Input" 57 | /> 58 | </SchemaField.Void> 59 | <SchemaField.Void 60 | name="panel2" 61 | x-component="FormCollapse.CollapsePanel" 62 | x-component-props={{ header: 'A2' }} 63 | > 64 | <SchemaField.String 65 | name="bbb" 66 | title="BBB" 67 | x-decorator="FormItem" 68 | required 69 | x-component="Input" 70 | /> 71 | </SchemaField.Void> 72 | <SchemaField.Void 73 | name="panel3" 74 | x-component="FormCollapse.CollapsePanel" 75 | x-component-props={{ header: 'A3' }} 76 | > 77 | <SchemaField.String 78 | name="ccc" 79 | title="CCC" 80 | x-decorator="FormItem" 81 | required 82 | x-component="Input" 83 | /> 84 | </SchemaField.Void> 85 | </SchemaField.Void> 86 | </SchemaField> 87 | <FormButtonGroup.FormItem> 88 | <Button 89 | onClick={() => { 90 | form.query('panel3').take((field) => { 91 | field.visible = !field.visible 92 | }) 93 | }} 94 | > 95 | 显示/隐藏最后一个Tab 96 | </Button> 97 | <Button 98 | onClick={() => { 99 | formCollapse.toggleActiveKey('panel2') 100 | }} 101 | > 102 | 切换第二个Tab 103 | </Button> 104 | <Submit onSubmit={console.log}>提交</Submit> 105 | </FormButtonGroup.FormItem> 106 | </FormLayout> 107 | </FormProvider> 108 | ) 109 | } 110 | ``` 111 | 112 | ## JSON Schema 案例 113 | 114 | ```tsx 115 | import React from 'react' 116 | import { 117 | FormCollapse, 118 | FormItem, 119 | FormLayout, 120 | Input, 121 | FormButtonGroup, 122 | Submit, 123 | } from '@formily/antd' 124 | import { createForm } from '@formily/core' 125 | import { FormProvider, createSchemaField } from '@formily/react' 126 | import { Button } from 'antd' 127 | 128 | const SchemaField = createSchemaField({ 129 | components: { 130 | FormItem, 131 | FormCollapse, 132 | Input, 133 | }, 134 | }) 135 | 136 | const form = createForm() 137 | const formCollapse = FormCollapse.createFormCollapse() 138 | const schema = { 139 | type: 'object', 140 | properties: { 141 | collapse: { 142 | type: 'void', 143 | title: '折叠面板', 144 | 'x-decorator': 'FormItem', 145 | 'x-component': 'FormCollapse', 146 | 'x-component-props': { 147 | formCollapse: '{{formCollapse}}', 148 | }, 149 | properties: { 150 | panel1: { 151 | type: 'void', 152 | 'x-component': 'FormCollapse.CollapsePanel', 153 | 'x-component-props': { 154 | header: 'A1', 155 | }, 156 | properties: { 157 | aaa: { 158 | type: 'string', 159 | title: 'AAA', 160 | 'x-decorator': 'FormItem', 161 | required: true, 162 | 'x-component': 'Input', 163 | }, 164 | }, 165 | }, 166 | panel2: { 167 | type: 'void', 168 | 'x-component': 'FormCollapse.CollapsePanel', 169 | 'x-component-props': { 170 | header: 'A2', 171 | }, 172 | properties: { 173 | bbb: { 174 | type: 'string', 175 | title: 'BBB', 176 | 'x-decorator': 'FormItem', 177 | required: true, 178 | 'x-component': 'Input', 179 | }, 180 | }, 181 | }, 182 | panel3: { 183 | type: 'void', 184 | 'x-component': 'FormCollapse.CollapsePanel', 185 | 'x-component-props': { 186 | header: 'A3', 187 | }, 188 | properties: { 189 | ccc: { 190 | type: 'string', 191 | title: 'CCC', 192 | 'x-decorator': 'FormItem', 193 | required: true, 194 | 'x-component': 'Input', 195 | }, 196 | }, 197 | }, 198 | }, 199 | }, 200 | }, 201 | } 202 | 203 | export default () => { 204 | return ( 205 | <FormProvider form={form}> 206 | <FormLayout labelCol={6} wrapperCol={10}> 207 | <SchemaField schema={schema} scope={{ formCollapse }} /> 208 | <FormButtonGroup.FormItem> 209 | <Button 210 | onClick={() => { 211 | form.query('panel3').take((field) => { 212 | field.visible = !field.visible 213 | }) 214 | }} 215 | > 216 | 显示/隐藏最后一个Tab 217 | </Button> 218 | <Button 219 | onClick={() => { 220 | formCollapse.toggleActiveKey('panel2') 221 | }} 222 | > 223 | 切换第二个Tab 224 | </Button> 225 | <Submit onSubmit={console.log}>提交</Submit> 226 | </FormButtonGroup.FormItem> 227 | </FormLayout> 228 | </FormProvider> 229 | ) 230 | } 231 | ``` 232 | 233 | ## API 234 | 235 | ### FormCollapse 236 | 237 | | 属性名 | 类型 | 描述 | 默认值 | 238 | | ------------ | ------------- | ---------------------------------------------------------- | ------ | 239 | | formCollapse | IFormCollapse | 传入通过 createFormCollapse/useFormCollapse 创建出来的模型 | | 240 | 241 | 其余参考 https://ant.design/components/collapse-cn/ 242 | 243 | ### FormCollapse.CollapsePanel 244 | 245 | 参考 https://ant.design/components/collapse-cn/ 246 | 247 | ### FormCollapse.createFormCollapse 248 | 249 | ```ts pure 250 | type ActiveKey = string | number 251 | type ActiveKeys = string | number | Array<string | number> 252 | 253 | interface createFormCollapse { 254 | (defaultActiveKeys?: ActiveKeys): IFormCollpase 255 | } 256 | 257 | interface IFormCollapse { 258 | //激活主键列表 259 | activeKeys: ActiveKeys 260 | //是否存在该激活主键 261 | hasActiveKey(key: ActiveKey): boolean 262 | //设置激活主键列表 263 | setActiveKeys(keys: ActiveKeys): void 264 | //添加激活主键 265 | addActiveKey(key: ActiveKey): void 266 | //删除激活主键 267 | removeActiveKey(key: ActiveKey): void 268 | //开关切换激活主键 269 | toggleActiveKey(key: ActiveKey): void 270 | } 271 | ``` 272 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/FormCollapse.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # FormCollapse 2 | 3 | > 折叠面板,通常用在布局空间要求较高的表单场景 4 | > 5 | > 注意:只能用在 Schema 场景 6 | 7 | ## Markup Schema 案例 8 | 9 | ```tsx 10 | import React from 'react' 11 | import { 12 | FormCollapse, 13 | FormItem, 14 | Input, 15 | FormButtonGroup, 16 | Submit, 17 | FormLayout, 18 | } from '@formily/next' 19 | import { createForm } from '@formily/core' 20 | import { FormProvider, createSchemaField } from '@formily/react' 21 | import { Button } from '@alifd/next' 22 | 23 | const SchemaField = createSchemaField({ 24 | components: { 25 | FormItem, 26 | FormCollapse, 27 | Input, 28 | }, 29 | }) 30 | 31 | const form = createForm() 32 | const formCollapse = FormCollapse.createFormCollapse() 33 | 34 | export default () => { 35 | return ( 36 | <FormProvider form={form}> 37 | <FormLayout labelCol={6} wrapperCol={10}> 38 | <SchemaField> 39 | <SchemaField.Void 40 | title="折叠面板" 41 | x-decorator="FormItem" 42 | x-component="FormCollapse" 43 | x-component-props={{ 44 | formCollapse, 45 | }} 46 | > 47 | <SchemaField.Void 48 | name="panel1" 49 | x-component="FormCollapse.CollapsePanel" 50 | x-component-props={{ title: 'A1' }} 51 | > 52 | <SchemaField.String 53 | name="aaa" 54 | title="AAA" 55 | x-decorator="FormItem" 56 | required 57 | x-component="Input" 58 | /> 59 | </SchemaField.Void> 60 | <SchemaField.Void 61 | name="panel2" 62 | x-component="FormCollapse.CollapsePanel" 63 | x-component-props={{ title: 'A2' }} 64 | > 65 | <SchemaField.String 66 | name="bbb" 67 | title="BBB" 68 | x-decorator="FormItem" 69 | required 70 | x-component="Input" 71 | /> 72 | </SchemaField.Void> 73 | <SchemaField.Void 74 | name="panel3" 75 | x-component="FormCollapse.CollapsePanel" 76 | x-component-props={{ title: 'A3' }} 77 | > 78 | <SchemaField.String 79 | name="ccc" 80 | title="CCC" 81 | x-decorator="FormItem" 82 | required 83 | x-component="Input" 84 | /> 85 | </SchemaField.Void> 86 | </SchemaField.Void> 87 | </SchemaField> 88 | <FormButtonGroup.FormItem> 89 | <Button 90 | onClick={() => { 91 | form.query('panel3').take((field) => { 92 | field.visible = !field.visible 93 | }) 94 | }} 95 | > 96 | 显示/隐藏最后一个Tab 97 | </Button> 98 | <Button 99 | onClick={() => { 100 | formCollapse.toggleActiveKey('panel2') 101 | }} 102 | > 103 | 切换第二个Tab 104 | </Button> 105 | <Submit onSubmit={console.log}>提交</Submit> 106 | </FormButtonGroup.FormItem> 107 | </FormLayout> 108 | </FormProvider> 109 | ) 110 | } 111 | ``` 112 | 113 | ## JSON Schema 案例 114 | 115 | ```tsx 116 | import React from 'react' 117 | import { 118 | FormCollapse, 119 | FormItem, 120 | Input, 121 | FormButtonGroup, 122 | Submit, 123 | FormLayout, 124 | } from '@formily/next' 125 | import { createForm } from '@formily/core' 126 | import { FormProvider, createSchemaField } from '@formily/react' 127 | import { Button } from '@alifd/next' 128 | 129 | const SchemaField = createSchemaField({ 130 | components: { 131 | FormItem, 132 | FormCollapse, 133 | Input, 134 | }, 135 | }) 136 | 137 | const form = createForm() 138 | const formCollapse = FormCollapse.createFormCollapse() 139 | 140 | const schema = { 141 | type: 'object', 142 | properties: { 143 | collapse: { 144 | type: 'void', 145 | title: '折叠面板', 146 | 'x-decorator': 'FormItem', 147 | 'x-component': 'FormCollapse', 148 | 'x-component-props': { 149 | formCollapse: '{{formCollapse}}', 150 | }, 151 | properties: { 152 | panel1: { 153 | type: 'void', 154 | 'x-component': 'FormCollapse.CollapsePanel', 155 | 'x-component-props': { 156 | title: 'A1', 157 | }, 158 | properties: { 159 | aaa: { 160 | type: 'string', 161 | title: 'AAA', 162 | 'x-decorator': 'FormItem', 163 | required: true, 164 | 'x-component': 'Input', 165 | }, 166 | }, 167 | }, 168 | panel2: { 169 | type: 'void', 170 | 'x-component': 'FormCollapse.CollapsePanel', 171 | 'x-component-props': { 172 | title: 'A2', 173 | }, 174 | properties: { 175 | bbb: { 176 | type: 'string', 177 | title: 'BBB', 178 | 'x-decorator': 'FormItem', 179 | required: true, 180 | 'x-component': 'Input', 181 | }, 182 | }, 183 | }, 184 | panel3: { 185 | type: 'void', 186 | 'x-component': 'FormCollapse.CollapsePanel', 187 | 'x-component-props': { 188 | title: 'A3', 189 | }, 190 | properties: { 191 | ccc: { 192 | type: 'string', 193 | title: 'CCC', 194 | 'x-decorator': 'FormItem', 195 | required: true, 196 | 'x-component': 'Input', 197 | }, 198 | }, 199 | }, 200 | }, 201 | }, 202 | }, 203 | } 204 | 205 | export default () => { 206 | return ( 207 | <FormProvider form={form}> 208 | <FormLayout labelCol={6} wrapperCol={10}> 209 | <SchemaField schema={schema} scope={{ formCollapse }} /> 210 | <FormButtonGroup.FormItem> 211 | <Button 212 | onClick={() => { 213 | form.query('panel3').take((field) => { 214 | field.visible = !field.visible 215 | }) 216 | }} 217 | > 218 | 显示/隐藏最后一个Tab 219 | </Button> 220 | <Button 221 | onClick={() => { 222 | formCollapse.toggleActiveKey('panel2') 223 | }} 224 | > 225 | 切换第二个Tab 226 | </Button> 227 | <Submit onSubmit={console.log}>提交</Submit> 228 | </FormButtonGroup.FormItem> 229 | </FormLayout> 230 | </FormProvider> 231 | ) 232 | } 233 | ``` 234 | 235 | ## API 236 | 237 | ### FormCollapse 238 | 239 | | 属性名 | 类型 | 描述 | 默认值 | 240 | | ------------ | ------------- | ---------------------------------------------------------- | ------ | 241 | | formCollapse | IFormCollapse | 传入通过 createFormCollapse/useFormCollapse 创建出来的模型 | | 242 | 243 | 其余参考 https://fusion.design/pc/component/basic/collapse 244 | 245 | ### FormCollapse.CollapsePanel 246 | 247 | 参考 https://fusion.design/pc/component/basic/collapse 248 | 249 | ### FormCollapse.createFormCollapse 250 | 251 | ```ts pure 252 | type ActiveKey = string | number 253 | type ActiveKeys = string | number | Array<string | number> 254 | 255 | interface createFormCollapse { 256 | (defaultActiveKeys?: ActiveKeys): IFormCollpase 257 | } 258 | 259 | interface IFormCollapse { 260 | //激活主键列表 261 | activeKeys: ActiveKeys 262 | //是否存在该激活主键 263 | hasActiveKey(key: ActiveKey): boolean 264 | //设置激活主键列表 265 | setActiveKeys(keys: ActiveKeys): void 266 | //添加激活主键 267 | addActiveKey(key: ActiveKey): void 268 | //删除激活主键 269 | removeActiveKey(key: ActiveKey): void 270 | //开关切换激活主键 271 | toggleActiveKey(key: ActiveKey): void 272 | } 273 | ``` 274 | ``` -------------------------------------------------------------------------------- /docs/guide/quick-start.md: -------------------------------------------------------------------------------- ```markdown 1 | # Quick Start 2 | 3 | ## Install Dependencies 4 | 5 | ### Install the Core Library 6 | 7 | To use Formily, you must use [@formily/core](https://core.formilyjs.org), which is responsible for managing the status of the form, form verification, linkage, and so on. 8 | 9 | ```bash 10 | $ npm install --save @formily/core 11 | ``` 12 | 13 | ### Install UI Bridge Library 14 | 15 | The kernel alone is not enough. We also need a UI library to access kernel data to achieve the final form interaction effect. For users of different frameworks, we have different bridge libraries. 16 | 17 | **React users** 18 | 19 | ```bash 20 | $ npm install --save @formily/react 21 | ``` 22 | 23 | **Vue users** 24 | 25 | ```bash 26 | $ npm install --save @formily/vue 27 | ``` 28 | 29 | ### Install component library 30 | 31 | To quickly implement beautiful forms, we usually need to use industry-leading component libraries, such as [Ant Design](https://ant.design) and [Alibaba Fusion](https://fusion.design). However, these excellent component libraries are not fully covered in some scenes of the form. For example, the detailed preview state is not supported by Ant Design, and some scene-based components are not supported, so Formily is in On top of this, @formily/antd and @formily/next are encapsulated to ensure that users can use it out of the box. 32 | 33 | **Ant Design users** 34 | 35 | ```bash 36 | $ npm install --save antd moment @formily/antd 37 | ``` 38 | 39 | **Alibaba Fusion users** 40 | 41 | ```bash 42 | $ npm install --save @alifd/next moment @formily/next 43 | ``` 44 | 45 | ## Import Dependencies 46 | 47 | Use ES Module import syntax to import dependencies 48 | 49 | ```ts 50 | import React from 'react' 51 | import { createForm } from '@formily/core' 52 | import { FormProvider, Field } from '@formily/react' 53 | import { FormItem, Input } from '@formily/antd' 54 | ``` 55 | 56 | ## Example 57 | 58 | ```tsx 59 | /** 60 | * defaultShowCode: true 61 | */ 62 | import React from 'react' 63 | import { createForm } from '@formily/core' 64 | import { FormProvider, FormConsumer, Field } from '@formily/react' 65 | import { 66 | FormItem, 67 | FormLayout, 68 | Input, 69 | FormButtonGroup, 70 | Submit, 71 | } from '@formily/antd' 72 | 73 | const form = createForm() 74 | 75 | export default () => { 76 | return ( 77 | <FormProvider form={form}> 78 | <FormLayout layout="vertical"> 79 | <Field 80 | name="input" 81 | title="Input box" 82 | required 83 | initialValue="Hello world" 84 | decorator={[FormItem]} 85 | component={[Input]} 86 | /> 87 | </FormLayout> 88 | <FormConsumer> 89 | {() => ( 90 | <div 91 | style={{ 92 | marginBottom: 20, 93 | padding: 5, 94 | border: '1px dashed #666', 95 | }} 96 | > 97 | Real-time response:{form.values.input} 98 | </div> 99 | )} 100 | </FormConsumer> 101 | <FormButtonGroup> 102 | <Submit onSubmit={console.log}>submit</Submit> 103 | </FormButtonGroup> 104 | </FormProvider> 105 | ) 106 | } 107 | ``` 108 | 109 | From the above examples, we can learn a lot: 110 | 111 | - [createForm](https://core.formilyjs.org/api/entry/create-form) is used to create the core domain model of the form, which is the standard ViewModel as the [MVVM](https://core.formilyjs.org/guide/mvvm) design pattern. 112 | - The [FormProvider](https://react.formilyjs.org/api/components/form-provider) component is used as the entrance to the view layer bridge form model. It has only one parameter, which is to receive the Form instance created by createForm and pass the Form instance to the child component in the form of context. 113 | - The [FormLayout](https://antd.formilyjs.org/components/form-layout) component is a component used to control the style of [FormItem](https://antd.formilyjs.org/components/form-item) in batches. Here we specify the layout as top and bottom layout, that is, the label is on the top and the component is on the bottom. 114 | - The [Field](https://react.formilyjs.org/api/components/field) component is a component used to undertake common fields. 115 | - The name attribute identifies the path of the field in the final submitted data of the form. 116 | - Title attribute, which identifies the title of the field 117 | - If the decorator is specified as FormItem, then the title attribute will be received as the label by default in the FormItem component. 118 | - If specified as a custom component, the consumer of the title will be taken over by the custom component. 119 | - If decorator is not specified, then the title will not be displayed on the UI. 120 | - Required attribute, a shorthand for required verification, which identifies that the field is required 121 | - If the decorator is specified as FormItem, then an asterisk prompt will automatically appear, and there will be corresponding status feedback if the verification fails. These are the default processing done inside the FormItem. 122 | - If the decorator is specified as a custom component, the corresponding UI style needs to be implemented by the custom component implementer. 123 | - If decorator is not specified, then required will just block submission, and there will be no UI feedback for verification failure. 124 | - InitialValue property, which represents the default value of the field 125 | - Decorator attribute, representing the UI decorator of the field, usually we will specify it as FormItem 126 | - Note that the decorator attribute is passed in the form of an array, the first parameter represents the specified component type, and the second parameter represents the specified component attribute. 127 | - The component attribute, which represents the input control of the field, can be Input or Select, etc. 128 | - Note that the component property is passed in the form of an array, the first parameter represents the specified component type, and the second parameter represents the specified component property. 129 | - The [FormConsumer](https://react.formilyjs.org/api/components/form-consumer) component exists as a responder of a responsive model. Its core is a render props mode. In the callback function as children, all dependencies are automatically collected. If the dependencies change, it will be re-rendered. With the help of FormConsumer, we can Conveniently realize the needs of various calculations and summaries. 130 | - The [FormButtonGroup](https://antd.formilyjs.org/components/form-button-group) component exists as a form button group container and is mainly responsible for the layout of the buttons. 131 | - The [Submit](https://antd.formilyjs.org/components/submit) component exists as an action trigger for form submission. In fact, we can also directly use the form.submit method to submit. But the advantage of using Submit is that there is no need to write the onClick event handler on the Button component every time, and it also handles the loading state of the Form. If the onSubmit method returns a Promise and the Promise is pending, the button will automatically enter the loading state. 132 | ```