@anaralabs/lector 3.3.0 → 3.5.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
@@ -1,29 +1,16 @@
1
1
  'use client';
2
2
 
3
- import { AnnotationLayer, TextLayer, getDocument } from 'pdfjs-dist';
4
- import React, { createContext, forwardRef, useContext, useCallback, cloneElement, useState, useRef, useEffect, createRef, useLayoutEffect } from 'react';
3
+ import React, { createContext, forwardRef, useCallback, useContext, useState, useRef, useEffect, cloneElement, createRef, useLayoutEffect } from 'react';
4
+ import { useFloating, autoUpdate, offset, shift, useDismiss, useInteractions, flip } from '@floating-ui/react';
5
5
  import { useVirtualizer, elementScroll, debounce } from '@tanstack/react-virtual';
6
- import { useStore, createStore } from 'zustand';
6
+ import { create, useStore, createStore } from 'zustand';
7
7
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
8
+ import { v4 } from 'uuid';
9
+ import { AnnotationLayer, TextLayer, getDocument } from 'pdfjs-dist';
8
10
  import { useDebounce } from 'use-debounce';
9
11
  import { Slot } from '@radix-ui/react-slot';
10
- import { useFloating, autoUpdate, offset, shift, useDismiss, useInteractions } from '@floating-ui/react';
11
12
 
12
- // ../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
13
- function r(e) {
14
- var t, f, n = "";
15
- if ("string" == typeof e || "number" == typeof e) n += e;
16
- else if ("object" == typeof e) if (Array.isArray(e)) {
17
- var o = e.length;
18
- for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
19
- } else for (f in e) e[f] && (n && (n += " "), n += f);
20
- return n;
21
- }
22
- function clsx() {
23
- for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
24
- return n;
25
- }
26
- var clsx_default = clsx;
13
+ // src/components/annotation-tooltip.tsx
27
14
 
28
15
  // src/lib/clamp.ts
29
16
  var clamp = (value, min, max) => {
@@ -130,12 +117,388 @@ var PDFStore = createZustandContext(
130
117
  set({
131
118
  customSelectionRects: val
132
119
  });
133
- }
120
+ },
121
+ coloredHighlights: [],
122
+ addColoredHighlight: (value) => set((prevState) => ({
123
+ coloredHighlights: [...prevState.coloredHighlights, value]
124
+ })),
125
+ deleteColoredHighlight: (uuid) => set((prevState) => ({
126
+ coloredHighlights: prevState.coloredHighlights.filter(
127
+ (rect) => rect.uuid !== uuid
128
+ )
129
+ }))
134
130
  }));
135
131
  }
136
132
  );
137
133
  var usePdf = (selector) => useStore(PDFStore.useContext(), selector);
138
134
 
135
+ // src/hooks/useAnnotationTooltip.ts
136
+ var defaultRect = {
137
+ width: 0,
138
+ height: 0,
139
+ x: 0,
140
+ y: 0,
141
+ top: 0,
142
+ right: 0,
143
+ bottom: 0,
144
+ left: 0
145
+ };
146
+ var useAnnotationTooltip = ({
147
+ annotation,
148
+ onOpenChange
149
+ }) => {
150
+ const isNewAnnotation = Date.now() - new Date(annotation.createdAt).getTime() < 1e3;
151
+ const [isOpen, setIsOpen] = useState(isNewAnnotation);
152
+ const viewportRef = usePdf((state) => state.viewportRef);
153
+ const {
154
+ refs,
155
+ floatingStyles,
156
+ context
157
+ } = useFloating({
158
+ placement: "top",
159
+ open: isOpen,
160
+ onOpenChange: (open) => {
161
+ setIsOpen(open);
162
+ onOpenChange?.(open);
163
+ },
164
+ whileElementsMounted: autoUpdate,
165
+ middleware: [
166
+ offset(10),
167
+ flip({
168
+ crossAxis: false
169
+ }),
170
+ shift({ padding: 8 })
171
+ ]
172
+ });
173
+ const dismiss = useDismiss(context);
174
+ const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);
175
+ const updateTooltipPosition = useCallback(() => {
176
+ if (!annotation.highlights.length) return;
177
+ const lastHighlight = annotation.highlights[annotation.highlights.length - 1];
178
+ refs.setReference({
179
+ getBoundingClientRect: () => {
180
+ const viewportElement = viewportRef.current;
181
+ if (!viewportElement || !lastHighlight) return defaultRect;
182
+ const pageElement = viewportElement.querySelector(`[data-page-number="${annotation.pageNumber}"]`);
183
+ if (!pageElement) return defaultRect;
184
+ const pageRect = pageElement.getBoundingClientRect();
185
+ const left = pageRect.left + lastHighlight.left;
186
+ const top = pageRect.top + lastHighlight.top;
187
+ const width = lastHighlight.width;
188
+ const height = lastHighlight.height;
189
+ return {
190
+ width,
191
+ height,
192
+ x: left,
193
+ y: top,
194
+ top,
195
+ right: left + width,
196
+ bottom: top + height,
197
+ left
198
+ };
199
+ }
200
+ });
201
+ }, [annotation.highlights, annotation.pageNumber, refs, viewportRef]);
202
+ useEffect(() => {
203
+ updateTooltipPosition();
204
+ const handleScroll = () => {
205
+ requestAnimationFrame(updateTooltipPosition);
206
+ };
207
+ const handleResize = () => {
208
+ requestAnimationFrame(updateTooltipPosition);
209
+ };
210
+ if (viewportRef.current) {
211
+ viewportRef.current.addEventListener("scroll", handleScroll, {
212
+ passive: true
213
+ });
214
+ }
215
+ window.addEventListener("resize", handleResize, { passive: true });
216
+ return () => {
217
+ if (viewportRef.current) {
218
+ viewportRef.current.removeEventListener("scroll", handleScroll);
219
+ }
220
+ window.removeEventListener("resize", handleResize);
221
+ };
222
+ }, [updateTooltipPosition, viewportRef]);
223
+ return {
224
+ isOpen,
225
+ setIsOpen,
226
+ refs,
227
+ floatingStyles,
228
+ getFloatingProps,
229
+ getReferenceProps
230
+ };
231
+ };
232
+ var AnnotationTooltip = ({
233
+ annotation,
234
+ children,
235
+ tooltipContent,
236
+ onOpenChange,
237
+ isOpen: controlledIsOpen
238
+ }) => {
239
+ const {
240
+ isOpen: uncontrolledIsOpen,
241
+ setIsOpen,
242
+ refs,
243
+ floatingStyles,
244
+ getFloatingProps,
245
+ getReferenceProps
246
+ } = useAnnotationTooltip({
247
+ annotation,
248
+ onOpenChange
249
+ });
250
+ const isOpen = controlledIsOpen ?? uncontrolledIsOpen;
251
+ const handleClick = useCallback(() => {
252
+ if (controlledIsOpen === void 0) {
253
+ setIsOpen(!isOpen);
254
+ }
255
+ }, [controlledIsOpen, isOpen, setIsOpen]);
256
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
257
+ /* @__PURE__ */ jsx(
258
+ "div",
259
+ {
260
+ ref: refs.setReference,
261
+ onClick: handleClick,
262
+ ...getReferenceProps(),
263
+ children
264
+ }
265
+ ),
266
+ isOpen && /* @__PURE__ */ jsx(
267
+ "div",
268
+ {
269
+ ref: refs.setFloating,
270
+ className: "bg-white shadow-lg rounded-lg p-3 z-50 min-w-[200px]",
271
+ style: {
272
+ ...floatingStyles,
273
+ position: "fixed",
274
+ zIndex: 9999
275
+ },
276
+ ...getFloatingProps(),
277
+ children: tooltipContent
278
+ }
279
+ )
280
+ ] });
281
+ };
282
+ var useAnnotations = create((set) => ({
283
+ annotations: [],
284
+ addAnnotation: (annotation) => set((state) => ({
285
+ annotations: [
286
+ ...state.annotations,
287
+ {
288
+ ...annotation,
289
+ id: v4(),
290
+ createdAt: /* @__PURE__ */ new Date(),
291
+ updatedAt: /* @__PURE__ */ new Date()
292
+ }
293
+ ]
294
+ })),
295
+ updateAnnotation: (id, updates) => set((state) => ({
296
+ annotations: state.annotations.map(
297
+ (annotation) => annotation.id === id ? {
298
+ ...annotation,
299
+ ...updates,
300
+ updatedAt: /* @__PURE__ */ new Date()
301
+ } : annotation
302
+ )
303
+ })),
304
+ deleteAnnotation: (id) => set((state) => ({
305
+ annotations: state.annotations.filter((annotation) => annotation.id !== id)
306
+ })),
307
+ setAnnotations: (annotations) => set({ annotations })
308
+ }));
309
+ var PDFPageNumberContext = createContext(0);
310
+ var usePDFPageNumber = () => {
311
+ return useContext(PDFPageNumberContext);
312
+ };
313
+ var useAnnotationActions = ({
314
+ annotation,
315
+ onClose
316
+ }) => {
317
+ const { updateAnnotation } = useAnnotations();
318
+ const [comment, setComment] = useState(annotation.comment || "");
319
+ const [isEditing, setIsEditing] = useState(false);
320
+ const handleSaveComment = useCallback(() => {
321
+ updateAnnotation(annotation.id, { comment });
322
+ setIsEditing(false);
323
+ onClose?.();
324
+ }, [annotation.id, comment, updateAnnotation, onClose]);
325
+ const handleColorChange = useCallback(
326
+ (color) => {
327
+ updateAnnotation(annotation.id, { color });
328
+ onClose?.();
329
+ },
330
+ [annotation.id, updateAnnotation, onClose]
331
+ );
332
+ const handleCancelEdit = useCallback(() => {
333
+ setIsEditing(false);
334
+ onClose?.();
335
+ }, [onClose]);
336
+ const colors = [
337
+ "rgba(255, 255, 0, 0.3)",
338
+ // Yellow
339
+ "rgba(0, 255, 0, 0.3)",
340
+ // Green
341
+ "rgba(255, 182, 193, 0.3)",
342
+ // Pink
343
+ "rgba(135, 206, 235, 0.3)"
344
+ // Sky Blue
345
+ ];
346
+ return {
347
+ comment,
348
+ isEditing,
349
+ setComment,
350
+ setIsEditing,
351
+ handleSaveComment,
352
+ handleColorChange,
353
+ handleCancelEdit,
354
+ colors
355
+ };
356
+ };
357
+ var DefaultAnnotationTooltipContent = ({
358
+ annotation,
359
+ onClose
360
+ }) => {
361
+ const {
362
+ comment,
363
+ isEditing,
364
+ setComment,
365
+ setIsEditing,
366
+ handleSaveComment,
367
+ handleColorChange,
368
+ handleCancelEdit,
369
+ colors
370
+ } = useAnnotationActions({
371
+ annotation,
372
+ onClose
373
+ });
374
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
375
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: colors.map((color) => /* @__PURE__ */ jsx(
376
+ "button",
377
+ {
378
+ className: "w-6 h-6 rounded",
379
+ style: { backgroundColor: color },
380
+ onClick: () => handleColorChange(color)
381
+ },
382
+ color
383
+ )) }),
384
+ isEditing ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
385
+ /* @__PURE__ */ jsx(
386
+ "textarea",
387
+ {
388
+ value: comment,
389
+ onChange: (e) => setComment(e.target.value),
390
+ className: "border rounded p-2 text-sm",
391
+ placeholder: "Add a comment...",
392
+ rows: 3
393
+ }
394
+ ),
395
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
396
+ /* @__PURE__ */ jsx(
397
+ "button",
398
+ {
399
+ onClick: handleCancelEdit,
400
+ className: "px-2 py-1 text-sm text-gray-600 hover:text-gray-800",
401
+ children: "Cancel"
402
+ }
403
+ ),
404
+ /* @__PURE__ */ jsx(
405
+ "button",
406
+ {
407
+ onClick: handleSaveComment,
408
+ className: "px-2 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600",
409
+ children: "Save"
410
+ }
411
+ )
412
+ ] })
413
+ ] }) : /* @__PURE__ */ jsx("div", { children: annotation.comment ? /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-700", children: annotation.comment }) : /* @__PURE__ */ jsx(
414
+ "button",
415
+ {
416
+ onClick: () => setIsEditing(true),
417
+ className: "text-sm text-blue-500 hover:text-blue-600",
418
+ children: "Add comment"
419
+ }
420
+ ) })
421
+ ] });
422
+ };
423
+ var AnnotationHighlightLayer = ({
424
+ className,
425
+ style,
426
+ renderTooltipContent,
427
+ focusedAnnotationId,
428
+ onAnnotationClick
429
+ }) => {
430
+ const { annotations } = useAnnotations();
431
+ const pageNumber = usePDFPageNumber();
432
+ const pageAnnotations = annotations.filter(
433
+ (annotation) => annotation.pageNumber === pageNumber
434
+ );
435
+ return /* @__PURE__ */ jsx("div", { className, style, children: pageAnnotations.map((annotation) => /* @__PURE__ */ jsx(
436
+ AnnotationTooltip,
437
+ {
438
+ annotation,
439
+ isOpen: focusedAnnotationId === annotation.id,
440
+ onOpenChange: (open) => {
441
+ if (open && onAnnotationClick) {
442
+ onAnnotationClick(annotation);
443
+ }
444
+ },
445
+ tooltipContent: renderTooltipContent ? renderTooltipContent({
446
+ annotation,
447
+ onClose: () => {
448
+ }
449
+ }) : /* @__PURE__ */ jsx(
450
+ DefaultAnnotationTooltipContent,
451
+ {
452
+ annotation,
453
+ onClose: () => {
454
+ }
455
+ }
456
+ ),
457
+ children: /* @__PURE__ */ jsx(
458
+ "div",
459
+ {
460
+ style: { cursor: "pointer" },
461
+ onClick: () => onAnnotationClick?.(annotation),
462
+ children: annotation.highlights.map((highlight, index) => /* @__PURE__ */ jsx(
463
+ "div",
464
+ {
465
+ style: {
466
+ position: "absolute",
467
+ top: highlight.top,
468
+ left: highlight.left,
469
+ width: highlight.width,
470
+ height: highlight.height,
471
+ backgroundColor: annotation.color || "rgba(255, 255, 0, 0.3)",
472
+ transition: "background-color 0.2s ease",
473
+ cursor: "pointer",
474
+ opacity: focusedAnnotationId === annotation.id ? 1 : 0.5
475
+ }
476
+ },
477
+ index
478
+ ))
479
+ }
480
+ )
481
+ },
482
+ annotation.id
483
+ )) });
484
+ };
485
+
486
+ // ../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
487
+ function r(e) {
488
+ var t, f, n = "";
489
+ if ("string" == typeof e || "number" == typeof e) n += e;
490
+ else if ("object" == typeof e) if (Array.isArray(e)) {
491
+ var o = e.length;
492
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
493
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
494
+ return n;
495
+ }
496
+ function clsx() {
497
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
498
+ return n;
499
+ }
500
+ var clsx_default = clsx;
501
+
139
502
  // src/lib/cancellable.ts
140
503
  var cancellable = (promise) => {
141
504
  let isCancelled = false;
@@ -160,10 +523,74 @@ var cancellable = (promise) => {
160
523
  }
161
524
  };
162
525
  };
526
+
527
+ // src/hooks/pages/usePdfJump.tsx
528
+ var usePdfJump = () => {
529
+ const virtualizer = usePdf((state) => state.virtualizer);
530
+ const setHighlight = usePdf((state) => state.setHighlight);
531
+ const jumpToPage = (pageIndex, options) => {
532
+ if (!virtualizer) return;
533
+ const defaultOptions = {
534
+ align: "start",
535
+ behavior: "smooth"
536
+ };
537
+ const finalOptions = { ...defaultOptions, ...options };
538
+ virtualizer.scrollToIndex(pageIndex - 1, finalOptions);
539
+ };
540
+ const jumpToOffset = (offset3) => {
541
+ if (!virtualizer) return;
542
+ virtualizer.scrollToOffset(offset3, {
543
+ align: "start",
544
+ behavior: "smooth"
545
+ });
546
+ };
547
+ const jumpToHighlightRects = (rects, type, align = "start", additionalOffset = 0) => {
548
+ if (!virtualizer) return;
549
+ setHighlight(rects);
550
+ const firstPage = Math.min(...rects.map((rect) => rect.pageNumber));
551
+ const pageOffset = virtualizer.getOffsetForIndex(firstPage - 1, "start");
552
+ if (pageOffset === null) return;
553
+ const targetRect = rects.find((rect) => rect.pageNumber === firstPage);
554
+ if (!targetRect) return;
555
+ const isNumber = pageOffset?.[0] != null;
556
+ if (!isNumber) return;
557
+ const pageStart = pageOffset[0] ?? 0;
558
+ let rectTop;
559
+ let rectHeight;
560
+ if (type === "percent") {
561
+ const estimatePageHeight = virtualizer.options.estimateSize(
562
+ firstPage - 1
563
+ );
564
+ rectTop = targetRect.top / 100 * estimatePageHeight;
565
+ rectHeight = targetRect.height / 100 * estimatePageHeight;
566
+ } else {
567
+ rectTop = targetRect.top;
568
+ rectHeight = targetRect.height;
569
+ }
570
+ let scrollOffset;
571
+ if (align === "center") {
572
+ const viewportHeight = virtualizer.scrollElement?.clientHeight || 0;
573
+ const rectCenter = pageStart + rectTop + rectHeight / 2;
574
+ scrollOffset = rectCenter - viewportHeight / 2;
575
+ } else {
576
+ scrollOffset = pageStart + rectTop;
577
+ }
578
+ scrollOffset += additionalOffset;
579
+ const adjustedOffset = Math.max(0, scrollOffset);
580
+ virtualizer.scrollToOffset(adjustedOffset, {
581
+ align: "start",
582
+ // Always use start when we've calculated our own centering
583
+ behavior: "smooth"
584
+ });
585
+ };
586
+ return { jumpToPage, jumpToOffset, jumpToHighlightRects };
587
+ };
163
588
  var LinkService = class {
164
589
  _pdfDocumentProxy;
165
590
  externalLinkEnabled = true;
166
591
  isInPresentationMode = false;
592
+ _currentPageNumber = 0;
593
+ _pageNavigationCallback;
167
594
  get pdfDocumentProxy() {
168
595
  if (!this._pdfDocumentProxy) {
169
596
  throw new Error("pdfDocumentProxy is not set");
@@ -173,17 +600,66 @@ var LinkService = class {
173
600
  constructor() {
174
601
  }
175
602
  get pagesCount() {
176
- return this.pdfDocumentProxy.numPages;
603
+ return this._pdfDocumentProxy?.numPages || 0;
604
+ }
605
+ get page() {
606
+ return this._currentPageNumber;
607
+ }
608
+ set page(value) {
609
+ this._currentPageNumber = value;
610
+ if (this._pageNavigationCallback) {
611
+ this._pageNavigationCallback(value);
612
+ }
613
+ }
614
+ // Required for link annotations to work
615
+ setDocument(pdfDocument) {
616
+ this._pdfDocumentProxy = pdfDocument;
617
+ }
618
+ setViewer() {
177
619
  }
178
620
  getDestinationHash(dest) {
179
- return `#dest-${dest[0].num}`;
621
+ if (!dest) return "";
622
+ const destRef = dest[0];
623
+ if (dest.length > 1 && typeof dest[1] === "object" && dest[1] !== null && "url" in dest[1]) {
624
+ const urlDest = dest[1];
625
+ return urlDest.url;
626
+ }
627
+ if (destRef && typeof destRef === "object") {
628
+ if ("num" in destRef) {
629
+ const numRef = destRef;
630
+ return `#page=${numRef.num + 1}`;
631
+ }
632
+ if ("gen" in destRef) {
633
+ const genRef = destRef;
634
+ const refNum = genRef.num ?? 0;
635
+ return `#page=${refNum + 1}`;
636
+ }
637
+ }
638
+ if (typeof destRef === "number") {
639
+ return `#page=${destRef + 1}`;
640
+ }
641
+ return `#dest-${String(dest)}`;
180
642
  }
181
- getAnchorUrl() {
182
- return "#";
643
+ getAnchorUrl(hash) {
644
+ if (hash.startsWith("http://") || hash.startsWith("https://")) {
645
+ return hash;
646
+ }
647
+ return `#${hash}`;
183
648
  }
184
649
  addLinkAttributes(link, url, newWindow) {
185
- link.href = url;
186
- link.target = newWindow === false ? "" : "_blank";
650
+ if (!link) return;
651
+ const isExternalLink = url.startsWith("http://") || url.startsWith("https://");
652
+ if (isExternalLink && this.externalLinkEnabled) {
653
+ link.href = url;
654
+ link.target = newWindow === false ? "" : "_blank";
655
+ link.rel = "noopener noreferrer";
656
+ } else if (!isExternalLink) {
657
+ link.href = url;
658
+ link.target = "";
659
+ } else {
660
+ link.href = "#";
661
+ link.target = "";
662
+ }
187
663
  }
188
664
  async goToDestination(dest) {
189
665
  let explicitDest;
@@ -197,18 +673,73 @@ var LinkService = class {
197
673
  if (!explicitDest) {
198
674
  return;
199
675
  }
200
- const explicitRef = explicitDest[0];
201
- await this.pdfDocumentProxy.getPageIndex(explicitRef);
676
+ if (explicitDest.length > 1 && typeof explicitDest[1] === "object" && explicitDest[1] !== null && "url" in explicitDest[1]) {
677
+ return;
678
+ }
679
+ const destRef = explicitDest[0];
680
+ let pageIndex;
681
+ if (destRef && typeof destRef === "object") {
682
+ if ("num" in destRef) {
683
+ try {
684
+ const refProxy = destRef;
685
+ pageIndex = await this.pdfDocumentProxy.getPageIndex(refProxy);
686
+ } catch (_error) {
687
+ return;
688
+ }
689
+ } else {
690
+ return;
691
+ }
692
+ } else if (typeof destRef === "number") {
693
+ pageIndex = destRef;
694
+ } else {
695
+ return;
696
+ }
697
+ const pageNumber = pageIndex + 1;
698
+ if (this._pageNavigationCallback) {
699
+ this._pageNavigationCallback(pageNumber);
700
+ }
701
+ }
702
+ executeNamedAction() {
703
+ }
704
+ navigateTo(dest) {
705
+ this.goToDestination(dest);
706
+ }
707
+ get rotation() {
708
+ return 0;
709
+ }
710
+ set rotation(value) {
711
+ }
712
+ goToPage(pageNumber) {
713
+ if (pageNumber >= 1 && pageNumber <= this.pagesCount) {
714
+ if (this._pageNavigationCallback) {
715
+ this._pageNavigationCallback(pageNumber);
716
+ }
717
+ }
718
+ }
719
+ setHash(hash) {
720
+ if (hash.startsWith("#page=")) {
721
+ const pageNumber = parseInt(hash.substring(6), 10);
722
+ if (!isNaN(pageNumber)) {
723
+ this.goToPage(pageNumber);
724
+ }
725
+ }
726
+ }
727
+ executeSetOCGState() {
728
+ }
729
+ // Method to register navigation callback
730
+ registerPageNavigationCallback(callback) {
731
+ this._pageNavigationCallback = callback;
732
+ }
733
+ // Method to unregister navigation callback
734
+ unregisterPageNavigationCallback() {
735
+ this._pageNavigationCallback = void 0;
202
736
  }
203
737
  };
204
- var PDFLinkServiceContext = createContext(null);
738
+ var defaultLinkService = new LinkService();
739
+ var PDFLinkServiceContext = createContext(defaultLinkService);
205
740
  var usePDFLinkService = () => {
206
741
  return useContext(PDFLinkServiceContext);
207
742
  };
208
- var PDFPageNumberContext = createContext(0);
209
- var usePDFPageNumber = () => {
210
- return useContext(PDFPageNumberContext);
211
- };
212
743
  var useVisibility = ({
213
744
  elementRef
214
745
  }) => {
@@ -230,9 +761,12 @@ var useVisibility = ({
230
761
 
231
762
  // src/hooks/layers/useAnnotationLayer.tsx
232
763
  var defaultAnnotationLayerParams = {
233
- renderForms: true
764
+ renderForms: true,
765
+ externalLinksEnabled: true,
766
+ jumpOptions: { behavior: "smooth", align: "start" }
234
767
  };
235
768
  var useAnnotationLayer = (params) => {
769
+ const mergedParams = { ...defaultAnnotationLayerParams, ...params };
236
770
  const annotationLayerRef = useRef(null);
237
771
  const annotationLayerObjectRef = useRef(null);
238
772
  const linkService = usePDFLinkService();
@@ -241,6 +775,83 @@ var useAnnotationLayer = (params) => {
241
775
  });
242
776
  const pageNumber = usePDFPageNumber();
243
777
  const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
778
+ const pdfDocumentProxy = usePdf((state) => state.pdfDocumentProxy);
779
+ useEffect(() => {
780
+ linkService.externalLinkEnabled = mergedParams.externalLinksEnabled;
781
+ }, [linkService, mergedParams.externalLinksEnabled]);
782
+ const { jumpToPage } = usePdfJump();
783
+ useEffect(() => {
784
+ if (!jumpToPage) return;
785
+ const handlePageNavigation = (pageNumber2) => {
786
+ jumpToPage(pageNumber2, mergedParams.jumpOptions);
787
+ };
788
+ linkService.registerPageNavigationCallback(handlePageNavigation);
789
+ return () => {
790
+ linkService.unregisterPageNavigationCallback();
791
+ };
792
+ }, [jumpToPage, linkService, mergedParams.jumpOptions]);
793
+ useEffect(() => {
794
+ const style = document.createElement("style");
795
+ style.textContent = `
796
+ .annotationLayer {
797
+ position: absolute;
798
+ left: 0;
799
+ top: 0;
800
+ right: 0;
801
+ bottom: 0;
802
+ overflow: hidden;
803
+ opacity: 1;
804
+ z-index: 3;
805
+ }
806
+
807
+ .annotationLayer section {
808
+ position: absolute;
809
+ }
810
+
811
+ .annotationLayer .linkAnnotation > a,
812
+ .annotationLayer .buttonWidgetAnnotation.pushButton > a {
813
+ position: absolute;
814
+ font-size: 1em;
815
+ top: 0;
816
+ left: 0;
817
+ width: 100%;
818
+ height: 100%;
819
+ background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0 repeat;
820
+ cursor: pointer;
821
+ }
822
+
823
+ .annotationLayer .linkAnnotation > a:hover,
824
+ .annotationLayer .buttonWidgetAnnotation.pushButton > a:hover {
825
+ opacity: 0.2;
826
+ background: rgba(255, 255, 0, 1);
827
+ box-shadow: 0 2px 10px rgba(255, 255, 0, 1);
828
+ }
829
+ `;
830
+ document.head.appendChild(style);
831
+ return () => {
832
+ document.head.removeChild(style);
833
+ };
834
+ }, []);
835
+ useEffect(() => {
836
+ if (!annotationLayerRef.current) return;
837
+ const element = annotationLayerRef.current;
838
+ const handleLinkClick = (e) => {
839
+ if (!e.target || !(e.target instanceof HTMLAnchorElement)) return;
840
+ const target = e.target;
841
+ const href = target.getAttribute("href") || "";
842
+ if (href.startsWith("#page=")) {
843
+ e.preventDefault();
844
+ const pageNumber2 = parseInt(href.substring(6), 10);
845
+ if (!isNaN(pageNumber2)) {
846
+ linkService.goToPage(pageNumber2);
847
+ }
848
+ }
849
+ };
850
+ element.addEventListener("click", handleLinkClick);
851
+ return () => {
852
+ element.removeEventListener("click", handleLinkClick);
853
+ };
854
+ }, [linkService]);
244
855
  useEffect(() => {
245
856
  if (!annotationLayerRef.current) {
246
857
  return;
@@ -252,14 +863,20 @@ var useAnnotationLayer = (params) => {
252
863
  }
253
864
  }, [visible]);
254
865
  useEffect(() => {
255
- if (!annotationLayerRef.current) {
866
+ if (!annotationLayerRef.current || !pdfPageProxy || !pdfDocumentProxy) {
256
867
  return;
257
868
  }
869
+ if (linkService._pdfDocumentProxy !== pdfDocumentProxy) {
870
+ linkService.setDocument(pdfDocumentProxy);
871
+ }
258
872
  annotationLayerRef.current.innerHTML = "";
873
+ annotationLayerRef.current.className = "annotationLayer";
874
+ const viewport = pdfPageProxy.getViewport({ scale: 1 });
259
875
  const annotationLayerConfig = {
260
876
  div: annotationLayerRef.current,
261
- viewport: pdfPageProxy.getViewport({ scale: 1 }),
877
+ viewport,
262
878
  page: pdfPageProxy,
879
+ linkService,
263
880
  accessibilityManager: void 0,
264
881
  annotationCanvasMap: void 0,
265
882
  annotationEditorUIManager: void 0,
@@ -269,33 +886,38 @@ var useAnnotationLayer = (params) => {
269
886
  annotationLayerObjectRef.current = annotationLayer;
270
887
  const { cancel } = cancellable(
271
888
  (async () => {
272
- await annotationLayer.render({
273
- ...annotationLayerConfig,
274
- ...defaultAnnotationLayerParams,
275
- ...params,
276
- annotations: await pdfPageProxy.getAnnotations(),
277
- // @ts-expect-error TODO: Fix this
278
- linkService
279
- });
889
+ try {
890
+ const annotations = await pdfPageProxy.getAnnotations();
891
+ await annotationLayer.render({
892
+ ...annotationLayerConfig,
893
+ ...mergedParams,
894
+ annotations,
895
+ linkService
896
+ });
897
+ } catch (_error) {
898
+ }
280
899
  })()
281
900
  );
282
- cancel();
283
901
  return () => {
284
902
  cancel();
285
903
  };
286
- }, [pdfPageProxy, params, linkService]);
904
+ }, [pdfPageProxy, pdfDocumentProxy, mergedParams, linkService]);
287
905
  return {
288
906
  annotationLayerRef
289
907
  };
290
908
  };
291
909
  var AnnotationLayer2 = ({
292
910
  renderForms = true,
911
+ externalLinksEnabled = true,
912
+ jumpOptions = { behavior: "smooth", align: "start" },
293
913
  className,
294
914
  style,
295
915
  ...props
296
916
  }) => {
297
917
  const { annotationLayerRef } = useAnnotationLayer({
298
- renderForms
918
+ renderForms,
919
+ externalLinksEnabled,
920
+ jumpOptions
299
921
  });
300
922
  return /* @__PURE__ */ jsx(
301
923
  "div",
@@ -374,27 +996,442 @@ var useCanvasLayer = ({ background }) => {
374
996
  canvasRef
375
997
  };
376
998
  };
377
- var CanvasLayer = ({
378
- style,
379
- background,
380
- ...props
999
+ var CanvasLayer = ({
1000
+ style,
1001
+ background,
1002
+ ...props
1003
+ }) => {
1004
+ const { canvasRef } = useCanvasLayer({ background });
1005
+ return /* @__PURE__ */ jsx(
1006
+ "canvas",
1007
+ {
1008
+ style: {
1009
+ ...style,
1010
+ position: "absolute",
1011
+ top: 0,
1012
+ left: 0,
1013
+ width: "100%",
1014
+ height: "100%"
1015
+ },
1016
+ ...props,
1017
+ ref: canvasRef
1018
+ }
1019
+ );
1020
+ };
1021
+
1022
+ // src/hooks/useSelectionDimensions.tsx
1023
+ var MERGE_THRESHOLD = 20;
1024
+ var shouldMergeRectangles = (rect1, rect2) => {
1025
+ const verticalOverlap = !(rect1.top > rect2.top + rect2.height || rect2.top > rect1.top + rect1.height);
1026
+ const horizontallyClose = Math.abs(rect1.left + rect1.width - rect2.left) < MERGE_THRESHOLD || Math.abs(rect2.left + rect2.width - rect1.left) < MERGE_THRESHOLD || rect1.left < rect2.left + rect2.width && rect2.left < rect1.left + rect1.width;
1027
+ return verticalOverlap && horizontallyClose;
1028
+ };
1029
+ var mergeRectangles = (rect1, rect2) => {
1030
+ return {
1031
+ left: Math.min(rect1.left, rect2.left),
1032
+ top: Math.min(rect1.top, rect2.top),
1033
+ width: Math.max(rect1.left + rect1.width, rect2.left + rect2.width) - Math.min(rect1.left, rect2.left),
1034
+ height: Math.max(rect1.top + rect1.height, rect2.top + rect2.height) - Math.min(rect1.top, rect2.top),
1035
+ pageNumber: rect1.pageNumber
1036
+ };
1037
+ };
1038
+ var consolidateRects = (rects) => {
1039
+ if (rects.length <= 1) return rects;
1040
+ const sortedRectangles = rects.toSorted((a, b) => a.top - b.top);
1041
+ let withinThresholdMergedRectangles = sortedRectangles[0];
1042
+ const allMergedRectangles = [];
1043
+ const blowUpMergedRectangles = (rect) => {
1044
+ return {
1045
+ ...rect,
1046
+ left: rect.left - 2,
1047
+ top: rect.top - 2,
1048
+ width: rect.width + 4,
1049
+ height: rect.height + 2
1050
+ };
1051
+ };
1052
+ for (const [index, currentRectangle] of sortedRectangles.entries()) {
1053
+ withinThresholdMergedRectangles = mergeRectangles(
1054
+ withinThresholdMergedRectangles,
1055
+ currentRectangle
1056
+ );
1057
+ const nextRectangle = sortedRectangles[index + 1];
1058
+ if (!nextRectangle) {
1059
+ allMergedRectangles.push(
1060
+ blowUpMergedRectangles(withinThresholdMergedRectangles)
1061
+ );
1062
+ break;
1063
+ }
1064
+ if (!shouldMergeRectangles(withinThresholdMergedRectangles, nextRectangle)) {
1065
+ allMergedRectangles.push(
1066
+ blowUpMergedRectangles(withinThresholdMergedRectangles)
1067
+ );
1068
+ withinThresholdMergedRectangles = nextRectangle;
1069
+ }
1070
+ }
1071
+ return allMergedRectangles;
1072
+ };
1073
+ var useSelectionDimensions = () => {
1074
+ const store = PDFStore.useContext();
1075
+ const getDimension = () => {
1076
+ const selection = window.getSelection();
1077
+ if (!selection || selection.isCollapsed) return;
1078
+ const range = selection.getRangeAt(0);
1079
+ const highlights = [];
1080
+ const textLayerMap = /* @__PURE__ */ new Map();
1081
+ const clientRects = Array.from(range.getClientRects()).filter(
1082
+ (rect) => rect.width > 2 && rect.height > 2
1083
+ );
1084
+ clientRects.forEach((clientRect) => {
1085
+ const element = document.elementFromPoint(
1086
+ clientRect.left + 1,
1087
+ clientRect.top + clientRect.height / 2
1088
+ );
1089
+ const textLayer = element?.closest(".textLayer");
1090
+ if (!textLayer) return;
1091
+ const pageNumber = parseInt(
1092
+ textLayer.getAttribute("data-page-number") || "1",
1093
+ 10
1094
+ );
1095
+ const textLayerRect = textLayer.getBoundingClientRect();
1096
+ const zoom = store.getState().zoom;
1097
+ const rect = {
1098
+ width: clientRect.width / zoom,
1099
+ height: clientRect.height / zoom,
1100
+ top: (clientRect.top - textLayerRect.top) / zoom,
1101
+ left: (clientRect.left - textLayerRect.left) / zoom,
1102
+ pageNumber
1103
+ };
1104
+ if (!textLayerMap.has(pageNumber)) {
1105
+ textLayerMap.set(pageNumber, []);
1106
+ }
1107
+ textLayerMap.get(pageNumber)?.push(rect);
1108
+ });
1109
+ textLayerMap.forEach((rects) => {
1110
+ if (rects.length > 0) {
1111
+ highlights.push(...consolidateRects(rects));
1112
+ }
1113
+ });
1114
+ return {
1115
+ highlights: highlights.sort((a, b) => a.pageNumber - b.pageNumber),
1116
+ text: range.toString().trim(),
1117
+ isCollapsed: false
1118
+ };
1119
+ };
1120
+ const getSelection = () => getDimension();
1121
+ return { getDimension, getSelection };
1122
+ };
1123
+ var SelectionTooltip = ({ children }) => {
1124
+ const [isOpen, setIsOpen] = useState(false);
1125
+ const lastSelectionRef = useRef(null);
1126
+ const viewportRef = usePdf((state) => state.viewportRef);
1127
+ const { refs, floatingStyles, context } = useFloating({
1128
+ placement: "bottom",
1129
+ open: isOpen,
1130
+ onOpenChange: setIsOpen,
1131
+ whileElementsMounted: autoUpdate,
1132
+ middleware: [offset(10), shift({ padding: 8 })]
1133
+ });
1134
+ const dismiss = useDismiss(context);
1135
+ const { getFloatingProps } = useInteractions([dismiss]);
1136
+ const updateTooltipPosition = useCallback(() => {
1137
+ const selection = document.getSelection();
1138
+ if (!selection || selection.isCollapsed) {
1139
+ setIsOpen(false);
1140
+ lastSelectionRef.current = null;
1141
+ return;
1142
+ }
1143
+ const range = selection.getRangeAt(0);
1144
+ if (!range) return;
1145
+ const rects = range.getClientRects();
1146
+ const lastRect = rects[rects.length - 1];
1147
+ lastSelectionRef.current = range;
1148
+ if (lastRect) {
1149
+ refs.setReference({
1150
+ getBoundingClientRect: () => ({
1151
+ width: lastRect.width,
1152
+ height: lastRect.height,
1153
+ x: lastRect.left,
1154
+ y: lastRect.bottom,
1155
+ // Position below the last line of selection
1156
+ top: lastRect.bottom,
1157
+ right: lastRect.right,
1158
+ bottom: lastRect.bottom + lastRect.height,
1159
+ left: lastRect.left
1160
+ }),
1161
+ getClientRects: () => [lastRect]
1162
+ });
1163
+ setIsOpen(true);
1164
+ } else {
1165
+ setIsOpen(false);
1166
+ }
1167
+ }, [refs]);
1168
+ useEffect(() => {
1169
+ const handleSelectionChange = () => {
1170
+ const selection = document.getSelection();
1171
+ if (selection && viewportRef.current?.contains(selection.anchorNode)) {
1172
+ requestAnimationFrame(updateTooltipPosition);
1173
+ } else {
1174
+ setIsOpen(false);
1175
+ }
1176
+ };
1177
+ const handleScroll = () => {
1178
+ if (!isOpen || !lastSelectionRef.current) return;
1179
+ requestAnimationFrame(updateTooltipPosition);
1180
+ };
1181
+ document.addEventListener("selectionchange", handleSelectionChange);
1182
+ if (viewportRef.current) {
1183
+ viewportRef.current.addEventListener("scroll", handleScroll, {
1184
+ passive: true
1185
+ });
1186
+ }
1187
+ return () => {
1188
+ document.removeEventListener("selectionchange", handleSelectionChange);
1189
+ if (viewportRef.current) {
1190
+ viewportRef.current.removeEventListener("scroll", handleScroll);
1191
+ }
1192
+ };
1193
+ }, [refs, isOpen, viewportRef, updateTooltipPosition]);
1194
+ useEffect(() => {
1195
+ const handleFloatingClick = (e) => {
1196
+ if (refs.floating.current?.contains(e.target)) {
1197
+ e.stopPropagation();
1198
+ }
1199
+ };
1200
+ document.addEventListener("click", handleFloatingClick);
1201
+ return () => document.removeEventListener("click", handleFloatingClick);
1202
+ }, [refs.floating]);
1203
+ return /* @__PURE__ */ jsx(Fragment, { children: isOpen && /* @__PURE__ */ jsx(
1204
+ "div",
1205
+ {
1206
+ ref: refs.setFloating,
1207
+ style: {
1208
+ ...floatingStyles
1209
+ },
1210
+ ...getFloatingProps(),
1211
+ children
1212
+ }
1213
+ ) });
1214
+ };
1215
+ var defaultColors = [
1216
+ {
1217
+ color: "#e3b127",
1218
+ localization: {
1219
+ id: "yellow",
1220
+ defaultMessage: "Yellow"
1221
+ }
1222
+ },
1223
+ {
1224
+ color: "#419931",
1225
+ localization: {
1226
+ id: "green",
1227
+ defaultMessage: "Green"
1228
+ }
1229
+ },
1230
+ {
1231
+ color: "#4286c9",
1232
+ localization: {
1233
+ id: "blue",
1234
+ defaultMessage: "Blue"
1235
+ }
1236
+ },
1237
+ {
1238
+ color: "#f246b6",
1239
+ localization: {
1240
+ id: "pink",
1241
+ defaultMessage: "Pink"
1242
+ }
1243
+ },
1244
+ {
1245
+ color: "#a53dd1",
1246
+ localization: {
1247
+ id: "purple",
1248
+ defaultMessage: "Purple"
1249
+ }
1250
+ },
1251
+ {
1252
+ color: "#f09037",
1253
+ localization: {
1254
+ id: "orange",
1255
+ defaultMessage: "Orange"
1256
+ }
1257
+ },
1258
+ {
1259
+ color: "#37f0d4",
1260
+ localization: {
1261
+ id: "teal",
1262
+ defaultMessage: "Teal"
1263
+ }
1264
+ },
1265
+ {
1266
+ color: "#3d0ff5",
1267
+ localization: {
1268
+ id: "purple",
1269
+ defaultMessage: "Purple"
1270
+ }
1271
+ },
1272
+ {
1273
+ color: "#f50f26",
1274
+ localization: {
1275
+ id: "red",
1276
+ defaultMessage: "Red"
1277
+ }
1278
+ }
1279
+ ];
1280
+ var ColorSelectionTool = ({
1281
+ highlighterColors = defaultColors,
1282
+ onColorSelection
1283
+ }) => {
1284
+ return /* @__PURE__ */ jsx(SelectionTooltip, { children: /* @__PURE__ */ jsx(
1285
+ "div",
1286
+ {
1287
+ style: {
1288
+ display: "flex",
1289
+ gap: "0.5rem",
1290
+ padding: "0.5rem",
1291
+ backgroundColor: "#363636",
1292
+ borderRadius: "0.5rem"
1293
+ },
1294
+ children: highlighterColors.map((colorItem, index) => /* @__PURE__ */ jsx(
1295
+ "button",
1296
+ {
1297
+ onClick: () => onColorSelection(colorItem),
1298
+ title: colorItem.localization.defaultMessage,
1299
+ "aria-label": colorItem.localization.defaultMessage,
1300
+ style: {
1301
+ width: "1.25rem",
1302
+ height: "1.25rem",
1303
+ borderRadius: "0.25rem",
1304
+ cursor: "pointer",
1305
+ backgroundColor: colorItem.color
1306
+ }
1307
+ },
1308
+ index
1309
+ ))
1310
+ }
1311
+ ) });
1312
+ };
1313
+
1314
+ // src/utils/selectionUtils.ts
1315
+ var getEndOfHighlight = (selection) => {
1316
+ const lastRectangle = selection.rectangles[selection.rectangles.length - 1];
1317
+ return lastRectangle.left + lastRectangle.width + 10;
1318
+ };
1319
+ var getMidHeightOfHighlightLine = (selection) => {
1320
+ const lastRectangle = selection.rectangles[selection.rectangles.length - 1];
1321
+ return lastRectangle.top + lastRectangle.height / 2;
1322
+ };
1323
+ var ColoredHighlightComponent = ({
1324
+ selection
1325
+ }) => {
1326
+ const deleteColoredHighlight = usePdf(
1327
+ (state) => state.deleteColoredHighlight
1328
+ );
1329
+ const [showButton, setShowButton] = useState(false);
1330
+ return /* @__PURE__ */ jsxs("div", { className: "colored-highlight", children: [
1331
+ selection.rectangles.map((rect, index) => /* @__PURE__ */ jsx(
1332
+ "span",
1333
+ {
1334
+ onClick: () => setShowButton(!showButton),
1335
+ style: {
1336
+ position: "absolute",
1337
+ top: rect.top,
1338
+ left: rect.left,
1339
+ height: rect.height,
1340
+ width: rect.width,
1341
+ cursor: "pointer",
1342
+ zIndex: 30,
1343
+ backgroundColor: selection.color,
1344
+ // mixBlendMode: "lighten", // changes the color of the text
1345
+ mixBlendMode: "darken",
1346
+ // best results
1347
+ // mixBlendMode: "multiply", // works but coloring has some inconsistencies
1348
+ borderRadius: "0.2rem"
1349
+ }
1350
+ },
1351
+ `${selection.uuid}-${index}`
1352
+ )),
1353
+ showButton && /* @__PURE__ */ jsx(
1354
+ "button",
1355
+ {
1356
+ style: {
1357
+ backgroundColor: "white",
1358
+ color: "white",
1359
+ borderRadius: "5px",
1360
+ padding: "5px",
1361
+ cursor: "pointer",
1362
+ boxShadow: "2px 2px 5px black",
1363
+ position: "absolute",
1364
+ top: getMidHeightOfHighlightLine(selection),
1365
+ left: getEndOfHighlight(selection),
1366
+ zIndex: 30,
1367
+ transform: "translateY(-50%)"
1368
+ },
1369
+ onClick: () => deleteColoredHighlight(selection.uuid),
1370
+ children: /* @__PURE__ */ jsx(
1371
+ "svg",
1372
+ {
1373
+ fill: "#000000",
1374
+ version: "1.1",
1375
+ id: "Capa_1",
1376
+ xmlns: "http://www.w3.org/2000/svg",
1377
+ width: "15px",
1378
+ height: "15px",
1379
+ viewBox: "0 0 485 485",
1380
+ children: /* @__PURE__ */ jsx("g", { children: /* @__PURE__ */ jsxs("g", { children: [
1381
+ /* @__PURE__ */ jsx("rect", { x: "67.224", width: "350.535", height: "71.81" }),
1382
+ /* @__PURE__ */ jsx(
1383
+ "path",
1384
+ {
1385
+ d: "M417.776,92.829H67.237V485h350.537V92.829H417.776z M165.402,431.447h-28.362V146.383h28.362V431.447z M256.689,431.447\n h-28.363V146.383h28.363V431.447z M347.97,431.447h-28.361V146.383h28.361V431.447z"
1386
+ }
1387
+ )
1388
+ ] }) })
1389
+ }
1390
+ )
1391
+ },
1392
+ `${selection.uuid}-delete-button`
1393
+ )
1394
+ ] });
1395
+ };
1396
+ var ColoredHighlightLayer = ({
1397
+ onHighlight
381
1398
  }) => {
382
- const { canvasRef } = useCanvasLayer({ background });
383
- return /* @__PURE__ */ jsx(
384
- "canvas",
385
- {
386
- style: {
387
- ...style,
388
- position: "absolute",
389
- top: 0,
390
- left: 0,
391
- width: "100%",
392
- height: "100%"
393
- },
394
- ...props,
395
- ref: canvasRef
396
- }
1399
+ const pageNumber = usePDFPageNumber();
1400
+ const { getSelection } = useSelectionDimensions();
1401
+ const highlights = usePdf(
1402
+ (state) => state.coloredHighlights
397
1403
  );
1404
+ const addColoredHighlight = usePdf((state) => state.addColoredHighlight);
1405
+ const handleHighlighting = useCallback((color) => {
1406
+ const { highlights: highlights2, text } = getSelection();
1407
+ if (highlights2[0]) {
1408
+ const highlight = {
1409
+ uuid: v4(),
1410
+ pageNumber: highlights2[0].pageNumber,
1411
+ // usePDFPageNumber() doesn't return the correct page number, so i'm getting the number directly from the first highlight
1412
+ color,
1413
+ rectangles: highlights2,
1414
+ text
1415
+ };
1416
+ addColoredHighlight(highlight);
1417
+ if (onHighlight) onHighlight(highlight);
1418
+ }
1419
+ }, []);
1420
+ return /* @__PURE__ */ jsxs("div", { className: "colored-highlights-layer", children: [
1421
+ highlights.filter((selection) => selection.pageNumber === pageNumber).map((selection) => /* @__PURE__ */ jsx(
1422
+ ColoredHighlightComponent,
1423
+ {
1424
+ selection
1425
+ },
1426
+ selection.uuid
1427
+ )),
1428
+ /* @__PURE__ */ jsx(
1429
+ ColorSelectionTool,
1430
+ {
1431
+ onColorSelection: (colorItem) => handleHighlighting(colorItem.color)
1432
+ }
1433
+ )
1434
+ ] });
398
1435
  };
399
1436
 
400
1437
  // src/components/layers/custom-layer.tsx
@@ -491,68 +1528,6 @@ var TextLayer2 = ({
491
1528
  }
492
1529
  );
493
1530
  };
494
-
495
- // src/hooks/pages/usePdfJump.tsx
496
- var usePdfJump = () => {
497
- const virtualizer = usePdf((state) => state.virtualizer);
498
- const setHighlight = usePdf((state) => state.setHighlight);
499
- const jumpToPage = (pageIndex, options) => {
500
- if (!virtualizer) return;
501
- const defaultOptions = {
502
- align: "start",
503
- behavior: "smooth"
504
- };
505
- const finalOptions = { ...defaultOptions, ...options };
506
- virtualizer.scrollToIndex(pageIndex - 1, finalOptions);
507
- };
508
- const jumpToOffset = (offset2) => {
509
- if (!virtualizer) return;
510
- virtualizer.scrollToOffset(offset2, {
511
- align: "start",
512
- behavior: "smooth"
513
- });
514
- };
515
- const jumpToHighlightRects = (rects, type, align = "start", additionalOffset = 0) => {
516
- if (!virtualizer) return;
517
- setHighlight(rects);
518
- const firstPage = Math.min(...rects.map((rect) => rect.pageNumber));
519
- const pageOffset = virtualizer.getOffsetForIndex(firstPage - 1, "start");
520
- if (pageOffset === null) return;
521
- const targetRect = rects.find((rect) => rect.pageNumber === firstPage);
522
- if (!targetRect) return;
523
- const isNumber = pageOffset?.[0] != null;
524
- if (!isNumber) return;
525
- const pageStart = pageOffset[0] ?? 0;
526
- let rectTop;
527
- let rectHeight;
528
- if (type === "percent") {
529
- const estimatePageHeight = virtualizer.options.estimateSize(
530
- firstPage - 1
531
- );
532
- rectTop = targetRect.top / 100 * estimatePageHeight;
533
- rectHeight = targetRect.height / 100 * estimatePageHeight;
534
- } else {
535
- rectTop = targetRect.top;
536
- rectHeight = targetRect.height;
537
- }
538
- let scrollOffset;
539
- if (align === "center") {
540
- const viewportHeight = virtualizer.scrollElement?.clientHeight || 0;
541
- const rectCenter = pageStart + rectTop + rectHeight / 2;
542
- scrollOffset = rectCenter - viewportHeight / 2;
543
- } else {
544
- scrollOffset = pageStart + rectTop;
545
- }
546
- scrollOffset += additionalOffset;
547
- const adjustedOffset = Math.max(0, scrollOffset);
548
- virtualizer.scrollToOffset(adjustedOffset, {
549
- align: "start",
550
- // Always use start when we've calculated our own centering
551
- behavior: "smooth"
552
- });
553
- };
554
- return { jumpToPage, jumpToOffset, jumpToHighlightRects };
555
- };
556
1531
  var usePDFOutline = () => {
557
1532
  const pdfDocumentProxy = usePdf((state) => state.pdfDocumentProxy);
558
1533
  const [outline, setOutline] = useState();
@@ -710,6 +1685,7 @@ var Page = ({
710
1685
  style: {
711
1686
  ...style,
712
1687
  "--scale-factor": 1,
1688
+ "--total-scale-factor": 1,
713
1689
  position: "relative",
714
1690
  width,
715
1691
  height
@@ -816,21 +1792,21 @@ var useObserveElement = () => {
816
1792
  if (!targetWindow) {
817
1793
  return;
818
1794
  }
819
- let offset2 = 0;
1795
+ let offset3 = 0;
820
1796
  const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : debounce(
821
1797
  targetWindow,
822
1798
  () => {
823
- cb(offset2, false);
1799
+ cb(offset3, false);
824
1800
  },
825
1801
  instance.options.isScrollingResetDelay
826
1802
  );
827
1803
  const createHandler = (isScrolling) => () => {
828
1804
  const { horizontal, isRtl } = instance.options;
829
- offset2 = horizontal ? element["scrollLeft"] * (isRtl && -1 || 1) : element["scrollTop"];
1805
+ offset3 = horizontal ? element["scrollLeft"] * (isRtl && -1 || 1) : element["scrollTop"];
830
1806
  const zoom = store.getState().zoom;
831
- offset2 = offset2 / zoom;
1807
+ offset3 = offset3 / zoom;
832
1808
  fallback();
833
- cb(offset2, isScrolling);
1809
+ cb(offset3, isScrolling);
834
1810
  };
835
1811
  const handler = createHandler(true);
836
1812
  const endHandler = createHandler(false);
@@ -851,8 +1827,8 @@ var useScrollFn = () => {
851
1827
  const scrollToFn = useCallback(
852
1828
  (_offset, canSmooth, instance) => {
853
1829
  const zoom = store.getState().zoom;
854
- const offset2 = _offset * zoom;
855
- elementScroll(offset2, canSmooth, instance);
1830
+ const offset3 = _offset * zoom;
1831
+ elementScroll(offset3, canSmooth, instance);
856
1832
  },
857
1833
  [store]
858
1834
  );
@@ -1967,10 +2943,10 @@ var PinchEngine = class extends Engine {
1967
2943
  }
1968
2944
  computeMovement() {
1969
2945
  const {
1970
- offset: offset2,
2946
+ offset: offset3,
1971
2947
  lastOffset
1972
2948
  } = this.state;
1973
- this.state.movement = [offset2[0] / lastOffset[0], offset2[1] - lastOffset[1]];
2949
+ this.state.movement = [offset3[0] / lastOffset[0], offset3[1] - lastOffset[1]];
1974
2950
  }
1975
2951
  axisIntent() {
1976
2952
  const state = this.state;
@@ -2955,12 +3931,10 @@ var Pages = ({
2955
3931
  toOffset -= size;
2956
3932
  }
2957
3933
  const scrollSizeProp = virtualizer.options.horizontal ? "scrollWidth" : "scrollHeight";
2958
- const scrollSize = virtualizer.scrollElement ? "document" in virtualizer.scrollElement ? (
3934
+ virtualizer.scrollElement ? "document" in virtualizer.scrollElement ? (
2959
3935
  //@ts-expect-error this is a private stuff
2960
3936
  virtualizer.scrollElement.document.documentElement[scrollSizeProp]
2961
3937
  ) : virtualizer.scrollElement[scrollSizeProp] : 0;
2962
- const maxOffset = scrollSize - size;
2963
- console.log(scrollSize, maxOffset, toOffset);
2964
3938
  return Math.max(toOffset, 0);
2965
3939
  };
2966
3940
  }, [zoom, virtualizer]);
@@ -3153,98 +4127,6 @@ var Search = ({ children, loading = "Loading..." }) => {
3153
4127
  }, [proxies, getTextContent]);
3154
4128
  return isLoading ? loading : children;
3155
4129
  };
3156
- var SelectionTooltip = ({ children }) => {
3157
- const [isOpen, setIsOpen] = useState(false);
3158
- const lastSelectionRef = useRef(null);
3159
- const viewportRef = usePdf((state) => state.viewportRef);
3160
- const { refs, floatingStyles, context } = useFloating({
3161
- placement: "bottom",
3162
- open: isOpen,
3163
- onOpenChange: setIsOpen,
3164
- whileElementsMounted: autoUpdate,
3165
- middleware: [offset(10), shift({ padding: 8 })]
3166
- });
3167
- const dismiss = useDismiss(context);
3168
- const { getFloatingProps } = useInteractions([dismiss]);
3169
- const updateTooltipPosition = useCallback(() => {
3170
- const selection = document.getSelection();
3171
- if (!selection || selection.isCollapsed) {
3172
- setIsOpen(false);
3173
- lastSelectionRef.current = null;
3174
- return;
3175
- }
3176
- const range = selection.getRangeAt(0);
3177
- if (!range) return;
3178
- const rects = range.getClientRects();
3179
- const lastRect = rects[rects.length - 1];
3180
- lastSelectionRef.current = range;
3181
- if (lastRect) {
3182
- refs.setReference({
3183
- getBoundingClientRect: () => ({
3184
- width: lastRect.width,
3185
- height: lastRect.height,
3186
- x: lastRect.left,
3187
- y: lastRect.bottom,
3188
- // Position below the last line of selection
3189
- top: lastRect.bottom,
3190
- right: lastRect.right,
3191
- bottom: lastRect.bottom + lastRect.height,
3192
- left: lastRect.left
3193
- }),
3194
- getClientRects: () => [lastRect]
3195
- });
3196
- setIsOpen(true);
3197
- } else {
3198
- setIsOpen(false);
3199
- }
3200
- }, [refs]);
3201
- useEffect(() => {
3202
- const handleSelectionChange = () => {
3203
- const selection = document.getSelection();
3204
- if (selection && viewportRef.current?.contains(selection.anchorNode)) {
3205
- requestAnimationFrame(updateTooltipPosition);
3206
- } else {
3207
- setIsOpen(false);
3208
- }
3209
- };
3210
- const handleScroll = () => {
3211
- if (!isOpen || !lastSelectionRef.current) return;
3212
- requestAnimationFrame(updateTooltipPosition);
3213
- };
3214
- document.addEventListener("selectionchange", handleSelectionChange);
3215
- if (viewportRef.current) {
3216
- viewportRef.current.addEventListener("scroll", handleScroll, {
3217
- passive: true
3218
- });
3219
- }
3220
- return () => {
3221
- document.removeEventListener("selectionchange", handleSelectionChange);
3222
- if (viewportRef.current) {
3223
- viewportRef.current.removeEventListener("scroll", handleScroll);
3224
- }
3225
- };
3226
- }, [refs, isOpen, viewportRef, updateTooltipPosition]);
3227
- useEffect(() => {
3228
- const handleFloatingClick = (e) => {
3229
- if (refs.floating.current?.contains(e.target)) {
3230
- e.stopPropagation();
3231
- }
3232
- };
3233
- document.addEventListener("click", handleFloatingClick);
3234
- return () => document.removeEventListener("click", handleFloatingClick);
3235
- }, [refs.floating]);
3236
- return /* @__PURE__ */ jsx(Fragment, { children: isOpen && /* @__PURE__ */ jsx(
3237
- "div",
3238
- {
3239
- ref: refs.setFloating,
3240
- style: {
3241
- ...floatingStyles
3242
- },
3243
- ...getFloatingProps(),
3244
- children
3245
- }
3246
- ) });
3247
- };
3248
4130
  var DEFAULT_CONFIG = {
3249
4131
  maxHeight: 800,
3250
4132
  maxWidth: 400
@@ -3444,7 +4326,8 @@ var useSearch = () => {
3444
4326
  text: text.substr(matchIndex, searchText.length + textSize),
3445
4327
  score: 1,
3446
4328
  matchIndex,
3447
- isExactMatch: true
4329
+ isExactMatch: true,
4330
+ searchText
3448
4331
  });
3449
4332
  index = matchIndex + searchText.length;
3450
4333
  }
@@ -3473,7 +4356,8 @@ var useSearch = () => {
3473
4356
  text: text.substr(index, searchLower.length + textSize),
3474
4357
  score,
3475
4358
  matchIndex: index,
3476
- isExactMatch: false
4359
+ isExactMatch: false,
4360
+ searchText
3477
4361
  });
3478
4362
  index += searchLower.length;
3479
4363
  } else {
@@ -3548,7 +4432,7 @@ var useSearch = () => {
3548
4432
  async function calculateHighlightRects(pageProxy, textMatch) {
3549
4433
  const textContent = await pageProxy.getTextContent();
3550
4434
  const items = textContent.items;
3551
- const matchLength = textMatch.text.length;
4435
+ const matchLength = textMatch.searchText ? textMatch.searchText.length : textMatch.text.length;
3552
4436
  const matchRects = [];
3553
4437
  let currentIndex = 0;
3554
4438
  let remainingMatchLength = matchLength;
@@ -3619,95 +4503,4 @@ function mergeAdjacentRects(rects) {
3619
4503
  return merged;
3620
4504
  }
3621
4505
 
3622
- // src/hooks/useSelectionDimensions.tsx
3623
- var MERGE_THRESHOLD = 5;
3624
- var shouldMergeRects = (rect1, rect2) => {
3625
- const verticalOverlap = !(rect1.top > rect2.top + rect2.height || rect2.top > rect1.top + rect1.height);
3626
- const horizontallyClose = Math.abs(rect1.left + rect1.width - rect2.left) < MERGE_THRESHOLD || Math.abs(rect2.left + rect2.width - rect1.left) < MERGE_THRESHOLD || rect1.left < rect2.left + rect2.width && rect2.left < rect1.left + rect1.width;
3627
- return verticalOverlap && horizontallyClose;
3628
- };
3629
- var mergeRects = (rect1, rect2) => {
3630
- return {
3631
- left: Math.min(rect1.left, rect2.left),
3632
- top: Math.min(rect1.top, rect2.top),
3633
- width: Math.max(rect1.left + rect1.width, rect2.left + rect2.width) - Math.min(rect1.left, rect2.left),
3634
- height: Math.max(rect1.top + rect1.height, rect2.top + rect2.height) - Math.min(rect1.top, rect2.top),
3635
- pageNumber: rect1.pageNumber
3636
- };
3637
- };
3638
- var consolidateRects = (rects) => {
3639
- if (rects.length <= 1) return rects;
3640
- const sortedRects = [...rects].sort((a, b) => a.top - b.top);
3641
- let hasChanges;
3642
- do {
3643
- hasChanges = false;
3644
- let currentRect = sortedRects[0];
3645
- const tempResult = [];
3646
- for (let i = 1; i < sortedRects.length; i++) {
3647
- const sorted = sortedRects[i];
3648
- if (!currentRect || !sorted) continue;
3649
- if (shouldMergeRects(currentRect, sorted)) {
3650
- currentRect = mergeRects(currentRect, sorted);
3651
- hasChanges = true;
3652
- } else {
3653
- tempResult.push(currentRect);
3654
- currentRect = sorted;
3655
- }
3656
- }
3657
- if (currentRect) tempResult.push(currentRect);
3658
- sortedRects.length = 0;
3659
- sortedRects.push(...tempResult);
3660
- } while (hasChanges);
3661
- return sortedRects;
3662
- };
3663
- var useSelectionDimensions = () => {
3664
- const store = PDFStore.useContext();
3665
- const getDimension = () => {
3666
- const selection = window.getSelection();
3667
- if (!selection || selection.isCollapsed) return;
3668
- const range = selection.getRangeAt(0);
3669
- const highlights = [];
3670
- const textLayerMap = /* @__PURE__ */ new Map();
3671
- const clientRects = Array.from(range.getClientRects()).filter(
3672
- (rect) => rect.width > 2 && rect.height > 2
3673
- );
3674
- clientRects.forEach((clientRect) => {
3675
- const element = document.elementFromPoint(
3676
- clientRect.left + 1,
3677
- clientRect.top + clientRect.height / 2
3678
- );
3679
- const textLayer = element?.closest(".textLayer");
3680
- if (!textLayer) return;
3681
- const pageNumber = parseInt(
3682
- textLayer.getAttribute("data-page-number") || "1",
3683
- 10
3684
- );
3685
- const textLayerRect = textLayer.getBoundingClientRect();
3686
- const zoom = store.getState().zoom;
3687
- const rect = {
3688
- width: clientRect.width / zoom,
3689
- height: clientRect.height / zoom,
3690
- top: (clientRect.top - textLayerRect.top) / zoom,
3691
- left: (clientRect.left - textLayerRect.left) / zoom,
3692
- pageNumber
3693
- };
3694
- if (!textLayerMap.has(pageNumber)) {
3695
- textLayerMap.set(pageNumber, []);
3696
- }
3697
- textLayerMap.get(pageNumber)?.push(rect);
3698
- });
3699
- textLayerMap.forEach((rects) => {
3700
- if (rects.length > 0) {
3701
- highlights.push(...consolidateRects(rects));
3702
- }
3703
- });
3704
- return {
3705
- highlights: highlights.sort((a, b) => a.pageNumber - b.pageNumber),
3706
- text: range.toString().trim(),
3707
- isCollapsed: false
3708
- };
3709
- };
3710
- return { getDimension };
3711
- };
3712
-
3713
- export { AnnotationLayer2 as AnnotationLayer, CanvasLayer, CurrentPage, CurrentZoom, CustomLayer, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer2 as TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, usePdf, usePdfJump, useSearch, useSelectionDimensions };
4506
+ export { AnnotationHighlightLayer, AnnotationLayer2 as AnnotationLayer, AnnotationTooltip, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer2 as TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, useAnnotations, usePDFPageNumber, usePdf, usePdfJump, useSearch, useSelectionDimensions };