feat(components): [el-message] & [el-notification] appendTo option added (#4012)

* feat(components): [el-message] & [el-notification] appendTo option added

* fix(components): unit test and replaced warn with debugWarn
This commit is contained in:
CodeSpikeX
2021-10-30 19:09:36 +05:00
committed by GitHub
parent 41981d8789
commit 7fe4e0b026
8 changed files with 126 additions and 30 deletions

View File

@@ -77,18 +77,19 @@ In this case you should call `ElMessage(options)`. We have also registered metho
## Options
| Attribute | Description | Type | Accepted Values | Default |
| ------------------------ | ------------------------------------------------------------------------------ | ------------------ | -------------------------- | ------- |
| message | message text | string / VNode | — | — |
| type | message type | string | success/warning/info/error | info |
| icon | custom icon component, overrides `type` | string / Component | — | — |
| dangerouslyUseHTMLString | whether `message` is treated as HTML string | boolean | — | false |
| custom-class | custom class name for Message | string | — | — |
| duration | display duration, millisecond. If set to 0, it will not turn off automatically | number | — | 3000 |
| show-close | whether to show a close button | boolean | — | false |
| center | whether to center the text | boolean | — | false |
| on-close | callback function when closed with the message instance as the parameter | function | — | — |
| offset | set the distance to the top of viewport | number | — | 20 |
| Attribute | Description | Type | Accepted Values | Default |
| ------------------------ | ------------------------------------------------------------------------------ | -------------------- | -------------------------- | ------------- |
| message | message text | string / VNode | — | — |
| type | message type | string | success/warning/info/error | info |
| icon-class | custom icon's class, overrides `type` | string | — | — |
| dangerouslyUseHTMLString | whether `message` is treated as HTML string | boolean | — | false |
| custom-class | custom class name for Message | string | — | — |
| duration | display duration, millisecond. If set to 0, it will not turn off automatically | number | — | 3000 |
| show-close | whether to show a close button | boolean | — | false |
| center | whether to center the text | boolean | — | false |
| on-close | callback function when closed with the message instance as the parameter | function | — | — |
| offset | set the distance to the top of viewport | number | — | 20 |
| appendTo | set the root element for the message | string / HTMLElement | - | document.body |
## Methods

View File

@@ -85,20 +85,21 @@ In this case you should call `ElNotification(options)`. We have also registered
## Options
| Attribute | Description | Type | Accepted Values | Default |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------------------ | ------------------------------------------- | --------- |
| title | title | string | — | — |
| message | description text | string/Vue.VNode | — | — |
| dangerouslyUseHTMLString | whether `message` is treated as HTML string | boolean | — | false |
| type | notification type | string | success/warning/info/error | — |
| icon | custom icon component. It will be overridden by `type` | string / Component | — | — |
| customClass | custom class name for Notification | string | — | — |
| duration | duration before close. It will not automatically close if set 0 | number | — | 4500 |
| position | custom position | string | top-right/top-left/bottom-right/bottom-left | top-right |
| showClose | whether to show a close button | boolean | — | true |
| onClose | callback function when closed | function | — | — |
| onClick | callback function when notification clicked | function | — | — |
| offset | offset from the top edge of the screen. Every Notification instance of the same moment should have the same offset | number | — | 0 |
| Attribute | Description | Type | Accepted Values | Default |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------ | -------------------- | ------------------------------------------- | ------------- |
| title | title | string | — | — |
| message | description text | string/Vue.VNode | — | — |
| dangerouslyUseHTMLString | whether `message` is treated as HTML string | boolean | — | false |
| type | notification type | string | success/warning/info/error | — |
| iconClass | custom icon's class. It will be overridden by `type` | string | — | — |
| customClass | custom class name for Notification | string | — | — |
| duration | duration before close. It will not automatically close if set 0 | number | — | 4500 |
| position | custom position | string | top-right/top-left/bottom-right/bottom-left | top-right |
| showClose | whether to show a close button | boolean | — | true |
| onClose | callback function when closed | function | — | — |
| onClick | callback function when notification clicked | function | — | — |
| offset | offset from the top edge of the screen. Every Notification instance of the same moment should have the same offset | number | — | 0 |
| appendTo | set the root element for the notification | string / HTMLElement | - | document.body |
## Methods

View File

@@ -87,4 +87,32 @@ describe('Message on command', () => {
expect(Message.info).toBeInstanceOf(Function)
expect(Message.error).toBeInstanceOf(Function)
})
test('it should appendTo specified HTMLElement', async () => {
const htmlElement = document.createElement('div')
const handle = Message({
appendTo: htmlElement,
})
await rAF()
expect(htmlElement.querySelector(selector)).toBeTruthy()
handle.close()
await rAF()
await nextTick()
expect(htmlElement.querySelector(selector)).toBeFalsy()
})
test('it should appendTo specified selector', async () => {
const htmlElement = document.createElement('div')
htmlElement.classList.add('message-manager')
document.body.appendChild(htmlElement)
const handle = Message({
appendTo: '.message-manager',
})
await rAF()
expect(htmlElement.querySelector(selector)).toBeTruthy()
handle.close()
await rAF()
await nextTick()
expect(htmlElement.querySelector(selector)).toBeFalsy()
})
})

View File

@@ -2,6 +2,7 @@ import { createVNode, render } from 'vue'
import { isVNode } from '@element-plus/utils/util'
import PopupManager from '@element-plus/utils/popup-manager'
import isServer from '@element-plus/utils/isServer'
import { debugWarn } from '@element-plus/utils/error'
import MessageConstructor from './message.vue'
import { messageTypes } from './message'
@@ -38,6 +39,21 @@ const message: MessageFn & Partial<Message> = function (options = {}) {
},
}
let appendTo: HTMLElement | null = document.body
if (options.appendTo instanceof HTMLElement) {
appendTo = options.appendTo
} else if (typeof options.appendTo === 'string') {
appendTo = document.querySelector(options.appendTo)
}
// should fallback to default value with a warning
if (!(appendTo instanceof HTMLElement)) {
debugWarn(
'ElMessage',
'the appendTo option is not an HTMLElement. Falling back to document.body.'
)
appendTo = document.body
}
const container = document.createElement('div')
container.className = `container_${id}`
@@ -60,7 +76,7 @@ const message: MessageFn & Partial<Message> = function (options = {}) {
render(vm, container)
// instances will remove this item when close function gets called. So we do not need to worry about it.
instances.push({ vm })
document.body.appendChild(container.firstElementChild!)
appendTo.appendChild(container.firstElementChild!)
return {
// instead of calling the onClose function directly, setting this value so that we can have the full lifecycle

View File

@@ -62,7 +62,9 @@ export const messageEmits = {
}
export type MessageEmits = typeof messageEmits
export type MessageOptions = Omit<MessageProps, 'id'>
export type MessageOptions = Omit<MessageProps, 'id'> & {
appendTo?: HTMLElement | string
}
export type MessageOptionsTyped = Omit<MessageOptions, 'type'>
export interface MessageHandle {

View File

@@ -80,4 +80,33 @@ describe('Notification on command', () => {
expect(document.querySelector(`.el-icon-${type}`)).toBeDefined()
}
})
test('it should appendTo specified HTMLElement', async () => {
const htmlElement = document.createElement('div')
const handle = Notification({
appendTo: htmlElement,
})
await rAF()
expect(htmlElement.querySelector(selector)).toBeDefined()
handle.close()
await rAF()
await nextTick()
expect(htmlElement.querySelector(selector)).toBeNull()
})
test('it should appendTo specified selector', async () => {
const htmlElement = document.createElement('div')
htmlElement.classList.add('notification-manager')
document.body.appendChild(htmlElement)
const handle = Notification({
appendTo: '.notification-manager',
})
await rAF()
expect(htmlElement.querySelector(selector)).toBeDefined()
handle.close()
await rAF()
await nextTick()
expect(htmlElement.querySelector(selector)).toBeNull()
})
})

View File

@@ -76,7 +76,9 @@ export const notificationEmits = {
}
export type NotificationEmits = typeof notificationEmits
export type NotificationOptions = Omit<NotificationProps, 'id'>
export type NotificationOptions = Omit<NotificationProps, 'id'> & {
appendTo?: HTMLElement | string
}
export type NotificationOptionsTyped = Omit<NotificationOptions, 'type'>
export interface NotificationHandle {

View File

@@ -2,6 +2,7 @@ import { createVNode, render } from 'vue'
import isServer from '@element-plus/utils/isServer'
import PopupManager from '@element-plus/utils/popup-manager'
import { isVNode } from '@element-plus/utils/util'
import { debugWarn } from '@element-plus/utils/error'
import NotificationConstructor from './notification.vue'
import { notificationTypes } from './notification'
@@ -57,6 +58,22 @@ const notify: NotifyFn & Partial<Notify> = function (options = {}) {
},
}
let appendTo: HTMLElement | null = document.body
if (options.appendTo instanceof HTMLElement) {
appendTo = options.appendTo
} else if (typeof options.appendTo === 'string') {
appendTo = document.querySelector(options.appendTo)
}
// should fallback to default value with a warning
if (!(appendTo instanceof HTMLElement)) {
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(
@@ -77,7 +94,7 @@ const notify: NotifyFn & Partial<Notify> = function (options = {}) {
// 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 })
document.body.appendChild(container.firstElementChild!)
appendTo.appendChild(container.firstElementChild!)
return {
// instead of calling the onClose function directly, setting this value so that we can have the full lifecycle