PageSourceSearch

https://www.bookstellyouwhy.com/manager/include/scripts/bootstrap-accessibility.min.js?v=2

js bookstellyouwhy.com collected 2026-10-02 06:40:01 UTC 23,429 bytes, 685 lines download raw bytes

1/* ========================================================================
2* Extends Bootstrap v3.1.1
3
4* Copyright (c) <2015> eBay Software Foundation
5
6* All rights reserved.
7
8* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9
10* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
11
12* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
13
14* Neither the name of eBay or any of its subsidiaries or affiliates nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
15
16* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17
18* ========================================================================
19*
20* Updated 02/05/2019 by Matt Baer - Added an id to the title inside of carousel caption
21* Updated 02/05/2019 by Matt Baer - Moved carousel play/pause button to top of carousel div container
22* Updated 04/22/2019 by Matt Baer - No longer add play/pause button if there is only one slide. Also don't add labelledby if there is no header for tabs/carousels,etc.
23* Updated 05/01/2022 by Matt Baer - Moved role=menuitem to the <li> instead of the inner <a> element.
24*
25* */
26
27
28(function($) {
29	"use strict";
30
31	// GENERAL UTILITY FUNCTIONS
32	// ===============================
33
34	var uniqueId = function(prefix) {
35		return (prefix || 'ui-id') + '-' + Math.floor((Math.random()*1000)+1)
36	}
37
38
39	var removeMultiValAttributes = function (el, attr, val) {
40		var describedby = (el.attr( attr ) || "").split( /\s+/ )
41			, index = $.inArray(val, describedby)
42		if ( index !== -1 ) {
43			describedby.splice( index, 1 )
44		}
45		describedby = $.trim( describedby.join( " " ) )
46		if (describedby ) {
47			el.attr( attr, describedby )
48		} else {
49			el.removeAttr( attr )
50		}
51	}
52
53// selectors  Courtesy: https://github.com/jquery/jquery-ui/blob/master/ui/core.js
54	var focusable = function ( element, isTabIndexNotNaN ) {
55		var map, mapName, img,
56			nodeName = element.nodeName.toLowerCase();
57		if ( "area" === nodeName ) {
58			map = element.parentNode;
59			mapName = map.name;
60			if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
61				return false;
62			}
63			img = $( "img[usemap='#" + mapName + "']" )[ 0 ];
64			return !!img && visible( img );
65		}
66		return ( /input|select|textarea|button|object/.test( nodeName ) ?
67			!element.disabled :
68			"a" === nodeName ?
69				element.href || isTabIndexNotNaN :isTabIndexNotNaN) && visible( element ); // the element and all of its ancestors must be visible
70	}
71	var visible = function ( element ) {
72		return $.expr.filters.visible( element ) &&
73			!$( element ).parents().addBack().filter(function() {
74				return $.css( this, "visibility" ) === "hidden";
75			}).length;
76	}
77
78	$.extend( $.expr[ ":" ], {
79		data: $.expr.createPseudo ?
80			$.expr.createPseudo(function( dataName ) {
81				return function( elem ) {
82					return !!$.data( elem, dataName );
83				};
84			}) :
85			// support: jQuery <1.8
86			function( elem, i, match ) {
87				return !!$.data( elem, match[ 3 ] );
88			},
89
90		focusable: function( element ) {
91			return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
92		},
93
94		tabbable: function( element ) {
95			var tabIndex = $.attr( element, "tabindex" ),
96				isTabIndexNaN = isNaN( tabIndex );
97			return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
98		}
99	});
100
101	// Modal Extension
102	// ===============================
103
104	$('.modal-dialog').attr( {'role' : 'document'})
105	var modalhide =   $.fn.modal.Constructor.prototype.hide
106	$.fn.modal.Constructor.prototype.hide = function(){
107		var modalOpener = this.$element.parent().find('[data-target="#' + this.$element.attr('id') + '"]')
108		modalhide.apply(this, arguments)
109		modalOpener.focus()
110		$(document).off('keydown.bs.modal')
111	}
112
113	var modalfocus =   $.fn.modal.Constructor.prototype.enforceFocus
114	$.fn.modal.Constructor.prototype.enforceFocus = function(){
115		var focEls = this.$element.find(":tabbable")
116			, lastEl = focEls[focEls.length-1]
117		$(document).on('keydown.bs.modal', $.proxy(function (ev) {
118			if(!this.$element.has(ev.target).length && ev.shiftKey && ev.keyCode === 9) {
119				lastEl.focus()
120				ev.preventDefault();
121			}
122		}, this))
123
124		modalfocus.apply(this, arguments)
125	}
126	// DROPDOWN Extension
127	// ===============================
128
129	var toggle   = '[data-toggle=dropdown]'
130		, $par
131		, firstItem
132		, focusDelay = 200
133		, menus = $(toggle).parent().find('ul').attr('role','menu')
134		, lis = menus.find('li').attr('role','menuitem')
135
136	lis.find('a').attr({'tabIndex':'-1'})
137	$(toggle).attr({ 'aria-haspopup':'true', 'aria-expanded': 'false'})
138
139	$(toggle).parent().on('shown.bs.dropdown',function(e){
140		$par = $(this)
141		var $toggle = $par.find(toggle)
142		$toggle.attr('aria-expanded','true')
143		$toggle.on('keydown.bs.dropdown', $.proxy(function (ev) {
144			setTimeout(function(){
145				firstItem = $('.dropdown-menu [role=menuitem]:visible a', $par)[0]
146				try{ firstItem.focus()} catch(ex) {}
147			}, focusDelay)
148		}, this))
149
150	})
151
152	$(toggle).parent().on('hidden.bs.dropdown',function(e){
153		$par = $(this)
154		var $toggle = $par.find(toggle)
155		$toggle.attr('aria-expanded','false')
156	})
157
158	$(document)
159		.on('focusout.dropdown.data-api', '.dropdown-menu', function(e){
160			var $this = $(this)
161				, that = this
162			setTimeout(function() {
163				if(!$.contains(that, document.activeElement)){
164					$this.parent().removeClass('open')
165					$this.parent().find('[data-toggle=dropdown]').attr('aria-expanded','false')
166				}
167			}, 150)
168		})
169		.on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , $.fn.dropdown.Constructor.prototype.keydown)
170	// Tab Extension
171	// ===============================
172
173	var $tablist = $('.nav-tabs, .nav-pills')
174		, $lis = $tablist.children('li')
175		, $tabs = $tablist.find('[data-toggle="tab"], [data-toggle="pill"]')
176
177	if($tabs){
178		$tablist.attr('role', 'tablist')
179		$lis.attr('role', 'presentation')
180		$tabs.attr('role', 'tab')
181	}
182
183	$tabs.each(function( index ) {
184		var tabpanel = $($(this).attr('href'))
185			, tab = $(this)
186			, tabid = tab.attr('id') || uniqueId('ui-tab')
187
188		tab.attr('id', tabid)
189
190		if(tab.parent().hasClass('active')){
191			tab.attr( { 'tabIndex' : '0', 'aria-selected' : 'true', 'aria-controls': tab.attr('href').substr(1) } )
192			tabpanel.attr({ 'role' : 'tabpanel', 'tabIndex' : '0', 'aria-hidden' : 'false' })
193		} else{
194			tab.attr( { 'tabIndex' : '-1', 'aria-selected' : 'false', 'aria-controls': tab.attr('href').substr(1) } )
195			tabpanel.attr( { 'role' : 'tabpanel', 'tabIndex' : '-1', 'aria-hidden' : 'true' } )
196		}
197
198		if($("#"+tabid).length > 0) {
199			collpanel.attr('aria-labelledby', tabid);
200		}
201	})
202
203	$.fn.tab.Constructor.prototype.keydown = function (e) {
204		var $this = $(this)
205			, $items
206			, $ul = $this.closest('ul[role=tablist] ')
207			, index
208			, k = e.which || e.keyCode
209
210		$this = $(this)
211		if (!/(37|38|39|40)/.test(k)) return
212
213		$items = $ul.find('[role=tab]:visible')
214		index = $items.index($items.filter(':focus'))
215
216		if (k == 38 || k == 37) index--                         // up & left
217		if (k == 39 || k == 40) index++                        // down & right
218
219
220		if(index < 0) index = $items.length -1
221		if(index == $items.length) index = 0
222
223		var nextTab = $items.eq(index)
224		if(nextTab.attr('role') ==='tab'){
225
226			nextTab.tab('show')      //Comment this line for dynamically loaded tabPabels, to save Ajax requests on arrow key navigation
227				.focus()
228		}
229		// nextTab.focus()
230
231		e.preventDefault()
232		e.stopPropagation()
233	}
234
235	$(document).on('keydown.tab.data-api','[data-toggle="tab"], [data-toggle="pill"]' , $.fn.tab.Constructor.prototype.keydown)
236
237	var tabactivate =    $.fn.tab.Constructor.prototype.activate;
238	$.fn.tab.Constructor.prototype.activate = function (element, container, callback) {
239		var $active = container.find('> .active')
240		$active.find('[data-toggle=tab], [data-toggle=pill]').attr({ 'tabIndex' : '-1','aria-selected' : false })
241		$active.filter('.tab-pane').attr({ 'aria-hidden' : true,'tabIndex' : '-1' })
242
243		tabactivate.apply(this, arguments)
244
245		element.addClass('active')
246		element.find('[data-toggle=tab], [data-toggle=pill]').attr({ 'tabIndex' : '0','aria-selected' : true })
247		element.filter('.tab-pane').attr({ 'aria-hidden' : false,'tabIndex' : '0' })
248	}
249
250	// Collapse Extension
251	// ===============================
252
253	var $colltabs =  $('[data-toggle="collapse"]')
254	$colltabs.each(function( index ) {
255		var colltab = $(this)
256			, collpanel = (colltab.attr('data-target')) ? $(colltab.attr('data-target')) : $(colltab.attr('href'))
257			, parent  = colltab.attr('data-parent')
258			, collparent = parent && $(parent)
259			, collid = colltab.attr('id') || uniqueId('ui-collapse')
260
261		colltab.attr('id', collid)
262
263		if(collparent){
264			colltab.attr({ 'role':'tab', 'aria-selected':'false', 'aria-expanded':'false' })
265			$(collparent).find('div:not(.collapse,.panel-body), h4').attr('role','presentation')
266			collparent.attr({ 'role' : 'tablist', 'aria-multiselectable' : 'true' })
267
268			if(collpanel.hasClass('in')){
269				colltab.attr({ 'aria-controls': collpanel.attr('id'), 'aria-selected':'true', 'aria-expanded':'true', 'tabindex':'0' })
270				collpanel.attr({ 'role':'tabpanel', 'tabindex':'0', 'aria-hidden':'false' })
271			}else{
272				colltab.attr({'aria-controls' : collpanel.attr('id'), 'tabindex':'-1' })
273				collpanel.attr({ 'role':'tabpanel', 'tabindex':'-1', 'aria-hidden':'true' })
274			}
275			if($("#"+collid).length > 0) {
276				collpanel.attr('aria-labelledby', collid);
277			}
278		}
279	})
280
281	var collToggle = $.fn.collapse.Constructor.prototype.toggle
282	$.fn.collapse.Constructor.prototype.toggle = function(){
283		var prevTab = this.$parent && this.$parent.find('[aria-expanded="true"]') , href
284
285		if(prevTab){
286			var prevPanel = prevTab.attr('data-target') || (href = prevTab.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')
287				, $prevPanel = $(prevPanel)
288				, $curPanel = this.$element
289				, par = this.$parent
290				, curTab
291
292			if (this.$parent) curTab = this.$parent.find('[data-toggle=collapse][href="#' + this.$element.attr('id') + '"]')
293
294			collToggle.apply(this, arguments)
295
296			if ($.support.transition) {
297				this.$element.one($.support.transition.end, function(){
298
299					prevTab.attr({ 'aria-selected':'false','aria-expanded':'false', 'tabIndex':'-1' })
300					$prevPanel.attr({ 'aria-hidden' : 'true','tabIndex' : '-1'})
301
302					curTab.attr({ 'aria-selected':'true','aria-expanded':'true', 'tabIndex':'0' })
303
304					if($curPanel.hasClass('in')){
305						$curPanel.attr({ 'aria-hidden' : 'false','tabIndex' : '0' })
306					}else{
307						curTab.attr({ 'aria-selected':'false','aria-expanded':'false'})
308						$curPanel.attr({ 'aria-hidden' : 'true','tabIndex' : '-1' })
309					}
310				})
311			}
312		}else{
313			collToggle.apply(this, arguments)
314		}
315	}
316
317	$.fn.collapse.Constructor.prototype.keydown = function (e) {
318		var $this = $(this)
319			, $items
320			, $tablist = $this.closest('div[role=tablist] ')
321			, index
322			, k = e.which || e.keyCode
323
324		$this = $(this)
325		if (!/(32|37|38|39|40)/.test(k)) return
326		if(k==32) $this.click()
327
328		$items = $tablist.find('[role=tab]')
329		index = $items.index($items.filter(':focus'))
330
331		if (k == 38 || k == 37) index--                                        // up & left
332		if (k == 39 || k == 40) index++                        // down & right
333		if(index < 0) index = $items.length -1
334		if(index == $items.length) index = 0
335
336		$items.eq(index).focus()
337
338		e.preventDefault()
339		e.stopPropagation()
340
341	}
342
343	$(document).on('keydown.collapse.data-api','[data-toggle="collapse"]' ,  $.fn.collapse.Constructor.prototype.keydown)
344
345// Carousel Extension
346	// ===============================
347
348	$('.carousel').each(function (index) {
349
350		// This function positions a highlight box around the tabs in the tablist to use in focus styling
351
352		function setTablistHighlightBox() {
353
354			var $tab
355				, offset
356				, height
357				, width
358				, highlightBox = {}
359
360			highlightBox.top     = 0
361			highlightBox.left    = 32000
362			highlightBox.height  = 0
363			highlightBox.width   = 0
364
365			for (var i = 0; i < $tabs.length; i++) {
366				$tab = $tabs[i]
367				offset = $($tab).offset()
368				height = $($tab).height()
369				width  = $($tab).width()
370
371//            console.log(" Top: " + offset.top + " Left: " + offset.left + " Height: " + height + " Width: " + width)
372
373				if (highlightBox.top < offset.top) {
374					highlightBox.top    = Math.round(offset.top)
375				}
376
377				if (highlightBox.height < height) {
378					highlightBox.height = Math.round(height)
379				}
380
381				if (highlightBox.left > offset.left) {
382					highlightBox.left = Math.round(offset.left)
383				}
384
385				var w = (offset.left - highlightBox.left) + Math.round(width)
386
387				if (highlightBox.width < w) {
388					highlightBox.width = w
389				}
390
391			} // end for
392
393//          console.log("[HIGHLIGHT]  Top: " +  highlightBox.top + " Left: " +  highlightBox.left + " Height: " +  highlightBox.height + " Width: " +  highlightBox.width)
394
395			$tablistHighlight.style.top    = (highlightBox.top    - 2)  + 'px'
396			$tablistHighlight.style.left   = (highlightBox.left   - 2)  + 'px'
397			$tablistHighlight.style.height = (highlightBox.height + 7)  + 'px'
398			$tablistHighlight.style.width  = (highlightBox.width  + 8)  + 'px'
399
400		} // end function
401
402		var $this = $(this)
403			, $prev        = $this.find('[data-slide="prev"]')
404			, $next        = $this.find('[data-slide="next"]')
405			, $tablist    = $this.find('.carousel-indicators')
406			, $tabs       = $this.find('.carousel-indicators li')
407			, $tabpanels  = $this.find('.item')
408			, $tabpanel
409			, $tablistHighlight
410			, $pauseCarousel
411			, $complementaryLandmark
412			, $tab
413			, $is_paused = false
414			, offset
415			, height
416			, width
417			, i
418			, id_title  = 'id_title'
419			, id_desc   = 'id_desc'
420
421
422		$tablist.attr('role', 'tablist')
423
424		$tabs.focus(function() {
425			$this.carousel('pause')
426			$is_paused = true
427			$pauseCarousel.innerHTML = '<span class="sr-only">Play Carousel</span>';
428			$pauseCarousel.className = "carousel-pause-button fa fa-play"
429			$(this).parent().addClass('active');
430//          $(this).addClass('focus')
431			setTablistHighlightBox()
432			$($tablistHighlight).addClass('focus')
433			$(this).parents('.carousel').addClass('contrast')
434		})
435
436		$tabs.blur(function(event) {
437			$(this).parent().removeClass('active');
438//          $(this).removeClass('focus')
439			$($tablistHighlight).removeClass('focus')
440			$(this).parents('.carousel').removeClass('contrast')
441		})
442
443
444		for (i = 0; i < $tabpanels.length; i++) {
445			$tabpanel = $tabpanels[i]
446			$tabpanel.setAttribute('role', 'tabpanel')
447			$tabpanel.setAttribute('id', 'tabpanel-' + index + '-' + i)
448			if($('#tab-' + index + '-' + i).length > 0) {
449				$tabpanel.setAttribute('aria-labelledby', 'tab-' + index + '-' + i)
450			}
451		}
452
453		if (typeof $this.attr('role') !== 'string') {
454			$this.attr('role', 'complementary');
455			$this.attr('aria-labelledby', id_title);
456			$this.attr('aria-describedby', id_desc);
457			$this.prepend('<p  id="' + id_desc   + '" class="sr-only">A carousel is a rotating set of images, rotation stops on keyboard focus on carousel tab controls or hovering the mouse pointer over images.  Use the tabs or the previous and next buttons to change the displayed slide.</p>')
458			$this.prepend('<h2 id="' + id_title  + '" class="sr-only">Carousel content with ' + $tabpanels.length + ' slides.</h2>')
459		}
460
461
462		for (i = 0; i < $tabs.length; i++) {
463			$tab = $tabs[i]
464
465			$tab.setAttribute('role', 'tab')
466			$tab.setAttribute('id', 'tab-' + index + '-' + i)
467			$tab.setAttribute('aria-controls', 'tabpanel-' + index + '-' + i)
468
469			var tpId = '#tabpanel-' + index + '-' + i
470			var tabId = 'tab-' + index + '-' + i;
471			var caption = $this.find(tpId).find('h1').text()
472			var captionEle = $this.find(tpId).find('h1')
473
474			if ((typeof caption !== 'string') || (caption.length === 0)) { caption = $this.find(tpId).find('h2').text(); captionEle = $this.find(tpId).find('h2') }
475			if ((typeof caption !== 'string') || (caption.length === 0)) { caption = $this.find(tpId).find('h3').text(); captionEle = $this.find(tpId).find('h3') }
476			if ((typeof caption !== 'string') || (caption.length === 0)) { caption = $this.find(tpId).find('h4').text(); captionEle = $this.find(tpId).find('h4') }
477			if ((typeof caption !== 'string') || (caption.length === 0)) { caption = $this.find(tpId).find('h5').text(); captionEle = $this.find(tpId).find('h5') }
478			if ((typeof caption !== 'string') || (caption.length === 0)) { caption = $this.find(tpId).find('h6').text(); captionEle = $this.find(tpId).find('h6') }
479			if ((typeof caption !== 'string') || (caption.length === 0)) { caption = $this.find(tpId).text(); captionEle = $this.find(tpId) }
480			if ((typeof caption !== 'string') || (caption.length === 0)) { caption = "no title"; captionEle = null; }
481
482//          console.log("CAPTION: " + caption )
483
484			if(typeof captionEle  !== 'undefined' && captionEle !== null) {
485				//captionEle.attr('id', tabId);
486			}
487
488			var tabName = document.createElement('span')
489			tabName.setAttribute('class', 'sr-only')
490			tabName.innerHTML='Slide ' + (i+1)
491			if (caption) tabName.innerHTML += ": " +  caption
492			$tab.appendChild(tabName)
493
494		}
495
496		// create div for focus styling of tablist
497		$tablistHighlight = document.createElement('div')
498		$tablistHighlight.className = 'carousel-tablist-highlight'
499		document.body.appendChild($tablistHighlight)
500
501		// if we have tabs to play or pause
502		if($tabs.length > 0) {
503			// create button for screen reader users to stop rotation of carousel
504
505			// create button for screen reader users to pause carousel for virtual mode review
506			$complementaryLandmark = document.createElement('aside')
507			$complementaryLandmark.className = 'carousel-pause-wrapper'
508			$complementaryLandmark.setAttribute('aria-label', 'carousel pause/play control')
509			$this.prepend($complementaryLandmark)
510
511			$pauseCarousel = document.createElement('button')
512			$pauseCarousel.className = "carousel-pause-button fa fa-pause"
513			$pauseCarousel.innerHTML = '<span class="sr-only">Pause Carousel</span>'
514			$pauseCarousel.setAttribute('title', "Pause/Play carousel button can be used by screen reader users to stop carousel animations")
515			$($complementaryLandmark).append($pauseCarousel)
516
517			$($pauseCarousel).click(function() {
518				if ($is_paused) {
519					$pauseCarousel.innerHTML = '<span class="sr-only">Pause Carousel</span>'
520					$pauseCarousel.className = "carousel-pause-button fa fa-pause"
521					$this.carousel('cycle')
522					$is_paused = false
523				} else {
524					$pauseCarousel.innerHTML = '<span class="sr-only">Play Carousel</span>'
525					$pauseCarousel.className = "carousel-pause-button fa fa-play"
526					$this.carousel('pause')
527					$is_paused = true
528				}
529			})
530			$($pauseCarousel).focus(function() {
531				$(this).addClass('focus')
532			})
533
534			$($pauseCarousel).blur(function() {
535				$(this).removeClass('focus')
536			})
537		}
538		setTablistHighlightBox()
539
540		$( window ).resize(function() {
541			setTablistHighlightBox()
542		})
543
544		// Add space bar behavior to prev and next buttons for SR compatibility
545		$prev.attr('aria-label', 'Previous Slide')
546		$prev.keydown(function(e) {
547			var k = e.which || e.keyCode
548			if (/(13|32)/.test(k)) {
549				e.preventDefault()
550				e.stopPropagation()
551				$prev.trigger('click');
552			}
553		});
554
555		$prev.focus(function() {
556			$(this).parents('.carousel').addClass('contrast')
557		})
558
559		$prev.blur(function() {
560			$(this).parents('.carousel').removeClass('contrast')
561		})
562
563		$next.attr('aria-label', 'Next Slide')
564		$next.keydown(function(e) {
565			var k = e.which || e.keyCode
566			if (/(13|32)/.test(k)) {
567				e.preventDefault()
568				e.stopPropagation()
569				$next.trigger('click');
570			}
571		});
572
573		$next.focus(function() {
574			$(this).parents('.carousel').addClass('contrast')
575		})
576
577		$next.blur(function() {
578			$(this).parents('.carousel').removeClass('contrast')
579		})
580
581		$('.carousel-inner a').focus(function() {
582			$(this).parents('.carousel').addClass('contrast')
583		})
584
585		$('.carousel-inner a').blur(function() {
586			$(this).parents('.carousel').removeClass('contrast')
587		})
588
589		$tabs.each(function () {
590			var item = $(this)
591			if(item.hasClass('active')) {
592				item.attr({ 'aria-selected': 'true', 'tabindex' : '0' })
593			}else{
594				item.attr({ 'aria-selected': 'false', 'tabindex' : '-1' })
595			}
596		})
597	})
598
599	var slideCarousel = $.fn.carousel.Constructor.prototype.slide
600	$.fn.carousel.Constructor.prototype.slide = function (type, next) {
601		var $element = this.$element
602			, $active  = $element.find('[role=tabpanel].active')
603			, $next    = next || $active[type]()
604			, $tab
605			, $tab_count = $element.find('[role=tabpanel]').length
606			, $prev_side = $element.find('[data-slide="prev"]')
607			, $next_side = $element.find('[data-slide="next"]')
608			, $index      = 0
609			, $prev_index = $tab_count -1
610			, $next_index = 1
611			, $id
612
613		if ($next && $next.attr('id')) {
614			$id = $next.attr('id')
615			$index = $id.lastIndexOf("-")
616			if ($index >= 0) $index = parseInt($id.substring($index+1), 10)
617
618			$prev_index = $index - 1
619			if ($prev_index < 1) $prev_index = $tab_count - 1
620
621			$next_index = $index + 1
622			if ($next_index >= $tab_count) $next_index = 0
623		}
624
625		$prev_side.attr('aria-label', 'Show slide ' + ($prev_index+1) + ' of ' + $tab_count)
626		$next_side.attr('aria-label', 'Show slide ' + ($next_index+1) + ' of ' + $tab_count)
627
628
629		slideCarousel.apply(this, arguments)
630
631		$active
632			.one('bsTransitionEnd', function () {
633				var $tab
634
635				$tab = $element.find('li[aria-controls="' + $active.attr('id') + '"]')
636				if ($tab) $tab.attr({'aria-selected':false, 'tabIndex': '-1'})
637
638				$tab = $element.find('li[aria-controls="' + $next.attr('id') + '"]')
639				if ($tab) $tab.attr({'aria-selected': true, 'tabIndex': '0'})
640
641			})
642	}
643
644	var $this;
645	$.fn.carousel.Constructor.prototype.keydown = function (e) {
646
647		$this = $this || $(this)
648		if(this instanceof Node) $this = $(this)
649
650		function selectTab(index) {
651			if (index >= $tabs.length) return
652			if (index < 0) return
653
654			$carousel.carousel(index)
655			setTimeout(function () {
656				$tabs[index].focus()
657				// $this.prev().focus()
658			}, 150)
659		}
660
661		var $carousel = $(e.target).closest('.carousel')
662			, $tabs      = $carousel.find('[role=tab]')
663			, k = e.which || e.keyCode
664			, index
665
666		if (!/(37|38|39|40)/.test(k)) return
667
668		index = $tabs.index($tabs.filter('.active'))
669		if (k == 37 || k == 38) {                           //  Up
670			index--
671			selectTab(index);
672		}
673
674		if (k == 39 || k == 40) {                          // Down
675			index++
676			selectTab(index);
677		}
678
679		e.preventDefault()
680		e.stopPropagation()
681	}
682	$(document).on('keydown.carousel.data-api', 'li[role=tab]', $.fn.carousel.Constructor.prototype.keydown)
683
684
685})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.