viewdoc 0.2.1 → 0.3.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.
Files changed (48) hide show
  1. package/README.md +7 -2
  2. package/dist/DocxViewer-F3MW62BA.js +10 -0
  3. package/dist/PdfViewer-6YF7AER3.js +9 -0
  4. package/dist/XlsxViewer-7X25BBRT.js +10 -0
  5. package/dist/{chunk-LE4YTMPN.js → chunk-3EUNNQXJ.js} +36 -9
  6. package/dist/chunk-3EUNNQXJ.js.map +1 -0
  7. package/dist/{chunk-EQCFZMLL.js → chunk-4BFQ7U2R.js} +10 -4
  8. package/dist/chunk-4BFQ7U2R.js.map +1 -0
  9. package/dist/{chunk-US2KTONG.js → chunk-AIDVQYXH.js} +6 -3
  10. package/dist/chunk-AIDVQYXH.js.map +1 -0
  11. package/dist/chunk-DKFD7M4R.js +12 -0
  12. package/dist/chunk-DKFD7M4R.js.map +1 -0
  13. package/dist/{chunk-OA35SYM6.js → chunk-HOF2XEU5.js} +43 -6
  14. package/dist/chunk-HOF2XEU5.js.map +1 -0
  15. package/dist/docx.cjs +53 -7
  16. package/dist/docx.cjs.map +1 -1
  17. package/dist/docx.d.cts +3 -1
  18. package/dist/docx.d.ts +3 -1
  19. package/dist/docx.js +4 -2
  20. package/dist/index.cjs +94 -16
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +5 -1
  23. package/dist/index.d.ts +5 -1
  24. package/dist/index.js +7 -5
  25. package/dist/index.js.map +1 -1
  26. package/dist/pdf.cjs +44 -6
  27. package/dist/pdf.cjs.map +1 -1
  28. package/dist/pdf.d.cts +3 -1
  29. package/dist/pdf.d.ts +3 -1
  30. package/dist/pdf.js +3 -2
  31. package/dist/styles.css +10 -0
  32. package/dist/styles.css.map +1 -1
  33. package/dist/xlsx.cjs +77 -12
  34. package/dist/xlsx.cjs.map +1 -1
  35. package/dist/xlsx.d.cts +9 -1
  36. package/dist/xlsx.d.ts +9 -1
  37. package/dist/xlsx.js +4 -2
  38. package/package.json +5 -1
  39. package/dist/DocxViewer-HQFSWXQW.js +0 -8
  40. package/dist/PdfViewer-YJOHTAPL.js +0 -8
  41. package/dist/XlsxViewer-SQMAF5UA.js +0 -8
  42. package/dist/chunk-EQCFZMLL.js.map +0 -1
  43. package/dist/chunk-LE4YTMPN.js.map +0 -1
  44. package/dist/chunk-OA35SYM6.js.map +0 -1
  45. package/dist/chunk-US2KTONG.js.map +0 -1
  46. /package/dist/{DocxViewer-HQFSWXQW.js.map → DocxViewer-F3MW62BA.js.map} +0 -0
  47. /package/dist/{PdfViewer-YJOHTAPL.js.map → PdfViewer-6YF7AER3.js.map} +0 -0
  48. /package/dist/{XlsxViewer-SQMAF5UA.js.map → XlsxViewer-7X25BBRT.js.map} +0 -0
package/README.md CHANGED
@@ -30,6 +30,8 @@ Don't forget the stylesheet:
30
30
  import 'viewdoc/styles.css'
31
31
  ```
32
32
 
33
+ **Next.js App Router**: every component ships with a `"use client"` directive, so you can import and render `DocViewer` directly from a Server Component without wrapping it yourself.
34
+
33
35
  ## Supported file types
34
36
 
35
37
  | Type | Extensions | Powered by |
@@ -141,7 +143,7 @@ In non-floating mode, the viewer still renders at its `width`/`height` (default
141
143
 
142
144
  ## Interaction reference
143
145
 
144
- - **Zoom**: `+`/`−` buttons, click the `%` label or the reset icon to return to 100%, or hold Ctrl/Cmd and scroll
146
+ - **Zoom**: `+`/`−` buttons, click the `%` label or the reset icon to return to 100%, hold Ctrl/Cmd and scroll, or pinch with two fingers on touch devices
145
147
  - **Pan**: click-drag the content directly (images), or click-drag/scroll anywhere on the canvas (PDF, DOCX, XLSX — these use native scrolling under the hood, so the mouse wheel always works too)
146
148
  - **Move the window**: click-drag the small grip icon (⊹) at the left of the toolbar (only shown when `floating` is `true`)
147
149
  - **Fullscreen**: toggle button on the right of the toolbar
@@ -152,8 +154,11 @@ In non-floating mode, the viewer still renders at its `width`/`height` (default
152
154
  - Old binary `.doc`/legacy Word formats before 2007 aren't supported (only `.docx`)
153
155
  - The Excel table view is read-only — no cell editing or selection
154
156
  - `XlsxViewer` falls back to CSV parsing for unrecognized content (this is intentional — SheetJS supports `.csv`), so pointing it at a non-spreadsheet file won't always error; it may render the raw text as a single cell instead
155
- - `DocxViewer` and `XlsxViewer` render content via `dangerouslySetInnerHTML` using HTML generated from the file itself (by `mammoth`/`xlsx`) treat untrusted files with the same caution you'd apply to any HTML you didn't author
157
+ - `DocxViewer` and `XlsxViewer` render content via `dangerouslySetInnerHTML` using HTML generated from the file itself (by `mammoth`/`xlsx`); that HTML is passed through [DOMPurify](https://github.com/cure53/DOMPurify) before rendering to strip scripts/event handlers/dangerous URI schemes (e.g. a `javascript:` hyperlink target embedded in a malicious file)
156
158
  - PDF rendering resolves its worker script via `import.meta.url`, which requires an ESM-aware bundler (Vite, Webpack 5+, esbuild). CommonJS (`require`) consumers may need extra worker configuration
159
+ - `PdfViewer` renders every page's canvas upfront rather than virtualizing by scroll position — fine for typical documents (tested to 150 pages, ~14MB memory), but very long PDFs (500+ pages) will be slower to open and use more memory
160
+ - `XlsxViewer` caps rendered rows at `maxRows` (default 2000) per sheet, since the table has no virtualization either — a 10,000-row sheet took ~9 seconds to render unbounded vs. ~1.7 seconds capped. Raise `maxRows` if you need more and can accept the tradeoff, or download the file for the full data
161
+ - The Fullscreen API isn't supported on iOS Safari for anything other than `<video>` elements — the fullscreen button is automatically hidden there rather than showing a control that can't work
157
162
 
158
163
  ## License
159
164
 
@@ -0,0 +1,10 @@
1
+ 'use client'
2
+ import {
3
+ DocxViewer
4
+ } from "./chunk-4BFQ7U2R.js";
5
+ import "./chunk-DKFD7M4R.js";
6
+ import "./chunk-HOF2XEU5.js";
7
+ export {
8
+ DocxViewer
9
+ };
10
+ //# sourceMappingURL=DocxViewer-F3MW62BA.js.map
@@ -0,0 +1,9 @@
1
+ 'use client'
2
+ import {
3
+ PdfViewer
4
+ } from "./chunk-AIDVQYXH.js";
5
+ import "./chunk-HOF2XEU5.js";
6
+ export {
7
+ PdfViewer
8
+ };
9
+ //# sourceMappingURL=PdfViewer-6YF7AER3.js.map
@@ -0,0 +1,10 @@
1
+ 'use client'
2
+ import {
3
+ XlsxViewer
4
+ } from "./chunk-3EUNNQXJ.js";
5
+ import "./chunk-DKFD7M4R.js";
6
+ import "./chunk-HOF2XEU5.js";
7
+ export {
8
+ XlsxViewer
9
+ };
10
+ //# sourceMappingURL=XlsxViewer-7X25BBRT.js.map
@@ -1,7 +1,11 @@
1
+ 'use client'
2
+ import {
3
+ sanitizeUntrustedHtml
4
+ } from "./chunk-DKFD7M4R.js";
1
5
  import {
2
6
  ViewerShell,
3
7
  useZoomPan
4
- } from "./chunk-OA35SYM6.js";
8
+ } from "./chunk-HOF2XEU5.js";
5
9
 
6
10
  // src/viewers/XlsxViewer.tsx
7
11
  import { useMemo, useState as useState2 } from "react";
@@ -63,13 +67,15 @@ function XlsxViewer({
63
67
  zoomStep,
64
68
  enableZoomControls = true,
65
69
  enableWheelZoom = true,
70
+ enablePinchZoom = true,
66
71
  enableFullscreen = true,
67
72
  enableDownload = true,
68
73
  onDownload,
69
74
  theme,
70
75
  floating = true,
71
76
  windowDraggable = true,
72
- defaultPosition
77
+ defaultPosition,
78
+ maxRows = 2e3
73
79
  }) {
74
80
  const zoomPan = useZoomPan({
75
81
  minScale,
@@ -77,18 +83,32 @@ function XlsxViewer({
77
83
  zoomStep,
78
84
  enablePan: false,
79
85
  enableDragScroll: true,
80
- enableWheelZoom
86
+ enableWheelZoom,
87
+ enablePinchZoom
81
88
  });
82
89
  const { workbook, error, loading } = useXlsxWorkbook(uri);
83
90
  const [activeSheet, setActiveSheet] = useState2(0);
84
91
  const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload(uri, fileName)) : void 0;
85
- const tableHtml = useMemo(() => {
86
- if (!workbook) return null;
92
+ const { tableHtml, truncatedRowCount, totalRowCount } = useMemo(() => {
93
+ if (!workbook) return { tableHtml: null, truncatedRowCount: null, totalRowCount: 0 };
87
94
  const sheetName = workbook.SheetNames[activeSheet];
88
95
  const sheet = workbook.Sheets[sheetName];
89
- if (!sheet) return null;
90
- return XLSX2.utils.sheet_to_html(sheet, { editable: false });
91
- }, [workbook, activeSheet]);
96
+ if (!sheet || !sheet["!ref"]) return { tableHtml: null, truncatedRowCount: null, totalRowCount: 0 };
97
+ const range = XLSX2.utils.decode_range(sheet["!ref"]);
98
+ const totalRows = range.e.r - range.s.r + 1;
99
+ let sheetToRender = sheet;
100
+ let truncated = null;
101
+ if (totalRows > maxRows) {
102
+ truncated = maxRows;
103
+ const clampedRange = { s: range.s, e: { r: range.s.r + maxRows - 1, c: range.e.c } };
104
+ sheetToRender = { ...sheet, "!ref": XLSX2.utils.encode_range(clampedRange) };
105
+ }
106
+ return {
107
+ tableHtml: sanitizeUntrustedHtml(XLSX2.utils.sheet_to_html(sheetToRender, { editable: false })),
108
+ truncatedRowCount: truncated,
109
+ totalRowCount: totalRows
110
+ };
111
+ }, [workbook, activeSheet, maxRows]);
92
112
  const wrapperStyle = floating ? { ...style } : { display: "flex", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", ...style };
93
113
  return /* @__PURE__ */ jsx("div", { className, style: wrapperStyle, children: /* @__PURE__ */ jsxs(
94
114
  ViewerShell,
@@ -122,6 +142,13 @@ function XlsxViewer({
122
142
  },
123
143
  name
124
144
  )) }),
145
+ truncatedRowCount != null && /* @__PURE__ */ jsxs("div", { className: "vd-xlsx-truncated-notice", children: [
146
+ "Showing the first ",
147
+ truncatedRowCount.toLocaleString(),
148
+ " of ",
149
+ totalRowCount.toLocaleString(),
150
+ " rows. Download the file to see the rest."
151
+ ] }),
125
152
  tableHtml && /* @__PURE__ */ jsx("div", { className: "vd-xlsx-table", dangerouslySetInnerHTML: { __html: tableHtml } })
126
153
  ] })
127
154
  ]
@@ -132,4 +159,4 @@ function XlsxViewer({
132
159
  export {
133
160
  XlsxViewer
134
161
  };
135
- //# sourceMappingURL=chunk-LE4YTMPN.js.map
162
+ //# sourceMappingURL=chunk-3EUNNQXJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/viewers/XlsxViewer.tsx","../src/viewers/useXlsxWorkbook.ts"],"sourcesContent":["import { useMemo, useState, type CSSProperties } from 'react'\nimport * as XLSX from 'xlsx'\nimport { ViewerShell } from '../core/ViewerShell'\nimport { sanitizeUntrustedHtml } from '../core/sanitizeHtml'\nimport type { ViewerTheme } from '../core/theme'\nimport type { Position } from '../core/useDraggable'\nimport { useZoomPan } from '../core/useZoomPan'\nimport { useXlsxWorkbook } from './useXlsxWorkbook'\n\nexport interface XlsxViewerProps {\n /** URL of the .xlsx/.xls (legacy binary supported)/.csv file to display. */\n uri: string\n /** Used as the suggested filename on download. */\n fileName?: string\n className?: string\n style?: CSSProperties\n\n /** Width of the viewer. Accepts any CSS size (e.g. 1200, '1200px', '100%'). Default: 1200. */\n width?: number | string\n /** Height of the viewer. Accepts any CSS size (e.g. 700, '700px', '100vh'). Default: 700. */\n height?: number | string\n\n /** Minimum zoom scale. Default: 0.5 */\n minScale?: number\n /** Maximum zoom scale. Default: 3 */\n maxScale?: number\n /** Zoom increment per step (buttons / Ctrl+scroll). Default: 0.25 */\n zoomStep?: number\n\n /** Show zoom in/out and reset-to-100% controls. Default: true. */\n enableZoomControls?: boolean\n /** Allow Ctrl/Cmd + scroll wheel to zoom. Default: true. */\n enableWheelZoom?: boolean\n /** Allow two-finger pinch to zoom on touch devices. Default: true. */\n enablePinchZoom?: boolean\n /** Show the fullscreen toggle button. Default: true. */\n enableFullscreen?: boolean\n /** Show the download button. Default: true. */\n enableDownload?: boolean\n /** Called when the download button is clicked. If omitted, downloads `uri` directly. */\n onDownload?: () => void\n /** Colors/radii to override the default look (toolbar background, text color, etc). */\n theme?: ViewerTheme\n\n /** Render as a floating window (position: fixed) that can be dragged, instead of filling the parent. Default: true. */\n floating?: boolean\n /** When floating, allow dragging the window by its toolbar. Default: true. */\n windowDraggable?: boolean\n /** Initial position when floating. Default: centered in the viewport. */\n defaultPosition?: Position\n\n /**\n * Maximum number of rows rendered per sheet. The table is plain HTML with no virtualization,\n * so very large sheets (tens of thousands of rows) can freeze the page for several seconds if\n * rendered in full. Default: 2000.\n */\n maxRows?: number\n}\n\nfunction defaultDownload(uri: string, fileName?: string) {\n const link = document.createElement('a')\n link.href = uri\n link.download = fileName ?? ''\n link.rel = 'noopener'\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n}\n\nexport function XlsxViewer({\n uri,\n fileName,\n className,\n style,\n width,\n height,\n minScale = 0.5,\n maxScale = 3,\n zoomStep,\n enableZoomControls = true,\n enableWheelZoom = true,\n enablePinchZoom = true,\n enableFullscreen = true,\n enableDownload = true,\n onDownload,\n theme,\n floating = true,\n windowDraggable = true,\n defaultPosition,\n maxRows = 2000,\n}: XlsxViewerProps) {\n const zoomPan = useZoomPan({\n minScale,\n maxScale,\n zoomStep,\n enablePan: false,\n enableDragScroll: true,\n enableWheelZoom,\n enablePinchZoom,\n })\n const { workbook, error, loading } = useXlsxWorkbook(uri)\n const [activeSheet, setActiveSheet] = useState(0)\n\n const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload(uri, fileName)) : undefined\n\n const { tableHtml, truncatedRowCount, totalRowCount } = useMemo(() => {\n if (!workbook) return { tableHtml: null, truncatedRowCount: null as number | null, totalRowCount: 0 }\n const sheetName = workbook.SheetNames[activeSheet]\n const sheet = workbook.Sheets[sheetName]\n if (!sheet || !sheet['!ref']) return { tableHtml: null, truncatedRowCount: null, totalRowCount: 0 }\n\n const range = XLSX.utils.decode_range(sheet['!ref'])\n const totalRows = range.e.r - range.s.r + 1\n let sheetToRender = sheet\n let truncated: number | null = null\n\n if (totalRows > maxRows) {\n truncated = maxRows\n const clampedRange = { s: range.s, e: { r: range.s.r + maxRows - 1, c: range.e.c } }\n sheetToRender = { ...sheet, '!ref': XLSX.utils.encode_range(clampedRange) }\n }\n\n return {\n tableHtml: sanitizeUntrustedHtml(XLSX.utils.sheet_to_html(sheetToRender, { editable: false })),\n truncatedRowCount: truncated,\n totalRowCount: totalRows,\n }\n }, [workbook, activeSheet, maxRows])\n\n const wrapperStyle: CSSProperties = floating\n ? { ...style }\n : { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%', ...style }\n\n return (\n <div className={className} style={wrapperStyle}>\n <ViewerShell\n zoomPan={zoomPan}\n onDownload={handleDownload}\n enableZoomControls={enableZoomControls}\n enableFullscreen={enableFullscreen}\n enablePan={false}\n theme={theme}\n width={width}\n height={height}\n floating={floating}\n draggable={windowDraggable}\n defaultPosition={defaultPosition}\n transformOrigin=\"top left\"\n >\n {loading && <span className=\"vd-doc-status\">Loading spreadsheet…</span>}\n {error && <span className=\"vd-doc-status\">Failed to load spreadsheet: {error.message}</span>}\n {workbook && (\n <div className=\"vd-xlsx\">\n {workbook.SheetNames.length > 1 && (\n <div className=\"vd-xlsx-tabs\">\n {workbook.SheetNames.map((name, i) => (\n <button\n key={name}\n type=\"button\"\n className={`vd-xlsx-tab${i === activeSheet ? ' vd-xlsx-tab--active' : ''}`}\n onClick={() => setActiveSheet(i)}\n >\n {name}\n </button>\n ))}\n </div>\n )}\n {truncatedRowCount != null && (\n <div className=\"vd-xlsx-truncated-notice\">\n Showing the first {truncatedRowCount.toLocaleString()} of {totalRowCount.toLocaleString()} rows.\n Download the file to see the rest.\n </div>\n )}\n {tableHtml && <div className=\"vd-xlsx-table\" dangerouslySetInnerHTML={{ __html: tableHtml }} />}\n </div>\n )}\n </ViewerShell>\n </div>\n )\n}\n","import { useEffect, useState } from 'react'\nimport * as XLSX from 'xlsx'\n\nexport interface UseXlsxWorkbookResult {\n workbook: XLSX.WorkBook | null\n error: Error | null\n loading: boolean\n}\n\nexport function useXlsxWorkbook(uri: string): UseXlsxWorkbookResult {\n const [workbook, setWorkbook] = useState<XLSX.WorkBook | null>(null)\n const [error, setError] = useState<Error | null>(null)\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n setWorkbook(null)\n\n fetch(uri)\n .then((res) => {\n if (!res.ok) throw new Error(`Failed to fetch spreadsheet: ${res.status} ${res.statusText}`)\n return res.arrayBuffer()\n })\n .then((arrayBuffer) => {\n if (cancelled) return\n const wb = XLSX.read(arrayBuffer, { type: 'array' })\n setWorkbook(wb)\n setLoading(false)\n })\n .catch((err: Error) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [uri])\n\n return { workbook, error, loading }\n}\n"],"mappings":"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","names":["useState","XLSX","useState"]}
@@ -1,7 +1,11 @@
1
+ 'use client'
2
+ import {
3
+ sanitizeUntrustedHtml
4
+ } from "./chunk-DKFD7M4R.js";
1
5
  import {
2
6
  ViewerShell,
3
7
  useZoomPan
4
- } from "./chunk-OA35SYM6.js";
8
+ } from "./chunk-HOF2XEU5.js";
5
9
 
6
10
  // src/viewers/useDocxHtml.ts
7
11
  import { useEffect, useState } from "react";
@@ -24,7 +28,7 @@ function useDocxHtml(uri) {
24
28
  })
25
29
  ).then((result) => {
26
30
  if (cancelled) return;
27
- setHtml(result.value);
31
+ setHtml(sanitizeUntrustedHtml(result.value));
28
32
  setLoading(false);
29
33
  }).catch((err) => {
30
34
  if (!cancelled) {
@@ -62,6 +66,7 @@ function DocxViewer({
62
66
  zoomStep,
63
67
  enableZoomControls = true,
64
68
  enableWheelZoom = true,
69
+ enablePinchZoom = true,
65
70
  enableFullscreen = true,
66
71
  enableDownload = true,
67
72
  onDownload,
@@ -76,7 +81,8 @@ function DocxViewer({
76
81
  zoomStep,
77
82
  enablePan: false,
78
83
  enableDragScroll: true,
79
- enableWheelZoom
84
+ enableWheelZoom,
85
+ enablePinchZoom
80
86
  });
81
87
  const { html, error, loading } = useDocxHtml(uri);
82
88
  const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload(uri, fileName)) : void 0;
@@ -111,4 +117,4 @@ function DocxViewer({
111
117
  export {
112
118
  DocxViewer
113
119
  };
114
- //# sourceMappingURL=chunk-EQCFZMLL.js.map
120
+ //# sourceMappingURL=chunk-4BFQ7U2R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/viewers/useDocxHtml.ts","../src/viewers/DocxViewer.tsx"],"sourcesContent":["import { useEffect, useState } from 'react'\nimport mammoth from 'mammoth'\nimport { sanitizeUntrustedHtml } from '../core/sanitizeHtml'\n\nexport interface UseDocxHtmlResult {\n html: string | null\n error: Error | null\n loading: boolean\n}\n\nexport function useDocxHtml(uri: string): UseDocxHtmlResult {\n const [html, setHtml] = useState<string | null>(null)\n const [error, setError] = useState<Error | null>(null)\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n setHtml(null)\n\n fetch(uri)\n .then((res) => {\n if (!res.ok) throw new Error(`Failed to fetch document: ${res.status} ${res.statusText}`)\n return res.arrayBuffer()\n })\n .then((arrayBuffer) =>\n mammoth\n .convertToHtml({ arrayBuffer })\n .catch(() => {\n throw new Error('This file could not be read as a Word document (.docx).')\n })\n )\n .then((result) => {\n if (cancelled) return\n setHtml(sanitizeUntrustedHtml(result.value))\n setLoading(false)\n })\n .catch((err: Error) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [uri])\n\n return { html, error, loading }\n}\n","import type { CSSProperties } from 'react'\nimport { ViewerShell } from '../core/ViewerShell'\nimport type { ViewerTheme } from '../core/theme'\nimport type { Position } from '../core/useDraggable'\nimport { useZoomPan } from '../core/useZoomPan'\nimport { useDocxHtml } from './useDocxHtml'\n\nexport interface DocxViewerProps {\n /** URL of the .docx file to display. */\n uri: string\n /** Used as the suggested filename on download. */\n fileName?: string\n className?: string\n style?: CSSProperties\n\n /** Width of the viewer. Accepts any CSS size (e.g. 1200, '1200px', '100%'). Default: 1200. */\n width?: number | string\n /** Height of the viewer. Accepts any CSS size (e.g. 700, '700px', '100vh'). Default: 700. */\n height?: number | string\n\n /** Minimum zoom scale. Default: 0.5 */\n minScale?: number\n /** Maximum zoom scale. Default: 3 */\n maxScale?: number\n /** Zoom increment per step (buttons / Ctrl+scroll). Default: 0.25 */\n zoomStep?: number\n\n /** Show zoom in/out and reset-to-100% controls. Default: true. */\n enableZoomControls?: boolean\n /** Allow Ctrl/Cmd + scroll wheel to zoom. Default: true. */\n enableWheelZoom?: boolean\n /** Allow two-finger pinch to zoom on touch devices. Default: true. */\n enablePinchZoom?: boolean\n /** Show the fullscreen toggle button. Default: true. */\n enableFullscreen?: boolean\n /** Show the download button. Default: true. */\n enableDownload?: boolean\n /** Called when the download button is clicked. If omitted, downloads `uri` directly. */\n onDownload?: () => void\n /** Colors/radii to override the default look (toolbar background, text color, etc). */\n theme?: ViewerTheme\n\n /** Render as a floating window (position: fixed) that can be dragged, instead of filling the parent. Default: true. */\n floating?: boolean\n /** When floating, allow dragging the window by its toolbar. Default: true. */\n windowDraggable?: boolean\n /** Initial position when floating. Default: centered in the viewport. */\n defaultPosition?: Position\n}\n\nfunction defaultDownload(uri: string, fileName?: string) {\n const link = document.createElement('a')\n link.href = uri\n link.download = fileName ?? ''\n link.rel = 'noopener'\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n}\n\nexport function DocxViewer({\n uri,\n fileName,\n className,\n style,\n width,\n height,\n minScale = 0.5,\n maxScale = 3,\n zoomStep,\n enableZoomControls = true,\n enableWheelZoom = true,\n enablePinchZoom = true,\n enableFullscreen = true,\n enableDownload = true,\n onDownload,\n theme,\n floating = true,\n windowDraggable = true,\n defaultPosition,\n}: DocxViewerProps) {\n const zoomPan = useZoomPan({\n minScale,\n maxScale,\n zoomStep,\n enablePan: false,\n enableDragScroll: true,\n enableWheelZoom,\n enablePinchZoom,\n })\n const { html, error, loading } = useDocxHtml(uri)\n\n const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload(uri, fileName)) : undefined\n\n const wrapperStyle: CSSProperties = floating\n ? { ...style }\n : { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%', ...style }\n\n return (\n <div className={className} style={wrapperStyle}>\n <ViewerShell\n zoomPan={zoomPan}\n onDownload={handleDownload}\n enableZoomControls={enableZoomControls}\n enableFullscreen={enableFullscreen}\n enablePan={false}\n theme={theme}\n width={width}\n height={height}\n floating={floating}\n draggable={windowDraggable}\n defaultPosition={defaultPosition}\n transformOrigin=\"top center\"\n >\n {loading && <span className=\"vd-doc-status\">Loading document…</span>}\n {error && <span className=\"vd-doc-status\">Failed to load document: {error.message}</span>}\n {html && <div className=\"vd-docx-page\" dangerouslySetInnerHTML={{ __html: html }} />}\n </ViewerShell>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,8 @@
1
+ 'use client'
1
2
  import {
2
3
  ViewerShell,
3
4
  useZoomPan
4
- } from "./chunk-OA35SYM6.js";
5
+ } from "./chunk-HOF2XEU5.js";
5
6
 
6
7
  // src/viewers/PdfPage.tsx
7
8
  import { useEffect, useRef } from "react";
@@ -115,6 +116,7 @@ function PdfViewer({
115
116
  renderScale,
116
117
  enableZoomControls = true,
117
118
  enableWheelZoom = true,
119
+ enablePinchZoom = true,
118
120
  enableFullscreen = true,
119
121
  enableDownload = true,
120
122
  onDownload,
@@ -129,7 +131,8 @@ function PdfViewer({
129
131
  zoomStep,
130
132
  enablePan: false,
131
133
  enableDragScroll: true,
132
- enableWheelZoom
134
+ enableWheelZoom,
135
+ enablePinchZoom
133
136
  });
134
137
  const { doc, numPages, error, loading } = usePdfDocument(uri);
135
138
  const currentPage = useVisiblePage(zoomPan.containerRef, numPages);
@@ -171,4 +174,4 @@ function PdfViewer({
171
174
  export {
172
175
  PdfViewer
173
176
  };
174
- //# sourceMappingURL=chunk-US2KTONG.js.map
177
+ //# sourceMappingURL=chunk-AIDVQYXH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/viewers/PdfPage.tsx","../src/viewers/usePdfDocument.ts","../src/viewers/useVisiblePage.ts","../src/viewers/PdfViewer.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react'\nimport type { PDFDocumentProxy } from 'pdfjs-dist'\n\nexport interface PdfPageProps {\n doc: PDFDocumentProxy\n pageNumber: number\n /** Render resolution multiplier. Higher = crisper at deep zoom, more memory. Default: 1.5 */\n renderScale?: number\n}\n\nexport function PdfPage({ doc, pageNumber, renderScale = 1.5 }: PdfPageProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null)\n const renderTaskRef = useRef<{ cancel: () => void } | null>(null)\n\n useEffect(() => {\n let cancelled = false\n\n doc.getPage(pageNumber).then((page) => {\n if (cancelled) return\n const viewport = page.getViewport({ scale: renderScale })\n const canvas = canvasRef.current\n if (!canvas) return\n const context = canvas.getContext('2d')\n if (!context) return\n\n canvas.width = viewport.width\n canvas.height = viewport.height\n canvas.style.width = `${viewport.width / renderScale}px`\n canvas.style.height = `${viewport.height / renderScale}px`\n\n const renderTask = page.render({ canvasContext: context, viewport, canvas })\n renderTaskRef.current = renderTask\n renderTask.promise.catch(() => {\n // ignore cancellation errors from rapid re-renders\n })\n })\n\n return () => {\n cancelled = true\n renderTaskRef.current?.cancel()\n }\n }, [doc, pageNumber, renderScale])\n\n return <canvas ref={canvasRef} className=\"vd-pdf-page\" data-page-number={pageNumber} />\n}\n","import { useEffect, useState } from 'react'\nimport * as pdfjsLib from 'pdfjs-dist'\nimport type { PDFDocumentLoadingTask, PDFDocumentProxy } from 'pdfjs-dist'\n\npdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString()\n\nexport interface UsePdfDocumentResult {\n doc: PDFDocumentProxy | null\n numPages: number\n error: Error | null\n loading: boolean\n}\n\nexport function usePdfDocument(uri: string): UsePdfDocumentResult {\n const [doc, setDoc] = useState<PDFDocumentProxy | null>(null)\n const [error, setError] = useState<Error | null>(null)\n const [loading, setLoading] = useState(true)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n setDoc(null)\n\n const loadingTask: PDFDocumentLoadingTask = pdfjsLib.getDocument({ url: uri })\n loadingTask.promise\n .then((pdf) => {\n if (cancelled) return\n setDoc(pdf)\n setLoading(false)\n })\n .catch((err: Error) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n loadingTask.destroy()\n }\n }, [uri])\n\n return { doc, numPages: doc?.numPages ?? 0, error, loading }\n}\n","import { useEffect, useState } from 'react'\n\n/** Tracks which page (by data-page-number) is most visible inside the given scroll container. */\nexport function useVisiblePage(containerRef: React.RefObject<HTMLElement>, numPages: number): number {\n const [currentPage, setCurrentPage] = useState(1)\n\n useEffect(() => {\n const root = containerRef.current\n if (!root || numPages === 0) return\n\n const observer = new IntersectionObserver(\n (entries) => {\n const mostVisible = entries\n .filter((e) => e.isIntersecting)\n .sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0]\n if (mostVisible) {\n const page = Number((mostVisible.target as HTMLElement).dataset.pageNumber)\n if (page) setCurrentPage(page)\n }\n },\n { root, threshold: [0.25, 0.5, 0.75] }\n )\n\n const pages = root.querySelectorAll('[data-page-number]')\n pages.forEach((el) => observer.observe(el))\n\n return () => observer.disconnect()\n }, [containerRef, numPages])\n\n return currentPage\n}\n","import type { CSSProperties } from 'react'\nimport { ViewerShell } from '../core/ViewerShell'\nimport type { ViewerTheme } from '../core/theme'\nimport type { Position } from '../core/useDraggable'\nimport { useZoomPan } from '../core/useZoomPan'\nimport { PdfPage } from './PdfPage'\nimport { usePdfDocument } from './usePdfDocument'\nimport { useVisiblePage } from './useVisiblePage'\n\nexport interface PdfViewerProps {\n /** URL or data URI of the PDF to display. */\n uri: string\n /** Used as the suggested filename on download. */\n fileName?: string\n className?: string\n style?: CSSProperties\n\n /** Width of the viewer. Accepts any CSS size (e.g. 1200, '1200px', '100%'). Default: 1200. */\n width?: number | string\n /** Height of the viewer. Accepts any CSS size (e.g. 700, '700px', '100vh'). Default: 700. */\n height?: number | string\n\n /** Minimum zoom scale. Default: 0.25 */\n minScale?: number\n /** Maximum zoom scale. Default: 5 */\n maxScale?: number\n /** Zoom increment per step (buttons / Ctrl+scroll). Default: 0.25 */\n zoomStep?: number\n /** Canvas render resolution multiplier (crispness at high zoom). Default: 1.5 */\n renderScale?: number\n\n /** Show zoom in/out and reset-to-100% controls. Default: true. */\n enableZoomControls?: boolean\n /** Allow Ctrl/Cmd + scroll wheel to zoom. Default: true. */\n enableWheelZoom?: boolean\n /** Allow two-finger pinch to zoom on touch devices. Default: true. */\n enablePinchZoom?: boolean\n /** Show the fullscreen toggle button. Default: true. */\n enableFullscreen?: boolean\n /** Show the download button. Default: true. */\n enableDownload?: boolean\n /** Called when the download button is clicked. If omitted, downloads `uri` directly. */\n onDownload?: () => void\n /** Colors/radii to override the default look (toolbar background, text color, etc). */\n theme?: ViewerTheme\n\n /** Render as a floating window (position: fixed) that can be dragged, instead of filling the parent. Default: true. */\n floating?: boolean\n /** When floating, allow dragging the window by its toolbar. Default: true. */\n windowDraggable?: boolean\n /** Initial position when floating. Default: centered in the viewport. */\n defaultPosition?: Position\n}\n\nfunction defaultDownload(uri: string, fileName?: string) {\n const link = document.createElement('a')\n link.href = uri\n link.download = fileName ?? ''\n link.rel = 'noopener'\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n}\n\nexport function PdfViewer({\n uri,\n fileName,\n className,\n style,\n width,\n height,\n minScale = 0.25,\n maxScale = 5,\n zoomStep,\n renderScale,\n enableZoomControls = true,\n enableWheelZoom = true,\n enablePinchZoom = true,\n enableFullscreen = true,\n enableDownload = true,\n onDownload,\n theme,\n floating = true,\n windowDraggable = true,\n defaultPosition,\n}: PdfViewerProps) {\n const zoomPan = useZoomPan({\n minScale,\n maxScale,\n zoomStep,\n enablePan: false,\n enableDragScroll: true,\n enableWheelZoom,\n enablePinchZoom,\n })\n const { doc, numPages, error, loading } = usePdfDocument(uri)\n const currentPage = useVisiblePage(zoomPan.containerRef, numPages)\n\n const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload(uri, fileName)) : undefined\n\n const wrapperStyle: CSSProperties = floating\n ? { ...style }\n : { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%', ...style }\n\n return (\n <div className={className} style={wrapperStyle}>\n <ViewerShell\n zoomPan={zoomPan}\n onDownload={handleDownload}\n enableZoomControls={enableZoomControls}\n enableFullscreen={enableFullscreen}\n enablePan={false}\n theme={theme}\n width={width}\n height={height}\n floating={floating}\n draggable={windowDraggable}\n defaultPosition={defaultPosition}\n transformOrigin=\"top center\"\n toolbarExtra={\n numPages > 0 ? (\n <span className=\"vd-pdf-page-indicator\">\n Page {currentPage} / {numPages}\n </span>\n ) : undefined\n }\n >\n {loading && <span className=\"vd-doc-status\">Loading PDF…</span>}\n {error && <span className=\"vd-doc-status\">Failed to load PDF: {error.message}</span>}\n {doc && (\n <div className=\"vd-pdf-pages\">\n {Array.from({ length: numPages }, (_, i) => (\n <PdfPage key={i + 1} doc={doc} pageNumber={i + 1} renderScale={renderScale} />\n ))}\n </div>\n )}\n </ViewerShell>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useEffect","useState","jsx"]}
@@ -0,0 +1,12 @@
1
+ 'use client'
2
+
3
+ // src/core/sanitizeHtml.ts
4
+ import DOMPurify from "dompurify";
5
+ function sanitizeUntrustedHtml(html) {
6
+ return DOMPurify.sanitize(html);
7
+ }
8
+
9
+ export {
10
+ sanitizeUntrustedHtml
11
+ };
12
+ //# sourceMappingURL=chunk-DKFD7M4R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/sanitizeHtml.ts"],"sourcesContent":["import DOMPurify from 'dompurify'\n\n/**\n * Sanitizes HTML generated from an untrusted file (via mammoth/xlsx) before it's passed to\n * dangerouslySetInnerHTML. Without this, a crafted hyperlink (e.g. a docx/xlsx hyperlink target\n * of `javascript:...`) would be rendered verbatim into an `href` attribute and execute on click.\n */\nexport function sanitizeUntrustedHtml(html: string): string {\n return DOMPurify.sanitize(html)\n}\n"],"mappings":";;;AAAA,OAAO,eAAe;AAOf,SAAS,sBAAsB,MAAsB;AAC1D,SAAO,UAAU,SAAS,IAAI;AAChC;","names":[]}
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  // src/core/useDraggable.ts
2
4
  import { useCallback, useState } from "react";
3
5
  function useDraggable(options = {}) {
@@ -244,9 +246,10 @@ function ViewerShell({
244
246
  document.addEventListener("fullscreenchange", onChange);
245
247
  return () => document.removeEventListener("fullscreenchange", onChange);
246
248
  }, []);
249
+ const fullscreenSupported = typeof document !== "undefined" && typeof document.fullscreenEnabled === "boolean" ? document.fullscreenEnabled : false;
247
250
  const onFullscreen = useCallback2(() => {
248
251
  const el = rootRef.current;
249
- if (!el) return;
252
+ if (!el || typeof el.requestFullscreen !== "function") return;
250
253
  if (document.fullscreenElement) {
251
254
  document.exitFullscreen();
252
255
  } else {
@@ -272,7 +275,7 @@ function ViewerShell({
272
275
  onZoomIn: zoomPan.zoomIn,
273
276
  onZoomOut: zoomPan.zoomOut,
274
277
  onReset: zoomPan.resetZoom,
275
- onFullscreen: enableFullscreen ? onFullscreen : void 0,
278
+ onFullscreen: enableFullscreen && fullscreenSupported ? onFullscreen : void 0,
276
279
  onDownload,
277
280
  isFullscreen,
278
281
  showZoomControls: enableZoomControls,
@@ -313,6 +316,9 @@ import { useCallback as useCallback3, useRef as useRef2, useState as useState3 }
313
316
  var DEFAULT_MIN = 0.1;
314
317
  var DEFAULT_MAX = 8;
315
318
  var DEFAULT_STEP = 0.25;
319
+ function pointerDistance(a, b) {
320
+ return Math.hypot(a.x - b.x, a.y - b.y);
321
+ }
316
322
  function useZoomPan(options = {}) {
317
323
  const {
318
324
  minScale = DEFAULT_MIN,
@@ -320,7 +326,8 @@ function useZoomPan(options = {}) {
320
326
  zoomStep = DEFAULT_STEP,
321
327
  enablePan = true,
322
328
  enableWheelZoom = true,
323
- enableDragScroll = false
329
+ enableDragScroll = false,
330
+ enablePinchZoom = true
324
331
  } = options;
325
332
  const [state, setState] = useState3({ scale: 1, translateX: 0, translateY: 0 });
326
333
  const containerRef = useRef2(null);
@@ -334,6 +341,12 @@ function useZoomPan(options = {}) {
334
341
  const scrollDragState = useRef2(
335
342
  { dragging: false, startX: 0, startY: 0, originLeft: 0, originTop: 0 }
336
343
  );
344
+ const activePointers = useRef2(/* @__PURE__ */ new Map());
345
+ const pinchState = useRef2({
346
+ pinching: false,
347
+ startDistance: 0,
348
+ startScale: 1
349
+ });
337
350
  const clamp = useCallback3(
338
351
  (value) => Math.min(maxScale, Math.max(minScale, value)),
339
352
  [minScale, maxScale]
@@ -357,6 +370,16 @@ function useZoomPan(options = {}) {
357
370
  );
358
371
  const onPointerDown = useCallback3(
359
372
  (e) => {
373
+ if (enablePinchZoom) {
374
+ activePointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
375
+ if (activePointers.current.size === 2) {
376
+ const [a, b] = Array.from(activePointers.current.values());
377
+ pinchState.current = { pinching: true, startDistance: pointerDistance(a, b), startScale: state.scale };
378
+ dragState.current.dragging = false;
379
+ scrollDragState.current.dragging = false;
380
+ return;
381
+ }
382
+ }
360
383
  if (enablePan) {
361
384
  dragState.current = {
362
385
  dragging: true,
@@ -377,10 +400,20 @@ function useZoomPan(options = {}) {
377
400
  e.target.setPointerCapture(e.pointerId);
378
401
  }
379
402
  },
380
- [enablePan, enableDragScroll, state.translateX, state.translateY]
403
+ [enablePan, enableDragScroll, enablePinchZoom, state.translateX, state.translateY, state.scale]
381
404
  );
382
405
  const onPointerMove = useCallback3(
383
406
  (e) => {
407
+ if (enablePinchZoom && activePointers.current.has(e.pointerId)) {
408
+ activePointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
409
+ }
410
+ if (pinchState.current.pinching && activePointers.current.size === 2) {
411
+ const [a, b] = Array.from(activePointers.current.values());
412
+ const distance = pointerDistance(a, b);
413
+ const ratio = distance / (pinchState.current.startDistance || distance);
414
+ setScale(pinchState.current.startScale * ratio);
415
+ return;
416
+ }
384
417
  if (enablePan) {
385
418
  if (!dragState.current.dragging) return;
386
419
  const dx = e.clientX - dragState.current.startX;
@@ -398,10 +431,14 @@ function useZoomPan(options = {}) {
398
431
  containerRef.current.scrollTop = scrollDragState.current.originTop - dy;
399
432
  }
400
433
  },
401
- [enablePan, enableDragScroll]
434
+ [enablePan, enableDragScroll, enablePinchZoom, setScale]
402
435
  );
403
436
  const onPointerUp = useCallback3(
404
437
  (e) => {
438
+ activePointers.current.delete(e.pointerId);
439
+ if (activePointers.current.size < 2) {
440
+ pinchState.current.pinching = false;
441
+ }
405
442
  if (enablePan) {
406
443
  dragState.current.dragging = false;
407
444
  e.target.releasePointerCapture(e.pointerId);
@@ -429,4 +466,4 @@ export {
429
466
  ViewerShell,
430
467
  useZoomPan
431
468
  };
432
- //# sourceMappingURL=chunk-OA35SYM6.js.map
469
+ //# sourceMappingURL=chunk-HOF2XEU5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/useDraggable.ts","../src/core/icons.tsx","../src/core/Toolbar.tsx","../src/core/ViewerShell.tsx","../src/core/theme.ts","../src/core/useZoomPan.ts"],"sourcesContent":["import { useCallback, useState } from 'react'\n\nexport interface Position {\n x: number\n y: number\n}\n\nexport interface UseDraggableOptions {\n defaultPosition?: Position\n /** Allow dragging via the returned handle props. Default: true. */\n draggable?: boolean\n}\n\nexport interface DraggableControls {\n position: Position\n onDragHandlePointerDown: (e: React.PointerEvent) => void\n}\n\nexport function useDraggable(options: UseDraggableOptions = {}): DraggableControls {\n const { defaultPosition = { x: 80, y: 80 }, draggable = true } = options\n const [position, setPosition] = useState<Position>(defaultPosition)\n\n const onDragHandlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n if (!draggable) return\n e.preventDefault()\n const startX = e.clientX\n const startY = e.clientY\n\n setPosition((current) => {\n const originX = current.x\n const originY = current.y\n\n const onMove = (ev: PointerEvent) => {\n setPosition({ x: originX + (ev.clientX - startX), y: originY + (ev.clientY - startY) })\n }\n const onUp = () => {\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n }\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n\n return current\n })\n },\n [draggable]\n )\n\n return { position, onDragHandlePointerDown }\n}\n","import type { SVGProps } from 'react'\n\nfunction Svg(props: SVGProps<SVGSVGElement>) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n {...props}\n />\n )\n}\n\nexport function ZoomOutIcon() {\n return (\n <Svg>\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\" />\n <line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\" />\n </Svg>\n )\n}\n\nexport function ZoomInIcon() {\n return (\n <Svg>\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\" />\n <line x1=\"11\" y1=\"8\" x2=\"11\" y2=\"14\" />\n <line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\" />\n </Svg>\n )\n}\n\nexport function DownloadIcon() {\n return (\n <Svg>\n <path d=\"M12 3v12\" />\n <path d=\"M7 10l5 5 5-5\" />\n <path d=\"M5 21h14\" />\n </Svg>\n )\n}\n\nexport function FullscreenEnterIcon() {\n return (\n <Svg>\n <polyline points=\"15 3 21 3 21 9\" />\n <polyline points=\"9 21 3 21 3 15\" />\n <line x1=\"21\" y1=\"3\" x2=\"14\" y2=\"10\" />\n <line x1=\"3\" y1=\"21\" x2=\"10\" y2=\"14\" />\n </Svg>\n )\n}\n\nexport function ResetIcon() {\n return (\n <Svg>\n <path d=\"M3 12a9 9 0 1 0 3-6.7\" />\n <polyline points=\"3 3 3 8 8 8\" />\n </Svg>\n )\n}\n\nexport function GripIcon() {\n return (\n <Svg>\n <polyline points=\"5 9 2 12 5 15\" />\n <polyline points=\"9 5 12 2 15 5\" />\n <polyline points=\"15 19 12 22 9 19\" />\n <polyline points=\"19 9 22 12 19 15\" />\n <line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\" />\n <line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"22\" />\n </Svg>\n )\n}\n\nexport function FullscreenExitIcon() {\n return (\n <Svg>\n <polyline points=\"9 3 9 9 3 9\" />\n <polyline points=\"15 21 15 15 21 15\" />\n <line x1=\"9\" y1=\"9\" x2=\"3\" y2=\"3\" />\n <line x1=\"15\" y1=\"15\" x2=\"21\" y2=\"21\" />\n </Svg>\n )\n}\n","import type { ReactNode } from 'react'\nimport { DownloadIcon, FullscreenEnterIcon, FullscreenExitIcon, ResetIcon, ZoomInIcon, ZoomOutIcon } from './icons'\n\nexport interface ToolbarProps {\n scale: number\n onZoomIn: () => void\n onZoomOut: () => void\n onReset: () => void\n onFullscreen?: () => void\n onDownload?: () => void\n /** Whether the viewer is currently in fullscreen mode (controls which icon is shown). */\n isFullscreen?: boolean\n /** Show zoom in/out/reset controls. Default: true. */\n showZoomControls?: boolean\n /** Icon shown at the start of the toolbar; pointer-down on it starts dragging. */\n dragHandle?: ReactNode\n /** Pointer-down handler attached to the drag handle icon. */\n onDragHandlePointerDown?: (e: React.PointerEvent) => void\n extra?: ReactNode\n}\n\nexport function Toolbar({\n scale,\n onZoomIn,\n onZoomOut,\n onReset,\n onFullscreen,\n onDownload,\n isFullscreen = false,\n showZoomControls = true,\n dragHandle,\n onDragHandlePointerDown,\n extra,\n}: ToolbarProps) {\n return (\n <div className=\"vd-toolbar\" role=\"toolbar\" aria-label=\"Document viewer controls\">\n {dragHandle && (\n <span\n className=\"vd-toolbar-drag-handle\"\n onPointerDown={onDragHandlePointerDown}\n title=\"Drag to move\"\n aria-label=\"Drag to move\"\n >\n {dragHandle}\n </span>\n )}\n {showZoomControls && (\n <>\n <button\n type=\"button\"\n className=\"vd-toolbar-btn\"\n onClick={onZoomOut}\n aria-label=\"Zoom out\"\n title=\"Zoom out\"\n >\n <ZoomOutIcon />\n </button>\n <span className=\"vd-toolbar-scale\" aria-live=\"polite\">\n {Math.round(scale * 100)}%\n </span>\n <button type=\"button\" className=\"vd-toolbar-btn\" onClick={onZoomIn} aria-label=\"Zoom in\" title=\"Zoom in\">\n <ZoomInIcon />\n </button>\n <button\n type=\"button\"\n className=\"vd-toolbar-btn\"\n onClick={onReset}\n aria-label=\"Reset zoom to 100%\"\n title=\"Reset zoom to 100%\"\n >\n <ResetIcon />\n </button>\n </>\n )}\n {extra}\n <span className=\"vd-toolbar-spacer\" />\n {onDownload && (\n <button\n type=\"button\"\n className=\"vd-toolbar-btn\"\n onClick={onDownload}\n aria-label=\"Download file\"\n title=\"Download file\"\n >\n <DownloadIcon />\n </button>\n )}\n {onFullscreen && (\n <button\n type=\"button\"\n className=\"vd-toolbar-btn\"\n onClick={onFullscreen}\n aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\n title={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\n >\n {isFullscreen ? <FullscreenExitIcon /> : <FullscreenEnterIcon />}\n </button>\n )}\n </div>\n )\n}\n","import { useCallback, useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'\nimport { useDraggable, type Position } from './useDraggable'\nimport { GripIcon } from './icons'\nimport { Toolbar } from './Toolbar'\nimport { themeToStyle, type ViewerTheme } from './theme'\nimport type { ZoomPanControls } from './useZoomPan'\n\nexport interface ViewerShellProps {\n zoomPan: ZoomPanControls\n onDownload?: () => void\n toolbarExtra?: ReactNode\n children: ReactNode\n /** Show the zoom in/out/reset-to-100% controls. Default: true. */\n enableZoomControls?: boolean\n /** Show the fullscreen toggle button. Default: true. */\n enableFullscreen?: boolean\n /** Allow click-drag-release panning of the content. Default: true. */\n enablePan?: boolean\n /** Colors/radii to override the default look. Any field left out keeps the default. */\n theme?: ViewerTheme\n /** Width of the viewer. Accepts any CSS size (e.g. 1200, '1200px', '100%'). Default: 1200. */\n width?: number | string\n /** Height of the viewer. Accepts any CSS size (e.g. 700, '700px', '100vh'). Default: 700. */\n height?: number | string\n\n /** Render as a floating window (position: fixed) that can be dragged, instead of filling the parent container. Default: true. */\n floating?: boolean\n /** When floating, allow dragging the window by its toolbar. Default: true. */\n draggable?: boolean\n /** Initial position when floating. Default: centered in the viewport. */\n defaultPosition?: Position\n /** CSS transform-origin for the zoomed content. Default: 'center center'. */\n transformOrigin?: string\n}\n\nexport function ViewerShell({\n zoomPan,\n onDownload,\n toolbarExtra,\n children,\n enableZoomControls = true,\n enableFullscreen = true,\n enablePan = true,\n theme,\n width,\n height,\n floating = true,\n draggable = true,\n defaultPosition,\n transformOrigin,\n}: ViewerShellProps) {\n const rootRef = useRef<HTMLDivElement>(null)\n const [isFullscreen, setIsFullscreen] = useState(false)\n\n const effectiveWidth = typeof width === 'number' ? width : 1200\n const effectiveHeight = typeof height === 'number' ? height : 700\n const resolvedDefaultPosition =\n defaultPosition ??\n (typeof window !== 'undefined'\n ? {\n x: Math.max(0, (window.innerWidth - effectiveWidth) / 2),\n y: Math.max(0, (window.innerHeight - effectiveHeight) / 2),\n }\n : { x: 80, y: 80 })\n\n const { position, onDragHandlePointerDown } = useDraggable({\n defaultPosition: resolvedDefaultPosition,\n draggable,\n })\n\n useEffect(() => {\n const onChange = () => setIsFullscreen(document.fullscreenElement === rootRef.current)\n document.addEventListener('fullscreenchange', onChange)\n return () => document.removeEventListener('fullscreenchange', onChange)\n }, [])\n\n const fullscreenSupported =\n typeof document !== 'undefined' && typeof document.fullscreenEnabled === 'boolean'\n ? document.fullscreenEnabled\n : false\n\n const onFullscreen = useCallback(() => {\n const el = rootRef.current\n if (!el || typeof el.requestFullscreen !== 'function') return\n if (document.fullscreenElement) {\n document.exitFullscreen()\n } else {\n el.requestFullscreen()\n }\n }, [])\n\n const sizeStyle: CSSProperties = {\n width: width ?? 1200,\n height: height ?? 700,\n }\n const floatingStyle: CSSProperties | undefined = floating\n ? { position: 'fixed', left: position.x, top: position.y }\n : undefined\n\n return (\n <div\n className={`vd-shell${floating ? ' vd-shell--floating' : ''}`}\n ref={rootRef}\n style={{ ...themeToStyle(theme), ...sizeStyle, ...floatingStyle }}\n >\n <Toolbar\n scale={zoomPan.scale}\n onZoomIn={zoomPan.zoomIn}\n onZoomOut={zoomPan.zoomOut}\n onReset={zoomPan.resetZoom}\n onFullscreen={enableFullscreen && fullscreenSupported ? onFullscreen : undefined}\n onDownload={onDownload}\n isFullscreen={isFullscreen}\n showZoomControls={enableZoomControls}\n dragHandle={floating && draggable ? <GripIcon /> : undefined}\n onDragHandlePointerDown={floating && draggable ? onDragHandlePointerDown : undefined}\n extra={toolbarExtra}\n />\n <div\n className={`vd-canvas${enablePan ? '' : ' vd-canvas--no-pan'}`}\n ref={zoomPan.containerRef}\n onWheel={zoomPan.handlers.onWheel}\n onPointerDown={zoomPan.handlers.onPointerDown}\n onPointerMove={zoomPan.handlers.onPointerMove}\n onPointerUp={zoomPan.handlers.onPointerUp}\n >\n <div\n className=\"vd-canvas-content\"\n style={{\n transform: `translate(${zoomPan.translateX}px, ${zoomPan.translateY}px) scale(${zoomPan.scale})`,\n transformOrigin,\n }}\n >\n {children}\n </div>\n </div>\n </div>\n )\n}\n","import type { CSSProperties } from 'react'\n\nexport interface ViewerTheme {\n /** Background color of the viewer canvas area. */\n background?: string\n /** Background color of the toolbar strip. */\n toolbarBackground?: string\n /** Border color under the toolbar. */\n toolbarBorderColor?: string\n /** Hover background for toolbar buttons. */\n toolbarButtonHoverBackground?: string\n /** Text/icon color used across the toolbar. */\n textColor?: string\n /** Corner radius of the outer viewer container. */\n borderRadius?: string\n /** Corner radius of individual toolbar buttons. */\n toolbarButtonRadius?: string\n}\n\nconst THEME_VAR_MAP: Record<keyof ViewerTheme, string> = {\n background: '--vd-bg',\n toolbarBackground: '--vd-toolbar-bg',\n toolbarBorderColor: '--vd-toolbar-border-color',\n toolbarButtonHoverBackground: '--vd-toolbar-btn-hover-bg',\n textColor: '--vd-text-color',\n borderRadius: '--vd-border-radius',\n toolbarButtonRadius: '--vd-toolbar-btn-radius',\n}\n\n/** Converts a ViewerTheme into inline CSS custom properties overriding the defaults in styles.css. */\nexport function themeToStyle(theme?: ViewerTheme): CSSProperties | undefined {\n if (!theme) return undefined\n const style: Record<string, string> = {}\n for (const key of Object.keys(theme) as (keyof ViewerTheme)[]) {\n const value = theme[key]\n if (value) style[THEME_VAR_MAP[key]] = value\n }\n return style as CSSProperties\n}\n","import { useCallback, useRef, useState } from 'react'\n\nexport interface ZoomPanState {\n scale: number\n translateX: number\n translateY: number\n}\n\nexport interface ZoomPanOptions {\n minScale?: number\n maxScale?: number\n zoomStep?: number\n /** Allow click-drag-release panning by transforming the content. Default: true. */\n enablePan?: boolean\n /** Allow Ctrl/Cmd + scroll wheel zoom. Default: true. */\n enableWheelZoom?: boolean\n /** Allow click-drag-release scrolling of a natively-scrollable container (used instead of enablePan for scrollable content like PDF pages). Default: false. */\n enableDragScroll?: boolean\n /** Allow two-finger pinch to zoom on touch devices. Default: true. */\n enablePinchZoom?: boolean\n}\n\nexport interface ZoomPanControls extends ZoomPanState {\n containerRef: React.RefObject<HTMLDivElement>\n zoomIn: () => void\n zoomOut: () => void\n resetZoom: () => void\n setScale: (scale: number) => void\n handlers: {\n onWheel: (e: React.WheelEvent) => void\n onPointerDown: (e: React.PointerEvent) => void\n onPointerMove: (e: React.PointerEvent) => void\n onPointerUp: (e: React.PointerEvent) => void\n }\n}\n\nconst DEFAULT_MIN = 0.1\nconst DEFAULT_MAX = 8\nconst DEFAULT_STEP = 0.25\n\nfunction pointerDistance(a: PointerData, b: PointerData) {\n return Math.hypot(a.x - b.x, a.y - b.y)\n}\n\ninterface PointerData {\n x: number\n y: number\n}\n\nexport function useZoomPan(options: ZoomPanOptions = {}): ZoomPanControls {\n const {\n minScale = DEFAULT_MIN,\n maxScale = DEFAULT_MAX,\n zoomStep = DEFAULT_STEP,\n enablePan = true,\n enableWheelZoom = true,\n enableDragScroll = false,\n enablePinchZoom = true,\n } = options\n\n const [state, setState] = useState<ZoomPanState>({ scale: 1, translateX: 0, translateY: 0 })\n const containerRef = useRef<HTMLDivElement>(null)\n const dragState = useRef<{ dragging: boolean; startX: number; startY: number; originX: number; originY: number }>({\n dragging: false,\n startX: 0,\n startY: 0,\n originX: 0,\n originY: 0,\n })\n const scrollDragState = useRef<{ dragging: boolean; startX: number; startY: number; originLeft: number; originTop: number }>(\n { dragging: false, startX: 0, startY: 0, originLeft: 0, originTop: 0 }\n )\n const activePointers = useRef<Map<number, PointerData>>(new Map())\n const pinchState = useRef<{ pinching: boolean; startDistance: number; startScale: number }>({\n pinching: false,\n startDistance: 0,\n startScale: 1,\n })\n\n const clamp = useCallback(\n (value: number) => Math.min(maxScale, Math.max(minScale, value)),\n [minScale, maxScale]\n )\n\n const setScale = useCallback(\n (scale: number) => setState((prev) => ({ ...prev, scale: clamp(scale) })),\n [clamp]\n )\n\n const zoomIn = useCallback(() => setScale(state.scale + zoomStep), [setScale, state.scale, zoomStep])\n const zoomOut = useCallback(() => setScale(state.scale - zoomStep), [setScale, state.scale, zoomStep])\n const resetZoom = useCallback(() => setState({ scale: 1, translateX: 0, translateY: 0 }), [])\n\n const onWheel = useCallback(\n (e: React.WheelEvent) => {\n if (!enableWheelZoom) return\n if (!e.ctrlKey && !e.metaKey) return\n e.preventDefault()\n const delta = e.deltaY > 0 ? -zoomStep : zoomStep\n setScale(state.scale + delta)\n },\n [enableWheelZoom, setScale, state.scale, zoomStep]\n )\n\n const onPointerDown = useCallback(\n (e: React.PointerEvent) => {\n if (enablePinchZoom) {\n activePointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY })\n if (activePointers.current.size === 2) {\n const [a, b] = Array.from(activePointers.current.values())\n pinchState.current = { pinching: true, startDistance: pointerDistance(a, b), startScale: state.scale }\n dragState.current.dragging = false\n scrollDragState.current.dragging = false\n return\n }\n }\n\n if (enablePan) {\n dragState.current = {\n dragging: true,\n startX: e.clientX,\n startY: e.clientY,\n originX: state.translateX,\n originY: state.translateY,\n }\n ;(e.target as HTMLElement).setPointerCapture(e.pointerId)\n } else if (enableDragScroll && containerRef.current) {\n scrollDragState.current = {\n dragging: true,\n startX: e.clientX,\n startY: e.clientY,\n originLeft: containerRef.current.scrollLeft,\n originTop: containerRef.current.scrollTop,\n }\n ;(e.target as HTMLElement).setPointerCapture(e.pointerId)\n }\n },\n [enablePan, enableDragScroll, enablePinchZoom, state.translateX, state.translateY, state.scale]\n )\n\n const onPointerMove = useCallback(\n (e: React.PointerEvent) => {\n if (enablePinchZoom && activePointers.current.has(e.pointerId)) {\n activePointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY })\n }\n\n if (pinchState.current.pinching && activePointers.current.size === 2) {\n const [a, b] = Array.from(activePointers.current.values())\n const distance = pointerDistance(a, b)\n const ratio = distance / (pinchState.current.startDistance || distance)\n setScale(pinchState.current.startScale * ratio)\n return\n }\n\n if (enablePan) {\n if (!dragState.current.dragging) return\n const dx = e.clientX - dragState.current.startX\n const dy = e.clientY - dragState.current.startY\n setState((prev) => ({\n ...prev,\n translateX: dragState.current.originX + dx,\n translateY: dragState.current.originY + dy,\n }))\n } else if (enableDragScroll) {\n if (!scrollDragState.current.dragging || !containerRef.current) return\n const dx = e.clientX - scrollDragState.current.startX\n const dy = e.clientY - scrollDragState.current.startY\n containerRef.current.scrollLeft = scrollDragState.current.originLeft - dx\n containerRef.current.scrollTop = scrollDragState.current.originTop - dy\n }\n },\n [enablePan, enableDragScroll, enablePinchZoom, setScale]\n )\n\n const onPointerUp = useCallback(\n (e: React.PointerEvent) => {\n activePointers.current.delete(e.pointerId)\n if (activePointers.current.size < 2) {\n pinchState.current.pinching = false\n }\n\n if (enablePan) {\n dragState.current.dragging = false\n ;(e.target as HTMLElement).releasePointerCapture(e.pointerId)\n } else if (enableDragScroll) {\n scrollDragState.current.dragging = false\n ;(e.target as HTMLElement).releasePointerCapture(e.pointerId)\n }\n },\n [enablePan, enableDragScroll]\n )\n\n return {\n ...state,\n containerRef,\n zoomIn,\n zoomOut,\n resetZoom,\n setScale,\n handlers: { onWheel, onPointerDown, onPointerMove, onPointerUp },\n }\n}\n"],"mappings":"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","names":["jsx","jsxs","useCallback","useState","jsx","jsxs","useState","useCallback","useCallback","useRef","useState"]}