1import createTag from './tag.js'; 2 3export function loadScript(url, attrs) { 4 const head = document.querySelector('head'); 5 const script = document.createElement('script'); 6 script.src = url; 7 if (attrs) { 8 // eslint-disable-next-line no-restricted-syntax, guard-for-in 9 for (const attr in attrs) { 10 script.setAttribute(attr, attrs[attr]); 11 } 12 } 13 head.append(script); 14 return script; 15} 16 17export function addCSSClassesToElementChildren(element, elementClasslistArray) { 18 let { firstElementChild } = element; 19 20 while (firstElementChild && elementClasslistArray.length) { 21 firstElementChild.classList.add(elementClasslistArray.shift()); 22 firstElementChild = firstElementChild.nextElementSibling; 23 } 24} 25 26/** 27 * * @param {HTMLElement} element the element with the parent undesired wrapper, like <p></p> 28 * * @param {targetSelector} string selector of the target element 29 * result: removed the undesired wrapper 30 */ 31export function removeOuterElementLayer(element, targetSelector) { 32 const targetElement = element.querySelector(targetSelector); 33 if (targetElement) { 34 const parent = targetElement.parentNode; 35 if (parent) parent.replaceWith(targetElement); 36 } 37} 38 39/** 40 * * @param {HTMLElement} element the elemen/block with mutilple child 41 * * that you want to combine that into single div only 42 * result: single div with all children elements 43 * e.g. input: <div class="wrapper"> 44 * * <div class="unwanted-wrapper-one"> <p/> </div> 45 * * <div class="unwanted-wrapper-two"> <br/> </div> 46 * * </div> 47 * * output: <div class="wrapper"> 48 * * <div> 49 * * <p/> <br/> 50 * * </div> 51 * * </div> 52 */ 53export function combineChildrenToSingleDiv(element) { 54 const targetChildren = element.querySelectorAll(':scope > div'); 55 if (targetChildren.length === 0) { 56 return; 57 } 58 59 const singleDiv = document.createElement('div'); 60 targetChildren.forEach((targetChild) => { 61 const children = Array.from(targetChild.childNodes); 62 children.forEach((childElement) => { 63 singleDiv.appendChild(childElement); 64 }); 65 targetChild.remove(); 66 }); 67 68 element.append(singleDiv); 69} 70 71/** 72 * * @param {HTMLElement} element 73 * * @param {string} targetTag, like 'ul' or 'div' 74 * * @param {string} className 75 * result: return the new element with inner content of the element, desired tag and css class 76 */ 77export function changeTag(element, targetTag, className) { 78 const newElClass = className || ''; 79 const innerContent = element.innerHTML; 80 return createTag( 81 targetTag, 82 { class: newElClass }, 83 innerContent, 84 ); 85} 86 87// as the blocks are loaded in aysnchronously, we don't have a specific timing 88// that the all blocks are loaded -> cannot use a single observer to 89// observe all blocks, so use functions here in blocks instead 90 91const requireRevealWrapper = ['slide-reveal-up', 'slide-reveal-up-slow']; 92 93export function addRevealWrapperToAnimationTarget(element) { 94 const revealWrapper = createTag('div', { class: 'slide-reveal-wrapper' }); 95 const parent = element.parentNode; 96 // Insert the wrapper before the element 97 parent.insertBefore(revealWrapper, element); 98 revealWrapper.appendChild(element); 99} 100 101export function addAnimatedClassToElement( 102 targetSelector, 103 animatedClass, 104 delayTime, 105 targetSelectorWrapper, 106) { 107 const target = targetSelectorWrapper.querySelector(targetSelector); 108 if (target) { 109 target.classList.add(animatedClass); 110 if (delayTime) target.style.transitionDelay = `${delayTime}s`; 111 if (requireRevealWrapper.indexOf(animatedClass) !== -1) { 112 addRevealWrapperToAnimationTarget(target); 113 } 114 } 115} 116 117export function addAnimatedClassToMultipleElements( 118 targetSelector, 119 animatedClass, 120 delayTime, 121 targetSelectorWrapper, 122 staggerTime, 123) { 124 const targets = targetSelectorWrapper.querySelectorAll(targetSelector); 125 if (targets) { 126 targets.forEach((target, i) => { 127 target.classList.add(animatedClass); 128 if (delayTime) target.style.transitionDelay = `${delayTime * (i + 1)}s`; 129 if (staggerTime) { 130 target.style.transitionDelay = `${delayTime + staggerTime * (i + 1)}s`; 131 } 132 if (requireRevealWrapper.indexOf(animatedClass) !== -1) { 133 addRevealWrapperToAnimationTarget(target); 134 } 135 }); 136 } 137} 138 139export function addInviewObserverToTriggerElement(triggerElement) { 140 const observerOptions = { 141 threshold: 0.25, // show when is 25% in view 142 }; 143 const observer = new IntersectionObserver((entries) => {
144 entries.forEach((entry) => { 145 if (entry.isIntersecting) { 146 entry.target.classList.add('in-view'); 147 observer.unobserve(entry.target); 148 } 149 }); 150 }, observerOptions); 151 observer.observe(triggerElement); 152} 153 154export function addInViewAnimationToSingleElement( 155 targetElement, 156 animatedClass, 157 triggerElement, 158 delayTime, 159) { 160 // if it's HTML element 161 if (targetElement.nodeType === 1) { 162 targetElement.classList.add(animatedClass); 163 if (requireRevealWrapper.indexOf(animatedClass) !== -1) { 164 addRevealWrapperToAnimationTarget(targetElement); 165 } 166 } 167 // if it's string only, which should be a selector 168 if (targetElement.nodeType === 3) { 169 addAnimatedClassToElement( 170 targetElement, 171 animatedClass, 172 triggerElement, 173 delayTime, 174 ); 175 } 176 const trigger = triggerElement || targetElement; 177 addInviewObserverToTriggerElement(trigger); 178} 179 180export function addInViewAnimationToMultipleElements( 181 animatedItems, 182 triggerElement, 183 staggerTime, 184) { 185 // set up animation class 186 animatedItems.forEach((el, i) => { 187 const delayTime = staggerTime ? i * staggerTime : null; 188 if (Object.prototype.hasOwnProperty.call(el, 'selector')) { 189 addAnimatedClassToElement( 190 el.selector, 191 el.animatedClass, 192 delayTime, 193 triggerElement, 194 ); 195 } 196 if (Object.prototype.hasOwnProperty.call(el, 'selectors')) { 197 addAnimatedClassToMultipleElements( 198 el.selectors, 199 el.animatedClass, 200 el.staggerTime, 201 triggerElement, 202 ); 203 } 204 }); 205 206 // add `.in-view` to triggerElement, so the elements inside will start animating 207 addInviewObserverToTriggerElement(triggerElement); 208} 209 210export function makeLinksAccessible(...links) { 211 const companyNames = { 212 adobe: 'Adobe', 213 blueacornici: 'Blue Acorn ICI', 214 shopify: 'Shopify', 215 salesforce: 'Salesforce', 216 optimizely: 'Optimizely', 217 mirakl: 'Mirakl', 218 gorgias: 'Gorgias', 219 klaviyo: 'Klaviyo', 220 shipperhq: 'ShipperHQ', 221 yotpo: 'Yotpo', 222 dynamicyield: 'Dynamic Yield', 223 digitalriver: 'Digital River', 224 signifyd: 'Signifyd', 225 avalara: 'Avalara', 226 algolia: 'Algolia', 227 salsify: 'Salsify', 228 listrak: 'Listrak', 229 facebook: 'Facebook', 230 linkedin: 'LinkedIn', 231 twitter: 'Twitter / X', 232 instagram: 'Instagram', 233 magento: 'Magento', 234 }; 235 236 links.forEach((link) => { 237 if (!link.getAttribute('aria-label')) { 238 // clean up the random empty title attributes 239 if (link.getAttribute('title') && link.getAttribute('title').trim().length === 0) { 240 link.removeAttribute('title'); 241 } 242 if (link.textContent.trim().length === 0) { 243 const companyName = Object.keys(companyNames).find((name) => link.href.includes(name)); 244 if (companyName) { 245 link.setAttribute('aria-label', companyNames[companyName]); 246 } 247 } else { 248 link.setAttribute('aria-label', link.textContent); 249 } 250 } 251 }); 252} 253 254export function targetBlank(elementClassname) { 255 // remove subdomain of current site's url and setup regex 256 const internal = window.location.host.replace('www.', ''); 257 const element = document.querySelector(elementClassname); 258 const a = element.querySelectorAll('a'); // then, grab every link inside of element 259 260 for (let i = 0; i < a.length; i += 1) { 261 const href = a[i].host; // set the host of each link 262 if (!internal.match(href)) { // make sure the href doesn't contain current site's host 263 a[i].setAttribute('target', '_blank'); // if it doesn't, set attributes 264 a[i].setAttribute('rel', 'noopener noreferrer'); 265 } 266 } 267} 268 269function checkLibraryOrigin() { 270 const { origin } = window.location; 271 272 // checking to see if the origin is null because the AEM Library tool uses a frame 273 // to pull in the fragments and doesn't have access to the window object 274 if (origin === 'null') { 275 return false; 276 } 277 return !!origin; 278} 279export function createHtmlVideo( 280 videoUrl, 281 posterUrl, 282 videoUrl2, 283 posterUrl2, 284 videoUrlMp4Mobile, 285 videoUrlMp4Desktop, 286) { 287 const video = document.createElement('video'); 288 const baseUrl = checkLibraryOrigin() ? window.location.origin : ''; 289 video.classList.add('lazy-video'); 290 video.muted = true; 291 video.autoplay = true; 292 video.setAttribute('playsinline', true); 293 video.setAttribute('loop', true); 294 295 // TODO: Add video fallback formats 296 // TODO: Add video pause for accessibility 297 if (window.innerWidth >= 815 && videoUrl2) { 298 const source2 = document.createElement('source'); 299 source2.setAttribute('data-src', `${baseUrl}/${videoUrl2}`); 300 source2.setAttribute('type', 'video/webm'); 301 if (posterUrl2) { 302 video.setAttribute('poster', `${baseUrl}/${posterUrl2}`); 303 } 304 video.append(source2); 305 } else { 306 const source = document.createElement('source'); 307 source.setAttribute('data-src', `${baseUrl}/${videoUrl}`); 308 source.setAttribute('type', 'video/webm'); 309 if (posterUrl) { 310 video.setAttribute('poster', `${baseUrl}/${posterUrl}`); 311 } 312 video.append(source); 313 if (videoUrlMp4Mobile) { 314 const sourceMp4 = document.createElement('source'); 315 sourceMp4.setAttribute('data-src', `${baseUrl}/${videoUrlMp4Mobile}`); 316 sourceMp4.setAttribute('type', 'video/mp4'); 317 video.append(sourceMp4); 318 } 319 } 320 321 // Add logic for iPad/desktop MP4 video 322 if (videoUrlMp4Desktop) { 323 const sourceMp4Desktop = document.createElement('source'); 324 sourceMp4Desktop.setAttribute('data-src', `${baseUrl}/${videoUrlMp4Desktop}`); 325 sourceMp4Desktop.setAttribute('type', 'video/mp4'); 326 video.append(sourceMp4Desktop); 327 } 328 329 return video; 330} 331 332export function decorateLottie() { 333 const lotties = [...document.querySelectorAll('.lottie-file')]; 334 const lottieScriptURL = 'https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs'; 335 336 if (lotties.length === 0) return; 337 338 // Check if IntersectionObserver is supported. 339 if ('IntersectionObserver' in window) { 340 const lazyLottieObserver = new IntersectionObserver((entries, _observer) => {
341 entries.forEach((lottie) => { 342 if (lottie.isIntersecting) { 343 loadScript(lottieScriptURL, { type: 'module', async: 'true' }); 344 345 const lottieData = lottie.target.innerHTML.replace(/<[^>]+>/g, '').trim(); 346 const lottiePlayer = document.createElement('dotlottie-player'); 347 lottiePlayer.setAttribute('src', lottieData); 348 lottiePlayer.setAttribute('background', 'transparent'); 349 lottiePlayer.setAttribute('speed', 1); 350 lottiePlayer.setAttribute('loop', true); 351 lottiePlayer.setAttribute('autoplay', true); 352 lottie.target.innerHTML = ''; 353 lottie.target.append(lottiePlayer); 354 355 // Unobserve the lottie element to prevent re-triggering. 356 lazyLottieObserver.unobserve(lottie.target); 357 } 358 }); 359 }); 360 361 lotties.forEach((lottie) => { 362 lazyLottieObserver.observe(lottie); 363 }); 364 } 365} 366 367export function wrapElementsWithSpan(elements) { 368 elements.forEach((element) => { 369 const content = element.innerHTML; 370 const span = document.createElement('span'); 371 span.innerHTML = content; 372 element.innerHTML = ''; 373 element.appendChild(span); 374 }); 375} 376 377export default { 378 loadScript, 379 addCSSClassesToElementChildren, 380 removeOuterElementLayer, 381 changeTag, 382 addInViewAnimationToSingleElement, 383 addInViewAnimationToMultipleElements, 384 addInviewObserverToTriggerElement, 385 makeLinksAccessible, 386 targetBlank, 387 createHtmlVideo, 388 decorateLottie, 389 wrapElementsWithSpan, 390};
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.