UNPKG

noty

Version:

Noty is a dependency-free notification library that makes it easy to create alert - success - error - warning - information - confirmation messages as an alternative the standard alert dialog. Each notification is added to a queue. (Optional)

420 lines (360 loc) 8.57 kB
import * as Utils from 'utils' export let PageHidden = false export let DocModalCount = 0 const DocTitleProps = { originalTitle: null, count: 0, changed: false, timer: -1 } export const docTitle = { increment: () => { DocTitleProps.count++ docTitle._update() }, decrement: () => { DocTitleProps.count-- if (DocTitleProps.count <= 0) { docTitle._clear() return } docTitle._update() }, _update: () => { let title = document.title if (!DocTitleProps.changed) { DocTitleProps.originalTitle = title document.title = `(${DocTitleProps.count}) ${title}` DocTitleProps.changed = true } else { document.title = `(${DocTitleProps.count}) ${DocTitleProps.originalTitle}` } }, _clear: () => { if (DocTitleProps.changed) { DocTitleProps.count = 0 document.title = DocTitleProps.originalTitle DocTitleProps.changed = false } } } export const DefaultMaxVisible = 5 export const Queues = { global: { maxVisible: DefaultMaxVisible, queue: [] } } export const Store = {} export let Defaults = { type: 'alert', layout: 'topRight', theme: 'mint', text: '', timeout: false, progressBar: true, closeWith: ['click'], animation: { open: 'noty_effects_open', close: 'noty_effects_close' }, id: false, force: false, killer: false, queue: 'global', container: false, buttons: [], callbacks: { beforeShow: null, onShow: null, afterShow: null, onClose: null, afterClose: null, onClick: null, onHover: null, onTemplate: null }, sounds: { sources: [], volume: 1, conditions: [] }, titleCount: { conditions: [] }, modal: false, visibilityControl: false } /** * @param {string} queueName * @return {object} */ export function getQueueCounts (queueName = 'global') { let count = 0 let max = DefaultMaxVisible if (Queues.hasOwnProperty(queueName)) { max = Queues[queueName].maxVisible Object.keys(Store).forEach(i => { if (Store[i].options.queue === queueName && !Store[i].closed) count++ }) } return { current: count, maxVisible: max } } /** * @param {Noty} ref * @return {void} */ export function addToQueue (ref) { if (!Queues.hasOwnProperty(ref.options.queue)) { Queues[ref.options.queue] = {maxVisible: DefaultMaxVisible, queue: []} } Queues[ref.options.queue].queue.push(ref) } /** * @param {Noty} ref * @return {void} */ export function removeFromQueue (ref) { if (Queues.hasOwnProperty(ref.options.queue)) { const queue = [] Object.keys(Queues[ref.options.queue].queue).forEach(i => { if (Queues[ref.options.queue].queue[i].id !== ref.id) { queue.push(Queues[ref.options.queue].queue[i]) } }) Queues[ref.options.queue].queue = queue } } /** * @param {string} queueName * @return {void} */ export function queueRender (queueName = 'global') { if (Queues.hasOwnProperty(queueName)) { const noty = Queues[queueName].queue.shift() if (noty) noty.show() } } /** * @return {void} */ export function queueRenderAll () { Object.keys(Queues).forEach(queueName => { queueRender(queueName) }) } /** * @param {Noty} ref * @return {void} */ export function ghostFix (ref) { const ghostID = Utils.generateID('ghost') let ghost = document.createElement('div') ghost.setAttribute('id', ghostID) Utils.css(ghost, { height: Utils.outerHeight(ref.barDom) + 'px' }) ref.barDom.insertAdjacentHTML('afterend', ghost.outerHTML) Utils.remove(ref.barDom) ghost = document.getElementById(ghostID) Utils.addClass(ghost, 'noty_fix_effects_height') Utils.addListener(ghost, Utils.animationEndEvents, () => { Utils.remove(ghost) }) } /** * @param {Noty} ref * @return {void} */ export function build (ref) { findOrCreateContainer(ref) const markup = `<div class="noty_body">${ref.options.text}</div>${buildButtons(ref)}<div class="noty_progressbar"></div>` ref.barDom = document.createElement('div') ref.barDom.setAttribute('id', ref.id) Utils.addClass( ref.barDom, `noty_bar noty_type__${ref.options.type} noty_theme__${ref.options.theme}` ) ref.barDom.innerHTML = markup fire(ref, 'onTemplate') } /** * @param {Noty} ref * @return {boolean} */ export function hasButtons (ref) { return !!(ref.options.buttons && Object.keys(ref.options.buttons).length) } /** * @param {Noty} ref * @return {string} */ function buildButtons (ref) { if (hasButtons(ref)) { let buttons = document.createElement('div') Utils.addClass(buttons, 'noty_buttons') Object.keys(ref.options.buttons).forEach(key => { buttons.appendChild(ref.options.buttons[key].dom) }) ref.options.buttons.forEach(btn => { buttons.appendChild(btn.dom) }) return buttons.outerHTML } return '' } /** * @param {Noty} ref * @return {void} */ export function handleModal (ref) { if (ref.options.modal) { if (DocModalCount === 0) { createModal(ref) } DocModalCount++ } } /** * @param {Noty} ref * @return {void} */ export function handleModalClose (ref) { if (ref.options.modal && DocModalCount > 0) { DocModalCount-- if (DocModalCount <= 0) { const modal = document.querySelector('.noty_modal') if (modal) { Utils.removeClass(modal, 'noty_modal_open') Utils.addClass(modal, 'noty_modal_close') Utils.addListener(modal, Utils.animationEndEvents, () => { Utils.remove(modal) }) } } } } /** * @return {void} */ function createModal () { const body = document.querySelector('body') const modal = document.createElement('div') Utils.addClass(modal, 'noty_modal') body.insertBefore(modal, body.firstChild) Utils.addClass(modal, 'noty_modal_open') Utils.addListener(modal, Utils.animationEndEvents, () => { Utils.removeClass(modal, 'noty_modal_open') }) } /** * @param {Noty} ref * @return {void} */ function findOrCreateContainer (ref) { if (ref.options.container) { ref.layoutDom = document.querySelector(ref.options.container) return } const layoutID = `noty_layout__${ref.options.layout}` ref.layoutDom = document.querySelector(`div#${layoutID}`) if (!ref.layoutDom) { ref.layoutDom = document.createElement('div') ref.layoutDom.setAttribute('id', layoutID) ref.layoutDom.setAttribute('role', 'alert') ref.layoutDom.setAttribute('aria-live', 'polite') Utils.addClass(ref.layoutDom, 'noty_layout') document.querySelector('body').appendChild(ref.layoutDom) } } /** * @param {Noty} ref * @return {void} */ export function queueClose (ref) { if (ref.options.timeout) { if (ref.options.progressBar && ref.progressDom) { Utils.css(ref.progressDom, { transition: `width ${ref.options.timeout}ms linear`, width: '0%' }) } clearTimeout(ref.closeTimer) ref.closeTimer = setTimeout( () => { ref.close() }, ref.options.timeout ) } } /** * @param {Noty} ref * @return {void} */ export function dequeueClose (ref) { if (ref.options.timeout && ref.closeTimer) { clearTimeout(ref.closeTimer) ref.closeTimer = -1 if (ref.options.progressBar && ref.progressDom) { Utils.css(ref.progressDom, { transition: 'width 0ms linear', width: '100%' }) } } } /** * @param {Noty} ref * @param {string} eventName * @return {void} */ export function fire (ref, eventName) { if (ref.listeners.hasOwnProperty(eventName)) { ref.listeners[eventName].forEach(cb => { if (typeof cb === 'function') { cb.apply(ref) } }) } } /** * @param {Noty} ref * @return {void} */ export function openFlow (ref) { fire(ref, 'afterShow') queueClose(ref) Utils.addListener(ref.barDom, 'mouseenter', () => { dequeueClose(ref) }) Utils.addListener(ref.barDom, 'mouseleave', () => { queueClose(ref) }) } /** * @param {Noty} ref * @return {void} */ export function closeFlow (ref) { delete Store[ref.id] ref.closing = false fire(ref, 'afterClose') Utils.remove(ref.barDom) if ( ref.layoutDom.querySelectorAll('.noty_bar').length === 0 && !ref.options.container ) { Utils.remove(ref.layoutDom) } if ( Utils.inArray('docVisible', ref.options.titleCount.conditions) || Utils.inArray('docHidden', ref.options.titleCount.conditions) ) { docTitle.decrement() } queueRender(ref.options.queue) }