UNPKG

vue-to-print

Version:

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

52 lines (50 loc) 14.2 kB
"use strict";var oe=Object.defineProperty,re=Object.defineProperties;var se=Object.getOwnPropertyDescriptors;var W=Object.getOwnPropertySymbols;var ie=Object.prototype.hasOwnProperty,le=Object.prototype.propertyIsEnumerable;var j=(e,t,n)=>t in e?oe(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,D=(e,t)=>{for(var n in t||(t={}))ie.call(t,n)&&j(e,n,t[n]);if(W)for(var n of W(t))le.call(t,n)&&j(e,n,t[n]);return e},$=(e,t)=>re(e,se(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())});Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const b=require("vue"),U=()=>({bodyClass:{type:String,default:""},content:{type:Object,required:!0},copyStyles:{type:Boolean,default:!0},documentTitle:{type:String,default:""},fonts:{type:Array,default:()=>[]},onAfterPrint:{type:Function,default:null},onBeforeGetContent:{type:Function,default:null},onBeforePrint:{type:Function,default:null},onPrintError:{type:Function,default:null},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 */ } } `},print:{type:Function,default:null},removeAfterPrint:{type:Boolean,default:!1},suppressErrors:{type:Boolean,default:!1},nonce:{type:String,default:""},contextSlots:{type:Object}});function B(e){return!!e.shadowRoot}let H=!1;function ce(){if(H)return;class e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}}customElements.define("vue-to-print-shadow-dom",e),H=!0}const ae=` class VueToPrintShadowDom extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } } customElements.define('vue-to-print-shadow-dom', VueToPrintShadowDom); `;function ue(e){const t=e.createElement("script");t.setAttribute("type","text/javascript"),t.setAttribute("vue-to-print-custom-script","registry-shadow-dom"),t.innerHTML=ae,e.body.appendChild(t)}const de=` 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 he(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=de,e.body.appendChild(t)}const V=new Map;function I(e){ce();const t=e.nodeName.toLowerCase(),r=e.shadowRoot.adoptedStyleSheets,i=document.createElement("vue-to-print-shadow-dom");i.setAttribute("original-tag-name",t),V.has(t)||V.set(t,new Set);const h=V.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 fe(){const e=new Map,t=new Map,n=Array.from(V.keys());for(let r=n.length;r--;){const i=[],h=n[r],m=Array.from(V.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 me(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");ue(n),he(n);const r=fe();t.retrieveStyleSheets(r)}function _(e){return!!customElements.get(e.nodeName.toLowerCase())}let q=!1;function pe(){if(q)return;class e extends HTMLElement{constructor(){super()}}customElements.define("vue-to-print-custom-element",e),q=!0}function G(e){pe();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 J(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 ye(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 ge(e){const t=e.cloneNode(),n=t.getContext("2d");return n&&n.drawImage(e,0,0),t}function we(e,t){const n=e.cloneNode();return t.push(J(n)),n}function be(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(J(i))}else t.push(ye(n));return n}function Se(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 Ee(e){const t=e.cloneNode();return t.value=e.value,t}function ve(e){const t=e.cloneNode();return t.selected=e.selected,t}const Y=new Map([["canvas",ge],["img",we],["video",be],["input",Se],["select",Ee],["option",ve]]);function Pe(e){return e.cloneNode()}function Ce(e,t=[]){const n=e.nodeName.toLowerCase();return(Y.has(n)?Y.get(n):Pe)(e,t)}function Te(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 Ne(e){return A(this,null,function*(){const t=new Map,n=[];let r;B(e)?r=I(e):_(e)?r=G(e):r=e.cloneNode(),t.set(e,r);const i=[e];for(;i.length;){const h=i.shift(),m=Te(h);if(m.length<=0)continue;const d=t.get(h),c=B(d)?d.shadowRoot:d;for(let l=0;l<m.length;l++){const o=m[l];let s;B(o)?s=I(o):_(o)?s=G(o):s=Ce(o,n),t.set(o,s),i.push(o),c.appendChild(s)}}return{node:r,result:yield Promise.allSettled(n)}})}const Ae={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 K(e){e=D(D({},Ae),e);let t=0,n=[],r=[];const i=o=>{const s=e.onAfterPrint,f=e.onPrintError,p=e.print,S=b.toValue(e.documentTitle);setTimeout(()=>{var E,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=(E=o.contentDocument)==null?void 0:E.title)!=null?C:"",M=o.ownerDocument.title;S&&(o.ownerDocument.title=S,o.contentDocument&&(o.contentDocument.title=S)),o.contentWindow.print(),S&&(o.ownerDocument.title=M,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(S=>{f&&f("onBeforePrint",S)}):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=b.toValue(e.bodyClass),s=b.toValue(e.content),f=b.toValue(e.copyStyles),p=b.toValue(e.fonts),S=b.toValue(e.pageStyle),E=b.toValue(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 M=C;if(!M){l(['"vue-to-print" could not locate the DOM node corresponding with the `content` prop']);return}const{node:Z,result:Q}=yield Ne(M);for(const u of Q)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 ee=document.querySelectorAll("link[rel~='stylesheet']"),te=p?p.length:0;t=ee.length+te,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,R;a.onload=null;const u=a.contentDocument||((k=a.contentWindow)==null?void 0:k.document);if(u){u.body.appendChild(Z),p&&((R=a.contentDocument)!=null&&R.fonts&&typeof FontFace!="undefined"?p.forEach(y=>{const v=new FontFace(y.family,y.source,{weight:y.weight,style:y.style});a.contentDocument.fonts.add(v),v.loaded.then(()=>{T(v)}).catch(O=>{T(v,["Failed loading the font:",v,"Load error:",O])})}):(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 F=typeof S=="function"?S():S;if(typeof F!="string")l([`"vue-to-print" expected a "string" from \`pageStyle\` but received "${typeof F}". Styles from \`pageStyle\` will not be applied.`]);else{const y=u.createElement("style");E&&(y.setAttribute("nonce",E),u.head.setAttribute("nonce",E)),y.appendChild(u.createTextNode(F)),u.head.appendChild(y)}if(o&&u.body.classList.add(...o.split(" ")),f){const y=document.querySelectorAll("style, link[rel~='stylesheet']");for(let v=0,O=y.length;v<O;++v){const g=y[v];if(g.tagName.toLowerCase()==="style"){const w=u.createElement(g.tagName),P=g.sheet;if(P){let x="";try{const N=P.cssRules.length;for(let L=0;L<N;++L)typeof P.cssRules[L].cssText=="string"&&(x+=`${P.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-${v}`),E&&w.setAttribute("nonce",E),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 P=0,x=g.attributes.length;P<x;++P){const N=g.attributes[P];N&&w.setAttribute(N.nodeName,N.nodeValue||"")}w.onload=()=>T(w),w.onerror=(P,x,N,L,ne)=>T(w,["Failed to load",w,"Error:",ne]),E&&w.setAttribute("nonce",E),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)}}}me(a),(t===0||!f)&&h(a)}),c(!0),document.body.appendChild(a)}),c=o=>{const s=b.toValue(e.removeAfterPrint);if(o||s){const f=document.getElementById("printWindow");f&&document.body.removeChild(f)}},l=(o,s="error")=>{b.toValue(e.suppressErrors)||(s==="error"?console.error(o):s==="warning"?console.warn(o):s==="debug"&&console.debug(o))};return{handlePrint:m}}const X=b.defineComponent({name:"VueToPrint",props:U(),setup(e,{slots:t,expose:n}){const{handlePrint:r}=K($(D({},b.toRefs(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=>b.cloneVNode(d,{onClick:r}));{const m={handlePrint:r};return i==null?void 0:i(m)}}}}),z=Object.freeze(Object.defineProperty({__proto__:null,VueToPrint:X},Symbol.toStringTag,{value:"Module"})),ke=function(e){Object.keys(z).forEach(n=>{const r=z[n];e.use(xe(r))})};function xe(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 Le={install:ke};exports.VueToPrint=X;exports.default=Le;exports.useVueToPrint=K;exports.vueToPrintProps=U;