You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
6.0 KiB
191 lines
6.0 KiB
define([ |
|
"dojo/_base/array", // array.forEach array.map |
|
"dojo/dom", // dom.byId |
|
"dojo/dom-attr", // domAttr.attr domAttr.has |
|
"dojo/dom-style", // domStyle.style |
|
"dojo/_base/lang", // lang.mixin() |
|
"dojo/sniff", // has("ie") has("extend-dojo") |
|
"./main" // for exporting methods to dijit namespace |
|
], function(array, dom, domAttr, domStyle, lang, has, dijit){ |
|
|
|
// module: |
|
// dijit/a11y |
|
|
|
var undefined; |
|
|
|
var a11y = { |
|
// summary: |
|
// Accessibility utility functions (keyboard, tab stops, etc.) |
|
|
|
_isElementShown: function(/*Element*/ elem){ |
|
var s = domStyle.get(elem); |
|
return (s.visibility != "hidden") |
|
&& (s.visibility != "collapsed") |
|
&& (s.display != "none") |
|
&& (domAttr.get(elem, "type") != "hidden"); |
|
}, |
|
|
|
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'; |
|
} |
|
}, |
|
|
|
effectiveTabIndex: function(/*Element*/ elem){ |
|
// summary: |
|
// Returns effective tabIndex of an element, either a number, or undefined if element isn't focusable. |
|
|
|
if(domAttr.get(elem, "disabled")){ |
|
return undefined; |
|
}else if(domAttr.has(elem, "tabIndex")){ |
|
// Explicit tab index setting |
|
return +domAttr.get(elem, "tabIndex");// + to convert string --> number |
|
}else{ |
|
// No explicit tabIndex setting, so depends on node type |
|
return a11y.hasDefaultTabStop(elem) ? 0 : undefined; |
|
} |
|
}, |
|
|
|
isTabNavigable: function(/*Element*/ elem){ |
|
// summary: |
|
// Tests if an element is tab-navigable |
|
|
|
return a11y.effectiveTabIndex(elem) >= 0; |
|
}, |
|
|
|
isFocusable: function(/*Element*/ elem){ |
|
// summary: |
|
// Tests if an element is focusable by tabbing to it, or clicking it with the mouse. |
|
|
|
return a11y.effectiveTabIndex(elem) >= -1; |
|
}, |
|
|
|
_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 shown = a11y._isElementShown, effectiveTabIndex = a11y.effectiveTabIndex; |
|
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") <= 9 && child.scopeName !== "HTML") || !shown(child)){ |
|
continue; |
|
} |
|
|
|
var tabindex = effectiveTabIndex(child); |
|
if(tabindex >= 0){ |
|
if(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) }; |
|
}, |
|
|
|
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 = a11y._getTabNavigable(dom.byId(root, doc)); |
|
return elems.lowest ? elems.lowest : elems.first; // DomNode |
|
}, |
|
|
|
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 = a11y._getTabNavigable(dom.byId(root, doc)); |
|
return elems.last ? elems.last : elems.highest; // DomNode |
|
} |
|
}; |
|
|
|
has("extend-dojo") && lang.mixin(dijit, a11y); |
|
|
|
return a11y; |
|
});
|
|
|