src/cm/media/js/lib/yui/yui_3.0.0b1/build/node-focusmanager/node-focusmanager-debug.js
changeset 0 40c8f766c9b8
equal deleted inserted replaced
-1:000000000000 0:40c8f766c9b8
       
     1 /*
       
     2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
       
     3 Code licensed under the BSD License:
       
     4 http://developer.yahoo.net/yui/license.txt
       
     5 version: 3.0.0b1
       
     6 build: 1163
       
     7 */
       
     8 YUI.add('node-focusmanager', function(Y) {
       
     9 
       
    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>
       
    15 * 
       
    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>
       
    25 * 
       
    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>
       
    37 *  
       
    38 * @module node-focusmanager
       
    39 */
       
    40 
       
    41 	//	Frequently used strings
       
    42 
       
    43 var ACTIVE_DESCENDANT = "activeDescendant",
       
    44 	ID = "id",
       
    45 	DISABLED = "disabled",
       
    46 	TAB_INDEX = "tabIndex",
       
    47 	FOCUSED = "focused",
       
    48 	FOCUS_CLASS = "focusClass",
       
    49 	CIRCULAR = "circular",
       
    50 	UI = "UI",
       
    51 	KEY = "key",
       
    52 	ACTIVE_DESCENDANT_CHANGE = ACTIVE_DESCENDANT + "Change",
       
    53 	HOST = "host",
       
    54 
       
    55 	//	Collection of keys that, when pressed, cause the browser viewport
       
    56 	//	to scroll.
       
    57 	scrollKeys = {
       
    58 		37: true,
       
    59 		38: true,
       
    60 		39: true,
       
    61 		40: true
       
    62 	},
       
    63 	
       
    64 	clickableElements = {
       
    65 		"a": true,
       
    66 		"button": true,
       
    67 		"input": true,
       
    68 		"object": true
       
    69 	},	
       
    70 
       
    71 	//	Library shortcuts
       
    72 
       
    73 	Lang = Y.Lang,
       
    74  	UA = Y.UA,
       
    75 
       
    76 	/**
       
    77 	* The NodeFocusManager class is a plugin for a Node instance.  The class is used 
       
    78 	* via the <a href="Node.html#method_plug"><code>plug</code></a> method of Node 
       
    79 	* and should not be instantiated directly.
       
    80 	* @namespace plugin
       
    81 	* @class NodeFocusManager
       
    82 	*/	
       
    83 	NodeFocusManager = function () {
       
    84 
       
    85 		NodeFocusManager.superclass.constructor.apply(this, arguments);
       
    86 
       
    87 	};
       
    88 
       
    89 
       
    90 NodeFocusManager.ATTRS = {
       
    91 
       
    92 	/**
       
    93 	* Boolean indicating that one of the descendants is focused.
       
    94 	*
       
    95 	* @attribute focused
       
    96 	* @readOnly
       
    97 	* @default false
       
    98 	* @type boolean
       
    99 	*/
       
   100 	focused: {
       
   101 		
       
   102 		value: false,
       
   103 		readOnly: true
       
   104 		
       
   105 	},
       
   106 
       
   107 
       
   108 	/**
       
   109 	* String representing the CSS selector used to define the descendant Nodes 
       
   110 	* whose focus should be managed.
       
   111 	*
       
   112 	* @attribute descendants
       
   113 	* @type Y.NodeList
       
   114 	*/
       
   115 	descendants: {
       
   116 
       
   117 		getter: function (value) {
       
   118 
       
   119 			return this.get(HOST).queryAll(value);
       
   120 			
       
   121 		}
       
   122 
       
   123 	},
       
   124 
       
   125 
       
   126 	/**
       
   127 	* <p>Node, or index of the Node, representing the descendant that is either 
       
   128 	* focused or is focusable (<code>tabIndex</code> attribute is set to 0).  
       
   129 	* The value cannot represent a disabled descendant Node.  Use a value of -1
       
   130 	* to remove all descendant Nodes from the default tab flow.
       
   131 	* If no value is specified, the active descendant will be inferred using 
       
   132 	* the following criteria:</p>
       
   133 	* <ol>
       
   134 	* <li>Examining the <code>tabIndex</code> attribute of each descendant and 
       
   135 	* using the first descendant whose <code>tabIndex</code> attribute is set 
       
   136 	* to 0</li>
       
   137 	* <li>If no default can be inferred then the value is set to either 0 or 
       
   138 	* the index of the first enabled descendant.</li>
       
   139 	* </ol>
       
   140 	* 
       
   141 	* @attribute activeDescendant
       
   142 	* @type Number
       
   143 	*/
       
   144 	activeDescendant: {
       
   145 
       
   146 		setter: function (value) {
       
   147 			
       
   148 			var isNumber = Lang.isNumber,
       
   149 				INVALID_VALUE = Y.Attribute.INVALID_VALUE,
       
   150 				descendantsMap = this._descendantsMap,
       
   151 				descendants = this._descendants,
       
   152 				nodeIndex,
       
   153 				returnValue,
       
   154 				oNode;
       
   155 			
       
   156 
       
   157 			if (isNumber(value)) {
       
   158 				nodeIndex = value;
       
   159 				returnValue = nodeIndex;
       
   160 			}
       
   161 			else if ((value instanceof Y.Node) && descendantsMap) {
       
   162 
       
   163 				nodeIndex = descendantsMap[value.get(ID)];
       
   164 
       
   165 				if (isNumber(nodeIndex)) {
       
   166 					returnValue = nodeIndex;
       
   167 				}
       
   168 				else {
       
   169 
       
   170 					//	The user passed a reference to a Node that wasn't one
       
   171 					//	of the descendants.
       
   172 					returnValue = INVALID_VALUE;					
       
   173 
       
   174 				}
       
   175 
       
   176 			}
       
   177 			else {
       
   178 				returnValue = INVALID_VALUE;
       
   179 			}
       
   180 
       
   181 
       
   182 			if (descendants) {
       
   183 
       
   184 				oNode = descendants.item(nodeIndex);
       
   185 			
       
   186 				if (oNode && oNode.get("disabled")) {
       
   187 
       
   188 					//	Setting the "activeDescendant" attribute to the index
       
   189 					//	of a disabled descendant is invalid.
       
   190 					returnValue = INVALID_VALUE;
       
   191 					
       
   192 				}
       
   193 
       
   194 			}
       
   195 
       
   196 			return returnValue;
       
   197 			
       
   198 		}		
       
   199 
       
   200 	},
       
   201 
       
   202 
       
   203 	/**
       
   204 	* Object literal representing the keys to be used to navigate between the 
       
   205 	* next/previous descendant.  The format for the attribute's value is 
       
   206 	* <code>{ next: "down:40", previous: "down:38" }</code>.  The value for the 
       
   207 	* "next" and "previous" properties are used to attach 
       
   208 	* <a href="event/#keylistener"><code>key</code></a> event listeners. See 
       
   209 	* the <a href="event/#keylistener">Using the key Event</a> section of 
       
   210 	* the Event documentation for more information on "key" event listeners.
       
   211 	* 
       
   212 	* @attribute keys
       
   213 	* @type Object
       
   214 	*/
       
   215 	keys: {
       
   216 	
       
   217 		value: {
       
   218 
       
   219 			next: null,
       
   220 			previous: null
       
   221 			
       
   222 		}
       
   223 
       
   224 		
       
   225 	},
       
   226 
       
   227 
       
   228 	/**
       
   229 	* String representing the name of class applied to the focused active  
       
   230 	* descendant Node.  Can also be an object literal used to define both the 
       
   231 	* class name, and the Node to which the class should be applied.  If using 
       
   232 	* an object literal, the format is:
       
   233 	* <code>{ className: "focus", fn: myFunction }</code>.  The function 
       
   234 	* referenced by the <code>fn</code> property in the object literal will be
       
   235 	* passed a reference to the currently focused active descendant Node.
       
   236 	* 
       
   237 	* @attribute focusClass
       
   238 	* @type String|Object
       
   239 	*/
       
   240 	focusClass: { },
       
   241 
       
   242 
       
   243 	/**
       
   244 	* Boolean indicating if focus should be set to the first/last descendant 
       
   245 	* when the end or beginning of the descendants has been reached.
       
   246 	* 
       
   247 	* @attribute circular
       
   248 	* @type Boolean
       
   249 	*/
       
   250 	circular: {
       
   251 		value: true
       
   252 	}
       
   253 	
       
   254 };
       
   255 
       
   256 Y.extend(NodeFocusManager, Y.Plugin.Base, {
       
   257 
       
   258 	//	Protected properties
       
   259 
       
   260 	//	Boolean indicating if the NodeFocusManager is active.
       
   261 	_stopped: true,
       
   262 
       
   263 	//	NodeList representing the descendants selected via the 
       
   264 	//	"descendants" attribute.
       
   265 	_descendants: null,
       
   266 	
       
   267 	//	Object literal mapping the IDs of each descendant to its index in the 
       
   268 	//	"_descendants" NodeList.
       
   269 	_descendantsMap: null,
       
   270 
       
   271 	//	Reference to the Node instance to which the focused class (defined 
       
   272 	//	by the "focusClass" attribute) is currently applied.
       
   273 	_focusedNode: null,
       
   274 	
       
   275 	//	Number representing the index of the last descendant Node.
       
   276 	_lastNodeIndex: 0,
       
   277 
       
   278 	//	Array of handles for event handlers used for a NodeFocusManager instance.
       
   279 	_eventHandlers: null,
       
   280 
       
   281 
       
   282 
       
   283 	//	Protected methods
       
   284 
       
   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 () {
       
   293 
       
   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;
       
   302 
       
   303 
       
   304 
       
   305 		if (Lang.isUndefined(nActiveDescendant)) {
       
   306 			nActiveDescendant = -1;
       
   307 		}
       
   308 
       
   309 
       
   310 		if (descendants) {
       
   311 
       
   312 			nDescendants = descendants.size();
       
   313 			
       
   314 
       
   315 			if (nDescendants > 1) {
       
   316 
       
   317 				for (i = 0; i < nDescendants; i++) {
       
   318 
       
   319 					oNode = descendants.item(i);
       
   320 
       
   321 					if (nFirstEnabled === -1 && !oNode.get(DISABLED)) {
       
   322 						nFirstEnabled = i;
       
   323 					}
       
   324 
       
   325 
       
   326 					//	If the user didn't specify a value for the 
       
   327 					//	"activeDescendant" attribute try to infer it from 
       
   328 					//	the markup.
       
   329 
       
   330 					if (nActiveDescendant < 0 && 
       
   331 							oNode.getAttribute(TAB_INDEX) === "0") {
       
   332 
       
   333 						nActiveDescendant = i;
       
   334 
       
   335 					}
       
   336 
       
   337 					oNode.set(TAB_INDEX, -1);
       
   338 
       
   339 					sID = oNode.get(ID);
       
   340 
       
   341 					if (!sID) {
       
   342 						sID = Y.guid();
       
   343 						oNode.set(ID, sID);
       
   344 					}
       
   345 					
       
   346 					descendantsMap[sID] = i;
       
   347 					
       
   348 				}
       
   349 				
       
   350 
       
   351 				//	If the user didn't specify a value for the  
       
   352 				//	"activeDescendant" attribute and no default value could be 
       
   353 				//	determined from the markup, then default to 0.
       
   354 				
       
   355 				if (nActiveDescendant < 0) {
       
   356 					nActiveDescendant = 0;
       
   357 				}
       
   358 				
       
   359 
       
   360 				oNode = descendants.item(nActiveDescendant);
       
   361 
       
   362 				//	Check to make sure the active descendant isn't disabled, 
       
   363 				//	and fall back to the first enabled descendant if it is.
       
   364 
       
   365 				if (!oNode || oNode.get(DISABLED)) {
       
   366 					oNode = descendants.item(nFirstEnabled);
       
   367 					nActiveDescendant = nFirstEnabled;
       
   368 				}
       
   369 
       
   370 				this._lastNodeIndex = nDescendants - 1;
       
   371 				this._descendants = descendants;
       
   372 				this._descendantsMap = descendantsMap;
       
   373 
       
   374 				this.set(ACTIVE_DESCENDANT, nActiveDescendant);
       
   375 
       
   376 				//	Need to set the "tabIndex" attribute here, since the 
       
   377 				//	"activeDescendantChange" event handler used to manage
       
   378 				//	the setting of the "tabIndex" attribute isn't wired up yet.
       
   379 
       
   380 				oNode.set(TAB_INDEX, 0);
       
   381 
       
   382 			}
       
   383 			
       
   384 		}
       
   385 
       
   386 	},
       
   387 
       
   388 
       
   389 	/**
       
   390 	* @method _isDescendant
       
   391 	* @description Determines if the specified Node instance is a descendant
       
   392 	* managed by the Focus Manager.
       
   393 	* @param node {Node} Node instance to be checked.
       
   394 	* @return {Boolean} Boolean indicating if the specified Node instance is a 
       
   395 	* descendant managed by the Focus Manager.
       
   396 	* @protected
       
   397 	*/
       
   398 	_isDescendant: function (node) {
       
   399 
       
   400 		return (node.get(ID) in this._descendantsMap);
       
   401 		
       
   402 	},
       
   403 	
       
   404 
       
   405 	/**
       
   406 	* @method _removeFocusClass
       
   407 	* @description Removes the class name representing focus (as specified by 
       
   408 	* the "focusClass" attribute) from the Node instance to which it is 
       
   409 	* currently applied.
       
   410 	* @protected
       
   411 	*/
       
   412 	_removeFocusClass: function () {
       
   413 
       
   414 		var oFocusedNode = this._focusedNode,
       
   415 			focusClass = this.get(FOCUS_CLASS),
       
   416 			sClassName;
       
   417 
       
   418 		if (focusClass) {
       
   419 			sClassName = Lang.isString(focusClass) ? 
       
   420 				focusClass : focusClass.className;		
       
   421 		}
       
   422 
       
   423 		if (oFocusedNode && sClassName) {
       
   424 			oFocusedNode.removeClass(sClassName);
       
   425 		}
       
   426 		
       
   427 	},
       
   428 
       
   429 
       
   430 	/**
       
   431 	* @method _detachKeyHandler
       
   432 	* @description Detaches the "key" event handlers used to support the "keys"
       
   433 	* attribute.
       
   434 	* @protected
       
   435 	*/
       
   436 	_detachKeyHandler: function () {
       
   437 
       
   438 		var prevKeyHandler = this._prevKeyHandler,
       
   439 			nextKeyHandler = this._nextKeyHandler;
       
   440 
       
   441 		if (prevKeyHandler) {
       
   442 			prevKeyHandler.detach();
       
   443 		}
       
   444 		
       
   445 		if (nextKeyHandler) {
       
   446 			nextKeyHandler.detach();
       
   447 		}
       
   448 		
       
   449 	},
       
   450 
       
   451 
       
   452 	/**
       
   453 	* @method _preventScroll
       
   454 	* @description Prevents the viewport from scolling when the user presses 
       
   455 	* the up, down, left, or right key.
       
   456 	* @protected
       
   457 	*/
       
   458 	_preventScroll: function (event) {
       
   459 
       
   460 		if (scrollKeys[event.keyCode]) {
       
   461 			event.preventDefault();
       
   462 		}
       
   463 		
       
   464 	},
       
   465 
       
   466 
       
   467 	/**
       
   468 	* @method _preventScroll
       
   469 	* @description Fires the click event if the enter key is pressed while 
       
   470 	* focused on an HTML element that is not natively clickable.
       
   471 	* @protected
       
   472 	*/
       
   473 	_fireClick: function (event) {
       
   474 		
       
   475 		var oTarget = event.target,
       
   476 			sNodeName = oTarget.get("nodeName").toLowerCase();
       
   477 
       
   478 		if (event.keyCode === 13 && (!clickableElements[sNodeName] || 
       
   479 				(sNodeName === "a" && !oTarget.getAttribute("href")))) {
       
   480 
       
   481 			Y.log(("Firing click event for node:" + oTarget.get("id")), "info", "nodeFocusManager");
       
   482 
       
   483 			oTarget.simulate("click");
       
   484 			
       
   485 		}
       
   486 		
       
   487 	},
       
   488 
       
   489 
       
   490 	/**
       
   491 	* @method _attachKeyHandler
       
   492 	* @description Attaches the "key" event handlers used to support the "keys"
       
   493 	* attribute.
       
   494 	* @protected
       
   495 	*/
       
   496 	_attachKeyHandler: function () {
       
   497 
       
   498 		this._detachKeyHandler();
       
   499 
       
   500 		var sNextKey = this.get("keys.next"),
       
   501 			sPrevKey = this.get("keys.previous"),
       
   502 			oNode = this.get(HOST),
       
   503 			aHandlers = this._eventHandlers;
       
   504 
       
   505 		if (sPrevKey) {
       
   506  			this._prevKeyHandler = 
       
   507 				Y.on(KEY, Y.bind(this._focusPrevious, this), oNode, sPrevKey);
       
   508 		}
       
   509 
       
   510 		if (sNextKey) {
       
   511  			this._nextKeyHandler = 
       
   512 				Y.on(KEY, Y.bind(this._focusNext, this), oNode, sNextKey);
       
   513 		}
       
   514 
       
   515 
       
   516 		//	In Opera it is necessary to call the "preventDefault" method in  
       
   517 		//	response to the user pressing the arrow keys in order to prevent 
       
   518 		//	the viewport from scrolling when the user is moving focus among 
       
   519 		//	the focusable descendants.
       
   520 		
       
   521 		if (UA.opera) {	
       
   522 			aHandlers.push(oNode.on("keypress", this._preventScroll, this));
       
   523 		}
       
   524 
       
   525 
       
   526 		//	For all browsers except Opera: HTML elements that are not natively
       
   527 		//	focusable but made focusable via the tabIndex attribute don't 
       
   528 		//	fire a click event when the user presses the enter key.  It is 
       
   529 		//	possible to work around this problem by simplying dispatching a 
       
   530 		//	click event in response to the user pressing the enter key.
       
   531 
       
   532 		if (!UA.opera) {
       
   533 			aHandlers.push(oNode.on("keypress", this._fireClick, this));
       
   534 		}
       
   535 
       
   536 	},
       
   537 
       
   538 
       
   539 	/**
       
   540 	* @method _detachEventHandlers
       
   541 	* @description Detaches all event handlers used by the Focus Manager.
       
   542 	* @protected
       
   543 	*/
       
   544 	_detachEventHandlers: function () {
       
   545 
       
   546 		this._detachKeyHandler();
       
   547 
       
   548 		var aHandlers = this._eventHandlers;
       
   549 
       
   550 		if (aHandlers) {
       
   551 
       
   552 			Y.Array.each(aHandlers, function (handle) {
       
   553 				handle.detach();
       
   554 			});
       
   555 
       
   556 			this._eventHandlers = null;
       
   557 
       
   558 		}
       
   559 		
       
   560 	},
       
   561 
       
   562 
       
   563 	/**
       
   564 	* @method _detachEventHandlers
       
   565 	* @description Attaches all event handlers used by the Focus Manager.
       
   566 	* @protected	
       
   567 	*/
       
   568 	_attachEventHandlers: function () {
       
   569 
       
   570 		var descendants = this._descendants,
       
   571 			aHandlers,
       
   572 			oDocument,
       
   573 			handle;
       
   574 
       
   575 		if (descendants && descendants.size() > 1) {
       
   576 
       
   577 			aHandlers = this._eventHandlers || [];
       
   578 			oDocument = this.get(HOST).get("ownerDocument");
       
   579 
       
   580 
       
   581 			if (aHandlers.length === 0) {
       
   582 
       
   583 		        Y.log("Attaching base set of event handlers.", "info", "nodeFocusManager");
       
   584 
       
   585 				aHandlers.push(oDocument.on("focus", this._onDocFocus, this));
       
   586 
       
   587 				aHandlers.push(oDocument.on("mousedown", 
       
   588 					this._onDocMouseDown, this));
       
   589 
       
   590 				aHandlers.push(
       
   591 						this.after("keysChange", this._attachKeyHandler));
       
   592 
       
   593 				aHandlers.push(
       
   594 						this.after("descendantsChange", this._initDescendants));
       
   595 
       
   596 				aHandlers.push(
       
   597 						this.after(ACTIVE_DESCENDANT_CHANGE, 
       
   598 								this._afterActiveDescendantChange));
       
   599 
       
   600 
       
   601 				//	For performance: defer attaching all key-related event 
       
   602 				//	handlers until the first time one of the specified 
       
   603 				//	descendants receives focus.
       
   604 
       
   605 				handle = this.after("focusedChange", Y.bind(function (event) {
       
   606 					
       
   607 					if (event.newVal) {
       
   608 						
       
   609 				        Y.log("Attaching key event handlers.", "info", "nodeFocusManager");
       
   610 						
       
   611 						this._attachKeyHandler();
       
   612 
       
   613 						//	Detach this "focusedChange" handler so that the 
       
   614 						//	key-related handlers only get attached once.
       
   615 
       
   616 						handle.detach();
       
   617 
       
   618 					}
       
   619 					
       
   620 				}, this));
       
   621 				
       
   622 				aHandlers.push(handle);
       
   623 				
       
   624 			}
       
   625 
       
   626 
       
   627 			this._eventHandlers = aHandlers;
       
   628 			
       
   629 		}
       
   630 		
       
   631 	},	
       
   632 	
       
   633 	
       
   634 	//	Protected event handlers
       
   635 
       
   636 	/**
       
   637 	* @method _onDocMouseDown
       
   638 	* @description "mousedown" event handler for the owner document of the 
       
   639 	* Focus Manager's Node.
       
   640 	* @protected
       
   641 	* @param event {Object} Object representing the DOM event.
       
   642 	*/
       
   643 	_onDocMouseDown: function (event) {
       
   644 	
       
   645 		var oHost = this.get(HOST),
       
   646 			oTarget = event.target,
       
   647 			bChildNode = oHost.contains(oTarget),
       
   648 			node,
       
   649 
       
   650 			getFocusable = function (node) {
       
   651 
       
   652 				var returnVal = false;
       
   653 
       
   654 				if (!node.compareTo(oHost)) {
       
   655 					returnVal = Lang.isNumber(node.get(TAB_INDEX)) ? node : 
       
   656 									getFocusable(node.get("parentNode"));
       
   657 				}
       
   658 		
       
   659 				return returnVal;
       
   660 
       
   661 			};
       
   662 		
       
   663 
       
   664 		if (bChildNode) {
       
   665 
       
   666 			//	Check to make sure that the target isn't a child node of one 
       
   667 			//	of the focusable descendants.
       
   668 
       
   669 			node = getFocusable(oTarget);
       
   670 
       
   671 			if (node) {
       
   672 				oTarget = node;
       
   673 			}
       
   674 			else if (!node && this.get(FOCUSED)) {
       
   675 
       
   676 				//	The target was a non-focusable descendant of the root 
       
   677 				//	node, so the "focused" attribute should be set to false.
       
   678 
       
   679 	 			this._set(FOCUSED, false);
       
   680 	 			this._onDocFocus(event);
       
   681 								
       
   682 			}
       
   683 
       
   684 		}
       
   685 		
       
   686 
       
   687 		if (bChildNode && this._isDescendant(oTarget)) {
       
   688 
       
   689 			//	Fix general problem in Webkit: mousing down on a button or an 
       
   690 			//	anchor element doesn't focus it.
       
   691 
       
   692 			//	For all browsers: makes sure that the descendant that 
       
   693 			//	was the target of the mousedown event is now considered the
       
   694 			//	active descendant.
       
   695 
       
   696 			this.focus(oTarget);
       
   697 		}
       
   698 		else if (UA.webkit && this.get(FOCUSED) && 
       
   699 			(!bChildNode || (bChildNode && !this._isDescendant(oTarget)))) {
       
   700 
       
   701 			//	Fix for Webkit:
       
   702 			//	Document doesn't receive focus in Webkit when the user mouses 
       
   703 			//	down on it, so the "focused" attribute won't get set to the 
       
   704 			//	correct value.
       
   705 
       
   706  			this._set(FOCUSED, false);
       
   707  			this._onDocFocus(event);
       
   708 
       
   709 		}
       
   710 	
       
   711 	},
       
   712 
       
   713 
       
   714 	/**
       
   715 	* @method _onDocFocus
       
   716 	* @description "focus" event handler for the owner document of the 
       
   717 	* Focus Manager's Node.
       
   718 	* @protected
       
   719 	* @param event {Object} Object representing the DOM event.
       
   720 	*/
       
   721 	_onDocFocus: function (event) {
       
   722 
       
   723 		var oTarget = this._focusTarget || event.target,
       
   724 			bFocused = this.get(FOCUSED),
       
   725 			focusClass = this.get(FOCUS_CLASS),
       
   726 			oFocusedNode = this._focusedNode,
       
   727 			bInCollection;
       
   728 
       
   729 		if (this._focusTarget) {
       
   730 			this._focusTarget = null;
       
   731 		}
       
   732 
       
   733 
       
   734 		if (this.get(HOST).contains(oTarget)) {	
       
   735 
       
   736 			//	The target is a descendant of the root Node.
       
   737 
       
   738 			bInCollection = this._isDescendant(oTarget);
       
   739 
       
   740 			if (!bFocused && bInCollection) {
       
   741 
       
   742 				//	The user has focused a focusable descendant.
       
   743 
       
   744 				bFocused = true;
       
   745 
       
   746 			}
       
   747 			else if (bFocused && !bInCollection) {  
       
   748 			
       
   749 				//	The user has focused a child of the root Node that is 
       
   750 				//	not one of the descendants managed by this Focus Manager
       
   751 				//	so clear the currently focused descendant.
       
   752 				
       
   753 				bFocused = false;
       
   754 			
       
   755 			}
       
   756 			
       
   757 		}
       
   758 		else { 
       
   759 		
       
   760 			// The target is some other node in the document.
       
   761 
       
   762 			bFocused = false;
       
   763 			
       
   764 		}
       
   765 
       
   766 
       
   767 		if (focusClass) {
       
   768 
       
   769 			if (oFocusedNode && (oFocusedNode !== oTarget || !bFocused)) {
       
   770 				this._removeFocusClass();
       
   771 			}
       
   772 
       
   773 			if (bInCollection && bFocused) {
       
   774 
       
   775 				if (focusClass.fn) {
       
   776 					oTarget = focusClass.fn(oTarget);
       
   777 					oTarget.addClass(focusClass.className);
       
   778 				}
       
   779 				else {
       
   780 					oTarget.addClass(focusClass);
       
   781 				}
       
   782 
       
   783 				this._focusedNode = oTarget;
       
   784 
       
   785 			}
       
   786 			
       
   787 		}
       
   788 
       
   789 
       
   790 		this._set(FOCUSED, bFocused);			
       
   791 
       
   792 	},
       
   793 
       
   794 
       
   795 	/**
       
   796 	* @method _focusNext
       
   797 	* @description Keydown event handler that moves focus to the next 
       
   798 	* enabled descendant.
       
   799 	* @protected
       
   800 	* @param event {Object} Object representing the DOM event.
       
   801 	* @param activeDescendant {Number} Number representing the index of the 
       
   802 	* next descendant to be focused
       
   803 	*/
       
   804 	_focusNext: function (event, activeDescendant) {
       
   805 
       
   806 		var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
       
   807 			oNode;
       
   808 
       
   809 
       
   810 		if (this._isDescendant(event.target) && 
       
   811 			(nActiveDescendant <= this._lastNodeIndex)) {
       
   812 
       
   813 			nActiveDescendant = nActiveDescendant + 1;
       
   814 
       
   815 			if (nActiveDescendant === (this._lastNodeIndex + 1) && 
       
   816 				this.get(CIRCULAR)) {
       
   817 
       
   818 				nActiveDescendant = 0;
       
   819 
       
   820 			}
       
   821 
       
   822 			oNode = this._descendants.item(nActiveDescendant);
       
   823 			
       
   824 			if (oNode.get(DISABLED)) {
       
   825 				this._focusNext(event, nActiveDescendant);
       
   826 			}
       
   827 			else {
       
   828 				this.focus(nActiveDescendant);
       
   829 			}
       
   830 
       
   831 		}
       
   832 		
       
   833 		this._preventScroll(event);
       
   834 
       
   835 	},
       
   836 
       
   837 
       
   838 	/**
       
   839 	* @method _focusPrevious
       
   840 	* @description Keydown event handler that moves focus to the previous 
       
   841 	* enabled descendant.
       
   842 	* @protected
       
   843 	* @param event {Object} Object representing the DOM event.
       
   844 	* @param activeDescendant {Number} Number representing the index of the 
       
   845 	* next descendant to be focused.
       
   846 	*/
       
   847 	_focusPrevious: function (event, activeDescendant) {
       
   848 	
       
   849 		var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
       
   850 			oNode;
       
   851 	
       
   852 		if (this._isDescendant(event.target) && nActiveDescendant >= 0) {
       
   853 
       
   854 			nActiveDescendant = nActiveDescendant - 1;
       
   855 
       
   856 			if (nActiveDescendant === -1 && this.get(CIRCULAR)) {
       
   857 				nActiveDescendant = this._lastNodeIndex;
       
   858 			}
       
   859 
       
   860 			oNode = this._descendants.item(nActiveDescendant);
       
   861 
       
   862 			if (oNode.get(DISABLED)) {
       
   863 				this._focusPrevious(event, nActiveDescendant);
       
   864 			}
       
   865 			else {
       
   866 				this.focus(nActiveDescendant);				
       
   867 			}
       
   868 
       
   869 		}
       
   870 		
       
   871 		this._preventScroll(event);			
       
   872 	
       
   873 	},
       
   874 
       
   875 
       
   876 	/**
       
   877 	* @method _afterActiveDescendantChange
       
   878 	* @description afterChange event handler for the 
       
   879 	* "activeDescendant" attribute.
       
   880 	* @protected
       
   881 	* @param event {Object} Object representing the change event.
       
   882 	*/	
       
   883 	_afterActiveDescendantChange: function (event) {
       
   884 
       
   885 		var oNode = this._descendants.item(event.prevVal);
       
   886 		
       
   887 		if (oNode) {
       
   888 			oNode.set(TAB_INDEX, -1);
       
   889 		}
       
   890 
       
   891 		oNode = this._descendants.item(event.newVal);
       
   892 
       
   893 		if (oNode) {
       
   894 			oNode.set(TAB_INDEX, 0);
       
   895 		}
       
   896 		
       
   897 	},
       
   898 
       
   899 
       
   900 
       
   901 	//	Public methods
       
   902 
       
   903     initializer: function (config) {
       
   904 
       
   905 		this.start();
       
   906 
       
   907     },
       
   908 
       
   909 	destructor: function () {
       
   910 		
       
   911 		this.stop();
       
   912 		this.get(HOST).focusManager = null;
       
   913 		
       
   914     },
       
   915 
       
   916 
       
   917 	/**
       
   918 	* @method focus
       
   919 	* @description Focuses the active descendant and sets the  
       
   920 	* <code>focused</code> attribute to true.
       
   921 	* @param index {Number} Optional. Number representing the index of the 
       
   922 	* descendant to be set as the active descendant.
       
   923 	* @param index {Node} Optional. Node instance representing the 
       
   924 	* descendant to be set as the active descendant.
       
   925 	*/
       
   926 	focus: function (index) {
       
   927 
       
   928 		if (Lang.isUndefined(index)) {
       
   929 			index = this.get(ACTIVE_DESCENDANT);
       
   930 		}
       
   931 
       
   932 		this.set(ACTIVE_DESCENDANT, index, { src: UI });
       
   933 
       
   934 		var oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
       
   935 
       
   936 		if (oNode) {
       
   937 
       
   938 			oNode.focus();
       
   939 
       
   940 			//	In Opera focusing a <BUTTON> element programmatically 
       
   941 			//	will result in the document-level focus event handler 
       
   942 			//	"_onDocFocus" being called, resulting in the handler 
       
   943 			//	incorrectly setting the "focused" Attribute to false.  To fix 
       
   944 			//	this, set a flag ("_focusTarget") that the "_onDocFocus" method 
       
   945 			//	can look for to properly handle this edge case.
       
   946 
       
   947 			if (UA.opera && oNode.get("nodeName").toLowerCase() === "button") {
       
   948 				this._focusTarget = oNode;
       
   949 			}
       
   950 
       
   951 		}
       
   952 
       
   953 	},
       
   954 
       
   955 
       
   956 	/**
       
   957 	* @method blur
       
   958 	* @description Blurs the current active descendant and sets the 
       
   959 	* <code>focused</code> attribute to false.
       
   960 	*/
       
   961 	blur: function () {
       
   962 
       
   963 		var oNode;
       
   964 
       
   965 		if (this.get(FOCUSED)) {
       
   966 
       
   967 			oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
       
   968 
       
   969 			if (oNode) {
       
   970 
       
   971 				oNode.blur();
       
   972 
       
   973 				//	For Opera and Webkit:  Blurring an element in either browser
       
   974 				//	doesn't result in another element (such as the document)
       
   975 				//	being focused.  Therefore, the "_onDocFocus" method 
       
   976 				//	responsible for managing the application and removal of the 
       
   977 				//	focus indicator class name is never called.
       
   978 
       
   979 				this._removeFocusClass();
       
   980 				
       
   981 			}
       
   982 
       
   983 			this._set(FOCUSED, false, { src: UI });
       
   984 		}
       
   985 		
       
   986 	},
       
   987 
       
   988 
       
   989 	/**
       
   990 	* @method start
       
   991 	* @description Enables the Focus Manager.
       
   992 	*/
       
   993 	start: function () {
       
   994 
       
   995 		if (this._stopped) {
       
   996 
       
   997 			this._initDescendants();
       
   998 			this._attachEventHandlers();
       
   999 
       
  1000 			this._stopped = false;
       
  1001 
       
  1002 		}
       
  1003 		
       
  1004 	},
       
  1005 
       
  1006 
       
  1007 	/**
       
  1008 	* @method stop
       
  1009 	* @description Disables the Focus Manager by detaching all event handlers.
       
  1010 	*/	
       
  1011 	stop: function () {
       
  1012 
       
  1013 		if (!this._stopped) {
       
  1014 
       
  1015 			this._detachEventHandlers();
       
  1016 
       
  1017 			this._descendants = null;
       
  1018 			this._focusedNode = null;
       
  1019 			this._lastNodeIndex = 0;
       
  1020 			this._stopped = true;
       
  1021 			
       
  1022 		}
       
  1023 
       
  1024 	},
       
  1025 
       
  1026 
       
  1027 	/**
       
  1028 	* @method refresh
       
  1029 	* @description Refreshes the Focus Manager's descendants by re-executing the 
       
  1030 	* CSS selector query specified by the <code>descendants</code> attribute.
       
  1031 	*/
       
  1032 	refresh: function () {
       
  1033 
       
  1034 		this._initDescendants();
       
  1035 		
       
  1036 	}
       
  1037 	
       
  1038 });
       
  1039 
       
  1040 
       
  1041 NodeFocusManager.NAME = "nodeFocusManager";
       
  1042 NodeFocusManager.NS = "focusManager";
       
  1043 
       
  1044 Y.namespace("Plugin");
       
  1045 Y.Plugin.NodeFocusManager = NodeFocusManager;
       
  1046 
       
  1047 
       
  1048 }, '3.0.0b1' ,{requires:['node', 'plugin']});