UNPKG

@progress/kendo-react-pdf-viewer

Version:
562 lines (561 loc) 18.5 kB
/** * @license *------------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the package root for more information *------------------------------------------------------------------------------------------- */ import * as e from "react"; import l from "prop-types"; import { ButtonGroup as Q, Button as d, Toolbar as tt, ToolbarSpacer as ot } from "@progress/kendo-react-buttons"; import { TextBox as at, InputSeparator as nt, InputSuffix as rt } from "@progress/kendo-react-inputs"; import { Pager as lt } from "@progress/kendo-react-data-tools"; import { Upload as ct } from "@progress/kendo-react-upload"; import { ComboBox as st } from "@progress/kendo-react-dropdowns"; import { Loader as it } from "@progress/kendo-react-indicators"; import { validatePackage as ut, getLicenseMessage as mt, classNames as dt, WatermarkOverlay as gt } from "@progress/kendo-react-common"; import { useLocalization as ft } from "@progress/kendo-react-intl"; import { zoomOutIcon as ht, zoomInIcon as vt, pointerIcon as bt, handIcon as kt, searchIcon as pt, folderOpenIcon as yt, downloadIcon as Ct, printIcon as Et, convertLowercaseIcon as St, arrowUpIcon as xt, arrowDownIcon as wt, xIcon as Zt } from "@progress/kendo-svg-icons"; import "pdfjs-dist/build/pdf.worker.min.mjs"; import { packageMetadata as X } from "./package-metadata.mjs"; import { Scroller as Lt, SearchService as zt, scrollToPage as Y, removeChildren as z, loadPDF as ee, reloadDocument as It, goToNextSearchMatch as te, goToPreviousSearchMatch as Mt, currentPage as Nt, calculateZoomLevel as Tt, download as Pt, print as Rt, DEFAULT_ZOOM_LEVEL as Ft } from "@progress/kendo-pdfviewer-common"; import { messages as i, popupBlocked as oe, zoomOut as ae, zoomIn as ne, zoomLevel as re, enableSelection as le, enablePanning as ce, search as I, open as se, download as ie, print as ue, matchCase as me, searchOf as de, prevMatch as ge, nextMatch as fe, close as he, actualWidth as Dt, fitToWidth as Ot, fitToPage as Bt } from "./messages.mjs"; const be = [ "pager", "spacer", "zoomInOut", "zoom", "selection", "spacer", "search", "open", "download", "print" ], y = { minZoom: 0.5, maxZoom: 4, tools: [...be], zoomRate: 0.25, zoomLevels: [ { id: 1, priority: 1, value: 1, text: "Actual width", type: "ActualWidth", locationString: Dt }, { id: 2, priority: 2, value: 1, text: "Fit to width", type: "FitToWidth", locationString: Ot }, { id: 3, priority: 3, value: 1, text: "Fit to page", type: "FitToPage", locationString: Bt }, { id: 4, priority: 4, value: 0.5, text: "50%", type: "" }, { id: 5, priority: 5, value: 0.75, text: "75%", type: "" }, { id: 6, priority: 100, value: 1, text: "100%", type: "" }, { id: 7, priority: 7, value: 1.25, text: "125%", type: "" }, { id: 8, priority: 8, value: 1.5, text: "150%", type: "" }, { id: 9, priority: 9, value: 2, text: "200%", type: "" }, { id: 10, priority: 10, value: 3, text: "300%", type: "" }, { id: 11, priority: 11, value: 4, text: "400%", type: "" } ], defaultZoom: Ft }, At = [ ".k-toolbar > button", ".k-toolbar .k-combobox > input", ".k-toolbar .k-button-group > button", ".k-toolbar .k-pager > a", ".k-toolbar .k-pager input" ]; let ve = 0; function Vt() { return ve++, ve; } const Wt = (a, E) => a.priority > E.priority ? -1 : a.priority < E.priority ? 1 : 0, ke = e.forwardRef((a, E) => { const pe = !ut(X, { component: "PDFViewer" }), ye = mt(X), { zoom: O, zoomLevels: B = y.zoomLevels, defaultZoom: M = y.defaultZoom, minZoom: N = y.minZoom, maxZoom: T = y.maxZoom, zoomRate: S = y.zoomRate } = a, s = ft(), c = e.useRef(null), [Ce, x] = e.useState(M), g = O !== void 0 ? O : Ce, C = B.slice().sort(Wt).find((o) => o.value === g) || { text: g * 100 + "%", value: g, id: g, locationString: "" }; C.locationString && (C.text = s.toLanguageString( C.locationString, i[C.locationString] )); const [u, P] = e.useState(!1), [Ee, f] = e.useState(!0), [w, A] = e.useState(0), [Z, V] = e.useState(!0), [Se, W] = e.useState(!1), [b, R] = e.useState(0), [h, k] = e.useState(0), [p, K] = e.useState(!1), [F, U] = e.useState(""), t = e.useMemo(() => ({}), []); t.currentZoom = g, t.props = a; const j = e.useCallback((o) => { c.current && c.current.style.setProperty("--scale-factor", String(o)); }, []), v = e.useRef(null), L = e.useRef(null); e.useImperativeHandle( v, () => ({ get element() { return L.current; }, props: a, get pages() { return t.pages; }, get document() { return t.document; } }), [] ), e.useImperativeHandle(E, () => v.current); const xe = e.useCallback(() => { if (t.props.onLoad) { const o = { target: v.current }; t.props.onLoad.call(void 0, o); } }, []), q = e.useCallback( (o, n, r) => { if (a.onDownload) { const m = { target: v.current, blob: o, fileName: n, saveOptions: r }; return a.onDownload.call(void 0, m) === !1; } return !1; }, [a.onDownload] ), we = e.useCallback(() => { var o; t.scroller && t.scroller.destroy(), t.scroller = new Lt((o = c.current) == null ? void 0 : o.parentNode, { filter: ".k-page", events: {} }), t.scroller.disablePanEventsTracking(); }, []), Ze = e.useCallback((o) => { const n = Array.from(o.querySelectorAll(".k-text-layer")); t.search = new zt({ textContainers: n || [], highlightClass: "k-search-highlight", highlightMarkClass: "k-search-highlight-mark", charClass: "k-text-char" }); }, []); t.done = e.useCallback(({ pdfPages: o, pdfDoc: n, zoom: r }) => { t.document = n, t.pages = o, t.zoom = r, we(), f(!1), P(!0), xe(), c.current && Y(c.current, 0); }, []), t.error = e.useCallback( (o) => { if (t.document = null, t.pages = [], f(!1), P(!1), a.onError) { const n = typeof o == "string" ? { message: o } : o, r = { error: n != null ? n : s.toLanguageString(oe, i[oe]), target: v.current }; a.onError.call(void 0, r); } }, [a.onError] ), e.useEffect(() => { c.current && (a.url || a.data || a.arrayBuffer ? (f(!0), z(c.current), ee({ url: a.url, data: a.data, arrayBuffer: a.arrayBuffer, dom: c.current, zoom: t.currentZoom, done: t.done, error: t.error }), j(t.currentZoom)) : (t.document = null, t.pages = [], P(!1), f(!1), z(c.current))); }, [a.url, a.data, a.arrayBuffer]); const H = e.useCallback((o, n) => { c.current && (f(!0), z(c.current), It({ pdfDoc: o, zoom: n, dom: c.current, done: (r) => { t.pages = r, t.zoom = n, f(!1); }, error: t.error })); }, []); e.useEffect(() => { j(g), c.current && t.document && g !== t.zoom && H(t.document, g); }, [g, H]), e.useEffect(() => () => { t.scroller && t.scroller.destroy(), t.search && t.search.destroy(), t.document = null, t.pages = []; }, []); const Le = e.useCallback(() => { W(!0), Ze(c.current); }, []), ze = e.useCallback( (o) => { const n = o.value, r = t.search.search({ text: n, matchCase: p }); k(r.length ? 1 : 0), R(r.length), U(n); }, [p] ), Ie = e.useCallback(() => { const o = t.search.search({ text: F, matchCase: !p }); k(o.length ? 1 : 0), R(o.length), K(!p); }, [p, F]), Me = e.useCallback(() => { te(t), k(h + 1 > b ? 1 : h + 1); }, [h, b]), Ne = e.useCallback(() => { Mt(t), k(h - 1 < 1 ? b : h - 1); }, [h, b]), $ = e.useCallback(() => { t.search.destroy(), k(0), R(0), K(!1), U(""), W(!1); }, []), Te = e.useCallback( (o) => { o.key === "Enter" ? (o.preventDefault(), te(t), k(h + 1 > b ? 1 : h + 1)) : o.key === "Escape" && $(); }, [h, b] ), Pe = e.useCallback( (o) => { if (c.current) { const n = o.skip; Y(c.current, n); const r = { page: n + 1, target: v.current, syntheticEvent: o.syntheticEvent }; a.onPageChange && a.onPageChange.call(void 0, r); } A(o.skip); }, [w, a.onPageChange] ), Re = e.useCallback( (o) => { if (L.current) { const n = Nt(L.current); if (n !== w) { A(n); const r = { page: n + 1, target: v.current, syntheticEvent: o }; a.onPageChange && a.onPageChange.call(void 0, r); } } }, [w, a.onPageChange] ), Fe = e.useCallback( (o) => { const n = Math.min(t.currentZoom + S, T); if (n !== t.currentZoom && t.document && (x(n), a.onZoom)) { const r = { zoom: n, target: v.current, syntheticEvent: o }; a.onZoom.call(void 0, r); } }, [S, T, a.onZoom] ), De = e.useCallback( (o) => { const n = Math.max(t.currentZoom - S, N); if (n !== t.currentZoom && t.document && (x(n), a.onZoom)) { const r = { zoom: n, target: v.current, syntheticEvent: o }; a.onZoom.call(void 0, r); } }, [S, N, a.onZoom] ), Oe = e.useCallback( (o) => { const n = o.value === null ? { text: "100%", value: 1, id: 100 } : { ...o.value }; if (n.value === void 0) { const m = parseFloat(n.text); typeof m == "number" && !Number.isNaN(m) ? n.value = m / 100 : n.value = 1; } let r = n ? Tt(n.value, n.type, t.currentZoom, c.current) : 1; if (r = Math.round(r * 100) / 100, t.currentZoom !== r && t.document && (x(r), a.onZoom)) { const m = { zoom: r, target: v.current, syntheticEvent: o.syntheticEvent }; a.onZoom.call(void 0, m); } }, [a.onZoom] ), Be = e.useCallback(() => { t.scroller.disablePanEventsTracking(), V(!0); }, []), Ae = e.useCallback(() => { t.scroller.enablePanEventsTracking(), V(!1); }, []), Ve = e.useCallback(() => { Pt( { pdf: t.document, error: t.error }, a.saveFileName, a.saveOptions, q ); }, [a.url, a.data, a.arrayBuffer, a.saveFileName, a.saveOptions, q]), We = e.useCallback(() => { f(!0); const o = (r) => { f(!1), t.error(r); }, n = () => { f(!1); }; Rt(t.pages, n, o); }, []), Ke = e.useCallback( (o) => { const n = o.newState; n[0] && n[0].getRawFile && n[0].getRawFile().arrayBuffer().then((m) => { if (c.current) { f(!0), z(c.current); const D = t.props.zoom === void 0 ? M : t.props.zoom; ee({ arrayBuffer: m, dom: c.current, zoom: D, done: t.done, error: t.error }), x(D); } }); }, [M] ), Ue = e.useCallback((o) => { const n = o.target; if (n instanceof Element && n.parentNode) { const r = n.closest(".k-toolbar"), m = r && r.querySelector(".k-upload input"); m && m.click(); } }, []), _ = Ee && /* @__PURE__ */ e.createElement("div", { className: "k-loader-container k-loader-container-md k-loader-top" }, /* @__PURE__ */ e.createElement("div", { className: "k-loader-container-overlay k-overlay-light" }), /* @__PURE__ */ e.createElement("div", { className: "k-loader-container-inner " }, /* @__PURE__ */ e.createElement(it, { size: "large" }))), je = /* @__PURE__ */ e.createElement(Q, { className: "k-toolbar-button-group k-button-group-solid" }, /* @__PURE__ */ e.createElement( d, { className: "k-group-start", fillMode: "flat", themeColor: "base", title: s.toLanguageString(ae, i[ae]), disabled: g <= N || !u, onClick: De, icon: "zoom-out", svgIcon: ht } ), /* @__PURE__ */ e.createElement( d, { className: "k-group-end", fillMode: "flat", themeColor: "base", title: s.toLanguageString(ne, i[ne]), disabled: g >= T || !u, onClick: Fe, icon: "zoom-in", svgIcon: vt } )), qe = /* @__PURE__ */ e.createElement( st, { className: "k-toolbar-combobox", disabled: !u, data: B.map((o) => ({ ...o, text: o.locationString ? s.toLanguageString(o.locationString, i[o.locationString]) : o.text })), dataItemKey: "id", textField: "text", value: u ? C : null, allowCustom: !0, onChange: Oe, placeholder: s.toLanguageString(re, i[re]) } ), He = /* @__PURE__ */ e.createElement( lt, { disabled: !u, previousNext: !0, type: "input", skip: w, take: 1, total: t.pages ? t.pages.length : 0, info: !1, onPageChange: Pe } ), $e = /* @__PURE__ */ e.createElement(ot, null), _e = /* @__PURE__ */ e.createElement(Q, { className: "k-toolbar-button-group k-button-group-solid" }, /* @__PURE__ */ e.createElement( d, { className: "k-group-start", fillMode: "flat", themeColor: "base", title: s.toLanguageString(le, i[le]), icon: "pointer", svgIcon: bt, disabled: !u, togglable: !0, selected: Z && u, onClick: Be } ), /* @__PURE__ */ e.createElement( d, { className: "k-group-end", fillMode: "flat", themeColor: "base", title: s.toLanguageString(ce, i[ce]), icon: "hand", svgIcon: kt, disabled: !u, togglable: !0, selected: !Z && u, onClick: Ae } )), Ge = /* @__PURE__ */ e.createElement( d, { className: "k-toolbar-button", fillMode: "flat", themeColor: "base", title: s.toLanguageString(I, i[I]), icon: "search", svgIcon: pt, disabled: !u, onClick: Le } ), Je = /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement( d, { className: "k-toolbar-button", fillMode: "flat", themeColor: "base", title: s.toLanguageString(se, i[se]), icon: "folder-open", svgIcon: yt, onClick: Ue } ), /* @__PURE__ */ e.createElement("div", { style: { display: "none" } }, /* @__PURE__ */ e.createElement( ct, { restrictions: { allowedExtensions: [".pdf"] }, onAdd: Ke, autoUpload: !1, defaultFiles: [], multiple: !1, accept: ".pdf,.PDF", withCredentials: !1 } ))), Qe = /* @__PURE__ */ e.createElement( d, { className: "k-toolbar-button", fillMode: "flat", themeColor: "base", title: s.toLanguageString(ie, i[ie]), icon: "download", svgIcon: Ct, disabled: !u, onClick: Ve } ), Xe = /* @__PURE__ */ e.createElement( d, { className: "k-toolbar-button", fillMode: "flat", themeColor: "base", title: s.toLanguageString(ue, i[ue]), icon: "print", svgIcon: Et, disabled: !u, onClick: We } ), Ye = { pager: He, spacer: $e, zoomInOut: je, zoom: qe, selection: _e, search: Ge, open: Je, download: Qe, print: Xe }, et = (a.tools || y.tools).map((o) => ({ ...Ye[o], key: "toobar-tool-" + o + Vt() })), G = /* @__PURE__ */ e.createElement(tt, { buttons: At }, ...et), J = /* @__PURE__ */ e.createElement( "div", { className: dt("k-canvas k-pdf-viewer-canvas k-pos-relative k-overflow-auto", { "k-enable-text-select": Z, "k-enable-panning": !Z }), onScroll: Re }, Se && /* @__PURE__ */ e.createElement("div", { className: "k-search-panel k-pos-sticky k-top-center" }, /* @__PURE__ */ e.createElement( at, { value: F, onChange: ze, placeholder: s.toLanguageString(I, i[I]), autoFocus: !0, onKeyDown: Te, suffix: () => /* @__PURE__ */ e.createElement(e.Fragment, null, /* @__PURE__ */ e.createElement(nt, null), /* @__PURE__ */ e.createElement(rt, null, /* @__PURE__ */ e.createElement( d, { icon: "convert-lowercase", svgIcon: St, title: s.toLanguageString(me, i[me]), fillMode: "flat", togglable: !0, selected: p, onClick: Ie } ))) } ), /* @__PURE__ */ e.createElement("span", { className: "k-search-matches" }, /* @__PURE__ */ e.createElement("span", null, h), " ", s.toLanguageString(de, i[de]), /* @__PURE__ */ e.createElement("span", null, b)), /* @__PURE__ */ e.createElement( d, { title: s.toLanguageString(ge, i[ge]), fillMode: "flat", icon: "arrow-up", svgIcon: xt, disabled: b === 0, onClick: Ne } ), /* @__PURE__ */ e.createElement( d, { title: s.toLanguageString(fe, i[fe]), fillMode: "flat", icon: "arrow-down", svgIcon: wt, disabled: b === 0, onClick: Me } ), /* @__PURE__ */ e.createElement( d, { title: s.toLanguageString(he, i[he]), fillMode: "flat", icon: "x", svgIcon: Zt, onClick: $ } )), /* @__PURE__ */ e.createElement("div", { ref: c, className: "k-pdf-viewer-pages" }) ); return /* @__PURE__ */ e.createElement("div", { className: "k-pdf-viewer", style: a.style, ref: L }, a.onRenderLoader ? a.onRenderLoader.call(void 0, _ || null) : _, a.onRenderToolbar ? a.onRenderToolbar.call(void 0, G) : G, a.onRenderContent ? a.onRenderContent.call(void 0, J) : J, pe && /* @__PURE__ */ e.createElement(gt, { message: ye })); }); ke.displayName = "KendoReactPDFViewer"; ke.propTypes = { url: l.string, data: l.string, arrayBuffer: l.any, typedArray: l.any, style: l.object, saveFileName: l.string, saveOptions: l.object, tools: l.arrayOf(l.oneOf(be).isRequired), zoomLevels: l.arrayOf(l.any), zoom: l.number, defaultZoom: l.number, minZoom: l.number, maxZoom: l.number, zoomRate: l.number, onError: l.func, onLoad: l.func, onDownload: l.func, onRenderToolbar: l.func, onRenderContent: l.func, onRenderLoader: l.func, onZoom: l.func }; export { ke as PDFViewer };