1/** 2 * Deep-link Elementor tabs: scroll to a section and open a specific tab. 3 * 4 * Elementor setup: 5 * 1. Tabs section â Advanced â CSS ID, e.g. usecases 6 * 2. Box link â #usecases-tab-2 (1-based tab index) 7 * 8 * Also supported: 9 * #usecases + custom attribute data-faddom-tab="2" 10 * #e-n-tab-title-⦠(copied tab button id) 11 */ 12(function () { 13 'use strict'; 14 15 var TAB_HASH = /^#([A-Za-z][\w-]*)-tab-(\d+)$/; 16 17 function isEditor() { 18 return ( 19 document.documentElement.classList.contains('faddom-el-edit') || 20 (document.body && document.body.classList.contains('faddom-el-edit')) 21 ); 22 } 23 24 function headerOffset() { 25 return window.matchMedia('(max-width: 1024px)').matches ? 51 : 76; 26 } 27 28 function toUrl(href) { 29 try { 30 return new URL(href, window.location.href); 31 } catch (e) { 32 return null; 33 } 34 } 35 36 function isSamePage(url) { 37 var here = window.location.pathname.replace(/\/+$/, '') || '/'; 38 var there = url.pathname.replace(/\/+$/, '') || '/'; 39 return here === there; 40 } 41 42 function activateTab(el) { 43 if (!el) { 44 return; 45 } 46 el.click(); 47 } 48 49 function tabsIn(scope) { 50 var nested = scope.querySelectorAll('.e-n-tab-title'); 51 if (nested.length) { 52 return nested; 53 } 54 return scope.querySelectorAll('.elementor-tab-title'); 55 } 56 57 function scrollToEl(el) { 58 if (!el) { 59 return; 60 } 61 var top = el.getBoundingClientRect().top + window.pageYOffset - headerOffset() - 8; 62 window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' }); 63 } 64 65 function resolve(href, trigger) { 66 var url = toUrl(href); 67 if (!url || !isSamePage(url)) { 68 return null; 69 } 70 71 var hash = url.hash || ''; 72 var tabIndex = 0; 73 var section = null; 74 var tabEl = null; 75 76 if (trigger) { 77 tabIndex = parseInt(trigger.getAttribute('data-faddom-tab'), 10) || 0; 78 } 79 if (!tabIndex) { 80 tabIndex = parseInt(url.searchParams.get('tab'), 10) || 0; 81 } 82 83 var named = hash.match(TAB_HASH); 84 if (named) { 85 section = document.getElementById(named[1]); 86 tabIndex = parseInt(named[2], 10) || tabIndex; 87 } else if (hash.length > 1) { 88 var byId = document.getElementById(hash.slice(1)); 89 if (byId && (byId.classList.contains('e-n-tab-title') || byId.classList.contains('elementor-tab-title'))) { 90 tabEl = byId; 91 section = byId.closest('.elementor-widget-n-tabs, .elementor-widget-tabs, .elementor-element') || byId; 92 } else if (byId && tabIndex) { 93 section = byId; 94 } 95 } 96 97 if (!tabEl && section && tabIndex > 0) { 98 var titles = tabsIn(section); 99 tabEl = titles[tabIndex - 1] || null; 100 } 101 102 if (!tabEl && !section) { 103 return null; 104 } 105 106 return { section: section, tabEl: tabEl }; 107 } 108 109 function openTarget(target) { 110 if (!target) { 111 return false; 112 } 113 activateTab(target.tabEl); 114 window.requestAnimationFrame(function () { 115 scrollToEl(target.section || target.tabEl); 116 }); 117 return true; 118 } 119 120 function openFromUrl(href, trigger) { 121 return openTarget(resolve(href, trigger)); 122 } 123 124 function init() { 125 if (isEditor()) { 126 return; 127 } 128 129 document.addEventListener( 130 'click', 131 function (event) { 132 var link = event.target.closest('a[href]'); 133 if (!link || event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) { 134 return; 135 } 136 var target = resolve(link.getAttribute('href'), link); 137 if (!target || !target.tabEl) { 138 return; 139 } 140 event.preventDefault(); 141 openTarget(target); 142 }, 143 true 144 ); 145 146 window.addEventListener('hashchange', function () { 147 openFromUrl(window.location.href, null); 148 }); 149 150 var openedFromLoad = false; 151 function fromLoad() { 152 var target = resolve(window.location.href, null); 153 if (!target || !target.tabEl) { 154 return; 155 } 156 var alreadyOn = target.tabEl.getAttribute('aria-selected') === 'true'; 157 if (alreadyOn && openedFromLoad) { 158 return; 159 } 160 openedFromLoad = true; 161 openTarget(target); 162 } 163 164 fromLoad(); 165 window.addEventListener('load', fromLoad); 166 window.addEventListener('elementor/frontend/init', function () { 167 window.setTimeout(fromLoad, 50); 168 }); 169 } 170 171 if (document.readyState === 'loading') { 172 document.addEventListener('DOMContentLoaded', init); 173 } else { 174 init(); 175 } 176})();
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.