diff --git a/packages/components/message/__tests__/message-manager.test.tsx b/packages/components/message/__tests__/message-manager.test.tsx index b3b65ea32a7b9..74b191e43ebc9 100644 --- a/packages/components/message/__tests__/message-manager.test.tsx +++ b/packages/components/message/__tests__/message-manager.test.tsx @@ -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`' diff --git a/packages/components/message/__tests__/message.test.ts b/packages/components/message/__tests__/message.test.ts index 478f8852064e3..8e291a1974c0a 100644 --- a/packages/components/message/__tests__/message.test.ts +++ b/packages/components/message/__tests__/message.test.ts @@ -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', () => { diff --git a/packages/components/message/index.ts b/packages/components/message/index.ts index 14524aac01e37..7c6dbffbb40a6 100644 --- a/packages/components/message/index.ts +++ b/packages/components/message/index.ts @@ -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 diff --git a/packages/components/message/src/instance.ts b/packages/components/message/src/instance.ts new file mode 100644 index 0000000000000..30e2f4133b382 --- /dev/null +++ b/packages/components/message/src/instance.ts @@ -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 +} + +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 +} diff --git a/packages/components/message/src/message.ts b/packages/components/message/src/message.ts index 21546531212b9..48f6c05893147 100644 --- a/packages/components/message/src/message.ts +++ b/packages/components/message/src/message.ts @@ -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[] diff --git a/packages/components/message/src/message.vue b/packages/components/message/src/message.vue index 4e025481c1c24..464bd1a33ec55 100644 --- a/packages/components/message/src/message.vue +++ b/packages/components/message/src/message.vue @@ -7,6 +7,7 @@
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() const visible = ref(false) -const badgeType = ref( - props.type ? (props.type === 'error' ? 'danger' : props.type) : 'info' -) +const height = ref(0) + let stopTimer: (() => void) | undefined = undefined +const badgeType = computed(() => + 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(() => ({ - 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, }) diff --git a/packages/components/message/src/message-method.ts b/packages/components/message/src/method.ts similarity index 83% rename from packages/components/message/src/message-method.ts rename to packages/components/message/src/method.ts index f9f83f82d3299..b53ae5c3124eb 100644 --- a/packages/components/message/src/message-method.ts +++ b/packages/components/message/src/method.ts @@ -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 } }