viewdoc 0.2.0 → 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.
- package/README.md +8 -2
- package/dist/DocxViewer-F3MW62BA.js +10 -0
- package/dist/PdfViewer-6YF7AER3.js +9 -0
- package/dist/XlsxViewer-7X25BBRT.js +10 -0
- package/dist/{chunk-LE4YTMPN.js → chunk-3EUNNQXJ.js} +36 -9
- package/dist/chunk-3EUNNQXJ.js.map +1 -0
- package/dist/{chunk-4VVVAGYE.js → chunk-4BFQ7U2R.js} +15 -5
- package/dist/chunk-4BFQ7U2R.js.map +1 -0
- package/dist/{chunk-KRN2POC4.js → chunk-AIDVQYXH.js} +8 -5
- package/dist/chunk-AIDVQYXH.js.map +1 -0
- package/dist/chunk-DKFD7M4R.js +12 -0
- package/dist/chunk-DKFD7M4R.js.map +1 -0
- package/dist/{chunk-OA35SYM6.js → chunk-HOF2XEU5.js} +43 -6
- package/dist/chunk-HOF2XEU5.js.map +1 -0
- package/dist/docx.cjs +58 -8
- package/dist/docx.cjs.map +1 -1
- package/dist/docx.d.cts +3 -1
- package/dist/docx.d.ts +3 -1
- package/dist/docx.js +4 -2
- package/dist/index.cjs +155 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +27 -9
- package/dist/index.js.map +1 -1
- package/dist/pdf.cjs +46 -8
- package/dist/pdf.cjs.map +1 -1
- package/dist/pdf.d.cts +3 -1
- package/dist/pdf.d.ts +3 -1
- package/dist/pdf.js +3 -2
- package/dist/styles.css +15 -0
- package/dist/styles.css.map +1 -1
- package/dist/xlsx.cjs +77 -12
- package/dist/xlsx.cjs.map +1 -1
- package/dist/xlsx.d.cts +9 -1
- package/dist/xlsx.d.ts +9 -1
- package/dist/xlsx.js +4 -2
- package/package.json +5 -1
- package/dist/DocxViewer-VQN3BESX.js +0 -8
- package/dist/PdfViewer-NVAQBI2Y.js +0 -8
- package/dist/XlsxViewer-SQMAF5UA.js +0 -8
- package/dist/chunk-4VVVAGYE.js.map +0 -1
- package/dist/chunk-KRN2POC4.js.map +0 -1
- package/dist/chunk-LE4YTMPN.js.map +0 -1
- package/dist/chunk-OA35SYM6.js.map +0 -1
- /package/dist/{DocxViewer-VQN3BESX.js.map → DocxViewer-F3MW62BA.js.map} +0 -0
- /package/dist/{PdfViewer-NVAQBI2Y.js.map → PdfViewer-6YF7AER3.js.map} +0 -0
- /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%,
|
|
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
|
|
@@ -151,8 +153,12 @@ In non-floating mode, the viewer still renders at its `width`/`height` (default
|
|
|
151
153
|
|
|
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
|
|
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)
|
|
155
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
|
|
156
162
|
|
|
157
163
|
## License
|
|
158
164
|
|
|
@@ -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-
|
|
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
|
-
|
|
91
|
-
|
|
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-
|
|
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-
|
|
8
|
+
} from "./chunk-HOF2XEU5.js";
|
|
5
9
|
|
|
6
10
|
// src/viewers/useDocxHtml.ts
|
|
7
11
|
import { useEffect, useState } from "react";
|
|
@@ -18,9 +22,13 @@ function useDocxHtml(uri) {
|
|
|
18
22
|
fetch(uri).then((res) => {
|
|
19
23
|
if (!res.ok) throw new Error(`Failed to fetch document: ${res.status} ${res.statusText}`);
|
|
20
24
|
return res.arrayBuffer();
|
|
21
|
-
}).then(
|
|
25
|
+
}).then(
|
|
26
|
+
(arrayBuffer) => mammoth.convertToHtml({ arrayBuffer }).catch(() => {
|
|
27
|
+
throw new Error("This file could not be read as a Word document (.docx).");
|
|
28
|
+
})
|
|
29
|
+
).then((result) => {
|
|
22
30
|
if (cancelled) return;
|
|
23
|
-
setHtml(result.value);
|
|
31
|
+
setHtml(sanitizeUntrustedHtml(result.value));
|
|
24
32
|
setLoading(false);
|
|
25
33
|
}).catch((err) => {
|
|
26
34
|
if (!cancelled) {
|
|
@@ -58,6 +66,7 @@ function DocxViewer({
|
|
|
58
66
|
zoomStep,
|
|
59
67
|
enableZoomControls = true,
|
|
60
68
|
enableWheelZoom = true,
|
|
69
|
+
enablePinchZoom = true,
|
|
61
70
|
enableFullscreen = true,
|
|
62
71
|
enableDownload = true,
|
|
63
72
|
onDownload,
|
|
@@ -72,7 +81,8 @@ function DocxViewer({
|
|
|
72
81
|
zoomStep,
|
|
73
82
|
enablePan: false,
|
|
74
83
|
enableDragScroll: true,
|
|
75
|
-
enableWheelZoom
|
|
84
|
+
enableWheelZoom,
|
|
85
|
+
enablePinchZoom
|
|
76
86
|
});
|
|
77
87
|
const { html, error, loading } = useDocxHtml(uri);
|
|
78
88
|
const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload(uri, fileName)) : void 0;
|
|
@@ -107,4 +117,4 @@ function DocxViewer({
|
|
|
107
117
|
export {
|
|
108
118
|
DocxViewer
|
|
109
119
|
};
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
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-
|
|
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);
|
|
@@ -157,8 +160,8 @@ function PdfViewer({
|
|
|
157
160
|
numPages
|
|
158
161
|
] }) : void 0,
|
|
159
162
|
children: [
|
|
160
|
-
loading && /* @__PURE__ */ jsx2("span", { className: "vd-
|
|
161
|
-
error && /* @__PURE__ */ jsxs("span", { className: "vd-
|
|
163
|
+
loading && /* @__PURE__ */ jsx2("span", { className: "vd-doc-status", children: "Loading PDF\u2026" }),
|
|
164
|
+
error && /* @__PURE__ */ jsxs("span", { className: "vd-doc-status", children: [
|
|
162
165
|
"Failed to load PDF: ",
|
|
163
166
|
error.message
|
|
164
167
|
] }),
|
|
@@ -171,4 +174,4 @@ function PdfViewer({
|
|
|
171
174
|
export {
|
|
172
175
|
PdfViewer
|
|
173
176
|
};
|
|
174
|
-
//# sourceMappingURL=chunk-
|
|
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 @@
|
|
|
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-
|
|
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"]}
|