From 11894307fc86adc77277fc82fdd437c8ae182d18 Mon Sep 17 00:00:00 2001 From: Abrahem Alhofe Date: Tue, 15 Jun 2021 12:33:42 +0200 Subject: [PATCH] feat( default behaviour ): pause notification kill-duration on mouseover and resume on mouseleave - add "should pause on mouseover" test - add "EventListener", "Timer" utilities' classes - public "NotyfNotification.triggerEvent" | to make "NotyfView" trigger "mouseover" event on "NotyfNotification" - add "NotyfEvent.MouseOver" - add "NotyfEvent.MouseLeave" - update "Notyf._pushNotification" = pause "NotyfNotification" timer and resume timer on "mouseleave" = remove "NotyfNotification" on timer "finished" event - trigger "mouseover" event on "NotyfNotification" by "NotyfView" - trigger "mouseleave" event on "NotyfNotification" by "NotyfView" close #110 --- cypress/integration/notyf_spec.js | 26 +++++++++++++++++ src/notyf.models.ts | 2 +- src/notyf.options.ts | 2 ++ src/notyf.ts | 19 ++++++++++--- src/notyf.view.ts | 8 ++++++ src/utils/classes/eventListener.ts | 17 +++++++++++ src/utils/classes/timer.ts | 45 ++++++++++++++++++++++++++++++ 7 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 src/utils/classes/eventListener.ts create mode 100644 src/utils/classes/timer.ts diff --git a/cypress/integration/notyf_spec.js b/cypress/integration/notyf_spec.js index 96dd6a1..ddcc6ea 100644 --- a/cypress/integration/notyf_spec.js +++ b/cypress/integration/notyf_spec.js @@ -54,6 +54,32 @@ context('Notyf', () => { expect(pos.top).to.be.greaterThan(VIEWPORT_HEIGHT / 2); }); }); + + it('should pause on mouseover', () => { + + setConfiguration({ message: 'Notyf 1' }) + cy.get('#success-btn').click() + + setConfiguration({ message: 'Notyf 2' }) + cy.get('#success-btn').click() + + setConfiguration({ message: 'Notyf 3' }) + cy.get('#success-btn').click() + + cy.get('.notyf__toast:nth-child(2)').trigger('mouseover') + + cy.wait(2000) + + cy.get('.notyf').children().should('have.length', 1) + + cy.get('.notyf__toast').trigger('mouseleave') + + cy.wait(2000) + + cy.get('.notyf__toast').should('not.be.exist') + + }) + }); describe('Global custom configuration', () => { diff --git a/src/notyf.models.ts b/src/notyf.models.ts index 943c3b6..2e3d09c 100644 --- a/src/notyf.models.ts +++ b/src/notyf.models.ts @@ -17,7 +17,7 @@ export class NotyfNotification { this.listeners[eventType] = callbacks.concat([cb]); } - private triggerEvent(eventType: NotyfEvent, event?: Event) { + triggerEvent(eventType: NotyfEvent, event?: Event) { const callbacks = this.listeners[eventType] || []; callbacks.forEach((cb) => cb({ target: this, event })); } diff --git a/src/notyf.options.ts b/src/notyf.options.ts index a498591..54e65de 100644 --- a/src/notyf.options.ts +++ b/src/notyf.options.ts @@ -11,6 +11,8 @@ export interface INotyfPosition { export enum NotyfEvent { Dismiss = 'dismiss', Click = 'click', + MouseOver = 'mouseover', + MouseLeave = 'mouseleave' } export interface INotyfIcon { diff --git a/src/notyf.ts b/src/notyf.ts index c97f929..af25712 100644 --- a/src/notyf.ts +++ b/src/notyf.ts @@ -7,6 +7,7 @@ import { NotyfEvent, } from './notyf.options'; import { NotyfView } from './notyf.view'; +import Timer from './utils/classes/timer'; /** * Main controller class. Defines the main Notyf API. @@ -82,12 +83,22 @@ export default class Notyf { } private _pushNotification(notification: NotyfNotification) { - this.notifications.push(notification); + + this.notifications.push( notification ); + const duration = notification.options.duration !== undefined ? notification.options.duration : this.options.duration; - if (duration) { - setTimeout(() => this._removeNotification(notification), duration); - } + + if ( !duration ) return + + const timer = new Timer( duration ); + + notification.on(NotyfEvent.MouseOver, () => timer.pause()); + + notification.on(NotyfEvent.MouseLeave, () => timer.resume()); + + timer.on('finished', () => this._removeNotification(notification) ) + } private _removeNotification(notification: NotyfNotification) { diff --git a/src/notyf.view.ts b/src/notyf.view.ts index a1b3942..40a5be9 100644 --- a/src/notyf.view.ts +++ b/src/notyf.view.ts @@ -196,6 +196,14 @@ export class NotyfView { this.events[NotyfEvent.Click]?.({ target: notification, event }), ); + notificationElem.addEventListener('mouseover', event => + notification.triggerEvent(NotyfEvent.MouseOver, event) + ) + + notificationElem.addEventListener('mouseleave', event => + notification.triggerEvent(NotyfEvent.MouseLeave, event) + ) + // Adjust margins depending on whether its an upper or lower notification const className = this.getYPosition(options) === 'top' ? 'upper' : 'lower'; notificationElem.classList.add(`notyf__toast--${className}`); diff --git a/src/utils/classes/eventListener.ts b/src/utils/classes/eventListener.ts new file mode 100644 index 0000000..4478307 --- /dev/null +++ b/src/utils/classes/eventListener.ts @@ -0,0 +1,17 @@ +type EventCallback = (event: any) => void; + +export default class EventeListener { + protected listeners: Partial> = {}; + + constructor() {} + + public on(eventType: string, cb: EventCallback) { + const callbacks = this.listeners[eventType] || []; + this.listeners[eventType] = callbacks.concat([cb]); + } + + protected triggerEvent(eventType: string, event?: any) { + const callbacks = this.listeners[eventType] || []; + callbacks.forEach((callback: EventCallback) => callback(event)); + } +} diff --git a/src/utils/classes/timer.ts b/src/utils/classes/timer.ts new file mode 100644 index 0000000..2a1e586 --- /dev/null +++ b/src/utils/classes/timer.ts @@ -0,0 +1,45 @@ +import EventListener from './eventListener'; + +export default class Timer extends EventListener { + + private startTime: number = Date.now(); + + private timer: ReturnType; + + private lastTime: number = Date.now(); + + get leftTime() { + return this.duration - (this.lastTime - this.startTime); + } + + constructor(public duration: number) { + super(); + + this.timer = setTimeout(() => { + this.triggerEvent('finished'); + + this.lastTime = Date.now(); + }, duration); + } + + pause() { + this.triggerEvent('pause'); + + clearTimeout(this.timer); + + this.lastTime = Date.now(); + } + + resume() { + this.triggerEvent('resume'); + + clearTimeout(this.timer); + + this.timer = setTimeout(() => { + this.triggerEvent('finished'); + + this.lastTime = Date.now(); + }, this.leftTime); + } + +}