1// Accessible tabs 2// https://a11yproject.com/patterns 3// https://codepen.io/svinkle/pen/edmDF 4 5(function(document, window, $, undefined) { 6 "use strict"; 7 8 var $tabWidget = $(".js-tab-widget"); 9 10 var setupTabs = function($tab, $allTabs, $tabPanels, $tabListItems, i) { 11 $tab.attr({ 12 id: "tab-link-" + i, 13 tabindex: "-1", 14 role: "tab", 15 "aria-selected": "false", 16 "aria-controls": "tab-panel-" + i 17 }); 18 19 if (i === 0) { 20 $tab 21 .attr({ 22 tabindex: "0", 23 "aria-selected": "true", 24 "aria-describedby": "tab-widget-description" 25 }) 26 .addClass("tab-widget__link--active"); 27 } 28 29 $tab.on("click", function(e) { 30 e.preventDefault(); 31 tabClick($(this), $allTabs, $tabPanels, i); 32 }); 33 34 $tab.on("focus", function(e) { 35 tabClick($(this), $allTabs, $tabPanels, i); 36 }); 37 38 $tab.on("keydown", function(e) { 39 tabKeydown($(this), $allTabs, $tabPanels, $tabListItems, i, e); 40 }); 41 }; 42 43 var setupTabPanels = function(tabPanel, i) { 44 tabPanel.attr({ 45 id: "tab-panel-" + i, 46 role: "tabpanel", 47 "aria-hidden": "true", 48 "aria-labelledby": "tab-link-" + i 49 }); 50 51 if (i === 0) { 52 tabPanel 53 .attr("aria-hidden", "false") 54 .addClass("tab-widget__tab-content--active"); 55 } 56 }; 57 58 var tabClick = function($thisTab, $allTabs, $tabPanels, i) { 59 $allTabs 60 .attr({ 61 tabindex: -1, 62 "aria-selected": "false" 63 }) 64 .removeAttr("aria-describedby") 65 .removeClass("tab-widget__link--active"); 66 67 $thisTab 68 .attr({ 69 tabindex: 0, 70 "aria-selected": "true", 71 "aria-describedby": "tab-widget-description" 72 }) 73 .addClass("tab-widget__link--active"); 74 75 $tabPanels 76 .attr("aria-hidden", "true") 77 .removeClass("tab-widget__tab-content--active"); 78 79 $tabPanels 80 .eq(i) 81 .attr("aria-hidden", "false") 82 .addClass("tab-widget__tab-content--active"); 83 }; 84 85 var tabKeydown = function( 86 $thisTab, 87 $allTabs, 88 $tabPanels, 89 $tabListItems, 90 i, 91 e 92 ) { 93 var keyCode = e.which, 94 $nextTab = $thisTab 95 .parent() 96 .next() 97 .is("li") 98 ? $thisTab 99 .parent() 100 .next() 101 .find("a") 102 : false, 103 $previousTab = $thisTab 104 .parent() 105 .prev() 106 .is("li") 107 ? $thisTab 108 .parent() 109 .prev() 110 .find("a") 111 : false, 112 $firstTab = $thisTab 113 .parent() 114 .parent() 115 .find("li:first") 116 .find("a"), 117 $lastTab = $thisTab 118 .parent() 119 .parent() 120 .find("li:last") 121 .find("a"); 122 123 switch (keyCode) { 124 // Left/Up 125 case 37: 126 case 38: 127 e.preventDefault(); 128 e.stopPropagation(); 129 130 if (!$previousTab) { 131 $lastTab.focus(); 132 } else { 133 $previousTab.focus(); 134 } 135 136 break; 137 138 // Right/Down 139 case 39: 140 case 40: 141 e.preventDefault(); 142 e.stopPropagation(); 143 144 if (!$nextTab) { 145 $firstTab.focus(); 146 } else { 147 $nextTab.focus(); 148 } 149 150 break; 151 152 // Home 153 case 36: 154 e.preventDefault(); 155 e.stopPropagation(); 156 157 $firstTab.focus(); 158 159 break; 160 161 // End 162 case 35: 163 e.preventDefault(); 164 e.stopPropagation(); 165 166 $lastTab.focus(); 167 168 break; 169 170 // Enter/Space 171 case 13: 172 case 32: 173 e.preventDefault(); 174 e.stopPropagation(); 175 176 break; 177 } 178 }; 179 180 $tabWidget.each(function() { 181 var $this = $(this), 182 $tabList = $this.find("> ul"), 183 $tabListItems = $tabList.find("li"), 184 $allTabs = $tabListItems.find("a"), 185 $tabPanels = $this.find("> div > div"); 186 187 $tabList.attr("role", "tablist"); 188 $tabListItems.attr("role", "presentation"); 189 190 $allTabs.each(function(i) { 191 setupTabs($(this), $allTabs, $tabPanels, $tabListItems, i); 192 }); 193 194 $tabPanels.each(function(i) { 195 setupTabPanels($(this), i); 196 }); 197 }); 198 199 $("html") 200 .addClass("js") 201 .removeClass("no-js"); 202})(document, window, 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.