@kopexa/pdf-viewer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import {
3
+ PDFViewerLazy
4
+ } from "./chunk-P4M32GKY.mjs";
5
+ import "./chunk-UJB5LFCW.mjs";
6
+ export {
7
+ PDFViewerLazy
8
+ };
@@ -0,0 +1,71 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
3
+ export { pdfjs } from 'react-pdf';
4
+
5
+ /** Loaded PDF document info passed to the onDocumentLoad callback. */
6
+ type LoadedPDFDocument = {
7
+ numPages: number;
8
+ };
9
+
10
+ /**
11
+ * Configures the PDF.js worker. Call this once before rendering `PDFViewer`.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { configurePDFWorker } from "@kopexa/pdf-viewer";
16
+ *
17
+ * // Option 1: CDN (simplest, no bundler config needed)
18
+ * configurePDFWorker();
19
+ *
20
+ * // Option 2: Local worker via import.meta.url (recommended for production)
21
+ * configurePDFWorker(
22
+ * new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url).toString()
23
+ * );
24
+ *
25
+ * // Option 3: Custom URL (e.g. self-hosted worker)
26
+ * configurePDFWorker("https://cdn.example.com/pdf.worker.min.mjs");
27
+ * ```
28
+ */
29
+ declare function configurePDFWorker(workerSrc?: string): void;
30
+ type OnPDFViewerPageClick = (event: {
31
+ pageNumber: number;
32
+ numPages: number;
33
+ originalEvent: React.MouseEvent<HTMLDivElement, MouseEvent>;
34
+ pageHeight: number;
35
+ pageWidth: number;
36
+ pageX: number;
37
+ pageY: number;
38
+ }) => void | Promise<void>;
39
+ /** Supported PDF source types. */
40
+ type PDFSource = {
41
+ type: "url";
42
+ url: string;
43
+ } | {
44
+ type: "data";
45
+ data: Uint8Array;
46
+ } | {
47
+ type: "base64";
48
+ data: string;
49
+ };
50
+ type PDFViewerProps = {
51
+ className?: string;
52
+ /** The PDF source to render. */
53
+ source: PDFSource;
54
+ /** Called when the document has been loaded. */
55
+ onDocumentLoad?: (doc: LoadedPDFDocument) => void;
56
+ /** Called when a page is clicked with position information. */
57
+ onPageClick?: OnPDFViewerPageClick;
58
+ /** Optional custom page renderer component. */
59
+ customPageRenderer?: React.FunctionComponent;
60
+ /** Optional PDF.js options (e.g. cMapUrl). */
61
+ options?: Record<string, unknown>;
62
+ /** Content shown while the PDF loads. */
63
+ loadingContent?: React.ReactNode;
64
+ /** Content shown when loading fails. */
65
+ errorContent?: React.ReactNode;
66
+ /** Label for the page indicator. Receives `{page, total}`. */
67
+ pageLabel?: (page: number, total: number) => React.ReactNode;
68
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, "onPageClick">;
69
+ declare const PDFViewer: ({ className, source, onDocumentLoad, onPageClick, customPageRenderer, options, loadingContent, errorContent, pageLabel: pageLabelProp, ...props }: PDFViewerProps) => string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element;
70
+
71
+ export { type LoadedPDFDocument, type OnPDFViewerPageClick, type PDFSource, PDFViewer, type PDFViewerProps, configurePDFWorker };
@@ -0,0 +1,71 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
3
+ export { pdfjs } from 'react-pdf';
4
+
5
+ /** Loaded PDF document info passed to the onDocumentLoad callback. */
6
+ type LoadedPDFDocument = {
7
+ numPages: number;
8
+ };
9
+
10
+ /**
11
+ * Configures the PDF.js worker. Call this once before rendering `PDFViewer`.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { configurePDFWorker } from "@kopexa/pdf-viewer";
16
+ *
17
+ * // Option 1: CDN (simplest, no bundler config needed)
18
+ * configurePDFWorker();
19
+ *
20
+ * // Option 2: Local worker via import.meta.url (recommended for production)
21
+ * configurePDFWorker(
22
+ * new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url).toString()
23
+ * );
24
+ *
25
+ * // Option 3: Custom URL (e.g. self-hosted worker)
26
+ * configurePDFWorker("https://cdn.example.com/pdf.worker.min.mjs");
27
+ * ```
28
+ */
29
+ declare function configurePDFWorker(workerSrc?: string): void;
30
+ type OnPDFViewerPageClick = (event: {
31
+ pageNumber: number;
32
+ numPages: number;
33
+ originalEvent: React.MouseEvent<HTMLDivElement, MouseEvent>;
34
+ pageHeight: number;
35
+ pageWidth: number;
36
+ pageX: number;
37
+ pageY: number;
38
+ }) => void | Promise<void>;
39
+ /** Supported PDF source types. */
40
+ type PDFSource = {
41
+ type: "url";
42
+ url: string;
43
+ } | {
44
+ type: "data";
45
+ data: Uint8Array;
46
+ } | {
47
+ type: "base64";
48
+ data: string;
49
+ };
50
+ type PDFViewerProps = {
51
+ className?: string;
52
+ /** The PDF source to render. */
53
+ source: PDFSource;
54
+ /** Called when the document has been loaded. */
55
+ onDocumentLoad?: (doc: LoadedPDFDocument) => void;
56
+ /** Called when a page is clicked with position information. */
57
+ onPageClick?: OnPDFViewerPageClick;
58
+ /** Optional custom page renderer component. */
59
+ customPageRenderer?: React.FunctionComponent;
60
+ /** Optional PDF.js options (e.g. cMapUrl). */
61
+ options?: Record<string, unknown>;
62
+ /** Content shown while the PDF loads. */
63
+ loadingContent?: React.ReactNode;
64
+ /** Content shown when loading fails. */
65
+ errorContent?: React.ReactNode;
66
+ /** Label for the page indicator. Receives `{page, total}`. */
67
+ pageLabel?: (page: number, total: number) => React.ReactNode;
68
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, "onPageClick">;
69
+ declare const PDFViewer: ({ className, source, onDocumentLoad, onPageClick, customPageRenderer, options, loadingContent, errorContent, pageLabel: pageLabelProp, ...props }: PDFViewerProps) => string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element;
70
+
71
+ export { type LoadedPDFDocument, type OnPDFViewerPageClick, type PDFSource, PDFViewer, type PDFViewerProps, configurePDFWorker };
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ "use client";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/pdf-viewer.tsx
22
+ var pdf_viewer_exports = {};
23
+ __export(pdf_viewer_exports, {
24
+ PDFViewer: () => PDFViewer,
25
+ configurePDFWorker: () => configurePDFWorker,
26
+ pdfjs: () => import_react_pdf.pdfjs
27
+ });
28
+ module.exports = __toCommonJS(pdf_viewer_exports);
29
+ var import_i18n2 = require("@kopexa/i18n");
30
+ var import_motion_utils = require("@kopexa/motion-utils");
31
+ var import_shared_utils = require("@kopexa/shared-utils");
32
+ var import_react = require("motion/react");
33
+ var import_react2 = require("react");
34
+ var import_react_pdf = require("react-pdf");
35
+ var import_AnnotationLayer = require("react-pdf/dist/Page/AnnotationLayer.css");
36
+ var import_TextLayer = require("react-pdf/dist/Page/TextLayer.css");
37
+
38
+ // src/messages.ts
39
+ var import_i18n = require("@kopexa/i18n");
40
+ var messages = (0, import_i18n.defineMessages)({
41
+ loading: {
42
+ id: "pdf_viewer.loading",
43
+ defaultMessage: "Loading document\u2026",
44
+ description: "Shown while the PDF document is loading"
45
+ },
46
+ error_title: {
47
+ id: "pdf_viewer.error_title",
48
+ defaultMessage: "Something went wrong while loading the document.",
49
+ description: "Primary error message when PDF fails to load"
50
+ },
51
+ error_hint: {
52
+ id: "pdf_viewer.error_hint",
53
+ defaultMessage: "Please try again.",
54
+ description: "Secondary error message hinting the user to retry"
55
+ },
56
+ page_label: {
57
+ id: "pdf_viewer.page_label",
58
+ defaultMessage: "Page {page} of {total}",
59
+ description: "Page indicator below each page of the PDF"
60
+ }
61
+ });
62
+
63
+ // src/pdf-viewer.tsx
64
+ var import_jsx_runtime = require("react/jsx-runtime");
65
+ function configurePDFWorker(workerSrc) {
66
+ if (typeof window === "undefined") return;
67
+ import_react_pdf.pdfjs.GlobalWorkerOptions.workerSrc = workerSrc != null ? workerSrc : `https://unpkg.com/pdfjs-dist@${import_react_pdf.pdfjs.version}/build/pdf.worker.min.mjs`;
68
+ }
69
+ var PAGE_SELECTOR = "[data-page-number]";
70
+ var FADE_TRANSITION = {
71
+ duration: 0.4,
72
+ ease: import_motion_utils.TRANSITION_EASINGS.ease
73
+ };
74
+ var PDFViewer = ({
75
+ className,
76
+ source,
77
+ onDocumentLoad,
78
+ onPageClick,
79
+ customPageRenderer,
80
+ options,
81
+ loadingContent,
82
+ errorContent,
83
+ pageLabel: pageLabelProp,
84
+ ...props
85
+ }) => {
86
+ var _a;
87
+ const intl = (0, import_i18n2.useSafeIntl)();
88
+ const $el = (0, import_react2.useRef)(null);
89
+ const [width, setWidth] = (0, import_react2.useState)(0);
90
+ const [numPages, setNumPages] = (0, import_react2.useState)(0);
91
+ const [pdfError, setPdfError] = (0, import_react2.useState)(false);
92
+ const sourceUrl = source.type === "url" ? source.url : null;
93
+ const sourceData = source.type === "data" ? source.data : null;
94
+ const sourceBase64 = source.type === "base64" ? source.data : null;
95
+ const fileRef = (0, import_react2.useRef)(null);
96
+ const prevKeyRef = (0, import_react2.useRef)(null);
97
+ const currentKey = (_a = sourceUrl != null ? sourceUrl : sourceData) != null ? _a : sourceBase64;
98
+ if (currentKey !== prevKeyRef.current) {
99
+ prevKeyRef.current = currentKey;
100
+ if (sourceUrl) {
101
+ fileRef.current = sourceUrl;
102
+ } else if (sourceData) {
103
+ fileRef.current = { data: sourceData };
104
+ } else if (sourceBase64) {
105
+ fileRef.current = { data: base64ToUint8Array(sourceBase64) };
106
+ } else {
107
+ fileRef.current = null;
108
+ }
109
+ }
110
+ const file = fileRef.current;
111
+ const onDocumentLoaded = (doc) => {
112
+ setNumPages(doc.numPages);
113
+ onDocumentLoad == null ? void 0 : onDocumentLoad(doc);
114
+ };
115
+ const onDocumentPageClick = (event, pageNumber) => {
116
+ const target = event.target instanceof HTMLElement ? event.target : null;
117
+ if (!target) return;
118
+ const $page = target.closest(PAGE_SELECTOR);
119
+ if (!$page) return;
120
+ const { height, width: width2, top, left } = $page.getBoundingClientRect();
121
+ onPageClick == null ? void 0 : onPageClick({
122
+ pageNumber,
123
+ numPages,
124
+ originalEvent: event,
125
+ pageHeight: height,
126
+ pageWidth: width2,
127
+ pageX: event.clientX - left,
128
+ pageY: event.clientY - top
129
+ });
130
+ };
131
+ (0, import_react2.useEffect)(() => {
132
+ if (!$el.current) return;
133
+ const $current = $el.current;
134
+ const updateWidth = () => {
135
+ setWidth($current.getBoundingClientRect().width);
136
+ };
137
+ updateWidth();
138
+ window.addEventListener("resize", updateWidth);
139
+ return () => window.removeEventListener("resize", updateWidth);
140
+ }, []);
141
+ const loader = loadingContent != null ? loadingContent : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex h-[80vh] max-h-[60rem] flex-col items-center justify-center", children: [
142
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-12 w-12 animate-spin rounded-full border-4 border-muted border-t-primary" }),
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mt-4 text-muted-foreground", children: intl.formatMessage(messages.loading) })
144
+ ] });
145
+ const error = errorContent != null ? errorContent : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex h-[80vh] max-h-[60rem] flex-col items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-center text-muted-foreground", children: [
146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: intl.formatMessage(messages.error_title) }),
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mt-1 text-sm", children: intl.formatMessage(messages.error_hint) })
148
+ ] }) });
149
+ if (!file) return error;
150
+ const isLoaded = numPages > 0;
151
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.LazyMotion, { features: import_react.domAnimation, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: $el, className: (0, import_shared_utils.cn)("overflow-hidden", className), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
152
+ import_react_pdf.Document,
153
+ {
154
+ file,
155
+ className: (0, import_shared_utils.cn)("w-full overflow-hidden rounded", {
156
+ "h-[80vh] max-h-[60rem]": !isLoaded
157
+ }),
158
+ onLoadSuccess: onDocumentLoaded,
159
+ onSourceError: () => setPdfError(true),
160
+ externalLinkTarget: "_blank",
161
+ loading: pdfError ? error : loader,
162
+ error,
163
+ options,
164
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.AnimatePresence, { mode: "wait", children: !isLoaded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
165
+ import_react.m.div,
166
+ {
167
+ initial: { opacity: 1 },
168
+ exit: { opacity: 0 },
169
+ transition: FADE_TRANSITION,
170
+ children: pdfError ? error : loader
171
+ },
172
+ "loader"
173
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
174
+ import_react.m.div,
175
+ {
176
+ initial: { opacity: 0 },
177
+ animate: { opacity: 1 },
178
+ transition: FADE_TRANSITION,
179
+ children: Array.from({ length: numPages }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "last:-mb-2", children: [
180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "overflow-hidden rounded border border-border will-change-transform", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
181
+ import_react_pdf.Page,
182
+ {
183
+ pageNumber: i + 1,
184
+ width,
185
+ renderAnnotationLayer: true,
186
+ renderTextLayer: true,
187
+ loading: () => "",
188
+ renderMode: customPageRenderer ? "custom" : "canvas",
189
+ customRenderer: customPageRenderer,
190
+ onClick: (e) => onDocumentPageClick(e, i + 1)
191
+ }
192
+ ) }),
193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "my-2 text-center text-[11px] text-muted-foreground/80", children: pageLabelProp ? pageLabelProp(i + 1, numPages) : intl.formatMessage(messages.page_label, {
194
+ page: i + 1,
195
+ total: numPages
196
+ }) })
197
+ ] }, `page-${i + 1}`))
198
+ },
199
+ "pages"
200
+ ) })
201
+ }
202
+ ) }) });
203
+ };
204
+ function base64ToUint8Array(base64String) {
205
+ const binaryString = atob(base64String);
206
+ const bytes = new Uint8Array(binaryString.length);
207
+ for (let i = 0; i < binaryString.length; i++) {
208
+ bytes[i] = binaryString.charCodeAt(i);
209
+ }
210
+ return bytes;
211
+ }
212
+ // Annotate the CommonJS export names for ESM import in node:
213
+ 0 && (module.exports = {
214
+ PDFViewer,
215
+ configurePDFWorker,
216
+ pdfjs
217
+ });
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import {
3
+ PDFViewer,
4
+ configurePDFWorker,
5
+ pdfjs
6
+ } from "./chunk-EIAXW4SI.mjs";
7
+ import "./chunk-UJB5LFCW.mjs";
8
+ export {
9
+ PDFViewer,
10
+ configurePDFWorker,
11
+ pdfjs
12
+ };
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@kopexa/pdf-viewer",
3
+ "version": "1.0.0",
4
+ "description": "A PDF viewer component powered by react-pdf",
5
+ "keywords": [
6
+ "pdf-viewer"
7
+ ],
8
+ "author": "Kopexa <hello@kopexa.com>",
9
+ "homepage": "https://kopexa.com",
10
+ "license": "Apache-2.0",
11
+ "main": "dist/index.js",
12
+ "sideEffects": [
13
+ "**/*.css"
14
+ ],
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/kopexa-grc/sight.git",
24
+ "directory": "packages/components/pdf-viewer"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/kopexa-grc/sight/issues"
28
+ },
29
+ "peerDependencies": {
30
+ "react": ">=19.0.0-rc.0",
31
+ "react-dom": ">=19.0.0-rc.0",
32
+ "react-pdf": ">=9.0.0",
33
+ "motion": ">=12.23.6"
34
+ },
35
+ "dependencies": {
36
+ "@kopexa/use-hydrated": "1.0.0",
37
+ "@kopexa/i18n": "17.9.0",
38
+ "@kopexa/motion-utils": "17.0.58",
39
+ "@kopexa/shared-utils": "17.0.58",
40
+ "@kopexa/react-utils": "17.1.0"
41
+ },
42
+ "clean-package": "../../../clean-package.config.json",
43
+ "tsup": {
44
+ "clean": true,
45
+ "target": "es2019",
46
+ "format": [
47
+ "cjs",
48
+ "esm"
49
+ ]
50
+ },
51
+ "module": "dist/index.mjs",
52
+ "types": "dist/index.d.ts",
53
+ "exports": {
54
+ ".": {
55
+ "types": "./dist/index.d.ts",
56
+ "import": "./dist/index.mjs",
57
+ "require": "./dist/index.js"
58
+ },
59
+ "./package.json": "./package.json"
60
+ },
61
+ "scripts": {
62
+ "build": "tsup src --dts",
63
+ "build:fast": "tsup src",
64
+ "dev": "pnpm build:fast --watch",
65
+ "clean": "rimraf dist .turbo",
66
+ "typecheck": "tsc --noEmit"
67
+ }
68
+ }