Skip to content

Commit

Permalink
fix(components): [message]: offset (#8379)
Browse files Browse the repository at this point in the history
* fix(components): [message]: offset

closes #7217, #8368
  • Loading branch information
sxzz committed Jun 22, 2022
1 parent 8f0341b commit 37ed7a1
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 47 deletions.
Expand Up @@ -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`'
Expand Down
2 changes: 1 addition & 1 deletion packages/components/message/__tests__/message.test.ts
Expand Up @@ -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', () => {
Expand Down
2 changes: 1 addition & 1 deletion 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
Expand Down
30 changes: 30 additions & 0 deletions 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<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
}
10 changes: 1 addition & 9 deletions packages/components/message/src/message.ts
Expand Up @@ -26,7 +26,7 @@ export const messageDefaults = mutable({
onClose: undefined,
showClose: false,
type: 'info',
offset: 20,
offset: 16,
zIndex: 0,
grouping: false,
repeatNum: 1,
Expand Down Expand Up @@ -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[]
25 changes: 19 additions & 6 deletions packages/components/message/src/message.vue
Expand Up @@ -7,6 +7,7 @@
<div
v-show="visible"
:id="id"
ref="messageRef"
:class="[
ns.b(),
{ [ns.m(type)]: type && !icon },
Expand Down Expand Up @@ -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'
Expand All @@ -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,
}))
Expand Down Expand Up @@ -121,8 +129,13 @@ watch(
useEventListener(document, 'keydown', keydown)
useResizeObserver(messageRef, () => {
height.value = messageRef.value!.getBoundingClientRect().height
})
defineExpose({
visible,
bottom,
close,
})
</script>
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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?.()
Expand Down Expand Up @@ -141,10 +120,12 @@ const createMessage = (
},
}

const instance = {
const instance: MessageContext = {
id,
vnode,
vm,
handler,
props: (vnode.component as any).props,
}

return instance
Expand All @@ -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
}
}
Expand Down

0 comments on commit 37ed7a1

Please sign in to comment.