mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
fix(components): [notification] type declaration error and four types of methods are missing context parameters (#18951)
* fix(components): [notification] type declaration error * feat(components): four types of methods add context parameters * test: [notification] add test case
This commit is contained in:
@@ -1,16 +1,17 @@
|
||||
import { nextTick } from 'vue'
|
||||
import { createApp, nextTick } from 'vue'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { rAF } from '@element-plus/test-utils/tick'
|
||||
import Notification, { closeAll } from '../src/notify'
|
||||
import { ElNotification } from '..'
|
||||
|
||||
import type { NotificationHandle } from '../src/notification'
|
||||
import type { VNode } from 'vue'
|
||||
|
||||
const selector = '.el-notification'
|
||||
|
||||
describe('Notification on command', () => {
|
||||
afterEach(() => {
|
||||
closeAll()
|
||||
Notification._context = null
|
||||
})
|
||||
|
||||
it('it should get component handle', async () => {
|
||||
@@ -111,20 +112,29 @@ describe('Notification on command', () => {
|
||||
await nextTick()
|
||||
expect(htmlElement.querySelector(selector)).toBeNull()
|
||||
})
|
||||
describe('context inheritance', () => {
|
||||
it('should globally inherit context correctly', () => {
|
||||
expect(ElNotification._context).toBe(null)
|
||||
const testContext = {
|
||||
config: {
|
||||
globalProperties: {},
|
||||
},
|
||||
_context: {},
|
||||
}
|
||||
ElNotification.install?.(testContext as any)
|
||||
expect(ElNotification._context).not.toBe(null)
|
||||
expect(ElNotification._context).toBe(testContext._context)
|
||||
// clean up
|
||||
ElNotification._context = null
|
||||
})
|
||||
|
||||
it('should globally inherit context correctly', async () => {
|
||||
const globalContext = createApp({})._context
|
||||
Notification._context = globalContext
|
||||
const onClose = vi.fn((vm: VNode) => vm.appContext)
|
||||
const handle = Notification({ duration: 0, onClose })
|
||||
await nextTick()
|
||||
handle.close()
|
||||
await nextTick()
|
||||
expect(onClose).toHaveBeenCalledTimes(1)
|
||||
expect(onClose).toHaveLastReturnedWith(globalContext)
|
||||
})
|
||||
|
||||
it('should be possible to set the context individually', async () => {
|
||||
const globalContext = createApp({})._context
|
||||
Notification._context = globalContext
|
||||
const localContext = createApp({})._context
|
||||
const onClose = vi.fn((vm: VNode) => vm.appContext)
|
||||
const handle = Notification({ duration: 0, onClose }, localContext)
|
||||
await nextTick()
|
||||
handle.close()
|
||||
await nextTick()
|
||||
expect(onClose).toHaveBeenCalledTimes(1)
|
||||
expect(onClose).toHaveLastReturnedWith(localContext)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { buildProps, definePropType, iconPropType } from '@element-plus/utils'
|
||||
|
||||
import type { ExtractPropTypes, VNode } from 'vue'
|
||||
import type { AppContext, ExtractPropTypes, VNode } from 'vue'
|
||||
import type Notification from './notification.vue'
|
||||
|
||||
export const notificationTypes = [
|
||||
@@ -118,11 +118,15 @@ export type NotificationEmits = typeof notificationEmits
|
||||
|
||||
export type NotificationInstance = InstanceType<typeof Notification>
|
||||
|
||||
export type NotificationOptions = Omit<NotificationProps, 'id'> & {
|
||||
export type NotificationOptions = Omit<NotificationProps, 'id' | 'onClose'> & {
|
||||
/**
|
||||
* @description set the root element for the notification, default to `document.body`
|
||||
*/
|
||||
appendTo?: HTMLElement | string
|
||||
/**
|
||||
* @description callback function when closed
|
||||
*/
|
||||
onClose?(vm: VNode): void
|
||||
}
|
||||
export type NotificationOptionsTyped = Omit<NotificationOptions, 'type'>
|
||||
|
||||
@@ -136,12 +140,18 @@ export type NotificationParamsTyped =
|
||||
| string
|
||||
| VNode
|
||||
|
||||
export type NotifyFn = ((
|
||||
options?: NotificationParams
|
||||
) => NotificationHandle) & { closeAll: () => void }
|
||||
export interface NotifyFn {
|
||||
(
|
||||
options?: NotificationParams,
|
||||
appContext?: null | AppContext
|
||||
): NotificationHandle
|
||||
closeAll(): void
|
||||
_context: AppContext | null
|
||||
}
|
||||
|
||||
export type NotifyTypedFn = (
|
||||
options?: NotificationParamsTyped
|
||||
options?: NotificationParamsTyped,
|
||||
appContext?: null | AppContext
|
||||
) => NotificationHandle
|
||||
|
||||
export interface Notify extends NotifyFn {
|
||||
|
||||
@@ -5,12 +5,13 @@ import {
|
||||
isElement,
|
||||
isFunction,
|
||||
isString,
|
||||
isUndefined,
|
||||
isVNode,
|
||||
} from '@element-plus/utils'
|
||||
import NotificationConstructor from './notification.vue'
|
||||
import { notificationTypes } from './notification'
|
||||
|
||||
import type { AppContext, Ref, VNode } from 'vue'
|
||||
import type { Ref, VNode } from 'vue'
|
||||
import type {
|
||||
NotificationOptions,
|
||||
NotificationProps,
|
||||
@@ -34,88 +35,84 @@ const notifications: Record<
|
||||
const GAP_SIZE = 16
|
||||
let seed = 1
|
||||
|
||||
const notify: NotifyFn & Partial<Notify> & { _context: AppContext | null } =
|
||||
function (options = {}, context: AppContext | null = null) {
|
||||
if (!isClient) return { close: () => undefined }
|
||||
const notify: NotifyFn & Partial<Notify> = function (options = {}, context) {
|
||||
if (!isClient) return { close: () => undefined }
|
||||
|
||||
if (isString(options) || isVNode(options)) {
|
||||
options = { message: options }
|
||||
}
|
||||
|
||||
const position = options.position || 'top-right'
|
||||
|
||||
let verticalOffset = options.offset || 0
|
||||
notifications[position].forEach(({ vm }) => {
|
||||
verticalOffset += (vm.el?.offsetHeight || 0) + GAP_SIZE
|
||||
})
|
||||
verticalOffset += GAP_SIZE
|
||||
|
||||
const id = `notification_${seed++}`
|
||||
const userOnClose = options.onClose
|
||||
const props: Partial<NotificationProps> = {
|
||||
...options,
|
||||
offset: verticalOffset,
|
||||
id,
|
||||
onClose: () => {
|
||||
close(id, position, userOnClose)
|
||||
},
|
||||
}
|
||||
|
||||
let appendTo: HTMLElement | null = document.body
|
||||
if (isElement(options.appendTo)) {
|
||||
appendTo = options.appendTo
|
||||
} else if (isString(options.appendTo)) {
|
||||
appendTo = document.querySelector(options.appendTo)
|
||||
}
|
||||
|
||||
// should fallback to default value with a warning
|
||||
if (!isElement(appendTo)) {
|
||||
debugWarn(
|
||||
'ElNotification',
|
||||
'the appendTo option is not an HTMLElement. Falling back to document.body.'
|
||||
)
|
||||
appendTo = document.body
|
||||
}
|
||||
|
||||
const container = document.createElement('div')
|
||||
|
||||
const vm = createVNode(
|
||||
NotificationConstructor,
|
||||
props,
|
||||
isFunction(props.message) ? props.message : () => props.message
|
||||
)
|
||||
vm.appContext = context ?? notify._context
|
||||
|
||||
// clean notification element preventing mem leak
|
||||
vm.props!.onDestroy = () => {
|
||||
render(null, container)
|
||||
}
|
||||
|
||||
// instances will remove this item when close function gets called. So we do not need to worry about it.
|
||||
render(vm, container)
|
||||
notifications[position].push({ vm })
|
||||
appendTo.appendChild(container.firstElementChild!)
|
||||
|
||||
return {
|
||||
// instead of calling the onClose function directly, setting this value so that we can have the full lifecycle
|
||||
// for out component, so that all closing steps will not be skipped.
|
||||
close: () => {
|
||||
;(vm.component!.exposed as { visible: Ref<boolean> }).visible.value =
|
||||
false
|
||||
},
|
||||
}
|
||||
if (isString(options) || isVNode(options)) {
|
||||
options = { message: options }
|
||||
}
|
||||
|
||||
const position = options.position || 'top-right'
|
||||
|
||||
let verticalOffset = options.offset || 0
|
||||
notifications[position].forEach(({ vm }) => {
|
||||
verticalOffset += (vm.el?.offsetHeight || 0) + GAP_SIZE
|
||||
})
|
||||
verticalOffset += GAP_SIZE
|
||||
|
||||
const id = `notification_${seed++}`
|
||||
const userOnClose = options.onClose
|
||||
const props: Partial<NotificationProps> = {
|
||||
...options,
|
||||
offset: verticalOffset,
|
||||
id,
|
||||
onClose: () => {
|
||||
close(id, position, userOnClose)
|
||||
},
|
||||
}
|
||||
|
||||
let appendTo: HTMLElement | null = document.body
|
||||
if (isElement(options.appendTo)) {
|
||||
appendTo = options.appendTo
|
||||
} else if (isString(options.appendTo)) {
|
||||
appendTo = document.querySelector(options.appendTo)
|
||||
}
|
||||
|
||||
// should fallback to default value with a warning
|
||||
if (!isElement(appendTo)) {
|
||||
debugWarn(
|
||||
'ElNotification',
|
||||
'the appendTo option is not an HTMLElement. Falling back to document.body.'
|
||||
)
|
||||
appendTo = document.body
|
||||
}
|
||||
|
||||
const container = document.createElement('div')
|
||||
|
||||
const vm = createVNode(
|
||||
NotificationConstructor,
|
||||
props,
|
||||
isFunction(props.message) ? props.message : () => props.message
|
||||
)
|
||||
vm.appContext = isUndefined(context) ? notify._context : context
|
||||
|
||||
// clean notification element preventing mem leak
|
||||
vm.props!.onDestroy = () => {
|
||||
render(null, container)
|
||||
}
|
||||
|
||||
// instances will remove this item when close function gets called. So we do not need to worry about it.
|
||||
render(vm, container)
|
||||
notifications[position].push({ vm })
|
||||
appendTo.appendChild(container.firstElementChild!)
|
||||
|
||||
return {
|
||||
// instead of calling the onClose function directly, setting this value so that we can have the full lifecycle
|
||||
// for out component, so that all closing steps will not be skipped.
|
||||
close: () => {
|
||||
;(vm.component!.exposed as { visible: Ref<boolean> }).visible.value =
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
notificationTypes.forEach((type) => {
|
||||
notify[type] = (options = {}) => {
|
||||
notify[type] = (options = {}, appContext) => {
|
||||
if (isString(options) || isVNode(options)) {
|
||||
options = {
|
||||
message: options,
|
||||
}
|
||||
}
|
||||
return notify({
|
||||
...options,
|
||||
type,
|
||||
})
|
||||
return notify({ ...options, type }, appContext)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user