mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
fix(components): [message]: offset (#8379)
* fix(components): [message]: offset closes #7217, #8368
This commit is contained in:
@@ -3,7 +3,7 @@ import { describe, expect, it, test, vi } from 'vitest'
|
||||
import { getStyle } from '@element-plus/utils'
|
||||
import { rAF } from '@element-plus/test-utils/tick'
|
||||
import { ElMessage } from '..'
|
||||
import Message from '../src/message-method'
|
||||
import Message from '../src/method'
|
||||
|
||||
const selector = '.el-message'
|
||||
// TODO: testing the original transition with `nextTick`'
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('Message.vue', () => {
|
||||
expect(wrapper.text()).toEqual(AXIOM)
|
||||
expect(vm.visible).toBe(true)
|
||||
expect(vm.iconComponent).toBe(TypeComponentsMap['info'])
|
||||
expect(vm.customStyle).toEqual({ top: '20px', zIndex: 0 })
|
||||
expect(vm.customStyle).toEqual({ top: '16px', zIndex: 0 })
|
||||
})
|
||||
|
||||
test('should be able to render VNode', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { withInstallFunction } from '@element-plus/utils'
|
||||
|
||||
import Message from './src/message-method'
|
||||
import Message from './src/method'
|
||||
|
||||
export const ElMessage = withInstallFunction(Message, '$message')
|
||||
export default ElMessage
|
||||
|
||||
30
packages/components/message/src/instance.ts
Normal file
30
packages/components/message/src/instance.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { shallowReactive } from 'vue'
|
||||
import type { VNode } from 'vue'
|
||||
import type { Mutable } from '@element-plus/utils'
|
||||
import type { MessageHandler, MessageInstance, MessageProps } from './message'
|
||||
|
||||
export type MessageContext = {
|
||||
id: string
|
||||
vnode: VNode
|
||||
handler: MessageHandler
|
||||
vm: MessageInstance
|
||||
props: Mutable<MessageProps>
|
||||
}
|
||||
|
||||
export const instances: MessageContext[] = shallowReactive([])
|
||||
|
||||
export const getInstance = (id: string) => {
|
||||
const idx = instances.findIndex((instance) => instance.id === id)
|
||||
const current = instances[idx]
|
||||
let prev: MessageContext | undefined
|
||||
if (idx > 0) {
|
||||
prev = instances[idx - 1]
|
||||
}
|
||||
return { current, prev }
|
||||
}
|
||||
|
||||
export const getLastOffset = (id: string): number => {
|
||||
const { prev } = getInstance(id)
|
||||
if (!prev) return 0
|
||||
return prev.vm.bottom
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export const messageDefaults = mutable({
|
||||
onClose: undefined,
|
||||
showClose: false,
|
||||
type: 'info',
|
||||
offset: 20,
|
||||
offset: 16,
|
||||
zIndex: 0,
|
||||
grouping: false,
|
||||
repeatNum: 1,
|
||||
@@ -140,11 +140,3 @@ export interface Message extends MessageFn {
|
||||
info: MessageTypedFn
|
||||
error: MessageTypedFn
|
||||
}
|
||||
|
||||
export type MessageQueueItem = {
|
||||
vnode: VNode
|
||||
handler: MessageHandler
|
||||
vm: MessageInstance
|
||||
}
|
||||
|
||||
export type MessageQueue = MessageQueueItem[]
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<div
|
||||
v-show="visible"
|
||||
:id="id"
|
||||
ref="messageRef"
|
||||
:class="[
|
||||
ns.b(),
|
||||
{ [ns.m(type)]: type && !icon },
|
||||
@@ -44,13 +45,14 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useEventListener, useTimeoutFn } from '@vueuse/core'
|
||||
import { useEventListener, useResizeObserver, useTimeoutFn } from '@vueuse/core'
|
||||
import { TypeComponents, TypeComponentsMap } from '@element-plus/utils'
|
||||
import { EVENT_CODE } from '@element-plus/constants'
|
||||
import ElBadge from '@element-plus/components/badge'
|
||||
import { ElIcon } from '@element-plus/components/icon'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { messageEmits, messageProps } from './message'
|
||||
import { getLastOffset } from './instance'
|
||||
import type { BadgeProps } from '@element-plus/components/badge'
|
||||
import type { CSSProperties } from 'vue'
|
||||
|
||||
@@ -64,23 +66,29 @@ const props = defineProps(messageProps)
|
||||
defineEmits(messageEmits)
|
||||
|
||||
const ns = useNamespace('message')
|
||||
|
||||
const messageRef = ref<HTMLDivElement>()
|
||||
const visible = ref(false)
|
||||
const badgeType = ref<BadgeProps['type']>(
|
||||
props.type ? (props.type === 'error' ? 'danger' : props.type) : 'info'
|
||||
)
|
||||
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 lastOffset = computed(() => getLastOffset(props.id))
|
||||
const offset = computed(() => props.offset + lastOffset.value)
|
||||
const bottom = computed((): number => height.value + offset.value)
|
||||
const customStyle = computed<CSSProperties>(() => ({
|
||||
top: `${props.offset}px`,
|
||||
top: `${offset.value}px`,
|
||||
zIndex: props.zIndex,
|
||||
}))
|
||||
|
||||
@@ -121,8 +129,13 @@ watch(
|
||||
|
||||
useEventListener(document, 'keydown', keydown)
|
||||
|
||||
useResizeObserver(messageRef, () => {
|
||||
height.value = messageRef.value!.getBoundingClientRect().height
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
visible,
|
||||
bottom,
|
||||
close,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -12,7 +12,9 @@ import { useZIndex } from '@element-plus/hooks'
|
||||
import { messageConfig } from '@element-plus/components/config-provider/src/config-provider'
|
||||
import MessageConstructor from './message.vue'
|
||||
import { messageDefaults, messageTypes } from './message'
|
||||
import { instances } from './instance'
|
||||
|
||||
import type { MessageContext } from './instance'
|
||||
import type { AppContext } from 'vue'
|
||||
import type {
|
||||
Message,
|
||||
@@ -22,11 +24,8 @@ import type {
|
||||
MessageOptions,
|
||||
MessageParams,
|
||||
MessageParamsNormalized,
|
||||
MessageQueue,
|
||||
MessageQueueItem,
|
||||
} from './message'
|
||||
|
||||
const instances: MessageQueue = []
|
||||
let seed = 1
|
||||
|
||||
// TODO: Since Notify.ts is basically the same like this file. So we could do some encapsulation against them to reduce code duplication.
|
||||
@@ -60,49 +59,29 @@ const normalizeOptions = (params?: MessageParams) => {
|
||||
return normalized as MessageParamsNormalized
|
||||
}
|
||||
|
||||
const closeMessage = (instance: MessageQueueItem) => {
|
||||
const closeMessage = (instance: MessageContext) => {
|
||||
const idx = instances.indexOf(instance)
|
||||
if (idx === -1) return
|
||||
|
||||
instances.splice(idx, 1)
|
||||
const { vnode, handler } = instance
|
||||
const { handler } = instance
|
||||
handler.close()
|
||||
|
||||
const removedHeight = vnode.el!.offsetHeight
|
||||
// adjust other instances vertical offset
|
||||
const len = instances.length
|
||||
if (len < 1) return
|
||||
for (let i = idx; i < len; i++) {
|
||||
const pos =
|
||||
Number.parseInt(instances[i].vnode.el!.style['top'], 10) -
|
||||
removedHeight -
|
||||
16
|
||||
|
||||
instances[i].vnode.component!.props.offset = pos
|
||||
}
|
||||
}
|
||||
|
||||
const createMessage = (
|
||||
{ appendTo, ...options }: MessageParamsNormalized,
|
||||
context?: AppContext | null
|
||||
): MessageQueueItem => {
|
||||
): MessageContext => {
|
||||
const { nextZIndex } = useZIndex()
|
||||
|
||||
const id = `message_${seed++}`
|
||||
const userOnClose = options.onClose
|
||||
|
||||
let verticalOffset = options.offset
|
||||
instances.forEach(({ vnode: vm }) => {
|
||||
verticalOffset += (vm.el?.offsetHeight || 0) + 16
|
||||
})
|
||||
verticalOffset += 16
|
||||
|
||||
const container = document.createElement('div')
|
||||
|
||||
const props = {
|
||||
...options,
|
||||
zIndex: options.zIndex ?? nextZIndex(),
|
||||
offset: verticalOffset,
|
||||
id,
|
||||
onClose: () => {
|
||||
userOnClose?.()
|
||||
@@ -141,10 +120,12 @@ const createMessage = (
|
||||
},
|
||||
}
|
||||
|
||||
const instance = {
|
||||
const instance: MessageContext = {
|
||||
id,
|
||||
vnode,
|
||||
vm,
|
||||
handler,
|
||||
props: (vnode.component as any).props,
|
||||
}
|
||||
|
||||
return instance
|
||||
@@ -168,8 +149,8 @@ const message: MessageFn &
|
||||
({ vnode: vm }) => vm.props?.message === normalized.message
|
||||
)
|
||||
if (instance) {
|
||||
;(instance.vnode.component as any).props.repeatNum += 1
|
||||
;(instance.vnode.component as any).props.type = normalized.type
|
||||
instance.props.repeatNum += 1
|
||||
instance.props.type = normalized.type
|
||||
return instance.handler
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user