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 };
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';
_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) {
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';
_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) {
_this._hide($elem);
}, _this.options.closingTime));
}
- });
+ }));
}
this.$menuItems.on('keydown.zf.dropdownmenu', function(e) {
var $element = $(e.target).parentsUntil('ul', '[role="menuitem"]'),
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';
}, _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) {
--- /dev/null
+<!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>