PageSourceSearch

https://site.fr/_static/8c8af5c529149f32/scripts/features/common/get-started-modal.js

js site.fr collected 2026-09-24 18:20:02 UTC 7,003 bytes, 238 lines download raw bytes

1import { initEmblaCarousel } from '../../ui/embla.js';
2import { createModal } from '../../ui/modal.js';
3import { allInOneEvent, allInOneConversionEvent } from './data-layer-events.js';
4import { DOMAIN_SEARCH_ACCEPTED_EVENT } from './domain-search-events.js';
5
6const initGetStartedModal = () => {
7  const root = document.querySelector('[data-get-started-modal]');
8  if (!root) return;
9
10  if (root.dataset.initialized === 'true') return;
11  root.dataset.initialized = 'true';
12
13  const overlay = root.querySelector('[data-get-started-overlay]');
14  const panel = root.querySelector('[role="dialog"]');
15  const closeButton = root.querySelector('[data-modal-close]');
16  const carouselRoot = root.querySelector('[data-get-started-carousel]');
17  const progressBar = root.querySelector('[data-embla-progress]');
18  const domainSearch = root.querySelector('[data-get-started-domain]');
19  const domainSearchForm = domainSearch?.querySelector('[data-domain-search]');
20  const nextButtons = root.querySelectorAll('[data-get-started-next]');
21  const domainTriggers = root.querySelectorAll('[data-get-started-show-domain]');
22
23  let embla = null;
24  let refreshTimer = null;
25  let visibilityTimer = null;
26  let carouselViewport = null;
27  let carouselContainer = null;
28  let autoHeightQuery = null;
29  let autoHeightEnabled = false;
30  let isOpen = false;
31  let trackedStep = null;
32
33  const trackSelectedStep = () => {
34    if (!isOpen) return;
35    const step = embla ? embla.selectedScrollSnap() + 1 : 1;
36    if (step === trackedStep) return;
37    trackedStep = step;
38    allInOneEvent(step);
39  };
40
41  const refreshEmbla = () => {
42    if (!embla) return;
43    embla.reInit();
44  };
45
46  const scheduleRefresh = (delay = 0) => {
47    if (!embla) return;
48    if (refreshTimer) {
49      window.clearTimeout(refreshTimer);
50    }
51    refreshTimer = window.setTimeout(() => {
52      refreshEmbla();
53      refreshTimer = null;
54    }, delay);
55  };
56
57  const setCarouselVisibility = (visible) => {
58    if (!carouselViewport) return;
59    carouselViewport.style.visibility = visible ? '' : 'hidden';
60  };
61
62  const setContainerTransition = (enabled) => {
63    if (!carouselContainer) return;
64    carouselContainer.style.transition = enabled ? '' : 'none';
65  };
66
67  const shouldUseAutoHeight = () => window.matchMedia?.('(min-width: 768px)')?.matches;
68
69  const buildPlugins = () => {
70    const plugins = [];
71    if (typeof EmblaCarouselFade === 'function') {
72      plugins.push(EmblaCarouselFade());
73    }
74    const useAutoHeight = shouldUseAutoHeight() && typeof EmblaCarouselAutoHeight === 'function';
75    if (useAutoHeight) {
76      plugins.push(EmblaCarouselAutoHeight());
77    }
78    return { plugins, useAutoHeight };
79  };
80
81  const destroyEmbla = () => {
82    if (!embla) return;
83    embla.destroy();
84    embla = null;
85  };
86
87  const createEmbla = ({ plugins, useAutoHeight }) => {
88    autoHeightEnabled = Boolean(useAutoHeight);
89    embla = initEmblaCarousel(
90      carouselRoot,
91      { watchDrag: false, startIndex: trackedStep ? trackedStep - 1 : 0 },
92      { progress: { bar: progressBar } },
93      plugins,
94    );
95    embla?.on('select', trackSelectedStep);
96    if (!embla && progressBar) {
97      progressBar.style.width = '100%';
98    }
99    if (!autoHeightEnabled && carouselContainer) {
100      carouselContainer.style.height = '';
101      if (carouselViewport) {
102        carouselViewport.style.height = '';
103      }
104      if (carouselRoot) {
105        carouselRoot.style.height = '';
106      }
107    }
108  };
109
110  const hideDomainSearch = () => {
111    if (domainSearch) {
112      domainSearch.classList.add('hidden');
113    }
114  };
115
116  const showDomainSearch = () => {
117    if (!domainSearch) return;
118    domainSearch.classList.remove('hidden');
119    if (typeof domainSearch.scrollIntoView === 'function') {
120      domainSearch.scrollIntoView({ behavior: 'smooth', block: 'start' });
121    }
122  };
123
124  const initCarousel = () => {
125    if (!carouselRoot) {
126      if (progressBar) {
127        progressBar.style.width = '100%';
128      }
129      return;
130    }
131
132    carouselViewport = carouselRoot.querySelector('[data-embla-viewport]');
133    carouselContainer = carouselRoot.querySelector('[data-embla-container]');
134
135    createEmbla(buildPlugins());
136
137    if (embla) {
138      const images = carouselRoot.querySelectorAll('img');
139      images.forEach((img) => {
140        if (!(img instanceof HTMLImageElement)) return;
141        if (img.complete) return;
142        img.addEventListener(
143          'load',
144          () => {
145            scheduleRefresh(0);
146          },
147          { once: true },
148        );
149      });
150    }
151
152    if (window.matchMedia) {
153      autoHeightQuery = window.matchMedia('(min-width: 768px)');
154      const handleChange = () => {
155        if (!carouselRoot) return;
156        destroyEmbla();
157        createEmbla(buildPlugins());
158        scheduleRefresh(0);
159      };
160      if (autoHeightQuery && typeof autoHeightQuery.addEventListener === 'function') {
161        autoHeightQuery.addEventListener('change', handleChange);
162      } else if (autoHeightQuery && typeof autoHeightQuery.addListener === 'function') {
163        autoHeightQuery.addListener(handleChange);
164      }
165    }
166  };
167
168  initCarousel();
169
170  nextButtons.forEach((button) => {
171    button.addEventListener('click', (event) => {
172      event.preventDefault();
173      hideDomainSearch();
174      if (embla) {
175        embla.scrollNext();
176      }
177    });
178  });
179
180  domainTriggers.forEach((button) => {
181    button.addEventListener('click', (event) => {
182      event.preventDefault();
183      showDomainSearch();
184    });
185  });
186
187  domainSearchForm?.addEventListener(DOMAIN_SEARCH_ACCEPTED_EVENT, () => {
188    const step = embla ? embla.selectedScrollSnap() + 1 : 1;
189    allInOneConversionEvent(step);
190  });
191
192  const modal = createModal({
193    root,
194    panel,
195    overlay,
196    openTriggerSelector: '[data-get-started-trigger]',
197    closeOnOverlay: true,
198    closeOnEsc: true,
199    trapFocus: true,
200    restoreFocus: true,
201    // The carousel is briefly visibility:hidden while Embla measures it, so its
202    // buttons cannot receive focus during the modal's opening animation.
203    initialFocus: () => closeButton || root.querySelector('[data-get-started-next]'),
204    onOpen: () => {
205      isOpen = false;
206      trackedStep = null;
207      hideDomainSearch();
208      if (embla) {
209        setCarouselVisibility(false);
210        setContainerTransition(false);
211        embla.scrollTo(0);
212        // Modal is hidden on init; reInit after open to get correct height.
213        scheduleRefresh(0);
214        scheduleRefresh(120);
215        if (visibilityTimer) {
216          window.clearTimeout(visibilityTimer);
217        }
218        visibilityTimer = window.setTimeout(() => {
219          setContainerTransition(true);
220          setCarouselVisibility(true);
221          visibilityTimer = null;
222        }, 160);
223      }
224      isOpen = true;
225      trackSelectedStep();
226    },
227    onClose: () => {
228      isOpen = false;
229      hideDomainSearch();
230    },
231  });
232
233  if (!modal) return;
234
235  // Close handled by shared modal close trigger.
236};
237
238initGetStartedModal();

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.