viewdoc 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,994 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DocViewer: () => DocViewer,
34
+ DocxViewer: () => DocxViewer,
35
+ ImageViewer: () => ImageViewer,
36
+ PdfViewer: () => PdfViewer,
37
+ Toolbar: () => Toolbar,
38
+ ViewerShell: () => ViewerShell,
39
+ XlsxViewer: () => XlsxViewer,
40
+ detectFileType: () => detectFileType,
41
+ getUrlExtension: () => getUrlExtension,
42
+ useDraggable: () => useDraggable,
43
+ useZoomPan: () => useZoomPan
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+
47
+ // src/core/ViewerShell.tsx
48
+ var import_react2 = require("react");
49
+
50
+ // src/core/useDraggable.ts
51
+ var import_react = require("react");
52
+ function useDraggable(options = {}) {
53
+ const { defaultPosition = { x: 80, y: 80 }, draggable = true } = options;
54
+ const [position, setPosition] = (0, import_react.useState)(defaultPosition);
55
+ const onDragHandlePointerDown = (0, import_react.useCallback)(
56
+ (e) => {
57
+ if (!draggable) return;
58
+ e.preventDefault();
59
+ const startX = e.clientX;
60
+ const startY = e.clientY;
61
+ setPosition((current) => {
62
+ const originX = current.x;
63
+ const originY = current.y;
64
+ const onMove = (ev) => {
65
+ setPosition({ x: originX + (ev.clientX - startX), y: originY + (ev.clientY - startY) });
66
+ };
67
+ const onUp = () => {
68
+ window.removeEventListener("pointermove", onMove);
69
+ window.removeEventListener("pointerup", onUp);
70
+ };
71
+ window.addEventListener("pointermove", onMove);
72
+ window.addEventListener("pointerup", onUp);
73
+ return current;
74
+ });
75
+ },
76
+ [draggable]
77
+ );
78
+ return { position, onDragHandlePointerDown };
79
+ }
80
+
81
+ // src/core/icons.tsx
82
+ var import_jsx_runtime = require("react/jsx-runtime");
83
+ function Svg(props) {
84
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
85
+ "svg",
86
+ {
87
+ width: "16",
88
+ height: "16",
89
+ viewBox: "0 0 24 24",
90
+ fill: "none",
91
+ stroke: "currentColor",
92
+ strokeWidth: "2",
93
+ strokeLinecap: "round",
94
+ strokeLinejoin: "round",
95
+ "aria-hidden": "true",
96
+ focusable: "false",
97
+ ...props
98
+ }
99
+ );
100
+ }
101
+ function ZoomOutIcon() {
102
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "11", cy: "11", r: "7" }),
104
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" }),
105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "8", y1: "11", x2: "14", y2: "11" })
106
+ ] });
107
+ }
108
+ function ZoomInIcon() {
109
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
110
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "11", cy: "11", r: "7" }),
111
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" }),
112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "11", y1: "8", x2: "11", y2: "14" }),
113
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "8", y1: "11", x2: "14", y2: "11" })
114
+ ] });
115
+ }
116
+ function DownloadIcon() {
117
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 3v12" }),
119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M7 10l5 5 5-5" }),
120
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 21h14" })
121
+ ] });
122
+ }
123
+ function FullscreenEnterIcon() {
124
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "15 3 21 3 21 9" }),
126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "9 21 3 21 3 15" }),
127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
129
+ ] });
130
+ }
131
+ function ResetIcon() {
132
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 12a9 9 0 1 0 3-6.7" }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "3 3 3 8 8 8" })
135
+ ] });
136
+ }
137
+ function GripIcon() {
138
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "5 9 2 12 5 15" }),
140
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "9 5 12 2 15 5" }),
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "15 19 12 22 9 19" }),
142
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "19 9 22 12 19 15" }),
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "2", x2: "12", y2: "22" })
145
+ ] });
146
+ }
147
+ function FullscreenExitIcon() {
148
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Svg, { children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "9 3 9 9 3 9" }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "15 21 15 15 21 15" }),
151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "9", y1: "9", x2: "3", y2: "3" }),
152
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "15", y1: "15", x2: "21", y2: "21" })
153
+ ] });
154
+ }
155
+
156
+ // src/core/Toolbar.tsx
157
+ var import_jsx_runtime2 = require("react/jsx-runtime");
158
+ function Toolbar({
159
+ scale,
160
+ onZoomIn,
161
+ onZoomOut,
162
+ onReset,
163
+ onFullscreen,
164
+ onDownload,
165
+ isFullscreen = false,
166
+ showZoomControls = true,
167
+ dragHandle,
168
+ onDragHandlePointerDown,
169
+ extra
170
+ }) {
171
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vd-toolbar", role: "toolbar", "aria-label": "Document viewer controls", children: [
172
+ dragHandle && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
173
+ "span",
174
+ {
175
+ className: "vd-toolbar-drag-handle",
176
+ onPointerDown: onDragHandlePointerDown,
177
+ title: "Drag to move",
178
+ "aria-label": "Drag to move",
179
+ children: dragHandle
180
+ }
181
+ ),
182
+ showZoomControls && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
183
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
184
+ "button",
185
+ {
186
+ type: "button",
187
+ className: "vd-toolbar-btn",
188
+ onClick: onZoomOut,
189
+ "aria-label": "Zoom out",
190
+ title: "Zoom out",
191
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ZoomOutIcon, {})
192
+ }
193
+ ),
194
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "vd-toolbar-scale", "aria-live": "polite", children: [
195
+ Math.round(scale * 100),
196
+ "%"
197
+ ] }),
198
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "vd-toolbar-btn", onClick: onZoomIn, "aria-label": "Zoom in", title: "Zoom in", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ZoomInIcon, {}) }),
199
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
200
+ "button",
201
+ {
202
+ type: "button",
203
+ className: "vd-toolbar-btn",
204
+ onClick: onReset,
205
+ "aria-label": "Reset zoom to 100%",
206
+ title: "Reset zoom to 100%",
207
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ResetIcon, {})
208
+ }
209
+ )
210
+ ] }),
211
+ extra,
212
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "vd-toolbar-spacer" }),
213
+ onDownload && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
214
+ "button",
215
+ {
216
+ type: "button",
217
+ className: "vd-toolbar-btn",
218
+ onClick: onDownload,
219
+ "aria-label": "Download file",
220
+ title: "Download file",
221
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DownloadIcon, {})
222
+ }
223
+ ),
224
+ onFullscreen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
225
+ "button",
226
+ {
227
+ type: "button",
228
+ className: "vd-toolbar-btn",
229
+ onClick: onFullscreen,
230
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
231
+ title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
232
+ children: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FullscreenExitIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FullscreenEnterIcon, {})
233
+ }
234
+ )
235
+ ] });
236
+ }
237
+
238
+ // src/core/theme.ts
239
+ var THEME_VAR_MAP = {
240
+ background: "--vd-bg",
241
+ toolbarBackground: "--vd-toolbar-bg",
242
+ toolbarBorderColor: "--vd-toolbar-border-color",
243
+ toolbarButtonHoverBackground: "--vd-toolbar-btn-hover-bg",
244
+ textColor: "--vd-text-color",
245
+ borderRadius: "--vd-border-radius",
246
+ toolbarButtonRadius: "--vd-toolbar-btn-radius"
247
+ };
248
+ function themeToStyle(theme) {
249
+ if (!theme) return void 0;
250
+ const style = {};
251
+ for (const key of Object.keys(theme)) {
252
+ const value = theme[key];
253
+ if (value) style[THEME_VAR_MAP[key]] = value;
254
+ }
255
+ return style;
256
+ }
257
+
258
+ // src/core/ViewerShell.tsx
259
+ var import_jsx_runtime3 = require("react/jsx-runtime");
260
+ function ViewerShell({
261
+ zoomPan,
262
+ onDownload,
263
+ toolbarExtra,
264
+ children,
265
+ enableZoomControls = true,
266
+ enableFullscreen = true,
267
+ enablePan = true,
268
+ theme,
269
+ width,
270
+ height,
271
+ floating = true,
272
+ draggable = true,
273
+ defaultPosition,
274
+ transformOrigin
275
+ }) {
276
+ const rootRef = (0, import_react2.useRef)(null);
277
+ const [isFullscreen, setIsFullscreen] = (0, import_react2.useState)(false);
278
+ const effectiveWidth = typeof width === "number" ? width : 1200;
279
+ const effectiveHeight = typeof height === "number" ? height : 700;
280
+ const resolvedDefaultPosition = defaultPosition ?? (typeof window !== "undefined" ? {
281
+ x: Math.max(0, (window.innerWidth - effectiveWidth) / 2),
282
+ y: Math.max(0, (window.innerHeight - effectiveHeight) / 2)
283
+ } : { x: 80, y: 80 });
284
+ const { position, onDragHandlePointerDown } = useDraggable({
285
+ defaultPosition: resolvedDefaultPosition,
286
+ draggable
287
+ });
288
+ (0, import_react2.useEffect)(() => {
289
+ const onChange = () => setIsFullscreen(document.fullscreenElement === rootRef.current);
290
+ document.addEventListener("fullscreenchange", onChange);
291
+ return () => document.removeEventListener("fullscreenchange", onChange);
292
+ }, []);
293
+ const onFullscreen = (0, import_react2.useCallback)(() => {
294
+ const el = rootRef.current;
295
+ if (!el) return;
296
+ if (document.fullscreenElement) {
297
+ document.exitFullscreen();
298
+ } else {
299
+ el.requestFullscreen();
300
+ }
301
+ }, []);
302
+ const sizeStyle = {
303
+ width: width ?? 1200,
304
+ height: height ?? 700
305
+ };
306
+ const floatingStyle = floating ? { position: "fixed", left: position.x, top: position.y } : void 0;
307
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
308
+ "div",
309
+ {
310
+ className: `vd-shell${floating ? " vd-shell--floating" : ""}`,
311
+ ref: rootRef,
312
+ style: { ...themeToStyle(theme), ...sizeStyle, ...floatingStyle },
313
+ children: [
314
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
315
+ Toolbar,
316
+ {
317
+ scale: zoomPan.scale,
318
+ onZoomIn: zoomPan.zoomIn,
319
+ onZoomOut: zoomPan.zoomOut,
320
+ onReset: zoomPan.resetZoom,
321
+ onFullscreen: enableFullscreen ? onFullscreen : void 0,
322
+ onDownload,
323
+ isFullscreen,
324
+ showZoomControls: enableZoomControls,
325
+ dragHandle: floating && draggable ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GripIcon, {}) : void 0,
326
+ onDragHandlePointerDown: floating && draggable ? onDragHandlePointerDown : void 0,
327
+ extra: toolbarExtra
328
+ }
329
+ ),
330
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
331
+ "div",
332
+ {
333
+ className: `vd-canvas${enablePan ? "" : " vd-canvas--no-pan"}`,
334
+ ref: zoomPan.containerRef,
335
+ onWheel: zoomPan.handlers.onWheel,
336
+ onPointerDown: zoomPan.handlers.onPointerDown,
337
+ onPointerMove: zoomPan.handlers.onPointerMove,
338
+ onPointerUp: zoomPan.handlers.onPointerUp,
339
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
340
+ "div",
341
+ {
342
+ className: "vd-canvas-content",
343
+ style: {
344
+ transform: `translate(${zoomPan.translateX}px, ${zoomPan.translateY}px) scale(${zoomPan.scale})`,
345
+ transformOrigin
346
+ },
347
+ children
348
+ }
349
+ )
350
+ }
351
+ )
352
+ ]
353
+ }
354
+ );
355
+ }
356
+
357
+ // src/core/useZoomPan.ts
358
+ var import_react3 = require("react");
359
+ var DEFAULT_MIN = 0.1;
360
+ var DEFAULT_MAX = 8;
361
+ var DEFAULT_STEP = 0.25;
362
+ function useZoomPan(options = {}) {
363
+ const {
364
+ minScale = DEFAULT_MIN,
365
+ maxScale = DEFAULT_MAX,
366
+ zoomStep = DEFAULT_STEP,
367
+ enablePan = true,
368
+ enableWheelZoom = true,
369
+ enableDragScroll = false
370
+ } = options;
371
+ const [state, setState] = (0, import_react3.useState)({ scale: 1, translateX: 0, translateY: 0 });
372
+ const containerRef = (0, import_react3.useRef)(null);
373
+ const dragState = (0, import_react3.useRef)({
374
+ dragging: false,
375
+ startX: 0,
376
+ startY: 0,
377
+ originX: 0,
378
+ originY: 0
379
+ });
380
+ const scrollDragState = (0, import_react3.useRef)(
381
+ { dragging: false, startX: 0, startY: 0, originLeft: 0, originTop: 0 }
382
+ );
383
+ const clamp = (0, import_react3.useCallback)(
384
+ (value) => Math.min(maxScale, Math.max(minScale, value)),
385
+ [minScale, maxScale]
386
+ );
387
+ const setScale = (0, import_react3.useCallback)(
388
+ (scale) => setState((prev) => ({ ...prev, scale: clamp(scale) })),
389
+ [clamp]
390
+ );
391
+ const zoomIn = (0, import_react3.useCallback)(() => setScale(state.scale + zoomStep), [setScale, state.scale, zoomStep]);
392
+ const zoomOut = (0, import_react3.useCallback)(() => setScale(state.scale - zoomStep), [setScale, state.scale, zoomStep]);
393
+ const resetZoom = (0, import_react3.useCallback)(() => setState({ scale: 1, translateX: 0, translateY: 0 }), []);
394
+ const onWheel = (0, import_react3.useCallback)(
395
+ (e) => {
396
+ if (!enableWheelZoom) return;
397
+ if (!e.ctrlKey && !e.metaKey) return;
398
+ e.preventDefault();
399
+ const delta = e.deltaY > 0 ? -zoomStep : zoomStep;
400
+ setScale(state.scale + delta);
401
+ },
402
+ [enableWheelZoom, setScale, state.scale, zoomStep]
403
+ );
404
+ const onPointerDown = (0, import_react3.useCallback)(
405
+ (e) => {
406
+ if (enablePan) {
407
+ dragState.current = {
408
+ dragging: true,
409
+ startX: e.clientX,
410
+ startY: e.clientY,
411
+ originX: state.translateX,
412
+ originY: state.translateY
413
+ };
414
+ e.target.setPointerCapture(e.pointerId);
415
+ } else if (enableDragScroll && containerRef.current) {
416
+ scrollDragState.current = {
417
+ dragging: true,
418
+ startX: e.clientX,
419
+ startY: e.clientY,
420
+ originLeft: containerRef.current.scrollLeft,
421
+ originTop: containerRef.current.scrollTop
422
+ };
423
+ e.target.setPointerCapture(e.pointerId);
424
+ }
425
+ },
426
+ [enablePan, enableDragScroll, state.translateX, state.translateY]
427
+ );
428
+ const onPointerMove = (0, import_react3.useCallback)(
429
+ (e) => {
430
+ if (enablePan) {
431
+ if (!dragState.current.dragging) return;
432
+ const dx = e.clientX - dragState.current.startX;
433
+ const dy = e.clientY - dragState.current.startY;
434
+ setState((prev) => ({
435
+ ...prev,
436
+ translateX: dragState.current.originX + dx,
437
+ translateY: dragState.current.originY + dy
438
+ }));
439
+ } else if (enableDragScroll) {
440
+ if (!scrollDragState.current.dragging || !containerRef.current) return;
441
+ const dx = e.clientX - scrollDragState.current.startX;
442
+ const dy = e.clientY - scrollDragState.current.startY;
443
+ containerRef.current.scrollLeft = scrollDragState.current.originLeft - dx;
444
+ containerRef.current.scrollTop = scrollDragState.current.originTop - dy;
445
+ }
446
+ },
447
+ [enablePan, enableDragScroll]
448
+ );
449
+ const onPointerUp = (0, import_react3.useCallback)(
450
+ (e) => {
451
+ if (enablePan) {
452
+ dragState.current.dragging = false;
453
+ e.target.releasePointerCapture(e.pointerId);
454
+ } else if (enableDragScroll) {
455
+ scrollDragState.current.dragging = false;
456
+ e.target.releasePointerCapture(e.pointerId);
457
+ }
458
+ },
459
+ [enablePan, enableDragScroll]
460
+ );
461
+ return {
462
+ ...state,
463
+ containerRef,
464
+ zoomIn,
465
+ zoomOut,
466
+ resetZoom,
467
+ setScale,
468
+ handlers: { onWheel, onPointerDown, onPointerMove, onPointerUp }
469
+ };
470
+ }
471
+
472
+ // src/viewers/ImageViewer.tsx
473
+ var import_jsx_runtime4 = require("react/jsx-runtime");
474
+ function defaultDownload(uri, fileName) {
475
+ const link = document.createElement("a");
476
+ link.href = uri;
477
+ link.download = fileName ?? "";
478
+ link.rel = "noopener";
479
+ document.body.appendChild(link);
480
+ link.click();
481
+ document.body.removeChild(link);
482
+ }
483
+ function ImageViewer({
484
+ uri,
485
+ fileName,
486
+ className,
487
+ style,
488
+ width,
489
+ height,
490
+ minScale,
491
+ maxScale,
492
+ zoomStep,
493
+ enableZoomControls = true,
494
+ enableWheelZoom = true,
495
+ enablePan = true,
496
+ enableFullscreen = true,
497
+ enableDownload = true,
498
+ onDownload,
499
+ theme,
500
+ floating = true,
501
+ windowDraggable = true,
502
+ defaultPosition
503
+ }) {
504
+ const zoomPan = useZoomPan({ minScale, maxScale, zoomStep, enablePan, enableWheelZoom });
505
+ const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload(uri, fileName)) : void 0;
506
+ const wrapperStyle = floating ? { ...style } : { display: "flex", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", ...style };
507
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, style: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
508
+ ViewerShell,
509
+ {
510
+ zoomPan,
511
+ onDownload: handleDownload,
512
+ enableZoomControls,
513
+ enableFullscreen,
514
+ enablePan,
515
+ theme,
516
+ width,
517
+ height,
518
+ floating,
519
+ draggable: windowDraggable,
520
+ defaultPosition,
521
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { className: "vd-image", src: uri, alt: fileName ?? "document image", draggable: false })
522
+ }
523
+ ) });
524
+ }
525
+
526
+ // src/viewers/PdfPage.tsx
527
+ var import_react4 = require("react");
528
+ var import_jsx_runtime5 = require("react/jsx-runtime");
529
+ function PdfPage({ doc, pageNumber, renderScale = 1.5 }) {
530
+ const canvasRef = (0, import_react4.useRef)(null);
531
+ const renderTaskRef = (0, import_react4.useRef)(null);
532
+ (0, import_react4.useEffect)(() => {
533
+ let cancelled = false;
534
+ doc.getPage(pageNumber).then((page) => {
535
+ if (cancelled) return;
536
+ const viewport = page.getViewport({ scale: renderScale });
537
+ const canvas = canvasRef.current;
538
+ if (!canvas) return;
539
+ const context = canvas.getContext("2d");
540
+ if (!context) return;
541
+ canvas.width = viewport.width;
542
+ canvas.height = viewport.height;
543
+ canvas.style.width = `${viewport.width / renderScale}px`;
544
+ canvas.style.height = `${viewport.height / renderScale}px`;
545
+ const renderTask = page.render({ canvasContext: context, viewport, canvas });
546
+ renderTaskRef.current = renderTask;
547
+ renderTask.promise.catch(() => {
548
+ });
549
+ });
550
+ return () => {
551
+ cancelled = true;
552
+ renderTaskRef.current?.cancel();
553
+ };
554
+ }, [doc, pageNumber, renderScale]);
555
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("canvas", { ref: canvasRef, className: "vd-pdf-page", "data-page-number": pageNumber });
556
+ }
557
+
558
+ // src/viewers/usePdfDocument.ts
559
+ var import_react5 = require("react");
560
+ var pdfjsLib = __toESM(require("pdfjs-dist"), 1);
561
+ var import_meta = {};
562
+ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.mjs", import_meta.url).toString();
563
+ function usePdfDocument(uri) {
564
+ const [doc, setDoc] = (0, import_react5.useState)(null);
565
+ const [error, setError] = (0, import_react5.useState)(null);
566
+ const [loading, setLoading] = (0, import_react5.useState)(true);
567
+ (0, import_react5.useEffect)(() => {
568
+ let cancelled = false;
569
+ setLoading(true);
570
+ setError(null);
571
+ setDoc(null);
572
+ const loadingTask = pdfjsLib.getDocument({ url: uri });
573
+ loadingTask.promise.then((pdf) => {
574
+ if (cancelled) return;
575
+ setDoc(pdf);
576
+ setLoading(false);
577
+ }).catch((err) => {
578
+ if (!cancelled) {
579
+ setError(err);
580
+ setLoading(false);
581
+ }
582
+ });
583
+ return () => {
584
+ cancelled = true;
585
+ loadingTask.destroy();
586
+ };
587
+ }, [uri]);
588
+ return { doc, numPages: doc?.numPages ?? 0, error, loading };
589
+ }
590
+
591
+ // src/viewers/useVisiblePage.ts
592
+ var import_react6 = require("react");
593
+ function useVisiblePage(containerRef, numPages) {
594
+ const [currentPage, setCurrentPage] = (0, import_react6.useState)(1);
595
+ (0, import_react6.useEffect)(() => {
596
+ const root = containerRef.current;
597
+ if (!root || numPages === 0) return;
598
+ const observer = new IntersectionObserver(
599
+ (entries) => {
600
+ const mostVisible = entries.filter((e) => e.isIntersecting).sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
601
+ if (mostVisible) {
602
+ const page = Number(mostVisible.target.dataset.pageNumber);
603
+ if (page) setCurrentPage(page);
604
+ }
605
+ },
606
+ { root, threshold: [0.25, 0.5, 0.75] }
607
+ );
608
+ const pages = root.querySelectorAll("[data-page-number]");
609
+ pages.forEach((el) => observer.observe(el));
610
+ return () => observer.disconnect();
611
+ }, [containerRef, numPages]);
612
+ return currentPage;
613
+ }
614
+
615
+ // src/viewers/PdfViewer.tsx
616
+ var import_jsx_runtime6 = require("react/jsx-runtime");
617
+ function defaultDownload2(uri, fileName) {
618
+ const link = document.createElement("a");
619
+ link.href = uri;
620
+ link.download = fileName ?? "";
621
+ link.rel = "noopener";
622
+ document.body.appendChild(link);
623
+ link.click();
624
+ document.body.removeChild(link);
625
+ }
626
+ function PdfViewer({
627
+ uri,
628
+ fileName,
629
+ className,
630
+ style,
631
+ width,
632
+ height,
633
+ minScale = 0.25,
634
+ maxScale = 5,
635
+ zoomStep,
636
+ renderScale,
637
+ enableZoomControls = true,
638
+ enableWheelZoom = true,
639
+ enableFullscreen = true,
640
+ enableDownload = true,
641
+ onDownload,
642
+ theme,
643
+ floating = true,
644
+ windowDraggable = true,
645
+ defaultPosition
646
+ }) {
647
+ const zoomPan = useZoomPan({
648
+ minScale,
649
+ maxScale,
650
+ zoomStep,
651
+ enablePan: false,
652
+ enableDragScroll: true,
653
+ enableWheelZoom
654
+ });
655
+ const { doc, numPages, error, loading } = usePdfDocument(uri);
656
+ const currentPage = useVisiblePage(zoomPan.containerRef, numPages);
657
+ const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload2(uri, fileName)) : void 0;
658
+ const wrapperStyle = floating ? { ...style } : { display: "flex", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", ...style };
659
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className, style: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
660
+ ViewerShell,
661
+ {
662
+ zoomPan,
663
+ onDownload: handleDownload,
664
+ enableZoomControls,
665
+ enableFullscreen,
666
+ enablePan: false,
667
+ theme,
668
+ width,
669
+ height,
670
+ floating,
671
+ draggable: windowDraggable,
672
+ defaultPosition,
673
+ transformOrigin: "top center",
674
+ toolbarExtra: numPages > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "vd-pdf-page-indicator", children: [
675
+ "Page ",
676
+ currentPage,
677
+ " / ",
678
+ numPages
679
+ ] }) : void 0,
680
+ children: [
681
+ loading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vd-pdf-page-indicator", children: "Loading PDF\u2026" }),
682
+ error && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "vd-pdf-page-indicator", children: [
683
+ "Failed to load PDF: ",
684
+ error.message
685
+ ] }),
686
+ doc && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "vd-pdf-pages", children: Array.from({ length: numPages }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PdfPage, { doc, pageNumber: i + 1, renderScale }, i + 1)) })
687
+ ]
688
+ }
689
+ ) });
690
+ }
691
+
692
+ // src/viewers/useDocxHtml.ts
693
+ var import_react7 = require("react");
694
+ var import_mammoth = __toESM(require("mammoth"), 1);
695
+ function useDocxHtml(uri) {
696
+ const [html, setHtml] = (0, import_react7.useState)(null);
697
+ const [error, setError] = (0, import_react7.useState)(null);
698
+ const [loading, setLoading] = (0, import_react7.useState)(true);
699
+ (0, import_react7.useEffect)(() => {
700
+ let cancelled = false;
701
+ setLoading(true);
702
+ setError(null);
703
+ setHtml(null);
704
+ fetch(uri).then((res) => {
705
+ if (!res.ok) throw new Error(`Failed to fetch document: ${res.status} ${res.statusText}`);
706
+ return res.arrayBuffer();
707
+ }).then((arrayBuffer) => import_mammoth.default.convertToHtml({ arrayBuffer })).then((result) => {
708
+ if (cancelled) return;
709
+ setHtml(result.value);
710
+ setLoading(false);
711
+ }).catch((err) => {
712
+ if (!cancelled) {
713
+ setError(err);
714
+ setLoading(false);
715
+ }
716
+ });
717
+ return () => {
718
+ cancelled = true;
719
+ };
720
+ }, [uri]);
721
+ return { html, error, loading };
722
+ }
723
+
724
+ // src/viewers/DocxViewer.tsx
725
+ var import_jsx_runtime7 = require("react/jsx-runtime");
726
+ function defaultDownload3(uri, fileName) {
727
+ const link = document.createElement("a");
728
+ link.href = uri;
729
+ link.download = fileName ?? "";
730
+ link.rel = "noopener";
731
+ document.body.appendChild(link);
732
+ link.click();
733
+ document.body.removeChild(link);
734
+ }
735
+ function DocxViewer({
736
+ uri,
737
+ fileName,
738
+ className,
739
+ style,
740
+ width,
741
+ height,
742
+ minScale = 0.5,
743
+ maxScale = 3,
744
+ zoomStep,
745
+ enableZoomControls = true,
746
+ enableWheelZoom = true,
747
+ enableFullscreen = true,
748
+ enableDownload = true,
749
+ onDownload,
750
+ theme,
751
+ floating = true,
752
+ windowDraggable = true,
753
+ defaultPosition
754
+ }) {
755
+ const zoomPan = useZoomPan({
756
+ minScale,
757
+ maxScale,
758
+ zoomStep,
759
+ enablePan: false,
760
+ enableDragScroll: true,
761
+ enableWheelZoom
762
+ });
763
+ const { html, error, loading } = useDocxHtml(uri);
764
+ const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload3(uri, fileName)) : void 0;
765
+ const wrapperStyle = floating ? { ...style } : { display: "flex", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", ...style };
766
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className, style: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
767
+ ViewerShell,
768
+ {
769
+ zoomPan,
770
+ onDownload: handleDownload,
771
+ enableZoomControls,
772
+ enableFullscreen,
773
+ enablePan: false,
774
+ theme,
775
+ width,
776
+ height,
777
+ floating,
778
+ draggable: windowDraggable,
779
+ defaultPosition,
780
+ transformOrigin: "top center",
781
+ children: [
782
+ loading && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "vd-doc-status", children: "Loading document\u2026" }),
783
+ error && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "vd-doc-status", children: [
784
+ "Failed to load document: ",
785
+ error.message
786
+ ] }),
787
+ html && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "vd-docx-page", dangerouslySetInnerHTML: { __html: html } })
788
+ ]
789
+ }
790
+ ) });
791
+ }
792
+
793
+ // src/viewers/XlsxViewer.tsx
794
+ var import_react9 = require("react");
795
+ var XLSX2 = __toESM(require("xlsx"), 1);
796
+
797
+ // src/viewers/useXlsxWorkbook.ts
798
+ var import_react8 = require("react");
799
+ var XLSX = __toESM(require("xlsx"), 1);
800
+ function useXlsxWorkbook(uri) {
801
+ const [workbook, setWorkbook] = (0, import_react8.useState)(null);
802
+ const [error, setError] = (0, import_react8.useState)(null);
803
+ const [loading, setLoading] = (0, import_react8.useState)(true);
804
+ (0, import_react8.useEffect)(() => {
805
+ let cancelled = false;
806
+ setLoading(true);
807
+ setError(null);
808
+ setWorkbook(null);
809
+ fetch(uri).then((res) => {
810
+ if (!res.ok) throw new Error(`Failed to fetch spreadsheet: ${res.status} ${res.statusText}`);
811
+ return res.arrayBuffer();
812
+ }).then((arrayBuffer) => {
813
+ if (cancelled) return;
814
+ const wb = XLSX.read(arrayBuffer, { type: "array" });
815
+ setWorkbook(wb);
816
+ setLoading(false);
817
+ }).catch((err) => {
818
+ if (!cancelled) {
819
+ setError(err);
820
+ setLoading(false);
821
+ }
822
+ });
823
+ return () => {
824
+ cancelled = true;
825
+ };
826
+ }, [uri]);
827
+ return { workbook, error, loading };
828
+ }
829
+
830
+ // src/viewers/XlsxViewer.tsx
831
+ var import_jsx_runtime8 = require("react/jsx-runtime");
832
+ function defaultDownload4(uri, fileName) {
833
+ const link = document.createElement("a");
834
+ link.href = uri;
835
+ link.download = fileName ?? "";
836
+ link.rel = "noopener";
837
+ document.body.appendChild(link);
838
+ link.click();
839
+ document.body.removeChild(link);
840
+ }
841
+ function XlsxViewer({
842
+ uri,
843
+ fileName,
844
+ className,
845
+ style,
846
+ width,
847
+ height,
848
+ minScale = 0.5,
849
+ maxScale = 3,
850
+ zoomStep,
851
+ enableZoomControls = true,
852
+ enableWheelZoom = true,
853
+ enableFullscreen = true,
854
+ enableDownload = true,
855
+ onDownload,
856
+ theme,
857
+ floating = true,
858
+ windowDraggable = true,
859
+ defaultPosition
860
+ }) {
861
+ const zoomPan = useZoomPan({
862
+ minScale,
863
+ maxScale,
864
+ zoomStep,
865
+ enablePan: false,
866
+ enableDragScroll: true,
867
+ enableWheelZoom
868
+ });
869
+ const { workbook, error, loading } = useXlsxWorkbook(uri);
870
+ const [activeSheet, setActiveSheet] = (0, import_react9.useState)(0);
871
+ const handleDownload = enableDownload ? onDownload ?? (() => defaultDownload4(uri, fileName)) : void 0;
872
+ const tableHtml = (0, import_react9.useMemo)(() => {
873
+ if (!workbook) return null;
874
+ const sheetName = workbook.SheetNames[activeSheet];
875
+ const sheet = workbook.Sheets[sheetName];
876
+ if (!sheet) return null;
877
+ return XLSX2.utils.sheet_to_html(sheet, { editable: false });
878
+ }, [workbook, activeSheet]);
879
+ const wrapperStyle = floating ? { ...style } : { display: "flex", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", ...style };
880
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className, style: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
881
+ ViewerShell,
882
+ {
883
+ zoomPan,
884
+ onDownload: handleDownload,
885
+ enableZoomControls,
886
+ enableFullscreen,
887
+ enablePan: false,
888
+ theme,
889
+ width,
890
+ height,
891
+ floating,
892
+ draggable: windowDraggable,
893
+ defaultPosition,
894
+ transformOrigin: "top left",
895
+ children: [
896
+ loading && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "vd-doc-status", children: "Loading spreadsheet\u2026" }),
897
+ error && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "vd-doc-status", children: [
898
+ "Failed to load spreadsheet: ",
899
+ error.message
900
+ ] }),
901
+ workbook && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "vd-xlsx", children: [
902
+ workbook.SheetNames.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "vd-xlsx-tabs", children: workbook.SheetNames.map((name, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
903
+ "button",
904
+ {
905
+ type: "button",
906
+ className: `vd-xlsx-tab${i === activeSheet ? " vd-xlsx-tab--active" : ""}`,
907
+ onClick: () => setActiveSheet(i),
908
+ children: name
909
+ },
910
+ name
911
+ )) }),
912
+ tableHtml && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "vd-xlsx-table", dangerouslySetInnerHTML: { __html: tableHtml } })
913
+ ] })
914
+ ]
915
+ }
916
+ ) });
917
+ }
918
+
919
+ // src/detectFileType.ts
920
+ var EXTENSION_MAP = {
921
+ png: "image",
922
+ jpg: "image",
923
+ jpeg: "image",
924
+ gif: "image",
925
+ webp: "image",
926
+ svg: "image",
927
+ bmp: "image",
928
+ ico: "image",
929
+ avif: "image",
930
+ pdf: "pdf",
931
+ docx: "docx",
932
+ xlsx: "xlsx",
933
+ xls: "xlsx",
934
+ xlsm: "xlsx",
935
+ csv: "xlsx"
936
+ };
937
+ function getUrlExtension(uri) {
938
+ let pathname;
939
+ try {
940
+ pathname = new URL(uri, typeof window !== "undefined" ? window.location.href : "http://localhost").pathname;
941
+ } catch {
942
+ pathname = uri.split("?")[0].split("#")[0];
943
+ }
944
+ const match = pathname.match(/\.([a-zA-Z0-9]+)$/);
945
+ return match ? match[1].toLowerCase() : null;
946
+ }
947
+ function detectFileType(uri, hint) {
948
+ if (hint) {
949
+ const normalizedHint = hint.toLowerCase().replace(/^\./, "");
950
+ if (normalizedHint in EXTENSION_MAP) return EXTENSION_MAP[normalizedHint];
951
+ if (normalizedHint.startsWith("image/")) return "image";
952
+ if (normalizedHint === "application/pdf") return "pdf";
953
+ }
954
+ const extension = getUrlExtension(uri);
955
+ if (extension && extension in EXTENSION_MAP) return EXTENSION_MAP[extension];
956
+ return null;
957
+ }
958
+
959
+ // src/DocViewer.tsx
960
+ var import_jsx_runtime9 = require("react/jsx-runtime");
961
+ function DocViewer({ uri, fileName, type, fallback, ...rest }) {
962
+ const detected = detectFileType(uri, type ?? fileName?.split(".").pop());
963
+ switch (detected) {
964
+ case "image":
965
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ImageViewer, { uri, fileName, ...rest });
966
+ case "pdf":
967
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PdfViewer, { uri, fileName, ...rest });
968
+ case "docx":
969
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DocxViewer, { uri, fileName, ...rest });
970
+ case "xlsx":
971
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(XlsxViewer, { uri, fileName, ...rest });
972
+ default:
973
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: fallback ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "vd-doc-status", children: [
974
+ 'Unable to detect file type for "',
975
+ fileName ?? uri,
976
+ '".'
977
+ ] }) });
978
+ }
979
+ }
980
+ // Annotate the CommonJS export names for ESM import in node:
981
+ 0 && (module.exports = {
982
+ DocViewer,
983
+ DocxViewer,
984
+ ImageViewer,
985
+ PdfViewer,
986
+ Toolbar,
987
+ ViewerShell,
988
+ XlsxViewer,
989
+ detectFileType,
990
+ getUrlExtension,
991
+ useDraggable,
992
+ useZoomPan
993
+ });
994
+ //# sourceMappingURL=index.cjs.map