@relevaince/document-viewer 0.1.0 → 0.1.1

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.
package/dist/index.js CHANGED
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
6
9
  var __export = (target, all) => {
7
10
  for (var name in all)
8
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,28 +20,11 @@ var __copyProps = (to, from, except, desc) => {
17
20
  };
18
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
22
 
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- DocumentViewer: () => DocumentViewer,
24
- getFileType: () => getFileType
25
- });
26
- module.exports = __toCommonJS(index_exports);
27
-
28
- // src/utils/getFileType.ts
29
- function getFileType(url) {
30
- const clean = url.split("?")[0].split("#")[0].toLowerCase();
31
- if (clean.endsWith(".pdf")) return "pdf";
32
- if (clean.endsWith(".docx") || clean.endsWith(".doc")) return "docx";
33
- return "unknown";
34
- }
35
-
36
23
  // src/components/renderers/PdfRenderer.tsx
37
- var import_react = require("react");
38
- var import_react_pdf_highlighter_extended = require("react-pdf-highlighter-extended");
39
- var import_jsx_runtime = require("react/jsx-runtime");
40
- var CITATION_ID = "citation-highlight";
41
- var DEFAULT_WORKER_URL = "https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs";
24
+ var PdfRenderer_exports = {};
25
+ __export(PdfRenderer_exports, {
26
+ PdfRenderer: () => PdfRenderer
27
+ });
42
28
  function bboxToHighlight(target) {
43
29
  if (!target.bbox) return null;
44
30
  const page = target.page ?? 1;
@@ -64,7 +50,7 @@ function bboxToHighlight(target) {
64
50
  }
65
51
  function CitationHighlightRenderer() {
66
52
  const { highlight, isScrolledTo } = (0, import_react_pdf_highlighter_extended.useHighlightContainerContext)();
67
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
68
54
  import_react_pdf_highlighter_extended.AreaHighlight,
69
55
  {
70
56
  highlight,
@@ -102,12 +88,12 @@ function PdfRenderer({
102
88
  const handleUtilsRef = (0, import_react.useCallback)((utils) => {
103
89
  highlighterRef.current = utils;
104
90
  }, []);
105
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
91
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
106
92
  import_react_pdf_highlighter_extended.PdfLoader,
107
93
  {
108
94
  document: target.url,
109
95
  workerSrc: workerUrl ?? DEFAULT_WORKER_URL,
110
- beforeLoad: () => loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
96
+ beforeLoad: () => loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
111
97
  "div",
112
98
  {
113
99
  style: {
@@ -125,7 +111,7 @@ function PdfRenderer({
125
111
  ),
126
112
  errorMessage: (error) => {
127
113
  onLoadError?.(error);
128
- return errorComponent ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
114
+ return errorComponent ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
129
115
  "div",
130
116
  {
131
117
  style: {
@@ -147,7 +133,7 @@ function PdfRenderer({
147
133
  loadNotifiedRef.current = true;
148
134
  onLoadSuccess?.();
149
135
  }
150
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
136
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
151
137
  import_react_pdf_highlighter_extended.PdfHighlighter,
152
138
  {
153
139
  pdfDocument,
@@ -157,21 +143,51 @@ function PdfRenderer({
157
143
  width: "100%",
158
144
  height: "100%"
159
145
  },
160
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CitationHighlightRenderer, {})
146
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CitationHighlightRenderer, {})
161
147
  }
162
148
  );
163
149
  }
164
150
  }
165
151
  ) });
166
152
  }
153
+ var import_react, import_react_pdf_highlighter_extended, import_jsx_runtime2, CITATION_ID, DEFAULT_WORKER_URL;
154
+ var init_PdfRenderer = __esm({
155
+ "src/components/renderers/PdfRenderer.tsx"() {
156
+ "use strict";
157
+ import_react = require("react");
158
+ import_react_pdf_highlighter_extended = require("react-pdf-highlighter-extended");
159
+ import_jsx_runtime2 = require("react/jsx-runtime");
160
+ CITATION_ID = "citation-highlight";
161
+ DEFAULT_WORKER_URL = "https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs";
162
+ }
163
+ });
164
+
165
+ // src/index.ts
166
+ var index_exports = {};
167
+ __export(index_exports, {
168
+ DocumentViewer: () => DocumentViewer,
169
+ getFileType: () => getFileType
170
+ });
171
+ module.exports = __toCommonJS(index_exports);
172
+
173
+ // src/components/DocumentViewer.tsx
174
+ var import_react2 = require("react");
175
+
176
+ // src/utils/getFileType.ts
177
+ function getFileType(url) {
178
+ const clean = url.split("?")[0].split("#")[0].toLowerCase();
179
+ if (clean.endsWith(".pdf")) return "pdf";
180
+ if (clean.endsWith(".docx") || clean.endsWith(".doc")) return "docx";
181
+ return "unknown";
182
+ }
167
183
 
168
184
  // src/components/renderers/DocxRenderer.tsx
169
- var import_jsx_runtime2 = require("react/jsx-runtime");
185
+ var import_jsx_runtime = require("react/jsx-runtime");
170
186
  function DocxRenderer({ target }) {
171
187
  const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
172
188
  target.url
173
189
  )}`;
174
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
190
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
175
191
  "iframe",
176
192
  {
177
193
  src: officeUrl,
@@ -189,6 +205,20 @@ function DocxRenderer({ target }) {
189
205
 
190
206
  // src/components/DocumentViewer.tsx
191
207
  var import_jsx_runtime3 = require("react/jsx-runtime");
208
+ var LazyPdfRenderer = (0, import_react2.lazy)(
209
+ () => Promise.resolve().then(() => (init_PdfRenderer(), PdfRenderer_exports)).then((mod) => ({
210
+ default: mod.PdfRenderer
211
+ }))
212
+ );
213
+ function ClientOnly({
214
+ children,
215
+ fallback
216
+ }) {
217
+ const [mounted, setMounted] = (0, import_react2.useState)(false);
218
+ (0, import_react2.useEffect)(() => setMounted(true), []);
219
+ if (!mounted) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: fallback ?? null });
220
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
221
+ }
192
222
  function DocumentViewer({
193
223
  target,
194
224
  pdfWorkerUrl,
@@ -209,9 +239,25 @@ function DocumentViewer({
209
239
  borderRadius: 8,
210
240
  background: "#fafafa"
211
241
  };
242
+ const loadingFallback = loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
243
+ "div",
244
+ {
245
+ style: {
246
+ display: "flex",
247
+ alignItems: "center",
248
+ justifyContent: "center",
249
+ width: "100%",
250
+ height: "100%",
251
+ color: "#888",
252
+ fontSize: 14,
253
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
254
+ },
255
+ children: "Loading\u2026"
256
+ }
257
+ );
212
258
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: wrapperStyle, children: [
213
- type === "pdf" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
214
- PdfRenderer,
259
+ type === "pdf" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ClientOnly, { fallback: loadingFallback, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react2.Suspense, { fallback: loadingFallback, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
260
+ LazyPdfRenderer,
215
261
  {
216
262
  target,
217
263
  workerUrl: pdfWorkerUrl,
@@ -220,7 +266,7 @@ function DocumentViewer({
220
266
  onLoadSuccess,
221
267
  onLoadError
222
268
  }
223
- ),
269
+ ) }) }),
224
270
  type === "docx" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DocxRenderer, { target }),
225
271
  type === "unknown" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
226
272
  "div",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils/getFileType.ts","../src/components/renderers/PdfRenderer.tsx","../src/components/renderers/DocxRenderer.tsx","../src/components/DocumentViewer.tsx"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* Components */\n/* ------------------------------------------------------------------ */\nexport { DocumentViewer } from \"./components/DocumentViewer\";\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\nexport type { ViewerTarget, BBox } from \"./types/ViewerTarget\";\nexport type { DocumentViewerProps } from \"./types/DocumentViewerProps\";\n\n/* ------------------------------------------------------------------ */\n/* Utilities */\n/* ------------------------------------------------------------------ */\nexport { getFileType } from \"./utils/getFileType\";\n","/**\n * Infer document type from URL extension.\n * Strips query-strings and fragments before matching.\n */\nexport function getFileType(url: string): \"pdf\" | \"docx\" | \"unknown\" {\n const clean = url.split(\"?\")[0].split(\"#\")[0].toLowerCase();\n\n if (clean.endsWith(\".pdf\")) return \"pdf\";\n if (clean.endsWith(\".docx\") || clean.endsWith(\".doc\")) return \"docx\";\n\n return \"unknown\";\n}\n","import React, { useRef, useEffect, useMemo, useCallback } from \"react\";\nimport {\n PdfLoader,\n PdfHighlighter,\n AreaHighlight,\n useHighlightContainerContext,\n} from \"react-pdf-highlighter-extended\";\nimport type {\n Highlight,\n ScaledPosition,\n PdfHighlighterUtils,\n} from \"react-pdf-highlighter-extended\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/* ------------------------------------------------------------------ */\n/* Constants */\n/* ------------------------------------------------------------------ */\n\nconst CITATION_ID = \"citation-highlight\";\n\nconst DEFAULT_WORKER_URL =\n \"https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs\";\n\n/* ------------------------------------------------------------------ */\n/* BBox → Highlight conversion */\n/* ------------------------------------------------------------------ */\n\n/**\n * Convert our normalised (0-1) BBox into the Scaled coordinate system\n * used by react-pdf-highlighter-extended.\n *\n * The library internally computes:\n * viewportX = (viewportWidth × scaled.x1) / scaled.width\n *\n * So `scaled.x1 / scaled.width` must equal the normalised value.\n * We simply store the 0-1 value directly and set width / height to 1.\n */\nfunction bboxToHighlight(target: ViewerTarget): Highlight | null {\n if (!target.bbox) return null;\n\n const page = target.page ?? 1;\n const { x1, y1, x2, y2 } = target.bbox;\n\n const scaledRect = {\n x1,\n y1,\n x2,\n y2,\n width: 1,\n height: 1,\n pageNumber: page,\n };\n\n const position: ScaledPosition = {\n boundingRect: scaledRect,\n rects: [scaledRect],\n };\n\n return {\n id: CITATION_ID,\n type: \"area\",\n position,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Highlight child renderer */\n/* ------------------------------------------------------------------ */\n\n/**\n * Rendered once per highlight by PdfHighlighter's internal loop.\n * Uses context to access the highlight's viewport position.\n */\nfunction CitationHighlightRenderer() {\n const { highlight, isScrolledTo } = useHighlightContainerContext();\n\n return (\n <AreaHighlight\n highlight={highlight}\n isScrolledTo={isScrolledTo}\n style={{\n background: isScrolledTo\n ? \"rgba(255, 171, 0, 0.35)\"\n : \"rgba(255, 171, 0, 0.2)\",\n border: \"1.5px solid rgba(255, 171, 0, 0.6)\",\n borderRadius: 2,\n transition: \"background 200ms ease\",\n }}\n />\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* PdfRenderer */\n/* ------------------------------------------------------------------ */\n\ninterface PdfRendererProps {\n target: ViewerTarget;\n workerUrl?: string;\n loadingComponent?: React.ReactNode;\n errorComponent?: React.ReactNode;\n onLoadSuccess?: () => void;\n onLoadError?: (err: Error) => void;\n}\n\nexport function PdfRenderer({\n target,\n workerUrl,\n loadingComponent,\n errorComponent,\n onLoadSuccess,\n onLoadError,\n}: PdfRendererProps) {\n const highlighterRef = useRef<PdfHighlighterUtils | null>(null);\n const loadNotifiedRef = useRef(false);\n\n /* Build highlight array from target bbox */\n const highlights = useMemo(() => {\n const h = bboxToHighlight(target);\n return h ? [h] : [];\n }, [target]);\n\n /* Scroll to the citation highlight once the viewer is ready */\n useEffect(() => {\n if (highlights.length === 0) return;\n\n const timer = setTimeout(() => {\n highlighterRef.current?.scrollToHighlight(highlights[0]);\n }, 400);\n\n return () => clearTimeout(timer);\n }, [highlights]);\n\n /* Stable callback so we don't re-trigger PdfHighlighter renders */\n const handleUtilsRef = useCallback((utils: PdfHighlighterUtils) => {\n highlighterRef.current = utils;\n }, []);\n\n return (\n <div style={{ width: \"100%\", height: \"100%\", position: \"relative\" }}>\n <PdfLoader\n document={target.url}\n workerSrc={workerUrl ?? DEFAULT_WORKER_URL}\n beforeLoad={() =>\n loadingComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Loading PDF…\n </div>\n )\n }\n errorMessage={(error) => {\n onLoadError?.(error);\n return (\n errorComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#ef4444\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Failed to load PDF\n </div>\n )\n );\n }}\n >\n {(pdfDocument) => {\n /* Notify once */\n if (!loadNotifiedRef.current) {\n loadNotifiedRef.current = true;\n onLoadSuccess?.();\n }\n\n return (\n <PdfHighlighter\n pdfDocument={pdfDocument}\n highlights={highlights}\n utilsRef={handleUtilsRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n }}\n >\n <CitationHighlightRenderer />\n </PdfHighlighter>\n );\n }}\n </PdfLoader>\n </div>\n );\n}\n","import React from \"react\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/**\n * Renders DOCX / DOC files via the free Microsoft Office Online embed viewer.\n * The document URL must be publicly accessible.\n */\nexport function DocxRenderer({ target }: { target: ViewerTarget }) {\n const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(\n target.url\n )}`;\n\n return (\n <iframe\n src={officeUrl}\n title={target.title ?? \"Document preview\"}\n style={{\n width: \"100%\",\n height: \"100%\",\n border: \"none\",\n borderRadius: 6,\n background: \"#fff\",\n }}\n />\n );\n}\n","import React from \"react\";\nimport type { DocumentViewerProps } from \"../types/DocumentViewerProps\";\nimport { getFileType } from \"../utils/getFileType\";\nimport { PdfRenderer } from \"./renderers/PdfRenderer\";\nimport { DocxRenderer } from \"./renderers/DocxRenderer\";\n\n/**\n * Top-level component that routes to the correct renderer\n * based on the target's file type.\n *\n * ```tsx\n * <DocumentViewer\n * target={{\n * url: \"https://example.com/report.pdf\",\n * page: 3,\n * bbox: { x1: 0.1, y1: 0.2, x2: 0.5, y2: 0.3 },\n * }}\n * />\n * ```\n */\nexport function DocumentViewer({\n target,\n pdfWorkerUrl,\n loadingComponent,\n errorComponent,\n onLoadStart,\n onLoadSuccess,\n onLoadError,\n className,\n}: DocumentViewerProps) {\n if (!target) return null;\n\n const type = target.fileType ?? getFileType(target.url);\n\n const wrapperStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n borderRadius: 8,\n background: \"#fafafa\",\n };\n\n return (\n <div className={className} style={wrapperStyle}>\n {type === \"pdf\" && (\n <PdfRenderer\n target={target}\n workerUrl={pdfWorkerUrl}\n loadingComponent={loadingComponent}\n errorComponent={errorComponent}\n onLoadSuccess={onLoadSuccess}\n onLoadError={onLoadError}\n />\n )}\n\n {type === \"docx\" && <DocxRenderer target={target} />}\n\n {type === \"unknown\" && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Unsupported file type\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/components/renderers/PdfRenderer.tsx","../src/index.ts","../src/components/DocumentViewer.tsx","../src/utils/getFileType.ts","../src/components/renderers/DocxRenderer.tsx"],"sourcesContent":["import React, { useRef, useEffect, useMemo, useCallback } from \"react\";\nimport {\n PdfLoader,\n PdfHighlighter,\n AreaHighlight,\n useHighlightContainerContext,\n} from \"react-pdf-highlighter-extended\";\nimport type {\n Highlight,\n ScaledPosition,\n PdfHighlighterUtils,\n} from \"react-pdf-highlighter-extended\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/* ------------------------------------------------------------------ */\n/* Constants */\n/* ------------------------------------------------------------------ */\n\nconst CITATION_ID = \"citation-highlight\";\n\nconst DEFAULT_WORKER_URL =\n \"https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs\";\n\n/* ------------------------------------------------------------------ */\n/* BBox → Highlight conversion */\n/* ------------------------------------------------------------------ */\n\n/**\n * Convert our normalised (0-1) BBox into the Scaled coordinate system\n * used by react-pdf-highlighter-extended.\n *\n * The library internally computes:\n * viewportX = (viewportWidth × scaled.x1) / scaled.width\n *\n * So `scaled.x1 / scaled.width` must equal the normalised value.\n * We simply store the 0-1 value directly and set width / height to 1.\n */\nfunction bboxToHighlight(target: ViewerTarget): Highlight | null {\n if (!target.bbox) return null;\n\n const page = target.page ?? 1;\n const { x1, y1, x2, y2 } = target.bbox;\n\n const scaledRect = {\n x1,\n y1,\n x2,\n y2,\n width: 1,\n height: 1,\n pageNumber: page,\n };\n\n const position: ScaledPosition = {\n boundingRect: scaledRect,\n rects: [scaledRect],\n };\n\n return {\n id: CITATION_ID,\n type: \"area\",\n position,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Highlight child renderer */\n/* ------------------------------------------------------------------ */\n\n/**\n * Rendered once per highlight by PdfHighlighter's internal loop.\n * Uses context to access the highlight's viewport position.\n */\nfunction CitationHighlightRenderer() {\n const { highlight, isScrolledTo } = useHighlightContainerContext();\n\n return (\n <AreaHighlight\n highlight={highlight}\n isScrolledTo={isScrolledTo}\n style={{\n background: isScrolledTo\n ? \"rgba(255, 171, 0, 0.35)\"\n : \"rgba(255, 171, 0, 0.2)\",\n border: \"1.5px solid rgba(255, 171, 0, 0.6)\",\n borderRadius: 2,\n transition: \"background 200ms ease\",\n }}\n />\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* PdfRenderer */\n/* ------------------------------------------------------------------ */\n\ninterface PdfRendererProps {\n target: ViewerTarget;\n workerUrl?: string;\n loadingComponent?: React.ReactNode;\n errorComponent?: React.ReactNode;\n onLoadSuccess?: () => void;\n onLoadError?: (err: Error) => void;\n}\n\nexport function PdfRenderer({\n target,\n workerUrl,\n loadingComponent,\n errorComponent,\n onLoadSuccess,\n onLoadError,\n}: PdfRendererProps) {\n const highlighterRef = useRef<PdfHighlighterUtils | null>(null);\n const loadNotifiedRef = useRef(false);\n\n /* Build highlight array from target bbox */\n const highlights = useMemo(() => {\n const h = bboxToHighlight(target);\n return h ? [h] : [];\n }, [target]);\n\n /* Scroll to the citation highlight once the viewer is ready */\n useEffect(() => {\n if (highlights.length === 0) return;\n\n const timer = setTimeout(() => {\n highlighterRef.current?.scrollToHighlight(highlights[0]);\n }, 400);\n\n return () => clearTimeout(timer);\n }, [highlights]);\n\n /* Stable callback so we don't re-trigger PdfHighlighter renders */\n const handleUtilsRef = useCallback((utils: PdfHighlighterUtils) => {\n highlighterRef.current = utils;\n }, []);\n\n return (\n <div style={{ width: \"100%\", height: \"100%\", position: \"relative\" }}>\n <PdfLoader\n document={target.url}\n workerSrc={workerUrl ?? DEFAULT_WORKER_URL}\n beforeLoad={() =>\n loadingComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Loading PDF…\n </div>\n )\n }\n errorMessage={(error) => {\n onLoadError?.(error);\n return (\n errorComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#ef4444\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Failed to load PDF\n </div>\n )\n );\n }}\n >\n {(pdfDocument) => {\n /* Notify once */\n if (!loadNotifiedRef.current) {\n loadNotifiedRef.current = true;\n onLoadSuccess?.();\n }\n\n return (\n <PdfHighlighter\n pdfDocument={pdfDocument}\n highlights={highlights}\n utilsRef={handleUtilsRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n }}\n >\n <CitationHighlightRenderer />\n </PdfHighlighter>\n );\n }}\n </PdfLoader>\n </div>\n );\n}\n","/* ------------------------------------------------------------------ */\n/* Components */\n/* ------------------------------------------------------------------ */\nexport { DocumentViewer } from \"./components/DocumentViewer\";\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\nexport type { ViewerTarget, BBox } from \"./types/ViewerTarget\";\nexport type { DocumentViewerProps } from \"./types/DocumentViewerProps\";\n\n/* ------------------------------------------------------------------ */\n/* Utilities */\n/* ------------------------------------------------------------------ */\nexport { getFileType } from \"./utils/getFileType\";\n","import React, { lazy, Suspense, useState, useEffect } from \"react\";\nimport type { DocumentViewerProps } from \"../types/DocumentViewerProps\";\nimport { getFileType } from \"../utils/getFileType\";\nimport { DocxRenderer } from \"./renderers/DocxRenderer\";\n\n/* ------------------------------------------------------------------ */\n/* Lazy-load PdfRenderer so pdfjs-dist never runs on the server. */\n/* pdfjs-dist references `window` / `document` at the module level, */\n/* which crashes during Next.js SSR. */\n/* ------------------------------------------------------------------ */\n\nconst LazyPdfRenderer = lazy(() =>\n import(\"./renderers/PdfRenderer\").then((mod) => ({\n default: mod.PdfRenderer,\n }))\n);\n\n/**\n * Tiny wrapper that defers children until after first client-side mount.\n * Prevents any browser-only code from executing during SSR.\n */\nfunction ClientOnly({\n children,\n fallback,\n}: {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n}) {\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n if (!mounted) return <>{fallback ?? null}</>;\n return <>{children}</>;\n}\n\n/**\n * Top-level component that routes to the correct renderer\n * based on the target's file type.\n *\n * ```tsx\n * <DocumentViewer\n * target={{\n * url: \"https://example.com/report.pdf\",\n * page: 3,\n * bbox: { x1: 0.1, y1: 0.2, x2: 0.5, y2: 0.3 },\n * }}\n * />\n * ```\n */\nexport function DocumentViewer({\n target,\n pdfWorkerUrl,\n loadingComponent,\n errorComponent,\n onLoadStart,\n onLoadSuccess,\n onLoadError,\n className,\n}: DocumentViewerProps) {\n if (!target) return null;\n\n const type = target.fileType ?? getFileType(target.url);\n\n const wrapperStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n borderRadius: 8,\n background: \"#fafafa\",\n };\n\n const loadingFallback = loadingComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Loading…\n </div>\n );\n\n return (\n <div className={className} style={wrapperStyle}>\n {type === \"pdf\" && (\n <ClientOnly fallback={loadingFallback}>\n <Suspense fallback={loadingFallback}>\n <LazyPdfRenderer\n target={target}\n workerUrl={pdfWorkerUrl}\n loadingComponent={loadingComponent}\n errorComponent={errorComponent}\n onLoadSuccess={onLoadSuccess}\n onLoadError={onLoadError}\n />\n </Suspense>\n </ClientOnly>\n )}\n\n {type === \"docx\" && <DocxRenderer target={target} />}\n\n {type === \"unknown\" && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Unsupported file type\n </div>\n )}\n </div>\n );\n}\n","/**\n * Infer document type from URL extension.\n * Strips query-strings and fragments before matching.\n */\nexport function getFileType(url: string): \"pdf\" | \"docx\" | \"unknown\" {\n const clean = url.split(\"?\")[0].split(\"#\")[0].toLowerCase();\n\n if (clean.endsWith(\".pdf\")) return \"pdf\";\n if (clean.endsWith(\".docx\") || clean.endsWith(\".doc\")) return \"docx\";\n\n return \"unknown\";\n}\n","import React from \"react\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/**\n * Renders DOCX / DOC files via the free Microsoft Office Online embed viewer.\n * The document URL must be publicly accessible.\n */\nexport function DocxRenderer({ target }: { target: ViewerTarget }) {\n const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(\n target.url\n )}`;\n\n return (\n <iframe\n src={officeUrl}\n title={target.title ?? \"Document preview\"}\n style={{\n width: \"100%\",\n height: \"100%\",\n border: \"none\",\n borderRadius: 6,\n background: \"#fff\",\n }}\n />\n );\n}\n"],"mappings":"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","names":["import_jsx_runtime","import_react","import_jsx_runtime"]}
package/dist/index.mjs CHANGED
@@ -1,12 +1,18 @@
1
- // src/utils/getFileType.ts
2
- function getFileType(url) {
3
- const clean = url.split("?")[0].split("#")[0].toLowerCase();
4
- if (clean.endsWith(".pdf")) return "pdf";
5
- if (clean.endsWith(".docx") || clean.endsWith(".doc")) return "docx";
6
- return "unknown";
7
- }
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
8
10
 
9
11
  // src/components/renderers/PdfRenderer.tsx
12
+ var PdfRenderer_exports = {};
13
+ __export(PdfRenderer_exports, {
14
+ PdfRenderer: () => PdfRenderer
15
+ });
10
16
  import { useRef, useEffect, useMemo, useCallback } from "react";
11
17
  import {
12
18
  PdfLoader,
@@ -14,9 +20,7 @@ import {
14
20
  AreaHighlight,
15
21
  useHighlightContainerContext
16
22
  } from "react-pdf-highlighter-extended";
17
- import { jsx } from "react/jsx-runtime";
18
- var CITATION_ID = "citation-highlight";
19
- var DEFAULT_WORKER_URL = "https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs";
23
+ import { jsx as jsx2 } from "react/jsx-runtime";
20
24
  function bboxToHighlight(target) {
21
25
  if (!target.bbox) return null;
22
26
  const page = target.page ?? 1;
@@ -42,7 +46,7 @@ function bboxToHighlight(target) {
42
46
  }
43
47
  function CitationHighlightRenderer() {
44
48
  const { highlight, isScrolledTo } = useHighlightContainerContext();
45
- return /* @__PURE__ */ jsx(
49
+ return /* @__PURE__ */ jsx2(
46
50
  AreaHighlight,
47
51
  {
48
52
  highlight,
@@ -80,12 +84,12 @@ function PdfRenderer({
80
84
  const handleUtilsRef = useCallback((utils) => {
81
85
  highlighterRef.current = utils;
82
86
  }, []);
83
- return /* @__PURE__ */ jsx("div", { style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ jsx(
87
+ return /* @__PURE__ */ jsx2("div", { style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ jsx2(
84
88
  PdfLoader,
85
89
  {
86
90
  document: target.url,
87
91
  workerSrc: workerUrl ?? DEFAULT_WORKER_URL,
88
- beforeLoad: () => loadingComponent ?? /* @__PURE__ */ jsx(
92
+ beforeLoad: () => loadingComponent ?? /* @__PURE__ */ jsx2(
89
93
  "div",
90
94
  {
91
95
  style: {
@@ -103,7 +107,7 @@ function PdfRenderer({
103
107
  ),
104
108
  errorMessage: (error) => {
105
109
  onLoadError?.(error);
106
- return errorComponent ?? /* @__PURE__ */ jsx(
110
+ return errorComponent ?? /* @__PURE__ */ jsx2(
107
111
  "div",
108
112
  {
109
113
  style: {
@@ -125,7 +129,7 @@ function PdfRenderer({
125
129
  loadNotifiedRef.current = true;
126
130
  onLoadSuccess?.();
127
131
  }
128
- return /* @__PURE__ */ jsx(
132
+ return /* @__PURE__ */ jsx2(
129
133
  PdfHighlighter,
130
134
  {
131
135
  pdfDocument,
@@ -135,21 +139,40 @@ function PdfRenderer({
135
139
  width: "100%",
136
140
  height: "100%"
137
141
  },
138
- children: /* @__PURE__ */ jsx(CitationHighlightRenderer, {})
142
+ children: /* @__PURE__ */ jsx2(CitationHighlightRenderer, {})
139
143
  }
140
144
  );
141
145
  }
142
146
  }
143
147
  ) });
144
148
  }
149
+ var CITATION_ID, DEFAULT_WORKER_URL;
150
+ var init_PdfRenderer = __esm({
151
+ "src/components/renderers/PdfRenderer.tsx"() {
152
+ "use strict";
153
+ CITATION_ID = "citation-highlight";
154
+ DEFAULT_WORKER_URL = "https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs";
155
+ }
156
+ });
157
+
158
+ // src/components/DocumentViewer.tsx
159
+ import { lazy, Suspense, useState, useEffect as useEffect2 } from "react";
160
+
161
+ // src/utils/getFileType.ts
162
+ function getFileType(url) {
163
+ const clean = url.split("?")[0].split("#")[0].toLowerCase();
164
+ if (clean.endsWith(".pdf")) return "pdf";
165
+ if (clean.endsWith(".docx") || clean.endsWith(".doc")) return "docx";
166
+ return "unknown";
167
+ }
145
168
 
146
169
  // src/components/renderers/DocxRenderer.tsx
147
- import { jsx as jsx2 } from "react/jsx-runtime";
170
+ import { jsx } from "react/jsx-runtime";
148
171
  function DocxRenderer({ target }) {
149
172
  const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
150
173
  target.url
151
174
  )}`;
152
- return /* @__PURE__ */ jsx2(
175
+ return /* @__PURE__ */ jsx(
153
176
  "iframe",
154
177
  {
155
178
  src: officeUrl,
@@ -166,7 +189,21 @@ function DocxRenderer({ target }) {
166
189
  }
167
190
 
168
191
  // src/components/DocumentViewer.tsx
169
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
192
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
193
+ var LazyPdfRenderer = lazy(
194
+ () => Promise.resolve().then(() => (init_PdfRenderer(), PdfRenderer_exports)).then((mod) => ({
195
+ default: mod.PdfRenderer
196
+ }))
197
+ );
198
+ function ClientOnly({
199
+ children,
200
+ fallback
201
+ }) {
202
+ const [mounted, setMounted] = useState(false);
203
+ useEffect2(() => setMounted(true), []);
204
+ if (!mounted) return /* @__PURE__ */ jsx3(Fragment, { children: fallback ?? null });
205
+ return /* @__PURE__ */ jsx3(Fragment, { children });
206
+ }
170
207
  function DocumentViewer({
171
208
  target,
172
209
  pdfWorkerUrl,
@@ -187,9 +224,25 @@ function DocumentViewer({
187
224
  borderRadius: 8,
188
225
  background: "#fafafa"
189
226
  };
227
+ const loadingFallback = loadingComponent ?? /* @__PURE__ */ jsx3(
228
+ "div",
229
+ {
230
+ style: {
231
+ display: "flex",
232
+ alignItems: "center",
233
+ justifyContent: "center",
234
+ width: "100%",
235
+ height: "100%",
236
+ color: "#888",
237
+ fontSize: 14,
238
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
239
+ },
240
+ children: "Loading\u2026"
241
+ }
242
+ );
190
243
  return /* @__PURE__ */ jsxs("div", { className, style: wrapperStyle, children: [
191
- type === "pdf" && /* @__PURE__ */ jsx3(
192
- PdfRenderer,
244
+ type === "pdf" && /* @__PURE__ */ jsx3(ClientOnly, { fallback: loadingFallback, children: /* @__PURE__ */ jsx3(Suspense, { fallback: loadingFallback, children: /* @__PURE__ */ jsx3(
245
+ LazyPdfRenderer,
193
246
  {
194
247
  target,
195
248
  workerUrl: pdfWorkerUrl,
@@ -198,7 +251,7 @@ function DocumentViewer({
198
251
  onLoadSuccess,
199
252
  onLoadError
200
253
  }
201
- ),
254
+ ) }) }),
202
255
  type === "docx" && /* @__PURE__ */ jsx3(DocxRenderer, { target }),
203
256
  type === "unknown" && /* @__PURE__ */ jsx3(
204
257
  "div",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/getFileType.ts","../src/components/renderers/PdfRenderer.tsx","../src/components/renderers/DocxRenderer.tsx","../src/components/DocumentViewer.tsx"],"sourcesContent":["/**\n * Infer document type from URL extension.\n * Strips query-strings and fragments before matching.\n */\nexport function getFileType(url: string): \"pdf\" | \"docx\" | \"unknown\" {\n const clean = url.split(\"?\")[0].split(\"#\")[0].toLowerCase();\n\n if (clean.endsWith(\".pdf\")) return \"pdf\";\n if (clean.endsWith(\".docx\") || clean.endsWith(\".doc\")) return \"docx\";\n\n return \"unknown\";\n}\n","import React, { useRef, useEffect, useMemo, useCallback } from \"react\";\nimport {\n PdfLoader,\n PdfHighlighter,\n AreaHighlight,\n useHighlightContainerContext,\n} from \"react-pdf-highlighter-extended\";\nimport type {\n Highlight,\n ScaledPosition,\n PdfHighlighterUtils,\n} from \"react-pdf-highlighter-extended\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/* ------------------------------------------------------------------ */\n/* Constants */\n/* ------------------------------------------------------------------ */\n\nconst CITATION_ID = \"citation-highlight\";\n\nconst DEFAULT_WORKER_URL =\n \"https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs\";\n\n/* ------------------------------------------------------------------ */\n/* BBox → Highlight conversion */\n/* ------------------------------------------------------------------ */\n\n/**\n * Convert our normalised (0-1) BBox into the Scaled coordinate system\n * used by react-pdf-highlighter-extended.\n *\n * The library internally computes:\n * viewportX = (viewportWidth × scaled.x1) / scaled.width\n *\n * So `scaled.x1 / scaled.width` must equal the normalised value.\n * We simply store the 0-1 value directly and set width / height to 1.\n */\nfunction bboxToHighlight(target: ViewerTarget): Highlight | null {\n if (!target.bbox) return null;\n\n const page = target.page ?? 1;\n const { x1, y1, x2, y2 } = target.bbox;\n\n const scaledRect = {\n x1,\n y1,\n x2,\n y2,\n width: 1,\n height: 1,\n pageNumber: page,\n };\n\n const position: ScaledPosition = {\n boundingRect: scaledRect,\n rects: [scaledRect],\n };\n\n return {\n id: CITATION_ID,\n type: \"area\",\n position,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Highlight child renderer */\n/* ------------------------------------------------------------------ */\n\n/**\n * Rendered once per highlight by PdfHighlighter's internal loop.\n * Uses context to access the highlight's viewport position.\n */\nfunction CitationHighlightRenderer() {\n const { highlight, isScrolledTo } = useHighlightContainerContext();\n\n return (\n <AreaHighlight\n highlight={highlight}\n isScrolledTo={isScrolledTo}\n style={{\n background: isScrolledTo\n ? \"rgba(255, 171, 0, 0.35)\"\n : \"rgba(255, 171, 0, 0.2)\",\n border: \"1.5px solid rgba(255, 171, 0, 0.6)\",\n borderRadius: 2,\n transition: \"background 200ms ease\",\n }}\n />\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* PdfRenderer */\n/* ------------------------------------------------------------------ */\n\ninterface PdfRendererProps {\n target: ViewerTarget;\n workerUrl?: string;\n loadingComponent?: React.ReactNode;\n errorComponent?: React.ReactNode;\n onLoadSuccess?: () => void;\n onLoadError?: (err: Error) => void;\n}\n\nexport function PdfRenderer({\n target,\n workerUrl,\n loadingComponent,\n errorComponent,\n onLoadSuccess,\n onLoadError,\n}: PdfRendererProps) {\n const highlighterRef = useRef<PdfHighlighterUtils | null>(null);\n const loadNotifiedRef = useRef(false);\n\n /* Build highlight array from target bbox */\n const highlights = useMemo(() => {\n const h = bboxToHighlight(target);\n return h ? [h] : [];\n }, [target]);\n\n /* Scroll to the citation highlight once the viewer is ready */\n useEffect(() => {\n if (highlights.length === 0) return;\n\n const timer = setTimeout(() => {\n highlighterRef.current?.scrollToHighlight(highlights[0]);\n }, 400);\n\n return () => clearTimeout(timer);\n }, [highlights]);\n\n /* Stable callback so we don't re-trigger PdfHighlighter renders */\n const handleUtilsRef = useCallback((utils: PdfHighlighterUtils) => {\n highlighterRef.current = utils;\n }, []);\n\n return (\n <div style={{ width: \"100%\", height: \"100%\", position: \"relative\" }}>\n <PdfLoader\n document={target.url}\n workerSrc={workerUrl ?? DEFAULT_WORKER_URL}\n beforeLoad={() =>\n loadingComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Loading PDF…\n </div>\n )\n }\n errorMessage={(error) => {\n onLoadError?.(error);\n return (\n errorComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#ef4444\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Failed to load PDF\n </div>\n )\n );\n }}\n >\n {(pdfDocument) => {\n /* Notify once */\n if (!loadNotifiedRef.current) {\n loadNotifiedRef.current = true;\n onLoadSuccess?.();\n }\n\n return (\n <PdfHighlighter\n pdfDocument={pdfDocument}\n highlights={highlights}\n utilsRef={handleUtilsRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n }}\n >\n <CitationHighlightRenderer />\n </PdfHighlighter>\n );\n }}\n </PdfLoader>\n </div>\n );\n}\n","import React from \"react\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/**\n * Renders DOCX / DOC files via the free Microsoft Office Online embed viewer.\n * The document URL must be publicly accessible.\n */\nexport function DocxRenderer({ target }: { target: ViewerTarget }) {\n const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(\n target.url\n )}`;\n\n return (\n <iframe\n src={officeUrl}\n title={target.title ?? \"Document preview\"}\n style={{\n width: \"100%\",\n height: \"100%\",\n border: \"none\",\n borderRadius: 6,\n background: \"#fff\",\n }}\n />\n );\n}\n","import React from \"react\";\nimport type { DocumentViewerProps } from \"../types/DocumentViewerProps\";\nimport { getFileType } from \"../utils/getFileType\";\nimport { PdfRenderer } from \"./renderers/PdfRenderer\";\nimport { DocxRenderer } from \"./renderers/DocxRenderer\";\n\n/**\n * Top-level component that routes to the correct renderer\n * based on the target's file type.\n *\n * ```tsx\n * <DocumentViewer\n * target={{\n * url: \"https://example.com/report.pdf\",\n * page: 3,\n * bbox: { x1: 0.1, y1: 0.2, x2: 0.5, y2: 0.3 },\n * }}\n * />\n * ```\n */\nexport function DocumentViewer({\n target,\n pdfWorkerUrl,\n loadingComponent,\n errorComponent,\n onLoadStart,\n onLoadSuccess,\n onLoadError,\n className,\n}: DocumentViewerProps) {\n if (!target) return null;\n\n const type = target.fileType ?? getFileType(target.url);\n\n const wrapperStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n borderRadius: 8,\n background: \"#fafafa\",\n };\n\n return (\n <div className={className} style={wrapperStyle}>\n {type === \"pdf\" && (\n <PdfRenderer\n target={target}\n workerUrl={pdfWorkerUrl}\n loadingComponent={loadingComponent}\n errorComponent={errorComponent}\n onLoadSuccess={onLoadSuccess}\n onLoadError={onLoadError}\n />\n )}\n\n {type === \"docx\" && <DocxRenderer target={target} />}\n\n {type === \"unknown\" && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Unsupported file type\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsx"]}
1
+ {"version":3,"sources":["../src/components/renderers/PdfRenderer.tsx","../src/components/DocumentViewer.tsx","../src/utils/getFileType.ts","../src/components/renderers/DocxRenderer.tsx"],"sourcesContent":["import React, { useRef, useEffect, useMemo, useCallback } from \"react\";\nimport {\n PdfLoader,\n PdfHighlighter,\n AreaHighlight,\n useHighlightContainerContext,\n} from \"react-pdf-highlighter-extended\";\nimport type {\n Highlight,\n ScaledPosition,\n PdfHighlighterUtils,\n} from \"react-pdf-highlighter-extended\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/* ------------------------------------------------------------------ */\n/* Constants */\n/* ------------------------------------------------------------------ */\n\nconst CITATION_ID = \"citation-highlight\";\n\nconst DEFAULT_WORKER_URL =\n \"https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs\";\n\n/* ------------------------------------------------------------------ */\n/* BBox → Highlight conversion */\n/* ------------------------------------------------------------------ */\n\n/**\n * Convert our normalised (0-1) BBox into the Scaled coordinate system\n * used by react-pdf-highlighter-extended.\n *\n * The library internally computes:\n * viewportX = (viewportWidth × scaled.x1) / scaled.width\n *\n * So `scaled.x1 / scaled.width` must equal the normalised value.\n * We simply store the 0-1 value directly and set width / height to 1.\n */\nfunction bboxToHighlight(target: ViewerTarget): Highlight | null {\n if (!target.bbox) return null;\n\n const page = target.page ?? 1;\n const { x1, y1, x2, y2 } = target.bbox;\n\n const scaledRect = {\n x1,\n y1,\n x2,\n y2,\n width: 1,\n height: 1,\n pageNumber: page,\n };\n\n const position: ScaledPosition = {\n boundingRect: scaledRect,\n rects: [scaledRect],\n };\n\n return {\n id: CITATION_ID,\n type: \"area\",\n position,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Highlight child renderer */\n/* ------------------------------------------------------------------ */\n\n/**\n * Rendered once per highlight by PdfHighlighter's internal loop.\n * Uses context to access the highlight's viewport position.\n */\nfunction CitationHighlightRenderer() {\n const { highlight, isScrolledTo } = useHighlightContainerContext();\n\n return (\n <AreaHighlight\n highlight={highlight}\n isScrolledTo={isScrolledTo}\n style={{\n background: isScrolledTo\n ? \"rgba(255, 171, 0, 0.35)\"\n : \"rgba(255, 171, 0, 0.2)\",\n border: \"1.5px solid rgba(255, 171, 0, 0.6)\",\n borderRadius: 2,\n transition: \"background 200ms ease\",\n }}\n />\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* PdfRenderer */\n/* ------------------------------------------------------------------ */\n\ninterface PdfRendererProps {\n target: ViewerTarget;\n workerUrl?: string;\n loadingComponent?: React.ReactNode;\n errorComponent?: React.ReactNode;\n onLoadSuccess?: () => void;\n onLoadError?: (err: Error) => void;\n}\n\nexport function PdfRenderer({\n target,\n workerUrl,\n loadingComponent,\n errorComponent,\n onLoadSuccess,\n onLoadError,\n}: PdfRendererProps) {\n const highlighterRef = useRef<PdfHighlighterUtils | null>(null);\n const loadNotifiedRef = useRef(false);\n\n /* Build highlight array from target bbox */\n const highlights = useMemo(() => {\n const h = bboxToHighlight(target);\n return h ? [h] : [];\n }, [target]);\n\n /* Scroll to the citation highlight once the viewer is ready */\n useEffect(() => {\n if (highlights.length === 0) return;\n\n const timer = setTimeout(() => {\n highlighterRef.current?.scrollToHighlight(highlights[0]);\n }, 400);\n\n return () => clearTimeout(timer);\n }, [highlights]);\n\n /* Stable callback so we don't re-trigger PdfHighlighter renders */\n const handleUtilsRef = useCallback((utils: PdfHighlighterUtils) => {\n highlighterRef.current = utils;\n }, []);\n\n return (\n <div style={{ width: \"100%\", height: \"100%\", position: \"relative\" }}>\n <PdfLoader\n document={target.url}\n workerSrc={workerUrl ?? DEFAULT_WORKER_URL}\n beforeLoad={() =>\n loadingComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Loading PDF…\n </div>\n )\n }\n errorMessage={(error) => {\n onLoadError?.(error);\n return (\n errorComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#ef4444\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Failed to load PDF\n </div>\n )\n );\n }}\n >\n {(pdfDocument) => {\n /* Notify once */\n if (!loadNotifiedRef.current) {\n loadNotifiedRef.current = true;\n onLoadSuccess?.();\n }\n\n return (\n <PdfHighlighter\n pdfDocument={pdfDocument}\n highlights={highlights}\n utilsRef={handleUtilsRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n }}\n >\n <CitationHighlightRenderer />\n </PdfHighlighter>\n );\n }}\n </PdfLoader>\n </div>\n );\n}\n","import React, { lazy, Suspense, useState, useEffect } from \"react\";\nimport type { DocumentViewerProps } from \"../types/DocumentViewerProps\";\nimport { getFileType } from \"../utils/getFileType\";\nimport { DocxRenderer } from \"./renderers/DocxRenderer\";\n\n/* ------------------------------------------------------------------ */\n/* Lazy-load PdfRenderer so pdfjs-dist never runs on the server. */\n/* pdfjs-dist references `window` / `document` at the module level, */\n/* which crashes during Next.js SSR. */\n/* ------------------------------------------------------------------ */\n\nconst LazyPdfRenderer = lazy(() =>\n import(\"./renderers/PdfRenderer\").then((mod) => ({\n default: mod.PdfRenderer,\n }))\n);\n\n/**\n * Tiny wrapper that defers children until after first client-side mount.\n * Prevents any browser-only code from executing during SSR.\n */\nfunction ClientOnly({\n children,\n fallback,\n}: {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n}) {\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n if (!mounted) return <>{fallback ?? null}</>;\n return <>{children}</>;\n}\n\n/**\n * Top-level component that routes to the correct renderer\n * based on the target's file type.\n *\n * ```tsx\n * <DocumentViewer\n * target={{\n * url: \"https://example.com/report.pdf\",\n * page: 3,\n * bbox: { x1: 0.1, y1: 0.2, x2: 0.5, y2: 0.3 },\n * }}\n * />\n * ```\n */\nexport function DocumentViewer({\n target,\n pdfWorkerUrl,\n loadingComponent,\n errorComponent,\n onLoadStart,\n onLoadSuccess,\n onLoadError,\n className,\n}: DocumentViewerProps) {\n if (!target) return null;\n\n const type = target.fileType ?? getFileType(target.url);\n\n const wrapperStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n borderRadius: 8,\n background: \"#fafafa\",\n };\n\n const loadingFallback = loadingComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Loading…\n </div>\n );\n\n return (\n <div className={className} style={wrapperStyle}>\n {type === \"pdf\" && (\n <ClientOnly fallback={loadingFallback}>\n <Suspense fallback={loadingFallback}>\n <LazyPdfRenderer\n target={target}\n workerUrl={pdfWorkerUrl}\n loadingComponent={loadingComponent}\n errorComponent={errorComponent}\n onLoadSuccess={onLoadSuccess}\n onLoadError={onLoadError}\n />\n </Suspense>\n </ClientOnly>\n )}\n\n {type === \"docx\" && <DocxRenderer target={target} />}\n\n {type === \"unknown\" && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Unsupported file type\n </div>\n )}\n </div>\n );\n}\n","/**\n * Infer document type from URL extension.\n * Strips query-strings and fragments before matching.\n */\nexport function getFileType(url: string): \"pdf\" | \"docx\" | \"unknown\" {\n const clean = url.split(\"?\")[0].split(\"#\")[0].toLowerCase();\n\n if (clean.endsWith(\".pdf\")) return \"pdf\";\n if (clean.endsWith(\".docx\") || clean.endsWith(\".doc\")) return \"docx\";\n\n return \"unknown\";\n}\n","import React from \"react\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/**\n * Renders DOCX / DOC files via the free Microsoft Office Online embed viewer.\n * The document URL must be publicly accessible.\n */\nexport function DocxRenderer({ target }: { target: ViewerTarget }) {\n const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(\n target.url\n )}`;\n\n return (\n <iframe\n src={officeUrl}\n title={target.title ?? \"Document preview\"}\n style={{\n width: \"100%\",\n height: \"100%\",\n border: \"none\",\n borderRadius: 6,\n background: \"#fff\",\n }}\n />\n );\n}\n"],"mappings":"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","names":["jsx","useEffect","jsx","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relevaince/document-viewer",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Citation-driven document viewer for Relevaince",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",