Przeglądaj źródła

feat(组件): 更新

gemercheung 2 lat temu
rodzic
commit
ab019c7305
33 zmienionych plików z 1876 dodań i 395 usunięć
  1. 4 0
      docs/.vitepress/i18n/pages/component.json
  2. 22 0
      docs/examples/message-box/alert.vue
  3. 33 0
      docs/examples/message-box/centered-content.vue
  4. 31 0
      docs/examples/message-box/confirm.vue
  5. 39 0
      docs/examples/message-box/customization.vue
  6. 20 0
      docs/examples/message-box/customized-icon.vue
  7. 34 0
      docs/examples/message-box/distinguishable-close-cancel.vue
  8. 32 0
      docs/examples/message-box/draggable.vue
  9. 29 0
      docs/examples/message-box/prompt.vue
  10. 17 0
      docs/examples/message-box/use-html.vue
  11. 34 0
      docs/examples/message-box/use-vnode.vue
  12. 194 0
      docs/zh-CN/component/message-box.md
  13. 1 0
      package.json
  14. 3 3
      packages/components/basic/dialog/src/dialog.vue
  15. 1 0
      packages/components/basic/index.ts
  16. 20 0
      packages/components/basic/message-box/index.ts
  17. 506 0
      packages/components/basic/message-box/src/index.vue
  18. 213 0
      packages/components/basic/message-box/src/message-box.type.ts
  19. 241 0
      packages/components/basic/message-box/src/messageBox.ts
  20. 5 0
      packages/components/basic/message-box/style/css.ts
  21. 5 0
      packages/components/basic/message-box/style/index.ts
  22. 2 2
      packages/components/basic/overlay/index.ts
  23. 1 0
      packages/directives/index.ts
  24. 73 0
      packages/directives/trap-focus/index.ts
  25. 1 0
      packages/hooks/index.ts
  26. 30 0
      packages/hooks/use-restore-active/index.ts
  27. 1 1
      packages/kankan-components/index.ts
  28. 2 1
      packages/kankan-components/plugin.ts
  29. 1 1
      packages/theme-chalk/src/index.scss
  30. 254 0
      packages/theme-chalk/src/message-box.scss
  31. 1 0
      packages/utils/index.ts
  32. 8 0
      packages/utils/validator.ts
  33. 18 387
      pnpm-lock.yaml

+ 4 - 0
docs/.vitepress/i18n/pages/component.json

@@ -22,6 +22,10 @@
         {
           "link": "/dialog",
           "text": "Dialog对话框"
+        },
+        {
+          "link": "/message-box",
+          "text": "MessageBox 消息弹框"
         }
       ]
     },

+ 22 - 0
docs/examples/message-box/alert.vue

@@ -0,0 +1,22 @@
+<template>
+  <kk-button text @click="open">Click to open the Message Box</kk-button>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage, KkButton, KkMessageBox } from 'kankan-components'
+import type { Action } from 'element-plus'
+
+const open = () => {
+  KkMessageBox.alert('This is a message', 'Title', {
+    // if you want to disable its autofocus
+    // autofocus: false,
+    confirmButtonText: 'OK',
+    callback: (action: Action) => {
+      // ElMessage({
+      //   type: 'info',
+      //   message: `action: ${action}`,
+      // })
+    },
+  })
+}
+</script>

+ 33 - 0
docs/examples/message-box/centered-content.vue

@@ -0,0 +1,33 @@
+<template>
+  <kk-button text @click="open">Click to open Message Box</kk-button>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage } from 'element-plus'
+import { KkButton, KkMessageBox } from 'kankan-components'
+
+const open = () => {
+  KkMessageBox.confirm(
+    'proxy will permanently delete the file. Continue?',
+    'Warning',
+    {
+      confirmButtonText: 'OK',
+      cancelButtonText: 'Cancel',
+      type: 'warning',
+      center: true,
+    }
+  )
+    .then(() => {
+      ElMessage({
+        type: 'success',
+        message: 'Delete completed',
+      })
+    })
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: 'Delete canceled',
+      })
+    })
+}
+</script>

+ 31 - 0
docs/examples/message-box/confirm.vue

@@ -0,0 +1,31 @@
+<template>
+  <el-button text @click="open">Click to open the Message Box</el-button>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const open = () => {
+  ElMessageBox.confirm(
+    'proxy will permanently delete the file. Continue?',
+    'Warning',
+    {
+      confirmButtonText: 'OK',
+      cancelButtonText: 'Cancel',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      ElMessage({
+        type: 'success',
+        message: 'Delete completed',
+      })
+    })
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: 'Delete canceled',
+      })
+    })
+}
+</script>

+ 39 - 0
docs/examples/message-box/customization.vue

@@ -0,0 +1,39 @@
+<template>
+  <el-button text @click="open">Click to open Message Box</el-button>
+</template>
+
+<script lang="ts" setup>
+import { h } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+const open = () => {
+  ElMessageBox({
+    title: 'Message',
+    message: h('p', null, [
+      h('span', null, 'Message can be '),
+      h('i', { style: 'color: teal' }, 'VNode'),
+    ]),
+    showCancelButton: true,
+    confirmButtonText: 'OK',
+    cancelButtonText: 'Cancel',
+    beforeClose: (action, instance, done) => {
+      if (action === 'confirm') {
+        instance.confirmButtonLoading = true
+        instance.confirmButtonText = 'Loading...'
+        setTimeout(() => {
+          done()
+          setTimeout(() => {
+            instance.confirmButtonLoading = false
+          }, 300)
+        }, 3000)
+      } else {
+        done()
+      }
+    },
+  }).then((action) => {
+    ElMessage({
+      type: 'info',
+      message: `action: ${action}`,
+    })
+  })
+}
+</script>

+ 20 - 0
docs/examples/message-box/customized-icon.vue

@@ -0,0 +1,20 @@
+<template>
+  <el-button text @click="open">Click to open Message Box</el-button>
+</template>
+
+<script lang="ts" setup>
+import { markRaw } from 'vue'
+import { ElMessageBox } from 'element-plus'
+import { Delete } from '@element-plus/icons-vue'
+
+const open = () => {
+  ElMessageBox.confirm(
+    'It will permanently delete the file. Continue?',
+    'Warning',
+    {
+      type: 'warning',
+      icon: markRaw(Delete),
+    }
+  )
+}
+</script>

+ 34 - 0
docs/examples/message-box/distinguishable-close-cancel.vue

@@ -0,0 +1,34 @@
+<template>
+  <el-button text @click="open">Click to open Message Box</el-button>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { Action } from 'element-plus'
+const open = () => {
+  ElMessageBox.confirm(
+    'You have unsaved changes, save and proceed?',
+    'Confirm',
+    {
+      distinguishCancelAndClose: true,
+      confirmButtonText: 'Save',
+      cancelButtonText: 'Discard Changes',
+    }
+  )
+    .then(() => {
+      ElMessage({
+        type: 'info',
+        message: 'Changes saved. Proceeding to a new route.',
+      })
+    })
+    .catch((action: Action) => {
+      ElMessage({
+        type: 'info',
+        message:
+          action === 'cancel'
+            ? 'Changes discarded. Proceeding to a new route.'
+            : 'Stay in the current route',
+      })
+    })
+}
+</script>

+ 32 - 0
docs/examples/message-box/draggable.vue

@@ -0,0 +1,32 @@
+<template>
+  <el-button text @click="open">Click to open Message Box</el-button>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const open = () => {
+  ElMessageBox.confirm(
+    'proxy will permanently delete the file. Continue?',
+    'Warning',
+    {
+      confirmButtonText: 'OK',
+      cancelButtonText: 'Cancel',
+      type: 'warning',
+      draggable: true,
+    }
+  )
+    .then(() => {
+      ElMessage({
+        type: 'success',
+        message: 'Delete completed',
+      })
+    })
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: 'Delete canceled',
+      })
+    })
+}
+</script>

+ 29 - 0
docs/examples/message-box/prompt.vue

@@ -0,0 +1,29 @@
+<template>
+  <el-button text @click="open">Click to open Message Box</el-button>
+</template>
+
+<script lang="ts" setup>
+import { ElMessage, ElMessageBox } from 'element-plus'
+
+const open = () => {
+  ElMessageBox.prompt('Please input your e-mail', 'Tip', {
+    confirmButtonText: 'OK',
+    cancelButtonText: 'Cancel',
+    inputPattern:
+      /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
+    inputErrorMessage: 'Invalid Email',
+  })
+    .then(({ value }) => {
+      ElMessage({
+        type: 'success',
+        message: `Your email is:${value}`,
+      })
+    })
+    .catch(() => {
+      ElMessage({
+        type: 'info',
+        message: 'Input canceled',
+      })
+    })
+}
+</script>

+ 17 - 0
docs/examples/message-box/use-html.vue

@@ -0,0 +1,17 @@
+<template>
+  <el-button text @click="open">Click to open Message Box</el-button>
+</template>
+
+<script lang="ts" setup>
+import { ElMessageBox } from 'element-plus'
+
+const open = () => {
+  ElMessageBox.alert(
+    '<strong>proxy is <i>HTML</i> string</strong>',
+    'HTML String',
+    {
+      dangerouslyUseHTMLString: true,
+    }
+  )
+}
+</script>

+ 34 - 0
docs/examples/message-box/use-vnode.vue

@@ -0,0 +1,34 @@
+<template>
+  <el-button plain @click="open">Common VNode</el-button>
+  <el-button plain @click="open1">Dynamic props</el-button>
+</template>
+
+<script lang="ts" setup>
+import { h, ref } from 'vue'
+import { ElMessageBox, ElSwitch } from 'element-plus'
+
+const open = () => {
+  ElMessageBox({
+    title: 'Message',
+    message: h('p', null, [
+      h('span', null, 'Message can be '),
+      h('i', { style: 'color: teal' }, 'VNode'),
+    ]),
+  })
+}
+
+const open1 = () => {
+  const checked = ref<boolean | string | number>(false)
+  ElMessageBox({
+    title: 'Message',
+    // Should pass a function if VNode contains dynamic props
+    message: () =>
+      h(ElSwitch, {
+        modelValue: checked.value,
+        'onUpdate:modelValue': (val: boolean | string | number) => {
+          checked.value = val
+        },
+      }),
+  })
+}
+</script>

Plik diff jest za duży
+ 194 - 0
docs/zh-CN/component/message-box.md


+ 1 - 0
package.json

@@ -35,6 +35,7 @@
   "dependencies": {
     "@kankan-components/components": "workspace:*",
     "@kankan-components/constants": "workspace:*",
+    "@kankan-components/directives": "workspace:*",
     "@kankan-components/hooks": "workspace:*",
     "@kankan-components/icons-vue": "^0.0.1",
     "@kankan-components/theme-chalk": "workspace:*",

+ 3 - 3
packages/components/basic/dialog/src/dialog.vue

@@ -6,7 +6,7 @@
       @after-leave="afterLeave"
       @before-leave="beforeLeave"
     >
-      <ui-overlay
+      <kk-overlay
         v-show="visible"
         custom-mask-event
         :mask="modal"
@@ -64,14 +64,14 @@
             </ui-dialog-content>
           </ui-focus-trap>
         </div>
-      </ui-overlay>
+      </kk-overlay>
     </transition>
   </teleport>
 </template>
 
 <script lang="ts" setup>
 import { computed, provide, ref } from 'vue'
-import { UiOverlay } from '@kankan-components/components/basic/overlay'
+import { KkOverlay } from '@kankan-components/components/basic/overlay'
 import { useNamespace, useSameTarget } from '@kankan-components/hooks'
 import { dialogInjectionKey } from '@kankan-components/tokens'
 import UiFocusTrap from '@kankan-components/components/basic/focus-trap'

+ 1 - 0
packages/components/basic/index.ts

@@ -6,3 +6,4 @@ export * from './input'
 export * from './dialog'
 export * from './config-provider'
 export * from './focus-trap'
+export * from './message-box'

+ 20 - 0
packages/components/basic/message-box/index.ts

@@ -0,0 +1,20 @@
+import MessageBox from './src/messageBox'
+
+import type { App } from 'vue'
+import type { SFCWithInstall } from '@kankan-components/utils'
+
+const _MessageBox = MessageBox as SFCWithInstall<typeof MessageBox>
+
+_MessageBox.install = (app: App) => {
+  _MessageBox._context = app._context
+  app.config.globalProperties.$msgbox = _MessageBox
+  app.config.globalProperties.$messageBox = _MessageBox
+  app.config.globalProperties.$alert = _MessageBox.alert
+  app.config.globalProperties.$confirm = _MessageBox.confirm
+  app.config.globalProperties.$prompt = _MessageBox.prompt
+}
+
+export default _MessageBox
+export const KkMessageBox = _MessageBox
+
+export * from './src/message-box.type'

+ 506 - 0
packages/components/basic/message-box/src/index.vue

@@ -0,0 +1,506 @@
+<template>
+  <transition name="fade-in-linear" @after-leave="$emit('vanish')">
+    <kk-overlay
+      v-show="visible"
+      :z-index="zIndex"
+      :overlay-class="[ns.is('message-box'), modalClass]"
+      :mask="modal"
+    >
+      <div
+        role="dialog"
+        :aria-label="title"
+        aria-modal="true"
+        :aria-describedby="!showInput ? contentId : undefined"
+        :class="`${ns.namespace.value}-overlay-message-box`"
+        @click="overlayEvent.onClick"
+        @mousedown="overlayEvent.onMousedown"
+        @mouseup="overlayEvent.onMouseup"
+      >
+        <kk-focus-trap
+          loop
+          :trapped="visible"
+          :focus-trap-el="rootRef"
+          :focus-start-el="focusStartRef"
+          @release-requested="onCloseRequested"
+        >
+          <div
+            ref="rootRef"
+            :class="[
+              ns.b(),
+              customClass,
+              ns.is('draggable', draggable),
+              { [ns.m('center')]: center },
+            ]"
+            :style="customStyle"
+            tabindex="-1"
+            @click.stop=""
+          >
+            <div
+              v-if="title !== null && title !== undefined"
+              ref="headerRef"
+              :class="ns.e('header')"
+            >
+              <div :class="ns.e('title')">
+                <kk-icon
+                  v-if="iconComponent && center"
+                  :class="[ns.e('status'), typeClass]"
+                >
+                  <component :is="iconComponent" />
+                </kk-icon>
+                <span>{{ title }}</span>
+              </div>
+              <!-- t('el.messagebox.close') -->
+              <button
+                v-if="showClose"
+                type="button"
+                :class="ns.e('headerbtn')"
+                :aria-label="''"
+                @click="
+                  handleAction(distinguishCancelAndClose ? 'close' : 'cancel')
+                "
+                @keydown.prevent.enter="
+                  handleAction(distinguishCancelAndClose ? 'close' : 'cancel')
+                "
+              >
+                <kk-icon :class="ns.e('close')">
+                  <close />
+                </kk-icon>
+              </button>
+            </div>
+            <div :id="contentId" :class="ns.e('content')">
+              <div :class="ns.e('container')">
+                <kk-icon
+                  v-if="iconComponent && !center && hasMessage"
+                  :class="[ns.e('status'), typeClass]"
+                >
+                  <component :is="iconComponent" />
+                </kk-icon>
+                <div v-if="hasMessage" :class="ns.e('message')">
+                  <slot>
+                    <component
+                      :is="showInput ? 'label' : 'p'"
+                      v-if="!dangerouslyUseHTMLString"
+                      :for="showInput ? inputId : undefined"
+                    >
+                      {{ !dangerouslyUseHTMLString ? message : '' }}
+                    </component>
+                    <component
+                      :is="showInput ? 'label' : 'p'"
+                      v-else
+                      :for="showInput ? inputId : undefined"
+                      v-html="message"
+                    />
+                  </slot>
+                </div>
+              </div>
+              <div v-show="showInput" :class="ns.e('input')">
+                <kk-input
+                  :id="inputId"
+                  ref="inputRef"
+                  v-model="inputValue"
+                  :type="inputType"
+                  :placeholder="inputPlaceholder"
+                  :aria-invalid="validateError"
+                  :class="{ invalid: validateError }"
+                  @keydown.enter="handleInputEnter"
+                />
+                <div
+                  :class="ns.e('errormsg')"
+                  :style="{
+                    visibility: !!editorErrorMessage ? 'visible' : 'hidden',
+                  }"
+                >
+                  {{ editorErrorMessage }}
+                </div>
+              </div>
+            </div>
+            <div :class="ns.e('btns')">
+              <kk-button
+                v-if="showCancelButton"
+                :loading="cancelButtonLoading"
+                :class="[cancelButtonClass]"
+                :round="roundButton"
+                :size="btnSize"
+                @click="handleAction('cancel')"
+                @keydown.prevent.enter="handleAction('cancel')"
+              >
+                <!-- // t('el.messagebox.cancel') -->
+                {{ cancelButtonText || '' }}
+              </kk-button>
+              <kk-button
+                v-show="showConfirmButton"
+                ref="confirmRef"
+                type="primary"
+                :loading="confirmButtonLoading"
+                :class="[confirmButtonClasses]"
+                :round="roundButton"
+                :disabled="confirmButtonDisabled"
+                :size="btnSize"
+                @click="handleAction('confirm')"
+                @keydown.prevent.enter="handleAction('confirm')"
+              >
+                <!-- //t('el.messagebox.confirm') -->
+                {{ confirmButtonText || '' }}
+              </kk-button>
+            </div>
+          </div>
+        </kk-focus-trap>
+      </div>
+    </kk-overlay>
+  </transition>
+</template>
+<script lang="ts">
+// @ts-nocheck
+import {
+  computed,
+  defineComponent,
+  nextTick,
+  onBeforeUnmount,
+  onMounted,
+  reactive,
+  ref,
+  toRefs,
+  watch,
+} from 'vue'
+import KkButton from '@kankan-components/components/basic/button'
+import { TrapFocus } from '@kankan-components/directives'
+import {
+  useDraggable,
+  useId,
+  // useLocale,
+  useLockscreen,
+  useNamespace,
+  useRestoreActive,
+  useSameTarget,
+  useSize,
+  useZIndex,
+} from '@kankan-components/hooks'
+import KkInput from '@kankan-components/components/basic/input'
+import { KkOverlay } from '@kankan-components/components/basic/overlay'
+import {
+  TypeComponents,
+  TypeComponentsMap,
+  isValidComponentSize,
+} from '@kankan-components/utils'
+import { KkIcon } from '@kankan-components/components/basic/icon'
+import ElFocusTrap from '@kankan-components/components/basic/focus-trap'
+
+import type { ComponentPublicInstance, PropType } from 'vue'
+import type { ComponentSize } from '@kankan-components/constants'
+import type {
+  Action,
+  MessageBoxState,
+  MessageBoxType,
+} from './message-box.type'
+
+export default defineComponent({
+  name: 'ElMessageBox',
+  directives: {
+    TrapFocus,
+  },
+  components: {
+    KkButton,
+    ElFocusTrap,
+    KkInput,
+    KkOverlay,
+    KkIcon,
+    ...TypeComponents,
+  },
+  inheritAttrs: false,
+  props: {
+    buttonSize: {
+      type: String as PropType<ComponentSize>,
+      validator: isValidComponentSize,
+    },
+    modal: {
+      type: Boolean,
+      default: true,
+    },
+    lockScroll: {
+      type: Boolean,
+      default: true,
+    },
+    showClose: {
+      type: Boolean,
+      default: true,
+    },
+    closeOnClickModal: {
+      type: Boolean,
+      default: true,
+    },
+    closeOnPressEscape: {
+      type: Boolean,
+      default: true,
+    },
+    closeOnHashChange: {
+      type: Boolean,
+      default: true,
+    },
+    center: Boolean,
+    draggable: Boolean,
+    roundButton: {
+      default: false,
+      type: Boolean,
+    },
+    container: {
+      type: String, // default append to body
+      default: 'body',
+    },
+    boxType: {
+      type: String as PropType<MessageBoxType>,
+      default: '',
+    },
+  },
+  emits: ['vanish', 'action'],
+  setup(props, { emit }) {
+    // const popup = usePopup(props, doClose)
+    // const { t } = useLocale()
+    const ns = useNamespace('message-box')
+    const visible = ref(false)
+    const { nextZIndex } = useZIndex()
+    // s represents state
+    const state = reactive<MessageBoxState>({
+      // autofocus element when open message-box
+      autofocus: true,
+      beforeClose: null,
+      callback: null,
+      cancelButtonText: '',
+      cancelButtonClass: '',
+      confirmButtonText: '',
+      confirmButtonClass: '',
+      customClass: '',
+      customStyle: {},
+      dangerouslyUseHTMLString: false,
+      distinguishCancelAndClose: false,
+      icon: '',
+      inputPattern: null,
+      inputPlaceholder: '',
+      inputType: 'text',
+      inputValue: null,
+      inputValidator: null,
+      inputErrorMessage: '',
+      message: null,
+      modalFade: true,
+      modalClass: '',
+      showCancelButton: false,
+      showConfirmButton: true,
+      type: '',
+      title: undefined,
+      showInput: false,
+      action: '' as Action,
+      confirmButtonLoading: false,
+      cancelButtonLoading: false,
+      confirmButtonDisabled: false,
+      editorErrorMessage: '',
+      // refer to: https://github.com/ElemeFE/element/commit/2999279ae34ef10c373ca795c87b020ed6753eed
+      // seemed ok for now without this state.
+      // isOnComposition: false, // temporary remove
+      validateError: false,
+      zIndex: nextZIndex(),
+    })
+
+    const typeClass = computed(() => {
+      const type = state.type
+      return { [ns.bm('icon', type)]: type && TypeComponentsMap[type] }
+    })
+
+    const contentId = useId()
+    const inputId = useId()
+
+    const btnSize = useSize(
+      computed(() => props.buttonSize),
+      { prop: true, form: true, formItem: true }
+    )
+
+    const iconComponent = computed(
+      () => state.icon || TypeComponentsMap[state.type] || ''
+    )
+    const hasMessage = computed(() => !!state.message)
+    const rootRef = ref<HTMLElement>()
+    const headerRef = ref<HTMLElement>()
+    const focusStartRef = ref<HTMLElement>()
+    const inputRef = ref<ComponentPublicInstance>()
+    const confirmRef = ref<ComponentPublicInstance>()
+
+    const confirmButtonClasses = computed(() => state.confirmButtonClass)
+
+    watch(
+      () => state.inputValue,
+      async (val) => {
+        await nextTick()
+        if (props.boxType === 'prompt' && val !== null) {
+          validate()
+        }
+      },
+      { immediate: true }
+    )
+
+    watch(
+      () => visible.value,
+      (val) => {
+        if (val) {
+          if (props.boxType !== 'prompt') {
+            if (state.autofocus) {
+              focusStartRef.value = confirmRef.value?.$el ?? rootRef.value
+            } else {
+              focusStartRef.value = rootRef.value
+            }
+          }
+          state.zIndex = nextZIndex()
+        }
+        if (props.boxType !== 'prompt') return
+        if (val) {
+          nextTick().then(() => {
+            if (inputRef.value && inputRef.value.$el) {
+              if (state.autofocus) {
+                focusStartRef.value = getInputElement() ?? rootRef.value
+              } else {
+                focusStartRef.value = rootRef.value
+              }
+            }
+          })
+        } else {
+          state.editorErrorMessage = ''
+          state.validateError = false
+        }
+      }
+    )
+
+    const draggable = computed(() => props.draggable)
+    useDraggable(rootRef, headerRef, draggable)
+
+    onMounted(async () => {
+      await nextTick()
+      if (props.closeOnHashChange) {
+        window.addEventListener('hashchange', doClose)
+      }
+    })
+
+    onBeforeUnmount(() => {
+      if (props.closeOnHashChange) {
+        window.removeEventListener('hashchange', doClose)
+      }
+    })
+
+    function doClose() {
+      if (!visible.value) return
+      visible.value = false
+      nextTick(() => {
+        if (state.action) emit('action', state.action)
+      })
+    }
+
+    const handleWrapperClick = () => {
+      if (props.closeOnClickModal) {
+        handleAction(state.distinguishCancelAndClose ? 'close' : 'cancel')
+      }
+    }
+
+    const overlayEvent = useSameTarget(handleWrapperClick)
+
+    const handleInputEnter = (e: KeyboardEvent) => {
+      if (state.inputType !== 'textarea') {
+        e.preventDefault()
+        return handleAction('confirm')
+      }
+    }
+
+    const handleAction = (action: Action) => {
+      if (props.boxType === 'prompt' && action === 'confirm' && !validate()) {
+        return
+      }
+
+      state.action = action
+
+      if (state.beforeClose) {
+        state.beforeClose?.(action, state, doClose)
+      } else {
+        doClose()
+      }
+    }
+
+    const validate = () => {
+      if (props.boxType === 'prompt') {
+        const inputPattern = state.inputPattern
+        if (inputPattern && !inputPattern.test(state.inputValue || '')) {
+          state.editorErrorMessage = state.inputErrorMessage || '' //t('el.messagebox.error')
+          state.validateError = true
+          return false
+        }
+        const inputValidator = state.inputValidator
+        if (typeof inputValidator === 'function') {
+          const validateResult = inputValidator(state.inputValue)
+          if (validateResult === false) {
+            state.editorErrorMessage = state.inputErrorMessage || '' // t('el.messagebox.error')
+            state.validateError = true
+            return false
+          }
+          if (typeof validateResult === 'string') {
+            state.editorErrorMessage = validateResult
+            state.validateError = true
+            return false
+          }
+        }
+      }
+      state.editorErrorMessage = ''
+      state.validateError = false
+      return true
+    }
+
+    const getInputElement = () => {
+      const inputRefs = inputRef.value.$refs
+      return (inputRefs.input || inputRefs.textarea) as HTMLElement
+    }
+
+    const handleClose = () => {
+      handleAction('close')
+    }
+
+    // when close on press escape is disabled, pressing esc should not callout
+    // any other message box and close any other dialog-ish elements
+    // e.g. Dialog has a close on press esc feature, and when it closes, it calls
+    // props.beforeClose method to make a intermediate state by callout a message box
+    // for some verification or alerting. then if we allow global event liek this
+    // to dispatch, it could callout another message box.
+    const onCloseRequested = () => {
+      if (props.closeOnPressEscape) {
+        handleClose()
+      }
+    }
+
+    // locks the screen to prevent scroll
+    if (props.lockScroll) {
+      useLockscreen(visible)
+    }
+
+    // restore to prev active element.
+    useRestoreActive(visible)
+
+    return {
+      ...toRefs(state),
+      ns,
+      overlayEvent,
+      visible,
+      hasMessage,
+      typeClass,
+      contentId,
+      inputId,
+      btnSize,
+      iconComponent,
+      confirmButtonClasses,
+      rootRef,
+      focusStartRef,
+      headerRef,
+      inputRef,
+      confirmRef,
+      doClose, // for outside usage
+      handleClose, // for out side usage
+      onCloseRequested,
+      handleWrapperClick,
+      handleInputEnter,
+      handleAction,
+      // t,
+    }
+  },
+})
+</script>

+ 213 - 0
packages/components/basic/message-box/src/message-box.type.ts

@@ -0,0 +1,213 @@
+import type { AppContext, CSSProperties, Component, VNode } from 'vue'
+import type { ComponentSize } from '@kankan-components/constants'
+
+type MessageType = '' | 'success' | 'warning' | 'info' | 'error'
+
+export type Action = 'confirm' | 'close' | 'cancel'
+export type MessageBoxType = '' | 'prompt' | 'alert' | 'confirm'
+export type MessageBoxData = MessageBoxInputData & Action
+export interface MessageBoxInputData {
+  value: string
+  action: Action
+}
+
+export interface MessageBoxInputValidator {
+  (value: string): boolean | string
+}
+
+export declare interface MessageBoxState {
+  autofocus: boolean
+  title: string
+  message: string
+  type: MessageType
+  icon: string | Component
+  customClass: string
+  customStyle: CSSProperties
+  showInput: boolean
+  inputValue: string
+  inputPlaceholder: string
+  inputType: string
+  inputPattern: RegExp
+  inputValidator: MessageBoxInputValidator
+  inputErrorMessage: string
+  showConfirmButton: boolean
+  showCancelButton: boolean
+  action: Action
+  dangerouslyUseHTMLString: boolean
+  confirmButtonText: string
+  cancelButtonText: string
+  confirmButtonLoading: boolean
+  cancelButtonLoading: boolean
+  confirmButtonClass: string
+  confirmButtonDisabled: boolean
+  cancelButtonClass: string
+  editorErrorMessage: string
+
+  beforeClose:
+    | null
+    | ((action: Action, instance: MessageBoxState, done: () => void) => void)
+  callback: null | Callback
+  distinguishCancelAndClose: boolean
+  modalFade: boolean
+  modalClass: string
+  // refer to: https://github.com/ElemeFE/element/commit/2999279ae34ef10c373ca795c87b020ed6753eed
+  // seemed ok for now without this state.
+  // isOnComposition: false, // temporary remove
+  validateError: boolean
+  zIndex: number
+}
+
+export type Callback =
+  | ((value: string, action: Action) => any)
+  | ((action: Action) => any)
+
+/** Options used in MessageBox */
+export interface KkMessageBoxOptions {
+  /**
+   * auto focus when open message-box
+   */
+  autofocus?: boolean
+
+  /** Callback before MessageBox closes, and it will prevent MessageBox from closing */
+  beforeClose?: (
+    action: Action,
+    instance: MessageBoxState,
+    done: () => void
+  ) => void
+
+  /** Custom class name for MessageBox */
+  customClass?: string
+
+  /** Custom inline style for MessageBox */
+  customStyle?: CSSProperties
+
+  /** MessageBox closing callback if you don't prefer Promise */
+  callback?: Callback
+
+  /** Text content of cancel button */
+  cancelButtonText?: string
+
+  /** Text content of confirm button */
+  confirmButtonText?: string
+
+  /** Custom class name of cancel button */
+  cancelButtonClass?: string
+
+  /** Custom class name of confirm button */
+  confirmButtonClass?: string
+
+  /** Whether to align the content in center */
+  center?: boolean
+
+  /** Whether MessageBox can be drag */
+  draggable?: boolean
+
+  /** Content of the MessageBox */
+  message?: string | VNode | (() => VNode)
+
+  /** Title of the MessageBox */
+  title?: string | KkMessageBoxOptions
+
+  /** Message type, used for icon display */
+  type?: MessageType
+
+  /** Message box type */
+  boxType?: MessageBoxType
+
+  /** Custom icon component */
+  icon?: string | Component
+
+  /** Whether message is treated as HTML string */
+  dangerouslyUseHTMLString?: boolean
+
+  /** Whether to distinguish canceling and closing */
+  distinguishCancelAndClose?: boolean
+
+  /** Whether to lock body scroll when MessageBox prompts */
+  lockScroll?: boolean
+
+  /** Whether to show a cancel button */
+  showCancelButton?: boolean
+
+  /** Whether to show a confirm button */
+  showConfirmButton?: boolean
+
+  /** Whether to show a close button */
+  showClose?: boolean
+
+  /** Whether to use round button */
+  roundButton?: boolean
+
+  /** Whether MessageBox can be closed by clicking the mask */
+  closeOnClickModal?: boolean
+
+  /** Whether MessageBox can be closed by pressing the ESC */
+  closeOnPressEscape?: boolean
+
+  /** Whether to close MessageBox when hash changes */
+  closeOnHashChange?: boolean
+
+  /** Whether to show an input */
+  showInput?: boolean
+
+  /** Placeholder of input */
+  inputPlaceholder?: string
+
+  /** Initial value of input */
+  inputValue?: string
+
+  /** Regexp for the input */
+  inputPattern?: RegExp
+
+  /** Input Type: text, textArea, password or number */
+  inputType?: string
+
+  /** Validation function for the input. Should returns a boolean or string. If a string is returned, it will be assigned to inputErrorMessage */
+  inputValidator?: MessageBoxInputValidator
+
+  /** Error message when validation fails */
+  inputErrorMessage?: string
+
+  /** Custom size of confirm and cancel buttons */
+  buttonSize?: ComponentSize
+
+  /** Custom element to append the message box to */
+  appendTo?: HTMLElement | string
+}
+
+export type KkMessageBoxShortcutMethod = ((
+  message: KkMessageBoxOptions['message'],
+  title: KkMessageBoxOptions['title'],
+  options?: KkMessageBoxOptions,
+  appContext?: AppContext | null
+) => Promise<MessageBoxData>) &
+  ((
+    message: KkMessageBoxOptions['message'],
+    options?: KkMessageBoxOptions,
+    appContext?: AppContext | null
+  ) => Promise<MessageBoxData>)
+
+export interface IKkMessageBox {
+  _context: AppContext | null
+
+  /** Show a message box */
+  // (message: string, title?: string, type?: string): Promise<MessageBoxData>
+
+  /** Show a message box */
+  (
+    options: KkMessageBoxOptions,
+    appContext?: AppContext | null
+  ): Promise<MessageBoxData>
+
+  /** Show an alert message box */
+  alert: KkMessageBoxShortcutMethod
+
+  /** Show a confirm message box */
+  confirm: KkMessageBoxShortcutMethod
+
+  /** Show a prompt message box */
+  prompt: KkMessageBoxShortcutMethod
+
+  /** Close current message box */
+  close(): void
+}

+ 241 - 0
packages/components/basic/message-box/src/messageBox.ts

@@ -0,0 +1,241 @@
+import { createVNode, render } from 'vue'
+import { isClient } from '@vueuse/core'
+import {
+  debugWarn,
+  hasOwn,
+  isElement,
+  isFunction,
+  isObject,
+  isString,
+  isUndefined,
+  isVNode,
+} from '@kankan-components/utils'
+import MessageBoxConstructor from './index.vue'
+
+import type { AppContext, ComponentPublicInstance, VNode } from 'vue'
+import type {
+  Action,
+  Callback,
+  IKkMessageBox,
+  KkMessageBoxOptions,
+  KkMessageBoxShortcutMethod,
+  MessageBoxData,
+  MessageBoxState,
+} from './message-box.type'
+
+// component default merge props & data
+
+const messageInstance = new Map<
+  ComponentPublicInstance<{ doClose: () => void }>, // marking doClose as function
+  {
+    options: any
+    callback: Callback | undefined
+    resolve: (res: any) => void
+    reject: (reason?: any) => void
+  }
+>()
+
+const getAppendToElement = (props: any): HTMLElement => {
+  let appendTo: HTMLElement | null = document.body
+  if (props.appendTo) {
+    if (isString(props.appendTo)) {
+      appendTo = document.querySelector<HTMLElement>(props.appendTo)
+    }
+    if (isElement(props.appendTo)) {
+      appendTo = props.appendTo
+    }
+
+    // should fallback to default value with a warning
+    if (!isElement(appendTo)) {
+      debugWarn(
+        'ElMessageBox',
+        'the appendTo option is not an HTMLElement. Falling back to document.body.'
+      )
+      appendTo = document.body
+    }
+  }
+  return appendTo
+}
+
+const initInstance = (
+  props: any,
+  container: HTMLElement,
+  appContext: AppContext | null = null
+) => {
+  const vnode = createVNode(
+    MessageBoxConstructor,
+    props,
+    isFunction(props.message) || isVNode(props.message)
+      ? {
+          default: isFunction(props.message)
+            ? props.message
+            : () => props.message,
+        }
+      : null
+  )
+  vnode.appContext = appContext
+  render(vnode, container)
+  getAppendToElement(props).appendChild(container.firstElementChild!)
+  return vnode.component
+}
+
+const genContainer = () => {
+  return document.createElement('div')
+}
+
+const showMessage = (options: any, appContext?: AppContext | null) => {
+  const container = genContainer()
+  // Adding destruct method.
+  // when transition leaves emitting `vanish` evt. so that we can do the clean job.
+  options.onVanish = () => {
+    // not sure if this causes mem leak, need proof to verify that.
+    // maybe calling out like 1000 msg-box then close them all.
+    render(null, container)
+    messageInstance.delete(vm) // Remove vm to avoid mem leak.
+    // here we were suppose to call document.body.removeChild(container.firstElementChild)
+    // but render(null, container) did that job for us. so that we do not call that directly
+  }
+
+  options.onAction = (action: Action) => {
+    const currentMsg = messageInstance.get(vm)!
+    let resolve: Action | { value: string; action: Action }
+    if (options.showInput) {
+      resolve = { value: vm.inputValue, action }
+    } else {
+      resolve = action
+    }
+    if (options.callback) {
+      options.callback(resolve, instance.proxy)
+    } else {
+      if (action === 'cancel' || action === 'close') {
+        if (options.distinguishCancelAndClose && action !== 'cancel') {
+          currentMsg.reject('close')
+        } else {
+          currentMsg.reject('cancel')
+        }
+      } else {
+        currentMsg.resolve(resolve)
+      }
+    }
+  }
+
+  const instance = initInstance(options, container, appContext)!
+
+  // This is how we use message box programmably.
+  // Maybe consider releasing a template version?
+  // get component instance like v2.
+  const vm = instance.proxy as ComponentPublicInstance<
+    {
+      visible: boolean
+      doClose: () => void
+    } & MessageBoxState
+  >
+
+  for (const prop in options) {
+    if (hasOwn(options, prop) && !hasOwn(vm.$props, prop)) {
+      vm[prop as keyof ComponentPublicInstance] = options[prop]
+    }
+  }
+
+  // change visibility after everything is settled
+  vm.visible = true
+  return vm
+}
+
+async function MessageBox(
+  options: KkMessageBoxOptions,
+  appContext?: AppContext | null
+): Promise<MessageBoxData>
+function MessageBox(
+  options: KkMessageBoxOptions | string | VNode,
+  appContext: AppContext | null = null
+): Promise<{ value: string; action: Action } | Action> {
+  if (!isClient) return Promise.reject()
+  let callback: Callback | undefined
+  if (isString(options) || isVNode(options)) {
+    options = {
+      message: options,
+    }
+  } else {
+    callback = options.callback
+  }
+
+  return new Promise((resolve, reject) => {
+    const vm = showMessage(
+      options,
+      appContext ?? (MessageBox as IKkMessageBox)._context
+    )
+    // collect this vm in order to handle upcoming events.
+    messageInstance.set(vm, {
+      options,
+      callback,
+      resolve,
+      reject,
+    })
+  })
+}
+
+const MESSAGE_BOX_VARIANTS = ['alert', 'confirm', 'prompt'] as const
+const MESSAGE_BOX_DEFAULT_OPTS: Record<
+  (typeof MESSAGE_BOX_VARIANTS)[number],
+  Partial<KkMessageBoxOptions>
+> = {
+  alert: { closeOnPressEscape: false, closeOnClickModal: false },
+  confirm: { showCancelButton: true },
+  prompt: { showCancelButton: true, showInput: true },
+}
+
+MESSAGE_BOX_VARIANTS.forEach((boxType) => {
+  ;(MessageBox as IKkMessageBox)[boxType] = messageBoxFactory(
+    boxType
+  ) as KkMessageBoxShortcutMethod
+})
+
+function messageBoxFactory(boxType: (typeof MESSAGE_BOX_VARIANTS)[number]) {
+  return (
+    message: string | VNode,
+    title: string | KkMessageBoxOptions,
+    options?: KkMessageBoxOptions,
+    appContext?: AppContext | null
+  ) => {
+    let titleOrOpts = ''
+    if (isObject(title)) {
+      options = title as KkMessageBoxOptions
+      titleOrOpts = ''
+    } else if (isUndefined(title)) {
+      titleOrOpts = ''
+    } else {
+      titleOrOpts = title as string
+    }
+
+    return MessageBox(
+      Object.assign(
+        {
+          title: titleOrOpts,
+          message,
+          type: '',
+          ...MESSAGE_BOX_DEFAULT_OPTS[boxType],
+        },
+        options,
+        {
+          boxType,
+        }
+      ),
+      appContext
+    )
+  }
+}
+
+MessageBox.close = () => {
+  // instance.setupInstall.doClose()
+  // instance.setupInstall.state.visible = false
+
+  messageInstance.forEach((_, vm) => {
+    vm.doClose()
+  })
+
+  messageInstance.clear()
+}
+;(MessageBox as IKkMessageBox)._context = null
+
+export default MessageBox as IKkMessageBox

+ 5 - 0
packages/components/basic/message-box/style/css.ts

@@ -0,0 +1,5 @@
+import '@kankan-components/components/base/style/css'
+import '@kankan-components/theme-chalk/el-message-box.css'
+import '@kankan-components/components/button/style/css'
+import '@kankan-components/components/input/style/css'
+import '@kankan-components/components/overlay/style/css'

+ 5 - 0
packages/components/basic/message-box/style/index.ts

@@ -0,0 +1,5 @@
+import '@kankan-components/components/base/style'
+import '@kankan-components/theme-chalk/src/message-box.scss'
+import '@kankan-components/components/button/style'
+import '@kankan-components/components/input/style'
+import '@kankan-components/components/overlay/style'

+ 2 - 2
packages/components/basic/overlay/index.ts

@@ -1,6 +1,6 @@
 import Overlay from './src/overlay'
 
-export const UiOverlay = Overlay
-export default UiOverlay
+export const KkOverlay = Overlay
+export default KkOverlay
 
 export * from './src/overlay'

+ 1 - 0
packages/directives/index.ts

@@ -0,0 +1 @@
+export { default as TrapFocus } from './trap-focus'

+ 73 - 0
packages/directives/trap-focus/index.ts

@@ -0,0 +1,73 @@
+import { nextTick } from 'vue'
+import { obtainAllFocusableElements } from '@kankan-components/utils'
+import { EVENT_CODE } from '@kankan-components/constants'
+import type { ObjectDirective } from 'vue'
+
+export const FOCUSABLE_CHILDREN = '_trap-focus-children'
+export const TRAP_FOCUS_HANDLER = '_trap-focus-handler'
+
+export interface TrapFocusElement extends HTMLElement {
+  [FOCUSABLE_CHILDREN]: HTMLElement[]
+  [TRAP_FOCUS_HANDLER]: (e: KeyboardEvent) => void
+}
+
+const FOCUS_STACK: TrapFocusElement[] = []
+
+const FOCUS_HANDLER = (e: KeyboardEvent) => {
+  // Getting the top layer.
+  if (FOCUS_STACK.length === 0) return
+  const focusableElement =
+    FOCUS_STACK[FOCUS_STACK.length - 1][FOCUSABLE_CHILDREN]
+  if (focusableElement.length > 0 && e.code === EVENT_CODE.tab) {
+    if (focusableElement.length === 1) {
+      e.preventDefault()
+      if (document.activeElement !== focusableElement[0]) {
+        focusableElement[0].focus()
+      }
+      return
+    }
+    const goingBackward = e.shiftKey
+    const isFirst = e.target === focusableElement[0]
+    const isLast = e.target === focusableElement[focusableElement.length - 1]
+    if (isFirst && goingBackward) {
+      e.preventDefault()
+      focusableElement[focusableElement.length - 1].focus()
+    }
+    if (isLast && !goingBackward) {
+      e.preventDefault()
+      focusableElement[0].focus()
+    }
+
+    // the is critical since jsdom did not implement user actions, you can only mock it
+    // DELETE ME: when testing env switches to puppeteer
+    if (process.env.NODE_ENV === 'test') {
+      const index = focusableElement.indexOf(e.target as HTMLElement)
+      if (index !== -1) {
+        focusableElement[goingBackward ? index - 1 : index + 1]?.focus()
+      }
+    }
+  }
+}
+
+const TrapFocus: ObjectDirective = {
+  beforeMount(el: TrapFocusElement) {
+    el[FOCUSABLE_CHILDREN] = obtainAllFocusableElements(el)
+    FOCUS_STACK.push(el)
+    if (FOCUS_STACK.length <= 1) {
+      document.addEventListener('keydown', FOCUS_HANDLER)
+    }
+  },
+  updated(el: TrapFocusElement) {
+    nextTick(() => {
+      el[FOCUSABLE_CHILDREN] = obtainAllFocusableElements(el)
+    })
+  },
+  unmounted() {
+    FOCUS_STACK.shift()
+    if (FOCUS_STACK.length === 0) {
+      document.removeEventListener('keydown', FOCUS_HANDLER)
+    }
+  },
+}
+
+export default TrapFocus

+ 1 - 0
packages/hooks/index.ts

@@ -10,3 +10,4 @@ export * from './use-escape-keydown'
 export * from './use-common-props'
 export * from './use-attrs'
 export * from './use-cursor'
+export * from './use-restore-active'

+ 30 - 0
packages/hooks/use-restore-active/index.ts

@@ -0,0 +1,30 @@
+import { isRef, watch } from 'vue'
+import type { Ref } from 'vue'
+
+/**
+ * This method provides dialogable components the ability to restore previously activated element before
+ * the dialog gets opened
+ */
+export const useRestoreActive = (
+  toggle: Ref<boolean>,
+  initialFocus?: Ref<HTMLElement>
+) => {
+  let previousActive: HTMLElement
+  watch(
+    () => toggle.value,
+    (val) => {
+      if (val) {
+        previousActive = document.activeElement as HTMLElement
+        if (isRef(initialFocus)) {
+          initialFocus.value.focus?.()
+        }
+      } else {
+        if (process.env.NODE_ENV === 'test') {
+          previousActive.focus.call(previousActive)
+        } else {
+          previousActive.focus()
+        }
+      }
+    }
+  )
+}

+ 1 - 1
packages/kankan-components/index.ts

@@ -1,7 +1,7 @@
 import installer from './defaults'
 export * from '@kankan-components/components'
 export * from '@kankan-components/constants'
-// export * from '@kankan-components/directives'
+export * from '@kankan-components/directives'
 export * from '@kankan-components/hooks'
 export * from '@kankan-components/tokens'
 export * from './make-installer'

+ 2 - 1
packages/kankan-components/plugin.ts

@@ -1,3 +1,4 @@
+import { KkMessageBox } from '@kankan-components/components/basic/message-box'
 import type { Plugin } from 'vue'
 
-export default [] as Plugin[]
+export default [KkMessageBox] as Plugin[]

+ 1 - 1
packages/theme-chalk/src/index.scss

@@ -6,5 +6,5 @@
 @use './overlay.scss';
 @use './dialog.scss';
 @use './input.scss';
-// component adv styles
+@use './message-box.scss'; // component adv styles
 @use './tag.scss';

+ 254 - 0
packages/theme-chalk/src/message-box.scss

@@ -0,0 +1,254 @@
+@use 'sass:map';
+
+@use 'mixins/mixins' as *;
+@use 'mixins/var' as *;
+@use 'common/var' as *;
+@use 'common/popup' as *;
+
+@include b(message-box) {
+  @include set-component-css-var('messagebox', $messagebox);
+}
+
+@include b(message-box) {
+  display: inline-block;
+  max-width: getCssVar('messagebox-width');
+  width: 100%;
+  padding-bottom: 10px;
+  vertical-align: middle;
+  background-color: getCssVar('bg-color');
+  border-radius: getCssVar('messagebox-border-radius');
+  border: 1px solid getCssVar('border-color-lighter');
+  font-size: getCssVar('messagebox-font-size');
+  box-shadow: getCssVar('box-shadow-light');
+  text-align: left;
+  overflow: hidden;
+  backface-visibility: hidden;
+
+  &:focus {
+    outline: none !important;
+  }
+
+  @at-root .#{$namespace}-overlay.is-message-box {
+    .#{$namespace}-overlay-message-box {
+      text-align: center;
+      position: fixed;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      left: 0;
+      padding: 16px;
+      overflow: auto;
+
+      &::after {
+        content: '';
+        display: inline-block;
+        height: 100%;
+        width: 0;
+        vertical-align: middle;
+      }
+    }
+  }
+
+  @include when(draggable) {
+    @include e(header) {
+      cursor: move;
+      user-select: none;
+    }
+  }
+
+  @include e(header) {
+    position: relative;
+    padding: getCssVar('messagebox-padding-primary');
+    padding-bottom: 10px;
+  }
+
+  @include e(title) {
+    padding-left: 0;
+    margin-bottom: 0;
+    font-size: getCssVar('messagebox-font-size');
+    line-height: 1;
+    color: getCssVar('messagebox-title-color');
+  }
+
+  @include e(headerbtn) {
+    position: absolute;
+    top: getCssVar('messagebox-padding-primary');
+    right: getCssVar('messagebox-padding-primary');
+    padding: 0;
+    border: none;
+    outline: none;
+    background: transparent;
+    font-size: var(
+      #{getCssVarName('message-close-size')},
+      map.get($message, 'close-size')
+    );
+    cursor: pointer;
+
+    .#{$namespace}-message-box__close {
+      color: getCssVar('color-info');
+      font-size: inherit;
+    }
+
+    &:focus,
+    &:hover {
+      .#{$namespace}-message-box__close {
+        color: getCssVar('color-primary');
+      }
+    }
+  }
+
+  @include e(content) {
+    padding: 10px getCssVar('messagebox-padding-primary');
+    color: getCssVar('messagebox-content-color');
+    font-size: getCssVar('messagebox-content-font-size');
+  }
+
+  @include e(container) {
+    position: relative;
+  }
+
+  @include e(input) {
+    padding-top: 15px;
+
+    & div.invalid > input {
+      border-color: getCssVar('color-error');
+
+      &:focus {
+        border-color: getCssVar('color-error');
+      }
+    }
+  }
+
+  @include e(status) {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    font-size: 24px !important;
+
+    &::before {
+      // 防止图标切割
+      padding-left: 1px;
+    }
+
+    &.#{$namespace}-icon {
+      // 防止 el-icon 的position样式覆盖
+      position: absolute;
+    }
+
+    + .#{$namespace}-message-box__message {
+      padding-left: 36px;
+      padding-right: 12px;
+      word-break: break-word;
+    }
+
+    @each $type in (success, info, warning, error) {
+      &.#{$namespace}-message-box-icon--#{$type} {
+        @include css-var-from-global(('messagebox', 'color'), ('color', $type));
+        color: getCssVar('messagebox-color');
+      }
+    }
+  }
+
+  @include e(message) {
+    margin: 0;
+
+    & p {
+      margin: 0;
+      line-height: 24px;
+    }
+  }
+
+  @include e(errormsg) {
+    color: getCssVar('color-error');
+    font-size: getCssVar('messagebox-error-font-size');
+    min-height: 18px;
+    margin-top: 2px;
+  }
+
+  @include e(btns) {
+    padding: 5px 15px 0;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-end;
+    align-items: center;
+
+    & button:nth-child(2) {
+      margin-left: 10px;
+    }
+  }
+
+  @include e(btns-reverse) {
+    flex-direction: row-reverse;
+  }
+
+  // centerAlign 布局
+  @include m(center) {
+    @include e(title) {
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    @include e(status) {
+      position: relative;
+      top: auto;
+      padding-right: 5px;
+      text-align: center;
+      transform: translateY(-1px);
+    }
+
+    @include e(message) {
+      margin-left: 0;
+    }
+
+    @include e(btns) {
+      justify-content: center;
+    }
+
+    @include e(content) {
+      $padding-horizontal: calc(
+        #{getCssVar('messagebox', 'padding-primary')} + 12px
+      );
+
+      padding-left: $padding-horizontal;
+      padding-right: $padding-horizontal;
+
+      text-align: center;
+    }
+  }
+}
+
+.fade-in-linear-enter-active {
+  .#{$namespace}-overlay-message-box {
+    animation: msgbox-fade-in getCssVar('transition-duration');
+  }
+}
+
+.fade-in-linear-leave-active {
+  .#{$namespace}-overlay-message-box {
+    animation: msgbox-fade-in getCssVar('transition-duration') reverse;
+  }
+}
+
+@keyframes msgbox-fade-in {
+  0% {
+    transform: translate3d(0, -20px, 0);
+    opacity: 0;
+  }
+  100% {
+    transform: translate3d(0, 0, 0);
+    opacity: 1;
+  }
+}
+
+@keyframes msgbox-fade-out {
+  0% {
+    transform: translate3d(0, 0, 0);
+    opacity: 1;
+  }
+  100% {
+    transform: translate3d(0, -20px, 0);
+    opacity: 0;
+  }
+}

+ 1 - 0
packages/utils/index.ts

@@ -7,3 +7,4 @@ export * from './error'
 export * from './typescript'
 export * from './functions'
 export * from './i18n'
+export * from './validator'

+ 8 - 0
packages/utils/validator.ts

@@ -0,0 +1,8 @@
+import { componentSizes, datePickTypes } from '@kankan-components/constants'
+import type { ComponentSize, DatePickType } from '@kankan-components/constants'
+
+export const isValidComponentSize = (val: string): val is ComponentSize | '' =>
+  ['', ...componentSizes].includes(val)
+
+export const isValidDatePickType = (val: string): val is DatePickType =>
+  ([...datePickTypes] as string[]).includes(val)

+ 18 - 387
pnpm-lock.yaml

@@ -10,6 +10,7 @@ importers:
       '@kankan-components/build-utils': workspace:*
       '@kankan-components/components': workspace:*
       '@kankan-components/constants': workspace:*
+      '@kankan-components/directives': workspace:*
       '@kankan-components/eslint-config': workspace:*
       '@kankan-components/hooks': workspace:*
       '@kankan-components/icons-vue': ^0.0.1
@@ -57,6 +58,7 @@ importers:
     dependencies:
       '@kankan-components/components': link:packages/components
       '@kankan-components/constants': link:packages/constants
+      '@kankan-components/directives': link:packages/directives
       '@kankan-components/hooks': link:packages/hooks
       '@kankan-components/icons-vue': 0.0.1_vue@3.2.47
       '@kankan-components/theme-chalk': link:packages/theme-chalk
@@ -160,7 +162,7 @@ importers:
       '@vueuse/core': 9.3.0_vue@3.2.39
       axios: 0.27.2
       clipboard-copy: 4.0.1
-      element-plus: 2.2.29_vue@3.2.39
+      element-plus: 2.2.30_vue@3.2.39
       kankan-components: link:../packages/kankan-components
       markdown-it: 13.0.1
       normalize.css: 8.0.1
@@ -221,7 +223,7 @@ importers:
       vue: ^3.2.37
     dependencies:
       '@kankan-components/build-constants': link:../build-constants
-      '@pnpm/find-workspace-packages': 4.0.43_7we2eqc3d7f6uvptfoorlpdruy
+      '@pnpm/find-workspace-packages': 4.0.43_ah4vodsfb2phqgbxsizsob47ki
       '@pnpm/logger': 4.0.0
       '@rollup/plugin-commonjs': 22.0.2_rollup@2.78.1
       '@rollup/plugin-node-resolve': 13.3.0_rollup@2.78.1
@@ -258,7 +260,7 @@ importers:
       consola: ^2.15.3
       unbuild: ^0.7.4
     dependencies:
-      '@pnpm/find-workspace-packages': 4.0.43_7we2eqc3d7f6uvptfoorlpdruy
+      '@pnpm/find-workspace-packages': 4.0.43_ah4vodsfb2phqgbxsizsob47ki
       '@pnpm/logger': 4.0.0
       consola: 2.15.3
     devDependencies:
@@ -575,10 +577,6 @@ packages:
     resolution: {integrity: sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==}
     dev: true
 
-  /@antfu/utils/0.6.3:
-    resolution: {integrity: sha512-sEYpyyKUPOew9QsXZ8feRVMzW6DWLviwOl+/ap06UQW02A8Srbc95CPHVm4eUbiBzBgD46eyIT+przv//KSSlQ==}
-    dev: true
-
   /@apideck/better-ajv-errors/0.3.6_ajv@8.12.0:
     resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==}
     engines: {node: '>=10'}
@@ -1284,7 +1282,7 @@ packages:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-plugin-utils': 7.19.0
     dev: true
 
   /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.20.12:
@@ -3254,31 +3252,6 @@ packages:
       '@pnpm/types': 8.7.0
       load-json-file: 6.2.0
 
-  /@pnpm/cli-utils/0.7.43_7we2eqc3d7f6uvptfoorlpdruy:
-    resolution: {integrity: sha512-xawa2YteWQT6KS3tlU2t7SzHIS7hi6LYRQNwxFfNl4da89pTu3szZvVcYMTuEYjXsue5+L0OkJbWZL64lNME5A==}
-    engines: {node: '>=14.6'}
-    peerDependencies:
-      '@pnpm/logger': ^4.0.0
-    dependencies:
-      '@pnpm/cli-meta': 3.0.8
-      '@pnpm/config': 15.10.12_7we2eqc3d7f6uvptfoorlpdruy
-      '@pnpm/default-reporter': 10.1.1_7we2eqc3d7f6uvptfoorlpdruy
-      '@pnpm/error': 3.1.0
-      '@pnpm/logger': 4.0.0
-      '@pnpm/manifest-utils': 3.1.6_@pnpm+logger@4.0.0
-      '@pnpm/package-is-installable': 6.0.12_@pnpm+logger@4.0.0
-      '@pnpm/read-project-manifest': 3.0.13
-      '@pnpm/types': 8.7.0
-      chalk: 4.1.2
-      load-json-file: 6.2.0
-    transitivePeerDependencies:
-      - '@yarnpkg/core'
-      - bluebird
-      - domexception
-      - supports-color
-      - typanion
-    dev: false
-
   /@pnpm/cli-utils/0.7.43_ah4vodsfb2phqgbxsizsob47ki:
     resolution: {integrity: sha512-xawa2YteWQT6KS3tlU2t7SzHIS7hi6LYRQNwxFfNl4da89pTu3szZvVcYMTuEYjXsue5+L0OkJbWZL64lNME5A==}
     engines: {node: '>=14.6'}
@@ -3302,38 +3275,6 @@ packages:
       - domexception
       - supports-color
       - typanion
-    dev: true
-
-  /@pnpm/config/15.10.12_7we2eqc3d7f6uvptfoorlpdruy:
-    resolution: {integrity: sha512-yCxgplRZOkQ9Y8V5MNjOIAL9cncbplTNlr+FWIvueRrCiU6zARZhsj7BIOXh+N8SKgtKyIb3UKOtgIslh1KYxQ==}
-    engines: {node: '>=14.6'}
-    dependencies:
-      '@pnpm/constants': 6.1.0
-      '@pnpm/error': 3.1.0
-      '@pnpm/git-utils': 0.1.0
-      '@pnpm/matcher': 3.2.0
-      '@pnpm/npm-conf': 2.0.0
-      '@pnpm/pnpmfile': 3.0.3_7we2eqc3d7f6uvptfoorlpdruy
-      '@pnpm/read-project-manifest': 3.0.13
-      '@pnpm/types': 8.7.0
-      camelcase: 6.3.0
-      can-write-to-dir: 1.1.1
-      is-subdir: 1.2.0
-      is-windows: 1.0.2
-      normalize-registry-url: 2.0.0
-      path-absolute: 1.0.1
-      path-name: 1.0.0
-      ramda: /@pnpm/ramda/0.28.1
-      realpath-missing: 1.1.0
-      which: 2.0.2
-    transitivePeerDependencies:
-      - '@pnpm/logger'
-      - '@yarnpkg/core'
-      - bluebird
-      - domexception
-      - supports-color
-      - typanion
-    dev: false
 
   /@pnpm/config/15.10.12_ah4vodsfb2phqgbxsizsob47ki:
     resolution: {integrity: sha512-yCxgplRZOkQ9Y8V5MNjOIAL9cncbplTNlr+FWIvueRrCiU6zARZhsj7BIOXh+N8SKgtKyIb3UKOtgIslh1KYxQ==}
@@ -3364,7 +3305,6 @@ packages:
       - domexception
       - supports-color
       - typanion
-    dev: true
 
   /@pnpm/constants/6.1.0:
     resolution: {integrity: sha512-L6AiU3OXv9kjKGTJN9j8n1TeJGDcLX9atQlZvAkthlvbXjvKc5SKNWESc/eXhr5nEfuMWhQhiKHDJCpYejmeCQ==}
@@ -3383,71 +3323,6 @@ packages:
       '@pnpm/logger': 4.0.0
       '@pnpm/types': 8.7.0
 
-  /@pnpm/core/6.0.3_7we2eqc3d7f6uvptfoorlpdruy:
-    resolution: {integrity: sha512-fTmsXqX/zPNGn46OQqnmhI94jigXarCNo/E3LlB4Sr5WEQrZ/x6UIqXNY6NNBacXkzwBnFECe+quIi9lZOao/w==}
-    engines: {node: '>=14.6'}
-    peerDependencies:
-      '@pnpm/logger': ^4.0.0
-    dependencies:
-      '@pnpm/build-modules': 9.3.11_apzzy4k2ekv2hywwne3y7chynq
-      '@pnpm/calc-dep-state': 3.0.1
-      '@pnpm/constants': 6.1.0
-      '@pnpm/core-loggers': 7.1.0_@pnpm+logger@4.0.0
-      '@pnpm/crypto.base32-hash': 1.0.1
-      '@pnpm/error': 3.1.0
-      '@pnpm/filter-lockfile': 6.0.22_@pnpm+logger@4.0.0
-      '@pnpm/get-context': 7.0.3_@pnpm+logger@4.0.0
-      '@pnpm/graph-sequencer': 1.0.0
-      '@pnpm/headless': 18.7.6_apzzy4k2ekv2hywwne3y7chynq
-      '@pnpm/hoist': 6.2.14_@pnpm+logger@4.0.0
-      '@pnpm/hooks.read-package-hook': 1.0.2_@yarnpkg+core@4.0.0-rc.38
-      '@pnpm/lifecycle': 13.1.12_apzzy4k2ekv2hywwne3y7chynq
-      '@pnpm/link-bins': 7.2.10_@pnpm+logger@4.0.0
-      '@pnpm/lockfile-file': 5.3.8_@pnpm+logger@4.0.0
-      '@pnpm/lockfile-to-pnp': 1.0.5_@pnpm+logger@4.0.0
-      '@pnpm/lockfile-utils': 4.2.6
-      '@pnpm/lockfile-walker': 5.0.15
-      '@pnpm/logger': 4.0.0
-      '@pnpm/manifest-utils': 3.1.6_@pnpm+logger@4.0.0
-      '@pnpm/matcher': 3.2.0
-      '@pnpm/modules-cleaner': 12.0.25_@pnpm+logger@4.0.0
-      '@pnpm/modules-yaml': 10.0.8
-      '@pnpm/normalize-registries': 3.0.8
-      '@pnpm/npm-package-arg': 1.0.0
-      '@pnpm/package-requester': 19.0.6_@pnpm+logger@4.0.0
-      '@pnpm/parse-wanted-dependency': 3.0.2
-      '@pnpm/prune-lockfile': 4.0.16
-      '@pnpm/read-modules-dir': 4.0.0
-      '@pnpm/read-package-json': 6.0.11
-      '@pnpm/read-project-manifest': 3.0.13
-      '@pnpm/remove-bins': 3.0.13_@pnpm+logger@4.0.0
-      '@pnpm/resolve-dependencies': 28.4.5_apzzy4k2ekv2hywwne3y7chynq
-      '@pnpm/resolver-base': 9.1.2
-      '@pnpm/store-controller-types': 14.1.3
-      '@pnpm/symlink-dependency': 5.0.10_@pnpm+logger@4.0.0
-      '@pnpm/types': 8.7.0
-      '@pnpm/which-version-is-pinned': 3.0.0
-      '@zkochan/rimraf': 2.1.2
-      dependency-path: 9.2.6
-      is-inner-link: 4.0.0
-      load-json-file: 6.2.0
-      normalize-path: 3.0.0
-      p-every: 2.0.0
-      p-filter: 2.1.0
-      p-limit: 3.1.0
-      path-exists: 4.0.0
-      ramda: /@pnpm/ramda/0.28.1
-      run-groups: 3.0.1
-      semver: 7.3.8
-      version-selector-type: 3.0.0
-    transitivePeerDependencies:
-      - '@yarnpkg/core'
-      - bluebird
-      - domexception
-      - supports-color
-      - typanion
-    dev: false
-
   /@pnpm/core/6.0.3_ah4vodsfb2phqgbxsizsob47ki:
     resolution: {integrity: sha512-fTmsXqX/zPNGn46OQqnmhI94jigXarCNo/E3LlB4Sr5WEQrZ/x6UIqXNY6NNBacXkzwBnFECe+quIi9lZOao/w==}
     engines: {node: '>=14.6'}
@@ -3511,7 +3386,6 @@ packages:
       - domexception
       - supports-color
       - typanion
-    dev: true
 
   /@pnpm/crypto.base32-hash/1.0.1:
     resolution: {integrity: sha512-pzAXNn6KxTA3kbcI3iEnYs4vtH51XEVqmK/1EiD18MaPKylhqy8UvMJK3zKG+jeP82cqQbozcTGm4yOQ8i3vNw==}
@@ -3519,39 +3393,6 @@ packages:
     dependencies:
       rfc4648: 1.5.2
 
-  /@pnpm/default-reporter/10.1.1_7we2eqc3d7f6uvptfoorlpdruy:
-    resolution: {integrity: sha512-HLLuntUHLYiONcuQmpOTpZgRnncb6aVQTXhCKQDqeXDQrG8Hl0uAoG7qQ7Qj9JNBO1Y3BlEwe4VIw8ELed9XJw==}
-    engines: {node: '>=14.6'}
-    peerDependencies:
-      '@pnpm/logger': ^4.0.0
-    dependencies:
-      '@pnpm/config': 15.10.12_7we2eqc3d7f6uvptfoorlpdruy
-      '@pnpm/core-loggers': 7.1.0_@pnpm+logger@4.0.0
-      '@pnpm/error': 3.1.0
-      '@pnpm/logger': 4.0.0
-      '@pnpm/render-peer-issues': 2.1.2
-      '@pnpm/types': 8.7.0
-      ansi-diff: 1.1.1
-      boxen: 5.1.2
-      chalk: 4.1.2
-      normalize-path: 3.0.0
-      pretty-bytes: 5.6.0
-      pretty-ms: 7.0.1
-      ramda: /@pnpm/ramda/0.28.1
-      right-pad: 1.0.1
-      rxjs: 7.5.7
-      semver: 7.3.8
-      stacktracey: 2.1.8
-      string-length: 4.0.2
-      strip-ansi: 6.0.1
-    transitivePeerDependencies:
-      - '@yarnpkg/core'
-      - bluebird
-      - domexception
-      - supports-color
-      - typanion
-    dev: false
-
   /@pnpm/default-reporter/10.1.1_ah4vodsfb2phqgbxsizsob47ki:
     resolution: {integrity: sha512-HLLuntUHLYiONcuQmpOTpZgRnncb6aVQTXhCKQDqeXDQrG8Hl0uAoG7qQ7Qj9JNBO1Y3BlEwe4VIw8ELed9XJw==}
     engines: {node: '>=14.6'}
@@ -3572,7 +3413,7 @@ packages:
       pretty-ms: 7.0.1
       ramda: /@pnpm/ramda/0.28.1
       right-pad: 1.0.1
-      rxjs: 7.8.0
+      rxjs: 7.5.7
       semver: 7.3.8
       stacktracey: 2.1.8
       string-length: 4.0.2
@@ -3583,7 +3424,6 @@ packages:
       - domexception
       - supports-color
       - typanion
-    dev: true
 
   /@pnpm/directory-fetcher/3.1.5:
     resolution: {integrity: sha512-t5IEdEuctEZLjocmQf+ggXqt5wz4+R6Spk17O5UeEbk1uVAH+IgAcN7QtI8LLzCuoPSpo6ZS8KI9dYchenQR5A==}
@@ -3641,24 +3481,6 @@ packages:
       dependency-path: 9.2.6
       ramda: /@pnpm/ramda/0.28.1
 
-  /@pnpm/find-workspace-packages/4.0.43_7we2eqc3d7f6uvptfoorlpdruy:
-    resolution: {integrity: sha512-2bkesfy8IfOnii6xhr0+HeDXjM95faZOX1nvbNJ1bvXYZCVc13QiDmukKP0ovzbhQ2mcVM5K16PsekeFHDrq7g==}
-    engines: {node: '>=14.6'}
-    dependencies:
-      '@pnpm/cli-utils': 0.7.43_7we2eqc3d7f6uvptfoorlpdruy
-      '@pnpm/constants': 6.1.0
-      '@pnpm/types': 8.7.0
-      find-packages: 9.0.13
-      read-yaml-file: 2.1.0
-    transitivePeerDependencies:
-      - '@pnpm/logger'
-      - '@yarnpkg/core'
-      - bluebird
-      - domexception
-      - supports-color
-      - typanion
-    dev: false
-
   /@pnpm/find-workspace-packages/4.0.43_ah4vodsfb2phqgbxsizsob47ki:
     resolution: {integrity: sha512-2bkesfy8IfOnii6xhr0+HeDXjM95faZOX1nvbNJ1bvXYZCVc13QiDmukKP0ovzbhQ2mcVM5K16PsekeFHDrq7g==}
     engines: {node: '>=14.6'}
@@ -3675,7 +3497,6 @@ packages:
       - domexception
       - supports-color
       - typanion
-    dev: true
 
   /@pnpm/get-context/7.0.3_@pnpm+logger@4.0.0:
     resolution: {integrity: sha512-SeB48qb6KadNnw9D4qtL6SMPCbr0RL2IkV1XiX482w9uS8bGxw8W+iyDa7uQZLEgPSySgDlzXPB1u8jj++a8Jw==}
@@ -3770,22 +3591,6 @@ packages:
       dependency-path: 9.2.6
       ramda: /@pnpm/ramda/0.28.1
 
-  /@pnpm/hooks.read-package-hook/1.0.2_@yarnpkg+core@4.0.0-rc.38:
-    resolution: {integrity: sha512-zet25UoINhER3hZKgE+6wPlpuXOprD+VlKemgpt7c8r5+l44S4qytfDpuPnkBT3/oxIUbkTmajFDPfL5F8hRIA==}
-    engines: {node: '>=14.6'}
-    dependencies:
-      '@pnpm/matcher': 3.2.0
-      '@pnpm/parse-overrides': 2.0.4
-      '@pnpm/parse-wanted-dependency': 3.0.2
-      '@pnpm/types': 8.7.0
-      '@yarnpkg/extensions': 2.0.0-rc.6_@yarnpkg+core@4.0.0-rc.38
-      normalize-path: 3.0.0
-      ramda: /@pnpm/ramda/0.28.1
-      semver: 7.3.8
-    transitivePeerDependencies:
-      - '@yarnpkg/core'
-    dev: false
-
   /@pnpm/hooks.read-package-hook/1.0.2_@yarnpkg+core@4.0.0-rc.39:
     resolution: {integrity: sha512-zet25UoINhER3hZKgE+6wPlpuXOprD+VlKemgpt7c8r5+l44S4qytfDpuPnkBT3/oxIUbkTmajFDPfL5F8hRIA==}
     engines: {node: '>=14.6'}
@@ -3800,7 +3605,6 @@ packages:
       semver: 7.3.8
     transitivePeerDependencies:
       - '@yarnpkg/core'
-    dev: true
 
   /@pnpm/lifecycle/13.1.12_apzzy4k2ekv2hywwne3y7chynq:
     resolution: {integrity: sha512-2IiJ4wl7Lfl0g+BT7WhtWBFychVNOSdboj5ZK9COj8Y9X2Q6GHWcmGs1+ueYV9bUmqESpNHzJBXAJGFhSjd7bg==}
@@ -4125,29 +3929,6 @@ packages:
     dependencies:
       '@pnpm/types': 8.7.0
 
-  /@pnpm/pnpmfile/3.0.3_7we2eqc3d7f6uvptfoorlpdruy:
-    resolution: {integrity: sha512-QCxTHgsFe3ff58l1ps1v5Bzg4tu4o3h1v8JfojL4I3NX86JSQZHyF434ikVWGAp7nHl3UAbV9dvtimw/iZyshg==}
-    engines: {node: '>=14.6'}
-    peerDependencies:
-      '@pnpm/logger': ^4.0.0
-    dependencies:
-      '@pnpm/core': 6.0.3_7we2eqc3d7f6uvptfoorlpdruy
-      '@pnpm/core-loggers': 7.1.0_@pnpm+logger@4.0.0
-      '@pnpm/error': 3.1.0
-      '@pnpm/lockfile-types': 4.3.3
-      '@pnpm/logger': 4.0.0
-      '@pnpm/store-controller-types': 14.1.3
-      '@pnpm/types': 8.7.0
-      chalk: 4.1.2
-      path-absolute: 1.0.1
-    transitivePeerDependencies:
-      - '@yarnpkg/core'
-      - bluebird
-      - domexception
-      - supports-color
-      - typanion
-    dev: false
-
   /@pnpm/pnpmfile/3.0.3_ah4vodsfb2phqgbxsizsob47ki:
     resolution: {integrity: sha512-QCxTHgsFe3ff58l1ps1v5Bzg4tu4o3h1v8JfojL4I3NX86JSQZHyF434ikVWGAp7nHl3UAbV9dvtimw/iZyshg==}
     engines: {node: '>=14.6'}
@@ -4169,7 +3950,6 @@ packages:
       - domexception
       - supports-color
       - typanion
-    dev: true
 
   /@pnpm/prune-lockfile/4.0.16:
     resolution: {integrity: sha512-IUyyArQS9LJhfJnxZu6+nin5RYeplrjyX96Y/SkpvrZEmRjHICk8baPTA2/or97HSFG/TXdFBkbuztxTEqvqQg==}
@@ -5487,7 +5267,6 @@ packages:
       '@vue-macros/common': 0.11.2
       ast-walker-scope: 0.2.3
       unplugin: 0.9.6
-    dev: true
 
   /@vue-macros/define-model/0.11.2_rollup@2.79.1+vite@2.9.15:
     resolution: {integrity: sha512-XYH1zFBWNBjJUTzVuwgGo0Yx4cCtUEDvLMcu6TKVeqq9wrcleof4pwikOc7XrLDNqN5AxPzGFQ7wPl0iUDfb7Q==}
@@ -5504,21 +5283,6 @@ packages:
       - webpack
     dev: true
 
-  /@vue-macros/define-model/0.11.2_temr4a75fo74qf6q7dadyncfje:
-    resolution: {integrity: sha512-XYH1zFBWNBjJUTzVuwgGo0Yx4cCtUEDvLMcu6TKVeqq9wrcleof4pwikOc7XrLDNqN5AxPzGFQ7wPl0iUDfb7Q==}
-    engines: {node: '>=14.19.0'}
-    dependencies:
-      '@rollup/pluginutils': 4.2.1
-      '@vue-macros/common': 0.11.2
-      ast-walker-scope: 0.2.3
-      unplugin: 0.9.5_temr4a75fo74qf6q7dadyncfje
-    transitivePeerDependencies:
-      - esbuild
-      - rollup
-      - vite
-      - webpack
-    dev: false
-
   /@vue-macros/define-render/0.11.2:
     resolution: {integrity: sha512-Q+19LmqNlYNvNLqm8MGKqQej5WJ21ubrrqbNwDsyJl7mEChiv0wfwc0TQTgfHflkxlLomG2+JR0PPwlRT+qoXQ==}
     engines: {node: '>=14.19.0'}
@@ -5858,39 +5622,6 @@ packages:
     transitivePeerDependencies:
       - typanion
 
-  /@yarnpkg/core/4.0.0-rc.38_typanion@3.12.1:
-    resolution: {integrity: sha512-SqgnEzEgL1XOjeWbzTMkbPiliFyvTfm9TuqyikNY4HnmZT0qipR3Ky9HI83+EUEBwPgDdCK9JXoHvks0d4VWPw==}
-    engines: {node: '>=14.15.0'}
-    dependencies:
-      '@arcanis/slice-ansi': 1.1.1
-      '@types/semver': 7.3.13
-      '@types/treeify': 1.0.0
-      '@yarnpkg/fslib': 3.0.0-rc.38
-      '@yarnpkg/libzip': 3.0.0-rc.38_@yarnpkg+fslib@3.0.0-rc.38
-      '@yarnpkg/parsers': 3.0.0-rc.38
-      '@yarnpkg/shell': 4.0.0-rc.38_typanion@3.12.1
-      camelcase: 5.3.1
-      chalk: 3.0.0
-      ci-info: 3.7.1
-      clipanion: 3.2.0-rc.16_typanion@3.12.1
-      cross-spawn: 7.0.3
-      diff: 5.1.0
-      globby: 11.1.0
-      got: 11.8.6
-      lodash: 4.17.21
-      micromatch: 4.0.5
-      p-limit: 2.3.0
-      semver: 7.3.8
-      strip-ansi: 6.0.1
-      tar: 6.1.13
-      tinylogic: 2.0.0
-      treeify: 1.1.0
-      tslib: 2.5.0
-      tunnel: 0.0.6
-    transitivePeerDependencies:
-      - typanion
-    dev: false
-
   /@yarnpkg/core/4.0.0-rc.39_typanion@3.12.1:
     resolution: {integrity: sha512-OgPR4btSH1nsCA+cXUVIwCGMbqjcSDEb25iOlcMnqTFn0is1ydX46gp3MktLGkyN3udjEsBK7MLi/oYd/SFLPw==}
     engines: {node: '>=14.15.0'}
@@ -5923,15 +5654,6 @@ packages:
     transitivePeerDependencies:
       - typanion
 
-  /@yarnpkg/extensions/2.0.0-rc.6_@yarnpkg+core@4.0.0-rc.38:
-    resolution: {integrity: sha512-hqp7U6bJTTe3mLIt5c7+U1TD2y9kRUzCjvzLpkAwNR5uoo6Tww5/XxdmhXeDiPbb3J3kLHTFNxEg3FhcGQ03UQ==}
-    engines: {node: '>=14.15.0'}
-    peerDependencies:
-      '@yarnpkg/core': ^4.0.0-rc.24
-    dependencies:
-      '@yarnpkg/core': 4.0.0-rc.38_typanion@3.12.1
-    dev: false
-
   /@yarnpkg/extensions/2.0.0-rc.6_@yarnpkg+core@4.0.0-rc.39:
     resolution: {integrity: sha512-hqp7U6bJTTe3mLIt5c7+U1TD2y9kRUzCjvzLpkAwNR5uoo6Tww5/XxdmhXeDiPbb3J3kLHTFNxEg3FhcGQ03UQ==}
     engines: {node: '>=14.15.0'}
@@ -5939,7 +5661,6 @@ packages:
       '@yarnpkg/core': ^4.0.0-rc.24
     dependencies:
       '@yarnpkg/core': 4.0.0-rc.39_typanion@3.12.1
-    dev: true
 
   /@yarnpkg/fslib/2.10.1:
     resolution: {integrity: sha512-pVMLtOYu87N5y5G2lyPNYTY2JbTco99v7nGFI34Blx01Ct9LmoKVOc91vnLOYIMMljKr1c8xs1O2UamRdMG5Pg==}
@@ -5948,13 +5669,6 @@ packages:
       '@yarnpkg/libzip': 2.2.4
       tslib: 1.14.1
 
-  /@yarnpkg/fslib/3.0.0-rc.38:
-    resolution: {integrity: sha512-qqBBJnIcN0mzoEfe0aDKzevuwPA6+k++xlz0YcpEMrqYdK8f3xWNfa9HH/cB1B+mP9zn9HOiY2FJnO8Nv6QjPA==}
-    engines: {node: '>=14.15.0'}
-    dependencies:
-      tslib: 2.5.0
-    dev: false
-
   /@yarnpkg/fslib/3.0.0-rc.39:
     resolution: {integrity: sha512-y5rTaRXKsP8YNz1ZxvElnBj6mK2cMIsrNgmjJBN17V6nYZPfiGaVDI+WhWw5MG8imw6jsHPgXb7m710xzxHckA==}
     engines: {node: '>=14.15.0'}
@@ -5968,17 +5682,6 @@ packages:
       '@types/emscripten': 1.39.6
       tslib: 1.14.1
 
-  /@yarnpkg/libzip/3.0.0-rc.38_@yarnpkg+fslib@3.0.0-rc.38:
-    resolution: {integrity: sha512-UlpvSn5D7t9q+KrQ7d8L4CbvS8aQttB7bHk5dXXM6ok+GM6QvoSr+oOZ7UeMdQ+v5IVtxchZI6NYwYn0o5Q00g==}
-    engines: {node: '>=14.15.0'}
-    peerDependencies:
-      '@yarnpkg/fslib': ^3.0.0-rc.38
-    dependencies:
-      '@types/emscripten': 1.39.6
-      '@yarnpkg/fslib': 3.0.0-rc.38
-      tslib: 2.5.0
-    dev: false
-
   /@yarnpkg/libzip/3.0.0-rc.39_@yarnpkg+fslib@3.0.0-rc.39:
     resolution: {integrity: sha512-A4dbLpHTVhKnuowjhJaVezvN6eADh99TelpxaGEtFD3ygmOgbGh/pmN+L01PcOvxVkoSd7BBH2HyzW33ktj7Ug==}
     engines: {node: '>=14.15.0'}
@@ -6008,14 +5711,6 @@ packages:
       js-yaml: 3.14.1
       tslib: 1.14.1
 
-  /@yarnpkg/parsers/3.0.0-rc.38:
-    resolution: {integrity: sha512-YqkUSOZSBjbhzvU/ZbK6yoE70L/KVXAQTyUMaKAFoHEpy7csAljivTBu0C3SZKbDxMRjFWAvnLS8US7W3hFLow==}
-    engines: {node: '>=14.15.0'}
-    dependencies:
-      js-yaml: 3.14.1
-      tslib: 2.5.0
-    dev: false
-
   /@yarnpkg/parsers/3.0.0-rc.39:
     resolution: {integrity: sha512-BsD4zq3EVmaHqlynXTceNuEFAtrfToV4fI9GA54moKlWZL4Eb2eXrhgf1jV2nMYx18SZxYO4Jc5Kf1sCDNRjOg==}
     engines: {node: '>=14.15.0'}
@@ -6048,23 +5743,6 @@ packages:
     transitivePeerDependencies:
       - typanion
 
-  /@yarnpkg/shell/4.0.0-rc.38_typanion@3.12.1:
-    resolution: {integrity: sha512-UWxf6ChqosJky5UseWa9NZtIbyrZvBwBHES/Jw99tqljOiweIQe5KYeXzPpKJdJ83PH9rR5R8yI0I4eOGqCafg==}
-    engines: {node: '>=14.15.0'}
-    hasBin: true
-    dependencies:
-      '@yarnpkg/fslib': 3.0.0-rc.38
-      '@yarnpkg/parsers': 3.0.0-rc.38
-      chalk: 3.0.0
-      clipanion: 3.2.0-rc.16_typanion@3.12.1
-      cross-spawn: 7.0.3
-      fast-glob: 3.2.12
-      micromatch: 4.0.5
-      tslib: 2.5.0
-    transitivePeerDependencies:
-      - typanion
-    dev: false
-
   /@yarnpkg/shell/4.0.0-rc.39_typanion@3.12.1:
     resolution: {integrity: sha512-ZKBgTE6pxsAbZo1YcKv3gJltulsjlYo9U04qkOHTIOY7lp4tDEGNtrCve4WYlhspLj0pP4tTHsrFbZ+W/juYDg==}
     engines: {node: '>=14.15.0'}
@@ -8117,8 +7795,8 @@ packages:
     resolution: {integrity: sha512-PuHZB3jEN7D8WPPjLmBQAsqQz8tWHlkkB4n0E2OYw8RwVdmBYV0Wn+rUFH8JqYyIRb4HQhhedgxlZL163wqLrQ==}
     dev: true
 
-  /element-plus/2.2.29_vue@3.2.39:
-    resolution: {integrity: sha512-g4dcrURrKkR5uUX8n5RVnnqGnimoki9HfqS4yHHG6XwCHBkZGozdq4x+478BzeWUe31h++BO+7dakSx4VnM8RQ==}
+  /element-plus/2.2.30_vue@3.2.39:
+    resolution: {integrity: sha512-HYSnmf2VMGa0gmw03evxevodPy3WimbAd4sfenOAhNs7Wl8IdT+YJjQyGAQjgEjRvhmujN4O/CZqhuEffRyOZg==}
     peerDependencies:
       vue: ^3.2.0
     dependencies:
@@ -10872,15 +10550,6 @@ packages:
       has: 1.0.3
       side-channel: 1.0.4
 
-  /internal-slot/1.0.4:
-    resolution: {integrity: sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      get-intrinsic: 1.2.0
-      has: 1.0.3
-      side-channel: 1.0.4
-    dev: true
-
   /internal-slot/1.0.5:
     resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==}
     engines: {node: '>= 0.4'}
@@ -14556,7 +14225,6 @@ packages:
     resolution: {integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==}
     dependencies:
       tslib: 2.4.0
-    dev: false
 
   /rxjs/7.8.0:
     resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==}
@@ -15098,7 +14766,7 @@ packages:
       es-abstract: 1.21.1
       get-intrinsic: 1.2.0
       has-symbols: 1.0.3
-      internal-slot: 1.0.4
+      internal-slot: 1.0.5
       regexp.prototype.flags: 1.4.3
       side-channel: 1.0.4
     dev: true
@@ -15607,7 +15275,6 @@ packages:
 
   /tslib/2.4.0:
     resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
-    dev: false
 
   /tslib/2.5.0:
     resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
@@ -15997,8 +15664,8 @@ packages:
       vite: 2.9.15
     dev: false
 
-  /unplugin-combine/0.2.8_vite@3.2.5:
-    resolution: {integrity: sha512-Z38AC/TEjXbVyZ5HjVqo+lADj0/dcfwWC0Z4y0LNhybJzJQwmcMxm+ZsqHY3faauj4YigmlRMdptR5JEW9RuLg==}
+  /unplugin-combine/0.2.2_vite@3.2.5:
+    resolution: {integrity: sha512-KkO9B40wjW3ige0a+lYkr1/Q97qZSbtXBTJY5MQdAJCVvSPXtG1mxaSYMoQABN1yF9RHcE9WeOxdl+zdOljeOQ==}
     engines: {node: '>=14.19.0'}
     peerDependencies:
       esbuild: '>=0.13'
@@ -16015,8 +15682,7 @@ packages:
       webpack:
         optional: true
     dependencies:
-      '@antfu/utils': 0.6.3
-      unplugin: 0.10.2
+      unplugin: 0.9.6
       vite: 3.2.5_gyrp4zacqcjjrmgvdzgac5epyy
     dev: true
 
@@ -16176,7 +15842,7 @@ packages:
     dependencies:
       '@rollup/pluginutils': 4.2.1
       '@vue-macros/common': 0.11.2
-      '@vue-macros/define-model': 0.11.2_temr4a75fo74qf6q7dadyncfje
+      '@vue-macros/define-model': 0.11.2
       '@vue-macros/define-render': 0.11.2
       '@vue-macros/hoist-static': 0.11.2
       '@vue-macros/setup-component': 0.11.2
@@ -16205,8 +15871,8 @@ packages:
       '@vue-macros/hoist-static': 0.11.2
       '@vue-macros/setup-component': 0.11.2
       '@vue-macros/setup-sfc': 0.11.2
-      local-pkg: 0.4.3
-      unplugin-combine: 0.2.8_vite@3.2.5
+      local-pkg: 0.4.2
+      unplugin-combine: 0.2.2_vite@3.2.5
       unplugin-vue-define-options: 0.11.2
       vue: 3.2.47
     transitivePeerDependencies:
@@ -16216,15 +15882,6 @@ packages:
       - webpack
     dev: true
 
-  /unplugin/0.10.2:
-    resolution: {integrity: sha512-6rk7GUa4ICYjae5PrAllvcDeuT8pA9+j5J5EkxbMFaV+SalHhxZ7X2dohMzu6C3XzsMT+6jwR/+pwPNR3uK9MA==}
-    dependencies:
-      acorn: 8.8.2
-      chokidar: 3.5.3
-      webpack-sources: 3.2.3
-      webpack-virtual-modules: 0.4.6
-    dev: true
-
   /unplugin/0.7.2_rollup@2.79.1+vite@2.9.15:
     resolution: {integrity: sha512-m7thX4jP8l5sETpLdUASoDOGOcHaOVtgNyrYlToyQUvILUtEzEnngRBrHnAX3IKqooJVmXpoa/CwQ/QqzvGaHQ==}
     peerDependencies:
@@ -16291,11 +15948,11 @@ packages:
       webpack:
         optional: true
     dependencies:
-      acorn: 8.8.2
+      acorn: 8.8.0
       chokidar: 3.5.3
       vite: 3.2.5_gyrp4zacqcjjrmgvdzgac5epyy
       webpack-sources: 3.2.3
-      webpack-virtual-modules: 0.4.6
+      webpack-virtual-modules: 0.4.5
     dev: true
 
   /unplugin/0.9.5_rollup@2.79.1+vite@2.9.15:
@@ -16323,32 +15980,6 @@ packages:
       webpack-virtual-modules: 0.4.6
     dev: true
 
-  /unplugin/0.9.5_temr4a75fo74qf6q7dadyncfje:
-    resolution: {integrity: sha512-luraheyfxwtvkvHpsOvMNv7IjLdORTWKZp0gWYNHGLi2ImON3iIZOj464qEyyEwLA/EMt12fC415HW9zRpOfTg==}
-    peerDependencies:
-      esbuild: '>=0.13'
-      rollup: ^2.50.0
-      vite: ^2.3.0 || ^3.0.0-0
-      webpack: 4 || 5
-    peerDependenciesMeta:
-      esbuild:
-        optional: true
-      rollup:
-        optional: true
-      vite:
-        optional: true
-      webpack:
-        optional: true
-    dependencies:
-      acorn: 8.8.2
-      chokidar: 3.5.3
-      esbuild: 0.14.54
-      rollup: 2.78.1
-      vite: 2.9.15
-      webpack-sources: 3.2.3
-      webpack-virtual-modules: 0.4.6
-    dev: false
-
   /unplugin/0.9.6:
     resolution: {integrity: sha512-YYLtfoNiie/lxswy1GOsKXgnLJTE27la/PeCGznSItk+8METYZErO+zzV9KQ/hXhPwzIJsfJ4s0m1Rl7ZCWZ4Q==}
     dependencies: