PageSourceSearch

https://vasaloppet.se/wp-content/plugins/breakdance/subplugins/bre…/popups@1/popups.js?bd_ver=2.5.2

js vasaloppet.se collected 2026-10-02 05:50:21 UTC 17,810 bytes, 661 lines download raw bytes

1(function () {
2  const { mergeObjects, getCurrentBreakpoint } = BreakdanceFrontend.utils;
3  window.breakdancePopupInstances = {};
4  window.breakdanceHasShownPopup = false;
5
6  class BreakdancePopup {
7    defaultOptions = {
8      keepOpenOnHashlinkClicks: false,
9      closeOnClickOutside: true,
10      closeOnEscapeKey: true,
11      closeAfterMilliseconds: null,
12      showCloseButtonAfterMilliseconds: null,
13      disableScrollWhenOpen: false,
14      avoidMultiple: false,
15      entranceAnimation: null,
16      exitAnimation: null,
17      limitPageLoad: null,
18      limitSession: null,
19      limitForever: null,
20      breakpointConditions: [],
21      triggers: [],
22    };
23
24    showCount = 0;
25    idleTimeMilliseconds = 0;
26    entranceAnimation = false;
27    exitAnimation = false;
28    lastScrollPosition = window.scrollY;
29
30    boundEvents = {};
31
32    openClass = "breakdance-popup-open";
33    animatingClass = "breakdance-popup-animating";
34
35    constructor(id, options) {
36      if (window.breakdancePopupInstances[id]) {
37        return;
38      }
39      this.id = id;
40      this.options = mergeObjects(this.defaultOptions, options);
41      this.triggers = this.options.triggers;
42      this.storageKey = `breakdance_popup_${id}_count`;
43      window.breakdancePopupInstances[id] = this;
44
45      this.triggers.forEach((trigger) => {
46        this.initTrigger(trigger.slug, trigger.options);
47      });
48      this.init();
49    }
50
51    init() {
52      this.element = document.querySelector(
53        `[data-breakdance-popup-id="${this.id}"]`
54      );
55      this.wrapper = this.element.closest(".bde-popup");
56
57      if (!this.element) {
58        return false;
59      }
60
61      if (!this.wrapper) {
62        return false;
63      }
64
65      this.initCloseButton();
66
67      this.wrapper.addEventListener("breakdance_popup_open", () => {
68      });
69
70      this.wrapper.addEventListener("breakdance_popup_close", () => {
71        this.handleClose();
72      });
73
74      return true;
75    }
76
77    setOptions(options) {
78      this.options = { ...this.options, ...options };
79    }
80
81    open(forceOpen = false) {
82      const event = new Event("breakdance_play_animations", { bubbles: true });
83      this.element.dispatchEvent(event);
84
85      return new Promise((resolve, reject) => {
86        if (this.shouldHideAtBreakpoint()) {
87          return resolve();
88        }
89
90        if (this.isOpen() || this.isAnimating()) {
91          return resolve();
92        }
93
94        if (!forceOpen && this.hasReachedLimit()) {
95          return resolve();
96        }
97
98        if (this.element.dataset?.breakdancePopupUsingProTriggers) {
99          alert(
100            `This popup is using a Pro-only trigger. The popup would show up now instead of this alert. Get Breakdance Pro to get access to all popup triggers.`
101          );
102
103          return resolve();
104        }
105
106        this.incrementShowCounts();
107
108        this.registerCloseListeners();
109
110        if (this.options.entranceAnimation) {
111          this.playEntranceAnimation();
112        } else {
113          this.wrapper.classList.add(this.openClass);
114        }
115
116        this.wrapper.dispatchEvent(new CustomEvent("breakdance_popup_open"));
117        return resolve();
118
119      });
120    }
121
122    registerCloseListeners() {
123      this.boundCloseIfEscapeOrClickOutside = this.closeIfEscapeOrClickOutside.bind(
124        this
125      );
126
127      if (this.options.closeOnEscapeKey) {
128        document.addEventListener(
129          "keydown",
130          this.boundCloseIfEscapeOrClickOutside
131        );
132      }
133
134      this.boundMaybeAutomaticallyClosePopup = this.maybeAutomaticallyClosePopup.bind(this);
135      this.element.addEventListener("click", this.boundMaybeAutomaticallyClosePopup);
136
137      if (this.options.closeOnClickOutside) {
138        document.addEventListener(
139          "mousedown",
140          this.boundCloseIfEscapeOrClickOutside
141        );
142      }
143
144      if (this.options.closeAfterMilliseconds) {
145        setTimeout(() => {
146          if (this.isOpen()) {
147            this.close();
148          }
149        }, this.options.closeAfterMilliseconds);
150      }
151
152      if (this.options.disableScrollWhenOpen) {
153        document.querySelector("html").classList.add("breakdance-noscroll");
154      }
155    }
156
157    removeCloseListeners() {
158      if (this.boundCloseIfEscapeOrClickOutside) {
159        document.removeEventListener(
160          "keydown",
161          this.boundCloseIfEscapeOrClickOutside
162        );
163        document.removeEventListener(
164          "mousedown",
165          this.boundCloseIfEscapeOrClickOutside
166        );
167        this.element.removeEventListener(
168          "click",
169          this.boundMaybeAutomaticallyClosePopup
170        );
171      }
172    }
173
174    hasReachedLimit() {
175      if (
176        this.options.avoidMultiple &&
177        window.breakdanceHasShownPopup === true
178      ) {
179        return true;
180      }
181
182      if (
183        this.options.limitPageLoad &&
184        this.getShowCount("page_load") >= this.options.limitPageLoad
185      ) {
186        return true;
187      }
188
189      if (
190        this.options.limitSession &&
191        this.getShowCount("session") >= this.options.limitSession
192      ) {
193        return true;
194      }
195
196      if (
197        this.options.limitForever &&
198        this.getShowCount("local") >= this.options.limitForever
199      ) {
200        return true;
201      }
202
203      return false;
204    }
205
206    isOpen() {
207      return this.wrapper && this.wrapper.classList.contains(this.openClass);
208    }
209
210    isAnimating() {
211      return this.wrapper && this.wrapper.classList.contains(this.animatingClass);
212    }
213
214    closeIfEscapeOrClickOutside(event) {
215      const shouldClose =
216        (event.keyCode === 27 && this.isPopupOnTop()) ||
217        event.target === this.wrapper;
218
219      if (shouldClose) {
220        event.preventDefault();
221        event.stopPropagation();
222        this.close();
223      }
224    }
225
226    isPopupOnTop() {
227      const topMostElement = document.elementFromPoint(
228        this.wrapper.offsetLeft,
229        this.wrapper.offsetTop
230      );
231      return topMostElement === this.wrapper;
232    }
233
234    maybeAutomaticallyClosePopup(event) {
235
236      if (this.options.keepOpenOnHashlinkClicks) {
237        return;
238      }
239
240      /*
241      if a disable-popup-autoclose attribute is present on a parent 
242      of the event target, don't automatically close the popup
243      this feature is experimental and may not be available in future versions of Breakdance
244      */
245      const elementWitDisablePopupAutocloseAttribute = event.target.closest("[disable-popup-autoclose]");
246
247      if (elementWitDisablePopupAutocloseAttribute) {
248        return;
249      }
250
251      /*
252      autoclose the popup when:
253       - clicking on a link with an attribute of close-popup-on-click (this feature is experimental and may not be available in future versions of Breakdance)
254       - clicking on a link with a href of #
255       - clicking a #hashlink that leads somewhere on the page
256      */
257
258      if (event.target.closest('[close-popup-on-click]')) {
259        this.close();
260        return;
261      }
262
263      const link = event.target.closest("a");
264      if (!link) return;
265
266      if (link.getAttribute('href') === '#') {
267        this.close();
268        return;
269      }
270      
271      const url = new URL(link);
272      if (!url.hash) return;
273
274      const hashAsIdOrName = url.hash.substring(1);
275
276      if (hashAsIdOrName && document.querySelector(`#${hashAsIdOrName}, a[name=${hashAsIdOrName}]`)) {
277        this.close();
278        return;
279      }
280
281    }
282
283    close() {
284      return new Promise((resolve, reject) => {
285        if (!this.isOpen() || this.isAnimating()) {
286          return resolve();
287        }
288
289        this.removeCloseListeners();
290
291        if (this.options.disableScrollWhenOpen) {
292          document
293            .querySelector("html")
294            .classList.remove("breakdance-noscroll");
295        }
296
297        if (this.options.exitAnimation) {
298          this.playExitAnimation();
299        } else {
300          this.wrapper.classList.remove(this.openClass);
301        }
302
303        this.wrapper.dispatchEvent(new CustomEvent("breakdance_popup_close"));
304        return resolve();
305
306      });
307    }
308
309    toggle() {
310      if (this.isOpen()) {
311        return this.close();
312      }
313      return this.open();
314    }
315
316    showPopupOnScrollPercentage(options, callback) {
317      return event => {
318        const targetPercent = options.percent / 100;
319        const scrollTop = window.scrollY;
320        const documentHeight = document.body.offsetHeight;
321        const windowHeight = window.innerHeight;
322        const scrollPercent = scrollTop / (documentHeight - windowHeight);
323        if (scrollPercent > targetPercent) {
324          this.open().then(callback);
325        }
326      };
327    }
328
329    showPopupOnScrollToSelector(options) {
330      const element = document.querySelector(options.selector);
331      if (!element) {
332        return;
333      }
334      const ob = new IntersectionObserver(
335        (entries, observer) => {
336          entries.forEach(entry => {
337            if (entry.isIntersecting && !this.isOpen()) {
338              this.open().then(() => {
339                observer.unobserve(entry.target);
340              });
341            }
342          });
343        },
344        {
345          root: null,
346          rootMargin: "0px",
347          threshold: 0.2
348        }
349      );
350      ob.observe(element);
351    }
352
353    showPopupOnScrollUp(callback) {
354      return event => {
355        const scrollTop = window.scrollY;
356        if (scrollTop < this.lastScrollPosition) {
357          this.open().then(callback);
358        }
359        this.lastScrollPosition = scrollTop;
360      };
361    }
362
363    showPopupAfterInactivityDelay(delayInMilliseconds) {
364      const idleTimeout = 1000;
365      const idleInterval = setInterval(() => {
366        if (this.idleTimeMilliseconds >= delayInMilliseconds) {
367          this.open();
368          clearInterval(idleInterval);
369          if (boundResetIdleTime) {
370            document.removeEventListener("mousemove", boundResetIdleTime);
371            document.removeEventListener("keydown", boundResetIdleTime);
372          }
373        } else {
374          this.idleTimeMilliseconds += idleTimeout;
375        }
376      }, idleTimeout);
377      const boundResetIdleTime = this.resetIdleTime.bind(this);
378      document.addEventListener("mousemove", boundResetIdleTime);
379      document.addEventListener("keydown", boundResetIdleTime);
380    }
381
382    resetIdleTime() {
383      this.idleTimeMilliseconds = 0;
384    }
385
386    showPopupOnExitIntent(callback) {
387      return event => {
388        const isExitIntent =
389          !event.toElement && !event.relatedTarget && event.clientY < 10;
390        if (isExitIntent) {
391          this.open().then(callback);
392        }
393      };
394    }
395
396    showPopupOnClickEvent(options, callback) {
397      return event => {
398        if (options.clickType === "anywhere") {
399          return this.open().then(callback);
400        }
401        if (options.selector !== null) {
402          if (event.target.closest(options.selector)) {
403            event.preventDefault();
404            event.stopPropagation();
405            return this.open().then(callback);
406          }
407        }
408      };
409    }
410
411    showPopupOnLoad(delayInMilliseconds) {
412      setTimeout(() => {
413        this.open();
414      }, delayInMilliseconds);
415    }
416
417    async playEntranceAnimation() {
418
419      if (this.entranceAnimation) {
420        this.entranceAnimation.destroy();
421      }
422
423      if (this.options.entranceAnimation !== null) {
424        this.entranceAnimation = new BreakdancePopupAnimation(
425          this.element,
426          this.wrapper,
427          this.options.entranceAnimation
428        );
429      }
430
431      await this.entranceAnimation.play();
432
433    }
434
435    async playExitAnimation(pauseTweenAtEnd = false) {
436
437      if (this.exitAnimation) {
438        this.exitAnimation.destroy();
439      }
440
441      if (this.options.exitAnimation !== null) {
442        this.exitAnimation = new BreakdancePopupAnimation(
443          this.element,
444          this.wrapper,
445          this.options.exitAnimation,
446          true
447        );
448      }
449
450      await this.exitAnimation.play(true);
451
452      if (pauseTweenAtEnd) {
453        this.exitAnimation.tween.pause(0);
454      }
455      
456      this.wrapper.classList.remove(this.openClass);
457      this.exitAnimation.destroy();
458
459    }
460
461    initTrigger(slug, options = {}) {
462      if (slug === "load") {
463        const delay = options.delay ? options.delay * 1000 : 0;
464        this.showPopupOnLoad(delay);
465      }
466
467      if (slug === "inactivity") {
468        const delay = options.delay ? options.delay * 1000 : 0;
469        this.showPopupAfterInactivityDelay(delay);
470      }
471
472      if (slug === "scroll" && options.scrollType === "selector") {
473        this.showPopupOnScrollToSelector(options);
474      }
475
476      if (slug === "scroll" && options.scrollType === "percent") {
477        const scrollEventListener = this.showPopupOnScrollPercentage(
478          options,
479          () => {
480            window.removeEventListener("scroll", scrollEventListener);
481          }
482        );
483        window.addEventListener("scroll", scrollEventListener);
484      }
485
486      if (slug === "scroll_up") {
487        const scrollUpEventListener = this.showPopupOnScrollUp(() => {
488          window.removeEventListener("scroll", scrollUpEventListener);
489        });
490        window.addEventListener("scroll", scrollUpEventListener);
491      }
492
493      if (slug === "exit_intent") {
494        const exitIntentEventListener = this.showPopupOnExitIntent
494(() => {
495          document.removeEventListener("mouseout", exitIntentEventListener);
496        });
497        document.addEventListener("mouseout", exitIntentEventListener);
498      }
499
500      if (slug === "click") {
501        const boundShowPopupOnClickEvent = this.showPopupOnClickEvent(
502          options,
503          () => {
504            if (options.clickType === "anywhere") {
505              document.removeEventListener("click", boundShowPopupOnClickEvent);
506            }
507          }
508        );
509        document.addEventListener("click", boundShowPopupOnClickEvent);
510      }
511    }
512
513    initCloseButton() {
514      if (this.options.showCloseButtonAfterMilliseconds) {
515        setTimeout(() => {
516          if (!this.isOpen()) {
517            return;
518          }
519          const closeButton = this.element.querySelector(
520            ".breakdance-popup-close-button"
521          );
522          if (closeButton) {
523            closeButton.classList.remove("hidden");
524          }
525        }, this.options.showCloseButtonAfterMilliseconds);
526      }
527    }
528
529    incrementShowCounts() {
530      window.breakdanceHasShownPopup = true;
531
532      const sessionStorageValue = this.getShowCount("session");
533      sessionStorage.setItem(
534        this.storageKey,
535        (sessionStorageValue + 1).toString()
536      );
537
538      const localStorageValue = this.getShowCount("local");
539      localStorage.setItem(this.storageKey, (localStorageValue + 1).toString());
540
541      this.showCount += 1;
542    }
543
544    getShowCount(type) {
545      let showCount = 0;
546
547      if (type === "page_load") {
548        showCount = this.showCount;
549      }
550
551      if (type === "session") {
552        const sessionStorageItem = sessionStorage.getItem(this.storageKey);
553        showCount = sessionStorageItem ? parseInt(sessionStorageItem) : 0;
554      }
555
556      if (type === "local") {
557        const localStorageItem = localStorage.getItem(this.storageKey);
558        showCount = localStorageItem ? parseInt(localStorageItem) : 0;
559      }
560
561      return showCount;
562    }
563
564    shouldHideAtBreakpoint() {
565      const breakpoint = getCurrentBreakpoint();
566
567      return this.options.breakpointConditions.some(condition => {
568        const conditionApplies = condition.breakpoints.includes(breakpoint.id);
569
570        if (condition.operand === "is one of") {
571          return !conditionApplies;
572        }
573
574        if (condition.operand === "is none of") {
575          return conditionApplies;
576        }
577
578        return false;
579      });
580    }
581
582    static runAction(popupId, action = "open") {
583      const isBuilder = !!window?.BreakdanceFrontend.utils.isBuilder();
584
585      if (isBuilder) {
586        return;
587      }
588      const breakdancePopupInstance = window.breakdancePopupInstances[popupId];
589      if (
590        breakdancePopupInstance &&
591        typeof breakdancePopupInstance[action] === "function"
592      ) {
593        breakdancePopupInstance[action].call(breakdancePopupInstance, true);
594      }
595    }
596
597    handleClose() {
598      pauseYouTubeVideosInsideElement(this.wrapper);
599      pauseVimeoVideosInsideElement(this.wrapper);
600      pauseHtml5VideosInsideElement(this.wrapper);
601    }
602  }
603
604  window.BreakdancePopup = BreakdancePopup;
605  document.addEventListener("click", event => {
606    const popupTrigger = event.target.closest("[data-breakdance-popup-action]");
607    if (popupTrigger) {
608      const {
609        breakdancePopupReference,
610        breakdancePopupAction
611      } = popupTrigger.dataset;
612      if (breakdancePopupReference) {
613        event.preventDefault();
614        event.stopPropagation();
615        BreakdancePopup.runAction(
616          breakdancePopupReference,
617          breakdancePopupAction
618        );
619      }
620    }
621  });
622
623  function pauseYouTubeVideosInsideElement(element) {
624    // youtube requires &enablejsapi=1 to be in the embed URL for the below to work
625    element.querySelectorAll(
626      "iframe"
627    ).forEach(
628      (maybeYouTubeVideoFrame) => {
629        maybeYouTubeVideoFrame.contentWindow.postMessage(
630          `{"event":"command","func":"stopVideo","args":[]}`,
631          "*"
632        );
633      }
634    );
635  }
636
637  function pauseVimeoVideosInsideElement(element) {
638    element.querySelectorAll(
639      "iframe"
640    ).forEach(
641      (maybeVimeoVideoIframe) => {
642        maybeVimeoVideoIframe.contentWindow.postMessage(
643          '{"method":"pause","value":""}',
644          '*'
645        );
646      }
647    );
648  }
649
650  function pauseHtml5VideosInsideElement(element) {
651    element.querySelectorAll(
652      "video"
653    ).forEach(
654      (html5Video) => {
655        html5Video.pause();
656      }
657    );
658  }
659
660
661})();

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.