define([ "dojo/_base/array", // array.forEach array.map "dojo/_base/config", // defaultDuration "dojo/_base/declare", // declare "dojo/dom", // dom.byId "dojo/dom-attr", // domAttr.attr domAttr.has "dojo/dom-style", // style.style "dojo/sniff", // has("ie") "./main" // for exporting methods to dijit namespace ], function(array, config, declare, dom, domAttr, domStyle, has, dijit){ // module: // dijit/a11y var shown = (dijit._isElementShown = function(/*Element*/ elem){ var s = domStyle.get(elem); return (s.visibility != "hidden") && (s.visibility != "collapsed") && (s.display != "none") && (domAttr.get(elem, "type") != "hidden"); }); dijit.hasDefaultTabStop = function(/*Element*/ elem){ // summary: // Tests if element is tab-navigable even without an explicit tabIndex setting // No explicit tabIndex setting, need to investigate node type switch(elem.nodeName.toLowerCase()){ case "a": // An <a> w/out a tabindex is only navigable if it has an href return domAttr.has(elem, "href"); case "area": case "button": case "input": case "object": case "select": case "textarea": // These are navigable by default return true; case "iframe": // If it's an editor <iframe> then it's tab navigable. var body; try{ // non-IE var contentDocument = elem.contentDocument; if("designMode" in contentDocument && contentDocument.designMode == "on"){ return true; } body = contentDocument.body; }catch(e1){ // contentWindow.document isn't accessible within IE7/8 // if the iframe.src points to a foreign url and this // page contains an element, that could get focus try{ body = elem.contentWindow.document.body; }catch(e2){ return false; } } return body && (body.contentEditable == 'true' || (body.firstChild && body.firstChild.contentEditable == 'true')); default: return elem.contentEditable == 'true'; } }; var isTabNavigable = (dijit.isTabNavigable = function(/*Element*/ elem){ // summary: // Tests if an element is tab-navigable // TODO: convert (and rename method) to return effective tabIndex; will save time in _getTabNavigable() if(domAttr.get(elem, "disabled")){ return false; }else if(domAttr.has(elem, "tabIndex")){ // Explicit tab index setting return domAttr.get(elem, "tabIndex") >= 0; // boolean }else{ // No explicit tabIndex setting, so depends on node type return dijit.hasDefaultTabStop(elem); } }); dijit._getTabNavigable = function(/*DOMNode*/ root){ // summary: // Finds descendants of the specified root node. // description: // Finds the following descendants of the specified root node: // // - the first tab-navigable element in document order // without a tabIndex or with tabIndex="0" // - the last tab-navigable element in document order // without a tabIndex or with tabIndex="0" // - the first element in document order with the lowest // positive tabIndex value // - the last element in document order with the highest // positive tabIndex value var first, last, lowest, lowestTabindex, highest, highestTabindex, radioSelected = {}; function radioName(node){ // If this element is part of a radio button group, return the name for that group. return node && node.tagName.toLowerCase() == "input" && node.type && node.type.toLowerCase() == "radio" && node.name && node.name.toLowerCase(); } var walkTree = function(/*DOMNode*/ parent){ for(var child = parent.firstChild; child; child = child.nextSibling){ // Skip text elements, hidden elements, and also non-HTML elements (those in custom namespaces) in IE, // since show() invokes getAttribute("type"), which crash on VML nodes in IE. if(child.nodeType != 1 || (has("ie") && child.scopeName !== "HTML") || !shown(child)){ continue; } if(isTabNavigable(child)){ var tabindex = +domAttr.get(child, "tabIndex"); // + to convert string --> number if(!domAttr.has(child, "tabIndex") || tabindex == 0){ if(!first){ first = child; } last = child; }else if(tabindex > 0){ if(!lowest || tabindex < lowestTabindex){ lowestTabindex = tabindex; lowest = child; } if(!highest || tabindex >= highestTabindex){ highestTabindex = tabindex; highest = child; } } var rn = radioName(child); if(domAttr.get(child, "checked") && rn){ radioSelected[rn] = child; } } if(child.nodeName.toUpperCase() != 'SELECT'){ walkTree(child); } } }; if(shown(root)){ walkTree(root); } function rs(node){ // substitute checked radio button for unchecked one, if there is a checked one with the same name. return radioSelected[radioName(node)] || node; } return { first: rs(first), last: rs(last), lowest: rs(lowest), highest: rs(highest) }; }; dijit.getFirstInTabbingOrder = function(/*String|DOMNode*/ root, /*Document?*/ doc){ // summary: // Finds the descendant of the specified root node // that is first in the tabbing order var elems = dijit._getTabNavigable(dom.byId(root, doc)); return elems.lowest ? elems.lowest : elems.first; // DomNode }; dijit.getLastInTabbingOrder = function(/*String|DOMNode*/ root, /*Document?*/ doc){ // summary: // Finds the descendant of the specified root node // that is last in the tabbing order var elems = dijit._getTabNavigable(dom.byId(root, doc)); return elems.last ? elems.last : elems.highest; // DomNode }; return { // summary: // Accessibility utility functions (keyboard, tab stops, etc.) hasDefaultTabStop: dijit.hasDefaultTabStop, isTabNavigable: dijit.isTabNavigable, _getTabNavigable: dijit._getTabNavigable, getFirstInTabbingOrder: dijit.getFirstInTabbingOrder, getLastInTabbingOrder: dijit.getLastInTabbingOrder }; });