diff -r 000000000000 -r 40c8f766c9b8 src/cm/media/js/lib/yui/yui3.0.0/api/node-focusmanager.js.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui3.0.0/api/node-focusmanager.js.html Mon Nov 23 15:14:29 2009 +0100 @@ -0,0 +1,1186 @@ + + +
+ +/**
+* <p>The Focus Manager Node Plugin makes it easy to manage focus among
+* a Node's descendants. Primarily intended to help with widget development,
+* the Focus Manager Node Plugin can be used to improve the keyboard
+* accessibility of widgets.</p>
+*
+* <p>
+* When designing widgets that manage a set of descendant controls (i.e. buttons
+* in a toolbar, tabs in a tablist, menuitems in a menu, etc.) it is important to
+* limit the number of descendants in the browser's default tab flow. The fewer
+* number of descendants in the default tab flow, the easier it is for keyboard
+* users to navigate between widgets by pressing the tab key. When a widget has
+* focus it should provide a set of shortcut keys (typically the arrow keys)
+* to move focus among its descendants.
+* </p>
+*
+* <p>
+* To this end, the Focus Manager Node Plugin makes it easy to define a Node's
+* focusable descendants, define which descendant should be in the default tab
+* flow, and define the keys that move focus among each descendant.
+* Additionally, as the CSS
+* <a href="http://www.w3.org/TR/CSS21/selector.html#x38"><code>:focus</code></a>
+* pseudo class is not supported on all elements in all
+* <a href="http://developer.yahoo.com/yui/articles/gbs/">A-Grade browsers</a>,
+* the Focus Manager Node Plugin provides an easy, cross-browser means of
+* styling focus.
+* </p>
+*
+* @module node-focusmanager
+*/
+
+ // Frequently used strings
+
+var ACTIVE_DESCENDANT = "activeDescendant",
+ ID = "id",
+ DISABLED = "disabled",
+ TAB_INDEX = "tabIndex",
+ FOCUSED = "focused",
+ FOCUS_CLASS = "focusClass",
+ CIRCULAR = "circular",
+ UI = "UI",
+ KEY = "key",
+ ACTIVE_DESCENDANT_CHANGE = ACTIVE_DESCENDANT + "Change",
+ HOST = "host",
+
+ // Collection of keys that, when pressed, cause the browser viewport
+ // to scroll.
+ scrollKeys = {
+ 37: true,
+ 38: true,
+ 39: true,
+ 40: true
+ },
+
+ clickableElements = {
+ "a": true,
+ "button": true,
+ "input": true,
+ "object": true
+ },
+
+ // Library shortcuts
+
+ Lang = Y.Lang,
+ UA = Y.UA,
+
+ /**
+ * The NodeFocusManager class is a plugin for a Node instance. The class is used
+ * via the <a href="Node.html#method_plug"><code>plug</code></a> method of Node
+ * and should not be instantiated directly.
+ * @namespace plugin
+ * @class NodeFocusManager
+ */
+ NodeFocusManager = function () {
+
+ NodeFocusManager.superclass.constructor.apply(this, arguments);
+
+ };
+
+
+NodeFocusManager.ATTRS = {
+
+ /**
+ * Boolean indicating that one of the descendants is focused.
+ *
+ * @attribute focused
+ * @readOnly
+ * @default false
+ * @type boolean
+ */
+ focused: {
+
+ value: false,
+ readOnly: true
+
+ },
+
+
+ /**
+ * String representing the CSS selector used to define the descendant Nodes
+ * whose focus should be managed.
+ *
+ * @attribute descendants
+ * @type Y.NodeList
+ */
+ descendants: {
+
+ getter: function (value) {
+
+ return this.get(HOST).all(value);
+
+ }
+
+ },
+
+
+ /**
+ * <p>Node, or index of the Node, representing the descendant that is either
+ * focused or is focusable (<code>tabIndex</code> attribute is set to 0).
+ * The value cannot represent a disabled descendant Node. Use a value of -1
+ * to remove all descendant Nodes from the default tab flow.
+ * If no value is specified, the active descendant will be inferred using
+ * the following criteria:</p>
+ * <ol>
+ * <li>Examining the <code>tabIndex</code> attribute of each descendant and
+ * using the first descendant whose <code>tabIndex</code> attribute is set
+ * to 0</li>
+ * <li>If no default can be inferred then the value is set to either 0 or
+ * the index of the first enabled descendant.</li>
+ * </ol>
+ *
+ * @attribute activeDescendant
+ * @type Number
+ */
+ activeDescendant: {
+
+ setter: function (value) {
+
+ var isNumber = Lang.isNumber,
+ INVALID_VALUE = Y.Attribute.INVALID_VALUE,
+ descendantsMap = this._descendantsMap,
+ descendants = this._descendants,
+ nodeIndex,
+ returnValue,
+ oNode;
+
+
+ if (isNumber(value)) {
+ nodeIndex = value;
+ returnValue = nodeIndex;
+ }
+ else if ((value instanceof Y.Node) && descendantsMap) {
+
+ nodeIndex = descendantsMap[value.get(ID)];
+
+ if (isNumber(nodeIndex)) {
+ returnValue = nodeIndex;
+ }
+ else {
+
+ // The user passed a reference to a Node that wasn't one
+ // of the descendants.
+ returnValue = INVALID_VALUE;
+
+ }
+
+ }
+ else {
+ returnValue = INVALID_VALUE;
+ }
+
+
+ if (descendants) {
+
+ oNode = descendants.item(nodeIndex);
+
+ if (oNode && oNode.get("disabled")) {
+
+ // Setting the "activeDescendant" attribute to the index
+ // of a disabled descendant is invalid.
+ returnValue = INVALID_VALUE;
+
+ }
+
+ }
+
+ return returnValue;
+
+ }
+
+ },
+
+
+ /**
+ * Object literal representing the keys to be used to navigate between the
+ * next/previous descendant. The format for the attribute's value is
+ * <code>{ next: "down:40", previous: "down:38" }</code>. The value for the
+ * "next" and "previous" properties are used to attach
+ * <a href="event/#keylistener"><code>key</code></a> event listeners. See
+ * the <a href="event/#keylistener">Using the key Event</a> section of
+ * the Event documentation for more information on "key" event listeners.
+ *
+ * @attribute keys
+ * @type Object
+ */
+ keys: {
+
+ value: {
+
+ next: null,
+ previous: null
+
+ }
+
+
+ },
+
+
+ /**
+ * String representing the name of class applied to the focused active
+ * descendant Node. Can also be an object literal used to define both the
+ * class name, and the Node to which the class should be applied. If using
+ * an object literal, the format is:
+ * <code>{ className: "focus", fn: myFunction }</code>. The function
+ * referenced by the <code>fn</code> property in the object literal will be
+ * passed a reference to the currently focused active descendant Node.
+ *
+ * @attribute focusClass
+ * @type String|Object
+ */
+ focusClass: { },
+
+
+ /**
+ * Boolean indicating if focus should be set to the first/last descendant
+ * when the end or beginning of the descendants has been reached.
+ *
+ * @attribute circular
+ * @type Boolean
+ */
+ circular: {
+ value: true
+ }
+
+};
+
+Y.extend(NodeFocusManager, Y.Plugin.Base, {
+
+ // Protected properties
+
+ // Boolean indicating if the NodeFocusManager is active.
+ _stopped: true,
+
+ // NodeList representing the descendants selected via the
+ // "descendants" attribute.
+ _descendants: null,
+
+ // Object literal mapping the IDs of each descendant to its index in the
+ // "_descendants" NodeList.
+ _descendantsMap: null,
+
+ // Reference to the Node instance to which the focused class (defined
+ // by the "focusClass" attribute) is currently applied.
+ _focusedNode: null,
+
+ // Number representing the index of the last descendant Node.
+ _lastNodeIndex: 0,
+
+ // Array of handles for event handlers used for a NodeFocusManager instance.
+ _eventHandlers: null,
+
+
+
+ // Protected methods
+
+ /**
+ * @method _initDescendants
+ * @description Sets the <code>tabIndex</code> attribute of all of the
+ * descendants to -1, except the active descendant, whose
+ * <code>tabIndex</code> attribute is set to 0.
+ * @protected
+ */
+ _initDescendants: function () {
+
+ var descendants = this.get("descendants"),
+ descendantsMap = {},
+ nFirstEnabled = -1,
+ nDescendants,
+ nActiveDescendant = this.get(ACTIVE_DESCENDANT),
+ oNode,
+ sID,
+ i = 0;
+
+
+
+ if (Lang.isUndefined(nActiveDescendant)) {
+ nActiveDescendant = -1;
+ }
+
+
+ if (descendants) {
+
+ nDescendants = descendants.size();
+
+
+ if (nDescendants > 1) {
+
+ for (i = 0; i < nDescendants; i++) {
+
+ oNode = descendants.item(i);
+
+ if (nFirstEnabled === -1 && !oNode.get(DISABLED)) {
+ nFirstEnabled = i;
+ }
+
+
+ // If the user didn't specify a value for the
+ // "activeDescendant" attribute try to infer it from
+ // the markup.
+
+ // Need to pass "2" when using "getAttribute" for IE to get
+ // the attribute value as it is set in the markup.
+ // Need to use "parseInt" because IE always returns the
+ // value as a number, whereas all other browsers return
+ // the attribute as a string when accessed
+ // via "getAttribute".
+
+ if (nActiveDescendant < 0 &&
+ parseInt(oNode.getAttribute(TAB_INDEX, 2), 10) === 0) {
+
+ nActiveDescendant = i;
+
+ }
+
+ oNode.set(TAB_INDEX, -1);
+
+ sID = oNode.get(ID);
+
+ if (!sID) {
+ sID = Y.guid();
+ oNode.set(ID, sID);
+ }
+
+ descendantsMap[sID] = i;
+
+ }
+
+
+ // If the user didn't specify a value for the
+ // "activeDescendant" attribute and no default value could be
+ // determined from the markup, then default to 0.
+
+ if (nActiveDescendant < 0) {
+ nActiveDescendant = 0;
+ }
+
+
+ oNode = descendants.item(nActiveDescendant);
+
+ // Check to make sure the active descendant isn't disabled,
+ // and fall back to the first enabled descendant if it is.
+
+ if (!oNode || oNode.get(DISABLED)) {
+ oNode = descendants.item(nFirstEnabled);
+ nActiveDescendant = nFirstEnabled;
+ }
+
+ this._lastNodeIndex = nDescendants - 1;
+ this._descendants = descendants;
+ this._descendantsMap = descendantsMap;
+
+ this.set(ACTIVE_DESCENDANT, nActiveDescendant);
+
+ // Need to set the "tabIndex" attribute here, since the
+ // "activeDescendantChange" event handler used to manage
+ // the setting of the "tabIndex" attribute isn't wired up yet.
+
+ oNode.set(TAB_INDEX, 0);
+
+ }
+
+ }
+
+ },
+
+
+ /**
+ * @method _isDescendant
+ * @description Determines if the specified Node instance is a descendant
+ * managed by the Focus Manager.
+ * @param node {Node} Node instance to be checked.
+ * @return {Boolean} Boolean indicating if the specified Node instance is a
+ * descendant managed by the Focus Manager.
+ * @protected
+ */
+ _isDescendant: function (node) {
+
+ return (node.get(ID) in this._descendantsMap);
+
+ },
+
+
+ /**
+ * @method _removeFocusClass
+ * @description Removes the class name representing focus (as specified by
+ * the "focusClass" attribute) from the Node instance to which it is
+ * currently applied.
+ * @protected
+ */
+ _removeFocusClass: function () {
+
+ var oFocusedNode = this._focusedNode,
+ focusClass = this.get(FOCUS_CLASS),
+ sClassName;
+
+ if (focusClass) {
+ sClassName = Lang.isString(focusClass) ?
+ focusClass : focusClass.className;
+ }
+
+ if (oFocusedNode && sClassName) {
+ oFocusedNode.removeClass(sClassName);
+ }
+
+ },
+
+
+ /**
+ * @method _detachKeyHandler
+ * @description Detaches the "key" event handlers used to support the "keys"
+ * attribute.
+ * @protected
+ */
+ _detachKeyHandler: function () {
+
+ var prevKeyHandler = this._prevKeyHandler,
+ nextKeyHandler = this._nextKeyHandler;
+
+ if (prevKeyHandler) {
+ prevKeyHandler.detach();
+ }
+
+ if (nextKeyHandler) {
+ nextKeyHandler.detach();
+ }
+
+ },
+
+
+ /**
+ * @method _preventScroll
+ * @description Prevents the viewport from scolling when the user presses
+ * the up, down, left, or right key.
+ * @protected
+ */
+ _preventScroll: function (event) {
+
+ if (scrollKeys[event.keyCode]) {
+ event.preventDefault();
+ }
+
+ },
+
+
+ /**
+ * @method _preventScroll
+ * @description Fires the click event if the enter key is pressed while
+ * focused on an HTML element that is not natively clickable.
+ * @protected
+ */
+ _fireClick: function (event) {
+
+ var oTarget = event.target,
+ sNodeName = oTarget.get("nodeName").toLowerCase();
+
+ if (event.keyCode === 13 && (!clickableElements[sNodeName] ||
+ (sNodeName === "a" && !oTarget.getAttribute("href")))) {
+
+ Y.log(("Firing click event for node:" + oTarget.get("id")), "info", "nodeFocusManager");
+
+ oTarget.simulate("click");
+
+ }
+
+ },
+
+
+ /**
+ * @method _attachKeyHandler
+ * @description Attaches the "key" event handlers used to support the "keys"
+ * attribute.
+ * @protected
+ */
+ _attachKeyHandler: function () {
+
+ this._detachKeyHandler();
+
+ var sNextKey = this.get("keys.next"),
+ sPrevKey = this.get("keys.previous"),
+ oNode = this.get(HOST),
+ aHandlers = this._eventHandlers;
+
+ if (sPrevKey) {
+ this._prevKeyHandler =
+ Y.on(KEY, Y.bind(this._focusPrevious, this), oNode, sPrevKey);
+ }
+
+ if (sNextKey) {
+ this._nextKeyHandler =
+ Y.on(KEY, Y.bind(this._focusNext, this), oNode, sNextKey);
+ }
+
+
+ // In Opera it is necessary to call the "preventDefault" method in
+ // response to the user pressing the arrow keys in order to prevent
+ // the viewport from scrolling when the user is moving focus among
+ // the focusable descendants.
+
+ if (UA.opera) {
+ aHandlers.push(oNode.on("keypress", this._preventScroll, this));
+ }
+
+
+ // For all browsers except Opera: HTML elements that are not natively
+ // focusable but made focusable via the tabIndex attribute don't
+ // fire a click event when the user presses the enter key. It is
+ // possible to work around this problem by simplying dispatching a
+ // click event in response to the user pressing the enter key.
+
+ if (!UA.opera) {
+ aHandlers.push(oNode.on("keypress", this._fireClick, this));
+ }
+
+ },
+
+
+ /**
+ * @method _detachEventHandlers
+ * @description Detaches all event handlers used by the Focus Manager.
+ * @protected
+ */
+ _detachEventHandlers: function () {
+
+ this._detachKeyHandler();
+
+ var aHandlers = this._eventHandlers;
+
+ if (aHandlers) {
+
+ Y.Array.each(aHandlers, function (handle) {
+ handle.detach();
+ });
+
+ this._eventHandlers = null;
+
+ }
+
+ },
+
+
+ /**
+ * @method _detachEventHandlers
+ * @description Attaches all event handlers used by the Focus Manager.
+ * @protected
+ */
+ _attachEventHandlers: function () {
+
+ var descendants = this._descendants,
+ aHandlers,
+ oDocument,
+ handle;
+
+ if (descendants && descendants.size() > 1) {
+
+ aHandlers = this._eventHandlers || [];
+ oDocument = this.get(HOST).get("ownerDocument");
+
+
+ if (aHandlers.length === 0) {
+
+ Y.log("Attaching base set of event handlers.", "info", "nodeFocusManager");
+
+ aHandlers.push(oDocument.on("focus", this._onDocFocus, this));
+
+ aHandlers.push(oDocument.on("mousedown",
+ this._onDocMouseDown, this));
+
+ aHandlers.push(
+ this.after("keysChange", this._attachKeyHandler));
+
+ aHandlers.push(
+ this.after("descendantsChange", this._initDescendants));
+
+ aHandlers.push(
+ this.after(ACTIVE_DESCENDANT_CHANGE,
+ this._afterActiveDescendantChange));
+
+
+ // For performance: defer attaching all key-related event
+ // handlers until the first time one of the specified
+ // descendants receives focus.
+
+ handle = this.after("focusedChange", Y.bind(function (event) {
+
+ if (event.newVal) {
+
+ Y.log("Attaching key event handlers.", "info", "nodeFocusManager");
+
+ this._attachKeyHandler();
+
+ // Detach this "focusedChange" handler so that the
+ // key-related handlers only get attached once.
+
+ handle.detach();
+
+ }
+
+ }, this));
+
+ aHandlers.push(handle);
+
+ }
+
+
+ this._eventHandlers = aHandlers;
+
+ }
+
+ },
+
+
+ // Protected event handlers
+
+ /**
+ * @method _onDocMouseDown
+ * @description "mousedown" event handler for the owner document of the
+ * Focus Manager's Node.
+ * @protected
+ * @param event {Object} Object representing the DOM event.
+ */
+ _onDocMouseDown: function (event) {
+
+ var oHost = this.get(HOST),
+ oTarget = event.target,
+ bChildNode = oHost.contains(oTarget),
+ node,
+
+ getFocusable = function (node) {
+
+ var returnVal = false;
+
+ if (!node.compareTo(oHost)) {
+
+ returnVal = this._isDescendant(node) ? node :
+ getFocusable.call(this, node.get("parentNode"));
+
+ }
+
+ return returnVal;
+
+ };
+
+
+ if (bChildNode) {
+
+ // Check to make sure that the target isn't a child node of one
+ // of the focusable descendants.
+
+ node = getFocusable.call(this, oTarget);
+
+ if (node) {
+ oTarget = node;
+ }
+ else if (!node && this.get(FOCUSED)) {
+
+ // The target was a non-focusable descendant of the root
+ // node, so the "focused" attribute should be set to false.
+
+ this._set(FOCUSED, false);
+ this._onDocFocus(event);
+
+ }
+
+ }
+
+
+ if (bChildNode && this._isDescendant(oTarget)) {
+
+ // Fix general problem in Webkit: mousing down on a button or an
+ // anchor element doesn't focus it.
+
+ // For all browsers: makes sure that the descendant that
+ // was the target of the mousedown event is now considered the
+ // active descendant.
+
+ this.focus(oTarget);
+ }
+ else if (UA.webkit && this.get(FOCUSED) &&
+ (!bChildNode || (bChildNode && !this._isDescendant(oTarget)))) {
+
+ // Fix for Webkit:
+
+ // Document doesn't receive focus in Webkit when the user mouses
+ // down on it, so the "focused" attribute won't get set to the
+ // correct value.
+
+ // The goal is to force a blur if the user moused down on
+ // either: 1) A descendant node, but not one that managed by
+ // the FocusManager, or 2) an element outside of the
+ // FocusManager
+
+ this._set(FOCUSED, false);
+ this._onDocFocus(event);
+
+ }
+
+ },
+
+
+ /**
+ * @method _onDocFocus
+ * @description "focus" event handler for the owner document of the
+ * Focus Manager's Node.
+ * @protected
+ * @param event {Object} Object representing the DOM event.
+ */
+ _onDocFocus: function (event) {
+
+ var oTarget = this._focusTarget || event.target,
+ bFocused = this.get(FOCUSED),
+ focusClass = this.get(FOCUS_CLASS),
+ oFocusedNode = this._focusedNode,
+ bInCollection;
+
+ if (this._focusTarget) {
+ this._focusTarget = null;
+ }
+
+
+ if (this.get(HOST).contains(oTarget)) {
+
+ // The target is a descendant of the root Node.
+
+ bInCollection = this._isDescendant(oTarget);
+
+ if (!bFocused && bInCollection) {
+
+ // The user has focused a focusable descendant.
+
+ bFocused = true;
+
+ }
+ else if (bFocused && !bInCollection) {
+
+ // The user has focused a child of the root Node that is
+ // not one of the descendants managed by this Focus Manager
+ // so clear the currently focused descendant.
+
+ bFocused = false;
+
+ }
+
+ }
+ else {
+
+ // The target is some other node in the document.
+
+ bFocused = false;
+
+ }
+
+
+ if (focusClass) {
+
+ if (oFocusedNode && (!oFocusedNode.compareTo(oTarget) || !bFocused)) {
+ this._removeFocusClass();
+ }
+
+ if (bInCollection && bFocused) {
+
+ if (focusClass.fn) {
+ oTarget = focusClass.fn(oTarget);
+ oTarget.addClass(focusClass.className);
+ }
+ else {
+ oTarget.addClass(focusClass);
+ }
+
+ this._focusedNode = oTarget;
+
+ }
+
+ }
+
+
+ this._set(FOCUSED, bFocused);
+
+ },
+
+
+ /**
+ * @method _focusNext
+ * @description Keydown event handler that moves focus to the next
+ * enabled descendant.
+ * @protected
+ * @param event {Object} Object representing the DOM event.
+ * @param activeDescendant {Number} Number representing the index of the
+ * next descendant to be focused
+ */
+ _focusNext: function (event, activeDescendant) {
+
+ var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
+ oNode;
+
+
+ if (this._isDescendant(event.target) &&
+ (nActiveDescendant <= this._lastNodeIndex)) {
+
+ nActiveDescendant = nActiveDescendant + 1;
+
+ if (nActiveDescendant === (this._lastNodeIndex + 1) &&
+ this.get(CIRCULAR)) {
+
+ nActiveDescendant = 0;
+
+ }
+
+ oNode = this._descendants.item(nActiveDescendant);
+
+ if (oNode.get(DISABLED)) {
+ this._focusNext(event, nActiveDescendant);
+ }
+ else {
+ this.focus(nActiveDescendant);
+ }
+
+ }
+
+ this._preventScroll(event);
+
+ },
+
+
+ /**
+ * @method _focusPrevious
+ * @description Keydown event handler that moves focus to the previous
+ * enabled descendant.
+ * @protected
+ * @param event {Object} Object representing the DOM event.
+ * @param activeDescendant {Number} Number representing the index of the
+ * next descendant to be focused.
+ */
+ _focusPrevious: function (event, activeDescendant) {
+
+ var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
+ oNode;
+
+ if (this._isDescendant(event.target) && nActiveDescendant >= 0) {
+
+ nActiveDescendant = nActiveDescendant - 1;
+
+ if (nActiveDescendant === -1 && this.get(CIRCULAR)) {
+ nActiveDescendant = this._lastNodeIndex;
+ }
+
+ oNode = this._descendants.item(nActiveDescendant);
+
+ if (oNode.get(DISABLED)) {
+ this._focusPrevious(event, nActiveDescendant);
+ }
+ else {
+ this.focus(nActiveDescendant);
+ }
+
+ }
+
+ this._preventScroll(event);
+
+ },
+
+
+ /**
+ * @method _afterActiveDescendantChange
+ * @description afterChange event handler for the
+ * "activeDescendant" attribute.
+ * @protected
+ * @param event {Object} Object representing the change event.
+ */
+ _afterActiveDescendantChange: function (event) {
+
+ var oNode = this._descendants.item(event.prevVal);
+
+ if (oNode) {
+ oNode.set(TAB_INDEX, -1);
+ }
+
+ oNode = this._descendants.item(event.newVal);
+
+ if (oNode) {
+ oNode.set(TAB_INDEX, 0);
+ }
+
+ },
+
+
+
+ // Public methods
+
+ initializer: function (config) {
+
+ this.start();
+
+ },
+
+ destructor: function () {
+
+ this.stop();
+ this.get(HOST).focusManager = null;
+
+ },
+
+
+ /**
+ * @method focus
+ * @description Focuses the active descendant and sets the
+ * <code>focused</code> attribute to true.
+ * @param index {Number} Optional. Number representing the index of the
+ * descendant to be set as the active descendant.
+ * @param index {Node} Optional. Node instance representing the
+ * descendant to be set as the active descendant.
+ */
+ focus: function (index) {
+
+ if (Lang.isUndefined(index)) {
+ index = this.get(ACTIVE_DESCENDANT);
+ }
+
+ this.set(ACTIVE_DESCENDANT, index, { src: UI });
+
+ var oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
+
+ if (oNode) {
+
+ oNode.focus();
+
+ // In Opera focusing a <BUTTON> element programmatically
+ // will result in the document-level focus event handler
+ // "_onDocFocus" being called, resulting in the handler
+ // incorrectly setting the "focused" Attribute to false. To fix
+ // this, set a flag ("_focusTarget") that the "_onDocFocus" method
+ // can look for to properly handle this edge case.
+
+ if (UA.opera && oNode.get("nodeName").toLowerCase() === "button") {
+ this._focusTarget = oNode;
+ }
+
+ }
+
+ },
+
+
+ /**
+ * @method blur
+ * @description Blurs the current active descendant and sets the
+ * <code>focused</code> attribute to false.
+ */
+ blur: function () {
+
+ var oNode;
+
+ if (this.get(FOCUSED)) {
+
+ oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
+
+ if (oNode) {
+
+ oNode.blur();
+
+ // For Opera and Webkit: Blurring an element in either browser
+ // doesn't result in another element (such as the document)
+ // being focused. Therefore, the "_onDocFocus" method
+ // responsible for managing the application and removal of the
+ // focus indicator class name is never called.
+
+ this._removeFocusClass();
+
+ }
+
+ this._set(FOCUSED, false, { src: UI });
+ }
+
+ },
+
+
+ /**
+ * @method start
+ * @description Enables the Focus Manager.
+ */
+ start: function () {
+
+ if (this._stopped) {
+
+ this._initDescendants();
+ this._attachEventHandlers();
+
+ this._stopped = false;
+
+ }
+
+ },
+
+
+ /**
+ * @method stop
+ * @description Disables the Focus Manager by detaching all event handlers.
+ */
+ stop: function () {
+
+ if (!this._stopped) {
+
+ this._detachEventHandlers();
+
+ this._descendants = null;
+ this._focusedNode = null;
+ this._lastNodeIndex = 0;
+ this._stopped = true;
+
+ }
+
+ },
+
+
+ /**
+ * @method refresh
+ * @description Refreshes the Focus Manager's descendants by re-executing the
+ * CSS selector query specified by the <code>descendants</code> attribute.
+ */
+ refresh: function () {
+
+ this._initDescendants();
+
+ }
+
+});
+
+
+NodeFocusManager.NAME = "nodeFocusManager";
+NodeFocusManager.NS = "focusManager";
+
+Y.namespace("Plugin");
+Y.Plugin.NodeFocusManager = NodeFocusManager;
+