PageSourceSearch

https://www.blueacornici.com/utils/utils.js

js blueacornici.com collected 2026-09-26 02:03:33 UTC 12,076 bytes, 390 lines download raw bytes

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.