diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000..b30565c
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,9 @@
+coverage:
+ status:
+ project:
+ default:
+ threshold: 0.1%
+ patch:
+ default:
+ threshold: 0.1%
+ target: 95%
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..5ec77a4
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+
+[*.gradle]
+indent_size = 4
\ No newline at end of file
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..ceabaaa
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,15 @@
+node_modules
+lib
+dist
+build
+coverage
+expected
+website
+gh-pages
+weex
+build.ts
+esm
+doc-site
+public
+package
+jest.config.js
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..938ddfb
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,52 @@
+{
+ "env": {
+ "node": true
+ },
+ "extends": [
+ "plugin:vue/vue3-essential",
+ "plugin:@typescript-eslint/recommended",
+ "prettier/@typescript-eslint"
+ ],
+ "globals": {
+ "sleep": true,
+ "prettyFormat": true
+ },
+ "parserOptions": {
+ "ecmaVersion": 10,
+ "sourceType": "module",
+ "ecmaFeatures": {
+ "jsx": true
+ }
+ },
+ "parser": "@typescript-eslint/parser",
+ "plugins": ["@typescript-eslint", "prettier", "markdown"],
+ "rules": {
+ "@typescript-eslint/explicit-module-boundary-types": "off",
+ "@typescript-eslint/no-var-requires": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-unused-vars": "error",
+ "@typescript-eslint/ban-ts-comment": "off"
+ },
+ "overrides": [
+ {
+ "files": ["**/*.md"],
+ "processor": "markdown/markdown"
+ },
+ {
+ "files": ["**/*.md/*.{jsx,tsx}"],
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "error",
+ "no-unused-vars": "error",
+ "no-console": "off"
+ }
+ },
+ {
+ "files": ["**/*.md/*.{js,ts}"],
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "off",
+ "no-unused-vars": "off",
+ "no-console": "off"
+ }
+ }
+ ]
+}
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..bb9c570
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,46 @@
+# Contributing Guide
+
+Hi! I’m really excited that you are interested in contributing to Formily. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines.
+
+- [Issue Reporting Guidelines](#issue-reporting-guidelines)
+- [Pull Request Guidelines](#pull-request-guidelines)
+- [Git Commit Specific](./GIT_COMMIT_SPECIFIC.md)
+
+## Issue Reporting Guidelines
+
+- The issue list of this repo is **exclusively** for bug reports and feature requests. Non-conforming issues will be closed immediately.
+
+ - For simple beginner questions, you can get quick answers from
+
+ - For more complicated questions, you can use Google or StackOverflow. Make sure to provide enough information when asking your questions - this makes it easier for others to help you!
+
+- Try to search for your issue, it may have already been answered or even fixed in the development branch.
+
+- Check if the issue is reproducible with the latest stable version of Formily. If you are using a pre-release, please indicate the specific version you are using.
+
+- It is **required** that you clearly describe the steps necessary to reproduce the issue you are running into. Issues with no clear repro steps will not be triaged. If an issue labeled "need repro" receives no further input from the issue author for more than 5 days, it will be closed.
+
+- For bugs that involves build setups, you can create a reproduction repository with steps in the README.
+
+- If your issue is resolved but still open, don’t hesitate to close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it.
+
+## Pull Request Guidelines
+
+- Only code that's ready for release should be committed to the master branch. All development should be done in dedicated branches.
+- Checkout a **new** topic branch from master branch, and merge back against master branch.
+- Work in the `src` folder and **DO NOT** checkin `dist` in the commits.
+- Make sure `npm test` passes.
+- If adding new feature:
+ - Add accompanying test case.
+ - Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it.
+- If fixing a bug:
+ - If you are resolving a special issue, add `(fix #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update entities encoding/decoding (fix #3899)`.
+ - Provide detailed description of the bug in the PR. Live demo preferred.
+ - Add appropriate test coverage if applicable.
+
+## Git Commit Specific
+
+- Your commits message must follow our [git commit specific](./GIT_COMMIT_SPECIFIC.md).
+- We will check your commit message, if it does not conform to the specification, the commit will be automatically refused, make sure you have read the specification above.
+- You could use `git cz` with a CLI interface to replace `git commit` command, it will help you to build a proper commit-message, see [commitizen](https://github.com/commitizen/cz-cli).
+- It's OK to have multiple small commits as you work on your branch - we will let GitHub automatically squash it before merging.
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..efdf0be
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: formily # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..c5bf596
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,11 @@
+blank_issues_enabled: true
+contact_links:
+ - name: Create new issue
+ url: https://formilyjs.org/guide/issue-helper
+ about: The issue which is not created via https://formilyjs.org/guide/issue-helper will be closed immediately.
+ - name: ✨ Question Answer / Idea
+ url: https://github.com/alibaba/formily/discussions/new
+ about: All questions can be solved here. At the same time you can provide all your ideas here.
+ - name: 📖 View documentation
+ url: https://formilyjs.org
+ about: Official Formily documentation
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..e2c6f8b
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,14 @@
+_Before_ submitting a pull request, please make sure the following is done...
+
+- [ ] Ensure the pull request title and commit message follow the [Commit Specific](https://github.com/alibaba/formily/blob/formily_next/.github/GIT_COMMIT_SPECIFIC.md) in **English**.
+- [ ] Fork the repo and create your branch from `master` or `formily_next`.
+- [ ] If you've added code that should be tested, add tests!
+- [ ] If you've changed APIs, update the documentation.
+- [ ] Ensure the test suite passes (`npm test`).
+- [ ] Make sure your code lints (`npm run lint`) - we've done our best to make sure these rules match our internal linting guidelines.
+
+**Please do not delete the above content**
+
+---
+
+## What have you changed?
diff --git a/.github/workflows/check-pr-title.yml b/.github/workflows/check-pr-title.yml
new file mode 100644
index 0000000..ed36fef
--- /dev/null
+++ b/.github/workflows/check-pr-title.yml
@@ -0,0 +1,18 @@
+name: Check PR title
+on:
+ pull_request_target:
+ types:
+ - opened
+ - reopened
+ - edited
+ - synchronize
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: aslafy-z/conventional-pr-title-action@master
+ with:
+ preset: conventional-changelog-angular@^5.0.6
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..8ae4ead
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,47 @@
+name: Node CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ if: contains(github.event.head_commit.message, 'chore(versions)') == false
+ strategy:
+ matrix:
+ node_version: [10.x, 11.x]
+ os: [ubuntu-latest]
+ steps:
+ - uses: actions/checkout@v1
+ - name: Use Node.js ${{ matrix.node_version }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node_version }}
+
+ - run: yarn -v
+ - run: yarn --ignore-engines
+ - name: ESlint
+ uses: reviewdog/action-eslint@v1
+ with:
+ reporter: github-check
+ eslint_flags: '.'
+ - run: yarn build
+ - run: yarn test:prod
+ env:
+ CI: true
+ HEADLESS: false
+ PROGRESS: none
+ NODE_ENV: test
+ NODE_OPTIONS: --max_old_space_size=4096
+
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v1
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ fail_ci_if_error: true
+ verbose: true
diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml
new file mode 100644
index 0000000..be68c23
--- /dev/null
+++ b/.github/workflows/commitlint.yml
@@ -0,0 +1,29 @@
+# This is a basic workflow to help you get started with Actions
+
+name: Check Commit spec
+
+# Controls when the action will run.
+on:
+ # Triggers the workflow on push or pull request events but only for the formily_next branch
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ # This workflow contains a single job called "build"
+ commitlint:
+ # The type of runner that the job will run on
+ runs-on: ubuntu-latest
+
+ # Steps represent a sequence of tasks that will be executed as part of the job
+ steps:
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0=
+ - uses: wagoid/commitlint-github-action@v3
diff --git a/.github/workflows/issue-open-check.yml b/.github/workflows/issue-open-check.yml
new file mode 100644
index 0000000..09f45be
--- /dev/null
+++ b/.github/workflows/issue-open-check.yml
@@ -0,0 +1,25 @@
+name: Issue Open Check
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ check-issue:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions-cool/check-user-permission@v1.0.0
+ id: checkUser
+ with:
+ require: 'write'
+
+ - name: check invalid
+ if: (contains(github.event.issue.body, 'formily-issue-helper') == false) && (steps.checkUser.outputs.result == 'false')
+ uses: actions-cool/issues-helper@v1.2
+ with:
+ actions: 'create-comment,add-labels,close-issue'
+ issue-number: ${{ github.event.issue.number }}
+ labels: 'Invalid'
+ body: |
+ Hello @${{ github.event.issue.user.login }}, your issue has been closed because it does not conform to our issue requirements. Please use the [Issue Helper](https://formilyjs.org/guide/issue-helper) to create an issue, thank you!
+ 你好 @${{ github.event.issue.user.login }},为了能够进行高效沟通,我们对 issue 有一定的格式要求,你的 issue 因为不符合要求而被自动关闭。你可以通过 [issue 助手](https://formilyjs.org/guide/issue-helper) 来创建 issue 以方便我们定位错误。谢谢配合!
diff --git a/.github/workflows/package-size.yml b/.github/workflows/package-size.yml
new file mode 100644
index 0000000..a2624b7
--- /dev/null
+++ b/.github/workflows/package-size.yml
@@ -0,0 +1,13 @@
+name: Compressed Size
+
+on: [pull_request]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: preactjs/compressed-size-action@v2
+ with:
+ repo-token: '${{ secrets.GITHUB_TOKEN }}'
diff --git a/.github/workflows/pr-welcome.yml b/.github/workflows/pr-welcome.yml
new file mode 100644
index 0000000..acf1904
--- /dev/null
+++ b/.github/workflows/pr-welcome.yml
@@ -0,0 +1,13 @@
+name: PR Welcome
+
+on:
+ pull_request_target:
+ types: [opened]
+
+jobs:
+ welcome:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions-cool/pr-welcome@v1.1.2
+ with:
+ pr-emoji: '+1, heart'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..53296bf
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,41 @@
+name: Release
+
+on:
+ push:
+ branches:
+ - formily_next
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ if: contains(github.event.head_commit.message, 'chore(release)')
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 12
+ registry-url: https://registry.npmjs.org/
+ - run: |
+ yarn -v
+ yarn --ignore-engines
+ yarn build
+ yarn test:prod
+ env:
+ NODE_OPTIONS: --max_old_space_size=4096
+ REGISTRY: https://registry.npmjs.org
+ - uses: janrywang/github-tag-release@main
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ - run: yarn run release
+ env:
+ NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
+ ACCESS_KEY_SECRET: ${{ secrets.ACCESS_KEY_SECRET }}
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v1
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ fail_ci_if_error: true
+ verbose: true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..713019f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,29 @@
+*~
+*.swp
+.DS_Store
+.changelog
+.tea
+npm-debug.log
+lerna-debug.log
+npm-debug.log*
+package-lock.json
+lib/
+esm/
+temp_esm/
+dist/
+build/
+coverage/
+node_modules/
+examples/test
+.idea/
+TODO.md
+tsconfig.tsbuildinfo
+package/
+package.zip
+.umi
+.umi-production
+.cjsescache
+doc-site
+.lerna-changelog
+.history
+.lint-report.log
\ No newline at end of file
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..eccc3c2
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+ semi: false,
+ tabWidth: 2,
+ singleQuote: true,
+}
diff --git a/.vscode/cspell.json b/.vscode/cspell.json
new file mode 100644
index 0000000..77b49fd
--- /dev/null
+++ b/.vscode/cspell.json
@@ -0,0 +1,27 @@
+{
+ "version": "0.1",
+ "language": "en",
+ "ignoreWords": [
+ "autorun",
+ "mutators",
+ "Formily",
+ "formily",
+ "untrack",
+ "untracker",
+ "untracked",
+ "Untracking",
+ "Unmount",
+ "octokit",
+ "repos",
+ "alibaba",
+ "Lifecycles",
+ "element-ui",
+ "Element",
+ "alifd",
+ "Mixins",
+ "builtins",
+ "cascader",
+ "Cascader",
+ "middlewares"
+ ]
+}
diff --git a/.yarnrc b/.yarnrc
new file mode 100644
index 0000000..6c8b0a1
--- /dev/null
+++ b/.yarnrc
@@ -0,0 +1 @@
+registry "https://registry.yarnpkg.com"
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..9b141a5
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2015-present, muuyao Holding Limited. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5589d19
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+English | [简体中文](./README.zh-cn.md)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+## Overview
+
+The Awesome Components Library with Formily & Element UI.
+
+## Features
+
+- 🖼 Designable, You can quickly develop forms at low cost through [Form Builder](https://designable-antd.formilyjs.org/).
+- 🚀 High performance, fields managed independently, rather rerender the whole tree.
+- 💡 Integrated Alibaba Fusion and Ant Design components are guaranteed to work out of the box.
+- 🎨 JSON Schema applied for BackEnd. JSchema applied for FrontEnd. Two paradigms can be converted to each other.
+- 🏅 Side effects are managed independently, making form data linkages easier than ever before.
+- 🌯 Override most complicated form layout use cases.
+
+## Form Builder
+
+![https://designable-antd.formilyjs.org/](https://img.alicdn.com/imgextra/i3/O1CN01xAJj1y1wcGzXYc1Uq_!!6000000006328-2-tps-2980-1740.png)
+
+## WebSite
+
+https://element.formilyjs.org
+
+## Community
+
+- [formily](https://github.com/alibaba/formily)
+- [formilyjs](https://github.com/formilyjs)
+- [designable](https://github.com/alibaba/designable)
+
+## How to contribute?
+
+- [Contribute document](https://formilyjs.org/zh-CN/guide/contribution)
+
+## Contributors
+
+This project exists thanks to all the people who contribute.
+
+
+## LICENSE
+
+Formily is open source software licensed as
+[MIT](./LICENSE.md).
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 0000000..44ea601
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,58 @@
+[English](./README.md) | 简体中文
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+## 概要
+
+这是一个结合了 Formily & Element UI 的超酷组件库.
+
+## 特性
+
+- 🖼 可设计,借助 Form Builder 可以快速搭建表单
+- 🚀 高性能,字段分布式渲染,大大减轻 React 渲染压力
+- 💡 支持 Ant Design/Fusion Next 组件体系
+- 🎨 JSX 标签化写法/JSON Schema 数据驱动方案无缝迁移过渡
+- 🏅 副作用逻辑独立管理,涵盖各种复杂联动校验逻辑
+- 🌯 支持各种表单复杂布局方案
+
+## 表单设计器
+
+![https://designable-antd.formilyjs.org/](https://img.alicdn.com/imgextra/i3/O1CN01xAJj1y1wcGzXYc1Uq_!!6000000006328-2-tps-2980-1740.png)
+
+## 官网
+
+https://element.formilyjs.org
+
+## 生态产品
+
+- [formily](https://github.com/alibaba/formily)
+- [formilyjs](https://github.com/formilyjs)
+- [designable](https://github.com/alibaba/designable)
+
+## 如何贡献?
+
+- [Contribute document](https://formilyjs.org/zh-CN/guide/contribution)
+
+## 贡献者
+
+This project exists thanks to all the people who contribute.
+
+
+## LICENSE
+
+Formily is open source software licensed as
+[MIT](./LICENSE.md).
diff --git a/commitlint.config.js b/commitlint.config.js
new file mode 100644
index 0000000..4fedde6
--- /dev/null
+++ b/commitlint.config.js
@@ -0,0 +1 @@
+module.exports = { extends: ['@commitlint/config-conventional'] }
diff --git a/docs/.vuepress/components/createCodeSandBox.js b/docs/.vuepress/components/createCodeSandBox.js
new file mode 100644
index 0000000..d6ca26e
--- /dev/null
+++ b/docs/.vuepress/components/createCodeSandBox.js
@@ -0,0 +1,114 @@
+import { getParameters } from 'codesandbox/lib/api/define'
+
+const CodeSandBoxHTML = '
'
+const CodeSandBoxJS = `
+import Vue from 'vue'
+import App from './App.vue'
+import Element from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+
+Vue.config.productionTip = false
+Vue.use(Element, { size: 'small' });
+
+new Vue({
+ render: h => h(App),
+}).$mount('#app')`
+
+const createForm = ({ method, action, data }) => {
+ const form = document.createElement('form') // 构造 form
+ form.style.display = 'none' // 设置为不显示
+ form.target = '_blank' // 指向 iframe
+
+ // 构造 formdata
+ Object.keys(data).forEach((key) => {
+ const input = document.createElement('input') // 创建 input
+
+ input.name = key // 设置 name
+ input.value = data[key] // 设置 value
+
+ form.appendChild(input)
+ })
+
+ form.method = method // 设置方法
+ form.action = action // 设置地址
+
+ document.body.appendChild(form)
+
+ // 对该 form 执行提交
+ form.submit()
+
+ document.body.removeChild(form)
+}
+
+export function createCodeSandBox(codeStr) {
+ const parameters = getParameters({
+ files: {
+ 'sandbox.config.json': {
+ content: {
+ template: 'node',
+ infiniteLoopProtection: true,
+ hardReloadOnChange: false,
+ view: 'browser',
+ container: {
+ port: 8080,
+ node: '14',
+ },
+ },
+ },
+ 'package.json': {
+ content: {
+ scripts: {
+ serve: 'vue-cli-service serve',
+ build: 'vue-cli-service build',
+ lint: 'vue-cli-service lint',
+ },
+ dependencies: {
+ '@formily/core': 'latest',
+ '@formily/vue': 'latest',
+ '@formily/element': 'latest',
+ axios: '^0.21.1',
+ 'core-js': '^3.6.5',
+ 'element-ui': 'latest',
+ 'vue-demi': 'latest',
+ vue: '^2.6.11',
+ },
+ devDependencies: {
+ '@vue/cli-plugin-babel': '~4.5.0',
+ '@vue/cli-service': '~4.5.0',
+ '@vue/composition-api': 'latest',
+ 'vue-template-compiler': '^2.6.11',
+ sass: '^1.34.1',
+ 'sass-loader': '^8.0.2',
+ },
+ babel: {
+ presets: ['@vue/cli-plugin-babel/preset'],
+ },
+ vue: {
+ devServer: {
+ host: '0.0.0.0',
+ disableHostCheck: true, // 必须
+ },
+ },
+ },
+ },
+ 'src/App.vue': {
+ content: codeStr,
+ },
+ 'src/main.js': {
+ content: CodeSandBoxJS,
+ },
+ 'public/index.html': {
+ content: CodeSandBoxHTML,
+ },
+ },
+ })
+
+ createForm({
+ method: 'post',
+ action: 'https://codesandbox.io/api/v1/sandboxes/define',
+ data: {
+ parameters,
+ query: 'file=/src/App.vue',
+ },
+ })
+}
diff --git a/docs/.vuepress/components/dumi-previewer.vue b/docs/.vuepress/components/dumi-previewer.vue
new file mode 100644
index 0000000..43462eb
--- /dev/null
+++ b/docs/.vuepress/components/dumi-previewer.vue
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vuepress/components/highlight.js b/docs/.vuepress/components/highlight.js
new file mode 100644
index 0000000..d68dfa6
--- /dev/null
+++ b/docs/.vuepress/components/highlight.js
@@ -0,0 +1,53 @@
+const prism = require('prismjs')
+const escapeHtml = require('escape-html')
+const loadLanguages = require('prismjs/components/index')
+
+function wrap(code, lang) {
+ if (lang === 'text') {
+ code = escapeHtml(code)
+ }
+ return `${code}
`
+}
+
+function getLangCodeFromExtension(extension) {
+ const extensionMap = {
+ vue: 'markup',
+ html: 'markup',
+ md: 'markdown',
+ rb: 'ruby',
+ ts: 'typescript',
+ py: 'python',
+ sh: 'bash',
+ yml: 'yaml',
+ styl: 'stylus',
+ kt: 'kotlin',
+ rs: 'rust',
+ }
+
+ return extensionMap[extension] || extension
+}
+
+module.exports = (str, lang) => {
+ if (!lang) {
+ return wrap(str, 'text')
+ }
+ lang = lang.toLowerCase()
+ const rawLang = lang
+
+ lang = getLangCodeFromExtension(lang)
+
+ if (!prism.languages[lang]) {
+ try {
+ loadLanguages([lang])
+ } catch (e) {
+ console.warn(
+ `[vuepress] Syntax highlight for language "${lang}" is not supported.`
+ )
+ }
+ }
+ if (prism.languages[lang]) {
+ const code = prism.highlight(str, prism.languages[lang], lang)
+ return wrap(code, rawLang)
+ }
+ return wrap(str, 'text')
+}
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
new file mode 100644
index 0000000..7dff9e5
--- /dev/null
+++ b/docs/.vuepress/config.js
@@ -0,0 +1,94 @@
+const path = require('path')
+const utils = require('./util')
+
+const componentFiles = utils
+ .getFiles(path.resolve(__dirname, '../guide'))
+ .map((item) => item.replace(/(\.md)/g, ''))
+ .filter((item) => !['el-form', 'el-form-item', 'index'].includes(item))
+
+module.exports = {
+ title: 'Formily Element',
+ dest: './doc-site',
+ theme: '@vuepress-dumi/dumi',
+ head: [
+ [
+ 'link',
+ {
+ rel: 'icon',
+ href: '//img.alicdn.com/imgextra/i3/O1CN01XtT3Tv1Wd1b5hNVKy_!!6000000002810-55-tps-360-360.svg',
+ },
+ ],
+ [
+ 'link',
+ {
+ rel: 'stylesheet',
+ href: 'https://unpkg.com/element-ui/lib/theme-chalk/index.css',
+ },
+ ],
+ ],
+ themeConfig: {
+ logo: '//img.alicdn.com/imgextra/i2/O1CN01Kq3OHU1fph6LGqjIz_!!6000000004056-55-tps-1141-150.svg',
+ nav: [
+ {
+ text: '指南',
+ link: '/guide/',
+ },
+ {
+ text: '主站',
+ link: 'https://formilyjs.org',
+ },
+ {
+ text: 'GITHUB',
+ link: 'https://github.com/alibaba/formily',
+ },
+ ],
+ sidebar: {
+ '/guide/': ['', ...componentFiles],
+ },
+ lastUpdated: 'Last Updated',
+ smoothScroll: true,
+ },
+ plugins: [
+ 'vuepress-plugin-typescript',
+ '@vuepress/back-to-top',
+ '@vuepress/last-updated',
+ '@vuepress-dumi/dumi-previewer',
+ [
+ '@vuepress/medium-zoom',
+ {
+ selector: '.content__default :not(a) > img',
+ },
+ ],
+ ],
+ configureWebpack: (config, isServer) => {
+ return {
+ resolve: {
+ alias: {
+ '@formily/element': path.resolve(
+ __dirname,
+ '../../packages/components/src'
+ ),
+ },
+ },
+ }
+ },
+ chainWebpack: (config, isServer) => {
+ config.module
+ .rule('js') // Find the rule.
+ .use('babel-loader') // Find the loader
+ .tap((options) =>
+ Object.assign(options, {
+ // Modifying options
+ presets: [
+ [
+ '@vue/babel-preset-jsx',
+ {
+ vModel: false,
+ compositionAPI: true,
+ },
+ ],
+ ],
+ })
+ )
+ },
+}
diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js
new file mode 100644
index 0000000..f12a17a
--- /dev/null
+++ b/docs/.vuepress/enhanceApp.js
@@ -0,0 +1,10 @@
+import pageComponents from '@internal/page-components'
+import Element from 'element-ui'
+import '@formily/element/style.ts'
+
+export default ({ Vue }) => {
+ for (const [name, component] of Object.entries(pageComponents)) {
+ Vue.component(name, component)
+ }
+ Vue.use(Element, { size: 'small' })
+}
diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl
new file mode 100644
index 0000000..be1b5c0
--- /dev/null
+++ b/docs/.vuepress/styles/index.styl
@@ -0,0 +1,98 @@
+.navbar {
+ padding: 0 28px !important;
+}
+
+.navbar .logo {
+ height: auto !important;
+ width: 150px !important;
+}
+
+.navbar .site-name {
+ display: none;
+}
+
+.navbar .sidebar-button {
+ padding: 0;
+}
+
+.home .feature {
+ margin-bottom: 40px;
+ text-align: center;
+}
+
+.theme-dumi-content:not(.custom) {
+ max-width: 100%;
+}
+
+.page .page-nav {
+ max-width: 100%;
+}
+
+.dumi-previewer .dumi-previewer-actions .dumi-previewer-actions__icon {
+ padding: 0 !important;
+}
+
+.page .page-edit {
+ max-width 100%
+}
+
+.sidebar-group .sidebar-heading {
+ color: #454d64;
+ font-size: 16px;
+}
+
+.sidebar-group a.sidebar-link {
+ font-size: 0.9em;
+}
+
+.theme-dumi-content .custom-block.warning {
+ padding: 10px 20px;
+ border-color: #FFC11F;
+ box-shadow: 0 6px 16px -2px rgba(0,0,0,.06);
+ background: rgba(255,229,100,0.1);
+}
+.theme-dumi-content .custom-block.danger {
+ padding: 10px 20px;
+ p {
+ margin: 0;
+ }
+}
+
+.theme-dumi-content:not(.custom) > h1, .theme-dumi-content:not(.custom) > h2, .theme-dumi-content:not(.custom) > h3, .theme-dumi-content:not(.custom) > h4, .theme-dumi-content:not(.custom) > h5, .theme-dumi-content:not(.custom) > h6 {
+ margin-bottom: 18px;
+}
+
+.theme-dumi-content p {
+ margin: 1em 0;
+}
+
+.custom-block.warning p {
+ margin: 0;
+}
+
+// .theme-dumi-content div[class*="language-"] {
+// background-color: #f9fafb;
+// }
+
+// .theme-dumi-content pre[class*="language-"] code {
+// color: #000;
+// }
+
+.dumi-previewer .dumi-previewer-source,
+.dumi-previewer .dumi-previewer-demo {
+ overflow: auto;
+}
+
+@media (max-width: 719px) {
+ .sidebar-button + .home-link {
+ margin-left: 20px;
+ }
+}
+
+@media (max-width: 419px) {
+ .theme-dumi-content div[class*="language-"] {
+ margin: 0;
+ border-radius: 0;
+ }
+}
+
diff --git a/docs/.vuepress/util.js b/docs/.vuepress/util.js
new file mode 100644
index 0000000..f5a7a35
--- /dev/null
+++ b/docs/.vuepress/util.js
@@ -0,0 +1,7 @@
+const fs = require('fs')
+
+module.exports = {
+ getFiles(dir) {
+ return fs.readdirSync(dir)
+ },
+}
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..77e414e
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,28 @@
+---
+home: true
+heroText: Formily Element
+tagline: 基于 Element UI 封装的Formily2.x组件体系
+actionText: 开发指南
+actionLink: /guide/
+features:
+ - title: 更易用
+ details: 开箱即用,案例丰富
+ - title: 更高效
+ details: 傻瓜写法,超高性能
+ - title: 更专业
+ details: 完备,灵活,优雅
+footer: Open-source MIT Licensed | Copyright © 2019-present
+---
+
+## 安装
+
+vue2:
+
+```bash
+$ npm install --save element-ui
+$ npm install --save @formily/core @formily/vue @vue/composition-api @formily/element
+```
+
+## 快速开始
+
+
diff --git a/docs/demos/guide/array-cards/effects-json-schema.vue b/docs/demos/guide/array-cards/effects-json-schema.vue
new file mode 100644
index 0000000..c0b9309
--- /dev/null
+++ b/docs/demos/guide/array-cards/effects-json-schema.vue
@@ -0,0 +1,125 @@
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-cards/effects-markup-schema.vue b/docs/demos/guide/array-cards/effects-markup-schema.vue
new file mode 100644
index 0000000..937d9e3
--- /dev/null
+++ b/docs/demos/guide/array-cards/effects-markup-schema.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-cards/json-schema.vue b/docs/demos/guide/array-cards/json-schema.vue
new file mode 100644
index 0000000..884442f
--- /dev/null
+++ b/docs/demos/guide/array-cards/json-schema.vue
@@ -0,0 +1,146 @@
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-cards/markup-schema.vue b/docs/demos/guide/array-cards/markup-schema.vue
new file mode 100644
index 0000000..faaa65e
--- /dev/null
+++ b/docs/demos/guide/array-cards/markup-schema.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-collapse/effects-json-schema.vue b/docs/demos/guide/array-collapse/effects-json-schema.vue
new file mode 100644
index 0000000..25b1974
--- /dev/null
+++ b/docs/demos/guide/array-collapse/effects-json-schema.vue
@@ -0,0 +1,129 @@
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-collapse/effects-markup-schema.vue b/docs/demos/guide/array-collapse/effects-markup-schema.vue
new file mode 100644
index 0000000..ad07d38
--- /dev/null
+++ b/docs/demos/guide/array-collapse/effects-markup-schema.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-collapse/json-schema.vue b/docs/demos/guide/array-collapse/json-schema.vue
new file mode 100644
index 0000000..d08ab3c
--- /dev/null
+++ b/docs/demos/guide/array-collapse/json-schema.vue
@@ -0,0 +1,196 @@
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-collapse/markup-schema.vue b/docs/demos/guide/array-collapse/markup-schema.vue
new file mode 100644
index 0000000..bf35d8a
--- /dev/null
+++ b/docs/demos/guide/array-collapse/markup-schema.vue
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ form.setInitialValues({
+ array: Array.from({ length: 10 }).map(() => ({
+ input: 'default value',
+ })),
+ string_array: Array.from({ length: 10 }).map(
+ () => 'default value'
+ ),
+ string_array_unshift: Array.from({ length: 10 }).map(
+ () => 'default value'
+ ),
+ })
+ }
+ "
+ >
+ 加载默认数据
+
+ 提交
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-items/json-schema.vue b/docs/demos/guide/array-items/json-schema.vue
new file mode 100644
index 0000000..bb62c6a
--- /dev/null
+++ b/docs/demos/guide/array-items/json-schema.vue
@@ -0,0 +1,219 @@
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-items/markup-schema.vue b/docs/demos/guide/array-items/markup-schema.vue
new file mode 100644
index 0000000..ad69508
--- /dev/null
+++ b/docs/demos/guide/array-items/markup-schema.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-table/effects-json-schema.vue b/docs/demos/guide/array-table/effects-json-schema.vue
new file mode 100644
index 0000000..16a2625
--- /dev/null
+++ b/docs/demos/guide/array-table/effects-json-schema.vue
@@ -0,0 +1,170 @@
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/array-table/effects-markup-schema.vue b/docs/demos/guide/array-table/effects-markup-schema.vue
new file mode 100644
index 0000000..72a9b64
--- /dev/null
+++ b/docs/demos/guide/array-table/effects-markup-schema.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/array-table/json-schema.vue b/docs/demos/guide/array-table/json-schema.vue
new file mode 100644
index 0000000..cfdb6f5
--- /dev/null
+++ b/docs/demos/guide/array-table/json-schema.vue
@@ -0,0 +1,140 @@
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/array-table/markup-schema.vue b/docs/demos/guide/array-table/markup-schema.vue
new file mode 100644
index 0000000..9585fd6
--- /dev/null
+++ b/docs/demos/guide/array-table/markup-schema.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+ {
+ form.setInitialValues({
+ array: range(100000),
+ })
+ }
+ "
+ >
+ 加载10W条超大数据
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-tabs/json-schema.vue b/docs/demos/guide/array-tabs/json-schema.vue
new file mode 100644
index 0000000..f9d23e4
--- /dev/null
+++ b/docs/demos/guide/array-tabs/json-schema.vue
@@ -0,0 +1,99 @@
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/array-tabs/markup-schema.vue b/docs/demos/guide/array-tabs/markup-schema.vue
new file mode 100644
index 0000000..6924fd1
--- /dev/null
+++ b/docs/demos/guide/array-tabs/markup-schema.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/cascader/json-schema.vue b/docs/demos/guide/cascader/json-schema.vue
new file mode 100644
index 0000000..18ec71a
--- /dev/null
+++ b/docs/demos/guide/cascader/json-schema.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+l
diff --git a/docs/demos/guide/cascader/markup-schema.vue b/docs/demos/guide/cascader/markup-schema.vue
new file mode 100644
index 0000000..bcaa223
--- /dev/null
+++ b/docs/demos/guide/cascader/markup-schema.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+l
diff --git a/docs/demos/guide/cascader/template.vue b/docs/demos/guide/cascader/template.vue
new file mode 100644
index 0000000..4ac53d0
--- /dev/null
+++ b/docs/demos/guide/cascader/template.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
diff --git a/docs/demos/guide/checkbox/json-schema.vue b/docs/demos/guide/checkbox/json-schema.vue
new file mode 100644
index 0000000..c36bdb8
--- /dev/null
+++ b/docs/demos/guide/checkbox/json-schema.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+l
diff --git a/docs/demos/guide/checkbox/markup-schema.vue b/docs/demos/guide/checkbox/markup-schema.vue
new file mode 100644
index 0000000..7eb3e9a
--- /dev/null
+++ b/docs/demos/guide/checkbox/markup-schema.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+l
diff --git a/docs/demos/guide/checkbox/template.vue b/docs/demos/guide/checkbox/template.vue
new file mode 100644
index 0000000..ac657da
--- /dev/null
+++ b/docs/demos/guide/checkbox/template.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
diff --git a/docs/demos/guide/date-picker/json-schema.vue b/docs/demos/guide/date-picker/json-schema.vue
new file mode 100644
index 0000000..129bbfd
--- /dev/null
+++ b/docs/demos/guide/date-picker/json-schema.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
+l
diff --git a/docs/demos/guide/date-picker/markup-schema.vue b/docs/demos/guide/date-picker/markup-schema.vue
new file mode 100644
index 0000000..3df0aa6
--- /dev/null
+++ b/docs/demos/guide/date-picker/markup-schema.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+l
diff --git a/docs/demos/guide/date-picker/template.vue b/docs/demos/guide/date-picker/template.vue
new file mode 100644
index 0000000..fd8ea73
--- /dev/null
+++ b/docs/demos/guide/date-picker/template.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+l
diff --git a/docs/demos/guide/editable/json-schema.vue b/docs/demos/guide/editable/json-schema.vue
new file mode 100644
index 0000000..a4ce79d
--- /dev/null
+++ b/docs/demos/guide/editable/json-schema.vue
@@ -0,0 +1,115 @@
+
+
+
+
+ 提交
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/editable/markup-schema.vue b/docs/demos/guide/editable/markup-schema.vue
new file mode 100644
index 0000000..ccb01d4
--- /dev/null
+++ b/docs/demos/guide/editable/markup-schema.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/editable/template.vue b/docs/demos/guide/editable/template.vue
new file mode 100644
index 0000000..78a4d21
--- /dev/null
+++ b/docs/demos/guide/editable/template.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-button-group.vue b/docs/demos/guide/form-button-group.vue
new file mode 100644
index 0000000..5878c76
--- /dev/null
+++ b/docs/demos/guide/form-button-group.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+ 提交
+ 重置
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-collapse/json-schema.vue b/docs/demos/guide/form-collapse/json-schema.vue
new file mode 100644
index 0000000..c49463c
--- /dev/null
+++ b/docs/demos/guide/form-collapse/json-schema.vue
@@ -0,0 +1,148 @@
+
+
+
+
+
+ {
+ form.query('tab3').take((field) => {
+ field.visible = !field.visible
+ })
+ }
+ "
+ >
+ 显示/隐藏最后一个Tab
+
+ {
+ formCollapse.toggleActiveKey('tab2')
+ }
+ "
+ >
+ 切换第二个Tab
+
+ 提交
+
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-collapse/markup-schema.vue b/docs/demos/guide/form-collapse/markup-schema.vue
new file mode 100644
index 0000000..5454a3b
--- /dev/null
+++ b/docs/demos/guide/form-collapse/markup-schema.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-dialog/json-schema.vue b/docs/demos/guide/form-dialog/json-schema.vue
new file mode 100644
index 0000000..202fa5d
--- /dev/null
+++ b/docs/demos/guide/form-dialog/json-schema.vue
@@ -0,0 +1,103 @@
+
+ 点击打开表单
+
+
+
diff --git a/docs/demos/guide/form-dialog/markup-schema.vue b/docs/demos/guide/form-dialog/markup-schema.vue
new file mode 100644
index 0000000..a06c39e
--- /dev/null
+++ b/docs/demos/guide/form-dialog/markup-schema.vue
@@ -0,0 +1,106 @@
+
+
+ 点击打开表单
+
+
+
+
diff --git a/docs/demos/guide/form-dialog/template.vue b/docs/demos/guide/form-dialog/template.vue
new file mode 100644
index 0000000..ae35866
--- /dev/null
+++ b/docs/demos/guide/form-dialog/template.vue
@@ -0,0 +1,79 @@
+
+ 点击打开表单
+
+
+
diff --git a/docs/demos/guide/form-drawer/json-schema.vue b/docs/demos/guide/form-drawer/json-schema.vue
new file mode 100644
index 0000000..1bee087
--- /dev/null
+++ b/docs/demos/guide/form-drawer/json-schema.vue
@@ -0,0 +1,90 @@
+
+ 点击打开表单
+
+
+
diff --git a/docs/demos/guide/form-drawer/markup-schema.vue b/docs/demos/guide/form-drawer/markup-schema.vue
new file mode 100644
index 0000000..69e271d
--- /dev/null
+++ b/docs/demos/guide/form-drawer/markup-schema.vue
@@ -0,0 +1,92 @@
+
+ 点击打开表单
+
+
+
diff --git a/docs/demos/guide/form-drawer/template.vue b/docs/demos/guide/form-drawer/template.vue
new file mode 100644
index 0000000..85bc219
--- /dev/null
+++ b/docs/demos/guide/form-drawer/template.vue
@@ -0,0 +1,66 @@
+
+ 点击打开表单
+
+
+
diff --git a/docs/demos/guide/form-grid/form.vue b/docs/demos/guide/form-grid/form.vue
new file mode 100644
index 0000000..298a820
--- /dev/null
+++ b/docs/demos/guide/form-grid/form.vue
@@ -0,0 +1,223 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-grid/json-schema.vue b/docs/demos/guide/form-grid/json-schema.vue
new file mode 100644
index 0000000..463a77a
--- /dev/null
+++ b/docs/demos/guide/form-grid/json-schema.vue
@@ -0,0 +1,94 @@
+
+
+
+ 提交
+
+
+
+
+l
diff --git a/docs/demos/guide/form-grid/markup-schema.vue b/docs/demos/guide/form-grid/markup-schema.vue
new file mode 100644
index 0000000..47d8e4c
--- /dev/null
+++ b/docs/demos/guide/form-grid/markup-schema.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/form-grid/native.vue b/docs/demos/guide/form-grid/native.vue
new file mode 100644
index 0000000..6f74717
--- /dev/null
+++ b/docs/demos/guide/form-grid/native.vue
@@ -0,0 +1,170 @@
+
+
+
maxColumns 3 + minColumns 2
+
+
+ 1 |
+
+
+ 2 |
+
+
+ 3 |
+
+
+ 4 |
+
+
+ 5 |
+
+
+ 6 |
+
+
+
maxColumns 3
+
+
+ 1 |
+
+
+ 2 |
+
+
+ 3 |
+
+
+ 4 |
+
+
+ 5 |
+
+
+ 6 |
+
+
+
minColumns 2
+
+
+ 1 |
+
+
+ 2 |
+
+
+ 3 |
+
+
+ 4 |
+
+
+ 5 |
+
+
+ 6 |
+
+
+
Null
+
+
+ 1 |
+
+
+ 2 |
+
+
+ 3 |
+
+
+ 4 |
+
+
+ 5 |
+
+
+ 6 |
+
+
+
minWidth 150 +maxColumns 3
+
+
+ 1 |
+
+
+ 2 |
+
+
+ 3 |
+
+
+ 4 |
+
+
+ 5 |
+
+
+ 6 |
+
+
+
maxWidth 120+minColumns 2
+
+
+ 1 |
+
+
+ 2 |
+
+
+ 3 |
+
+
+ 4 |
+
+
+ 5 |
+
+
+ 6 |
+
+
+
maxWidth 120 + gridSpan -1
+
+
+ 1 |
+
+
+ 2 |
+
+
+ 3 |
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-item/bordered-none.vue b/docs/demos/guide/form-item/bordered-none.vue
new file mode 100644
index 0000000..344f9c1
--- /dev/null
+++ b/docs/demos/guide/form-item/bordered-none.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
diff --git a/docs/demos/guide/form-item/common.vue b/docs/demos/guide/form-item/common.vue
new file mode 100644
index 0000000..b857e7a
--- /dev/null
+++ b/docs/demos/guide/form-item/common.vue
@@ -0,0 +1,449 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-item/feedback.vue b/docs/demos/guide/form-item/feedback.vue
new file mode 100644
index 0000000..653a6fb
--- /dev/null
+++ b/docs/demos/guide/form-item/feedback.vue
@@ -0,0 +1,258 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-item/inset.vue b/docs/demos/guide/form-item/inset.vue
new file mode 100644
index 0000000..ec4024a
--- /dev/null
+++ b/docs/demos/guide/form-item/inset.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
diff --git a/docs/demos/guide/form-item/json-schema.vue b/docs/demos/guide/form-item/json-schema.vue
new file mode 100644
index 0000000..c68efcd
--- /dev/null
+++ b/docs/demos/guide/form-item/json-schema.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
diff --git a/docs/demos/guide/form-item/markup-schema.vue b/docs/demos/guide/form-item/markup-schema.vue
new file mode 100644
index 0000000..c3c03b3
--- /dev/null
+++ b/docs/demos/guide/form-item/markup-schema.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
diff --git a/docs/demos/guide/form-item/size.vue b/docs/demos/guide/form-item/size.vue
new file mode 100644
index 0000000..485ec8b
--- /dev/null
+++ b/docs/demos/guide/form-item/size.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
diff --git a/docs/demos/guide/form-item/template.vue b/docs/demos/guide/form-item/template.vue
new file mode 100644
index 0000000..30fdfc2
--- /dev/null
+++ b/docs/demos/guide/form-item/template.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
diff --git a/docs/demos/guide/form-layout/json-schema.vue b/docs/demos/guide/form-layout/json-schema.vue
new file mode 100644
index 0000000..ecf1f4b
--- /dev/null
+++ b/docs/demos/guide/form-layout/json-schema.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-layout/markup-schema.vue b/docs/demos/guide/form-layout/markup-schema.vue
new file mode 100644
index 0000000..2acce23
--- /dev/null
+++ b/docs/demos/guide/form-layout/markup-schema.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-layout/template.vue b/docs/demos/guide/form-layout/template.vue
new file mode 100644
index 0000000..8996fc7
--- /dev/null
+++ b/docs/demos/guide/form-layout/template.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-step/json-schema.vue b/docs/demos/guide/form-step/json-schema.vue
new file mode 100644
index 0000000..8d4710d
--- /dev/null
+++ b/docs/demos/guide/form-step/json-schema.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+ {
+ formStep.back()
+ }
+ "
+ >
+ 上一步
+
+ {
+ formStep.next()
+ }
+ "
+ >
+ 下一步
+
+ 提交
+
+
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-step/markup-schema.vue b/docs/demos/guide/form-step/markup-schema.vue
new file mode 100644
index 0000000..04fa7af
--- /dev/null
+++ b/docs/demos/guide/form-step/markup-schema.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ formStep.back()
+ }
+ "
+ >
+ 上一步
+
+ {
+ formStep.next()
+ }
+ "
+ >
+ 下一步
+
+ 提交
+
+
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-tab/json-schema.vue b/docs/demos/guide/form-tab/json-schema.vue
new file mode 100644
index 0000000..b152644
--- /dev/null
+++ b/docs/demos/guide/form-tab/json-schema.vue
@@ -0,0 +1,140 @@
+
+
+
+
+ {
+ form.query('tab3').take((field) => {
+ field.visible = !field.visible
+ })
+ }
+ "
+ >
+ 显示/隐藏最后一个Tab
+
+ {
+ formTab.setActiveKey('tab2')
+ }
+ "
+ >
+ 切换第二个Tab
+
+ 提交
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form-tab/markup-schema.vue b/docs/demos/guide/form-tab/markup-schema.vue
new file mode 100644
index 0000000..f299723
--- /dev/null
+++ b/docs/demos/guide/form-tab/markup-schema.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ form.query('tab3').take((field) => {
+ field.visible = !field.visible
+ })
+ }
+ "
+ >
+ 显示/隐藏最后一个Tab
+
+ {
+ formTab.setActiveKey('tab2')
+ }
+ "
+ >
+ 切换第二个Tab
+
+ 提交
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/form.vue b/docs/demos/guide/form.vue
new file mode 100644
index 0000000..65c48aa
--- /dev/null
+++ b/docs/demos/guide/form.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
diff --git a/docs/demos/guide/input-number/json-schema.vue b/docs/demos/guide/input-number/json-schema.vue
new file mode 100644
index 0000000..3e6ebd2
--- /dev/null
+++ b/docs/demos/guide/input-number/json-schema.vue
@@ -0,0 +1,52 @@
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/input-number/markup-schema.vue b/docs/demos/guide/input-number/markup-schema.vue
new file mode 100644
index 0000000..1bcb513
--- /dev/null
+++ b/docs/demos/guide/input-number/markup-schema.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/input-number/template.vue b/docs/demos/guide/input-number/template.vue
new file mode 100644
index 0000000..5601f26
--- /dev/null
+++ b/docs/demos/guide/input-number/template.vue
@@ -0,0 +1,42 @@
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/input/json-schema.vue b/docs/demos/guide/input/json-schema.vue
new file mode 100644
index 0000000..04fceab
--- /dev/null
+++ b/docs/demos/guide/input/json-schema.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
diff --git a/docs/demos/guide/input/markup-schema.vue b/docs/demos/guide/input/markup-schema.vue
new file mode 100644
index 0000000..d418237
--- /dev/null
+++ b/docs/demos/guide/input/markup-schema.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/input/template.vue b/docs/demos/guide/input/template.vue
new file mode 100644
index 0000000..e7f88aa
--- /dev/null
+++ b/docs/demos/guide/input/template.vue
@@ -0,0 +1,41 @@
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/password/json-schema.vue b/docs/demos/guide/password/json-schema.vue
new file mode 100644
index 0000000..41c8ed8
--- /dev/null
+++ b/docs/demos/guide/password/json-schema.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
diff --git a/docs/demos/guide/password/markup-schema.vue b/docs/demos/guide/password/markup-schema.vue
new file mode 100644
index 0000000..d2f3706
--- /dev/null
+++ b/docs/demos/guide/password/markup-schema.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/password/template.vue b/docs/demos/guide/password/template.vue
new file mode 100644
index 0000000..64a163f
--- /dev/null
+++ b/docs/demos/guide/password/template.vue
@@ -0,0 +1,35 @@
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/preview-text/base.vue b/docs/demos/guide/preview-text/base.vue
new file mode 100644
index 0000000..9db1365
--- /dev/null
+++ b/docs/demos/guide/preview-text/base.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/demos/guide/preview-text/extend.vue b/docs/demos/guide/preview-text/extend.vue
new file mode 100644
index 0000000..0aaee9a
--- /dev/null
+++ b/docs/demos/guide/preview-text/extend.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
diff --git a/docs/demos/guide/radio/json-schema.vue b/docs/demos/guide/radio/json-schema.vue
new file mode 100644
index 0000000..107f21c
--- /dev/null
+++ b/docs/demos/guide/radio/json-schema.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
diff --git a/docs/demos/guide/radio/markup-schema.vue b/docs/demos/guide/radio/markup-schema.vue
new file mode 100644
index 0000000..55f1aee
--- /dev/null
+++ b/docs/demos/guide/radio/markup-schema.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/radio/template.vue b/docs/demos/guide/radio/template.vue
new file mode 100644
index 0000000..ad4aaf8
--- /dev/null
+++ b/docs/demos/guide/radio/template.vue
@@ -0,0 +1,45 @@
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/reset/base.vue b/docs/demos/guide/reset/base.vue
new file mode 100644
index 0000000..c6042ad
--- /dev/null
+++ b/docs/demos/guide/reset/base.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+ 重置
+
+
+
+
+
diff --git a/docs/demos/guide/reset/force.vue b/docs/demos/guide/reset/force.vue
new file mode 100644
index 0000000..ccdf63f
--- /dev/null
+++ b/docs/demos/guide/reset/force.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+ 重置
+
+
+
+
+
diff --git a/docs/demos/guide/reset/validate.vue b/docs/demos/guide/reset/validate.vue
new file mode 100644
index 0000000..39e9ce0
--- /dev/null
+++ b/docs/demos/guide/reset/validate.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+ 重置
+
+
+
+
+
diff --git a/docs/demos/guide/select/json-schema-async.vue b/docs/demos/guide/select/json-schema-async.vue
new file mode 100644
index 0000000..34f6aab
--- /dev/null
+++ b/docs/demos/guide/select/json-schema-async.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
diff --git a/docs/demos/guide/select/json-schema-sync.vue b/docs/demos/guide/select/json-schema-sync.vue
new file mode 100644
index 0000000..bdfd5a7
--- /dev/null
+++ b/docs/demos/guide/select/json-schema-sync.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
diff --git a/docs/demos/guide/select/markup-schema-async-search.vue b/docs/demos/guide/select/markup-schema-async-search.vue
new file mode 100644
index 0000000..97fbb11
--- /dev/null
+++ b/docs/demos/guide/select/markup-schema-async-search.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/select/markup-schema-async.vue b/docs/demos/guide/select/markup-schema-async.vue
new file mode 100644
index 0000000..77dd68c
--- /dev/null
+++ b/docs/demos/guide/select/markup-schema-async.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
diff --git a/docs/demos/guide/select/markup-schema-sync.vue b/docs/demos/guide/select/markup-schema-sync.vue
new file mode 100644
index 0000000..0a09193
--- /dev/null
+++ b/docs/demos/guide/select/markup-schema-sync.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/select/template-async.vue b/docs/demos/guide/select/template-async.vue
new file mode 100644
index 0000000..102afb5
--- /dev/null
+++ b/docs/demos/guide/select/template-async.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
diff --git a/docs/demos/guide/select/template-sync.vue b/docs/demos/guide/select/template-sync.vue
new file mode 100644
index 0000000..8c99bbc
--- /dev/null
+++ b/docs/demos/guide/select/template-sync.vue
@@ -0,0 +1,52 @@
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/space/json-schema.vue b/docs/demos/guide/space/json-schema.vue
new file mode 100644
index 0000000..d1dba31
--- /dev/null
+++ b/docs/demos/guide/space/json-schema.vue
@@ -0,0 +1,137 @@
+
+
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/space/markup-schema.vue b/docs/demos/guide/space/markup-schema.vue
new file mode 100644
index 0000000..3de83cb
--- /dev/null
+++ b/docs/demos/guide/space/markup-schema.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/space/template.vue b/docs/demos/guide/space/template.vue
new file mode 100644
index 0000000..9b12581
--- /dev/null
+++ b/docs/demos/guide/space/template.vue
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
diff --git a/docs/demos/guide/submit/base.vue b/docs/demos/guide/submit/base.vue
new file mode 100644
index 0000000..f91bbd8
--- /dev/null
+++ b/docs/demos/guide/submit/base.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
diff --git a/docs/demos/guide/submit/loading.vue b/docs/demos/guide/submit/loading.vue
new file mode 100644
index 0000000..2d5fb2c
--- /dev/null
+++ b/docs/demos/guide/submit/loading.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
diff --git a/docs/demos/guide/switch/json-schema.vue b/docs/demos/guide/switch/json-schema.vue
new file mode 100644
index 0000000..608e3c4
--- /dev/null
+++ b/docs/demos/guide/switch/json-schema.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
diff --git a/docs/demos/guide/switch/markup-schema.vue b/docs/demos/guide/switch/markup-schema.vue
new file mode 100644
index 0000000..02f533a
--- /dev/null
+++ b/docs/demos/guide/switch/markup-schema.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/switch/template.vue b/docs/demos/guide/switch/template.vue
new file mode 100644
index 0000000..f8786c5
--- /dev/null
+++ b/docs/demos/guide/switch/template.vue
@@ -0,0 +1,35 @@
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/time-picker/json-schema.vue b/docs/demos/guide/time-picker/json-schema.vue
new file mode 100644
index 0000000..48435e5
--- /dev/null
+++ b/docs/demos/guide/time-picker/json-schema.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+l
diff --git a/docs/demos/guide/time-picker/markup-schema.vue b/docs/demos/guide/time-picker/markup-schema.vue
new file mode 100644
index 0000000..433a0de
--- /dev/null
+++ b/docs/demos/guide/time-picker/markup-schema.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/time-picker/template.vue b/docs/demos/guide/time-picker/template.vue
new file mode 100644
index 0000000..723b5eb
--- /dev/null
+++ b/docs/demos/guide/time-picker/template.vue
@@ -0,0 +1,57 @@
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/transfer/json-schema.vue b/docs/demos/guide/transfer/json-schema.vue
new file mode 100644
index 0000000..b9a0de3
--- /dev/null
+++ b/docs/demos/guide/transfer/json-schema.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
diff --git a/docs/demos/guide/transfer/markup-schema.vue b/docs/demos/guide/transfer/markup-schema.vue
new file mode 100644
index 0000000..9530770
--- /dev/null
+++ b/docs/demos/guide/transfer/markup-schema.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/transfer/template.vue b/docs/demos/guide/transfer/template.vue
new file mode 100644
index 0000000..1b63e44
--- /dev/null
+++ b/docs/demos/guide/transfer/template.vue
@@ -0,0 +1,45 @@
+
+
+
+ 提交
+
+
+
+
diff --git a/docs/demos/guide/upload/json-schema.vue b/docs/demos/guide/upload/json-schema.vue
new file mode 100644
index 0000000..52ea574
--- /dev/null
+++ b/docs/demos/guide/upload/json-schema.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
diff --git a/docs/demos/guide/upload/markup-schema.vue b/docs/demos/guide/upload/markup-schema.vue
new file mode 100644
index 0000000..1c108dd
--- /dev/null
+++ b/docs/demos/guide/upload/markup-schema.vue
@@ -0,0 +1,97 @@
+
+
+
+
+
diff --git a/docs/demos/guide/upload/template.vue b/docs/demos/guide/upload/template.vue
new file mode 100644
index 0000000..b8b6b02
--- /dev/null
+++ b/docs/demos/guide/upload/template.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
diff --git a/docs/demos/index.vue b/docs/demos/index.vue
new file mode 100644
index 0000000..5dd067d
--- /dev/null
+++ b/docs/demos/index.vue
@@ -0,0 +1,63 @@
+
+
+
+
+ ×
+
+
+
+
+ = {{ `${form.values.price * form.values.count} 元` }}
+
+
+
+
+
+
+
diff --git a/docs/guide/array-cards.md b/docs/guide/array-cards.md
new file mode 100644
index 0000000..fde314b
--- /dev/null
+++ b/docs/guide/array-cards.md
@@ -0,0 +1,95 @@
+# ArrayCards
+
+> 卡片列表,对于每行字段数量较多,联动较多的场景比较适合使用 ArrayCards
+>
+> 注意:该组件只适用于 Schema 场景
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Effects 联动案例
+
+
+
+## JSON Schema 联动案例
+
+
+
+## API
+
+### ArrayCards
+
+> 表格组件
+
+参考 [https://element.eleme.io/#/zh-CN/component/card](https://element.eleme.io/#/zh-CN/component/card)
+
+### ArrayCards.Addition
+
+> 添加按钮
+
+扩展属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------------ | ------- | ---------- | -------- | -------- |
+| title | string | 文案 | |
+| method | `'push' | 'unshift'` | 添加方式 | `'push'` |
+| defaultValue | any | 默认值 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayCards.Remove
+
+> 删除按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayCards.MoveDown
+
+> 下移按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayCards.MoveUp
+
+> 上移按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayCards.Index
+
+> 索引渲染器
+
+无属性
+
+### ArrayCards.useIndex
+
+> 读取当前渲染行索引的 Hook
+
+### ArrayCards.useRecord
+
+> 读取当前渲染记录的 Hook
diff --git a/docs/guide/array-collapse.md b/docs/guide/array-collapse.md
new file mode 100644
index 0000000..63e48bb
--- /dev/null
+++ b/docs/guide/array-collapse.md
@@ -0,0 +1,97 @@
+# ArrayCollapse
+
+> 折叠面板,对于每行字段数量较多,联动较多的场景比较适合使用 ArrayCollapse
+>
+> 注意:该组件只适用于 Schema 场景
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Effects 联动案例
+
+
+
+## JSON Schema 联动案例
+
+
+
+## API
+
+### ArrayCollapse
+
+参考 [https://element.eleme.io/#/zh-CN/component/collapse](https://element.eleme.io/#/zh-CN/component/collapse)
+
+### ArrayCollapse.Item
+
+参考 [https://element.eleme.io/#/zh-CN/component/collapse](https://element.eleme.io/#/zh-CN/component/collapse)
+
+### ArrayCollapse.Addition
+
+> 添加按钮
+
+扩展属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------------ | ------- | ---------- | -------- | -------- |
+| title | string | 文案 | |
+| method | `'push' | 'unshift'` | 添加方式 | `'push'` |
+| defaultValue | any | 默认值 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayCollapse.Remove
+
+> 删除按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayCollapse.MoveDown
+
+> 下移按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayCollapse.MoveUp
+
+> 上移按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayCollapse.Index
+
+> 索引渲染器
+
+无属性
+
+### ArrayCollapse.useIndex
+
+> 读取当前渲染行索引的 Hook
+
+### ArrayCollapse.useRecord
+
+> 读取当前渲染记录的 Hook
diff --git a/docs/guide/array-items.md b/docs/guide/array-items.md
new file mode 100644
index 0000000..76ff46c
--- /dev/null
+++ b/docs/guide/array-items.md
@@ -0,0 +1,97 @@
+# ArrayItems
+
+> 自增列表,对于简单的自增编辑场景比较适合,或者对于空间要求高的场景比较适合
+>
+> 注意:该组件只适用于 Schema 场景
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## API
+
+### ArrayItems
+
+继承 HTMLDivElement Props
+
+### ArrayItems.Item
+
+> 列表区块
+
+继承 HTMLDivElement Props
+
+### ArrayItems.SortHandle
+
+> 拖拽手柄
+
+参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+### ArrayItems.Addition
+
+> 添加按钮
+
+扩展属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------------ | ------- | ---------- | -------- | -------- |
+| title | string | 文案 | |
+| method | `'push' | 'unshift'` | 添加方式 | `'push'` |
+| defaultValue | any | 默认值 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayItems.Remove
+
+> 删除按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayItems.MoveDown
+
+> 下移按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayItems.MoveUp
+
+> 上移按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayItems.Index
+
+> 索引渲染器
+
+无属性
+
+### ArrayItems.useIndex
+
+> 读取当前渲染行索引的 Hook
+
+### ArrayItems.useRecord
+
+> 读取当前渲染记录的 Hook
diff --git a/docs/guide/array-table.md b/docs/guide/array-table.md
new file mode 100644
index 0000000..ab137ab
--- /dev/null
+++ b/docs/guide/array-table.md
@@ -0,0 +1,108 @@
+# ArrayTable
+
+> 自增表格,对于数据量超大的场景比较适合使用该组件,虽然数据量大到一定程度会有些许卡顿,但是不会影响基本操作
+>
+> 注意:该组件只适用于 Schema 场景,且只能是对象数组
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Effects 联动案例
+
+
+
+## JSON Schema 联动案例
+
+
+
+## API
+
+### ArrayTable
+
+> 表格组件
+
+参考 [https://element.eleme.io/#/zh-CN/component/table](https://element.eleme.io/#/zh-CN/component/table)
+
+### ArrayTable.Column
+
+> 表格列
+
+参考 [https://element.eleme.io/#/zh-CN/component/table](https://element.eleme.io/#/zh-CN/component/table)
+
+扩展属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| -------- | ------- | -------- | ------ |
+| asterisk | boolean | 星号显示 | true |
+
+> ArrayTableColumn 会自动检查内部的 FormItem 是否必填,并自动在表头加上红色星号。如果不希望显示,可通过 `asterisk` 属性进行覆盖。
+
+### ArrayTable.Addition
+
+> 添加按钮
+
+扩展属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------- | ---------- | -------- | -------- |
+| title | string | 文案 | |
+| method | `'push' | 'unshift'` | 添加方式 | `'push'` |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayTable.Remove
+
+> 删除按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayTable.MoveDown
+
+> 下移按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayTable.MoveUp
+
+> 上移按钮
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---- | ------ |
+| title | string | 文案 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button)
+
+注意:title 属性可以接收 Field 模型中的 title 映射,也就是在 Field 上传 title 也是生效的
+
+### ArrayTable.Index
+
+> 索引渲染器
+
+无属性
+
+### ArrayTable.useIndex
+
+> 读取当前渲染行索引的 Hook
+
+### ArrayTable.useRecord
+
+> 读取当前渲染记录的 Hook
diff --git a/docs/guide/array-tabs.md b/docs/guide/array-tabs.md
new file mode 100644
index 0000000..383c84e
--- /dev/null
+++ b/docs/guide/array-tabs.md
@@ -0,0 +1,19 @@
+# ArrayTabs
+
+> 自增选项卡,对于纵向空间要求较高的场景可以考虑使用该组件
+>
+> 注意:该组件只适用于 Schema 场景
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## API
+
+### ArrayTabs
+
+参考 [https://element.eleme.io/#/zh-CN/component/tab](https://element.eleme.io/#/zh-CN/component/tab)
diff --git a/docs/guide/cascader.md b/docs/guide/cascader.md
new file mode 100644
index 0000000..b30f515
--- /dev/null
+++ b/docs/guide/cascader.md
@@ -0,0 +1,19 @@
+# Cascader
+
+> 级联选择器
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/cascader](https://element.eleme.io/#/zh-CN/component/cascader)
diff --git a/docs/guide/checkbox.md b/docs/guide/checkbox.md
new file mode 100644
index 0000000..a6a36d5
--- /dev/null
+++ b/docs/guide/checkbox.md
@@ -0,0 +1,26 @@
+# Checkbox
+
+> 复选框
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/checkbox](https://element.eleme.io/#/zh-CN/component/checkbox)
+
+### 扩展属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ---------- | ------------------------------------------------------------------------------------------ | -------- | ------- |
+| options | [CheckboxProps](https://element.eleme.io/#/zh-CN/component/checkbox#checkbox-attributes)[] | 选项 | [] |
+| optionType | default/button | 样式类型 | default |
diff --git a/docs/guide/date-picker.md b/docs/guide/date-picker.md
new file mode 100644
index 0000000..c7ad6bd
--- /dev/null
+++ b/docs/guide/date-picker.md
@@ -0,0 +1,19 @@
+# DatePicker
+
+> 日期选择器
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/date-picker](https://element.eleme.io/#/zh-CN/component/date-picker)
diff --git a/docs/guide/editable.md b/docs/guide/editable.md
new file mode 100644
index 0000000..bc2926d
--- /dev/null
+++ b/docs/guide/editable.md
@@ -0,0 +1,31 @@
+# Editable
+
+> 局部编辑器,对于一些空间要求较高的表单区域可以使用该组件
+>
+> Editable 组件相当于是 FormItem 组件的变体,所以通常放在 decorator 中
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+### Editable
+
+> 内联编辑
+
+参考 [https://element.formilyjs.org/guide/form-item.html#api](https://element.formilyjs.org/guide/form-item.html#api)
+
+### Editable.Popover
+
+> 浮层编辑
+
+参考 [https://element.eleme.io/#/zh-CN/component/popover](https://element.eleme.io/#/zh-CN/component/popover)
diff --git a/docs/guide/form-button-group.md b/docs/guide/form-button-group.md
new file mode 100644
index 0000000..bd8648c
--- /dev/null
+++ b/docs/guide/form-button-group.md
@@ -0,0 +1,15 @@
+# FormButtonGroup
+
+> 表单按钮组布局组件
+
+## 使用案例
+
+
+
+## API
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------------- | ------- | ------------- | -------- | -------- | -------- |
+| gutter | number | 间隙大小 | 8px |
+| align | `'left' | 'center' | 'right'` | 对齐方式 | `'left'` |
+| alignFormItem | boolean | 对齐 FormItem | `false` |
diff --git a/docs/guide/form-collapse.md b/docs/guide/form-collapse.md
new file mode 100644
index 0000000..e4e837f
--- /dev/null
+++ b/docs/guide/form-collapse.md
@@ -0,0 +1,53 @@
+# FormCollapse
+
+> 折叠面板,通常用在布局空间要求较高的表单场景
+>
+> 注意:只能用在 Schema 场景
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## API
+
+### FormCollapse
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------------ | ------------- | ---------------------------------------------------------- | ------ |
+| formCollapse | IFormCollapse | 传入通过 createFormCollapse/useFormCollapse 创建出来的模型 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/collapse](https://element.eleme.io/#/zh-CN/component/collapse)
+
+### FormCollapse.Item
+
+参考 [https://element.eleme.io/#/zh-CN/component/collapse](https://element.eleme.io/#/zh-CN/component/collapse)
+
+### FormCollapse.createFormCollapse
+
+```ts pure
+type ActiveKey = string | number
+type ActiveKeys = string | number | Array
+
+interface createFormCollapse {
+ (defaultActiveKeys?: ActiveKeys): IFormCollpase
+}
+
+interface IFormCollapse {
+ //激活主键列表
+ activeKeys: ActiveKeys
+ //是否存在该激活主键
+ hasActiveKey(key: ActiveKey): boolean
+ //设置激活主键列表
+ setActiveKeys(keys: ActiveKeys): void
+ //添加激活主键
+ addActiveKey(key: ActiveKey): void
+ //删除激活主键
+ removeActiveKey(key: ActiveKey): void
+ //开关切换激活主键
+ toggleActiveKey(key: ActiveKey): void
+}
+```
diff --git a/docs/guide/form-dialog.md b/docs/guide/form-dialog.md
new file mode 100644
index 0000000..746dfa9
--- /dev/null
+++ b/docs/guide/form-dialog.md
@@ -0,0 +1,77 @@
+# FormDialog
+
+> 弹窗表单,主要用在简单的事件打开表单场景
+
+## Markup Schema 案例
+
+以下例子演示了 FormDialog 的几个能力:
+
+- 快速打开,关闭能力
+- 中间件能力,自动出现加载态
+- 渲染函数内可以响应式能力
+- 上下文共享能力
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+### FormDialog
+
+```ts pure
+import { IFormProps, Form } from '@formily/core'
+
+type FormDialogContentProps = { form: Form }
+
+type FormDialogContent = Component | ((props: FormDialogContentProps) => VNode)
+
+type DialogTitle = string | number | Component | VNode | (() => VNode)
+
+type IFormDialogProps = Omit & {
+ title?: DialogTitle
+ footer?: null | Component | VNode | (() => VNode)
+ cancelText?: string | Component | VNode | (() => VNode)
+ cancelButtonProps?: ButtonProps
+ okText?: string | Component | VNode | (() => VNode)
+ okButtonProps?: ButtonProps
+ onOpen?: () => void
+ onOpend?: () => void
+ onClose?: () => void
+ onClosed?: () => void
+ onCancel?: () => void
+ onOK?: () => void
+ loadingText?: string
+}
+
+interface IFormDialog {
+ forOpen(middleware: IMiddleware): IFormDialog
+ forConfirm(middleware: IMiddleware): IFormDialog
+ forCancel(middleware: IMiddleware): IFormDialog
+ open(props?: IFormProps): Promise
+ close(): void
+}
+
+interface FormDialog {
+ (title: IFormDialogProps, id: string, content: FormDialogContent): IFormDialog
+ (title: IFormDialogProps, id: FormDialogContent): IFormDialog
+ (title: DialogTitle, id: string, content: FormDialogContent): IFormDialog
+ (title: DialogTitle, id: FormDialogContent): IFormDialog
+}
+```
+
+`DialogProps`类型定义参考 [Element-UI Dialog API](https://element.eleme.io/#/zh-CN/component/dialog#attributes)
+
+### FormDialog.Footer
+
+无属性,只接收子节点
+
+### FormDialog.Portal
+
+接收可选的 id 属性,默认值为 form-dialog,如果一个应用存在多个 prefixCls,不同区域的弹窗内部 prefixCls 不一样,那推荐指定 id 为区域级 id
diff --git a/docs/guide/form-drawer.md b/docs/guide/form-drawer.md
new file mode 100644
index 0000000..740b235
--- /dev/null
+++ b/docs/guide/form-drawer.md
@@ -0,0 +1,62 @@
+# FormDrawer
+
+> 抽屉表单,主要用在简单的事件打开表单场景
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+### FormDrawer
+
+```ts pure
+import { IFormProps, Form } from '@formily/core'
+
+type FormDrawerContentProps = { form: Form }
+
+type FormDrawerContent = Component | ((props: FormDrawerContentProps) => VNode)
+
+type DrawerTitle = string | number | Component | VNode | (() => VNode)
+
+type IFormDrawerProps = Omit & {
+ title?: DrawerTitle
+ footer?: null | Component | VNode | (() => VNode)
+ cancelText?: string | Component | VNode | (() => VNode)
+ cancelButtonProps?: ButtonProps
+ okText?: string | Component | VNode | (() => VNode)
+ okButtonProps?: ButtonProps
+ onOpen?: () => void
+ onOpend?: () => void
+ onClose?: () => void
+ onClosed?: () => void
+ onCancel?: () => void
+ onOK?: () => void
+ loadingText?: string
+}
+
+interface FormDrawer {
+ (title: IFormDrawerProps, id: string, content: FormDrawerContent): IFormDrawer
+ (title: IFormDrawerProps, id: FormDrawerContent): IFormDrawer
+ (title: DrawerTitle, id: string, content: FormDrawerContent): IFormDrawer
+ (title: DrawerTitle, id: FormDrawerContent): IFormDrawer
+}
+```
+
+`DrawerProps`类型定义参考 [Element-UI Drawer API](https://element.eleme.io/#/zh-CN/component/drawer#attributes)
+
+### FormDrawer.Footer
+
+无属性,只接收子节点
+
+### FormDrawer.Portal
+
+接收可选的 id 属性,默认值为 form-dialog,如果一个应用存在多个 prefixCls,不同区域的弹窗内部 prefixCls 不一样,那推荐指定 id 为区域级 id
diff --git a/docs/guide/form-grid.md b/docs/guide/form-grid.md
new file mode 100644
index 0000000..3288642
--- /dev/null
+++ b/docs/guide/form-grid.md
@@ -0,0 +1,74 @@
+# FormGrid
+
+> FormGrid 组件
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## 原生案例
+
+
+
+## 查询表单实现案例
+
+
+
+## API
+
+### FormGrid
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------------- | ---------------------- | -------------------------------------------------------------- | ----------------- |
+| minWidth | `number / number[]` | 元素最小宽度 | 100 |
+| maxWidth | `number / number[]` | 元素最大宽度 | - |
+| minColumns | `number / number[]` | 最小列数 | 0 |
+| maxColumns | `number / number[]` | 最大列数 | - |
+| breakpoints | number[] | 容器尺寸断点 | `[720,1280,1920]` |
+| columnGap | number | 列间距 | 8 |
+| rowGap | number | 行间距 | 4 |
+| colWrap | boolean | 自动换行 | true |
+| strictAutoFit | boolean | GridItem 宽度是否严格受限于 maxWidth,不受限的话会自动占满容器 | false |
+| shouldVisible | `(node,grid)=>boolean` | 是否需要显示当前节点 | `()=>true` |
+| grid | `Grid` | 外部传入 Grid 实例,用于实现更复杂的布局逻辑 | - |
+
+注意:
+
+- minWidth 生效优先级高于 minColumn
+- maxWidth 优先级高于 maxColumn
+- minWidth/maxWidth/minColumns/maxColumns 的数组格式代表与断点数组映射
+
+### FormGrid.GridColumn
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| -------- | ------ | ---------------------------------------------------- | ------ |
+| gridSpan | number | 元素所跨列数,如果为-1,那么会自动反向跨列填补单元格 | 1 |
+
+### FormGrid.createFormGrid
+
+从上下文中读取 Grid 实例
+
+```ts
+interface createFormGrid {
+ (props: IGridProps): Grid
+}
+```
+
+- IGridProps 参考 FormGrid 属性
+- Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid
+
+### FormGrid.useFormGrid
+
+从上下文中读取 Grid 实例
+
+```ts
+interface useFormGrid {
+ (): Grid
+}
+```
+
+- Grid 实例属性方法参考 https://github.com/alibaba/formily/tree/formily_next/packages/grid
diff --git a/docs/guide/form-item.md b/docs/guide/form-item.md
new file mode 100644
index 0000000..53fbc96
--- /dev/null
+++ b/docs/guide/form-item.md
@@ -0,0 +1,80 @@
+# FormItem
+
+> 全新的 FormItem 组件,相比于 Element 的 FormItem,它支持的功能更多,同时它的定位是纯样式组件,不管理表单状态,所以也会更轻量,更方便定制
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## 常用属性案例
+
+
+
+## 无边框案例
+
+设置去除组件边框
+
+
+
+## 内嵌模式案例
+
+设置表单组件为内嵌模式
+
+
+
+## 反馈信息定制案例
+
+可通过 `feedbackIcon` 传入指定反馈的按钮
+
+
+
+## 尺寸控制案例
+
+
+
+## API
+
+### FormItem
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| -------------- | ------------------------------------------------------ | ------------------------------------------- | ---------- | -------- |
+| style | CSSProperties | 样式 | - |
+| label | String \| Vue Component | 标签 | - |
+| labelStyle | CSSProperties | 标签样式 | - |
+| wrapperStyle | CSSProperties | 组件容器样式 | - |
+| className | string | 组件样式类名 | - |
+| colon | boolean | 冒号 | - |
+| tooltip | String \| Vue Component | 问号提示 | - |
+| tooltipLayout | `"icon" | "text"` | 问提示布局 | `"icon"` |
+| labelAlign | `"left"` \| `"right"` | 标签文本对齐方式 | `"right"` |
+| labelWrap | boolean | 标签换⾏,否则出现省略号,hover 有 tooltip | false |
+| labelWidth | `number` | 标签固定宽度 | - |
+| wrapperWidth | `number` | 内容固定宽度 | - |
+| labelCol | number | 标签⽹格所占列数,和内容列数加起来总和为 24 | - |
+| wrapperCol | number | 内容⽹格所占列数,和标签列数加起来总和为 24 | - |
+| wrapperAlign | `"left"` \| `"right"` | 内容文本对齐方式⻬ | `"left"` |
+| wrapperWrap | boolean | 内容换⾏,否则出现省略号,hover 有 tooltip | false |
+| fullness | boolean | 内容撑满 | false |
+| addonBefore | String \| Vue Component | 前缀内容 | - |
+| addonAfter | String \| Vue Component | 后缀内容 | - |
+| size | `"small"` \| `"default"` \| `"large"` | 尺⼨ | - |
+| extra | ReactNode | 扩展描述⽂案 | - |
+| feedbackText | ReactNode | 反馈⽂案 | - |
+| feedbackLayout | `"loose"` \| `"terse"` \| `"popover"` \| `"none"` | 反馈布局 | - |
+| feedbackStatus | `"error"` \| `"warning"` \| `"success"` \| `"pending"` | 反馈布局 | - |
+| feedbackIcon | string | 反馈图标 | - |
+| required | boolean | 星号提醒 | - |
+| asterisk | boolean | 星号提醒 | - |
+| gridSpan | number | ⽹格布局占宽 | - |
+
+### FormItem.BaseItem
+
+纯样式组件,属性与 FormItem 一样,与 Formily Core 不做状态桥接,主要用于一些需要依赖 FormItem 的样式布局能力,但不希望接入 Field 状态的场景
diff --git a/docs/guide/form-layout.md b/docs/guide/form-layout.md
new file mode 100644
index 0000000..695e5fd
--- /dev/null
+++ b/docs/guide/form-layout.md
@@ -0,0 +1,44 @@
+# FormLayout
+
+> 区块级布局批量控制组件,借助该组件,我们可以轻松的控制被 FormLayout 圈住的所有 FormItem 组件的布局模式
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| -------------- | ------------- | ----------------- | ----------------------- | ----------- | ---------------- | ------------ | -------- | ---------- |
+| style | CSSProperties | 样式 | - |
+| className | string | 类名 | - |
+| colon | boolean | 是否有冒号 | true |
+| labelAlign | `'right' | 'left' | ('right' | 'left')[]` | 标签内容对齐 | - |
+| wrapperAlign | `'right' | 'left' | ('right' | 'left')[]` | 组件容器内容对齐 | - |
+| labelWrap | boolean | 标签内容换行 | false |
+| labelWidth | number | 标签宽度(px) | - |
+| wrapperWidth | number | 组件容器宽度(px) | - |
+| wrapperWrap | boolean | 组件容器换行 | false |
+| labelCol | `number | number[]` | 标签宽度(24 column) | - |
+| wrapperCol | `number | number[]` | 组件容器宽度(24 column) | - |
+| fullness | boolean | 组件容器宽度 100% | false |
+| size | `'small' | 'default' | 'large'` | 组件尺寸 | default |
+| layout | `'vertical' | 'horizontal' | 'inline' | ('vertical' | 'horizontal' | 'inline')[]` | 布局模式 | horizontal |
+| direction | `'rtl' | 'ltr'` | 方向(暂不支持) | ltr |
+| inset | boolean | 内联布局 | false |
+| shallow | boolean | 上下文浅层传递 | true |
+| feedbackLayout | `'loose' | 'terse' | 'popover' | 'none'` | 反馈布局 | true |
+| tooltipLayout | `'icon'` | `'text'` | 问提示布局 | `"icon"` |
+| bordered | boolean | 是否有边框 | true |
+| breakpoints | number[] | 容器尺寸断点 | - |
+| gridColumnGap | number | 网格布局列间距 | 8 |
+| gridRowGap | number | 网格布局行间距 | 4 |
+| spaceGap | number | 弹性间距 | 8 |
diff --git a/docs/guide/form-step.md b/docs/guide/form-step.md
new file mode 100644
index 0000000..256f49a
--- /dev/null
+++ b/docs/guide/form-step.md
@@ -0,0 +1,52 @@
+# FormStep
+
+> 分步表单组件
+>
+> 注意:该组件只能用在 Schema 场景
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## API
+
+### FormStep
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| -------- | --------- | -------------------------------------- | ------ |
+| formStep | IFormStep | 传入通过 createFormStep 创建出来的模型 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/steps](https://element.eleme.io/#/zh-CN/component/steps)
+
+### FormStep.StepPane
+
+参考 [https://element.eleme.io/#/zh-CN/component/steps](https://element.eleme.io/#/zh-CN/component/steps)
+
+### FormStep.createFormStep
+
+```ts pure
+interface createFormStep {
+ (current?: number): IFormStep
+}
+
+interface IFormStep {
+ //当前索引
+ current: number
+ //是否允许向后
+ allowNext: boolean
+ //是否允许向前
+ allowBack: boolean
+ //设置当前索引
+ setCurrent(key: number): void
+ //提交表单
+ submit: Formily.Core.Models.Form['submit']
+ //向后
+ next(): void
+ //向前
+ back(): void
+}
+```
diff --git a/docs/guide/form-tab.md b/docs/guide/form-tab.md
new file mode 100644
index 0000000..e418720
--- /dev/null
+++ b/docs/guide/form-tab.md
@@ -0,0 +1,44 @@
+# FormTab
+
+> 选项卡表单
+>
+> 注意:该组件只适用于 Schema 场景
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## API
+
+### FormTab
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------- | -------- | ------------------------------------- | ------ |
+| formTab | IFormTab | 传入通过 createFormTab 创建出来的模型 | |
+
+其余参考 [https://element.eleme.io/#/zh-CN/component/tabs](https://element.eleme.io/#/zh-CN/component/tabs)
+
+### FormTab.TabPane
+
+参考 [https://element.eleme.io/#/zh-CN/component/tabs](https://element.eleme.io/#/zh-CN/component/tabs)
+
+### FormTab.createFormTab
+
+```ts pure
+type ActiveKey = string | number
+
+interface createFormTab {
+ (defaultActiveKey?: ActiveKey): IFormTab
+}
+
+interface IFormTab {
+ //激活主键
+ activeKey: ActiveKey
+ //设置激活主键
+ setActiveKey(key: ActiveKey): void
+}
+```
diff --git a/docs/guide/form.md b/docs/guide/form.md
new file mode 100644
index 0000000..0079c13
--- /dev/null
+++ b/docs/guide/form.md
@@ -0,0 +1,21 @@
+# Form
+
+> FormProvider + FormLayout + form 标签的组合组件,可以帮助我们快速实现带回车提交的且能批量布局的表单
+
+## 使用案例
+
+
+
+> 注意:想要实现回车提交,我们在使用 Submit 组件的时候不能给其传 submit 事件,否则回车提交会失效,这样做的目的是为了防止用户同时在多处写 submit 事件监听器,处理逻辑不一致的话,提交时很难定位问题。
+
+## API
+
+布局相关的 API 属性,我们参考 [FormLayout](./form-layout) 即可,剩下是 Form 组件独有的 API 属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ---------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------- | ------ |
+| form | [Form](https://core.formilyjs.org/api/models/form) | Form 实例 | - |
+| component | string | 渲染组件,可以指定为自定义组件渲染 | `form` |
+| previewTextPlaceholder | string \| Vue Component | 预览态占位符 | `N/A` |
+| onAutoSubmit | `(values:any)=>any` | 回车提交事件回调 | - |
+| onAutoSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | 回车提交校验失败事件回调 | - |
diff --git a/docs/guide/index.md b/docs/guide/index.md
new file mode 100644
index 0000000..4e97b10
--- /dev/null
+++ b/docs/guide/index.md
@@ -0,0 +1,126 @@
+# Element-UI
+
+## 介绍
+
+@formily/element 是基于 Element UI 封装的针对表单场景专业级(Professional)组件库,它主要有以下几个特点:
+
+- 更丰富的组件体系
+
+ - 布局组件
+
+ - FormLayout
+ - FormItem
+ - FormGrid
+ - FormButtonGroup
+ - Space
+ - Submit
+ - Reset
+
+ - 输入控件
+ - Input
+ - Password
+ - Select
+ - DatePicker
+ - TimePicker
+ - InputNumber
+ - Transfer
+ - Cascader
+ - Radio
+ - Checkbox
+ - Upload
+ - Switch
+ - 场景组件
+ - ArrayCards
+ - ArrayItems
+ - ArrayTable
+ - ArrayTabs
+ - FormCollapse
+ - FormStep
+ - FormTab
+ - FormDialog
+ - FormDrawer
+ - Editable
+ - 阅读态组件
+ - PreviewText
+
+- 主题定制能力
+ - follow 组件库的样式体系,更方便定制主题
+- 支持二次封装
+ - 所有组件都能二次封装
+- 支持阅读态
+ - 提供了 PreviewText 组件,用户可以基于它自己做阅读态封装,灵活性更强
+- 类型更加友好
+ - 每个组件都有着极其完整的类型定义,用户在实际开发过程中,可以感受到前所未有的智能提示体验
+- 更完备的布局控制能力
+ - 基于 FormLayout、FormItem、FormGrid 组件,提供更智能的布局能力。
+- 更优雅易用的 API
+ - FormStep,用户只需要关注 FormStep Reactive Model 即可,通过 createFormStep 就可以创建出 Reactive Model,传给 FormStep 组件即可快速通讯。同理,FormTab/FormCollapse 也是一样的通讯模式
+ - 弹窗表单,抽屉表单,想必过去,用户几乎每次都得在这两个场景上写大量的代码,这次直接提供了极其简易的 API 让用户使用,最大化提升开发效率
+
+## 注意
+
+因为 Element UI 是基于 Sass 构建的,如果你用 Webpack 配置请使用以下两个 Sass 工具
+
+```
+"sass": "^1.32.11",
+"sass-loader": "^8.0.2"
+```
+
+## 安装
+
+```bash
+$ npm install --save element-ui
+$ npm install --save @formily/core @formily/vue @vue/composition-api @formily/element
+```
+
+## 按需打包
+
+`Element-UI` 按需引入参见 [https://element.eleme.io/#/zh-CN/component/quickstart#an-xu-yin-ru](https://element.eleme.io/#/zh-CN/component/quickstart#an-xu-yin-ru)
+
+`@formily/element`按需引入需借助 `babel-plugin-import`
+
+#### 安装 `babel-plugin-import`
+
+```shell
+npm install babel-plugin-import --save-dev
+```
+
+或者
+
+```shell
+yarn add babel-plugin-import --dev
+```
+
+修改 `.babelrc`
+
+```json
+{
+ "plugins": [
+ [
+ "component",
+ {
+ "libraryName": "element-ui",
+ "styleLibraryName": "theme-chalk"
+ }
+ ],
+ [
+ "import",
+ {
+ "libraryName": "@formily/element",
+ "libraryDirectory": "esm",
+ "style": true
+ }
+ ]
+ ]
+}
+```
+
+## Q/A
+
+问:我想自己封装一套组件库,该怎么做?
+
+答:如果是开源组件库,可以直接参与项目共建,提供 PR,如果是企业内私有组件库,参考源码即可,源码并没有太多复杂逻辑。
+
+问:为什么 ArrayCards/ArrayTable/FormStep 这类组件只支持 Schema 模式,不支持纯 Template 模式?
+
+答:这就是 Schema 模式的核心优势,借助协议,我们可以做场景化抽象,相反,纯 Template 模式,受限于 Template 的不可解析性,我们很难做到 UI 级别的场景化抽象,更多的只是抽象 Hook。
diff --git a/docs/guide/input-number.md b/docs/guide/input-number.md
new file mode 100644
index 0000000..9c17ea3
--- /dev/null
+++ b/docs/guide/input-number.md
@@ -0,0 +1,19 @@
+# InputNumber
+
+> 数字输入框
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/input-number](https://element.eleme.io/#/zh-CN/component/input-number)
diff --git a/docs/guide/input.md b/docs/guide/input.md
new file mode 100644
index 0000000..846471f
--- /dev/null
+++ b/docs/guide/input.md
@@ -0,0 +1,19 @@
+# Input
+
+> 文本输入框
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/input](https://element.eleme.io/#/zh-CN/component/input)
diff --git a/docs/guide/password.md b/docs/guide/password.md
new file mode 100644
index 0000000..5eb79c5
--- /dev/null
+++ b/docs/guide/password.md
@@ -0,0 +1,19 @@
+# Password
+
+> 密码输入框
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/input](https://element.eleme.io/#/zh-CN/component/input)
diff --git a/docs/guide/preview-text.md b/docs/guide/preview-text.md
new file mode 100644
index 0000000..4898c92
--- /dev/null
+++ b/docs/guide/preview-text.md
@@ -0,0 +1,53 @@
+# PreviewText
+
+> 阅读态组件,主要用来实现类 Input,类 DatePicker 这些组件的阅读态
+
+## 简单案例
+
+
+
+## 扩展案例
+
+
+
+## API
+
+### PreviewText.Input
+
+参考 [https://element.eleme.io/#/zh-CN/component/input](https://element.eleme.io/#/zh-CN/component/input)
+
+### PreviewText.Select
+
+参考 [https://element.eleme.io/#/zh-CN/component/select](https://element.eleme.io/#/zh-CN/component/select)
+
+### PreviewText.Cascader
+
+参考 [https://element.eleme.io/#/zh-CN/component/cascader](https://element.eleme.io/#/zh-CN/component/cascader)
+
+### PreviewText.DatePicker
+
+参考 [https://element.eleme.io/#/zh-CN/component/date-picker](https://element.eleme.io/#/zh-CN/component/date-picker)
+
+### PreviewText.TimePicker
+
+参考 [https://element.eleme.io/#/zh-CN/component/time-picker](https://element.eleme.io/#/zh-CN/component/time-picker)
+
+### PreviewText
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---------- | ------ |
+| value | stirng | 缺省占位符 | N/A |
+
+### PreviewText.Placeholder
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------ | ------ | ---------- | ------ |
+| value | stirng | 缺省占位符 | N/A |
+
+### PreviewText.usePlaceholder
+
+```ts pure
+interface usePreviewTextPlaceholder {
+ (): string
+}
+```
diff --git a/docs/guide/radio.md b/docs/guide/radio.md
new file mode 100644
index 0000000..fff7df3
--- /dev/null
+++ b/docs/guide/radio.md
@@ -0,0 +1,26 @@
+# Radio
+
+> 单选框
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/radio](https://element.eleme.io/#/zh-CN/component/radio)
+
+### 扩展属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ---------- | --------------------------------------------------------------------------------- | -------- | ------- |
+| options | [RadioProps](https://element.eleme.io/#/zh-CN/component/radio#radio-attributes)[] | 选项 | [] |
+| optionType | default/button | 样式类型 | default |
diff --git a/docs/guide/reset.md b/docs/guide/reset.md
new file mode 100644
index 0000000..a2b42b0
--- /dev/null
+++ b/docs/guide/reset.md
@@ -0,0 +1,29 @@
+# Reset
+
+> 重置按钮
+
+## 普通重置
+
+> 有默认值的控件无法清空
+
+
+
+## 强制清空重置
+
+
+
+## 强制清空重置并校验
+
+
+
+## API
+
+按钮相关的 API 属性,我们参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button) 即可,剩下是 Reset 组件独有的 API 属性
+
+### 事件
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ---------------------- | ------------------------------------------------------------------------------------------------ | ---------------- | ------------------------------------- | --- |
+| onClick | `(event: MouseEvent) => void | boolean` | 点击事件,如果返回 false 可以阻塞重置 | - |
+| onResetValidateSuccess | (payload: any) => void | 重置校验成功事件 | - |
+| onResetValidateFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | 重置校验失败事件 | - |
diff --git a/docs/guide/select.md b/docs/guide/select.md
new file mode 100644
index 0000000..956e154
--- /dev/null
+++ b/docs/guide/select.md
@@ -0,0 +1,41 @@
+# Select
+
+> 下拉框组件
+
+## Markup Schema 同步数据源案例
+
+
+
+## Markup Schema 异步搜索案例
+
+
+
+## Markup Schema 异步联动数据源案例
+
+
+
+## JSON Schema 同步数据源案例
+
+
+
+## JSON Schema 异步联动数据源案例
+
+
+
+## Template 同步数据源案例
+
+
+
+## Template 异步联动数据源案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/select](https://element.eleme.io/#/zh-CN/component/select)
+
+### 扩展属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| ------- | ------------------------------------------------------------------------------------------ | ---- | ------ |
+| options | [SelectOptionProps](https://element.eleme.io/#/zh-CN/component/select#option-attributes)[] | 选项 | [] |
diff --git a/docs/guide/space.md b/docs/guide/space.md
new file mode 100644
index 0000000..fd1bb87
--- /dev/null
+++ b/docs/guide/space.md
@@ -0,0 +1,19 @@
+# Space
+
+> 超级便捷的 Flex 布局组件,可以帮助用户快速实现任何元素的并排紧挨布局
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://ant.design/components/space-cn/](https://ant.design/components/space-cn/)
diff --git a/docs/guide/submit.md b/docs/guide/submit.md
new file mode 100644
index 0000000..efdf6a4
--- /dev/null
+++ b/docs/guide/submit.md
@@ -0,0 +1,22 @@
+# Submit
+
+> 提交按钮
+
+## 普通提交
+
+
+
+## 防重提交
+
+
+
+## API
+
+按钮相关的 API 属性,我们参考 [https://element.eleme.io/#/zh-CN/component/button](https://element.eleme.io/#/zh-CN/component/button) 即可,剩下是 Submit 组件独有的 API 属性
+
+| 属性名 | 类型 | 描述 | 默认值 |
+| --------------- | ------------------------------------------------------------------------------------------------ | -------------------- | ------------------------------------- | --- |
+| onClick | `(event: MouseEvent) => void | boolean` | 点击事件,如果返回 false 可以阻塞提交 | - |
+| onSubmit | `(values: any) => Promise | any` | 提交事件回调 | - |
+| onSubmitSuccess | (payload: any) => void | 提交成功响应事件 | - |
+| onSubmitFailed | (feedbacks: [IFormFeedback](https://core.formilyjs.org/api/models/form#iformfeedback)[]) => void | 提交校验失败事件回调 | - |
diff --git a/docs/guide/switch.md b/docs/guide/switch.md
new file mode 100644
index 0000000..5d6cd40
--- /dev/null
+++ b/docs/guide/switch.md
@@ -0,0 +1,19 @@
+# Switch
+
+> 开关组件
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/switch](https://element.eleme.io/#/zh-CN/component/switch)
diff --git a/docs/guide/time-picker.md b/docs/guide/time-picker.md
new file mode 100644
index 0000000..ab0e18d
--- /dev/null
+++ b/docs/guide/time-picker.md
@@ -0,0 +1,19 @@
+# TimePicker
+
+> 时间选择器
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/time-picker](https://element.eleme.io/#/zh-CN/component/time-picker)
diff --git a/docs/guide/transfer.md b/docs/guide/transfer.md
new file mode 100644
index 0000000..0af1e7d
--- /dev/null
+++ b/docs/guide/transfer.md
@@ -0,0 +1,19 @@
+# Transfer
+
+> 穿梭框
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/transfer](https://element.eleme.io/#/zh-CN/component/transfer)
diff --git a/docs/guide/upload.md b/docs/guide/upload.md
new file mode 100644
index 0000000..cc9a1a5
--- /dev/null
+++ b/docs/guide/upload.md
@@ -0,0 +1,21 @@
+# Upload
+
+> 上传组件
+>
+> 注意:使用上传组件,推荐用户进行二次封装,用户无需关心上传组件与 Formily 的数据通信,只需要处理样式与基本上传配置即可。
+
+## Markup Schema 案例
+
+
+
+## JSON Schema 案例
+
+
+
+## Template 案例
+
+
+
+## API
+
+参考 [https://element.eleme.io/#/zh-CN/component/upload](https://element.eleme.io/#/zh-CN/component/upload)
diff --git a/global.config.d.ts b/global.config.d.ts
new file mode 100644
index 0000000..336ce12
--- /dev/null
+++ b/global.config.d.ts
@@ -0,0 +1 @@
+export {}
diff --git a/global.config.js b/global.config.js
new file mode 100644
index 0000000..aacca1c
--- /dev/null
+++ b/global.config.js
@@ -0,0 +1,31 @@
+'use strict'
+var __importDefault =
+ (this && this.__importDefault) ||
+ function (mod) {
+ return mod && mod.__esModule ? mod : { default: mod }
+ }
+Object.defineProperty(exports, '__esModule', { value: true })
+var pretty_format_1 = __importDefault(require('pretty-format'))
+global['prettyFormat'] = pretty_format_1.default
+global['sleep'] = function (time) {
+ return new Promise(function (resolve) {
+ return setTimeout(resolve, time)
+ })
+}
+global['requestAnimationFrame'] = function (fn) {
+ return setTimeout(fn)
+}
+global.document.documentElement.style['grid-column-gap'] = true
+;(function () {
+ var spy = jest.spyOn(console, 'error')
+ beforeAll(function () {
+ spy.mockImplementation(function (message) {
+ console.log(message)
+ throw new Error(message)
+ })
+ })
+ afterAll(function () {
+ spy.mockRestore()
+ })
+})()
+//# sourceMappingURL=global.config.js.map
diff --git a/global.config.js.map b/global.config.js.map
new file mode 100644
index 0000000..2a6921c
--- /dev/null
+++ b/global.config.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"global.config.js","sourceRoot":"","sources":["global.config.ts"],"names":[],"mappings":";;;;;AAAA,gEAAwC;AAExC,MAAM,CAAC,cAAc,CAAC,GAAG,uBAAY,CAAA;AAErC,MAAM,CAAC,OAAO,CAAC,GAAG,UAAC,IAAI;IACrB,OAAO,IAAI,OAAO,CAAC,UAAC,OAAO,IAAK,OAAA,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,EAAzB,CAAyB,CAAC,CAAA;AAC5D,CAAC,CAAA;AAED,MAAM,CAAC,uBAAuB,CAAC,GAAG,UAAC,EAAE,IAAK,OAAA,UAAU,CAAC,EAAE,CAAC,EAAd,CAAc,CAAA;AAExD,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAG9D;AAAA,CAAC;IACA,IAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IACxC,SAAS,CAAC;QACR,GAAG,CAAC,kBAAkB,CAAC,UAAC,OAAO;YAC7B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACpB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;QAC1B,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC;QACP,GAAG,CAAC,WAAW,EAAE,CAAA;IACnB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,EAAE,CAAA","sourcesContent":["import prettyFormat from 'pretty-format'\n\nglobal['prettyFormat'] = prettyFormat\n\nglobal['sleep'] = (time) => {\n return new Promise((resolve) => setTimeout(resolve, time))\n}\n\nglobal['requestAnimationFrame'] = (fn) => setTimeout(fn)\n\nglobal.document.documentElement.style['grid-column-gap'] = true\n\n// 把 console.error 转换成 error,方便断言\n;(() => {\n const spy = jest.spyOn(console, 'error')\n beforeAll(() => {\n spy.mockImplementation((message) => {\n console.log(message)\n throw new Error(message)\n })\n })\n\n afterAll(() => {\n spy.mockRestore()\n })\n})()\n"]}
\ No newline at end of file
diff --git a/global.config.ts b/global.config.ts
new file mode 100644
index 0000000..7b968bd
--- /dev/null
+++ b/global.config.ts
@@ -0,0 +1,26 @@
+import prettyFormat from 'pretty-format'
+
+global['prettyFormat'] = prettyFormat
+
+global['sleep'] = (time) => {
+ return new Promise((resolve) => setTimeout(resolve, time))
+}
+
+global['requestAnimationFrame'] = (fn) => setTimeout(fn)
+
+global.document.documentElement.style['grid-column-gap'] = true
+
+// 把 console.error 转换成 error,方便断言
+;(() => {
+ const spy = jest.spyOn(console, 'error')
+ beforeAll(() => {
+ spy.mockImplementation((message) => {
+ console.log(message)
+ throw new Error(message)
+ })
+ })
+
+ afterAll(() => {
+ spy.mockRestore()
+ })
+})()
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..f90028c
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,32 @@
+const path = require('path')
+module.exports = {
+ collectCoverage: true,
+ verbose: true,
+ testEnvironment: 'jsdom',
+ preset: 'ts-jest',
+ testMatch: ['**/__tests__/**/*.spec.[jt]s?(x)'],
+ setupFilesAfterEnv: [
+ require.resolve('jest-dom/extend-expect'),
+ './global.config.ts',
+ ],
+ // moduleNameMapper: process.env.TEST_ENV === 'production' ? undefined : alias,
+ globals: {
+ 'ts-jest': {
+ babelConfig: false,
+ tsconfig: './tsconfig.jest.json',
+ diagnostics: false,
+ },
+ },
+ coveragePathIgnorePatterns: [
+ '/node_modules/',
+ '/__tests__/',
+ '/esm/',
+ '/lib/',
+ 'package.json',
+ '/demo/',
+ '/packages/builder/src/__tests__/',
+ '/packages/builder/src/components/',
+ '/packages/builder/src/configs/',
+ 'package-lock.json',
+ ],
+}
diff --git a/lerna.json b/lerna.json
new file mode 100644
index 0000000..70fa5ef
--- /dev/null
+++ b/lerna.json
@@ -0,0 +1,13 @@
+{
+ "version": "1.0.0-alpha.0",
+ "npmClient": "yarn",
+ "useWorkspaces": true,
+ "npmClientArgs": ["--ignore-engines"],
+ "command": {
+ "version": {
+ "forcePublish": true,
+ "exact": true,
+ "message": "chore(release): 😊 publish %s"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..40e8764
--- /dev/null
+++ b/package.json
@@ -0,0 +1,103 @@
+{
+ "name": "root",
+ "private": true,
+ "devEngines": {
+ "node": "8.x || 9.x || 10.x || 11.x"
+ },
+ "workspaces": [
+ "packages/*"
+ ],
+ "scripts": {
+ "start": "vuepress dev docs",
+ "build": "formily-tpl build",
+ "build:docs": "vuepress build docs",
+ "test": "jest --coverage",
+ "test:prod": "jest --coverage --silent",
+ "preversion": "yarn install --ignore-engines && npm run build && npm run lint && npm run test",
+ "version:alpha": "lerna version prerelease --preid alpha",
+ "version:beta": "lerna version prerelease --preid beta",
+ "version:rc": "lerna version prerelease --preid rc",
+ "version:patch": "lerna version patch",
+ "version:minor": "lerna version minor",
+ "version:preminor": "lerna version preminor --preid beta",
+ "version:major": "lerna version major",
+ "release": "lerna publish from-package --yes",
+ "lint": "eslint ."
+ },
+ "devDependencies": {
+ "@formily/template": "^1.0.0-alpha.0",
+ "@formily/core": "^2.0.0",
+ "@formily/vue": "^2.0.0",
+ "@vue/composition-api": "^1.4.0",
+ "@testing-library/jest-dom": "^5.0.0",
+ "@testing-library/vue": "^5.6.2",
+ "@vue/test-utils": "1.0.0-beta.22",
+ "@types/jest": "^24.0.18",
+ "@typescript-eslint/eslint-plugin": "^4.9.1",
+ "@typescript-eslint/parser": "^4.8.2",
+ "@vuepress-dumi/vuepress-plugin-dumi-previewer": "0.3.3",
+ "@vuepress-dumi/vuepress-theme-dumi": "0.3.3",
+ "@vuepress/plugin-back-to-top": "^1.8.2",
+ "@vuepress/plugin-medium-zoom": "^1.8.2",
+ "codesandbox": "^2.2.3",
+ "core-js": "^2.4.0",
+ "element-ui": "^2.15.7",
+ "eslint": "^7.14.0",
+ "eslint-config-prettier": "^7.0.0",
+ "eslint-plugin-import": "^2.13.0",
+ "eslint-plugin-markdown": "^2.0.1",
+ "eslint-plugin-node": "^11.1.0",
+ "eslint-plugin-prettier": "^3.1.0",
+ "eslint-plugin-promise": "^4.0.0",
+ "eslint-plugin-vue": "^7.0.1",
+ "ghooks": "^2.0.4",
+ "lint-staged": "^8.2.1",
+ "@commitlint/cli": "^14.1.0",
+ "@commitlint/prompt-cli": "^14.1.0",
+ "@commitlint/config-conventional": "^14.1.0",
+ "prettier": "^2.2.1",
+ "pretty-quick": "^3.1.0",
+ "ts-import-plugin": "1.6.1",
+ "ts-jest": "^26.0.0",
+ "ts-node": "^9.1.1",
+ "lerna": "^4.0.0",
+ "typescript": "^4.1.5",
+ "vue": "^2.6.0",
+ "vuepress": "^1.8.2",
+ "vuepress-plugin-typescript": "^0.3.1",
+ "sass": "^1.34.1",
+ "sass-loader": "^8.0.2",
+ "raw-loader": "^4.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/formilyjs/element.git"
+ },
+ "config": {
+ "ghooks": {
+ "pre-commit": "lint-staged",
+ "commit-msg": "commitlint --edit"
+ }
+ },
+ "lint-staged": {
+ "*.{ts,tsx,js}": [
+ "eslint --ext .ts,.tsx,.js",
+ "pretty-quick --staged",
+ "git add"
+ ],
+ "*.md": [
+ "pretty-quick --staged",
+ "git add"
+ ]
+ },
+ "dependencies": {},
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-beta.1",
+ "vue": "^2.6.0 || >=3.0.0-rc.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+}
diff --git a/packages/.eslintrc b/packages/.eslintrc
new file mode 100644
index 0000000..a1e4a5f
--- /dev/null
+++ b/packages/.eslintrc
@@ -0,0 +1,72 @@
+{
+ "parser": "@typescript-eslint/parser",
+ "extends": [
+ "plugin:vue/vue3-essential",
+ "plugin:@typescript-eslint/recommended",
+ "prettier/@typescript-eslint"
+ ],
+ "env": {
+ "node": true
+ },
+ "plugins": ["@typescript-eslint", "prettier", "markdown"],
+ "parserOptions": {
+ "sourceType": "module",
+ "ecmaVersion": 10,
+ "ecmaFeatures": {
+ "jsx": true
+ }
+ },
+ "rules": {
+ "prettier/prettier": 0,
+ // don't force es6 functions to include space before paren
+ "space-before-function-paren": 0,
+ // maybe we should no-public
+ "@typescript-eslint/explicit-member-accessibility": 0,
+ "@typescript-eslint/interface-name-prefix": 0,
+ "@typescript-eslint/no-explicit-any": 0,
+ "@typescript-eslint/explicit-function-return-type": 0,
+ "@typescript-eslint/no-parameter-properties": 0,
+ "@typescript-eslint/array-type": 0,
+ "@typescript-eslint/no-object-literal-type-assertion": 0,
+ "@typescript-eslint/no-use-before-define": 0,
+ "@typescript-eslint/no-unused-vars": 1,
+ "@typescript-eslint/no-namespace": 0,
+ "@typescript-eslint/ban-ts-comment": 0,
+ "@typescript-eslint/ban-types": 0,
+ "@typescript-eslint/adjacent-overload-signatures": 0,
+ "@typescript-eslint/explicit-module-boundary-types": 0,
+ "@typescript-eslint/triple-slash-reference": 0,
+ "@typescript-eslint/no-empty-function": 0,
+ "no-console": [
+ "error",
+ {
+ "allow": ["warn", "error", "info"]
+ }
+ ],
+ "prefer-const": 0,
+ "no-var": 1,
+ "prefer-rest-params": 0
+ },
+ "overrides": [
+ {
+ "files": ["**/*.md.{jsx,tsx}"],
+ "processor": "markdown/markdown"
+ },
+ {
+ "files": ["**/*.md/*.{jsx,tsx}"],
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "error",
+ "no-unused-vars": "error",
+ "no-console": "off"
+ }
+ },
+ {
+ "files": ["**/*.md/*.{js,ts}"],
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "off",
+ "no-unused-vars": "off",
+ "no-console": "off"
+ }
+ }
+ ]
+}
diff --git a/packages/components/.npmignore b/packages/components/.npmignore
new file mode 100644
index 0000000..1ff3374
--- /dev/null
+++ b/packages/components/.npmignore
@@ -0,0 +1,11 @@
+node_modules
+*.log
+build
+docs
+doc-site
+__tests__
+.eslintrc
+jest.config.js
+tsconfig.json
+.umi
+src
\ No newline at end of file
diff --git a/packages/components/README.md b/packages/components/README.md
new file mode 100644
index 0000000..7727db7
--- /dev/null
+++ b/packages/components/README.md
@@ -0,0 +1,9 @@
+# @formily/element
+
+> Formily Component Adaptor
+
+## Install
+
+```bash
+npm install @formily/element --save
+```
diff --git a/packages/components/README.zh-CN.md b/packages/components/README.zh-CN.md
new file mode 100644
index 0000000..a0ae0ee
--- /dev/null
+++ b/packages/components/README.zh-CN.md
@@ -0,0 +1,9 @@
+# @formily/element
+
+> Formily 组件库桥接层
+
+## 安装
+
+```bash
+npm install @formily/element --save
+```
diff --git a/packages/components/builder.config.ts b/packages/components/builder.config.ts
new file mode 100644
index 0000000..c840ea0
--- /dev/null
+++ b/packages/components/builder.config.ts
@@ -0,0 +1,7 @@
+import { IBuilderConfig } from '@formily/template'
+
+export const BuilderConfig: IBuilderConfig = {
+ targetLibName: 'element-ui',
+ targetLibCjsDir: 'lib',
+ targetLibEsDir: 'es',
+}
diff --git a/packages/components/package.json b/packages/components/package.json
new file mode 100644
index 0000000..1b2590b
--- /dev/null
+++ b/packages/components/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "@formily/element",
+ "version": "2.0.2",
+ "license": "MIT",
+ "main": "lib",
+ "types": "lib/index.d.ts",
+ "engines": {
+ "npm": ">=3.0.0"
+ },
+ "module": "esm",
+ "umd:main": "dist/formily.element.umd.production.js",
+ "unpkg": "dist/formily.element.umd.production.js",
+ "jsdelivr": "dist/formily.element.umd.production.js",
+ "jsnext:main": "esm",
+ "sideEffects": [
+ "dist/*",
+ "esm/*.js",
+ "lib/*.js",
+ "src/*.ts",
+ "*.less",
+ "*.scss",
+ "**/*/style.js"
+ ],
+ "scripts": {
+ "build": "formily-tpl build"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/formilyjs/element.git"
+ },
+ "bugs": {
+ "url": "https://github.com/formilyjs/element/issues"
+ },
+ "homepage": "https://github.com/formilyjs/element#readme",
+ "publishConfig": {
+ "access": "public"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-beta.1",
+ "vue": "^2.6.0 || >=3.0.0-rc.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ },
+ "dependencies": {
+ "@formily/core": "^2.0.2",
+ "@formily/grid": "^2.0.2",
+ "@formily/json-schema": "^2.0.2",
+ "@formily/reactive": "^2.0.2",
+ "@formily/reactive-vue": "^2.0.2",
+ "@formily/shared": "^2.0.2",
+ "@formily/vue": "^2.0.2",
+ "portal-vue": "^2.1.7",
+ "vue-slicksort": "^1.2.0"
+ },
+ "devDependencies": {
+ "vue": "^2.6.0"
+ }
+}
diff --git a/packages/components/src/__builtins__/configs/index.ts b/packages/components/src/__builtins__/configs/index.ts
new file mode 100644
index 0000000..ac6f4d3
--- /dev/null
+++ b/packages/components/src/__builtins__/configs/index.ts
@@ -0,0 +1 @@
+export const stylePrefix = 'formily-element'
diff --git a/packages/components/src/__builtins__/index.ts b/packages/components/src/__builtins__/index.ts
new file mode 100644
index 0000000..8bf1dec
--- /dev/null
+++ b/packages/components/src/__builtins__/index.ts
@@ -0,0 +1,2 @@
+export * from './configs'
+export * from './shared'
diff --git a/packages/components/src/__builtins__/shared/create-context.ts b/packages/components/src/__builtins__/shared/create-context.ts
new file mode 100644
index 0000000..e3b2caf
--- /dev/null
+++ b/packages/components/src/__builtins__/shared/create-context.ts
@@ -0,0 +1,57 @@
+import type { Component } from 'vue'
+import {
+ defineComponent,
+ provide,
+ inject,
+ readonly,
+ InjectionKey,
+ ref,
+ Ref,
+ toRef,
+} from '@vue/composition-api'
+
+export type CreateContext = {
+ Provider: Component
+ Consumer: Component
+ injectKey: InjectionKey[>
+}
+
+export const createContext = ](defaultValue?: T): CreateContext => {
+ const injectKey: InjectionKey[> = Symbol()
+
+ return {
+ Provider: defineComponent({
+ name: 'ContextProvider',
+ props: {
+ value: {
+ type: null,
+ default() {
+ return defaultValue ?? null
+ },
+ },
+ },
+ setup(props, { slots }) {
+ const value = toRef(props, 'value')
+ provide(injectKey, readonly(value))
+
+ return () => slots?.default?.()
+ },
+ }),
+
+ Consumer: defineComponent({
+ name: 'ContextConsumer',
+ setup(_props, { slots }) {
+ const value = inject(injectKey)
+
+ return () => slots?.default?.(value)
+ },
+ }),
+ injectKey,
+ }
+}
+
+export const useContext = ](context: CreateContext) => {
+ const key = context.injectKey
+
+ return inject(key, ref(null))
+}
diff --git a/packages/components/src/__builtins__/shared/index.ts b/packages/components/src/__builtins__/shared/index.ts
new file mode 100644
index 0000000..25aee28
--- /dev/null
+++ b/packages/components/src/__builtins__/shared/index.ts
@@ -0,0 +1,7 @@
+export * from './transform-component'
+export * from './resolve-component'
+export * from './create-context'
+export * from './utils'
+export * from './portal'
+export * from './loading'
+export * from './types'
diff --git a/packages/components/src/__builtins__/shared/loading.ts b/packages/components/src/__builtins__/shared/loading.ts
new file mode 100644
index 0000000..af33cf2
--- /dev/null
+++ b/packages/components/src/__builtins__/shared/loading.ts
@@ -0,0 +1,20 @@
+import { Loading } from 'element-ui'
+
+export const loading = async (
+ loadingText = 'Loading...',
+ processor: () => Promise
+) => {
+ let loadingInstance = null
+ let loading = setTimeout(() => {
+ loadingInstance = Loading.service({
+ text: loadingText,
+ background: 'transparent',
+ })
+ }, 100)
+ try {
+ return await processor()
+ } finally {
+ loadingInstance?.close()
+ clearTimeout(loading)
+ }
+}
diff --git a/packages/components/src/__builtins__/shared/portal.ts b/packages/components/src/__builtins__/shared/portal.ts
new file mode 100644
index 0000000..7ec1698
--- /dev/null
+++ b/packages/components/src/__builtins__/shared/portal.ts
@@ -0,0 +1,43 @@
+import { defineComponent, onBeforeUnmount } from '@vue/composition-api'
+import { h, Fragment } from '@formily/vue'
+export interface IPortalProps {
+ id?: string | symbol
+}
+
+const PortalMap = new Map()
+
+export const createPortalProvider = (id: string | symbol) => {
+ const Portal = defineComponent({
+ name: 'ProtalProvider',
+ props: {
+ id: {
+ type: [String, Symbol],
+ default: id,
+ },
+ },
+
+ setup(props) {
+ onBeforeUnmount(() => {
+ const { id } = props
+ if (id && PortalMap.has(id)) {
+ PortalMap.delete(id)
+ }
+ })
+ },
+
+ render() {
+ const { id } = this
+ if (id && !PortalMap.has(id)) {
+ PortalMap.set(id, this)
+ }
+
+ return h(Fragment, {}, this.$scopedSlots)
+ },
+ })
+
+ return Portal
+}
+
+export function getProtalContext(id: string | symbol) {
+ return PortalMap.get(id)
+}
diff --git a/packages/components/src/__builtins__/shared/resolve-component.ts b/packages/components/src/__builtins__/shared/resolve-component.ts
new file mode 100644
index 0000000..b9ff661
--- /dev/null
+++ b/packages/components/src/__builtins__/shared/resolve-component.ts
@@ -0,0 +1,23 @@
+import { Component } from 'vue'
+import { h, toRaw } from '@vue/composition-api'
+import { SlotTypes } from '.'
+import { isVnode } from './utils'
+
+export const resolveComponent = (
+ child?: SlotTypes,
+ props?: Record
+) => {
+ if (child) {
+ if (typeof child === 'string' || typeof child === 'number') {
+ return child
+ } else if (typeof child === 'function') {
+ return (child as Function)(props)
+ } else if (isVnode(child)) {
+ return child
+ } else {
+ return h(toRaw(child as Component), { props })
+ }
+ }
+
+ return null
+}
diff --git a/packages/components/src/__builtins__/shared/transform-component.ts b/packages/components/src/__builtins__/shared/transform-component.ts
new file mode 100644
index 0000000..2aef0ce
--- /dev/null
+++ b/packages/components/src/__builtins__/shared/transform-component.ts
@@ -0,0 +1,65 @@
+import type { Component } from 'vue'
+import { merge } from '@formily/shared'
+import { h } from '@formily/vue'
+import { isVue2, defineComponent } from 'vue-demi'
+
+type ListenersTransformRules = Record
+
+export const transformComponent = >(
+ tag: any,
+ transformRules?: ListenersTransformRules,
+ defaultProps?: Partial
+): Component | any => {
+ if (isVue2) {
+ return defineComponent({
+ setup(props, { attrs, slots, listeners }) {
+ return () => {
+ const data = {
+ attrs: {
+ ...attrs,
+ },
+ on: {
+ ...listeners,
+ },
+ }
+
+ if (transformRules) {
+ const transformListeners = transformRules
+ Object.keys(transformListeners).forEach((extract) => {
+ if (data.on !== undefined) {
+ data.on[transformListeners[extract]] = listeners[extract]
+ }
+ })
+ }
+ if (defaultProps) {
+ data.attrs = merge(defaultProps, data.attrs)
+ }
+
+ return h(tag, data, slots)
+ }
+ },
+ })
+ } else {
+ return defineComponent({
+ setup(props, { attrs, slots }) {
+ return () => {
+ let data = {
+ ...attrs,
+ }
+ if (transformRules) {
+ const listeners = transformRules
+ Object.keys(listeners).forEach((extract) => {
+ const event = listeners[extract]
+ data[`on${event[0].toUpperCase()}${event.slice(1)}`] =
+ attrs[`on${extract[0].toUpperCase()}${extract.slice(1)}`]
+ })
+ }
+ if (defaultProps) {
+ data = merge(defaultProps, data)
+ }
+ return h(tag, data, slots)
+ }
+ },
+ })
+ }
+}
diff --git a/packages/components/src/__builtins__/shared/types.ts b/packages/components/src/__builtins__/shared/types.ts
new file mode 100644
index 0000000..2daff05
--- /dev/null
+++ b/packages/components/src/__builtins__/shared/types.ts
@@ -0,0 +1,8 @@
+import { Component, VNode } from 'vue'
+
+export type SlotTypes =
+ | Component
+ | string
+ | number
+ | ((props: Record) => VNode[] | VNode)
+ | VNode
diff --git a/packages/components/src/__builtins__/shared/utils.ts b/packages/components/src/__builtins__/shared/utils.ts
new file mode 100644
index 0000000..e987034
--- /dev/null
+++ b/packages/components/src/__builtins__/shared/utils.ts
@@ -0,0 +1,35 @@
+export function isValidElement(element) {
+ return (
+ isVueOptions(element) ||
+ (element &&
+ typeof element === 'object' &&
+ 'componentOptions' in element &&
+ 'context' in element &&
+ element.tag !== undefined)
+ ) // remove text node
+}
+
+export function isVnode(element: any): boolean {
+ return (
+ element &&
+ typeof element === 'object' &&
+ 'componentOptions' in element &&
+ 'context' in element &&
+ element.tag !== undefined
+ )
+}
+
+export function isVueOptions(options) {
+ return (
+ options &&
+ (typeof options.template === 'string' ||
+ typeof options.render === 'function')
+ )
+}
+
+export function composeExport(
+ s0: T0,
+ s1: T1
+): T0 & T1 {
+ return Object.assign(s0, s1)
+}
diff --git a/packages/components/src/__builtins__/styles/common.scss b/packages/components/src/__builtins__/styles/common.scss
new file mode 100644
index 0000000..6db266f
--- /dev/null
+++ b/packages/components/src/__builtins__/styles/common.scss
@@ -0,0 +1,15 @@
+$formily-prefix: 'formily-element';
+$namespace: 'el';
+@import '~element-ui/packages/theme-chalk/src/common/var.scss';
+
+@mixin active {
+ border-color: $--color-primary;
+ outline: 0;
+ border-right-width: $--border-width-base !important;
+}
+
+@mixin hover {
+ border-color: $--border-color-hover;
+ outline: 0;
+ border-right-width: $--border-width-base !important;
+}
diff --git a/packages/components/src/array-base/index.ts b/packages/components/src/array-base/index.ts
new file mode 100644
index 0000000..1d9771d
--- /dev/null
+++ b/packages/components/src/array-base/index.ts
@@ -0,0 +1,432 @@
+import {
+ defineComponent,
+ provide,
+ InjectionKey,
+ Ref,
+ inject,
+ toRefs,
+ ref,
+ onBeforeUnmount,
+ PropType,
+} from '@vue/composition-api'
+import { Fragment, useField, useFieldSchema, h } from '@formily/vue'
+import { isValid, uid, clone } from '@formily/shared'
+import { ArrayField } from '@formily/core'
+import { stylePrefix } from '../__builtins__/configs'
+
+import type { Button as ButtonProps } from 'element-ui'
+import { Button } from 'element-ui'
+import type { Schema } from '@formily/json-schema'
+import { HandleDirective } from 'vue-slicksort'
+import { composeExport } from '../__builtins__/shared'
+
+export interface IArrayBaseAdditionProps extends ButtonProps {
+ title?: string
+ method?: 'push' | 'unshift'
+ defaultValue?: any
+}
+
+export type ArrayBaseMixins = {
+ Addition?: typeof ArrayBaseAddition
+ Remove?: typeof ArrayBaseRemove
+ MoveUp?: typeof ArrayBaseMoveUp
+ MoveDown?: typeof ArrayBaseMoveDown
+ SortHandle?: typeof ArrayBaseSortHandle
+ Index?: typeof ArrayBaseIndex
+ useArray?: typeof useArray
+ useIndex?: typeof useIndex
+ useRecord?: typeof useRecord
+}
+
+export interface IArrayBaseProps {
+ disabled?: boolean
+ keyMap?: WeakMap | String[] | null
+}
+
+export interface IArrayBaseItemProps {
+ index: number
+ record: any
+}
+
+export interface IArrayBaseContext {
+ field: Ref
+ schema: Ref
+ props: IArrayBaseProps
+ listeners: {
+ [key in string]?: Function
+ }
+ keyMap?: WeakMap | String[] | null
+}
+
+const ArrayBaseSymbol: InjectionKey =
+ Symbol('ArrayBaseContext')
+const ItemSymbol: InjectionKey = Symbol('ItemContext')
+
+const useArray = () => {
+ return inject(ArrayBaseSymbol, null)
+}
+
+const useIndex = (index?: number) => {
+ const { index: indexRef } = toRefs(inject(ItemSymbol))
+ return indexRef ?? ref(index)
+}
+
+const useRecord = (record?: number) => {
+ const { record: recordRef } = toRefs(inject(ItemSymbol))
+ return recordRef ?? ref(record)
+}
+
+const isObjectValue = (schema: Schema) => {
+ if (Array.isArray(schema?.items)) return isObjectValue(schema.items[0])
+
+ if (schema?.items?.type === 'array' || schema?.items?.type === 'object') {
+ return true
+ }
+ return false
+}
+
+const useKey = (schema: Schema) => {
+ const isObject = isObjectValue(schema)
+ let keyMap: WeakMap | String[] | null = null
+
+ if (isObject) {
+ keyMap = new WeakMap()
+ } else {
+ keyMap = []
+ }
+
+ onBeforeUnmount(() => {
+ keyMap = null
+ })
+
+ return {
+ keyMap,
+ getKey: (record: any, index?: number) => {
+ if (keyMap instanceof WeakMap) {
+ if (!keyMap.has(record)) {
+ keyMap.set(record, uid())
+ }
+ return `${keyMap.get(record)}-${index}`
+ }
+
+ if (!keyMap[index]) {
+ keyMap[index] = uid()
+ }
+
+ return `${keyMap[index]}-${index}`
+ },
+ }
+}
+
+const getDefaultValue = (defaultValue: any, schema: Schema): any => {
+ if (isValid(defaultValue)) return clone(defaultValue)
+ if (Array.isArray(schema?.items))
+ return getDefaultValue(defaultValue, schema.items[0])
+ if (schema?.items?.type === 'array') return []
+ if (schema?.items?.type === 'boolean') return true
+ if (schema?.items?.type === 'date') return ''
+ if (schema?.items?.type === 'datetime') return ''
+ if (schema?.items?.type === 'number') return 0
+ if (schema?.items?.type === 'object') return {}
+ if (schema?.items?.type === 'string') return ''
+ return null
+}
+
+const ArrayBaseInner = defineComponent({
+ name: 'ArrayBase',
+ props: {
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
+ keyMap: {
+ type: [WeakMap, Array] as PropType | String[]>,
+ },
+ },
+ setup(props, { slots, listeners }) {
+ const field = useField()
+ const schema = useFieldSchema()
+
+ provide(ArrayBaseSymbol, {
+ field,
+ schema,
+ props,
+ listeners,
+ keyMap: props.keyMap,
+ })
+ return () => {
+ return h(Fragment, {}, slots)
+ }
+ },
+})
+
+const ArrayBaseItem = defineComponent({
+ name: 'ArrayBaseItem',
+ props: ['index', 'record'],
+ setup(props: IArrayBaseItemProps, { slots }) {
+ provide(ItemSymbol, props)
+ return () => {
+ return h(Fragment, {}, slots)
+ }
+ },
+})
+
+const ArrayBaseSortHandle = defineComponent({
+ name: 'ArrayBaseSortHandle',
+ props: ['index'],
+ directives: {
+ handle: HandleDirective,
+ },
+ setup(props, { attrs }) {
+ const array = useArray()
+ const prefixCls = `${stylePrefix}-array-base`
+
+ return () => {
+ if (!array) return null
+ if (array.field.value?.pattern !== 'editable') return null
+
+ return h(
+ Button,
+ {
+ directives: [{ name: 'handle' }],
+ class: [`${prefixCls}-sort-handle`],
+ attrs: {
+ size: 'mini',
+ type: 'text',
+ icon: 'el-icon-rank',
+ ...attrs,
+ },
+ },
+ {}
+ )
+ }
+ },
+})
+
+const ArrayBaseIndex = defineComponent({
+ name: 'ArrayBaseIndex',
+ setup(props, { attrs }) {
+ const index = useIndex()
+ const prefixCls = `${stylePrefix}-array-base`
+ return () => {
+ return h(
+ 'span',
+ {
+ class: `${prefixCls}-index`,
+ attrs,
+ },
+ {
+ default: () => [`#${index.value + 1}.`],
+ }
+ )
+ }
+ },
+})
+
+const ArrayBaseAddition = defineComponent({
+ name: 'ArrayBaseAddition',
+ props: ['title', 'method', 'defaultValue'],
+ setup(props: IArrayBaseAdditionProps, { listeners }) {
+ const self = useField()
+ const array = useArray()
+ const prefixCls = `${stylePrefix}-array-base`
+ return () => {
+ if (!array) return null
+ if (array?.field.value.pattern !== 'editable') return null
+ return h(
+ Button,
+ {
+ class: `${prefixCls}-addition`,
+ attrs: {
+ type: 'ghost',
+ icon: 'qax-icon-Alone-Plus',
+ ...props,
+ },
+ on: {
+ ...listeners,
+ click: (e) => {
+ if (array.props?.disabled) return
+ const defaultValue = getDefaultValue(
+ props.defaultValue,
+ array?.schema.value
+ )
+ if (props.method === 'unshift') {
+ array?.field?.value.unshift(defaultValue)
+ array.listeners?.add?.(0)
+ } else {
+ array?.field?.value.push(defaultValue)
+ array.listeners?.add?.(array?.field?.value?.value?.length - 1)
+ }
+ if (listeners.click) {
+ listeners.click(e)
+ }
+ },
+ },
+ },
+ {
+ default: () => [self.value.title || props.title],
+ }
+ )
+ }
+ },
+})
+
+const ArrayBaseRemove = defineComponent<
+ ButtonProps & { title?: string; index?: number }
+>({
+ name: 'ArrayBaseRemove',
+ props: ['title', 'index'],
+ setup(props, { attrs, listeners }) {
+ const indexRef = useIndex(props.index)
+ const base = useArray()
+ const prefixCls = `${stylePrefix}-array-base`
+ return () => {
+ if (base?.field.value.pattern !== 'editable') return null
+ return h(
+ Button,
+ {
+ class: `${prefixCls}-remove`,
+ attrs: {
+ type: 'text',
+ size: 'mini',
+ icon: 'el-icon-delete',
+ ...attrs,
+ },
+ on: {
+ ...listeners,
+ click: (e: MouseEvent) => {
+ e.stopPropagation()
+ if (Array.isArray(base?.keyMap)) {
+ base?.keyMap?.splice(indexRef.value, 1)
+ }
+
+ base?.field.value.remove(indexRef.value as number)
+ base?.listeners?.remove?.(indexRef.value as number)
+
+ if (listeners.click) {
+ listeners.click(e)
+ }
+ },
+ },
+ },
+ {
+ default: () => [props.title],
+ }
+ )
+ }
+ },
+})
+
+const ArrayBaseMoveDown = defineComponent<
+ ButtonProps & { title?: string; index?: number }
+>({
+ name: 'ArrayBaseMoveDown',
+ props: ['title', 'index'],
+ setup(props, { attrs, listeners }) {
+ const indexRef = useIndex(props.index)
+ const base = useArray()
+ const prefixCls = `${stylePrefix}-array-base`
+ return () => {
+ if (base?.field.value.pattern !== 'editable') return null
+ return h(
+ Button,
+ {
+ class: `${prefixCls}-move-down`,
+ attrs: {
+ size: 'mini',
+ type: 'text',
+ icon: 'el-icon-arrow-down',
+ ...attrs,
+ },
+ on: {
+ ...listeners,
+ click: (e: MouseEvent) => {
+ e.stopPropagation()
+ if (Array.isArray(base?.keyMap)) {
+ base.keyMap.splice(
+ indexRef.value + 1,
+ 0,
+ base.keyMap.splice(indexRef.value, 1)[0]
+ )
+ }
+
+ base?.field.value.moveDown(indexRef.value as number)
+ base?.listeners?.moveDown?.(indexRef.value as number)
+
+ if (listeners.click) {
+ listeners.click(e)
+ }
+ },
+ },
+ },
+ {
+ default: () => [props.title],
+ }
+ )
+ }
+ },
+})
+
+const ArrayBaseMoveUp = defineComponent<
+ ButtonProps & { title?: string; index?: number }
+>({
+ name: 'ArrayBaseMoveUp',
+ props: ['title', 'index'],
+ setup(props, { attrs, listeners }) {
+ const indexRef = useIndex(props.index)
+ const base = useArray()
+ const prefixCls = `${stylePrefix}-array-base`
+ return () => {
+ if (base?.field.value.pattern !== 'editable') return null
+ return h(
+ Button,
+ {
+ class: `${prefixCls}-move-up`,
+ attrs: {
+ size: 'mini',
+ type: 'text',
+ icon: 'el-icon-arrow-up',
+ ...attrs,
+ },
+ on: {
+ ...listeners,
+ click: (e: MouseEvent) => {
+ e.stopPropagation()
+ if (Array.isArray(base?.keyMap)) {
+ base.keyMap.splice(
+ indexRef.value - 1,
+ 0,
+ base.keyMap.splice(indexRef.value, 1)[0]
+ )
+ }
+
+ base?.field.value.moveUp(indexRef.value as number)
+ base?.listeners?.moveUp?.(indexRef.value as number)
+
+ if (listeners.click) {
+ listeners.click(e)
+ }
+ },
+ },
+ },
+ {
+ default: () => [props.title],
+ }
+ )
+ }
+ },
+})
+
+export const ArrayBase = composeExport(ArrayBaseInner, {
+ Index: ArrayBaseIndex,
+ Item: ArrayBaseItem,
+ SortHandle: ArrayBaseSortHandle,
+ Addition: ArrayBaseAddition,
+ Remove: ArrayBaseRemove,
+ MoveDown: ArrayBaseMoveDown,
+ MoveUp: ArrayBaseMoveUp,
+ useArray: useArray,
+ useIndex: useIndex,
+ useKey: useKey,
+ useRecord: useRecord,
+})
diff --git a/packages/components/src/array-base/style.scss b/packages/components/src/array-base/style.scss
new file mode 100644
index 0000000..a630198
--- /dev/null
+++ b/packages/components/src/array-base/style.scss
@@ -0,0 +1,32 @@
+@import '../__builtins__/styles/common.scss';
+
+$array-base-prefix-cls: '#{$formily-prefix}-array-base';
+
+.#{$array-base-prefix-cls}-addition {
+ transition: $--all-transition;
+}
+
+.#{$array-base-prefix-cls}-remove {
+ i {
+ font-size: $--font-size-base;
+ }
+}
+
+.#{$array-base-prefix-cls}-move-down {
+ i {
+ font-size: $--font-size-base;
+ }
+}
+
+.#{$array-base-prefix-cls}-move-up {
+ i {
+ font-size: $--font-size-base;
+ }
+}
+
+.#{$array-base-prefix-cls}-sort-handle {
+ i {
+ font-size: $--font-size-base;
+ cursor: move;
+ }
+}
diff --git a/packages/components/src/array-base/style.ts b/packages/components/src/array-base/style.ts
new file mode 100644
index 0000000..5760431
--- /dev/null
+++ b/packages/components/src/array-base/style.ts
@@ -0,0 +1,2 @@
+import 'element-ui/packages/theme-chalk/src/button.scss'
+import './style.scss'
diff --git a/packages/components/src/array-cards/index.ts b/packages/components/src/array-cards/index.ts
new file mode 100644
index 0000000..53bbc6c
--- /dev/null
+++ b/packages/components/src/array-cards/index.ts
@@ -0,0 +1,248 @@
+import { defineComponent } from '@vue/composition-api'
+import { Card, Empty, Row } from 'element-ui'
+import type { Card as CardProps } from 'element-ui'
+import { ArrayField } from '@formily/core'
+import { useField, useFieldSchema, RecursionField, h } from '@formily/vue'
+import { observer } from '@formily/reactive-vue'
+import { ISchema } from '@formily/json-schema'
+import { stylePrefix } from '../__builtins__/configs'
+import { ArrayBase } from '../array-base'
+import { composeExport } from '../__builtins__/shared'
+
+const isAdditionComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('Addition') > -1
+}
+
+const isIndexComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('Index') > -1
+}
+
+const isRemoveComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('Remove') > -1
+}
+
+const isMoveUpComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('MoveUp') > -1
+}
+
+const isMoveDownComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('MoveDown') > -1
+}
+
+const isOperationComponent = (schema: ISchema) => {
+ return (
+ isAdditionComponent(schema) ||
+ isRemoveComponent(schema) ||
+ isMoveDownComponent(schema) ||
+ isMoveUpComponent(schema)
+ )
+}
+const ArrayCardsInner = observer(
+ defineComponent({
+ name: 'FArrayCards',
+ props: [],
+ setup(props, { attrs }) {
+ const fieldRef = useField()
+ const schemaRef = useFieldSchema()
+ const prefixCls = `${stylePrefix}-array-cards`
+ const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value)
+
+ return () => {
+ const field = fieldRef.value
+ const schema = schemaRef.value
+ const dataSource = Array.isArray(field.value) ? field.value : []
+ if (!schema) throw new Error('can not found schema object')
+
+ const renderItems = () => {
+ return dataSource?.map((item, index) => {
+ const items = Array.isArray(schema.items)
+ ? schema.items[index] || schema.items[0]
+ : schema.items
+
+ const title = h(
+ 'span',
+ {},
+ {
+ default: () => [
+ h(
+ RecursionField,
+ {
+ props: {
+ schema: items,
+ name: index,
+ filterProperties: (schema) => {
+ if (!isIndexComponent(schema)) return false
+ return true
+ },
+ onlyRenderProperties: true,
+ },
+ },
+ {}
+ ),
+ attrs.title || field.title,
+ ],
+ }
+ )
+ const extra = h(
+ 'span',
+ {},
+ {
+ default: () => [
+ h(
+ RecursionField,
+ {
+ props: {
+ schema: items,
+ name: index,
+ filterProperties: (schema) => {
+ if (!isOperationComponent(schema)) return false
+ return true
+ },
+ onlyRenderProperties: true,
+ },
+ },
+ {}
+ ),
+ attrs.extra,
+ ],
+ }
+ )
+ const content = h(
+ RecursionField,
+ {
+ props: {
+ schema: items,
+ name: index,
+ filterProperties: (schema) => {
+ if (isIndexComponent(schema)) return false
+ if (isOperationComponent(schema)) return false
+ return true
+ },
+ },
+ },
+ {}
+ )
+
+ return h(
+ ArrayBase.Item,
+ {
+ key: getKey(item, index),
+ props: {
+ index,
+ record: item,
+ },
+ },
+ {
+ default: () =>
+ h(
+ Card,
+ {
+ class: [`${prefixCls}-item`],
+ attrs: {
+ shadow: 'never',
+ ...attrs,
+ },
+ },
+ {
+ default: () => [content],
+ header: () =>
+ h(
+ Row,
+ {
+ props: {
+ type: 'flex',
+ justify: 'space-between',
+ },
+ },
+ {
+ default: () => [title, extra],
+ }
+ ),
+ }
+ ),
+ }
+ )
+ })
+ }
+ const renderAddition = () => {
+ return schema.reduceProperties((addition, schema) => {
+ if (isAdditionComponent(schema)) {
+ return h(
+ RecursionField,
+ {
+ props: {
+ schema,
+ name: 'addition',
+ },
+ },
+ {}
+ )
+ }
+ return addition
+ }, null)
+ }
+ const renderEmpty = () => {
+ if (dataSource?.length) return
+ return h(
+ Card,
+ {
+ class: [`${prefixCls}-item`],
+ attrs: {
+ shadow: 'never',
+ ...attrs,
+ header: attrs.title || field.title,
+ },
+ },
+ {
+ default: () =>
+ h(
+ Empty,
+ { props: { description: 'No Data', imageSize: 100 } },
+ {}
+ ),
+ }
+ )
+ }
+
+ return h(
+ 'div',
+ {
+ class: [prefixCls],
+ },
+ {
+ default: () =>
+ h(
+ ArrayBase,
+ {
+ props: {
+ keyMap,
+ },
+ },
+ {
+ default: () => [
+ renderEmpty(),
+ renderItems(),
+ renderAddition(),
+ ],
+ }
+ ),
+ }
+ )
+ }
+ },
+ })
+)
+
+export const ArrayCards = composeExport(ArrayCardsInner, {
+ Index: ArrayBase.Index,
+ SortHandle: ArrayBase.SortHandle,
+ Addition: ArrayBase.Addition,
+ Remove: ArrayBase.Remove,
+ MoveDown: ArrayBase.MoveDown,
+ MoveUp: ArrayBase.MoveUp,
+ useArray: ArrayBase.useArray,
+ useIndex: ArrayBase.useIndex,
+ useRecord: ArrayBase.useRecord,
+})
+
+export default ArrayCards
diff --git a/packages/components/src/array-cards/style.scss b/packages/components/src/array-cards/style.scss
new file mode 100644
index 0000000..3eee7f5
--- /dev/null
+++ b/packages/components/src/array-cards/style.scss
@@ -0,0 +1,33 @@
+@import '../__builtins__/styles/common.scss';
+
+$array-table-prefix-cls: '#{$formily-prefix}-array-cards';
+
+.#{$array-table-prefix-cls} {
+ .el-card__header {
+ padding-top: 12.5px;
+ padding-bottom: 12.5px;
+ }
+ .el-empty {
+ padding: 0;
+ }
+
+ .#{$array-table-prefix-cls}-item {
+ margin-bottom: 10px;
+ }
+
+ .#{$formily-prefix}-array-base-addition {
+ width: 100%;
+ border: $--border-width-base dashed $--border-color-base;
+
+ &:hover {
+ background-color: $--color-white;
+ border-color: $--border-color-hover;
+ }
+
+ &:active,
+ &:focus {
+ background-color: $--color-white;
+ border-color: $--color-primary;
+ }
+ }
+}
diff --git a/packages/components/src/array-cards/style.ts b/packages/components/src/array-cards/style.ts
new file mode 100644
index 0000000..1c1cc62
--- /dev/null
+++ b/packages/components/src/array-cards/style.ts
@@ -0,0 +1,7 @@
+import './style.scss'
+import 'element-ui/packages/theme-chalk/src/card.scss'
+import 'element-ui/packages/theme-chalk/src/empty.scss'
+import 'element-ui/packages/theme-chalk/src/row.scss'
+
+// 依赖
+import '../array-base/style'
diff --git a/packages/components/src/array-collapse/index.ts b/packages/components/src/array-collapse/index.ts
new file mode 100644
index 0000000..b4d8bca
--- /dev/null
+++ b/packages/components/src/array-collapse/index.ts
@@ -0,0 +1,388 @@
+import { defineComponent, ref, watchEffect, Ref } from '@vue/composition-api'
+import { Card, Collapse, CollapseItem, Empty, Row, Badge } from 'element-ui'
+import { ArrayField } from '@formily/core'
+import type {
+ Collapse as CollapseProps,
+ CollapseItem as CollapseItemProps,
+} from 'element-ui'
+import {
+ useField,
+ useFieldSchema,
+ RecursionField,
+ h,
+ Fragment,
+} from '@formily/vue'
+import { observer } from '@formily/reactive-vue'
+import { ISchema } from '@formily/json-schema'
+import { stylePrefix } from '../__builtins__/configs'
+import { ArrayBase } from '../array-base'
+import { composeExport } from '../__builtins__/shared'
+
+export interface IArrayCollapseProps extends CollapseProps {
+ defaultOpenPanelCount?: number
+}
+
+const isAdditionComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('Addition') > -1
+}
+
+const isIndexComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('Index') > -1
+}
+
+const isRemoveComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('Remove') > -1
+}
+
+const isMoveUpComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('MoveUp') > -1
+}
+
+const isMoveDownComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('MoveDown') > -1
+}
+
+const isOperationComponent = (schema: ISchema) => {
+ return (
+ isAdditionComponent(schema) ||
+ isRemoveComponent(schema) ||
+ isMoveDownComponent(schema) ||
+ isMoveUpComponent(schema)
+ )
+}
+
+const range = (count: number) => Array.from({ length: count }).map((_, i) => i)
+
+const takeDefaultActiveKeys = (
+ dataSourceLength: number,
+ defaultOpenPanelCount: number,
+ accordion = false
+) => {
+ if (accordion) {
+ return 0
+ }
+ if (dataSourceLength < defaultOpenPanelCount) return range(dataSourceLength)
+
+ return range(defaultOpenPanelCount)
+}
+
+const insertActiveKeys = (
+ activeKeys: number[] | number,
+ index: number,
+ accordion = false
+) => {
+ if (accordion) return index
+ if ((activeKeys as number[]).length <= index)
+ return (activeKeys as number[]).concat(index)
+ return (activeKeys as number[]).reduce((buf, key) => {
+ if (key < index) return buf.concat(key)
+ if (key === index) return buf.concat([key, key + 1])
+ return buf.concat(key + 1)
+ }, [])
+}
+
+export const ArrayCollapseInner = observer(
+ defineComponent({
+ name: 'FArrayCollapse',
+ props: {
+ defaultOpenPanelCount: {
+ type: Number,
+ default: 5,
+ },
+ },
+ setup(props, { attrs }) {
+ const fieldRef = useField()
+ const schemaRef = useFieldSchema()
+
+ const prefixCls = `${stylePrefix}-array-collapse`
+ const activeKeys: Ref = ref([])
+
+ watchEffect(() => {
+ const field = fieldRef.value
+ const dataSource = Array.isArray(field.value) ? field.value.slice() : []
+ if (!field.modified && dataSource.length) {
+ activeKeys.value = takeDefaultActiveKeys(
+ dataSource.length,
+ props.defaultOpenPanelCount,
+ attrs.accordion as boolean
+ )
+ }
+ })
+
+ const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value)
+
+ return () => {
+ const field = fieldRef.value
+ const schema = schemaRef.value
+ const dataSource = Array.isArray(field.value) ? field.value.slice() : []
+ if (!schema) throw new Error('can not found schema object')
+
+ const renderItems = () => {
+ if (!dataSource.length) {
+ return null
+ }
+
+ const items = dataSource?.map((item, index) => {
+ const items = Array.isArray(schema.items)
+ ? schema.items[index] || schema.items[0]
+ : schema.items
+ const key = getKey(item, index)
+ const panelProps = field
+ .query(`${field.address}.${index}`)
+ .get('componentProps')
+ const props: CollapseItemProps = items['x-component-props']
+ const headerTitle = panelProps?.title || props.title || field.title
+ const path = field.address.concat(index)
+ const errors = field.form.queryFeedbacks({
+ type: 'error',
+ address: `${path}.**`,
+ })
+
+ const title = h(
+ ArrayBase.Item,
+ {
+ props: {
+ index,
+ record: item,
+ },
+ },
+ {
+ default: () => [
+ h(
+ RecursionField,
+ {
+ props: {
+ schema: items,
+ name: index,
+ filterProperties: (schema) => {
+ if (!isIndexComponent(schema)) return false
+ return true
+ },
+ onlyRenderProperties: true,
+ },
+ },
+ {}
+ ),
+ errors.length
+ ? h(
+ Badge,
+ {
+ class: [`${prefixCls}-errors-badge`],
+ props: {
+ value: errors.length,
+ },
+ },
+ { default: () => headerTitle }
+ )
+ : headerTitle,
+ ],
+ }
+ )
+ const extra = h(
+ ArrayBase.Item,
+ {
+ props: {
+ index,
+ record: item,
+ },
+ },
+ {
+ default: () => [
+ h(
+ RecursionField,
+ {
+ props: {
+ schema: items,
+ name: index,
+ filterProperties: (schema) => {
+ if (!isOperationComponent(schema)) return false
+ return true
+ },
+ onlyRenderProperties: true,
+ },
+ },
+ {}
+ ),
+ ],
+ }
+ )
+ const content = h(
+ RecursionField,
+ {
+ props: {
+ schema: items,
+ name: index,
+ filterProperties: (schema) => {
+ if (isIndexComponent(schema)) return false
+ if (isOperationComponent(schema)) return false
+ return true
+ },
+ },
+ },
+ {}
+ )
+
+ return h(
+ CollapseItem,
+ {
+ attrs: {
+ ...props,
+ ...panelProps,
+ name: index,
+ },
+ key,
+ },
+ {
+ default: () => [
+ h(
+ ArrayBase.Item,
+ {
+ props: {
+ index,
+ record: item,
+ },
+ },
+ {
+ default: () => [content],
+ }
+ ),
+ ],
+ title: () =>
+ h(
+ Row,
+ {
+ style: { flex: 1 },
+ props: {
+ type: 'flex',
+ justify: 'space-between',
+ },
+ },
+ {
+ default: () => [
+ h('span', {}, { default: () => title }),
+ h('span', {}, { default: () => extra }),
+ ],
+ }
+ ),
+ }
+ )
+ })
+
+ return h(
+ Collapse,
+ {
+ class: [`${prefixCls}-item`],
+ attrs: {
+ ...attrs,
+ value: activeKeys.value,
+ },
+ on: {
+ change: (keys: number[] | number) => {
+ activeKeys.value = keys
+ },
+ },
+ },
+ {
+ default: () => [items],
+ }
+ )
+ }
+ const renderAddition = () => {
+ return schema.reduceProperties((addition, schema) => {
+ if (isAdditionComponent(schema)) {
+ return h(
+ RecursionField,
+ {
+ props: {
+ schema,
+ name: 'addition',
+ },
+ },
+ {}
+ )
+ }
+ return addition
+ }, null)
+ }
+ const renderEmpty = () => {
+ if (dataSource?.length) return
+ return h(
+ Card,
+ {
+ class: [`${prefixCls}-item`],
+ attrs: {
+ shadow: 'never',
+ ...attrs,
+ header: attrs.title || field.title,
+ },
+ },
+ {
+ default: () =>
+ h(
+ Empty,
+ { props: { description: 'No Data', imageSize: 100 } },
+ {}
+ ),
+ }
+ )
+ }
+
+ return h(
+ 'div',
+ {
+ class: [prefixCls],
+ },
+ {
+ default: () =>
+ h(
+ ArrayBase,
+ {
+ props: {
+ keyMap,
+ },
+ on: {
+ add: (index: number) => {
+ activeKeys.value = insertActiveKeys(
+ activeKeys.value,
+ index,
+ attrs.accordion as boolean
+ )
+ },
+ },
+ },
+ {
+ default: () => [
+ renderEmpty(),
+ renderItems(),
+ renderAddition(),
+ ],
+ }
+ ),
+ }
+ )
+ }
+ },
+ })
+)
+
+export const ArrayCollapseItem = defineComponent({
+ name: 'FArrayCollapseItem',
+ setup(_props, { slots }) {
+ return () => h(Fragment, {}, slots)
+ },
+})
+
+export const ArrayCollapse = composeExport(ArrayCollapseInner, {
+ Item: ArrayCollapseItem,
+ Index: ArrayBase.Index,
+ SortHandle: ArrayBase.SortHandle,
+ Addition: ArrayBase.Addition,
+ Remove: ArrayBase.Remove,
+ MoveDown: ArrayBase.MoveDown,
+ MoveUp: ArrayBase.MoveUp,
+ useArray: ArrayBase.useArray,
+ useIndex: ArrayBase.useIndex,
+ useRecord: ArrayBase.useRecord,
+})
+
+export default ArrayCollapse
diff --git a/packages/components/src/array-collapse/style.scss b/packages/components/src/array-collapse/style.scss
new file mode 100644
index 0000000..6bdd5b3
--- /dev/null
+++ b/packages/components/src/array-collapse/style.scss
@@ -0,0 +1,38 @@
+@import '../__builtins__/styles/common.scss';
+
+$array-table-prefix-cls: '#{$formily-prefix}-array-collapse';
+
+.#{$array-table-prefix-cls} {
+ .el-card__header {
+ padding-top: 12.5px;
+ padding-bottom: 12.5px;
+ }
+ .el-empty {
+ padding: 0;
+ }
+
+ .#{$array-table-prefix-cls}-item {
+ margin-bottom: 10px;
+ }
+
+ .#{$array-table-prefix-cls}-errors-badge {
+ line-height: 1;
+ vertical-align: initial;
+ }
+
+ .#{$formily-prefix}-array-base-addition {
+ width: 100%;
+ border: $--border-width-base dashed $--border-color-base;
+
+ &:hover {
+ background-color: $--color-white;
+ border-color: $--border-color-hover;
+ }
+
+ &:active,
+ &:focus {
+ background-color: $--color-white;
+ border-color: $--color-primary;
+ }
+ }
+}
diff --git a/packages/components/src/array-collapse/style.ts b/packages/components/src/array-collapse/style.ts
new file mode 100644
index 0000000..0e4da93
--- /dev/null
+++ b/packages/components/src/array-collapse/style.ts
@@ -0,0 +1,10 @@
+import './style.scss'
+import 'element-ui/packages/theme-chalk/src/empty.scss'
+import 'element-ui/packages/theme-chalk/src/row.scss'
+import 'element-ui/packages/theme-chalk/src/collapse.scss'
+import 'element-ui/packages/theme-chalk/src/collapse-item.scss'
+import 'element-ui/packages/theme-chalk/src/card.scss'
+import 'element-ui/packages/theme-chalk/src/badge.scss'
+
+// 依赖
+import '../array-base/style'
diff --git a/packages/components/src/array-items/index.ts b/packages/components/src/array-items/index.ts
new file mode 100644
index 0000000..a2f97fb
--- /dev/null
+++ b/packages/components/src/array-items/index.ts
@@ -0,0 +1,182 @@
+import { defineComponent } from '@vue/composition-api'
+import { ArrayField } from '@formily/core'
+import { useField, useFieldSchema, RecursionField, h } from '@formily/vue'
+import { observer } from '@formily/reactive-vue'
+import { ISchema } from '@formily/json-schema'
+import { stylePrefix } from '../__builtins__/configs'
+import { ArrayBase } from '../array-base'
+import { SlickList, SlickItem } from 'vue-slicksort'
+import { composeExport } from '../__builtins__/shared'
+
+const isAdditionComponent = (schema: ISchema) => {
+ return schema['x-component']?.indexOf('Addition') > -1
+}
+
+export interface IArrayItemsItemProps {
+ type?: 'card' | 'divide'
+}
+
+const ArrayItemsInner = observer(
+ defineComponent({
+ name: 'FArrayItems',
+ setup(props, { attrs }) {
+ const fieldRef = useField()
+ const schemaRef = useFieldSchema()
+
+ const prefixCls = `${stylePrefix}-array-items`
+ const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value)
+
+ return () => {
+ const field = fieldRef.value
+ const schema = schemaRef.value
+ const dataSource = Array.isArray(field.value) ? field.value.slice() : []
+
+ const renderItems = () => {
+ const items = dataSource?.map((item, index) => {
+ const items = Array.isArray(schema.items)
+ ? schema.items[index] || schema.items[0]
+ : schema.items
+ const key = getKey(item, index)
+ return h(
+ ArrayBase.Item,
+ {
+ key,
+ props: {
+ index,
+ record: item,
+ },
+ },
+ {
+ default: () =>
+ h(
+ SlickItem,
+ {
+ class: [`${prefixCls}-item-inner`],
+ props: {
+ index,
+ },
+ key,
+ },
+ {
+ default: () =>
+ h(
+ RecursionField,
+ {
+ props: {
+ schema: items,
+ name: index,
+ },
+ },
+ {}
+ ),
+ }
+ ),
+ }
+ )
+ })
+
+ return h(
+ SlickList,
+ {
+ class: [`${prefixCls}-list`],
+ props: {
+ useDragHandle: true,
+ lockAxis: 'y',
+ helperClass: `${prefixCls}-sort-helper`,
+ value: [],
+ },
+ on: {
+ 'sort-end': ({ oldIndex, newIndex }) => {
+ if (Array.isArray(keyMap)) {
+ keyMap.splice(newIndex, 0, keyMap.splice(oldIndex, 1)[0])
+ }
+ field.move(oldIndex, newIndex)
+ },
+ },
+ },
+ { default: () => items }
+ )
+ }
+ const renderAddition = () => {
+ return schema.reduceProperties((addition, schema) => {
+ if (isAdditionComponent(schema)) {
+ return h(
+ RecursionField,
+ {
+ props: {
+ schema,
+ name: 'addition',
+ },
+ },
+ {}
+ )
+ }
+ return addition
+ }, null)
+ }
+
+ return h(
+ ArrayBase,
+ {
+ props: {
+ keyMap,
+ },
+ },
+ {
+ default: () =>
+ h(
+ 'div',
+ {
+ class: [prefixCls],
+ on: {
+ change: () => {},
+ },
+ },
+ {
+ default: () => [renderItems(), renderAddition()],
+ }
+ ),
+ }
+ )
+ }
+ },
+ })
+)
+
+const ArrayItemsItem = defineComponent({
+ name: 'FArrayItemsItem',
+ props: ['type'],
+ setup(props, { attrs, slots }) {
+ const prefixCls = `${stylePrefix}-array-items`
+
+ return () =>
+ h(
+ 'div',
+ {
+ class: [`${prefixCls}-${props.type || 'card'}`],
+ attrs: {
+ ...attrs,
+ },
+ on: {
+ change: () => {},
+ },
+ },
+ slots
+ )
+ },
+})
+
+export const ArrayItems = composeExport(ArrayItemsInner, {
+ Item: ArrayItemsItem,
+ Index: ArrayBase.Index,
+ SortHandle: ArrayBase.SortHandle,
+ Addition: ArrayBase.Addition,
+ Remove: ArrayBase.Remove,
+ MoveDown: ArrayBase.MoveDown,
+ MoveUp: ArrayBase.MoveUp,
+ useArray: ArrayBase.useArray,
+ useIndex: ArrayBase.useIndex,
+ useRecord: ArrayBase.useRecord,
+})
+
+export default ArrayItems
diff --git a/packages/components/src/array-items/style.scss b/packages/components/src/array-items/style.scss
new file mode 100644
index 0000000..cdb22e4
--- /dev/null
+++ b/packages/components/src/array-items/style.scss
@@ -0,0 +1,75 @@
+@import '../__builtins__/styles/common.scss';
+
+$array-items-prefix-cls: '#{$formily-prefix}-array-items';
+
+.#{$array-items-prefix-cls}-item-inner {
+ visibility: visible;
+}
+
+.#{$array-items-prefix-cls} {
+ .#{$formily-prefix}-array-base-addition {
+ width: 100%;
+ border: $--border-width-base dashed $--border-color-base;
+
+ &:hover {
+ background-color: $--color-white;
+ border-color: $--border-color-hover;
+ }
+
+ &:active,
+ &:focus {
+ background-color: $--color-white;
+ border-color: $--color-primary;
+ }
+ }
+}
+
+.#{$array-items-prefix-cls}-card {
+ display: flex;
+ border: 1px solid $--card-border-color;
+ margin-bottom: 10px;
+ padding: 3px 6px;
+ background: $--color-white;
+ justify-content: space-between;
+
+ .#{$formily-prefix}-form-item:not(.#{$formily-prefix}-form-item-feedback-layout-popover) {
+ margin-bottom: 0 !important;
+
+ .#{$formily-prefix}-form-item-help {
+ position: absolute;
+ font-size: 12px;
+ top: 100%;
+ background: $--color-white;
+ width: 100%;
+ margin-top: 3px;
+ padding: 3px;
+ z-index: 1;
+ border-radius: 3px;
+ box-shadow: 0 0 10px $--border-color-base;
+ }
+ }
+}
+
+.#{$array-items-prefix-cls}-divide {
+ display: flex;
+ border-bottom: 1px solid $--card-border-color;
+ padding: 10px 0;
+ justify-content: space-between;
+
+ .#{$formily-prefix}-form-item:not(.#{$formily-prefix}-form-item-feedback-layout-popover) {
+ margin-bottom: 0 !important;
+
+ .#{$formily-prefix}-form-item-help {
+ position: absolute;
+ font-size: 12px;
+ top: 100%;
+ background: $--color-white;
+ width: 100%;
+ margin-top: 3px;
+ padding: 3px;
+ z-index: 1;
+ border-radius: 3px;
+ box-shadow: 0 0 10px $--card-border-color;
+ }
+ }
+}
diff --git a/packages/components/src/array-items/style.ts b/packages/components/src/array-items/style.ts
new file mode 100644
index 0000000..21fefe9
--- /dev/null
+++ b/packages/components/src/array-items/style.ts
@@ -0,0 +1,4 @@
+import './style.scss'
+
+// 依赖
+import '../array-base/style'
diff --git a/packages/components/src/array-table/index.ts b/packages/components/src/array-table/index.ts
new file mode 100644
index 0000000..8164c8f
--- /dev/null
+++ b/packages/components/src/array-table/index.ts
@@ -0,0 +1,570 @@
+import {
+ defineComponent,
+ onBeforeUnmount,
+ ref,
+ Ref,
+ shallowRef,
+} from '@vue/composition-api'
+import { observe } from '@formily/reactive'
+import {
+ GeneralField,
+ IVoidFieldFactoryProps,
+ FieldDisplayTypes,
+ ArrayField,
+} from '@formily/core'
+import {
+ useField,
+ useFieldSchema,
+ RecursionField as _RecursionField,
+ h,
+ Fragment,
+ useForm,
+} from '@formily/vue'
+import { observer } from '@formily/reactive-vue'
+import { FormPath, isArr, isBool } from '@formily/shared'
+import { ArrayBase } from '../array-base'
+import { stylePrefix } from '../__builtins__/configs'
+import { composeExport } from '../__builtins__/shared'
+import type { Schema } from '@formily/json-schema'
+import type {
+ Table as TableProps,
+ TableColumn as ElColumnProps,
+ Pagination as PaginationProps,
+} from 'element-ui'
+import type { VNode, Component } from 'vue'
+import {
+ Table as ElTable,
+ TableColumn as ElTableColumn,
+ Pagination,
+ Select,
+ Option,
+ Badge,
+} from 'element-ui'
+import { Space } from '../space'
+
+const RecursionField = _RecursionField as unknown as Component
+
+interface IArrayTableProps extends TableProps {
+ pagination?: PaginationProps | boolean
+}
+interface IArrayTablePaginationProps extends PaginationProps {
+ dataSource?: any[]
+}
+
+interface ObservableColumnSource {
+ field: GeneralField
+ fieldProps: IVoidFieldFactoryProps
+ columnProps: ElColumnProps & { title: string; asterisk: boolean }
+ schema: Schema
+ display: FieldDisplayTypes
+ required: boolean
+ name: string
+}
+
+type ColumnProps = ElColumnProps & {
+ key: string | number
+ asterisk: boolean
+ render?: (props: {
+ row: Record
+ column: ElColumnProps
+ $index: number
+ }) => VNode
+}
+
+const isColumnComponent = (schema: Schema) => {
+ return schema['x-component']?.indexOf('Column') > -1
+}
+
+const isOperationsComponent = (schema: Schema) => {
+ return schema['x-component']?.indexOf('Operations') > -1
+}
+
+const isAdditionComponent = (schema: Schema) => {
+ return schema['x-component']?.indexOf('Addition') > -1
+}
+
+const getArrayTableSources = (
+ arrayFieldRef: Ref,
+ schemaRef: Ref
+) => {
+ const arrayField = arrayFieldRef.value
+ const parseSources = (schema: Schema): ObservableColumnSource[] => {
+ if (
+ isColumnComponent(schema) ||
+ isOperationsComponent(schema) ||
+ isAdditionComponent(schema)
+ ) {
+ if (!schema['x-component-props']?.['prop'] && !schema['name']) return []
+ const name = schema['x-component-props']?.['prop'] || schema['name']
+ const field = arrayField.query(arrayField.address.concat(name)).take()
+ const fieldProps = field?.props || schema.toFieldProps()
+ const columnProps =
+ (field?.component as any[])?.[1] || schema['x-component-props'] || {}
+ const display = field?.display || schema['x-display']
+ const required = schema.reduceProperties((required, property) => {
+ if (required) {
+ return required
+ }
+ return !!property.required
+ }, false)
+
+ return [
+ {
+ name,
+ display,
+ required,
+ field,
+ fieldProps,
+ schema,
+ columnProps,
+ },
+ ]
+ } else if (schema.properties) {
+ return schema.reduceProperties((buf: any[], schema) => {
+ return buf.concat(parseSources(schema))
+ }, [])
+ } else {
+ return []
+ }
+ }
+
+ const parseArrayTable = (schema: Schema['items']) => {
+ if (!schema) return []
+ const sources: ObservableColumnSource[] = []
+ const items = isArr(schema) ? schema : ([schema] as Schema[])
+ return items.reduce((columns, schema) => {
+ const item = parseSources(schema)
+ if (item) {
+ return columns.concat(item)
+ }
+ return columns
+ }, sources)
+ }
+
+ if (!schemaRef.value) throw new Error('can not found schema object')
+
+ return parseArrayTable(schemaRef.value.items)
+}
+
+const getArrayTableColumns = (
+ reactiveDataSource: Ref,
+ sources: ObservableColumnSource[]
+): ColumnProps[] => {
+ return sources.reduce(
+ (
+ buf: ColumnProps[],
+ { name, columnProps, schema, display, required },
+ key
+ ) => {
+ const { title, asterisk, ...props } = columnProps
+ if (display !== 'visible') return buf
+ if (!isColumnComponent(schema)) return buf
+
+ const render =
+ columnProps?.type && columnProps?.type !== 'default'
+ ? undefined
+ : (props: {
+ row: Record
+ column: ElColumnProps
+ $index: number
+ }): VNode => {
+ // let index = props.$index
+ const index = reactiveDataSource.value.indexOf(props.row)
+
+ const children = h(
+ ArrayBase.Item,
+ { props: { index, record: props.row }, key: `${key}${index}` },
+ {
+ default: () =>
+ h(
+ RecursionField,
+ {
+ props: {
+ schema,
+ name: index,
+ onlyRenderProperties: true,
+ },
+ },
+ {}
+ ),
+ }
+ )
+ return children
+ }
+ return buf.concat({
+ label: title,
+ ...props,
+ key,
+ prop: name,
+ asterisk: asterisk ?? required,
+ render,
+ })
+ },
+ []
+ )
+}
+
+const renderAddition = () => {
+ const schema = useFieldSchema()
+ return schema.value.reduceProperties((addition, schema) => {
+ if (isAdditionComponent(schema)) {
+ return h(
+ RecursionField,
+ {
+ props: {
+ schema,
+ name: 'addition',
+ },
+ },
+ {}
+ )
+ }
+ return addition
+ }, null)
+}
+
+const StatusSelect = observer(
+ defineComponent({
+ props: {
+ value: Number,
+ onChange: Function,
+ options: Array,
+ pageSize: Number,
+ },
+ setup(props) {
+ const formRef = useForm()
+ const fieldRef = useField()
+ const prefixCls = `${stylePrefix}-array-table`
+ const width = String(props.options?.length).length * 15
+
+ return () => {
+ const form = formRef.value
+ const field = fieldRef.value
+
+ const errors = form.queryFeedbacks({
+ type: 'error',
+ address: `${field.address}.*`,
+ })
+ const createIndexPattern = (page: number) => {
+ const pattern = `${field.address}.*[${(page - 1) * props.pageSize}:${
+ page * props.pageSize
+ }].*`
+ return FormPath.parse(pattern)
+ }
+
+ return h(
+ Select,
+ {
+ style: {
+ width: `${width < 60 ? 60 : width}px`,
+ },
+ class: [
+ `${prefixCls}-status-select`,
+ {
+ 'has-error': errors?.length,
+ },
+ ],
+ props: {
+ value: props.value,
+ popperClass: `${prefixCls}-status-select-dropdown`,
+ },
+ on: {
+ input: props.onChange,
+ },
+ },
+ {
+ default: () => {
+ return props.options?.map(({ label, value }) => {
+ const hasError = errors.some(({ address }) => {
+ return createIndexPattern(value).match(address)
+ })
+
+ return h(
+ Option,
+ {
+ key: value,
+ props: {
+ label,
+ value,
+ },
+ },
+ {
+ default: () => {
+ if (hasError) {
+ return h(
+ Badge,
+ {
+ props: {
+ isDot: true,
+ },
+ },
+ { default: () => label }
+ )
+ }
+
+ return label
+ },
+ }
+ )
+ })
+ },
+ }
+ )
+ }
+ },
+ })
+)
+
+const ArrayTablePagination = defineComponent({
+ inheritAttrs: false,
+ props: [],
+ setup(props, { attrs, slots }) {
+ const prefixCls = `${stylePrefix}-array-table`
+ const current = ref(1)
+ return () => {
+ const props = attrs as unknown as IArrayTablePaginationProps
+ const pageSize = props.pageSize || 10
+ const dataSource = props.dataSource || []
+ const startIndex = (current.value - 1) * pageSize
+ const endIndex = startIndex + pageSize - 1
+ const total = dataSource?.length || 0
+ const totalPage = Math.ceil(total / pageSize)
+ const pages = Array.from(new Array(totalPage)).map((_, index) => {
+ const page = index + 1
+ return {
+ label: page,
+ value: page,
+ }
+ })
+
+ const renderPagination = function () {
+ if (totalPage <= 1) return
+ return h(
+ 'div',
+ {
+ class: [`${prefixCls}-pagination`],
+ },
+ {
+ default: () =>
+ h(
+ Space,
+ {},
+ {
+ default: () => [
+ h(
+ StatusSelect,
+ {
+ props: {
+ value: current.value,
+ onChange: (val: number) => {
+ current.value = val
+ },
+ pageSize,
+ options: pages,
+ },
+ },
+ {}
+ ),
+ h(
+ Pagination,
+ {
+ props: {
+ background: true,
+ layout: 'prev, pager, next',
+ ...props,
+ pageSize,
+ pageCount: totalPage,
+ currentPage: current.value,
+ },
+ on: {
+ 'current-change': (val: number) => {
+ current.value = val
+ },
+ },
+ },
+ {}
+ ),
+ ],
+ }
+ ),
+ }
+ )
+ }
+
+ return h(
+ Fragment,
+ {},
+ {
+ default: () =>
+ slots?.default?.(
+ dataSource?.slice(startIndex, endIndex + 1),
+ renderPagination
+ ),
+ }
+ )
+ }
+ },
+})
+
+const ArrayTableInner = observer(
+ defineComponent({
+ name: 'FArrayTable',
+ inheritAttrs: false,
+ setup(props, { attrs, listeners, slots }) {
+ const fieldRef = useField()
+ const schemaRef = useFieldSchema()
+ const prefixCls = `${stylePrefix}-array-table`
+ const { getKey, keyMap } = ArrayBase.useKey(schemaRef.value)
+
+ const defaultRowKey = (record: any) => {
+ return getKey(record)
+ }
+ const reactiveDataSource = shallowRef([])
+
+ const dispose = observe(
+ fieldRef.value,
+ () => {
+ reactiveDataSource.value = fieldRef.value.value
+ },
+ false
+ )
+
+ onBeforeUnmount(dispose)
+
+ return () => {
+ const props = attrs as unknown as IArrayTableProps
+ const field = fieldRef.value
+ const dataSource = Array.isArray(field.value) ? field.value.slice() : []
+ const pagination = props.pagination
+ const sources = getArrayTableSources(fieldRef, schemaRef)
+ const columns = getArrayTableColumns(reactiveDataSource, sources)
+
+ const renderColumns = () =>
+ columns.map(({ key, render, asterisk, ...props }) => {
+ const children = {} as Record
+ if (render) {
+ children.default = render
+ }
+ if (asterisk) {
+ children.header = ({ column }: { column: ElColumnProps }) =>
+ h(
+ 'span',
+ {},
+ {
+ default: () => [
+ h(
+ 'span',
+ { class: `${prefixCls}-asterisk` },
+ { default: () => ['*'] }
+ ),
+ column.label,
+ ],
+ }
+ )
+ }
+ return h(
+ ElTableColumn,
+ {
+ key,
+ props,
+ },
+ children
+ )
+ })
+ const renderStateManager = () =>
+ sources.map((column, key) => {
+ //专门用来承接对Column的状态管理
+ if (!isColumnComponent(column.schema)) return
+ return h(
+ RecursionField,
+ {
+ props: {
+ name: column.name,
+ schema: column.schema,
+ onlyRenderSelf: true,
+ },
+ key,
+ },
+ {}
+ )
+ })
+
+ const renderTable = (dataSource?: any[], pager?: () => VNode) => {
+ return h(
+ 'div',
+ { class: prefixCls },
+ {
+ default: () =>
+ h(
+ ArrayBase,
+ {
+ props: {
+ keyMap,
+ },
+ },
+ {
+ default: () => [
+ h(
+ ElTable,
+ {
+ props: {
+ rowKey: defaultRowKey,
+ ...attrs,
+ data: dataSource,
+ },
+ on: listeners,
+ },
+ {
+ ...slots,
+ default: renderColumns,
+ }
+ ),
+ pager?.(),
+ renderStateManager(),
+ renderAddition(),
+ ],
+ }
+ ),
+ }
+ )
+ }
+
+ if (!pagination) {
+ return renderTable(dataSource, null)
+ }
+ return h(
+ ArrayTablePagination,
+ {
+ attrs: {
+ ...(isBool(pagination) ? {} : pagination),
+ dataSource,
+ },
+ },
+ { default: renderTable }
+ )
+ }
+ },
+ })
+)
+
+const ArrayTableColumn: Component = {
+ name: 'FArrayTableColumn',
+ render(h) {
+ return h()
+ },
+}
+
+export const ArrayTable = composeExport(ArrayTableInner, {
+ Column: ArrayTableColumn,
+ Index: ArrayBase.Index,
+ SortHandle: ArrayBase.SortHandle,
+ Addition: ArrayBase.Addition,
+ Remove: ArrayBase.Remove,
+ MoveDown: ArrayBase.MoveDown,
+ MoveUp: ArrayBase.MoveUp,
+ useArray: ArrayBase.useArray,
+ useIndex: ArrayBase.useIndex,
+ useRecord: ArrayBase.useRecord,
+})
+
+export default ArrayTable
diff --git a/packages/components/src/array-table/style.scss b/packages/components/src/array-table/style.scss
new file mode 100644
index 0000000..c7d7c45
--- /dev/null
+++ b/packages/components/src/array-table/style.scss
@@ -0,0 +1,90 @@
+@import '../__builtins__/styles/common.scss';
+
+$array-table-prefix-cls: '#{$formily-prefix}-array-table';
+
+.#{$array-table-prefix-cls} {
+ .#{$formily-prefix}-form-item:not(.#{$formily-prefix}-form-item-feedback-layout-popover) {
+ margin-bottom: 0 !important;
+ }
+
+ &-status-select-dropdown {
+ .#{$namespace}-badge {
+ line-height: 1;
+ }
+ }
+
+ &-pagination {
+ display: flex;
+ justify-content: center;
+ margin-top: 8px;
+
+ .#{$array-table-prefix-cls}-status-select.has-error {
+ .#{$namespace}-input__inner {
+ border-color: $--color-danger !important;
+ }
+ }
+ }
+
+ .#{$namespace}-table {
+ overflow: visible;
+
+ &__body-wrapper {
+ overflow: visible;
+ }
+
+ .cell {
+ overflow: visible;
+ }
+
+ .cell.el-tooltip {
+ overflow: hidden;
+ }
+
+ &__fixed {
+ box-shadow: 10px 0 10px -10px rgb(0 0 0 / 12%);
+ }
+
+ &__fixed-right {
+ box-shadow: -10px 0 10px -10px rgb(0 0 0 / 12%);
+ }
+ }
+
+ .#{$formily-prefix}-form-item-help {
+ position: absolute;
+ font-size: 12px;
+ top: 100%;
+ background: #fff;
+ width: 100%;
+ margin-top: 3px;
+ padding: 3px;
+ z-index: 2;
+ border-radius: 3px;
+ box-shadow: 0 0 10px #eee;
+ }
+
+ .#{$formily-prefix}-array-base-addition {
+ margin-top: 8px;
+ width: 100%;
+ border: $--border-width-base dashed $--border-color-base;
+
+ &:hover {
+ background-color: $--color-white;
+ border-color: $--border-color-hover;
+ }
+
+ &:active,
+ &:focus {
+ background-color: $--color-white;
+ border-color: $--color-primary;
+ }
+ }
+
+ .#{$formily-prefix}-form-item-feedback-layout-popover {
+ margin-bottom: 0;
+ }
+
+ &-inner-asterisk {
+ color: $--color-danger;
+ font-weight: $--font-weight-primary;
+ }
+}
diff --git a/packages/components/src/array-table/style.ts b/packages/components/src/array-table/style.ts
new file mode 100644
index 0000000..f003c7f
--- /dev/null
+++ b/packages/components/src/array-table/style.ts
@@ -0,0 +1,11 @@
+import './style.scss'
+
+import 'element-ui/packages/theme-chalk/src/table.scss'
+import 'element-ui/packages/theme-chalk/src/table-column.scss'
+import 'element-ui/packages/theme-chalk/src/button.scss'
+import 'element-ui/packages/theme-chalk/src/select.scss'
+import 'element-ui/packages/theme-chalk/src/badge.scss'
+
+// 依赖
+import '../array-base/style'
+import '../space/style'
diff --git a/packages/components/src/array-tabs/index.ts b/packages/components/src/array-tabs/index.ts
new file mode 100644
index 0000000..089b9c2
--- /dev/null
+++ b/packages/components/src/array-tabs/index.ts
@@ -0,0 +1,150 @@
+import { defineComponent, ref } from '@vue/composition-api'
+import { observer } from '@formily/reactive-vue'
+import { ArrayField } from '@formily/core'
+import { h, useField, useFieldSchema, RecursionField } from '@formily/vue'
+import { Tabs, TabPane, Badge } from 'element-ui'
+import { stylePrefix } from '../__builtins__/configs'
+
+import type { Tabs as TabsProps } from 'element-ui'
+
+export const ArrayTabs = observer(
+ defineComponent({
+ name: 'ArrayTabs',
+ props: [],
+ setup(props, { attrs, listeners }) {
+ const fieldRef = useField()
+ const schemaRef = useFieldSchema()
+
+ const prefixCls = `${stylePrefix}-array-tabs`
+ const activeKey = ref('tab-0')
+
+ return () => {
+ const field = fieldRef.value
+ const schema = schemaRef.value
+ const value = Array.isArray(field.value) ? field.value : []
+ const dataSource = value?.length ? value : [{}]
+
+ const onEdit = (targetKey: any, type: 'add' | 'remove') => {
+ if (type == 'add') {
+ const id = dataSource.length
+ if (field?.value?.length) {
+ field.push(null)
+ } else {
+ field.push(null, null)
+ }
+ activeKey.value = `tab-${id}`
+ } else if (type == 'remove') {
+ const index = targetKey.match(/-(\d+)/)?.[1]
+ field.remove(Number(index))
+ if (activeKey.value === targetKey) {
+ activeKey.value = `tab-${index - 1}`
+ }
+ }
+ }
+
+ const badgedTab = (index: number) => {
+ const tab = `${field.title || 'Untitled'} ${index + 1}`
+ const path = field.address.concat(index)
+ const errors = field.form.queryFeedbacks({
+ type: 'error',
+ address: `${path}.**`,
+ })
+ if (errors.length) {
+ return h(
+ 'span',
+ {},
+ {
+ default: () => [
+ h(
+ Badge,
+ {
+ class: [`${prefixCls}-errors-badge`],
+ props: {
+ value: errors.length,
+ },
+ },
+ {
+ default: () => [tab],
+ }
+ ),
+ ],
+ }
+ )
+ }
+ return h(
+ 'span',
+ {},
+ {
+ default: () => [tab],
+ }
+ )
+ }
+
+ const renderItems = () =>
+ dataSource?.map((item, index) => {
+ const items = Array.isArray(schema.items)
+ ? schema.items[index]
+ : schema.items
+ const key = `tab-${index}`
+
+ return h(
+ TabPane,
+ {
+ key,
+ attrs: {
+ closable: index !== 0,
+ name: key,
+ },
+ },
+ {
+ default: () =>
+ h(
+ RecursionField,
+ {
+ props: {
+ schema: items,
+ name: index,
+ },
+ },
+ {}
+ ),
+
+ label: () => [badgedTab(index)],
+ }
+ )
+ })
+ return h(
+ Tabs,
+ {
+ class: [prefixCls],
+ attrs: {
+ ...attrs,
+ type: 'card',
+ value: activeKey.value,
+ addable: true,
+ },
+ on: {
+ ...listeners,
+ input: (key) => {
+ activeKey.value = key
+ },
+ 'tab-remove': (target) => {
+ onEdit(target, 'remove')
+ listeners?.['tab-remove']?.(target)
+ },
+ 'tab-add': () => {
+ onEdit(null, 'add')
+ listeners?.['tab-add']?.()
+ },
+ },
+ },
+ {
+ default: () => [renderItems()],
+ }
+ )
+ }
+ },
+ })
+)
+
+export default ArrayTabs
diff --git a/packages/components/src/array-tabs/style.scss b/packages/components/src/array-tabs/style.scss
new file mode 100644
index 0000000..41162cc
--- /dev/null
+++ b/packages/components/src/array-tabs/style.scss
@@ -0,0 +1,16 @@
+@import '../__builtins__/styles/common.scss';
+
+$array-table-prefix-cls: '#{$formily-prefix}-array-tabs';
+
+.#{$array-table-prefix-cls} {
+ .#{$formily-prefix}-array-tabs-addition {
+ position: absolute;
+ right: -56px;
+ top: -1px;
+ }
+
+ .#{$array-table-prefix-cls}-errors-badge {
+ line-height: 1;
+ vertical-align: initial;
+ }
+}
diff --git a/packages/components/src/array-tabs/style.ts b/packages/components/src/array-tabs/style.ts
new file mode 100644
index 0000000..e25a729
--- /dev/null
+++ b/packages/components/src/array-tabs/style.ts
@@ -0,0 +1,5 @@
+import './style.scss'
+import 'element-ui/packages/theme-chalk/src/tabs.scss'
+import 'element-ui/packages/theme-chalk/src/tab-pane.scss'
+import 'element-ui/packages/theme-chalk/src/badge.scss'
+import 'element-ui/packages/theme-chalk/src/button.scss'
diff --git a/packages/components/src/cascader/index.ts b/packages/components/src/cascader/index.ts
new file mode 100644
index 0000000..0612430
--- /dev/null
+++ b/packages/components/src/cascader/index.ts
@@ -0,0 +1,15 @@
+import { connect, mapProps, mapReadPretty } from '@formily/vue'
+import { Cascader as ELCascader } from 'element-ui'
+
+import type { Cascader as ElCascaderProps } from 'element-ui'
+import { PreviewText } from '../preview-text'
+
+export type CascaderProps = ElCascaderProps
+
+export const Cascader = connect(
+ ELCascader,
+ mapProps({ dataSource: 'options' }),
+ mapReadPretty(PreviewText.Cascader)
+)
+
+export default Cascader
diff --git a/packages/components/src/cascader/style.ts b/packages/components/src/cascader/style.ts
new file mode 100644
index 0000000..1912c32
--- /dev/null
+++ b/packages/components/src/cascader/style.ts
@@ -0,0 +1,4 @@
+import 'element-ui/packages/theme-chalk/src/cascader.scss'
+
+// 依赖
+import '../preview-text/style'
diff --git a/packages/components/src/checkbox/index.ts b/packages/components/src/checkbox/index.ts
new file mode 100644
index 0000000..d014470
--- /dev/null
+++ b/packages/components/src/checkbox/index.ts
@@ -0,0 +1,171 @@
+import { connect, mapProps, h, mapReadPretty } from '@formily/vue'
+import { defineComponent, PropType } from '@vue/composition-api'
+import {
+ composeExport,
+ transformComponent,
+ resolveComponent,
+ SlotTypes,
+} from '../__builtins__/shared'
+import type {
+ Checkbox as _ElCheckboxProps,
+ CheckboxGroup as ElCheckboxGroupProps,
+} from 'element-ui'
+import {
+ Checkbox as ElCheckbox,
+ CheckboxGroup as ElCheckboxGroup,
+ CheckboxButton as ElCheckboxButton,
+} from 'element-ui'
+import { PreviewText } from '../preview-text'
+
+type ElCheckboxProps = Omit<_ElCheckboxProps, 'value'> & {
+ value: ElCheckboxProps['label']
+}
+
+export interface CheckboxProps extends ElCheckboxProps {
+ option: Omit<_ElCheckboxProps, 'value'> & {
+ value: ElCheckboxProps['label']
+ label: SlotTypes
+ }
+}
+
+const CheckboxOption = defineComponent({
+ name: 'Checkbox',
+ inheritAttrs: false,
+ props: {
+ option: {
+ type: Object,
+ default: null,
+ },
+ },
+ setup(curtomProps, { attrs, slots, listeners }) {
+ return () => {
+ const props = attrs as unknown as CheckboxProps
+ const option = curtomProps?.option
+ if (option) {
+ const children = {
+ default: () => [
+ resolveComponent(slots.default ?? option.label, { option }),
+ ],
+ }
+ const newProps = {} as Partial
+ Object.assign(newProps, option)
+ newProps.label = option.value
+ delete newProps.value
+
+ return h(
+ attrs.optionType === 'button' ? ElCheckboxButton : ElCheckbox,
+ {
+ attrs: {
+ ...newProps,
+ },
+ },
+ children
+ )
+ }
+
+ return h(
+ ElCheckbox,
+ {
+ attrs: {
+ ...props,
+ },
+ on: listeners,
+ },
+ slots
+ )
+ }
+ },
+})
+
+export type CheckboxGroupProps = ElCheckboxGroupProps & {
+ value: any[]
+ options?: Array
+ optionType: 'default' | 'button'
+}
+
+const TransformElCheckboxGroup = transformComponent(ElCheckboxGroup, {
+ change: 'input',
+})
+
+const CheckboxGroupOption = defineComponent({
+ name: 'CheckboxGroup',
+ props: {
+ options: {
+ type: Array,
+ default: () => [],
+ },
+ optionType: {
+ type: String as PropType,
+ default: 'default',
+ },
+ },
+ setup(customProps, { attrs, slots, listeners }) {
+ return () => {
+ const options = customProps.options || []
+ const children =
+ options.length !== 0
+ ? {
+ default: () =>
+ options.map((option) => {
+ if (typeof option === 'string') {
+ return h(
+ Checkbox,
+ {
+ props: {
+ option: {
+ label: option,
+ value: option,
+ },
+ },
+ attrs: {
+ optionType: customProps.optionType,
+ },
+ },
+ slots?.option
+ ? { default: () => slots.option({ option }) }
+ : {}
+ )
+ } else {
+ return h(
+ Checkbox,
+ {
+ props: {
+ option,
+ },
+ attrs: {
+ optionType: customProps.optionType,
+ },
+ },
+ slots?.option
+ ? { default: () => slots.option({ option }) }
+ : {}
+ )
+ }
+ }),
+ }
+ : slots
+ return h(
+ TransformElCheckboxGroup,
+ {
+ attrs: {
+ ...attrs,
+ },
+ on: listeners,
+ },
+ children
+ )
+ }
+ },
+})
+
+const CheckboxGroup = connect(
+ CheckboxGroupOption,
+ mapProps({ dataSource: 'options' }),
+ mapReadPretty(PreviewText.Select, {
+ multiple: true,
+ })
+)
+
+export const Checkbox = composeExport(connect(CheckboxOption), {
+ Group: CheckboxGroup,
+})
diff --git a/packages/components/src/checkbox/style.ts b/packages/components/src/checkbox/style.ts
new file mode 100644
index 0000000..7683643
--- /dev/null
+++ b/packages/components/src/checkbox/style.ts
@@ -0,0 +1,5 @@
+import 'element-ui/packages/theme-chalk/src/checkbox.scss'
+import 'element-ui/packages/theme-chalk/src/checkbox-group.scss'
+import 'element-ui/packages/theme-chalk/src/checkbox-button.scss'
+// 依赖
+import '../preview-text/style'
diff --git a/packages/components/src/date-picker/index.ts b/packages/components/src/date-picker/index.ts
new file mode 100644
index 0000000..eaab986
--- /dev/null
+++ b/packages/components/src/date-picker/index.ts
@@ -0,0 +1,45 @@
+import { transformComponent } from '../__builtins__/shared'
+import { connect, mapProps, mapReadPretty } from '@formily/vue'
+
+import type { DatePicker as ElDatePickerProps } from 'element-ui'
+import { DatePicker as ElDatePicker } from 'element-ui'
+import { PreviewText } from '../preview-text'
+
+export type DatePickerProps = ElDatePickerProps
+
+const TransformElDatePicker = transformComponent(
+ ElDatePicker,
+ {
+ change: 'input',
+ }
+)
+
+const getDefaultFormat = (props, formatType = 'format') => {
+ const type = props.type
+
+ if (type === 'week' && formatType === 'format') {
+ return 'yyyy-WW'
+ } else if (type === 'month') {
+ return 'yyyy-MM'
+ } else if (type === 'year') {
+ return 'yyyy'
+ } else if (type === 'datetime' || type === 'datetimerange') {
+ return 'yyyy-MM-dd HH:mm:ss'
+ }
+
+ return 'yyyy-MM-dd'
+}
+
+export const DatePicker = connect(
+ TransformElDatePicker,
+ mapProps({ readOnly: 'readonly' }, (props) => {
+ return {
+ ...props,
+ format: props.format || getDefaultFormat(props),
+ valueFormat: props.valueFormat || getDefaultFormat(props, 'valueFormat'),
+ }
+ }),
+ mapReadPretty(PreviewText.DatePicker)
+)
+
+export default DatePicker
diff --git a/packages/components/src/date-picker/style.ts b/packages/components/src/date-picker/style.ts
new file mode 100644
index 0000000..3442141
--- /dev/null
+++ b/packages/components/src/date-picker/style.ts
@@ -0,0 +1,4 @@
+import 'element-ui/packages/theme-chalk/src/date-picker.scss'
+
+// 依赖
+import '../preview-text/style'
diff --git a/packages/components/src/editable/index.ts b/packages/components/src/editable/index.ts
new file mode 100644
index 0000000..e0de10f
--- /dev/null
+++ b/packages/components/src/editable/index.ts
@@ -0,0 +1,274 @@
+import { defineComponent, ref, onBeforeUnmount } from '@vue/composition-api'
+import { observer } from '@formily/reactive-vue'
+import { reaction } from '@formily/reactive'
+import { isVoidField, Field } from '@formily/core'
+import { h, useField } from '@formily/vue'
+import { Popover } from 'element-ui'
+import { stylePrefix } from '../__builtins__/configs'
+
+import type { Popover as PopoverProps } from 'element-ui'
+import { FormBaseItem, FormItemProps } from '../form-item'
+import { composeExport } from '../__builtins__/shared'
+
+export type EditableProps = FormItemProps
+export type EditablePopoverProps = PopoverProps
+
+const getParentPattern = (fieldRef) => {
+ const field = fieldRef.value
+ return field?.parent?.pattern || field?.form?.pattern
+}
+
+const getFormItemProps = (fieldRef): FormItemProps => {
+ const field = fieldRef.value
+
+ if (isVoidField(field)) return {}
+ if (!field) return {}
+ const takeMessage = () => {
+ if (field.selfErrors.length) return field.selfErrors[0]
+ if (field.selfWarnings.length) return field.selfWarnings[0]
+ if (field.selfSuccesses.length) return field.selfSuccesses[0]
+ }
+
+ return {
+ feedbackStatus:
+ field.validateStatus === 'validating' ? 'pending' : field.validateStatus,
+ feedbackText: takeMessage(),
+ extra: field.description,
+ }
+}
+
+const EditableInner = observer(
+ defineComponent({
+ name: 'FEditable',
+ setup(props, { attrs, slots, refs }) {
+ const fieldRef = useField()
+
+ const prefixCls = `${stylePrefix}-editable`
+ const setEditable = (payload: boolean) => {
+ const pattern = getParentPattern(fieldRef)
+
+ if (pattern !== 'editable') return
+ fieldRef.value.setPattern(payload ? 'editable' : 'readPretty')
+ }
+
+ const dispose = reaction(
+ () => {
+ const pattern = getParentPattern(fieldRef)
+
+ return pattern
+ },
+ (pattern) => {
+ if (pattern === 'editable') {
+ fieldRef.value.setPattern('readPretty')
+ }
+ },
+ {
+ fireImmediately: true,
+ }
+ )
+
+ onBeforeUnmount(dispose)
+
+ return () => {
+ const field = fieldRef.value
+ const editable = field.pattern === 'editable'
+ const pattern = getParentPattern(fieldRef)
+ const itemProps = getFormItemProps(fieldRef)
+
+ const recover = () => {
+ if (editable && !fieldRef.value?.errors?.length) {
+ setEditable(false)
+ }
+ }
+
+ const onClick = (e: MouseEvent) => {
+ const innerRef = refs.innerRef as HTMLElement
+ const target = e.target as HTMLElement
+ const close = innerRef.querySelector(`.${prefixCls}-close-btn`)
+
+ if (target?.contains(close) || close?.contains(target)) {
+ recover()
+ } else if (!editable) {
+ setTimeout(() => {
+ setEditable(true)
+ setTimeout(() => {
+ innerRef.querySelector('input')?.focus()
+ })
+ })
+ }
+ }
+
+ const renderEditHelper = () => {
+ if (editable) return null
+
+ return h(
+ FormBaseItem,
+ {
+ attrs: {
+ ...attrs,
+ ...itemProps,
+ },
+ },
+ {
+ default: () => {
+ return h(
+ 'i',
+ {
+ class: [
+ `${prefixCls}-edit-btn`,
+ pattern === 'editable'
+ ? 'el-icon-edit'
+ : 'el-icon-chat-dot-round',
+ ],
+ },
+ {}
+ )
+ },
+ }
+ )
+ }
+
+ const renderCloseHelper = () => {
+ if (!editable) return null
+ return h(
+ FormBaseItem,
+ {
+ attrs: {
+ ...attrs,
+ },
+ },
+ {
+ default: () => {
+ return h(
+ 'i',
+ {
+ class: [`${prefixCls}-close-btn`, 'el-icon-close'],
+ },
+ {}
+ )
+ },
+ }
+ )
+ }
+
+ return h(
+ 'div',
+ {
+ class: prefixCls,
+ ref: 'innerRef',
+ on: {
+ click: onClick,
+ },
+ },
+ {
+ default: () =>
+ h(
+ 'div',
+ {
+ class: `${prefixCls}-content`,
+ },
+ {
+ default: () => [
+ h(
+ FormBaseItem,
+ {
+ attrs: {
+ ...attrs,
+ ...itemProps,
+ },
+ },
+ slots
+ ),
+ renderEditHelper(),
+ renderCloseHelper(),
+ ],
+ }
+ ),
+ }
+ )
+ }
+ },
+ })
+)
+
+const EditablePopover = observer(
+ defineComponent({
+ name: 'FEditablePopover',
+ setup(props, { attrs, slots }) {
+ const fieldRef = useField()
+
+ const prefixCls = `${stylePrefix}-editable`
+ const visible = ref(false)
+
+ return () => {
+ const field = fieldRef.value
+ const pattern = getParentPattern(fieldRef)
+ return h(
+ Popover,
+ {
+ class: [prefixCls],
+ attrs: {
+ ...attrs,
+ title: attrs.title || field.title,
+ value: visible.value,
+ trigger: 'click',
+ },
+ on: {
+ input: (value) => {
+ visible.value = value
+ },
+ },
+ },
+ {
+ default: () => [slots.default()],
+ reference: () =>
+ h(
+ FormBaseItem,
+ { class: [`${prefixCls}-trigger`] },
+ {
+ default: () =>
+ h(
+ 'div',
+ {
+ class: [`${prefixCls}-content`],
+ },
+ {
+ default: () => [
+ h(
+ 'span',
+ {
+ class: [`${prefixCls}-preview`],
+ },
+ {
+ default: () => [attrs.title || field.title],
+ }
+ ),
+ h(
+ 'i',
+ {
+ class: [
+ `${prefixCls}-edit-btn`,
+ pattern === 'editable'
+ ? 'el-icon-edit'
+ : 'el-icon-chat-dot-round',
+ ],
+ },
+ {}
+ ),
+ ],
+ }
+ ),
+ }
+ ),
+ }
+ )
+ }
+ },
+ })
+)
+
+export const Editable = composeExport(EditableInner, {
+ Popover: EditablePopover,
+})
+
+export default Editable
diff --git a/packages/components/src/editable/style.scss b/packages/components/src/editable/style.scss
new file mode 100644
index 0000000..57870c8
--- /dev/null
+++ b/packages/components/src/editable/style.scss
@@ -0,0 +1,53 @@
+@import '../__builtins__/styles/common.scss';
+
+$editable-prefix-cls: '#{$formily-prefix}-editable';
+
+.#{$editable-prefix-cls} {
+ cursor: pointer;
+ display: inline-block !important;
+
+ .#{$formily-prefix}-form-text {
+ .#{$formily-prefix}-tag {
+ transition: none !important;
+ }
+
+ .#{$formily-prefix}-tag:last-child {
+ margin-right: 0 !important;
+ }
+ }
+
+ &-content {
+ display: flex;
+ align-items: center;
+
+ > * {
+ margin-right: 3px;
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+
+ .#{$editable-prefix-cls}-edit-btn,
+ .#{$editable-prefix-cls}-close-btn {
+ transition: all 0.25s ease-in-out;
+
+ &:hover {
+ color: $--color-primary;
+ }
+ }
+
+ .#{$formily-prefix}-form-text {
+ display: flex;
+ align-items: center;
+ }
+
+ .#{$editable-prefix-cls}-preview {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ word-break: break-all;
+ max-width: 100px;
+ display: block;
+ }
+}
diff --git a/packages/components/src/editable/style.ts b/packages/components/src/editable/style.ts
new file mode 100644
index 0000000..a4cf6b2
--- /dev/null
+++ b/packages/components/src/editable/style.ts
@@ -0,0 +1,5 @@
+import './style.scss'
+import 'element-ui/packages/theme-chalk/src/popover.scss'
+
+// 依赖
+import '../form-item/style'
diff --git a/packages/components/src/el-form-item/index.ts b/packages/components/src/el-form-item/index.ts
new file mode 100644
index 0000000..01b56ab
--- /dev/null
+++ b/packages/components/src/el-form-item/index.ts
@@ -0,0 +1,20 @@
+import { isVoidField } from '@formily/core'
+import { connect, mapProps } from '@formily/vue'
+
+import type { FormItem as _ElFormItemProps } from 'element-ui'
+import { FormItem as ElFormItemComponent } from 'element-ui'
+
+export type ElFormItemProps = _ElFormItemProps & { title: string }
+
+export const ElFormItem = connect(
+ ElFormItemComponent,
+ mapProps({ title: 'label', required: true }, (props, field) => ({
+ error: !isVoidField(field)
+ ? field.errors.length
+ ? field.errors.join(',')
+ : undefined
+ : undefined,
+ }))
+)
+
+export default ElFormItem
diff --git a/packages/components/src/el-form-item/style.ts b/packages/components/src/el-form-item/style.ts
new file mode 100644
index 0000000..612a4a5
--- /dev/null
+++ b/packages/components/src/el-form-item/style.ts
@@ -0,0 +1 @@
+import 'element-ui/packages/theme-chalk/src/form-item.scss'
diff --git a/packages/components/src/el-form/index.ts b/packages/components/src/el-form/index.ts
new file mode 100644
index 0000000..4da80ac
--- /dev/null
+++ b/packages/components/src/el-form/index.ts
@@ -0,0 +1,47 @@
+import { Form } from '@formily/core'
+import { FormProvider as _FormProvider, createForm } from '@formily/vue'
+import type { Form as _ElFormProps } from 'element-ui'
+import type { FunctionalComponentOptions, Component } from 'vue'
+import { Form as ElFormComponent } from 'element-ui'
+
+const FormProvider = _FormProvider as unknown as Component
+
+export type ElFormProps = _ElFormProps & {
+ form?: Form
+ component: Component
+ onAutoSubmit?: (values: any) => any
+}
+
+export const ElForm: FunctionalComponentOptions = {
+ functional: true,
+ render(h, context) {
+ const {
+ form = createForm({}),
+ component = ElFormComponent,
+ onAutoSubmit = context.listeners?.autoSubmit,
+ ...props
+ } = context.props
+ const submitHandler = (
+ Array.isArray(onAutoSubmit) ? onAutoSubmit[0] : onAutoSubmit
+ ) as (values: any) => any
+ return h(FormProvider, { props: { form } }, [
+ h(
+ component,
+ {
+ ...context.data,
+ props,
+ nativeOn: {
+ submit: (e: Event) => {
+ e?.stopPropagation?.()
+ e?.preventDefault?.()
+ form.submit(submitHandler)
+ },
+ },
+ },
+ context.children
+ ),
+ ])
+ },
+}
+
+export default ElForm
diff --git a/packages/components/src/el-form/style.ts b/packages/components/src/el-form/style.ts
new file mode 100644
index 0000000..023e8d5
--- /dev/null
+++ b/packages/components/src/el-form/style.ts
@@ -0,0 +1 @@
+import 'element-ui/packages/theme-chalk/src/form.scss'
diff --git a/packages/components/src/form-button-group/index.ts b/packages/components/src/form-button-group/index.ts
new file mode 100644
index 0000000..34cd55b
--- /dev/null
+++ b/packages/components/src/form-button-group/index.ts
@@ -0,0 +1,79 @@
+import { h } from '@formily/vue'
+import { defineComponent } from '@vue/composition-api'
+import { Space, SpaceProps } from '../space'
+import { FormBaseItem } from '../form-item'
+import { stylePrefix } from '../__builtins__/configs'
+
+export type FormButtonGroupProps = Omit & {
+ align?: 'left' | 'right' | 'center'
+ gutter?: number
+ className?: string
+ alignFormItem: boolean
+}
+
+export const FormButtonGroup = defineComponent({
+ name: 'FFormButtonGroup',
+ props: {
+ align: {
+ type: String,
+ default: 'left',
+ },
+ gutter: {
+ type: Number,
+ default: 8,
+ },
+ alignFormItem: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ setup(props, { slots, attrs }) {
+ const prefixCls = `${stylePrefix}-form-button-group`
+ return () => {
+ if (props.alignFormItem) {
+ return h(
+ FormBaseItem,
+ {
+ style: {
+ margin: 0,
+ padding: 0,
+ width: '100%',
+ },
+ attrs: {
+ colon: false,
+ label: ' ',
+ ...attrs,
+ },
+ },
+ {
+ default: () => h(Space, { props: { size: props.gutter } }, slots),
+ }
+ )
+ } else {
+ return h(
+ Space,
+ {
+ class: [prefixCls],
+ style: {
+ justifyContent:
+ props.align === 'left'
+ ? 'flex-start'
+ : props.align === 'right'
+ ? 'flex-end'
+ : 'center',
+ display: 'flex',
+ },
+ props: {
+ ...attrs,
+ size: props.gutter,
+ },
+ attrs,
+ },
+ slots
+ )
+ }
+ }
+ },
+})
+
+export default FormButtonGroup
diff --git a/packages/components/src/form-button-group/style.scss b/packages/components/src/form-button-group/style.scss
new file mode 100644
index 0000000..e69de29
diff --git a/packages/components/src/form-button-group/style.ts b/packages/components/src/form-button-group/style.ts
new file mode 100644
index 0000000..0ddedd8
--- /dev/null
+++ b/packages/components/src/form-button-group/style.ts
@@ -0,0 +1,5 @@
+import './style.scss'
+
+// 依赖
+import '../form-item/style'
+import '../space/style'
diff --git a/packages/components/src/form-collapse/index.ts b/packages/components/src/form-collapse/index.ts
new file mode 100644
index 0000000..bbccd6e
--- /dev/null
+++ b/packages/components/src/form-collapse/index.ts
@@ -0,0 +1,208 @@
+import { Collapse, CollapseItem, Badge } from 'element-ui'
+import { model } from '@formily/reactive'
+import type {
+ Collapse as CollapseProps,
+ CollapseItem as CollapseItemProps,
+} from 'element-ui'
+import {
+ useField,
+ useFieldSchema,
+ RecursionField,
+ h,
+ Fragment,
+} from '@formily/vue'
+import { observer } from '@formily/reactive-vue'
+import { Schema, SchemaKey } from '@formily/json-schema'
+import { composeExport, stylePrefix } from '../__builtins__'
+import { toArr } from '@formily/shared'
+import { computed, defineComponent, PropType } from 'vue-demi'
+import { GeneralField } from '@formily/core'
+
+type ActiveKeys = string | number | Array
+
+type ActiveKey = string | number
+
+type Panels = { name: SchemaKey; props: any; schema: Schema }[]
+
+export interface IFormCollapse {
+ activeKeys: ActiveKeys
+ hasActiveKey(key: ActiveKey): boolean
+ setActiveKeys(key: ActiveKeys): void
+ addActiveKey(key: ActiveKey): void
+ removeActiveKey(key: ActiveKey): void
+ toggleActiveKey(key: ActiveKey): void
+}
+
+export interface IFormCollapseProps extends CollapseProps {
+ formCollapse?: IFormCollapse
+ activeKey?: ActiveKey
+}
+
+const usePanels = (collapseField: GeneralField, schema: Schema) => {
+ const panels: Panels = []
+ schema.mapProperties((schema, name) => {
+ const field = collapseField.query(collapseField.address.concat(name)).take()
+ if (field?.display === 'none' || field?.display === 'hidden') return
+ if (schema['x-component']?.indexOf('FormCollapse.Item') > -1) {
+ panels.push({
+ name,
+ props: {
+ ...schema?.['x-component-props'],
+ key: schema?.['x-component-props']?.key || name,
+ },
+ schema,
+ })
+ }
+ })
+ return panels
+}
+
+const createFormCollapse = (defaultActiveKeys?: ActiveKeys) => {
+ const formCollapse = model({
+ activeKeys: defaultActiveKeys,
+ setActiveKeys(keys: ActiveKeys) {
+ formCollapse.activeKeys = keys
+ },
+ hasActiveKey(key: ActiveKey) {
+ if (Array.isArray(formCollapse.activeKeys)) {
+ if (formCollapse.activeKeys.includes(key)) {
+ return true
+ }
+ } else if (formCollapse.activeKeys == key) {
+ return true
+ }
+ return false
+ },
+ addActiveKey(key: ActiveKey) {
+ if (formCollapse.hasActiveKey(key)) return
+ formCollapse.activeKeys = toArr(formCollapse.activeKeys).concat(key)
+ },
+ removeActiveKey(key: ActiveKey) {
+ if (Array.isArray(formCollapse.activeKeys)) {
+ formCollapse.activeKeys = formCollapse.activeKeys.filter(
+ (item) => item != key
+ )
+ } else {
+ formCollapse.activeKeys = ''
+ }
+ },
+ toggleActiveKey(key: ActiveKey) {
+ if (formCollapse.hasActiveKey(key)) {
+ formCollapse.removeActiveKey(key)
+ } else {
+ formCollapse.addActiveKey(key)
+ }
+ },
+ })
+ return formCollapse
+}
+
+const FormCollapse = observer(
+ defineComponent({
+ inheritAttrs: false,
+ props: {
+ formCollapse: { type: Object as PropType },
+ activeKey: {
+ type: [String, Number],
+ },
+ },
+ setup(props, { attrs, emit }) {
+ const field = useField()
+ const schema = useFieldSchema()
+ const prefixCls = `${stylePrefix}-form-collapse`
+ const _formCollapse = computed(
+ () => props.formCollapse ?? createFormCollapse()
+ )
+
+ const takeActiveKeys = (panels: Panels) => {
+ if (props.activeKey) return props.activeKey
+ if (_formCollapse.value?.activeKeys)
+ return _formCollapse.value?.activeKeys
+ if (attrs.accordion) return panels[0]?.name
+ return panels.map((item) => item.name)
+ }
+
+ const badgedHeader = (key: SchemaKey, props: any) => {
+ const errors = field.value.form.queryFeedbacks({
+ type: 'error',
+ address: `${field.value.address.concat(key)}.*`,
+ })
+ if (errors.length) {
+ return h(
+ Badge,
+ {
+ class: [`${prefixCls}-errors-badge`],
+ props: {
+ value: errors.length,
+ },
+ },
+ { default: () => props.title }
+ )
+ }
+ return props.title
+ }
+
+ return () => {
+ const panels = usePanels(field.value, schema.value)
+ const activeKey = takeActiveKeys(panels)
+ return h(
+ Collapse,
+ {
+ class: prefixCls,
+ props: {
+ value: activeKey,
+ },
+ on: {
+ change: (key: string | string[]) => {
+ emit('input', key)
+ _formCollapse.value.setActiveKeys(key)
+ },
+ },
+ },
+ {
+ default: () => {
+ return panels.map(({ props, schema, name }, index) => {
+ return h(
+ CollapseItem,
+ {
+ key: index,
+ props: {
+ ...props,
+ name,
+ },
+ },
+ {
+ default: () => [
+ h(RecursionField, { props: { schema, name } }, {}),
+ ],
+ title: () =>
+ h(
+ 'span',
+ {},
+ { default: () => badgedHeader(name, props) }
+ ),
+ }
+ )
+ })
+ },
+ }
+ )
+ }
+ },
+ })
+)
+
+export const FormCollapseItem = defineComponent({
+ name: 'FFormCollapseItem',
+ setup(_props, { slots }) {
+ return () => h(Fragment, {}, slots)
+ },
+})
+
+const composeFormCollapse = composeExport(FormCollapse, {
+ Item: FormCollapseItem,
+ createFormCollapse,
+})
+
+export { composeFormCollapse as FormCollapse }
+export default composeFormCollapse
diff --git a/packages/components/src/form-collapse/style.scss b/packages/components/src/form-collapse/style.scss
new file mode 100644
index 0000000..e9368b7
--- /dev/null
+++ b/packages/components/src/form-collapse/style.scss
@@ -0,0 +1,6 @@
+@import '../__builtins__/styles/common.scss';
+
+.#{$formily-prefix}-form-collapse-errors-badge {
+ line-height: 1;
+ vertical-align: initial;
+}
diff --git a/packages/components/src/form-collapse/style.ts b/packages/components/src/form-collapse/style.ts
new file mode 100644
index 0000000..cf0af47
--- /dev/null
+++ b/packages/components/src/form-collapse/style.ts
@@ -0,0 +1,4 @@
+import './style.scss'
+import 'element-ui/packages/theme-chalk/src/collapse.scss'
+import 'element-ui/packages/theme-chalk/src/collapse-item.scss'
+import 'element-ui/packages/theme-chalk/src/badge.scss'
diff --git a/packages/components/src/form-dialog/index.ts b/packages/components/src/form-dialog/index.ts
new file mode 100644
index 0000000..f69e8ac
--- /dev/null
+++ b/packages/components/src/form-dialog/index.ts
@@ -0,0 +1,410 @@
+import { h, FormProvider, Fragment } from '@formily/vue'
+import { toJS } from '@formily/reactive'
+import { observer } from '@formily/reactive-vue'
+import { createForm, Form, IFormProps } from '@formily/core'
+import {
+ isNum,
+ isStr,
+ isBool,
+ isFn,
+ IMiddleware,
+ applyMiddleware,
+} from '@formily/shared'
+import { Dialog, Button } from 'element-ui'
+import type { Dialog as DialogProps, Button as ButtonProps } from 'element-ui'
+import { t } from 'element-ui/src/locale'
+import Vue, { Component, VNode } from 'vue'
+import {
+ isValidElement,
+ resolveComponent,
+ createPortalProvider,
+ getProtalContext,
+ loading,
+} from '../__builtins__/shared'
+import { stylePrefix } from '../__builtins__/configs'
+import { defineComponent } from '@vue/composition-api'
+import { Portal, PortalTarget } from 'portal-vue'
+
+type FormDialogContentProps = { form: Form }
+
+type FormDialogContent = Component | ((props: FormDialogContentProps) => VNode)
+
+type DialogTitle = string | number | Component | VNode | (() => VNode)
+
+type IFormDialogProps = Omit & {
+ title?: DialogTitle
+ footer?: null | Component | VNode | (() => VNode)
+ cancelText?: string | Component | VNode | (() => VNode)
+ cancelButtonProps?: ButtonProps
+ okText?: string | Component | VNode | (() => VNode)
+ okButtonProps?: ButtonProps
+ onOpen?: () => void
+ onOpend?: () => void
+ onClose?: () => void
+ onClosed?: () => void
+ onCancel?: () => void
+ onOK?: () => void
+ loadingText?: string
+}
+
+const PORTAL_TARGET_NAME = 'FormDialogFooter'
+
+const isDialogTitle = (props: any): props is DialogTitle => {
+ return isNum(props) || isStr(props) || isBool(props) || isValidElement(props)
+}
+
+const getDialogProps = (props: any): IFormDialogProps => {
+ if (isDialogTitle(props)) {
+ return {
+ title: props,
+ } as IFormDialogProps
+ } else {
+ return props
+ }
+}
+
+export interface IFormDialog {
+ forOpen(middleware: IMiddleware): IFormDialog
+ forConfirm(middleware: IMiddleware): IFormDialog
+ forCancel(middleware: IMiddleware): IFormDialog
+ open(props?: IFormProps): Promise
+ close(): void
+}
+
+export interface IFormDialogComponentProps {
+ content: FormDialogContent
+ resolve: () => any
+ reject: () => any
+}
+
+export function FormDialog(
+ title: IFormDialogProps | DialogTitle,
+ content: FormDialogContent
+): IFormDialog
+
+export function FormDialog(
+ title: IFormDialogProps | DialogTitle,
+ id: string | symbol,
+ content: FormDialogContent
+): IFormDialog
+
+export function FormDialog(
+ title: DialogTitle,
+ id: string,
+ content: FormDialogContent
+): IFormDialog
+
+export function FormDialog(
+ title: IFormDialogProps | DialogTitle,
+ id: string | symbol | FormDialogContent,
+ content?: FormDialogContent
+): IFormDialog {
+ if (isFn(id) || isValidElement(id)) {
+ content = id as FormDialogContent
+ id = 'form-dialog'
+ }
+
+ const prefixCls = `${stylePrefix}-form-dialog`
+ const env = {
+ root: document.createElement('div'),
+ form: null,
+ promise: null,
+ instance: null,
+ openMiddlewares: [],
+ confirmMiddlewares: [],
+ cancelMiddlewares: [],
+ }
+
+ document.body.appendChild(env.root)
+
+ const props = getDialogProps(title)
+ const dialogProps = {
+ ...props,
+ onClosed: () => {
+ props.onClosed?.()
+ env.instance.$destroy()
+ env.instance = null
+ env.root?.parentNode?.removeChild(env.root)
+ env.root = undefined
+ },
+ }
+
+ const component = observer(
+ defineComponent({
+ setup() {
+ return () =>
+ h(
+ Fragment,
+ {},
+ {
+ default: () =>
+ resolveComponent(content, {
+ form: env.form,
+ }),
+ }
+ )
+ },
+ })
+ )
+
+ const render = (visible = true, resolve?: () => any, reject?: () => any) => {
+ if (!env.instance) {
+ const ComponentConstructor = observer(
+ Vue.extend({
+ props: ['dialogProps'],
+ data() {
+ return {
+ visible: false,
+ }
+ },
+ render() {
+ const {
+ onClose,
+ onClosed,
+ onOpen,
+ onOpend,
+ onOK,
+ onCancel,
+ title,
+ footer,
+ okText,
+ cancelText,
+ okButtonProps,
+ cancelButtonProps,
+ ...dialogProps
+ } = this.dialogProps
+
+ return h(
+ FormProvider,
+ {
+ props: {
+ form: env.form,
+ },
+ },
+ {
+ default: () =>
+ h(
+ Dialog,
+ {
+ class: [`${prefixCls}`],
+ attrs: {
+ visible: this.visible,
+ ...dialogProps,
+ },
+ on: {
+ 'update:visible': (val) => {
+ this.visible = val
+ },
+ close: () => {
+ onClose?.()
+ },
+
+ closed: () => {
+ onClosed?.()
+ },
+ open: () => {
+ onOpen?.()
+ },
+ opend: () => {
+ onOpend?.()
+ },
+ },
+ },
+ {
+ default: () => [h(component, {}, {})],
+ title: () =>
+ h(
+ 'div',
+ {},
+ { default: () => resolveComponent(title) }
+ ),
+ footer: () =>
+ h(
+ 'div',
+ {},
+ {
+ default: () => {
+ const FooterProtalTarget = h(
+ PortalTarget,
+ {
+ props: {
+ name: PORTAL_TARGET_NAME,
+ slim: true,
+ },
+ },
+ {}
+ )
+ if (footer === null) {
+ return [null, FooterProtalTarget]
+ } else if (footer) {
+ return [
+ resolveComponent(footer),
+ FooterProtalTarget,
+ ]
+ }
+
+ return [
+ h(
+ Button,
+ {
+ attrs: cancelButtonProps,
+ on: {
+ click: (e) => {
+ onCancel?.(e)
+ reject()
+ },
+ },
+ },
+ {
+ default: () =>
+ resolveComponent(
+ cancelText ||
+ t('el.popconfirm.cancelButtonText')
+ ),
+ }
+ ),
+
+ h(
+ Button,
+ {
+ attrs: {
+ type: 'primary',
+ ...okButtonProps,
+ loading: env.form.submitting,
+ },
+ on: {
+ click: (e) => {
+ onOK?.(e)
+ resolve()
+ },
+ },
+ },
+ {
+ default: () =>
+ resolveComponent(
+ okText ||
+ t('el.popconfirm.confirmButtonText')
+ ),
+ }
+ ),
+ FooterProtalTarget,
+ ]
+ },
+ }
+ ),
+ }
+ ),
+ }
+ )
+ },
+ })
+ )
+ env.instance = new ComponentConstructor({
+ propsData: {
+ dialogProps,
+ },
+ parent: getProtalContext(id as string | symbol),
+ })
+ env.instance.$mount(env.root)
+ }
+
+ env.instance.visible = visible
+ }
+
+ const formDialog = {
+ forOpen: (middleware: IMiddleware) => {
+ if (isFn(middleware)) {
+ env.openMiddlewares.push(middleware)
+ }
+ return formDialog
+ },
+ forConfirm: (middleware: IMiddleware