NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / node-focusmanager / node-focusmanager.js
blob2b8f0d84da2e654d7006007a39c4d93e6f590742
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('node-focusmanager', function (Y, NAME) {
10 /**
11 * <p>The Focus Manager Node Plugin makes it easy to manage focus among
12 * a Node's descendants.  Primarily intended to help with widget development,
13 * the Focus Manager Node Plugin can be used to improve the keyboard
14 * accessibility of widgets.</p>
16 * <p>
17 * When designing widgets that manage a set of descendant controls (i.e. buttons
18 * in a toolbar, tabs in a tablist, menuitems in a menu, etc.) it is important to
19 * limit the number of descendants in the browser's default tab flow.  The fewer
20 * number of descendants in the default tab flow, the easier it is for keyboard
21 * users to navigate between widgets by pressing the tab key.  When a widget has
22 * focus it should provide a set of shortcut keys (typically the arrow keys)
23 * to move focus among its descendants.
24 * </p>
26 * <p>
27 * To this end, the Focus Manager Node Plugin makes it easy to define a Node's
28 * focusable descendants, define which descendant should be in the default tab
29 * flow, and define the keys that move focus among each descendant.
30 * Additionally, as the CSS
31 * <a href="http://www.w3.org/TR/CSS21/selector.html#x38"><code>:focus</code></a>
32 * pseudo class is not supported on all elements in all
33 * <a href="http://developer.yahoo.com/yui/articles/gbs/">A-Grade browsers</a>,
34 * the Focus Manager Node Plugin provides an easy, cross-browser means of
35 * styling focus.
36 * </p>
39 DEPRECATED: The FocusManager Node Plugin has been deprecated as of YUI 3.9.0. This module will be removed from the library in a future version. If you require functionality similar to the one provided by this  module, consider taking a look at the various modules in the YUI Gallery <http://yuilibrary.com/gallery/>.
41 * @module node-focusmanager
42 * @deprecated 3.9.0
45         //      Frequently used strings
47 var ACTIVE_DESCENDANT = "activeDescendant",
48         ID = "id",
49         DISABLED = "disabled",
50         TAB_INDEX = "tabIndex",
51         FOCUSED = "focused",
52         FOCUS_CLASS = "focusClass",
53         CIRCULAR = "circular",
54         UI = "UI",
55         KEY = "key",
56         ACTIVE_DESCENDANT_CHANGE = ACTIVE_DESCENDANT + "Change",
57         HOST = "host",
59         //      Collection of keys that, when pressed, cause the browser viewport
60         //      to scroll.
61         scrollKeys = {
62                 37: true,
63                 38: true,
64                 39: true,
65                 40: true
66         },
68         clickableElements = {
69                 "a": true,
70                 "button": true,
71                 "input": true,
72                 "object": true
73         },
75         //      Library shortcuts
77         Lang = Y.Lang,
78         UA = Y.UA,
80         /**
81         * The NodeFocusManager class is a plugin for a Node instance.  The class is used
82         * via the <a href="Node.html#method_plug"><code>plug</code></a> method of Node
83         * and should not be instantiated directly.
84         * @namespace plugin
85         * @class NodeFocusManager
86         */
87         NodeFocusManager = function () {
89                 NodeFocusManager.superclass.constructor.apply(this, arguments);
91         };
94 NodeFocusManager.ATTRS = {
96         /**
97         * Boolean indicating that one of the descendants is focused.
98         *
99         * @attribute focused
100         * @readOnly
101         * @default false
102         * @type boolean
103         */
104         focused: {
106                 value: false,
107                 readOnly: true
109         },
112         /**
113         * String representing the CSS selector used to define the descendant Nodes
114         * whose focus should be managed.
115         *
116         * @attribute descendants
117         * @type Y.NodeList
118         */
119         descendants: {
121                 getter: function (value) {
123                         return this.get(HOST).all(value);
125                 }
127         },
130         /**
131         * <p>Node, or index of the Node, representing the descendant that is either
132         * focused or is focusable (<code>tabIndex</code> attribute is set to 0).
133         * The value cannot represent a disabled descendant Node.  Use a value of -1
134         * to remove all descendant Nodes from the default tab flow.
135         * If no value is specified, the active descendant will be inferred using
136         * the following criteria:</p>
137         * <ol>
138         * <li>Examining the <code>tabIndex</code> attribute of each descendant and
139         * using the first descendant whose <code>tabIndex</code> attribute is set
140         * to 0</li>
141         * <li>If no default can be inferred then the value is set to either 0 or
142         * the index of the first enabled descendant.</li>
143         * </ol>
144         *
145         * @attribute activeDescendant
146         * @type Number
147         */
148         activeDescendant: {
150                 setter: function (value) {
152                         var isNumber = Lang.isNumber,
153                                 INVALID_VALUE = Y.Attribute.INVALID_VALUE,
154                                 descendantsMap = this._descendantsMap,
155                                 descendants = this._descendants,
156                                 nodeIndex,
157                                 returnValue,
158                                 oNode;
161                         if (isNumber(value)) {
162                                 nodeIndex = value;
163                                 returnValue = nodeIndex;
164                         }
165                         else if ((value instanceof Y.Node) && descendantsMap) {
167                                 nodeIndex = descendantsMap[value.get(ID)];
169                                 if (isNumber(nodeIndex)) {
170                                         returnValue = nodeIndex;
171                                 }
172                                 else {
174                                         //      The user passed a reference to a Node that wasn't one
175                                         //      of the descendants.
176                                         returnValue = INVALID_VALUE;
178                                 }
180                         }
181                         else {
182                                 returnValue = INVALID_VALUE;
183                         }
186                         if (descendants) {
188                                 oNode = descendants.item(nodeIndex);
190                                 if (oNode && oNode.get("disabled")) {
192                                         //      Setting the "activeDescendant" attribute to the index
193                                         //      of a disabled descendant is invalid.
194                                         returnValue = INVALID_VALUE;
196                                 }
198                         }
201                         return returnValue;
203                 }
205         },
208         /**
209         * Object literal representing the keys to be used to navigate between the
210         * next/previous descendant.  The format for the attribute's value is
211         * <code>{ next: "down:40", previous: "down:38" }</code>.  The value for the
212         * "next" and "previous" properties are used to attach
213         * <a href="event/#keylistener"><code>key</code></a> event listeners. See
214         * the <a href="event/#keylistener">Using the key Event</a> section of
215         * the Event documentation for more information on "key" event listeners.
216         *
217         * @attribute keys
218         * @type Object
219         */
220         keys: {
222                 value: {
224                         next: null,
225                         previous: null
227                 }
230         },
233         /**
234         * String representing the name of class applied to the focused active
235         * descendant Node.  Can also be an object literal used to define both the
236         * class name, and the Node to which the class should be applied.  If using
237         * an object literal, the format is:
238         * <code>{ className: "focus", fn: myFunction }</code>.  The function
239         * referenced by the <code>fn</code> property in the object literal will be
240         * passed a reference to the currently focused active descendant Node.
241         *
242         * @attribute focusClass
243         * @type String|Object
244         */
245         focusClass: { },
248         /**
249         * Boolean indicating if focus should be set to the first/last descendant
250         * when the end or beginning of the descendants has been reached.
251         *
252         * @attribute circular
253         * @type Boolean
254         * @default true
255         */
256         circular: {
257                 value: true
258         }
262 Y.extend(NodeFocusManager, Y.Plugin.Base, {
264         //      Protected properties
266         //      Boolean indicating if the NodeFocusManager is active.
267         _stopped: true,
269         //      NodeList representing the descendants selected via the
270         //      "descendants" attribute.
271         _descendants: null,
273         //      Object literal mapping the IDs of each descendant to its index in the
274         //      "_descendants" NodeList.
275         _descendantsMap: null,
277         //      Reference to the Node instance to which the focused class (defined
278         //      by the "focusClass" attribute) is currently applied.
279         _focusedNode: null,
281         //      Number representing the index of the last descendant Node.
282         _lastNodeIndex: 0,
284         //      Array of handles for event handlers used for a NodeFocusManager instance.
285         _eventHandlers: null,
289         //      Protected methods
291         /**
292         * @method _initDescendants
293         * @description Sets the <code>tabIndex</code> attribute of all of the
294         * descendants to -1, except the active descendant, whose
295         * <code>tabIndex</code> attribute is set to 0.
296         * @protected
297         */
298         _initDescendants: function () {
300                 var descendants = this.get("descendants"),
301                         descendantsMap = {},
302                         nFirstEnabled = -1,
303                         nDescendants,
304                         nActiveDescendant = this.get(ACTIVE_DESCENDANT),
305                         oNode,
306                         sID,
307                         i = 0;
311                 if (Lang.isUndefined(nActiveDescendant)) {
312                         nActiveDescendant = -1;
313                 }
316                 if (descendants) {
318                         nDescendants = descendants.size();
321             for (i = 0; i < nDescendants; i++) {
323                 oNode = descendants.item(i);
325                 if (nFirstEnabled === -1 && !oNode.get(DISABLED)) {
326                     nFirstEnabled = i;
327                 }
330                 //      If the user didn't specify a value for the
331                 //      "activeDescendant" attribute try to infer it from
332                 //      the markup.
334                 //      Need to pass "2" when using "getAttribute" for IE to get
335                 //      the attribute value as it is set in the markup.
336                 //      Need to use "parseInt" because IE always returns the
337                 //      value as a number, whereas all other browsers return
338                 //      the attribute as a string when accessed
339                 //      via "getAttribute".
341                 if (nActiveDescendant < 0 &&
342                         parseInt(oNode.getAttribute(TAB_INDEX, 2), 10) === 0) {
344                     nActiveDescendant = i;
346                 }
348                 if (oNode) {
349                     oNode.set(TAB_INDEX, -1);
350                 }
352                 sID = oNode.get(ID);
354                 if (!sID) {
355                     sID = Y.guid();
356                     oNode.set(ID, sID);
357                 }
359                 descendantsMap[sID] = i;
361             }
364             //  If the user didn't specify a value for the
365             //  "activeDescendant" attribute and no default value could be
366             //  determined from the markup, then default to 0.
368             if (nActiveDescendant < 0) {
369                 nActiveDescendant = 0;
370             }
373             oNode = descendants.item(nActiveDescendant);
375             //  Check to make sure the active descendant isn't disabled,
376             //  and fall back to the first enabled descendant if it is.
378             if (!oNode || oNode.get(DISABLED)) {
379                 oNode = descendants.item(nFirstEnabled);
380                 nActiveDescendant = nFirstEnabled;
381             }
383             this._lastNodeIndex = nDescendants - 1;
384             this._descendants = descendants;
385             this._descendantsMap = descendantsMap;
387             this.set(ACTIVE_DESCENDANT, nActiveDescendant);
389             //  Need to set the "tabIndex" attribute here, since the
390             //  "activeDescendantChange" event handler used to manage
391             //  the setting of the "tabIndex" attribute isn't wired up yet.
393             if (oNode) {
394                 oNode.set(TAB_INDEX, 0);
395             }
397                 }
399         },
402         /**
403         * @method _isDescendant
404         * @description Determines if the specified Node instance is a descendant
405         * managed by the Focus Manager.
406         * @param node {Node} Node instance to be checked.
407         * @return {Boolean} Boolean indicating if the specified Node instance is a
408         * descendant managed by the Focus Manager.
409         * @protected
410         */
411         _isDescendant: function (node) {
413                 return (node.get(ID) in this._descendantsMap);
415         },
418         /**
419         * @method _removeFocusClass
420         * @description Removes the class name representing focus (as specified by
421         * the "focusClass" attribute) from the Node instance to which it is
422         * currently applied.
423         * @protected
424         */
425         _removeFocusClass: function () {
427                 var oFocusedNode = this._focusedNode,
428                         focusClass = this.get(FOCUS_CLASS),
429                         sClassName;
431                 if (focusClass) {
432                         sClassName = Lang.isString(focusClass) ?
433                                 focusClass : focusClass.className;
434                 }
436                 if (oFocusedNode && sClassName) {
437                         oFocusedNode.removeClass(sClassName);
438                 }
440         },
443         /**
444         * @method _detachKeyHandler
445         * @description Detaches the "key" event handlers used to support the "keys"
446         * attribute.
447         * @protected
448         */
449         _detachKeyHandler: function () {
451                 var prevKeyHandler = this._prevKeyHandler,
452                         nextKeyHandler = this._nextKeyHandler;
454                 if (prevKeyHandler) {
455                         prevKeyHandler.detach();
456                 }
458                 if (nextKeyHandler) {
459                         nextKeyHandler.detach();
460                 }
462         },
465         /**
466         * @method _preventScroll
467         * @description Prevents the viewport from scolling when the user presses
468         * the up, down, left, or right key.
469         * @protected
470         */
471         _preventScroll: function (event) {
473                 if (scrollKeys[event.keyCode] && this._isDescendant(event.target)) {
474                         event.preventDefault();
475                 }
477         },
480         /**
481         * @method _fireClick
482         * @description Fires the click event if the enter key is pressed while
483         * focused on an HTML element that is not natively clickable.
484         * @protected
485         */
486         _fireClick: function (event) {
488                 var oTarget = event.target,
489                         sNodeName = oTarget.get("nodeName").toLowerCase();
491                 if (event.keyCode === 13 && (!clickableElements[sNodeName] ||
492                                 (sNodeName === "a" && !oTarget.getAttribute("href")))) {
495                         oTarget.simulate("click");
497                 }
499         },
502         /**
503         * @method _attachKeyHandler
504         * @description Attaches the "key" event handlers used to support the "keys"
505         * attribute.
506         * @protected
507         */
508         _attachKeyHandler: function () {
510                 this._detachKeyHandler();
512                 var sNextKey = this.get("keys.next"),
513                         sPrevKey = this.get("keys.previous"),
514                         oNode = this.get(HOST),
515                         aHandlers = this._eventHandlers;
517                 if (sPrevKey) {
518                         this._prevKeyHandler =
519                                 Y.on(KEY, Y.bind(this._focusPrevious, this), oNode, sPrevKey);
520                 }
522                 if (sNextKey) {
523                         this._nextKeyHandler =
524                                 Y.on(KEY, Y.bind(this._focusNext, this), oNode, sNextKey);
525                 }
528                 //      In Opera it is necessary to call the "preventDefault" method in
529                 //      response to the user pressing the arrow keys in order to prevent
530                 //      the viewport from scrolling when the user is moving focus among
531                 //      the focusable descendants.
533                 if (UA.opera) {
534                         aHandlers.push(oNode.on("keypress", this._preventScroll, this));
535                 }
538                 //      For all browsers except Opera: HTML elements that are not natively
539                 //      focusable but made focusable via the tabIndex attribute don't
540                 //      fire a click event when the user presses the enter key.  It is
541                 //      possible to work around this problem by simplying dispatching a
542                 //      click event in response to the user pressing the enter key.
544                 if (!UA.opera) {
545                         aHandlers.push(oNode.on("keypress", this._fireClick, this));
546                 }
548         },
551         /**
552         * @method _detachEventHandlers
553         * @description Detaches all event handlers used by the Focus Manager.
554         * @protected
555         */
556         _detachEventHandlers: function () {
558                 this._detachKeyHandler();
560                 var aHandlers = this._eventHandlers;
562                 if (aHandlers) {
564                         Y.Array.each(aHandlers, function (handle) {
565                                 handle.detach();
566                         });
568                         this._eventHandlers = null;
570                 }
572         },
575         /**
576         * @method _detachEventHandlers
577         * @description Attaches all event handlers used by the Focus Manager.
578         * @protected
579         */
580         _attachEventHandlers: function () {
582                 var descendants = this._descendants,
583                         aHandlers,
584                         oDocument,
585                         handle;
587                 if (descendants && descendants.size()) {
589                         aHandlers = this._eventHandlers || [];
590                         oDocument = this.get(HOST).get("ownerDocument");
593                         if (aHandlers.length === 0) {
596                                 aHandlers.push(oDocument.on("focus", this._onDocFocus, this));
598                                 aHandlers.push(oDocument.on("mousedown",
599                                         this._onDocMouseDown, this));
601                                 aHandlers.push(
602                                                 this.after("keysChange", this._attachKeyHandler));
604                                 aHandlers.push(
605                                                 this.after("descendantsChange", this._initDescendants));
607                                 aHandlers.push(
608                                                 this.after(ACTIVE_DESCENDANT_CHANGE,
609                                                                 this._afterActiveDescendantChange));
612                                 //      For performance: defer attaching all key-related event
613                                 //      handlers until the first time one of the specified
614                                 //      descendants receives focus.
616                                 handle = this.after("focusedChange", Y.bind(function (event) {
618                                         if (event.newVal) {
621                                                 this._attachKeyHandler();
623                                                 //      Detach this "focusedChange" handler so that the
624                                                 //      key-related handlers only get attached once.
626                                                 handle.detach();
628                                         }
630                                 }, this));
632                                 aHandlers.push(handle);
634                         }
637                         this._eventHandlers = aHandlers;
639                 }
641         },
644         //      Protected event handlers
646         /**
647         * @method _onDocMouseDown
648         * @description "mousedown" event handler for the owner document of the
649         * Focus Manager's Node.
650         * @protected
651         * @param event {Object} Object representing the DOM event.
652         */
653         _onDocMouseDown: function (event) {
655                 var oHost = this.get(HOST),
656                         oTarget = event.target,
657                         bChildNode = oHost.contains(oTarget),
658                         node,
660                         getFocusable = function (node) {
662                                 var returnVal = false;
664                                 if (!node.compareTo(oHost)) {
666                                         returnVal = this._isDescendant(node) ? node :
667                                                                         getFocusable.call(this, node.get("parentNode"));
669                                 }
671                                 return returnVal;
673                         };
676                 if (bChildNode) {
678                         //      Check to make sure that the target isn't a child node of one
679                         //      of the focusable descendants.
681                         node = getFocusable.call(this, oTarget);
683                         if (node) {
684                                 oTarget = node;
685                         }
686                         else if (!node && this.get(FOCUSED)) {
688                                 //      The target was a non-focusable descendant of the root
689                                 //      node, so the "focused" attribute should be set to false.
691                                 this._set(FOCUSED, false);
692                                 this._onDocFocus(event);
694                         }
696                 }
699                 if (bChildNode && this._isDescendant(oTarget)) {
701                         //      Fix general problem in Webkit: mousing down on a button or an
702                         //      anchor element doesn't focus it.
704                         //      For all browsers: makes sure that the descendant that
705                         //      was the target of the mousedown event is now considered the
706                         //      active descendant.
708                         this.focus(oTarget);
709                 }
710                 else if (UA.webkit && this.get(FOCUSED) &&
711                         (!bChildNode || (bChildNode && !this._isDescendant(oTarget)))) {
713                         //      Fix for Webkit:
715                         //      Document doesn't receive focus in Webkit when the user mouses
716                         //      down on it, so the "focused" attribute won't get set to the
717                         //      correct value.
719                         //      The goal is to force a blur if the user moused down on
720                         //      either: 1) A descendant node, but not one that managed by
721                         //      the FocusManager, or 2) an element outside of the
722                         //      FocusManager
724                         this._set(FOCUSED, false);
725                         this._onDocFocus(event);
727                 }
729         },
732         /**
733         * @method _onDocFocus
734         * @description "focus" event handler for the owner document of the
735         * Focus Manager's Node.
736         * @protected
737         * @param event {Object} Object representing the DOM event.
738         */
739         _onDocFocus: function (event) {
741                 var oTarget = this._focusTarget || event.target,
742                         bFocused = this.get(FOCUSED),
743                         focusClass = this.get(FOCUS_CLASS),
744                         oFocusedNode = this._focusedNode,
745                         bInCollection;
747                 if (this._focusTarget) {
748                         this._focusTarget = null;
749                 }
752                 if (this.get(HOST).contains(oTarget)) {
754                         //      The target is a descendant of the root Node.
756                         bInCollection = this._isDescendant(oTarget);
758                         if (!bFocused && bInCollection) {
760                                 //      The user has focused a focusable descendant.
762                                 bFocused = true;
764                         }
765                         else if (bFocused && !bInCollection) {
767                                 //      The user has focused a child of the root Node that is
768                                 //      not one of the descendants managed by this Focus Manager
769                                 //      so clear the currently focused descendant.
771                                 bFocused = false;
773                         }
775                 }
776                 else {
778                         // The target is some other node in the document.
780                         bFocused = false;
782                 }
785                 if (focusClass) {
787                         if (oFocusedNode && (!oFocusedNode.compareTo(oTarget) || !bFocused)) {
788                                 this._removeFocusClass();
789                         }
791                         if (bInCollection && bFocused) {
793                                 if (focusClass.fn) {
794                                         oTarget = focusClass.fn(oTarget);
795                                         oTarget.addClass(focusClass.className);
796                                 }
797                                 else {
798                                         oTarget.addClass(focusClass);
799                                 }
801                                 this._focusedNode = oTarget;
803                         }
805                 }
808                 this._set(FOCUSED, bFocused);
810         },
813         /**
814         * @method _focusNext
815         * @description Keydown event handler that moves focus to the next
816         * enabled descendant.
817         * @protected
818         * @param event {Object} Object representing the DOM event.
819         * @param activeDescendant {Number} Number representing the index of the
820         * next descendant to be focused
821         */
822         _focusNext: function (event, activeDescendant) {
824                 var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
825                         oNode;
828                 if (this._isDescendant(event.target) &&
829                         (nActiveDescendant <= this._lastNodeIndex)) {
831                         nActiveDescendant = nActiveDescendant + 1;
833                         if (nActiveDescendant === (this._lastNodeIndex + 1) &&
834                                 this.get(CIRCULAR)) {
836                                 nActiveDescendant = 0;
838                         }
840                         oNode = this._descendants.item(nActiveDescendant);
842             if (oNode) {
844                 if (oNode.get("disabled")) {
845                     this._focusNext(event, nActiveDescendant);
846                 }
847                 else {
848                     this.focus(nActiveDescendant);
849                 }
851             }
853                 }
855                 this._preventScroll(event);
857         },
860         /**
861         * @method _focusPrevious
862         * @description Keydown event handler that moves focus to the previous
863         * enabled descendant.
864         * @protected
865         * @param event {Object} Object representing the DOM event.
866         * @param activeDescendant {Number} Number representing the index of the
867         * next descendant to be focused.
868         */
869         _focusPrevious: function (event, activeDescendant) {
871                 var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
872                         oNode;
874                 if (this._isDescendant(event.target) && nActiveDescendant >= 0) {
876                         nActiveDescendant = nActiveDescendant - 1;
878                         if (nActiveDescendant === -1 && this.get(CIRCULAR)) {
879                                 nActiveDescendant = this._lastNodeIndex;
880                         }
882             oNode = this._descendants.item(nActiveDescendant);
884             if (oNode) {
886                 if (oNode.get("disabled")) {
887                     this._focusPrevious(event, nActiveDescendant);
888                 }
889                 else {
890                     this.focus(nActiveDescendant);
891                 }
893             }
895                 }
897                 this._preventScroll(event);
899         },
902         /**
903         * @method _afterActiveDescendantChange
904         * @description afterChange event handler for the
905         * "activeDescendant" attribute.
906         * @protected
907         * @param event {Object} Object representing the change event.
908         */
909         _afterActiveDescendantChange: function (event) {
911                 var oNode = this._descendants.item(event.prevVal);
913                 if (oNode) {
914                         oNode.set(TAB_INDEX, -1);
915                 }
917                 oNode = this._descendants.item(event.newVal);
919                 if (oNode) {
920                         oNode.set(TAB_INDEX, 0);
921                 }
923         },
927         //      Public methods
929     initializer: function (config) {
930                 this.start();
932     },
934         destructor: function () {
936                 this.stop();
937                 this.get(HOST).focusManager = null;
939     },
942         /**
943         * @method focus
944         * @description Focuses the active descendant and sets the
945         * <code>focused</code> attribute to true.
946         * @param index {Number} Optional. Number representing the index of the
947         * descendant to be set as the active descendant.
948         * @param index {Node} Optional. Node instance representing the
949         * descendant to be set as the active descendant.
950         */
951         focus: function (index) {
953                 if (Lang.isUndefined(index)) {
954                         index = this.get(ACTIVE_DESCENDANT);
955                 }
957                 this.set(ACTIVE_DESCENDANT, index, { src: UI });
959                 var oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
961                 if (oNode) {
963                         oNode.focus();
965                         //      In Opera focusing a <BUTTON> element programmatically
966                         //      will result in the document-level focus event handler
967                         //      "_onDocFocus" being called, resulting in the handler
968                         //      incorrectly setting the "focused" Attribute to false.  To fix
969                         //      this, set a flag ("_focusTarget") that the "_onDocFocus" method
970                         //      can look for to properly handle this edge case.
972                         if (UA.opera && oNode.get("nodeName").toLowerCase() === "button") {
973                                 this._focusTarget = oNode;
974                         }
976                 }
978         },
981         /**
982         * @method blur
983         * @description Blurs the current active descendant and sets the
984         * <code>focused</code> attribute to false.
985         */
986         blur: function () {
988                 var oNode;
990                 if (this.get(FOCUSED)) {
992                         oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
994                         if (oNode) {
996                                 oNode.blur();
998                                 //      For Opera and Webkit:  Blurring an element in either browser
999                                 //      doesn't result in another element (such as the document)
1000                                 //      being focused.  Therefore, the "_onDocFocus" method
1001                                 //      responsible for managing the application and removal of the
1002                                 //      focus indicator class name is never called.
1004                                 this._removeFocusClass();
1006                         }
1008                         this._set(FOCUSED, false, { src: UI });
1009                 }
1011         },
1014         /**
1015         * @method start
1016         * @description Enables the Focus Manager.
1017         */
1018         start: function () {
1020                 if (this._stopped) {
1022                         this._initDescendants();
1023                         this._attachEventHandlers();
1025                         this._stopped = false;
1027                 }
1029         },
1032         /**
1033         * @method stop
1034         * @description Disables the Focus Manager by detaching all event handlers.
1035         */
1036         stop: function () {
1038                 if (!this._stopped) {
1040                         this._detachEventHandlers();
1042                         this._descendants = null;
1043                         this._focusedNode = null;
1044                         this._lastNodeIndex = 0;
1045                         this._stopped = true;
1047                 }
1049         },
1052         /**
1053         * @method refresh
1054         * @description Refreshes the Focus Manager's descendants by re-executing the
1055         * CSS selector query specified by the <code>descendants</code> attribute.
1056         */
1057         refresh: function () {
1059                 this._initDescendants();
1061                 if (!this._eventHandlers) {
1062                         this._attachEventHandlers();
1063                 }
1065         }
1070 NodeFocusManager.NAME = "nodeFocusManager";
1071 NodeFocusManager.NS = "focusManager";
1073 Y.namespace("Plugin");
1074 Y.Plugin.NodeFocusManager = NodeFocusManager;
1077 }, '3.13.0', {"requires": ["attribute", "node", "plugin", "node-event-simulate", "event-key", "event-focus"]});