/** * DOM ready and mutation observer utilities */ export type InitCallback = () => void; let initScheduled = false; const scheduledCallbacks: Set = new Set(); /** * Schedule initialization callbacks to run on next animation frame * Prevents redundant DOM scans during rapid updates */ export const scheduleInit = (callback: InitCallback) => { scheduledCallbacks.add(callback); if (initScheduled) return; initScheduled = true; // rAF keeps this cheap during rapid DOM updates (page builders). requestAnimationFrame(() => { initScheduled = false; scheduledCallbacks.forEach((cb) => cb()); // Don't clear callbacks - they should be reusable }); }; /** * Run callback when DOM is ready, or immediately if already loaded */ export const onDomReady = (callback: InitCallback) => { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", callback); } else { callback(); } }; /** * Set up mutation observer to detect new swatch mount points and variation forms */ export const observeDomChanges = (callbacks: { onSwatchMountAdded?: InitCallback; onVariationFormAdded?: InitCallback; }) => { if (typeof MutationObserver === "undefined") return; const root = document.body || document.documentElement; if (!root) return; const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of Array.from(mutation.addedNodes)) { if (!(node instanceof HTMLElement)) continue; if ( node.matches(".b3-wvs-swatches-mount") || node.querySelector(".b3-wvs-swatches-mount") ) { callbacks.onSwatchMountAdded?.(); // Builders may also replace the add-to-cart form. callbacks.onVariationFormAdded?.(); return; } if ( node.matches("form.variations_form") || node.querySelector("form.variations_form") ) { callbacks.onVariationFormAdded?.(); return; } } } }); observer.observe(root, { childList: true, subtree: true }); };