1(() => { 2 const gap = 8; 3 const viewportMargin = 12; 4 5 const panel = document.createElement('div'); 6 panel.id = 'click-tooltip-panel'; 7 panel.className = 'click-tooltip-panel'; 8 panel.setAttribute('role', 'dialog'); 9 panel.hidden = true; 10 document.body.append(panel); 11 12 let activeTrigger = null; 13 let positionFrame = null; 14 15 function closeTooltip({ returnFocus = false } = {}) { 16 if (!activeTrigger) return; 17 18 const previousTrigger = activeTrigger; 19 previousTrigger.setAttribute('aria-expanded', 'false'); 20 previousTrigger.removeAttribute('aria-controls'); 21 22 activeTrigger = null; 23 panel.hidden = true; 24 panel.replaceChildren(); 25 26 if (returnFocus && previousTrigger.isConnected) { 27 previousTrigger.focus(); 28 } 29 } 30 31 function positionTooltip() { 32 if (!activeTrigger || panel.hidden) return; 33 34 const triggerRect = activeTrigger.getBoundingClientRect(); 35 const panelRect = panel.getBoundingClientRect(); 36 37 let left = triggerRect.left + (triggerRect.width - panelRect.width) / 2; 38 left = Math.max( 39 viewportMargin, 40 Math.min(left, window.innerWidth - panelRect.width - viewportMargin) 41 ); 42 43 let top = triggerRect.bottom + gap; 44 45 if ( 46 top + panelRect.height > window.innerHeight - viewportMargin && 47 triggerRect.top - panelRect.height - gap >= viewportMargin 48 ) { 49 top = triggerRect.top - panelRect.height - gap; 50 } 51 52 panel.style.left = `${Math.round(left)}px`; 53 panel.style.top = `${Math.round(top)}px`; 54 } 55 56 function schedulePosition() { 57 if (!activeTrigger || positionFrame !== null) return; 58 59 positionFrame = requestAnimationFrame(() => { 60 positionFrame = null; 61 positionTooltip(); 62 }); 63 } 64 65 function openTooltip(trigger) { 66 const container = trigger.closest('.click-tooltip'); 67 const template = container?.querySelector(':scope > [data-tooltip-content]'); 68 69 if (!(template instanceof HTMLTemplateElement)) return; 70 71 if (activeTrigger === trigger) { 72 closeTooltip(); 73 return; 74 } 75 76 closeTooltip(); 77 activeTrigger = trigger; 78 79 panel.replaceChildren(template.content.cloneNode(true)); 80 panel.hidden = false; 81 82 trigger.setAttribute('aria-expanded', 'true'); 83 trigger.setAttribute('aria-controls', panel.id); 84 85 interface.modals(); 86 87 positionTooltip(); 88 } 89 90 document.addEventListener('click', event => { 91 const trigger = event.target.closest('[data-tooltip-trigger]'); 92 93 if (trigger) { 94 event.preventDefault(); 95 openTooltip(trigger); 96 return; 97 } 98 99 if (!panel.contains(event.target)) { 100 closeTooltip(); 101 } 102 }); 103 104 document.addEventListener('keydown', event => { 105 if (event.key === 'Escape') { 106 closeTooltip({ returnFocus: true }); 107 } 108 }); 109 110 window.addEventListener('resize', schedulePosition); 111 window.addEventListener('scroll', schedulePosition, true); 112})();
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.