mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
address PR comments
This commit is contained in:
@@ -33,7 +33,7 @@ describe('Notification.vue', () => {
|
||||
expect(wrapper.vm.typeClass).toBe('')
|
||||
expect(wrapper.vm.horizontalClass).toBe('right')
|
||||
expect(wrapper.vm.verticalProperty).toBe('top')
|
||||
expect(wrapper.vm.positionStyle).toEqual({ top: 0 })
|
||||
expect(wrapper.vm.positionStyle).toEqual({ top: '0px' })
|
||||
})
|
||||
|
||||
test('should be able to render VNode', () => {
|
||||
@@ -86,21 +86,6 @@ describe('Notification.vue', () => {
|
||||
offMock.mockRestore()
|
||||
})
|
||||
|
||||
test('should call init function when it\'s provided', () => {
|
||||
const _init = jest.fn()
|
||||
const wrapper = _mount({
|
||||
slots: {
|
||||
default: AXIOM,
|
||||
},
|
||||
props: {
|
||||
_init,
|
||||
_idx: 0,
|
||||
},
|
||||
})
|
||||
expect(_init).toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
test('should add event listener to target element when init', () => {
|
||||
|
||||
jest.spyOn(domExports, 'on')
|
||||
@@ -243,10 +228,12 @@ describe('Notification.vue', () => {
|
||||
const wrapper = _mount({
|
||||
props: {
|
||||
duration: 0,
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.trigger('click')
|
||||
expect(wrapper.emitted('click')).toHaveLength(1)
|
||||
expect(wrapper.vm.onClick).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('should be able to delete timer when press delete', async () => {
|
||||
@@ -282,10 +269,12 @@ describe('Notification.vue', () => {
|
||||
keyCode: eventKeys.esc,
|
||||
// eslint-disable-next-line
|
||||
} as any)
|
||||
const oldClose = wrapper.vm.close
|
||||
wrapper.vm.close = jest.fn(() => oldClose())
|
||||
document.dispatchEvent(event)
|
||||
jest.runAllTimers()
|
||||
expect(wrapper.vm.closed).toBe(true)
|
||||
expect(wrapper.emitted('close')).toHaveLength(1)
|
||||
expect(wrapper.vm.close).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { isVNode } from 'vue'
|
||||
import Notification, { close, closeAll } from '../src/notify'
|
||||
import type { NotificationVM } from '../src/notification.constants'
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
||||
@@ -10,15 +12,15 @@ describe('Notification on command', () => {
|
||||
})
|
||||
|
||||
test('it should get component instance when calling notification constructor', async () => {
|
||||
const vm = Notification({})
|
||||
expect(vm).toBeNull()
|
||||
const vm = Notification()
|
||||
expect(isVNode(vm)).toBe(true)
|
||||
expect(document.querySelector(selector)).toBeDefined()
|
||||
jest.runAllTicks()
|
||||
})
|
||||
|
||||
|
||||
test('it should be able to close notification by manually close', () => {
|
||||
Notification({})
|
||||
Notification()
|
||||
const element = document.querySelector(selector)
|
||||
expect(element).toBeDefined()
|
||||
close(element.id)
|
||||
@@ -26,11 +28,18 @@ describe('Notification on command', () => {
|
||||
})
|
||||
|
||||
test('it should close all notifications', () => {
|
||||
const notifications: NotificationVM[] = []
|
||||
const onClose = jest.fn()
|
||||
for (let i = 0; i < 4; i++) {
|
||||
Notification({})
|
||||
notifications.push(Notification({
|
||||
onClose,
|
||||
}))
|
||||
}
|
||||
expect(document.querySelectorAll(selector).length).toBe(4)
|
||||
closeAll()
|
||||
for (let i = 0; i < notifications.length; i++) {
|
||||
expect(onClose).toHaveBeenCalledTimes(4)
|
||||
}
|
||||
expect(document.querySelectorAll(selector).length).toBe(0)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<transition name="el-notification-fade">
|
||||
<div
|
||||
v-show="visible"
|
||||
v-if="visible"
|
||||
:id="id"
|
||||
:class="['el-notification', customClass, horizontalClass]"
|
||||
:style="positionStyle"
|
||||
@@ -10,15 +10,8 @@
|
||||
@mouseleave="startTimer()"
|
||||
@click="click"
|
||||
>
|
||||
<i
|
||||
v-if="type || iconClass"
|
||||
class="el-notification__icon"
|
||||
:class="[typeClass, iconClass]"
|
||||
></i>
|
||||
<div
|
||||
class="el-notification__group"
|
||||
:class="{ 'is-with-icon': typeClass || iconClass }"
|
||||
>
|
||||
<i v-if="type || iconClass" class="el-notification__icon" :class="[typeClass, iconClass]"></i>
|
||||
<div class="el-notification__group" :class="{ 'is-with-icon': typeClass || iconClass }">
|
||||
<h2 class="el-notification__title" v-text="title"></h2>
|
||||
<div v-show="message" class="el-notification__content">
|
||||
<slot>
|
||||
@@ -28,18 +21,14 @@
|
||||
<p v-else v-html="message"></p>
|
||||
</slot>
|
||||
</div>
|
||||
<div
|
||||
v-if="showClose"
|
||||
class="el-notification__closeBtn el-icon-close"
|
||||
@click.stop="close"
|
||||
></div>
|
||||
<div v-if="showClose" class="el-notification__closeBtn el-icon-close" @click.stop="close"></div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, computed, ref, PropType } from 'vue'
|
||||
import { defaultProps } from './notification.constants'
|
||||
import { defineComponent, computed, ref, PropType } from 'vue'
|
||||
// notificationVM is an alias of vue.VNode
|
||||
import type { NotificationVM } from './notification.constants'
|
||||
import { eventKeys } from '../../utils/aria'
|
||||
import { on, off } from '../../utils/dom'
|
||||
@@ -55,53 +44,67 @@ export default defineComponent({
|
||||
name: 'ElNotification',
|
||||
props: {
|
||||
customClass: { type: String, default: '' },
|
||||
dangerouslyUseHTMLString: { type: Boolean, default: false }, // default false
|
||||
duration: { type: Number, default: 4500 }, // default 4500
|
||||
dangerouslyUseHTMLString: { type: Boolean, default: false },
|
||||
duration: { type: Number, default: 4500 },
|
||||
iconClass: { type: String, default: '' },
|
||||
id: { type: String, default: '' },
|
||||
message: { type: [String, Object] as PropType<>, default: '' },
|
||||
offset: { type: Number, default: 0 }, // defaults 0
|
||||
onClick: { type: Function, default: () => void 0 },
|
||||
onClose: { type: Function, required: true },
|
||||
position: { type: String, default: 'top-right' }, // default top-right
|
||||
message: {
|
||||
type: [String, Object] as PropType<string | NotificationVM>,
|
||||
default: '',
|
||||
},
|
||||
offset: { type: Number, default: 0 },
|
||||
onClick: {
|
||||
type: Function as PropType<() => void>,
|
||||
default: () => void 0,
|
||||
},
|
||||
onClose: {
|
||||
type: Function as PropType<() => void>,
|
||||
required: true,
|
||||
},
|
||||
position: {
|
||||
type: String as PropType<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'>,
|
||||
default: 'top-right',
|
||||
},
|
||||
showClose: { type: Boolean, default: true },
|
||||
title: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
// disabling linter next two lines due to this is a internal prop which should not be accessed by end user
|
||||
// eslint-disable-next-line
|
||||
_idx: { type: Number, required: false, default: null },
|
||||
// eslint-disable-next-line
|
||||
_init: { type: Function as (idx: number, vm: NotificationVM) => void, required: false, default: null }
|
||||
zIndex: { type: Number, default: 0 },
|
||||
},
|
||||
emits: ['close', 'click'],
|
||||
setup(props) {
|
||||
const data = reactive({
|
||||
...defaultProps,
|
||||
...props,
|
||||
})
|
||||
|
||||
data.typeClass = computed(() => {
|
||||
const type = data.type
|
||||
const typeClass = computed(() => {
|
||||
const type = props.type
|
||||
return type && TypeMap[type] ? `el-icon-${TypeMap[type]}` : ''
|
||||
})
|
||||
|
||||
data.horizontalClass = computed(() => {
|
||||
return data.position.indexOf('right') > 1 ? 'right' : 'left'
|
||||
const horizontalClass = computed(() => {
|
||||
return props.position.indexOf('right') > 1 ? 'right' : 'left'
|
||||
})
|
||||
|
||||
data.verticalProperty = computed(() => {
|
||||
return data.position.startsWith('top') ? 'top' : 'bottom'
|
||||
const verticalProperty = computed(() => {
|
||||
return props.position.startsWith('top') ? 'top' : 'bottom'
|
||||
})
|
||||
|
||||
data.positionStyle = ref({
|
||||
[data.verticalProperty]: props.offset,
|
||||
const positionStyle = computed(() => {
|
||||
return {
|
||||
[verticalProperty.value]: `${props.offset}px`,
|
||||
}
|
||||
})
|
||||
|
||||
console.log(data.positionStyle)
|
||||
const visible = ref(true)
|
||||
const closed = ref(false)
|
||||
const timer = ref(null)
|
||||
|
||||
data.visible = ref(true)
|
||||
|
||||
return data
|
||||
return {
|
||||
horizontalClass,
|
||||
typeClass,
|
||||
positionStyle,
|
||||
verticalProperty,
|
||||
visible,
|
||||
closed,
|
||||
timer,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
closed(newVal: boolean) {
|
||||
@@ -110,11 +113,6 @@ export default defineComponent({
|
||||
on(this.$el, 'transitionend', this.destroyElement)
|
||||
}
|
||||
},
|
||||
offset(newVal: number) {
|
||||
this.positionStyle = {
|
||||
[this.verticalProperty]: `${newVal}px`,
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.duration > 0) {
|
||||
@@ -124,11 +122,6 @@ export default defineComponent({
|
||||
}
|
||||
}, this.duration)
|
||||
}
|
||||
// When using notification programmably, this is line of code is critical
|
||||
// to obtain the public component instance
|
||||
if (typeof this._init === 'function') {
|
||||
// this._init(this._idx, this)
|
||||
}
|
||||
on(document, 'keydown', this.keydown)
|
||||
},
|
||||
beforeUnmount() {
|
||||
@@ -157,13 +150,11 @@ export default defineComponent({
|
||||
// Event handlers
|
||||
click() {
|
||||
this?.onClick()
|
||||
this.$emit('click')
|
||||
},
|
||||
close() {
|
||||
this.closed = true
|
||||
this.timer = null
|
||||
this.onClose()
|
||||
this.$emit('close')
|
||||
},
|
||||
keydown({ keyCode }: KeyboardEvent) {
|
||||
if (keyCode === eventKeys.delete || keyCode === eventKeys.backspace) {
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import type { VNode } from 'vue'
|
||||
|
||||
export interface INotification<P = (options: INotificationOptions) => void> {
|
||||
(options: INotificationOptions): void
|
||||
success?: P
|
||||
warning?: P
|
||||
info?: P
|
||||
error?: P
|
||||
}
|
||||
export type INotification = (options?: INotificationOptions) => NotificationVM
|
||||
|
||||
export type INotificationOptions = {
|
||||
customClass?: string
|
||||
dangerouslyUseHTMLString?: boolean // default false
|
||||
@@ -14,15 +9,14 @@ export type INotificationOptions = {
|
||||
iconClass?: string
|
||||
id?: string
|
||||
message?: string | VNode
|
||||
zIndex?: number
|
||||
onClose?: () => unknown
|
||||
onClick?: () => unknown
|
||||
offset?: number // defaults 0
|
||||
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' // default top-right
|
||||
showClose?: boolean
|
||||
type?: 'success' | 'warning' | 'info' | 'error' | ''
|
||||
title?: string
|
||||
type?: 'success' | 'warning' | 'info' | 'error'
|
||||
_idx?: number
|
||||
_init?: (idx: number, vm: NotificationVM) => void
|
||||
}
|
||||
|
||||
export type NotificationVM = VNode
|
||||
@@ -33,23 +27,3 @@ type NotificationQueueItem = {
|
||||
}
|
||||
|
||||
export type NotificationQueue = Array<NotificationQueueItem>
|
||||
|
||||
|
||||
export const defaultProps = {
|
||||
visible: false,
|
||||
title: '',
|
||||
message: '',
|
||||
duration: 4500,
|
||||
type: '',
|
||||
showClose: true,
|
||||
customClass: '',
|
||||
iconClass: '',
|
||||
onClose: null,
|
||||
onClick: null,
|
||||
closed: false,
|
||||
verticalOffset: 0,
|
||||
timer: null,
|
||||
dangerouslyUseHTMLString: false,
|
||||
position: 'top-right',
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
import { reactive, createVNode, render } from 'vue'
|
||||
import { createVNode, render } from 'vue'
|
||||
import NotificationConstructor from './index.vue'
|
||||
import type { INotificationOptions, INotification, NotificationQueue, NotificationVM } from './notification.constants'
|
||||
import isServer from '../../utils/isServer'
|
||||
import PopupManager from '../../utils/popup-manager'
|
||||
import { isVNode } from '../../utils/util'
|
||||
|
||||
// const NotificationRenderer = (props: Record<string, unknown>) => {
|
||||
// if (isVNode(props.message)) {
|
||||
// return h(NotificationConstructor, props, { default: () => h(props.message) })
|
||||
// }
|
||||
// return h(NotificationConstructor, props)
|
||||
// }
|
||||
|
||||
let vm: NotificationVM
|
||||
const notifications: NotificationQueue = []
|
||||
let seed = 1
|
||||
|
||||
const Notification: INotification = function(options: INotificationOptions): NotificationVM {
|
||||
const Notification: INotification = function(options = {}) {
|
||||
if (isServer) return
|
||||
const id = 'notification_' + seed++
|
||||
const userOnClose = options.onClose
|
||||
options.onClose = function() {
|
||||
close(id, userOnClose)
|
||||
}
|
||||
|
||||
const position = options.position || 'top-right'
|
||||
|
||||
let verticalOffset = options.offset || 0
|
||||
@@ -34,33 +21,31 @@ const Notification: INotification = function(options: INotificationOptions): Not
|
||||
})
|
||||
verticalOffset += 16
|
||||
|
||||
const defaultOptions: INotificationOptions = {
|
||||
const id = 'notification_' + seed++
|
||||
const userOnClose = options.onClose
|
||||
options = {
|
||||
dangerouslyUseHTMLString: false,
|
||||
duration: 4500,
|
||||
position: 'top-right',
|
||||
showClose: true,
|
||||
offset: 0,
|
||||
_idx: notifications.length,
|
||||
// _init: function(idx: number, vm: NotificationVM): void {
|
||||
// obtainInstance(idx, vm)
|
||||
|
||||
// },
|
||||
}
|
||||
options = {
|
||||
...defaultOptions,
|
||||
// default options end
|
||||
...options,
|
||||
onClose: () => {
|
||||
close(id, userOnClose)
|
||||
},
|
||||
offset: verticalOffset,
|
||||
id,
|
||||
zIndex: PopupManager.nextZIndex(),
|
||||
}
|
||||
|
||||
options = reactive(options)
|
||||
|
||||
const container = document.createElement('div')
|
||||
|
||||
container.className = `container_${id}`
|
||||
container.style.zIndex = String(PopupManager.nextZIndex())
|
||||
// notifications.push({ vm: null, container })
|
||||
vm = createVNode(NotificationConstructor, options)
|
||||
container.style.zIndex = String()
|
||||
|
||||
vm = createVNode(NotificationConstructor, options, isVNode(options.message) ? {
|
||||
default: () => options.message,
|
||||
}: null)
|
||||
render(vm, container)
|
||||
notifications.push({ vm, $el: container })
|
||||
document.body.appendChild(container.firstElementChild)
|
||||
@@ -68,23 +53,25 @@ const Notification: INotification = function(options: INotificationOptions): Not
|
||||
return vm
|
||||
};
|
||||
|
||||
['success', 'warning', 'info', 'error'].forEach(type => {
|
||||
Notification[type] = options => {
|
||||
if (typeof options === 'string' || isVNode(options)) {
|
||||
options = {
|
||||
message: options,
|
||||
(['success', 'warning', 'info', 'error'] as const).forEach(type => {
|
||||
Object.assign(Notification, {
|
||||
type: options => {
|
||||
if (typeof options === 'string' || isVNode(options)) {
|
||||
options = {
|
||||
message: options,
|
||||
}
|
||||
}
|
||||
}
|
||||
options.type = type
|
||||
return Notification(options)
|
||||
}
|
||||
options.type = type
|
||||
return Notification(options)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
export function close(
|
||||
id: string,
|
||||
userOnClose?: (vm: NotificationVM) => void,
|
||||
): void {
|
||||
const idx = notifications.findIndex( ({ vm }) => {
|
||||
const idx = notifications.findIndex(({ vm }) => {
|
||||
const { id: _id } = vm.component.props
|
||||
return id === _id
|
||||
})
|
||||
@@ -96,18 +83,18 @@ export function close(
|
||||
if (!vm) return
|
||||
userOnClose?.(vm)
|
||||
const removedHeight = vm.el.offsetHeight
|
||||
// document.body.removeChild(notification.container)
|
||||
render(null, $el)
|
||||
|
||||
notifications.splice(idx, 1)
|
||||
const len = notifications.length
|
||||
if (len < 1) return
|
||||
const position = vm.component.props.position
|
||||
const position = vm.props.position
|
||||
for (let i = idx; i < len; i++) {
|
||||
if (notifications[i].vm.component.props.position === position) {
|
||||
notifications[i].vm.el.style[vm.component.ctx.verticalProperty] =
|
||||
const verticalPos = vm.props.position.split('-')[0]
|
||||
notifications[i].vm.el.style[verticalPos] =
|
||||
parseInt(
|
||||
notifications[i].vm.el.style[vm.component.ctx.verticalProperty],
|
||||
notifications[i].vm.el.style[verticalPos],
|
||||
10,
|
||||
) -
|
||||
removedHeight -
|
||||
@@ -119,12 +106,8 @@ export function close(
|
||||
|
||||
export function closeAll(): void {
|
||||
for (let i = notifications.length - 1; i >= 0; i--) {
|
||||
notifications[i].vm.component.ctx.onClose()
|
||||
(notifications[i].vm.component.props as INotificationOptions).onClose()
|
||||
}
|
||||
}
|
||||
|
||||
// function obtainInstance(idx: number, vm: NotificationVM): void {
|
||||
// notifications[idx] = vm
|
||||
// }
|
||||
|
||||
export default Notification
|
||||
|
||||
Reference in New Issue
Block a user