1/** 2 * Continuous Image Carousel With Lightbox â modern lightbox 3 * Dependency-free replacement for the vendored FancyBox 1.3.6 fork. 4 * Opt-in via the "Lightbox Engine" setting; legacy engine is untouched. 5 */ 6(function () { 7 'use strict'; 8 9 var overlay, imgEl, captionEl, closeBtn, prevBtn, nextBtn; 10 var currentGroup = []; 11 var currentIndex = 0; 12 var lastFocused = null; 13 14 function buildOverlay() { 15 if (overlay) { 16 return; 17 } 18 overlay = document.createElement('div'); 19 overlay.className = 'cicwl-modern-lightbox-overlay'; 20 overlay.setAttribute('role', 'dialog'); 21 overlay.setAttribute('aria-modal', 'true'); 22 overlay.innerHTML = 23 '<button type="button" class="cicwl-modern-lightbox-close" aria-label="Close">×</button>' + 24 '<button type="button" class="cicwl-modern-lightbox-prev" aria-label="Previous image">❮</button>' + 25 '<div class="cicwl-modern-lightbox-stage">' + 26 '<img class="cicwl-modern-lightbox-img" alt="" />' + 27 '<div class="cicwl-modern-lightbox-caption"></div>' + 28 '</div>' + 29 '<button type="button" class="cicwl-modern-lightbox-next" aria-label="Next image">❯</button>'; 30 document.body.appendChild(overlay); 31 32 imgEl = overlay.querySelector('.cicwl-modern-lightbox-img'); 33 captionEl = overlay.querySelector('.cicwl-modern-lightbox-caption'); 34 closeBtn = overlay.querySelector('.cicwl-modern-lightbox-close'); 35 prevBtn = overlay.querySelector('.cicwl-modern-lightbox-prev'); 36 nextBtn = overlay.querySelector('.cicwl-modern-lightbox-next'); 37 38 closeBtn.addEventListener('click', close); 39 prevBtn.addEventListener('click', function () { show(currentIndex - 1); }); 40 nextBtn.addEventListener('click', function () { show(currentIndex + 1); }); 41 overlay.addEventListener('click', function (e) { 42 if (e.target === overlay) { 43 close(); 44 } 45 }); 46 47 var touchStartX = null; 48 overlay.addEventListener('touchstart', function (e) { 49 touchStartX = e.changedTouches[0].clientX; 50 }, { passive: true }); 51 overlay.addEventListener('touchend', function (e) { 52 if (touchStartX === null) { 53 return; 54 } 55 var dx = e.changedTouches[0].clientX - touchStartX; 56 if (Math.abs(dx) > 40) { 57 dx < 0 ? show(currentIndex + 1) : show(currentIndex - 1); 58 } 59 touchStartX = null; 60 }, { passive: true }); 61 } 62 63 function show(index) { 64 if (!currentGroup.length) { 65 return; 66 } 67 currentIndex = (index + currentGroup.length) % currentGroup.length; 68 var item = currentGroup[currentIndex]; 69 imgEl.src = item.src; 70 imgEl.alt = item.caption || ''; 71 captionEl.innerHTML = item.caption || ''; 72 captionEl.style.display = item.caption ? '' : 'none'; 73 var multi = currentGroup.length > 1; 74 prevBtn.style.display = multi ? '' : 'none'; 75 nextBtn.style.display = multi ? '' : 'none'; 76 } 77 78 function open(group, index) { 79 buildOverlay(); 80 currentGroup = group; 81 lastFocused = document.activeElement; 82 show(index); 83 overlay.classList.add('is-open'); 84 document.body.style.overflow = 'hidden'; 85 closeBtn.focus(); 86 document.addEventListener('keydown', onKeydown); 87 } 88 89 function close() { 90 if (!overlay) { 91 return; 92 } 93 overlay.classList.remove('is-open'); 94 document.body.style.overflow = ''; 95 document.removeEventListener('keydown', onKeydown); 96 if (lastFocused && lastFocused.focus) { 97 lastFocused.focus(); 98 } 99 } 100 101 function onKeydown(e) { 102 if (e.key === 'Escape') { 103 close(); 104 return; 105 } 106 if (e.key === 'ArrowLeft') { 107 show(currentIndex - 1); 108 return; 109 } 110 if (e.key === 'ArrowRight') { 111 show(currentIndex + 1); 112 return; 113 } 114 if (e.key === 'Tab') { 115 var focusable = [closeBtn, prevBtn, nextBtn];
116 var idx = focusable.indexOf(document.activeElement); 117 e.preventDefault(); 118 if (e.shiftKey) { 119 focusable[idx <= 0 ? focusable.length - 1 : idx - 1].focus(); 120 } else { 121 focusable[idx === focusable.length - 1 ? 0 : idx + 1].focus(); 122 } 123 } 124 } 125 126 function collectGroup(trigger) { 127 var container = (trigger.closest && trigger.closest('.cicwl-slider-wrap')) || document; 128 var rel = trigger.getAttribute('rel'); 129 var nodes = Array.prototype.slice.call(container.querySelectorAll('[data-cicwl-lightbox-src]')); 130 var seen = {}; 131 var group = []; 132 nodes.forEach(function (node) { 133 if (rel && node.getAttribute('rel') !== rel) { 134 return; 135 } 136 var src = node.getAttribute('data-cicwl-lightbox-src'); 137 // The modern carousel clones its item set for a seamless loop â 138 // de-duplicate so each image appears once in the lightbox group. 139 if (seen[src]) { 140 return; 141 } 142 seen[src] = true; 143 group.push({ 144 src: src, 145 caption: node.getAttribute('data-cicwl-lightbox-caption') || '' 146 }); 147 }); 148 return group; 149 } 150 151 document.addEventListener('click', function (e) { 152 var trigger = e.target.closest && e.target.closest('[data-cicwl-lightbox-src]'); 153 if (!trigger) { 154 return; 155 } 156 e.preventDefault(); 157 var group = collectGroup(trigger); 158 var src = trigger.getAttribute('data-cicwl-lightbox-src'); 159 var index = -1; 160 for (var i = 0; i < group.length; i++) { 161 if (group[i].src === src) { 162 index = i; 163 break; 164 } 165 } 166 open(group, index < 0 ? 0 : index); 167 }); 168})();
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.