@arbidocs/react 0.3.135 → 0.3.137

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