// Print the given element using a hidden iframe, setting a custom default // filename by changing ONLY the iframe's document.title (not the host). export async function printViaIframe({ root, // HTMLElement to print (required) filename = 'report.pdf', // default download name extraCss = '', // optional extra CSS to inject } = {}) { if (!(root instanceof Element)) { throw new Error('printViaIframe: "root" must be an HTMLElement'); } // Clone the root so we can normalize asset URLs safely const clone = root.cloneNode(true); // Normalize to absolute URLs so they load inside the blob iframe clone.querySelectorAll('img').forEach(img => { const src = img.getAttribute('src'); if (!src) return; try { // Handle "/", "./", "../", and absolute http(s) const abs = new URL(src, window.location.origin).href; img.setAttribute('src', abs); } catch { /* ignore */ } }); // Gather existing so the iframe uses the same CSS bundle(s) const linksHtml = Array.from(document.querySelectorAll('link[rel="stylesheet"]')) .map(l => ``) .join('\n'); // Gather inline
${clone.outerHTML}
`; // Create the iframe pointed to a blob URL const blob = new Blob([html], { type: 'text/html' }); const url = URL.createObjectURL(blob); const iframe = document.createElement('iframe'); iframe.style.position = 'fixed'; iframe.style.right = '0'; iframe.style.bottom = '0'; iframe.style.width = '0'; iframe.style.height = '0'; iframe.style.border = '0'; iframe.src = url; document.body.appendChild(iframe); // Cleanup when the iframe signals it printed (or after a timeout) const onMsg = (ev) => { if (ev?.data?.__closePrintIframe) finish(); }; const timer = setTimeout(finish, 10000); window.addEventListener('message', onMsg); function finish() { try { window.removeEventListener('message', onMsg); clearTimeout(timer); URL.revokeObjectURL(url); iframe.remove(); } catch {} } }