PageSourceSearch

https://greatpines.com/wp-content/plugins/breakdance/subplugins/br…/popups@1/popups.js?bd_ver=2.8.3

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

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.