Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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