import { createVNode, render } from 'vue' import { defu } from 'defu' import NotificationConstructor from './notification.vue' import type { NotificationOptions, NotificationPlacement, NotificationQueue, } from './types' const notifications: Record = { '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