@anaralabs/lector 3.14.1 → 3.14.3

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.
@@ -344,7 +344,8 @@ export declare class LinkService {
344
344
  externalLinkEnabled: boolean;
345
345
  isInPresentationMode: boolean;
346
346
  _currentPageNumber: number;
347
- _pageNavigationCallback?: (pageNumber: number) => void;
347
+ _pageNavigationCallbacks: Set<PageNavigationCallback>;
348
+ _dispatchPageNavigation(pageNumber: number, explicitDest?: unknown[]): void;
348
349
  get pdfDocumentProxy(): PDFDocumentProxy;
349
350
  get pagesCount(): number;
350
351
  get page(): number;
@@ -359,11 +360,11 @@ export declare class LinkService {
359
360
  navigateTo(dest: string | unknown[] | Promise<unknown[]>): void;
360
361
  get rotation(): number;
361
362
  set rotation(_value: number);
362
- goToPage(_page_valuer: number): void;
363
+ goToPage(pageNumber: number): void;
363
364
  setHash(hash: string): void;
364
365
  executeSetOCGState(): void;
365
- registerPageNavigationCallback(callback: (pageNumber: number) => void): void;
366
- unregisterPageNavigationCallback(): void;
366
+ registerPageNavigationCallback(callback: PageNavigationCallback): void;
367
+ unregisterPageNavigationCallback(callback?: PageNavigationCallback): void;
367
368
  }
368
369
 
369
370
  export declare const loadPdfJs: () => Promise<pdfjsDist>;
@@ -398,6 +399,8 @@ children: ReactNode;
398
399
  pageNumber?: number;
399
400
  }) => JSX.Element>;
400
401
 
402
+ declare type PageNavigationCallback = (pageNumber: number, explicitDest?: unknown[]) => void;
403
+
401
404
  export declare const Pages: ({ children, gap, virtualizerOptions, initialOffset, onOffsetChange, ...props }: HTMLProps<HTMLDivElement> & {
402
405
  virtualizerOptions?: {
403
406
  overscan?: number;
@@ -729,7 +732,7 @@ export declare const usePdfJump: () => {
729
732
  }) => void;
730
733
  jumpToOffset: (offset: number) => void;
731
734
  jumpToHighlightRects: (rects: HighlightRect[], type: "pixels" | "percent", align?: "start" | "center", additionalOffset?: number) => void;
732
- scrollToHighlightRects: (rects: HighlightRect[], type: "pixels" | "percent", align?: "start" | "center", additionalOffset?: number) => void;
735
+ scrollToHighlightRects: (rects: HighlightRect[], type: "pixels" | "percent", align?: "start" | "center", additionalOffset?: number, behavior?: "auto" | "smooth") => boolean;
733
736
  };
734
737
 
735
738
  export declare const usePDFLinkService: () => LinkService;
package/dist/index.js CHANGED
@@ -985,15 +985,15 @@ var usePdfJump = () => {
985
985
  [virtualizer]
986
986
  );
987
987
  const scrollToHighlightRects = useCallback(
988
- (rects, type, align = "start", additionalOffset = 0) => {
989
- if (!virtualizer) return;
988
+ (rects, type, align = "start", additionalOffset = 0, behavior = "smooth") => {
989
+ if (!virtualizer || rects.length === 0) return false;
990
990
  const firstPage = Math.min(...rects.map((rect) => rect.pageNumber));
991
991
  const pageOffset = virtualizer.getOffsetForIndex(firstPage - 1, "start");
992
- if (pageOffset === null) return;
992
+ if (pageOffset === null) return false;
993
993
  const targetRect = rects.find((rect) => rect.pageNumber === firstPage);
994
- if (!targetRect) return;
994
+ if (!targetRect) return false;
995
995
  const isNumber = pageOffset?.[0] != null;
996
- if (!isNumber) return;
996
+ if (!isNumber) return false;
997
997
  const pageStart = pageOffset[0] ?? 0;
998
998
  let rectTop;
999
999
  let rectHeight;
@@ -1020,8 +1020,9 @@ var usePdfJump = () => {
1020
1020
  virtualizer.scrollToOffset(adjustedOffset, {
1021
1021
  align: "start",
1022
1022
  // Always use start when we've calculated our own centering
1023
- behavior: "smooth"
1023
+ behavior
1024
1024
  });
1025
+ return true;
1025
1026
  },
1026
1027
  [virtualizer]
1027
1028
  );
@@ -1045,7 +1046,18 @@ var LinkService = class {
1045
1046
  externalLinkEnabled = true;
1046
1047
  isInPresentationMode = false;
1047
1048
  _currentPageNumber = 0;
1048
- _pageNavigationCallback;
1049
+ // Every mounted AnnotationLayer registers a callback here. A single slot
1050
+ // breaks under page virtualization: any layer unmounting would wipe the
1051
+ // callback registered by a still-mounted layer, silently killing internal
1052
+ // link navigation. Keep them all and dispatch to the latest registrant.
1053
+ _pageNavigationCallbacks = /* @__PURE__ */ new Set();
1054
+ _dispatchPageNavigation(pageNumber, explicitDest) {
1055
+ let latest;
1056
+ for (const callback of this._pageNavigationCallbacks) {
1057
+ latest = callback;
1058
+ }
1059
+ latest?.(pageNumber, explicitDest);
1060
+ }
1049
1061
  get pdfDocumentProxy() {
1050
1062
  if (!this._pdfDocumentProxy) {
1051
1063
  throw new Error("pdfDocumentProxy is not set");
@@ -1060,9 +1072,7 @@ var LinkService = class {
1060
1072
  }
1061
1073
  set page(value) {
1062
1074
  this._currentPageNumber = value;
1063
- if (this._pageNavigationCallback) {
1064
- this._pageNavigationCallback(value);
1065
- }
1075
+ this._dispatchPageNavigation(value);
1066
1076
  }
1067
1077
  // Required for link annotations to work
1068
1078
  setDocument(pdfDocument) {
@@ -1148,9 +1158,8 @@ var LinkService = class {
1148
1158
  return;
1149
1159
  }
1150
1160
  const pageNumber = pageIndex + 1;
1151
- if (this._pageNavigationCallback) {
1152
- this._pageNavigationCallback(pageNumber);
1153
- }
1161
+ this._currentPageNumber = pageNumber;
1162
+ this._dispatchPageNavigation(pageNumber, explicitDest);
1154
1163
  }
1155
1164
  executeNamedAction() {
1156
1165
  }
@@ -1162,7 +1171,12 @@ var LinkService = class {
1162
1171
  }
1163
1172
  set rotation(_value) {
1164
1173
  }
1165
- goToPage(_page_valuer) {
1174
+ goToPage(pageNumber) {
1175
+ if (!Number.isInteger(pageNumber) || pageNumber < 1 || this.pagesCount > 0 && pageNumber > this.pagesCount) {
1176
+ return;
1177
+ }
1178
+ this._currentPageNumber = pageNumber;
1179
+ this._dispatchPageNavigation(pageNumber);
1166
1180
  }
1167
1181
  setHash(hash) {
1168
1182
  if (hash.startsWith("#page=")) {
@@ -1174,13 +1188,20 @@ var LinkService = class {
1174
1188
  }
1175
1189
  executeSetOCGState() {
1176
1190
  }
1177
- // Method to register navigation callback
1191
+ // Method to register navigation callback. Delete-then-add so re-registering
1192
+ // an already-known callback still makes it the latest registrant.
1178
1193
  registerPageNavigationCallback(callback) {
1179
- this._pageNavigationCallback = callback;
1180
- }
1181
- // Method to unregister navigation callback
1182
- unregisterPageNavigationCallback() {
1183
- this._pageNavigationCallback = void 0;
1194
+ this._pageNavigationCallbacks.delete(callback);
1195
+ this._pageNavigationCallbacks.add(callback);
1196
+ }
1197
+ // Method to unregister navigation callback. Only removes the caller's own
1198
+ // callback so one layer's cleanup can't drop another layer's registration.
1199
+ unregisterPageNavigationCallback(callback) {
1200
+ if (!callback) {
1201
+ this._pageNavigationCallbacks.clear();
1202
+ } else {
1203
+ this._pageNavigationCallbacks.delete(callback);
1204
+ }
1184
1205
  }
1185
1206
  };
1186
1207
  var useCreatePDFLinkService = (pdfDocumentProxy) => {
@@ -1218,6 +1239,48 @@ var useVisibility = ({
1218
1239
  };
1219
1240
 
1220
1241
  // src/hooks/layers/useAnnotationLayer.tsx
1242
+ var getDestinationScrollTop = (viewport, explicitDest) => {
1243
+ if (!viewport || !explicitDest) return null;
1244
+ const destType = explicitDest[1];
1245
+ const destName = destType && typeof destType === "object" && "name" in destType ? destType.name : null;
1246
+ if (destName === "FitR") {
1247
+ const [x1, y1, x2, y2] = explicitDest.slice(2, 6);
1248
+ if (typeof x1 !== "number" || typeof y1 !== "number" || typeof x2 !== "number" || typeof y2 !== "number") {
1249
+ return null;
1250
+ }
1251
+ const [, t1] = viewport.convertToViewportPoint(x1, y1);
1252
+ const [, t2] = viewport.convertToViewportPoint(x2, y2);
1253
+ const top2 = Math.min(t1, t2);
1254
+ if (typeof top2 !== "number" || Number.isNaN(top2)) return null;
1255
+ return Math.min(Math.max(top2, 0), viewport.height);
1256
+ }
1257
+ let x = null;
1258
+ let y = null;
1259
+ switch (destName) {
1260
+ case "XYZ":
1261
+ x = explicitDest[2];
1262
+ y = explicitDest[3];
1263
+ break;
1264
+ case "FitH":
1265
+ case "FitBH":
1266
+ y = explicitDest[2];
1267
+ break;
1268
+ case "FitV":
1269
+ case "FitBV":
1270
+ x = explicitDest[2];
1271
+ break;
1272
+ default:
1273
+ return null;
1274
+ }
1275
+ const sideways = viewport.rotation % 180 !== 0;
1276
+ if (typeof (sideways ? x : y) !== "number") return null;
1277
+ const [, top] = viewport.convertToViewportPoint(
1278
+ typeof x === "number" ? x : 0,
1279
+ typeof y === "number" ? y : 0
1280
+ );
1281
+ if (typeof top !== "number" || Number.isNaN(top)) return null;
1282
+ return Math.min(Math.max(top, 0), viewport.height);
1283
+ };
1221
1284
  var defaultAnnotationLayerParams = {
1222
1285
  renderForms: true,
1223
1286
  externalLinksEnabled: true,
@@ -1244,17 +1307,37 @@ var useAnnotationLayer = (params) => {
1244
1307
  useEffect(() => {
1245
1308
  linkService.externalLinkEnabled = mergedParams.externalLinksEnabled;
1246
1309
  }, [linkService, mergedParams.externalLinksEnabled]);
1247
- const { jumpToPage } = usePdfJump();
1310
+ const { jumpToPage, scrollToHighlightRects } = usePdfJump();
1311
+ const viewports = usePdf((state) => state.viewports);
1248
1312
  useEffect(() => {
1249
1313
  if (!jumpToPage) return;
1250
- const handlePageNavigation = (pageNumber2) => {
1251
- jumpToPage(pageNumber2, mergedParams.jumpOptions);
1314
+ const handlePageNavigation = (targetPageNumber, explicitDest) => {
1315
+ const top = getDestinationScrollTop(
1316
+ viewports?.[targetPageNumber - 1],
1317
+ explicitDest
1318
+ );
1319
+ const scrolled = top !== null && scrollToHighlightRects(
1320
+ [{ pageNumber: targetPageNumber, top, left: 0, width: 0, height: 0 }],
1321
+ "pixels",
1322
+ mergedParams.jumpOptions?.align === "center" ? "center" : "start",
1323
+ 0,
1324
+ mergedParams.jumpOptions?.behavior ?? "smooth"
1325
+ );
1326
+ if (!scrolled) {
1327
+ jumpToPage(targetPageNumber, mergedParams.jumpOptions);
1328
+ }
1252
1329
  };
1253
1330
  linkService.registerPageNavigationCallback(handlePageNavigation);
1254
1331
  return () => {
1255
- linkService.unregisterPageNavigationCallback();
1332
+ linkService.unregisterPageNavigationCallback(handlePageNavigation);
1256
1333
  };
1257
- }, [jumpToPage, linkService, mergedParams.jumpOptions]);
1334
+ }, [
1335
+ jumpToPage,
1336
+ scrollToHighlightRects,
1337
+ viewports,
1338
+ linkService,
1339
+ mergedParams.jumpOptions
1340
+ ]);
1258
1341
  useEffect(() => {
1259
1342
  ensureAnnotationLayerStyles();
1260
1343
  }, []);
@@ -1267,9 +1350,11 @@ var useAnnotationLayer = (params) => {
1267
1350
  const href = target.getAttribute("href") || "";
1268
1351
  if (href.startsWith("#page=")) {
1269
1352
  e.preventDefault();
1270
- const pageNumber2 = parseInt(href.substring(6), 10);
1271
- if (!Number.isNaN(pageNumber2)) {
1272
- linkService.goToPage(pageNumber2);
1353
+ if (!target.closest("[data-internal-link]")) {
1354
+ const pageNumber2 = parseInt(href.substring(6), 10);
1355
+ if (!Number.isNaN(pageNumber2)) {
1356
+ linkService.goToPage(pageNumber2);
1357
+ }
1273
1358
  }
1274
1359
  }
1275
1360
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anaralabs/lector",
3
- "version": "3.14.1",
3
+ "version": "3.14.3",
4
4
  "description": "A headless PDF viewer for React",
5
5
  "author": "andrewdr (https://github.com/andrewdoro)",
6
6
  "license": "MIT",