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
JavaScript
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
};