Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / node-focusmanager / node-focusmanager-debug.js
blob0e1c5e346929d391c9687c22dd7c9d494a0be88b
1 /*
2 YUI 3.5.0 (build 5089)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('node-focusmanager', function(Y) {
9 /**
10 * <p>The Focus Manager Node Plugin makes it easy to manage focus among
11 * a Node's descendants.  Primarily intended to help with widget development,
12 * the Focus Manager Node Plugin can be used to improve the keyboard
13 * accessibility of widgets.</p>
15 * <p>
16 * When designing widgets that manage a set of descendant controls (i.e. buttons
17 * in a toolbar, tabs in a tablist, menuitems in a menu, etc.) it is important to
18 * limit the number of descendants in the browser's default tab flow.  The fewer
19 * number of descendants in the default tab flow, the easier it is for keyboard
20 * users to navigate between widgets by pressing the tab key.  When a widget has
21 * focus it should provide a set of shortcut keys (typically the arrow keys)
22 * to move focus among its descendants.
23 * </p>
25 * <p>
26 * To this end, the Focus Manager Node Plugin makes it easy to define a Node's
27 * focusable descendants, define which descendant should be in the default tab
28 * flow, and define the keys that move focus among each descendant.
29 * Additionally, as the CSS
30 * <a href="http://www.w3.org/TR/CSS21/selector.html#x38"><code>:focus</code></a>
31 * pseudo class is not supported on all elements in all
32 * <a href="http://developer.yahoo.com/yui/articles/gbs/">A-Grade browsers</a>,
33 * the Focus Manager Node Plugin provides an easy, cross-browser means of
34 * styling focus.
35 * </p>
37 * @module node-focusmanager
40         //      Frequently used strings
42 var ACTIVE_DESCENDANT = "activeDescendant",
43         ID = "id",
44         DISABLED = "disabled",
45         TAB_INDEX = "tabIndex",
46         FOCUSED = "focused",
47         FOCUS_CLASS = "focusClass",
48         CIRCULAR = "circular",
49         UI = "UI",
50         KEY = "key",
51         ACTIVE_DESCENDANT_CHANGE = ACTIVE_DESCENDANT + "Change",
52         HOST = "host",
54         //      Collection of keys that, when pressed, cause the browser viewport
55         //      to scroll.
56         scrollKeys = {
57                 37: true,
58                 38: true,
59                 39: true,
60                 40: true
61         },
63         clickableElements = {
64                 "a": true,
65                 "button": true,
66                 "input": true,
67                 "object": true
68         },
70         //      Library shortcuts
72         Lang = Y.Lang,
73         UA = Y.UA,
75         /**
76         * The NodeFocusManager class is a plugin for a Node instance.  The class is used
77         * via the <a href="Node.html#method_plug"><code>plug</code></a> method of Node
78         * and should not be instantiated directly.
79         * @namespace plugin
80         * @class NodeFocusManager
81         */
82         NodeFocusManager = function () {
84                 NodeFocusManager.superclass.constructor.apply(this, arguments);
86         };
89 NodeFocusManager.ATTRS = {
91         /**
92         * Boolean indicating that one of the descendants is focused.
93         *
94         * @attribute focused
95         * @readOnly
96         * @default false
97         * @type boolean
98         */
99         focused: {
101                 value: false,
102                 readOnly: true
104         },
107         /**
108         * String representing the CSS selector used to define the descendant Nodes
109         * whose focus should be managed.
110         *
111         * @attribute descendants
112         * @type Y.NodeList
113         */
114         descendants: {
116                 getter: function (value) {
118                         return this.get(HOST).all(value);
120                 }
122         },
125         /**
126         * <p>Node, or index of the Node, representing the descendant that is either
127         * focused or is focusable (<code>tabIndex</code> attribute is set to 0).
128         * The value cannot represent a disabled descendant Node.  Use a value of -1
129         * to remove all descendant Nodes from the default tab flow.
130         * If no value is specified, the active descendant will be inferred using
131         * the following criteria:</p>
132         * <ol>
133         * <li>Examining the <code>tabIndex</code> attribute of each descendant and
134         * using the first descendant whose <code>tabIndex</code> attribute is set
135         * to 0</li>
136         * <li>If no default can be inferred then the value is set to either 0 or
137         * the index of the first enabled descendant.</li>
138         * </ol>
139         *
140         * @attribute activeDescendant
141         * @type Number
142         */
143         activeDescendant: {
145                 setter: function (value) {
147                         var isNumber = Lang.isNumber,
148                                 INVALID_VALUE = Y.Attribute.INVALID_VALUE,
149                                 descendantsMap = this._descendantsMap,
150                                 descendants = this._descendants,
151                                 nodeIndex,
152                                 returnValue,
153                                 oNode;
156                         if (isNumber(value)) {
157                                 nodeIndex = value;
158                                 returnValue = nodeIndex;
159                         }
160                         else if ((value instanceof Y.Node) && descendantsMap) {
162                                 nodeIndex = descendantsMap[value.get(ID)];
164                                 if (isNumber(nodeIndex)) {
165                                         returnValue = nodeIndex;
166                                 }
167                                 else {
169                                         //      The user passed a reference to a Node that wasn't one
170                                         //      of the descendants.
171                                         returnValue = INVALID_VALUE;
173                                 }
175                         }
176                         else {
177                                 returnValue = INVALID_VALUE;
178                         }
181                         if (descendants) {
183                                 oNode = descendants.item(nodeIndex);
185                                 if (oNode && oNode.get("disabled")) {
187                                         //      Setting the "activeDescendant" attribute to the index
188                                         //      of a disabled descendant is invalid.
189                                         returnValue = INVALID_VALUE;
191                                 }
193                         }
195                         return returnValue;
197                 }
199         },
202         /**
203         * Object literal representing the keys to be used to navigate between the
204         * next/previous descendant.  The format for the attribute's value is
205         * <code>{ next: "down:40", previous: "down:38" }</code>.  The value for the
206         * "next" and "previous" properties are used to attach
207         * <a href="event/#keylistener"><code>key</code></a> event listeners. See
208         * the <a href="event/#keylistener">Using the key Event</a> section of
209         * the Event documentation for more information on "key" event listeners.
210         *
211         * @attribute keys
212         * @type Object
213         */
214         keys: {
216                 value: {
218                         next: null,
219                         previous: null
221                 }
224         },
227         /**
228         * String representing the name of class applied to the focused active
229         * descendant Node.  Can also be an object literal used to define both the
230         * class name, and the Node to which the class should be applied.  If using
231         * an object literal, the format is:
232         * <code>{ className: "focus", fn: myFunction }</code>.  The function
233         * referenced by the <code>fn</code> property in the object literal will be
234         * passed a reference to the currently focused active descendant Node.
235         *
236         * @attribute focusClass
237         * @type String|Object
238         */
239         focusClass: { },
242         /**
243         * Boolean indicating if focus should be set to the first/last descendant
244         * when the end or beginning of the descendants has been reached.
245         *
246         * @attribute circular
247         * @type Boolean
248         * @default true
249         */
250         circular: {
251                 value: true
252         }
256 Y.extend(NodeFocusManager, Y.Plugin.Base, {
258         //      Protected properties
260         //      Boolean indicating if the NodeFocusManager is active.
261         _stopped: true,
263         //      NodeList representing the descendants selected via the
264         //      "descendants" attribute.
265         _descendants: null,
267         //      Object literal mapping the IDs of each descendant to its index in the
268         //      "_descendants" NodeList.
269         _descendantsMap: null,
271         //      Reference to the Node instance to which the focused class (defined
272         //      by the "focusClass" attribute) is currently applied.
273         _focusedNode: null,
275         //      Number representing the index of the last descendant Node.
276         _lastNodeIndex: 0,
278         //      Array of handles for event handlers used for a NodeFocusManager instance.
279         _eventHandlers: null,
283         //      Protected methods
285         /**
286         * @method _initDescendants
287         * @description Sets the <code>tabIndex</code> attribute of all of the
288         * descendants to -1, except the active descendant, whose
289         * <code>tabIndex</code> attribute is set to 0.
290         * @protected
291         */
292         _initDescendants: function () {
294                 var descendants = this.get("descendants"),
295                         descendantsMap = {},
296                         nFirstEnabled = -1,
297                         nDescendants,
298                         nActiveDescendant = this.get(ACTIVE_DESCENDANT),
299                         oNode,
300                         sID,
301                         i = 0;
305                 if (Lang.isUndefined(nActiveDescendant)) {
306                         nActiveDescendant = -1;
307                 }
310                 if (descendants) {
312                         nDescendants = descendants.size();
315             for (i = 0; i < nDescendants; i++) {
317                 oNode = descendants.item(i);
319                 if (nFirstEnabled === -1 && !oNode.get(DISABLED)) {
320                     nFirstEnabled = i;
321                 }
324                 //      If the user didn't specify a value for the
325                 //      "activeDescendant" attribute try to infer it from
326                 //      the markup.
328                 //      Need to pass "2" when using "getAttribute" for IE to get
329                 //      the attribute value as it is set in the markup.
330                 //      Need to use "parseInt" because IE always returns the
331                 //      value as a number, whereas all other browsers return
332                 //      the attribute as a string when accessed
333                 //      via "getAttribute".
335                 if (nActiveDescendant < 0 &&
336                         parseInt(oNode.getAttribute(TAB_INDEX, 2), 10) === 0) {
338                     nActiveDescendant = i;
340                 }
342                 if (oNode) {
343                     oNode.set(TAB_INDEX, -1);
344                 }
346                 sID = oNode.get(ID);
348                 if (!sID) {
349                     sID = Y.guid();
350                     oNode.set(ID, sID);
351                 }
353                 descendantsMap[sID] = i;
355             }
358             //  If the user didn't specify a value for the
359             //  "activeDescendant" attribute and no default value could be
360             //  determined from the markup, then default to 0.
362             if (nActiveDescendant < 0) {
363                 nActiveDescendant = 0;
364             }
367             oNode = descendants.item(nActiveDescendant);
369             //  Check to make sure the active descendant isn't disabled,
370             //  and fall back to the first enabled descendant if it is.
372             if (!oNode || oNode.get(DISABLED)) {
373                 oNode = descendants.item(nFirstEnabled);
374                 nActiveDescendant = nFirstEnabled;
375             }
377             this._lastNodeIndex = nDescendants - 1;
378             this._descendants = descendants;
379             this._descendantsMap = descendantsMap;
381             this.set(ACTIVE_DESCENDANT, nActiveDescendant);
383             //  Need to set the "tabIndex" attribute here, since the
384             //  "activeDescendantChange" event handler used to manage
385             //  the setting of the "tabIndex" attribute isn't wired up yet.
387             if (oNode) {
388                 oNode.set(TAB_INDEX, 0);
389             }
391                 }
393         },
396         /**
397         * @method _isDescendant
398         * @description Determines if the specified Node instance is a descendant
399         * managed by the Focus Manager.
400         * @param node {Node} Node instance to be checked.
401         * @return {Boolean} Boolean indicating if the specified Node instance is a
402         * descendant managed by the Focus Manager.
403         * @protected
404         */
405         _isDescendant: function (node) {
407                 return (node.get(ID) in this._descendantsMap);
409         },
412         /**
413         * @method _removeFocusClass
414         * @description Removes the class name representing focus (as specified by
415         * the "focusClass" attribute) from the Node instance to which it is
416         * currently applied.
417         * @protected
418         */
419         _removeFocusClass: function () {
421                 var oFocusedNode = this._focusedNode,
422                         focusClass = this.get(FOCUS_CLASS),
423                         sClassName;
425                 if (focusClass) {
426                         sClassName = Lang.isString(focusClass) ?
427                                 focusClass : focusClass.className;
428                 }
430                 if (oFocusedNode && sClassName) {
431                         oFocusedNode.removeClass(sClassName);
432                 }
434         },
437         /**
438         * @method _detachKeyHandler
439         * @description Detaches the "key" event handlers used to support the "keys"
440         * attribute.
441         * @protected
442         */
443         _detachKeyHandler: function () {
445                 var prevKeyHandler = this._prevKeyHandler,
446                         nextKeyHandler = this._nextKeyHandler;
448                 if (prevKeyHandler) {
449                         prevKeyHandler.detach();
450                 }
452                 if (nextKeyHandler) {
453                         nextKeyHandler.detach();
454                 }
456         },
459         /**
460         * @method _preventScroll
461         * @description Prevents the viewport from scolling when the user presses
462         * the up, down, left, or right key.
463         * @protected
464         */
465         _preventScroll: function (event) {
467                 if (scrollKeys[event.keyCode] && this._isDescendant(event.target)) {
468                         event.preventDefault();
469                 }
471         },
474         /**
475         * @method _fireClick
476         * @description Fires the click event if the enter key is pressed while
477         * focused on an HTML element that is not natively clickable.
478         * @protected
479         */
480         _fireClick: function (event) {
482                 var oTarget = event.target,
483                         sNodeName = oTarget.get("nodeName").toLowerCase();
485                 if (event.keyCode === 13 && (!clickableElements[sNodeName] ||
486                                 (sNodeName === "a" && !oTarget.getAttribute("href")))) {
488                         Y.log(("Firing click event for node:" + oTarget.get("id")), "info", "nodeFocusManager");
490                         oTarget.simulate("click");
492                 }
494         },
497         /**
498         * @method _attachKeyHandler
499         * @description Attaches the "key" event handlers used to support the "keys"
500         * attribute.
501         * @protected
502         */
503         _attachKeyHandler: function () {
505                 this._detachKeyHandler();
507                 var sNextKey = this.get("keys.next"),
508                         sPrevKey = this.get("keys.previous"),
509                         oNode = this.get(HOST),
510                         aHandlers = this._eventHandlers;
512                 if (sPrevKey) {
513                         this._prevKeyHandler =
514                                 Y.on(KEY, Y.bind(this._focusPrevious, this), oNode, sPrevKey);
515                 }
517                 if (sNextKey) {
518                         this._nextKeyHandler =
519                                 Y.on(KEY, Y.bind(this._focusNext, this), oNode, sNextKey);
520                 }
523                 //      In Opera it is necessary to call the "preventDefault" method in
524                 //      response to the user pressing the arrow keys in order to prevent
525                 //      the viewport from scrolling when the user is moving focus among
526                 //      the focusable descendants.
528                 if (UA.opera) {
529                         aHandlers.push(oNode.on("keypress", this._preventScroll, this));
530                 }
533                 //      For all browsers except Opera: HTML elements that are not natively
534                 //      focusable but made focusable via the tabIndex attribute don't
535                 //      fire a click event when the user presses the enter key.  It is
536                 //      possible to work around this problem by simplying dispatching a
537                 //      click event in response to the user pressing the enter key.
539                 if (!UA.opera) {
540                         aHandlers.push(oNode.on("keypress", this._fireClick, this));
541                 }
543         },
546         /**
547         * @method _detachEventHandlers
548         * @description Detaches all event handlers used by the Focus Manager.
549         * @protected
550         */
551         _detachEventHandlers: function () {
553                 this._detachKeyHandler();
555                 var aHandlers = this._eventHandlers;
557                 if (aHandlers) {
559                         Y.Array.each(aHandlers, function (handle) {
560                                 handle.detach();
561                         });
563                         this._eventHandlers = null;
565                 }
567         },
570         /**
571         * @method _detachEventHandlers
572         * @description Attaches all event handlers used by the Focus Manager.
573         * @protected
574         */
575         _attachEventHandlers: function () {
577                 var descendants = this._descendants,
578                         aHandlers,
579                         oDocument,
580                         handle;
582                 if (descendants && descendants.size()) {
584                         aHandlers = this._eventHandlers || [];
585                         oDocument = this.get(HOST).get("ownerDocument");
588                         if (aHandlers.length === 0) {
590                         Y.log("Attaching base set of event handlers.", "info", "nodeFocusManager");
592                                 aHandlers.push(oDocument.on("focus", this._onDocFocus, this));
594                                 aHandlers.push(oDocument.on("mousedown",
595                                         this._onDocMouseDown, this));
597                                 aHandlers.push(
598                                                 this.after("keysChange", this._attachKeyHandler));
600                                 aHandlers.push(
601                                                 this.after("descendantsChange", this._initDescendants));
603                                 aHandlers.push(
604                                                 this.after(ACTIVE_DESCENDANT_CHANGE,
605                                                                 this._afterActiveDescendantChange));
608                                 //      For performance: defer attaching all key-related event
609                                 //      handlers until the first time one of the specified
610                                 //      descendants receives focus.
612                                 handle = this.after("focusedChange", Y.bind(function (event) {
614                                         if (event.newVal) {
616                                         Y.log("Attaching key event handlers.", "info", "nodeFocusManager");
618                                                 this._attachKeyHandler();
620                                                 //      Detach this "focusedChange" handler so that the
621                                                 //      key-related handlers only get attached once.
623                                                 handle.detach();
625                                         }
627                                 }, this));
629                                 aHandlers.push(handle);
631                         }
634                         this._eventHandlers = aHandlers;
636                 }
638         },
641         //      Protected event handlers
643         /**
644         * @method _onDocMouseDown
645         * @description "mousedown" event handler for the owner document of the
646         * Focus Manager's Node.
647         * @protected
648         * @param event {Object} Object representing the DOM event.
649         */
650         _onDocMouseDown: function (event) {
652                 var oHost = this.get(HOST),
653                         oTarget = event.target,
654                         bChildNode = oHost.contains(oTarget),
655                         node,
657                         getFocusable = function (node) {
659                                 var returnVal = false;
661                                 if (!node.compareTo(oHost)) {
663                                         returnVal = this._isDescendant(node) ? node :
664                                                                         getFocusable.call(this, node.get("parentNode"));
666                                 }
668                                 return returnVal;
670                         };
673                 if (bChildNode) {
675                         //      Check to make sure that the target isn't a child node of one
676                         //      of the focusable descendants.
678                         node = getFocusable.call(this, oTarget);
680                         if (node) {
681                                 oTarget = node;
682                         }
683                         else if (!node && this.get(FOCUSED)) {
685                                 //      The target was a non-focusable descendant of the root
686                                 //      node, so the "focused" attribute should be set to false.
688                                 this._set(FOCUSED, false);
689                                 this._onDocFocus(event);
691                         }
693                 }
696                 if (bChildNode && this._isDescendant(oTarget)) {
698                         //      Fix general problem in Webkit: mousing down on a button or an
699                         //      anchor element doesn't focus it.
701                         //      For all browsers: makes sure that the descendant that
702                         //      was the target of the mousedown event is now considered the
703                         //      active descendant.
705                         this.focus(oTarget);
706                 }
707                 else if (UA.webkit && this.get(FOCUSED) &&
708                         (!bChildNode || (bChildNode && !this._isDescendant(oTarget)))) {
710                         //      Fix for Webkit:
712                         //      Document doesn't receive focus in Webkit when the user mouses
713                         //      down on it, so the "focused" attribute won't get set to the
714                         //      correct value.
716                         //      The goal is to force a blur if the user moused down on
717                         //      either: 1) A descendant node, but not one that managed by
718                         //      the FocusManager, or 2) an element outside of the
719                         //      FocusManager
721                         this._set(FOCUSED, false);
722                         this._onDocFocus(event);
724                 }
726         },
729         /**
730         * @method _onDocFocus
731         * @description "focus" event handler for the owner document of the
732         * Focus Manager's Node.
733         * @protected
734         * @param event {Object} Object representing the DOM event.
735         */
736         _onDocFocus: function (event) {
738                 var oTarget = this._focusTarget || event.target,
739                         bFocused = this.get(FOCUSED),
740                         focusClass = this.get(FOCUS_CLASS),
741                         oFocusedNode = this._focusedNode,
742                         bInCollection;
744                 if (this._focusTarget) {
745                         this._focusTarget = null;
746                 }
749                 if (this.get(HOST).contains(oTarget)) {
751                         //      The target is a descendant of the root Node.
753                         bInCollection = this._isDescendant(oTarget);
755                         if (!bFocused && bInCollection) {
757                                 //      The user has focused a focusable descendant.
759                                 bFocused = true;
761                         }
762                         else if (bFocused && !bInCollection) {
764                                 //      The user has focused a child of the root Node that is
765                                 //      not one of the descendants managed by this Focus Manager
766                                 //      so clear the currently focused descendant.
768                                 bFocused = false;
770                         }
772                 }
773                 else {
775                         // The target is some other node in the document.
777                         bFocused = false;
779                 }
782                 if (focusClass) {
784                         if (oFocusedNode && (!oFocusedNode.compareTo(oTarget) || !bFocused)) {
785                                 this._removeFocusClass();
786                         }
788                         if (bInCollection && bFocused) {
790                                 if (focusClass.fn) {
791                                         oTarget = focusClass.fn(oTarget);
792                                         oTarget.addClass(focusClass.className);
793                                 }
794                                 else {
795                                         oTarget.addClass(focusClass);
796                                 }
798                                 this._focusedNode = oTarget;
800                         }
802                 }
805                 this._set(FOCUSED, bFocused);
807         },
810         /**
811         * @method _focusNext
812         * @description Keydown event handler that moves focus to the next
813         * enabled descendant.
814         * @protected
815         * @param event {Object} Object representing the DOM event.
816         * @param activeDescendant {Number} Number representing the index of the
817         * next descendant to be focused
818         */
819         _focusNext: function (event, activeDescendant) {
821                 var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
822                         oNode;
825                 if (this._isDescendant(event.target) &&
826                         (nActiveDescendant <= this._lastNodeIndex)) {
828                         nActiveDescendant = nActiveDescendant + 1;
830                         if (nActiveDescendant === (this._lastNodeIndex + 1) &&
831                                 this.get(CIRCULAR)) {
833                                 nActiveDescendant = 0;
835                         }
837                         oNode = this._descendants.item(nActiveDescendant);
839             if (oNode) {
841                 if (oNode.get("disabled")) {
842                     this._focusNext(event, nActiveDescendant);
843                 }
844                 else {
845                     this.focus(nActiveDescendant);
846                 }
848             }
850                 }
852                 this._preventScroll(event);
854         },
857         /**
858         * @method _focusPrevious
859         * @description Keydown event handler that moves focus to the previous
860         * enabled descendant.
861         * @protected
862         * @param event {Object} Object representing the DOM event.
863         * @param activeDescendant {Number} Number representing the index of the
864         * next descendant to be focused.
865         */
866         _focusPrevious: function (event, activeDescendant) {
868                 var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
869                         oNode;
871                 if (this._isDescendant(event.target) && nActiveDescendant >= 0) {
873                         nActiveDescendant = nActiveDescendant - 1;
875                         if (nActiveDescendant === -1 && this.get(CIRCULAR)) {
876                                 nActiveDescendant = this._lastNodeIndex;
877                         }
879             oNode = this._descendants.item(nActiveDescendant);
881             if (oNode) {
883                 if (oNode.get("disabled")) {
884                     this._focusPrevious(event, nActiveDescendant);
885                 }
886                 else {
887                     this.focus(nActiveDescendant);
888                 }
890             }
892                 }
894                 this._preventScroll(event);
896         },
899         /**
900         * @method _afterActiveDescendantChange
901         * @description afterChange event handler for the
902         * "activeDescendant" attribute.
903         * @protected
904         * @param event {Object} Object representing the change event.
905         */
906         _afterActiveDescendantChange: function (event) {
908                 var oNode = this._descendants.item(event.prevVal);
910                 if (oNode) {
911                         oNode.set(TAB_INDEX, -1);
912                 }
914                 oNode = this._descendants.item(event.newVal);
916                 if (oNode) {
917                         oNode.set(TAB_INDEX, 0);
918                 }
920         },
924         //      Public methods
926     initializer: function (config) {
928                 this.start();
930     },
932         destructor: function () {
934                 this.stop();
935                 this.get(HOST).focusManager = null;
937     },
940         /**
941         * @method focus
942         * @description Focuses the active descendant and sets the
943         * <code>focused</code> attribute to true.
944         * @param index {Number} Optional. Number representing the index of the
945         * descendant to be set as the active descendant.
946         * @param index {Node} Optional. Node instance representing the
947         * descendant to be set as the active descendant.
948         */
949         focus: function (index) {
951                 if (Lang.isUndefined(index)) {
952                         index = this.get(ACTIVE_DESCENDANT);
953                 }
955                 this.set(ACTIVE_DESCENDANT, index, { src: UI });
957                 var oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
959                 if (oNode) {
961                         oNode.focus();
963                         //      In Opera focusing a <BUTTON> element programmatically
964                         //      will result in the document-level focus event handler
965                         //      "_onDocFocus" being called, resulting in the handler
966                         //      incorrectly setting the "focused" Attribute to false.  To fix
967                         //      this, set a flag ("_focusTarget") that the "_onDocFocus" method
968                         //      can look for to properly handle this edge case.
970                         if (UA.opera && oNode.get("nodeName").toLowerCase() === "button") {
971                                 this._focusTarget = oNode;
972                         }
974                 }
976         },
979         /**
980         * @method blur
981         * @description Blurs the current active descendant and sets the
982         * <code>focused</code> attribute to false.
983         */
984         blur: function () {
986                 var oNode;
988                 if (this.get(FOCUSED)) {
990                         oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
992                         if (oNode) {
994                                 oNode.blur();
996                                 //      For Opera and Webkit:  Blurring an element in either browser
997                                 //      doesn't result in another element (such as the document)
998                                 //      being focused.  Therefore, the "_onDocFocus" method
999                                 //      responsible for managing the application and removal of the
1000                                 //      focus indicator class name is never called.
1002                                 this._removeFocusClass();
1004                         }
1006                         this._set(FOCUSED, false, { src: UI });
1007                 }
1009         },
1012         /**
1013         * @method start
1014         * @description Enables the Focus Manager.
1015         */
1016         start: function () {
1018                 if (this._stopped) {
1020                         this._initDescendants();
1021                         this._attachEventHandlers();
1023                         this._stopped = false;
1025                 }
1027         },
1030         /**
1031         * @method stop
1032         * @description Disables the Focus Manager by detaching all event handlers.
1033         */
1034         stop: function () {
1036                 if (!this._stopped) {
1038                         this._detachEventHandlers();
1040                         this._descendants = null;
1041                         this._focusedNode = null;
1042                         this._lastNodeIndex = 0;
1043                         this._stopped = true;
1045                 }
1047         },
1050         /**
1051         * @method refresh
1052         * @description Refreshes the Focus Manager's descendants by re-executing the
1053         * CSS selector query specified by the <code>descendants</code> attribute.
1054         */
1055         refresh: function () {
1057                 this._initDescendants();
1059                 if (!this._eventHandlers) {
1060                         this._attachEventHandlers();
1061                 }
1063         }
1068 NodeFocusManager.NAME = "nodeFocusManager";
1069 NodeFocusManager.NS = "focusManager";
1071 Y.namespace("Plugin");
1072 Y.Plugin.NodeFocusManager = NodeFocusManager;
1075 }, '3.5.0' ,{requires:['attribute', 'node', 'plugin', 'node-event-simulate', 'event-key', 'event-focus']});