@sesionario/blocks 1.23.0 → 1.24.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/pdf.js ADDED
@@ -0,0 +1,1021 @@
1
+ import { useState, useRef, useCallback, useEffect } from 'react';
2
+ import { pdfjs, Document, Page, Thumbnail } from 'react-pdf';
3
+ import { PanelLeftClose, PanelLeft, ChevronLeft, ChevronRight, ZoomOut, ZoomIn, RotateCw, Minimize2, Maximize2, Download, AlertTriangle, Loader2, X, XIcon } from 'lucide-react';
4
+ import { clsx } from 'clsx';
5
+ import { twMerge } from 'tailwind-merge';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+ import 'react-pdf/dist/Page/TextLayer.css';
8
+ import 'react-pdf/dist/Page/AnnotationLayer.css';
9
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
10
+
11
+ function cn(...inputs) {
12
+ return twMerge(clsx(inputs));
13
+ }
14
+ var ZOOM_PAN_MIN_SCALE = 1;
15
+ var ZOOM_PAN_MAX_SCALE = 4;
16
+ var ZOOM_PAN_DOUBLE_TAP_MS = 300;
17
+ var ZOOM_PAN_SWIPE_LOCK_PX = 8;
18
+ var ZOOM_PAN_SWIPE_COMMIT_PX = 56;
19
+ var ZOOM_PAN_SWIPE_SNAP_MS = 180;
20
+ var IDENTITY = { scale: 1, tx: 0, ty: 0 };
21
+ function clamp(value, min, max) {
22
+ return Math.min(max, Math.max(min, value));
23
+ }
24
+ function zoomToward(prev, nextScale, originX, originY, minScale = ZOOM_PAN_MIN_SCALE, maxScale = ZOOM_PAN_MAX_SCALE) {
25
+ const scale = clamp(nextScale, minScale, maxScale);
26
+ if (scale === minScale) return { ...IDENTITY, scale: minScale };
27
+ const ratio = scale / prev.scale;
28
+ return {
29
+ scale,
30
+ tx: originX - (originX - prev.tx) * ratio,
31
+ ty: originY - (originY - prev.ty) * ratio
32
+ };
33
+ }
34
+ function pointerDistance(a, b) {
35
+ return Math.hypot(b.x - a.x, b.y - a.y);
36
+ }
37
+ function prefersReducedMotion() {
38
+ if (typeof window === "undefined" || !window.matchMedia) return false;
39
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
40
+ }
41
+ function useZoomPanGestures(options = {}) {
42
+ const {
43
+ minScale = ZOOM_PAN_MIN_SCALE,
44
+ maxScale = ZOOM_PAN_MAX_SCALE,
45
+ swipeEnabled = true,
46
+ onSwipeCommit,
47
+ wheelZoom = true,
48
+ doubleTap = "reset"
49
+ } = options;
50
+ const [transform, setTransform] = useState({
51
+ ...IDENTITY,
52
+ scale: minScale
53
+ });
54
+ const transformRef = useRef(transform);
55
+ transformRef.current = transform;
56
+ const [isSwipeDragging, setIsSwipeDragging] = useState(false);
57
+ const [snapBack, setSnapBack] = useState(false);
58
+ const stageRef = useRef(null);
59
+ const pointersRef = useRef(/* @__PURE__ */ new Map());
60
+ const pinchStartRef = useRef(null);
61
+ const panRef = useRef(null);
62
+ const swipeRef = useRef(null);
63
+ const lastTapRef = useRef(
64
+ null
65
+ );
66
+ const onSwipeCommitRef = useRef(onSwipeCommit);
67
+ onSwipeCommitRef.current = onSwipeCommit;
68
+ const swipeEnabledRef = useRef(swipeEnabled);
69
+ swipeEnabledRef.current = swipeEnabled;
70
+ const minScaleRef = useRef(minScale);
71
+ minScaleRef.current = minScale;
72
+ const maxScaleRef = useRef(maxScale);
73
+ maxScaleRef.current = maxScale;
74
+ const doubleTapRef = useRef(doubleTap);
75
+ doubleTapRef.current = doubleTap;
76
+ const reset = useCallback(() => {
77
+ setTransform({ ...IDENTITY, scale: minScaleRef.current });
78
+ setIsSwipeDragging(false);
79
+ setSnapBack(false);
80
+ }, []);
81
+ const setScale = useCallback((next) => {
82
+ setTransform(
83
+ (prev) => zoomToward(prev, next, 0, 0, minScaleRef.current, maxScaleRef.current)
84
+ );
85
+ }, []);
86
+ const clearSnapBack = useCallback(() => {
87
+ setSnapBack(false);
88
+ }, []);
89
+ const stageOrigin = useCallback((clientX, clientY) => {
90
+ const stage = stageRef.current;
91
+ if (!stage) return { x: 0, y: 0 };
92
+ const rect = stage.getBoundingClientRect();
93
+ return {
94
+ x: clientX - rect.left - rect.width / 2,
95
+ y: clientY - rect.top - rect.height / 2
96
+ };
97
+ }, []);
98
+ useEffect(() => {
99
+ if (!wheelZoom) return;
100
+ const stage = stageRef.current;
101
+ if (!stage) return;
102
+ const handleWheel = (e) => {
103
+ e.preventDefault();
104
+ e.stopPropagation();
105
+ const prev = transformRef.current;
106
+ const factor = e.deltaY > 0 ? 0.9 : 1.1;
107
+ const { x, y } = stageOrigin(e.clientX, e.clientY);
108
+ setTransform(
109
+ zoomToward(
110
+ prev,
111
+ prev.scale * factor,
112
+ x,
113
+ y,
114
+ minScaleRef.current,
115
+ maxScaleRef.current
116
+ )
117
+ );
118
+ };
119
+ stage.addEventListener("wheel", handleWheel, { passive: false });
120
+ return () => stage.removeEventListener("wheel", handleWheel);
121
+ }, [stageOrigin, wheelZoom]);
122
+ function clearSwipe() {
123
+ swipeRef.current = null;
124
+ setIsSwipeDragging(false);
125
+ }
126
+ function beginPinch() {
127
+ const pts = [...pointersRef.current.values()];
128
+ const [a, b] = pts;
129
+ if (!a || !b) return;
130
+ const midX = (a.x + b.x) / 2;
131
+ const midY = (a.y + b.y) / 2;
132
+ const { x, y } = stageOrigin(midX, midY);
133
+ pinchStartRef.current = {
134
+ distance: Math.max(pointerDistance(a, b), 1),
135
+ originX: x,
136
+ originY: y,
137
+ transform: { ...transformRef.current }
138
+ };
139
+ panRef.current = null;
140
+ clearSwipe();
141
+ setSnapBack(false);
142
+ }
143
+ function onPointerDown(e) {
144
+ e.stopPropagation();
145
+ const stage = stageRef.current;
146
+ if (!stage) return;
147
+ if (typeof stage.setPointerCapture === "function") {
148
+ stage.setPointerCapture(e.pointerId);
149
+ }
150
+ pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
151
+ setSnapBack(false);
152
+ if (pointersRef.current.size === 2) {
153
+ beginPinch();
154
+ return;
155
+ }
156
+ if (transformRef.current.scale > minScaleRef.current) {
157
+ panRef.current = {
158
+ pointerId: e.pointerId,
159
+ startX: e.clientX,
160
+ startY: e.clientY,
161
+ originTx: transformRef.current.tx,
162
+ originTy: transformRef.current.ty,
163
+ moved: false
164
+ };
165
+ return;
166
+ }
167
+ if (swipeEnabledRef.current) {
168
+ swipeRef.current = {
169
+ pointerId: e.pointerId,
170
+ startX: e.clientX,
171
+ startY: e.clientY,
172
+ moved: false,
173
+ axis: null
174
+ };
175
+ setIsSwipeDragging(true);
176
+ }
177
+ }
178
+ function onPointerMove(e) {
179
+ if (!pointersRef.current.has(e.pointerId)) return;
180
+ pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
181
+ const pinchStart = pinchStartRef.current;
182
+ if (pointersRef.current.size >= 2 && pinchStart) {
183
+ const pts = [...pointersRef.current.values()];
184
+ const [a, b] = pts;
185
+ if (!a || !b) return;
186
+ const distance = Math.max(pointerDistance(a, b), 1);
187
+ const nextScale = pinchStart.transform.scale * (distance / pinchStart.distance);
188
+ setTransform(
189
+ zoomToward(
190
+ pinchStart.transform,
191
+ nextScale,
192
+ pinchStart.originX,
193
+ pinchStart.originY,
194
+ minScaleRef.current,
195
+ maxScaleRef.current
196
+ )
197
+ );
198
+ return;
199
+ }
200
+ const pan = panRef.current;
201
+ if (pan && pan.pointerId === e.pointerId) {
202
+ const dx2 = e.clientX - pan.startX;
203
+ const dy2 = e.clientY - pan.startY;
204
+ if (Math.abs(dx2) > 2 || Math.abs(dy2) > 2) pan.moved = true;
205
+ setTransform((prev) => ({
206
+ ...prev,
207
+ tx: pan.originTx + dx2,
208
+ ty: pan.originTy + dy2
209
+ }));
210
+ return;
211
+ }
212
+ const swipe = swipeRef.current;
213
+ if (!swipe || swipe.pointerId !== e.pointerId) return;
214
+ const dx = e.clientX - swipe.startX;
215
+ const dy = e.clientY - swipe.startY;
216
+ if (Math.abs(dx) > 2 || Math.abs(dy) > 2) swipe.moved = true;
217
+ if (swipe.axis === null) {
218
+ const absX = Math.abs(dx);
219
+ const absY = Math.abs(dy);
220
+ if (absX < ZOOM_PAN_SWIPE_LOCK_PX && absY < ZOOM_PAN_SWIPE_LOCK_PX) {
221
+ return;
222
+ }
223
+ swipe.axis = absX >= absY ? "x" : "y";
224
+ if (swipe.axis === "y") {
225
+ clearSwipe();
226
+ setTransform({ ...IDENTITY, scale: minScaleRef.current });
227
+ return;
228
+ }
229
+ }
230
+ if (swipe.axis === "x") {
231
+ setTransform({
232
+ scale: minScaleRef.current,
233
+ tx: dx,
234
+ ty: 0
235
+ });
236
+ }
237
+ }
238
+ function finishSwipe(e) {
239
+ const swipe = swipeRef.current;
240
+ if (!swipe || swipe.pointerId !== e.pointerId) return false;
241
+ const dx = e.clientX - swipe.startX;
242
+ const wasMoved = swipe.moved;
243
+ const axis = swipe.axis;
244
+ clearSwipe();
245
+ if (axis === "x" && Math.abs(dx) >= ZOOM_PAN_SWIPE_COMMIT_PX) {
246
+ onSwipeCommitRef.current?.(dx < 0 ? "next" : "prev");
247
+ return true;
248
+ }
249
+ if (wasMoved || transformRef.current.tx !== 0) {
250
+ if (!prefersReducedMotion()) setSnapBack(true);
251
+ setTransform({ ...IDENTITY, scale: minScaleRef.current });
252
+ }
253
+ return wasMoved;
254
+ }
255
+ function onPointerUp(e) {
256
+ const stage = stageRef.current;
257
+ if (stage && typeof stage.hasPointerCapture === "function" && stage.hasPointerCapture(e.pointerId) && typeof stage.releasePointerCapture === "function") {
258
+ stage.releasePointerCapture(e.pointerId);
259
+ }
260
+ pointersRef.current.delete(e.pointerId);
261
+ if (pointersRef.current.size < 2) {
262
+ pinchStartRef.current = null;
263
+ }
264
+ if (pointersRef.current.size === 1) {
265
+ const remaining = [...pointersRef.current.entries()][0];
266
+ if (remaining && transformRef.current.scale > minScaleRef.current) {
267
+ const [pointerId, point] = remaining;
268
+ panRef.current = {
269
+ pointerId,
270
+ startX: point.x,
271
+ startY: point.y,
272
+ originTx: transformRef.current.tx,
273
+ originTy: transformRef.current.ty,
274
+ moved: true
275
+ };
276
+ }
277
+ }
278
+ const swipeHandled = finishSwipe(e);
279
+ if (swipeHandled) return;
280
+ const pan = panRef.current;
281
+ if (pan && pan.pointerId === e.pointerId) {
282
+ const wasMoved = pan.moved;
283
+ panRef.current = null;
284
+ if (wasMoved) return;
285
+ }
286
+ if (pointersRef.current.size === 0 && e.button === 0) {
287
+ const now = Date.now();
288
+ const last = lastTapRef.current;
289
+ if (last && now - last.time < ZOOM_PAN_DOUBLE_TAP_MS && Math.hypot(e.clientX - last.x, e.clientY - last.y) < 24) {
290
+ lastTapRef.current = null;
291
+ if (doubleTapRef.current === "toggle" && transformRef.current.scale <= minScaleRef.current) {
292
+ const { x, y } = stageOrigin(e.clientX, e.clientY);
293
+ setTransform(
294
+ zoomToward(
295
+ transformRef.current,
296
+ Math.min(2, maxScaleRef.current),
297
+ x,
298
+ y,
299
+ minScaleRef.current,
300
+ maxScaleRef.current
301
+ )
302
+ );
303
+ } else {
304
+ reset();
305
+ }
306
+ return;
307
+ }
308
+ lastTapRef.current = { time: now, x: e.clientX, y: e.clientY };
309
+ }
310
+ }
311
+ return {
312
+ stageRef,
313
+ transform,
314
+ isDragging: isSwipeDragging,
315
+ snapBack,
316
+ handlers: {
317
+ onPointerDown,
318
+ onPointerMove,
319
+ onPointerUp,
320
+ onPointerCancel: onPointerUp
321
+ },
322
+ reset,
323
+ setScale,
324
+ clearSnapBack
325
+ };
326
+ }
327
+ function LoadingState({
328
+ label = "Cargando\u2026",
329
+ className
330
+ }) {
331
+ return /* @__PURE__ */ jsxs(
332
+ "div",
333
+ {
334
+ role: "status",
335
+ "aria-live": "polite",
336
+ className: cn(
337
+ "flex items-center justify-center gap-2 py-12 text-sm text-muted-foreground",
338
+ className
339
+ ),
340
+ children: [
341
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }),
342
+ /* @__PURE__ */ jsx("span", { children: label })
343
+ ]
344
+ }
345
+ );
346
+ }
347
+ function ErrorState({
348
+ title = "Algo sali\xF3 mal",
349
+ description,
350
+ action,
351
+ className
352
+ }) {
353
+ return /* @__PURE__ */ jsxs(
354
+ "div",
355
+ {
356
+ role: "alert",
357
+ className: cn(
358
+ "flex flex-col items-center gap-3 rounded-xl border border-dashed border-destructive/40 bg-destructive/5 py-12 text-center",
359
+ className
360
+ ),
361
+ children: [
362
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "size-6 text-destructive", "aria-hidden": "true" }),
363
+ /* @__PURE__ */ jsxs("div", { children: [
364
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: title }),
365
+ description ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description }) : null
366
+ ] }),
367
+ action
368
+ ]
369
+ }
370
+ );
371
+ }
372
+ function LazyThumbnail({
373
+ pageNumber,
374
+ active,
375
+ width,
376
+ onClick,
377
+ ariaLabel
378
+ }) {
379
+ const ref = useRef(null);
380
+ const [visible, setVisible] = useState(false);
381
+ useEffect(() => {
382
+ const node = ref.current;
383
+ if (!node || typeof IntersectionObserver === "undefined") {
384
+ setVisible(true);
385
+ return;
386
+ }
387
+ const observer = new IntersectionObserver(
388
+ (entries) => {
389
+ if (entries.some((e) => e.isIntersecting)) {
390
+ setVisible(true);
391
+ observer.disconnect();
392
+ }
393
+ },
394
+ { rootMargin: "120px 0px" }
395
+ );
396
+ observer.observe(node);
397
+ return () => observer.disconnect();
398
+ }, []);
399
+ return /* @__PURE__ */ jsxs(
400
+ "button",
401
+ {
402
+ ref,
403
+ type: "button",
404
+ onClick,
405
+ "aria-label": ariaLabel,
406
+ "aria-current": active ? "page" : void 0,
407
+ className: cn(
408
+ "block w-full overflow-hidden rounded-md border bg-background p-1 transition-colors hover:border-primary",
409
+ active ? "border-primary ring-2 ring-primary/30" : "border-border"
410
+ ),
411
+ children: [
412
+ visible ? /* @__PURE__ */ jsx(Thumbnail, { pageNumber, width, className: "mx-auto" }) : /* @__PURE__ */ jsx(
413
+ "div",
414
+ {
415
+ className: "mx-auto bg-muted",
416
+ style: { width, height: Math.round(width * 1.4) },
417
+ "aria-hidden": true
418
+ }
419
+ ),
420
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-center text-[10px] text-muted-foreground", children: pageNumber })
421
+ ]
422
+ }
423
+ );
424
+ }
425
+ var configuredSrc = null;
426
+ function configurePdfWorker(workerSrc) {
427
+ if (!workerSrc || configuredSrc === workerSrc) return;
428
+ pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;
429
+ configuredSrc = workerSrc;
430
+ }
431
+ var ZOOM_STEPS = [0.5, 0.75, 1, 1.25, 1.5, 2, 2.5, 3];
432
+ var THUMB_WIDTH = 120;
433
+ function PdfViewer({
434
+ fileUrl,
435
+ fileName,
436
+ downloadUrl,
437
+ workerSrc,
438
+ initialPage = 1,
439
+ className,
440
+ labels
441
+ }) {
442
+ configurePdfWorker(workerSrc);
443
+ const [numPages, setNumPages] = useState(0);
444
+ const [pageNumber, setPageNumber] = useState(initialPage);
445
+ const [zoomIndex, setZoomIndex] = useState(2);
446
+ const [fitMode, setFitMode] = useState("width");
447
+ const [rotation, setRotation] = useState(0);
448
+ const [containerWidth, setContainerWidth] = useState(0);
449
+ const [containerHeight, setContainerHeight] = useState(0);
450
+ const [isFullscreen, setIsFullscreen] = useState(false);
451
+ const [thumbsOpen, setThumbsOpen] = useState(false);
452
+ const [loadError, setLoadError] = useState(false);
453
+ const [pageDraft, setPageDraft] = useState(String(initialPage));
454
+ const viewportRef = useRef(null);
455
+ const shellRef = useRef(null);
456
+ const pageNumberRef = useRef(pageNumber);
457
+ pageNumberRef.current = pageNumber;
458
+ const numPagesRef = useRef(numPages);
459
+ numPagesRef.current = numPages;
460
+ const baseScale = ZOOM_STEPS[zoomIndex] ?? 1;
461
+ const goToPage = useCallback((next) => {
462
+ const total = numPagesRef.current;
463
+ if (total <= 0) return;
464
+ const clamped = Math.min(Math.max(1, next), total);
465
+ setPageNumber(clamped);
466
+ setPageDraft(String(clamped));
467
+ }, []);
468
+ const onSwipeCommit = useCallback(
469
+ (direction) => {
470
+ if (direction === "next") {
471
+ goToPage(pageNumberRef.current + 1);
472
+ } else {
473
+ goToPage(pageNumberRef.current - 1);
474
+ }
475
+ },
476
+ [goToPage]
477
+ );
478
+ const {
479
+ stageRef,
480
+ transform,
481
+ isDragging,
482
+ snapBack,
483
+ handlers,
484
+ reset,
485
+ clearSnapBack
486
+ } = useZoomPanGestures({
487
+ swipeEnabled: numPages > 1,
488
+ onSwipeCommit,
489
+ wheelZoom: true,
490
+ doubleTap: "toggle"
491
+ });
492
+ useEffect(() => {
493
+ setPageNumber(initialPage);
494
+ setPageDraft(String(initialPage));
495
+ setZoomIndex(2);
496
+ setFitMode("width");
497
+ setRotation(0);
498
+ setNumPages(0);
499
+ setLoadError(false);
500
+ setThumbsOpen(false);
501
+ reset();
502
+ }, [fileUrl, initialPage, reset]);
503
+ useEffect(() => {
504
+ reset();
505
+ }, [pageNumber, reset]);
506
+ useEffect(() => {
507
+ if (!viewportRef.current) return;
508
+ const el = viewportRef.current;
509
+ const observer = new ResizeObserver((entries) => {
510
+ const rect = entries[0]?.contentRect;
511
+ if (!rect) return;
512
+ setContainerWidth(rect.width);
513
+ setContainerHeight(rect.height);
514
+ });
515
+ observer.observe(el);
516
+ setContainerWidth(el.clientWidth);
517
+ setContainerHeight(el.clientHeight);
518
+ return () => observer.disconnect();
519
+ }, []);
520
+ useEffect(() => {
521
+ function onFullscreenChange() {
522
+ setIsFullscreen(document.fullscreenElement === shellRef.current);
523
+ }
524
+ document.addEventListener("fullscreenchange", onFullscreenChange);
525
+ return () => document.removeEventListener("fullscreenchange", onFullscreenChange);
526
+ }, []);
527
+ useEffect(() => {
528
+ function onKeyDown(e) {
529
+ const target = e.target;
530
+ if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) {
531
+ return;
532
+ }
533
+ switch (e.key) {
534
+ case "ArrowLeft":
535
+ case "PageUp":
536
+ e.preventDefault();
537
+ goToPage(pageNumberRef.current - 1);
538
+ break;
539
+ case "ArrowRight":
540
+ case "PageDown":
541
+ e.preventDefault();
542
+ goToPage(pageNumberRef.current + 1);
543
+ break;
544
+ case "Home":
545
+ e.preventDefault();
546
+ goToPage(1);
547
+ break;
548
+ case "End":
549
+ e.preventDefault();
550
+ goToPage(numPagesRef.current);
551
+ break;
552
+ case "+":
553
+ case "=":
554
+ e.preventDefault();
555
+ setZoomIndex((i) => Math.min(i + 1, ZOOM_STEPS.length - 1));
556
+ break;
557
+ case "-":
558
+ e.preventDefault();
559
+ setZoomIndex((i) => Math.max(i - 1, 0));
560
+ break;
561
+ }
562
+ }
563
+ window.addEventListener("keydown", onKeyDown);
564
+ return () => window.removeEventListener("keydown", onKeyDown);
565
+ }, [goToPage]);
566
+ const fitWidth = useCallback(() => {
567
+ setFitMode("width");
568
+ setZoomIndex(2);
569
+ reset();
570
+ }, [reset]);
571
+ const fitPage = useCallback(() => {
572
+ setFitMode("page");
573
+ setZoomIndex(2);
574
+ reset();
575
+ }, [reset]);
576
+ const zoomIn = useCallback(() => {
577
+ setZoomIndex((i) => Math.min(i + 1, ZOOM_STEPS.length - 1));
578
+ }, []);
579
+ const zoomOut = useCallback(() => {
580
+ setZoomIndex((i) => Math.max(i - 1, 0));
581
+ }, []);
582
+ const toggleFullscreen = useCallback(async () => {
583
+ if (!shellRef.current) return;
584
+ if (document.fullscreenElement === shellRef.current) {
585
+ await document.exitFullscreen();
586
+ } else {
587
+ await shellRef.current.requestFullscreen();
588
+ }
589
+ }, []);
590
+ function submitPageJump(e) {
591
+ e.preventDefault();
592
+ const parsed = Number.parseInt(pageDraft, 10);
593
+ if (Number.isFinite(parsed)) goToPage(parsed);
594
+ else setPageDraft(String(pageNumber));
595
+ }
596
+ const pageWidth = fitMode === "width" ? Math.max(containerWidth - 32, 240) * baseScale : void 0;
597
+ const pageHeight = fitMode === "page" ? Math.max(containerHeight - 32, 240) * baseScale : void 0;
598
+ const rawDownload = downloadUrl ?? fileUrl;
599
+ const downloadHref = `${rawDownload}${rawDownload.includes("?") ? "&" : "?"}download=1`;
600
+ const { scale: gestureScale, tx, ty } = transform;
601
+ const transition = snapBack && !isDragging && !prefersReducedMotion() ? `transform ${ZOOM_PAN_SWIPE_SNAP_MS}ms ease-out` : void 0;
602
+ const pageOf = labels.pageOf?.(pageNumber, numPages) ?? (numPages > 0 ? `${pageNumber} / ${numPages}` : "\u2014");
603
+ function renderThumbList(onSelect) {
604
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: Array.from({ length: numPages }, (_, i) => {
605
+ const n = i + 1;
606
+ return /* @__PURE__ */ jsx(
607
+ LazyThumbnail,
608
+ {
609
+ pageNumber: n,
610
+ active: n === pageNumber,
611
+ width: THUMB_WIDTH,
612
+ onClick: () => onSelect(n),
613
+ ariaLabel: labels.thumbnailAria?.(n) ?? `P\xE1gina ${n}`
614
+ },
615
+ n
616
+ );
617
+ }) });
618
+ }
619
+ return /* @__PURE__ */ jsxs(
620
+ "div",
621
+ {
622
+ ref: shellRef,
623
+ className: cn(
624
+ "relative flex h-full min-h-0 w-full flex-col bg-background",
625
+ className
626
+ ),
627
+ children: [
628
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1 overflow-x-auto border-b px-2 py-2", children: [
629
+ /* @__PURE__ */ jsx(
630
+ "button",
631
+ {
632
+ type: "button",
633
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background text-foreground hover:bg-accent",
634
+ onClick: () => setThumbsOpen((v) => !v),
635
+ "aria-label": thumbsOpen ? labels.hideThumbnails ?? "Ocultar miniaturas" : labels.showThumbnails ?? "Mostrar miniaturas",
636
+ "aria-pressed": thumbsOpen,
637
+ children: thumbsOpen ? /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" })
638
+ }
639
+ ),
640
+ /* @__PURE__ */ jsx(
641
+ "button",
642
+ {
643
+ type: "button",
644
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background hover:bg-accent disabled:opacity-50",
645
+ onClick: () => goToPage(pageNumber - 1),
646
+ disabled: pageNumber <= 1,
647
+ "aria-label": labels.previousPage ?? "P\xE1gina anterior",
648
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
649
+ }
650
+ ),
651
+ /* @__PURE__ */ jsxs(
652
+ "form",
653
+ {
654
+ onSubmit: submitPageJump,
655
+ className: "flex shrink-0 items-center gap-1 text-xs text-muted-foreground",
656
+ children: [
657
+ /* @__PURE__ */ jsx("label", { className: "sr-only", htmlFor: "pdf-page-jump", children: labels.pageJumpAria ?? "Ir a p\xE1gina" }),
658
+ /* @__PURE__ */ jsx(
659
+ "input",
660
+ {
661
+ id: "pdf-page-jump",
662
+ value: pageDraft,
663
+ onChange: (e) => setPageDraft(e.target.value),
664
+ className: "h-8 w-12 rounded-md border bg-background px-1 text-center text-foreground",
665
+ inputMode: "numeric"
666
+ }
667
+ ),
668
+ /* @__PURE__ */ jsx("span", { className: "min-w-10 whitespace-nowrap", children: pageOf })
669
+ ]
670
+ }
671
+ ),
672
+ /* @__PURE__ */ jsx(
673
+ "button",
674
+ {
675
+ type: "button",
676
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background hover:bg-accent disabled:opacity-50",
677
+ onClick: () => goToPage(pageNumber + 1),
678
+ disabled: pageNumber >= numPages,
679
+ "aria-label": labels.nextPage ?? "P\xE1gina siguiente",
680
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
681
+ }
682
+ ),
683
+ /* @__PURE__ */ jsx(
684
+ "button",
685
+ {
686
+ type: "button",
687
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background hover:bg-accent disabled:opacity-50",
688
+ onClick: zoomOut,
689
+ disabled: zoomIndex <= 0,
690
+ "aria-label": labels.zoomOut ?? "Alejar",
691
+ children: /* @__PURE__ */ jsx(ZoomOut, { className: "h-4 w-4" })
692
+ }
693
+ ),
694
+ /* @__PURE__ */ jsx(
695
+ "button",
696
+ {
697
+ type: "button",
698
+ className: "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-background px-2 text-xs hover:bg-accent",
699
+ onClick: fitMode === "width" ? fitPage : fitWidth,
700
+ children: fitMode === "width" ? labels.fitPage ?? "Ajustar p\xE1gina" : labels.fitWidth ?? "Ajustar ancho"
701
+ }
702
+ ),
703
+ /* @__PURE__ */ jsx(
704
+ "button",
705
+ {
706
+ type: "button",
707
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background hover:bg-accent disabled:opacity-50",
708
+ onClick: zoomIn,
709
+ disabled: zoomIndex >= ZOOM_STEPS.length - 1,
710
+ "aria-label": labels.zoomIn ?? "Acercar",
711
+ children: /* @__PURE__ */ jsx(ZoomIn, { className: "h-4 w-4" })
712
+ }
713
+ ),
714
+ /* @__PURE__ */ jsx(
715
+ "button",
716
+ {
717
+ type: "button",
718
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background hover:bg-accent",
719
+ onClick: () => setRotation((r) => (r + 90) % 360),
720
+ "aria-label": labels.rotate ?? "Rotar",
721
+ children: /* @__PURE__ */ jsx(RotateCw, { className: "h-4 w-4" })
722
+ }
723
+ ),
724
+ /* @__PURE__ */ jsx(
725
+ "button",
726
+ {
727
+ type: "button",
728
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-background hover:bg-accent",
729
+ onClick: () => void toggleFullscreen(),
730
+ "aria-label": isFullscreen ? labels.exitFullscreen ?? "Salir de pantalla completa" : labels.enterFullscreen ?? "Pantalla completa",
731
+ children: isFullscreen ? /* @__PURE__ */ jsx(Minimize2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Maximize2, { className: "h-4 w-4" })
732
+ }
733
+ ),
734
+ /* @__PURE__ */ jsxs(
735
+ "a",
736
+ {
737
+ href: downloadHref,
738
+ download: fileName,
739
+ "aria-label": labels.downloadAria ?? labels.download ?? "Descargar",
740
+ className: "inline-flex h-8 shrink-0 items-center gap-1 rounded-md border bg-background px-2 text-xs hover:bg-accent",
741
+ children: [
742
+ /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" }),
743
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: labels.download ?? "Descargar" })
744
+ ]
745
+ }
746
+ )
747
+ ] }),
748
+ /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 flex-1", children: [
749
+ numPages > 0 && /* @__PURE__ */ jsx(
750
+ "div",
751
+ {
752
+ className: cn(
753
+ "hidden w-40 shrink-0 overflow-y-auto border-r bg-background p-2 lg:block",
754
+ !thumbsOpen && "lg:hidden"
755
+ ),
756
+ children: renderThumbList(goToPage)
757
+ }
758
+ ),
759
+ thumbsOpen && numPages > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
760
+ /* @__PURE__ */ jsx(
761
+ "button",
762
+ {
763
+ type: "button",
764
+ "aria-label": labels.hideThumbnails ? `${labels.hideThumbnails} (fondo)` : "Cerrar miniaturas (fondo)",
765
+ className: "absolute inset-0 z-20 bg-black/40 lg:hidden",
766
+ onClick: () => setThumbsOpen(false)
767
+ }
768
+ ),
769
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-y-0 left-0 z-30 w-40 overflow-y-auto border-r bg-background p-2 shadow-lg lg:hidden", children: renderThumbList((n) => {
770
+ goToPage(n);
771
+ setThumbsOpen(false);
772
+ }) })
773
+ ] }),
774
+ /* @__PURE__ */ jsx(
775
+ "div",
776
+ {
777
+ ref: viewportRef,
778
+ className: cn(
779
+ "relative min-h-0 flex-1 overflow-hidden bg-muted/40",
780
+ gestureScale > ZOOM_PAN_MIN_SCALE && (isDragging ? "cursor-grabbing" : "cursor-grab")
781
+ ),
782
+ children: loadError ? /* @__PURE__ */ jsx(
783
+ ErrorState,
784
+ {
785
+ title: labels.loadError ?? "No se pudo cargar el PDF",
786
+ description: labels.loadErrorDescription ?? "El archivo puede estar da\xF1ado, cifrado o bloqueado.",
787
+ action: /* @__PURE__ */ jsx(
788
+ "a",
789
+ {
790
+ href: downloadHref,
791
+ download: fileName,
792
+ className: "text-sm text-primary underline",
793
+ children: labels.download ?? "Descargar"
794
+ }
795
+ )
796
+ }
797
+ ) : /* @__PURE__ */ jsx(
798
+ "div",
799
+ {
800
+ ref: stageRef,
801
+ className: "flex min-h-full touch-none items-start justify-center p-4",
802
+ style: {
803
+ transform: `translate(${tx}px, ${ty}px) scale(${gestureScale})`,
804
+ transition
805
+ },
806
+ onPointerDown: handlers.onPointerDown,
807
+ onPointerMove: handlers.onPointerMove,
808
+ onPointerUp: handlers.onPointerUp,
809
+ onPointerCancel: handlers.onPointerCancel,
810
+ onTransitionEnd: clearSnapBack,
811
+ children: /* @__PURE__ */ jsx(
812
+ Document,
813
+ {
814
+ file: fileUrl,
815
+ onLoadSuccess: ({ numPages: pages }) => {
816
+ setNumPages(pages);
817
+ setLoadError(false);
818
+ const start = Math.min(Math.max(1, initialPage), pages);
819
+ setPageNumber(start);
820
+ setPageDraft(String(start));
821
+ },
822
+ onLoadError: () => setLoadError(true),
823
+ loading: /* @__PURE__ */ jsx(LoadingState, { label: labels.loading ?? "Cargando PDF\u2026" }),
824
+ error: /* @__PURE__ */ jsx(
825
+ ErrorState,
826
+ {
827
+ title: labels.loadError ?? "No se pudo cargar el PDF"
828
+ }
829
+ ),
830
+ children: numPages > 0 && /* @__PURE__ */ jsx(
831
+ Page,
832
+ {
833
+ pageNumber,
834
+ width: pageWidth,
835
+ height: pageHeight,
836
+ rotate: rotation,
837
+ renderTextLayer: true,
838
+ renderAnnotationLayer: true
839
+ }
840
+ )
841
+ }
842
+ )
843
+ }
844
+ )
845
+ }
846
+ )
847
+ ] })
848
+ ]
849
+ }
850
+ );
851
+ }
852
+ function Dialog({
853
+ ...props
854
+ }) {
855
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
856
+ }
857
+ function DialogPortal({
858
+ ...props
859
+ }) {
860
+ return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
861
+ }
862
+ function DialogOverlay({
863
+ className,
864
+ ...props
865
+ }) {
866
+ return /* @__PURE__ */ jsx(
867
+ DialogPrimitive.Overlay,
868
+ {
869
+ "data-slot": "dialog-overlay",
870
+ className: cn(
871
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
872
+ className
873
+ ),
874
+ ...props
875
+ }
876
+ );
877
+ }
878
+ function DialogContent({
879
+ className,
880
+ children,
881
+ showCloseButton = true,
882
+ ...props
883
+ }) {
884
+ return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
885
+ /* @__PURE__ */ jsx(DialogOverlay, {}),
886
+ /* @__PURE__ */ jsxs(
887
+ DialogPrimitive.Content,
888
+ {
889
+ "data-slot": "dialog-content",
890
+ className: cn(
891
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
892
+ className
893
+ ),
894
+ ...props,
895
+ children: [
896
+ children,
897
+ showCloseButton && /* @__PURE__ */ jsxs(
898
+ DialogPrimitive.Close,
899
+ {
900
+ "data-slot": "dialog-close",
901
+ className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
902
+ children: [
903
+ /* @__PURE__ */ jsx(XIcon, {}),
904
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
905
+ ]
906
+ }
907
+ )
908
+ ]
909
+ }
910
+ )
911
+ ] });
912
+ }
913
+ function DialogTitle({
914
+ className,
915
+ ...props
916
+ }) {
917
+ return /* @__PURE__ */ jsx(
918
+ DialogPrimitive.Title,
919
+ {
920
+ "data-slot": "dialog-title",
921
+ className: cn("text-lg leading-none font-semibold", className),
922
+ ...props
923
+ }
924
+ );
925
+ }
926
+ function DialogDescription({
927
+ className,
928
+ ...props
929
+ }) {
930
+ return /* @__PURE__ */ jsx(
931
+ DialogPrimitive.Description,
932
+ {
933
+ "data-slot": "dialog-description",
934
+ className: cn("text-muted-foreground text-sm", className),
935
+ ...props
936
+ }
937
+ );
938
+ }
939
+ function MediaViewerDialog({
940
+ open,
941
+ onOpenChange,
942
+ children,
943
+ labels,
944
+ className,
945
+ contentClassName,
946
+ showCloseButton = true
947
+ }) {
948
+ const closeLabel = labels?.close ?? "Cerrar";
949
+ const title = labels?.title ?? "Visor";
950
+ const description = labels?.description ?? title;
951
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
952
+ DialogContent,
953
+ {
954
+ showCloseButton: false,
955
+ className: cn(
956
+ "inset-0 flex h-dvh max-h-dvh w-screen max-w-none translate-x-0 translate-y-0 top-0 left-0 gap-0 overflow-hidden rounded-none border-0 p-0 shadow-none sm:max-w-none",
957
+ contentClassName,
958
+ className
959
+ ),
960
+ children: [
961
+ /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: title }),
962
+ /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: description }),
963
+ showCloseButton && /* @__PURE__ */ jsx(
964
+ "button",
965
+ {
966
+ type: "button",
967
+ onClick: () => onOpenChange(false),
968
+ "aria-label": closeLabel,
969
+ className: "absolute top-3 right-3 z-20 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70",
970
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
971
+ }
972
+ ),
973
+ children
974
+ ]
975
+ }
976
+ ) });
977
+ }
978
+ function PdfViewerDialog({
979
+ open,
980
+ onOpenChange,
981
+ title,
982
+ fileUrl,
983
+ fileName,
984
+ downloadUrl,
985
+ workerSrc,
986
+ initialPage,
987
+ labels
988
+ }) {
989
+ const viewerLabels = labels;
990
+ return /* @__PURE__ */ jsx(
991
+ MediaViewerDialog,
992
+ {
993
+ open,
994
+ onOpenChange,
995
+ labels: {
996
+ close: labels.close,
997
+ title,
998
+ description: labels.dialogDescription ?? title
999
+ },
1000
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col pt-12", children: [
1001
+ /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-2 pr-14", children: /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: title }) }),
1002
+ /* @__PURE__ */ jsx(
1003
+ PdfViewer,
1004
+ {
1005
+ fileUrl,
1006
+ fileName: fileName ?? title,
1007
+ downloadUrl,
1008
+ workerSrc,
1009
+ initialPage,
1010
+ labels: viewerLabels,
1011
+ className: "min-h-0 flex-1"
1012
+ }
1013
+ )
1014
+ ] })
1015
+ }
1016
+ );
1017
+ }
1018
+
1019
+ export { PdfViewer, PdfViewerDialog, configurePdfWorker };
1020
+ //# sourceMappingURL=pdf.js.map
1021
+ //# sourceMappingURL=pdf.js.map