pdfjs-reader-core 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -310,6 +310,9 @@ var init_highlight_storage = __esm({
310
310
 
311
311
  // src/store/viewer-store.ts
312
312
  import { createStore } from "zustand/vanilla";
313
+ function generateRequestId() {
314
+ return `scroll-${Date.now()}-${++requestCounter}`;
315
+ }
313
316
  function createViewerStore(initialOverrides = {}) {
314
317
  return createStore()((set, get) => ({
315
318
  ...initialState,
@@ -362,6 +365,46 @@ function createViewerStore(initialOverrides = {}) {
362
365
  set({ currentPage: currentPage - 1 });
363
366
  }
364
367
  },
368
+ // Scroll coordination actions
369
+ requestScrollToPage: (page, behavior = "smooth") => {
370
+ const { numPages } = get();
371
+ const validPage = Math.max(1, Math.min(page, numPages));
372
+ return new Promise((resolve) => {
373
+ const requestId = generateRequestId();
374
+ const timeoutId = setTimeout(() => {
375
+ const callback = scrollCallbacks.get(requestId);
376
+ if (callback) {
377
+ scrollCallbacks.delete(requestId);
378
+ callback.resolve();
379
+ }
380
+ const currentRequest = get().scrollToPageRequest;
381
+ if (currentRequest?.requestId === requestId) {
382
+ set({ scrollToPageRequest: null });
383
+ }
384
+ }, SCROLL_TIMEOUT_MS);
385
+ scrollCallbacks.set(requestId, { resolve, timeoutId });
386
+ set({
387
+ currentPage: validPage,
388
+ scrollToPageRequest: {
389
+ page: validPage,
390
+ requestId,
391
+ behavior
392
+ }
393
+ });
394
+ });
395
+ },
396
+ completeScrollRequest: (requestId) => {
397
+ const callback = scrollCallbacks.get(requestId);
398
+ if (callback) {
399
+ clearTimeout(callback.timeoutId);
400
+ scrollCallbacks.delete(requestId);
401
+ callback.resolve();
402
+ }
403
+ const currentRequest = get().scrollToPageRequest;
404
+ if (currentRequest?.requestId === requestId) {
405
+ set({ scrollToPageRequest: null });
406
+ }
407
+ },
365
408
  // Zoom actions
366
409
  setScale: (scale) => {
367
410
  const clampedScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
@@ -428,13 +471,16 @@ function createViewerStore(initialOverrides = {}) {
428
471
  }
429
472
  }));
430
473
  }
431
- var ZOOM_LEVELS, MIN_SCALE, MAX_SCALE, initialState;
474
+ var ZOOM_LEVELS, MIN_SCALE, MAX_SCALE, SCROLL_TIMEOUT_MS, scrollCallbacks, requestCounter, initialState;
432
475
  var init_viewer_store = __esm({
433
476
  "src/store/viewer-store.ts"() {
434
477
  "use strict";
435
478
  ZOOM_LEVELS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4];
436
479
  MIN_SCALE = 0.1;
437
480
  MAX_SCALE = 10;
481
+ SCROLL_TIMEOUT_MS = 3e3;
482
+ scrollCallbacks = /* @__PURE__ */ new Map();
483
+ requestCounter = 0;
438
484
  initialState = {
439
485
  // Document state
440
486
  document: null,
@@ -445,6 +491,8 @@ var init_viewer_store = __esm({
445
491
  currentPage: 1,
446
492
  scale: 1,
447
493
  rotation: 0,
494
+ // Scroll coordination
495
+ scrollToPageRequest: null,
448
496
  // UI state
449
497
  viewMode: "single",
450
498
  scrollMode: "single",
@@ -1253,6 +1301,252 @@ var init_agent_api = __esm({
1253
1301
  }
1254
1302
  });
1255
1303
 
1304
+ // src/utils/text-search.ts
1305
+ async function extractPageText(document2, pageNumber) {
1306
+ const page = await document2.getPage(pageNumber);
1307
+ const textContent = await page.getTextContent();
1308
+ const viewport = page.getViewport({ scale: 1 });
1309
+ let fullText = "";
1310
+ const charPositions = [];
1311
+ for (const item of textContent.items) {
1312
+ if ("str" in item && item.str) {
1313
+ const tx = item.transform;
1314
+ const x = tx[4];
1315
+ const y = viewport.height - tx[5];
1316
+ const width = item.width ?? 0;
1317
+ const height = item.height ?? 12;
1318
+ const charWidth = item.str.length > 0 ? width / item.str.length : width;
1319
+ for (let i = 0; i < item.str.length; i++) {
1320
+ charPositions.push({
1321
+ char: item.str[i],
1322
+ rect: {
1323
+ x: x + i * charWidth,
1324
+ y: y - height,
1325
+ width: charWidth,
1326
+ height
1327
+ }
1328
+ });
1329
+ }
1330
+ fullText += item.str;
1331
+ }
1332
+ }
1333
+ return { fullText, charPositions };
1334
+ }
1335
+ async function findTextOnPage(document2, pageNumber, query, options = {}) {
1336
+ const { caseSensitive = false, wholeWord = false } = options;
1337
+ if (!query || pageNumber < 1 || pageNumber > document2.numPages) {
1338
+ return [];
1339
+ }
1340
+ const { fullText, charPositions } = await extractPageText(document2, pageNumber);
1341
+ const matches = [];
1342
+ const searchText = caseSensitive ? query : query.toLowerCase();
1343
+ const textToSearch = caseSensitive ? fullText : fullText.toLowerCase();
1344
+ let startIndex = 0;
1345
+ while (true) {
1346
+ const matchIndex = textToSearch.indexOf(searchText, startIndex);
1347
+ if (matchIndex === -1) break;
1348
+ if (wholeWord) {
1349
+ const beforeChar = matchIndex > 0 ? textToSearch[matchIndex - 1] : " ";
1350
+ const afterChar = matchIndex + query.length < textToSearch.length ? textToSearch[matchIndex + query.length] : " ";
1351
+ if (/\w/.test(beforeChar) || /\w/.test(afterChar)) {
1352
+ startIndex = matchIndex + 1;
1353
+ continue;
1354
+ }
1355
+ }
1356
+ const matchRects = [];
1357
+ for (let i = matchIndex; i < matchIndex + query.length && i < charPositions.length; i++) {
1358
+ matchRects.push(charPositions[i].rect);
1359
+ }
1360
+ const mergedRects = mergeAdjacentRects(matchRects);
1361
+ matches.push({
1362
+ text: fullText.substring(matchIndex, matchIndex + query.length),
1363
+ rects: mergedRects,
1364
+ pageNumber,
1365
+ startIndex: matchIndex
1366
+ });
1367
+ startIndex = matchIndex + 1;
1368
+ }
1369
+ return matches;
1370
+ }
1371
+ async function findTextInDocument(document2, query, options = {}) {
1372
+ const { pageRange, ...findOptions } = options;
1373
+ const pagesToSearch = pageRange ?? Array.from({ length: document2.numPages }, (_, i) => i + 1);
1374
+ const allMatches = [];
1375
+ for (const pageNum of pagesToSearch) {
1376
+ if (pageNum < 1 || pageNum > document2.numPages) continue;
1377
+ try {
1378
+ const matches = await findTextOnPage(document2, pageNum, query, findOptions);
1379
+ allMatches.push(...matches);
1380
+ } catch {
1381
+ }
1382
+ }
1383
+ return allMatches;
1384
+ }
1385
+ function mergeAdjacentRects(rects) {
1386
+ if (rects.length === 0) return [];
1387
+ const sorted = [...rects].sort((a, b) => a.y - b.y || a.x - b.x);
1388
+ const merged = [];
1389
+ let current = { ...sorted[0] };
1390
+ for (let i = 1; i < sorted.length; i++) {
1391
+ const rect = sorted[i];
1392
+ if (Math.abs(rect.y - current.y) < 2 && rect.x <= current.x + current.width + 2) {
1393
+ const newRight = Math.max(current.x + current.width, rect.x + rect.width);
1394
+ current.width = newRight - current.x;
1395
+ current.height = Math.max(current.height, rect.height);
1396
+ } else {
1397
+ merged.push(current);
1398
+ current = { ...rect };
1399
+ }
1400
+ }
1401
+ merged.push(current);
1402
+ return merged;
1403
+ }
1404
+ async function getPageText(document2, pageNumber) {
1405
+ if (pageNumber < 1 || pageNumber > document2.numPages) {
1406
+ return "";
1407
+ }
1408
+ const page = await document2.getPage(pageNumber);
1409
+ const textContent = await page.getTextContent();
1410
+ return textContent.items.filter((item) => "str" in item).map((item) => item.str).join("");
1411
+ }
1412
+ async function countTextOnPage(document2, pageNumber, query, options = {}) {
1413
+ const { caseSensitive = false, wholeWord = false } = options;
1414
+ if (!query || pageNumber < 1 || pageNumber > document2.numPages) {
1415
+ return 0;
1416
+ }
1417
+ const text = await getPageText(document2, pageNumber);
1418
+ const searchText = caseSensitive ? query : query.toLowerCase();
1419
+ const textToSearch = caseSensitive ? text : text.toLowerCase();
1420
+ let count = 0;
1421
+ let startIndex = 0;
1422
+ while (true) {
1423
+ const matchIndex = textToSearch.indexOf(searchText, startIndex);
1424
+ if (matchIndex === -1) break;
1425
+ if (wholeWord) {
1426
+ const beforeChar = matchIndex > 0 ? textToSearch[matchIndex - 1] : " ";
1427
+ const afterChar = matchIndex + query.length < textToSearch.length ? textToSearch[matchIndex + query.length] : " ";
1428
+ if (/\w/.test(beforeChar) || /\w/.test(afterChar)) {
1429
+ startIndex = matchIndex + 1;
1430
+ continue;
1431
+ }
1432
+ }
1433
+ count++;
1434
+ startIndex = matchIndex + 1;
1435
+ }
1436
+ return count;
1437
+ }
1438
+ var init_text_search = __esm({
1439
+ "src/utils/text-search.ts"() {
1440
+ "use strict";
1441
+ }
1442
+ });
1443
+
1444
+ // src/utils/coordinates.ts
1445
+ function pdfToViewport(x, y, scale, pageHeight) {
1446
+ return {
1447
+ x: x * scale,
1448
+ y: (pageHeight - y) * scale
1449
+ };
1450
+ }
1451
+ function viewportToPDF(x, y, scale, pageHeight) {
1452
+ return {
1453
+ x: x / scale,
1454
+ y: pageHeight - y / scale
1455
+ };
1456
+ }
1457
+ function percentToPDF(xPercent, yPercent, pageWidth, pageHeight) {
1458
+ return {
1459
+ x: xPercent / 100 * pageWidth,
1460
+ y: yPercent / 100 * pageHeight
1461
+ };
1462
+ }
1463
+ function pdfToPercent(x, y, pageWidth, pageHeight) {
1464
+ return {
1465
+ x: x / pageWidth * 100,
1466
+ y: y / pageHeight * 100
1467
+ };
1468
+ }
1469
+ function percentToViewport(xPercent, yPercent, pageWidth, pageHeight, scale) {
1470
+ return {
1471
+ x: xPercent / 100 * pageWidth * scale,
1472
+ y: yPercent / 100 * pageHeight * scale
1473
+ };
1474
+ }
1475
+ function viewportToPercent(x, y, pageWidth, pageHeight, scale) {
1476
+ return {
1477
+ x: x / (pageWidth * scale) * 100,
1478
+ y: y / (pageHeight * scale) * 100
1479
+ };
1480
+ }
1481
+ function applyRotation(x, y, rotation, pageWidth, pageHeight) {
1482
+ const normalizedRotation = (rotation % 360 + 360) % 360;
1483
+ switch (normalizedRotation) {
1484
+ case 90:
1485
+ return { x: y, y: pageWidth - x };
1486
+ case 180:
1487
+ return { x: pageWidth - x, y: pageHeight - y };
1488
+ case 270:
1489
+ return { x: pageHeight - y, y: x };
1490
+ default:
1491
+ return { x, y };
1492
+ }
1493
+ }
1494
+ function removeRotation(x, y, rotation, pageWidth, pageHeight) {
1495
+ const normalizedRotation = (rotation % 360 + 360) % 360;
1496
+ switch (normalizedRotation) {
1497
+ case 90:
1498
+ return { x: pageWidth - y, y: x };
1499
+ case 180:
1500
+ return { x: pageWidth - x, y: pageHeight - y };
1501
+ case 270:
1502
+ return { x: y, y: pageHeight - x };
1503
+ default:
1504
+ return { x, y };
1505
+ }
1506
+ }
1507
+ function getRotatedDimensions(width, height, rotation) {
1508
+ const normalizedRotation = (rotation % 360 + 360) % 360;
1509
+ if (normalizedRotation === 90 || normalizedRotation === 270) {
1510
+ return { width: height, height: width };
1511
+ }
1512
+ return { width, height };
1513
+ }
1514
+ function scaleRect(rect, fromScale, toScale) {
1515
+ const ratio = toScale / fromScale;
1516
+ return {
1517
+ x: rect.x * ratio,
1518
+ y: rect.y * ratio,
1519
+ width: rect.width * ratio,
1520
+ height: rect.height * ratio
1521
+ };
1522
+ }
1523
+ function isPointInRect(point, rect) {
1524
+ return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
1525
+ }
1526
+ function doRectsIntersect(rectA, rectB) {
1527
+ return !(rectA.x + rectA.width < rectB.x || rectB.x + rectB.width < rectA.x || rectA.y + rectA.height < rectB.y || rectB.y + rectB.height < rectA.y);
1528
+ }
1529
+ function getRectIntersection(rectA, rectB) {
1530
+ const x = Math.max(rectA.x, rectB.x);
1531
+ const y = Math.max(rectA.y, rectB.y);
1532
+ const right = Math.min(rectA.x + rectA.width, rectB.x + rectB.width);
1533
+ const bottom = Math.min(rectA.y + rectA.height, rectB.y + rectB.height);
1534
+ if (right <= x || bottom <= y) {
1535
+ return null;
1536
+ }
1537
+ return {
1538
+ x,
1539
+ y,
1540
+ width: right - x,
1541
+ height: bottom - y
1542
+ };
1543
+ }
1544
+ var init_coordinates = __esm({
1545
+ "src/utils/coordinates.ts"() {
1546
+ "use strict";
1547
+ }
1548
+ });
1549
+
1256
1550
  // src/utils/index.ts
1257
1551
  var init_utils = __esm({
1258
1552
  "src/utils/index.ts"() {
@@ -1265,6 +1559,8 @@ var init_utils = __esm({
1265
1559
  init_export_annotations();
1266
1560
  init_student_storage();
1267
1561
  init_agent_api();
1562
+ init_text_search();
1563
+ init_coordinates();
1268
1564
  }
1269
1565
  });
1270
1566
 
@@ -7889,6 +8185,8 @@ var init_DocumentContainer = __esm({
7889
8185
  nextPage,
7890
8186
  previousPage
7891
8187
  } = usePDFViewer();
8188
+ const scrollToPageRequest = useViewerStore((s) => s.scrollToPageRequest);
8189
+ const { viewerStore } = usePDFViewerStores();
7892
8190
  const [currentPageObj, setCurrentPageObj] = useState18(null);
7893
8191
  const [isLoadingPage, setIsLoadingPage] = useState18(false);
7894
8192
  const containerRef = useRef16(null);
@@ -7954,6 +8252,11 @@ var init_DocumentContainer = __esm({
7954
8252
  const page = await document2.getPage(currentPage);
7955
8253
  if (!cancelled && document2 === documentRef.current) {
7956
8254
  setCurrentPageObj(page);
8255
+ if (scrollToPageRequest && scrollToPageRequest.page === currentPage) {
8256
+ requestAnimationFrame(() => {
8257
+ viewerStore.getState().completeScrollRequest(scrollToPageRequest.requestId);
8258
+ });
8259
+ }
7957
8260
  }
7958
8261
  } catch (error) {
7959
8262
  if (!cancelled) {
@@ -7972,7 +8275,7 @@ var init_DocumentContainer = __esm({
7972
8275
  return () => {
7973
8276
  cancelled = true;
7974
8277
  };
7975
- }, [document2, currentPage]);
8278
+ }, [document2, currentPage, scrollToPageRequest, viewerStore]);
7976
8279
  const getPageElement = useCallback29(() => {
7977
8280
  return containerRef.current?.querySelector(`[data-page-number="${currentPage}"]`);
7978
8281
  }, [currentPage]);
@@ -8114,6 +8417,8 @@ var init_VirtualizedDocumentContainer = __esm({
8114
8417
  nextPage,
8115
8418
  previousPage
8116
8419
  } = usePDFViewer();
8420
+ const scrollToPageRequest = useViewerStore((s) => s.scrollToPageRequest);
8421
+ const { viewerStore } = usePDFViewerStores();
8117
8422
  const containerRef = useRef17(null);
8118
8423
  const scrollContainerRef = useRef17(null);
8119
8424
  const documentRef = useRef17(null);
@@ -8251,6 +8556,45 @@ var init_VirtualizedDocumentContainer = __esm({
8251
8556
  loadPages();
8252
8557
  }, [document2, visiblePages, pageObjects]);
8253
8558
  useEffect20(() => {
8559
+ if (!scrollToPageRequest || !scrollContainerRef.current || pageInfos.length === 0) return;
8560
+ const { page, requestId, behavior } = scrollToPageRequest;
8561
+ const pageInfo = pageInfos.find((p) => p.pageNumber === page);
8562
+ if (!pageInfo) {
8563
+ viewerStore.getState().completeScrollRequest(requestId);
8564
+ return;
8565
+ }
8566
+ const container = scrollContainerRef.current;
8567
+ const targetScroll = pageInfo.top - pageGap;
8568
+ const scrollTop = container.scrollTop;
8569
+ const viewportHeight = container.clientHeight;
8570
+ const isVisible = targetScroll >= scrollTop && pageInfo.top + pageInfo.height <= scrollTop + viewportHeight;
8571
+ if (isVisible) {
8572
+ viewerStore.getState().completeScrollRequest(requestId);
8573
+ return;
8574
+ }
8575
+ container.scrollTo({
8576
+ top: targetScroll,
8577
+ behavior
8578
+ });
8579
+ if (behavior === "instant") {
8580
+ requestAnimationFrame(() => {
8581
+ viewerStore.getState().completeScrollRequest(requestId);
8582
+ });
8583
+ } else {
8584
+ let scrollEndTimeout;
8585
+ const handleScrollEnd = () => {
8586
+ clearTimeout(scrollEndTimeout);
8587
+ scrollEndTimeout = setTimeout(() => {
8588
+ container.removeEventListener("scroll", handleScrollEnd);
8589
+ viewerStore.getState().completeScrollRequest(requestId);
8590
+ }, 100);
8591
+ };
8592
+ container.addEventListener("scroll", handleScrollEnd, { passive: true });
8593
+ handleScrollEnd();
8594
+ }
8595
+ }, [scrollToPageRequest, pageInfos, pageGap, viewerStore]);
8596
+ useEffect20(() => {
8597
+ if (scrollToPageRequest) return;
8254
8598
  if (!scrollContainerRef.current || pageInfos.length === 0) return;
8255
8599
  const pageInfo = pageInfos.find((p) => p.pageNumber === currentPage);
8256
8600
  if (pageInfo) {
@@ -8265,7 +8609,7 @@ var init_VirtualizedDocumentContainer = __esm({
8265
8609
  });
8266
8610
  }
8267
8611
  }
8268
- }, [currentPage, pageInfos, pageGap]);
8612
+ }, [currentPage, pageInfos, pageGap, scrollToPageRequest]);
8269
8613
  const handlePinchZoom = useCallback30(
8270
8614
  (pinchScale) => {
8271
8615
  const newScale = Math.max(0.25, Math.min(4, baseScaleRef.current * pinchScale));
@@ -8472,6 +8816,8 @@ var init_DualPageContainer = __esm({
8472
8816
  setScale,
8473
8817
  goToPage
8474
8818
  } = usePDFViewer();
8819
+ const scrollToPageRequest = useViewerStore((s) => s.scrollToPageRequest);
8820
+ const { viewerStore } = usePDFViewerStores();
8475
8821
  const containerRef = useRef18(null);
8476
8822
  const documentRef = useRef18(null);
8477
8823
  const baseScaleRef = useRef18(scale);
@@ -8557,6 +8903,14 @@ var init_DualPageContainer = __esm({
8557
8903
  if (!cancelled) {
8558
8904
  setLeftPage(left);
8559
8905
  setRightPage(right);
8906
+ if (scrollToPageRequest) {
8907
+ const requestedPage = scrollToPageRequest.page;
8908
+ if (requestedPage === spread2.left || requestedPage === spread2.right) {
8909
+ requestAnimationFrame(() => {
8910
+ viewerStore.getState().completeScrollRequest(scrollToPageRequest.requestId);
8911
+ });
8912
+ }
8913
+ }
8560
8914
  }
8561
8915
  } catch (error) {
8562
8916
  if (!cancelled) {
@@ -8572,7 +8926,7 @@ var init_DualPageContainer = __esm({
8572
8926
  return () => {
8573
8927
  cancelled = true;
8574
8928
  };
8575
- }, [document2, currentPage, getSpreadPages]);
8929
+ }, [document2, currentPage, getSpreadPages, scrollToPageRequest, viewerStore]);
8576
8930
  const goToPreviousSpread = useCallback31(() => {
8577
8931
  const spread2 = getSpreadPages(currentPage);
8578
8932
  const leftmostPage = spread2.left || spread2.right || currentPage;
@@ -8769,10 +9123,14 @@ var init_FloatingZoomControls = __esm({
8769
9123
  const scale = useViewerStore((s) => s.scale);
8770
9124
  const document2 = useViewerStore((s) => s.document);
8771
9125
  const handleZoomIn = useCallback32(() => {
8772
- viewerStore.getState().zoomIn();
9126
+ const currentScale = viewerStore.getState().scale;
9127
+ const newScale = Math.min(4, currentScale + 0.05);
9128
+ viewerStore.getState().setScale(newScale);
8773
9129
  }, [viewerStore]);
8774
9130
  const handleZoomOut = useCallback32(() => {
8775
- viewerStore.getState().zoomOut();
9131
+ const currentScale = viewerStore.getState().scale;
9132
+ const newScale = Math.max(0.1, currentScale - 0.05);
9133
+ viewerStore.getState().setScale(newScale);
8776
9134
  }, [viewerStore]);
8777
9135
  const handleFitToWidth = useCallback32(() => {
8778
9136
  viewerStore.getState().setScale(1);
@@ -8945,6 +9303,7 @@ var init_PDFViewerClient = __esm({
8945
9303
  PDFViewerInner = memo25(function PDFViewerInner2({
8946
9304
  src,
8947
9305
  initialPage = 1,
9306
+ page: controlledPage,
8948
9307
  initialScale = "auto",
8949
9308
  showToolbar = true,
8950
9309
  showSidebar = true,
@@ -8954,9 +9313,17 @@ var init_PDFViewerClient = __esm({
8954
9313
  onDocumentLoad,
8955
9314
  onPageChange,
8956
9315
  onScaleChange,
9316
+ onZoomChange,
8957
9317
  onError,
9318
+ onPageRenderStart,
9319
+ onPageRenderComplete,
9320
+ onHighlightAdded,
9321
+ onHighlightRemoved,
9322
+ onAnnotationAdded,
8958
9323
  workerSrc,
8959
9324
  className,
9325
+ loadingComponent,
9326
+ errorComponent,
8960
9327
  onReady
8961
9328
  }) {
8962
9329
  const { viewerStore, annotationStore, searchStore } = usePDFViewerStores();
@@ -8966,12 +9333,26 @@ var init_PDFViewerClient = __esm({
8966
9333
  const onErrorRef = useRef19(onError);
8967
9334
  const onPageChangeRef = useRef19(onPageChange);
8968
9335
  const onScaleChangeRef = useRef19(onScaleChange);
9336
+ const onZoomChangeRef = useRef19(onZoomChange);
9337
+ const onPageRenderStartRef = useRef19(onPageRenderStart);
9338
+ const onPageRenderCompleteRef = useRef19(onPageRenderComplete);
9339
+ const onHighlightAddedRef = useRef19(onHighlightAdded);
9340
+ const onHighlightRemovedRef = useRef19(onHighlightRemoved);
9341
+ const onAnnotationAddedRef = useRef19(onAnnotationAdded);
8969
9342
  const onReadyRef = useRef19(onReady);
8970
9343
  onDocumentLoadRef.current = onDocumentLoad;
8971
9344
  onErrorRef.current = onError;
8972
9345
  onPageChangeRef.current = onPageChange;
8973
9346
  onScaleChangeRef.current = onScaleChange;
9347
+ onZoomChangeRef.current = onZoomChange;
9348
+ onPageRenderStartRef.current = onPageRenderStart;
9349
+ onPageRenderCompleteRef.current = onPageRenderComplete;
9350
+ onHighlightAddedRef.current = onHighlightAdded;
9351
+ onHighlightRemovedRef.current = onHighlightRemoved;
9352
+ onAnnotationAddedRef.current = onAnnotationAdded;
8974
9353
  onReadyRef.current = onReady;
9354
+ const isControlled = controlledPage !== void 0;
9355
+ const prevControlledPageRef = useRef19(controlledPage);
8975
9356
  const srcIdRef = useRef19(null);
8976
9357
  const currentPage = useViewerStore((s) => s.currentPage);
8977
9358
  const scale = useViewerStore((s) => s.scale);
@@ -9112,8 +9493,9 @@ var init_PDFViewerClient = __esm({
9112
9493
  }
9113
9494
  },
9114
9495
  // ==================== Navigation ====================
9115
- goToPage: (page) => {
9116
- viewerStore.getState().goToPage(page);
9496
+ goToPage: async (page, options) => {
9497
+ const behavior = options?.behavior ?? "smooth";
9498
+ await viewerStore.getState().requestScrollToPage(page, behavior);
9117
9499
  },
9118
9500
  nextPage: () => {
9119
9501
  viewerStore.getState().nextPage();
@@ -9173,6 +9555,246 @@ var init_PDFViewerClient = __esm({
9173
9555
  clearSearch: () => {
9174
9556
  searchStore.getState().clearSearch();
9175
9557
  },
9558
+ // ==================== Combined Search & Highlight ====================
9559
+ searchAndHighlight: async (query, options) => {
9560
+ const doc = viewerStore.getState().document;
9561
+ if (!doc) {
9562
+ return { matchCount: 0, highlightIds: [], matches: [] };
9563
+ }
9564
+ const color = options?.color ?? "yellow";
9565
+ const caseSensitive = options?.caseSensitive ?? false;
9566
+ const wholeWord = options?.wholeWord ?? false;
9567
+ const scrollToFirst = options?.scrollToFirst ?? true;
9568
+ const clearPrevious = options?.clearPrevious ?? true;
9569
+ if (clearPrevious) {
9570
+ const existingHighlights = annotationStore.getState().highlights;
9571
+ for (const h of existingHighlights) {
9572
+ if (h.source === "search") {
9573
+ annotationStore.getState().removeHighlight(h.id);
9574
+ }
9575
+ }
9576
+ }
9577
+ let pagesToSearch;
9578
+ if (options?.pageRange) {
9579
+ if (Array.isArray(options.pageRange)) {
9580
+ pagesToSearch = options.pageRange;
9581
+ } else {
9582
+ const { start, end } = options.pageRange;
9583
+ pagesToSearch = Array.from({ length: end - start + 1 }, (_, i) => start + i);
9584
+ }
9585
+ } else {
9586
+ pagesToSearch = Array.from({ length: doc.numPages }, (_, i) => i + 1);
9587
+ }
9588
+ const result = {
9589
+ matchCount: 0,
9590
+ highlightIds: [],
9591
+ matches: []
9592
+ };
9593
+ const searchText = caseSensitive ? query : query.toLowerCase();
9594
+ for (const pageNum of pagesToSearch) {
9595
+ if (pageNum < 1 || pageNum > doc.numPages) continue;
9596
+ try {
9597
+ const page = await doc.getPage(pageNum);
9598
+ const textContent = await page.getTextContent();
9599
+ const viewport = page.getViewport({ scale: 1 });
9600
+ let fullText = "";
9601
+ const charPositions = [];
9602
+ for (const item of textContent.items) {
9603
+ if ("str" in item && item.str) {
9604
+ const tx = item.transform;
9605
+ const x = tx[4];
9606
+ const y = viewport.height - tx[5];
9607
+ const width = item.width ?? 0;
9608
+ const height = item.height ?? 12;
9609
+ const charWidth = item.str.length > 0 ? width / item.str.length : width;
9610
+ for (let i = 0; i < item.str.length; i++) {
9611
+ charPositions.push({
9612
+ char: item.str[i],
9613
+ rect: {
9614
+ x: x + i * charWidth,
9615
+ y: y - height,
9616
+ width: charWidth,
9617
+ height
9618
+ }
9619
+ });
9620
+ }
9621
+ fullText += item.str;
9622
+ }
9623
+ }
9624
+ const textToSearch = caseSensitive ? fullText : fullText.toLowerCase();
9625
+ let startIndex = 0;
9626
+ while (true) {
9627
+ let matchIndex = textToSearch.indexOf(searchText, startIndex);
9628
+ if (matchIndex === -1) break;
9629
+ if (wholeWord) {
9630
+ const beforeChar = matchIndex > 0 ? textToSearch[matchIndex - 1] : " ";
9631
+ const afterChar = matchIndex + query.length < textToSearch.length ? textToSearch[matchIndex + query.length] : " ";
9632
+ if (/\w/.test(beforeChar) || /\w/.test(afterChar)) {
9633
+ startIndex = matchIndex + 1;
9634
+ continue;
9635
+ }
9636
+ }
9637
+ const matchRects = [];
9638
+ for (let i = matchIndex; i < matchIndex + query.length && i < charPositions.length; i++) {
9639
+ matchRects.push(charPositions[i].rect);
9640
+ }
9641
+ const mergedRects = mergeRects2(matchRects);
9642
+ const highlight = annotationStore.getState().addHighlight({
9643
+ pageNumber: pageNum,
9644
+ rects: mergedRects,
9645
+ color,
9646
+ text: fullText.substring(matchIndex, matchIndex + query.length),
9647
+ source: "search"
9648
+ });
9649
+ result.matchCount++;
9650
+ result.highlightIds.push(highlight.id);
9651
+ result.matches.push({
9652
+ pageNumber: pageNum,
9653
+ text: fullText.substring(matchIndex, matchIndex + query.length),
9654
+ highlightId: highlight.id,
9655
+ rects: mergedRects
9656
+ });
9657
+ startIndex = matchIndex + 1;
9658
+ }
9659
+ } catch {
9660
+ }
9661
+ }
9662
+ if (scrollToFirst && result.matches.length > 0) {
9663
+ const firstMatch = result.matches[0];
9664
+ await viewerStore.getState().requestScrollToPage(firstMatch.pageNumber, "smooth");
9665
+ }
9666
+ return result;
9667
+ },
9668
+ // ==================== Agent Tools ====================
9669
+ agentTools: {
9670
+ navigateToPage: async (page) => {
9671
+ try {
9672
+ const { currentPage: currentPage2, numPages } = viewerStore.getState();
9673
+ if (numPages === 0) {
9674
+ return {
9675
+ success: false,
9676
+ error: { code: "NO_DOCUMENT", message: "No document is loaded" }
9677
+ };
9678
+ }
9679
+ if (page < 1 || page > numPages) {
9680
+ return {
9681
+ success: false,
9682
+ error: { code: "INVALID_PAGE", message: `Page ${page} is out of range (1-${numPages})` }
9683
+ };
9684
+ }
9685
+ const previousPage = currentPage2;
9686
+ await viewerStore.getState().requestScrollToPage(page, "smooth");
9687
+ return {
9688
+ success: true,
9689
+ data: { previousPage, currentPage: page }
9690
+ };
9691
+ } catch (err) {
9692
+ return {
9693
+ success: false,
9694
+ error: { code: "NAVIGATION_FAILED", message: err instanceof Error ? err.message : "Unknown error" }
9695
+ };
9696
+ }
9697
+ },
9698
+ highlightText: async (text, options) => {
9699
+ try {
9700
+ const highlightIds = await handle.highlightText(text, options);
9701
+ return {
9702
+ success: true,
9703
+ data: { matchCount: highlightIds.length, highlightIds }
9704
+ };
9705
+ } catch (err) {
9706
+ return {
9707
+ success: false,
9708
+ error: { code: "HIGHLIGHT_FAILED", message: err instanceof Error ? err.message : "Unknown error" }
9709
+ };
9710
+ }
9711
+ },
9712
+ getPageContent: async (page) => {
9713
+ try {
9714
+ const doc = viewerStore.getState().document;
9715
+ if (!doc) {
9716
+ return {
9717
+ success: false,
9718
+ error: { code: "NO_DOCUMENT", message: "No document is loaded" }
9719
+ };
9720
+ }
9721
+ if (page < 1 || page > doc.numPages) {
9722
+ return {
9723
+ success: false,
9724
+ error: { code: "INVALID_PAGE", message: `Page ${page} is out of range (1-${doc.numPages})` }
9725
+ };
9726
+ }
9727
+ const pageObj = await doc.getPage(page);
9728
+ const textContent = await pageObj.getTextContent();
9729
+ const text = textContent.items.filter((item) => "str" in item).map((item) => item.str).join("");
9730
+ return {
9731
+ success: true,
9732
+ data: { text }
9733
+ };
9734
+ } catch (err) {
9735
+ return {
9736
+ success: false,
9737
+ error: { code: "CONTENT_FETCH_FAILED", message: err instanceof Error ? err.message : "Unknown error" }
9738
+ };
9739
+ }
9740
+ },
9741
+ clearAllVisuals: async () => {
9742
+ try {
9743
+ const highlights = annotationStore.getState().highlights;
9744
+ for (const h of highlights) {
9745
+ annotationStore.getState().removeHighlight(h.id);
9746
+ }
9747
+ const annotations = annotationStore.getState().annotations;
9748
+ for (const a of annotations) {
9749
+ annotationStore.getState().removeAnnotation(a.id);
9750
+ }
9751
+ return { success: true };
9752
+ } catch (err) {
9753
+ return {
9754
+ success: false,
9755
+ error: { code: "CLEAR_FAILED", message: err instanceof Error ? err.message : "Unknown error" }
9756
+ };
9757
+ }
9758
+ }
9759
+ },
9760
+ // ==================== Coordinate Helpers ====================
9761
+ coordinates: {
9762
+ getPageDimensions: (page) => {
9763
+ const doc = viewerStore.getState().document;
9764
+ if (!doc || page < 1 || page > doc.numPages) {
9765
+ return null;
9766
+ }
9767
+ try {
9768
+ return {
9769
+ width: 612,
9770
+ // Default US Letter width
9771
+ height: 792,
9772
+ // Default US Letter height
9773
+ rotation: viewerStore.getState().rotation
9774
+ };
9775
+ } catch {
9776
+ return null;
9777
+ }
9778
+ },
9779
+ percentToPixels: (xPercent, yPercent, page) => {
9780
+ const dimensions = handle.coordinates.getPageDimensions(page);
9781
+ if (!dimensions) return null;
9782
+ const scale2 = viewerStore.getState().scale;
9783
+ return {
9784
+ x: xPercent / 100 * dimensions.width * scale2,
9785
+ y: yPercent / 100 * dimensions.height * scale2
9786
+ };
9787
+ },
9788
+ pixelsToPercent: (x, y, page) => {
9789
+ const dimensions = handle.coordinates.getPageDimensions(page);
9790
+ if (!dimensions) return null;
9791
+ const scale2 = viewerStore.getState().scale;
9792
+ return {
9793
+ x: x / (dimensions.width * scale2) * 100,
9794
+ y: y / (dimensions.height * scale2) * 100
9795
+ };
9796
+ }
9797
+ },
9176
9798
  // ==================== Document ====================
9177
9799
  getDocument: () => {
9178
9800
  return viewerStore.getState().document;
@@ -9259,10 +9881,36 @@ var init_PDFViewerClient = __esm({
9259
9881
  if (prevScaleRef.current !== scale) {
9260
9882
  prevScaleRef.current = scale;
9261
9883
  onScaleChangeRef.current?.(scale);
9884
+ onZoomChangeRef.current?.(scale);
9262
9885
  }
9263
9886
  }, [scale]);
9887
+ useEffect22(() => {
9888
+ if (!isControlled || controlledPage === void 0) return;
9889
+ if (prevControlledPageRef.current === controlledPage) return;
9890
+ prevControlledPageRef.current = controlledPage;
9891
+ const { numPages, currentPage: currentPage2 } = viewerStore.getState();
9892
+ if (numPages > 0 && controlledPage !== currentPage2) {
9893
+ viewerStore.getState().requestScrollToPage(controlledPage, "smooth");
9894
+ }
9895
+ }, [controlledPage, isControlled, viewerStore]);
9264
9896
  const themeClass = theme === "dark" ? "dark" : "";
9265
9897
  if (error) {
9898
+ if (errorComponent) {
9899
+ const errorContent = typeof errorComponent === "function" ? errorComponent(error, handleRetry) : errorComponent;
9900
+ return /* @__PURE__ */ jsx26(
9901
+ "div",
9902
+ {
9903
+ className: cn(
9904
+ "pdf-viewer pdf-viewer-error",
9905
+ "flex flex-col h-full",
9906
+ "bg-white dark:bg-gray-900",
9907
+ themeClass,
9908
+ className
9909
+ ),
9910
+ children: errorContent
9911
+ }
9912
+ );
9913
+ }
9266
9914
  return /* @__PURE__ */ jsx26(
9267
9915
  "div",
9268
9916
  {
@@ -9318,7 +9966,7 @@ var init_PDFViewerClient = __esm({
9318
9966
  renderContainer()
9319
9967
  ] }),
9320
9968
  showFloatingZoom && /* @__PURE__ */ jsx26(FloatingZoomControls, { position: "bottom-right" }),
9321
- isLoading && /* @__PURE__ */ jsx26("div", { className: "absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-900/80", children: /* @__PURE__ */ jsxs22("div", { className: "flex flex-col items-center", children: [
9969
+ isLoading && /* @__PURE__ */ jsx26("div", { className: "absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-900/80", children: loadingComponent ?? /* @__PURE__ */ jsxs22("div", { className: "flex flex-col items-center", children: [
9322
9970
  /* @__PURE__ */ jsx26("div", { className: "w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" }),
9323
9971
  /* @__PURE__ */ jsx26("div", { className: "mt-2 text-sm text-gray-500", children: "Loading PDF..." })
9324
9972
  ] }) })
@@ -10692,10 +11340,247 @@ var Minimap = memo34(function Minimap2({
10692
11340
  // src/components/index.ts
10693
11341
  init_FloatingZoomControls2();
10694
11342
 
11343
+ // src/components/PDFThumbnailNav/PDFThumbnailNav.tsx
11344
+ init_hooks();
11345
+ init_utils();
11346
+ import { memo as memo35, useEffect as useEffect26, useState as useState28, useRef as useRef25, useCallback as useCallback41 } from "react";
11347
+ import { jsx as jsx36, jsxs as jsxs30 } from "react/jsx-runtime";
11348
+ var DEFAULT_WIDTH = 612;
11349
+ var DEFAULT_HEIGHT = 792;
11350
+ var PDFThumbnailNav = memo35(function PDFThumbnailNav2({
11351
+ thumbnailScale = 0.15,
11352
+ orientation = "vertical",
11353
+ maxVisible = 10,
11354
+ className,
11355
+ onThumbnailClick,
11356
+ gap = 8,
11357
+ showPageNumbers = true
11358
+ }) {
11359
+ const { document: document2, numPages, currentPage } = usePDFViewer();
11360
+ const { viewerStore } = usePDFViewerStores();
11361
+ const containerRef = useRef25(null);
11362
+ const [thumbnails, setThumbnails] = useState28(/* @__PURE__ */ new Map());
11363
+ const [visibleRange, setVisibleRange] = useState28({ start: 1, end: maxVisible });
11364
+ const renderQueueRef = useRef25(/* @__PURE__ */ new Set());
11365
+ const pageCache = useRef25(/* @__PURE__ */ new Map());
11366
+ const thumbnailWidth = Math.floor(DEFAULT_WIDTH * thumbnailScale);
11367
+ const thumbnailHeight = Math.floor(DEFAULT_HEIGHT * thumbnailScale);
11368
+ const updateVisibleRange = useCallback41(() => {
11369
+ if (!containerRef.current || numPages === 0) return;
11370
+ const container = containerRef.current;
11371
+ const isHorizontal2 = orientation === "horizontal";
11372
+ const scrollPosition = isHorizontal2 ? container.scrollLeft : container.scrollTop;
11373
+ const viewportSize = isHorizontal2 ? container.clientWidth : container.clientHeight;
11374
+ const itemSize = (isHorizontal2 ? thumbnailWidth : thumbnailHeight) + gap;
11375
+ const firstVisible = Math.max(1, Math.floor(scrollPosition / itemSize) + 1);
11376
+ const visibleCount = Math.ceil(viewportSize / itemSize) + 2;
11377
+ const lastVisible = Math.min(numPages, firstVisible + visibleCount);
11378
+ setVisibleRange({ start: firstVisible, end: lastVisible });
11379
+ }, [numPages, orientation, thumbnailWidth, thumbnailHeight, gap]);
11380
+ useEffect26(() => {
11381
+ const container = containerRef.current;
11382
+ if (!container) return;
11383
+ const handleScroll = () => {
11384
+ requestAnimationFrame(updateVisibleRange);
11385
+ };
11386
+ container.addEventListener("scroll", handleScroll, { passive: true });
11387
+ updateVisibleRange();
11388
+ return () => container.removeEventListener("scroll", handleScroll);
11389
+ }, [updateVisibleRange]);
11390
+ useEffect26(() => {
11391
+ if (!document2) {
11392
+ setThumbnails(/* @__PURE__ */ new Map());
11393
+ pageCache.current.clear();
11394
+ return;
11395
+ }
11396
+ const renderThumbnails = async () => {
11397
+ const newThumbnails = new Map(thumbnails);
11398
+ const pagesToRender = [];
11399
+ for (let i = visibleRange.start; i <= visibleRange.end; i++) {
11400
+ if (!newThumbnails.has(i) && !renderQueueRef.current.has(i)) {
11401
+ pagesToRender.push(i);
11402
+ renderQueueRef.current.add(i);
11403
+ }
11404
+ }
11405
+ for (const pageNum of pagesToRender) {
11406
+ try {
11407
+ let page = pageCache.current.get(pageNum);
11408
+ if (!page) {
11409
+ page = await document2.getPage(pageNum);
11410
+ pageCache.current.set(pageNum, page);
11411
+ }
11412
+ const viewport = page.getViewport({ scale: thumbnailScale });
11413
+ const canvas = window.document.createElement("canvas");
11414
+ canvas.width = Math.floor(viewport.width);
11415
+ canvas.height = Math.floor(viewport.height);
11416
+ const ctx = canvas.getContext("2d");
11417
+ if (ctx) {
11418
+ await page.render({
11419
+ canvasContext: ctx,
11420
+ viewport
11421
+ }).promise;
11422
+ newThumbnails.set(pageNum, {
11423
+ pageNumber: pageNum,
11424
+ canvas,
11425
+ width: canvas.width,
11426
+ height: canvas.height
11427
+ });
11428
+ }
11429
+ } catch (error) {
11430
+ console.error(`Failed to render thumbnail for page ${pageNum}:`, error);
11431
+ } finally {
11432
+ renderQueueRef.current.delete(pageNum);
11433
+ }
11434
+ }
11435
+ if (pagesToRender.length > 0) {
11436
+ setThumbnails(newThumbnails);
11437
+ }
11438
+ };
11439
+ renderThumbnails();
11440
+ }, [document2, visibleRange, thumbnailScale, thumbnails]);
11441
+ useEffect26(() => {
11442
+ if (!containerRef.current || numPages === 0) return;
11443
+ const container = containerRef.current;
11444
+ const isHorizontal2 = orientation === "horizontal";
11445
+ const itemSize = (isHorizontal2 ? thumbnailWidth : thumbnailHeight) + gap;
11446
+ const targetPosition = (currentPage - 1) * itemSize;
11447
+ const scrollPosition = isHorizontal2 ? container.scrollLeft : container.scrollTop;
11448
+ const viewportSize = isHorizontal2 ? container.clientWidth : container.clientHeight;
11449
+ if (targetPosition < scrollPosition || targetPosition + itemSize > scrollPosition + viewportSize) {
11450
+ const targetScroll = targetPosition - (viewportSize - itemSize) / 2;
11451
+ container.scrollTo({
11452
+ [isHorizontal2 ? "left" : "top"]: Math.max(0, targetScroll),
11453
+ behavior: "smooth"
11454
+ });
11455
+ }
11456
+ }, [currentPage, numPages, orientation, thumbnailWidth, thumbnailHeight, gap]);
11457
+ const handleThumbnailClick = useCallback41((pageNum) => {
11458
+ onThumbnailClick?.(pageNum);
11459
+ viewerStore.getState().requestScrollToPage(pageNum, "smooth");
11460
+ }, [onThumbnailClick, viewerStore]);
11461
+ if (!document2 || numPages === 0) {
11462
+ return /* @__PURE__ */ jsx36(
11463
+ "div",
11464
+ {
11465
+ className: cn(
11466
+ "pdf-thumbnail-nav",
11467
+ "flex items-center justify-center",
11468
+ "bg-gray-100 dark:bg-gray-800",
11469
+ "text-gray-500 dark:text-gray-400",
11470
+ "text-sm",
11471
+ className
11472
+ ),
11473
+ style: {
11474
+ width: orientation === "vertical" ? thumbnailWidth + 24 : "100%",
11475
+ height: orientation === "horizontal" ? thumbnailHeight + 40 : "100%"
11476
+ },
11477
+ children: "No document"
11478
+ }
11479
+ );
11480
+ }
11481
+ const isHorizontal = orientation === "horizontal";
11482
+ const totalSize = numPages * ((isHorizontal ? thumbnailWidth : thumbnailHeight) + gap) - gap;
11483
+ return /* @__PURE__ */ jsx36(
11484
+ "div",
11485
+ {
11486
+ ref: containerRef,
11487
+ className: cn(
11488
+ "pdf-thumbnail-nav",
11489
+ "overflow-auto",
11490
+ "bg-gray-100 dark:bg-gray-800",
11491
+ isHorizontal ? "flex-row" : "flex-col",
11492
+ className
11493
+ ),
11494
+ style: {
11495
+ ...isHorizontal ? { overflowX: "auto", overflowY: "hidden" } : { overflowX: "hidden", overflowY: "auto" }
11496
+ },
11497
+ children: /* @__PURE__ */ jsx36(
11498
+ "div",
11499
+ {
11500
+ className: cn(
11501
+ "relative",
11502
+ isHorizontal ? "flex flex-row items-center" : "flex flex-col items-center"
11503
+ ),
11504
+ style: {
11505
+ [isHorizontal ? "width" : "height"]: totalSize,
11506
+ [isHorizontal ? "minWidth" : "minHeight"]: totalSize,
11507
+ padding: gap / 2,
11508
+ gap
11509
+ },
11510
+ children: Array.from({ length: numPages }, (_, i) => i + 1).map((pageNum) => {
11511
+ const thumbnail = thumbnails.get(pageNum);
11512
+ const isActive = pageNum === currentPage;
11513
+ const isVisible = pageNum >= visibleRange.start && pageNum <= visibleRange.end;
11514
+ return /* @__PURE__ */ jsxs30(
11515
+ "div",
11516
+ {
11517
+ className: cn(
11518
+ "pdf-thumbnail",
11519
+ "flex-shrink-0 cursor-pointer transition-all duration-200",
11520
+ "border-2 rounded shadow-sm hover:shadow-md",
11521
+ isActive ? "border-blue-500 ring-2 ring-blue-200 dark:ring-blue-800" : "border-gray-300 dark:border-gray-600 hover:border-blue-400"
11522
+ ),
11523
+ style: {
11524
+ width: thumbnailWidth,
11525
+ height: thumbnailHeight + (showPageNumbers ? 24 : 0)
11526
+ },
11527
+ onClick: () => handleThumbnailClick(pageNum),
11528
+ role: "button",
11529
+ tabIndex: 0,
11530
+ "aria-label": `Go to page ${pageNum}`,
11531
+ "aria-current": isActive ? "page" : void 0,
11532
+ onKeyDown: (e) => {
11533
+ if (e.key === "Enter" || e.key === " ") {
11534
+ e.preventDefault();
11535
+ handleThumbnailClick(pageNum);
11536
+ }
11537
+ },
11538
+ children: [
11539
+ /* @__PURE__ */ jsx36(
11540
+ "div",
11541
+ {
11542
+ className: "relative bg-white dark:bg-gray-700",
11543
+ style: {
11544
+ width: thumbnailWidth,
11545
+ height: thumbnailHeight
11546
+ },
11547
+ children: isVisible && thumbnail?.canvas ? /* @__PURE__ */ jsx36(
11548
+ "img",
11549
+ {
11550
+ src: thumbnail.canvas.toDataURL(),
11551
+ alt: `Page ${pageNum}`,
11552
+ className: "w-full h-full object-contain",
11553
+ loading: "lazy"
11554
+ }
11555
+ ) : /* @__PURE__ */ jsx36("div", { className: "absolute inset-0 flex items-center justify-center text-gray-400 dark:text-gray-500 text-xs", children: pageNum })
11556
+ }
11557
+ ),
11558
+ showPageNumbers && /* @__PURE__ */ jsx36(
11559
+ "div",
11560
+ {
11561
+ className: cn(
11562
+ "text-center text-xs py-1",
11563
+ "bg-gray-50 dark:bg-gray-700",
11564
+ isActive ? "text-blue-600 dark:text-blue-400 font-medium" : "text-gray-600 dark:text-gray-400"
11565
+ ),
11566
+ children: pageNum
11567
+ }
11568
+ )
11569
+ ]
11570
+ },
11571
+ pageNum
11572
+ );
11573
+ })
11574
+ }
11575
+ )
11576
+ }
11577
+ );
11578
+ });
11579
+
10695
11580
  // src/components/ErrorBoundary/PDFErrorBoundary.tsx
10696
11581
  init_utils();
10697
11582
  import { Component } from "react";
10698
- import { jsx as jsx36, jsxs as jsxs30 } from "react/jsx-runtime";
11583
+ import { jsx as jsx37, jsxs as jsxs31 } from "react/jsx-runtime";
10699
11584
  var PDFErrorBoundary = class extends Component {
10700
11585
  constructor(props) {
10701
11586
  super(props);
@@ -10723,7 +11608,7 @@ var PDFErrorBoundary = class extends Component {
10723
11608
  return fallback;
10724
11609
  }
10725
11610
  if (showDefaultUI) {
10726
- return /* @__PURE__ */ jsx36(
11611
+ return /* @__PURE__ */ jsx37(
10727
11612
  DefaultErrorUI,
10728
11613
  {
10729
11614
  error,
@@ -10742,7 +11627,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10742
11627
  const isNetworkError = error.message.includes("fetch") || error.message.includes("network") || error.message.includes("Failed to load");
10743
11628
  let title = "Something went wrong";
10744
11629
  let description = error.message;
10745
- let icon = /* @__PURE__ */ jsx36("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx36(
11630
+ let icon = /* @__PURE__ */ jsx37("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx37(
10746
11631
  "path",
10747
11632
  {
10748
11633
  strokeLinecap: "round",
@@ -10754,7 +11639,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10754
11639
  if (isPDFError) {
10755
11640
  title = "Unable to load PDF";
10756
11641
  description = "The PDF file could not be loaded. It may be corrupted or in an unsupported format.";
10757
- icon = /* @__PURE__ */ jsx36("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx36(
11642
+ icon = /* @__PURE__ */ jsx37("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx37(
10758
11643
  "path",
10759
11644
  {
10760
11645
  strokeLinecap: "round",
@@ -10766,7 +11651,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10766
11651
  } else if (isNetworkError) {
10767
11652
  title = "Network error";
10768
11653
  description = "Unable to fetch the PDF file. Please check your internet connection and try again.";
10769
- icon = /* @__PURE__ */ jsx36("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx36(
11654
+ icon = /* @__PURE__ */ jsx37("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx37(
10770
11655
  "path",
10771
11656
  {
10772
11657
  strokeLinecap: "round",
@@ -10776,7 +11661,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10776
11661
  }
10777
11662
  ) });
10778
11663
  }
10779
- return /* @__PURE__ */ jsxs30(
11664
+ return /* @__PURE__ */ jsxs31(
10780
11665
  "div",
10781
11666
  {
10782
11667
  className: cn(
@@ -10789,14 +11674,14 @@ function DefaultErrorUI({ error, onReset, className }) {
10789
11674
  ),
10790
11675
  children: [
10791
11676
  icon,
10792
- /* @__PURE__ */ jsx36("h2", { className: "mt-4 text-xl font-semibold text-gray-900 dark:text-gray-100", children: title }),
10793
- /* @__PURE__ */ jsx36("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 max-w-md", children: description }),
10794
- /* @__PURE__ */ jsxs30("details", { className: "mt-4 text-left max-w-md w-full", children: [
10795
- /* @__PURE__ */ jsx36("summary", { className: "cursor-pointer text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200", children: "Technical details" }),
10796
- /* @__PURE__ */ jsx36("pre", { className: "mt-2 p-2 bg-gray-100 dark:bg-gray-800 rounded text-xs overflow-auto", children: error.stack || error.message })
11677
+ /* @__PURE__ */ jsx37("h2", { className: "mt-4 text-xl font-semibold text-gray-900 dark:text-gray-100", children: title }),
11678
+ /* @__PURE__ */ jsx37("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 max-w-md", children: description }),
11679
+ /* @__PURE__ */ jsxs31("details", { className: "mt-4 text-left max-w-md w-full", children: [
11680
+ /* @__PURE__ */ jsx37("summary", { className: "cursor-pointer text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200", children: "Technical details" }),
11681
+ /* @__PURE__ */ jsx37("pre", { className: "mt-2 p-2 bg-gray-100 dark:bg-gray-800 rounded text-xs overflow-auto", children: error.stack || error.message })
10797
11682
  ] }),
10798
- /* @__PURE__ */ jsxs30("div", { className: "mt-6 flex gap-3", children: [
10799
- /* @__PURE__ */ jsx36(
11683
+ /* @__PURE__ */ jsxs31("div", { className: "mt-6 flex gap-3", children: [
11684
+ /* @__PURE__ */ jsx37(
10800
11685
  "button",
10801
11686
  {
10802
11687
  onClick: onReset,
@@ -10810,7 +11695,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10810
11695
  children: "Try again"
10811
11696
  }
10812
11697
  ),
10813
- /* @__PURE__ */ jsx36(
11698
+ /* @__PURE__ */ jsx37(
10814
11699
  "button",
10815
11700
  {
10816
11701
  onClick: () => window.location.reload(),
@@ -10830,7 +11715,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10830
11715
  );
10831
11716
  }
10832
11717
  function withErrorBoundary({ component, ...props }) {
10833
- return /* @__PURE__ */ jsx36(PDFErrorBoundary, { ...props, children: component });
11718
+ return /* @__PURE__ */ jsx37(PDFErrorBoundary, { ...props, children: component });
10834
11719
  }
10835
11720
 
10836
11721
  // src/index.ts
@@ -10864,6 +11749,7 @@ export {
10864
11749
  OutlinePanel,
10865
11750
  PDFErrorBoundary,
10866
11751
  PDFPage,
11752
+ PDFThumbnailNav,
10867
11753
  PDFViewer,
10868
11754
  PDFViewerClient,
10869
11755
  PDFViewerContext,
@@ -10882,9 +11768,11 @@ export {
10882
11768
  ThumbnailPanel,
10883
11769
  Toolbar,
10884
11770
  VirtualizedDocumentContainer,
11771
+ applyRotation,
10885
11772
  clearHighlights,
10886
11773
  clearStudentData,
10887
11774
  cn,
11775
+ countTextOnPage,
10888
11776
  createAgentAPI,
10889
11777
  createAgentStore,
10890
11778
  createAnnotationStore,
@@ -10893,6 +11781,7 @@ export {
10893
11781
  createSearchStore,
10894
11782
  createStudentStore,
10895
11783
  createViewerStore,
11784
+ doRectsIntersect,
10896
11785
  downloadAnnotationsAsJSON,
10897
11786
  downloadAnnotationsAsMarkdown,
10898
11787
  downloadFile,
@@ -10900,25 +11789,39 @@ export {
10900
11789
  exportAnnotationsAsMarkdown,
10901
11790
  exportHighlightsAsJSON,
10902
11791
  exportHighlightsAsMarkdown,
11792
+ extractPageText,
11793
+ findTextInDocument,
11794
+ findTextOnPage,
10903
11795
  generateDocumentId,
10904
11796
  getAllDocumentIds,
10905
11797
  getAllStudentDataDocumentIds,
10906
11798
  getMetadata,
10907
11799
  getOutline,
10908
11800
  getPage,
11801
+ getPageText,
10909
11802
  getPageTextContent,
10910
11803
  getPluginManager,
11804
+ getRectIntersection,
11805
+ getRotatedDimensions,
10911
11806
  getStorageStats,
10912
11807
  importHighlightsFromJSON,
10913
11808
  initializePDFJS,
10914
11809
  isPDFJSInitialized,
11810
+ isPointInRect,
10915
11811
  loadDocument,
10916
11812
  loadHighlights,
10917
11813
  loadStudentData,
11814
+ mergeAdjacentRects,
11815
+ pdfToPercent,
11816
+ pdfToViewport,
10918
11817
  pdfjsLib,
11818
+ percentToPDF,
11819
+ percentToViewport,
10919
11820
  quickViewer,
11821
+ removeRotation,
10920
11822
  saveHighlights,
10921
11823
  saveStudentData,
11824
+ scaleRect,
10922
11825
  useAgentContext,
10923
11826
  useAgentStore,
10924
11827
  useAnnotationStore,
@@ -10940,6 +11843,8 @@ export {
10940
11843
  useTouchGestures,
10941
11844
  useViewerStore,
10942
11845
  useZoom,
11846
+ viewportToPDF,
11847
+ viewportToPercent,
10943
11848
  withErrorBoundary
10944
11849
  };
10945
11850
  //# sourceMappingURL=index.js.map