From 8cd4e7f4cad61aa98daf0f9a53473288d2224d5b Mon Sep 17 00:00:00 2001 From: Kieran Klukas Date: Thu, 5 Feb 2026 22:50:27 -0500 Subject: [PATCH] feat: move lightbox over to web components --- static/js/lightbox.js | 200 +++++++++++++++++++++------------ templates/shortcodes/img.html | 2 +- templates/shortcodes/imgs.html | 2 +- 3 files changed, 132 insertions(+), 72 deletions(-) diff --git a/static/js/lightbox.js b/static/js/lightbox.js index 5f109e8..100f458 100644 --- a/static/js/lightbox.js +++ b/static/js/lightbox.js @@ -1,92 +1,152 @@ -let currentLightboxImages = []; -let currentLightboxIndex = 0; +class ImageLightbox extends HTMLElement { + constructor() { + super(); + this.currentImages = []; + this.currentIndex = 0; + this.handleImageClick = this.handleImageClick.bind(this); + this.handleKeyPress = this.handleKeyPress.bind(this); + } -function openLightbox(src) { - currentLightboxImages = [src]; - currentLightboxIndex = 0; - showLightbox(); -} + connectedCallback() { + this.render(); + this.setupEventListeners(); + } -function openLightboxGroup(element) { - const group = element.closest('.img-group'); - const images = Array.from(group.querySelectorAll('img')).map(img => img.src); - const clickedImg = element.querySelector('img'); - - currentLightboxImages = images; - currentLightboxIndex = images.indexOf(clickedImg.src); - showLightbox(); -} + disconnectedCallback() { + this.cleanup(); + } -function showLightbox() { - let lightbox = document.getElementById('lightbox'); - - if (!lightbox) { - lightbox = document.createElement('div'); - lightbox.id = 'lightbox'; - lightbox.innerHTML = ` + render() { + this.id = 'lightbox'; + this.style.display = 'none'; + this.innerHTML = ` `; - document.body.appendChild(lightbox); - - lightbox.addEventListener('click', (e) => { - if (e.target === lightbox) closeLightbox(); + } + + setupEventListeners() { + // Delegate clicks on images with data-lightbox attribute + document.addEventListener('click', this.handleImageClick); + + // Lightbox controls + this.querySelector('.lightbox-close').addEventListener('click', () => this.close()); + this.querySelector('.lightbox-prev').addEventListener('click', () => this.prev()); + this.querySelector('.lightbox-next').addEventListener('click', () => this.next()); + + // Click backdrop to close + this.addEventListener('click', (e) => { + if (e.target === this) this.close(); }); - - document.addEventListener('keydown', handleKeyPress); + + // Keyboard navigation + document.addEventListener('keydown', this.handleKeyPress); } - - updateLightboxImage(); - lightbox.style.display = 'flex'; - document.body.style.overflow = 'hidden'; -} -function closeLightbox() { - const lightbox = document.getElementById('lightbox'); - if (lightbox) { - lightbox.style.display = 'none'; + handleImageClick(e) { + // Check if clicked element or its parent has data-lightbox + const target = e.target.closest('[data-lightbox]'); + if (!target) return; + + e.preventDefault(); + + const group = target.dataset.lightboxGroup; + + if (group) { + // Find all images in the same group + const groupElements = Array.from( + document.querySelectorAll(`[data-lightbox-group="${group}"]`) + ); + + // Extract image sources + this.currentImages = groupElements.map(el => { + const img = el.tagName === 'IMG' ? el : el.querySelector('img'); + return img ? img.src : null; + }).filter(Boolean); + + // Find the index of the clicked image + this.currentIndex = groupElements.indexOf(target); + } else { + // Single image + const img = target.tagName === 'IMG' ? target : target.querySelector('img'); + this.currentImages = img ? [img.src] : []; + this.currentIndex = 0; + } + + if (this.currentImages.length > 0) { + this.open(); + } + } + + handleKeyPress(e) { + if (this.style.display !== 'flex') return; + + if (e.key === 'Escape') { + this.close(); + } else if (e.key === 'ArrowLeft') { + this.prev(); + } else if (e.key === 'ArrowRight') { + this.next(); + } + } + + open() { + this.style.display = 'flex'; + const controls = this.querySelector('.lightbox-controls'); + if (this.currentImages.length > 1) { + controls.style.display = 'flex'; + } else { + controls.style.display = 'none'; + } + document.body.style.overflow = 'hidden'; + this.updateImage(); + } + + close() { + this.style.display = 'none'; document.body.style.overflow = ''; } -} -function updateLightboxImage() { - const img = document.getElementById('lightbox-img'); - const controls = document.querySelector('.lightbox-controls'); - - img.src = currentLightboxImages[currentLightboxIndex]; - - if (currentLightboxImages.length === 1) { - controls.style.display = 'none'; - } else { - controls.style.display = 'flex'; + updateImage() { + const img = this.querySelector('#lightbox-img'); + img.src = this.currentImages[this.currentIndex]; } -} -function prevImage() { - currentLightboxIndex = (currentLightboxIndex - 1 + currentLightboxImages.length) % currentLightboxImages.length; - updateLightboxImage(); + prev() { + this.currentIndex = (this.currentIndex - 1 + this.currentImages.length) % this.currentImages.length; + this.updateImage(); + } + + next() { + this.currentIndex = (this.currentIndex + 1) % this.currentImages.length; + this.updateImage(); + } + + cleanup() { + document.removeEventListener('click', this.handleImageClick); + document.removeEventListener('keydown', this.handleKeyPress); + document.body.style.overflow = ''; + } } -function nextImage() { - currentLightboxIndex = (currentLightboxIndex + 1) % currentLightboxImages.length; - updateLightboxImage(); +customElements.define('image-lightbox', ImageLightbox); + +// Auto-initialize: add lightbox to page if not already present +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initLightbox); +} else { + initLightbox(); } -function handleKeyPress(e) { - const lightbox = document.getElementById('lightbox'); - if (!lightbox || lightbox.style.display !== 'flex') return; - - if (e.key === 'Escape') { - closeLightbox(); - } else if (e.key === 'ArrowLeft') { - prevImage(); - } else if (e.key === 'ArrowRight') { - nextImage(); +function initLightbox() { + if (!document.querySelector('image-lightbox')) { + const lightbox = document.createElement('image-lightbox'); + document.body.appendChild(lightbox); } } diff --git a/templates/shortcodes/img.html b/templates/shortcodes/img.html index 5d34cbb..e4cfd2c 100644 --- a/templates/shortcodes/img.html +++ b/templates/shortcodes/img.html @@ -1,5 +1,5 @@
-
+
{% if caption %} diff --git a/templates/shortcodes/imgs.html b/templates/shortcodes/imgs.html index 24c04d7..97a718f 100644 --- a/templates/shortcodes/imgs.html +++ b/templates/shortcodes/imgs.html @@ -3,7 +3,7 @@ {% set images = id | split(pat=",") %} {% set alts = alt | default(value="") | split(pat=",") %} {% for image in images %} -
+
{% endfor %} -- 2.51.2