1$(document).on('popupOpen popupClosed', function(e, array) { 2 let temp_popup_id; 3 4 if (e.type === 'popupOpen') { 5 temp_popup_id = (array.popupId === 'tooltip') 6 ? '#page_PopupContainer_tooltip' 7 : '.shop_popup_' + array.popupId; 8 9 manageFocus(temp_popup_id, { 10 trigger: array.trigger, 11 ajaxContext: array.ajax 12 }); 13 } else if (e.type === 'popupClosed') { 14 temp_popup_id = (array.id === 'tooltip') 15 ? '#page_PopupContainer_tooltip' 16 : '.shop_popup_' + array.id; 17 18 restoreFocus(temp_popup_id); 19 } 20}); 21 22$(document).on('onLoad onClose', function(e) { 23 const { target, type } = e; 24 25 if (!isValidTarget(target)) return; 26 27 const overlayId = `#${target.id}`; 28 29 if (type === 'onLoad') { 30 handleOverlayLoad(overlayId); 31 } else if (type === 'onClose') { 32 restoreFocus(overlayId); 33 } 34 35 function isValidTarget(target) { 36 return target.id && target.getAttribute('data-inited') !== '1'; 37 } 38 39 function handleOverlayLoad(overlayId) { 40 if (overlayId === '#overlay_login_outer') { 41 const trigger = getLoginTrigger(); 42 manageFocus(overlayId, { trigger }); 43 } else { 44 manageFocus(overlayId); 45 } 46 } 47 48 function getLoginTrigger() { 49 return document.querySelector('.js-btn-next') || 50 document.querySelector('.orderflow-main-btn'); 51 } 52}); 53 54function manageFocus(id, options = {}) { 55 const { trigger, ajaxContext = null, focusTo = null } = options; 56 const $overlay = $(id); 57 const $title = $overlay.find('.overlay_title'); 58 const $text = $overlay.find('.overlay_text'); 59 60 $overlay.data('trigger', trigger); 61 62 setupAccessibility(); 63 setupFocusManagement(); 64 setupFocusTrap(); 65 66 function setupAccessibility() { 67 const titleId = `${id.substring(1)}_title`; 68 const descId = `${id.substring(1)}_desc`; 69 70 if ($title.length > 0) { 71 const finalId = $title.attr('id') || titleId; 72 $title.attr('id', finalId); 73 $overlay.attr('aria-labelledby', finalId); 74 } 75 76 if ($text.length > 0) { 77 $overlay.attr('aria-describedby', descId); 78 $text.attr('id', descId); 79 } 80 81 $overlay.attr({ 82 role: 'dialog', 83 tabindex: '-1' 84 }); 85 86 if (typeof announceToScreenReader === 'function' && !focusTo) { 87 announceToScreenReader('modalOpen'); 88 } 89 } 90 91 function setupFocusManagement() { 92 let $focusableElements, $firstFocusable, $lastFocusable; 93 94 const updateFocusableElements = () => { 95 const selector = [ 96 'input:not([disabled])', 97 'a[href]:not([href="#"]):not(.close)', 98 'button', 99 '[tabindex]:not([tabindex="-1"])', 100 'a.close:not(:has(+ button.btn-close))' 101 ].join(', '); 102 103 $focusableElements = $overlay.find(selector).filter(':visible'); 104 $firstFocusable = $focusableElements.first(); 105 $lastFocusable = $focusableElements.last(); 106 }; 107 108 const setInitialFocus = () => { 109 updateFocusableElements(); 110 111 if (focusTo) { 112 const $focusEl = $(focusTo); 113 if ($focusEl.length) { 114 $focusEl.focus(); 115 return; 116 } 117 } 118 119 setTimeout(() => $overlay.focus(), 50); 120 }; 121 122 if (ajaxContext) { 123 const observer = new MutationObserver((mutations, obs) => { 124 const focusableCount = $overlay.find('input, button, a[href]:not([href="#"])').length; 125 const hasContent = $overlay.find('.overlay-content-placeholder').children().length > 0; 126 127 if (focusableCount > 1 || hasContent) { 128 setInitialFocus(); 129 obs.disconnect(); 130 } 131 }); 132 133 observer.observe($overlay[0], { childList: true, subtree: true }); 134 } else { 135 setInitialFocus(); 136 } 137 138 // Expose updateFocusableElements for the focus trap 139 $overlay.data('updateFocusableElements', updateFocusableElements); 140 $overlay.data('getFocusableElements', () =>
140 ({ $firstFocusable, $lastFocusable })); 141 } 142 143 function setupFocusTrap() { 144 $overlay.on('keydown', function(event) { 145 if (event.key !== 'Tab') return; 146 147 const updateFocusableElements = $overlay.data('updateFocusableElements'); 148 const getFocusableElements = $overlay.data('getFocusableElements'); 149 150 updateFocusableElements(); 151 const { $firstFocusable, $lastFocusable } = getFocusableElements(); 152 153 const isFirstOrOverlay = document.activeElement === $firstFocusable?.[0] || 154 document.activeElement === $overlay[0]; 155 const isLast = document.activeElement === $lastFocusable?.[0]; 156 157 if (event.shiftKey && isFirstOrOverlay) { 158 event.preventDefault(); 159 $lastFocusable.focus(); 160 } else if (!event.shiftKey && isLast) { 161 event.preventDefault(); 162 $firstFocusable.focus(); 163 } 164 }); 165 } 166} 167 168function restoreFocus(id) { 169 const $overlay = $(id); 170 const trigger = $overlay.data('trigger'); 171 172 announceModalClose(); 173 handleFocusRestoration(); 174 175 function announceModalClose() { 176 const isCookieAlert = id.includes('overlay_cookie_alert'); 177 if (typeof announceToScreenReader === 'function' && !isCookieAlert) { 178 announceToScreenReader('modalClose'); 179 } 180 } 181 182 function setTriggerFocus() { 183 if (trigger === document.body) { 184 $(document.activeElement).blur(); 185 $('body').attr('tabindex', '-1').focus().removeAttr('tabindex'); 186 } else { 187 const element = trigger?.[0] ?? trigger; 188 element?.focus?.(); 189 $overlay.removeData('trigger'); 190 } 191 } 192 193 function handleErrorOverlayFocus() { 194 const isOrderCouponError = $overlay[0].classList.contains('overlay_order_coupon_error_no'); 195 196 if (isOrderCouponError) { 197 $('#coupon_id').focus(); 198 return true; 199 } 200 201 if (add_focus_to_first_invalid('.js-validation')) { 202 return true; 203 } 204 205 return false; 206 } 207 208 function handleFocusRestoration() { 209 const isErrorOverlay = $overlay[0].classList.contains('overlay_error'); 210 211 if (isErrorOverlay && handleErrorOverlayFocus()) { 212 return; 213 } 214 215 setTimeout(setTriggerFocus, 100); 216 } 217}
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.