From d1c81a41de27500a6cc96c7a4a8e1d412da3e151 Mon Sep 17 00:00:00 2001 From: Kurtis Shaner Date: Tue, 22 Mar 2016 16:53:11 -0400 Subject: [PATCH] implement focus events so menu can be tabbed through --- README.md | 4 ++++ dropdown.js | 33 +++++++++++++++++++++++++++++++-- dropdown.min.js | 17 +---------------- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index fd6ecf5..f348f5e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ It handles: * Pointer Events * Mouse Events * Hover Intent +* Focus Events If a menu has a top level link and is opened with touch, the menu is opened and the link is not followed. Then if it is touched again while the menu is open, the link followed. This behavior is similar to aria-haspopup. @@ -46,3 +47,6 @@ jQuery('#node').dropdown(options); * Android 4+ (haven't tested with 2.3) * Edge * IE8+ + +### Develop Notes +Use uglifyjs to create minified version: uglifyjs dropdown.js -o dropdown.min.js --mangle --compress diff --git a/dropdown.js b/dropdown.js index d0eb756..e605099 100644 --- a/dropdown.js +++ b/dropdown.js @@ -124,7 +124,7 @@ window.Dropdown = (function() { } }; - evts.mouseenter = function(e) { + evts.mouseenter = function() { if (typeof _this.timeout.leave === 'number') { window.clearTimeout(_this.timeout.leave); } else { @@ -134,7 +134,7 @@ window.Dropdown = (function() { } }; - evts.mouseleave = function(e) { + evts.mouseleave = function() { if (typeof _this.timeout.enter === 'number') { window.clearTimeout(_this.timeout.enter); } else { @@ -198,6 +198,35 @@ window.Dropdown = (function() { document.removeEventListener('click', evts.maybeClose); }; + evts.focus = function() { + if (!_this.active) { + _this.open(); + } + }; + + evts.blur = function() { + if (_this.menuLinks.length < 1) { + _this.close(); + return; + } + + // make this async so we can detect the correct document.activeElement + window.setTimeout(function() { + for(var i = 0; i<_this.menuLinks.length; i++) { + if (document.activeElement === _this.menuLinks[i]) { + return; + } + } + _this.close(); + }, 0); + }; + + // use event capturing to detect event focus and blur because do not bubble + if (this.container.addEventListener) { + this.container.addEventListener('focus', evts.focus, true); + this.container.addEventListener('blur', evts.blur, true); + } + if (window.PointerEvent) { this.container.addEventListener('pointerenter', evts.pointerenter); this.container.addEventListener('pointerleave', evts.pointerleave); diff --git a/dropdown.min.js b/dropdown.min.js index 54a7fcc..c5df566 100644 --- a/dropdown.min.js +++ b/dropdown.min.js @@ -1,16 +1 @@ -window.Dropdown=function(){"use strict" -var e=0,t={addClass:function(e,t){e.classList?e.classList.add(t):e.className+=" "+t},removeClass:function(e,t){e.classList?e.classList.remove(t):e.className=e.className.replace(new RegExp("(^|\\b)"+t.split(" ").join("|")+"(\\b|$)","gi")," ")},preventDefault:function(e){e.preventDefault?e.preventDefault():e.returnValue=!1},hasTouch:function(){if("ontouchstart"in window)return!0 -var e,t=document.body,n=!1,o=document.createElement("div"),i=document.createElement("style"),s=["@media (",["-webkit-","-moz-","-o-","-ms-"].join("touch-enabled),("),"heartz",")","{#hasTouchNode{top:9px;position:absolute}}"].join("") -return t||(t=document.createElement("body"),n=!0),o.id="hasTouchNode",i.styleSheet?i.styleSheet.cssText=s:i.appendChild(document.createTextNode(s)),(n?t:o).appendChild(i),t.appendChild(o),n&&document.documentElement.appendChild(t),e=9===o.offsetTop,o.parentNode.removeChild(o),i.parentNode.removeChild(i),n&&t.parentNode.removeChild(t),e}()},n=function(n,o){var i=this,s={},a=!1,r=!1 -if(n){this.container="string"==typeof n?document.querySelector(n):n,this.opts={delay:250,openClass:"dropdown--open",menu:"> ul",spa:!1} -for(var c in o)o.hasOwnProperty(c)&&(this.opts[c]=o[c]) -this.container.id||(a=!0,e++,this.container.id="dropdown-temp-id-"+e),this.mainLink=document.querySelector("#"+this.container.id+" > a"),this.menu=document.querySelector("#"+this.container.id+" "+this.opts.menu),a&&this.container.removeAttribute("id"),this.menu&&(this.menuLinks=this.menu.querySelectorAll("a"),this.timeout={enter:null,leave:null},this.active=!1,s.touch=function(e){i.active?i.close():(t.preventDefault(e),e.stopPropagation(),i.open())},s.mouseenter=function(e){"number"==typeof i.timeout.leave?window.clearTimeout(i.timeout.leave):i.timeout.enter=window.setTimeout(function(){i.open()},i.opts.delay)},s.mouseleave=function(e){"number"==typeof i.timeout.enter?window.clearTimeout(i.timeout.enter):i.timeout.leave=window.setTimeout(function(){i.close()},i.opts.delay)},s.pointerenter=function(e){switch(e.pointerType){case"touch":r=!0,i.mainLink.addEventListener("click",s.touch),document.addEventListener("click",s.maybeClose) -break -default:s.mouseenter(e)}},s.pointerleave=function(e){switch(e.pointerType){case"touch":r?r=!1:s.touch(e) -break -default:s.mouseleave(e)}},s.handleTouchClose=function(e){e.preventDefault(),"A"===e.target.tagName&&i.close()},s.maybeClose=function(e){var t=e.target||e.srcElement -if(i.active){for(;t&&"A"!==t.tagName;){if(t===this.node)return -t=t.parentNode}i.close(),document.removeEventListener("click",s.maybeClose)}},window.PointerEvent?(this.container.addEventListener("pointerenter",s.pointerenter),this.container.addEventListener("pointerleave",s.pointerleave),this.opts.spa&&this.menu.addEventListener("click",s.handleTouchClose)):(t.hasTouch&&(this.mainLink.addEventListener("click",function(e){s.touch(e)}),this.opts.spa&&this.menu.addEventListener("click",s.handleTouchClose)),this.container.addEventListener?(this.container.addEventListener("mouseenter",s.mouseenter),this.container.addEventListener("mouseleave",s.mouseleave)):this.container.attachEvent&&(this.container.attachEvent("onmouseenter",s.mouseenter),this.container.attachEvent("onmouseleave",s.mouseleave))))}} -n.prototype={open:function(){("function"!=typeof this.opts.beforeOpen||this.opts.beforeOpen.call(this)!==!1)&&(this.active=!0,"number"==typeof this.timeout.enter&&(window.clearTimeout(this.timeout.enter),this.timeout.enter=null),t.addClass(this.container,this.opts.openClass),"function"==typeof this.opts.afterOpen&&this.opts.afterOpen.call(this))},close:function(){("function"!=typeof this.opts.beforeClose||this.opts.beforeClose.call(this)!==!1)&&(this.active=!1,"number"==typeof this.timeout.leave&&(window.clearTimeout(this.timeout.leave),this.timeout.leave=null),t.removeClass(this.container,this.opts.openClass),"function"==typeof this.opts.afterClose&&this.opts.afterClose.call(this))}} -var o=function(e,t){return new n(e,t)} -return"function"==typeof window.jQuery&&(window.jQuery.fn.dropdown=function(e){window.jQuery(this).each(function(){window.jQuery(this).data("dropdown",new o(this,e))})}),o}() +window.Dropdown=function(){"use strict";var e=0,t={addClass:function(e,t){e.classList?e.classList.add(t):e.className+=" "+t},removeClass:function(e,t){e.classList?e.classList.remove(t):e.className=e.className.replace(new RegExp("(^|\\b)"+t.split(" ").join("|")+"(\\b|$)","gi")," ")},preventDefault:function(e){e.preventDefault?e.preventDefault():e.returnValue=!1},hasTouch:function(){if("ontouchstart"in window)return!0;var e,t=document.body,n=!1,o=document.createElement("div"),i=document.createElement("style"),s=["@media (",["-webkit-","-moz-","-o-","-ms-"].join("touch-enabled),("),"heartz",")","{#hasTouchNode{top:9px;position:absolute}}"].join("");return t||(t=document.createElement("body"),n=!0),o.id="hasTouchNode",i.styleSheet?i.styleSheet.cssText=s:i.appendChild(document.createTextNode(s)),(n?t:o).appendChild(i),t.appendChild(o),n&&document.documentElement.appendChild(t),e=9===o.offsetTop,o.parentNode.removeChild(o),i.parentNode.removeChild(i),n&&t.parentNode.removeChild(t),e}()},n=function(n,o){var i=this,s={},a=!1,r=!1;if(n){this.container="string"==typeof n?document.querySelector(n):n,this.opts={delay:250,openClass:"dropdown--open",menu:"> ul",spa:!1};for(var u in o)o.hasOwnProperty(u)&&(this.opts[u]=o[u]);this.container.id||(a=!0,e++,this.container.id="dropdown-temp-id-"+e),this.mainLink=document.querySelector("#"+this.container.id+" > a"),this.menu=document.querySelector("#"+this.container.id+" "+this.opts.menu),a&&this.container.removeAttribute("id"),this.menu&&(this.menuLinks=this.menu.querySelectorAll("a"),this.timeout={enter:null,leave:null},this.active=!1,s.touch=function(e){i.active?i.close():(t.preventDefault(e),e.stopPropagation(),i.open())},s.mouseenter=function(){"number"==typeof i.timeout.leave?window.clearTimeout(i.timeout.leave):i.timeout.enter=window.setTimeout(function(){i.open()},i.opts.delay)},s.mouseleave=function(){"number"==typeof i.timeout.enter?window.clearTimeout(i.timeout.enter):i.timeout.leave=window.setTimeout(function(){i.close()},i.opts.delay)},s.pointerenter=function(e){switch(e.pointerType){case"touch":r=!0,i.mainLink.addEventListener("click",s.touch),document.addEventListener("click",s.maybeClose);break;default:s.mouseenter(e)}},s.pointerleave=function(e){switch(e.pointerType){case"touch":r?r=!1:s.touch(e);break;default:s.mouseleave(e)}},s.handleTouchClose=function(e){e.preventDefault(),"A"===e.target.tagName&&i.close()},s.maybeClose=function(e){var t=e.target||e.srcElement;if(i.active){for(;t&&"A"!==t.tagName;){if(t===this.node)return;t=t.parentNode}i.close(),document.removeEventListener("click",s.maybeClose)}},s.focus=function(){i.active||i.open()},s.blur=function(){return i.menuLinks.length<1?void i.close():void window.setTimeout(function(){for(var e=0;e