UNPKG

vue-to-print

Version:

A Vue 3 component to print the content of an element or a component.

634 lines (632 loc) 19.8 kB
var ee = Object.defineProperty, te = Object.defineProperties; var ne = Object.getOwnPropertyDescriptors; var W = Object.getOwnPropertySymbols; var oe = Object.prototype.hasOwnProperty, re = Object.prototype.propertyIsEnumerable; var $ = (e, t, n) => t in e ? ee(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n, M = (e, t) => { for (var n in t || (t = {})) oe.call(t, n) && $(e, n, t[n]); if (W) for (var n of W(t)) re.call(t, n) && $(e, n, t[n]); return e; }, j = (e, t) => te(e, ne(t)); var A = (e, t, n) => new Promise((r, i) => { var h = (c) => { try { d(n.next(c)); } catch (l) { i(l); } }, m = (c) => { try { d(n.throw(c)); } catch (l) { i(l); } }, d = (c) => c.done ? r(c.value) : Promise.resolve(c.value).then(h, m); d((n = n.apply(e, t)).next()); }); import { toValue as P, defineComponent as se, toRefs as ie, cloneVNode as le } from "vue"; const ce = () => ({ /** * Class to pass to the print window body */ bodyClass: { type: String, default: "" }, /** * Content to be printed */ content: { type: Object, required: !0 }, /** * Copy styles over into print window. default: true */ copyStyles: { type: Boolean, default: !0 }, /** * Set the title for printing when saving as a file. * Will result in the calling page's `<title>` being temporarily changed while printing. */ documentTitle: { type: String, default: "" }, /** * Pre-load these fonts to ensure availability when printing */ fonts: { type: Array, default: () => [] }, /** * Callback function to trigger after print */ onAfterPrint: { type: Function, default: null }, /** * Callback function to trigger before page content is retrieved for printing */ onBeforeGetContent: { type: Function, default: null }, /** * Callback function to trigger before print */ onBeforePrint: { type: Function, default: null }, /** * Callback function to listen for printing errors */ onPrintError: { type: Function, default: null }, /** * Override default print window styling */ pageStyle: { type: [String, Function], default: ` @page { /* Remove browser default header (title) and footer (url) */ margin: 0; } @media print { body { /* Tell browsers to print background colors */ -webkit-print-color-adjust: exact; /* Chrome/Safari/Edge/Opera */ color-adjust: exact; /* Firefox */ } } ` }, /** * Override the default `window.print` method that is used for printing */ print: { type: Function, default: null }, /** * Remove the iframe after printing. * NOTE: `onAfterPrint` will run before the iframe is removed */ removeAfterPrint: { type: Boolean, default: !1 }, /** * Suppress error messages */ suppressErrors: { type: Boolean, default: !1 }, /** * Set the nonce attribute for whitelisting script and style -elements for CSP (content security policy) */ nonce: { type: String, default: "" }, contextSlots: { type: Object } }); function O(e) { return !!e.shadowRoot; } let H = !1; function ae() { if (H) return; class e extends HTMLElement { constructor() { super(), this.attachShadow({ mode: "open" }); } } customElements.define("vue-to-print-shadow-dom", e), H = !0; } const ue = ` class VueToPrintShadowDom extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } } customElements.define('vue-to-print-shadow-dom', VueToPrintShadowDom); `; function de(e) { const t = e.createElement("script"); t.setAttribute("type", "text/javascript"), t.setAttribute("vue-to-print-custom-script", "registry-shadow-dom"), t.innerHTML = ue, e.body.appendChild(t); } const he = ` function retrieveStyleSheets(styleSheetMap) { styleSheetMap.forEach((styleSheetStrings, tagName) => { const styleSheets = []; for (let i = styleSheetStrings.length; i--;) { const styleSheet = new CSSStyleSheet(); styleSheet.replaceSync(styleSheetStrings[i]); styleSheets.push(styleSheet); } const elements = document.querySelectorAll('vue-to-print-shadow-dom[original-tag-name=' + tagName + ']'); for (let i = elements.length; i--;) { const element = elements[i]; element.shadowRoot.adoptedStyleSheets = styleSheets; } }); } `; function fe(e) { const t = e.createElement("script"); t.setAttribute("type", "text/javascript"), t.setAttribute("vue-to-print-custom-script", "registry-retrieve-style-sheets-func"), t.innerHTML = he, e.body.appendChild(t); } const D = /* @__PURE__ */ new Map(); function I(e) { ae(); const t = e.nodeName.toLowerCase(), r = e.shadowRoot.adoptedStyleSheets, i = document.createElement("vue-to-print-shadow-dom"); i.setAttribute("original-tag-name", t), D.has(t) || D.set(t, /* @__PURE__ */ new Set()); const h = D.get(t); for (let c = r.length; c--; ) h.add(r[c]); const m = i.attributes, d = e.attributes; for (let c = d.length; c--; ) m.setNamedItem(d[c].cloneNode()); return i; } function me() { const e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map(), n = Array.from(D.keys()); for (let r = n.length; r--; ) { const i = [], h = n[r], m = Array.from(D.get(h)); for (let d = m.length; d--; ) { const c = m[d]; if (!t.has(c)) { let l = ""; const o = Array.from(c.cssRules); for (let s = o.length; s--; ) l += o[s].cssText; t.set(c, l); } i.push(t.get(c)); } e.set(h, i); } return e; } function pe(e) { const t = e.contentWindow || null; if (!t) throw new Error("Cannot access print window"); const n = t.document; if (!n) throw new Error("Cannot access print document"); de(n), fe(n); const r = me(); t.retrieveStyleSheets(r); } function _(e) { return !!customElements.get(e.nodeName.toLowerCase()); } let G = !1; function ye() { if (G) return; class e extends HTMLElement { constructor() { super(); } } customElements.define("vue-to-print-custom-element", e), G = !0; } function q(e) { ye(); const t = e.nodeName.toLowerCase(), n = document.createElement("vue-to-print-custom-element"); n.setAttribute("original-tag-name", t); const r = n.attributes, i = e.attributes; for (let h = i.length; h--; ) r.setNamedItem(i[h].cloneNode()); return n; } function U(e) { return A(this, null, function* () { e.getAttribute("src") && (e.complete || (yield new Promise((t, n) => { e.addEventListener("load", t, { once: !0 }), e.addEventListener("error", (r) => n(r.error), { once: !0 }); }))); }); } function ge(e) { return A(this, null, function* () { e.readyState >= 2 || (yield new Promise((t, n) => { e.addEventListener("loadeddata", t, { once: !0 }), e.addEventListener("error", (r) => n(r.error), { once: !0 }), e.addEventListener("stalled", () => n(new Error("Loading video stalled, skipping")), { once: !0 }); })); }); } function we(e) { const t = e.cloneNode(), n = t.getContext("2d"); return n && n.drawImage(e, 0, 0), t; } function be(e, t) { const n = e.cloneNode(); return t.push(U(n)), n; } function Se(e, t) { const n = e.cloneNode(); n.preload = "auto"; const r = n.getAttribute("poster"); if (r) { const i = new Image(); i.src = r, t.push(U(i)); } else t.push(ge(n)); return n; } function Ee(e) { const t = e.cloneNode(); switch (e.type) { case "checkbox": case "radio": t.checked = e.checked; break; default: t.value = e.value; break; } return t; } function ve(e) { const t = e.cloneNode(); return t.value = e.value, t; } function Pe(e) { const t = e.cloneNode(); return t.selected = e.selected, t; } const Y = /* @__PURE__ */ new Map([ ["canvas", we], ["img", be], ["video", Se], ["input", Ee], ["select", ve], ["option", Pe] ]); function Ce(e) { return e.cloneNode(); } function Te(e, t = []) { const n = e.nodeName.toLowerCase(); return (Y.has(n) ? Y.get(n) : Ce)(e, t); } function Ne(e) { var n; if (e.nodeName.toLowerCase() === "slot") { const r = e.assignedNodes(); return r.length > 0 ? r : Array.from(e.childNodes); } else return Array.from(((n = e.shadowRoot) != null ? n : e).childNodes); } function Ae(e) { return A(this, null, function* () { const t = /* @__PURE__ */ new Map(), n = []; let r; O(e) ? r = I(e) : _(e) ? r = q(e) : r = e.cloneNode(), t.set(e, r); const i = [e]; for (; i.length; ) { const h = i.shift(), m = Ne(h); if (m.length <= 0) continue; const d = t.get(h), c = O(d) ? d.shadowRoot : d; for (let l = 0; l < m.length; l++) { const o = m[l]; let s; O(o) ? s = I(o) : _(o) ? s = q(o) : s = Te(o, n), t.set(o, s), i.push(o), c.appendChild(s); } } return { node: r, result: yield Promise.allSettled(n) }; }); } const ke = { copyStyles: !0, pageStyle: ` @page { /* Remove browser default header (title) and footer (url) */ margin: 0; } @media print { body { /* Tell browsers to print background colors */ -webkit-print-color-adjust: exact; /* Chrome/Safari/Edge/Opera */ color-adjust: exact; /* Firefox */ } } `, removeAfterPrint: !1, suppressErrors: !1 }; function xe(e) { e = M(M({}, ke), e); let t = 0, n = [], r = []; const i = (o) => { const s = e.onAfterPrint, f = e.onPrintError, p = e.print, b = P(e.documentTitle); setTimeout(() => { var S, C; if (o.contentWindow) if (o.contentWindow.focus(), p) Promise.resolve(p(o)).then(() => s == null ? void 0 : s()).then(() => c()).catch((a) => { f ? f("print", a) : l(["An error was thrown by the specified `print` function"]); }); else { if (o.contentWindow.print) { const a = (C = (S = o.contentDocument) == null ? void 0 : S.title) != null ? C : "", R = o.ownerDocument.title; b && (o.ownerDocument.title = b, o.contentDocument && (o.contentDocument.title = b)), o.contentWindow.print(), b && (o.ownerDocument.title = R, o.contentDocument && (o.contentDocument.title = a)); } else l([ "Printing for this browser is not currently possible: the browser does not have a `print` method available for iframes." ]); s == null || s(), c(); } else l([ "Printing failed because the `contentWindow` of the print iframe did not load. This is possibly an error with `vue-to-print`. Please file an issue: https://github.com/gregnb/react-to-print/issues/" ]); }, 500); }, h = (o) => { const s = e.onBeforePrint, f = e.onPrintError; if (s) { const p = s(); p && typeof p.then == "function" ? p.then(() => { i(o); }).catch((b) => { f && f("onBeforePrint", b); }) : i(o); } else i(o); }, m = () => { const o = e.onBeforeGetContent, s = e.onPrintError; if (o) { const f = o(); f && typeof f.then == "function" ? f.then(d).catch((p) => { s && s("onBeforeGetContent", p); }) : d(); } else d(); }, d = () => A(this, null, function* () { const o = P(e.bodyClass), s = P(e.content), f = P(e.copyStyles), p = P(e.fonts), b = P(e.pageStyle), S = P(e.nonce); let C; if (s instanceof HTMLElement ? C = s : s.$el && (C = s.$el.nodeName === "#text" ? s.$el.parentElement : s.$el), C === void 0) { l([ "To print a functional component ensure it is wrapped with `React.forwardRef`, and ensure the forwarded ref is used. See the README for an example: https://github.com/gregnb/react-to-print#examples" ]); return; } if (C === null) { l([ 'There is nothing to print because the "content" prop returned "null". Please ensure "content" is renderable before allowing "vue-to-print" to be called.' ]); return; } const a = document.createElement("iframe"); a.width = `${document.documentElement.clientWidth}px`, a.height = `${document.documentElement.clientHeight}px`, a.style.position = "absolute", a.style.top = `-${document.documentElement.clientHeight + 100}px`, a.style.left = `-${document.documentElement.clientWidth + 100}px`, a.id = "printWindow", a.srcdoc = "<!DOCTYPE html>"; const R = C; if (!R) { l([ '"vue-to-print" could not locate the DOM node corresponding with the `content` prop' ]); return; } const { node: J, result: K } = yield Ae(R); for (const u of K) u.status !== "fulfilled" && l([ "An error occurred while cloning the content to print. Printing will continue, but some content may be missing.", `Error: ${u.reason}` ], "warning"); const X = document.querySelectorAll("link[rel~='stylesheet']"), Z = p ? p.length : 0; t = X.length + Z, n = [], r = []; const T = (u, k) => { if (n.includes(u)) { l(["Tried to mark a resource that has already been handled", u], "debug"); return; } k ? (l([ '"vue-to-print" was unable to load a resource but will continue attempting to print the page', ...k ]), r.push(u)) : n.push(u), n.length + r.length === t && h(a); }; a.onload = () => A(this, null, function* () { var k, F; a.onload = null; const u = a.contentDocument || ((k = a.contentWindow) == null ? void 0 : k.document); if (u) { u.body.appendChild(J), p && ((F = a.contentDocument) != null && F.fonts && typeof FontFace != "undefined" ? p.forEach((y) => { const E = new FontFace(y.family, y.source, { weight: y.weight, style: y.style }); a.contentDocument.fonts.add(E), E.loaded.then(() => { T(E); }).catch((B) => { T(E, [ "Failed loading the font:", E, "Load error:", B ]); }); }) : (p.forEach((y) => T(y)), l([ '"vue-to-print" is not able to load custom fonts because the browser does not support the FontFace API but will continue attempting to print the page' ]))); const V = typeof b == "function" ? b() : b; if (typeof V != "string") l([ `"vue-to-print" expected a "string" from \`pageStyle\` but received "${typeof V}". Styles from \`pageStyle\` will not be applied.` ]); else { const y = u.createElement("style"); S && (y.setAttribute("nonce", S), u.head.setAttribute("nonce", S)), y.appendChild(u.createTextNode(V)), u.head.appendChild(y); } if (o && u.body.classList.add(...o.split(" ")), f) { const y = document.querySelectorAll("style, link[rel~='stylesheet']"); for (let E = 0, B = y.length; E < B; ++E) { const g = y[E]; if (g.tagName.toLowerCase() === "style") { const w = u.createElement(g.tagName), v = g.sheet; if (v) { let x = ""; try { const N = v.cssRules.length; for (let L = 0; L < N; ++L) typeof v.cssRules[L].cssText == "string" && (x += `${v.cssRules[L].cssText}\r `); } catch (N) { l( [ "A stylesheet could not be accessed. This is likely due to the stylesheet having cross-origin imports, and many browsers block script access to cross-origin stylesheets. See https://github.com/gregnb/react-to-print/issues/429 for details. You may be able to load the sheet by both marking the stylesheet with the cross `crossorigin` attribute, and setting the `Access-Control-Allow-Origin` header on the server serving the stylesheet. Alternatively, host the stylesheet on your domain to avoid this issue entirely.", g ], "warning" ); } w.setAttribute("id", `vue-to-print-${E}`), S && w.setAttribute("nonce", S), w.appendChild(u.createTextNode(x)), u.head.appendChild(w); } } else if (g.getAttribute("href")) if (g.hasAttribute("disabled")) l( [ "`vue-to-print` encountered a <link> tag with a `disabled` attribute and will ignore it. Note that the `disabled` attribute is deprecated, and some browsers ignore it. You should stop using it. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-disabled. The <link> is:", g ], "warning" ), T(g); else { const w = u.createElement(g.tagName); for (let v = 0, x = g.attributes.length; v < x; ++v) { const N = g.attributes[v]; N && w.setAttribute(N.nodeName, N.nodeValue || ""); } w.onload = () => T(w), w.onerror = (v, x, N, L, Q) => T(w, ["Failed to load", w, "Error:", Q]), S && w.setAttribute("nonce", S), u.head.appendChild(w); } else l( [ "`vue-to-print` encountered a <link> tag with an empty `href` attribute. In addition to being invalid HTML, this can cause problems in many browsers, and so the <link> was not loaded. The <link> is:", g ], "warning" ), T(g); } } } pe(a), (t === 0 || !f) && h(a); }), c(!0), document.body.appendChild(a); }), c = (o) => { const s = P(e.removeAfterPrint); if (o || s) { const f = document.getElementById("printWindow"); f && document.body.removeChild(f); } }, l = (o, s = "error") => { P(e.suppressErrors) || (s === "error" ? console.error(o) : s === "warning" ? console.warn(o) : s === "debug" && console.debug(o)); }; return { handlePrint: m }; } const Le = /* @__PURE__ */ se({ name: "VueToPrint", props: ce(), setup(e, { slots: t, expose: n }) { const { handlePrint: r } = xe(j(M({}, ie(e)), { onAfterPrint: e.onAfterPrint, onBeforePrint: e.onBeforePrint, onBeforeGetContent: e.onBeforeGetContent, onPrintError: e.onPrintError, print: e.print })); return n({ handlePrint: r }), () => { const { default: i, trigger: h } = t; if (h) return h().map((d) => le(d, { onClick: r })); { const m = { handlePrint: r }; return i == null ? void 0 : i(m); } }; } }), z = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, VueToPrint: Le }, Symbol.toStringTag, { value: "Module" })), De = function(e) { Object.keys(z).forEach((n) => { const r = z[n]; e.use(Re(r)); }); }; function Re(e) { return { install: (t) => { const n = e.name; if (typeof n != "string" || n.length <= 0) throw new Error("Component name is required"); t.component(n, e); } }; } const Ve = { install: De }; export { Le as VueToPrint, Ve as default, xe as useVueToPrint, ce as vueToPrintProps };