Some checks are pending
build-gate / build (push) Waiting to run
Two halves of the Driver's directive: 1. FULL PAGE (BUG-PRAYER-WALK-PLANNER-MAP-DOES-NOT-FILL-THE-PAGE). The BUILT leaf was already full-viewport; the DEV staging page boxed the element in a centred 640px card -- measured 781x584 at y=206 on a 1440x900 viewport, exactly 'the map is not filling the full page'. The dev harness now mounts fixed inset:0 like the leaf (two-host rule). 2. AGENT-OPERABLE PRINT. PrayerWalkPlanner.print() used window.open, which is pop-up-blocked without a user gesture -- an agent calling the documented API got silence. The poster-wall's in-place hidden-iframe mechanism (e957cbe) graduates VERBATIM to @map/composables/printInPlace.js (library bar: 2+ consumers, parameterized) and the planner uses it; @page pins letter portrait (the839b0c4ruling). Verified end-to-end as an agent would: registry -> addPolygon -> selectPolygon -> print() => {opened:true,count:1}, hidden iframe print() fired once, portrait pinned, zero pop-ups.
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
|
|
* Driver'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 Driver: "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
|
|
}
|