@progress/kendo-react-pdf-viewer
Version:
KendoReact PDFViewer package
562 lines (561 loc) • 18.5 kB
JavaScript
/**
* @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
};