1/** 2 * File tabs.jquery-plugin.js. 3 * 4 * Handles toggling the navigation menu for small screens and enables TAB key 5 * navigation support for dropdown menus. 6 */ 7( function($) { 8 const ns = 'plethoraplugins'; 9 const dataNS = 'pds-tabs--'; 10 const pds = 'pds'; 11 const classToAdd = 'js-' + ns + '-tabs'; 12 const classToAddToLinks = 'js-' + ns + '-tabs--link'; //adding a class to the link will prevent the default link styles from kicking in. 13 const activeClass = 'active'; 14 const linkSelector = 'a[href]'; 15 const headerSelector = 'h1,h2,h3,h4,h5,h6'; 16 const isMobileClass = ns + '-tabs-mobile'; 17 const initialHash = window.location.hash; 18 const defaultSettings = { 19 mobileDisplay: null,// determines how to display this for mobile devices. Options: 'accordion' 20 mobileBreakpoint: null,//determines the breakpoint at which the mobile display takes effect. Options: an integer representing the max width, or 'auto' (in which case the assumption is that this is a 'horizontal' tab, and that all tabs should fit the browser's screen width) 21 mobileBreakpointForced: '', // if set, it will be considered mobile below this threshold (example: 500 for 500px) 22 accordionAutoClose: true, //if responsive behavior of vertical tab results in an accordion, should it auto close? 23 responsiveAccordionsCollapsedInitially: false, //if responsive behavior of vertical tab results in an accordion, should all accordion items be collapsed initially? 24 responsiveAccordionsBreakpoint: false, //set this to a valid css breakpoint value, like 500px, where you want to always have it collapse to accordion 25 }; 26 const scrollIntoViewOptions = {behavior: "smooth"}; 27 let sideNavNumber = 0; 28 if(!$.fn.scrollIntoViewIfNeeded) $.fn.scrollIntoViewIfNeeded = function(){ 29 if (this.length) { 30 if(this[0].scrollIntoViewIfNeeded) this[0].scrollIntoViewIfNeeded(scrollIntoViewOptions); 31 else this[0].scrollIntoView(scrollIntoViewOptions); 32 } 33 return this; 34 }; 35 36 $.fn[ns + 'Tabs'] = function(inputOptions){ 37 const options = $.extend({}, defaultSettings, inputOptions); 38 return this.each(function(){ 39 const $nav = $(this); 40 if ($nav.hasClass(classToAdd)) return; //prevent double initialization 41 $nav.addClass(classToAdd); 42 $nav.attr('role', 'tablist').children('li').attr('role','presentation'); 43 if(options.layout == 'vertical') $nav.attr('aria-orientation', 'vertical'); 44 if(options.layout == 'horizontal') $nav.attr('aria-orientation', 'horizontal'); 45 var linkIds = []; 46 47 const $container = $nav.closest('.' + ns + '-tabs-container'); 48 const $contentContainer = $container.find('.' + ns + '-sidenavjump-content:first,.' + ns + '-tabs--content:first'); 49 let $mobileDisplay = null; 50 let isMobile = false; 51 sideNavNumber++; 52 $nav.attr('data-' + dataNS + 'tab-number', sideNavNumber); 53 const $header = $nav.prev(headerSelector); 54 if($header.length && !$nav.attr('aria-labelledby')) { 55 let headerId = $header.attr('id'); 56 if(!headerId) { 57 headerId = ns + 'tab_' + sideNavNumber + '_header'; 58 $header.attr('id', headerId); 59 } 60 $nav.attr('aria-labelledby', headerId); 61 } 62 let $tabButtons = null; 63 const intializeTabButtons = function(){ 64 $tabButtons = $nav.find(linkSelector).each(function(index){ 65 const $link = $(this).addClass(classToAddToLinks); 66 if(!$link.attr('id')) $link.attr('id', ns + 'tab_' + sideNavNumber + '_tablink_' + index); 67 linkIds.push($link.attr('id')); 68 if(!$link.attr('role')) $link.attr('role', 'tab'); 69 if($link.hasClass(activeClass)) $link.attr('aria-selected', 'true').removeAttr('tabindex'); 70 else $link.attr('aria-selected', 'false').attr('tabindex', '-1'); 71 const href = $link.attr('href'); 72 if(!href || !href.charAt(0) == '#') return; 73 let $content = $(href).attr('aria-hidden', 'true'); 74 if(!$content.length) { 75 if($contentContainer.length){ 76 var children = $contentContainer.children(); 77 if (children.length > index) $content = $(children[index]); 78 else return; 79 } 80 else return; 81 } 82 if(!$content.attr('id')) $content.attr('id', href.replace('#', '')); 83 if($link.hasClass('active')) $content.addClass('active').attr('aria-hidden', 'false'); 84 $content.addClass('js-' + ns + '-tab-panel'); 85 $link.attr('aria-controls', $content.attr('id')); 86 if(!$content.attr('role')) $content.attr('role', 'tabpanel'); 87 if(!$content.attr('aria-labelledby')) $content.attr('aria-labelledby', $link.attr('id')); 88 }); 89 $nav.attr('aria-owns', linkIds.join(' '));
90 91 $tabButtons.on('click', function(e){ e.preventDefault(); activateTab($(this)); return false; }); 92 $tabButtons.on('keydown', function(e){ 93 if (event.keyCode === 37 || event.keyCode === 38 || event.keyCode === 39 || event.keyCode === 40) { 94 event.preventDefault(); 95 navigateTabs(event.keyCode); 96 return false; 97 } 98 }); 99 //it is possible that NO tabs are active initially, in which case there would be no way for the user to access any of them. In this case, add the first button back into the tab loop 100 if($tabButtons.filter('[tabindex="-1"]').length === $tabButtons.length) $tabButtons.first().removeAttr('tabindex'); 101 }; 102 103 104 105 const removeActiveClasses = function(){ 106 //remove active class from all currently active links 107 $nav.find('a.' + activeClass).each(function(){ 108 const $curLink = $(this); 109 $curLink.removeClass(activeClass).attr('aria-selected', 'false').attr('tabindex','-1'); 110 const tabId = $curLink.attr('aria-controls'); 111 if(tabId) $('#' + tabId).removeClass(activeClass).attr('aria-hidden', 'true'); 112 }); 113 }; 114 const activateTab = function($link, doNotPushState){ 115 const tabId = $link.attr('aria-controls'); 116 if(!tabId) return; 117 const newHash = '#' + tabId; 118 const $content = $(newHash); 119 removeActiveClasses(); 120 $content.addClass(activeClass).attr('aria-hidden','false'); 121 $link.addClass(activeClass).attr('aria-selected', 'true').removeAttr('tabindex'); 122 if(!doNotPushState) history.pushState(undefined, undefined, newHash); 123 return $link; 124 } 125 const onHashChange = function(){ 126 $tabButtons.each(function(){ 127 //by default, the markup can have the 'active' class on the target content div, but not yet on the link in the side nav - so we add it to the link to match the content... 128 const $link = $(this); 129 const href = $link.attr('href'); 130 if(!href || !href.charAt(0) == '#') return; 131 if(window.location.hash === href) { 132 activateTab($link, true).scrollIntoViewIfNeeded($link); 133 } 134 135 }); 136 }; 137 138 // Navigate tabs with arrow keys 139 const navigateTabs = function (keyCode) { 140 var $currentTabButton = $tabButtons.filter('[aria-selected="true"]'); 141 var index = $tabButtons.index($currentTabButton); 142 143 if (keyCode === 37 || keyCode === 38) { // Previous tab 144 index--; 145 if (index < 0) { 146 index = $tabButtons.length - 1; 147 } 148 } else if (keyCode === 39 || keyCode === 40) { // Next tab 149 index++; 150 if (index >= $tabButtons.length) { 151 index = 0; 152 } 153 } 154 const $newTabButton = $tabButtons.eq(index); 155 activateTab($newTabButton); 156 $newTabButton.trigger('focus'); 157 }; 158 159 160 const setInitialTab = function(){ 161 let $initialTabButton = null; 162 let $tabButtonThatAlreadyHadActiveClass = null; 163 $tabButtons.each(function(){ 164 //by default, the markup can have the 'active' class on the target content div, but not yet on the link in the side nav - so we add it to the link to match the content... 165 const $link = $(this); 166 const href = $link.attr('href'); 167 if(!href || !href.charAt(0) == '#') return; 168 if(initialHash === href) $initialTabButton = $link; //detect if the href upon loading the page has a hash that matches one of our links 169 const $content = $(href); 170 if($content.hasClass(activeClass)) $tabButtonThatAlreadyHadActiveClass = $link; 171 172 }); 173 // if one of our links matched the current location's hash, then trigger a click to load that content and have the page jump down. 174 if($initialTabButton) { 175 activateTab($initialTabButton, true).scrollIntoViewIfNeeded(); 176 } 177 else if ($tabButtonThatAlreadyHadActiveClass){ 178 $tabButtonThatAlreadyHadActiveClass.addClass(activeClass); 179 //MAYBE: update the URL to show which tab is currently selected... 180 //if(history.replaceState) history.replaceState(undefined, undefined, $tabButtonThatAlreadyHadActiveClass.attr('href')); 181 } 182 }; 183 184 intializeTabButtons(); 185 window.addEventListener("hashchange", onHashChange, false); 186 setInitialTab(); 187 188 if(options.mobileDisplay && options.mobileBreakpoint !== null){ 189 const detectModeSwitch = function(){ 190 let wasMobile = isMobile; 191 192 if(options.mobileBreakpoint === 'always') isMobile = true; 193 else if(options.mobileBreakpointForced && window.matchMedia('(max-width: ' + options.mobileBreakpointForced + 'px)').matches) isMobile = true; 194 else if(options.mobileBreakpoint === 'auto'){ 195 $nav.removeClass(isMobileClass); 196 const heightBeforeWrap = $nav.height(); 197 $nav.css('flex-wrap', 'wrap'); 198 const heightAfterWrap = $nav.height(); 199 $nav.css('flex-wrap', ''); 200 if(wasMobile) $nav.addClass(isMobileClass); 201 isMobile = heightBeforeWrap !== heightAfterWrap; 202 } 203 else isMobile = window.matchMedia('(max-width: ' + mobileBreakpoint + 'px)').matches; 204 205 206 207 if(isMobile !== wasMobile) { 208 $nav[isMobile ? 'addClass' : 'removeClass'](isMobileClass); 209 if($mobileDisplay) { 210 $tabButtons.each(function(){ 211 const $link = $(this); 212 const href = $link.attr('href'); 213 const $content = $('[data-' + dataNS + 'tab-content-for="' + $(href).attr('id') + '"]'); 214 $(href).empty().append($content.children()); 215 });
216 $mobileDisplay.remove(); 217 $mobileDisplay = null; 218 } 219 if(isMobile) { 220 switch(options.mobileDisplay){ 221 case 'accordion': 222 var theme = getTheme($container); 223 var themeClasses = getThemeClasses(theme); 224 $mobileDisplay = $('<div class="' + ns + '-accordion ' + themeClasses + '"></div>'); 225 226 $tabButtons.each(function(){ 227 const $link = $(this); 228 const itemId = $link.attr('id') + '_acc'; 229 const href = $link.attr('href'); 230 const $content = $(href); 231 let isActive = options.responsiveAccordionsCollapsedInitially ? false : $link.hasClass(activeClass); 232 // if Accordion Item: Open Initially is checked, isActive should be forced to true 233 if($content.attr('data-' + dataNS + 'accordion-initially-open') === 'true') isActive = true; 234 const $item = $('<div id="' + itemId + '" class="wp-block-pb-accordion-item pds-accordion__item pds-js-accordion-item pds-no-js" data-' + dataNS + 'click-to-close="true" data-' + dataNS + 'scroll="false" data-' + dataNS + 'scroll-offset="0"/>') 235 .attr('data-' + dataNS + 'auto-close', options.accordionAutoClose) 236 .attr('data-' + dataNS + 'initially-open', isActive) 237 .attr('data-' + dataNS + 'icon', options.accordionIcon) 238 .attr('data-' + dataNS + 'icon-size', options.accordionIconSize); 239 const headingLevelInt = parseInt(options.accordionHeadingLevel.charAt(0).toLowerCase() === 'h' ? options.accordionHeadingLevel.substring(1) : options.accordionHeadingLevel, 10); 240 const $controller = $('<div id="at-' + itemId + '" class="pds-accordion__title pds-js-accordion-controller"></div>'); 241 $controller.append($('<span class="pds-accordion__heading" role="heading" />').attr('aria-level', headingLevelInt).html($link.html())); 242 $item.append($controller); 243 const $mobileContent = $('<div id="ac-' + itemId + '" class="pds-accordion__content"></div>').attr('data-' + dataNS + 'tab-content-for', $content.attr('id')).append($content.children()); 244 $item.append($mobileContent); 245 $mobileDisplay.append($item); 246 $mobileDisplay.insertAfter($nav); 247 }); 248 $(document).trigger('pds-accordion--apply-widgets'); 249 break; 250 } 251 } 252 } 253 }; 254 $(window).on('resize', detectModeSwitch); 255 detectModeSwitch(); 256 } 257 }); 258 }; 259 function getThemeClasses(theme){ 260 var themeClasses = []; 261 if (theme != 'none' && theme != 'minimal') themeClasses.push(ns + '-theme__minimal'); 262 themeClasses.push(ns + '-theme__' + (theme || 'basic')); 263 return themeClasses.join(' '); 264 } 265 function getTheme($element){ 266 var themeAtt = 'data-' + dataNS + 'theme'; 267 return $element.closest('[' + themeAtt + ']').attr(themeAtt); 268 } 269 function getResponsiveBehavior($element){ 270 return $element.closest('.' + ns + '-tabs-container').attr('data-' + dataNS + 'responsive'); 271 } 272 function getAccordionAutoClose($element){ 273 var b = $element.closest('.' + ns + '-tabs-container').attr('data-' + dataNS + 'accordion-auto-close'); 274 if(b) return b == 'true'; 275 return null; 276 } 277 function getResponsiveAccordionsCollapsedInitially($element){ 278 var b = $element.closest('.' + ns + '-tabs-container').attr('data-' + dataNS + 'responsive-accordion-collapsed-initially'); 279 if(b) return b == 'true'; 280 return null; 281 } 282 function getMobileBreakpointForced($element){ 283 return $element.closest('.' + ns + '-tabs-container').attr('data-' + dataNS + 'mobile-breakpoint-forced') || ''; 284 } 285 function getLayout($element){ 286 return $element.closest('.' + ns + '-tabs-container').attr('data-' + dataNS + 'layout') || ''; 287 } 288 function getAccordionIconTemplate($element){ 289 let $container = $element.closest('.' + ns + '-tabs-container'); 290 return $container.attr('data-' + dataNS + 'accordion-icon') || ''; 291 } 292 function getAccordionIconSize($element){ 293 let $container = $element.closest('.' + ns + '-tabs-container'); 294 return $container.data(dataNS + 'accordion-icon-size'); 295 } 296 function getAccordionHeadingLevel($element){ 297 let $container = $element.closest('.' + ns + '-tabs-container'); 298 return $container.data(dataNS + 'accordion-heading-level') || 'h3'; 299 } 300 $(function(){ 301 $('.' + ns + '-sidenavjump,.' + ns + '-tabs').each(function(){
302 const $container = $(this); 303 const $ul = $container.find('ul'); 304 const isAccordion = $container.is('.' + ns + '-tabs') && $container.closest('.' + ns + '-tabs-container').is('.' + ns + '-tabs-container--accordion'); 305 const isTabs = !isAccordion && $container.is('.' + ns + '-tabs'); 306 let options = {}; 307 308 const responsive = getResponsiveBehavior($container); 309 if (responsive) { 310 const mobileBreakpoint = isAccordion ? 'always' : 'auto'; 311 var e = { 312 mobileDisplay: responsive, 313 mobileBreakpoint: mobileBreakpoint, 314 accordionAutoClose: getAccordionAutoClose($container), 315 responsiveAccordionsCollapsedInitially: getResponsiveAccordionsCollapsedInitially($container), 316 accordionIcon: getAccordionIconTemplate($container), 317 accordionIconSize: getAccordionIconSize($container), 318 accordionHeadingLevel: getAccordionHeadingLevel($container), 319 mobileBreakpointForced: getMobileBreakpointForced($container), 320 layout: getLayout($container) 321 }; 322 var accordionAutoClose = getAccordionAutoClose($container); 323 if(accordionAutoClose !== null) e.accordionAutoClose = accordionAutoClose; 324 var responsiveAccordionsCollapsedInitially = getResponsiveAccordionsCollapsedInitially($container); 325 if(responsiveAccordionsCollapsedInitially !== null) e.responsiveAccordionsCollapsedInitially = responsiveAccordionsCollapsedInitially; 326 options = $.extend(options, e); 327 } 328 $ul[ns + 'Tabs'](options); 329 }); 330 }); 331}(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.