PageSourceSearch

https://alittlegesture.org/wp-content/plugins/plethora-tabs-accordions/js/tabs.jquery-plugin.js?ver=1.1.3

js alittlegesture.org collected 2026-10-04 02:06:27 UTC 15,947 bytes, 331 lines download raw bytes

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.