This is page 9 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 -------------------------------------------------------------------------------- /packages/reactive-test-cases-for-react18/webpack.base.ts: -------------------------------------------------------------------------------- ```typescript 1 | import path from 'path' 2 | import fs from 'fs-extra' 3 | import { GlobSync } from 'glob' 4 | import MiniCssExtractPlugin from 'mini-css-extract-plugin' 5 | //import { getThemeVariables } from 'antd/dist/theme' 6 | 7 | const getWorkspaceAlias = () => { 8 | const basePath = path.resolve(__dirname, '../../') 9 | const pkg = fs.readJSONSync(path.resolve(basePath, 'package.json')) || {} 10 | const results = {} 11 | const workspaces = pkg.workspaces 12 | if (Array.isArray(workspaces)) { 13 | workspaces.forEach((pattern) => { 14 | const { found } = new GlobSync(pattern, { cwd: basePath }) 15 | found.forEach((name) => { 16 | const pkg = fs.readJSONSync( 17 | path.resolve(basePath, name, './package.json') 18 | ) 19 | results[pkg.name] = path.resolve(basePath, name, './src') 20 | }) 21 | }) 22 | } 23 | return results 24 | } 25 | 26 | export default { 27 | mode: 'development', 28 | devtool: 'inline-source-map', // 嵌入到源文件中 29 | stats: { 30 | entrypoints: false, 31 | children: false, 32 | }, 33 | entry: { 34 | index: path.resolve(__dirname, './src/index'), 35 | }, 36 | output: { 37 | path: path.resolve(__dirname, '../build'), 38 | filename: '[name].[hash].bundle.js', 39 | }, 40 | resolve: { 41 | modules: ['node_modules'], 42 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], 43 | alias: getWorkspaceAlias(), 44 | }, 45 | externals: { 46 | react: 'React', 47 | 'react-dom': 'ReactDOM', 48 | moment: 'moment', 49 | antd: 'antd', 50 | }, 51 | module: { 52 | rules: [ 53 | { 54 | test: /\.(tsx?|jsx?)$/, 55 | use: [ 56 | { 57 | loader: require.resolve('ts-loader'), 58 | options: { 59 | transpileOnly: true, 60 | }, 61 | }, 62 | ], 63 | }, 64 | { 65 | test: /\.css$/, 66 | use: [MiniCssExtractPlugin.loader, require.resolve('css-loader')], 67 | }, 68 | { 69 | test: /\.less$/, 70 | use: [ 71 | MiniCssExtractPlugin.loader, 72 | { loader: 'css-loader' }, 73 | { 74 | loader: 'postcss-loader', 75 | }, 76 | { 77 | loader: 'less-loader', 78 | options: { 79 | // modifyVars: getThemeVariables({ 80 | // dark: true // 开启暗黑模式 81 | // }), 82 | javascriptEnabled: true, 83 | }, 84 | }, 85 | ], 86 | }, 87 | { 88 | test: /\.(woff|woff2|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, 89 | use: ['url-loader'], 90 | }, 91 | { 92 | test: /\.html?$/, 93 | loader: require.resolve('file-loader'), 94 | options: { 95 | name: '[name].[ext]', 96 | }, 97 | }, 98 | ], 99 | }, 100 | } 101 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-dialog/markup-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormDialogPortal :id="portalId"> 3 | <Button @click="handleOpen">点击打开表单</Button> 4 | </FormDialogPortal> 5 | </template> 6 | 7 | <script> 8 | import { FormDialog, FormLayout, FormItem, Input } from '@formily/element' 9 | import { Button } from 'element-ui' 10 | import { createSchemaField } from '@formily/vue' 11 | 12 | const { SchemaField, SchemaStringField } = createSchemaField({ 13 | components: { 14 | FormItem, 15 | Input, 16 | }, 17 | }) 18 | 19 | // 弹框表单组件 20 | const DialogForm = { 21 | props: ['form'], 22 | inject: ['foo'], 23 | render() { 24 | const form = this.form 25 | console.log(this.foo) 26 | return ( 27 | <FormLayout labelCol={6} wrapperCol={10}> 28 | <SchemaField> 29 | <SchemaStringField 30 | name="aaa" 31 | required 32 | title="输入框1" 33 | x-decorator="FormItem" 34 | x-component="Input" 35 | /> 36 | <SchemaStringField 37 | name="bbb" 38 | required 39 | title="输入框2" 40 | x-decorator="FormItem" 41 | x-component="Input" 42 | /> 43 | <SchemaStringField 44 | name="ccc" 45 | required 46 | title="输入框3" 47 | x-decorator="FormItem" 48 | x-component="Input" 49 | /> 50 | <SchemaStringField 51 | name="ddd" 52 | required 53 | title="输入框4" 54 | x-decorator="FormItem" 55 | x-component="Input" 56 | /> 57 | </SchemaField> 58 | <FormDialog.Footer> 59 | <span style={{ marginLeft: '4px' }}>扩展文案: {form.values.aaa}</span> 60 | </FormDialog.Footer> 61 | </FormLayout> 62 | ) 63 | }, 64 | } 65 | 66 | export default { 67 | components: { Button, FormDialogPortal: FormDialog.Portal }, 68 | data() { 69 | return { 70 | portalId: '可以传,也可以不传的ID,默认是form-dialog', 71 | } 72 | }, 73 | provide: { 74 | foo: '自定义上下文可以直接传到弹窗内部,只需要ID一致即可', 75 | }, 76 | methods: { 77 | handleOpen() { 78 | FormDialog('弹框表单', this.portalId, DialogForm) 79 | .forOpen((payload, next) => { 80 | setTimeout(() => { 81 | next({ 82 | initialValues: { 83 | aaa: '123', 84 | }, 85 | }) 86 | }, 1000) 87 | }) 88 | .forConfirm((payload, next) => { 89 | setTimeout(() => { 90 | console.log(payload) 91 | next(payload) 92 | }, 1000) 93 | }) 94 | .forCancel((payload, next) => { 95 | setTimeout(() => { 96 | console.log(payload) 97 | next(payload) 98 | }, 1000) 99 | }) 100 | .open() 101 | .then(console.log) 102 | .catch(console.error) 103 | }, 104 | }, 105 | } 106 | </script> 107 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Transfer.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Transfer 2 | 3 | > 穿梭框 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | Transfer, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Array 26 | name="transfer" 27 | title="穿梭框" 28 | x-decorator="FormItem" 29 | x-component="Transfer" 30 | enum={[ 31 | { label: '选项1', value: 'aaa' }, 32 | { label: '选项2', value: 'bbb' }, 33 | ]} 34 | /> 35 | </SchemaField> 36 | <FormButtonGroup> 37 | <Submit onSubmit={console.log}>提交</Submit> 38 | </FormButtonGroup> 39 | </FormProvider> 40 | ) 41 | ``` 42 | 43 | ## JSON Schema 案例 44 | 45 | ```tsx 46 | import React from 'react' 47 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/next' 48 | import { createForm } from '@formily/core' 49 | import { FormProvider, createSchemaField } from '@formily/react' 50 | 51 | const SchemaField = createSchemaField({ 52 | components: { 53 | Transfer, 54 | FormItem, 55 | }, 56 | }) 57 | 58 | const form = createForm() 59 | 60 | const schema = { 61 | type: 'object', 62 | properties: { 63 | transfer: { 64 | type: 'array', 65 | title: '穿梭框', 66 | 'x-decorator': 'FormItem', 67 | 'x-component': 'Transfer', 68 | enum: [ 69 | { label: '选项1', value: 'aaa' }, 70 | { label: '选项2', value: 'bbb' }, 71 | ], 72 | }, 73 | }, 74 | } 75 | 76 | const renderTitle = (item) => item.title 77 | 78 | export default () => ( 79 | <FormProvider form={form}> 80 | <SchemaField schema={schema} scope={{ renderTitle }} /> 81 | <FormButtonGroup> 82 | <Submit onSubmit={console.log}>提交</Submit> 83 | </FormButtonGroup> 84 | </FormProvider> 85 | ) 86 | ``` 87 | 88 | ## 纯 JSX 案例 89 | 90 | ```tsx 91 | import React from 'react' 92 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/next' 93 | import { createForm } from '@formily/core' 94 | import { FormProvider, Field } from '@formily/react' 95 | 96 | const form = createForm() 97 | 98 | export default () => ( 99 | <FormProvider form={form}> 100 | <Field 101 | name="transfer" 102 | title="穿梭框" 103 | dataSource={[ 104 | { label: '选项1', value: 'aaa' }, 105 | { label: '选项2', value: 'bbb' }, 106 | ]} 107 | decorator={[FormItem]} 108 | component={[Transfer]} 109 | /> 110 | <FormButtonGroup> 111 | <Submit onSubmit={console.log}>提交</Submit> 112 | </FormButtonGroup> 113 | </FormProvider> 114 | ) 115 | ``` 116 | 117 | ## API 118 | 119 | 参考 https://fusion.design/pc/component/basic/transfer 120 | ``` -------------------------------------------------------------------------------- /packages/vue/src/shared/h.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { h, isVue2 } from 'vue-demi' 2 | import { Fragment, FragmentComponent } from './fragment' 3 | import { formatVue3VNodeData } from '../utils/formatVNodeData' 4 | 5 | type RenderChildren = { 6 | [key in string]?: (...args: any[]) => (VNode | string)[] 7 | } 8 | 9 | type Tag = any 10 | type VNodeData = Record<string, any> 11 | type VNode = any 12 | type VNodeChildren = any 13 | 14 | const compatibleCreateElement = ( 15 | tag: Tag, 16 | data: VNodeData, 17 | components: RenderChildren 18 | ): any => { 19 | /* istanbul ignore else */ 20 | if (isVue2) { 21 | const hInVue2 = h as ( 22 | tag: Tag, 23 | data?: VNodeData, 24 | components?: VNodeChildren 25 | ) => VNode 26 | const scopedSlots = components // 默认全部作为 scopedSlots 处理 27 | const children = [] 28 | 29 | /** 30 | * scopedSlots 不会映射为slots,所以这里手动映射一遍 31 | * 主要为了解决 slots.x 问题 32 | */ 33 | Object.keys(components).forEach((key) => { 34 | const func = components[key] 35 | 36 | // 转换为 slots 传递 37 | if (typeof func === 'function' && func.length === 0) { 38 | /** 39 | * func 参数为0的判断不准确,因为composition-api包了一层,导致全部为0 40 | * try catch 解决scoped slots 转换参数异常问题 41 | * */ 42 | try { 43 | const child = func() 44 | children.push( 45 | key === 'default' 46 | ? child 47 | : hInVue2(FragmentComponent, { slot: key }, [child]) 48 | ) 49 | } catch (error) {} 50 | } 51 | }) 52 | const newData = Object.assign({}, data) 53 | if (Object.keys(scopedSlots).length > 0) { 54 | if (!newData.scopedSlots) { 55 | newData.scopedSlots = scopedSlots 56 | } else { 57 | newData.scopedSlots = { 58 | ...newData.scopedSlots, 59 | ...scopedSlots, 60 | } 61 | } 62 | } 63 | if (tag === Fragment) { 64 | // sometimes we needn't to use Fragment component. 65 | if (children.length === 1) { 66 | if (!Array.isArray(children[0])) { 67 | return children[0] 68 | } else if (children[0].length === 1) { 69 | if (!Array.isArray(children[0][0])) { 70 | return children[0][0] 71 | } else if (children[0][0].length === 1) { 72 | return children[0][0][0] 73 | } 74 | } 75 | } 76 | tag = FragmentComponent 77 | } 78 | return hInVue2(tag, newData, children) 79 | } else { 80 | if (tag === Fragment) { 81 | tag = FragmentComponent 82 | } 83 | const hInVue3 = h as ( 84 | tag: Tag, 85 | data?: VNodeData, 86 | components?: RenderChildren 87 | ) => VNode 88 | return hInVue3(tag, formatVue3VNodeData(data), components) 89 | } 90 | } 91 | 92 | export default compatibleCreateElement 93 | 94 | export { compatibleCreateElement as h } 95 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/NumberPicker.md: -------------------------------------------------------------------------------- ```markdown 1 | # NumberPicker 2 | 3 | > Number input box 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { NumberPicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | NumberPicker, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="input" 27 | title="input box" 28 | x-decorator="FormItem" 29 | x-component="NumberPicker" 30 | required 31 | x-component-props={{ 32 | style: { 33 | width: 240, 34 | }, 35 | }} 36 | /> 37 | </SchemaField> 38 | <FormButtonGroup> 39 | <Submit onSubmit={console.log}>Submit</Submit> 40 | </FormButtonGroup> 41 | </FormProvider> 42 | ) 43 | ``` 44 | 45 | ## JSON Schema case 46 | 47 | ```tsx 48 | import React from 'react' 49 | import { NumberPicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 50 | import { createForm } from '@formily/core' 51 | import { FormProvider, createSchemaField } from '@formily/react' 52 | 53 | const SchemaField = createSchemaField({ 54 | components: { 55 | NumberPicker, 56 | FormItem, 57 | }, 58 | }) 59 | 60 | const form = createForm() 61 | 62 | const schema = { 63 | type: 'object', 64 | properties: { 65 | input: { 66 | type: 'string', 67 | title: 'input box', 68 | 'x-decorator': 'FormItem', 69 | 'x-component': 'NumberPicker', 70 | 'x-component-props': { 71 | style: { 72 | width: 240, 73 | }, 74 | }, 75 | }, 76 | }, 77 | } 78 | 79 | export default () => ( 80 | <FormProvider form={form}> 81 | <SchemaField schema={schema} /> 82 | <FormButtonGroup> 83 | <Submit onSubmit={console.log}>Submit</Submit> 84 | </FormButtonGroup> 85 | </FormProvider> 86 | ) 87 | ``` 88 | 89 | ## Pure JSX case 90 | 91 | ```tsx 92 | import React from 'react' 93 | import { NumberPicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 94 | import { createForm } from '@formily/core' 95 | import { FormProvider, Field } from '@formily/react' 96 | 97 | const form = createForm() 98 | 99 | export default () => ( 100 | <FormProvider form={form}> 101 | <Field 102 | name="input" 103 | title="input box" 104 | required 105 | decorator={[FormItem]} 106 | component={[ 107 | NumberPicker, 108 | { 109 | style: { 110 | width: 240, 111 | }, 112 | }, 113 | ]} 114 | /> 115 | <FormButtonGroup> 116 | <Submit onSubmit={console.log}>Submit</Submit> 117 | </FormButtonGroup> 118 | </FormProvider> 119 | ) 120 | ``` 121 | 122 | ## API 123 | 124 | Reference https://ant.design/components/input-number-cn/ 125 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/NumberPicker.md: -------------------------------------------------------------------------------- ```markdown 1 | # NumberPicker 2 | 3 | > Number input box 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { NumberPicker, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | NumberPicker, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="input" 27 | title="input box" 28 | x-decorator="FormItem" 29 | x-component="NumberPicker" 30 | required 31 | x-component-props={{ 32 | style: { 33 | width: 240, 34 | }, 35 | }} 36 | /> 37 | </SchemaField> 38 | <FormButtonGroup> 39 | <Submit onSubmit={console.log}>Submit</Submit> 40 | </FormButtonGroup> 41 | </FormProvider> 42 | ) 43 | ``` 44 | 45 | ## JSON Schema case 46 | 47 | ```tsx 48 | import React from 'react' 49 | import { NumberPicker, FormItem, FormButtonGroup, Submit } from '@formily/next' 50 | import { createForm } from '@formily/core' 51 | import { FormProvider, createSchemaField } from '@formily/react' 52 | 53 | const SchemaField = createSchemaField({ 54 | components: { 55 | NumberPicker, 56 | FormItem, 57 | }, 58 | }) 59 | 60 | const form = createForm() 61 | 62 | const schema = { 63 | type: 'object', 64 | properties: { 65 | input: { 66 | type: 'string', 67 | title: 'input box', 68 | 'x-decorator': 'FormItem', 69 | 'x-component': 'NumberPicker', 70 | 'x-component-props': { 71 | style: { 72 | width: 240, 73 | }, 74 | }, 75 | }, 76 | }, 77 | } 78 | 79 | export default () => ( 80 | <FormProvider form={form}> 81 | <SchemaField schema={schema} /> 82 | <FormButtonGroup> 83 | <Submit onSubmit={console.log}>Submit</Submit> 84 | </FormButtonGroup> 85 | </FormProvider> 86 | ) 87 | ``` 88 | 89 | ## Pure JSX case 90 | 91 | ```tsx 92 | import React from 'react' 93 | import { NumberPicker, FormItem, FormButtonGroup, Submit } from '@formily/next' 94 | import { createForm } from '@formily/core' 95 | import { FormProvider, Field } from '@formily/react' 96 | 97 | const form = createForm() 98 | 99 | export default () => ( 100 | <FormProvider form={form}> 101 | <Field 102 | name="input" 103 | title="input box" 104 | required 105 | decorator={[FormItem]} 106 | component={[ 107 | NumberPicker, 108 | { 109 | style: { 110 | width: 240, 111 | }, 112 | }, 113 | ]} 114 | /> 115 | <FormButtonGroup> 116 | <Submit onSubmit={console.log}>Submit</Submit> 117 | </FormButtonGroup> 118 | </FormProvider> 119 | ) 120 | ``` 121 | 122 | ## API 123 | 124 | Reference https://fusion.design/pc/component/basic/number-picker 125 | ``` -------------------------------------------------------------------------------- /packages/validator/src/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | export type ValidatorFormats = 2 | | 'url' 3 | | 'email' 4 | | 'ipv6' 5 | | 'ipv4' 6 | | 'number' 7 | | 'integer' 8 | | 'idcard' 9 | | 'qq' 10 | | 'phone' 11 | | 'money' 12 | | 'zh' 13 | | 'date' 14 | | 'zip' 15 | | (string & {}) 16 | 17 | export interface IValidateResult { 18 | type: 'error' | 'warning' | 'success' | (string & {}) 19 | message: string 20 | } 21 | 22 | export interface IValidateResults { 23 | error?: string[] 24 | warning?: string[] 25 | success?: string[] 26 | } 27 | 28 | export const isValidateResult = (obj: any): obj is IValidateResult => 29 | !!obj['type'] && !!obj['message'] 30 | 31 | export type ValidatorFunctionResponse = 32 | | null 33 | | string 34 | | boolean 35 | | IValidateResult 36 | 37 | export type ValidatorFunction<Context = any> = ( 38 | value: any, 39 | rule: IValidatorRules<Context>, 40 | ctx: Context, 41 | render: (message: string, scope?: any) => string 42 | ) => ValidatorFunctionResponse | Promise<ValidatorFunctionResponse> | null 43 | 44 | export type ValidatorParsedFunction<Context = any> = ( 45 | value: any, 46 | ctx: Context 47 | ) => IValidateResult | Promise<IValidateResult> | null 48 | 49 | export type ValidatorTriggerType = 50 | | 'onInput' 51 | | 'onFocus' 52 | | 'onBlur' 53 | | (string & {}) 54 | 55 | export interface IValidatorRules<Context = any> { 56 | triggerType?: ValidatorTriggerType 57 | format?: ValidatorFormats 58 | validator?: ValidatorFunction<Context> 59 | required?: boolean 60 | pattern?: RegExp | string 61 | max?: number 62 | maximum?: number 63 | maxItems?: number 64 | minItems?: number 65 | maxLength?: number 66 | minLength?: number 67 | exclusiveMaximum?: number 68 | exclusiveMinimum?: number 69 | minimum?: number 70 | min?: number 71 | len?: number 72 | whitespace?: boolean 73 | enum?: any[] 74 | const?: any 75 | multipleOf?: number 76 | uniqueItems?: boolean 77 | maxProperties?: number 78 | minProperties?: number 79 | message?: string 80 | [key: string]: any 81 | } 82 | 83 | export interface IRegistryLocaleMessages { 84 | [key: string]: string | IRegistryLocaleMessages 85 | } 86 | 87 | export interface IRegistryLocales { 88 | [language: string]: IRegistryLocaleMessages 89 | } 90 | 91 | export interface IRegistryRules<Context = any> { 92 | [key: string]: ValidatorFunction<Context> 93 | } 94 | 95 | export interface IRegistryFormats { 96 | [key: string]: string | RegExp 97 | } 98 | 99 | export type ValidatorDescription<Context = any> = 100 | | ValidatorFormats 101 | | ValidatorFunction<Context> 102 | | IValidatorRules<Context> 103 | 104 | export type MultiValidator<Context = any> = ValidatorDescription<Context>[] 105 | 106 | export type Validator<Context = any> = 107 | | ValidatorDescription<Context> 108 | | MultiValidator<Context> 109 | 110 | export interface IValidatorOptions<Context = any> { 111 | validateFirst?: boolean 112 | triggerType?: ValidatorTriggerType 113 | context?: Context 114 | } 115 | ``` -------------------------------------------------------------------------------- /packages/element/src/space/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | // https://github.com/vueComponent/ant-design-vue/blob/next/components/space/index.tsx 2 | 3 | import { h } from '@formily/vue' 4 | import { defineComponent } from 'vue-demi' 5 | import { stylePrefix } from '../__builtins__/configs' 6 | 7 | import type { VNode } from 'vue' 8 | import { useFormLayout } from '../form-layout' 9 | 10 | export type SpaceProps = { 11 | size: 'small' | 'middle' | 'large' | number 12 | direction: 'horizontal' | 'vertical' 13 | align: 'start' | 'end' | 'center' | 'baseline' 14 | } 15 | 16 | const spaceSize = { 17 | small: 8, 18 | middle: 16, 19 | large: 24, 20 | } 21 | 22 | export const Space = defineComponent<SpaceProps>({ 23 | name: 'FSpace', 24 | props: ['size', 'direction', 'align'], 25 | setup(props, { attrs, slots }) { 26 | const layout = useFormLayout() 27 | 28 | return () => { 29 | const { 30 | align, 31 | size = layout.value?.spaceGap ?? 'small', 32 | direction = 'horizontal', 33 | } = props 34 | 35 | const prefixCls = `${stylePrefix}-space` 36 | const children = slots.default?.() 37 | let items: VNode[] = [] 38 | if (Array.isArray(children)) { 39 | if (children.length === 1) { 40 | if ((children[0]['tag'] as string)?.endsWith('Fragment')) { 41 | // Fragment hack 42 | items = (children[0]['componentOptions'] as { children: VNode[] }) 43 | ?.children 44 | } else { 45 | items = children 46 | } 47 | } else { 48 | items = children 49 | } 50 | } 51 | const len = items.length 52 | 53 | if (len === 0) { 54 | return null 55 | } 56 | 57 | const mergedAlign = 58 | align === undefined && direction === 'horizontal' ? 'center' : align 59 | 60 | const someSpaceClass = { 61 | [prefixCls]: true, 62 | [`${prefixCls}-${direction}`]: true, 63 | [`${prefixCls}-align-${mergedAlign}`]: mergedAlign, 64 | } 65 | 66 | const itemClassName = `${prefixCls}-item` 67 | const marginDirection = 'marginRight' // directionConfig === 'rtl' ? 'marginLeft' : 'marginRight'; 68 | 69 | const renderItems = items.map((child, i) => 70 | h( 71 | 'div', 72 | { 73 | class: itemClassName, 74 | key: `${itemClassName}-${i}`, 75 | }, 76 | { default: () => [child] } 77 | ) 78 | ) 79 | 80 | return h( 81 | 'div', 82 | { 83 | ...attrs, 84 | class: { ...(attrs as any).class, ...someSpaceClass }, 85 | style: { 86 | ...(attrs as any).style, 87 | gap: 88 | typeof size === 'string' ? `${spaceSize[size]}px` : `${size}px`, 89 | }, 90 | }, 91 | { default: () => renderItems } 92 | ) 93 | } 94 | }, 95 | }) 96 | 97 | export default Space 98 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/space/template.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <FormLayout :labelCol="6" :wrapperCol="16"> 4 | <VoidField 5 | name="name" 6 | title="姓名" 7 | :decorator="[ 8 | FormItem, 9 | { 10 | asterisk: true, 11 | feedbackLayout: 'none', 12 | }, 13 | ]" 14 | :component="[Space]" 15 | > 16 | <Field 17 | name="firstName" 18 | :decorator="[FormItem]" 19 | :component="[Input]" 20 | required 21 | /> 22 | <Field 23 | name="lastName" 24 | :decorator="[FormItem]" 25 | :component="[Input]" 26 | required 27 | /> 28 | </VoidField> 29 | <VoidField 30 | name="texts" 31 | title="文本串联" 32 | :decorator="[ 33 | FormItem, 34 | { 35 | asterisk: true, 36 | feedbackLayout: 'none', 37 | }, 38 | ]" 39 | :component="[Space]" 40 | > 41 | <Field 42 | name="aa" 43 | :decorator="[ 44 | FormItem, 45 | { 46 | addonAfter: '单位', 47 | }, 48 | ]" 49 | :component="[Input]" 50 | required 51 | /> 52 | <Field 53 | name="bb" 54 | :decorator="[ 55 | FormItem, 56 | { 57 | addonAfter: '单位', 58 | }, 59 | ]" 60 | :component="[Input]" 61 | required 62 | /> 63 | <Field 64 | name="cc" 65 | :decorator="[ 66 | FormItem, 67 | { 68 | addonAfter: '单位', 69 | }, 70 | ]" 71 | :component="[Input]" 72 | required 73 | /> 74 | </VoidField> 75 | <Field 76 | name="textarea" 77 | title="文本框" 78 | :decorator="[FormItem]" 79 | :component="[ 80 | TextArea, 81 | { 82 | style: { 83 | width: 400, 84 | }, 85 | }, 86 | ]" 87 | required 88 | /> 89 | <FormButtonGroup alignFormItem> 90 | <Submit :onSubmit="log">提交</Submit> 91 | </FormButtonGroup> 92 | </FormLayout> 93 | </FormProvider> 94 | </template> 95 | 96 | <script> 97 | import { createForm } from '@formily/core' 98 | import { FormProvider, Field, VoidField } from '@formily/vue' 99 | import { 100 | FormLayout, 101 | FormItem, 102 | Input, 103 | TextArea, 104 | Submit, 105 | Space, 106 | FormButtonGroup, 107 | } from '@formily/element' 108 | 109 | const form = createForm() 110 | 111 | export default { 112 | components: { 113 | FormProvider, 114 | FormLayout, 115 | FormButtonGroup, 116 | VoidField, 117 | Field, 118 | Submit, 119 | }, 120 | data() { 121 | return { 122 | FormItem, 123 | Input, 124 | TextArea, 125 | Space, 126 | form, 127 | } 128 | }, 129 | methods: { 130 | log(value) { 131 | console.log(value) 132 | }, 133 | }, 134 | } 135 | </script> 136 | ``` -------------------------------------------------------------------------------- /packages/core/docs/api/entry/createForm.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | order: 0 3 | --- 4 | 5 | # createForm 6 | 7 | ## Description 8 | 9 | Create a Form instance as a ViewModel for consumption by the UI framework layer 10 | 11 | ## Signature 12 | 13 | ```ts 14 | interface createForm { 15 | (props: IFormProps): Form 16 | } 17 | ``` 18 | 19 | ## IFormProps 20 | 21 | | Property | Description | Type | Default Value | 22 | | ------------- | ---------------------------------------------------------- | -------------------------------------------------------- | ------------- | 23 | | values | form values | Object | `{}` | 24 | | initialValues | Form default values | Object | `{}` | 25 | | pattern | Form interaction mode | `"editable" \| "disabled" \| "readOnly" \| "readPretty"` | `"editable"` | 26 | | display | The form is visible and hidden | `"visible" \| "hidden" \| "none"` | `"visible` | 27 | | hidden | UI hidden | Boolean | `false` | 28 | | visible | show/hide (data hiding) | Boolean | `true` | 29 | | editable | Editable | Boolean | `true` | 30 | | disabled | Whether to disable | Boolean | `false` | 31 | | readOnly | Is it read-only | Boolean | `false` | 32 | | readPretty | Is it an elegant reading state | Boolean | `false` | 33 | | effects | Side effect logic, used to implement various linkage logic | `(form:Form)=>void` | | 34 | | validateFirst | Whether to validate only the first illegal rule | Boolean | `false` | 35 | 36 | ## Example 37 | 38 | ```ts 39 | import { createForm } from '@formily/core' 40 | 41 | const form = createForm({ 42 | initialValues: { 43 | say: 'hello', 44 | }, 45 | }) 46 | ``` 47 | ``` -------------------------------------------------------------------------------- /packages/next/__tests__/moment.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { momentable, formatMomentValue } from '../src/__builtins__/moment' 2 | import moment from 'moment' 3 | 4 | test('momentable is usable', () => { 5 | expect(moment.isMoment(momentable('2021-09-08'))).toBe(true) 6 | expect( 7 | momentable(['2021-09-08', '2021-12-29']).every((item) => 8 | moment.isMoment(item) 9 | ) 10 | ).toBe(true) 11 | expect(momentable(0)).toBe(0) 12 | }) 13 | 14 | test('formatMomentValue is usable', () => { 15 | expect(formatMomentValue('', 'YYYY-MM-DD', '~')).toBe('~') 16 | expect(formatMomentValue('2021-12-22 15:47:00', 'YYYY-MM-DD')).toBe( 17 | '2021-12-22' 18 | ) 19 | expect(formatMomentValue('2021-12-23 15:47:00', undefined)).toBe( 20 | '2021-12-23 15:47:00' 21 | ) 22 | expect(formatMomentValue('2021-12-21 15:47:00', (date: string) => date)).toBe( 23 | '2021-12-21 15:47:00' 24 | ) 25 | expect(formatMomentValue('12:11', 'HH:mm')).toBe('12:11') 26 | expect(formatMomentValue('12:11:11', 'HH:mm:ss')).toBe('12:11:11') 27 | expect(formatMomentValue(['12:11'], ['HH:mm'])).toEqual(['12:11']) 28 | expect(formatMomentValue(['12:11:11'], ['HH:mm:ss'])).toEqual(['12:11:11']) 29 | expect(formatMomentValue(1663155911097, 'YYYY-MM-DD HH:mm:ss')).toBe( 30 | moment(1663155911097).format('YYYY-MM-DD HH:mm:ss') 31 | ) 32 | expect(formatMomentValue([1663155911097], ['YYYY-MM-DD HH:mm:ss'])).toEqual([ 33 | moment(1663155911097).format('YYYY-MM-DD HH:mm:ss'), 34 | ]) 35 | expect( 36 | formatMomentValue('2022-09-15T09:56:26.000Z', 'YYYY-MM-DD HH:mm:ss') 37 | ).toBe(moment('2022-09-15T09:56:26.000Z').format('YYYY-MM-DD HH:mm:ss')) 38 | expect( 39 | formatMomentValue(['2022-09-15T09:56:26.000Z'], ['YYYY-MM-DD HH:mm:ss']) 40 | ).toEqual([moment('2022-09-15T09:56:26.000Z').format('YYYY-MM-DD HH:mm:ss')]) 41 | expect(formatMomentValue('2022-09-15 09:56:26', 'HH:mm:ss')).toBe('09:56:26') 42 | expect(formatMomentValue(['2022-09-15 09:56:26'], ['HH:mm:ss'])).toEqual([ 43 | '09:56:26', 44 | ]) 45 | expect( 46 | formatMomentValue( 47 | ['2021-12-21 15:47:00', '2021-12-29 15:47:00'], 48 | 'YYYY-MM-DD' 49 | ) 50 | ).toEqual(['2021-12-21', '2021-12-29']) 51 | expect( 52 | formatMomentValue( 53 | ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], 54 | (date: string) => date 55 | ) 56 | ).toEqual(['2021-12-21 16:47:00', '2021-12-29 18:47:00']) 57 | expect( 58 | formatMomentValue( 59 | ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], 60 | ['YYYY-MM-DD', (date: string) => date] 61 | ) 62 | ).toEqual(['2021-12-21', '2021-12-29 18:47:00']) 63 | expect( 64 | formatMomentValue( 65 | ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], 66 | ['YYYY-MM-DD', undefined] 67 | ) 68 | ).toEqual(['2021-12-21', '2021-12-29 18:47:00']) 69 | }) 70 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/Radio.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Radio 2 | 3 | > 单选框 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | Radio, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Number 26 | name="radio" 27 | title="单选" 28 | enum={[ 29 | { 30 | label: '选项1', 31 | value: 1, 32 | }, 33 | { 34 | label: '选项2', 35 | value: 2, 36 | }, 37 | ]} 38 | x-decorator="FormItem" 39 | x-component="Radio.Group" 40 | /> 41 | </SchemaField> 42 | <FormButtonGroup> 43 | <Submit onSubmit={console.log}>提交</Submit> 44 | </FormButtonGroup> 45 | </FormProvider> 46 | ) 47 | ``` 48 | 49 | ## JSON Schema 案例 50 | 51 | ```tsx 52 | import React from 'react' 53 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' 54 | import { createForm } from '@formily/core' 55 | import { FormProvider, createSchemaField } from '@formily/react' 56 | 57 | const SchemaField = createSchemaField({ 58 | components: { 59 | Radio, 60 | FormItem, 61 | }, 62 | }) 63 | 64 | const form = createForm() 65 | 66 | const schema = { 67 | type: 'object', 68 | properties: { 69 | radio: { 70 | type: 'number', 71 | title: '单选', 72 | enum: [ 73 | { 74 | label: '选项1', 75 | value: 1, 76 | }, 77 | { 78 | label: '选项2', 79 | value: 2, 80 | }, 81 | ], 82 | 'x-decorator': 'FormItem', 83 | 'x-component': 'Radio.Group', 84 | }, 85 | }, 86 | } 87 | 88 | export default () => ( 89 | <FormProvider form={form}> 90 | <SchemaField schema={schema} /> 91 | <FormButtonGroup> 92 | <Submit onSubmit={console.log}>提交</Submit> 93 | </FormButtonGroup> 94 | </FormProvider> 95 | ) 96 | ``` 97 | 98 | ## 纯 JSX 案例 99 | 100 | ```tsx 101 | import React from 'react' 102 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/antd' 103 | import { createForm } from '@formily/core' 104 | import { FormProvider, Field } from '@formily/react' 105 | 106 | const form = createForm() 107 | 108 | export default () => ( 109 | <FormProvider form={form}> 110 | <Field 111 | name="radio" 112 | title="单选" 113 | dataSource={[ 114 | { 115 | label: '选项1', 116 | value: 1, 117 | }, 118 | { 119 | label: '选项2', 120 | value: 2, 121 | }, 122 | ]} 123 | decorator={FormItem} 124 | component={Radio.Group} 125 | /> 126 | <FormButtonGroup> 127 | <Submit onSubmit={console.log}>提交</Submit> 128 | </FormButtonGroup> 129 | </FormProvider> 130 | ) 131 | ``` 132 | 133 | ## API 134 | 135 | 参考 https://ant.design/components/radio-cn/ 136 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-tab/markup-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaVoidField 5 | type="void" 6 | x-component="FormTab" 7 | :x-component-props="{ formTab }" 8 | > 9 | <SchemaVoidField 10 | type="void" 11 | name="tab1" 12 | x-component="FormTab.TabPane" 13 | :x-component-props="{ label: 'A1' }" 14 | > 15 | <SchemaStringField 16 | name="aaa" 17 | x-decorator="FormItem" 18 | title="AAA" 19 | required 20 | x-component="Input" 21 | /> 22 | </SchemaVoidField> 23 | <SchemaVoidField 24 | name="tab2" 25 | x-component="FormTab.TabPane" 26 | :x-component-props="{ label: 'A2' }" 27 | > 28 | <SchemaStringField 29 | name="bbb" 30 | x-decorator="FormItem" 31 | title="BBB" 32 | required 33 | x-component="Input" 34 | /> 35 | </SchemaVoidField> 36 | <SchemaVoidField 37 | name="tab3" 38 | x-component="FormTab.TabPane" 39 | :x-component-props="{ label: 'A3' }" 40 | > 41 | <SchemaStringField 42 | name="ccc" 43 | x-decorator="FormItem" 44 | title="CCC" 45 | required 46 | x-component="Input" 47 | /> 48 | </SchemaVoidField> 49 | </SchemaVoidField> 50 | </SchemaField> 51 | <FormButtonGroup alignFormItem> 52 | <Button 53 | @click=" 54 | () => { 55 | form.query('tab3').take((field) => { 56 | field.visible = !field.visible 57 | }) 58 | } 59 | " 60 | > 61 | 显示/隐藏最后一个Tab 62 | </Button> 63 | <Button 64 | @click=" 65 | () => { 66 | formTab.setActiveKey('tab2') 67 | } 68 | " 69 | > 70 | 切换第二个Tab 71 | </Button> 72 | <Submit @submit="log">提交</Submit> 73 | </FormButtonGroup> 74 | </FormProvider> 75 | </template> 76 | 77 | <script> 78 | import { createForm } from '@formily/core' 79 | import { FormProvider, createSchemaField } from '@formily/vue' 80 | import { 81 | FormItem, 82 | FormTab, 83 | FormButtonGroup, 84 | Submit, 85 | Input, 86 | } from '@formily/element' 87 | import { Button } from 'element-ui' 88 | 89 | const SchemaField = createSchemaField({ 90 | components: { 91 | FormItem, 92 | FormTab, 93 | Input, 94 | }, 95 | }) 96 | 97 | export default { 98 | components: { 99 | FormProvider, 100 | FormButtonGroup, 101 | Button, 102 | Submit, 103 | ...SchemaField, 104 | }, 105 | 106 | data() { 107 | const form = createForm() 108 | const formTab = FormTab.createFormTab() 109 | 110 | return { 111 | form, 112 | formTab, 113 | } 114 | }, 115 | methods: { 116 | log(values) { 117 | console.log(values) 118 | }, 119 | }, 120 | } 121 | </script> 122 | 123 | <style lang="scss" scoped></style> 124 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Radio.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # Radio 2 | 3 | > 单选框 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | Radio, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Number 26 | name="radio" 27 | title="单选" 28 | enum={[ 29 | { 30 | label: '选项1', 31 | value: 1, 32 | }, 33 | { 34 | label: '选项2', 35 | value: 2, 36 | }, 37 | ]} 38 | x-decorator="FormItem" 39 | x-component="Radio.Group" 40 | /> 41 | </SchemaField> 42 | <FormButtonGroup> 43 | <Submit onSubmit={console.log}>提交</Submit> 44 | </FormButtonGroup> 45 | </FormProvider> 46 | ) 47 | ``` 48 | 49 | ## JSON Schema 案例 50 | 51 | ```tsx 52 | import React from 'react' 53 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' 54 | import { createForm } from '@formily/core' 55 | import { FormProvider, createSchemaField } from '@formily/react' 56 | 57 | const SchemaField = createSchemaField({ 58 | components: { 59 | Radio, 60 | FormItem, 61 | }, 62 | }) 63 | 64 | const form = createForm() 65 | 66 | const schema = { 67 | type: 'object', 68 | properties: { 69 | radio: { 70 | type: 'number', 71 | title: '单选', 72 | enum: [ 73 | { 74 | label: '选项1', 75 | value: 1, 76 | }, 77 | { 78 | label: '选项2', 79 | value: 2, 80 | }, 81 | ], 82 | 'x-decorator': 'FormItem', 83 | 'x-component': 'Radio.Group', 84 | }, 85 | }, 86 | } 87 | 88 | export default () => ( 89 | <FormProvider form={form}> 90 | <SchemaField schema={schema} /> 91 | <FormButtonGroup> 92 | <Submit onSubmit={console.log}>提交</Submit> 93 | </FormButtonGroup> 94 | </FormProvider> 95 | ) 96 | ``` 97 | 98 | ## 纯 JSX 案例 99 | 100 | ```tsx 101 | import React from 'react' 102 | import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' 103 | import { createForm } from '@formily/core' 104 | import { FormProvider, Field } from '@formily/react' 105 | 106 | const form = createForm() 107 | 108 | export default () => ( 109 | <FormProvider form={form}> 110 | <Field 111 | name="radio" 112 | title="单选" 113 | dataSource={[ 114 | { 115 | label: '选项1', 116 | value: 1, 117 | }, 118 | { 119 | label: '选项2', 120 | value: 2, 121 | }, 122 | ]} 123 | decorator={FormItem} 124 | component={Radio.Group} 125 | /> 126 | <FormButtonGroup> 127 | <Submit onSubmit={console.log}>提交</Submit> 128 | </FormButtonGroup> 129 | </FormProvider> 130 | ) 131 | ``` 132 | 133 | ## API 134 | 135 | 参考 https://fusion.design/pc/component/basic/radio 136 | ``` -------------------------------------------------------------------------------- /packages/reactive/docs/api/react/observer.md: -------------------------------------------------------------------------------- ```markdown 1 | # observer 2 | 3 | ## observer 4 | 5 | ### Description 6 | 7 | In React, turn Function Component into Reaction, and dependencies will be collected every time the view is re-rendered, and dependency updates will be automatically re-rendered 8 | 9 | <Alert> 10 | Note: Only Function Component is supported 11 | </Alert> 12 | 13 | ### Signature 14 | 15 | ```ts 16 | interface IObserverOptions { 17 | forwardRef?: boolean //Whether to pass the reference transparently 18 | scheduler?: (updater: () => void) => void //The scheduler, you can manually control the timing of the update 19 | displayName?: string //displayName of the packaged component 20 | } 21 | 22 | interface observer<T extends React.FC> { 23 | (component: T, options?: IObserverOptions): T 24 | } 25 | ``` 26 | 27 | ### Example 28 | 29 | ```tsx 30 | /** 31 | * defaultShowCode: true 32 | */ 33 | import React from 'react' 34 | import { observable } from '@formily/reactive' 35 | import { observer } from '@formily/reactive-react' 36 | 37 | const obs = observable({ 38 | value: 'Hello world', 39 | }) 40 | 41 | export default observer(() => { 42 | return ( 43 | <div> 44 | <div> 45 | <input 46 | style={{ 47 | height: 28, 48 | padding: '0 8px', 49 | border: '2px solid #888', 50 | borderRadius: 3, 51 | }} 52 | value={obs.value} 53 | onChange={(e) => { 54 | obs.value = e.target.value 55 | }} 56 | /> 57 | </div> 58 | <div>{obs.value}</div> 59 | </div> 60 | ) 61 | }) 62 | ``` 63 | 64 | ## Observer 65 | 66 | ### Description 67 | 68 | Similar to Vue's responsive slot, it receives a Function RenderProps, as long as any responsive data consumed inside the Function, it will be automatically re-rendered as the data changes, and it is easier to achieve local accurate rendering 69 | 70 | ### Signature 71 | 72 | ```ts 73 | interface IObserverProps { 74 | children?: () => React.ReactElement 75 | } 76 | 77 | type Observer = React.FC<React.PropsWithChildren<IObserverProps>> 78 | ``` 79 | 80 | ### Example 81 | 82 | ```tsx 83 | /** 84 | * defaultShowCode: true 85 | */ 86 | import React from 'react' 87 | import { observable } from '@formily/reactive' 88 | import { Observer } from '@formily/reactive-react' 89 | 90 | const obs = observable({ 91 | value: 'Hello world', 92 | }) 93 | 94 | export default () => { 95 | return ( 96 | <div> 97 | <div> 98 | <Observer> 99 | {() => ( 100 | <input 101 | style={{ 102 | height: 28, 103 | padding: '0 8px', 104 | border: '2px solid #888', 105 | borderRadius: 3, 106 | }} 107 | value={obs.value} 108 | onChange={(e) => { 109 | obs.value = e.target.value 110 | }} 111 | /> 112 | )} 113 | </Observer> 114 | </div> 115 | <Observer>{() => <div>{obs.value}</div>}</Observer> 116 | </div> 117 | ) 118 | } 119 | ``` 120 | ``` -------------------------------------------------------------------------------- /packages/vue/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@formily/vue", 3 | "version": "2.3.7", 4 | "license": "MIT", 5 | "main": "lib", 6 | "module": "esm", 7 | "umd:main": "dist/formily.vue.umd.production.js", 8 | "unpkg": "dist/formily.vue.umd.production.js", 9 | "jsdelivr": "dist/formily.vue.umd.production.js", 10 | "jsnext:main": "esm", 11 | "types": "type-artefacts/cur/index.d.ts", 12 | "engines": { 13 | "npm": ">=3.0.0" 14 | }, 15 | "scripts": { 16 | "postinstall": "node ./scripts/postinstall.js", 17 | "start": "vuepress dev docs", 18 | "build": "rimraf -rf lib esm dist type-artefacts && npm run build:cjs && npm run build:esm && npm run build:umd && npm run build:types", 19 | "build:cjs": "tsc --project tsconfig.build.json", 20 | "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm", 21 | "build:umd": "rollup --config", 22 | "build:types": "npm run build:types-vue2 && npm run build:types-vue3 && rimraf type-artefacts/**/*.js type-artefacts/**/**/*.js", 23 | "build:types-vue2": "tsc --project tsconfig.types.json --outDir type-artefacts/v2", 24 | "build:types-vue3": "npx vue-demi-switch 3 vue3 && tsc --project tsconfig.types.json --outDir type-artefacts/v3 && tsc --project tsconfig.types.json --outDir type-artefacts/cur && npx vue-demi-switch 2", 25 | "build:docs": "vuepress build docs" 26 | }, 27 | "bin": { 28 | "formily-vue-fix": "bin/formily-vue-fix.js", 29 | "formily-vue-switch": "bin/formily-vue-switch.js" 30 | }, 31 | "devDependencies": { 32 | "@ant-design/icons": "^2.1.1", 33 | "@ant-design/icons-vue": "^2.0.0", 34 | "@vue/composition-api": "^1.0.0-rc.7", 35 | "@vuepress-dumi/vuepress-plugin-dumi-previewer": "0.3.3", 36 | "@vuepress-dumi/vuepress-theme-dumi": "0.3.3", 37 | "@vuepress/plugin-back-to-top": "^1.8.2", 38 | "@vuepress/plugin-medium-zoom": "^1.8.2", 39 | "ant-design-vue": "^1.7.3", 40 | "codesandbox": "^2.2.3", 41 | "core-js": "^2.4.0", 42 | "vue": "^2.6.12", 43 | "vue3": "npm:vue@3", 44 | "vuepress": "^1.8.2", 45 | "vuepress-plugin-typescript": "^0.3.1" 46 | }, 47 | "dependencies": { 48 | "@formily/core": "2.3.7", 49 | "@formily/json-schema": "2.3.7", 50 | "@formily/reactive": "2.3.7", 51 | "@formily/reactive-vue": "2.3.7", 52 | "@formily/shared": "2.3.7", 53 | "@formily/validator": "2.3.7", 54 | "fs-extra": "^10.0.0", 55 | "vue-demi": ">=0.13.6", 56 | "vue-frag": "^1.1.4" 57 | }, 58 | "peerDependencies": { 59 | "@vue/composition-api": "^1.0.0-beta.1", 60 | "vue": "^2.6.0 || >=3.0.0-rc.0" 61 | }, 62 | "peerDependenciesMeta": { 63 | "@vue/composition-api": { 64 | "optional": true 65 | } 66 | }, 67 | "publishConfig": { 68 | "access": "public" 69 | }, 70 | "gitHead": "ac79c196ae9324889aca5e0501146f9b37b04283" 71 | } 72 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/Transfer.md: -------------------------------------------------------------------------------- ```markdown 1 | # Transfer 2 | 3 | > Shuttle Box 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | Transfer, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.Array 26 | name="transfer" 27 | title="shuttle box" 28 | x-decorator="FormItem" 29 | x-component="Transfer" 30 | enum={[ 31 | { label: 'Option 1', value: 'aaa' }, 32 | { label: 'Option 2', value: 'bbb' }, 33 | ]} 34 | /> 35 | </SchemaField> 36 | <FormButtonGroup> 37 | <Submit onSubmit={console.log}>Submit</Submit> 38 | </FormButtonGroup> 39 | </FormProvider> 40 | ) 41 | ``` 42 | 43 | ## JSON Schema case 44 | 45 | ```tsx 46 | import React from 'react' 47 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/next' 48 | import { createForm } from '@formily/core' 49 | import { FormProvider, createSchemaField } from '@formily/react' 50 | 51 | const SchemaField = createSchemaField({ 52 | components: { 53 | Transfer, 54 | FormItem, 55 | }, 56 | }) 57 | 58 | const form = createForm() 59 | 60 | const schema = { 61 | type: 'object', 62 | properties: { 63 | transfer: { 64 | type: 'array', 65 | title: 'shuttle box', 66 | 'x-decorator': 'FormItem', 67 | 'x-component': 'Transfer', 68 | enum: [ 69 | { label: 'Option 1', value: 'aaa' }, 70 | { label: 'Option 2', value: 'bbb' }, 71 | ], 72 | }, 73 | }, 74 | } 75 | 76 | const renderTitle = (item) => item.title 77 | 78 | export default () => ( 79 | <FormProvider form={form}> 80 | <SchemaField schema={schema} scope={{ renderTitle }} /> 81 | <FormButtonGroup> 82 | <Submit onSubmit={console.log}>Submit</Submit> 83 | </FormButtonGroup> 84 | </FormProvider> 85 | ) 86 | ``` 87 | 88 | ## Pure JSX case 89 | 90 | ```tsx 91 | import React from 'react' 92 | import { Transfer, FormItem, FormButtonGroup, Submit } from '@formily/next' 93 | import { createForm } from '@formily/core' 94 | import { FormProvider, Field } from '@formily/react' 95 | 96 | const form = createForm() 97 | 98 | export default () => ( 99 | <FormProvider form={form}> 100 | <Field 101 | name="transfer" 102 | title="shuttle box" 103 | dataSource={[ 104 | { label: 'Option 1', value: 'aaa' }, 105 | { label: 'Option 2', value: 'bbb' }, 106 | ]} 107 | decorator={[FormItem]} 108 | component={[Transfer]} 109 | /> 110 | <FormButtonGroup> 111 | <Submit onSubmit={console.log}>Submit</Submit> 112 | </FormButtonGroup> 113 | </FormProvider> 114 | ) 115 | ``` 116 | 117 | ## API 118 | 119 | Reference https://fusion.design/pc/component/basic/transfer 120 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/TimePicker.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # TimePicker 2 | 3 | > 时间选择器 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | TimePicker, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="time" 27 | title="时间" 28 | required 29 | x-decorator="FormItem" 30 | x-component="TimePicker" 31 | /> 32 | <SchemaField.String 33 | name="[startTime,endTime]" 34 | title="时间范围" 35 | x-decorator="FormItem" 36 | x-component="TimePicker.RangePicker" 37 | /> 38 | </SchemaField> 39 | <FormButtonGroup> 40 | <Submit onSubmit={console.log}>提交</Submit> 41 | </FormButtonGroup> 42 | </FormProvider> 43 | ) 44 | ``` 45 | 46 | ## JSON Schema 案例 47 | 48 | ```tsx 49 | import React from 'react' 50 | import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 51 | import { createForm } from '@formily/core' 52 | import { FormProvider, createSchemaField } from '@formily/react' 53 | 54 | const SchemaField = createSchemaField({ 55 | components: { 56 | TimePicker, 57 | FormItem, 58 | }, 59 | }) 60 | 61 | const form = createForm() 62 | 63 | const schema = { 64 | type: 'object', 65 | properties: { 66 | time: { 67 | title: '时间', 68 | 'x-decorator': 'FormItem', 69 | 'x-component': 'TimePicker', 70 | type: 'string', 71 | }, 72 | '[startTime,endTime]': { 73 | title: '时间范围', 74 | 'x-decorator': 'FormItem', 75 | 'x-component': 'TimePicker.RangePicker', 76 | type: 'string', 77 | }, 78 | }, 79 | } 80 | 81 | export default () => ( 82 | <FormProvider form={form}> 83 | <SchemaField schema={schema} /> 84 | <FormButtonGroup> 85 | <Submit onSubmit={console.log}>提交</Submit> 86 | </FormButtonGroup> 87 | </FormProvider> 88 | ) 89 | ``` 90 | 91 | ## 纯 JSX 案例 92 | 93 | ```tsx 94 | import React from 'react' 95 | import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 96 | import { createForm } from '@formily/core' 97 | import { FormProvider, Field } from '@formily/react' 98 | 99 | const form = createForm() 100 | 101 | export default () => ( 102 | <FormProvider form={form}> 103 | <Field 104 | name="time" 105 | title="时间" 106 | decorator={[FormItem]} 107 | component={[TimePicker]} 108 | /> 109 | <Field 110 | name="[startTime,endTime]" 111 | title="时间范围" 112 | decorator={[FormItem]} 113 | component={[TimePicker.RangePicker]} 114 | /> 115 | <FormButtonGroup> 116 | <Submit onSubmit={console.log}>提交</Submit> 117 | </FormButtonGroup> 118 | </FormProvider> 119 | ) 120 | ``` 121 | 122 | ## API 123 | 124 | 参考 https://ant.design/components/time-picker-cn/ 125 | ``` -------------------------------------------------------------------------------- /packages/element/src/form-layout/useResponsiveFormLayout.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { isArr, isValid } from '@formily/shared' 2 | import { onMounted, Ref, ref } from 'vue-demi' 3 | 4 | interface IProps { 5 | breakpoints?: number[] 6 | layout?: 7 | | 'vertical' 8 | | 'horizontal' 9 | | 'inline' 10 | | ('vertical' | 'horizontal' | 'inline')[] 11 | labelCol?: number | number[] 12 | wrapperCol?: number | number[] 13 | labelAlign?: 'right' | 'left' | ('right' | 'left')[] 14 | wrapperAlign?: 'right' | 'left' | ('right' | 'left')[] 15 | [props: string]: any 16 | } 17 | 18 | interface ICalcBreakpointIndex { 19 | (originalBreakpoints: number[], width: number): number 20 | } 21 | 22 | interface ICalculateProps { 23 | (target: Element, props: IProps): IProps 24 | } 25 | 26 | interface IUseResponsiveFormLayout { 27 | (props: IProps, root: Ref<Element>): { 28 | props: Ref<IProps> 29 | } 30 | } 31 | 32 | const calcBreakpointIndex: ICalcBreakpointIndex = (breakpoints, width) => { 33 | for (let i = 0; i < breakpoints.length; i++) { 34 | if (width <= breakpoints[i]) { 35 | return i 36 | } 37 | } 38 | } 39 | 40 | const calcFactor = <T>(value: T | T[], breakpointIndex: number): T => { 41 | if (Array.isArray(value)) { 42 | if (breakpointIndex === -1) return value[0] 43 | return value[breakpointIndex] ?? value[value.length - 1] 44 | } else { 45 | return value 46 | } 47 | } 48 | 49 | const factor = <T>(value: T | T[], breakpointIndex: number): T => 50 | isValid(value) ? calcFactor(value as any, breakpointIndex) : value 51 | 52 | const calculateProps: ICalculateProps = (target, props) => { 53 | const { clientWidth } = target 54 | const { 55 | breakpoints, 56 | layout, 57 | labelAlign, 58 | wrapperAlign, 59 | labelCol, 60 | wrapperCol, 61 | ...otherProps 62 | } = props 63 | const breakpointIndex = calcBreakpointIndex(breakpoints, clientWidth) 64 | 65 | return { 66 | layout: factor(layout, breakpointIndex), 67 | labelAlign: factor(labelAlign, breakpointIndex), 68 | wrapperAlign: factor(wrapperAlign, breakpointIndex), 69 | labelCol: factor(labelCol, breakpointIndex), 70 | wrapperCol: factor(wrapperCol, breakpointIndex), 71 | ...otherProps, 72 | } 73 | } 74 | 75 | export const useResponsiveFormLayout: IUseResponsiveFormLayout = ( 76 | props, 77 | root 78 | ) => { 79 | const { breakpoints } = props 80 | if (!isArr(breakpoints)) { 81 | return { props: ref(props) } 82 | } 83 | const layoutProps = ref<IProps>(props) 84 | 85 | const updateUI = () => { 86 | if (root.value) { 87 | layoutProps.value = calculateProps(root.value, props) 88 | } 89 | } 90 | 91 | onMounted(() => { 92 | const observer = () => { 93 | updateUI() 94 | } 95 | const resizeObserver = new ResizeObserver(observer) 96 | if (root.value) { 97 | resizeObserver.observe(root.value) 98 | } 99 | 100 | updateUI() 101 | 102 | return () => { 103 | resizeObserver.disconnect() 104 | } 105 | }) 106 | 107 | return { 108 | props: layoutProps, 109 | } 110 | } 111 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/TimePicker2.zh-CN.md: -------------------------------------------------------------------------------- ```markdown 1 | # TimePicker2 2 | 3 | > 时间选择器 4 | 5 | ## Markup Schema 案例 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | TimePicker2, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="time" 27 | title="时间" 28 | x-decorator="FormItem" 29 | x-component="TimePicker2" 30 | /> 31 | <SchemaField.String 32 | name="[startTime,endTime]" 33 | title="时间范围" 34 | x-decorator="FormItem" 35 | x-component="TimePicker2.RangePicker" 36 | /> 37 | </SchemaField> 38 | <FormButtonGroup> 39 | <Submit onSubmit={console.log}>提交</Submit> 40 | </FormButtonGroup> 41 | </FormProvider> 42 | ) 43 | ``` 44 | 45 | ## JSON Schema 案例 46 | 47 | ```tsx 48 | import React from 'react' 49 | import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' 50 | import { createForm } from '@formily/core' 51 | import { FormProvider, createSchemaField } from '@formily/react' 52 | 53 | const SchemaField = createSchemaField({ 54 | components: { 55 | TimePicker2, 56 | FormItem, 57 | }, 58 | }) 59 | 60 | const form = createForm() 61 | 62 | const schema = { 63 | type: 'object', 64 | properties: { 65 | time: { 66 | title: '时间', 67 | 'x-decorator': 'FormItem', 68 | 'x-component': 'TimePicker2', 69 | type: 'string', 70 | }, 71 | '[startTime,endTime]': { 72 | title: '时间范围', 73 | 'x-decorator': 'FormItem', 74 | 'x-component': 'TimePicker2.RangePicker', 75 | type: 'string', 76 | }, 77 | }, 78 | } 79 | 80 | export default () => ( 81 | <FormProvider form={form}> 82 | <SchemaField schema={schema} /> 83 | <FormButtonGroup> 84 | <Submit onSubmit={console.log}>提交</Submit> 85 | </FormButtonGroup> 86 | </FormProvider> 87 | ) 88 | ``` 89 | 90 | ## 纯 JSX 案例 91 | 92 | ```tsx 93 | import React from 'react' 94 | import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' 95 | import { createForm } from '@formily/core' 96 | import { FormProvider, Field } from '@formily/react' 97 | 98 | const form = createForm() 99 | 100 | export default () => ( 101 | <FormProvider form={form}> 102 | <Field 103 | name="time" 104 | title="时间" 105 | decorator={[FormItem]} 106 | component={[TimePicker2]} 107 | /> 108 | <Field 109 | name="[startTime,endTime]" 110 | title="时间范围" 111 | decorator={[FormItem]} 112 | component={[TimePicker2.RangePicker]} 113 | /> 114 | <FormButtonGroup> 115 | <Submit onSubmit={console.log}>提交</Submit> 116 | </FormButtonGroup> 117 | </FormProvider> 118 | ) 119 | ``` 120 | 121 | ## API 122 | 123 | 参考 https://fusion.design/pc/component/basic/time-picker2 124 | ``` -------------------------------------------------------------------------------- /packages/benchmark/webpack.base.ts: -------------------------------------------------------------------------------- ```typescript 1 | import path from 'path' 2 | import fs from 'fs-extra' 3 | import { GlobSync } from 'glob' 4 | import MiniCssExtractPlugin from 'mini-css-extract-plugin' 5 | import autoprefixer from 'autoprefixer' 6 | //import { getThemeVariables } from 'antd/dist/theme' 7 | 8 | const getWorkspaceAlias = () => { 9 | const basePath = path.resolve(__dirname, '../../') 10 | const pkg = fs.readJSONSync(path.resolve(basePath, 'package.json')) || {} 11 | const results = {} 12 | const workspaces = pkg.workspaces 13 | if (Array.isArray(workspaces)) { 14 | workspaces.forEach((pattern) => { 15 | const { found } = new GlobSync(pattern, { cwd: basePath }) 16 | found.forEach((name) => { 17 | const pkg = fs.readJSONSync( 18 | path.resolve(basePath, name, './package.json') 19 | ) 20 | results[pkg.name] = path.resolve(basePath, name, './src') 21 | }) 22 | }) 23 | } 24 | return results 25 | } 26 | 27 | export default { 28 | mode: 'development', 29 | devtool: 'inline-source-map', // 嵌入到源文件中 30 | stats: { 31 | entrypoints: false, 32 | children: false, 33 | }, 34 | entry: { 35 | index: path.resolve(__dirname, './src/index'), 36 | }, 37 | output: { 38 | path: path.resolve(__dirname, 'build'), 39 | filename: '[name].[hash].bundle.js', 40 | }, 41 | resolve: { 42 | modules: ['node_modules'], 43 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], 44 | alias: getWorkspaceAlias(), 45 | }, 46 | externals: { 47 | react: 'React', 48 | 'react-dom': 'ReactDOM', 49 | moment: 'moment', 50 | antd: 'antd', 51 | }, 52 | module: { 53 | rules: [ 54 | { 55 | test: /\.tsx?$/, 56 | use: [ 57 | { 58 | loader: require.resolve('ts-loader'), 59 | options: { 60 | transpileOnly: true, 61 | }, 62 | }, 63 | ], 64 | }, 65 | { 66 | test: /\.css$/, 67 | use: [MiniCssExtractPlugin.loader, require.resolve('css-loader')], 68 | }, 69 | { 70 | test: /\.less$/, 71 | use: [ 72 | MiniCssExtractPlugin.loader, 73 | { loader: 'css-loader' }, 74 | { 75 | loader: 'postcss-loader', 76 | options: { 77 | plugins: () => autoprefixer(), 78 | }, 79 | }, 80 | { 81 | loader: 'less-loader', 82 | options: { 83 | // modifyVars: getThemeVariables({ 84 | // dark: true, // 开启暗黑模式 85 | // }), 86 | javascriptEnabled: true, 87 | }, 88 | }, 89 | ], 90 | }, 91 | { 92 | test: /\.(woff|woff2|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, 93 | use: ['url-loader'], 94 | }, 95 | { 96 | test: /\.html?$/, 97 | loader: require.resolve('file-loader'), 98 | options: { 99 | name: '[name].[ext]', 100 | }, 101 | }, 102 | ], 103 | }, 104 | } 105 | ``` -------------------------------------------------------------------------------- /packages/react/src/shared/connect.ts: -------------------------------------------------------------------------------- ```typescript 1 | import React from 'react' 2 | import { isFn, isStr, FormPath, each, isValid } from '@formily/shared' 3 | import { isVoidField } from '@formily/core' 4 | import { observer, Observer } from '@formily/reactive-react' 5 | import { JSXComponent, IComponentMapper, IStateMapper } from '../types' 6 | import { useField } from '../hooks' 7 | import hoistNonReactStatics from 'hoist-non-react-statics' 8 | 9 | export function mapProps<T extends JSXComponent>( 10 | ...args: IStateMapper<React.ComponentProps<T>>[] 11 | ) { 12 | return (target: T) => { 13 | return observer( 14 | (props: any) => { 15 | const field = useField() 16 | const results = args.reduce( 17 | (props, mapper) => { 18 | if (isFn(mapper)) { 19 | props = Object.assign(props, mapper(props, field)) 20 | } else { 21 | each(mapper, (to, extract) => { 22 | const extractValue = FormPath.getIn(field, extract) 23 | const targetValue = isStr(to) ? to : (extract as any) 24 | const originalValue = FormPath.getIn(props, targetValue) 25 | if (extract === 'value') { 26 | if (to !== extract) { 27 | delete props.value 28 | } 29 | } 30 | if (isValid(originalValue) && !isValid(extractValue)) return 31 | FormPath.setIn(props, targetValue, extractValue) 32 | }) 33 | } 34 | return props 35 | }, 36 | { ...props } 37 | ) 38 | return React.createElement(target, results) 39 | }, 40 | { 41 | forwardRef: true, 42 | } 43 | ) 44 | } 45 | } 46 | 47 | export function mapReadPretty<T extends JSXComponent, C extends JSXComponent>( 48 | component: C, 49 | readPrettyProps?: React.ComponentProps<C> 50 | ) { 51 | return (target: T) => { 52 | return observer( 53 | (props) => { 54 | const field = useField() 55 | if (!isVoidField(field) && field?.pattern === 'readPretty') { 56 | return React.createElement(component, { 57 | ...readPrettyProps, 58 | ...props, 59 | }) 60 | } 61 | return React.createElement(target, props) 62 | }, 63 | { 64 | forwardRef: true, 65 | } 66 | ) 67 | } 68 | } 69 | 70 | export function connect<T extends JSXComponent>( 71 | target: T, 72 | ...args: IComponentMapper<T>[] 73 | ) { 74 | const Target = args.reduce((target, mapper) => { 75 | return mapper(target) 76 | }, target) 77 | 78 | const Destination = React.forwardRef( 79 | (props: Partial<React.ComponentProps<T>>, ref) => { 80 | return React.createElement(Target, { ...props, ref }) 81 | } 82 | ) 83 | 84 | if (target) hoistNonReactStatics(Destination, target as any) 85 | 86 | return Destination 87 | } 88 | 89 | export { observer, Observer } 90 | ``` -------------------------------------------------------------------------------- /packages/reactive/src/__tests__/collections-weakset.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { observable, autorun, raw } from '..' 2 | 3 | describe('WeakSet', () => { 4 | test('should be a proper JS WeakSet', () => { 5 | const weakSet = observable(new WeakSet()) 6 | expect(weakSet).toBeInstanceOf(WeakSet) 7 | expect(raw(weakSet)).toBeInstanceOf(WeakSet) 8 | }) 9 | 10 | test('should autorun mutations', () => { 11 | const handler = jest.fn() 12 | const value = {} 13 | const weakSet = observable(new WeakSet()) 14 | autorun(() => handler(weakSet.has(value))) 15 | 16 | expect(handler).toBeCalledTimes(1) 17 | expect(handler).lastCalledWith(false) 18 | weakSet.add(value) 19 | expect(handler).toBeCalledTimes(2) 20 | expect(handler).lastCalledWith(true) 21 | weakSet.delete(value) 22 | expect(handler).toBeCalledTimes(3) 23 | expect(handler).lastCalledWith(false) 24 | }) 25 | 26 | test('should not autorun custom property mutations', () => { 27 | const handler = jest.fn() 28 | const weakSet = observable(new WeakSet()) 29 | autorun(() => handler(weakSet['customProp'])) 30 | 31 | expect(handler).toBeCalledTimes(1) 32 | expect(handler).lastCalledWith(undefined) 33 | weakSet['customProp'] = 'Hello World' 34 | expect(handler).toBeCalledTimes(1) 35 | }) 36 | 37 | test('should not autorun non value changing mutations', () => { 38 | const handler = jest.fn() 39 | const value = {} 40 | const weakSet = observable(new WeakSet()) 41 | autorun(() => handler(weakSet.has(value))) 42 | 43 | expect(handler).toBeCalledTimes(1) 44 | expect(handler).lastCalledWith(false) 45 | weakSet.add(value) 46 | expect(handler).toBeCalledTimes(2) 47 | expect(handler).lastCalledWith(true) 48 | weakSet.add(value) 49 | expect(handler).toBeCalledTimes(2) 50 | weakSet.delete(value) 51 | expect(handler).toBeCalledTimes(3) 52 | expect(handler).lastCalledWith(false) 53 | weakSet.delete(value) 54 | expect(handler).toBeCalledTimes(3) 55 | }) 56 | 57 | test('should not autorun raw data', () => { 58 | const handler = jest.fn() 59 | const value = {} 60 | const weakSet = observable(new WeakSet()) 61 | autorun(() => handler(raw(weakSet).has(value))) 62 | 63 | expect(handler).toBeCalledTimes(1) 64 | expect(handler).lastCalledWith(false) 65 | weakSet.add(value) 66 | expect(handler).toBeCalledTimes(1) 67 | weakSet.delete(value) 68 | expect(handler).toBeCalledTimes(1) 69 | }) 70 | 71 | test('should not be triggered by raw mutations', () => { 72 | const handler = jest.fn() 73 | const value = {} 74 | const weakSet = observable(new WeakSet()) 75 | autorun(() => handler(weakSet.has(value))) 76 | 77 | expect(handler).toBeCalledTimes(1) 78 | expect(handler).lastCalledWith(false) 79 | raw(weakSet).add(value) 80 | expect(handler).toBeCalledTimes(1) 81 | raw(weakSet).delete(value) 82 | expect(handler).toBeCalledTimes(1) 83 | }) 84 | }) 85 | ``` -------------------------------------------------------------------------------- /packages/antd/src/array-tabs/index.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import React, { Fragment, useState } from 'react' 2 | import { Tabs, Badge } from 'antd' 3 | import { ArrayField } from '@formily/core' 4 | import { 5 | useField, 6 | observer, 7 | useFieldSchema, 8 | RecursionField, 9 | ReactFC, 10 | } from '@formily/react' 11 | import { TabsProps } from 'antd/lib/tabs' 12 | 13 | interface IFeedbackBadgeProps { 14 | index: number 15 | } 16 | 17 | const FeedbackBadge: ReactFC<IFeedbackBadgeProps> = observer( 18 | (props) => { 19 | const field = useField<ArrayField>() 20 | const tab = `${field.title || 'Untitled'} ${props.index + 1}` 21 | const errors = field.errors.filter((error) => 22 | error.address.includes(`${field.address}.${props.index}`) 23 | ) 24 | if (errors.length) { 25 | return ( 26 | <Badge size="small" className="errors-badge" count={errors.length}> 27 | {tab} 28 | </Badge> 29 | ) 30 | } 31 | return <Fragment>{tab}</Fragment> 32 | }, 33 | { 34 | scheduler(request) { 35 | requestAnimationFrame(request) 36 | }, 37 | } 38 | ) 39 | 40 | export const ArrayTabs: React.FC<React.PropsWithChildren<TabsProps>> = observer( 41 | (props) => { 42 | const field = useField<ArrayField>() 43 | const schema = useFieldSchema() 44 | const [activeKey, setActiveKey] = useState('tab-0') 45 | const value = Array.isArray(field.value) ? field.value : [] 46 | const dataSource = value?.length ? value : [{}] 47 | const onEdit = (targetKey: any, type: 'add' | 'remove') => { 48 | if (type == 'add') { 49 | const id = dataSource.length 50 | if (field?.value?.length) { 51 | field.push(null) 52 | } else { 53 | field.push(null, null) 54 | } 55 | setActiveKey(`tab-${id}`) 56 | } else if (type == 'remove') { 57 | const index = Number(targetKey.match(/-(\d+)/)?.[1]) 58 | if (index - 1 > -1) { 59 | setActiveKey(`tab-${index - 1}`) 60 | } 61 | field.remove(index) 62 | } 63 | } 64 | return ( 65 | <Tabs 66 | {...props} 67 | activeKey={activeKey} 68 | onChange={(key) => { 69 | setActiveKey(key) 70 | }} 71 | type="editable-card" 72 | onEdit={onEdit} 73 | > 74 | {dataSource?.map((item, index) => { 75 | const items = Array.isArray(schema.items) 76 | ? schema.items[index] 77 | : schema.items 78 | const key = `tab-${index}` 79 | return ( 80 | <Tabs.TabPane 81 | key={key} 82 | forceRender 83 | closable={index !== 0} 84 | tab={<FeedbackBadge index={index} />} 85 | > 86 | <RecursionField schema={items} name={index} /> 87 | </Tabs.TabPane> 88 | ) 89 | })} 90 | </Tabs> 91 | ) 92 | }, 93 | { 94 | scheduler(request) { 95 | requestAnimationFrame(request) 96 | }, 97 | } 98 | ) 99 | 100 | export default ArrayTabs 101 | ``` -------------------------------------------------------------------------------- /docs/guide/scenes/query-list.md: -------------------------------------------------------------------------------- ```markdown 1 | # Query list 2 | 3 | Because Formily Schema can completely describe the UI, we can simply abstract out the QueryList/QueryForm/QueryTable components to combine to implement the query list component. The following is only the pseudo code, because the query list scenario usually involves a lot of business packaging. At present, Formily hasn't figured out how to consider both versatility and quick start of business, so it will not open up specific components for the time being. 4 | 5 | But you can take a look at the pseudo-code first. If these components are officially implemented, the usage will definitely be like this: 6 | 7 | ```tsx pure 8 | import React from 'react' 9 | import { Void, Object, Array, String } from './MySchemaField' 10 | export default () => ( 11 | <Void 12 | x-component="QueryList" 13 | x-component-props={{ 14 | service: (params) => fetchRecords(params), 15 | }} 16 | > 17 | <Object name="query" x-component="QueryForm"> 18 | <String name="name" x-component="Input" /> 19 | <String name="id" x-component="Input" /> 20 | </Object> 21 | <Void name="toolbar" x-component="QueryToolbar"></Void> 22 | <Array name="list" x-component="QueryTable"> 23 | <Object> 24 | <Void x-component="QueryTable.Column"> 25 | <String name="name" x-component="PreviewText" /> 26 | </Void> 27 | <Void x-component="QueryTable.Column"> 28 | <String name="id" x-component="PreviewText" /> 29 | </Void> 30 | </Object> 31 | </Array> 32 | </Void> 33 | ) 34 | ``` 35 | 36 | ## Ideas 37 | 38 | - QueryList 39 | - Mainly responsible for sending requests at the top level, and issuing query methods to QueryForm and QueryTable for consumption through React Context 40 | - Query parameters need to call `form.query('query')` to find the field of QueryForm, and then take out the value of the field to send the request 41 | - When you have finished querying the data, you need to call `form.query('list')` to find the QueryTable field, and then fill in the table data for the value of the field model 42 | - QueryTable 43 | - The idea is very similar to that of ArrayTable. The main thing is to parse the Schema subtree and assemble the Columns data needed by the Table by yourself. If you want to support column merging and row merging, you need to parse more complex data 44 | - Based on props.value for rendering Table structure 45 | - Rely on RecursionField to render the internal data of the Table Column 46 | - Rely on the query method passed down from the context to achieve paging query 47 | - QueryForm 48 | - There is no special logic, the main thing is to combine Form+FormGrid to realize a query form layout 49 | - Realize query form query by relying on the query method passed down from the context 50 | ``` -------------------------------------------------------------------------------- /packages/antd/docs/components/TimePicker.md: -------------------------------------------------------------------------------- ```markdown 1 | # TimePicker 2 | 3 | > Time Picker 4 | 5 | ## Markup Schema example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | TimePicker, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="time" 27 | title="time" 28 | required 29 | x-decorator="FormItem" 30 | x-component="TimePicker" 31 | /> 32 | <SchemaField.String 33 | name="[startTime,endTime]" 34 | title="time range" 35 | x-decorator="FormItem" 36 | x-component="TimePicker.RangePicker" 37 | /> 38 | </SchemaField> 39 | <FormButtonGroup> 40 | <Submit onSubmit={console.log}>Submit</Submit> 41 | </FormButtonGroup> 42 | </FormProvider> 43 | ) 44 | ``` 45 | 46 | ## JSON Schema case 47 | 48 | ```tsx 49 | import React from 'react' 50 | import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 51 | import { createForm } from '@formily/core' 52 | import { FormProvider, createSchemaField } from '@formily/react' 53 | 54 | const SchemaField = createSchemaField({ 55 | components: { 56 | TimePicker, 57 | FormItem, 58 | }, 59 | }) 60 | 61 | const form = createForm() 62 | 63 | const schema = { 64 | type: 'object', 65 | properties: { 66 | time: { 67 | title: 'Time', 68 | 'x-decorator': 'FormItem', 69 | 'x-component': 'TimePicker', 70 | type: 'string', 71 | }, 72 | '[startTime,endTime]': { 73 | title: 'Time Range', 74 | 'x-decorator': 'FormItem', 75 | 'x-component': 'TimePicker.RangePicker', 76 | type: 'string', 77 | }, 78 | }, 79 | } 80 | 81 | export default () => ( 82 | <FormProvider form={form}> 83 | <SchemaField schema={schema} /> 84 | <FormButtonGroup> 85 | <Submit onSubmit={console.log}>Submit</Submit> 86 | </FormButtonGroup> 87 | </FormProvider> 88 | ) 89 | ``` 90 | 91 | ## Pure JSX case 92 | 93 | ```tsx 94 | import React from 'react' 95 | import { TimePicker, FormItem, FormButtonGroup, Submit } from '@formily/antd' 96 | import { createForm } from '@formily/core' 97 | import { FormProvider, Field } from '@formily/react' 98 | 99 | const form = createForm() 100 | 101 | export default () => ( 102 | <FormProvider form={form}> 103 | <Field 104 | name="time" 105 | title="time" 106 | decorator={[FormItem]} 107 | component={[TimePicker]} 108 | /> 109 | <Field 110 | name="[startTime,endTime]" 111 | title="time range" 112 | decorator={[FormItem]} 113 | component={[TimePicker.RangePicker]} 114 | /> 115 | <FormButtonGroup> 116 | <Submit onSubmit={console.log}>Submit</Submit> 117 | </FormButtonGroup> 118 | </FormProvider> 119 | ) 120 | ``` 121 | 122 | ## API 123 | 124 | Reference https://ant.design/components/time-picker-cn/ 125 | ``` -------------------------------------------------------------------------------- /packages/next/docs/components/TimePicker2.md: -------------------------------------------------------------------------------- ```markdown 1 | # TimePicker2 2 | 3 | > Time 选择器 4 | 5 | ## Markup Schema Example 6 | 7 | ```tsx 8 | import React from 'react' 9 | import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' 10 | import { createForm } from '@formily/core' 11 | import { FormProvider, createSchemaField } from '@formily/react' 12 | 13 | const SchemaField = createSchemaField({ 14 | components: { 15 | TimePicker2, 16 | FormItem, 17 | }, 18 | }) 19 | 20 | const form = createForm() 21 | 22 | export default () => ( 23 | <FormProvider form={form}> 24 | <SchemaField> 25 | <SchemaField.String 26 | name="time" 27 | title="time" 28 | x-decorator="FormItem" 29 | x-component="TimePicker2" 30 | /> 31 | <SchemaField.String 32 | name="[startTime,endTime]" 33 | title="Time Range" 34 | x-decorator="FormItem" 35 | x-component="TimePicker2.RangePicker" 36 | /> 37 | </SchemaField> 38 | <FormButtonGroup> 39 | <Submit onSubmit={console.log}>Submit</Submit> 40 | </FormButtonGroup> 41 | </FormProvider> 42 | ) 43 | ``` 44 | 45 | ## JSON Schema Case 46 | 47 | ```tsx 48 | import React from 'react' 49 | import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' 50 | import { createForm } from '@formily/core' 51 | import { FormProvider, createSchemaField } from '@formily/react' 52 | 53 | const SchemaField = createSchemaField({ 54 | components: { 55 | TimePicker2, 56 | FormItem, 57 | }, 58 | }) 59 | 60 | const form = createForm() 61 | 62 | const schema = { 63 | type: 'object', 64 | properties: { 65 | time: { 66 | title: 'Time', 67 | 'x-decorator': 'FormItem', 68 | 'x-component': 'TimePicker2', 69 | type: 'string', 70 | }, 71 | '[startTime,endTime]': { 72 | title: 'Time Range', 73 | 'x-decorator': 'FormItem', 74 | 'x-component': 'TimePicker2.RangePicker', 75 | type: 'string', 76 | }, 77 | }, 78 | } 79 | 80 | export default () => ( 81 | <FormProvider form={form}> 82 | <SchemaField schema={schema} /> 83 | <FormButtonGroup> 84 | <Submit onSubmit={console.log}>Submit</Submit> 85 | </FormButtonGroup> 86 | </FormProvider> 87 | ) 88 | ``` 89 | 90 | ## Pure JSX case 91 | 92 | ```tsx 93 | import React from 'react' 94 | import { TimePicker2, FormItem, FormButtonGroup, Submit } from '@formily/next' 95 | import { createForm } from '@formily/core' 96 | import { FormProvider, Field } from '@formily/react' 97 | 98 | const form = createForm() 99 | 100 | export default () => ( 101 | <FormProvider form={form}> 102 | <Field 103 | name="time" 104 | title="Time" 105 | decorator={[FormItem]} 106 | component={[TimePicker2]} 107 | /> 108 | <Field 109 | name="[startTime,endTime]" 110 | title="Time Range" 111 | decorator={[FormItem]} 112 | component={[TimePicker2.RangePicker]} 113 | /> 114 | <FormButtonGroup> 115 | <Submit onSubmit={console.log}>Submit</Submit> 116 | </FormButtonGroup> 117 | </FormProvider> 118 | ) 119 | ``` 120 | 121 | ## API 122 | 123 | Reference https://fusion.design/pc/component/basic/time-picker2 124 | ``` -------------------------------------------------------------------------------- /packages/element/docs/demos/guide/form-step/markup-schema.vue: -------------------------------------------------------------------------------- ```vue 1 | <template> 2 | <FormProvider :form="form"> 3 | <SchemaField> 4 | <SchemaVoidField x-component="FormStep" :x-component-props="{ formStep }"> 5 | <SchemaVoidField 6 | x-component="FormStep.StepPane" 7 | :x-component-props="{ title: '第一步' }" 8 | > 9 | <SchemaStringField 10 | name="aaa" 11 | x-decorator="FormItem" 12 | required 13 | x-component="Input" 14 | /> 15 | </SchemaVoidField> 16 | <SchemaVoidField 17 | x-component="FormStep.StepPane" 18 | :x-component-props="{ title: '第二步' }" 19 | > 20 | <SchemaStringField 21 | name="bbb" 22 | x-decorator="FormItem" 23 | required 24 | x-component="Input" 25 | /> 26 | </SchemaVoidField> 27 | <SchemaVoidField 28 | type="void" 29 | x-component="FormStep.StepPane" 30 | :x-component-props="{ title: '第三步' }" 31 | > 32 | <SchemaStringField 33 | name="ccc" 34 | x-decorator="FormItem" 35 | required 36 | x-component="Input" 37 | /> 38 | </SchemaVoidField> 39 | </SchemaVoidField> 40 | </SchemaField> 41 | <FormConsumer> 42 | <template #default> 43 | <FormButtonGroup> 44 | <Button 45 | :disabled="!formStep.allowBack" 46 | @click=" 47 | () => { 48 | formStep.back() 49 | } 50 | " 51 | > 52 | 上一步 53 | </Button> 54 | <Button 55 | :disabled="!formStep.allowNext" 56 | @click=" 57 | () => { 58 | formStep.next() 59 | } 60 | " 61 | > 62 | 下一步 63 | </Button> 64 | <Submit :disabled="formStep.allowNext" @submit="log">提交</Submit> 65 | </FormButtonGroup> 66 | </template> 67 | </FormConsumer> 68 | </FormProvider> 69 | </template> 70 | 71 | <script> 72 | import { createForm } from '@formily/core' 73 | import { FormProvider, FormConsumer, createSchemaField } from '@formily/vue' 74 | import { 75 | FormItem, 76 | FormStep, 77 | FormButtonGroup, 78 | Submit, 79 | Input, 80 | } from '@formily/element' 81 | import { Button } from 'element-ui' 82 | import Template from '../editable/template.vue' 83 | 84 | const SchemaField = createSchemaField({ 85 | components: { 86 | FormItem, 87 | FormStep, 88 | Input, 89 | }, 90 | }) 91 | const formStep = FormStep.createFormStep() 92 | 93 | export default { 94 | components: { 95 | FormConsumer, 96 | FormProvider, 97 | FormButtonGroup, 98 | Button, 99 | Submit, 100 | Template, 101 | ...SchemaField, 102 | }, 103 | 104 | data() { 105 | const form = createForm() 106 | 107 | return { 108 | form, 109 | formStep, 110 | } 111 | }, 112 | methods: { 113 | log() { 114 | this.formStep.submit(console.log) 115 | }, 116 | }, 117 | } 118 | </script> 119 | 120 | <style lang="scss" scoped></style> 121 | ``` -------------------------------------------------------------------------------- /packages/antd/__tests__/moment.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import moment from 'moment' 2 | import { formatMomentValue, momentable } from '../src/__builtins__/moment' 3 | 4 | test('momentable is usable', () => { 5 | expect(moment.isMoment(momentable('2021-09-08'))).toBe(true) 6 | expect( 7 | momentable(['2021-09-08', '2021-12-29']).every((item) => 8 | moment.isMoment(item) 9 | ) 10 | ).toBe(true) 11 | expect(momentable(0)).toBe(0) 12 | }) 13 | 14 | test('formatMomentValue is usable', () => { 15 | expect(formatMomentValue('', 'YYYY-MM-DD', '~')).toBe('~') 16 | expect(formatMomentValue('2021-12-21 15:47:00', 'YYYY-MM-DD')).toBe( 17 | '2021-12-21' 18 | ) 19 | expect(formatMomentValue('2021-12-21 15:47:00', undefined)).toBe( 20 | '2021-12-21 15:47:00' 21 | ) 22 | expect(formatMomentValue('2021-12-21 15:47:00', (date: string) => date)).toBe( 23 | '2021-12-21 15:47:00' 24 | ) 25 | expect(formatMomentValue('12:11', 'HH:mm')).toBe('12:11') 26 | expect(formatMomentValue('12:11:11', 'HH:mm:ss')).toBe('12:11:11') 27 | expect(formatMomentValue(['12:11'], ['HH:mm'])).toEqual(['12:11']) 28 | expect(formatMomentValue(['12:11:11'], ['HH:mm:ss'])).toEqual(['12:11:11']) 29 | expect(formatMomentValue(1663155911097, 'YYYY-MM-DD HH:mm:ss')).toBe( 30 | moment(1663155911097).format('YYYY-MM-DD HH:mm:ss') 31 | ) 32 | expect(formatMomentValue([1663155911097], ['YYYY-MM-DD HH:mm:ss'])).toEqual([ 33 | moment(1663155911097).format('YYYY-MM-DD HH:mm:ss'), 34 | ]) 35 | expect( 36 | formatMomentValue('2022-09-15T09:56:26.000Z', 'YYYY-MM-DD HH:mm:ss') 37 | ).toBe(moment('2022-09-15T09:56:26.000Z').format('YYYY-MM-DD HH:mm:ss')) 38 | expect( 39 | formatMomentValue(['2022-09-15T09:56:26.000Z'], ['YYYY-MM-DD HH:mm:ss']) 40 | ).toEqual([moment('2022-09-15T09:56:26.000Z').format('YYYY-MM-DD HH:mm:ss')]) 41 | expect(formatMomentValue('2022-09-15 09:56:26', 'HH:mm:ss')).toBe('09:56:26') 42 | expect(formatMomentValue(['2022-09-15 09:56:26'], ['HH:mm:ss'])).toEqual([ 43 | '09:56:26', 44 | ]) 45 | expect( 46 | formatMomentValue( 47 | ['2021-12-21 15:47:00', '2021-12-29 15:47:00'], 48 | 'YYYY-MM-DD' 49 | ) 50 | ).toEqual(['2021-12-21', '2021-12-29']) 51 | expect( 52 | formatMomentValue( 53 | ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], 54 | (date: string) => date 55 | ) 56 | ).toEqual(['2021-12-21 16:47:00', '2021-12-29 18:47:00']) 57 | expect( 58 | formatMomentValue( 59 | ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], 60 | ['YYYY-MM-DD', (date: string) => date] 61 | ) 62 | ).toEqual(['2021-12-21', '2021-12-29 18:47:00']) 63 | expect( 64 | formatMomentValue( 65 | ['2021-12-21 16:47:00', '2021-12-29 18:47:00'], 66 | ['YYYY-MM-DD', undefined] 67 | ) 68 | ).toEqual(['2021-12-21', '2021-12-29 18:47:00']) 69 | expect(formatMomentValue([undefined], 'YYYY-MM-DD', 'placeholder')).toEqual([ 70 | 'placeholder', 71 | ]) 72 | }) 73 | ``` -------------------------------------------------------------------------------- /packages/element/src/form/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Form as FormType, IFormFeedback } from '@formily/core' 2 | import { FormProvider as _FormProvider, h, useForm } from '@formily/vue' 3 | import { Component, VNode } from 'vue' 4 | import { defineComponent } from 'vue-demi' 5 | import { FormLayout, FormLayoutProps } from '../form-layout' 6 | import { PreviewText } from '../preview-text' 7 | 8 | const FormProvider = _FormProvider as unknown as Component 9 | 10 | export interface FormProps extends FormLayoutProps { 11 | form?: FormType 12 | component?: Component 13 | previewTextPlaceholder: string | (() => VNode) 14 | onAutoSubmit?: (values: any) => any 15 | onAutoSubmitFailed?: (feedbacks: IFormFeedback[]) => void 16 | } 17 | 18 | export const Form = defineComponent<FormProps>({ 19 | name: 'FForm', 20 | props: [ 21 | 'form', 22 | 'component', 23 | 'previewTextPlaceholder', 24 | 'onAutoSubmit', 25 | 'onAutoSubmitFailed', 26 | ], 27 | setup(props, { attrs, slots, listeners }) { 28 | const top = useForm() 29 | 30 | return () => { 31 | const { 32 | form, 33 | component = 'form', 34 | onAutoSubmit = listeners?.autoSubmit, 35 | onAutoSubmitFailed = listeners?.autoSubmitFailed, 36 | previewTextPlaceholder = slots?.previewTextPlaceholder, 37 | } = props 38 | 39 | const renderContent = (form: FormType) => { 40 | return h( 41 | PreviewText.Placeholder, 42 | { 43 | props: { 44 | value: previewTextPlaceholder, 45 | }, 46 | }, 47 | { 48 | default: () => [ 49 | h( 50 | FormLayout, 51 | { 52 | attrs: { 53 | ...attrs, 54 | }, 55 | }, 56 | { 57 | default: () => [ 58 | h( 59 | component, 60 | { 61 | on: { 62 | submit: (e: Event) => { 63 | e?.stopPropagation?.() 64 | e?.preventDefault?.() 65 | form 66 | .submit(onAutoSubmit as (e: any) => void) 67 | .catch(onAutoSubmitFailed as (e: any) => void) 68 | }, 69 | }, 70 | }, 71 | slots 72 | ), 73 | ], 74 | } 75 | ), 76 | ], 77 | } 78 | ) 79 | } 80 | 81 | if (form) { 82 | return h( 83 | FormProvider, 84 | { props: { form } }, 85 | { 86 | default: () => renderContent(form), 87 | } 88 | ) 89 | } 90 | 91 | if (!top.value) throw new Error('must pass form instance by createForm') 92 | 93 | return renderContent(top.value) 94 | } 95 | }, 96 | }) 97 | 98 | export default Form 99 | ``` -------------------------------------------------------------------------------- /packages/next/src/form-item/scss/variable.scss: -------------------------------------------------------------------------------- ```scss 1 | //// 2 | /// @module form: 表单 3 | /// @tag Form 4 | /// @category component 5 | /// @family data-entry 6 | /// @varPrefix $form- 7 | /// @classPrefix {prefix}-form 8 | /// @order {"size/bounding":10,"size/item":11,"size/label":12,"size/help":13,"size/border":14,"statement/help":10,"statement/label":11,"statement/normal":12,"statement/border":13} 9 | //// 10 | 11 | @charset "UTF-8"; 12 | 13 | // form variables 14 | // -------------------------------------------------- 15 | 16 | $form-item-cls: '#{$css-prefix}formily-item'; 17 | 18 | $input-border-color: $color-line1-3 !default; 19 | /// label padding (r) 20 | /// @namespace size/bounding 21 | $form-label-padding-r: $s-3 !default; 22 | 23 | /// margin (b) 24 | /// @namespace size/item 25 | $form-item-m-margin-b: $s-4 !default; 26 | 27 | /// margin (b) 28 | /// @namespace size/item 29 | $form-item-l-margin-b: $s-5 !default; 30 | 31 | /// margin (b) 32 | /// @namespace size/item 33 | $form-item-s-margin-b: $s-3 !default; 34 | 35 | /// margin (r) 36 | /// @namespace size/item 37 | $form-inline-l-item-margin-r: $s-6 !default; 38 | 39 | /// margin (r) 40 | /// @namespace size/item 41 | $form-inline-m-item-margin-r: $s-5 !default; 42 | 43 | /// margin (r) 44 | /// @namespace size/item 45 | $form-inline-s-item-margin-r: $s-4 !default; 46 | 47 | /// margin (t) 48 | /// @namespace size/help 49 | $form-help-margin-top: $s-1 !default; 50 | 51 | /// text 52 | /// @namespace size/help 53 | $form-help-font-size: $font-size-caption !default; 54 | 55 | /// text 56 | /// @namespace statement/help 57 | $form-help-color: $color-text1-2 !default; 58 | 59 | /// error text 60 | /// @namespace statement/help 61 | $form-error-color: $color-error-3 !default; 62 | 63 | /// warning text 64 | /// @namespace statement/help 65 | $form-warning-color: $color-warning-3 !default; 66 | 67 | $form-success-color: $color-success-3 !default; 68 | 69 | /// margin (b) 70 | /// @type length 71 | /// @namespace size/label 72 | $form-top-label-margin-b: 2px !default; 73 | 74 | /// text 75 | /// @namespace statement/label 76 | $form-label-color: $color-text1-3 !default; 77 | 78 | /// padding 79 | /// @namespace size/bounding 80 | $input-l-padding: $s-3 !default; 81 | 82 | /// padding(l) 83 | /// @namespace size/label 84 | $input-l-label-padding-left: $s-3 !default; 85 | 86 | /// padding(r) 87 | /// @namespace size/label 88 | $input-l-icon-padding-right: $s-2 !default; 89 | 90 | // medium 91 | // -------------------------------------------------- 92 | 93 | /// padding 94 | /// @namespace size/bounding 95 | $input-m-padding: $s-2 !default; 96 | 97 | /// padding(l) 98 | /// @namespace size/label 99 | $input-m-label-padding-left: $s-2 !default; 100 | 101 | /// padding(r) 102 | /// @namespace size/label 103 | $input-m-icon-padding-right: $s-2 !default; 104 | 105 | // small 106 | // -------------------------------------------------- 107 | 108 | /// padding 109 | /// @namespace size/bounding 110 | $input-s-padding: $s-1 !default; 111 | 112 | /// padding(l) 113 | /// @namespace size/label 114 | $input-s-label-padding-left: $s-2 !default; 115 | 116 | /// padding(r) 117 | /// @namespace size/label 118 | $input-s-icon-padding-right: $s-1 !default; 119 | ```