Browse Source

feat(组件): 移植组件

gemercheung 2 years ago
parent
commit
35766e05f2
66 changed files with 2610 additions and 1291 deletions
  1. 35 35
      .cz-config.js
  2. 71 77
      .eslintrc.js
  3. 11 11
      commitlint.config.js
  4. 68 68
      package.json
  5. 25 25
      packages/components/audio/__tests__/audio.test.tsx
  6. 6 6
      packages/components/audio/index.ts
  7. 34 34
      packages/components/audio/src/audio.vue
  8. 6 6
      packages/components/button/index.ts
  9. 23 23
      packages/components/button/src/button.vue
  10. 8 8
      packages/components/components.d.ts
  11. 57 59
      packages/components/icon/iconfont/iconfont.js
  12. 9 9
      packages/components/icon/index.ts
  13. 7 7
      packages/components/icon/src/icon.ts
  14. 31 33
      packages/components/icon/src/icon.vue
  15. 23 23
      packages/components/icon/src/props.ts
  16. 3 3
      packages/components/index.ts
  17. 0 0
      packages/components/input/index.ts
  18. 21 0
      packages/components/input/src/check-radio.vue
  19. 21 0
      packages/components/input/src/checkbox.vue
  20. 202 0
      packages/components/input/src/file.vue
  21. 125 0
      packages/components/input/src/index.vue
  22. 76 0
      packages/components/input/src/number.vue
  23. 3 0
      packages/components/input/src/password.vue
  24. 20 0
      packages/components/input/src/radio.vue
  25. 121 0
      packages/components/input/src/range.vue
  26. 168 0
      packages/components/input/src/richtext.vue
  27. 69 0
      packages/components/input/src/search.vue
  28. 138 0
      packages/components/input/src/select.vue
  29. 253 0
      packages/components/input/src/state.js
  30. 15 0
      packages/components/input/src/switch.vue
  31. 73 0
      packages/components/input/src/test.vue
  32. 68 0
      packages/components/input/src/text.vue
  33. 56 0
      packages/components/input/src/textarea.vue
  34. 19 19
      packages/components/rollup.config.js
  35. 19 19
      packages/directives/rollup.config.js
  36. 39 40
      packages/utils/dom.ts
  37. 64 78
      packages/utils/dom/aria.ts
  38. 11 15
      packages/utils/dom/event.ts
  39. 5 5
      packages/utils/dom/index.ts
  40. 45 50
      packages/utils/dom/position.ts
  41. 73 76
      packages/utils/dom/scroll.ts
  42. 53 57
      packages/utils/dom/style.ts
  43. 13 13
      packages/utils/error.ts
  44. 3 2
      packages/utils/index.ts
  45. 9 0
      packages/utils/normal.ts
  46. 16 20
      packages/utils/objects.ts
  47. 12 15
      packages/utils/types.ts
  48. 19 19
      packages/utils/vue/global-node.ts
  49. 21 30
      packages/utils/vue/icon.ts
  50. 6 6
      packages/utils/vue/index.ts
  51. 27 27
      packages/utils/vue/install.ts
  52. 3 3
      packages/utils/vue/props/index.ts
  53. 48 82
      packages/utils/vue/props/runtime.ts
  54. 37 64
      packages/utils/vue/props/types.ts
  55. 6 6
      packages/utils/vue/props/util.ts
  56. 13 13
      packages/utils/vue/refs.ts
  57. 4 4
      packages/utils/vue/typescript.ts
  58. 91 96
      packages/utils/vue/vnode.ts
  59. 31 31
      playground/src/App.vue
  60. 22 23
      playground/src/components/HelloWorld.vue
  61. 4 4
      playground/src/main.ts
  62. 3 3
      playground/src/vite-env.d.ts
  63. 4 4
      playground/vite.config.ts
  64. 14 10
      prettier.config.js
  65. 25 25
      vitest.config.ts
  66. 5 5
      vitest.setup.ts

+ 35 - 35
.cz-config.js

@@ -1,37 +1,37 @@
 module.exports = {
 module.exports = {
-  types: [
-    { value: 'feat', name: 'feat 🍄:    新增新的特性' },
-    { value: 'fix', name: 'fix 🐛:    修复 BUG' },
-    { value: 'docs', name: 'docs 📄:    修改文档、注释' },
-    {
-      value: 'refactor',
-      name: 'refactor 🎸:    代码重构,注意和特性、修复区分开',
-    },
-    { value: 'chore', name: 'chore 🧹:   构建过程或辅助工具的变动' },
-    { value: 'perf', name: 'perf ⚡:    提升性能' },
-    { value: 'test', name: 'test 👀:    添加一个测试' },
-    { value: 'tool', name: 'tool 🚗:    开发工具变动(构建、脚手架工具等)' },
-    { value: 'style', name: 'style ✂:    对代码格式的修改不影响逻辑' },
-    { value: 'revert', name: 'revert 🌝:     版本回滚' },
-    { value: 'update', name: 'update ⬆:    第三方库升级 ' },
-  ],
+    types: [
+        { value: 'feat', name: 'feat 🍄:    新增新的特性' },
+        { value: 'fix', name: 'fix 🐛:    修复 BUG' },
+        { value: 'docs', name: 'docs 📄:    修改文档、注释' },
+        {
+            value: 'refactor',
+            name: 'refactor 🎸:    代码重构,注意和特性、修复区分开',
+        },
+        { value: 'chore', name: 'chore 🧹:   构建过程或辅助工具的变动' },
+        { value: 'perf', name: 'perf ⚡:    提升性能' },
+        { value: 'test', name: 'test 👀:    添加一个测试' },
+        { value: 'tool', name: 'tool 🚗:    开发工具变动(构建、脚手架工具等)' },
+        { value: 'style', name: 'style ✂:    对代码格式的修改不影响逻辑' },
+        { value: 'revert', name: 'revert 🌝:     版本回滚' },
+        { value: 'update', name: 'update ⬆:    第三方库升级 ' },
+    ],
 
 
-  scopes: [{ name: '组件' }, { name: '样式' }, { name: '文档更改' }, { name: '其它变更' }],
-  allowTicketNumber: false,
-  isTicketNumberRequired: false,
-  ticketNumberPrefix: 'TICKET-',
-  ticketNumberRegExp: 'd{1,5}',
-  messages: {
-    type: '选择一种你的提交类型:',
-    scope: '选择一个scope (可选):',
-    customScope: 'Denote the SCOPE of this change:',
-    subject: '简要说明:\n',
-    body: '详细说明,使用"|"换行(可选):\n',
-    breaking: '非兼容性说明 (可选):\n',
-    footer: '关联关闭的issue,例如:#31, #34(可选):\n',
-    confirmCommit: '确定提交?',
-  },
-  allowCustomScopes: true,
-  allowBreakingChanges: ['新增', '修复'],
-  subjectLimit: 100,
-};
+    scopes: [{ name: '组件' }, { name: '样式' }, { name: '文档更改' }, { name: '其它变更' }],
+    allowTicketNumber: false,
+    isTicketNumberRequired: false,
+    ticketNumberPrefix: 'TICKET-',
+    ticketNumberRegExp: 'd{1,5}',
+    messages: {
+        type: '选择一种你的提交类型:',
+        scope: '选择一个scope (可选):',
+        customScope: 'Denote the SCOPE of this change:',
+        subject: '简要说明:\n',
+        body: '详细说明,使用"|"换行(可选):\n',
+        breaking: '非兼容性说明 (可选):\n',
+        footer: '关联关闭的issue,例如:#31, #34(可选):\n',
+        confirmCommit: '确定提交?',
+    },
+    allowCustomScopes: true,
+    allowBreakingChanges: ['新增', '修复'],
+    subjectLimit: 100,
+}

+ 71 - 77
.eslintrc.js

@@ -1,81 +1,75 @@
 // @ts-check
 // @ts-check
-const { defineConfig } = require('eslint-define-config');
+const { defineConfig } = require('eslint-define-config')
 module.exports = defineConfig({
 module.exports = defineConfig({
-  root: true,
-  env: {
-    browser: true,
-    node: true,
-    es6: true,
-  },
-  parser: 'vue-eslint-parser',
-  parserOptions: {
-    parser: '@typescript-eslint/parser',
-    ecmaVersion: 2020,
-    sourceType: 'module',
-    jsxPragma: 'React',
-    ecmaFeatures: {
-      jsx: true,
+    root: true,
+    env: {
+        browser: true,
+        node: true,
+        es6: true,
     },
     },
-  },
-  extends: [
-    'plugin:vue/vue3-recommended',
-    'plugin:@typescript-eslint/recommended',
-    'prettier',
-    'plugin:prettier/recommended',
-    'plugin:jest/recommended',
-  ],
-  rules: {
-    'vue/script-setup-uses-vars': 'error',
-    '@typescript-eslint/ban-ts-ignore': 'off',
-    '@typescript-eslint/explicit-function-return-type': 'off',
-    '@typescript-eslint/no-explicit-any': 'off',
-    '@typescript-eslint/no-var-requires': 'off',
-    '@typescript-eslint/no-empty-function': 'off',
-    'vue/custom-event-name-casing': 'off',
-    'no-use-before-define': 'off',
-    '@typescript-eslint/no-use-before-define': 'off',
-    '@typescript-eslint/ban-ts-comment': 'off',
-    '@typescript-eslint/ban-types': 'off',
-    '@typescript-eslint/no-non-null-assertion': 'off',
-    '@typescript-eslint/explicit-module-boundary-types': 'off',
-    '@typescript-eslint/no-unused-vars': [
-      'error',
-      {
-        argsIgnorePattern: '^_',
-        varsIgnorePattern: '^_',
-      },
-    ],
-    'no-unused-vars': [
-      'error',
-      {
-        argsIgnorePattern: '^_',
-        varsIgnorePattern: '^_',
-      },
-    ],
-    'space-before-function-paren': 'off',
-
-    'vue/attributes-order': 'off',
-    'vue/one-component-per-file': 'off',
-    'vue/html-closing-bracket-newline': 'off',
-    'vue/max-attributes-per-line': 'off',
-    'vue/multiline-html-element-content-newline': 'off',
-    'vue/singleline-html-element-content-newline': 'off',
-    'vue/attribute-hyphenation': 'off',
-    'vue/require-default-prop': 'off',
-    'vue/require-explicit-emits': 'off',
-    'vue/no-useless-template-attributes': 'off',
-    'vue/html-self-closing': [
-      'error',
-      {
-        html: {
-          void: 'always',
-          normal: 'never',
-          component: 'always',
+    parser: 'vue-eslint-parser',
+    parserOptions: {
+        parser: '@typescript-eslint/parser',
+        ecmaVersion: 2020,
+        sourceType: 'module',
+        jsxPragma: 'React',
+        ecmaFeatures: {
+            jsx: true,
         },
         },
-        svg: 'always',
-        math: 'always',
-      },
-    ],
-    'vue/multi-word-component-names': 'off',
-  },
-});
+    },
+    extends: ['plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'prettier', 'plugin:prettier/recommended', 'plugin:jest/recommended'],
+    rules: {
+        'vue/script-setup-uses-vars': 'error',
+        '@typescript-eslint/ban-ts-ignore': 'off',
+        '@typescript-eslint/explicit-function-return-type': 'off',
+        '@typescript-eslint/no-explicit-any': 'off',
+        '@typescript-eslint/no-var-requires': 'off',
+        '@typescript-eslint/no-empty-function': 'off',
+        'vue/custom-event-name-casing': 'off',
+        'no-use-before-define': 'off',
+        '@typescript-eslint/no-use-before-define': 'off',
+        '@typescript-eslint/ban-ts-comment': 'off',
+        '@typescript-eslint/ban-types': 'off',
+        '@typescript-eslint/no-non-null-assertion': 'off',
+        '@typescript-eslint/explicit-module-boundary-types': 'off',
+        '@typescript-eslint/no-unused-vars': [
+            'error',
+            {
+                argsIgnorePattern: '^_',
+                varsIgnorePattern: '^_',
+            },
+        ],
+        'no-unused-vars': [
+            'error',
+            {
+                argsIgnorePattern: '^_',
+                varsIgnorePattern: '^_',
+            },
+        ],
+        'space-before-function-paren': 'off',
+
+        'vue/attributes-order': 'off',
+        'vue/one-component-per-file': 'off',
+        'vue/html-closing-bracket-newline': 'off',
+        'vue/max-attributes-per-line': 'off',
+        'vue/multiline-html-element-content-newline': 'off',
+        'vue/singleline-html-element-content-newline': 'off',
+        'vue/attribute-hyphenation': 'off',
+        'vue/require-default-prop': 'off',
+        'vue/require-explicit-emits': 'off',
+        'vue/no-useless-template-attributes': 'off',
+        'vue/html-self-closing': [
+            'error',
+            {
+                html: {
+                    void: 'always',
+                    normal: 'never',
+                    component: 'always',
+                },
+                svg: 'always',
+                math: 'always',
+            },
+        ],
+        'vue/multi-word-component-names': 'off',
+    },
+})

+ 11 - 11
commitlint.config.js

@@ -1,13 +1,13 @@
 module.exports = {
 module.exports = {
-  extends: ['cz'],
-  parserPreset: {
-    parserOpts: {
-      headerPattern: /^(\w*)\(([\u4e00-\u9fa5]*)\)/,
-      headerCorrespondence: ['type', 'scope'],
+    extends: ['cz'],
+    parserPreset: {
+        parserOpts: {
+            headerPattern: /^(\w*)\(([\u4e00-\u9fa5]*)\)/,
+            headerCorrespondence: ['type', 'scope'],
+        },
     },
     },
-  },
-  rules: {
-    'type-empty': [2, 'never'],
-    'scope-empty': [2, 'never'],
-  },
-};
+    rules: {
+        'type-empty': [2, 'never'],
+        'scope-empty': [2, 'never'],
+    },
+}

+ 68 - 68
package.json

@@ -1,71 +1,71 @@
 {
 {
-  "name": "4dkankan-components",
-  "description": "a collection of 4dkankan components",
-  "private": true,
-  "workspaces": [
-    "packages/*",
-    "apps/*"
-  ],
-  "scripts": {
-    "docs": "doctoc --title '**Table of content**' README.md",
-    "clean": "pnpm run -r clean",
-    "build": "pnpm run -r build",
-    "test": "vitest",
-    "test:coverage": "vitest --coverage",
-    "lint": "eslint --ext js,ts,tsx . --fix",
-    "commit": "git cz",
-    "preinstall": "npx only-allow pnpm",
-    "postinstall": "husky install",
-    "changeset": "changeset",
-    "version-packages": "changeset version",
-    "dev:playground": "pnpm -C playground dev"
-  },
-  "peerDependencies": {
-    "vue": "^3.2.0"
-  },
-  "dependencies": {
-    "@vueuse/core": "^9.1.0",
-    "lodash": "^4.17.21",
-    "lodash-es": "^4.17.21",
-    "lodash-unified": "^1.0.2"
-  },
-  "devDependencies": {
-    "@changesets/cli": "^2.24.4",
-    "@commitlint/cli": "^17.1.2",
-    "@typescript-eslint/eslint-plugin": "^5.38.1",
-    "@vitejs/plugin-vue": "^3.1.0",
-    "@vitejs/plugin-vue-jsx": "^2.0.1",
-    "@vue/test-utils": "^2.0.2",
-    "@types/jsdom": "^16.2.14",
-    "@types/node": "*",
-    "commitizen": "^4.2.5",
-    "commitlint-config-cz": "^0.13.3",
-    "cz-customizable": "^7.0.0",
-    "doctoc": "~2.2.0",
-    "eslint": "~8.23.1",
-    "eslint-config-prettier": "^8.5.0",
-    "eslint-define-config": "^1.7.0",
-    "eslint-plugin-import": "~2.26.0",
-    "eslint-plugin-jest": "^25.7.0",
-    "eslint-plugin-prettier": "^4.2.1",
-    "eslint-plugin-vue": "^8.7.1",
-    "husky": "^8.0.1",
-    "jest": "^29.0.3",
-    "jsdom": "16.4.0",
-    "lint-staged": "^13.0.3",
-    "resize-observer-polyfill": "^1.5.1",
-    "typescript": "~4.7.4",
-    "unplugin-vue-macros": "^0.11.2",
-    "vitest": "^0.23.4"
-  },
-  "lint-staged": {
-    "*.{vue,js,ts,jsx,tsx,json}": [
-      "eslint --fix"
-    ]
-  },
-  "config": {
-    "commitizen": {
-      "path": "cz-customizable"
+    "name": "4dkankan-components",
+    "description": "a collection of 4dkankan components",
+    "private": true,
+    "workspaces": [
+        "packages/*",
+        "apps/*"
+    ],
+    "scripts": {
+        "docs": "doctoc --title '**Table of content**' README.md",
+        "clean": "pnpm run -r clean",
+        "build": "pnpm run -r build",
+        "test": "vitest",
+        "test:coverage": "vitest --coverage",
+        "lint": "eslint --ext js,ts,tsx,vue . --fix",
+        "commit": "git cz",
+        "preinstall": "npx only-allow pnpm",
+        "postinstall": "husky install",
+        "changeset": "changeset",
+        "version-packages": "changeset version",
+        "dev:playground": "pnpm -C playground dev"
+    },
+    "peerDependencies": {
+        "vue": "^3.2.0"
+    },
+    "dependencies": {
+        "@vueuse/core": "^9.1.0",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "lodash-unified": "^1.0.2"
+    },
+    "devDependencies": {
+        "@changesets/cli": "^2.24.4",
+        "@commitlint/cli": "^17.1.2",
+        "@typescript-eslint/eslint-plugin": "^5.38.1",
+        "@vitejs/plugin-vue": "^3.1.0",
+        "@vitejs/plugin-vue-jsx": "^2.0.1",
+        "@vue/test-utils": "^2.0.2",
+        "@types/jsdom": "^16.2.14",
+        "@types/node": "*",
+        "commitizen": "^4.2.5",
+        "commitlint-config-cz": "^0.13.3",
+        "cz-customizable": "^7.0.0",
+        "doctoc": "~2.2.0",
+        "eslint": "~8.23.1",
+        "eslint-config-prettier": "^8.5.0",
+        "eslint-define-config": "^1.7.0",
+        "eslint-plugin-import": "~2.26.0",
+        "eslint-plugin-jest": "^25.7.0",
+        "eslint-plugin-prettier": "^4.2.1",
+        "eslint-plugin-vue": "^8.7.1",
+        "husky": "^8.0.1",
+        "jest": "^29.0.3",
+        "jsdom": "16.4.0",
+        "lint-staged": "^13.0.3",
+        "resize-observer-polyfill": "^1.5.1",
+        "typescript": "~4.7.4",
+        "unplugin-vue-macros": "^0.11.2",
+        "vitest": "^0.23.4"
+    },
+    "lint-staged": {
+        "*.{vue,js,ts,jsx,tsx,json}": [
+            "eslint --fix"
+        ]
+    },
+    "config": {
+        "commitizen": {
+            "path": "cz-customizable"
+        }
     }
     }
-  }
 }
 }

+ 25 - 25
packages/components/audio/__tests__/audio.test.tsx

@@ -1,25 +1,25 @@
-import { describe, expect, test } from 'vitest';
-import { mount } from '@vue/test-utils';
-import { ref } from 'vue';
-import Audio from '../src/audio.vue';
-// import type { VNode } from 'vue';
-
-// const _mount = (render: () => VNode) => {
-//     return mount(render, { attachTo: document.body })
-//   }
-
-describe('Audio.vue', () => {
-  //   test('render test', async () => {
-  //     const AudioSrc = '';
-  //     const wrapper = mount(() => <Audio src={AudioSrc}></Audio>);
-  //     await nextTick();
-  //   });
-
-  test('play', async () => {
-    const radio = ref('');
-    const wrapper = mount(() => <Audio v-model={radio.value} />);
-    await wrapper.trigger('click');
-    expect(radio.value).toBe('');
-    expect(wrapper.classes()).toContain('is-disabled');
-  });
-});
+import { describe, expect, test } from 'vitest'
+import { mount } from '@vue/test-utils'
+import { ref } from 'vue'
+import Audio from '../src/audio.vue'
+// import type { VNode } from 'vue';
+
+// const _mount = (render: () => VNode) => {
+//     return mount(render, { attachTo: document.body })
+//   }
+
+describe('Audio.vue', () => {
+    //   test('render test', async () => {
+    //     const AudioSrc = '';
+    //     const wrapper = mount(() => <Audio src={AudioSrc}></Audio>);
+    //     await nextTick();
+    //   });
+
+    test('play', async () => {
+        const radio = ref('')
+        const wrapper = mount(() => <Audio v-model={radio.value} />)
+        await wrapper.trigger('click')
+        expect(radio.value).toBe('')
+        expect(wrapper.classes()).toContain('is-disabled')
+    })
+})

+ 6 - 6
packages/components/audio/index.ts

@@ -1,6 +1,6 @@
-import { withInstall } from '@kankan/utils';
-import Audio from './src/audio.vue';
-
-export const UIAudio = withInstall(Audio);
-
-export default UIAudio;
+import { withInstall } from '@kankan/utils'
+import Audio from './src/audio.vue'
+
+export const UIAudio = withInstall(Audio)
+
+export default UIAudio

+ 34 - 34
packages/components/audio/src/audio.vue

@@ -1,54 +1,54 @@
 <template>
 <template>
-  <div class="ui-audio" @click="clickHandler">
-    <audio @play="rotation" ref="audio" autoplay loop>
-      <source :src="src" />
-    </audio>
-    <span v-for="random in randoms" :style="{ '--percent': random }" :key="random"></span>
-  </div>
+    <div class="ui-audio" @click="clickHandler">
+        <audio @play="rotation" ref="audio" autoplay loop>
+            <source :src="src" />
+        </audio>
+        <span v-for="random in randoms" :style="{ '--percent': random }" :key="random"></span>
+    </div>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
-  import { defineProps, ref, watchEffect } from 'vue';
-  defineProps({
+import { defineProps, ref, watchEffect } from 'vue'
+defineProps({
     src: String,
     src: String,
-  });
-  const audio = ref();
-  const randoms = ref([1, 0.5, 1, 0.5]);
-  const playIng = ref(false);
+})
+const audio = ref()
+const randoms = ref([1, 0.5, 1, 0.5])
+const playIng = ref(false)
 
 
-  let timeout: ReturnType<typeof setTimeout>;
-  const rotation = () => {
-    if (!playIng.value) return;
+let timeout: ReturnType<typeof setTimeout>
+const rotation = () => {
+    if (!playIng.value) return
     for (let i = 0; i < randoms.value.length; i++) {
     for (let i = 0; i < randoms.value.length; i++) {
-      randoms.value[i] = Math.random();
+        randoms.value[i] = Math.random()
     }
     }
-    timeout = setTimeout(rotation, 200);
-  };
+    timeout = setTimeout(rotation, 200)
+}
 
 
-  watchEffect(() => {
+watchEffect(() => {
     if (audio.value) {
     if (audio.value) {
-      if (playIng.value) {
-        audio.value.play();
-      } else {
-        audio.value.pause();
-      }
-      clearTimeout(timeout);
-      rotation();
+        if (playIng.value) {
+            audio.value.play()
+        } else {
+            audio.value.pause()
+        }
+        clearTimeout(timeout)
+        rotation()
     }
     }
-  });
+})
 
 
-  const clickHandler = () => {
-    playIng.value = !playIng.value;
-  };
+const clickHandler = () => {
+    playIng.value = !playIng.value
+}
 
 
-  defineExpose({
+defineExpose({
     play() {
     play() {
-      playIng.value = true;
+        playIng.value = true
     },
     },
     pause() {
     pause() {
-      playIng.value = false;
+        playIng.value = false
     },
     },
-  });
+})
 </script>
 </script>
 
 
 <!-- <script>
 <!-- <script>

+ 6 - 6
packages/components/button/index.ts

@@ -1,6 +1,6 @@
-import { withInstall } from '@kankan/utils';
-import Button from './src/button.vue';
-
-export const UIButton = withInstall(Button);
-
-export default UIButton;
+import { withInstall } from '@kankan/utils'
+import Button from './src/button.vue'
+
+export const UIButton = withInstall(Button)
+
+export default UIButton

+ 23 - 23
packages/components/button/src/button.vue

@@ -1,42 +1,42 @@
 <template>
 <template>
-  <button class="ui-button" :class="className" :style="style">
-    <UIIcon :type="icon" v-if="icon" class="ui-button-icon" />
-    <slot></slot>
-  </button>
+    <button class="ui-button" :class="className" :style="style">
+        <UIIcon :type="icon" v-if="icon" class="ui-button-icon" />
+        <slot></slot>
+    </button>
 </template>
 </template>
 
 
-<script setup>
-  import { defineProps, computed } from 'vue';
-  import { normalizeUnitToStyle } from '@kankan/utils';
-  import UIIcon from '../../icon/src/icon.vue';
+<script lang="ts" setup>
+import { defineProps, computed } from 'vue'
+import { normalizeUnitToStyle } from '@kankan/utils'
+import UIIcon from '../../icon/src/icon.vue'
 
 
-  const props = defineProps({
+const props = defineProps({
     type: {
     type: {
-      type: String,
-      default: 'normal',
+        type: String,
+        default: 'normal',
     },
     },
     color: {
     color: {
-      type: String,
+        type: String,
     },
     },
     width: {
     width: {
-      type: [String, Number],
+        type: [String, Number],
     },
     },
     icon: {
     icon: {
-      type: String,
+        type: String,
     },
     },
-  });
+})
 
 
-  const custom = `customize`;
-  const className = computed(() => (props.color ? custom : props.type));
+const custom = `customize`
+const className = computed(() => (props.color ? custom : props.type))
 
 
-  const style = computed(() => {
+const style = computed(() => {
     const style = {
     const style = {
-      width: normalizeUnitToStyle(props.width),
-    };
+        width: normalizeUnitToStyle(props.width),
+    }
 
 
     if (className.value === custom) {
     if (className.value === custom) {
-      style['--color'] = props.color;
+        style['--color'] = props.color
     }
     }
-    return style;
-  });
+    return style
+})
 </script>
 </script>

+ 8 - 8
packages/components/components.d.ts

@@ -1,14 +1,14 @@
 // For this project development
 // For this project development
-import '@vue/runtime-core';
+import '@vue/runtime-core'
 
 
 declare module '@vue/runtime-core' {
 declare module '@vue/runtime-core' {
-  // GlobalComponents for Volar
-  export interface GlobalComponents {
-    KKAudio: typeof import('./component/audio');
-  }
+    // GlobalComponents for Volar
+    export interface GlobalComponents {
+        KKAudio: typeof import('./component/audio')
+    }
 
 
-  // interface ComponentCustomProperties {
-  // }
+    // interface ComponentCustomProperties {
+    // }
 }
 }
 
 
-export {};
+export {}

File diff suppressed because it is too large
+ 57 - 59
packages/components/icon/iconfont/iconfont.js


+ 9 - 9
packages/components/icon/index.ts

@@ -1,9 +1,9 @@
-import { withInstall } from '@kankan/utils';
-
-import Icon from './src/icon.vue';
-
-export const UIIcon = withInstall(Icon);
-
-export default UIIcon;
-
-export * from './src/icon';
+import { withInstall } from '@kankan/utils'
+
+import Icon from './src/icon.vue'
+
+export const UIIcon = withInstall(Icon)
+
+export default UIIcon
+
+export * from './src/icon'

+ 7 - 7
packages/components/icon/src/icon.ts

@@ -1,7 +1,7 @@
-import type { ExtractPropTypes } from 'vue';
-import type Icon from './icon.vue';
-import { iconProps } from './props';
-
-export type IconProps = ExtractPropTypes<typeof iconProps>;
-
-export type IconInstance = InstanceType<typeof Icon>;
+import type { ExtractPropTypes } from 'vue'
+import type Icon from './icon.vue'
+import { iconProps } from './props'
+
+export type IconProps = ExtractPropTypes<typeof iconProps>
+
+export type IconInstance = InstanceType<typeof Icon>

+ 31 - 33
packages/components/icon/src/icon.vue

@@ -1,48 +1,46 @@
 <template>
 <template>
-  <i
-    class="iconfont ui-kankan-icon icon"
-    :class="className"
-    :style="style"
-    @click="(ev) => emit('click', ev)"
-  >
-    <slot></slot>
+    <i class="iconfont ui-kankan-icon icon" :class="className" :style="style" @click="ev => emit('click', ev)">
+        <slot></slot>
 
 
-    <p class="tip" v-if="tip && os.isPc && !os.isTablet">{{ tip }}</p>
-  </i>
+        <p class="tip" v-if="tip && os.isPc && !os.isTablet">{{ tip }}</p>
+    </i>
 </template>
 </template>
 
 
-<script setup>
-  import { defineProps, computed, defineEmits } from 'vue';
-  import { normalizeUnitToStyle, os } from '@kankan/utils';
-  import { iconProps } from './props';
-  const props = defineProps(iconProps);
+<script lang="ts" setup>
+import { defineProps, computed, defineEmits } from 'vue'
+import { normalizeUnitToStyle, os } from '@kankan/utils'
+import { iconProps } from './props'
+const props = defineProps(iconProps)
 
 
-  const style = computed(() => ({
+const style = computed(() => ({
     'font-size': normalizeUnitToStyle(props.size),
     'font-size': normalizeUnitToStyle(props.size),
     color: props.color,
     color: props.color,
-  }));
-  const className = computed(() => {
+}))
+const className = computed(() => {
     const base = {
     const base = {
-      small: props.small,
-      medium: props.medium,
-      big: props.big,
-      disabled: props.disabled,
-      [`tip-h-` + props.tipH]: true,
-      [`tip-v-` + props.tipV]: true,
-      ['fun-ctrl']: props.ctrl,
-    };
+        small: props.small,
+        medium: props.medium,
+        big: props.big,
+        disabled: props.disabled,
+        [`tip-h-` + props.tipH]: true,
+        [`tip-v-` + props.tipV]: true,
+        ['fun-ctrl']: props.ctrl,
+    }
     if (props.type) {
     if (props.type) {
-      return {
-        ...base,
-        [`icon-${props.type}`]: props.type,
-      };
+        return {
+            ...base,
+            [`icon-${props.type}`]: props.type,
+        }
     } else {
     } else {
-      return base;
+        return base
     }
     }
-  });
+})
 
 
-  const emit = defineEmits(['click']);
+const emit = defineEmits(['click'])
+// defineOptions({
+//   name: 'UIIcon',
+// });
 </script>
 </script>
 <style>
 <style>
-  /* @import url('./iconfont/iconfont.css'); */
+/* @import url('./iconfont/iconfont.css'); */
 </style>
 </style>

+ 23 - 23
packages/components/icon/src/props.ts

@@ -1,23 +1,23 @@
-import { buildProps, definePropType } from '@kankan/utils';
-
-export const iconProps = buildProps({
-  type: { type: String },
-  size: {
-    type: definePropType<number | string>([Number, String]),
-  },
-  color: { type: String },
-  small: { type: Boolean },
-  ctrl: { type: Boolean },
-  medium: { type: Boolean },
-  big: { type: Boolean },
-  disabled: { type: Boolean },
-  tip: { type: String },
-  tipH: {
-    type: String,
-    default: 'center',
-  },
-  tipV: {
-    type: String,
-    default: 'bottom',
-  },
-});
+import { buildProps, definePropType } from '@kankan/utils'
+
+export const iconProps = buildProps({
+    type: { type: String },
+    size: {
+        type: definePropType<number | string>([Number, String]),
+    },
+    color: { type: String },
+    small: { type: Boolean },
+    ctrl: { type: Boolean },
+    medium: { type: Boolean },
+    big: { type: Boolean },
+    disabled: { type: Boolean },
+    tip: { type: String },
+    tipH: {
+        type: String,
+        default: 'center',
+    },
+    tipV: {
+        type: String,
+        default: 'bottom',
+    },
+})

+ 3 - 3
packages/components/index.ts

@@ -1,3 +1,3 @@
-export * from './audio';
-export * from './icon';
-export * from './button';
+export * from './audio'
+export * from './icon'
+export * from './button'

+ 0 - 0
packages/components/input/index.ts


+ 21 - 0
packages/components/input/src/check-radio.vue

@@ -0,0 +1,21 @@
+<template>
+    <div class="input radio" :style="{ width, height }">
+        <input :disabled="disabled" :id="id" type="checkbox" :checked="props.modelValue" @input="ev => emit('update:modelValue', ev.target.checked)" />
+        <span class="replace">
+            <icon type="checkbox" :size="width > height ? height : width" />
+        </span>
+    </div>
+    <label class="label" v-if="props.label" :for="id">
+        {{ props.label }}
+    </label>
+</template>
+
+<script setup lang="ts">
+import icon from '../icon'
+import { checkboxPropsDesc } from './state'
+import { randomId } from '@kankan/utils'
+import { defineProps, defineEmits } from 'vue'
+const props = defineProps(checkboxPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const id = randomId(4)
+</script>

+ 21 - 0
packages/components/input/src/checkbox.vue

@@ -0,0 +1,21 @@
+<template>
+    <div class="input checkbox" :style="{ width, height }" :class="{ disabled }">
+        <input :disabled="disabled" :id="id" type="checkbox" class="replace-input" :checked="props.modelValue" @input="ev => emit('update:modelValue', ev.target.checked)" />
+        <span class="replace">
+            <icon :type="props.modelValue ? 'checkbox' : 'nor'" :size="width > height ? height : width" />
+        </span>
+    </div>
+    <label class="label" v-if="props.label" :for="id">
+        {{ props.label }}
+    </label>
+</template>
+
+<script setup lang="ts">
+import icon from '../icon'
+import { checkboxPropsDesc } from './state'
+import { randomId } from '../../utils'
+import { defineProps, defineEmits } from 'vue'
+const props = defineProps(checkboxPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const id = randomId(4)
+</script>

+ 202 - 0
packages/components/input/src/file.vue

@@ -0,0 +1,202 @@
+<template>
+    <div class="input file" :class="{ suffix: $slots.icon, disabled: disabled, valuable }">
+        <template v-if="valuable">
+            <slot name="valuable" :key="modelValue"></slot>
+        </template>
+        <input title="" class="ui-text" type="file" ref="inputRef" :accept="accept" :multiple="multiple" @change="selectFileHandler" v-if="!maxLen || maxLen > modelValue.length" />
+        <template v-if="!$slots.replace">
+            <span class="replace">
+                <div class="placeholder" v-if="!valuable">
+                    <p><ui-icon type="add" /></p>
+                    <p>{{ placeholder }}</p>
+                    <p class="bottom">
+                        <template v-if="!othPlaceholder">
+                            <template v-if="accept">支持 {{ accept }} 等格式,</template>
+                            <template v-if="normalizeScale">宽*高比例 {{ scale }},</template>
+                            <template v-if="maxSize">大小不超过 {{ sizeStr }}{{ maxLen ? ',' : '' }}</template>
+                            <template v-if="maxLen">个数不超过 {{ maxLen }}个</template>
+                        </template>
+                        <template v-else>
+                            {{ othPlaceholder }}
+                        </template>
+                    </p>
+                </div>
+
+                <span v-else-if="!maxLen || maxLen > modelValue.length">
+                    {{ multiple ? '继续添加' : '替换' }}
+                </span>
+                <span class="tj" v-if="maxLen && modelValue.length">
+                    <span>{{ modelValue.length || 0 }}</span> / {{ maxLen }}
+                </span>
+            </span>
+        </template>
+        <div class="use-replace" v-else>
+            <slot name="replace"></slot>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { filePropsDesc } from './state'
+import { toRawType } from '../../utils'
+// import Message from '../message';
+import Dialog from '../dialog'
+import { defineProps, defineEmits, defineExpose, ref, computed } from 'vue'
+import { useI18n } from '@/i18n'
+const { t } = useI18n({ useScope: 'global' })
+const props = defineProps({
+    ...filePropsDesc,
+})
+const emit = defineEmits(['update:modelValue'])
+const inputRef = ref(null)
+const normalizeScale = computed(() => {
+    if (props.scale) {
+        const [w, h] = props.scale.split(':')
+        if (Number(w) && Number(h)) {
+            return [Number(w), Number(h)]
+        }
+    }
+    return []
+})
+
+const valuable = computed(() => (Array.isArray(props.modelValue) ? props.modelValue.length : !!props.modelValue))
+const sizeStr = computed(() => props.maxSize && props.maxSize / 1024 / 1024 + 'MB')
+
+const supports = {
+    image: {
+        types: ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'],
+        preview(file, url) {
+            return new Promise((resolve, reject) => {
+                const img = new Image()
+                img.onload = () => resolve([img.width, img.height, file])
+                img.onerror = reject
+                img.src = url
+            })
+        },
+    },
+    video: {
+        types: ['video/mp4'],
+        preview(file, url) {
+            return new Promise((resolve, reject) => {
+                const video = document.createElement('video')
+                video.preload = 'metadata'
+                video.onloadedmetadata = () => resolve([video.videoWidth, video.videoHeight, file])
+                video.onerror = reject
+                video.src = url
+            })
+        },
+    },
+}
+
+const producePreviews = files =>
+    Promise.all(
+        files.map(
+            file =>
+                new Promise((resolve, reject) => {
+                    const fr = new FileReader()
+                    fr.onloadend = e => resolve(e.target.result)
+                    fr.onerror = e => loaderror(file, reject(e))
+                    fr.readAsDataURL(file)
+                })
+        )
+    )
+
+const calcScale = (w, h) => parseInt((w / h) * 1000)
+
+const selectFileHandler = async ev => {
+    const fileEl = ev.target
+    const files = Array.from(fileEl.files)
+    const previewError = (e, msg = `预览加载失败!`) => {
+        console.error(e)
+        // Message.error(msg)
+        Dialog.toast({
+            content: msg,
+            type: 'error',
+        })
+        fileEl.value = ''
+    }
+
+    if (props.accept) {
+        for (const file of files) {
+            const accepts = props.accept.split(',').map(atom => {
+                return atom.trim()
+            })
+            const hname = file.name.substr(file.name.lastIndexOf('.')).toLowerCase()
+            if (!accepts.includes(hname)) {
+                // return previewError('格式错误', `仅支持${props.accept}格式文件`)
+                let accept = props.accept.split('.').join('').replace(',', '/').toLowerCase()
+                return previewError('格式错误', `${t('limit.formFile', { form: accept })}`)
+            }
+        }
+    }
+
+    let previews
+    if (props.preview || normalizeScale.value) {
+        try {
+            previews = await producePreviews(files)
+        } catch (e) {
+            return previewError(e)
+        }
+    }
+
+    if (normalizeScale.value) {
+        const sizesConfirm = []
+        for (let i = 0; i < files.length; i++) {
+            const support = Object.values(supports).find(support => support.types.includes(files[i].type))
+            if (support) {
+                sizesConfirm.push(support.preview(files[i], previews[i]))
+            }
+        }
+
+        let sizes
+        try {
+            sizes = await Promise.all(sizesConfirm)
+        } catch (e) {
+            return previewError(e)
+        }
+
+        for (const [w, h, file] of sizes) {
+            const scaleDiff = calcScale(...normalizeScale.value) - calcScale(w, h)
+
+            if (Math.abs(scaleDiff) > 300) {
+                // return previewError('error scale', `${file.name}的比例部位不为${props.scale}`)
+                return previewError('error scale', `${t('limit.scaleFile', { fileName: file.name, scale: props.scale })}`)
+            }
+        }
+    }
+
+    if (props.maxSize) {
+        for (const file of files) {
+            if (file.size > props.maxSize) {
+                // return previewError('error size', `${file.name}的大小超过${sizeStr.value}`)
+                // return previewError('error size', `${file.name}的大小超过${sizeStr.value}`)
+
+                let accept = props.accept.split('.').join('').replace(',', '/').toLowerCase()
+                return previewError('格式错误', `${t('limit.formSize', { size: sizeStr.value, form: accept })}`)
+            }
+        }
+    }
+
+    const value = props.modelValue ? (props.multiple ? (toRawType(props.modelValue) === 'Array' ? props.modelValue : [props.modelValue]) : null) : props.multiple ? [] : null
+
+    const emitData = props.multiple
+        ? props.preview
+            ? [...value, ...files.map((file, i) => ({ file, preview: previews[i] }))]
+            : [...value, files]
+        : props.preview
+        ? { file: files[0], preview: previews[0] }
+        : files[0]
+
+    if (Array.isArray(emitData) && props.maxLen && emitData.length > props.maxLen) {
+        // return previewError('err len', `最多仅支持${props.maxLen}个文件!`)
+        return previewError('err len', `${t('limit.maxLengthFile', { length: props.maxLen })}`)
+    }
+
+    emit('update:modelValue', emitData)
+    fileEl.value = ''
+}
+
+defineExpose({
+    input: inputRef,
+})
+</script>

+ 125 - 0
packages/components/input/src/index.vue

@@ -0,0 +1,125 @@
+<template>
+    <div class="ui-input" v-if="types[type]" :style="style" :class="{ require: props.require, error: props.error }">
+        <component :is="types[type].component" v-bind="childProps" :modelValue="props.modelValue" @update:modelValue="newValue => emit('update:modelValue', newValue)">
+            <template v-for="(slot, name) in $slots" #[name]="raw">
+                <slot :name="name" v-bind="raw"></slot>
+            </template>
+        </component>
+        <slot></slot>
+
+        <p class="error-msg" v-if="error">{{ error }}</p>
+    </div>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+import radio from './radio.vue'
+import checkbox from './checkbox.vue'
+import text from './text.vue'
+import select from './select.vue'
+import range from './range.vue'
+import textarea from './textarea.vue'
+import number from './number.vue'
+import uiSwitch from './switch.vue'
+import file from './file.vue'
+import search from './search.vue'
+import richtext from './richtext.vue'
+import {
+    inputPropsDesc,
+    textPropsDesc,
+    selectPropsDesc,
+    checkboxPropsDesc,
+    radioPropsDesc,
+    rangePropsDesc,
+    numberPropsDesc,
+    switchPropsDesc,
+    textareaPropsDesc,
+    filePropsDesc,
+    searchPropsDesc,
+    richtextPropsDesc,
+} from './state'
+
+const types = {
+    checkbox: {
+        component: checkbox,
+        propsDesc: checkboxPropsDesc,
+    },
+    text: {
+        component: text,
+        propsDesc: textPropsDesc,
+    },
+    select: {
+        component: select,
+        propsDesc: selectPropsDesc,
+    },
+    radio: {
+        component: radio,
+        propsDesc: radioPropsDesc,
+    },
+    range: {
+        component: range,
+        propsDesc: rangePropsDesc,
+    },
+    number: {
+        component: number,
+        propsDesc: numberPropsDesc,
+    },
+    switch: {
+        component: uiSwitch,
+        propsDesc: switchPropsDesc,
+    },
+    textarea: {
+        component: textarea,
+        propsDesc: textareaPropsDesc,
+    },
+    file: {
+        component: file,
+        propsDesc: filePropsDesc,
+    },
+    search: {
+        component: search,
+        propsDesc: searchPropsDesc,
+    },
+    richtext: {
+        component: richtext,
+        propsDesc: richtextPropsDesc,
+    },
+}
+
+const props = defineProps(inputPropsDesc)
+
+const emit = defineEmits(['update:modelValue'])
+const type = computed(() => (types[props.type] ? props.type : 'text'))
+const childProps = computed(() => {
+    const retain = Object.keys(types[type.value].propsDesc)
+    const childProps = {}
+    for (let key in props) {
+        if (retain.includes(key)) {
+            childProps[key] = props[key]
+        }
+    }
+    if (!types[props.type]) {
+        childProps.type = props.type
+    }
+    return childProps
+})
+
+const style = computed(() => {
+    const style = {}
+    const keys = Object.keys(childProps.value)
+
+    if (!keys.includes('width')) {
+        style.width = props.width
+    }
+
+    if (!keys.includes('height')) {
+        style.height = props.height
+    }
+
+    return style
+})
+</script>
+
+<script>
+export default { name: 'UiInput' }
+</script>

+ 76 - 0
packages/components/input/src/number.vue

@@ -0,0 +1,76 @@
+<template>
+    <UIText
+        class="number ready"
+        :class="{ ctrl }"
+        type="number"
+        :right="right"
+        :modelValue="tempValue"
+        :placeholder="placeholder"
+        @update:model-value="updateTempValue"
+        :other="{ min, max, step }"
+        @blur="blurHandler"
+        :readonly="!inInput"
+    >
+        <template v-for="(slot, name) in $slots" #[name]="raw">
+            <slot :name="name" v-bind="raw"></slot>
+        </template>
+        <template #icon v-if="ctrl">
+            <div class="ctrls">
+                <UIIcon type="up-a" ctrl class="up" @click="updateModelValue(normValue(modelValue) + step)" />
+                <UIIcon type="d-r" ctrl class="down" @click="updateModelValue(normValue(modelValue) - step)" />
+            </div>
+        </template>
+    </UIText>
+</template>
+
+<script setup lang="ts">
+import UIText from './text.vue'
+import { numberPropsDesc } from './state'
+import { defineProps, defineEmits, watchEffect, ref } from 'vue'
+import { toRawType } from '@kankan/utils'
+import UIIcon from '../../icon/src/icon.vue'
+
+const emit = defineEmits(['update:modelValue'])
+const props = defineProps(numberPropsDesc)
+
+const isNumber = raw => !(toRawType(raw) === 'Number' ? isNaN(raw) : isNaN(Number(raw)))
+const tempValue = ref(props.modelValue)
+
+watchEffect(() => {
+    tempValue.value = props.modelValue
+})
+const updateTempValue = val => {
+    tempValue.value = val
+    const tval = Number(val)
+    if (!isNaN(tval) && tval !== props.modelValue) {
+        updateModelValue(tval)
+    }
+}
+
+const blurHandler = () => {
+    tempValue.value = props.modelValue
+    updateModelValue(props.modelValue)
+}
+
+const normValue = val => {
+    val = Number(val)
+    if (isNaN(val)) {
+        return props.min || 0
+    } else {
+        return val
+    }
+}
+
+const updateModelValue = val => {
+    val = normValue(val)
+    if (isNumber(props.min)) {
+        let min = Number(props.min)
+        val = val < min ? min : val
+    }
+    if (isNumber(props.max)) {
+        let max = Number(props.max)
+        val = val > max ? max : val
+    }
+    emit('update:modelValue', val)
+}
+</script>

+ 3 - 0
packages/components/input/src/password.vue

@@ -0,0 +1,3 @@
+<template>
+    <div></div>
+</template>

+ 20 - 0
packages/components/input/src/radio.vue

@@ -0,0 +1,20 @@
+<template>
+    <div class="input radio" :style="{ width, height }">
+        <input :name="name" :disabled="disabled" :id="id" type="radio" class="replace-input" :checked="props.modelValue" @change="ev => emit('update:modelValue', ev.target.checked)" />
+        <span class="replace"></span>
+    </div>
+    <label class="label" v-if="props.label || props.icon" :for="id">
+        <Icon :type="props.icon" :tip="props.tip" v-if="props.icon" />
+        {{ props.label }}
+    </label>
+</template>
+
+<script setup lang="ts">
+import Icon from '../icon'
+import { radioPropsDesc } from './state'
+import { randomId } from '../../utils'
+import { defineProps, defineEmits } from 'vue'
+const props = defineProps(radioPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const id = randomId(4)
+</script>

+ 121 - 0
packages/components/input/src/range.vue

@@ -0,0 +1,121 @@
+<template>
+    <div class="input range">
+        <div class="range-content" :class="{ animation: mode === modeEmun.default }" :style="{ '--percentage': percenStyle }" @click="rangeClickHandler" ref="rangeRef">
+            <div class="range-locus" ref="locusRef">
+                <span class="range-slide" @click.stop @mousedown="slideDownHandler">
+                    <span v-if="rangeTips" class="tips">{{ modelValue }}%</span>
+                </span>
+            </div>
+        </div>
+        <div v-if="rangeInput">
+            <UItext v-if="inch" :modelValue="inch" class="range-text" style="pointer-events: none" />
+            <UInumber v-else :modelValue="modelValue" @update:modelValue="inputUpdateHandler" :min="min" :max="max" :step="step" :ctrl="false" right class="range-text" />
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted, defineProps, nextTick } from 'vue'
+import { rangePropsDesc } from './state'
+import UInumber from './number.vue'
+import UItext from './text.vue'
+const props = defineProps(rangePropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const getValue = value => {
+    const calcStep = Math.ceil(1 / props.step)
+    const calcValue = Math.round(value * calcStep)
+    const calcMin = props.min * calcStep
+    const calcMax = props.max * calcStep
+
+    const newVal = calcValue >= calcMax ? calcMax : calcValue <= calcMin ? calcMin : calcValue - (calcValue % (calcStep * props.step))
+
+    return newVal / calcStep
+}
+
+const percen = computed({
+    get() {
+        return (Number(props.modelValue) - props.min) / (props.max - props.min)
+    },
+    set(val) {
+        const len = props.max - props.min
+        let num
+        if (props.limit > 0 && props.min + len * val > props.limit) {
+            num = getValue(props.limit)
+        } else {
+            num = getValue(props.min + len * val)
+        }
+
+        emit('update:modelValue', num)
+    },
+})
+
+const percenStyle = computed(() => `${percen.value * 100}%`)
+
+const inputUpdateHandler = val => {
+    let num
+    if (props.limit > 0 && val > props.limit) {
+        num = getValue(props.limit)
+    } else {
+        num = getValue(val)
+    }
+    emit('update:modelValue', num)
+}
+
+const modeEmun = {
+    slide: 0,
+    default: 1,
+}
+const mode = ref(modeEmun.default)
+const locusWidth = ref(0)
+const locusRef = ref(null)
+const rangeWidth = ref(0)
+const rangeRef = ref(null)
+onMounted(() => {
+    nextTick(() => {
+        locusWidth.value = locusRef.value.offsetWidth
+        rangeWidth.value = rangeRef.value.offsetWidth
+    })
+})
+
+const rangeClickHandler = ev => {
+    percen.value = ev.offsetX / rangeWidth.value
+    console.log(ev.offsetX, rangeWidth.value)
+    //首次点击,获取limit的值
+    let t = setTimeout(() => {
+        const len = props.max - props.min
+        if (props.limit > 0 && props.min + len * percen.value > props.limit) {
+            percen.value = (props.limit - props.min) / len
+        } else {
+            // percen.value = ev.offsetX / rangeWidth.value
+        }
+        clearTimeout(t)
+    }, 0)
+}
+
+const parent = document.documentElement
+const player = document.querySelector('.player[name="main"]')
+const slideDownHandler = ev => {
+    ev.preventDefault()
+    const moveStartX = ev.clientX
+    const startPercen = percen.value
+    mode.value = modeEmun.slide
+    const moveHandler = ev => {
+        ev.preventDefault()
+        const moveX = ev.clientX - moveStartX
+
+        const readyPercen = startPercen + moveX / locusWidth.value
+
+        percen.value = readyPercen < 0 ? 0 : readyPercen > 1 ? 1 : readyPercen
+    }
+
+    const upHandler = _ => {
+        mode.value = modeEmun.default
+        parent.removeEventListener('mousemove', moveHandler, false)
+        parent.removeEventListener('mouseup', upHandler, false)
+        player.removeEventListener('mouseup', upHandler, false)
+    }
+    parent.addEventListener('mousemove', moveHandler, false)
+    parent.addEventListener('mouseup', upHandler, false)
+    player.addEventListener('mouseup', upHandler, false)
+}
+</script>

+ 168 - 0
packages/components/input/src/richtext.vue

@@ -0,0 +1,168 @@
+<template>
+    <div class="input textarea" :class="{ suffix: $slots.icon || maxlength, disabled, right }" ref="textRef">
+        <div
+            contenteditable="true"
+            class="ui-text input-div"
+            @input="inputHandler"
+            :placeholder="props.placeholder"
+            :readonly="readonly"
+            @click="emit('click')"
+            @focus="focusHandler"
+            @blur="blurHandler"
+            @paste="pasteHandler"
+            @compositionstart="compositionstartHandler"
+            @compositionend="compositionendHandler"
+            ref="inputRef"
+            v-bind="other"
+        ></div>
+        <span class="replace"></span>
+        <span v-if="$slots.icon || props.maxlength" class="retouch">
+            <slot name="icon"></slot>
+            <span v-if="props.maxlength" class="len">
+                <span>{{ length }}</span> / {{ maxlength }}
+            </span>
+        </span>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { richtextPropsDesc } from './state'
+import { defineProps, defineEmits, defineExpose, nextTick, ref, watchEffect } from 'vue'
+const props = defineProps({
+    ...richtextPropsDesc,
+})
+
+const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'click', ''])
+const textRef = ref(null)
+const inputRef = ref(null)
+const length = ref(0)
+
+const updateContent = html => {
+    inputRef.value.innerHTML = html
+    length.value = inputRef.value.textContent.length
+}
+
+watchEffect(() => {
+    if (inputRef.value && props.modelValue !== inputRef.value.innerHTML) {
+        updateContent(props.modelValue)
+    }
+})
+
+let inComposition = false
+const compositionstartHandler = () => {
+    inComposition = true
+}
+const compositionendHandler = (ev: any) => {
+    inComposition = false
+    inputHandler(ev)
+}
+
+const inputHandler = (ev: any) => {
+    if (inComposition) return
+    if (!props.maxlength || ev.target.textContent.length <= Number(props.maxlength)) {
+        length.value = inputRef.value.textContent.length
+        emit('update:modelValue', ev.target.innerHTML)
+    } else {
+        nextTick(() => {
+            if (ev.target.innerHTML !== props.modelValue.toString()) {
+                updateContent(props.modelValue.toString())
+                inputFocus()
+            }
+        })
+    }
+}
+//获取当前光标位置
+const getCursortPosition = function (element = inputRef.value) {
+    var caretOffset = 0
+    var doc = element.ownerDocument || element.document
+    var win = doc.defaultView || doc.parentWindow
+    var sel
+    if (typeof win.getSelection != 'undefined') {
+        //谷歌、火狐
+        sel = win.getSelection()
+        if (sel.rangeCount > 0) {
+            //选中的区域
+            var range = win.getSelection().getRangeAt(0)
+            var preCaretRange = range.cloneRange() //克隆一个选中区域
+            preCaretRange.selectNodeContents(element) //设置选中区域的节点内容为当前节点
+            preCaretRange.setEnd(range.endContainer, range.endOffset) //重置选中区域的结束位置
+            caretOffset = preCaretRange.toString().length
+        }
+    } else if ((sel = doc.selection) && sel.type != 'Control') {
+        //IE
+        var textRange = sel.createRange()
+        var preCaretTextRange = doc.body.createTextRange()
+        preCaretTextRange.moveToElementText(element)
+        preCaretTextRange.setEndPoint('EndToEnd', textRange)
+        caretOffset = preCaretTextRange.text.length
+    }
+    return caretOffset
+}
+
+let interval: number
+const focusHandler = () => {
+    clearInterval(interval)
+    interval = window.setInterval(() => {
+        console.log(getCursortPosition())
+        emit('updatePos', getCursortPosition())
+    }, 100)
+    emit('focus')
+}
+const blurHandler = () => {
+    clearInterval(interval)
+    emit('blur')
+}
+
+const inputFocus = () => {
+    inputRef.value.focus()
+    const range = window.getSelection()
+    range.selectAllChildren(inputRef.value)
+    range.collapseToEnd()
+}
+
+const getPasteText = text => {
+    if (!props.maxlength) {
+        return text
+    }
+
+    const $el = document.createElement('div')
+    $el.innerHTML = text
+    if ($el.textContent.length > props.maxlength - length.value) {
+        return $el.textContent.substring(0, props.maxlength - length.value)
+    } else {
+        return text
+    }
+}
+
+const pasteHandler = event => {
+    event.preventDefault()
+    var text
+    var clp = (event.originalEvent || event).clipboardData
+    // 兼容针对于opera ie等浏览器
+    if (clp === undefined || clp === null) {
+        text = window.clipboardData.getData('text') || ''
+        if (text !== '') {
+            if (window.getSelection) {
+                // 针对于ie11 10 9 safari
+                var newNode = document.createElement('span')
+                newNode.innerHTML = getPasteText(text)
+                window.getSelection().getRangeAt(0).insertNode(newNode)
+            } else {
+                document.selection.createRange().pasteHTML(text)
+            }
+        }
+    } else {
+        // 兼容chorme或hotfire
+        text = clp.getData('text/plain') || ''
+        if (text !== '') {
+            document.execCommand('insertText', false, getPasteText(text))
+        }
+    }
+}
+
+defineExpose({
+    root: textRef,
+    input: inputRef,
+    getCursortPosition: getCursortPosition,
+})
+</script>

+ 69 - 0
packages/components/input/src/search.vue

@@ -0,0 +1,69 @@
+<template>
+    <UISelect ref="selectVM" className="search" v-bind="props" :readonly="false" @update:modelValue="update" :labelValue="labelValue" :options="options">
+        <template v-for="(slot, name) in $slots" #[name]="raw">
+            <slot :name="name" v-bind="raw"></slot>
+        </template>
+        <template #icon>
+            <ui-icon type="close" class="clear" small @click="clearHandler" v-if="labelValue" />
+        </template>
+        <template #preIcon>
+            <ui-icon type="search" color="rgba(255,255,255,.3)" small />
+        </template>
+    </UISelect>
+</template>
+
+<script setup lang="ts">
+import { ref, watchEffect, defineEmits, onUnmounted } from 'vue'
+import { searchPropsDesc } from './state'
+import UISelect from './select.vue'
+
+const props = defineProps(searchPropsDesc)
+const labelValue = ref('')
+const options = ref([])
+const selectVM = ref()
+const emit = defineEmits(['update:modelValue'])
+
+watchEffect(() => {
+    options.value = labelValue.value ? props.options.filter(({ label }) => label.includes(labelValue.value)) : props.options
+})
+
+const clearHandler = () => {
+    emit('update:modelValue', null)
+    update('')
+    inputEl.focus()
+}
+
+const update = val => {
+    const valItem = props.options.find(({ value }) => value === val)
+    const labelItem = props.options.find(({ label }) => label === val)
+    const item = valItem || labelItem
+
+    if (item) {
+        labelValue.value = item.label
+        emit('update:modelValue', item.value)
+    } else if (typeof val === 'string') {
+        labelValue.value = val
+    }
+}
+
+update(props.modelValue)
+const blurHandler = () => {
+    setTimeout(() => update(props.modelValue), 100)
+}
+
+let inputEl
+watchEffect(() => {
+    if (selectVM.value) {
+        if (inputEl) {
+            inputEl.removeEventListener('blur', blurHandler)
+            inputEl = null
+        }
+        inputEl = selectVM.value.vmRef.input
+        inputEl.addEventListener('blur', blurHandler)
+    }
+})
+
+onUnmounted(() => {
+    inputEl && inputEl.removeEventListener('blur', blurHandler)
+})
+</script>

+ 138 - 0
packages/components/input/src/select.vue

@@ -0,0 +1,138 @@
+<template>
+    <UItext
+        :disabled="props.disabled"
+        class="select ready"
+        :class="{
+            focus: showOption,
+            [className]: className,
+        }"
+        ref="vmRef"
+        :modelValue="typeof labelValue === 'string' ? labelValue : inputValue"
+        @update:model-value="val => emit('update:modelValue', val)"
+        :width="props.width"
+        :height="props.height"
+        :readonly="readonly"
+        :placeholder="props.placeholder"
+        @blur="blurHandler"
+        @focus="showHandler"
+        @click="clickShowHandler"
+    >
+        <template #icon>
+            <icon type="pull-down" small v-if="!$slots.icon" />
+            <slot name="icon" v-else></slot>
+        </template>
+        <template #preIcon v-if="$slots.preIcon">
+            <slot name="preIcon"></slot>
+        </template>
+    </UItext>
+
+    <UIFloating
+        :mount="mountEl"
+        :refer="vmRef && vmRef.root"
+        width="100%"
+        :class="{
+            show: showOption || props.showOptions,
+            [`dire-${dire}`]: true,
+            ...(floatingClass ? { [floatingClass]: true } : {}),
+        }"
+        class="select-float"
+        :dire="dire === 'top' ? 'left-top' : 'left-bottom'"
+    >
+        <slot name="floating-pre"></slot>
+        <div class="select-replace" :class="{ 'hide-scroll': props.hideScroll }">
+            <ul>
+                <template v-for="option in props.options" :key="option.value">
+                    <li v-if="props.options?.length" :key="option.value" :class="{ active: props.modelValue === option.value }" @mousedown="ev => optionClickHandler(ev, option)">
+                        <template v-if="$slots.option">
+                            <slot name="option" :raw="option" :active="props.modelValue === option.value"></slot>
+                        </template>
+                        <template v-else>{{ option.label }}</template>
+                    </li>
+                    <li v-else class="un-data">{{ unplaceholder }}</li>
+                </template>
+            </ul>
+        </div>
+    </UIFloating>
+</template>
+
+<script setup lang="ts">
+import UItext from './text.vue'
+import UIFloating from '../floating/index.vue'
+import { ref, computed, defineExpose } from 'vue'
+import { selectPropsDesc } from './state'
+import icon from '../icon'
+
+const props = defineProps({
+    ...selectPropsDesc,
+    readonly: {
+        type: Boolean,
+        default: true,
+    },
+    className: {
+        type: String,
+    },
+    labelValue: {
+        type: String,
+        require: false,
+    },
+    dbhide: {
+        type: Boolean,
+        default: true,
+    },
+})
+const emit = defineEmits(['update:modelValue'])
+const vmRef = ref(null)
+const showOption = ref(false)
+const mountEl = document.body
+
+const inputValue = computed(() => {
+    const selectOption = props.options.find(({ value }) => value === props.modelValue)
+    return selectOption ? selectOption.label : ''
+})
+
+const optionClickHandler = (ev, option) => {
+    if (props.stopEl && props.stopEl.toUpperCase() === ev.target.tagName.toUpperCase()) {
+        setTimeout(() => {
+            vmRef.value.input.focus()
+        })
+    } else {
+        clickCount = 0
+        emit('update:modelValue', option.value)
+        vmRef.value.input.focus()
+        showOption.value = false
+    }
+}
+
+let clickCount = 0
+const clickShowHandler = () => {
+    clickCount++
+    if (showOption.value && props.dbhide && !(clickCount % 2)) {
+        showOption.value = false
+        vmRef.value.input.blur()
+    } else {
+        showHandler()
+    }
+}
+
+const showHandler = () => {
+    clearTimeout(timeout)
+    showOption.value = true
+    vmRef.value.input.focus()
+}
+
+let timeout
+const blurHandler = () =>
+    (timeout = setTimeout(() => {
+        showOption.value = false
+        clickCount = 0
+    }, 16))
+
+defineExpose({
+    vmRef,
+    animationRef: {
+        changeShow(show) {
+            showOption.value = show
+        },
+    },
+})
+</script>

+ 253 - 0
packages/components/input/src/state.js

@@ -0,0 +1,253 @@
+const instalcePublic = {
+    name: {
+        type: String,
+    },
+    disabled: {
+        type: [Boolean],
+    },
+    modelValue: {
+        required: false,
+        default: '',
+    },
+    placeholder: {
+        require: false,
+        default: '请输入',
+    },
+}
+
+export const colorPropsDesc = {
+    ...instalcePublic,
+    width: {
+        type: String,
+        default: '100px',
+    },
+    height: {
+        type: String,
+        default: '34px',
+    },
+}
+
+export const filePropsDesc = {
+    ...instalcePublic,
+    placeholder: {
+        require: false,
+        default: '请选择',
+    },
+    othPlaceholder: {
+        require: false,
+        default: '',
+    },
+    accept: {
+        type: String,
+    },
+    scale: {
+        type: String,
+    },
+    multiple: {
+        type: Boolean,
+    },
+    preview: {
+        type: Boolean,
+    },
+    maxSize: {
+        type: Number,
+    },
+    maxLen: {
+        type: Number,
+    },
+}
+
+export const switchPropsDesc = {
+    ...instalcePublic,
+    width: {
+        type: [Number, String],
+    },
+    height: {
+        type: [Number, String],
+    },
+}
+
+export const checkboxPropsDesc = {
+    ...switchPropsDesc,
+    label: {
+        type: String,
+        required: false,
+    },
+}
+
+export const radioPropsDesc = {
+    ...checkboxPropsDesc,
+    icon: {
+        type: String,
+    },
+    tip: {
+        type: String,
+    },
+}
+
+export const textPropsDesc = {
+    ...instalcePublic,
+    maxlength: {
+        type: [String, Number],
+    },
+    placeholder: {
+        type: String,
+        default: '请输入',
+    },
+    readonly: {
+        type: Boolean,
+        default: false,
+    },
+    other: {
+        type: Object,
+        default: () => ({}),
+    },
+    right: {
+        type: Boolean,
+    },
+}
+
+export const textEmitsDesc = ['update:modelValue', 'focus', 'blur', 'click', 'keydown']
+
+export const textareaPropsDesc = {
+    ...textPropsDesc,
+    rich: {
+        type: Boolean,
+    },
+}
+
+export const richtextPropsDesc = {
+    ...textareaPropsDesc,
+    onUpdatePos: Function,
+}
+
+export const selectPropsDesc = {
+    ...textPropsDesc,
+    stopEl: {
+        type: String,
+        require: false,
+    },
+    floatingClass: {
+        type: String,
+        require: false,
+    },
+    showOptions: {
+        type: Boolean,
+        require: false,
+    },
+    placeholder: { ...textPropsDesc.placeholder, default: '请选择' },
+    unplaceholder: { ...textPropsDesc.placeholder, default: '暂无选项' },
+    hideScroll: {
+        type: Boolean,
+        default: false,
+    },
+    options: {
+        type: Array,
+        default: () => [],
+    },
+    dire: {
+        type: String,
+        default: 'bottom',
+    },
+}
+
+export const searchPropsDesc = {
+    ...selectPropsDesc,
+    unplaceholder: { ...textPropsDesc.placeholder, default: '无搜索结果' },
+}
+
+export const numberPropsDesc = {
+    ...textPropsDesc,
+    inInput: {
+        type: Boolean,
+        default: true,
+    },
+    ctrl: {
+        type: Boolean,
+        default: true,
+    },
+    step: {
+        type: Number,
+        require: true,
+        default: 1,
+    },
+    min: {
+        type: [Number, String],
+        require: false,
+    },
+    max: {
+        type: [Number, String],
+        require: false,
+    },
+    limit: {
+        type: Number,
+        default: 0,
+    },
+    inch: {
+        type: [Boolean, String],
+        default: false,
+    },
+    rangeInput: {
+        type: Boolean,
+        default: true,
+    },
+    rangeTips: {
+        type: Boolean,
+        default: false,
+    },
+}
+
+export const rangePropsDesc = {
+    ...numberPropsDesc,
+    min: { ...numberPropsDesc.min, require: true },
+    min: { ...numberPropsDesc.min, require: true },
+}
+
+const summary = {
+    ...checkboxPropsDesc,
+    ...radioPropsDesc,
+    ...selectPropsDesc,
+    ...textPropsDesc,
+    ...rangePropsDesc,
+    ...numberPropsDesc,
+    ...switchPropsDesc,
+    ...textareaPropsDesc,
+    ...filePropsDesc,
+    ...searchPropsDesc,
+    ...richtextPropsDesc,
+    ...colorPropsDesc,
+}
+for (let key in summary) {
+    summary[key] = {
+        ...summary[key],
+        default: undefined,
+    }
+}
+
+export const inputEmitDesc = {
+    text: textEmitsDesc,
+}
+
+export const inputPropsDesc = {
+    ...summary,
+    type: {
+        type: String,
+        required: true,
+        default: 'text',
+    },
+    width: {
+        type: [Number, String],
+    },
+    height: {
+        type: [Number, String],
+    },
+    require: {
+        type: Boolean,
+    },
+    error: {
+        type: String,
+    },
+    disabled: {
+        type: Boolean,
+    },
+}

+ 15 - 0
packages/components/input/src/switch.vue

@@ -0,0 +1,15 @@
+<template>
+    <div class="input switch" :style="{ width, height }" :class="{ disabled }">
+        <input class="replace-input" :disabled="disabled" :id="id" type="checkbox" :checked="props.modelValue" @input="ev => emit('update:modelValue', ev.target.checked)" />
+        <span class="replace"></span>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { switchPropsDesc } from './state'
+import { randomId } from '../../utils'
+import { defineProps, defineEmits } from 'vue'
+const props = defineProps(switchPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const id = randomId(4)
+</script>

+ 73 - 0
packages/components/input/src/test.vue

@@ -0,0 +1,73 @@
+<template>
+    <UItext
+        :disabled="props.disabled"
+        class="select"
+        :class="{ ready, focus: showOptions }"
+        ref="vmRef"
+        v-model="inputValue"
+        :width="props.width"
+        :height="props.height"
+        readonly
+        :placeholder="props.placeholder"
+        @blur="blurHandler"
+        @click="changShow(!showOptions)"
+    >
+        <template #icon>
+            <icon type="pull-down" small />
+        </template>
+    </UItext>
+
+    <UIFloating :mount="mountEl" :refer="vmRef && vmRef.root">
+        <ul class="select-replace" :class="{ ready }" :style="originHeight && { 'max-height': maxHeight + 'px' }" ref="contentRef">
+            <li v-for="option in props.options" :key="option.value" :class="{ active: props.modelValue === option.value }" @click="optionClickHandler(option)">
+                {{ option.label }}
+            </li>
+        </ul>
+    </UIFloating>
+</template>
+
+<script setup lang="ts">
+import UItext from './text.vue'
+import UIFloating from '../floating/index.vue'
+import { ref, onUnmounted, computed } from 'vue'
+import { selectPropsDesc } from './state'
+import icon from '../icon'
+import { changeWHFactory } from '../../utils'
+
+const props = defineProps(selectPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const vmRef = ref(null)
+const mountEl = document.body
+let timeout: number
+
+const inputValue = computed(() => {
+    const selectOption = props.options.find(({ value }) => value === props.modelValue)
+    return selectOption ? selectOption.label : ''
+})
+
+const [contentRef, changShow, maxHeight, originHeight, showOptions, ready] = changeWHFactory()
+
+const optionClickHandler = option => {
+    emit('update:modelValue', option.value)
+    vmRef.value.input.focus()
+    changShow(false)
+}
+
+const blurHandler = () => {
+    timeout = setTimeout(() => changShow(false), 500)
+}
+
+const attach = document.documentElement
+const htmlClickHandler = ev => {
+    if (vmRef.value.root.contains(ev.target)) {
+        clearTimeout(timeout)
+    } else {
+        changShow(false)
+    }
+}
+
+attach.addEventListener('click', htmlClickHandler)
+onUnmounted(() => {
+    attach.removeEventListener('click', htmlClickHandler)
+})
+</script>

+ 68 - 0
packages/components/input/src/text.vue

@@ -0,0 +1,68 @@
+<template>
+    <div @click="emit('click')" class="input text" :class="{ suffix: $slots.icon || maxlength, disabled, right, 'pre-suffix': $slots.preIcon }" ref="textRef">
+        <template v-if="type === 'password'">
+            <div class="is-hidden">
+                <input type="text" class="is-hidden" />
+                <input type="password" autocomplete="off" class="is-hidden" />
+            </div>
+        </template>
+
+        <span v-if="$slots.preIcon" class="pre-icon">
+            <slot name="preIcon"></slot>
+        </span>
+        <input
+            class="ui-text"
+            :class="{ icon: $slots.icon }"
+            :type="type"
+            :value="modelValue"
+            autocomplete="off"
+            @input="inputHandler"
+            :placeholder="props.placeholder"
+            :readonly="readonly"
+            :maxlength="props.maxlength"
+            @focus="emit('focus')"
+            @blur="emit('blur')"
+            ref="inputRef"
+            v-bind="other"
+        />
+        <span v-if="$slots.icon || props.maxlength" class="retouch">
+            <slot name="icon"></slot>
+            <span v-if="props.maxlength" class="len">
+                <span>{{ modelValue.length }}</span> / {{ maxlength }}
+            </span>
+        </span>
+        <slot></slot>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { textPropsDesc } from './state'
+import { defineProps, defineEmits, defineExpose, nextTick, ref } from 'vue'
+const props = defineProps({
+    type: {
+        type: String,
+        default: 'text',
+    },
+    ...textPropsDesc,
+})
+const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'click'])
+// const test = () => {
+//   emit('focus');
+// };
+const textRef = ref(null)
+const inputRef = ref(null)
+
+const inputHandler = ev => {
+    emit('update:modelValue', ev.target.value)
+    nextTick(() => {
+        if (ev.target.value !== props.modelValue.toString()) {
+            ev.target.value = props.modelValue.toString()
+        }
+    })
+}
+
+defineExpose({
+    root: textRef,
+    input: inputRef,
+})
+</script>

+ 56 - 0
packages/components/input/src/textarea.vue

@@ -0,0 +1,56 @@
+<template>
+    <div class="input textarea" :class="{ suffix: $slots.icon || maxlength, disabled, right }" ref="textRef">
+        {{ modelValue }}
+        <textarea
+            class="ui-text"
+            :value="modelValue"
+            @input="inputHandler"
+            :placeholder="props.placeholder"
+            :readonly="readonly"
+            :maxlength="props.maxlength"
+            @click="emit('click')"
+            @focus="emit('focus')"
+            @blur="emit('blur')"
+            ref="inputRef"
+            v-bind="other"
+        ></textarea>
+        <span class="replace"></span>
+        <span v-if="$slots.icon || props.maxlength" class="retouch">
+            <slot name="icon"></slot>
+            <span v-if="props.maxlength" class="len">
+                <span>{{ modelValue.length }}</span> / {{ maxlength }}
+            </span>
+        </span>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { textareaPropsDesc } from './state'
+import { defineProps, defineEmits, defineExpose, nextTick, ref } from 'vue'
+const props = defineProps({
+    type: {
+        type: String,
+        default: 'text',
+    },
+    ...textareaPropsDesc,
+})
+
+console.log(props)
+const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'click'])
+const textRef = ref(null)
+const inputRef = ref(null)
+
+const inputHandler = ev => {
+    emit('update:modelValue', ev.target.value)
+    nextTick(() => {
+        if (ev.target.value !== props.modelValue.toString()) {
+            ev.target.value = props.modelValue.toString()
+        }
+    })
+}
+
+defineExpose({
+    root: textRef,
+    input: inputRef,
+})
+</script>

+ 19 - 19
packages/components/rollup.config.js

@@ -1,21 +1,21 @@
-import typescript from '@rollup/plugin-typescript';
-import { terser } from 'rollup-plugin-terser';
-import pkg from './package.json';
+import typescript from '@rollup/plugin-typescript'
+import { terser } from 'rollup-plugin-terser'
+import pkg from './package.json'
 
 
 export default [
 export default [
-  {
-    input: 'src/index.ts',
-    external: Object.keys(pkg.dependencies),
-    plugins: [
-      typescript({
-        tsconfig: './tsconfig.build.json',
-      }),
-      terser({
-        compress: {
-          drop_console: false,
-        },
-      }),
-    ],
-    output: [{ dir: './dist', format: 'esm', sourcemap: true }],
-  },
-];
+    {
+        input: 'src/index.ts',
+        external: Object.keys(pkg.dependencies),
+        plugins: [
+            typescript({
+                tsconfig: './tsconfig.build.json',
+            }),
+            terser({
+                compress: {
+                    drop_console: false,
+                },
+            }),
+        ],
+        output: [{ dir: './dist', format: 'esm', sourcemap: true }],
+    },
+]

+ 19 - 19
packages/directives/rollup.config.js

@@ -1,21 +1,21 @@
-import typescript from '@rollup/plugin-typescript';
-import { terser } from 'rollup-plugin-terser';
-import pkg from './package.json';
+import typescript from '@rollup/plugin-typescript'
+import { terser } from 'rollup-plugin-terser'
+import pkg from './package.json'
 
 
 export default [
 export default [
-  {
-    input: 'src/index.ts',
-    external: Object.keys(pkg.dependencies),
-    plugins: [
-      typescript({
-        tsconfig: './tsconfig.build.json',
-      }),
-      terser({
-        compress: {
-          drop_console: false,
-        },
-      }),
-    ],
-    output: [{ dir: './dist', format: 'esm', sourcemap: true }],
-  },
-];
+    {
+        input: 'src/index.ts',
+        external: Object.keys(pkg.dependencies),
+        plugins: [
+            typescript({
+                tsconfig: './tsconfig.build.json',
+            }),
+            terser({
+                compress: {
+                    drop_console: false,
+                },
+            }),
+        ],
+        output: [{ dir: './dist', format: 'esm', sourcemap: true }],
+    },
+]

+ 39 - 40
packages/utils/dom.ts

@@ -1,40 +1,39 @@
-export const toRawType = (value) => toTypeString(value).slice(8, -1);
-
-export const normalizeUnitToStyle = (unit) => {
-  if (unit === void 0) {
-    return unit;
-  } else if (toRawType(unit) === 'Number') {
-    return unit ? ((unit <= 1) & (unit >= 0) ? 100 * unit + '%' : unit + 'px') : void 0;
-  } else if (unit.includes('px')) {
-    return normalizeUnitToStyle(parseFloat(unit));
-  } else if (unit.includes('%')) {
-    return normalizeUnitToStyle(parseFloat(unit) / 100);
-  } else {
-    return unit;
-  }
-};
-
-export const os = (function () {
-  const ua = navigator.userAgent;
-  const isWindowsPhone = /(?:Windows Phone)/.test(ua);
-  const isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone;
-  const isAndroid = /(?:Android)/.test(ua);
-  const isFireFox = /(?:Firefox)/.test(ua);
-  // const isChrome = /(?:Chrome|CriOS)/.test(ua);
-  const isTablet =
-    /(?:iPad|PlayBook)/.test(ua) ||
-    (isAndroid && !/(?:Mobile)/.test(ua)) ||
-    (isFireFox && /(?:Tablet)/.test(ua));
-  const isPhone = /(?:iPhone)/.test(ua) && !isTablet;
-  const isPc = !isPhone && !isAndroid && !isSymbian;
-
-  if (isPc && navigator.maxTouchPoints > 1) {
-    isTablet = true;
-  }
-  return {
-    isTablet: isTablet,
-    isPhone: isPhone,
-    isAndroid: isAndroid,
-    isPc: isPc,
-  };
-})();
+import { toTypeString } from '@vue/shared'
+
+export const toRawType = (value: any) => toTypeString(value).slice(8, -1)
+
+export const normalizeUnitToStyle = unit => {
+    if (unit === void 0) {
+        return unit
+    } else if (toRawType(unit) === 'Number') {
+        return unit ? ((unit <= 1) & (unit >= 0) ? 100 * unit + '%' : unit + 'px') : void 0
+    } else if (unit.includes('px')) {
+        return normalizeUnitToStyle(parseFloat(unit))
+    } else if (unit.includes('%')) {
+        return normalizeUnitToStyle(parseFloat(unit) / 100)
+    } else {
+        return unit
+    }
+}
+
+export const os = (function () {
+    const ua = navigator.userAgent
+    const isWindowsPhone = /(?:Windows Phone)/.test(ua)
+    const isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone
+    const isAndroid = /(?:Android)/.test(ua)
+    const isFireFox = /(?:Firefox)/.test(ua)
+    // const isChrome = /(?:Chrome|CriOS)/.test(ua);
+    let isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua))
+    const isPhone = /(?:iPhone)/.test(ua) && !isTablet
+    const isPc = !isPhone && !isAndroid && !isSymbian
+
+    if (isPc && navigator.maxTouchPoints > 1) {
+        isTablet = true
+    }
+    return {
+        isTablet: isTablet,
+        isPhone: isPhone,
+        isAndroid: isAndroid,
+        isPc: isPc,
+    }
+})()

+ 64 - 78
packages/utils/dom/aria.ts

@@ -1,21 +1,19 @@
-const FOCUSABLE_ELEMENT_SELECTORS = `a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])`;
+const FOCUSABLE_ELEMENT_SELECTORS = `a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])`
 
 
 /**
 /**
  * Determine if the testing element is visible on screen no matter if its on the viewport or not
  * Determine if the testing element is visible on screen no matter if its on the viewport or not
  */
  */
 export const isVisible = (element: HTMLElement) => {
 export const isVisible = (element: HTMLElement) => {
-  if (process.env.NODE_ENV === 'test') return true;
-  const computed = getComputedStyle(element);
-  // element.offsetParent won't work on fix positioned
-  // WARNING: potential issue here, going to need some expert advices on this issue
-  return computed.position === 'fixed' ? false : element.offsetParent !== null;
-};
+    if (process.env.NODE_ENV === 'test') return true
+    const computed = getComputedStyle(element)
+    // element.offsetParent won't work on fix positioned
+    // WARNING: potential issue here, going to need some expert advices on this issue
+    return computed.position === 'fixed' ? false : element.offsetParent !== null
+}
 
 
 export const obtainAllFocusableElements = (element: HTMLElement): HTMLElement[] => {
 export const obtainAllFocusableElements = (element: HTMLElement): HTMLElement[] => {
-  return Array.from(element.querySelectorAll<HTMLElement>(FOCUSABLE_ELEMENT_SELECTORS)).filter(
-    (item: HTMLElement) => isFocusable(item) && isVisible(item),
-  );
-};
+    return Array.from(element.querySelectorAll<HTMLElement>(FOCUSABLE_ELEMENT_SELECTORS)).filter((item: HTMLElement) => isFocusable(item) && isVisible(item))
+}
 
 
 /**
 /**
  * @desc Determine if target element is focusable
  * @desc Determine if target element is focusable
@@ -23,40 +21,32 @@ export const obtainAllFocusableElements = (element: HTMLElement): HTMLElement[]
  * @returns {Boolean} true if it is focusable
  * @returns {Boolean} true if it is focusable
  */
  */
 export const isFocusable = (element: HTMLElement): boolean => {
 export const isFocusable = (element: HTMLElement): boolean => {
-  if (
-    element.tabIndex > 0 ||
-    (element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)
-  ) {
-    return true;
-  }
-  // HTMLButtonElement has disabled
-  if ((element as HTMLButtonElement).disabled) {
-    return false;
-  }
-
-  switch (element.nodeName) {
-    case 'A': {
-      // casting current element to Specific HTMLElement in order to be more type precise
-      return (
-        !!(element as HTMLAnchorElement).href && (element as HTMLAnchorElement).rel !== 'ignore'
-      );
-    }
-    case 'INPUT': {
-      return !(
-        (element as HTMLInputElement).type === 'hidden' ||
-        (element as HTMLInputElement).type === 'file'
-      );
+    if (element.tabIndex > 0 || (element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)) {
+        return true
     }
     }
-    case 'BUTTON':
-    case 'SELECT':
-    case 'TEXTAREA': {
-      return true;
+    // HTMLButtonElement has disabled
+    if ((element as HTMLButtonElement).disabled) {
+        return false
     }
     }
-    default: {
-      return false;
+
+    switch (element.nodeName) {
+        case 'A': {
+            // casting current element to Specific HTMLElement in order to be more type precise
+            return !!(element as HTMLAnchorElement).href && (element as HTMLAnchorElement).rel !== 'ignore'
+        }
+        case 'INPUT': {
+            return !((element as HTMLInputElement).type === 'hidden' || (element as HTMLInputElement).type === 'file')
+        }
+        case 'BUTTON':
+        case 'SELECT':
+        case 'TEXTAREA': {
+            return true
+        }
+        default: {
+            return false
+        }
     }
     }
-  }
-};
+}
 
 
 /**
 /**
  * @desc Set Attempt to set focus on the current node.
  * @desc Set Attempt to set focus on the current node.
@@ -66,13 +56,13 @@ export const isFocusable = (element: HTMLElement): boolean => {
  *  true if element is focused.
  *  true if element is focused.
  */
  */
 export const attemptFocus = (element: HTMLElement): boolean => {
 export const attemptFocus = (element: HTMLElement): boolean => {
-  if (!isFocusable(element)) {
-    return false;
-  }
-  // Remove the old try catch block since there will be no error to be thrown
-  element.focus?.();
-  return document.activeElement === element;
-};
+    if (!isFocusable(element)) {
+        return false
+    }
+    // Remove the old try catch block since there will be no error to be thrown
+    element.focus?.()
+    return document.activeElement === element
+}
 
 
 /**
 /**
  * Trigger an event
  * Trigger an event
@@ -81,39 +71,35 @@ export const attemptFocus = (element: HTMLElement): boolean => {
  * @param  {String} name
  * @param  {String} name
  * @param  {*} opts
  * @param  {*} opts
  */
  */
-export const triggerEvent = function (
-  elm: HTMLElement,
-  name: string,
-  ...opts: Array<boolean>
-): HTMLElement {
-  let eventName: string;
+export const triggerEvent = function (elm: HTMLElement, name: string, ...opts: Array<boolean>): HTMLElement {
+    let eventName: string
 
 
-  if (name.includes('mouse') || name.includes('click')) {
-    eventName = 'MouseEvents';
-  } else if (name.includes('key')) {
-    eventName = 'KeyboardEvent';
-  } else {
-    eventName = 'HTMLEvents';
-  }
-  const evt = document.createEvent(eventName);
+    if (name.includes('mouse') || name.includes('click')) {
+        eventName = 'MouseEvents'
+    } else if (name.includes('key')) {
+        eventName = 'KeyboardEvent'
+    } else {
+        eventName = 'HTMLEvents'
+    }
+    const evt = document.createEvent(eventName)
 
 
-  evt.initEvent(name, ...opts);
-  elm.dispatchEvent(evt);
-  return elm;
-};
+    evt.initEvent(name, ...opts)
+    elm.dispatchEvent(evt)
+    return elm
+}
 
 
-export const isLeaf = (el: HTMLElement) => !el.getAttribute('aria-owns');
+export const isLeaf = (el: HTMLElement) => !el.getAttribute('aria-owns')
 
 
 export const getSibling = (el: HTMLElement, distance: number, elClass: string) => {
 export const getSibling = (el: HTMLElement, distance: number, elClass: string) => {
-  const { parentNode } = el;
-  if (!parentNode) return null;
-  const siblings = parentNode.querySelectorAll(elClass);
-  const index = Array.prototype.indexOf.call(siblings, el);
-  return siblings[index + distance] || null;
-};
+    const { parentNode } = el
+    if (!parentNode) return null
+    const siblings = parentNode.querySelectorAll(elClass)
+    const index = Array.prototype.indexOf.call(siblings, el)
+    return siblings[index + distance] || null
+}
 
 
 export const focusNode = (el: HTMLElement) => {
 export const focusNode = (el: HTMLElement) => {
-  if (!el) return;
-  el.focus();
-  !isLeaf(el) && el.click();
-};
+    if (!el) return
+    el.focus()
+    !isLeaf(el) && el.click()
+}

+ 11 - 15
packages/utils/dom/event.ts

@@ -1,19 +1,15 @@
-export const composeEventHandlers = <E>(
-  theirsHandler?: (event: E) => boolean | void,
-  oursHandler?: (event: E) => void,
-  { checkForDefaultPrevented = true } = {},
-) => {
-  const handleEvent = (event: E) => {
-    const shouldPrevent = theirsHandler?.(event);
+export const composeEventHandlers = <E>(theirsHandler?: (event: E) => boolean | void, oursHandler?: (event: E) => void, { checkForDefaultPrevented = true } = {}) => {
+    const handleEvent = (event: E) => {
+        const shouldPrevent = theirsHandler?.(event)
 
 
-    if (checkForDefaultPrevented === false || !shouldPrevent) {
-      return oursHandler?.(event);
+        if (checkForDefaultPrevented === false || !shouldPrevent) {
+            return oursHandler?.(event)
+        }
     }
     }
-  };
-  return handleEvent;
-};
+    return handleEvent
+}
 
 
-type WhenMouseHandler = (e: PointerEvent) => any;
+type WhenMouseHandler = (e: PointerEvent) => any
 export const whenMouse = (handler: WhenMouseHandler): WhenMouseHandler => {
 export const whenMouse = (handler: WhenMouseHandler): WhenMouseHandler => {
-  return (e: PointerEvent) => (e.pointerType === 'mouse' ? handler(e) : undefined);
-};
+    return (e: PointerEvent) => (e.pointerType === 'mouse' ? handler(e) : undefined)
+}

+ 5 - 5
packages/utils/dom/index.ts

@@ -1,5 +1,5 @@
-export * from './aria';
-export * from './event';
-export * from './position';
-export * from './scroll';
-export * from './style';
+export * from './aria'
+export * from './event'
+export * from './position'
+export * from './scroll'
+export * from './style'

+ 45 - 50
packages/utils/dom/position.ts

@@ -1,60 +1,55 @@
-import { isClient } from '@vueuse/core';
+import { isClient } from '@vueuse/core'
 
 
 export const isInContainer = (el?: Element, container?: Element | Window): boolean => {
 export const isInContainer = (el?: Element, container?: Element | Window): boolean => {
-  if (!isClient || !el || !container) return false;
-
-  const elRect = el.getBoundingClientRect();
-
-  let containerRect: Pick<DOMRect, 'top' | 'bottom' | 'left' | 'right'>;
-  if (container instanceof Element) {
-    containerRect = container.getBoundingClientRect();
-  } else {
-    containerRect = {
-      top: 0,
-      right: window.innerWidth,
-      bottom: window.innerHeight,
-      left: 0,
-    };
-  }
-  return (
-    elRect.top < containerRect.bottom &&
-    elRect.bottom > containerRect.top &&
-    elRect.right > containerRect.left &&
-    elRect.left < containerRect.right
-  );
-};
+    if (!isClient || !el || !container) return false
+
+    const elRect = el.getBoundingClientRect()
+
+    let containerRect: Pick<DOMRect, 'top' | 'bottom' | 'left' | 'right'>
+    if (container instanceof Element) {
+        containerRect = container.getBoundingClientRect()
+    } else {
+        containerRect = {
+            top: 0,
+            right: window.innerWidth,
+            bottom: window.innerHeight,
+            left: 0,
+        }
+    }
+    return elRect.top < containerRect.bottom && elRect.bottom > containerRect.top && elRect.right > containerRect.left && elRect.left < containerRect.right
+}
 
 
 export const getOffsetTop = (el: HTMLElement) => {
 export const getOffsetTop = (el: HTMLElement) => {
-  let offset = 0;
-  let parent = el;
+    let offset = 0
+    let parent = el
 
 
-  while (parent) {
-    offset += parent.offsetTop;
-    parent = parent.offsetParent as HTMLElement;
-  }
+    while (parent) {
+        offset += parent.offsetTop
+        parent = parent.offsetParent as HTMLElement
+    }
 
 
-  return offset;
-};
+    return offset
+}
 
 
 export const getOffsetTopDistance = (el: HTMLElement, containerEl: HTMLElement) => {
 export const getOffsetTopDistance = (el: HTMLElement, containerEl: HTMLElement) => {
-  return Math.abs(getOffsetTop(el) - getOffsetTop(containerEl));
-};
+    return Math.abs(getOffsetTop(el) - getOffsetTop(containerEl))
+}
 
 
 export const getClientXY = (event: MouseEvent | TouchEvent) => {
 export const getClientXY = (event: MouseEvent | TouchEvent) => {
-  let clientX: number;
-  let clientY: number;
-  if (event.type === 'touchend') {
-    clientY = (event as TouchEvent).changedTouches[0].clientY;
-    clientX = (event as TouchEvent).changedTouches[0].clientX;
-  } else if (event.type.startsWith('touch')) {
-    clientY = (event as TouchEvent).touches[0].clientY;
-    clientX = (event as TouchEvent).touches[0].clientX;
-  } else {
-    clientY = (event as MouseEvent).clientY;
-    clientX = (event as MouseEvent).clientX;
-  }
-  return {
-    clientX,
-    clientY,
-  };
-};
+    let clientX: number
+    let clientY: number
+    if (event.type === 'touchend') {
+        clientY = (event as TouchEvent).changedTouches[0].clientY
+        clientX = (event as TouchEvent).changedTouches[0].clientX
+    } else if (event.type.startsWith('touch')) {
+        clientY = (event as TouchEvent).touches[0].clientY
+        clientX = (event as TouchEvent).touches[0].clientX
+    } else {
+        clientY = (event as MouseEvent).clientY
+        clientX = (event as MouseEvent).clientX
+    }
+    return {
+        clientX,
+        clientY,
+    }
+}

+ 73 - 76
packages/utils/dom/scroll.ts

@@ -1,91 +1,88 @@
-import { isClient } from '@vueuse/core';
-import { getStyle } from './style';
+import { isClient } from '@vueuse/core'
+import { getStyle } from './style'
 
 
 export const isScroll = (el: HTMLElement, isVertical?: boolean): boolean => {
 export const isScroll = (el: HTMLElement, isVertical?: boolean): boolean => {
-  if (!isClient) return false;
-
-  const key = (
-    {
-      undefined: 'overflow',
-      true: 'overflow-y',
-      false: 'overflow-x',
-    } as const
-  )[String(isVertical)]!;
-  const overflow = getStyle(el, key);
-  return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s));
-};
-
-export const getScrollContainer = (
-  el: HTMLElement,
-  isVertical?: boolean,
-): Window | HTMLElement | undefined => {
-  if (!isClient) return;
-
-  let parent: HTMLElement = el;
-  while (parent) {
-    if ([window, document, document.documentElement].includes(parent)) return window;
-
-    if (isScroll(parent, isVertical)) return parent;
-
-    parent = parent.parentNode as HTMLElement;
-  }
-
-  return parent;
-};
-
-let scrollBarWidth: number;
+    if (!isClient) return false
+
+    const key = (
+        {
+            undefined: 'overflow',
+            true: 'overflow-y',
+            false: 'overflow-x',
+        } as const
+    )[String(isVertical)]!
+    const overflow = getStyle(el, key)
+    return ['scroll', 'auto', 'overlay'].some(s => overflow.includes(s))
+}
+
+export const getScrollContainer = (el: HTMLElement, isVertical?: boolean): Window | HTMLElement | undefined => {
+    if (!isClient) return
+
+    let parent: HTMLElement = el
+    while (parent) {
+        if ([window, document, document.documentElement].includes(parent)) return window
+
+        if (isScroll(parent, isVertical)) return parent
+
+        parent = parent.parentNode as HTMLElement
+    }
+
+    return parent
+}
+
+let scrollBarWidth: number
 export const getScrollBarWidth = (namespace: string): number => {
 export const getScrollBarWidth = (namespace: string): number => {
-  if (!isClient) return 0;
-  if (scrollBarWidth !== undefined) return scrollBarWidth;
+    if (!isClient) return 0
+    if (scrollBarWidth !== undefined) return scrollBarWidth
 
 
-  const outer = document.createElement('div');
-  outer.className = `${namespace}-scrollbar__wrap`;
-  outer.style.visibility = 'hidden';
-  outer.style.width = '100px';
-  outer.style.position = 'absolute';
-  outer.style.top = '-9999px';
-  document.body.appendChild(outer);
+    const outer = document.createElement('div')
+    outer.className = `${namespace}-scrollbar__wrap`
+    outer.style.visibility = 'hidden'
+    outer.style.width = '100px'
+    outer.style.position = 'absolute'
+    outer.style.top = '-9999px'
+    document.body.appendChild(outer)
 
 
-  const widthNoScroll = outer.offsetWidth;
-  outer.style.overflow = 'scroll';
+    const widthNoScroll = outer.offsetWidth
+    outer.style.overflow = 'scroll'
 
 
-  const inner = document.createElement('div');
-  inner.style.width = '100%';
-  outer.appendChild(inner);
+    const inner = document.createElement('div')
+    inner.style.width = '100%'
+    outer.appendChild(inner)
 
 
-  const widthWithScroll = inner.offsetWidth;
-  outer.parentNode?.removeChild(outer);
-  scrollBarWidth = widthNoScroll - widthWithScroll;
+    const widthWithScroll = inner.offsetWidth
+    outer.parentNode?.removeChild(outer)
+    scrollBarWidth = widthNoScroll - widthWithScroll
 
 
-  return scrollBarWidth;
-};
+    return scrollBarWidth
+}
 
 
 /**
 /**
  * Scroll with in the container element, positioning the **selected** element at the top
  * Scroll with in the container element, positioning the **selected** element at the top
  * of the container
  * of the container
  */
  */
 export function scrollIntoView(container: HTMLElement, selected: HTMLElement): void {
 export function scrollIntoView(container: HTMLElement, selected: HTMLElement): void {
-  if (!isClient) return;
-
-  if (!selected) {
-    container.scrollTop = 0;
-    return;
-  }
-
-  const offsetParents: HTMLElement[] = [];
-  let pointer = selected.offsetParent;
-  while (pointer !== null && container !== pointer && container.contains(pointer)) {
-    offsetParents.push(pointer as HTMLElement);
-    pointer = (pointer as HTMLElement).offsetParent;
-  }
-  const top = selected.offsetTop + offsetParents.reduce((prev, curr) => prev + curr.offsetTop, 0);
-  const bottom = top + selected.offsetHeight;
-  const viewRectTop = container.scrollTop;
-  const viewRectBottom = viewRectTop + container.clientHeight;
-
-  if (top < viewRectTop) {
-    container.scrollTop = top;
-  } else if (bottom > viewRectBottom) {
-    container.scrollTop = bottom - container.clientHeight;
-  }
+    if (!isClient) return
+
+    if (!selected) {
+        container.scrollTop = 0
+        return
+    }
+
+    const offsetParents: HTMLElement[] = []
+    let pointer = selected.offsetParent
+    while (pointer !== null && container !== pointer && container.contains(pointer)) {
+        offsetParents.push(pointer as HTMLElement)
+        pointer = (pointer as HTMLElement).offsetParent
+    }
+    const top = selected.offsetTop + offsetParents.reduce((prev, curr) => prev + curr.offsetTop, 0)
+    const bottom = top + selected.offsetHeight
+    const viewRectTop = container.scrollTop
+    const viewRectBottom = viewRectTop + container.clientHeight
+
+    if (top < viewRectTop) {
+        container.scrollTop = top
+    } else if (bottom > viewRectBottom) {
+        container.scrollTop = bottom - container.clientHeight
+    }
 }
 }

+ 53 - 57
packages/utils/dom/style.ts

@@ -1,76 +1,72 @@
-import { isClient } from '@vueuse/core';
-import { isNumber, isObject, isString } from '../types';
-import { camelize } from '../strings';
-import { entriesOf, keysOf } from '../objects';
-import { debugWarn } from '../error';
-import type { CSSProperties } from 'vue';
+import { isClient } from '@vueuse/core'
+import { isNumber, isObject, isString } from '../types'
+import { camelize } from '../strings'
+import { entriesOf, keysOf } from '../objects'
+import { debugWarn } from '../error'
+import type { CSSProperties } from 'vue'
 
 
-const SCOPE = 'utils/dom/style';
+const SCOPE = 'utils/dom/style'
 
 
-export const classNameToArray = (cls = '') => cls.split(' ').filter((item) => !!item.trim());
+export const classNameToArray = (cls = '') => cls.split(' ').filter(item => !!item.trim())
 
 
 export const hasClass = (el: Element, cls: string): boolean => {
 export const hasClass = (el: Element, cls: string): boolean => {
-  if (!el || !cls) return false;
-  if (cls.includes(' ')) throw new Error('className should not contain space.');
-  return el.classList.contains(cls);
-};
+    if (!el || !cls) return false
+    if (cls.includes(' ')) throw new Error('className should not contain space.')
+    return el.classList.contains(cls)
+}
 
 
 export const addClass = (el: Element, cls: string) => {
 export const addClass = (el: Element, cls: string) => {
-  if (!el || !cls.trim()) return;
-  el.classList.add(...classNameToArray(cls));
-};
+    if (!el || !cls.trim()) return
+    el.classList.add(...classNameToArray(cls))
+}
 
 
 export const removeClass = (el: Element, cls: string) => {
 export const removeClass = (el: Element, cls: string) => {
-  if (!el || !cls.trim()) return;
-  el.classList.remove(...classNameToArray(cls));
-};
+    if (!el || !cls.trim()) return
+    el.classList.remove(...classNameToArray(cls))
+}
 
 
 export const getStyle = (element: HTMLElement, styleName: keyof CSSProperties): string => {
 export const getStyle = (element: HTMLElement, styleName: keyof CSSProperties): string => {
-  if (!isClient || !element || !styleName) return '';
+    if (!isClient || !element || !styleName) return ''
 
 
-  let key = camelize(styleName);
-  if (key === 'float') key = 'cssFloat';
-  try {
-    const style = (element.style as any)[key];
-    if (style) return style;
-    const computed: any = document.defaultView?.getComputedStyle(element, '');
-    return computed ? computed[key] : '';
-  } catch {
-    return (element.style as any)[key];
-  }
-};
+    let key = camelize(styleName)
+    if (key === 'float') key = 'cssFloat'
+    try {
+        const style = (element.style as any)[key]
+        if (style) return style
+        const computed: any = document.defaultView?.getComputedStyle(element, '')
+        return computed ? computed[key] : ''
+    } catch {
+        return (element.style as any)[key]
+    }
+}
 
 
-export const setStyle = (
-  element: HTMLElement,
-  styleName: CSSProperties | keyof CSSProperties,
-  value?: string | number,
-) => {
-  if (!element || !styleName) return;
+export const setStyle = (element: HTMLElement, styleName: CSSProperties | keyof CSSProperties, value?: string | number) => {
+    if (!element || !styleName) return
 
 
-  if (isObject(styleName)) {
-    entriesOf(styleName).forEach(([prop, value]) => setStyle(element, prop, value));
-  } else {
-    const key: any = camelize(styleName);
-    element.style[key] = value as any;
-  }
-};
+    if (isObject(styleName)) {
+        entriesOf(styleName).forEach(([prop, value]) => setStyle(element, prop, value))
+    } else {
+        const key: any = camelize(styleName)
+        element.style[key] = value as any
+    }
+}
 
 
 export const removeStyle = (element: HTMLElement, style: CSSProperties | keyof CSSProperties) => {
 export const removeStyle = (element: HTMLElement, style: CSSProperties | keyof CSSProperties) => {
-  if (!element || !style) return;
+    if (!element || !style) return
 
 
-  if (isObject(style)) {
-    keysOf(style).forEach((prop) => removeStyle(element, prop));
-  } else {
-    setStyle(element, style, '');
-  }
-};
+    if (isObject(style)) {
+        keysOf(style).forEach(prop => removeStyle(element, prop))
+    } else {
+        setStyle(element, style, '')
+    }
+}
 
 
 export function addUnit(value?: string | number, defaultUnit = 'px') {
 export function addUnit(value?: string | number, defaultUnit = 'px') {
-  if (!value) return '';
-  if (isString(value)) {
-    return value;
-  } else if (isNumber(value)) {
-    return `${value}${defaultUnit}`;
-  }
-  debugWarn(SCOPE, 'binding value must be a string or number');
+    if (!value) return ''
+    if (isString(value)) {
+        return value
+    } else if (isNumber(value)) {
+        return `${value}${defaultUnit}`
+    }
+    debugWarn(SCOPE, 'binding value must be a string or number')
 }
 }

+ 13 - 13
packages/utils/error.ts

@@ -1,22 +1,22 @@
-import { isString } from './types';
+import { isString } from './types'
 
 
 class ElementPlusError extends Error {
 class ElementPlusError extends Error {
-  constructor(m: string) {
-    super(m);
-    this.name = 'ElementPlusError';
-  }
+    constructor(m: string) {
+        super(m)
+        this.name = 'ElementPlusError'
+    }
 }
 }
 
 
 export function throwError(scope: string, m: string): never {
 export function throwError(scope: string, m: string): never {
-  throw new ElementPlusError(`[${scope}] ${m}`);
+    throw new ElementPlusError(`[${scope}] ${m}`)
 }
 }
 
 
-export function debugWarn(err: Error): void;
-export function debugWarn(scope: string, message: string): void;
+export function debugWarn(err: Error): void
+export function debugWarn(scope: string, message: string): void
 export function debugWarn(scope: string | Error, message?: string): void {
 export function debugWarn(scope: string | Error, message?: string): void {
-  if (process.env.NODE_ENV !== 'production') {
-    const error: Error = isString(scope) ? new ElementPlusError(`[${scope}] ${message}`) : scope;
-    // eslint-disable-next-line no-console
-    console.warn(error);
-  }
+    if (process.env.NODE_ENV !== 'production') {
+        const error: Error = isString(scope) ? new ElementPlusError(`[${scope}] ${message}`) : scope
+        // eslint-disable-next-line no-console
+        console.warn(error)
+    }
 }
 }

+ 3 - 2
packages/utils/index.ts

@@ -1,2 +1,3 @@
-export * from './vue';
-export * from './dom';
+export * from './vue'
+export * from './dom'
+export * from './normal'

+ 9 - 0
packages/utils/normal.ts

@@ -0,0 +1,9 @@
+export const randomId = (e = 6): string => {
+    const t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
+    const a = t.length
+    let n = ''
+    for (let i = 0; i < e; i++) {
+        n += t.charAt(Math.floor(Math.random() * a))
+    }
+    return n
+}

+ 16 - 20
packages/utils/objects.ts

@@ -1,22 +1,18 @@
-import { get, set } from 'lodash-unified';
-import type { Entries } from 'type-fest';
-import type { Arrayable } from '.';
+import { get, set } from 'lodash-unified'
+import type { Entries } from 'type-fest'
+import type { Arrayable } from '.'
 
 
-export const keysOf = <T>(arr: T) => Object.keys(arr) as Array<keyof T>;
-export const entriesOf = <T>(arr: T) => Object.entries(arr) as Entries<T>;
-export { hasOwn } from '@vue/shared';
+export const keysOf = <T>(arr: T) => Object.keys(arr) as Array<keyof T>
+export const entriesOf = <T>(arr: T) => Object.entries(arr) as Entries<T>
+export { hasOwn } from '@vue/shared'
 
 
-export const getProp = <T = any>(
-  obj: Record<string, any>,
-  path: Arrayable<string>,
-  defaultValue?: any,
-): { value: T } => {
-  return {
-    get value() {
-      return get(obj, path, defaultValue);
-    },
-    set value(val: any) {
-      set(obj, path, val);
-    },
-  };
-};
+export const getProp = <T = any>(obj: Record<string, any>, path: Arrayable<string>, defaultValue?: any): { value: T } => {
+    return {
+        get value() {
+            return get(obj, path, defaultValue)
+        },
+        set value(val: any) {
+            set(obj, path, val)
+        },
+    }
+}

+ 12 - 15
packages/utils/types.ts

@@ -1,22 +1,19 @@
-import { isArray, isObject } from '@vue/shared';
-import { isNil } from 'lodash-unified';
+import { isArray, isObject } from '@vue/shared'
+import { isNil } from 'lodash-unified'
 
 
-export { isArray, isFunction, isObject, isString, isDate, isPromise, isSymbol } from '@vue/shared';
-export { isBoolean, isNumber } from '@vueuse/core';
-export { isVNode } from 'vue';
+export { isArray, isFunction, isObject, isString, isDate, isPromise, isSymbol } from '@vue/shared'
+export { isBoolean, isNumber } from '@vueuse/core'
+export { isVNode } from 'vue'
 
 
-export const isUndefined = (val: any): val is undefined => val === undefined;
+export const isUndefined = (val: any): val is undefined => val === undefined
 
 
-export const isEmpty = (val: unknown) =>
-  (!val && val !== 0) ||
-  (isArray(val) && val.length === 0) ||
-  (isObject(val) && !Object.keys(val).length);
+export const isEmpty = (val: unknown) => (!val && val !== 0) || (isArray(val) && val.length === 0) || (isObject(val) && !Object.keys(val).length)
 
 
 export const isElement = (e: unknown): e is Element => {
 export const isElement = (e: unknown): e is Element => {
-  if (typeof Element === 'undefined') return false;
-  return e instanceof Element;
-};
+    if (typeof Element === 'undefined') return false
+    return e instanceof Element
+}
 
 
 export const isPropAbsent = (prop: unknown): prop is null | undefined => {
 export const isPropAbsent = (prop: unknown): prop is null | undefined => {
-  return isNil(prop);
-};
+    return isNil(prop)
+}

+ 19 - 19
packages/utils/vue/global-node.ts

@@ -1,32 +1,32 @@
-import { isClient } from '@vueuse/core';
+import { isClient } from '@vueuse/core'
 
 
-const globalNodes: HTMLElement[] = [];
-let target: HTMLElement = !isClient ? (undefined as any) : document.body;
+const globalNodes: HTMLElement[] = []
+let target: HTMLElement = !isClient ? (undefined as any) : document.body
 
 
 export function createGlobalNode(id?: string) {
 export function createGlobalNode(id?: string) {
-  const el = document.createElement('div');
-  if (id !== undefined) {
-    el.setAttribute('id', id);
-  }
+    const el = document.createElement('div')
+    if (id !== undefined) {
+        el.setAttribute('id', id)
+    }
 
 
-  target.appendChild(el);
-  globalNodes.push(el);
+    target.appendChild(el)
+    globalNodes.push(el)
 
 
-  return el;
+    return el
 }
 }
 
 
 export function removeGlobalNode(el: HTMLElement) {
 export function removeGlobalNode(el: HTMLElement) {
-  globalNodes.splice(globalNodes.indexOf(el), 1);
-  el.remove();
+    globalNodes.splice(globalNodes.indexOf(el), 1)
+    el.remove()
 }
 }
 
 
 export function changeGlobalNodesTarget(el: HTMLElement) {
 export function changeGlobalNodesTarget(el: HTMLElement) {
-  if (el === target) return;
+    if (el === target) return
 
 
-  target = el;
-  globalNodes.forEach((el) => {
-    if (el.contains(target) === false) {
-      target.appendChild(el);
-    }
-  });
+    target = el
+    globalNodes.forEach(el => {
+        if (el.contains(target) === false) {
+            target.appendChild(el)
+        }
+    })
 }
 }

+ 21 - 30
packages/utils/vue/icon.ts

@@ -1,40 +1,31 @@
-import {
-  CircleCheck,
-  CircleClose,
-  CircleCloseFilled,
-  Close,
-  InfoFilled,
-  Loading,
-  SuccessFilled,
-  WarningFilled,
-} from '@element-plus/icons-vue';
-import { definePropType } from './props';
+import { CircleCheck, CircleClose, CircleCloseFilled, Close, InfoFilled, Loading, SuccessFilled, WarningFilled } from '@element-plus/icons-vue'
+import { definePropType } from './props'
 
 
-import type { Component } from 'vue';
+import type { Component } from 'vue'
 
 
-export const iconPropType = definePropType<string | Component>([String, Object, Function]);
+export const iconPropType = definePropType<string | Component>([String, Object, Function])
 
 
 export const CloseComponents = {
 export const CloseComponents = {
-  Close,
-};
+    Close,
+}
 
 
 export const TypeComponents = {
 export const TypeComponents = {
-  Close,
-  SuccessFilled,
-  InfoFilled,
-  WarningFilled,
-  CircleCloseFilled,
-};
+    Close,
+    SuccessFilled,
+    InfoFilled,
+    WarningFilled,
+    CircleCloseFilled,
+}
 
 
 export const TypeComponentsMap = {
 export const TypeComponentsMap = {
-  success: SuccessFilled,
-  warning: WarningFilled,
-  error: CircleCloseFilled,
-  info: InfoFilled,
-};
+    success: SuccessFilled,
+    warning: WarningFilled,
+    error: CircleCloseFilled,
+    info: InfoFilled,
+}
 
 
 export const ValidateComponentsMap = {
 export const ValidateComponentsMap = {
-  validating: Loading,
-  success: CircleCheck,
-  error: CircleClose,
-};
+    validating: Loading,
+    success: CircleCheck,
+    error: CircleClose,
+}

+ 6 - 6
packages/utils/vue/index.ts

@@ -1,6 +1,6 @@
-export * from './global-node';
-export * from './install';
-export * from './props';
-export * from './refs';
-export * from './typescript';
-export * from './vnode';
+export * from './global-node'
+export * from './install'
+export * from './props'
+export * from './refs'
+export * from './typescript'
+export * from './vnode'

+ 27 - 27
packages/utils/vue/install.ts

@@ -1,42 +1,42 @@
-import { NOOP } from '@vue/shared';
+import { NOOP } from '@vue/shared'
 
 
-import type { App } from 'vue';
-import type { SFCInstallWithContext, SFCWithInstall } from './typescript';
+import type { App } from 'vue'
+import type { SFCInstallWithContext, SFCWithInstall } from './typescript'
 
 
 export const withInstall = <T, E extends Record<string, any>>(main: T, extra?: E) => {
 export const withInstall = <T, E extends Record<string, any>>(main: T, extra?: E) => {
-  (main as SFCWithInstall<T>).install = (app): void => {
-    for (const comp of [main, ...Object.values(extra ?? {})]) {
-      app.component(comp.name, comp);
+    ;(main as SFCWithInstall<T>).install = (app): void => {
+        for (const comp of [main, ...Object.values(extra ?? {})]) {
+            app.component(comp.name, comp)
+        }
     }
     }
-  };
 
 
-  if (extra) {
-    for (const [key, comp] of Object.entries(extra)) {
-      (main as any)[key] = comp;
+    if (extra) {
+        for (const [key, comp] of Object.entries(extra)) {
+            ;(main as any)[key] = comp
+        }
     }
     }
-  }
-  return main as SFCWithInstall<T> & E;
-};
+    return main as SFCWithInstall<T> & E
+}
 
 
 export const withInstallFunction = <T>(fn: T, name: string) => {
 export const withInstallFunction = <T>(fn: T, name: string) => {
-  (fn as SFCWithInstall<T>).install = (app: App) => {
-    (fn as SFCInstallWithContext<T>)._context = app._context;
-    app.config.globalProperties[name] = fn;
-  };
+    ;(fn as SFCWithInstall<T>).install = (app: App) => {
+        ;(fn as SFCInstallWithContext<T>)._context = app._context
+        app.config.globalProperties[name] = fn
+    }
 
 
-  return fn as SFCInstallWithContext<T>;
-};
+    return fn as SFCInstallWithContext<T>
+}
 
 
 export const withInstallDirective = <T>(directive: T, name: string) => {
 export const withInstallDirective = <T>(directive: T, name: string) => {
-  (directive as SFCWithInstall<T>).install = (app: App): void => {
-    app.directive(name, directive);
-  };
+    ;(directive as SFCWithInstall<T>).install = (app: App): void => {
+        app.directive(name, directive)
+    }
 
 
-  return directive as SFCWithInstall<T>;
-};
+    return directive as SFCWithInstall<T>
+}
 
 
 export const withNoopInstall = <T>(component: T) => {
 export const withNoopInstall = <T>(component: T) => {
-  (component as SFCWithInstall<T>).install = NOOP;
+    ;(component as SFCWithInstall<T>).install = NOOP
 
 
-  return component as SFCWithInstall<T>;
-};
+    return component as SFCWithInstall<T>
+}

+ 3 - 3
packages/utils/vue/props/index.ts

@@ -1,3 +1,3 @@
-export * from './util';
-export * from './types';
-export * from './runtime';
+export * from './util'
+export * from './types'
+export * from './runtime'

+ 48 - 82
packages/utils/vue/props/runtime.ts

@@ -1,26 +1,16 @@
-import { warn } from 'vue';
-import { fromPairs } from 'lodash-unified';
-import { isObject } from '../../types';
-import { hasOwn } from '../../objects';
+import { warn } from 'vue'
+import { fromPairs } from 'lodash-unified'
+import { isObject } from '../../types'
+import { hasOwn } from '../../objects'
 
 
-import type { PropType } from 'vue';
-import type {
-  EpProp,
-  EpPropConvert,
-  EpPropFinalized,
-  EpPropInput,
-  EpPropMergeType,
-  IfEpProp,
-  IfNativePropType,
-  NativePropType,
-} from './types';
+import type { PropType } from 'vue'
+import type { EpProp, EpPropConvert, EpPropFinalized, EpPropInput, EpPropMergeType, IfEpProp, IfNativePropType, NativePropType } from './types'
 
 
-export const epPropKey = '__epPropKey';
+export const epPropKey = '__epPropKey'
 
 
-export const definePropType = <T>(val: any): PropType<T> => val;
+export const definePropType = <T>(val: any): PropType<T> => val
 
 
-export const isEpProp = (val: unknown): val is EpProp<any, any, any> =>
-  isObject(val) && !!(val as any)[epPropKey];
+export const isEpProp = (val: unknown): val is EpProp<any, any, any> => isObject(val) && !!(val as any)[epPropKey]
 
 
 /**
 /**
  * @description Build prop. It can better optimize prop types
  * @description Build prop. It can better optimize prop types
@@ -42,74 +32,50 @@ export const isEpProp = (val: unknown): val is EpProp<any, any, any> =>
   } as const)
   } as const)
   @link see more: https://github.com/element-plus/element-plus/pull/3341
   @link see more: https://github.com/element-plus/element-plus/pull/3341
  */
  */
-export const buildProp = <
-  Type = never,
-  Value = never,
-  Validator = never,
-  Default extends EpPropMergeType<Type, Value, Validator> = never,
-  Required extends boolean = false,
->(
-  prop: EpPropInput<Type, Value, Validator, Default, Required>,
-  key?: string,
+export const buildProp = <Type = never, Value = never, Validator = never, Default extends EpPropMergeType<Type, Value, Validator> = never, Required extends boolean = false>(
+    prop: EpPropInput<Type, Value, Validator, Default, Required>,
+    key?: string
 ): EpPropFinalized<Type, Value, Validator, Default, Required> => {
 ): EpPropFinalized<Type, Value, Validator, Default, Required> => {
-  // filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`)
-  if (!isObject(prop) || isEpProp(prop)) return prop as any;
+    // filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`)
+    if (!isObject(prop) || isEpProp(prop)) return prop as any
 
 
-  const { values, required, default: defaultValue, type, validator } = prop;
+    const { values, required, default: defaultValue, type, validator } = prop
 
 
-  const _validator =
-    values || validator
-      ? (val: unknown) => {
-          let valid = false;
-          let allowedValues: unknown[] = [];
+    const _validator =
+        values || validator
+            ? (val: unknown) => {
+                  let valid = false
+                  let allowedValues: unknown[] = []
 
 
-          if (values) {
-            allowedValues = Array.from(values);
-            if (hasOwn(prop, 'default')) {
-              allowedValues.push(defaultValue);
-            }
-            valid ||= allowedValues.includes(val);
-          }
-          if (validator) valid ||= validator(val);
+                  if (values) {
+                      allowedValues = Array.from(values)
+                      if (hasOwn(prop, 'default')) {
+                          allowedValues.push(defaultValue)
+                      }
+                      valid ||= allowedValues.includes(val)
+                  }
+                  if (validator) valid ||= validator(val)
 
 
-          if (!valid && allowedValues.length > 0) {
-            const allowValuesText = [...new Set(allowedValues)]
-              .map((value) => JSON.stringify(value))
-              .join(', ');
-            warn(
-              `Invalid prop: validation failed${
-                key ? ` for prop "${key}"` : ''
-              }. Expected one of [${allowValuesText}], got value ${JSON.stringify(val)}.`,
-            );
-          }
-          return valid;
-        }
-      : undefined;
+                  if (!valid && allowedValues.length > 0) {
+                      const allowValuesText = [...new Set(allowedValues)].map(value => JSON.stringify(value)).join(', ')
+                      warn(`Invalid prop: validation failed${key ? ` for prop "${key}"` : ''}. Expected one of [${allowValuesText}], got value ${JSON.stringify(val)}.`)
+                  }
+                  return valid
+              }
+            : undefined
 
 
-  const epProp: any = {
-    type,
-    required: !!required,
-    validator: _validator,
-    [epPropKey]: true,
-  };
-  if (hasOwn(prop, 'default')) epProp.default = defaultValue;
-  return epProp;
-};
+    const epProp: any = {
+        type,
+        required: !!required,
+        validator: _validator,
+        [epPropKey]: true,
+    }
+    if (hasOwn(prop, 'default')) epProp.default = defaultValue
+    return epProp
+}
 
 
-export const buildProps = <
-  Props extends Record<
-    string,
-    { [epPropKey]: true } | NativePropType | EpPropInput<any, any, any, any, any>
-  >,
->(
-  props: Props,
+export const buildProps = <Props extends Record<string, { [epPropKey]: true } | NativePropType | EpPropInput<any, any, any, any, any>>>(
+    props: Props
 ): {
 ): {
-  [K in keyof Props]: IfEpProp<
-    Props[K],
-    Props[K],
-    IfNativePropType<Props[K], Props[K], EpPropConvert<Props[K]>>
-  >;
-} =>
-  fromPairs(
-    Object.entries(props).map(([key, option]) => [key, buildProp(option as any, key)]),
-  ) as any;
+    [K in keyof Props]: IfEpProp<Props[K], Props[K], IfNativePropType<Props[K], Props[K], EpPropConvert<Props[K]>>>
+} => fromPairs(Object.entries(props).map(([key, option]) => [key, buildProp(option as any, key)])) as any

+ 37 - 64
packages/utils/vue/props/types.ts

@@ -1,8 +1,8 @@
-import type { epPropKey } from './runtime';
-import type { ExtractPropTypes, PropType } from 'vue';
-import type { IfNever, UnknownToNever, WritableArray } from './util';
+import type { epPropKey } from './runtime'
+import type { ExtractPropTypes, PropType } from 'vue'
+import type { IfNever, UnknownToNever, WritableArray } from './util'
 
 
-type Value<T> = T[keyof T];
+type Value<T> = T[keyof T]
 
 
 /**
 /**
  * Extract the type of a single prop
  * Extract the type of a single prop
@@ -15,10 +15,10 @@ type Value<T> = T[keyof T];
  * ExtractPropType<{ type: BooleanConstructor }> => boolean
  * ExtractPropType<{ type: BooleanConstructor }> => boolean
  */
  */
 export type ExtractPropType<T extends object> = Value<
 export type ExtractPropType<T extends object> = Value<
-  ExtractPropTypes<{
-    key: T;
-  }>
->;
+    ExtractPropTypes<{
+        key: T
+    }>
+>
 
 
 /**
 /**
  * Extracts types via `ExtractPropTypes`, accepting `PropType<T>`, `XXXConstructor`, `never`...
  * Extracts types via `ExtractPropTypes`, accepting `PropType<T>`, `XXXConstructor`, `never`...
@@ -30,13 +30,13 @@ export type ExtractPropType<T extends object> = Value<
  * ResolvePropType<PropType<T>> => T
  * ResolvePropType<PropType<T>> => T
  **/
  **/
 export type ResolvePropType<T> = IfNever<
 export type ResolvePropType<T> = IfNever<
-  T,
-  never,
-  ExtractPropType<{
-    type: WritableArray<T>;
-    required: true;
-  }>
->;
+    T,
+    never,
+    ExtractPropType<{
+        type: WritableArray<T>
+        required: true
+    }>
+>
 
 
 /**
 /**
  * Merge Type, Value, Validator types
  * Merge Type, Value, Validator types
@@ -46,33 +46,22 @@ export type ResolvePropType<T> = IfNever<
  * EpPropMergeType<StringConstructor, '1', 1> =>  1 | "1" // ignores StringConstructor
  * EpPropMergeType<StringConstructor, '1', 1> =>  1 | "1" // ignores StringConstructor
  * EpPropMergeType<StringConstructor, never, number> =>  string | number
  * EpPropMergeType<StringConstructor, never, number> =>  string | number
  */
  */
-export type EpPropMergeType<Type, Value, Validator> =
-  | IfNever<UnknownToNever<Value>, ResolvePropType<Type>, never>
-  | UnknownToNever<Value>
-  | UnknownToNever<Validator>;
+export type EpPropMergeType<Type, Value, Validator> = IfNever<UnknownToNever<Value>, ResolvePropType<Type>, never> | UnknownToNever<Value> | UnknownToNever<Validator>
 
 
 /**
 /**
  * Handling default values for input (constraints)
  * Handling default values for input (constraints)
  *
  *
  * 处理输入参数的默认值(约束)
  * 处理输入参数的默认值(约束)
  */
  */
-export type EpPropInputDefault<Required extends boolean, Default> = Required extends true
-  ? never
-  : Default extends Record<string, unknown> | Array<any>
-  ? () => Default
-  : (() => Default) | Default;
+export type EpPropInputDefault<Required extends boolean, Default> = Required extends true ? never : Default extends Record<string, unknown> | Array<any> ? () => Default : (() => Default) | Default
 
 
 /**
 /**
  * Native prop types, e.g: `BooleanConstructor`, `StringConstructor`, `null`, `undefined`, etc.
  * Native prop types, e.g: `BooleanConstructor`, `StringConstructor`, `null`, `undefined`, etc.
  *
  *
  * 原生 prop `类型,BooleanConstructor`、`StringConstructor`、`null`、`undefined` 等
  * 原生 prop `类型,BooleanConstructor`、`StringConstructor`、`null`、`undefined` 等
  */
  */
-export type NativePropType =
-  | ((...args: any) => any)
-  | { new (...args: any): any }
-  | undefined
-  | null;
-export type IfNativePropType<T, Y, N> = [T] extends [NativePropType] ? Y : N;
+export type NativePropType = ((...args: any) => any) | { new (...args: any): any } | undefined | null
+export type IfNativePropType<T, Y, N> = [T] extends [NativePropType] ? Y : N
 
 
 /**
 /**
  * input prop `buildProp` or `buildProps` (constraints)
  * input prop `buildProp` or `buildProps` (constraints)
@@ -90,19 +79,13 @@ export type IfNativePropType<T, Y, N> = [T] extends [NativePropType] ? Y : N;
     default?: undefined;
     default?: undefined;
   }
   }
  */
  */
-export type EpPropInput<
-  Type,
-  Value,
-  Validator,
-  Default extends EpPropMergeType<Type, Value, Validator>,
-  Required extends boolean,
-> = {
-  type?: Type;
-  required?: Required;
-  values?: readonly Value[];
-  validator?: ((val: any) => val is Validator) | ((val: any) => boolean);
-  default?: EpPropInputDefault<Required, Default>;
-};
+export type EpPropInput<Type, Value, Validator, Default extends EpPropMergeType<Type, Value, Validator>, Required extends boolean> = {
+    type?: Type
+    required?: Required
+    values?: readonly Value[]
+    validator?: ((val: any) => val is Validator) | ((val: any) => boolean)
+    default?: EpPropInputDefault<Required, Default>
+}
 
 
 /**
 /**
  * output prop `buildProp` or `buildProps`.
  * output prop `buildProp` or `buildProps`.
@@ -121,41 +104,31 @@ export type EpPropInput<
   }
   }
  */
  */
 export type EpProp<Type, Default, Required> = {
 export type EpProp<Type, Default, Required> = {
-  readonly type: PropType<Type>;
-  readonly required: [Required] extends [true] ? true : false;
-  readonly validator: ((val: unknown) => boolean) | undefined;
-  [epPropKey]: true;
-} & IfNever<Default, unknown, { readonly default: Default }>;
+    readonly type: PropType<Type>
+    readonly required: [Required] extends [true] ? true : false
+    readonly validator: ((val: unknown) => boolean) | undefined
+    [epPropKey]: true
+} & IfNever<Default, unknown, { readonly default: Default }>
 
 
 /**
 /**
  * Determine if it is `EpProp`
  * Determine if it is `EpProp`
  */
  */
-export type IfEpProp<T, Y, N> = T extends { [epPropKey]: true } ? Y : N;
+export type IfEpProp<T, Y, N> = T extends { [epPropKey]: true } ? Y : N
 
 
 /**
 /**
  * Converting input to output.
  * Converting input to output.
  *
  *
  * 将输入转换为输出
  * 将输入转换为输出
  */
  */
-export type EpPropConvert<Input> = Input extends EpPropInput<
-  infer Type,
-  infer Value,
-  infer Validator,
-  any,
-  infer Required
->
-  ? EpPropFinalized<Type, Value, Validator, Input['default'], Required>
-  : never;
+export type EpPropConvert<Input> = Input extends EpPropInput<infer Type, infer Value, infer Validator, any, infer Required>
+    ? EpPropFinalized<Type, Value, Validator, Input['default'], Required>
+    : never
 
 
 /**
 /**
  * Finalized conversion output
  * Finalized conversion output
  *
  *
  * 最终转换 EpProp
  * 最终转换 EpProp
  */
  */
-export type EpPropFinalized<Type, Value, Validator, Default, Required> = EpProp<
-  EpPropMergeType<Type, Value, Validator>,
-  UnknownToNever<Default>,
-  Required
->;
+export type EpPropFinalized<Type, Value, Validator, Default, Required> = EpProp<EpPropMergeType<Type, Value, Validator>, UnknownToNever<Default>, Required>
 
 
-export {};
+export {}

+ 6 - 6
packages/utils/vue/props/util.ts

@@ -1,10 +1,10 @@
-export type Writable<T> = { -readonly [P in keyof T]: T[P] };
-export type WritableArray<T> = T extends readonly any[] ? Writable<T> : T;
+export type Writable<T> = { -readonly [P in keyof T]: T[P] }
+export type WritableArray<T> = T extends readonly any[] ? Writable<T> : T
 
 
-export type IfNever<T, Y = true, N = false> = [T] extends [never] ? Y : N;
+export type IfNever<T, Y = true, N = false> = [T] extends [never] ? Y : N
 
 
-export type IfUnknown<T, Y, N> = [unknown] extends [T] ? Y : N;
+export type IfUnknown<T, Y, N> = [unknown] extends [T] ? Y : N
 
 
-export type UnknownToNever<T> = IfUnknown<T, never, T>;
+export type UnknownToNever<T> = IfUnknown<T, never, T>
 
 
-export {};
+export {}

+ 13 - 13
packages/utils/vue/refs.ts

@@ -1,17 +1,17 @@
-import { isFunction } from '../types';
+import { isFunction } from '../types'
 
 
-import type { ComponentPublicInstance, Ref } from 'vue';
+import type { ComponentPublicInstance, Ref } from 'vue'
 
 
-export type RefSetter = (el: Element | ComponentPublicInstance | undefined) => void;
+export type RefSetter = (el: Element | ComponentPublicInstance | undefined) => void
 
 
 export const composeRefs = (...refs: (Ref<HTMLElement | undefined> | RefSetter)[]) => {
 export const composeRefs = (...refs: (Ref<HTMLElement | undefined> | RefSetter)[]) => {
-  return (el: Element | ComponentPublicInstance | null) => {
-    refs.forEach((ref) => {
-      if (isFunction(ref)) {
-        ref(el as Element | ComponentPublicInstance);
-      } else {
-        ref.value = el as HTMLElement | undefined;
-      }
-    });
-  };
-};
+    return (el: Element | ComponentPublicInstance | null) => {
+        refs.forEach(ref => {
+            if (isFunction(ref)) {
+                ref(el as Element | ComponentPublicInstance)
+            } else {
+                ref.value = el as HTMLElement | undefined
+            }
+        })
+    }
+}

+ 4 - 4
packages/utils/vue/typescript.ts

@@ -1,7 +1,7 @@
-import type { AppContext, Plugin } from 'vue';
+import type { AppContext, Plugin } from 'vue'
 
 
-export type SFCWithInstall<T> = T & Plugin;
+export type SFCWithInstall<T> = T & Plugin
 
 
 export type SFCInstallWithContext<T> = SFCWithInstall<T> & {
 export type SFCInstallWithContext<T> = SFCWithInstall<T> & {
-  _context: AppContext | null;
-};
+    _context: AppContext | null
+}

+ 91 - 96
packages/utils/vue/vnode.ts

@@ -1,63 +1,63 @@
-import { Comment, Fragment, Text, createBlock, createCommentVNode, isVNode, openBlock } from 'vue';
-import { camelize, isArray } from '@vue/shared';
-import { hasOwn } from '../objects';
-import { debugWarn } from '../error';
-import type { VNode, VNodeArrayChildren, VNodeChild, VNodeNormalizedChildren } from 'vue';
+import { Comment, Fragment, Text, createBlock, createCommentVNode, isVNode, openBlock } from 'vue'
+import { camelize, isArray } from '@vue/shared'
+import { hasOwn } from '../objects'
+import { debugWarn } from '../error'
+import type { VNode, VNodeArrayChildren, VNodeChild, VNodeNormalizedChildren } from 'vue'
 
 
-const SCOPE = 'utils/vue/vnode';
+const SCOPE = 'utils/vue/vnode'
 
 
 export enum PatchFlags {
 export enum PatchFlags {
-  TEXT = 1,
-  CLASS = 2,
-  STYLE = 4,
-  PROPS = 8,
-  FULL_PROPS = 16,
-  HYDRATE_EVENTS = 32,
-  STABLE_FRAGMENT = 64,
-  KEYED_FRAGMENT = 128,
-  UNKEYED_FRAGMENT = 256,
-  NEED_PATCH = 512,
-  DYNAMIC_SLOTS = 1024,
-  HOISTED = -1,
-  BAIL = -2,
+    TEXT = 1,
+    CLASS = 2,
+    STYLE = 4,
+    PROPS = 8,
+    FULL_PROPS = 16,
+    HYDRATE_EVENTS = 32,
+    STABLE_FRAGMENT = 64,
+    KEYED_FRAGMENT = 128,
+    UNKEYED_FRAGMENT = 256,
+    NEED_PATCH = 512,
+    DYNAMIC_SLOTS = 1024,
+    HOISTED = -1,
+    BAIL = -2,
 }
 }
 
 
-export type VNodeChildAtom = Exclude<VNodeChild, Array<any>>;
-export type RawSlots = Exclude<VNodeNormalizedChildren, Array<any> | null | string>;
+export type VNodeChildAtom = Exclude<VNodeChild, Array<any>>
+export type RawSlots = Exclude<VNodeNormalizedChildren, Array<any> | null | string>
 
 
-export function isFragment(node: VNode): boolean;
-export function isFragment(node: unknown): node is VNode;
+export function isFragment(node: VNode): boolean
+export function isFragment(node: unknown): node is VNode
 export function isFragment(node: unknown): node is VNode {
 export function isFragment(node: unknown): node is VNode {
-  return isVNode(node) && node.type === Fragment;
+    return isVNode(node) && node.type === Fragment
 }
 }
 
 
-export function isText(node: VNode): boolean;
-export function isText(node: unknown): node is VNode;
+export function isText(node: VNode): boolean
+export function isText(node: unknown): node is VNode
 export function isText(node: unknown): node is VNode {
 export function isText(node: unknown): node is VNode {
-  return isVNode(node) && node.type === Text;
+    return isVNode(node) && node.type === Text
 }
 }
 
 
-export function isComment(node: VNode): boolean;
-export function isComment(node: unknown): node is VNode;
+export function isComment(node: VNode): boolean
+export function isComment(node: unknown): node is VNode
 export function isComment(node: unknown): node is VNode {
 export function isComment(node: unknown): node is VNode {
-  return isVNode(node) && node.type === Comment;
+    return isVNode(node) && node.type === Comment
 }
 }
 
 
-const TEMPLATE = 'template';
-export function isTemplate(node: VNode): boolean;
-export function isTemplate(node: unknown): node is VNode;
+const TEMPLATE = 'template'
+export function isTemplate(node: VNode): boolean
+export function isTemplate(node: unknown): node is VNode
 export function isTemplate(node: unknown): node is VNode {
 export function isTemplate(node: unknown): node is VNode {
-  return isVNode(node) && node.type === TEMPLATE;
+    return isVNode(node) && node.type === TEMPLATE
 }
 }
 
 
 /**
 /**
  * determine if the element is a valid element type rather than fragments and comment e.g. <template> v-if
  * determine if the element is a valid element type rather than fragments and comment e.g. <template> v-if
  * @param node {VNode} node to be tested
  * @param node {VNode} node to be tested
  */
  */
-export function isValidElementNode(node: VNode): boolean;
-export function isValidElementNode(node: unknown): node is VNode;
+export function isValidElementNode(node: VNode): boolean
+export function isValidElementNode(node: unknown): node is VNode
 export function isValidElementNode(node: unknown): node is VNode {
 export function isValidElementNode(node: unknown): node is VNode {
-  return isVNode(node) && !isFragment(node) && !isComment(node);
+    return isVNode(node) && !isFragment(node) && !isComment(node)
 }
 }
 
 
 /**
 /**
@@ -65,79 +65,74 @@ export function isValidElementNode(node: unknown): node is VNode {
  * @param node {VNode} node to be searched
  * @param node {VNode} node to be searched
  * @param depth {number} depth to be searched
  * @param depth {number} depth to be searched
  */
  */
-function getChildren(
-  node: VNodeNormalizedChildren | VNodeChild,
-  depth: number,
-): VNodeNormalizedChildren | VNodeChild {
-  if (isComment(node)) return;
-  if (isFragment(node) || isTemplate(node)) {
-    return depth > 0 ? getFirstValidNode(node.children, depth - 1) : undefined;
-  }
-  return node;
+function getChildren(node: VNodeNormalizedChildren | VNodeChild, depth: number): VNodeNormalizedChildren | VNodeChild {
+    if (isComment(node)) return
+    if (isFragment(node) || isTemplate(node)) {
+        return depth > 0 ? getFirstValidNode(node.children, depth - 1) : undefined
+    }
+    return node
 }
 }
 
 
 export const getFirstValidNode = (nodes: VNodeNormalizedChildren, maxDepth = 3) => {
 export const getFirstValidNode = (nodes: VNodeNormalizedChildren, maxDepth = 3) => {
-  if (Array.isArray(nodes)) {
-    return getChildren(nodes[0], maxDepth);
-  } else {
-    return getChildren(nodes, maxDepth);
-  }
-};
+    if (Array.isArray(nodes)) {
+        return getChildren(nodes[0], maxDepth)
+    } else {
+        return getChildren(nodes, maxDepth)
+    }
+}
 
 
 export function renderIf(condition: boolean, ...args: Parameters<typeof createBlock>) {
 export function renderIf(condition: boolean, ...args: Parameters<typeof createBlock>) {
-  return condition ? renderBlock(...args) : createCommentVNode('v-if', true);
+    return condition ? renderBlock(...args) : createCommentVNode('v-if', true)
 }
 }
 
 
 export function renderBlock(...args: Parameters<typeof createBlock>) {
 export function renderBlock(...args: Parameters<typeof createBlock>) {
-  return openBlock(), createBlock(...args);
+    return openBlock(), createBlock(...args)
 }
 }
 
 
 export const getNormalizedProps = (node: VNode) => {
 export const getNormalizedProps = (node: VNode) => {
-  if (!isVNode(node)) {
-    debugWarn(SCOPE, '[getNormalizedProps] must be a VNode');
-    return {};
-  }
-
-  const raw = node.props || {};
-  const type = (isVNode(node.type) ? node.type.props : undefined) || {};
-  const props: Record<string, any> = {};
-
-  Object.keys(type).forEach((key) => {
-    if (hasOwn(type[key], 'default')) {
-      props[key] = type[key].default;
+    if (!isVNode(node)) {
+        debugWarn(SCOPE, '[getNormalizedProps] must be a VNode')
+        return {}
     }
     }
-  });
 
 
-  Object.keys(raw).forEach((key) => {
-    props[camelize(key)] = raw[key];
-  });
+    const raw = node.props || {}
+    const type = (isVNode(node.type) ? node.type.props : undefined) || {}
+    const props: Record<string, any> = {}
+
+    Object.keys(type).forEach(key => {
+        if (hasOwn(type[key], 'default')) {
+            props[key] = type[key].default
+        }
+    })
 
 
-  return props;
-};
+    Object.keys(raw).forEach(key => {
+        props[camelize(key)] = raw[key]
+    })
+
+    return props
+}
 
 
 export const ensureOnlyChild = (children: VNodeArrayChildren | undefined) => {
 export const ensureOnlyChild = (children: VNodeArrayChildren | undefined) => {
-  if (!isArray(children) || children.length > 1) {
-    throw new Error('expect to receive a single Vue element child');
-  }
-  return children[0];
-};
-
-export type FlattenVNodes = Array<VNodeChildAtom | RawSlots>;
-
-export const flattedChildren = (
-  children: FlattenVNodes | VNode | VNodeNormalizedChildren,
-): FlattenVNodes => {
-  const vNodes = isArray(children) ? children : [children];
-  const result: FlattenVNodes = [];
-
-  vNodes.forEach((child) => {
-    if (isArray(child)) {
-      result.push(...flattedChildren(child));
-    } else if (isVNode(child) && isArray(child.children)) {
-      result.push(...flattedChildren(child.children));
-    } else {
-      result.push(child);
+    if (!isArray(children) || children.length > 1) {
+        throw new Error('expect to receive a single Vue element child')
     }
     }
-  });
-  return result;
-};
+    return children[0]
+}
+
+export type FlattenVNodes = Array<VNodeChildAtom | RawSlots>
+
+export const flattedChildren = (children: FlattenVNodes | VNode | VNodeNormalizedChildren): FlattenVNodes => {
+    const vNodes = isArray(children) ? children : [children]
+    const result: FlattenVNodes = []
+
+    vNodes.forEach(child => {
+        if (isArray(child)) {
+            result.push(...flattedChildren(child))
+        } else if (isVNode(child) && isArray(child.children)) {
+            result.push(...flattedChildren(child.children))
+        } else {
+            result.push(child)
+        }
+    })
+    return result
+}

+ 31 - 31
playground/src/App.vue

@@ -1,18 +1,18 @@
 <script setup lang="ts">
 <script setup lang="ts">
-  import { onMounted, VNode } from 'vue';
-  import { UIAudio, UIIcon } from '@kankan/components';
-  // import { buildProps } from '@kankan/utils';
-  // import { h } from 'vue';
-  // import * as KanKanSDK from '@kankan/sdk';
-  console.log('UI', UIAudio, UIIcon);
-
-  onMounted(async () => {
-    const KanKan = (window as any).KanKan;
+import { onMounted, VNode } from 'vue'
+import { UIAudio, UIIcon } from '@kankan/components'
+// import { buildProps } from '@kankan/utils';
+// import { h } from 'vue';
+// import * as KanKanSDK from '@kankan/sdk';
+console.log('UI', UIAudio, UIIcon)
+
+onMounted(async () => {
+    const KanKan = (window as any).KanKan
     const app = new KanKan({
     const app = new KanKan({
-      // dom: '#scene',
-      num: 'KJ-JYo2ZZyKKJ',
-    });
-    app.mount('#scene').render();
+        // dom: '#scene',
+        num: 'KJ-JYo2ZZyKKJ',
+    })
+    app.mount('#scene').render()
 
 
     // console.log('TagView', await TagView);
     // console.log('TagView', await TagView);
     // const el = h(KKAudio, {
     // const el = h(KKAudio, {
@@ -23,40 +23,40 @@
     // console.log('kankan', KKAudio);
     // console.log('kankan', KKAudio);
     // const res = await kankan.use(KKAudio.render());
     // const res = await kankan.use(KKAudio.render());
     // console.log('11', res);
     // console.log('11', res);
-    app.use('TagEditor');
+    app.use('TagEditor')
 
 
     const TagView = await app.use('TagView', {
     const TagView = await app.use('TagView', {
-      render: (data: VNode) => {
-        console.log('data', data.type);
-      },
-    });
+        render: (data: VNode) => {
+            console.log('data', data.type)
+        },
+    })
 
 
     TagView.on('click', (event: Event) => {
     TagView.on('click', (event: Event) => {
-      console.log('event', event);
-      // debugger;
-    });
-    console.log('1app', app);
+        console.log('event', event)
+        // debugger;
+    })
+    console.log('1app', app)
 
 
-    const TagEditor = await app.TagManager.editor.promise();
-    console.log('TagEditor', TagEditor);
+    const TagEditor = await app.TagManager.editor.promise()
+    console.log('TagEditor', TagEditor)
     // TagEditor.enter();
     // TagEditor.enter();
 
 
-    console.log('TagView', TagView);
+    console.log('TagView', TagView)
     // .then(function () {
     // .then(function () {
     //   console.log(arguments);
     //   console.log(arguments);
     // });
     // });
-  });
+})
 </script>
 </script>
 
 
 <template>
 <template>
-  <div id="scene">
-    <KKAudio src="http://samplelib.com/lib/preview/mp3/sample-3s.mp3" />
-  </div>
+    <div id="scene">
+        <KKAudio src="http://samplelib.com/lib/preview/mp3/sample-3s.mp3" />
+    </div>
 </template>
 </template>
 
 
 <style scoped>
 <style scoped>
-  #scene {
+#scene {
     width: 100vw;
     width: 100vw;
     height: 100vh;
     height: 100vh;
-  }
+}
 </style>
 </style>

+ 22 - 23
playground/src/components/HelloWorld.vue

@@ -1,37 +1,36 @@
 <script setup lang="ts">
 <script setup lang="ts">
-  import { ref } from 'vue';
+import { ref } from 'vue'
 
 
-  defineProps<{ msg: string }>();
+defineProps<{ msg: string }>()
 
 
-  const count = ref(0);
+const count = ref(0)
 </script>
 </script>
 
 
 <template>
 <template>
-  <h1>{{ msg }}</h1>
+    <h1>{{ msg }}</h1>
+
+    <div class="card">
+        <button type="button" @click="count++">count is {{ count }}</button>
+        <p>
+            Edit
+            <code>components/HelloWorld.vue</code> to test HMR
+        </p>
+    </div>
 
 
-  <div class="card">
-    <button type="button" @click="count++">count is {{ count }}</button>
     <p>
     <p>
-      Edit
-      <code>components/HelloWorld.vue</code> to test HMR
+        Check out
+        <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank">create-vue</a>, the official Vue + Vite starter
     </p>
     </p>
-  </div>
-
-  <p>
-    Check out
-    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank">create-vue</a>, the
-    official Vue + Vite starter
-  </p>
-  <p>
-    Install
-    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
-    in your IDE for a better DX
-  </p>
-  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
+    <p>
+        Install
+        <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
+        in your IDE for a better DX
+    </p>
+    <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
 </template>
 </template>
 
 
 <style scoped>
 <style scoped>
-  .read-the-docs {
+.read-the-docs {
     color: #888;
     color: #888;
-  }
+}
 </style>
 </style>

+ 4 - 4
playground/src/main.ts

@@ -1,5 +1,5 @@
-import { createApp } from 'vue';
-import './style.css';
-import App from './App.vue';
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
 
 
-createApp(App).mount('#app');
+createApp(App).mount('#app')

+ 3 - 3
playground/src/vite-env.d.ts

@@ -1,7 +1,7 @@
 /// <reference types="vite/client" />
 /// <reference types="vite/client" />
 
 
 declare module '*.vue' {
 declare module '*.vue' {
-  import type { DefineComponent } from 'vue';
-  const component: DefineComponent<{}, {}, any>;
-  export default component;
+    import type { DefineComponent } from 'vue'
+    const component: DefineComponent<{}, {}, any>
+    export default component
 }
 }

+ 4 - 4
playground/vite.config.ts

@@ -1,7 +1,7 @@
-import { defineConfig } from 'vite';
-import vue from '@vitejs/plugin-vue';
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
 
 
 // https://vitejs.dev/config/
 // https://vitejs.dev/config/
 export default defineConfig({
 export default defineConfig({
-  plugins: [vue()],
-});
+    plugins: [vue()],
+})

+ 14 - 10
prettier.config.js

@@ -1,10 +1,14 @@
-module.exports = {
-  printWidth: 100,
-  semi: true,
-  vueIndentScriptAndStyle: true,
-  singleQuote: true,
-  trailingComma: 'all',
-  proseWrap: 'never',
-  htmlWhitespaceSensitivity: 'strict',
-  endOfLine: 'auto',
-};
+module.exports = {
+    printWidth: 200,
+    tabWidth: 4,
+    useTabs: false,
+    semi: false,
+    singleQuote: true,
+    arrowParens: 'avoid',
+    bracketSpacing: true,
+    disableLanguages: [],
+    eslintIntegration: false,
+    stylelintIntegration: false,
+    tslintIntegration: false,
+    proseWrap: 'preserve',
+}

+ 25 - 25
vitest.config.ts

@@ -1,28 +1,28 @@
-import { defineConfig } from 'vitest/config';
-import Vue from '@vitejs/plugin-vue';
-import VueJsx from '@vitejs/plugin-vue-jsx';
-import VueMacros from 'unplugin-vue-macros/vite';
+import { defineConfig } from 'vitest/config'
+import Vue from '@vitejs/plugin-vue'
+import VueJsx from '@vitejs/plugin-vue-jsx'
+import VueMacros from 'unplugin-vue-macros/vite'
 
 
 export default defineConfig({
 export default defineConfig({
-  plugins: [
-    VueMacros({
-      setupComponent: false,
-      setupSFC: false,
-      plugins: {
-        vue: Vue(),
-        vueJsx: VueJsx(),
-      },
-    }),
-  ],
-  optimizeDeps: {
-    disabled: true,
-  },
-  test: {
-    clearMocks: true,
-    environment: 'jsdom',
-    setupFiles: ['./vitest.setup.ts'],
-    transformMode: {
-      web: [/\.[jt]sx$/],
+    plugins: [
+        VueMacros({
+            setupComponent: false,
+            setupSFC: false,
+            plugins: {
+                vue: Vue(),
+                vueJsx: VueJsx(),
+            },
+        }),
+    ],
+    optimizeDeps: {
+        disabled: true,
     },
     },
-  },
-});
+    test: {
+        clearMocks: true,
+        environment: 'jsdom',
+        setupFiles: ['./vitest.setup.ts'],
+        transformMode: {
+            web: [/\.[jt]sx$/],
+        },
+    },
+})

+ 5 - 5
vitest.setup.ts

@@ -1,7 +1,7 @@
-import { config } from '@vue/test-utils';
-import { vi } from 'vitest';
-import ResizeObserver from 'resize-observer-polyfill';
+import { config } from '@vue/test-utils'
+import { vi } from 'vitest'
+import ResizeObserver from 'resize-observer-polyfill'
 
 
-vi.stubGlobal('ResizeObserver', ResizeObserver);
+vi.stubGlobal('ResizeObserver', ResizeObserver)
 
 
-config.global.stubs = {};
+config.global.stubs = {}