preact-missing-hooks
Version:
A lightweight, extendable collection of missing React-like hooks for Preact — plus fresh, powerful new ones designed specifically for modern Preact apps.
117 lines (103 loc) • 3.42 kB
text/typescript
import type { RefObject } from "preact";
import { useCallback, useRef } from "preact/hooks";
const PRINT_CLASS = "use-ref-print-target";
const PRINT_STYLE_ID = "use-ref-print-styles";
const PRINT_CSS = `
@media print {
body * {
visibility: hidden;
}
.${PRINT_CLASS},
.${PRINT_CLASS} * {
visibility: visible;
}
.${PRINT_CLASS} {
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
}
}
`;
export interface UseRefPrintOptions {
/**
* When true, the print flow is triggered so the user can choose
* "Save as PDF" in the native print dialog. Uses the same window.print() path.
*/
downloadAsPdf?: boolean;
/** Title for the print document (e.g. used when saving as PDF). */
documentTitle?: string;
}
export interface UseRefPrintReturn {
/** Triggers native print for the section bound to the ref (opens print dialog; only that section is printed via @media print). */
print: () => void;
}
/**
* A Preact hook that binds a ref to a printable section and provides a function
* to print only that section using the native window.print() and @media print CSS.
* When print() is called (or user presses Ctrl+P after focusing that section), only
* the ref section is visible in the print layout. User can then print or save as PDF.
*
* @param printRef - Ref to the DOM element (e.g. a div) that should be printed.
* @param options - Optional: downloadAsPdf hint, documentTitle for the print document.
* @returns Object with a print() function.
*
* @example
* ```tsx
* function Report() {
* const printRef = useRef<HTMLDivElement>(null);
* const { print } = useRefPrint(printRef, { documentTitle: 'Report', downloadAsPdf: true });
* return (
* <div>
* <div ref={printRef}>Content to print or save as PDF</div>
* <button onClick={print}>Print / Save as PDF</button>
* </div>
* );
* }
* ```
*/
export function useRefPrint(
printRef: RefObject<HTMLElement | null>,
options: UseRefPrintOptions = {}
): UseRefPrintReturn {
const { documentTitle } = options;
const originalTitleRef = useRef<string>("");
const print = useCallback(() => {
const el = printRef.current;
if (!el || typeof window === "undefined" || !window.print) {
return;
}
// Ensure print-only styles exist (once per document)
let styleEl = document.getElementById(
PRINT_STYLE_ID
) as HTMLStyleElement | null;
if (!styleEl) {
styleEl = document.createElement("style");
styleEl.id = PRINT_STYLE_ID;
styleEl.textContent = PRINT_CSS;
document.head.appendChild(styleEl);
}
el.classList.add(PRINT_CLASS);
if (documentTitle) {
originalTitleRef.current = document.title;
document.title = documentTitle;
}
const cleanup = () => {
el.classList.remove(PRINT_CLASS);
if (documentTitle && originalTitleRef.current !== undefined) {
document.title = originalTitleRef.current;
}
};
if ("onafterprint" in window) {
const onAfterPrint = () => {
cleanup();
window.removeEventListener("afterprint", onAfterPrint);
};
window.addEventListener("afterprint", onAfterPrint);
}
window.print();
// Fallback cleanup if afterprint is not fired (e.g. user cancels)
setTimeout(cleanup, 1000);
}, [printRef, documentTitle]);
return { print };
}