📜
overlay_copy.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// /core/js/overlay.js (function(){ const SWIPE_THRESHOLD = 50; // px horizontal const VERTICAL_LIMIT = 40; // px vertical // --- Config (persisted) --- const LS_KEY = 'AppOverlayConfig'; const defaultConfig = { showArrows: true, enableSwipe: true }; function loadConfig(){ try { return Object.assign({}, defaultConfig, JSON.parse(localStorage.getItem(LS_KEY) || '{}')); } catch { return { ...defaultConfig }; } } function saveConfig(cfg){ try { localStorage.setItem(LS_KEY, JSON.stringify(cfg)); } catch {} } let config = loadConfig(); // --- DOM creation (once) --- function createOverlayDOM(){ const overlay = document.createElement('div'); overlay.className = 'app-overlay'; overlay.setAttribute('role','dialog'); overlay.setAttribute('aria-modal','true'); overlay.setAttribute('aria-hidden','true'); overlay.innerHTML = ` <section class="app-dialog" aria-labelledby="appDialogTitle"> <header class="app-dialog__header"> <div class="app-dialog__title" id="appDialogTitle">Title</div> <button class="app-dialog__close" type="button" aria-label="Close overlay">✕</button> </header> <div class="app-dialog__body"></div> <footer class="app-dialog__footer" data-footer> <button class="app-navbtn" data-prev type="button">← Prev</button> <div class="app-index" data-index>1 / 1</div> <button class="app-navbtn" data-next type="button">Next →</button> </footer> </section> `; document.body.appendChild(overlay); return overlay; } const overlay = createOverlayDOM(); const titleEl = overlay.querySelector('#appDialogTitle'); const bodyEl = overlay.querySelector('.app-dialog__body'); const closeEl = overlay.querySelector('.app-dialog__close'); const prevEl = overlay.querySelector('[data-prev]'); const nextEl = overlay.querySelector('[data-next]'); const indexEl = overlay.querySelector('[data-index]'); const footerEl= overlay.querySelector('[data-footer]'); let slides = []; let slideEls = []; let current = 0; let opener = null; // --- Render / Update --- function renderSlides(){ bodyEl.innerHTML = ''; slideEls = slides.map(s => { const el = document.createElement('article'); el.className = 'app-slide'; el.innerHTML = s.html || ''; bodyEl.appendChild(el); return el; }); } function applyNavVisibility(){ const single = slides.length <= 1; // Hide arrows when: (a) single item OR (b) user turned them off const showArrows = !single && !!config.showArrows; prevEl.style.display = showArrows ? '' : 'none'; nextEl.style.display = showArrows ? '' : 'none'; // Index can stay; if you want to hide on single too, uncomment next line: // indexEl.style.display = single ? 'none' : ''; // Keyboard left/right still work; if you want to disable when arrows hidden, // you can check config.showArrows in the key handler (left as-is for now). } function update(){ titleEl.textContent = slides[current]?.title || ''; slideEls.forEach((el,i)=>el.classList.toggle('is-active', i===current)); indexEl.textContent = `${current+1} / ${slides.length}`; applyNavVisibility(); } function mountOnTop(){ if (overlay.parentElement !== document.body || document.body.lastElementChild !== overlay) { document.body.appendChild(overlay); } } // --- Controls --- function open(items, startIndex=0, openerEl=null){ slides = Array.isArray(items) ? items : []; if (slides.length === 0) return; opener = openerEl || document.activeElement; renderSlides(); current = Math.max(0, Math.min(startIndex, slides.length - 1)); mountOnTop(); overlay.classList.add('open'); overlay.setAttribute('aria-hidden','false'); document.body.classList.add('body-lock'); // block background scroll/P2R update(); closeEl.focus(); } function close(){ overlay.classList.remove('open'); overlay.setAttribute('aria-hidden','true'); document.body.classList.remove('body-lock'); if (opener && typeof opener.focus === 'function') opener.focus(); } function next(){ if (slides.length > 1) { current = (current + 1) % slides.length; update(); } } function prev(){ if (slides.length > 1) { current = (current - 1 + slides.length) % slides.length; update(); } } // --- Events --- closeEl.addEventListener('click', close); nextEl.addEventListener('click', next); prevEl.addEventListener('click', prev); overlay.addEventListener('click', (e)=>{ if (e.target === overlay) close(); }); window.addEventListener('keydown', (e)=>{ if (!overlay.classList.contains('open')) return; if (e.key === 'Escape') { e.preventDefault(); close(); } if (e.key === 'ArrowRight') { e.preventDefault(); next(); } if (e.key === 'ArrowLeft') { e.preventDefault(); prev(); } }); // --- Swipe (obeys enableSwipe and slide count) --- let startX = 0, startY = 0, tracking = false; bodyEl.addEventListener('touchstart', (e)=>{ if (!overlay.classList.contains('open')) return; if (!config.enableSwipe || slides.length <= 1) return; if (e.touches.length !== 1) return; tracking = true; startX = e.touches[0].clientX; startY = e.touches[0].clientY; }, { passive: true }); bodyEl.addEventListener('touchend', (e)=>{ if (!tracking) return; tracking = false; if (!config.enableSwipe || slides.length <= 1) return; const endX = e.changedTouches[0].clientX; const endY = e.changedTouches[0].clientY; const dx = endX - startX; const dy = endY - startY; if (Math.abs(dx) >= SWIPE_THRESHOLD && Math.abs(dy) <= VERTICAL_LIMIT) { if (dx > 0) prev(); else next(); } }, { passive: true }); // --- Public config API --- function configure(partial){ if (!partial || typeof partial !== 'object') return; config = Object.assign({}, config, partial); saveConfig(config); // Re-apply current UI state without closing overlay applyNavVisibility(); } function getConfig(){ return Object.assign({}, config); } // --- Export API --- window.AppOverlay = { open, close, next, prev, configure, getConfig }; })();