refactor(notification): support vue3

This commit is contained in:
07akioni 2020-09-14 17:38:11 +08:00
parent 50580f5cfc
commit 89a819e344
20 changed files with 447 additions and 454 deletions

View File

@ -6,17 +6,19 @@
:language="lang"
>
<n-message-provider>
<n-layout position="absolute" class="root-layout">
<doc-header
:lang="lang"
:items="flattenedItems"
:env="env"
@lang-change="handleLangChange"
/>
<n-layout class="home-layout" style="top: 64px; overflow: hidden;" position="absolute">
<router-view />
<n-notification-provider>
<n-layout position="absolute" class="root-layout">
<doc-header
:lang="lang"
:items="flattenedItems"
:env="env"
@lang-change="handleLangChange"
/>
<n-layout class="home-layout" style="top: 64px; overflow: hidden;" position="absolute">
<router-view />
</n-layout>
</n-layout>
</n-layout>
</n-notification-provider>
</n-message-provider>
</n-config-provider>
</template>

View File

@ -1,17 +1,20 @@
# 基础用法
```html
<n-button @click="notify1">
<n-button @click="handleClick1">
Wouldn't it be Nice
</n-button>
<n-button @click="notify2">
<n-button @click="handleClick2">
Satisfaction
</n-button>
```
```js
import { h, resolveComponent } from 'vue'
export default {
inject: ['notification', 'message'],
methods: {
notify1 () {
this.$NNotification.open({
handleClick1 () {
this.notification.create({
title: `Wouldn't it be Nice`,
description: 'From the Beach Boys',
content: `Wouldn't it be nice if we were older
@ -25,47 +28,41 @@ In the morning when the day is new
And after having spent the day together
Hold each other close the whole night through`,
meta: '2019-5-27 15:11',
avatar: h =>
h('n-avatar', {
props: {
size: 'small',
round: true,
src:'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg'
}
avatar: () =>
h(resolveComponent('n-avatar'), {
size: 'small',
round: true,
src:'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg'
}),
onAfterHide: () => {
this.$NMessage.success(`Wouldn't it be Nice`)
onAfterLeave: () => {
this.message.success(`Wouldn't it be Nice`)
},
})
},
notify2 () {
handleClick2 () {
let markAsRead = false
const notification = this.$NNotification.open({
const notification = this.notification.create({
title: 'Satisfaction',
content: `I cant get no satisfaction
I cant get no satisfaction
Cause I try and I try and I try and I try
I cant get no, I cant get no`,
meta: '2019-5-27 15:11',
action: h => h(
'n-button',
action: () => h(
resolveComponent('n-button'),
{
props: {
text: true,
type: 'primary'
},
on: {
click: () => {
markAsRead = true
notification.hide()
}
text: true,
type: 'primary',
onClick: () => {
markAsRead = true
notification.destroy()
}
},
['已读']
),
onClose: () => {
if (!markAsRead) {
this.$NMessage.warning('请设为已读')
this.message.warning('请设为已读')
return false
}
}

View File

@ -9,7 +9,10 @@
</n-button>
```
```js
import { h, resolveComponent } from 'vue'
export default {
inject: ['notification'],
data () {
return {
notification: null
@ -17,7 +20,7 @@ export default {
},
methods: {
open () {
this.notification = this.$NNotification.open({
this.notification = this.notification.create({
title: `Wouldn't it be Nice`,
description: 'From the Beach Boys',
content: `Wouldn't it be nice if we were older
@ -31,13 +34,11 @@ In the morning when the day is new
And after having spent the day together
Hold each other close the whole night through`,
meta: '2019-5-27 15:11',
avatar: h =>
h('n-avatar', {
props: {
size: 'small',
round: true,
src:'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg'
}
avatar: () =>
h(resolveComponent('n-avatar'), {
size: 'small',
round: true,
src:'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg'
}),
onClose: () => {
this.notification = null
@ -46,13 +47,9 @@ Hold each other close the whole night through`,
},
change () {
if (this.notification) {
this.notification.content = h => h('img', {
attrs: {
src: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg'
},
style: {
width: '100%'
}
this.notification.content = () => h('img', {
src: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
style: 'width: 100%;'
})
}
}

View File

@ -1,28 +1,30 @@
# 不可关闭
通知可以不能被关闭
```html
<n-button @click="notify('info')">
<n-button @click="handleClick">
不能关闭
</n-button>
```
```js
export default {
inject: ['notification'],
methods: {
notify (type) {
this.$NNotification.open({
title: `你能关掉我吗?`,
handleClick () {
const notification = this.notification
notification.create({
title: '你能关掉我吗?',
duration: 2000,
closable: false,
onAfterHide: () => {
this.$NNotification.open({
onAfterLeave: () => {
notification.create({
title: `哈哈哈哈!`,
duration: 2000,
closable: false,
onAfterHide: () => {
this.$NNotification.open({
onAfterLeave: () => {
notification.create({
title: `你不能`,
duration: 2000,
closable: false,
closable: false
})
}
})

View File

@ -1,21 +1,22 @@
# 持续时间
自动关闭。
```html
<n-button @click="notify('info')">
<n-button @click="handleClick">
持续时间 10000ms
</n-button>
```
```js
export default {
inject: ['notification'],
methods: {
notify (type) {
handleClick () {
let count = 10
const notification = this.$NNotification.open({
const notification = this.notification.create({
title: `平山道 + 雨 = 什么?`,
content: `你有 ${count} 秒来回答这个问题`,
duration: 10000,
closable: false,
onAfterShow: () => {
onAfterEnter: () => {
const minusCount = () => {
count--
notification.content = `你有 ${count} 秒来回答这个问题`
@ -25,8 +26,8 @@ export default {
}
window.setTimeout(minusCount, 1000)
},
onAfterHide: () => {
this.$NNotification.open({
onAfterLeave: () => {
this.notification.create({
title: `答案是平山河`,
content: '这其实连个冷笑话都算不上',
duration: 10000

View File

@ -12,60 +12,60 @@ closable
duration
```
## API
#### $Notification API
#### $NNotification Methods
|名称|类型|说明|
|-|-|-|
|open|`(option: NotificationOption, type: string = 'default') => NotificationEnvironment`|`type` 可以是 `'default'`, `'warning'`, `'info'`, `'success'``'error'`|
|success|`(option: NofiticationOption) => NotificationEnvironment`||
|info|`(option: NofiticationOption) => NotificationEnvironment`||
|warning|`(option: NofiticationOption) => NotificationEnvironment`||
|error|`(option: NofiticationOption) => NotificationEnvironment`||
#### $Notification Properties
### NotificationProvider Props
|名称|类型|默认值|说明|
|-|-|-|-|
|scrollable|`boolean`|`false`||
|scrollable|`boolean`|`true`||
|to|`string \| HTMLElement`|`'body'`||
### NotificationProvider Injection API
#### NotificationProvider Injection Methods
|名称|类型|说明|
|-|-|-|
|create|`(option: NotificationOption) => NotificationReactive`||
|success|`(option: NotificationOption) => NotificationReactive`||
|info|`(option: NotificationOption) => NotificationReactive`||
|warning|`(option: NotificationOption) => NotificationReactive`||
|error|`(option: NotificationOption) => NotificationReactive`||
### NotificationOption API
#### NotificationOption Properties
|名称|类型|默认值|说明|
|-|-|-|-|
|theme|`'light' \| 'dark'`|`null`|如果设定会将该通知的主题设为该主题,如果没有设定则全局主题则取决于调用位置(它工作起来和 <n-a to="n-message#about-theme">$NMessage 的主题</n-a>比较像,在大多数情况下你不用为此而操心)|
|avatar|`() => VNode \| Array<VNode>`|`null`|可以是 render 函数|
|title|`string \| (() => VNode \| Array<VNode>)`|`null`|可以是 render 函数|
|description|`string \| (() => VNode \| Array<VNode>)`|`null`|可以是 render 函数|
|content|`string \| (() => VNode \| Array<VNode>)`|`null`|可以是 render 函数|
|meta|`string \| (() => VNode \| Array<VNode>)`|`null`|可以是 render 函数|
|action|`string \| (() => VNode \| Array<VNode>)`|`null`|可以是 render 函数|
|avatar|`() => VNode \| Array<VNode>`|`null`|可以是 render 函数|
|closable|`boolean`|`true`||
|onClose|`() => boolean \| Promise<boolean> \| any`|`() => {}`|关闭通知的回调。返回 `false`、Promise resolve `false` 或者 reject 会取消这次关闭|
|onAfterHide|`Function`|`null`||
|onAfterShow|`Function`|`null`||
|content|`string \| (() => VNode \| Array<VNode>)`|`null`|可以是 render 函数|
|description|`string \| (() => VNode \| Array<VNode>)`|`null`|可以是 render 函数|
|duration|`number`|`null`|如果没有设定则不会自动关闭,单位毫秒|
|meta|`string \| (() => VNode \| Array<VNode>)`|`null`|可以是 render 函数|
|theme|`'light' \| 'dark' \| null \| string`|`null`||
|title|`string \| (() => VNode \| Array<VNode>)`|`null`|可以是 render 函数|
|onAfterEnter|`Function`|`null`||
|onAfterLeave|`Function`|`null`||
|onClose|`() => boolean \| Promise<boolean>`|`() => {}`|关闭通知的回调。返回 `false`、Promise resolve `false` 或者 reject 会取消这次关闭|
|onLeave|`Function`||
### NotificationEnvironment API
#### NotificationEnvironment Properties
### NotificationReactive API
#### NotificationReactive Properties
NofiticationEnvironment 实例的属性可以被动态改变。
|名称|类型|说明|
|-|-|-|
|theme|`'light' \| 'dark'`|如果设定会将该通知的主题设为该主题,如果没有设定则全局主题则取决于调用位置(它工作起来和 <n-a to="n-message#about-theme">$NMessage 的主题</n-a>比较像,在大多数情况下你不用为此而操心)|
|avatar|`() => VNode \| Array<VNode>`|可以是 render 函数|
|title|`string \| (() => VNode \| Array<VNode>)`|可以是 render 函数|
|description|`string \| (() => VNode \| Array<VNode>)`|可以是 render 函数|
|content|`string \| (() => VNode \| Array<VNode>)`|可以是 render 函数|
|meta|`string \| (() => VNode \| Array<VNode>)`|可以是 render 函数|
|action|`string \| (() => VNode \| Array<VNode>)`|可以是 render 函数|
|avatar|`() => VNode \| Array<VNode>`|可以是 render 函数|
|closable|`boolean`||
|onClose|`(next: function) => any`|点击了关闭按钮的回调。只有调用了 next 通知才会被关闭|
|onHide|`Function`||
|onAfterHide|`Function`||
|onAfterShow|`Function`||
|content|`string \| (() => VNode \| Array<VNode>)`|可以是 render 函数|
|description|`string \| (() => VNode \| Array<VNode>)`|可以是 render 函数|
|meta|`string \| (() => VNode \| Array<VNode>)`|可以是 render 函数|
|theme|`'light' \| 'dark' \| null \| string`||
|title|`string \| (() => VNode \| Array<VNode>)`|可以是 render 函数|
|onAfterEnter|`Function`||
|onAfterLeave|`Function`||
|onClose|`() => boolean \| Promise<boolean>`|`() => {}`|关闭通知的回调。返回 `false`、Promise resolve `false` 或者 reject 会取消这次关闭|
|onLeave|`Function`||
#### NotificationEnvironment Methods
#### NotificationReactive Methods
|名称|类型|说明|
|-|-|-|
|hide|`()`||
|destroy|`()`|销毁该通知|

View File

@ -1,16 +1,27 @@
# 可滚动
如果有太多信息,你可以通过设定 `$NNotification.scrollable = true` 让他们变得可以滚动。但是在那种情况下,通知会比他们看起来的多占据一点点空间,会挡住一些通知外面离通知很近的鼠标操作。如果你不想要这个特性,什么都不做就好
如果有太多信息,通知的容器是可以滚动的。但是在那种情况下,通知会比他们看起来的多占据一点点空间,会挡住一些通知外面离通知很近的鼠标操作。如果你不想要这个特性,你可以通过设定 `<n-notification-provider :scrollable="false" />` 来使通知不可滚动
改变这个属性会导致已经存在全部通知被清空,确保你在合适的时机修改了这个属性。
```html
<n-button @click="handleClick(true)">可以滚动(点完多开几个通知)</n-button>
<n-button @click="handleClick(false)">不可以滚动</n-button>
<n-button @click="handleClick">看看这个东西怎么滚动</n-button>
```
```js
export default {
inject: ['notification'],
methods: {
handleClick (scrollable) {
this.$NNotification.scrollable = scrollable
Array.apply(null, { length: 5 }).forEach(
notification => this.notification.create({
title: '很多个通知',
content: `试着滚起来
试着滚起来
试着滚起来
试着滚起来
试着滚起来
试着滚起来
试着滚起来`
})
)
}
}
}

View File

@ -15,10 +15,11 @@
```
```js
export default {
inject: ['notification'],
methods: {
notify (type) {
this.$NNotification[type]({
content: `说点啥呢`,
this.notification[type]({
content: '说点啥呢',
meta: '想不出来'
})
}

View File

@ -44,4 +44,3 @@ export function useIsMounted () {
}
export { default as useLastClickPosition } from './use-last-click-position'
export { default as useContainer } from './use-container'

View File

@ -1,31 +0,0 @@
import { computed } from 'vue'
export default function useContainer (
toRef,
containerClassNameRef
) {
const getContainerTarget = computed(
() => typeof toRef.value === 'string' ? () => document.querySelector(toRef.value) : () => toRef.value
)
const getContainer = computed(
() => () => getContainerTarget.value().querySelector('.' + containerClassNameRef.value)
)
const mountIfNotExist = () => {
const targetEl = getContainerTarget.value()
if (!targetEl.querySelector('.' + containerClassNameRef.value)) {
const containerEl = document.createElement('div')
containerEl.className = containerClassNameRef.value
targetEl.appendChild(containerEl)
}
}
const unmountIfEmpty = () => {
const container = getContainer.value()
if (!container.childElementCount) {
container.parentNode.removeChild(container)
}
}
return [
mountIfNotExist,
unmountIfEmpty
]
}

View File

@ -41,7 +41,7 @@ import Log from './log'
import Menu from './menu'
import Message from './message'
import Modal from './modal'
// import Notification from './notification'
import Notification from './notification'
import Pagination from './pagination'
import Popconfirm from './popconfirm'
import Popselect from './popselect'
@ -232,7 +232,7 @@ export default create({
Modal,
Input,
Message,
// Notification,
Notification,
Pagination,
Tooltip,
Popup,

View File

@ -1,9 +1,7 @@
import Notification from './src/NotificationPlugin'
import { install } from '../_utils/naive/installThemeAwarableProperty'
import NotificationProvider from './src/NotificationProvider'
Notification.install = function (app, naive) {
Notification.Vue = app
install(app, Notification, `$${naive.componentPrefix}Notification`)
app.component(naive.componentPrefix + NotificationProvider.name, NotificationProvider)
}
export default Notification

View File

@ -1,23 +1,23 @@
<template>
<div
:class="{
[`n-${theme}-theme`]: theme,
'n-notification--no-avatar': noAvatar,
[`n-${syntheticTheme}-theme`]: syntheticTheme,
'n-notification--closable': closable,
'n-notification--show-avatar': showAvatar,
[`n-notification--${type}-type`]: type
}"
class="n-notification"
>
<div
v-if="!noAvatar"
v-if="showAvatar"
class="n-notification__avatar"
>
<render v-if="avatar" :render="avatar" />
<n-icon v-else>
<md-information-circle v-if="type === 'info'" />
<md-alert v-else-if="type === 'warning'" />
<md-close-circle v-else-if="type === 'error'" />
<md-checkmark-circle v-else-if="type === 'success'" />
<info-icon v-if="type === 'info'" />
<warning-icon v-else-if="type === 'warning'" />
<error-icon v-else-if="type === 'error'" />
<success-icon v-else-if="type === 'success'" />
</n-icon>
</div>
<div
@ -26,7 +26,7 @@
@click="handleCloseClick"
>
<n-icon>
<md-close />
<close-icon />
</n-icon>
</div>
<div
@ -56,32 +56,32 @@
</template>
<script>
import withapp from '../../_mixins/withapp'
import themeable from '../../_mixins/themeable'
import NIcon from '../../icon'
import mdClose from '../../_icons/md-close'
import mdCheckmarkCircle from '../../_icons/md-checkmark-circle'
import mdAlert from '../../_icons/md-alert'
import mdInformationCircle from '../../_icons/md-information-circle'
import mdCloseCircle from '../../_icons/md-close-circle'
import render from '../../_utils/vue/render'
import asthemecontext from '../../_mixins/asthemecontext'
import usecssr from '../../_mixins/usecssr'
import render from '../../_utils/vue/render'
import styles from './styles'
import NIcon from '../../icon'
import CloseIcon from '../../_icons/md-close'
import SuccessIcon from '../../_icons/md-checkmark-circle'
import WarningIcon from '../../_icons/md-alert'
import InfoIcon from '../../_icons/md-information-circle'
import ErrorIcon from '../../_icons/md-close-circle'
export default {
name: 'Notification',
components: {
NIcon,
mdClose,
render,
mdCheckmarkCircle,
mdAlert,
mdInformationCircle,
mdCloseCircle
CloseIcon,
SuccessIcon,
WarningIcon,
InfoIcon,
ErrorIcon
},
cssrName: 'Notification',
mixins: [
withapp,
themeable,
asthemecontext,
usecssr(styles)
],
props: {
@ -96,7 +96,7 @@ export default {
default: 'default'
},
avatar: {
type: [Function],
type: Function,
default: null
},
title: {
@ -116,18 +116,22 @@ export default {
default: null
},
action: {
type: [Object, Function],
type: Function,
default: null
},
onClose: {
type: Function,
default: () => {}
}
},
computed: {
noAvatar () {
return !(this.avatar || this.type !== 'default')
showAvatar () {
return this.avatar || this.type !== 'default'
}
},
methods: {
handleCloseClick () {
this.$emit('close')
this.onClose()
}
}
}

View File

@ -5,7 +5,12 @@
'n-notification-container--scrollable': scrollable
}"
>
<n-scrollbar v-if="scrollable" ref="scrollbar" :theme="theme" />
<n-scrollbar
v-if="scrollable"
>
<slot />
</n-scrollbar>
<slot v-else />
</div>
</template>
@ -13,18 +18,14 @@
import NScrollbar from '../../scrollbar'
export default {
name: 'NotificationContainer',
components: {
NScrollbar
},
props: {
scrollable: {
type: Boolean,
default: true
}
},
data () {
return {
theme: null
required: true
}
}
}

View File

@ -0,0 +1,178 @@
import { nextTick, Transition, h } from 'vue'
import NNotification from './Notification'
export default {
name: 'NotificationEnvironment',
props: {
duration: {
type: Number,
default: undefined
},
type: {
type: String,
default: undefined
},
theme: {
type: String,
default: undefined
},
avatar: {
type: Function,
default: undefined
},
title: {
type: [String, Function],
default: undefined
},
description: {
type: [String, Function],
default: undefined
},
content: {
type: [String, Function],
default: undefined
},
meta: {
type: [String, Function],
default: undefined
},
action: {
type: Function,
default: undefined
},
closable: {
type: Boolean,
default: undefined
},
onClose: {
type: Function,
default: () => {}
},
onHide: {
type: Function,
default: () => {}
},
onAfterEnter: {
type: Function,
default: () => {}
},
onAfterLeave: {
type: Function,
default: () => {}
},
// private
onInternalAfterLeave: {
type: Function,
required: true
},
// deprecated
onAfterShow: {
type: Function,
default: () => {}
},
onAfterHide: {
type: Function,
default: () => {}
}
},
data () {
return {
show: true,
timerId: null
}
},
mounted () {
if (this.duration) {
this.timerId = window.setTimeout(this.hide, this.duration)
}
},
methods: {
hide () {
const {
timerId
} = this
this.show = false
if (timerId) {
window.clearTimeout(timerId)
}
},
handleBeforeEnter (el) {
nextTick(() => {
el.style.height = el.offsetHeight + 'px'
el.style.maxHeight = 0
el.style.transition = 'none'
void el.offsetHeight
el.style.transition = null
el.style.maxHeight = el.style.height
})
},
handleAfterEnter (el) {
el.style.height = null
el.style.maxHeight = null
this.onAfterEnter(this)
// deprecated
this.onAfterShow(this)
},
handleBeforeLeave (el) {
el.style.maxHeight = el.offsetHeight + 'px'
el.style.height = el.offsetHeight + 'px'
void el.offsetHeight
},
handleLeave (el) {
this.onHide()
el.style.maxHeight = 0
void el.offsetHeight
},
handleAfterLeave () {
const {
onAfterLeave,
onInternalAfterLeave,
onAfterHide
} = this
onAfterLeave()
onInternalAfterLeave(this._.vnode.key)
// deprecated
onAfterHide()
},
handleClose () {
Promise
.resolve(
this.onClose()
)
.then(feedback => {
if (feedback === false) return
this.hide()
})
},
// deprecated
deactivate () {
this.hide()
}
},
render () {
return h(Transition, {
name: 'n-notification-transition',
appear: true,
onBeforeEnter: this.handleBeforeEnter,
onAfterEnter: this.handleAfterEnter,
onBeforeLeave: this.handleBeforeLeave,
onLeave: this.handleLeave,
onAfterLeave: this.handleAfterLeave
}, {
default: () => [
this.show ? h(NNotification, {
type: this.type,
theme: this.theme,
avatar: this.avatar,
title: this.title,
description: this.description,
content: this.content,
meta: this.meta,
action: this.action,
closable: this.closable,
onClose: this.handleClose
}) : null
]
})
}
}

View File

@ -1,127 +0,0 @@
<script>
import NNotification from './Notification'
export default {
name: 'NNotificationEnvironment',
props: {
onDestroy: {
type: Function,
required: true
},
duration: {
type: Number,
default: null
}
},
data () {
return {
type: 'default',
active: true,
theme: null,
inheritedTheme: null,
avatar: null,
title: null,
description: null,
content: null,
meta: null,
action: null,
closable: true,
onClose: () => {},
onHide: () => {},
onAfterShow: () => {},
onAfterHide: () => {}
}
},
computed: {
syntheticTheme () {
return this.theme || this.inheritedTheme
}
},
mounted () {
if (this.duration !== null) {
window.setTimeout(this.hide, this.duration)
}
},
methods: {
deactivate () {
this.hide()
},
hide () {
this.active = false
},
handleEnter () {
this.$nextTick().then(() => {
this.$el.style.height = this.$el.offsetHeight + 'px'
this.$el.style.maxHeight = 0
this.$el.style.transition = 'none'
this.$el.getBoundingClientRect()
this.$el.style.transition = null
this.$el.style.maxHeight = this.$el.style.height
})
},
handleAfterEnter () {
this.$el.style.height = null
this.$el.style.maxHeight = null
this.onAfterShow(this)
},
handleBeforeLeave () {
this.$el.style.maxHeight = this.$el.offsetHeight + 'px'
this.$el.style.height = this.$el.offsetHeight + 'px'
this.$el.getBoundingClientRect()
},
handleLeave () {
this.onHide()
this.$el.style.maxHeight = 0
this.$el.getBoundingClientRect()
},
handleAfterLeave () {
this.onDestroy(this)
this.onAfterHide()
},
handleClose () {
Promise
.resolve(
this.onClose()
)
.then(feedback => {
if (feedback === false) return
this.hide()
})
}
},
render (h) {
return h('transition', {
props: {
name: 'n-notification-transition',
appear: true
},
on: {
'enter': this.handleEnter,
'after-enter': this.handleAfterEnter,
'before-leave': this.handleBeforeLeave,
'leave': this.handleLeave,
'after-leave': this.handleAfterLeave
}
}, [
this.active ? h(NNotification, {
props: {
type: this.type,
theme: this.syntheticTheme,
avatar: this.avatar,
title: this.title,
description: this.description,
content: this.content,
meta: this.meta,
action: this.action,
closable: this.closable
},
on: {
close: () => {
this.handleClose()
}
}
}) : null
])
}
}
</script>

View File

@ -1,136 +0,0 @@
import NNotificationEnvironment from './NotificationEnvironment'
import NNotificationContainer from './NotificationContainer'
function mountNotificationContainer () {
let container = Notification.container
if (!container) {
container = new Notification.Vue(Object.assign(NNotificationContainer, {
propsData: {
scrollable: Notification.scrollable
}
}))
container.$mount()
Notification.container = container
document.body.appendChild(container.$el)
}
return container
}
function unmountNotificationContainer () {
const container = Notification.container
if (Notification.instances.size) {
const instances = Array.from(Notification.instances)
instances.forEach(unmountNotification)
}
if (container) {
const el = container.$el
if (el && el.parentElement) {
el.parentElement.removeChild(el)
}
container.$destroy()
Notification.container = null
}
}
function createNotification (option) {
const instance = new Notification.Vue(Object.assign(
NNotificationEnvironment,
{
propsData: {
onDestroy: unmountNotification,
duration: option.duration
}
}
))
updateNotification(instance, option)
return instance
}
function mountNotification (instance) {
if (!Notification.container) {
throw new Error('[naive-ui/notification]: container not exist when try to mount notification')
}
Notification.instances.add(instance)
instance.$mount()
const el = instance.$el
if (Notification.scrollable) {
const slot = Notification.container.$refs.scrollbar.$refs.scrollContent
slot.appendChild(el)
} else {
const slot = Notification.container.$el
slot.appendChild(el)
}
}
function unmountNotification (instance) {
Notification.instances.delete(instance)
const el = instance.$el
if (el && el.parentElement) {
el.parentElement.removeChild(el)
}
instance.$destroy()
if (!Notification.instances.size) {
unmountNotificationContainer()
}
}
function updateNotification (instance, option) {
Object.keys(option).forEach(key => {
if (instance.hasOwnProperty(key)) {
instance[key] = option[key]
}
})
}
const Notification = {
theme: null,
inheritedTheme: null,
instances: new Set(),
container: null,
_scrollable: false,
get scrollable () {
return Notification._scrollable
},
set scrollable (value) {
if (value !== Notification._scrollable) {
Notification._scrollable = value
unmountNotificationContainer()
}
},
handleThemeChange (theme) {
const container = Notification.container
Notification.inheritedTheme = theme
const syntheticTheme = Notification.theme || Notification.inheritedTheme
if (container) {
container.theme = syntheticTheme
}
Notification.instances.forEach(instance => {
instance.inheritedTheme = syntheticTheme
})
},
open (options, type = 'default') {
mountNotificationContainer()
const syntheticTheme = Notification.theme || Notification.inheritedTheme
if (Notification.container && syntheticTheme) {
Notification.container.theme = syntheticTheme
}
const notificationOptions = { type, ...options, inheritedTheme: syntheticTheme }
const instance = createNotification(notificationOptions)
mountNotification(instance)
return instance
},
success (option) {
return this.open(option, 'success')
},
info (option) {
return this.open(option, 'info')
},
warning (option) {
return this.open(option, 'warning')
},
error (option) {
return this.open(option, 'error')
}
}
export default Notification

View File

@ -0,0 +1,92 @@
import { Fragment, h, Teleport, reactive, ref } from 'vue'
import createId from '../../_utils/vue/createId'
import NotificationContainer from './NotificationContainer'
import NotificationEnvironment from './NotificationEnvironment'
import omit from '../../_utils/vue/omit'
export default {
name: 'NotificationProvider',
provide () {
return {
notification: {
create: this.create,
info: this.info,
success: this.success,
warning: this.warning,
error: this.error
}
}
},
props: {
to: {
type: [String, Object],
default: 'body'
},
scrollable: {
type: Boolean,
default: true
}
},
setup () {
const notificationListRef = ref([])
return {
notificationList: notificationListRef
}
},
methods: {
create (options) {
const key = createId()
const notificationReactive = reactive({
...options,
key,
destroy: () => this.$refs[`n-notification-${key}`].hide()
})
this.notificationList.push(notificationReactive)
return notificationReactive
},
...[
'info',
'success',
'warning',
'error'
].reduce((api, type) => {
api[type] = function (options) {
return this.create({ ...options, type })
}
return api
}, {}),
handleAfterLeave (key) {
const { notificationList } = this
notificationList.splice(
notificationList.findIndex(notification => notification.key === key),
1
)
},
// deprecated
open (...args) {
return this.create(...args)
}
},
render () {
return h(Fragment, null, [
h(Teleport, {
to: this.to
}, [
this.notificationList.length ? h(NotificationContainer, {
scrollable: this.scrollable
}, {
default: () => {
return this.notificationList.map(
notification => h(NotificationEnvironment, {
ref: `n-notification-${notification.key}`,
...omit(notification, ['destroy']),
onInternalAfterLeave: this.handleAfterLeave
})
)
}
}) : null
]),
this.$slots.default()
])
}
}

View File

@ -109,24 +109,24 @@ export default c([
// TODO: refactor type styles & transition
['success', 'info', 'warning', 'error', 'default']
.map(type => typeStyle(type, props.$local[createKey('iconColor', type)])),
c('&-transition-enter, &-transition-leave-to', {
c('&-transition-enter-from, &-transition-leave-to', {
raw: `
opacity: 0;
margin-bottom: 0;
transform: translateX(calc(100% + 16px));
`
}),
c('&-transition-leave, &-transition-enter-to', {
c('&-transition-leave-from, &-transition-enter-to', {
raw: `
opacity: 1;
transform: translateX(0);
`
}),
cM('no-avatar', [
cM('show-avatar', [
cB('notification-main', {
raw: `
margin-left: 8px;
width: calc(100% - 8px);
margin-left: 40px;
width: calc(100% - 40px);
`
})
]),
@ -188,8 +188,8 @@ export default c([
box-sizing: border-box;
display: flex;
flex-direction: column;
margin-left: 40px;
width: calc(100% - 40px);
margin-left: 8px;
width: calc(100% - 8px);
`
}, [
cB('notification-main-footer', {

View File

@ -52,7 +52,7 @@ placeable 进行了大调整
- deprecate `onAfterHide`, new `onAfterLeave`
- remove `hide` on message instance, new `destroy`
- TODO: update enUS docs
- [ ] modal
- [x] modal
- rewrite with teleport
- deprecate v-model show hide
- add prop `display-directive`
@ -60,6 +60,10 @@ placeable 进行了大调整
- deprecate `overlay-style`, use `body-style`
- TODO: update docs, scrollbar mouseup
- [ ] notification
- deprecate `open`, use `create`
- deprecate `onHide`, use `onLeave`
- deprecate `onAfterShow`, use `onAfterEnter`
- deprecate `onAfterHide`, use `onAfterHide`
- [ ] pagination
- [ ] popconfirm
- [ ] popover