UNPKG

react-to-print

Version:
388 lines (387 loc) 13 kB
import { useCallback as Y } from "react"; const q = "printWindow"; function V(e) { const t = document.createElement("iframe"); return t.width = `${document.documentElement.clientWidth}px`, t.height = `${document.documentElement.clientHeight}px`, t.style.position = "absolute", t.style.top = `-${document.documentElement.clientHeight + 100}px`, t.style.left = `-${document.documentElement.clientWidth + 100}px`, t.id = q, t.srcdoc = "<!DOCTYPE html>", e && (e.allow && (t.allow = e.allow), e.referrerPolicy !== void 0 && (t.referrerPolicy = e.referrerPolicy), e.sandbox !== void 0 && (t.sandbox = e.sandbox)), t; } function f({ level: e = "error", messages: t, suppressErrors: n = !1 }) { n || (e === "error" ? console.error(t) : e === "warning" ? console.warn(t) : console.debug(t)); } function $(e, t) { if (t || !e) { const n = document.getElementById(q); n && document.body.removeChild(n); } } function P(e) { return e instanceof Error ? e : new Error("Unknown Error"); } function H(e, t) { const { documentTitle: n, onAfterPrint: l, onPrintError: p, preserveAfterPrint: m, print: h, suppressErrors: g } = t; setTimeout(() => { if (e.contentWindow) { let a = function() { l?.(), $(m); }; if (e.contentWindow.focus(), h) h(e).then(a).catch((c) => { p ? p("print", P(c)) : f({ messages: ["An error was thrown by the specified `print` function"], suppressErrors: g }); }); else { if (e.contentWindow.print) { const c = e.contentDocument?.title ?? "", E = e.ownerDocument.title, y = typeof n == "function" ? n() : n; y && (e.ownerDocument.title = y, e.contentDocument && (e.contentDocument.title = y)), e.contentWindow.print(), y && (e.ownerDocument.title = E, e.contentDocument && (e.contentDocument.title = c)); } else f({ messages: ["Printing for this browser is not currently possible: the browser does not have a `print` method available for iframes."], suppressErrors: g }); z() ? setTimeout(a, 500) : a(); } } else f({ messages: ["Printing failed because the `contentWindow` of the print iframe did not load. This is possibly an error with `react-to-print`. Please file an issue: https://github.com/MatthewHerbst/react-to-print/issues/"], suppressErrors: g }); }, 500); } function z() { return [ /Android/i, /webOS/i, /iPhone/i, /iPad/i, /iPod/i, /BlackBerry/i, /Windows Phone/i ].some((t) => ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition (navigator.userAgent ?? // Retained for compatibility with browsers that use `navigator.vendor` to identify the browser. // eslint-disable-next-line @typescript-eslint/no-deprecated navigator.vendor ?? // Retained for compatibility with older versions of Opera that use `window.opera`. ("opera" in window && window.opera)).match(t) )); } function O(e) { const t = [], n = document.createTreeWalker(e, NodeFilter.SHOW_ELEMENT, null); let l = n.nextNode(); for (; l; ) t.push(l), l = n.nextNode(); return t; } function j(e, t, n) { const l = O(e), p = O(t); if (l.length !== p.length) { f({ messages: ["When cloning shadow root content, source and target elements have different size. `onBeforePrint` likely resolved too early.", e, t], suppressErrors: n }); return; } for (let m = 0; m < l.length; m++) { const h = l[m], g = p[m], a = h.shadowRoot; if (a !== null) { const c = g.attachShadow({ mode: a.mode }); c.innerHTML = a.innerHTML, j(a, c, n); } } } const G = ` @page { /* Remove browser default header (title) and footer (url) */ margin: 0; } @media print { body { /* Tell browsers to print background colors */ color-adjust: exact; /* Firefox. This is an older version of "print-color-adjust" */ print-color-adjust: exact; /* Firefox/Safari */ -webkit-print-color-adjust: exact; /* Chrome/Safari/Edge/Opera */ } } `; function W(e, t, n) { const { contentNode: l, clonedContentNode: p, clonedImgNodes: m, clonedVideoNodes: h, numResourcesToLoad: g, originalCanvasNodes: a } = t, { bodyClass: c, fonts: E, ignoreGlobalStyles: y, pageStyle: C, nonce: T, suppressErrors: A, copyShadowRoots: F } = n, L = [], _ = []; function i(k, x) { if (L.includes(k)) { f({ level: "debug", messages: ["Tried to mark a resource that has already been handled", k], suppressErrors: A }); return; } x ? (f({ messages: [ '"react-to-print" was unable to load a resource but will continue attempting to print the page', ...x ], suppressErrors: A }), _.push(k)) : L.push(k), L.length + _.length === g && H(e, n); } e.onload = null; const d = e.contentDocument ?? e.contentWindow?.document; if (d) { const k = d.body.appendChild(p); F && j(l, k, !!A), E && (e.contentDocument?.fonts && e.contentWindow?.FontFace ? E.forEach((s) => { const o = new FontFace( s.family, s.source, { weight: s.weight, style: s.style } ); e.contentDocument.fonts.add(o), o.loaded.then(() => { i(o); }).catch((b) => { i(o, ["Failed loading the font:", o, "Load error:", P(b)]); }); }) : (E.forEach((s) => { i(s); }), f({ messages: ['"react-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'], suppressErrors: A }))); const x = C ?? G, D = d.createElement("style"); T && (D.setAttribute("nonce", T), d.head.setAttribute("nonce", T)), D.appendChild(d.createTextNode(x)), d.head.appendChild(D), c && d.body.classList.add(...c.split(" ")); const U = d.querySelectorAll("canvas"); for (let s = 0; s < a.length; ++s) { const o = a[s], b = U[s]; if (b === void 0) { f({ messages: ["A canvas element could not be copied for printing, has it loaded? `onBeforePrint` likely resolved too early.", o], suppressErrors: A }); continue; } const r = b.getContext("2d"); r && r.drawImage(o, 0, 0); } for (let s = 0; s < m.length; s++) { const o = m[s], b = o.getAttribute("src"); if (!b) i(o, ['Found an <img> tag with an empty "src" attribute. This prevents pre-loading it.', o]); else { const r = new Image(); r.onload = () => { i(o); }, r.onerror = (u, w, S, v, N) => { i(o, ["Error loading <img>", o, "Error", N]); }, r.src = b; } } for (let s = 0; s < h.length; s++) { const o = h[s]; o.preload = "auto"; const b = o.getAttribute("poster"); if (b) { const r = new Image(); r.onload = () => { i(o); }, r.onerror = (u, w, S, v, N) => { i(o, ["Error loading video poster", b, "for video", o, "Error:", N]); }, r.src = b; } else o.readyState >= 2 ? i(o) : o.src ? (o.onloadeddata = () => { i(o); }, o.onerror = (r, u, w, S, v) => { i(o, ["Error loading video", o, "Error", v]); }, o.onstalled = () => { i(o, ["Loading video stalled, skipping", o]); }) : i(o, ["Error loading video, `src` is empty", o]); } const R = "select", M = l.querySelectorAll(R), B = d.querySelectorAll(R); for (let s = 0; s < M.length; s++) B[s].value = M[s].value; if (!y) { const s = document.querySelectorAll("style, link[rel~='stylesheet'], link[as='style']"); for (let o = 0, b = s.length; o < b; ++o) { const r = s[o]; if (r.tagName.toLowerCase() === "style") { const u = d.createElement(r.tagName), w = r.sheet; if (w) { let S = ""; try { const v = w.cssRules.length; for (let N = 0; N < v; ++N) typeof w.cssRules[N].cssText == "string" && (S += `${w.cssRules[N].cssText}\r `); } catch (v) { f({ messages: [ "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/MatthewHerbst/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.", // eslint-disable-line max-len r, `Original error: ${P(v).message}` ], level: "warning" }); } u.setAttribute("id", `react-to-print-${o}`), T && u.setAttribute("nonce", T), u.appendChild(d.createTextNode(S)), d.head.appendChild(u); } } else if (r.getAttribute("href")) if (r.hasAttribute("disabled")) f({ messages: ["`react-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:", r], level: "warning" }), i(r); else { const u = d.createElement(r.tagName); for (let w = 0, S = r.attributes.length; w < S; ++w) { const v = r.attributes[w]; v && u.setAttribute(v.nodeName, v.nodeValue ?? ""); } u.onload = () => { i(u); }, u.onerror = (w, S, v, N, I) => { i(u, ["Failed to load", u, "Error:", I]); }, T && u.setAttribute("nonce", T), d.head.appendChild(u); } else f({ messages: ["`react-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:", r], level: "warning" }), i(r); } } } g === 0 && H(e, n); } function J(e, t, n) { e.onload = () => { W( e, t, n ); }, document.body.appendChild(e); } function K({ contentRef: e, optionalContent: t, suppressErrors: n }) { if (t && typeof t == "function") return e && f({ level: "warning", messages: ['"react-to-print" received a `contentRef` option and an optional-content param passed to its callback. The `contentRef` option will be ignored.'] }), t(); if (e) return e.current; f({ messages: ['"react-to-print" did not receive a `contentRef` option or a optional-content param pass to its callback.'], suppressErrors: n }); } function Q(e, t) { const { contentRef: n, fonts: l, ignoreGlobalStyles: p, suppressErrors: m } = t, h = K({ contentRef: n, optionalContent: e, suppressErrors: m }); if (!h) return; const g = h.cloneNode(!0), a = document.querySelectorAll("link[rel~='stylesheet'], link[as='style']"), c = g.querySelectorAll("img"), E = g.querySelectorAll("video"), y = l ? l.length : 0, C = (p ? 0 : a.length) + c.length + E.length + y; return { contentNode: h, clonedContentNode: g, clonedImgNodes: c, clonedVideoNodes: E, numResourcesToLoad: C, originalCanvasNodes: h.querySelectorAll("canvas") }; } function Z({ bodyClass: e, contentRef: t, copyShadowRoots: n, documentTitle: l, fonts: p, ignoreGlobalStyles: m, nonce: h, onAfterPrint: g, onBeforePrint: a, onPrintError: c, pageStyle: E, preserveAfterPrint: y, print: C, printIframeProps: T, suppressErrors: A }) { return Y((L) => { $(y, !0); function _() { const i = { bodyClass: e, contentRef: t, copyShadowRoots: n, documentTitle: l, fonts: p, ignoreGlobalStyles: m, nonce: h, onAfterPrint: g, onPrintError: c, pageStyle: E, preserveAfterPrint: y, print: C, suppressErrors: A }, d = V(T), k = Q(L, i); if (!k) { f({ messages: ["There is nothing to print"], suppressErrors: A }); return; } J(d, k, i); } a ? a().then(() => { _(); }).catch((i) => { c?.("onBeforePrint", P(i)); }) : _(); }, [ e, t, n, l, p, m, h, g, a, c, E, y, T, C, A ]); } export { Z as useReactToPrint };