@arbidocs/react 0.3.136 → 0.3.138

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,715 @@
1
+ import { useArbi } from '../chunk-J4MX2W4H.js';
2
+ import { useEffect, useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
3
+ import { Document, Page, pdfjs } from 'react-pdf';
4
+ import 'react-pdf/dist/Page/AnnotationLayer.css';
5
+ import 'react-pdf/dist/Page/TextLayer.css';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+
8
+ var PAGE_UPDATE_DEBOUNCE_MS = 10;
9
+ var MIN_VISIBILITY_THRESHOLD = 0.15;
10
+ var INTERSECTION_THRESHOLDS = [0, 0.25, 0.5, 0.75, 1];
11
+ function usePageIntersectionObserver({
12
+ containerRef,
13
+ scrollCompleteRef,
14
+ isEnabled,
15
+ getPageElements,
16
+ getPageNumber,
17
+ onPageChange,
18
+ onVisibilityUpdate
19
+ }) {
20
+ useEffect(() => {
21
+ if (!isEnabled || !containerRef.current) return;
22
+ const container = containerRef.current;
23
+ const visibilityMap = /* @__PURE__ */ new Map();
24
+ let lastReportedPage = null;
25
+ let updateTimeout = null;
26
+ const updatePageNumber = () => {
27
+ let mostVisiblePage = 1;
28
+ let maxVisibility = 0;
29
+ visibilityMap.forEach((ratio, pageNum) => {
30
+ if (ratio > maxVisibility) {
31
+ maxVisibility = ratio;
32
+ mostVisiblePage = pageNum;
33
+ }
34
+ });
35
+ if (scrollCompleteRef.current && mostVisiblePage !== lastReportedPage && maxVisibility > MIN_VISIBILITY_THRESHOLD) {
36
+ lastReportedPage = mostVisiblePage;
37
+ onPageChange(mostVisiblePage);
38
+ }
39
+ if (onVisibilityUpdate) {
40
+ onVisibilityUpdate(new Map(visibilityMap));
41
+ }
42
+ };
43
+ const observer = new IntersectionObserver(
44
+ (entries) => {
45
+ entries.forEach((entry) => {
46
+ const pageNum = getPageNumber(entry.target);
47
+ if (pageNum > 0) {
48
+ visibilityMap.set(pageNum, entry.intersectionRatio);
49
+ }
50
+ });
51
+ if (updateTimeout) {
52
+ clearTimeout(updateTimeout);
53
+ }
54
+ updateTimeout = setTimeout(updatePageNumber, PAGE_UPDATE_DEBOUNCE_MS);
55
+ },
56
+ {
57
+ root: container,
58
+ threshold: INTERSECTION_THRESHOLDS
59
+ }
60
+ );
61
+ const elements = getPageElements();
62
+ elements.forEach((element) => observer.observe(element));
63
+ return () => {
64
+ observer.disconnect();
65
+ if (updateTimeout) {
66
+ clearTimeout(updateTimeout);
67
+ }
68
+ };
69
+ }, [
70
+ containerRef,
71
+ scrollCompleteRef,
72
+ isEnabled,
73
+ getPageElements,
74
+ getPageNumber,
75
+ onPageChange,
76
+ onVisibilityUpdate
77
+ ]);
78
+ }
79
+
80
+ // src/viewers/pdf-viewer-constants.ts
81
+ var LAZY_LOAD_BUFFER_BEFORE = 2;
82
+ var LAZY_LOAD_BUFFER_AFTER = 5;
83
+ var INITIAL_LAZY_LOAD_BUFFER = { before: 0, after: 1 };
84
+ var DEFAULT_ASSETS = {
85
+ workerSrc: "/pdf.worker.min.mjs",
86
+ cMapUrl: "/cmaps/",
87
+ standardFontDataUrl: "/standard_fonts/"
88
+ };
89
+ function ensurePdfWorker(workerSrc) {
90
+ if (pdfjs.GlobalWorkerOptions.workerSrc !== workerSrc) {
91
+ pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;
92
+ }
93
+ }
94
+ var CONTAINER_PADDING_PX = 32;
95
+ var PAGE_ASPECT_RATIO = 1.414;
96
+ var TEXT_LAYER_BUFFER = 1;
97
+ function PdfViewer({
98
+ file,
99
+ currentPage,
100
+ onLoadSuccess,
101
+ scrollTrigger,
102
+ onPageChange,
103
+ disableFontFace = false,
104
+ assets
105
+ }) {
106
+ const workerSrc = assets?.workerSrc ?? DEFAULT_ASSETS.workerSrc;
107
+ const cMapUrl = assets?.cMapUrl ?? DEFAULT_ASSETS.cMapUrl;
108
+ const standardFontDataUrl = assets?.standardFontDataUrl ?? DEFAULT_ASSETS.standardFontDataUrl;
109
+ ensurePdfWorker(workerSrc);
110
+ const [numPages, setNumPages] = useState(0);
111
+ const [containerWidth, setContainerWidth] = useState(0);
112
+ const [loadError, setLoadError] = useState(null);
113
+ const [documentKey, setDocumentKey] = useState(() => `pdf-${Date.now()}`);
114
+ const initialPage = currentPage || 1;
115
+ const [renderedPages, setRenderedPages] = useState(
116
+ /* @__PURE__ */ new Set([
117
+ Math.max(1, initialPage - INITIAL_LAZY_LOAD_BUFFER.before),
118
+ initialPage,
119
+ initialPage + INITIAL_LAZY_LOAD_BUFFER.after
120
+ ])
121
+ );
122
+ const [visiblePages, setVisiblePages] = useState(/* @__PURE__ */ new Set([initialPage]));
123
+ useEffect(() => {
124
+ setDocumentKey(`pdf-${Date.now()}`);
125
+ setNumPages(0);
126
+ setLoadError(null);
127
+ }, [file]);
128
+ const containerRef = useRef(null);
129
+ const pageRefs = useRef(/* @__PURE__ */ new Map());
130
+ const onDocumentLoadSuccess = useCallback(
131
+ ({ numPages: n }) => {
132
+ setNumPages(n);
133
+ setLoadError(null);
134
+ const expand = () => {
135
+ setRenderedPages((prev) => {
136
+ if (prev.size === 0) return prev;
137
+ const newSet = new Set(prev);
138
+ const anchor = currentPage || 1;
139
+ for (let i = Math.max(1, anchor - LAZY_LOAD_BUFFER_BEFORE); i <= Math.min(n, anchor + LAZY_LOAD_BUFFER_AFTER); i++) {
140
+ newSet.add(i);
141
+ }
142
+ return newSet;
143
+ });
144
+ };
145
+ if (typeof requestIdleCallback !== "undefined") {
146
+ requestIdleCallback(expand, { timeout: 500 });
147
+ } else {
148
+ setTimeout(expand, 500);
149
+ }
150
+ try {
151
+ onLoadSuccess?.(n);
152
+ } catch (error) {
153
+ console.error("Error in onLoadSuccess callback:", error);
154
+ }
155
+ },
156
+ [onLoadSuccess, currentPage]
157
+ );
158
+ const onDocumentLoadError = useCallback((error) => {
159
+ console.error("PDF loading error:", error);
160
+ setLoadError(error);
161
+ setNumPages(0);
162
+ }, []);
163
+ const onPassword = useCallback((updatePassword, reason) => {
164
+ const message = reason === 2 ? "Incorrect password. Enter the password to unlock this PDF:" : "This PDF is password-protected. Enter the password to view it:";
165
+ const password = window.prompt(message);
166
+ if (password === null) {
167
+ setLoadError(new Error("Password required to open this PDF"));
168
+ setNumPages(0);
169
+ return;
170
+ }
171
+ updatePassword(password);
172
+ }, []);
173
+ useEffect(() => {
174
+ if (!containerRef.current) return;
175
+ const updateWidth = () => {
176
+ if (containerRef.current) {
177
+ setContainerWidth(containerRef.current.offsetWidth - CONTAINER_PADDING_PX);
178
+ }
179
+ };
180
+ updateWidth();
181
+ const resizeObserver = new ResizeObserver(updateWidth);
182
+ resizeObserver.observe(containerRef.current);
183
+ return () => {
184
+ resizeObserver.disconnect();
185
+ };
186
+ }, []);
187
+ const scrollCompleteRef = useRef(false);
188
+ useEffect(() => {
189
+ if (numPages === 0) return;
190
+ if (scrollCompleteRef.current) return;
191
+ if (currentPage) {
192
+ setRenderedPages((prev) => {
193
+ const newSet = new Set(prev);
194
+ for (let i = Math.max(1, currentPage - LAZY_LOAD_BUFFER_BEFORE); i <= Math.min(numPages, currentPage + LAZY_LOAD_BUFFER_AFTER); i++) {
195
+ newSet.add(i);
196
+ }
197
+ return newSet;
198
+ });
199
+ requestAnimationFrame(() => {
200
+ const pageElement = pageRefs.current.get(currentPage);
201
+ if (pageElement) {
202
+ pageElement.scrollIntoView({ behavior: "auto", block: "start" });
203
+ }
204
+ setTimeout(() => {
205
+ scrollCompleteRef.current = true;
206
+ }, 100);
207
+ });
208
+ }
209
+ }, [numPages]);
210
+ useEffect(() => {
211
+ if (!scrollTrigger || numPages === 0 || !currentPage) return;
212
+ scrollCompleteRef.current = false;
213
+ setRenderedPages((prev) => {
214
+ const newSet = new Set(prev);
215
+ for (let i = Math.max(1, currentPage - LAZY_LOAD_BUFFER_BEFORE); i <= Math.min(numPages, currentPage + LAZY_LOAD_BUFFER_AFTER); i++) {
216
+ newSet.add(i);
217
+ }
218
+ return newSet;
219
+ });
220
+ requestAnimationFrame(() => {
221
+ requestAnimationFrame(() => {
222
+ const pageElement = pageRefs.current.get(currentPage);
223
+ if (pageElement) {
224
+ pageElement.scrollIntoView({ behavior: "instant", block: "start" });
225
+ }
226
+ scrollCompleteRef.current = true;
227
+ });
228
+ });
229
+ }, [scrollTrigger, currentPage, numPages]);
230
+ const handleVisibilityUpdate = useCallback(
231
+ (visibilityMap) => {
232
+ const visiblePageNums = Array.from(visibilityMap.entries()).filter(([, ratio]) => ratio > 0).map(([pageNum]) => pageNum);
233
+ if (visiblePageNums.length > 0) {
234
+ const minVisible = Math.min(...visiblePageNums);
235
+ const maxVisible = Math.max(...visiblePageNums);
236
+ const pagesToRender = /* @__PURE__ */ new Set();
237
+ for (let i = Math.max(1, minVisible - LAZY_LOAD_BUFFER_BEFORE); i <= Math.min(numPages, maxVisible + LAZY_LOAD_BUFFER_AFTER); i++) {
238
+ pagesToRender.add(i);
239
+ }
240
+ setRenderedPages(pagesToRender);
241
+ const nearViewport = /* @__PURE__ */ new Set();
242
+ for (let i = Math.max(1, minVisible - TEXT_LAYER_BUFFER); i <= Math.min(numPages, maxVisible + TEXT_LAYER_BUFFER); i++) {
243
+ nearViewport.add(i);
244
+ }
245
+ setVisiblePages(nearViewport);
246
+ }
247
+ },
248
+ [numPages]
249
+ );
250
+ const documentOptions = useMemo(
251
+ () => ({
252
+ standardFontDataUrl,
253
+ cMapUrl,
254
+ cMapPacked: true,
255
+ ...disableFontFace && { disableFontFace: true }
256
+ }),
257
+ [standardFontDataUrl, cMapUrl, disableFontFace]
258
+ );
259
+ const handlePageChange = useCallback(
260
+ (pageNumber) => onPageChange?.(pageNumber),
261
+ [onPageChange]
262
+ );
263
+ usePageIntersectionObserver({
264
+ containerRef,
265
+ scrollCompleteRef,
266
+ isEnabled: numPages > 0,
267
+ getPageElements: () => Array.from(pageRefs.current.values()),
268
+ getPageNumber: (element) => parseInt(element.getAttribute("data-page-number") ?? "0"),
269
+ onPageChange: handlePageChange,
270
+ onVisibilityUpdate: handleVisibilityUpdate
271
+ });
272
+ return /* @__PURE__ */ jsx(
273
+ "div",
274
+ {
275
+ ref: containerRef,
276
+ className: "w-full h-full overflow-y-auto overflow-x-hidden bg-muted flex flex-col items-center p-4",
277
+ role: "region",
278
+ "aria-label": "PDF document viewer",
279
+ "data-testid": "pdf-viewer",
280
+ children: /* @__PURE__ */ jsx(
281
+ Document,
282
+ {
283
+ file,
284
+ onLoadSuccess: onDocumentLoadSuccess,
285
+ onLoadError: onDocumentLoadError,
286
+ onPassword,
287
+ options: documentOptions,
288
+ loading: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading PDF..." }) }),
289
+ error: /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center h-full gap-4 p-8", children: /* @__PURE__ */ jsx("div", { className: "text-destructive text-center", children: loadError?.message === "Cannot read properties of null (reading 'sendWithPromise')" ? /* @__PURE__ */ jsxs(Fragment, { children: [
290
+ /* @__PURE__ */ jsx("p", { className: "font-semibold mb-2", children: "PDF viewer initialization failed" }),
291
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Please refresh the page to retry" })
292
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
293
+ /* @__PURE__ */ jsx("p", { className: "font-semibold mb-2", children: "Failed to load PDF" }),
294
+ loadError && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: loadError.message })
295
+ ] }) }) }),
296
+ children: containerWidth > 0 && Array.from(new Array(numPages), (_, index) => {
297
+ const pageNumber = index + 1;
298
+ const shouldRender = renderedPages.has(pageNumber);
299
+ const pageHeight = containerWidth * PAGE_ASPECT_RATIO;
300
+ const shouldRenderTextLayer = visiblePages.has(pageNumber);
301
+ return /* @__PURE__ */ jsx(
302
+ "div",
303
+ {
304
+ ref: (el) => {
305
+ if (el) {
306
+ pageRefs.current.set(pageNumber, el);
307
+ } else {
308
+ pageRefs.current.delete(pageNumber);
309
+ }
310
+ },
311
+ "data-page-number": pageNumber,
312
+ className: "mb-6 shadow-xl bg-white border border-border",
313
+ style: !shouldRender ? { minHeight: pageHeight } : void 0,
314
+ role: "article",
315
+ "aria-label": `Page ${pageNumber} of ${numPages}`,
316
+ children: shouldRender ? /* @__PURE__ */ jsx(
317
+ Page,
318
+ {
319
+ pageNumber,
320
+ width: containerWidth,
321
+ renderTextLayer: shouldRenderTextLayer,
322
+ renderAnnotationLayer: false,
323
+ devicePixelRatio: Math.min(window.devicePixelRatio || 1, 2),
324
+ loading: /* @__PURE__ */ jsx(
325
+ "div",
326
+ {
327
+ className: "flex items-center justify-center bg-muted",
328
+ style: { width: containerWidth, height: pageHeight },
329
+ children: /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
330
+ "Loading page ",
331
+ pageNumber,
332
+ "..."
333
+ ] })
334
+ }
335
+ )
336
+ }
337
+ ) : /* @__PURE__ */ jsx(
338
+ "div",
339
+ {
340
+ className: "flex items-center justify-center bg-muted",
341
+ style: { width: containerWidth, height: pageHeight },
342
+ children: /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
343
+ "Page ",
344
+ pageNumber
345
+ ] })
346
+ }
347
+ )
348
+ },
349
+ `page_${pageNumber}`
350
+ );
351
+ })
352
+ },
353
+ documentKey
354
+ )
355
+ }
356
+ );
357
+ }
358
+ function measureContentWidth(container) {
359
+ let pageWidth = 0;
360
+ container.querySelectorAll("section.docx").forEach((section) => {
361
+ pageWidth = Math.max(pageWidth, section.offsetWidth);
362
+ });
363
+ if (pageWidth === 0) return 0;
364
+ const wrapper = container.querySelector(".docx-wrapper");
365
+ if (wrapper) {
366
+ const style = window.getComputedStyle(wrapper);
367
+ pageWidth += (parseFloat(style.paddingLeft) || 0) + (parseFloat(style.paddingRight) || 0);
368
+ }
369
+ return pageWidth;
370
+ }
371
+ function DocxViewer({ blob }) {
372
+ const outerRef = useRef(null);
373
+ const containerRef = useRef(null);
374
+ const [error, setError] = useState(null);
375
+ const [isLoading, setIsLoading] = useState(true);
376
+ const [containerWidth, setContainerWidth] = useState(0);
377
+ const [contentWidth, setContentWidth] = useState(0);
378
+ useEffect(() => {
379
+ const outer = outerRef.current;
380
+ if (!outer) return;
381
+ const updateWidth = () => setContainerWidth(outer.clientWidth);
382
+ updateWidth();
383
+ const resizeObserver = new ResizeObserver(updateWidth);
384
+ resizeObserver.observe(outer);
385
+ return () => resizeObserver.disconnect();
386
+ }, []);
387
+ useEffect(() => {
388
+ const container = containerRef.current;
389
+ if (!container) return;
390
+ let cancelled = false;
391
+ setIsLoading(true);
392
+ setError(null);
393
+ setContentWidth(0);
394
+ container.innerHTML = "";
395
+ import('docx-preview').then(({ renderAsync }) => {
396
+ if (cancelled) return;
397
+ return renderAsync(blob, container, void 0, {
398
+ ignoreLastRenderedPageBreak: false
399
+ });
400
+ }).then(() => {
401
+ if (!cancelled) setIsLoading(false);
402
+ }).catch((err) => {
403
+ if (!cancelled) {
404
+ console.error("DocxViewer render error:", err);
405
+ setError("Failed to render document");
406
+ setIsLoading(false);
407
+ }
408
+ });
409
+ return () => {
410
+ cancelled = true;
411
+ };
412
+ }, [blob]);
413
+ useLayoutEffect(() => {
414
+ const container = containerRef.current;
415
+ if (isLoading || error || !container) return;
416
+ setContentWidth(measureContentWidth(container));
417
+ }, [isLoading, error]);
418
+ const scale = containerWidth > 0 && contentWidth > 0 ? Math.min(1, containerWidth / contentWidth) : 1;
419
+ return /* @__PURE__ */ jsxs("div", { ref: outerRef, className: "w-full h-full overflow-auto bg-muted", "data-testid": "docx-viewer", children: [
420
+ isLoading && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading document..." }) }),
421
+ error && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx("div", { className: "text-destructive", children: error }) }),
422
+ /* @__PURE__ */ jsx(
423
+ "div",
424
+ {
425
+ ref: containerRef,
426
+ "data-testid": "docx-viewer-body",
427
+ className: `docx-viewer-body${isLoading || error ? " hidden" : ""}`,
428
+ style: { zoom: scale }
429
+ }
430
+ )
431
+ ] });
432
+ }
433
+ function XlsxViewer({ blob }) {
434
+ const [sheets, setSheets] = useState([]);
435
+ const [activeSheet, setActiveSheet] = useState("");
436
+ const [rows, setRows] = useState([]);
437
+ const [error, setError] = useState(null);
438
+ const [isLoading, setIsLoading] = useState(true);
439
+ const workbookRef = useRef(null);
440
+ const renderSheet = async (sheetName, wb) => {
441
+ const XLSX = await import('xlsx');
442
+ const sheet = wb.Sheets[sheetName];
443
+ const data = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: "" });
444
+ setRows(data);
445
+ setActiveSheet(sheetName);
446
+ };
447
+ useEffect(() => {
448
+ let cancelled = false;
449
+ blob.arrayBuffer().then(async (buffer) => {
450
+ if (cancelled) return;
451
+ const XLSX = await import('xlsx');
452
+ const wb = XLSX.read(buffer, { type: "array" });
453
+ workbookRef.current = wb;
454
+ const sheetNames = wb.SheetNames;
455
+ setSheets(sheetNames);
456
+ const data = XLSX.utils.sheet_to_json(wb.Sheets[sheetNames[0]], {
457
+ header: 1,
458
+ defval: ""
459
+ });
460
+ if (!cancelled) {
461
+ setRows(data);
462
+ setActiveSheet(sheetNames[0]);
463
+ setIsLoading(false);
464
+ }
465
+ }).catch((err) => {
466
+ if (!cancelled) {
467
+ console.error("XlsxViewer error:", err);
468
+ setError("Failed to render spreadsheet");
469
+ setIsLoading(false);
470
+ }
471
+ });
472
+ return () => {
473
+ cancelled = true;
474
+ };
475
+ }, [blob]);
476
+ const handleSheetChange = (sheetName) => {
477
+ if (workbookRef.current) {
478
+ renderSheet(sheetName, workbookRef.current);
479
+ }
480
+ };
481
+ if (isLoading) {
482
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading spreadsheet..." }) });
483
+ }
484
+ if (error) {
485
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsx("div", { className: "text-destructive", children: error }) });
486
+ }
487
+ const colCount = rows.reduce((max, row) => Math.max(max, row.length), 0);
488
+ return /* @__PURE__ */ jsxs(
489
+ "div",
490
+ {
491
+ className: "w-full h-full flex flex-col overflow-hidden bg-background",
492
+ "data-testid": "xlsx-viewer",
493
+ children: [
494
+ sheets.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 flex items-center gap-0.5 px-2 pt-1 border-b bg-muted overflow-x-auto", children: sheets.map((sheet) => /* @__PURE__ */ jsx(
495
+ "button",
496
+ {
497
+ onClick: () => handleSheetChange(sheet),
498
+ className: `px-3 py-1 text-sm font-medium whitespace-nowrap rounded-t transition-colors border-x border-t ${sheet === activeSheet ? "bg-background text-primary border-border" : "bg-muted text-muted-foreground border-transparent hover:bg-muted hover:text-foreground"}`,
499
+ children: sheet
500
+ },
501
+ sheet
502
+ )) }),
503
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto", children: [
504
+ /* @__PURE__ */ jsx("table", { className: "text-sm border-collapse min-w-full", children: /* @__PURE__ */ jsx("tbody", { children: rows.map((row, rowIdx) => /* @__PURE__ */ jsxs(
505
+ "tr",
506
+ {
507
+ className: rowIdx === 0 ? "bg-muted font-semibold sticky top-0" : "hover:bg-accent",
508
+ children: [
509
+ /* @__PURE__ */ jsx("td", { className: "border border-border px-1 text-center text-muted-foreground bg-muted select-none w-8 min-w-[2rem]", children: rowIdx + 1 }),
510
+ Array.from({ length: colCount }).map((_, colIdx) => /* @__PURE__ */ jsx(
511
+ "td",
512
+ {
513
+ className: "border border-border px-2 py-0.5 whitespace-nowrap max-w-[200px] overflow-hidden text-ellipsis",
514
+ title: String(row[colIdx] ?? ""),
515
+ children: String(row[colIdx] ?? "")
516
+ },
517
+ colIdx
518
+ ))
519
+ ]
520
+ },
521
+ rowIdx
522
+ )) }) }),
523
+ rows.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-8 text-sm text-muted-foreground", children: "Empty sheet" })
524
+ ] })
525
+ ]
526
+ }
527
+ );
528
+ }
529
+ function ImageViewer({ blob, alt }) {
530
+ const [objectUrl, setObjectUrl] = useState("");
531
+ useEffect(() => {
532
+ const url = URL.createObjectURL(blob);
533
+ setObjectUrl(url);
534
+ return () => URL.revokeObjectURL(url);
535
+ }, [blob]);
536
+ if (!objectUrl) return null;
537
+ return /* @__PURE__ */ jsx(
538
+ "div",
539
+ {
540
+ className: "h-full w-full overflow-auto flex items-center justify-center p-4 bg-muted/30",
541
+ "data-testid": "image-viewer",
542
+ children: /* @__PURE__ */ jsx(
543
+ "img",
544
+ {
545
+ src: objectUrl,
546
+ alt,
547
+ className: "max-w-full max-h-full object-contain rounded shadow-sm",
548
+ "data-testid": "image-viewer-img"
549
+ }
550
+ )
551
+ }
552
+ );
553
+ }
554
+ function AudioViewer({ blob, title }) {
555
+ const [objectUrl, setObjectUrl] = useState("");
556
+ useEffect(() => {
557
+ const url = URL.createObjectURL(blob);
558
+ setObjectUrl(url);
559
+ return () => URL.revokeObjectURL(url);
560
+ }, [blob]);
561
+ if (!objectUrl) return null;
562
+ return /* @__PURE__ */ jsx(
563
+ "div",
564
+ {
565
+ className: "h-full w-full flex items-center justify-center p-4 bg-muted/30",
566
+ "data-testid": "audio-viewer",
567
+ children: /* @__PURE__ */ jsx("audio", { src: objectUrl, controls: true, title, className: "w-full max-w-lg" })
568
+ }
569
+ );
570
+ }
571
+ function VideoViewer({ blob, title }) {
572
+ const [objectUrl, setObjectUrl] = useState("");
573
+ useEffect(() => {
574
+ const url = URL.createObjectURL(blob);
575
+ setObjectUrl(url);
576
+ return () => URL.revokeObjectURL(url);
577
+ }, [blob]);
578
+ if (!objectUrl) return null;
579
+ return /* @__PURE__ */ jsx(
580
+ "div",
581
+ {
582
+ className: "h-full w-full overflow-auto flex items-center justify-center p-4 bg-muted/30",
583
+ "data-testid": "video-viewer",
584
+ children: /* @__PURE__ */ jsx(
585
+ "video",
586
+ {
587
+ src: objectUrl,
588
+ controls: true,
589
+ title,
590
+ className: "max-w-full max-h-full rounded shadow-sm"
591
+ }
592
+ )
593
+ }
594
+ );
595
+ }
596
+ var BY_EXTENSION = {
597
+ pdf: "pdf",
598
+ doc: "docx",
599
+ docx: "docx",
600
+ xls: "xlsx",
601
+ xlsx: "xlsx",
602
+ csv: "xlsx",
603
+ png: "image",
604
+ jpg: "image",
605
+ jpeg: "image",
606
+ gif: "image",
607
+ webp: "image",
608
+ bmp: "image",
609
+ tif: "image",
610
+ tiff: "image",
611
+ mp3: "audio",
612
+ wav: "audio",
613
+ m4a: "audio",
614
+ ogg: "audio",
615
+ mp4: "video",
616
+ mov: "video",
617
+ webm: "video",
618
+ avi: "video"
619
+ };
620
+ function nativeViewerKind(doc) {
621
+ const name = (doc?.file_name ?? "").toLowerCase();
622
+ const ext = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1) : "";
623
+ if (BY_EXTENSION[ext]) return BY_EXTENSION[ext];
624
+ const type = (doc?.file_type ?? "").toLowerCase();
625
+ if (!type) return "none";
626
+ if (type.includes("pdf")) return "pdf";
627
+ if (type.includes("word") || type.includes("officedocument.wordprocessing")) return "docx";
628
+ if (type.includes("sheet") || type.includes("excel") || type.includes("csv")) return "xlsx";
629
+ if (type.startsWith("image/")) return "image";
630
+ if (type.startsWith("audio/")) return "audio";
631
+ if (type.startsWith("video/")) return "video";
632
+ return "none";
633
+ }
634
+ function DocumentViewer({
635
+ doc,
636
+ pdfAssets,
637
+ onPageChange,
638
+ currentPage,
639
+ scrollTrigger,
640
+ onLoadSuccess,
641
+ fallback,
642
+ loading,
643
+ errorRender,
644
+ className
645
+ }) {
646
+ const arbi = useArbi();
647
+ const [blob, setBlob] = useState(null);
648
+ const [error, setError] = useState(null);
649
+ const kind = useMemo(() => nativeViewerKind(doc), [doc]);
650
+ const docId = doc?.external_id;
651
+ useEffect(() => {
652
+ if (!docId || kind === "none") {
653
+ setBlob(null);
654
+ return;
655
+ }
656
+ let cancelled = false;
657
+ setBlob(null);
658
+ setError(null);
659
+ arbi.documents.download(docId).then((res) => res.blob()).then((b) => {
660
+ if (!cancelled) setBlob(b);
661
+ }).catch((err) => {
662
+ if (!cancelled) setError(err instanceof Error ? err.message : "Could not load the file");
663
+ });
664
+ return () => {
665
+ cancelled = true;
666
+ };
667
+ }, [arbi, docId, kind]);
668
+ if (!doc) return null;
669
+ if (kind === "none") {
670
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? /* @__PURE__ */ jsx("div", { className: "grid h-full place-items-center p-6 text-center text-sm text-muted-foreground", children: "No native view for this file type. The original is preserved and can be downloaded." }) });
671
+ }
672
+ if (error) {
673
+ return /* @__PURE__ */ jsx(Fragment, { children: errorRender?.(error) ?? /* @__PURE__ */ jsx("div", { className: "grid h-full place-items-center p-6 text-center", children: /* @__PURE__ */ jsxs("div", { children: [
674
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: "Could not load the original" }),
675
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: error })
676
+ ] }) }) });
677
+ }
678
+ if (!blob) {
679
+ return /* @__PURE__ */ jsx(Fragment, { children: loading ?? /* @__PURE__ */ jsx("div", { className: "grid h-full place-items-center text-sm text-muted-foreground", children: "Loading the original\u2026" }) });
680
+ }
681
+ const title = doc.file_name ?? doc.external_id;
682
+ const body = (() => {
683
+ switch (kind) {
684
+ case "pdf":
685
+ return /* @__PURE__ */ jsx(
686
+ PdfViewer,
687
+ {
688
+ file: blob,
689
+ assets: pdfAssets,
690
+ onPageChange,
691
+ currentPage,
692
+ scrollTrigger,
693
+ onLoadSuccess
694
+ }
695
+ );
696
+ case "docx":
697
+ return /* @__PURE__ */ jsx(DocxViewer, { blob });
698
+ case "xlsx":
699
+ return /* @__PURE__ */ jsx(XlsxViewer, { blob });
700
+ case "image":
701
+ return /* @__PURE__ */ jsx(ImageViewer, { blob, alt: title });
702
+ case "audio":
703
+ return /* @__PURE__ */ jsx(AudioViewer, { blob, title });
704
+ case "video":
705
+ return /* @__PURE__ */ jsx(VideoViewer, { blob, title });
706
+ default:
707
+ return null;
708
+ }
709
+ })();
710
+ return className ? /* @__PURE__ */ jsx("div", { className, "data-testid": "document-viewer", children: body }) : body;
711
+ }
712
+
713
+ export { AudioViewer, DocumentViewer, DocxViewer, INITIAL_LAZY_LOAD_BUFFER, ImageViewer, LAZY_LOAD_BUFFER_AFTER, LAZY_LOAD_BUFFER_BEFORE, PdfViewer, VideoViewer, XlsxViewer, nativeViewerKind, usePageIntersectionObserver };
714
+ //# sourceMappingURL=index.js.map
715
+ //# sourceMappingURL=index.js.map