📜
overlay_extra_copy.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
(function(){ const SWIPE_THRESHOLD = 50; const VERTICAL_LIMIT = 40; // --- Config --- const LS_KEY = 'AppOverlayConfig'; const defaultConfig = { showArrows: false, enableSwipe: false }; 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(); // --- Global menu items --- window.AppOverlayMenuItems = window.AppOverlayMenuItems || []; // --- Nested overlay stack --- let nestedOverlays = []; // --- DOM creation --- function createOverlayDOM(options = {}){ 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" style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#2d2d2d;border-bottom:1px solid #3a3a3a;"> <div style="display:flex;align-items:center;gap:10px;"> <div class="app-dialog__menu" data-menu style="font-size:20px;cursor:pointer;color:#e0e0e0;">&#9776;</div> <div class="app-dialog__title" id="appDialogTitle" style="color:#e0e0e0;font-size:16px;font-weight:500;">Title</div> </div> <div style="display:flex;align-items:center;gap:8px;flex-shrink:0;"> <button class="app-navbtn" data-prev type="button" style="display:none;background:#3a3a3a;color:#e0e0e0;border:none;padding:6px 12px;border-radius:4px;cursor:pointer;">←</button> <button class="app-navbtn" data-next type="button" style="display:none;background:#3a3a3a;color:#e0e0e0;border:none;padding:6px 12px;border-radius:4px;cursor:pointer;">→</button> <button class="app-dialog__close" type="button" aria-label="Close overlay" style="background:#3a3a3a;color:#e0e0e0;border:none;padding:6px 12px;border-radius:4px;cursor:pointer;">✕</button> </div> </header> <div class="app-dialog__toolbar" style="display:none;background:#252525;padding:8px 16px;border-bottom:1px solid #3a3a3a;gap:8px;flex-wrap:wrap;"></div> <div class="app-dialog__body" style="background:#1e1e1e;padding:20px;min-height:60vh;overflow:auto;color:#e0e0e0;"></div> </section> `; // --- Check for fullscreen mode --- const isFullscreen = options.fullscreen === true; // --- Determine alignment (defaults to center) - ignored if fullscreen const align = options.align || 'center'; // Options: 'center', 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right' let justifyContent = 'center'; let alignItems = 'center'; if (!isFullscreen) { if (align.includes('top')) justifyContent = 'flex-start'; else if (align.includes('bottom')) justifyContent = 'flex-end'; if (align.includes('left')) alignItems = 'flex-start'; else if (align.includes('right')) alignItems = 'flex-end'; } // --- Overlay Styles overlay.style.cssText = ` position: fixed; inset: 0; display: none; flex-direction: column; align-items: ${alignItems}; justify-content: ${justifyContent}; background: rgba(0,0,0,0.8); z-index: 2147483647; overflow-y: auto; padding: ${isFullscreen ? '0' : '20px'}; -webkit-overflow-scrolling: touch; `; // --- Dialog container adjustments const dialog = overlay.querySelector('.app-dialog'); // Get border color option const borderColor = options.borderColor || '#3a3a3a'; // Default dimensions (overridden by fullscreen) let width, maxWidth, height, maxHeight, minHeight, borderRadius; if (isFullscreen) { width = '100vw'; maxWidth = '100vw'; height = '100vh'; maxHeight = '100vh'; minHeight = '100vh'; borderRadius = '0'; } else { width = options.width || '94%'; maxWidth = options.maxWidth || '900px'; height = options.height || 'auto'; maxHeight = options.maxHeight || '90vh'; minHeight = options.minHeight || 'auto'; borderRadius = options.borderRadius || '8px'; } dialog.style.cssText = ` background: #1e1e1e; border: ${isFullscreen ? 'none' : `1px solid ${borderColor}`}; border-radius: ${borderRadius}; width: ${width}; max-width: ${maxWidth}; height: ${height}; max-height: ${maxHeight}; min-height: ${minHeight}; display: flex; flex-direction: column; box-shadow: ${isFullscreen ? 'none' : '0 10px 40px rgba(0,0,0,0.5)'}; overflow: hidden; position: relative; `; const bodyEl = overlay.querySelector('.app-dialog__body'); bodyEl.style.overflowY = 'auto'; bodyEl.style.flex = '1'; bodyEl.style.minHeight = '200px'; document.body.appendChild(overlay); return overlay; } const overlay = createOverlayDOM(); const titleEl = overlay.querySelector('#appDialogTitle'); const bodyEl = overlay.querySelector('.app-dialog__body'); const toolbarEl = overlay.querySelector('.app-dialog__toolbar'); const closeEl = overlay.querySelector('.app-dialog__close'); const prevEl = overlay.querySelector('[data-prev]'); const nextEl = overlay.querySelector('[data-next]'); const menuEl = overlay.querySelector('[data-menu]'); let slides = []; let slideEls = []; let current = 0; let opener = null; let toolbarButtons = []; // --- Menu toggle --- let menuOpen = false; let currentMenuStack = []; menuEl.addEventListener('click', (e) => { e.stopPropagation(); menuOpen = !menuOpen; if (menuOpen) showMenu(); else hideMenu(); }); function showMenu(items = window.AppOverlayMenuItems, parentDropdown = null) { const rect = menuEl.getBoundingClientRect(); let dropdown = document.createElement('div'); dropdown.className = 'app-menu-dropdown'; dropdown.style.position = 'fixed'; dropdown.style.background = '#fff'; dropdown.style.border = '1px solid #ddd'; dropdown.style.borderRadius = '4px'; dropdown.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)'; dropdown.style.minWidth = '180px'; dropdown.style.zIndex = '2147483647'; dropdown.style.transition = 'all 0.2s ease'; dropdown.style.overflow = 'hidden'; if (!parentDropdown) { dropdown.style.top = (rect.bottom + 4) + 'px'; dropdown.style.left = rect.left + 'px'; currentMenuStack = []; } else { const r = parentDropdown.getBoundingClientRect(); dropdown.style.top = r.top + 'px'; dropdown.style.left = (r.right - 2) + 'px'; items = [{ label: "◀ Back", type: "back" }, ...items]; } items.forEach(item => { const btn = document.createElement('button'); btn.textContent = item.label; btn.className = 'app-menu-item'; btn.style.display = 'flex'; btn.style.alignItems = 'center'; btn.style.justifyContent = 'space-between'; btn.style.width = '100%'; btn.style.padding = '8px 12px'; btn.style.border = 'none'; btn.style.background = 'transparent'; btn.style.cursor = 'pointer'; btn.style.fontSize = '14px'; btn.style.color = '#333'; btn.onmouseenter = () => btn.style.background = '#f0f0f0'; btn.onmouseleave = () => btn.style.background = 'transparent'; if (item.type === 'toggle') { const toggle = document.createElement('span'); toggle.textContent = item.state ? '✅' : '⬜'; btn.appendChild(toggle); btn.onclick = (e) => { e.stopPropagation(); item.state = !item.state; toggle.textContent = item.state ? '✅' : '⬜'; if (item.onToggle) item.onToggle(item.state); hideMenu(); }; } else if (item.submenu) { const arrow = document.createElement('span'); arrow.textContent = '▶'; btn.appendChild(arrow); btn.onclick = (e) => { e.stopPropagation(); currentMenuStack.push(dropdown); dropdown.style.display = 'none'; const sub = showMenu(item.submenu, dropdown); document.body.appendChild(sub); }; } else if (item.type === 'back') { btn.textContent = '◀ Back'; btn.onclick = (e) => { e.stopPropagation(); dropdown.remove(); if (currentMenuStack.length > 0) { const prevMenu = currentMenuStack.pop(); prevMenu.style.display = 'block'; } }; } else { btn.onclick = (e) => { e.stopPropagation(); if (item.action) item.action(); hideMenu(); }; } dropdown.appendChild(btn); }); if (!parentDropdown) { hideMenu(); document.body.appendChild(dropdown); setTimeout(() => { const closeOnOutsideClick = (e) => { const clickedOnMenu = e.target.closest('.app-menu-dropdown') || e.target === menuEl; if (!clickedOnMenu) { hideMenu(); document.removeEventListener('click', closeOnOutsideClick); } }; document.addEventListener('click', closeOnOutsideClick); }, 10); } dropdown.style.display = 'block'; return dropdown; } function hideMenu() { document.querySelectorAll('.app-menu-dropdown').forEach(m => m.remove()); currentMenuStack = []; menuOpen = false; } // --- Toolbar Buttons --- function renderToolbar(buttons = []) { toolbarButtons = Array.isArray(buttons) ? buttons : []; toolbarEl.innerHTML = ''; if (toolbarButtons.length === 0) { toolbarEl.style.display = 'none'; return; } toolbarEl.style.display = 'flex'; toolbarButtons.forEach(btn => { const button = document.createElement('button'); button.textContent = btn.label || 'Button'; button.type = 'button'; button.style.cssText = ` background: #3a3a3a; color: #e0e0e0; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.2s; `; button.onmouseenter = () => button.style.background = '#4a4a4a'; button.onmouseleave = () => button.style.background = '#3a3a3a'; button.onclick = (e) => { e.stopPropagation(); if (btn.overlay) { openNestedOverlay(btn.overlay); } else if (btn.action) { btn.action(); } }; toolbarEl.appendChild(button); }); } // --- Nested Overlay --- function openNestedOverlay(overlayConfig) { const nestedOverlay = createOverlayDOM(overlayConfig); nestedOverlay.classList.add('nested-overlay'); const baseZIndex = 2147483647; nestedOverlay.style.zIndex = baseZIndex + nestedOverlays.length + 1; const nestedTitleEl = nestedOverlay.querySelector('#appDialogTitle'); const nestedBodyEl = nestedOverlay.querySelector('.app-dialog__body'); const nestedCloseEl = nestedOverlay.querySelector('.app-dialog__close'); const nestedToolbarEl = nestedOverlay.querySelector('.app-dialog__toolbar'); const nestedMenuEl = nestedOverlay.querySelector('[data-menu]'); const nestedPrevEl = nestedOverlay.querySelector('[data-prev]'); const nestedNextEl = nestedOverlay.querySelector('[data-next]'); nestedPrevEl.style.display = 'none'; nestedNextEl.style.display = 'none'; nestedMenuEl.style.display = 'none'; nestedTitleEl.textContent = overlayConfig.title || 'Nested View'; if (overlayConfig.buttons) { renderNestedToolbar(nestedToolbarEl, overlayConfig.buttons, nestedOverlay); } nestedBodyEl.innerHTML = overlayConfig.html || ''; if (typeof overlayConfig.onRender === 'function') { requestAnimationFrame(() => overlayConfig.onRender(nestedBodyEl)); } nestedCloseEl.onclick = () => closeNestedOverlay(nestedOverlay); nestedOverlay.onclick = (e) => { if (e.target === nestedOverlay) { closeNestedOverlay(nestedOverlay); } }; nestedOverlays.push(nestedOverlay); nestedOverlay.classList.add('open'); nestedOverlay.setAttribute('aria-hidden', 'false'); nestedOverlay.style.display = 'flex'; nestedCloseEl.focus(); } function renderNestedToolbar(toolbarEl, buttons, overlayEl) { toolbarEl.innerHTML = ''; if (!buttons || buttons.length === 0) { toolbarEl.style.display = 'none'; return; } toolbarEl.style.display = 'flex'; buttons.forEach(btn => { const button = document.createElement('button'); button.textContent = btn.label || 'Button'; button.type = 'button'; button.style.cssText = ` background: #3a3a3a; color: #e0e0e0; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.2s; `; button.onmouseenter = () => button.style.background = '#4a4a4a'; button.onmouseleave = () => button.style.background = '#3a3a3a'; button.onclick = (e) => { e.stopPropagation(); if (btn.overlay) { openNestedOverlay(btn.overlay); } else if (btn.action) { btn.action(); } }; toolbarEl.appendChild(button); }); } function closeNestedOverlay(nestedOverlay) { nestedOverlay.classList.remove('open'); nestedOverlay.setAttribute('aria-hidden', 'true'); nestedOverlay.style.display = 'none'; const index = nestedOverlays.indexOf(nestedOverlay); if (index > -1) { nestedOverlays.splice(index, 1); } setTimeout(() => { if (nestedOverlay.parentElement) { nestedOverlay.parentElement.removeChild(nestedOverlay); } }, 300); if (nestedOverlays.length > 0) { const lastNested = nestedOverlays[nestedOverlays.length - 1]; const closeBtn = lastNested.querySelector('.app-dialog__close'); if (closeBtn) closeBtn.focus(); } else { closeEl.focus(); } } // --- 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); if (typeof s.onRender === 'function') requestAnimationFrame(() => s.onRender(el)); return el; }); } function update(){ titleEl.textContent = slides[current]?.title || ''; slideEls.forEach((el,i)=>el.classList.toggle('is-active', i===current)); const showArrows = config.showArrows && slides.length > 1; prevEl.style.display = showArrows ? '' : 'none'; nextEl.style.display = showArrows ? '' : 'none'; menuEl.style.display = window.AppOverlayMenuItems.length > 0 ? 'block' : 'none'; const currentSlide = slides[current]; renderToolbar(currentSlide?.buttons || []); } function mountOnTop(){ if (overlay.parentElement !== document.body || document.body.lastElementChild !== overlay) { document.body.appendChild(overlay); } } // --- Controls --- function open(items, startIndex=0, openerEl=null, options={}){ slides = Array.isArray(items) ? items : []; if (slides.length === 0) return; opener = openerEl || document.activeElement; // If options are provided, recreate the overlay with new size/position if (options && (options.width || options.height || options.align || options.maxWidth || options.maxHeight || options.minHeight || options.fullscreen || options.borderColor || options.borderRadius)) { // Remove old overlay if (overlay.parentElement) overlay.parentElement.removeChild(overlay); // Create new overlay with options const newOverlay = createOverlayDOM(options); // Update references Object.assign(window, { overlay: newOverlay, titleEl: newOverlay.querySelector('#appDialogTitle'), bodyEl: newOverlay.querySelector('.app-dialog__body'), toolbarEl: newOverlay.querySelector('.app-dialog__toolbar'), closeEl: newOverlay.querySelector('.app-dialog__close'), prevEl: newOverlay.querySelector('[data-prev]'), nextEl: newOverlay.querySelector('[data-next]'), menuEl: newOverlay.querySelector('[data-menu]') }); // Re-attach event listeners closeEl.addEventListener('click', close); nextEl.addEventListener('click', next); prevEl.addEventListener('click', prev); overlay.addEventListener('click', (e)=>{ if (e.target === overlay) close(); if (menuOpen && !menuEl.contains(e.target) && !e.target.closest('.app-menu-dropdown')) { hideMenu(); } }); menuEl.addEventListener('click', (e) => { e.stopPropagation(); menuOpen = !menuOpen; if (menuOpen) showMenu(); else hideMenu(); }); } renderSlides(); current = Math.max(0, Math.min(startIndex, slides.length - 1)); mountOnTop(); overlay.classList.add('open'); overlay.setAttribute('aria-hidden','false'); overlay.style.display = 'flex'; update(); closeEl.focus(); } function close(){ overlay.classList.remove('open'); overlay.setAttribute('aria-hidden','true'); overlay.style.display = 'none'; hideMenu(); nestedOverlays.forEach(nested => { nested.style.display = 'none'; if (nested.parentElement) { nested.parentElement.removeChild(nested); } }); nestedOverlays = []; 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(); if (menuOpen && !menuEl.contains(e.target) && !e.target.closest('.app-menu-dropdown')) { hideMenu(); } }); window.addEventListener('keydown', (e)=>{ if (!overlay.classList.contains('open') && nestedOverlays.length === 0) return; if (e.key === 'Escape') { e.preventDefault(); if (nestedOverlays.length > 0) { const topNested = nestedOverlays[nestedOverlays.length - 1]; closeNestedOverlay(topNested); } else if (menuOpen) { hideMenu(); } else { close(); } } if (nestedOverlays.length === 0) { if (e.key === 'ArrowRight') { e.preventDefault(); next(); } if (e.key === 'ArrowLeft') { e.preventDefault(); prev(); } } }); // --- Public API --- function configure(partial){ if (!partial || typeof partial !== 'object') return; config = Object.assign({}, config, partial); saveConfig(config); update(); } function getConfig(){ return Object.assign({}, config); } window.AppOverlay = { open, close, next, prev, configure, getConfig }; })();