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.