1/* Native dialog keeps keyboard focus inside the popup and the background inert. */ 2'use strict'; 3{ 4 const dialog = document.getElementById('ifi-promotions'); 5 if (dialog && typeof dialog.showModal === 'function') { 6 const preview = dialog.dataset.preview === '1'; 7 // Old cookies combined clicks and closes; this key records only explicit dismissal. 8 const cookie = `ifi_promotions_dismissed_${dialog.dataset.campaign}`; 9 let priorFocus; 10 let openTimer; 11 12 function rememberDismissal() { 13 if (preview) return; 14 const seconds = Number(dialog.dataset.days) * 86400; 15 const secure = location.protocol === 'https:' ? '; Secure' : ''; 16 document.cookie = `${cookie}=1; Max-Age=${seconds}; Path=/; SameSite=Lax${secure}`; 17 } 18 19 function isDismissed() { 20 return document.cookie.split(';').some(value => value.trim() === `${cookie}=1`); 21 } 22 23 function dismissPopup() { 24 rememberDismissal(); 25 document.documentElement.classList.remove('ifi-promotions-open'); 26 dialog.close(); 27 } 28 29 function openPopup() { 30 if ((!preview && isDismissed()) || document.querySelector('dialog[open]')) return; 31 // Do not interrupt someone already filling a form during the delay. 32 if (!preview && document.activeElement?.matches('input, textarea, select, [contenteditable="true"]')) return; 33 const template = dialog.querySelector('template'); 34 if (template) template.replaceWith(template.content.cloneNode(true)); 35 priorFocus = document.activeElement; 36 dialog.showModal(); 37 document.documentElement.classList.add('ifi-promotions-open'); 38 } 39 40 function start(immediate = false) { 41 window.clearTimeout(openTimer); 42 if (!preview && isDismissed()) { 43 if (dialog.open) dialog.close(); 44 return; 45 } 46 const historyReturn = performance.getEntriesByType('navigation')[0]?.type === 'back_forward'; 47 if (preview || immediate || historyReturn) openPopup(); 48 else openTimer = window.setTimeout(openPopup, Number(dialog.dataset.delay) * 1000); 49 } 50 51 function keepFocus(event) { 52 if (event.key !== 'Tab') return; 53 const controls = [...dialog.querySelectorAll('button:not([disabled]), a[href]')]; 54 const first = controls[0]; 55 const last = controls[controls.length - 1]; 56 // Native modal inertness does not prevent Tab from reaching browser chrome. 57 if (event.shiftKey && document.activeElement === first) { 58 event.preventDefault(); 59 last.focus(); 60 } else if (!event.shiftKey && document.activeElement === last) { 61 event.preventDefault(); 62 first.focus(); 63 } 64 } 65 66 dialog.addEventListener('keydown', keepFocus); 67 dialog.querySelector('.ifi-promotions-close').addEventListener('click', dismissPopup); 68 dialog.addEventListener('cancel', event => { 69 event.preventDefault(); 70 dismissPopup(); 71 }); 72 dialog.addEventListener('click', event => { 73 if (event.target !== dialog) return; 74 const rect = dialog.getBoundingClientRect(); 75 if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) { 76 dismissPopup(); 77 } 78 }); 79 dialog.addEventListener('close', () => { 80 if (dialog.open) return; 81 document.documentElement.classList.remove('ifi-promotions-open'); 82 if (priorFocus instanceof HTMLElement) priorFocus.focus({preventScroll: true}); 83 }); 84 window.addEventListener('pagehide', () => { 85 // Leave the modal intact for cached Back; leaving is not a dismissal. 86 window.clearTimeout(openTimer); 87 }); 88 window.addEventListener('pageshow', event => { 89 if (event.persisted) start(true); 90 }); 91 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => start(), {once: true}); 92 else start(); 93 } 94}
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.