Никита Круглицкий b3c99a667a Initial commit
2024-11-26 16:15:12 +03:00

133 lines
3.1 KiB
TypeScript

import { createVNode, render } from 'vue'
import { defu } from 'defu'
import NotificationConstructor from './notification.vue'
import type {
NotificationOptions,
NotificationPlacement,
NotificationQueue,
} from './types'
const notifications: Record<NotificationPlacement, NotificationQueue> = {
'top-left': [],
'top-right': [],
'bottom-left': [],
'bottom-right': [],
}
const GAP_SIZE = 16
let SEED = 1
const DEFAULT_OPTIONS: NotificationOptions = {
text: '',
placement: 'top-right',
duration: 5000,
onClose: () => {},
}
const notify = function (options: NotificationOptions, context = null) {
// if (process.server) return { close: () => undefined };
options = defu(options, DEFAULT_OPTIONS)
const orientedNotifications = notifications[options.placement!]
const id = options.id
? `${options.placement!}_${options.id}`
: `notification_${SEED++}`
const idx = orientedNotifications.findIndex(
({ vm }) => vm.component?.props.id === id,
)
if (idx > -1)
return
let verticalOffset = options.offset || 0
notifications[options.placement!].forEach(({ vm }) => {
verticalOffset += (vm.el?.offsetHeight || 0) + GAP_SIZE
})
verticalOffset += GAP_SIZE
const userOnClose = options.onClose
const props = {
...options,
offset: verticalOffset,
id,
onClose: () => {
close(id, options.placement!, userOnClose)
},
}
const container = document.createElement('div')
const vm = createVNode(
NotificationConstructor,
props,
options.text
? {
default: () => options.text,
}
: null,
)
vm.appContext = context ?? notify._context
vm.props!.onDestroy = () => {
render(null, container)
}
render(vm, container)
notifications[options.placement!].push({ vm })
document.body.appendChild(container.firstElementChild!)
return {
close: () => {
vm.component!.exposed!.close()
},
}
}
export function close(
id: NotificationOptions['id'],
placement: NotificationOptions['placement'],
userOnClose: NotificationOptions['onClose'],
) {
const orientedNotifications = notifications[placement!]
const idx = orientedNotifications.findIndex(
({ vm }) => vm.component?.props.id === id,
)
if (idx === -1)
return
const { vm } = orientedNotifications[idx]
if (!vm)
return
userOnClose?.(vm)
const removedHeight = vm.el!.offsetHeight
const verticalPos = placement!.split('-')[0]
orientedNotifications.splice(idx, 1)
if (orientedNotifications.length < 1)
return
for (let i = idx; i < orientedNotifications.length; i++) {
const { el, component } = orientedNotifications[i].vm
const styles = getComputedStyle(el as Element)
const pos = Number.parseInt(styles.getPropertyValue(verticalPos), 10)
component!.props.offset = pos - removedHeight - GAP_SIZE
}
}
export function closeAll() {
for (const orientedNotifications of Object.values(notifications)) {
orientedNotifications.forEach(({ vm }) => {
vm.component!.exposed!.close()
})
}
}
notify.closeAll = closeAll
notify._context = null
export default notify