GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat(notifications): hold auto-close while the mouse is over a notifi… · phcode-dev/phoenix@a40ab64

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • src/widgets
    • NotificationUI.js
  • test/spec
    • NotificationUI-test.js
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/widgets/NotificationUI.js‎

Copy file name to clipboardExpand all lines: src/widgets/NotificationUI.js
+40-8Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,34 @@ define(function (require, exports, module) {
110110
* @type {Object}
111111
*/
112112

113+
/**
114+
* Close after the given seconds, holding off while the mouse is over the notification so a user
115+
* reading it or reaching for one of its actions does not lose it. The countdown restarts in
116+
* full once the mouse leaves.
117+
* @param {jQuery} $notification - The notification element.
118+
* @param {number} seconds - Time to wait before closing.
119+
* @param {function} closeFn - Closes the notification with the timeout reason.
120+
* @private
121+
*/
122+
function _autoCloseUnlessHovered($notification, seconds, closeFn) {
123+
let timer = null;
124+
function stop() {
125+
if (timer) {
126+
clearTimeout(timer);
127+
timer = null;
128+
}
129+
}
130+
function start() {
131+
stop();
132+
timer = setTimeout(function () {
133+
timer = null;
134+
closeFn();
135+
}, seconds * 1000);
136+
}
137+
$notification.on("mouseenter", stop).on("mouseleave", start);
138+
start();
139+
}
140+
113141
/**
114142
* @constructor
115143
* @private
@@ -211,6 +239,7 @@ define(function (require, exports, module) {
211239
* * `allowedPlacements` - Optional String array with values restricting where the notification will be shown.
212240
* Values can be a mix of `['top', 'bottom', 'left', 'right']`
213241
* * `autoCloseTimeS` - Time in seconds after which the notification should be auto closed. Default is never.
242+
* The countdown pauses while the mouse is over the notification.
214243
* * `dismissOnClick` - when clicked, the notification is closed. Default is true(dismiss).
215244
* * `toastStyle` - To style the toast notification for error, warning, info etc. Can be
216245
* one of `NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.*` or your own css class name.
@@ -229,9 +258,9 @@ define(function (require, exports, module) {
229258
let notification = (new Notification($tooltip, NOTIFICATION_TYPE_ARROW));
230259

231260
if(options.autoCloseTimeS){
232-
setTimeout(()=>{
261+
_autoCloseUnlessHovered($tooltip, options.autoCloseTimeS, function () {
233262
notification.close(CLOSE_REASON.TIMEOUT);
234-
}, options.autoCloseTimeS * 1000);
263+
});
235264
}
236265

237266
if(options.dismissOnClick){
@@ -352,6 +381,7 @@ define(function (require, exports, module) {
352381
* @param {{dismissOnClick, autoCloseTimeS, toastStyle, instantOpen}} [options] optional, supported
353382
* * options are:
354383
* * `autoCloseTimeS` - Time in seconds after which the notification should be auto closed. Default is never.
384+
* The countdown pauses while the mouse is over the notification.
355385
* * `dismissOnClick` - when clicked, the notification is closed. Default is true(dismiss).
356386
* * `toastStyle` - To style the toast notification for error, warning, info etc. Can be
357387
* one of `NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.*` or your own css class name.
@@ -387,9 +417,9 @@ define(function (require, exports, module) {
387417
}, 0);
388418

389419
if(options.autoCloseTimeS){
390-
setTimeout(()=>{
420+
_autoCloseUnlessHovered($NotificationPopup, options.autoCloseTimeS, function () {
391421
notification.close(CLOSE_REASON.TIMEOUT);
392-
}, options.autoCloseTimeS * 1000);
422+
});
393423
}
394424

395425
if(options.dismissOnClick){
@@ -416,6 +446,7 @@ define(function (require, exports, module) {
416446
* @param {string|Element} template HTML string or DOM Element for the toast content.
417447
* @param {Object} [options] optional, supported options:
418448
* * `autoCloseTimeS` - Time in seconds after which the toast auto-closes. Default is 5.
449+
* The countdown pauses while the mouse is over the toast.
419450
* * `dismissOnClick` - If true, clicking the toast dismisses it. Default is true.
420451
* @return {Notification} Object with a done handler that resolves when the toast closes.
421452
* @type {function}
@@ -466,11 +497,11 @@ define(function (require, exports, module) {
466497
};
467498

468499
if (autoCloseTimeS) {
469-
setTimeout(function () {
500+
_autoCloseUnlessHovered($toast, autoCloseTimeS, function () {
470501
if (notification.$notification) {
471502
notification.close(CLOSE_REASON.TIMEOUT);
472503
}
473-
}, autoCloseTimeS * 1000);
504+
});
474505
}
475506

476507
if (dismissOnClick) {
@@ -497,6 +528,7 @@ define(function (require, exports, module) {
497528
* @param {string} label Text to display below the icon (e.g. "110%").
498529
* @param {Object} [options] optional, supported options:
499530
* * `autoCloseTimeS` - Time in seconds after which the HUD auto-closes. Default is 1.
531+
* The countdown pauses while the mouse is over the HUD.
500532
* @return {Notification} Object with a done handler that resolves when the HUD closes.
501533
* @type {function}
502534
*/
@@ -531,11 +563,11 @@ define(function (require, exports, module) {
531563
};
532564

533565
if (autoCloseTimeS) {
534-
setTimeout(function () {
566+
_autoCloseUnlessHovered($hud, autoCloseTimeS, function () {
535567
if (notification.$notification) {
536568
notification.close(CLOSE_REASON.TIMEOUT);
537569
}
538-
}, autoCloseTimeS * 1000);
570+
});
539571
}
540572

541573
return notification;
Collapse file

‎test/spec/NotificationUI-test.js‎

Copy file name to clipboardExpand all lines: test/spec/NotificationUI-test.js
+68-1Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
*/
2121

22-
/*global describe, it, expect, beforeAll, afterAll, awaitsFor, awaitsForDone, awaits */
22+
/*global describe, it, expect, beforeAll, afterAll, awaitsFor, awaitsForDone, awaits, jasmine, spyOn */
2323

2424
define(function (require, exports, module) {
2525
let NotificationUI = require("widgets/NotificationUI");
@@ -106,6 +106,50 @@ define(function (require, exports, module) {
106106
}, "waiting for notification to close");
107107
}
108108

109+
it("Should hold the auto close while the mouse is over the toast", function () {
110+
// Mock time from the start so the auto-close timer itself is under the clock. The close
111+
// itself is observed on the notification, since the removal waits for a CSS transition.
112+
jasmine.clock().install();
113+
try {
114+
const notification = NotificationUI.createToastFromTemplate("hello", "world", {autoCloseTimeS: 1});
115+
spyOn(notification, "close").and.callThrough();
116+
const $popup = $("#toast-notification-container").children().last();
117+
expect($popup.length).toBe(1);
118+
$popup.trigger("mouseenter");
119+
jasmine.clock().tick(1500);
120+
expect($popup[0].isConnected).toBe(true);
121+
expect(notification.close).not.toHaveBeenCalled();
122+
$popup.trigger("mouseleave");
123+
jasmine.clock().tick(900);
124+
expect(notification.close).not.toHaveBeenCalled();
125+
jasmine.clock().tick(200);
126+
expect(notification.close).toHaveBeenCalledWith(NotificationUI.CLOSE_REASON.TIMEOUT);
127+
} finally {
128+
jasmine.clock().uninstall();
129+
}
130+
});
131+
132+
it("Should hold the auto close while the mouse is over the HUD", function () {
133+
jasmine.clock().install();
134+
let closeReason;
135+
try {
136+
const notification = NotificationUI.showHUD("fa-solid fa-magnifying-glass-plus", "110%");
137+
notification.done(function (reason) { closeReason = reason; });
138+
const $hud = $("body > .hud-overlay");
139+
expect($hud.length).toBe(1);
140+
$hud.trigger("mouseenter");
141+
jasmine.clock().tick(1500);
142+
expect($hud[0].isConnected).toBe(true);
143+
expect(closeReason).toBeUndefined();
144+
$hud.trigger("mouseleave");
145+
jasmine.clock().tick(1100);
146+
expect(closeReason).toBe(NotificationUI.CLOSE_REASON.TIMEOUT);
147+
expect($("body > .hud-overlay").length).toBe(0);
148+
} finally {
149+
jasmine.clock().uninstall();
150+
}
151+
});
152+
109153
it("Should style toast notification", async function () {
110154
await verifyToast(NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.INFO);
111155
await verifyToast(NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.WARNING);
@@ -152,6 +196,29 @@ define(function (require, exports, module) {
152196
}, "waiting for inline toast to auto-close", 3000);
153197
});
154198

199+
it("Should hold the auto close while the mouse is over the inline toast", function () {
200+
jasmine.clock().install();
201+
let closeReason;
202+
try {
203+
const notification = NotificationUI.showToastOn($container[0], "hover me", {autoCloseTimeS: 1});
204+
notification.done(function (reason) { closeReason = reason; });
205+
const $toast = $container.find(".inline-toast");
206+
expect($toast.length).toBe(1);
207+
$toast.trigger("mouseenter");
208+
jasmine.clock().tick(1500);
209+
expect($toast[0].isConnected).toBe(true);
210+
expect(closeReason).toBeUndefined();
211+
$toast.trigger("mouseleave");
212+
jasmine.clock().tick(1100);
213+
// The close falls back to a timer when no transition event arrives.
214+
jasmine.clock().tick(600);
215+
expect(closeReason).toBe(NotificationUI.CLOSE_REASON.TIMEOUT);
216+
expect($container.find(".inline-toast").length).toBe(0);
217+
} finally {
218+
jasmine.clock().uninstall();
219+
}
220+
});
221+
155222
it("Should dismiss on click by default", async function () {
156223
NotificationUI.showToastOn($container[0], "Click me");
157224
await awaitsFor(function () {

0 commit comments

Comments
0 (0)
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.