Files
element-plus/packages/components/message/src/message.vue

183 lines
4.5 KiB
Vue

<template>
<transition
:name="ns.b('fade')"
@before-enter="isStartTransition = true"
@before-leave="onClose"
@after-leave="$emit('destroy')"
>
<div
v-show="visible"
:id="id"
ref="messageRef"
:class="[
ns.b(),
{ [ns.m(type)]: type },
ns.is('closable', showClose),
ns.is('plain', plain),
ns.is('bottom', verticalProperty === 'bottom'),
horizontalClass,
customClass,
]"
:style="customStyle"
role="alert"
@mouseenter="clearTimer"
@mouseleave="startTimer"
>
<el-badge
v-if="repeatNum > 1"
:value="repeatNum"
:type="badgeType"
:class="ns.e('badge')"
/>
<el-icon v-if="iconComponent" :class="[ns.e('icon'), typeClass]">
<component :is="iconComponent" />
</el-icon>
<slot>
<p v-if="!dangerouslyUseHTMLString" :class="ns.e('content')">
{{ message }}
</p>
<!-- Caution here, message could've been compromised, never use user's input as message -->
<p v-else :class="ns.e('content')" v-html="message" />
</slot>
<el-icon v-if="showClose" :class="ns.e('closeBtn')" @click.stop="close">
<Close />
</el-icon>
</div>
</transition>
</template>
<script lang="ts" setup>
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { useEventListener, useResizeObserver, useTimeoutFn } from '@vueuse/core'
import {
TypeComponents,
TypeComponentsMap,
getEventCode,
} from '@element-plus/utils'
import { EVENT_CODE } from '@element-plus/constants'
import ElBadge from '@element-plus/components/badge'
import { useGlobalComponentSettings } from '@element-plus/components/config-provider'
import { ElIcon } from '@element-plus/components/icon'
import {
MESSAGE_DEFAULT_PLACEMENT,
messageEmits,
messageProps,
} from './message'
import { getLastOffset, getOffsetOrSpace } from './instance'
import type { BadgeProps } from '@element-plus/components/badge'
import type { CSSProperties } from 'vue'
const { Close } = TypeComponents
defineOptions({
name: 'ElMessage',
})
const props = defineProps(messageProps)
const emit = defineEmits(messageEmits)
const isStartTransition = ref(false)
const { ns, zIndex } = useGlobalComponentSettings('message')
const { currentZIndex, nextZIndex } = zIndex
const messageRef = ref<HTMLDivElement>()
const visible = ref(false)
const height = ref(0)
let stopTimer: (() => void) | undefined = undefined
const badgeType = computed<BadgeProps['type']>(() =>
props.type ? (props.type === 'error' ? 'danger' : props.type) : 'info'
)
const typeClass = computed(() => {
const type = props.type
return { [ns.bm('icon', type)]: type && TypeComponentsMap[type] }
})
const iconComponent = computed(
() => props.icon || TypeComponentsMap[props.type] || ''
)
const placement = computed(() => props.placement || MESSAGE_DEFAULT_PLACEMENT)
const lastOffset = computed(() => getLastOffset(props.id, placement.value))
const offset = computed(() => {
return (
getOffsetOrSpace(props.id, props.offset, placement.value) + lastOffset.value
)
})
const bottom = computed(() => height.value + offset.value)
const horizontalClass = computed(() => {
if (placement.value.includes('left')) return ns.is('left')
if (placement.value.includes('right')) return ns.is('right')
return ns.is('center')
})
const verticalProperty = computed(() =>
placement.value.startsWith('top') ? 'top' : 'bottom'
)
const customStyle = computed<CSSProperties>(() => ({
[verticalProperty.value]: `${offset.value}px`,
zIndex: currentZIndex.value,
}))
function startTimer() {
if (props.duration === 0) return
;({ stop: stopTimer } = useTimeoutFn(() => {
close()
}, props.duration))
}
function clearTimer() {
stopTimer?.()
}
function close() {
visible.value = false
// if the message has never started a transition, we can destroy it immediately
nextTick(() => {
if (!isStartTransition.value) {
props.onClose?.()
emit('destroy')
}
})
}
function keydown(event: KeyboardEvent) {
const code = getEventCode(event)
if (code === EVENT_CODE.esc) {
// press esc to close the message
close()
}
}
onMounted(() => {
startTimer()
nextZIndex()
visible.value = true
})
watch(
() => props.repeatNum,
() => {
clearTimer()
startTimer()
}
)
useEventListener(document, 'keydown', keydown)
useResizeObserver(messageRef, () => {
height.value = messageRef.value!.getBoundingClientRect().height
})
defineExpose({
visible,
bottom,
close,
})
</script>