define([ "dojo/on", "dojo/_base/array", // array.forEach "dojo/keys", // keys.ENTER keys.SPACE "dojo/_base/declare", // declare "dojo/has", // has("dom-addeventlistener") "dojo/_base/unload", // unload.addOnWindowUnload "dojo/_base/window" // win.doc.addEventListener win.doc.attachEvent win.doc.detachEvent ], function(on, array, keys, declare, has, unload, win){ // module: // dijit/a11yclick // Keep track of where the last keydown event was, to help avoid generating // spurious ondijitclick events when: // 1. focus is on a <button> or <a> // 2. user presses then releases the ENTER key // 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler // 4. onkeyup event fires, causing the ondijitclick handler to fire var lastKeyDownNode = null; if(has("dom-addeventlistener")){ win.doc.addEventListener('keydown', function(evt){ lastKeyDownNode = evt.target; }, true); }else{ // Fallback path for IE6-8 (function(){ var keydownCallback = function(evt){ lastKeyDownNode = evt.srcElement; }; win.doc.attachEvent('onkeydown', keydownCallback); unload.addOnWindowUnload(function(){ win.doc.detachEvent('onkeydown', keydownCallback); }); })(); } function clickKey(/*Event*/ e){ return (e.keyCode === keys.ENTER || e.keyCode === keys.SPACE) && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey; } return function(node, listener){ // summary: // Custom a11yclick (a.k.a. ondijitclick) event // which triggers on a mouse click, touch, or space/enter keyup. if(/input|button/i.test(node.nodeName)){ // pass through, the browser already generates click event on SPACE/ENTER key return on(node, "click", listener); }else{ // Don't fire the click event unless both the keydown and keyup occur on this node. // Avoids problems where focus shifted to this node or away from the node on keydown, // either causing this node to process a stray keyup event, or causing another node // to get a stray keyup event. var handles = [ on(node, "keydown", function(e){ //console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode)); if(clickKey(e)){ // needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work lastKeyDownNode = e.target; // Prevent viewport scrolling on space key in IE<9. // (Reproducible on test_Button.html on any of the first dijit/form/Button examples) e.preventDefault(); } }), on(node, "keyup", function(e){ //console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode)); if(clickKey(e) && e.target == lastKeyDownNode){ // === breaks greasemonkey //need reset here or have problems in FF when focus returns to trigger element after closing popup/alert lastKeyDownNode = null; on.emit(e.target, "click", { cancelable: true, bubbles: true }); } }), on(node, "click", function(e){ // catch mouse clicks, plus the on.emit() calls from above and below listener.call(this, e); }) ]; if(has("touch")){ // touchstart-->touchend will automatically generate a click event, but there are problems // on iOS after focus has been programatically shifted (#14604, #14918), so setup a failsafe // if click doesn't fire naturally. var clickTimer; handles.push( on(node, "touchend", function(e){ var target = e.target; clickTimer = setTimeout(function(){ clickTimer = null; on.emit(target, "click", { cancelable: true, bubbles: true }); }, 600); }), on(node, "click", function(e){ // If browser generates a click naturally, clear the timer to fire a synthetic click event if(clickTimer){ clearTimeout(clickTimer); } }) // TODO: if the touchstart and touchend were <100ms apart, and then there's another touchstart // event <300ms after the touchend event, then clear the synthetic click timer, because user // is doing a zoom. Alternately monitor screen.deviceXDPI (or something similar) to see if // zoom level has changed. ); } return { remove: function(){ array.forEach(handles, function(h){ h.remove(); }); if(clickTimer){ clearTimeout(clickTimer); clickTimer = null; } } }; } }; return ret; });