/** * 썸네일 미리보기 확대 (클릭 고정 / 길게 누르기) */ (function initDeckPreviewHold() { /** @type {HTMLElement|null} */ let overlay = null; /** @type {number} */ let pressStartAt = 0; /** @type {boolean} */ let suppressClick = false; /** @type {number} */ const HOLD_THRESHOLD_MS = 280; /** * @returns {HTMLElement} */ function ensureOverlay() { if (!overlay) { overlay = document.createElement('div'); overlay.className = 'deck-preview-hold-overlay hidden'; overlay.setAttribute('role', 'dialog'); overlay.setAttribute('aria-modal', 'true'); overlay.innerHTML = '
'; overlay.addEventListener('click', (e) => { if (e.target === overlay) hide(); }); document.body.appendChild(overlay); } return overlay; } /** * @returns {boolean} */ function isVisible() { return Boolean(overlay && !overlay.classList.contains('hidden')); } /** * @param {string} src */ function show(src) { if (!src) return; const el = ensureOverlay(); const img = el.querySelector('img'); if (img) img.src = src; el.classList.remove('hidden'); } function hide() { if (overlay) overlay.classList.add('hidden'); pressStartAt = 0; suppressClick = false; } /** * @param {Event} e * @returns {HTMLElement|null} */ function findHoldTarget(e) { const target = e.target; if (!(target instanceof Element)) return null; return target.closest('[data-hold-preview]'); } /** * @param {HTMLElement} hold * @returns {string} */ function getPreviewSrc(hold) { return hold.dataset.previewSrc || ''; } function initGlobal() { if (document.documentElement.dataset.deckHoldInit === '1') return; document.documentElement.dataset.deckHoldInit = '1'; document.addEventListener( 'pointerdown', (e) => { const hold = findHoldTarget(e); if (!hold) return; const src = getPreviewSrc(hold); if (!src) return; pressStartAt = Date.now(); show(src); if (hold.setPointerCapture && e.pointerId !== undefined) { try { hold.setPointerCapture(e.pointerId); } catch { /* ignore */ } } }, true, ); document.addEventListener( 'pointerup', (e) => { if (!pressStartAt) return; const hold = findHoldTarget(e); if (hold?.releasePointerCapture && e.pointerId !== undefined) { try { hold.releasePointerCapture(e.pointerId); } catch { /* ignore */ } } const heldMs = Date.now() - pressStartAt; pressStartAt = 0; if (heldMs >= HOLD_THRESHOLD_MS) { hide(); suppressClick = true; window.setTimeout(() => { suppressClick = false; }, 0); } else { suppressClick = true; window.setTimeout(() => { suppressClick = false; }, 400); } }, true, ); document.addEventListener( 'pointercancel', () => { hide(); }, true, ); document.addEventListener('click', (e) => { if (suppressClick) return; const hold = findHoldTarget(e); if (hold) { const src = getPreviewSrc(hold); if (!src) return; e.preventDefault(); e.stopPropagation(); if (isVisible()) hide(); else show(src); return; } if (isVisible() && e.target === overlay) { hide(); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hide(); }); } /** * @param {HTMLElement} _container */ function bindHoldPreview(_container) { initGlobal(); } initGlobal(); window.UaDeckPreviewHold = { bindHoldPreview, hide }; })();