143 lines
7.4 KiB
TypeScript
143 lines
7.4 KiB
TypeScript
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<HTMLDialogElement>('[data-service-inquiry-drawer]').forEach((dialog) => {
|
|
const controller = new AbortController();
|
|
const { signal } = controller;
|
|
const authorized = dialog.dataset.consultationTest === 'true';
|
|
const closeButton = dialog.querySelector<HTMLButtonElement>('[data-service-inquiry-close]');
|
|
const triggers = [...document.querySelectorAll<HTMLAnchorElement>('[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<HTMLElement>('[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<HTMLElement>('[data-jotform-form-a]');
|
|
const frame = container?.querySelector<HTMLIFrameElement>('[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(); });
|