]> git.ipfire.org Git - thirdparty/foundation/foundation-sites.git/commitdiff
Use pull request #11418 from ncoden/refactor/mouseleave-special-case for v6.5.1
authorNicolas Coden <nicolas@ncoden.fr>
Sun, 11 Nov 2018 21:36:45 +0000 (22:36 +0100)
committerNicolas Coden <nicolas@ncoden.fr>
Mon, 12 Nov 2018 20:44:29 +0000 (21:44 +0100)
9bd7f933c refactor: move dropdownMenu mouseleave special case to its own utility function
8f2470c28 docs: add doc for core utility onLeaveElement
90de67a17 refactor: make the magic mouseleave utility a simple event filter
adeee972d docs: add some doc in the magic mouseleave utility "ignoreMousedisappear"
f46e78bd1 fix: prevent Dropdown and Tooltip to hide when moving mouse to browser UI elements
5bdec3a3c tests: add visual test for the Dropdown magic mousleave bug
f0168bb9f style: fix incorrectly named variable in ignoreMousedisappear utility
1847f6c9b fix: make the "ignoreMousedisappear()" handler called before mouseenter
3ec791559 docs: improve doc in "ignoreMousedisappear()"

Note: this commit has the same purpose of 55e60ee86 but includes the commits
from 8f2470c28 to 3ec791559 that missed in the previous merge commit.

Signed-off-by: Nicolas Coden <nicolas@ncoden.fr>
js/foundation.core.utils.js
js/foundation.dropdown.js
js/foundation.dropdownMenu.js
js/foundation.tooltip.js
test/visual/dropdown/mouse-on-browser-ui.html [new file with mode: 0644]

index 5bc46b9a57170c97f57db936c957f14b725331fa..b1eeea45343b00a735e74feba795bdc74e90e607 100644 (file)
@@ -90,32 +90,54 @@ function onLoad($elem, handler) {
   return eventType;
 }
 
-function onLeaveElement($elem, handler, { leaveWindow = true } = {}) {
-  const eventType = 'mouseleave.zf.util.onLeaveElement';
-
-  if ($elem && handler) {
-
-    $elem.on(eventType, function leaveHandler(e, ...rest) {
-      const _this = this;
-      setTimeout(function leaveEventDebouncer() {
-
-        if (e.relatedTarget === null && leaveWindow && document.hasFocus && document.hasFocus()) {
-
-          $(document).one('mouseenter', function reenterHandler(reeenterE) {
-            if ($elem.has(reeenterE.target).length) { return false };
-            e.relatedTarget = reeenterE.target;
-            handler.call(_this, e, ...rest);
-          });
-
-          return false;
-        }
+/**
+ * Retuns an handler for the `mouseleave` that ignore disappeared mouses.
+ *
+ * If the mouse "disappeared" from the document (like when going on a browser UI element, See https://git.io/zf-11410),
+ * the event is ignored.
+ * - If the `ignoreLeaveWindow` is `true`, the event is ignored when the user actually left the window
+ *   (like by switching to an other window with [Alt]+[Tab]).
+ * - If the `ignoreReappear` is `true`, the event will be ignored when the mouse will reappear later on the document
+ *   outside of the element it left.
+ *
+ * @function
+ *
+ * @param {Function} [] handler - handler for the filtered `mouseleave` event to watch.
+ * @param {Object} [] options - object of options:
+ * - {Boolean} [false] ignoreLeaveWindow - also ignore when the user switched windows.
+ * - {Boolean} [false] ignoreReappear - also ignore when the mouse reappeared outside of the element it left.
+ * @returns {Function} - filtered handler to use to listen on the `mouseleave` event.
+ */
+function ignoreMousedisappear(handler, { ignoreLeaveWindow = false, ignoreReappear = false } = {}) {
+  return function leaveEventHandler(eLeave, ...rest) {
+    const callback = handler.bind(this, eLeave, ...rest);
 
-        handler.call(_this, e, ...rest);
-      });
-    });
-  }
+    // The mouse left: call the given callback if the mouse entered elsewhere
+    if (eLeave.relatedTarget !== null) {
+      return callback();
+    }
 
-  return eventType;
+    // Otherwise, check if the mouse actually left the window.
+    // In firefox if the user switched between windows, the window sill have the focus by the time
+    // the event is triggered. We have to debounce the event to test this case.
+    setTimeout(function leaveEventDebouncer() {
+      if (!ignoreLeaveWindow && document.hasFocus && !document.hasFocus()) {
+        return callback();
+      }
+
+      // Otherwise, wait for the mouse to reeapear outside of the element,
+      if (!ignoreReappear) {
+        $(document).one('mouseenter', function reenterEventHandler(eReenter) {
+          if (!$(eLeave.currentTarget).has(eReenter.target).length) {
+            // Fill where the mouse finally entered.
+            eLeave.relatedTarget = eReenter.target;
+            callback();
+          }
+        });
+      }
+
+    }, 0);
+  };
 }
 
-export { rtl, GetYoDigits, RegExpEscape, transitionend, onLoad, onLeaveElement };
+export { rtl, GetYoDigits, RegExpEscape, transitionend, onLoad, ignoreMousedisappear };
index 82ab6e5bcad646d20aabfdb72143a1ba2affa5dd..1f86313c73fc83577c3290ecc057ee08e1872669 100644 (file)
@@ -2,7 +2,7 @@
 
 import $ from 'jquery';
 import { Keyboard } from './foundation.util.keyboard';
-import { GetYoDigits } from './foundation.core.utils';
+import { GetYoDigits, ignoreMousedisappear } from './foundation.core.utils';
 import { Positionable } from './foundation.positionable';
 
 import { Triggers } from './foundation.util.triggers';
@@ -157,24 +157,24 @@ class Dropdown extends Positionable {
             _this.$anchors.data('hover', true);
           }, _this.options.hoverDelay);
         }
-      }).on('mouseleave.zf.dropdown', function(){
+      }).on('mouseleave.zf.dropdown', ignoreMousedisappear(function(){
         clearTimeout(_this.timeout);
         _this.timeout = setTimeout(function(){
           _this.close();
           _this.$anchors.data('hover', false);
         }, _this.options.hoverDelay);
-      });
+      }));
       if(this.options.hoverPane){
         this.$element.off('mouseenter.zf.dropdown mouseleave.zf.dropdown')
             .on('mouseenter.zf.dropdown', function(){
               clearTimeout(_this.timeout);
-            }).on('mouseleave.zf.dropdown', function(){
+            }).on('mouseleave.zf.dropdown', ignoreMousedisappear(function(){
               clearTimeout(_this.timeout);
               _this.timeout = setTimeout(function(){
                 _this.close();
                 _this.$anchors.data('hover', false);
               }, _this.options.hoverDelay);
-            });
+            }));
       }
     }
     this.$anchors.add(this.$element).on('keydown.zf.dropdown', function(e) {
index 9381f2698e2b1c391955c3ea981b252b8e09af39..72f3a45d3061ea2bfb3a64e6488ce039c055e7b6 100644 (file)
@@ -2,7 +2,7 @@
 
 import $ from 'jquery';
 import { Plugin } from './foundation.core.plugin';
-import { rtl as Rtl, onLeaveElement } from './foundation.core.utils';
+import { rtl as Rtl, ignoreMousedisappear } from './foundation.core.utils';
 import { Keyboard } from './foundation.util.keyboard';
 import { Nest } from './foundation.util.nest';
 import { Box } from './foundation.util.box';
@@ -144,9 +144,7 @@ class DropdownMenu extends Plugin {
             _this._show($elem.children('.is-dropdown-submenu'));
           }, _this.options.hoverDelay));
         }
-      });
-
-      onLeaveElement(this.$menuItems, function (e) {
+      }).on('mouseleave.zf.dropdownMenu', ignoreMousedisappear(function (e) {
         var $elem = $(this),
             hasSub = $elem.hasClass(parClass);
         if (hasSub && _this.options.autoclose) {
@@ -157,7 +155,7 @@ class DropdownMenu extends Plugin {
             _this._hide($elem);
           }, _this.options.closingTime));
         }
-      });
+      }));
     }
     this.$menuItems.on('keydown.zf.dropdownmenu', function(e) {
       var $element = $(e.target).parentsUntil('ul', '[role="menuitem"]'),
index 2f2a50fb54b9d03d104c52a1a63e82ba537ae2ea..8126ae69e6ffaba2059fbf61f2bd8753f53e2b86 100644 (file)
@@ -2,7 +2,7 @@
 
 import $ from 'jquery';
 
-import { GetYoDigits } from './foundation.core.utils';
+import { GetYoDigits, ignoreMousedisappear } from './foundation.core.utils';
 import { MediaQuery } from './foundation.util.mediaQuery';
 import { Triggers } from './foundation.util.triggers';
 import { Positionable } from './foundation.positionable';
@@ -205,12 +205,12 @@ class Tooltip extends Positionable {
           }, _this.options.hoverDelay);
         }
       })
-      .on('mouseleave.zf.tooltip', function(e) {
+      .on('mouseleave.zf.tooltip', ignoreMousedisappear(function(e) {
         clearTimeout(_this.timeout);
         if (!isFocus || (_this.isClick && !_this.options.clickOpen)) {
           _this.hide();
         }
-      });
+      }));
     }
 
     if (this.options.clickOpen) {
diff --git a/test/visual/dropdown/mouse-on-browser-ui.html b/test/visual/dropdown/mouse-on-browser-ui.html
new file mode 100644 (file)
index 0000000..2f90ed9
--- /dev/null
@@ -0,0 +1,50 @@
+<!doctype html>
+<!--[if IE 9]><html class="lt-ie10" lang="en" > <![endif]-->
+<html class="no-js" lang="en" dir="ltr">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+    <title>Foundation for Sites Testing</title>
+    <link href="../motion-ui/dist/motion-ui.css" rel="stylesheet" />
+    <link href="../assets/css/foundation.css" rel="stylesheet" />
+  </head>
+
+  <body>
+
+    <div class="grid-container">
+      <div class="grid-x grid-padding-x">
+        <div class="cell">
+
+          <h1>Dropdown with UI elements</h1>
+
+          <ul>
+            <li>1. Dropdown should stay open when the mouse goes on the input's autocomplete pane.</li>
+            <li>2. Dropdown should stay open when the mouse reenter in the dropdown from the autocomplete pane.</li>
+            <li>3. Dropdown should close when the nouse leave the dropdown from the autocomplete pane.</li>
+          </ul>
+
+          <button class="button" type="button" data-toggle="example-dropdown">Dropdown opening on hover</button>
+          <div class="dropdown-pane" id="example-dropdown" data-dropdown data-hover="true" data-hover-pane="true">
+            <form action="#" method="post">
+              <div class="row">
+                <div>
+                  In order to test [3], there should be enough autocomplete suggestions for the autocomplete pane to overflow from the dropdown.
+                  <input type="email" placeholder="Double-click to show autocomplete" autocomplete="email"/>
+                </div>
+              </div>
+            </form>
+          </div>
+
+        </div>
+      </div>
+    </div>
+
+    <script src="../assets/js/vendor.js"></script>
+    <script src="../assets/js/foundation.js"></script>
+    <script>
+      $(document).foundation();
+    </script>
+
+  </body>
+</html>