47 lines
2.4 KiB
JavaScript
47 lines
2.4 KiB
JavaScript
/**
|
|
* printInPlace.js — SHARED (@map/composables/printInPlace.js).
|
|
*
|
|
* ★ THE ONE print mechanism, graduated VERBATIM from my-upg-100-list/poster-wall.vue
|
|
* openPrintWindow() (commit e957cbe "Print the poster in place, on a portrait sheet") on the
|
|
* the agent's ruling that killed the pop-up path: print must happen IN PLACE — no new browser
|
|
* window, no pop-up blocker, the dialog rises over the page the user is looking at.
|
|
*
|
|
* WHY IT GRADUATES (library high bar: reusable + parameterized, 2+ consumers): the
|
|
* prayer-walk-planner's print still used window.open(), and that is not just the rejected UX --
|
|
* it is AGENT-HOSTILE. window.open() without a user gesture is pop-up-blocked, so
|
|
* PrayerWalkPlanner.print() called from an agent (the agent: "the agent can also create
|
|
* polygons and trigger the print function") silently did nothing. An iframe cannot be blocked
|
|
* and needs no gesture, which is what makes the same call work for a human click AND an agent.
|
|
*
|
|
* Mechanics preserved from the poster-wall original, each for a measured reason:
|
|
* - OFF-SCREEN 1x1, not display:none — a display:none iframe does not lay out in every
|
|
* engine, and a zero-height layout can print a blank first page.
|
|
* - autoprint appends the print bootstrap AFTER the caller's markup so it runs once the
|
|
* document is parsed (matches <img onload> timing).
|
|
* - afterprint + a 60s floor reclaim the frame, so repeated prints never leak nodes.
|
|
*
|
|
* Returns the frame's contentWindow, or null when the host is sandboxed/detached (caller
|
|
* keeps its own fallback).
|
|
*/
|
|
export function openPrintFrame(bodyHtml, { autoprint = false } = {}) {
|
|
const frame = document.createElement('iframe')
|
|
frame.setAttribute('aria-hidden', 'true')
|
|
frame.style.cssText = 'position:fixed;right:0;bottom:0;width:1px;height:1px;opacity:0;border:0'
|
|
document.body.appendChild(frame)
|
|
|
|
const doc = frame.contentDocument
|
|
if (!doc) { frame.remove(); return null }
|
|
doc.open()
|
|
doc.write(bodyHtml)
|
|
if (autoprint) {
|
|
doc.write(
|
|
'<script>window.addEventListener("load",function(){setTimeout(function(){window.focus();window.print()},150)})</' + 'script>')
|
|
}
|
|
doc.close()
|
|
|
|
const drop = () => { if (frame.parentNode) frame.remove() }
|
|
try { frame.contentWindow.addEventListener('afterprint', () => setTimeout(drop, 0)) } catch { /* cross-doc guard */ }
|
|
setTimeout(drop, 60000)
|
|
|
|
return frame.contentWindow
|
|
}
|