Skip to content

Commit

Permalink
Use pull request #11418 from ncoden/refactor/mouseleave-special-case …
Browse files Browse the repository at this point in the history
…for v6.5.1

9bd7f93 refactor: move dropdownMenu mouseleave special case to its own utility function
8f2470c docs: add doc for core utility onLeaveElement
90de67a refactor: make the magic mouseleave utility a simple event filter
adeee97 docs: add some doc in the magic mouseleave utility "ignoreMousedisappear"
f46e78b fix: prevent Dropdown and Tooltip to hide when moving mouse to browser UI elements
5bdec3a tests: add visual test for the Dropdown magic mousleave bug
f0168bb style: fix incorrectly named variable in ignoreMousedisappear utility
1847f6c fix: make the "ignoreMousedisappear()" handler called before mouseenter
3ec7915 docs: improve doc in "ignoreMousedisappear()"

Note: this commit has the same purpose of 55e60ee but includes the commits
from 8f2470c to 3ec7915 that missed in the previous merge commit.

Signed-off-by: Nicolas Coden <nicolas@ncoden.fr>
  • Loading branch information
ncoden committed Nov 12, 2018
1 parent acad207 commit 16b8e38
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 38 deletions.
72 changes: 47 additions & 25 deletions js/foundation.core.utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
10 changes: 5 additions & 5 deletions js/foundation.dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 3 additions & 5 deletions js/foundation.dropdownMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand All @@ -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"]'),
Expand Down
6 changes: 3 additions & 3 deletions js/foundation.tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down
50 changes: 50 additions & 0 deletions test/visual/dropdown/mouse-on-browser-ui.html
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit 16b8e38

Please sign in to comment.