@arbidocs/react 0.3.185 → 0.4.1

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.
Files changed (41) hide show
  1. package/dist/{chunk-YJAYST7S.js → chunk-3CLGHHME.js} +880 -240
  2. package/dist/chunk-3CLGHHME.js.map +1 -0
  3. package/dist/chunk-6ALMBER7.js +176 -0
  4. package/dist/chunk-6ALMBER7.js.map +1 -0
  5. package/dist/chunk-BXP6S34V.cjs +284 -0
  6. package/dist/chunk-BXP6S34V.cjs.map +1 -0
  7. package/dist/chunk-EJ2BMHYK.js +272 -0
  8. package/dist/chunk-EJ2BMHYK.js.map +1 -0
  9. package/dist/{chunk-IZPXHVPJ.cjs → chunk-OU2QLIKS.cjs} +969 -344
  10. package/dist/chunk-OU2QLIKS.cjs.map +1 -0
  11. package/dist/chunk-RZQACYNQ.cjs +218 -0
  12. package/dist/chunk-RZQACYNQ.cjs.map +1 -0
  13. package/dist/{index-4WwLZsQO.d.cts → index-CcbVSNSP.d.cts} +40 -2
  14. package/dist/{index-4WwLZsQO.d.ts → index-CcbVSNSP.d.ts} +40 -2
  15. package/dist/index.cjs +589 -1070
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +11 -155
  18. package/dist/index.d.ts +11 -155
  19. package/dist/index.js +223 -725
  20. package/dist/index.js.map +1 -1
  21. package/dist/shell/index.cjs +13 -4
  22. package/dist/shell/index.d.cts +1 -1
  23. package/dist/shell/index.d.ts +1 -1
  24. package/dist/shell/index.js +2 -1
  25. package/dist/ui/index.cjs +211 -190
  26. package/dist/ui/index.d.cts +204 -3
  27. package/dist/ui/index.d.ts +204 -3
  28. package/dist/ui/index.js +2 -1
  29. package/dist/viewers/index.cjs +327 -76
  30. package/dist/viewers/index.cjs.map +1 -1
  31. package/dist/viewers/index.d.cts +90 -4
  32. package/dist/viewers/index.d.ts +90 -4
  33. package/dist/viewers/index.js +316 -78
  34. package/dist/viewers/index.js.map +1 -1
  35. package/package.json +4 -3
  36. package/dist/chunk-3MLQUKT4.cjs +0 -97
  37. package/dist/chunk-3MLQUKT4.cjs.map +0 -1
  38. package/dist/chunk-IZPXHVPJ.cjs.map +0 -1
  39. package/dist/chunk-J3XEKOV5.js +0 -87
  40. package/dist/chunk-J3XEKOV5.js.map +0 -1
  41. package/dist/chunk-YJAYST7S.js.map +0 -1
@@ -7,6 +7,59 @@ require('react-pdf/dist/Page/AnnotationLayer.css');
7
7
  require('react-pdf/dist/Page/TextLayer.css');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
9
 
10
+ var PAGE_SELECTOR = '[role="article"][data-page-number]';
11
+ function anchorDelta(previousHeight, nextHeight, pageTop, containerTop) {
12
+ const delta = nextHeight - previousHeight;
13
+ if (delta === 0) return 0;
14
+ return pageTop + previousHeight <= containerTop + 1 ? delta : 0;
15
+ }
16
+ function measureVisiblePages(pages, viewTop, viewBottom) {
17
+ const visibility = /* @__PURE__ */ new Map();
18
+ let page = null;
19
+ let bestPx = 0;
20
+ let score = 0;
21
+ const viewHeight = viewBottom - viewTop;
22
+ for (const p of pages) {
23
+ const height = p.bottom - p.top;
24
+ const px = Math.min(p.bottom, viewBottom) - Math.max(p.top, viewTop);
25
+ if (px <= 0 || height <= 0) continue;
26
+ visibility.set(p.n, px / height);
27
+ if (px > bestPx) {
28
+ bestPx = px;
29
+ page = p.n;
30
+ score = px / Math.min(height, viewHeight);
31
+ }
32
+ }
33
+ return { page, score, visibility };
34
+ }
35
+ function useScrollAnchor(containerRef, key) {
36
+ react.useEffect(() => {
37
+ const container = containerRef.current;
38
+ if (!container || typeof ResizeObserver === "undefined") return;
39
+ container.style.overflowAnchor = "none";
40
+ const heights = /* @__PURE__ */ new Map();
41
+ const observer = new ResizeObserver((entries) => {
42
+ let shift = 0;
43
+ const containerTop = container.getBoundingClientRect().top;
44
+ for (const entry of entries) {
45
+ const el = entry.target;
46
+ const height = el.getBoundingClientRect().height;
47
+ const previous = heights.get(el);
48
+ heights.set(el, height);
49
+ if (previous === void 0) continue;
50
+ shift += anchorDelta(previous, height, el.getBoundingClientRect().top, containerTop);
51
+ }
52
+ if (shift !== 0 && !container.dataset.zooming) container.scrollTop += shift;
53
+ });
54
+ container.querySelectorAll(PAGE_SELECTOR).forEach((el) => {
55
+ heights.set(el, el.getBoundingClientRect().height);
56
+ observer.observe(el);
57
+ });
58
+ return () => observer.disconnect();
59
+ }, [containerRef, key]);
60
+ }
61
+
62
+ // src/viewers/usePageIntersectionObserver.ts
10
63
  var PAGE_UPDATE_DEBOUNCE_MS = 10;
11
64
  var MIN_VISIBILITY_THRESHOLD = 0.15;
12
65
  var INTERSECTION_THRESHOLDS = [0, 0.25, 0.5, 0.75, 1];
@@ -17,66 +70,194 @@ function usePageIntersectionObserver({
17
70
  getPageElements,
18
71
  getPageNumber,
19
72
  onPageChange,
20
- onVisibilityUpdate
73
+ onVisibilityUpdate,
74
+ observeKey
21
75
  }) {
76
+ const latest = react.useRef({ getPageElements, getPageNumber, onPageChange, onVisibilityUpdate });
77
+ latest.current = { getPageElements, getPageNumber, onPageChange, onVisibilityUpdate };
22
78
  react.useEffect(() => {
23
79
  if (!isEnabled || !containerRef.current) return;
24
80
  const container = containerRef.current;
25
- const visibilityMap = /* @__PURE__ */ new Map();
26
81
  let lastReportedPage = null;
27
82
  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);
83
+ let frame = 0;
84
+ const evaluate = () => {
85
+ const root = container.getBoundingClientRect();
86
+ const pages = [];
87
+ for (const el of latest.current.getPageElements()) {
88
+ const n = latest.current.getPageNumber(el);
89
+ if (n <= 0) continue;
90
+ const r = el.getBoundingClientRect();
91
+ pages.push({ n, top: r.top, bottom: r.bottom });
40
92
  }
41
- if (onVisibilityUpdate) {
42
- onVisibilityUpdate(new Map(visibilityMap));
93
+ const { page, score, visibility } = measureVisiblePages(pages, root.top, root.bottom);
94
+ if (scrollCompleteRef.current && page !== null && page !== lastReportedPage && score > MIN_VISIBILITY_THRESHOLD) {
95
+ lastReportedPage = page;
96
+ latest.current.onPageChange(page);
43
97
  }
98
+ latest.current.onVisibilityUpdate?.(visibility);
44
99
  };
45
100
  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);
101
+ () => {
102
+ if (updateTimeout) clearTimeout(updateTimeout);
103
+ updateTimeout = setTimeout(evaluate, PAGE_UPDATE_DEBOUNCE_MS);
57
104
  },
58
- {
59
- root: container,
60
- threshold: INTERSECTION_THRESHOLDS
61
- }
105
+ { root: container, threshold: INTERSECTION_THRESHOLDS }
62
106
  );
63
- const elements = getPageElements();
64
- elements.forEach((element) => observer.observe(element));
107
+ const onScroll = () => {
108
+ if (!frame) {
109
+ frame = requestAnimationFrame(() => {
110
+ frame = 0;
111
+ evaluate();
112
+ });
113
+ }
114
+ };
115
+ container.addEventListener("scroll", onScroll, { passive: true });
116
+ latest.current.getPageElements().forEach((element) => observer.observe(element));
65
117
  return () => {
66
118
  observer.disconnect();
67
- if (updateTimeout) {
68
- clearTimeout(updateTimeout);
119
+ container.removeEventListener("scroll", onScroll);
120
+ if (updateTimeout) clearTimeout(updateTimeout);
121
+ if (frame) cancelAnimationFrame(frame);
122
+ };
123
+ }, [containerRef, scrollCompleteRef, isEnabled, observeKey]);
124
+ }
125
+ var MIN_ZOOM = 0.5;
126
+ var MAX_ZOOM = 4;
127
+ var DOUBLE_TAP_ZOOM = 2;
128
+ var MAX_CANVAS_PIXELS = 8e6;
129
+ var MAX_RENDER_DPR = 2;
130
+ function clampZoom(zoom) {
131
+ if (!Number.isFinite(zoom)) return 1;
132
+ return Math.round(Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom)) * 100) / 100;
133
+ }
134
+ var ZOOM_PRESETS = [1.5, 2, 3];
135
+ function cycleZoom(zoom) {
136
+ return ZOOM_PRESETS.find((preset) => preset > zoom + 0.05) ?? 1;
137
+ }
138
+ function doubleTapZoom(zoom) {
139
+ return zoom > 1.05 ? 1 : DOUBLE_TAP_ZOOM;
140
+ }
141
+ function zoomLabel(zoom) {
142
+ return `${Math.round(zoom * 100)}%`;
143
+ }
144
+ function pinchZoom(startZoom, startDistance, distance2) {
145
+ if (startDistance <= 0) return startZoom;
146
+ return clampZoom(startZoom * distance2 / startDistance);
147
+ }
148
+ function anchoredScroll(scroll, focal, oldZoom, newZoom) {
149
+ if (oldZoom <= 0) return scroll;
150
+ return (scroll + focal) * newZoom / oldZoom - focal;
151
+ }
152
+ function renderDpr(cssWidth, cssHeight, devicePixelRatio, maxPixels = MAX_CANVAS_PIXELS) {
153
+ const wanted = Math.min(Math.max(devicePixelRatio || 1, 1), MAX_RENDER_DPR);
154
+ const area = cssWidth * cssHeight;
155
+ if (area <= 0) return wanted;
156
+ const fits = Math.sqrt(maxPixels / area);
157
+ return Math.max(1, Math.min(wanted, fits));
158
+ }
159
+ var distance = (a, b) => Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
160
+ var INTERACTIVE = 'button, a, input, textarea, select, [role="button"]';
161
+ var DOUBLE_TAP_MS = 300;
162
+ var DOUBLE_TAP_SLOP_PX = 30;
163
+ function usePinchZoom({ containerRef, zoom, onZoomChange }) {
164
+ const zoomRef = react.useRef(zoom);
165
+ const onChangeRef = react.useRef(onZoomChange);
166
+ onChangeRef.current = onZoomChange;
167
+ const focalRef = react.useRef(null);
168
+ const appliedZoomRef = react.useRef(zoom);
169
+ const request = react.useCallback((next, focal) => {
170
+ const clamped = clampZoom(next);
171
+ if (clamped === zoomRef.current) return;
172
+ focalRef.current = focal;
173
+ zoomRef.current = clamped;
174
+ onChangeRef.current?.(clamped);
175
+ }, []);
176
+ react.useLayoutEffect(() => {
177
+ zoomRef.current = zoom;
178
+ const container = containerRef.current;
179
+ const previous = appliedZoomRef.current;
180
+ appliedZoomRef.current = zoom;
181
+ if (!container || previous === zoom) return;
182
+ const rect = container.getBoundingClientRect();
183
+ const focal = focalRef.current ?? { x: rect.width / 2, y: rect.height / 2 };
184
+ focalRef.current = null;
185
+ container.dataset.zooming = "true";
186
+ container.scrollLeft = anchoredScroll(container.scrollLeft, focal.x, previous, zoom);
187
+ container.scrollTop = anchoredScroll(container.scrollTop, focal.y, previous, zoom);
188
+ requestAnimationFrame(
189
+ () => requestAnimationFrame(() => {
190
+ delete container.dataset.zooming;
191
+ })
192
+ );
193
+ }, [zoom, containerRef]);
194
+ react.useEffect(() => {
195
+ const el = containerRef.current;
196
+ if (!el) return;
197
+ let pinch = null;
198
+ let lastTap = null;
199
+ let frame = 0;
200
+ let pending = null;
201
+ const focalOf = (a, b) => {
202
+ const rect = el.getBoundingClientRect();
203
+ return {
204
+ x: (a.clientX + b.clientX) / 2 - rect.left,
205
+ y: (a.clientY + b.clientY) / 2 - rect.top
206
+ };
207
+ };
208
+ const flush = () => {
209
+ frame = 0;
210
+ if (pending) request(pending.zoom, pending.focal);
211
+ pending = null;
212
+ };
213
+ const onStart = (e) => {
214
+ if (e.touches.length === 2) {
215
+ pinch = { startDistance: distance(e.touches[0], e.touches[1]), startZoom: zoomRef.current };
216
+ lastTap = null;
69
217
  }
70
218
  };
71
- }, [
72
- containerRef,
73
- scrollCompleteRef,
74
- isEnabled,
75
- getPageElements,
76
- getPageNumber,
77
- onPageChange,
78
- onVisibilityUpdate
79
- ]);
219
+ const onMove = (e) => {
220
+ if (!pinch || e.touches.length !== 2) return;
221
+ e.preventDefault();
222
+ pending = {
223
+ zoom: pinchZoom(pinch.startZoom, pinch.startDistance, distance(e.touches[0], e.touches[1])),
224
+ focal: focalOf(e.touches[0], e.touches[1])
225
+ };
226
+ if (!frame) frame = requestAnimationFrame(flush);
227
+ };
228
+ const onEnd = (e) => {
229
+ if (pinch && e.touches.length < 2) {
230
+ pinch = null;
231
+ return;
232
+ }
233
+ if (e.touches.length !== 0 || e.changedTouches.length !== 1) return;
234
+ const t = e.changedTouches[0];
235
+ if (e.target?.closest?.(INTERACTIVE)) return;
236
+ const now = e.timeStamp;
237
+ if (lastTap && now - lastTap.time < DOUBLE_TAP_MS && Math.hypot(t.clientX - lastTap.x, t.clientY - lastTap.y) < DOUBLE_TAP_SLOP_PX) {
238
+ e.preventDefault();
239
+ const rect = el.getBoundingClientRect();
240
+ request(doubleTapZoom(zoomRef.current), {
241
+ x: t.clientX - rect.left,
242
+ y: t.clientY - rect.top
243
+ });
244
+ lastTap = null;
245
+ } else {
246
+ lastTap = { time: now, x: t.clientX, y: t.clientY };
247
+ }
248
+ };
249
+ el.addEventListener("touchstart", onStart, { passive: true });
250
+ el.addEventListener("touchmove", onMove, { passive: false });
251
+ el.addEventListener("touchend", onEnd, { passive: false });
252
+ el.addEventListener("touchcancel", onEnd, { passive: false });
253
+ return () => {
254
+ el.removeEventListener("touchstart", onStart);
255
+ el.removeEventListener("touchmove", onMove);
256
+ el.removeEventListener("touchend", onEnd);
257
+ el.removeEventListener("touchcancel", onEnd);
258
+ if (frame) cancelAnimationFrame(frame);
259
+ };
260
+ }, [containerRef, request]);
80
261
  }
81
262
 
82
263
  // src/viewers/pdf-viewer-constants.ts
@@ -94,14 +275,28 @@ function ensurePdfWorker(workerSrc) {
94
275
  reactPdf.pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;
95
276
  }
96
277
  }
97
- var CONTAINER_PADDING_PX = 32;
98
- var PAGE_ASPECT_RATIO = 1.414;
278
+ var CONTAINER_PADDING_PX = 34;
279
+ var DEFAULT_PAGE_ASPECT_RATIO = 1.414;
280
+ var PAGE_BORDER_PX = 2;
281
+ var RERENDER_DEBOUNCE_MS = 250;
282
+ var ZOOMED_BUFFER_THRESHOLD = 1.5;
283
+ function sameSet(a, b) {
284
+ if (a.size !== b.size) return false;
285
+ for (const v of a) if (!b.has(v)) return false;
286
+ return true;
287
+ }
99
288
  var TEXT_LAYER_BUFFER = 1;
289
+ var ratioOf = (page) => {
290
+ const { width, height } = page.getViewport({ scale: 1 });
291
+ return width > 0 && height > 0 ? height / width : DEFAULT_PAGE_ASPECT_RATIO;
292
+ };
100
293
  function PdfViewer({
101
294
  file,
102
295
  currentPage,
103
296
  onLoadSuccess,
104
297
  scrollTrigger,
298
+ zoom = 1,
299
+ onZoomChange,
105
300
  onPageChange,
106
301
  disableFontFace = false,
107
302
  assets
@@ -113,6 +308,8 @@ function PdfViewer({
113
308
  ensurePdfWorker(workerSrc);
114
309
  const [numPages, setNumPages] = react.useState(0);
115
310
  const [containerWidth, setContainerWidth] = react.useState(0);
311
+ const [defaultRatio, setDefaultRatio] = react.useState(DEFAULT_PAGE_ASPECT_RATIO);
312
+ const [pageRatios, setPageRatios] = react.useState(/* @__PURE__ */ new Map());
116
313
  const [loadError, setLoadError] = react.useState(null);
117
314
  const [documentKey, setDocumentKey] = react.useState(() => `pdf-${Date.now()}`);
118
315
  const initialPage = currentPage || 1;
@@ -132,7 +329,10 @@ function PdfViewer({
132
329
  const containerRef = react.useRef(null);
133
330
  const pageRefs = react.useRef(/* @__PURE__ */ new Map());
134
331
  const onDocumentLoadSuccess = react.useCallback(
135
- ({ numPages: n }) => {
332
+ (pdf) => {
333
+ const n = pdf.numPages;
334
+ void pdf.getPage?.(1).then((page) => setDefaultRatio(ratioOf(page))).catch(() => {
335
+ });
136
336
  setNumPages(n);
137
337
  setLoadError(null);
138
338
  const expand = () => {
@@ -211,45 +411,51 @@ function PdfViewer({
211
411
  });
212
412
  }
213
413
  }, [numPages]);
414
+ const currentPageRef = react.useRef(currentPage);
415
+ currentPageRef.current = currentPage;
214
416
  react.useEffect(() => {
215
- if (!scrollTrigger || numPages === 0 || !currentPage) return;
417
+ const currentPage2 = currentPageRef.current;
418
+ if (!scrollTrigger || numPages === 0 || !currentPage2) return;
216
419
  scrollCompleteRef.current = false;
217
420
  setRenderedPages((prev) => {
218
421
  const newSet = new Set(prev);
219
- for (let i = Math.max(1, currentPage - LAZY_LOAD_BUFFER_BEFORE); i <= Math.min(numPages, currentPage + LAZY_LOAD_BUFFER_AFTER); i++) {
422
+ for (let i = Math.max(1, currentPage2 - LAZY_LOAD_BUFFER_BEFORE); i <= Math.min(numPages, currentPage2 + LAZY_LOAD_BUFFER_AFTER); i++) {
220
423
  newSet.add(i);
221
424
  }
222
425
  return newSet;
223
426
  });
224
427
  requestAnimationFrame(() => {
225
428
  requestAnimationFrame(() => {
226
- const pageElement = pageRefs.current.get(currentPage);
429
+ const pageElement = pageRefs.current.get(currentPage2);
227
430
  if (pageElement) {
228
431
  pageElement.scrollIntoView({ behavior: "instant", block: "start" });
229
432
  }
230
433
  scrollCompleteRef.current = true;
231
434
  });
232
435
  });
233
- }, [scrollTrigger, currentPage, numPages]);
436
+ }, [scrollTrigger, numPages]);
234
437
  const handleVisibilityUpdate = react.useCallback(
235
438
  (visibilityMap) => {
236
439
  const visiblePageNums = Array.from(visibilityMap.entries()).filter(([, ratio]) => ratio > 0).map(([pageNum]) => pageNum);
237
440
  if (visiblePageNums.length > 0) {
238
441
  const minVisible = Math.min(...visiblePageNums);
239
442
  const maxVisible = Math.max(...visiblePageNums);
443
+ const zoomed = zoom > ZOOMED_BUFFER_THRESHOLD;
444
+ const before = zoomed ? 1 : LAZY_LOAD_BUFFER_BEFORE;
445
+ const after = zoomed ? 2 : LAZY_LOAD_BUFFER_AFTER;
240
446
  const pagesToRender = /* @__PURE__ */ new Set();
241
- for (let i = Math.max(1, minVisible - LAZY_LOAD_BUFFER_BEFORE); i <= Math.min(numPages, maxVisible + LAZY_LOAD_BUFFER_AFTER); i++) {
447
+ for (let i = Math.max(1, minVisible - before); i <= Math.min(numPages, maxVisible + after); i++) {
242
448
  pagesToRender.add(i);
243
449
  }
244
- setRenderedPages(pagesToRender);
450
+ setRenderedPages((prev) => sameSet(prev, pagesToRender) ? prev : pagesToRender);
245
451
  const nearViewport = /* @__PURE__ */ new Set();
246
452
  for (let i = Math.max(1, minVisible - TEXT_LAYER_BUFFER); i <= Math.min(numPages, maxVisible + TEXT_LAYER_BUFFER); i++) {
247
453
  nearViewport.add(i);
248
454
  }
249
- setVisiblePages(nearViewport);
455
+ setVisiblePages((prev) => sameSet(prev, nearViewport) ? prev : nearViewport);
250
456
  }
251
457
  },
252
- [numPages]
458
+ [numPages, zoom]
253
459
  );
254
460
  const documentOptions = react.useMemo(
255
461
  () => ({
@@ -272,19 +478,30 @@ function PdfViewer({
272
478
  getPageElements: () => Array.from(pageRefs.current.values()),
273
479
  getPageNumber: (element) => parseInt(element.getAttribute("data-page-number") ?? "0"),
274
480
  onPageChange: handlePageChange,
275
- onVisibilityUpdate: handleVisibilityUpdate
481
+ onVisibilityUpdate: handleVisibilityUpdate,
482
+ observeKey: `${numPages}:${containerWidth > 0}`
276
483
  });
484
+ useScrollAnchor(containerRef, `${numPages}:${containerWidth > 0}`);
485
+ usePinchZoom({ containerRef, zoom, onZoomChange });
486
+ const [renderZoom, setRenderZoom] = react.useState(zoom);
487
+ react.useEffect(() => {
488
+ if (zoom === renderZoom) return;
489
+ const timer = setTimeout(() => setRenderZoom(zoom), RERENDER_DEBOUNCE_MS);
490
+ return () => clearTimeout(timer);
491
+ }, [zoom, renderZoom]);
277
492
  return /* @__PURE__ */ jsxRuntime.jsx(
278
493
  "div",
279
494
  {
280
495
  ref: containerRef,
281
- className: "w-full h-full overflow-y-auto overflow-x-hidden bg-muted flex flex-col items-center p-4",
496
+ className: "w-full h-full overflow-auto bg-muted flex flex-col items-start p-4 pb-20",
497
+ style: { touchAction: "pan-x pan-y" },
282
498
  role: "region",
283
499
  "aria-label": "PDF document viewer",
284
500
  "data-testid": "pdf-viewer",
285
501
  children: /* @__PURE__ */ jsxRuntime.jsx(
286
502
  reactPdf.Document,
287
503
  {
504
+ className: "flex w-max min-w-full flex-col",
288
505
  file,
289
506
  onLoadSuccess: onDocumentLoadSuccess,
290
507
  onLoadError: onDocumentLoadError,
@@ -301,7 +518,11 @@ function PdfViewer({
301
518
  children: containerWidth > 0 && Array.from(new Array(numPages), (_, index) => {
302
519
  const pageNumber = index + 1;
303
520
  const shouldRender = renderedPages.has(pageNumber);
304
- const pageHeight = containerWidth * PAGE_ASPECT_RATIO;
521
+ const ratio = pageRatios.get(pageNumber) ?? defaultRatio;
522
+ const pageWidth = containerWidth * zoom;
523
+ const pageHeight = pageWidth * ratio;
524
+ const renderWidth = containerWidth * renderZoom;
525
+ const scale = zoom / renderZoom;
305
526
  const shouldRenderTextLayer = visiblePages.has(pageNumber);
306
527
  return /* @__PURE__ */ jsxRuntime.jsx(
307
528
  "div",
@@ -314,28 +535,45 @@ function PdfViewer({
314
535
  }
315
536
  },
316
537
  "data-page-number": pageNumber,
317
- className: "mb-6 shadow-xl bg-white border border-border",
318
- style: !shouldRender ? { minHeight: pageHeight } : void 0,
538
+ className: "mx-auto mb-6 shrink-0 shadow-xl bg-white border border-border",
539
+ style: { width: pageWidth + PAGE_BORDER_PX, height: pageHeight + PAGE_BORDER_PX },
319
540
  role: "article",
320
541
  "aria-label": `Page ${pageNumber} of ${numPages}`,
321
542
  children: shouldRender ? /* @__PURE__ */ jsxRuntime.jsx(
322
- reactPdf.Page,
543
+ "div",
323
544
  {
324
- pageNumber,
325
- width: containerWidth,
326
- renderTextLayer: shouldRenderTextLayer,
327
- renderAnnotationLayer: false,
328
- devicePixelRatio: Math.min(window.devicePixelRatio || 1, 2),
329
- loading: /* @__PURE__ */ jsxRuntime.jsx(
330
- "div",
545
+ "data-testid": `pdf-page-scaler-${pageNumber}`,
546
+ style: scale === 1 ? void 0 : { transform: `scale(${scale})`, transformOrigin: "0 0" },
547
+ children: /* @__PURE__ */ jsxRuntime.jsx(
548
+ reactPdf.Page,
331
549
  {
332
- className: "flex items-center justify-center bg-muted",
333
- style: { width: containerWidth, height: pageHeight },
334
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-muted-foreground", children: [
335
- "Loading page ",
336
- pageNumber,
337
- "..."
338
- ] })
550
+ pageNumber,
551
+ width: renderWidth,
552
+ renderTextLayer: shouldRenderTextLayer,
553
+ renderAnnotationLayer: false,
554
+ onLoadSuccess: (page) => {
555
+ const ratio2 = page.originalHeight / page.originalWidth;
556
+ setPageRatios(
557
+ (prev) => Math.abs((prev.get(pageNumber) ?? defaultRatio) - ratio2) < 1e-3 ? prev : new Map(prev).set(pageNumber, ratio2)
558
+ );
559
+ },
560
+ devicePixelRatio: renderDpr(
561
+ renderWidth,
562
+ renderWidth * ratio,
563
+ window.devicePixelRatio || 1
564
+ ),
565
+ loading: /* @__PURE__ */ jsxRuntime.jsx(
566
+ "div",
567
+ {
568
+ className: "flex items-center justify-center bg-muted",
569
+ style: { width: renderWidth, height: renderWidth * ratio },
570
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-muted-foreground", children: [
571
+ "Loading page ",
572
+ pageNumber,
573
+ "..."
574
+ ] })
575
+ }
576
+ )
339
577
  }
340
578
  )
341
579
  }
@@ -343,7 +581,7 @@ function PdfViewer({
343
581
  "div",
344
582
  {
345
583
  className: "flex items-center justify-center bg-muted",
346
- style: { width: containerWidth, height: pageHeight },
584
+ style: { width: pageWidth, height: pageHeight },
347
585
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-muted-foreground", children: [
348
586
  "Page ",
349
587
  pageNumber
@@ -665,9 +903,22 @@ exports.DocxViewer = DocxViewer;
665
903
  exports.INITIAL_LAZY_LOAD_BUFFER = INITIAL_LAZY_LOAD_BUFFER;
666
904
  exports.LAZY_LOAD_BUFFER_AFTER = LAZY_LOAD_BUFFER_AFTER;
667
905
  exports.LAZY_LOAD_BUFFER_BEFORE = LAZY_LOAD_BUFFER_BEFORE;
906
+ exports.MAX_ZOOM = MAX_ZOOM;
907
+ exports.MIN_ZOOM = MIN_ZOOM;
908
+ exports.PAGE_SELECTOR = PAGE_SELECTOR;
668
909
  exports.PdfViewer = PdfViewer;
669
910
  exports.XlsxViewer = XlsxViewer;
911
+ exports.anchorDelta = anchorDelta;
912
+ exports.anchoredScroll = anchoredScroll;
913
+ exports.clampZoom = clampZoom;
914
+ exports.cycleZoom = cycleZoom;
915
+ exports.doubleTapZoom = doubleTapZoom;
670
916
  exports.nativeViewerKind = nativeViewerKind;
917
+ exports.pinchZoom = pinchZoom;
918
+ exports.renderDpr = renderDpr;
671
919
  exports.usePageIntersectionObserver = usePageIntersectionObserver;
920
+ exports.usePinchZoom = usePinchZoom;
921
+ exports.useScrollAnchor = useScrollAnchor;
922
+ exports.zoomLabel = zoomLabel;
672
923
  //# sourceMappingURL=index.cjs.map
673
924
  //# sourceMappingURL=index.cjs.map