import { isSuppressed, reachedHalf, suppressionDuration, suppressionKey } from '../utils/drawer-preference'; import { isFormACompletion } from '../utils/jotform-completion'; let disposeAll: (() => void) | undefined; // Memory preference also covers storage-denied browsing during this document lifetime. let memoryExpiry: string | null = null; function suppressed() { try { return isSuppressed(localStorage.getItem(suppressionKey)) || isSuppressed(memoryExpiry); } catch { return isSuppressed(memoryExpiry); } } function remember() { memoryExpiry = String(Date.now() + suppressionDuration); try { localStorage.setItem(suppressionKey, memoryExpiry); } catch { /* Functional preference only. */ } } export function initializeServiceInquiryDrawers() { disposeAll?.(); const disposers: (() => void)[] = []; document.querySelectorAll('[data-service-inquiry-drawer]').forEach((dialog) => { const controller = new AbortController(); const { signal } = controller; const authorized = dialog.dataset.consultationTest === 'true'; const closeButton = dialog.querySelector('[data-service-inquiry-close]'); const triggers = [...document.querySelectorAll('[data-service-inquiry-trigger]')] .filter((anchor) => anchor.dataset.serviceInquiryTrigger === dialog.dataset.service); let timer: number | undefined; let closeTimer: number | undefined; let completionTimer: number | undefined; const acknowledgment = dialog.querySelector('[data-service-inquiry-completed]'); let firstFrame = 0; let secondFrame = 0; let trigger: HTMLElement | null = null; let autoConsumed = false; let closing = false; const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)'); const stopAuto = () => { clearTimeout(timer); removeEventListener('scroll', checkScroll); removeEventListener('resize', checkScroll); observer?.disconnect(); }; const conflict = () => Boolean(document.querySelector('dialog[open]')) || document.activeElement?.matches('input:not([disabled]), textarea, select, [contenteditable="true"], iframe'); const open = (manual?: HTMLElement) => { if (dialog.open) return; if (!manual && (autoConsumed || suppressed())) { stopAuto(); return; } // One opportunity, never interrupt another modal or an edited field; CTA remains usable. if (!manual) autoConsumed = true; stopAuto(); if (conflict() && !manual) return; if (document.querySelector('dialog[open]')) return; autoConsumed = true; trigger = manual ?? (document.activeElement instanceof HTMLElement ? document.activeElement : null); if (dialog.dataset.submissionCompleted === 'true') { dialog.querySelector('[data-jotform-form-a]')?.dispatchEvent(new Event('aia:jotform-reset')); delete dialog.dataset.submissionCompleted; if (acknowledgment) acknowledgment.hidden = true; } dialog.showModal(); document.documentElement.classList.add('service-inquiry-open'); dialog.querySelector('[data-jotform-form-a]')?.dispatchEvent(new Event('aia:jotform-activate')); firstFrame = requestAnimationFrame(() => { secondFrame = requestAnimationFrame(() => { if (!dialog.open || closing) return; dialog.classList.add('is-open'); closeButton?.focus({ preventScroll: true }); }); }); }; const dismiss = () => { if (!dialog.open || closing) return; closing = true; clearTimeout(completionTimer); if (dialog.dataset.submissionCompleted !== 'true') remember(); stopAuto(); dialog.classList.remove('is-open'); const finish = () => { dialog.close(); closing = false; }; if (reduceMotion.matches) finish(); else closeTimer = window.setTimeout(finish, 500); }; function checkScroll() { if (reachedHalf(scrollY, document.documentElement.scrollHeight, innerHeight)) open(); } const observer = typeof ResizeObserver === 'function' ? new ResizeObserver(checkScroll) : undefined; closeButton?.addEventListener('click', dismiss, { signal }); dialog.addEventListener('cancel', (event) => { event.preventDefault(); dismiss(); }, { signal }); dialog.addEventListener('click', (event) => { if (event.target === dialog) dismiss(); }, { signal }); dialog.addEventListener('close', () => { clearTimeout(completionTimer); stopAuto(); dialog.classList.remove('is-open'); document.documentElement.classList.remove('service-inquiry-open'); if (trigger?.isConnected) trigger.focus({ preventScroll: true }); trigger = null; }, { signal }); if (authorized) { addEventListener('message', (event) => { const container = dialog.querySelector('[data-jotform-form-a]'); const frame = container?.querySelector('[data-jotform-iframe-frame]'); const active = dialog.open && !closing && dialog.dataset.submissionCompleted !== 'true' && container?.dataset.formContext === 'consultation' && container.dataset.jotformIframeActivated === 'true'; if (!isFormACompletion(event, frame?.contentWindow, active, frame?.dataset.jotformIframeUrl)) return; dialog.dataset.submissionCompleted = 'true'; remember(); if (acknowledgment) { acknowledgment.hidden = false; acknowledgment.scrollIntoView({ block: 'nearest', behavior: 'instant' }); } completionTimer = window.setTimeout(dismiss, 5000); }, { signal }); for (const anchor of triggers) { anchor.setAttribute('aria-haspopup', 'dialog'); anchor.setAttribute('aria-controls', dialog.id); anchor.addEventListener('click', (event) => { if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; if (document.querySelector('dialog[open]')) return; event.preventDefault(); open(anchor); }, { signal }); } if (!suppressed()) { timer = window.setTimeout(() => open(), 4000); addEventListener('scroll', checkScroll, { passive: true, signal }); addEventListener('resize', checkScroll, { passive: true, signal }); observer?.observe(document.documentElement); checkScroll(); } } else if (!suppressed()) { // Retain the existing inactive/mock demonstration timer outside test mode. timer = window.setTimeout(() => open(), 10000); } addEventListener('storage', (event) => { if (event.key === suppressionKey && suppressed()) stopAuto(); }, { signal }); disposers.push(() => { stopAuto(); clearTimeout(closeTimer); clearTimeout(completionTimer); cancelAnimationFrame(firstFrame); cancelAnimationFrame(secondFrame); controller.abort(); dialog.classList.remove('is-open'); if (dialog.open) dialog.close(); for (const anchor of triggers) { anchor.removeAttribute('aria-controls'); anchor.removeAttribute('aria-haspopup'); } document.documentElement.classList.remove('service-inquiry-open'); }); }); disposeAll = () => { disposers.forEach((dispose) => dispose()); disposeAll = undefined; }; } document.addEventListener('astro:before-swap', () => disposeAll?.()); document.addEventListener('astro:page-load', initializeServiceInquiryDrawers); window.addEventListener('pagehide', () => disposeAll?.()); window.addEventListener('pageshow', (event) => { if (event.persisted) initializeServiceInquiryDrawers(); });