PageSourceSearch

https://www.luxconnect.lu/themes/luxconnect/js/main.js?v=11.4.6

js luxconnect.lu collected 2026-10-01 11:46:24 UTC 11,457 bytes, 399 lines download raw bytes

1/* global Drupal, once */
2((Drupal, once) => {
3  "use strict";
4
5  function rafThrottle(fn) {
6    let ticking = false;
7    return (...args) => {
8      if (ticking) return;
9      ticking = true;
10      requestAnimationFrame(() => {
11        ticking = false;
12        fn(...args);
13      });
14    };
15  }
16
17  Drupal.behaviors.headerSearchToggle = {
18    attach(context) {
19      once('header-search-toggle', '.js-header-search', context).forEach((wrapper) => {
20        const button = wrapper.querySelector('.js-header-search-toggle');
21        const form = wrapper.querySelector('.header-search__form');
22        const input = wrapper.querySelector('input[type="search"], input[type="text"]');
23
24
25        if (!button || !form) {
26          return;
27        }
28
29        const isDesktop = () => window.innerWidth > 1199;
30
31        if (!isDesktop()) {
32          form.hidden = false;
33          button.setAttribute('aria-expanded', 'true');
34          wrapper.classList.add('is-open');
35          return;
36        }
37
38        button.addEventListener('click', () => {
39          if (!isDesktop()) {
40            return;
41          }
42
43          const isOpen = wrapper.classList.toggle('is-open');
44
45          button.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
46          form.hidden = !isOpen;
47
48          if (isOpen && input) {
49            input.focus();
50          }
51        });
52
53        document.addEventListener('click', (event) => {
54          if (!wrapper.contains(event.target)) {
55            wrapper.classList.remove('is-open');
56            button.setAttribute('aria-expanded', 'false');
57            form.hidden = true;
58          }
59        });
60
61        document.addEventListener('keydown', (event) => {
62          if (event.key === 'Escape') {
63            wrapper.classList.remove('is-open');
64            button.setAttribute('aria-expanded', 'false');
65            form.hidden = true;
66          }
67        });
68      });
69    }
70  };
71
72  Drupal.behaviors.mobileMenu = {
73    attach(context) {
74      once('mobile-menu', '.region-header', context).forEach((header) => {
75        const branding = header.querySelector('#block-luxconnect-site-branding');
76        const mainMenu = header.querySelector('.menu--main');
77        const secondaryMenu = header.querySelector('.menu--secondary');
78        const search = header.querySelector('.header-search');
79        const isMobile = () => window.innerWidth <= 1199;
80
81        if (!branding || !mainMenu) {
82          return;
83        }
84
85        const toggle = document.createElement('button');
86        toggle.type = 'button';
87        toggle.className = 'mobile-menu-toggle';
88        toggle.setAttribute('aria-expanded', 'false');
89        toggle.setAttribute('aria-controls', 'header-mobile-panel');
90        toggle.setAttribute('aria-label', 'Ouvrir le menu');
91        toggle.innerHTML = '<span></span>';
92
93        const top = document.createElement('div');
94        top.classList.add('header__top');
95        top.append(branding, toggle);
96
97        const panel = document.createElement('div');
98        panel.classList.add('header__panel');
99        panel.id = 'header-mobile-panel';
100
101        panel.append(mainMenu);
102
103        if (secondaryMenu) {
104          panel.append(secondaryMenu);
105        }
106
107        if (search) {
108          panel.append(search);
109        }
110
111        header.append(top, panel);
112
113        toggle.addEventListener('click', () => {
114          if (!isMobile()) {
115            return;
116          }
117          const isOpen = header.classList.toggle('is-menu-open');
118
119          toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
120          toggle.setAttribute(
121            'aria-label',
122            isOpen ? 'Fermer le menu' : 'Ouvrir le menu'
123          );
124
125          document.body.classList.toggle('is-menu-open', isOpen);
126
127          if (!isOpen) {
128            mainMenu.classList.remove('has-active-submenu');
129
130            mainMenu.querySelectorAll('.is-submenu-active').forEach((item) => {
131              item.classList.remove('is-submenu-active');
132            });
133
134            mainMenu.querySelectorAll('.menu-item--expanded > button').forEach((button) => {
135              button.setAttribute('aria-expanded', 'false');
136            });
137          }
138        });
139
140        mainMenu.querySelectorAll('.menu-item--expanded').forEach((item) => {
141          const button = item.querySelector(':scope > button');
142          const submenu = item.querySelector(':scope > .menu');
143
144          if (!button || !submenu) {
145            return;
146          }
147
148          button.setAttribute('aria-expanded', 'false');
149
150          const backButton = document.createElement('button');
151          backButton.type = 'button';
152          backButton.className = 'submenu-back';
153          backButton.innerHTML = `
154            <span class="submenu-back__label">${button.textContent}</span>
155            <span class="submenu-back__icon" aria-hidden="true">←</span>
156          `;
157
158          submenu.prepend(backButton);
159
160          button.addEventListener('click', (event) => {
161            if (!isMobile()) {
162              return;
163            }
164
165            event.preventDefault();
166            event.stopPropagation();
167
168            mainMenu.classList.add('has-active-submenu');
169            item.classList.add('is-submenu-active');
170            button.setAttribute('aria-expanded', 'true');
171          });
172
173          submenu.addEventListener('click', (event) => {
174            event.stopPropagation();
175          });
176
177          backButton.addEventListener('click', (event) => {
178            if (!isMobile()) {
179              return;
180            }
181
182            event.preventDefault();
183            event.stopPropagation();
184
185            item.classList.remove('is-submenu-active');
186            mainMenu.classList.remove('has-active-submenu');
187            button.setAttribute('aria-expanded', 'false');
188          });
189        });
190
191
192      });
193    }
194  };
195
196  Drupal.behaviors.jobDetailsAnimatedChecks = {
197    attach(context) {
198      once(
199        'job-details-animated-checks',
200        [
201          '.node--type-job .field--name-field-details ul li',
202          '.node--type-data-center .field--name-field-caracteristiques li',
203        ].join(','),
204        context
205      ).forEach((li) => {
206        const wrapper = li.closest('[data-check-icon]');
207        const icon = wrapper ? wrapper.dataset.checkIcon : '';
208
209        li.insertAdjacentHTML('afterbegin', `
210          <span class="animated-check">
211            <svg class="animated-check__circle" viewBox="0 0 104 104" aria-hidden="true">
212              <path class="animated-check__circle-path" d="M74.31,7.24c-6.72-3.36-14.29-5.24-22.31-5.24C24.39,2,2,24.39,2,52s22.39,50,50,50,50-22.39,50-50c0-6.06-1.08-11.87-3.05-17.24"/>
213            </svg>
214            <img class="animated-check__mark" src="${icon}" alt="" loading="lazy">
215          </span>
216        `);
217      });
218    }
219  };
220
221  Drupal.behaviors.mainMenu = {
222    attach(context) {
223      const items = once('mainMenu', '.menu-item--expanded', context);
224
225      const isDesktop = window.innerWidth > 1199;
226
227      if (!isDesktop) {
228        return;
229      }
230
231      items.forEach((item) => {
232        const submenu = item.querySelector(':scope > .menu');
233
234        if (!submenu) {
235          return;
236        }
237
238        item.addEventListener('mouseenter', () => {
239          item.classList.add('is-open');
240        });
241
242        item.addEventListener('mouseleave', () => {
243          item.classList.remove('is-open');
244        });
245      });
246    }
247  };
248  // Drupal.behaviors.mainMenu = {
249  //   attach(context) {
250  //     const items = once('mainMenu', '.menu-item--expanded', context);
251
252  //     const isDesktop = window.matchMedia('(hover: hover)').matches;
253
254  //     items.forEach((item) => {
255  //       const link = item.querySelector('a');
256  //       const submenu = item.querySelector('.menu');
257
258  //       if (!link || !submenu) {
259  //         return;
260  //       }
261
262  //       // --- DESKTOP (hover)
263  //       if (isDesktop) {
264  //         item.addEventListener('mouseenter', () => {
265  //           item.classList.add('is-open');
266  //         });
267
268  //         item.addEventListener('mouseleave', () => {
269  //           item.classList.remove('is-open');
270  //         });
271  //       }
272
273  //       // --- MOBILE (click)
274  //       else {
275  //         link.addEventListener('click', (e) => {
276  //           // Empêche le lien si sous-menu
277  //           e.preventDefault();
278
279  //           const isOpen = item.classList.contains('is-open');
280
281  //           // ferme les autres
282  //           document.querySelectorAll('.menu-item--expanded.is-open')
283  //             .forEach((el) => el.classList.remove('is-open'));
284
285  //           // toggle
286  //           item.classList.toggle('is-open', !isOpen);
287  //         });
288  //       }
289  //     });
290  //   }
291  // };
292
293  Drupal.behaviors.statCounter = {
294    attach(context) {
295      once('stat-counter', '.stat', context).forEach((stat) => {
296        const original = stat.textContent.trim();
297        const target = parseInt(original.replace(/\D/g, ''), 10);
298        const suffix = original.replace(/[0-9]/g, '');
299
300        if (Number.isNaN(target)) {
301          return;
302        }
303
304        let isAnimating = false;
305
306        const animate = () => {
307
308          if (isAnimating) {
309            return;
310          }
311
312          isAnimating = true;
313
314          const duration = 1200;
315          const start = performance.now();
316
317          const update = (now) => {
318            const progress = Math.min((now - start) / duration, 1);
319            const value = Math.floor(progress * target);
320
321            stat.textContent = `${value}${suffix}`;
322
323            if (progress < 1) {
324              requestAnimationFrame(update);
325            } else {
326              stat.textContent = original;
327              isAnimating = false;
328            }
329          };
330
331          requestAnimationFrame(update);
332        };
333
334        const observer = new IntersectionObserver((entries) => {
335          entries.forEach((entry) => {
336
337            if (entry.isIntersecting) {
338              animate();
339            } else {
340              stat.textContent = `0${suffix}`;
341            }
342
343          });
344        }, {
345          threshold: 0.4,
346        });
347
348        observer.observe(stat);
349      });
350    }
351  };
352  // Drupal.behaviors.headerFixedOnScroll = {
353  //   attach(context) {
354  //     const body = once("header-fixed-on-scroll", "body", context);
355  //     if (!body.length) return;
356
357  //     const updateHeaderState = () => {
358  //       document.body.classList.toggle(
359  //         "has-header-fixed",
360  //         window.scrollY > 200,
361  //       );
362  //     };
363
364  //     const onScroll = rafThrottle(updateHeaderState);
365
366  //     // état initial au chargement
367  //     updateHeaderState();
368
369  //     window.addEventListener("scroll", onScroll, { passive: true });
370  //   },
371  // };
372
373  Drupal.behaviors.alertPopup = {
374    attach(context) {
375      once('alert-popup', '[data-alert-popup]', context).forEach((popup) => {
376        const closeButton = popup.querySelector('[data-alert-close]');
377
378        if (!closeButton) {
379          return;
380        }
381
382        const nid = popup.dataset.alertNid;
383        const key = `alert_popup_closed_${nid}`;
384
385        if (!sessionStorage.getItem(key)) {
386          requestAnimationFrame(() => {
387            popup.classList.add('is-visible');
388          });
389        }
390
391        closeButton.addEventListener('click', () => {
392          popup.classList.remove('is-visible');
393          sessionStorage.setItem(key, '1');
394        });
395      });
396    }
397  };
398
399})(Drupal, once);

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.