|
@@ -1,21 +1,20 @@
|
|
|
<script setup lang="ts">
|
|
|
import {
|
|
|
+ ElOverlay,
|
|
|
+ ElFocusTrap,
|
|
|
useZIndex,
|
|
|
ElInput,
|
|
|
ElSelect,
|
|
|
ElOption,
|
|
|
ElIcon,
|
|
|
useDraggable,
|
|
|
- ElButton, useEscapeKeydown,
|
|
|
+ ElButton,
|
|
|
} from "element-plus";
|
|
|
import {nextTick, onMounted, ref, computed, watch} from "vue";
|
|
|
import sleep from "@/utils/sleep";
|
|
|
import {Close} from '@element-plus/icons-vue';
|
|
|
import {useCompRef} from "@/utils/useCompRef";
|
|
|
import {uuid} from '@/utils/getUuid';
|
|
|
-import * as focusTrap from 'focus-trap';
|
|
|
-import {FocusTrap} from "focus-trap";
|
|
|
-import usePromise from "@/utils/cy-use/usePromise";
|
|
|
|
|
|
const props = defineProps({
|
|
|
title: {
|
|
@@ -112,7 +111,8 @@ const props = defineProps({
|
|
|
const emits = defineEmits(['cancel', 'confirm', 'vanish'])
|
|
|
const visible = ref(false)
|
|
|
const zIndex = ref(0)
|
|
|
-const rootRef = ref<HTMLDivElement | undefined>()
|
|
|
+const rootRef = ref<HTMLDivElement>()
|
|
|
+const focusStartRef = ref<HTMLElement>()
|
|
|
const inputRef = ref()
|
|
|
const headerRef = ref<HTMLElement>()
|
|
|
const draggable = computed(() => props.draggable)
|
|
@@ -120,9 +120,6 @@ const confirmRef = useCompRef(ElButton)
|
|
|
const dithering = ref(false)
|
|
|
const inputValue = ref(props.inputDefaultValue)
|
|
|
const errorMsg = ref('')
|
|
|
-const overlayRef = ref()
|
|
|
-let trap: FocusTrap
|
|
|
-
|
|
|
const inputId = 'cy-message' + uuid(5, 62)
|
|
|
|
|
|
const reboundClick = async () => {
|
|
@@ -178,7 +175,6 @@ const handelClose = (val: 'cancel' | 'confirm' | 'close') => {
|
|
|
reboundClick();
|
|
|
return;
|
|
|
}
|
|
|
- trap!.deactivate()
|
|
|
visible.value = false
|
|
|
if (val === 'confirm') {
|
|
|
emits('confirm', {action: val, value: inputValue.value})
|
|
@@ -195,163 +191,139 @@ const onCloseRequested = () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-useDraggable(rootRef, headerRef, draggable)
|
|
|
-
|
|
|
-useEscapeKeydown(() => {
|
|
|
- if (trap!.active) {
|
|
|
- onCloseRequested()
|
|
|
+watch(() => visible.value, (val) => {
|
|
|
+ if (val) {
|
|
|
+ nextTick(() => {
|
|
|
+ if (props.showInput) {
|
|
|
+ focusStartRef.value = inputRef.value?.$el ?? rootRef.value
|
|
|
+ sleep(200).then(() => {
|
|
|
+ inputRef.value?.focus()
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ focusStartRef.value = confirmRef.value?.$el ?? rootRef.value
|
|
|
+ }
|
|
|
+ })
|
|
|
}
|
|
|
})
|
|
|
|
|
|
-let activeElement = document.activeElement
|
|
|
-
|
|
|
-const [animationPromise, animationEnter] = usePromise()
|
|
|
-
|
|
|
-async function handleAfterEnter() {
|
|
|
- await nextTick()
|
|
|
- animationEnter(true)
|
|
|
-}
|
|
|
+useDraggable(rootRef, headerRef, draggable)
|
|
|
|
|
|
onMounted(async () => {
|
|
|
- activeElement = document.activeElement
|
|
|
props.setClose(() => {
|
|
|
handelClose('close')
|
|
|
})
|
|
|
zIndex.value = useZIndex().nextZIndex()
|
|
|
await nextTick()
|
|
|
- // @ts-ignore
|
|
|
- trap = focusTrap.createFocusTrap(rootRef.value, {
|
|
|
- escapeDeactivates: false,
|
|
|
- allowOutsideClick: true,
|
|
|
- clickOutsideDeactivates: true,
|
|
|
- checkCanFocusTrap() {
|
|
|
- return animationPromise
|
|
|
- },
|
|
|
- setReturnFocus() {
|
|
|
- return activeElement
|
|
|
- },
|
|
|
- initialFocus: () => {
|
|
|
- if (props.showInput) {
|
|
|
- inputRef.value.focus()
|
|
|
- } else {
|
|
|
- confirmRef.value.ref.focus()
|
|
|
- }
|
|
|
- return false
|
|
|
- }
|
|
|
- })
|
|
|
await sleep(100);
|
|
|
visible.value = true
|
|
|
- trap.activate()
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
- <transition
|
|
|
- name="overlay_background"
|
|
|
- >
|
|
|
- <div v-show="visible"
|
|
|
- ref="overlayRef"
|
|
|
- class="cy-message_overlay"
|
|
|
- :style="{zIndex}">
|
|
|
- <transition name="cy-enlarge"
|
|
|
- @afterEnter="handleAfterEnter()"
|
|
|
- @after-leave="emits('vanish')">
|
|
|
+ <transition name="overlay_background">
|
|
|
+ <ElOverlay v-show="visible"
|
|
|
+ overlay-class="cy-message_overlay"
|
|
|
+ :z-index="zIndex">
|
|
|
+ <transition name="cy-enlarge" @after-leave="emits('vanish')">
|
|
|
<div role="dialog"
|
|
|
v-show="visible"
|
|
|
aria-modal="true"
|
|
|
class="cy-overlay-message-box"
|
|
|
@click="overlayClick">
|
|
|
- <div :class="{'cy-message-box_dithering' : dithering}">
|
|
|
- <div ref="rootRef"
|
|
|
- tabindex="-1"
|
|
|
- class="cy-message-box"
|
|
|
- @click.stop>
|
|
|
- <header ref="headerRef">
|
|
|
- <div class="cy-message-box_title">
|
|
|
- <el-icon v-if="props.icon"
|
|
|
- :size="24"
|
|
|
- :style="{color: props.iconColor}">
|
|
|
- <component :is="props.icon"/>
|
|
|
- </el-icon>
|
|
|
- {{ props.title }}
|
|
|
- </div>
|
|
|
+ <ElFocusTrap loop
|
|
|
+ :trapped="visible"
|
|
|
+ :focus-trap-el="rootRef"
|
|
|
+ :focus-start-el="focusStartRef"
|
|
|
+ @release-requested="onCloseRequested">
|
|
|
+ <div :class="dithering ? 'cy-message-box_dithering' : ''">
|
|
|
+ <div ref="rootRef" tabindex="-1" class="cy-message-box" @click.stop>
|
|
|
+ <header ref="headerRef">
|
|
|
+ <div class="cy-message-box_title">
|
|
|
+ <el-icon v-if="props.icon"
|
|
|
+ :size="24"
|
|
|
+ :style="{color: props.iconColor}">
|
|
|
+ <component :is="props.icon"/>
|
|
|
+ </el-icon>
|
|
|
+ {{ props.title }}
|
|
|
+ </div>
|
|
|
|
|
|
- <button @click.stop="handelClose('close')"
|
|
|
- v-if="props.showIcon"
|
|
|
- @keydown.prevent.enter="handelClose('close')">
|
|
|
- <el-icon>
|
|
|
- <Close/>
|
|
|
- </el-icon>
|
|
|
- </button>
|
|
|
-
|
|
|
- </header>
|
|
|
- <div class="cy-message-box_body">
|
|
|
- <div class="cy-message-box_message" v-if="props.message">
|
|
|
- <component :is="props.showInput ? 'label' : 'p'"
|
|
|
- v-if="props.dangerouslyUseHTMLString"
|
|
|
- v-html="props.message"
|
|
|
- :for="inputId"></component>
|
|
|
- <component :is="props.showInput ? 'label' : 'p'"
|
|
|
- v-else
|
|
|
- :for="inputId">
|
|
|
- {{ props.message }}
|
|
|
- </component>
|
|
|
- </div>
|
|
|
- <div v-if="props.showInput" style="padding-top: 15px">
|
|
|
- <el-select v-model="inputValue"
|
|
|
- v-if="props.selectOption"
|
|
|
- size="small"
|
|
|
- filterable
|
|
|
- style="width: 100%"
|
|
|
- allow-create
|
|
|
- :reserve-keyword="true"
|
|
|
- default-first-option
|
|
|
- :id="inputId"
|
|
|
- ref="inputRef">
|
|
|
- <el-option v-for="item in props.selectOption"
|
|
|
- :label="item.name"
|
|
|
- :value="item.code"
|
|
|
- :key="item.code"/>
|
|
|
- </el-select>
|
|
|
- <el-input v-model="inputValue"
|
|
|
- size="small"
|
|
|
- v-else
|
|
|
- @keydown.prevent.enter="handelClose('confirm')"
|
|
|
- :type="props.inputRows ? 'textarea' : 'text'"
|
|
|
- :rows="props.inputRows"
|
|
|
- :id="inputId"
|
|
|
- ref="inputRef"
|
|
|
- :maxlength="props.inputMaxLength"
|
|
|
- show-word-limit/>
|
|
|
- <div class="cy-message-box_error-msg"
|
|
|
- :style="{visibility: errorMsg ? 'visible' : 'hidden'}">
|
|
|
- {{ errorMsg }}
|
|
|
+ <button @click.stop="handelClose('close')"
|
|
|
+ v-if="props.showIcon"
|
|
|
+ @keydown.prevent.enter="handelClose('close')">
|
|
|
+ <el-icon>
|
|
|
+ <Close/>
|
|
|
+ </el-icon>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ </header>
|
|
|
+ <div class="cy-message-box_body">
|
|
|
+ <div class="cy-message-box_message" v-if="props.message">
|
|
|
+ <component :is="props.showInput ? 'label' : 'p'"
|
|
|
+ v-if="props.dangerouslyUseHTMLString"
|
|
|
+ v-html="props.message"
|
|
|
+ :for="inputId"></component>
|
|
|
+ <component :is="props.showInput ? 'label' : 'p'"
|
|
|
+ v-else
|
|
|
+ :for="inputId">
|
|
|
+ {{ props.message }}
|
|
|
+ </component>
|
|
|
+ </div>
|
|
|
+ <div v-if="props.showInput" style="padding-top: 15px">
|
|
|
+ <el-select v-model="inputValue"
|
|
|
+ v-if="props.selectOption"
|
|
|
+ size="small"
|
|
|
+ filterable
|
|
|
+ style="width: 100%"
|
|
|
+ allow-create
|
|
|
+ :reserve-keyword="true"
|
|
|
+ default-first-option
|
|
|
+ :id="inputId"
|
|
|
+ ref="inputRef">
|
|
|
+ <el-option v-for="item in props.selectOption"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item.code"
|
|
|
+ :key="item.code"/>
|
|
|
+ </el-select>
|
|
|
+ <el-input v-model="inputValue"
|
|
|
+ size="small"
|
|
|
+ v-else
|
|
|
+ @keydown.prevent.enter="handelClose('confirm')"
|
|
|
+ :type="props.inputRows ? 'textarea' : 'text'"
|
|
|
+ :rows="props.inputRows"
|
|
|
+ :id="inputId"
|
|
|
+ ref="inputRef"
|
|
|
+ :maxlength="props.inputMaxLength"
|
|
|
+ show-word-limit/>
|
|
|
+ <div class="cy-message-box_error-msg"
|
|
|
+ :style="{visibility: errorMsg ? 'visible' : 'hidden'}">
|
|
|
+ {{ errorMsg }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <footer>
|
|
|
+ <el-button text
|
|
|
+ v-if="props.showCancelButton"
|
|
|
+ @click.stop="handelClose('cancel')"
|
|
|
+ type="danger"
|
|
|
+ @keydown.prevent.enter="handelClose('cancel')"
|
|
|
+ class="cancel">
|
|
|
+ {{ props.cancelButtonText }}
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary"
|
|
|
+ class="confirm"
|
|
|
+ @keydown.prevent.enter="handelClose('confirm')"
|
|
|
+ ref="confirmRef"
|
|
|
+ @click.stop="handelClose('confirm')">
|
|
|
+ {{ props.confirmButtonText }}
|
|
|
+ </el-button>
|
|
|
+ </footer>
|
|
|
</div>
|
|
|
- <footer>
|
|
|
- <el-button text
|
|
|
- v-if="props.showCancelButton"
|
|
|
- @click.stop="handelClose('cancel')"
|
|
|
- type="danger"
|
|
|
- @keydown.prevent.enter="handelClose('cancel')"
|
|
|
- class="cancel">
|
|
|
- {{ props.cancelButtonText }}
|
|
|
- </el-button>
|
|
|
- <el-button type="primary"
|
|
|
- class="confirm"
|
|
|
- @keydown.prevent.enter="handelClose('confirm')"
|
|
|
- ref="confirmRef"
|
|
|
- @click.stop="handelClose('confirm')">
|
|
|
- {{ props.confirmButtonText }}
|
|
|
- </el-button>
|
|
|
- </footer>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </ElFocusTrap>
|
|
|
</div>
|
|
|
</transition>
|
|
|
- </div>
|
|
|
+ </ElOverlay>
|
|
|
</transition>
|
|
|
</template>
|
|
|
|
|
@@ -360,18 +332,6 @@ onMounted(async () => {
|
|
|
|
|
|
$cy-message-box-radius: 13px;
|
|
|
|
|
|
-.cy-message_overlay {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- z-index: 2000;
|
|
|
- height: 100%;
|
|
|
- background-color: var(--el-overlay-color-lighter);
|
|
|
- overflow: auto;
|
|
|
-}
|
|
|
-
|
|
|
.cy-overlay-message-box {
|
|
|
position: fixed;
|
|
|
top: 0;
|