Skip to content

Commit

Permalink
Update devDependencies, break up and organize code (#683)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewHerbst authored Feb 12, 2024
1 parent 3cc32aa commit 756888b
Show file tree
Hide file tree
Showing 12 changed files with 1,051 additions and 734 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The component accepts the following props:
| Name | Type | Description |
| :-------------------: | :------- | :---------------------------------------------------------------------------------------------------------------------------------- |
| **`bodyClass?`** | `string` | One or more class names to pass to the print window, separated by spaces |
| **`content?`** | `function` | A function that returns a component reference value. The content of this reference value is then used for print |
| **`content?`** | `function` | A function that returns a component reference value. The content of this reference value is then used for print. Alternatively, pass the content directly to the callback returned by `useReactToPrint` |
| **`copyStyles?`** | `boolean` | Copy all `<style>` and `<link type="stylesheet" />` tags from `<head>` inside the parent window into the print window. (default: `true`) |
| **`documentTitle?`** | `string` | Set the title for printing when saving as a file |
| **`fonts?`** | `{ family: string, source: string; weight?: string; style?: string; }[]` | You may optionally provide a list of fonts which will be loaded into the printing iframe. This is useful if you are using custom fonts |
Expand All @@ -47,7 +47,7 @@ If you need extra control over printing and don't want to specify `trigger` dire

### `useReactToPrint`

For functional components, use the `useReactToPrint` hook, which accepts an object with the same configuration props as `<ReactToPrint />` and returns a `handlePrint` function which when called will trigger the print action. Requires React >=16.8.0. See the examples below for usage.
For functional components, use the `useReactToPrint` hook, which accepts an object with the same configuration props as `<ReactToPrint />` and returns a `handlePrint` function which when called will trigger the print action. Requires React >=16.8.0. See the examples below for usage. Additionally, for-fine tuned control, the `handlePrint` callback can accept an optional `content` prop which will can be used instead of passing a `content` prop to the hook itself.

## Compatibility

Expand Down Expand Up @@ -528,4 +528,4 @@ Set the container to `overflow: visible; height: fit-content` when printing, can
## Running locally
*NOTE*: Node >=12 is required to build the library locally. We use Node ^14 for our tests.
*NOTE*: The library is tested and built locally using Node >= 20.
1,443 changes: 868 additions & 575 deletions package-lock.json

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,27 @@
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
},
"devDependencies": {
"@types/react": "^18.2.22",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.7.2",
"@typescript-eslint/parser": "^6.7.2",
"acorn": "^8.10.0",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"acorn": "^8.11.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"eslint": "^8.49.0",
"html-webpack-plugin": "^5.5.3",
"husky": "^8.0.3",
"lint-staged": "^14.0.1",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^6.10.0",
"eslint": "^8.56.0",
"html-webpack-plugin": "^5.6.0",
"husky": "^9.0.10",
"lint-staged": "^15.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-tabs": "^6.0.2",
"style-loader": "^3.3.3",
"ts-loader": "^9.4.4",
"style-loader": "^3.3.4",
"ts-loader": "^9.5.1",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"typescript": "^5.3.3",
"url-loader": "^4.1.1",
"webpack": "^5.88.2",
"webpack": "^5.90.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
Expand Down
10 changes: 10 additions & 0 deletions src/components/PrintContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from "react";

const contextEnabled = Object.prototype.hasOwnProperty.call(React, "createContext");

export interface IPrintContextProps {
handlePrint: (event: unknown, content?: (() => React.ReactInstance | null)) => void,
}

export const PrintContext = contextEnabled ? React.createContext({} as IPrintContextProps) : null;
export const PrintContextConsumer = PrintContext ? PrintContext.Consumer : () => null;
153 changes: 13 additions & 140 deletions src/index.tsx → src/components/ReactToPrint.tsx
Original file line number Diff line number Diff line change
@@ -1,111 +1,13 @@
import * as React from "react";
import { findDOMNode } from "react-dom";

const contextEnabled = Object.prototype.hasOwnProperty.call(React, "createContext");
const hooksEnabled = Object.prototype.hasOwnProperty.call(React, "useMemo") && Object.prototype.hasOwnProperty.call(React, "useCallback");

export interface IPrintContextProps {
handlePrint: (event: unknown, content?: (() => React.ReactInstance | null)) => void,
}
const PrintContext = contextEnabled ? React.createContext({} as IPrintContextProps) : null;
export const PrintContextConsumer = PrintContext ? PrintContext.Consumer : () => null;

export interface ITriggerProps<T> {
onClick: (event: unknown) => void;
ref: (v: T) => void;
}

// https://developer.mozilla.org/en-US/docs/Web/API/FontFace/FontFace
type Font = {
family: string;
source: string;
weight?: string;
style?: string;
};

type PropertyFunction<T> = () => T;

/**
* This function helps to curry arguments to a bound function
* and partially apply them at the end of the argument list.
*
* @param {Object} thisArg
* @param {Function} callback
* @param {Array.<*>} predefinedArgs
*
* @returns {*}
*/
function wrapCallbackWithArgs<CallbackReturnValue, BoundObject>(
thisArg: BoundObject,
callback: (...curryArgs: any[]) => CallbackReturnValue,
...predefinedArgs: unknown[]
) {
return function (...args: unknown[]) {
return callback.apply(thisArg, [...args, ...predefinedArgs]);
};
}

// NOTE: https://github.com/Microsoft/TypeScript/issues/23812
const defaultProps = {
copyStyles: true,
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: false,
suppressErrors: false,
};

export interface IReactToPrintProps {
/** Class to pass to the print window body */
bodyClass?: string;
children?: React.ReactNode;
/** Content to be printed */
content?: () => React.ReactInstance | null;
/** Copy styles over into print window. default: true */
copyStyles?: boolean;
/**
* Set the title for printing when saving as a file.
* Will result in the calling page's `<title>` being temporarily changed while printing.
*/
documentTitle?: string;
/** Pre-load these fonts to ensure availability when printing */
fonts?: Font[];
/** Callback function to trigger after print */
onAfterPrint?: () => void;
/** Callback function to trigger before page content is retrieved for printing */
onBeforeGetContent?: () => void | Promise<any>;
/** Callback function to trigger before print */
onBeforePrint?: () => void | Promise<any>;
/** Callback function to listen for printing errors */
onPrintError?: (errorLocation: "onBeforeGetContent" | "onBeforePrint" | "print", error: Error) => void;
/** Override default print window styling */
pageStyle?: string | PropertyFunction<string>;
/** Override the default `window.print` method that is used for printing */
print?: (target: HTMLIFrameElement) => Promise<any>;
/**
* Remove the iframe after printing.
* NOTE: `onAfterPrint` will run before the iframe is removed
*/
removeAfterPrint?: boolean;
/** Suppress error messages */
suppressErrors?: boolean;
/** Trigger action used to open browser print */
trigger?: <T>() => React.ReactElement<ITriggerProps<T>>;
/** Set the nonce attribute for whitelisting script and style -elements for CSP (content security policy) */
nonce?: string;
}

export default class ReactToPrint extends React.Component<IReactToPrintProps> {
import { PrintContext } from "./PrintContext";
import type { IPrintContextProps } from "./PrintContext";
import { defaultProps } from "../consts/defaultProps";
import type { Font } from "../types/font";
import type { IReactToPrintProps } from "../types/reactToPrintProps";

export class ReactToPrint extends React.Component<IReactToPrintProps> {
private numResourcesToLoad!: number;
private resourcesLoaded!: (Element | Font | FontFace)[];
private resourcesErrored!: (Element | Font | FontFace)[];
Expand Down Expand Up @@ -205,7 +107,6 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {
this.startPrint(target);
}
}


public handleClick (
/* eslint-disable-next-line no-unused-vars */
Expand Down Expand Up @@ -247,6 +148,10 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {
} = this.props;

let contentEl = typeof optionalContent === "function" ? optionalContent() : null;

if (contentEl && typeof content === "function") {
this.logMessages(['"react-to-print" received a `content` prop and a content param passed the callback return by `useReactToPrint. The `content` prop will be ignored.'], "warning");
}

if (!contentEl && typeof content === "function") {
contentEl = content();
Expand Down Expand Up @@ -468,7 +373,7 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {

for (let i = 0, styleAndLinkNodesLen = styleAndLinkNodes.length; i < styleAndLinkNodesLen; ++i) {
const node = styleAndLinkNodes[i];

if (node.tagName.toLowerCase() === 'style') { // <style> nodes
const newHeadEl = domDoc.createElement(node.tagName);
const sheet = (node as HTMLStyleElement).sheet as CSSStyleSheet;
Expand Down Expand Up @@ -616,36 +521,4 @@ export default class ReactToPrint extends React.Component<IReactToPrintProps> {
);
}
}
}

type UseReactToPrintHookReturn = (
event: unknown,
content?: (() => React.ReactInstance | null)
) => void;

export const useReactToPrint = (props: IReactToPrintProps): UseReactToPrintHookReturn => {
if (!hooksEnabled) {
if (!props.suppressErrors) {
console.error('"react-to-print" requires React ^16.8.0 to be able to use "useReactToPrint"'); // eslint-disable-line no-console
}

return () => {
throw new Error('"react-to-print" requires React ^16.8.0 to be able to use "useReactToPrint"');
};
}

const reactToPrint = React.useMemo(
// TODO: is there a better way of applying the defaultProps?
() => new ReactToPrint({ ...defaultProps, ...props }),
[props]
);

return React.useCallback(
(event: unknown, content?: (() => React.ReactInstance | null)) => {
/* eslint-disable-next-line @typescript-eslint/unbound-method */
const triggerPrint = wrapCallbackWithArgs(reactToPrint, reactToPrint.handleClick, content);
// NOTE: `event` is a no-use argument
// (necessary for backward compatibility with older versions)
return triggerPrint(event);
}, [reactToPrint]);
};
}
19 changes: 19 additions & 0 deletions src/consts/defaultProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// NOTE: https://github.com/Microsoft/TypeScript/issues/23812
export const defaultProps = {
copyStyles: true,
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: false,
suppressErrors: false,
};
40 changes: 40 additions & 0 deletions src/hooks/useReactToPrint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from "react";

import { ReactToPrint } from "../components/ReactToPrint";
import { defaultProps } from "../consts/defaultProps";
import type { IReactToPrintProps } from "../types/reactToPrintProps";
import { wrapCallbackWithArgs } from "../utils/wrapCallbackWithArgs";

type UseReactToPrintHookReturn = (
event: unknown,
content?: (() => React.ReactInstance | null)
) => void;

const hooksEnabled = Object.prototype.hasOwnProperty.call(React, "useMemo") && Object.prototype.hasOwnProperty.call(React, "useCallback");

export const useReactToPrint = (props: IReactToPrintProps): UseReactToPrintHookReturn => {
if (!hooksEnabled) {
if (!props.suppressErrors) {
console.error('"react-to-print" requires React ^16.8.0 to be able to use "useReactToPrint"'); // eslint-disable-line no-console
}

return () => {
throw new Error('"react-to-print" requires React ^16.8.0 to be able to use "useReactToPrint"');
};
}

const reactToPrint = React.useMemo(
// TODO: is there a better way of applying the defaultProps?
() => new ReactToPrint({ ...defaultProps, ...props }),
[props]
);

return React.useCallback(
(event: unknown, content?: (() => React.ReactInstance | null)) => {
/* eslint-disable-next-line @typescript-eslint/unbound-method */
const triggerPrint = wrapCallbackWithArgs(reactToPrint, reactToPrint.handleClick, content);
// NOTE: `event` is a no-use argument
// (necessary for backward compatibility with older versions)
return triggerPrint(event);
}, [reactToPrint]);
};
16 changes: 16 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type { IPrintContextProps } from "./components/PrintContext";
export { PrintContextConsumer } from "./components/PrintContext";
export { ReactToPrint } from "./components/ReactToPrint";
export { useReactToPrint } from "./hooks/useReactToPrint";
export type { IReactToPrintProps, ITriggerProps } from "./types/reactToPrintProps";

import { ReactToPrint } from "./components/ReactToPrint";
export default ReactToPrint;








7 changes: 7 additions & 0 deletions src/types/font.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// https://developer.mozilla.org/en-US/docs/Web/API/FontFace/FontFace
export type Font = {
family: string;
source: string;
weight?: string;
style?: string;
};
Loading

0 comments on commit 756888b

Please sign in to comment.