@anaralabs/lector 3.14.12 → 3.15.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,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { createContext, memo, forwardRef, useMemo, cloneElement, useEffect, useRef, useState, useCallback, useContext, useLayoutEffect, createRef, useSyncExternalStore, useImperativeHandle } from 'react';
3
+ import React, { createContext, memo, forwardRef, useMemo, cloneElement, useEffect, useRef, useLayoutEffect, useContext, useState, useCallback, useSyncExternalStore, createRef, useImperativeHandle } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { useFloating, autoUpdate, offset, flip, shift, useDismiss, useInteractions, inline } from '@floating-ui/react';
6
6
  import { useStore, createStore } from 'zustand';
@@ -9,7 +9,7 @@ import { useShallow } from 'zustand/react/shallow';
9
9
  import { useDebounce } from 'use-debounce';
10
10
  import { v4 } from 'uuid';
11
11
  import { Slot } from '@radix-ui/react-slot';
12
- import { useVirtualizer, elementScroll, debounce } from '@tanstack/react-virtual';
12
+ import { useVirtualizer, elementScroll, observeElementRect } from '@tanstack/react-virtual';
13
13
 
14
14
  // src/lib/clamp.ts
15
15
  var clamp = (value, minimum, maximum) => {
@@ -219,6 +219,828 @@ function createDarkModeColorMap(colors) {
219
219
  return map;
220
220
  }
221
221
 
222
+ // src/lib/selection-page.ts
223
+ var SelectionPage = class {
224
+ constructor(element, pageNumber, width, height) {
225
+ this.element = element;
226
+ this.pageNumber = pageNumber;
227
+ this.width = width;
228
+ this.height = height;
229
+ const walker = element.ownerDocument.createTreeWalker(
230
+ element,
231
+ NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT
232
+ );
233
+ const chunks = [];
234
+ const lineBreaks = [];
235
+ let offset3 = 0;
236
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
237
+ if (node.nodeType === Node.TEXT_NODE) {
238
+ const text = node.textContent ?? "";
239
+ if (!text) continue;
240
+ this.nodes.push({
241
+ node,
242
+ start: offset3,
243
+ end: offset3 + text.length
244
+ });
245
+ chunks.push(text);
246
+ offset3 += text.length;
247
+ } else if (node.tagName === "BR") lineBreaks.push(offset3);
248
+ }
249
+ this.content = { text: chunks.join(""), lineBreaks };
250
+ }
251
+ nodes = [];
252
+ content;
253
+ offset(node, offset3) {
254
+ if (!this.element.contains(node)) return null;
255
+ const item = this.nodes.find((item2) => item2.node === node);
256
+ if (item)
257
+ return item.start + Math.max(0, Math.min(offset3, item.end - item.start));
258
+ const range = this.element.ownerDocument.createRange();
259
+ range.selectNodeContents(this.element);
260
+ try {
261
+ range.setEnd(node, offset3);
262
+ } catch {
263
+ return null;
264
+ }
265
+ return Math.min(range.toString().length, this.content.text.length);
266
+ }
267
+ point(offset3, end = false) {
268
+ const index = Math.max(0, Math.min(offset3, this.content.text.length));
269
+ const item = this.nodes.find((item2) => end ? item2.end >= index : item2.end > index) ?? this.nodes.at(-1);
270
+ return item ? { node: item.node, offset: Math.max(0, index - item.start) } : null;
271
+ }
272
+ rects(from, to) {
273
+ const bounds = this.element.getBoundingClientRect();
274
+ const scaleX = bounds.width / this.width || 1;
275
+ const scaleY = bounds.height / this.height || 1;
276
+ const rects = [];
277
+ for (const item of this.nodes) {
278
+ const start = Math.max(from, item.start);
279
+ const end = Math.min(to, item.end);
280
+ if (end <= start || !item.node.textContent?.slice(start - item.start, end - item.start).trim())
281
+ continue;
282
+ const range = this.element.ownerDocument.createRange();
283
+ range.setStart(item.node, start - item.start);
284
+ range.setEnd(item.node, end - item.start);
285
+ for (const rect of range.getClientRects()) {
286
+ if (rect.width <= 0 || rect.height <= 0) continue;
287
+ rects.push({
288
+ pageNumber: this.pageNumber,
289
+ left: (rect.left - bounds.left) / scaleX,
290
+ top: (rect.top - bounds.top) / scaleY,
291
+ width: rect.width / scaleX,
292
+ height: rect.height / scaleY
293
+ });
294
+ }
295
+ }
296
+ return rects;
297
+ }
298
+ };
299
+ async function withSelectionPage(proxy, owner, signal, read) {
300
+ signal.throwIfAborted();
301
+ const { TextLayer: TextLayer2 } = await import('pdfjs-dist/legacy/build/pdf.mjs');
302
+ signal.throwIfAborted();
303
+ const viewport = proxy.getViewport({ scale: 1 });
304
+ const container = owner.createElement("div");
305
+ container.className = "textLayer";
306
+ container.setAttribute("aria-hidden", "true");
307
+ container.style.cssText = `position:fixed;left:-100000px;top:0;visibility:hidden;pointer-events:none;width:${viewport.width}px;height:${viewport.height}px;--scale-factor:1;--total-scale-factor:1;`;
308
+ owner.body.append(container);
309
+ let layer;
310
+ const cancel = () => layer?.cancel();
311
+ signal.addEventListener("abort", cancel, { once: true });
312
+ try {
313
+ layer = new TextLayer2({
314
+ container,
315
+ viewport,
316
+ textContentSource: proxy.streamTextContent()
317
+ });
318
+ await layer.render();
319
+ signal.throwIfAborted();
320
+ const unrotated = proxy.getViewport({ scale: 1, rotation: 0 });
321
+ container.style.width = `${unrotated.width}px`;
322
+ container.style.height = `${unrotated.height}px`;
323
+ return read(
324
+ new SelectionPage(
325
+ container,
326
+ proxy.pageNumber,
327
+ viewport.width,
328
+ viewport.height
329
+ )
330
+ );
331
+ } finally {
332
+ signal.removeEventListener("abort", cancel);
333
+ layer?.cancel();
334
+ container.remove();
335
+ }
336
+ }
337
+
338
+ // src/lib/text-normalization.ts
339
+ function replace(source, replacements2) {
340
+ const chunks = [];
341
+ const edits = [];
342
+ let offset3 = 0;
343
+ let outputLength = 0;
344
+ for (const replacement of replacements2) {
345
+ const { index, length, text } = replacement;
346
+ if (source.text.slice(index, index + length) === text) continue;
347
+ const before = source.text.slice(offset3, index);
348
+ chunks.push(before, text);
349
+ outputLength += before.length;
350
+ edits.push({
351
+ start: outputLength,
352
+ end: outputLength + text.length,
353
+ sourceStart: index,
354
+ sourceEnd: index + length
355
+ });
356
+ outputLength += text.length;
357
+ offset3 = index + length;
358
+ }
359
+ if (!edits.length) return source;
360
+ chunks.push(source.text.slice(offset3));
361
+ return { text: chunks.join(""), maps: [...source.maps, edits] };
362
+ }
363
+ function* replacements(text, pattern, value) {
364
+ for (const match of text.matchAll(pattern)) {
365
+ yield { index: match.index, length: match[0].length, text: value };
366
+ }
367
+ }
368
+ function* insertLineBreaks(text, lineBreaks) {
369
+ let previous = -1;
370
+ for (const index of lineBreaks) {
371
+ if (!Number.isInteger(index) || index <= previous || index < 0 || index > text.length)
372
+ continue;
373
+ previous = index;
374
+ yield { index, length: 0, text: "\n" };
375
+ }
376
+ }
377
+ var ligatures = ["ff", "fi", "fl", "ffi", "ffl", "st", "st"];
378
+ var softHyphen = /\u00ad(?:[^\S\r\n]*\r?\n[^\S\r\n]*(?=\p{L}))?/gu;
379
+ var wrappedHyphen = /(?<=[\p{L}\p{M}])[-\u2010][^\S\r\n]*\r?\n[^\S\r\n]*(?=\p{Ll})/gu;
380
+ var cjkLineBreak = /(?<=[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])\r?\n(?=[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])/gu;
381
+ function normalizeSelectionText(text, options = {}) {
382
+ let result = normalizeUnicode(text).replace(softHyphen, "");
383
+ if (options.ignoreHyphenation) result = result.replace(wrappedHyphen, "");
384
+ if (options.lineBreaks === "space") {
385
+ result = result.replace(cjkLineBreak, "").replace(/(?<!\n)\n(?!\n)/g, " ");
386
+ }
387
+ return result;
388
+ }
389
+ function normalizeUnicode(text, matchDiacritics = true) {
390
+ let result = text.replace(
391
+ /[\uFB00-\uFB06]/g,
392
+ (char) => ligatures[char.charCodeAt(0) - 64256]
393
+ );
394
+ if (!matchDiacritics) {
395
+ result = result.normalize("NFD").replace(
396
+ /([\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}])\p{M}+/gu,
397
+ "$1"
398
+ );
399
+ }
400
+ return result.normalize("NFC");
401
+ }
402
+ var segmenter;
403
+ function* unicodeReplacements(text, matchDiacritics) {
404
+ segmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
405
+ for (const match of text.matchAll(/\p{ASCII}?\P{ASCII}+/gu)) {
406
+ if (normalizeUnicode(match[0], matchDiacritics) === match[0]) continue;
407
+ for (const { segment, index } of segmenter.segment(match[0])) {
408
+ yield {
409
+ index: match.index + index,
410
+ length: segment.length,
411
+ text: normalizeUnicode(segment, matchDiacritics)
412
+ };
413
+ }
414
+ }
415
+ }
416
+ function normalizeSearchText(text, options = {}, lineBreaks = []) {
417
+ let result = { text, maps: [] };
418
+ if (lineBreaks.length)
419
+ result = replace(result, insertLineBreaks(text, lineBreaks));
420
+ const matchDiacritics = options.matchDiacritics !== false;
421
+ if (normalizeUnicode(result.text, matchDiacritics) !== result.text) {
422
+ result = replace(result, unicodeReplacements(result.text, matchDiacritics));
423
+ }
424
+ result = replace(result, replacements(result.text, softHyphen, ""));
425
+ if (options.ignoreHyphenation) {
426
+ result = replace(result, replacements(result.text, wrappedHyphen, ""));
427
+ }
428
+ result = replace(result, replacements(result.text, cjkLineBreak, ""));
429
+ result = replace(result, replacements(result.text, /\s+/gu, " "));
430
+ const lower = result.text.toLowerCase();
431
+ if (lower.length !== result.text.length) {
432
+ function* caseReplacements(value) {
433
+ let index = 0;
434
+ for (const char of value) {
435
+ const folded = char.toLowerCase();
436
+ if (folded.length !== char.length)
437
+ yield { index, length: char.length, text: folded };
438
+ index += char.length;
439
+ }
440
+ }
441
+ result = replace(result, caseReplacements(result.text));
442
+ }
443
+ return { ...result, text: lower };
444
+ }
445
+ function sourceOffset(edits, offset3, end) {
446
+ let low = 0;
447
+ let high = edits.length;
448
+ while (low < high) {
449
+ const middle = low + high >>> 1;
450
+ if (edits[middle].start <= offset3) low = middle + 1;
451
+ else high = middle;
452
+ }
453
+ const edit = edits[low - 1];
454
+ if (!edit) return offset3 + (end ? 1 : 0);
455
+ if (offset3 < edit.end) return end ? edit.sourceEnd : edit.sourceStart;
456
+ return offset3 + edit.sourceEnd - edit.end + (end ? 1 : 0);
457
+ }
458
+ function originalTextRange(normalized, start, end) {
459
+ for (let i = normalized.maps.length - 1; i >= 0; i--) {
460
+ const map = normalized.maps[i];
461
+ start = sourceOffset(map, start, false);
462
+ end = sourceOffset(map, end - 1, true);
463
+ }
464
+ return { start, end };
465
+ }
466
+
467
+ // src/lib/pdf-selection.ts
468
+ var sameAnchor = (a, b) => a.pageNumber === b.pageNumber && a.offset === b.offset;
469
+ var compare = (a, b) => a.pageNumber - b.pageNumber || a.offset - b.offset;
470
+ var samePoint = (a, node, offset3) => a.node === node && a.offset === offset3;
471
+ var selectionOwners = /* @__PURE__ */ new WeakMap();
472
+ var PDFSelectionController = class {
473
+ constructor(options) {
474
+ this.options = options;
475
+ }
476
+ snapshot = null;
477
+ listeners = /* @__PURE__ */ new Set();
478
+ layers = /* @__PURE__ */ new Map();
479
+ pages = /* @__PURE__ */ new Map();
480
+ container = null;
481
+ projection = null;
482
+ frame = null;
483
+ work = null;
484
+ structuralChange = false;
485
+ drag = null;
486
+ connectedCleanup = null;
487
+ generation = 0;
488
+ keyboardWork = null;
489
+ keyboardMoves = [];
490
+ getSnapshot = () => this.snapshot;
491
+ subscribe = (listener) => {
492
+ this.listeners.add(listener);
493
+ return () => {
494
+ this.listeners.delete(listener);
495
+ };
496
+ };
497
+ get isDragging() {
498
+ return this.drag !== null;
499
+ }
500
+ includesPage(page) {
501
+ const range = this.ordered();
502
+ return Boolean(
503
+ range && page >= range.start.pageNumber && page <= range.end.pageNumber
504
+ );
505
+ }
506
+ publish(snapshot) {
507
+ this.snapshot = snapshot;
508
+ for (const listener of this.listeners) listener();
509
+ }
510
+ ordered() {
511
+ if (!this.snapshot) return null;
512
+ const { anchor, focus } = this.snapshot;
513
+ return compare(anchor, focus) <= 0 ? { start: anchor, end: focus } : { start: focus, end: anchor };
514
+ }
515
+ bounds(page, length) {
516
+ const range = this.ordered();
517
+ return {
518
+ from: page === range.start.pageNumber ? Math.min(range.start.offset, length) : 0,
519
+ to: page === range.end.pageNumber ? Math.min(range.end.offset, length) : length
520
+ };
521
+ }
522
+ remember(page) {
523
+ if (!this.includesPage(page.pageNumber)) return;
524
+ for (const point of [this.snapshot.anchor, this.snapshot.focus]) {
525
+ if (point.pageNumber === page.pageNumber && point.offset > page.content.text.length)
526
+ throw new RangeError("Selection offset exceeds page text length");
527
+ }
528
+ const { from, to } = this.bounds(page.pageNumber, page.content.text.length);
529
+ const cached = this.pages.get(page.pageNumber);
530
+ if (cached?.content.text === page.content.text && cached.from === from && cached.to === to)
531
+ return;
532
+ this.pages.set(page.pageNumber, {
533
+ content: page.content,
534
+ from,
535
+ to,
536
+ rects: page.rects(from, to)
537
+ });
538
+ }
539
+ complete() {
540
+ const range = this.ordered();
541
+ if (!range) return false;
542
+ for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++) {
543
+ const page = this.pages.get(n);
544
+ if (!page) return false;
545
+ const bounds = this.bounds(n, page.content.text.length);
546
+ if (page.from !== bounds.from || page.to !== bounds.to) return false;
547
+ }
548
+ return true;
549
+ }
550
+ setSelection = (anchor, focus) => {
551
+ for (const point of [anchor, focus]) {
552
+ const text = this.layers.get(point.pageNumber)?.content.text ?? this.pages.get(point.pageNumber)?.content.text;
553
+ if (text !== void 0 && point.offset > text.length)
554
+ throw new RangeError("Selection offset exceeds page text length");
555
+ if (!Number.isInteger(point.pageNumber) || point.pageNumber < 1 || point.pageNumber > this.options.pageCount || !Number.isInteger(point.offset) || point.offset < 0)
556
+ throw new RangeError(
557
+ "Selection anchors need a valid page and nonnegative UTF-16 offset"
558
+ );
559
+ }
560
+ if (sameAnchor(anchor, focus)) {
561
+ const drag = this.drag;
562
+ this.clear();
563
+ this.drag = drag;
564
+ return;
565
+ }
566
+ if (this.snapshot && this.snapshot.status !== "error" && sameAnchor(this.snapshot.anchor, anchor) && sameAnchor(this.snapshot.focus, focus))
567
+ return;
568
+ if (this.container) {
569
+ const owner = this.container.ownerDocument;
570
+ const previous = selectionOwners.get(owner);
571
+ if (previous !== this) previous?.clear();
572
+ selectionOwners.set(owner, this);
573
+ }
574
+ this.work?.abort();
575
+ this.keyboardWork?.abort();
576
+ this.keyboardWork = null;
577
+ this.generation++;
578
+ this.snapshot = {
579
+ anchor: { ...anchor },
580
+ focus: { ...focus },
581
+ status: "loading"
582
+ };
583
+ for (const n of this.pages.keys())
584
+ if (!this.includesPage(n)) this.pages.delete(n);
585
+ for (const page of this.layers.values()) this.remember(page);
586
+ this.publish({
587
+ ...this.snapshot,
588
+ status: this.complete() ? "ready" : "loading"
589
+ });
590
+ this.project();
591
+ this.schedule();
592
+ this.hydrate();
593
+ };
594
+ clear = () => {
595
+ const owner = this.container?.ownerDocument;
596
+ if (owner && selectionOwners.get(owner) === this)
597
+ selectionOwners.delete(owner);
598
+ this.work?.abort();
599
+ this.keyboardWork?.abort();
600
+ this.keyboardWork = null;
601
+ this.generation++;
602
+ this.pages.clear();
603
+ this.keyboardMoves = [];
604
+ this.drag = null;
605
+ const selection = this.container?.ownerDocument.getSelection();
606
+ if (this.projection && selection && samePoint(
607
+ this.projection.anchor,
608
+ selection.anchorNode,
609
+ selection.anchorOffset
610
+ ) && samePoint(
611
+ this.projection.focus,
612
+ selection.focusNode,
613
+ selection.focusOffset
614
+ ))
615
+ selection.removeAllRanges();
616
+ this.projection = null;
617
+ if (this.snapshot) this.publish(null);
618
+ };
619
+ getText = (options = {}) => {
620
+ const range = this.ordered();
621
+ if (!range || !this.complete()) return null;
622
+ const chunks = [];
623
+ for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++) {
624
+ const page = this.pages.get(n);
625
+ const pieces = [];
626
+ let offset3 = page.from;
627
+ for (const end of page.content.lineBreaks) {
628
+ if (end <= page.from || end >= page.to) continue;
629
+ pieces.push(page.content.text.slice(offset3, end), "\n");
630
+ offset3 = end;
631
+ }
632
+ pieces.push(page.content.text.slice(offset3, page.to));
633
+ chunks.push(pieces.join(""));
634
+ }
635
+ return normalizeSelectionText(chunks.join("\n\n"), options);
636
+ };
637
+ getSelection = (options) => {
638
+ const text = this.getText(options);
639
+ if (text === null || !this.snapshot) return null;
640
+ const range = this.ordered();
641
+ const highlights = [];
642
+ for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++)
643
+ highlights.push(...this.pages.get(n).rects.map((rect) => ({ ...rect })));
644
+ return {
645
+ anchor: { ...this.snapshot.anchor },
646
+ focus: { ...this.snapshot.focus },
647
+ text,
648
+ highlights,
649
+ underlines: highlights.map((rect) => ({
650
+ ...rect,
651
+ top: rect.top + rect.height - 1,
652
+ height: 1
653
+ })),
654
+ isCollapsed: false
655
+ };
656
+ };
657
+ getSelectionAsync = async (options) => {
658
+ const generation = this.generation;
659
+ await new Promise((resolve) => {
660
+ if (this.snapshot?.status !== "loading") {
661
+ resolve();
662
+ return;
663
+ }
664
+ const unsubscribe = this.subscribe(() => {
665
+ if (generation !== this.generation || this.snapshot?.status !== "loading") {
666
+ unsubscribe();
667
+ resolve();
668
+ }
669
+ });
670
+ });
671
+ if (generation !== this.generation)
672
+ throw new DOMException("Selection changed", "AbortError");
673
+ if (this.snapshot?.status === "error") throw this.snapshot.error;
674
+ return this.getSelection(options);
675
+ };
676
+ finishSelection() {
677
+ if (this.snapshot?.status !== "loading" || !this.complete()) return;
678
+ this.work?.abort();
679
+ this.work = null;
680
+ this.publish({ ...this.snapshot, status: "ready" });
681
+ }
682
+ registerLayer(element, pageNumber, width, height) {
683
+ const page = new SelectionPage(element, pageNumber, width, height);
684
+ this.layers.set(pageNumber, page);
685
+ try {
686
+ this.remember(page);
687
+ this.finishSelection();
688
+ } catch (error) {
689
+ this.work?.abort();
690
+ if (this.snapshot)
691
+ this.publish({ ...this.snapshot, status: "error", error });
692
+ }
693
+ this.schedule();
694
+ return () => {
695
+ if (this.layers.get(pageNumber) !== page) return;
696
+ this.structuralChange = true;
697
+ this.layers.delete(pageNumber);
698
+ this.projection = null;
699
+ this.schedule();
700
+ };
701
+ }
702
+ anchorFor(node, offset3) {
703
+ if (!node) return null;
704
+ for (const [pageNumber, page] of this.layers) {
705
+ const index = page.offset(node, offset3);
706
+ if (index !== null) return { pageNumber, offset: index };
707
+ }
708
+ return null;
709
+ }
710
+ capture = () => {
711
+ const owner = this.container?.ownerDocument;
712
+ const active = owner?.activeElement;
713
+ if (active && active !== owner?.body && !this.container?.contains(active) && !active.closest(
714
+ 'input, textarea, [contenteditable]:not([contenteditable="false"])'
715
+ ))
716
+ return;
717
+ const selection = owner?.getSelection();
718
+ if (!selection || selection.rangeCount > 1) return;
719
+ if (this.projection && samePoint(
720
+ this.projection.anchor,
721
+ selection.anchorNode,
722
+ selection.anchorOffset
723
+ ) && samePoint(
724
+ this.projection.focus,
725
+ selection.focusNode,
726
+ selection.focusOffset
727
+ ))
728
+ return;
729
+ if (this.structuralChange) {
730
+ this.schedule();
731
+ return;
732
+ }
733
+ if (selection.isCollapsed) {
734
+ if (this.structuralChange || this.drag || !this.projection && this.snapshot && (!this.layers.has(this.snapshot.anchor.pageNumber) || !this.layers.has(this.snapshot.focus.pageNumber))) {
735
+ this.schedule();
736
+ return;
737
+ }
738
+ if (this.snapshot) this.clear();
739
+ return;
740
+ }
741
+ const anchor = this.anchorFor(selection.anchorNode, selection.anchorOffset);
742
+ const focus = this.anchorFor(selection.focusNode, selection.focusOffset);
743
+ if (anchor && focus) {
744
+ if (this.drag && !this.drag.started) {
745
+ if (!this.drag.shift && !this.drag.moved) this.drag.anchor = anchor;
746
+ this.drag.started = true;
747
+ }
748
+ const savedAnchor = this.projection && samePoint(
749
+ this.projection.anchor,
750
+ selection.anchorNode,
751
+ selection.anchorOffset
752
+ ) ? this.snapshot?.anchor : void 0;
753
+ this.setSelection(this.drag?.anchor ?? savedAnchor ?? anchor, focus);
754
+ this.projection = {
755
+ anchor: { node: selection.anchorNode, offset: selection.anchorOffset },
756
+ focus: { node: selection.focusNode, offset: selection.focusOffset }
757
+ };
758
+ } else if (!this.structuralChange && !this.drag && (selection.anchorNode || selection.focusNode))
759
+ this.clear();
760
+ };
761
+ schedule() {
762
+ if (!this.container || this.frame !== null) return;
763
+ this.frame = this.container.ownerDocument.defaultView.requestAnimationFrame(() => {
764
+ this.frame = null;
765
+ this.project();
766
+ this.structuralChange = false;
767
+ this.flushKeyboard();
768
+ if (this.drag) this.autoScroll();
769
+ });
770
+ }
771
+ project() {
772
+ const range = this.ordered();
773
+ if (!range || !this.container) return;
774
+ const visible = [...this.layers.values()].filter((page) => this.includesPage(page.pageNumber) && page.nodes.length).sort((a, b) => a.pageNumber - b.pageNumber);
775
+ const first = visible[0];
776
+ const last = visible.at(-1);
777
+ if (!first || !last) return;
778
+ const start = first.point(
779
+ this.bounds(first.pageNumber, first.content.text.length).from
780
+ );
781
+ const end = last.point(
782
+ this.bounds(last.pageNumber, last.content.text.length).to,
783
+ true
784
+ );
785
+ if (!start || !end) return;
786
+ const backwards = compare(this.snapshot.anchor, this.snapshot.focus) > 0;
787
+ const anchor = backwards ? end : start;
788
+ const focus = backwards ? start : end;
789
+ const selection = this.container.ownerDocument.getSelection();
790
+ this.projection = { anchor, focus };
791
+ if (!samePoint(anchor, selection.anchorNode, selection.anchorOffset) || !samePoint(focus, selection.focusNode, selection.focusOffset))
792
+ selection.setBaseAndExtent(
793
+ anchor.node,
794
+ anchor.offset,
795
+ focus.node,
796
+ focus.offset
797
+ );
798
+ }
799
+ hydrate() {
800
+ if (!this.snapshot || this.complete() || !this.container) {
801
+ return;
802
+ }
803
+ const range = this.ordered();
804
+ const owner = this.container.ownerDocument;
805
+ const controller = new AbortController();
806
+ this.work = controller;
807
+ const generation = this.generation;
808
+ let next = range.start.pageNumber;
809
+ const worker = async () => {
810
+ while (next <= range.end.pageNumber) {
811
+ controller.signal.throwIfAborted();
812
+ const n = next++;
813
+ const cached = this.pages.get(n);
814
+ if (cached) {
815
+ const bounds = this.bounds(n, cached.content.text.length);
816
+ if (bounds.from === cached.from && bounds.to === cached.to) continue;
817
+ }
818
+ const page = await this.options.loadPage(n);
819
+ controller.signal.throwIfAborted();
820
+ await withSelectionPage(
821
+ page,
822
+ owner,
823
+ controller.signal,
824
+ (page2) => this.remember(page2)
825
+ );
826
+ }
827
+ };
828
+ void Promise.all([worker(), worker()]).then(() => {
829
+ if (!controller.signal.aborted && generation === this.generation) {
830
+ this.finishSelection();
831
+ this.schedule();
832
+ }
833
+ }).catch((error) => {
834
+ if (!controller.signal.aborted && generation === this.generation && this.snapshot) {
835
+ this.publish({ ...this.snapshot, status: "error", error });
836
+ controller.abort();
837
+ }
838
+ });
839
+ }
840
+ caret(x, y) {
841
+ const owner = this.container.ownerDocument;
842
+ const doc = owner;
843
+ const position = doc.caretPositionFromPoint?.(x, y);
844
+ if (position) return this.anchorFor(position.offsetNode, position.offset);
845
+ const range = doc.caretRangeFromPoint?.(x, y);
846
+ return range ? this.anchorFor(range.startContainer, range.startOffset) : null;
847
+ }
848
+ autoScroll() {
849
+ if (!this.drag?.moved || !this.container) return;
850
+ const bounds = this.container.getBoundingClientRect();
851
+ const { x, y } = this.drag;
852
+ const edge = Math.min(40, bounds.height / 4);
853
+ const delta = y < bounds.top + edge ? -Math.min(24, (bounds.top + edge - y) * 0.5) : y > bounds.bottom - edge ? Math.min(24, (y - bounds.bottom + edge) * 0.5) : 0;
854
+ if (delta) this.container.scrollTop += delta;
855
+ const focus = this.caret(
856
+ Math.max(bounds.left + 1, Math.min(x, bounds.right - 1)),
857
+ Math.max(bounds.top + 1, Math.min(y, bounds.bottom - 1))
858
+ );
859
+ if (focus) this.setSelection(this.drag.anchor, focus);
860
+ if (delta) this.schedule();
861
+ }
862
+ flushKeyboard() {
863
+ if (!this.snapshot || !this.keyboardMoves.length || this.keyboardWork)
864
+ return;
865
+ const page = this.layers.get(this.snapshot.focus.pageNumber);
866
+ if (!page) {
867
+ this.options.revealPage?.(this.snapshot.focus.pageNumber);
868
+ return;
869
+ }
870
+ const move = this.keyboardMoves.shift();
871
+ const native = this.container.ownerDocument.getSelection();
872
+ if (!native?.modify) return;
873
+ const { anchor, focus } = this.snapshot;
874
+ native.modify("extend", move.direction, move.granularity);
875
+ const next = this.anchorFor(native.focusNode, native.focusOffset);
876
+ if (next && !sameAnchor(next, focus)) {
877
+ this.setSelection(anchor, next);
878
+ } else {
879
+ const point = page.point(focus.offset);
880
+ const rtl = point && this.container.ownerDocument.defaultView.getComputedStyle(
881
+ point.node.parentElement
882
+ ).direction === "rtl";
883
+ const backwards = move.direction === "backward" || move.direction === (rtl ? "right" : "left");
884
+ if (move.granularity === "lineboundary" || (backwards ? focus.offset > (move.granularity === "line" ? page.content.lineBreaks[0] ?? 0 : 0) : focus.offset < (move.granularity === "line" ? page.content.lineBreaks.at(-1) ?? page.content.text.length : page.content.text.length))) {
885
+ this.schedule();
886
+ return;
887
+ }
888
+ const n = focus.pageNumber + (backwards ? -1 : 1);
889
+ if (n >= 1 && n <= this.options.pageCount) {
890
+ if (!backwards) {
891
+ this.setSelection(anchor, { pageNumber: n, offset: 0 });
892
+ this.options.revealPage?.(n);
893
+ } else {
894
+ const generation = this.generation;
895
+ const controller = new AbortController();
896
+ this.keyboardWork = controller;
897
+ const owner = this.container.ownerDocument;
898
+ void this.options.loadPage(n).then(
899
+ (proxy) => withSelectionPage(
900
+ proxy,
901
+ owner,
902
+ controller.signal,
903
+ (page2) => page2.content.text.length
904
+ )
905
+ ).then((length) => {
906
+ if (controller.signal.aborted || generation !== this.generation || !this.container)
907
+ return;
908
+ this.setSelection(anchor, { pageNumber: n, offset: length });
909
+ this.options.revealPage?.(n);
910
+ }).catch(() => {
911
+ }).finally(() => {
912
+ if (this.keyboardWork === controller) this.keyboardWork = null;
913
+ this.schedule();
914
+ });
915
+ }
916
+ }
917
+ }
918
+ if (this.keyboardMoves.length) this.schedule();
919
+ }
920
+ connect(container) {
921
+ this.connectedCleanup?.();
922
+ this.container = container;
923
+ const owner = container.ownerDocument;
924
+ const events = new AbortController();
925
+ const { signal } = events;
926
+ const down = (event) => {
927
+ if (event.defaultPrevented) return;
928
+ const target = event.target instanceof Element ? event.target : null;
929
+ if (target?.closest("button, a, [role=button], [data-selection-tooltip]"))
930
+ return;
931
+ if (!container.contains(event.target)) {
932
+ this.clear();
933
+ return;
934
+ }
935
+ if (event.defaultPrevented || event.button !== 0 || event.pointerType === "touch" || !container.contains(event.target))
936
+ return;
937
+ const point = this.caret(event.clientX, event.clientY);
938
+ if (!point) return;
939
+ const anchor = event.shiftKey && this.snapshot ? this.snapshot.anchor : point;
940
+ if (!event.shiftKey) this.clear();
941
+ this.drag = {
942
+ anchor,
943
+ x: event.clientX,
944
+ y: event.clientY,
945
+ pointerId: event.pointerId,
946
+ moved: false,
947
+ started: false,
948
+ shift: event.shiftKey
949
+ };
950
+ this.schedule();
951
+ };
952
+ const move = (event) => {
953
+ if (!this.drag || event.pointerId !== this.drag.pointerId) return;
954
+ this.drag.moved ||= Math.abs(this.drag.x - event.clientX) + Math.abs(this.drag.y - event.clientY) > 2;
955
+ this.drag.x = event.clientX;
956
+ this.drag.y = event.clientY;
957
+ this.schedule();
958
+ };
959
+ const up = () => {
960
+ this.capture();
961
+ this.drag = null;
962
+ };
963
+ owner.addEventListener("selectionchange", this.capture, { signal });
964
+ owner.addEventListener("pointerdown", down, { signal });
965
+ owner.addEventListener("pointermove", move, { signal });
966
+ owner.addEventListener("pointerup", up, { signal });
967
+ owner.addEventListener("pointercancel", up, { signal });
968
+ owner.defaultView.addEventListener("blur", up, { signal });
969
+ const cancelKeyboard = () => {
970
+ this.keyboardMoves = [];
971
+ this.keyboardWork?.abort();
972
+ this.keyboardWork = null;
973
+ };
974
+ container.addEventListener(
975
+ "focusout",
976
+ (event) => {
977
+ if (!container.contains(event.relatedTarget))
978
+ cancelKeyboard();
979
+ },
980
+ { signal }
981
+ );
982
+ owner.defaultView.addEventListener("blur", cancelKeyboard, { signal });
983
+ container.addEventListener(
984
+ "keydown",
985
+ (event) => {
986
+ if (event.defaultPrevented || event.target instanceof Element && event.target.closest(
987
+ 'input, textarea, [contenteditable]:not([contenteditable="false"])'
988
+ ))
989
+ return;
990
+ if (event.key === "Escape" && this.snapshot) {
991
+ this.clear();
992
+ return;
993
+ }
994
+ if (!event.shiftKey || !this.snapshot) return;
995
+ const directions = {
996
+ ArrowLeft: "left",
997
+ ArrowRight: "right",
998
+ ArrowUp: "backward",
999
+ ArrowDown: "forward",
1000
+ Home: "backward",
1001
+ End: "forward"
1002
+ };
1003
+ const direction = directions[event.key];
1004
+ if (!direction) return;
1005
+ const native = owner.getSelection();
1006
+ if (!native?.modify) return;
1007
+ event.preventDefault();
1008
+ const boundary = event.key === "Home" || event.key === "End" || event.metaKey && (event.key === "ArrowLeft" || event.key === "ArrowRight");
1009
+ const granularity = boundary ? "lineboundary" : event.key === "ArrowUp" || event.key === "ArrowDown" ? "line" : event.ctrlKey || event.altKey ? "word" : "character";
1010
+ this.keyboardMoves.push({ direction, granularity });
1011
+ this.schedule();
1012
+ },
1013
+ { signal }
1014
+ );
1015
+ const cleanup = () => {
1016
+ events.abort();
1017
+ if (this.frame !== null)
1018
+ owner.defaultView.cancelAnimationFrame(this.frame);
1019
+ this.frame = null;
1020
+ this.clear();
1021
+ this.container = null;
1022
+ this.connectedCleanup = null;
1023
+ };
1024
+ this.connectedCleanup = cleanup;
1025
+ this.hydrate();
1026
+ this.schedule();
1027
+ return cleanup;
1028
+ }
1029
+ };
1030
+
1031
+ // src/lib/viewport-zoom.ts
1032
+ var viewportZoomHandlers = /* @__PURE__ */ new WeakMap();
1033
+ function applyViewportZoom(viewport, zoom) {
1034
+ if (viewport) viewportZoomHandlers.get(viewport)?.(zoom);
1035
+ }
1036
+ function registerViewportZoom(viewport, apply) {
1037
+ viewportZoomHandlers.set(viewport, apply);
1038
+ return () => {
1039
+ if (viewportZoomHandlers.get(viewport) === apply)
1040
+ viewportZoomHandlers.delete(viewport);
1041
+ };
1042
+ }
1043
+
222
1044
  // src/lib/zoom.ts
223
1045
  var USE_LAYOUT_ZOOM = typeof navigator !== "undefined" && /AppleWebKit/i.test(navigator.userAgent) && !/Chrome|Chromium|Android/i.test(navigator.userAgent);
224
1046
  var getFitWidthZoom = (containerWidth, viewports, zoomOptions) => {
@@ -255,6 +1077,11 @@ var PDFStore = createZustandContext(
255
1077
  renderColorMapRef.current = initialColorScheme === "dark" ? createDarkModeColorMap(initialDarkModeColors) : null;
256
1078
  return createStore((set, get) => ({
257
1079
  pdfDocumentProxy: initialState.pdfDocumentProxy,
1080
+ selection: new PDFSelectionController({
1081
+ pageCount: initialState.pdfDocumentProxy.numPages,
1082
+ loadPage: (page) => get().loadPdfPageProxy(page),
1083
+ revealPage: (page) => get().virtualizer?.scrollToIndex(page - 1, { align: "auto" })
1084
+ }),
258
1085
  zoom: initialState.zoom,
259
1086
  isZoomFitWidth: initialState.isZoomFitWidth ?? false,
260
1087
  zoomOptions: {
@@ -271,6 +1098,7 @@ var PDFStore = createZustandContext(
271
1098
  minZoom,
272
1099
  maxZoom
273
1100
  );
1101
+ applyViewportZoom(state.viewportRef.current, newZoom);
274
1102
  return newZoom === state.zoom && isZoomFitWidth === state.isZoomFitWidth ? state : { zoom: newZoom, isZoomFitWidth };
275
1103
  });
276
1104
  },
@@ -285,7 +1113,8 @@ var PDFStore = createZustandContext(
285
1113
  get().updateZoom(clampedZoom, true);
286
1114
  return clampedZoom;
287
1115
  },
288
- currentPage: 1,
1116
+ currentPage: initialState.initialPage ?? 1,
1117
+ initialPage: initialState.initialPage ?? 1,
289
1118
  setCurrentPage: (val) => {
290
1119
  set(
291
1120
  (state) => state.currentPage === val ? state : { currentPage: val }
@@ -310,9 +1139,18 @@ var PDFStore = createZustandContext(
310
1139
  );
311
1140
  },
312
1141
  pageProxies: initialState.pageProxies,
1142
+ pagesLoaded: initialState.pageProxies.length === initialState.pdfDocumentProxy.numPages,
1143
+ loadPdfPageProxies: () => initialState.pageResources ? initialState.pageResources.loadAll() : Promise.resolve(get().pageProxies),
1144
+ isPageLoaded: (pageNumber) => Boolean(
1145
+ initialState.pageResources?.get(pageNumber) ?? get().pageProxies[pageNumber - 1]
1146
+ ),
1147
+ loadPdfPageProxy: (pageNumber) => initialState.pageResources ? initialState.pageResources.load(pageNumber) : Promise.resolve().then(() => get().getPdfPageProxy(pageNumber)),
313
1148
  getPdfPageProxy: (pageNumber) => {
314
- const proxy = get().pageProxies[pageNumber - 1];
315
- if (!proxy) throw new Error(`Page ${pageNumber} does not exist`);
1149
+ const proxy = initialState.pageResources?.get(pageNumber) ?? get().pageProxies[pageNumber - 1];
1150
+ if (!proxy)
1151
+ throw new Error(
1152
+ initialState.pageResources && Number.isInteger(pageNumber) && pageNumber >= 1 && pageNumber <= initialState.pdfDocumentProxy.numPages ? `Page ${pageNumber} is not loaded. Await loadPdfPageProxy(${pageNumber}) first.` : `Page ${pageNumber} does not exist`
1153
+ );
316
1154
  return proxy;
317
1155
  },
318
1156
  textContent: [],
@@ -665,23 +1503,44 @@ var AnnotationTooltip = ({
665
1503
  };
666
1504
  var createAnnotationStore = () => createStore((set) => ({
667
1505
  annotations: [],
668
- addAnnotation: (annotation) => set((state) => ({
669
- annotations: [...state.annotations, annotation]
670
- })),
671
- updateAnnotation: (id, updates) => set((state) => ({
672
- annotations: state.annotations.map(
673
- (annotation) => annotation.id === id ? {
1506
+ addAnnotation: (annotation) => set((state) => {
1507
+ const annotations = [...state.annotations, annotation];
1508
+ updatePageIndex(state.annotations, annotations, /* @__PURE__ */ new Map(), [
1509
+ annotation
1510
+ ]);
1511
+ return { annotations };
1512
+ }),
1513
+ updateAnnotation: (id, updates) => set((state) => {
1514
+ const changes = /* @__PURE__ */ new Map();
1515
+ const keys = Object.keys(updates);
1516
+ const annotations = state.annotations.map((annotation) => {
1517
+ if (annotation.id !== id || keys.every((key) => Object.is(annotation[key], updates[key])))
1518
+ return annotation;
1519
+ const updated = changes.get(annotation) ?? {
674
1520
  ...annotation,
675
1521
  ...updates
676
- } : annotation
677
- )
678
- })),
679
- deleteAnnotation: (id) => set((state) => ({
680
- annotations: state.annotations.filter(
681
- (annotation) => annotation.id !== id
682
- )
683
- })),
684
- setAnnotations: (annotations) => set({ annotations })
1522
+ };
1523
+ changes.set(annotation, updated);
1524
+ return updated;
1525
+ });
1526
+ if (!changes.size) return state;
1527
+ updatePageIndex(state.annotations, annotations, changes);
1528
+ return { annotations };
1529
+ }),
1530
+ deleteAnnotation: (id) => set((state) => {
1531
+ const changes = /* @__PURE__ */ new Map();
1532
+ const annotations = state.annotations.filter((annotation) => {
1533
+ if (annotation.id !== id) return true;
1534
+ changes.set(annotation, null);
1535
+ return false;
1536
+ });
1537
+ if (!changes.size) return state;
1538
+ updatePageIndex(state.annotations, annotations, changes);
1539
+ return { annotations };
1540
+ }),
1541
+ setAnnotations: (annotations) => set(
1542
+ (state) => state.annotations === annotations ? state : { annotations }
1543
+ )
685
1544
  }));
686
1545
  var AnnotationsStore = createZustandContext(
687
1546
  () => createAnnotationStore()
@@ -713,9 +1572,7 @@ function annotationsByPage(annotations) {
713
1572
  if (index) return index;
714
1573
  index = /* @__PURE__ */ new Map();
715
1574
  for (const annotation of annotations) {
716
- const pages = /* @__PURE__ */ new Set([annotation.pageNumber]);
717
- for (const rect of annotation.highlights) pages.add(rect.pageNumber);
718
- for (const rect of annotation.underlines ?? []) pages.add(rect.pageNumber);
1575
+ const pages = annotationPages(annotation);
719
1576
  for (const page of pages) {
720
1577
  let values = index.get(page);
721
1578
  if (!values) {
@@ -728,6 +1585,45 @@ function annotationsByPage(annotations) {
728
1585
  indexes.set(annotations, index);
729
1586
  return index;
730
1587
  }
1588
+ function annotationPages(annotation) {
1589
+ const pages = /* @__PURE__ */ new Set([annotation.pageNumber]);
1590
+ for (const rect of annotation.highlights) pages.add(rect.pageNumber);
1591
+ for (const rect of annotation.underlines ?? []) pages.add(rect.pageNumber);
1592
+ return pages;
1593
+ }
1594
+ function isOnPage(annotation, page) {
1595
+ return annotation.pageNumber === page || annotation.highlights.some((rect) => rect.pageNumber === page) || annotation.underlines?.some((rect) => rect.pageNumber === page);
1596
+ }
1597
+ function updatePageIndex(previous, next, changes, appended = []) {
1598
+ const previousIndex = indexes.get(previous);
1599
+ if (!previousIndex) return;
1600
+ const index = new Map(previousIndex);
1601
+ const affected = /* @__PURE__ */ new Set();
1602
+ const movedTo = /* @__PURE__ */ new Set();
1603
+ for (const [before, after] of changes) {
1604
+ const oldPages = annotationPages(before);
1605
+ for (const page of oldPages) affected.add(page);
1606
+ if (after)
1607
+ for (const page of annotationPages(after)) {
1608
+ affected.add(page);
1609
+ if (!oldPages.has(page)) movedTo.add(page);
1610
+ }
1611
+ }
1612
+ for (const page of affected) {
1613
+ const values = movedTo.has(page) ? next.filter((annotation) => isOnPage(annotation, page)) : (previousIndex.get(page) ?? []).flatMap((annotation) => {
1614
+ if (!changes.has(annotation)) return [annotation];
1615
+ const updated = changes.get(annotation);
1616
+ return updated && isOnPage(updated, page) ? [updated] : [];
1617
+ });
1618
+ if (values.length) index.set(page, values);
1619
+ else index.delete(page);
1620
+ }
1621
+ for (const annotation of appended)
1622
+ for (const page of annotationPages(annotation)) {
1623
+ index.set(page, [...index.get(page) ?? [], annotation]);
1624
+ }
1625
+ indexes.set(next, index);
1626
+ }
731
1627
  function usePageAnnotations(pageNumber) {
732
1628
  return useAnnotations(
733
1629
  useShallow(
@@ -1035,7 +1931,7 @@ var usePdfJump = () => {
1035
1931
  }
1036
1932
  let scrollOffset;
1037
1933
  if (align === "center") {
1038
- const viewportHeight = virtualizer.scrollElement?.clientHeight || 0;
1934
+ const viewportHeight = virtualizer.scrollRect?.height ?? 0;
1039
1935
  const rectCenter = pageStart + rectTop + rectHeight / 2;
1040
1936
  scrollOffset = rectCenter - viewportHeight / 2;
1041
1937
  } else {
@@ -2108,6 +3004,69 @@ function applyContextRecolor(ctx, map, options) {
2108
3004
  function removeContextRecolor(ctx) {
2109
3005
  ctx[RECOLOR_CLEANUP]?.();
2110
3006
  }
3007
+
3008
+ // src/lib/viewport-invalidation.ts
3009
+ var viewportInvalidationRegistry = /* @__PURE__ */ new WeakMap();
3010
+ var notifyCallbacks = (entry) => {
3011
+ entry.rafId = null;
3012
+ entry.callbacks.forEach((callback) => {
3013
+ callback();
3014
+ });
3015
+ };
3016
+ var scheduleInvalidation = (entry) => {
3017
+ if (entry.rafId !== null) {
3018
+ return;
3019
+ }
3020
+ entry.rafId = requestAnimationFrame(() => {
3021
+ notifyCallbacks(entry);
3022
+ });
3023
+ };
3024
+ var subscribeToViewportInvalidation = (viewport, callback) => {
3025
+ let entry = viewportInvalidationRegistry.get(viewport);
3026
+ if (!entry) {
3027
+ const callbacks = /* @__PURE__ */ new Set();
3028
+ const schedule = () => {
3029
+ const currentEntry = viewportInvalidationRegistry.get(viewport);
3030
+ if (!currentEntry) {
3031
+ return;
3032
+ }
3033
+ scheduleInvalidation(currentEntry);
3034
+ };
3035
+ entry = {
3036
+ callbacks,
3037
+ scrollListener: schedule,
3038
+ windowResizeListener: schedule,
3039
+ resizeObserver: typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(schedule),
3040
+ rafId: null
3041
+ };
3042
+ viewport.addEventListener("scroll", entry.scrollListener, {
3043
+ passive: true
3044
+ });
3045
+ window.addEventListener("resize", entry.windowResizeListener, {
3046
+ passive: true
3047
+ });
3048
+ entry.resizeObserver?.observe(viewport);
3049
+ viewportInvalidationRegistry.set(viewport, entry);
3050
+ }
3051
+ entry.callbacks.add(callback);
3052
+ return () => {
3053
+ const currentEntry = viewportInvalidationRegistry.get(viewport);
3054
+ if (!currentEntry) {
3055
+ return;
3056
+ }
3057
+ currentEntry.callbacks.delete(callback);
3058
+ if (currentEntry.callbacks.size > 0) {
3059
+ return;
3060
+ }
3061
+ if (currentEntry.rafId !== null) {
3062
+ cancelAnimationFrame(currentEntry.rafId);
3063
+ }
3064
+ viewport.removeEventListener("scroll", currentEntry.scrollListener);
3065
+ window.removeEventListener("resize", currentEntry.windowResizeListener);
3066
+ currentEntry.resizeObserver?.disconnect();
3067
+ viewportInvalidationRegistry.delete(viewport);
3068
+ };
3069
+ };
2111
3070
  var listeners = /* @__PURE__ */ new Set();
2112
3071
  var media;
2113
3072
  var getSnapshot = () => Math.min(window.devicePixelRatio || 1, 3);
@@ -2369,7 +3328,53 @@ var useCanvasLayer = ({ background }) => {
2369
3328
  releaseBuffer();
2370
3329
  throw error;
2371
3330
  }
3331
+ let continuation = null;
3332
+ let idleTimer = null;
3333
+ let unsubscribeViewport = null;
3334
+ let priorityStopped = false;
3335
+ const clearPriorityWait = () => {
3336
+ if (idleTimer !== null) clearTimeout(idleTimer);
3337
+ idleTimer = null;
3338
+ unsubscribeViewport?.();
3339
+ unsubscribeViewport = null;
3340
+ };
3341
+ const resumeWhenVisible = () => {
3342
+ if (priorityStopped || !continuation) return;
3343
+ const { virtualizer, viewportRef } = store.getState();
3344
+ const item = virtualizer?.getVirtualItems().find((candidate) => candidate.index === pageNumber - 1);
3345
+ const offset3 = virtualizer?.scrollOffset;
3346
+ const height = virtualizer?.scrollRect?.height;
3347
+ if (virtualizer?.isScrolling && item && offset3 != null && height && (item.end < offset3 || item.start > offset3 + height)) {
3348
+ if (!unsubscribeViewport && viewportRef.current) {
3349
+ unsubscribeViewport = subscribeToViewportInvalidation(
3350
+ viewportRef.current,
3351
+ resumeWhenVisible
3352
+ );
3353
+ }
3354
+ if (idleTimer === null) {
3355
+ idleTimer = setTimeout(() => {
3356
+ idleTimer = null;
3357
+ resumeWhenVisible();
3358
+ }, 160);
3359
+ }
3360
+ return;
3361
+ }
3362
+ clearPriorityWait();
3363
+ const proceed = continuation;
3364
+ continuation = null;
3365
+ proceed();
3366
+ };
3367
+ const stopPriority = () => {
3368
+ priorityStopped = true;
3369
+ continuation = null;
3370
+ clearPriorityWait();
3371
+ };
3372
+ renderingTask.onContinue = (proceed) => {
3373
+ continuation = proceed;
3374
+ resumeWhenVisible();
3375
+ };
2372
3376
  renderingTask.promise.then(() => {
3377
+ stopPriority();
2373
3378
  if (cancelled) {
2374
3379
  restoreRecolor?.();
2375
3380
  releaseBuffer();
@@ -2414,6 +3419,7 @@ var useCanvasLayer = ({ background }) => {
2414
3419
  releaseBuffer();
2415
3420
  }
2416
3421
  }).catch((error) => {
3422
+ stopPriority();
2417
3423
  restoreRecolor?.();
2418
3424
  releaseBuffer();
2419
3425
  if (cancelled) return;
@@ -2423,102 +3429,38 @@ var useCanvasLayer = ({ background }) => {
2423
3429
  });
2424
3430
  return () => {
2425
3431
  cancelled = true;
3432
+ stopPriority();
2426
3433
  void renderingTask.cancel();
2427
3434
  };
2428
- }, [
2429
- pdfPageProxy,
2430
- background,
2431
- dpr,
2432
- zoom,
2433
- isResizing,
2434
- isPinching,
2435
- pageNumber,
2436
- markPageRendered,
2437
- docId,
2438
- recolor,
2439
- recolorKey,
2440
- store
2441
- ]);
2442
- useEffect(() => {
2443
- const canvas = canvasRef.current;
2444
- return () => {
2445
- unmarkPageRendered(pageNumber);
2446
- if (canvas) {
2447
- canvas.width = 0;
2448
- canvas.height = 0;
2449
- }
2450
- paintedRef.current = null;
2451
- };
2452
- }, [pageNumber, unmarkPageRendered]);
2453
- return {
2454
- canvasRef
2455
- };
2456
- };
2457
-
2458
- // src/lib/viewport-invalidation.ts
2459
- var viewportInvalidationRegistry = /* @__PURE__ */ new WeakMap();
2460
- var notifyCallbacks = (entry) => {
2461
- entry.rafId = null;
2462
- entry.callbacks.forEach((callback) => {
2463
- callback();
2464
- });
2465
- };
2466
- var scheduleInvalidation = (entry) => {
2467
- if (entry.rafId !== null) {
2468
- return;
2469
- }
2470
- entry.rafId = requestAnimationFrame(() => {
2471
- notifyCallbacks(entry);
2472
- });
2473
- };
2474
- var subscribeToViewportInvalidation = (viewport, callback) => {
2475
- let entry = viewportInvalidationRegistry.get(viewport);
2476
- if (!entry) {
2477
- const callbacks = /* @__PURE__ */ new Set();
2478
- const schedule = () => {
2479
- const currentEntry = viewportInvalidationRegistry.get(viewport);
2480
- if (!currentEntry) {
2481
- return;
2482
- }
2483
- scheduleInvalidation(currentEntry);
2484
- };
2485
- entry = {
2486
- callbacks,
2487
- scrollListener: schedule,
2488
- windowResizeListener: schedule,
2489
- resizeObserver: typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(schedule),
2490
- rafId: null
3435
+ }, [
3436
+ pdfPageProxy,
3437
+ background,
3438
+ dpr,
3439
+ zoom,
3440
+ isResizing,
3441
+ isPinching,
3442
+ pageNumber,
3443
+ markPageRendered,
3444
+ docId,
3445
+ recolor,
3446
+ recolorKey,
3447
+ store
3448
+ ]);
3449
+ useEffect(() => {
3450
+ const canvas = canvasRef.current;
3451
+ return () => {
3452
+ unmarkPageRendered(pageNumber);
3453
+ if (canvas) {
3454
+ canvas.width = 0;
3455
+ canvas.height = 0;
3456
+ }
3457
+ paintedRef.current = null;
2491
3458
  };
2492
- viewport.addEventListener("scroll", entry.scrollListener, {
2493
- passive: true
2494
- });
2495
- window.addEventListener("resize", entry.windowResizeListener, {
2496
- passive: true
2497
- });
2498
- entry.resizeObserver?.observe(viewport);
2499
- viewportInvalidationRegistry.set(viewport, entry);
2500
- }
2501
- entry.callbacks.add(callback);
2502
- return () => {
2503
- const currentEntry = viewportInvalidationRegistry.get(viewport);
2504
- if (!currentEntry) {
2505
- return;
2506
- }
2507
- currentEntry.callbacks.delete(callback);
2508
- if (currentEntry.callbacks.size > 0) {
2509
- return;
2510
- }
2511
- if (currentEntry.rafId !== null) {
2512
- cancelAnimationFrame(currentEntry.rafId);
2513
- }
2514
- viewport.removeEventListener("scroll", currentEntry.scrollListener);
2515
- window.removeEventListener("resize", currentEntry.windowResizeListener);
2516
- currentEntry.resizeObserver?.disconnect();
2517
- viewportInvalidationRegistry.delete(viewport);
3459
+ }, [pageNumber, unmarkPageRendered]);
3460
+ return {
3461
+ canvasRef
2518
3462
  };
2519
3463
  };
2520
-
2521
- // src/hooks/layers/useDetailCanvasLayer.tsx
2522
3464
  var DETAIL_RENDER_IDLE_MS = 80;
2523
3465
  var SCROLL_POLL_MS = 160;
2524
3466
  var ZOOM_DEBOUNCE_MS = 200;
@@ -2976,6 +3918,114 @@ var mergeTextRuns = (rects) => {
2976
3918
  });
2977
3919
  };
2978
3920
 
3921
+ // src/lib/selection-text.ts
3922
+ function textLayer(node) {
3923
+ const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
3924
+ return element?.closest(".textLayer[data-page-number]") ?? null;
3925
+ }
3926
+ function getPdfSelectionText(selection, container, options = {}) {
3927
+ if (!selection || selection.isCollapsed || selection.rangeCount !== 1)
3928
+ return null;
3929
+ const range = selection.getRangeAt(0);
3930
+ const first = textLayer(range.startContainer);
3931
+ const last = textLayer(range.endContainer);
3932
+ if (!first || !last || !container.contains(first) || !container.contains(last))
3933
+ return null;
3934
+ const layers = [
3935
+ ...container.querySelectorAll(".textLayer[data-page-number]")
3936
+ ].filter((layer) => range.intersectsNode(layer));
3937
+ let previousPage;
3938
+ for (const layer of layers) {
3939
+ const page = Number(layer.dataset.pageNumber);
3940
+ if (!Number.isInteger(page) || page < 1 || previousPage !== void 0 && page !== previousPage + 1)
3941
+ return null;
3942
+ previousPage = page;
3943
+ }
3944
+ const selectedLayers = new Set(layers);
3945
+ const root = range.commonAncestorContainer;
3946
+ const walker = container.ownerDocument.createTreeWalker(
3947
+ root,
3948
+ NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
3949
+ {
3950
+ acceptNode(node2) {
3951
+ if (node2.nodeType !== Node.TEXT_NODE && node2.tagName !== "BR")
3952
+ return NodeFilter.FILTER_SKIP;
3953
+ const layer = textLayer(node2);
3954
+ return layer && selectedLayers.has(layer) && range.intersectsNode(node2) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
3955
+ }
3956
+ }
3957
+ );
3958
+ const chunks = [];
3959
+ let previousLayer = null;
3960
+ let trailingLineBreaks = 0;
3961
+ let node = root.nodeType === Node.TEXT_NODE ? root : walker.nextNode();
3962
+ while (node) {
3963
+ const layer = textLayer(node);
3964
+ const start = node === range.startContainer ? range.startOffset : 0;
3965
+ const end = node === range.endContainer ? range.endOffset : node.textContent?.length ?? 0;
3966
+ const text = node.nodeType === Node.TEXT_NODE ? node.textContent.slice(start, end) : "\n";
3967
+ if (text) {
3968
+ if (previousLayer && previousLayer !== layer && trailingLineBreaks < 2) {
3969
+ chunks.push("\n".repeat(2 - trailingLineBreaks));
3970
+ trailingLineBreaks = 2;
3971
+ }
3972
+ chunks.push(text);
3973
+ let trailing = 0;
3974
+ for (let i = text.length - 1; i >= 0 && text[i] === "\n"; i--) trailing++;
3975
+ trailingLineBreaks = trailing === text.length ? trailingLineBreaks + trailing : trailing;
3976
+ previousLayer = layer;
3977
+ }
3978
+ node = walker.nextNode();
3979
+ }
3980
+ return normalizeSelectionText(chunks.join(""), options);
3981
+ }
3982
+ var documents = /* @__PURE__ */ new WeakMap();
3983
+ function registerPdfCopy(container, options, read) {
3984
+ const owner = container.ownerDocument;
3985
+ let binding = documents.get(owner);
3986
+ if (!binding) {
3987
+ const registrations = /* @__PURE__ */ new Set();
3988
+ const listener = (event) => {
3989
+ if (event.defaultPrevented || !event.clipboardData) return;
3990
+ const target = event.target;
3991
+ if (target instanceof Element && target.closest(
3992
+ 'input, textarea, [contenteditable]:not([contenteditable="false"])'
3993
+ ))
3994
+ return;
3995
+ for (const registration2 of registrations) {
3996
+ const settings = registration2.options();
3997
+ if (settings === false) continue;
3998
+ const persistent = registration2.read?.(settings);
3999
+ if (registration2.read && persistent === void 0) {
4000
+ event.preventDefault();
4001
+ break;
4002
+ }
4003
+ const text = persistent ?? getPdfSelectionText(
4004
+ owner.getSelection(),
4005
+ registration2.container,
4006
+ settings
4007
+ );
4008
+ if (text === null) continue;
4009
+ event.clipboardData.setData("text/plain", text);
4010
+ event.preventDefault();
4011
+ break;
4012
+ }
4013
+ };
4014
+ binding = { registrations, listener };
4015
+ documents.set(owner, binding);
4016
+ owner.addEventListener("copy", listener);
4017
+ }
4018
+ const registration = { container, options, read };
4019
+ binding.registrations.add(registration);
4020
+ return () => {
4021
+ binding.registrations.delete(registration);
4022
+ if (!binding.registrations.size) {
4023
+ owner.removeEventListener("copy", binding.listener);
4024
+ documents.delete(owner);
4025
+ }
4026
+ };
4027
+ }
4028
+
2979
4029
  // src/hooks/useSelectionDimensions.tsx
2980
4030
  var MERGE_THRESHOLD = 2;
2981
4031
  var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
@@ -3023,7 +4073,19 @@ var mapSelectionRectsToLayers = (range) => {
3023
4073
  };
3024
4074
  var useSelectionDimensions = () => {
3025
4075
  const store = PDFStore.useContext();
4076
+ const getText = (options) => {
4077
+ if (store.getState().selection?.getSnapshot())
4078
+ return store.getState().selection.getText(options);
4079
+ const container = store.getState().viewportRef.current;
4080
+ return container ? getPdfSelectionText(
4081
+ container.ownerDocument.getSelection(),
4082
+ container,
4083
+ options
4084
+ ) : null;
4085
+ };
3026
4086
  const getAnnotationDimension = () => {
4087
+ if (store.getState().selection?.getSnapshot())
4088
+ return store.getState().selection.getSelection() ?? void 0;
3027
4089
  const selection = window.getSelection();
3028
4090
  if (!selection || selection.isCollapsed) return;
3029
4091
  const range = selection.getRangeAt(0);
@@ -3161,7 +4223,7 @@ var useSelectionDimensions = () => {
3161
4223
  underlines: consolidateUnderlines(underlineRects).sort(
3162
4224
  (a, b) => a.pageNumber - b.pageNumber
3163
4225
  ),
3164
- text: range.toString().trim(),
4226
+ text: (getText() ?? range.toString()).trim(),
3165
4227
  isCollapsed: false
3166
4228
  };
3167
4229
  };
@@ -3218,6 +4280,8 @@ var useSelectionDimensions = () => {
3218
4280
  return consolidated;
3219
4281
  };
3220
4282
  const getDimension = () => {
4283
+ if (store.getState().selection?.getSnapshot())
4284
+ return store.getState().selection.getSelection() ?? void 0;
3221
4285
  const selection = window.getSelection();
3222
4286
  if (!selection || selection.isCollapsed) return;
3223
4287
  const range = selection.getRangeAt(0);
@@ -3246,12 +4310,12 @@ var useSelectionDimensions = () => {
3246
4310
  });
3247
4311
  return {
3248
4312
  highlights: highlights.sort((a, b) => a.pageNumber - b.pageNumber),
3249
- text: range.toString().trim(),
4313
+ text: (getText() ?? range.toString()).trim(),
3250
4314
  isCollapsed: false
3251
4315
  };
3252
4316
  };
3253
4317
  const getSelection = () => getDimension();
3254
- return { getDimension, getSelection, getAnnotationDimension };
4318
+ return { getDimension, getSelection, getAnnotationDimension, getText };
3255
4319
  };
3256
4320
  var SelectionTooltip = ({ children }) => {
3257
4321
  const [isOpen, setIsOpen] = useState(false);
@@ -3386,6 +4450,7 @@ var SelectionTooltip = ({ children }) => {
3386
4450
  return /* @__PURE__ */ jsx(Fragment, { children: isOpen && /* @__PURE__ */ jsx(
3387
4451
  "div",
3388
4452
  {
4453
+ "data-selection-tooltip": "",
3389
4454
  ref: refs.setFloating,
3390
4455
  style: {
3391
4456
  ...floatingStyles
@@ -3801,13 +4866,13 @@ var createTextSelectionManager = () => {
3801
4866
  }
3802
4867
  selectionChangeAbortController = new AbortController();
3803
4868
  const { signal } = selectionChangeAbortController;
3804
- const reset = (endDiv, textLayer) => {
3805
- if (endDiv.parentNode !== textLayer) {
3806
- textLayer.appendChild(endDiv);
4869
+ const reset = (endDiv, textLayer2) => {
4870
+ if (endDiv.parentNode !== textLayer2) {
4871
+ textLayer2.appendChild(endDiv);
3807
4872
  }
3808
4873
  endDiv.style.width = "";
3809
4874
  endDiv.style.height = "";
3810
- textLayer.classList.remove("selecting");
4875
+ textLayer2.classList.remove("selecting");
3811
4876
  };
3812
4877
  document.addEventListener(
3813
4878
  "pointerdown",
@@ -3933,12 +4998,14 @@ var useTextLayer = () => {
3933
4998
  const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
3934
4999
  const viewportRef = usePdf((state) => state.viewportRef);
3935
5000
  const store = PDFStore.useContext();
5001
+ const selection = store.getState().selection;
3936
5002
  useEffect(() => {
3937
5003
  const textContainer = textContainerRef.current;
3938
5004
  if (!textContainer) {
3939
5005
  return;
3940
5006
  }
3941
5007
  let isCancelled = false;
5008
+ let unregisterSelection;
3942
5009
  let buildTimer = null;
3943
5010
  let unsubscribe = null;
3944
5011
  let built = false;
@@ -3955,13 +5022,13 @@ var useTextLayer = () => {
3955
5022
  void import('pdfjs-dist/legacy/build/pdf.mjs').then(({ TextLayer: TextLayer2 }) => {
3956
5023
  if (isCancelled || gen !== buildGen || textContainerRef.current !== textContainer)
3957
5024
  return;
3958
- const textLayer = new TextLayer2({
5025
+ const textLayer2 = new TextLayer2({
3959
5026
  textContentSource: pdfPageProxy.streamTextContent(),
3960
5027
  container: textContainer,
3961
5028
  viewport: pdfPageProxy.getViewport({ scale: 1 })
3962
5029
  });
3963
- textLayerRef.current = textLayer;
3964
- return textLayer.render();
5030
+ textLayerRef.current = textLayer2;
5031
+ return textLayer2.render();
3965
5032
  }).then(() => {
3966
5033
  if (isCancelled || gen !== buildGen || textContainerRef.current !== textContainer) {
3967
5034
  return;
@@ -3970,6 +5037,16 @@ var useTextLayer = () => {
3970
5037
  endOfContent.className = "endOfContent";
3971
5038
  textContainer.appendChild(endOfContent);
3972
5039
  bindMouseEvents(textContainer, endOfContent);
5040
+ const viewport = pdfPageProxy.getViewport({ scale: 1 });
5041
+ const unrotated = pdfPageProxy.getViewport({ scale: 1, rotation: 0 });
5042
+ textContainer.style.width = `${unrotated.width}px`;
5043
+ textContainer.style.height = `${unrotated.height}px`;
5044
+ unregisterSelection = selection?.registerLayer(
5045
+ textContainer,
5046
+ pageNumber,
5047
+ viewport.width,
5048
+ viewport.height
5049
+ );
3973
5050
  built = true;
3974
5051
  building = false;
3975
5052
  unsubscribe?.();
@@ -3983,6 +5060,12 @@ var useTextLayer = () => {
3983
5060
  };
3984
5061
  const schedule = () => {
3985
5062
  if (built) return;
5063
+ if (selection?.isDragging || selection?.includesPage(pageNumber)) {
5064
+ if (buildTimer) clearTimeout(buildTimer);
5065
+ buildTimer = null;
5066
+ if (!building) build();
5067
+ return;
5068
+ }
3986
5069
  if (building) {
3987
5070
  buildGen++;
3988
5071
  building = false;
@@ -4012,9 +5095,15 @@ var useTextLayer = () => {
4012
5095
  unsubscribe = subscribeToViewportInvalidation(scrollViewport, schedule);
4013
5096
  };
4014
5097
  attachScrollSubscription();
5098
+ const unsubscribeSelection = selection?.subscribe(() => {
5099
+ if (selection.isDragging || selection.includesPage(pageNumber))
5100
+ schedule();
5101
+ });
4015
5102
  schedule();
4016
5103
  return () => {
4017
5104
  isCancelled = true;
5105
+ unregisterSelection?.();
5106
+ unsubscribeSelection?.();
4018
5107
  if (buildTimer) clearTimeout(buildTimer);
4019
5108
  if (attachRaf !== null) cancelAnimationFrame(attachRaf);
4020
5109
  unsubscribe?.();
@@ -4027,7 +5116,13 @@ var useTextLayer = () => {
4027
5116
  delete textContainer._cleanupTextSelection;
4028
5117
  }
4029
5118
  };
4030
- }, [pdfPageProxy.streamTextContent, pdfPageProxy.getViewport, viewportRef]);
5119
+ }, [
5120
+ pdfPageProxy.streamTextContent,
5121
+ pdfPageProxy.getViewport,
5122
+ viewportRef,
5123
+ selection,
5124
+ pageNumber
5125
+ ]);
4031
5126
  useEffect(() => {
4032
5127
  const textContainer = textContainerRef.current;
4033
5128
  if (!textContainer) return;
@@ -4046,7 +5141,10 @@ var useTextLayer = () => {
4046
5141
  show();
4047
5142
  };
4048
5143
  const hideAndDebounce = () => {
4049
- if (selecting) return;
5144
+ if (selecting || selection?.isDragging || selection?.includesPage(pageNumber)) {
5145
+ show();
5146
+ return;
5147
+ }
4050
5148
  textContainer.style.visibility = "hidden";
4051
5149
  if (settleTimer) clearTimeout(settleTimer);
4052
5150
  settleTimer = setTimeout(restoreWhenSettled, TEXT_SCROLL_SETTLE_MS);
@@ -4114,7 +5212,7 @@ var useTextLayer = () => {
4114
5212
  if (settleTimer) clearTimeout(settleTimer);
4115
5213
  show();
4116
5214
  };
4117
- }, [viewportRef, store]);
5215
+ }, [viewportRef, store, selection, pageNumber]);
4118
5216
  return {
4119
5217
  textContainerRef,
4120
5218
  pageNumber: pdfPageProxy.pageNumber
@@ -4311,6 +5409,37 @@ var Outline = ({
4311
5409
  });
4312
5410
  }) });
4313
5411
  };
5412
+ function PageLoadingBoundary({
5413
+ pageNumber,
5414
+ children,
5415
+ fallback = null
5416
+ }) {
5417
+ const loaded = usePdf((state) => state.isPageLoaded(pageNumber));
5418
+ const loadPage = usePdf((state) => state.loadPdfPageProxy);
5419
+ const [failure, setFailure] = useState(null);
5420
+ const [attempt, setAttempt] = useState(0);
5421
+ useEffect(() => {
5422
+ if (loaded) return;
5423
+ let disposed = false;
5424
+ setFailure(null);
5425
+ void loadPage(pageNumber).catch((error) => {
5426
+ if (!disposed) setFailure({ pageNumber, error });
5427
+ });
5428
+ return () => {
5429
+ disposed = true;
5430
+ };
5431
+ }, [pageNumber, loaded, loadPage, attempt]);
5432
+ if (loaded) return children;
5433
+ if (failure?.pageNumber === pageNumber)
5434
+ return /* @__PURE__ */ jsxs("div", { role: "alert", children: [
5435
+ "Unable to load page ",
5436
+ pageNumber,
5437
+ ".",
5438
+ " ",
5439
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setAttempt((value) => value + 1), children: "Retry" })
5440
+ ] });
5441
+ return fallback;
5442
+ }
4314
5443
  var Page = memo(
4315
5444
  ({
4316
5445
  children,
@@ -4318,14 +5447,16 @@ var Page = memo(
4318
5447
  style,
4319
5448
  ...props
4320
5449
  }) => {
4321
- const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
5450
+ const loaded = usePdf((state) => state.isPageLoaded(pageNumber));
5451
+ const getPage = usePdf((state) => state.getPdfPageProxy);
5452
+ const proxy = loaded ? getPage(pageNumber) : void 0;
4322
5453
  const viewport = usePdf((state) => state.viewports[pageNumber - 1]);
4323
5454
  const colorScheme = usePdf((state) => state.colorScheme);
4324
5455
  const darkModeColors = usePdf((state) => state.darkModeColors);
4325
5456
  const pageBackground = colorScheme === "dark" ? createDarkModeColorMap(darkModeColors)("#ffffff") : "white";
4326
- const width = viewport?.width ?? (pdfPageProxy.view[2] ?? 0) - (pdfPageProxy.view[0] ?? 0);
4327
- const height = viewport?.height ?? (pdfPageProxy.view[3] ?? 0) - (pdfPageProxy.view[1] ?? 0);
4328
- return /* @__PURE__ */ jsx(PDFPageNumberContext.Provider, { value: pdfPageProxy.pageNumber, children: /* @__PURE__ */ jsx(
5457
+ const width = viewport?.width ?? (proxy?.view[2] ?? 0) - (proxy?.view[0] ?? 0);
5458
+ const height = viewport?.height ?? (proxy?.view[3] ?? 0) - (proxy?.view[1] ?? 0);
5459
+ return /* @__PURE__ */ jsx(PDFPageNumberContext.Provider, { value: pageNumber, children: /* @__PURE__ */ jsx(
4329
5460
  Primitive.div,
4330
5461
  {
4331
5462
  style: {
@@ -4343,8 +5474,9 @@ var Page = memo(
4343
5474
  height,
4344
5475
  ...style
4345
5476
  },
5477
+ "aria-busy": !loaded || void 0,
4346
5478
  ...props,
4347
- children
5479
+ children: /* @__PURE__ */ jsx(PageLoadingBoundary, { pageNumber, children })
4348
5480
  }
4349
5481
  )
4350
5482
  }
@@ -4465,6 +5597,28 @@ var addEventListenerOptions = {
4465
5597
  };
4466
5598
  var useObserveElement = () => {
4467
5599
  const store = PDFStore.useContext();
5600
+ const observeElementRect$1 = (instance, cb) => {
5601
+ let physicalRect;
5602
+ const publish = () => {
5603
+ if (!physicalRect) return;
5604
+ const zoom = store.getState().zoom;
5605
+ cb({
5606
+ width: physicalRect.width / zoom,
5607
+ height: physicalRect.height / zoom
5608
+ });
5609
+ };
5610
+ const stopObserving = observeElementRect(instance, (rect) => {
5611
+ physicalRect = rect;
5612
+ publish();
5613
+ });
5614
+ const unsubscribe = store.subscribe((state, previous) => {
5615
+ if (state.zoom !== previous.zoom) publish();
5616
+ });
5617
+ return () => {
5618
+ stopObserving?.();
5619
+ unsubscribe();
5620
+ };
5621
+ };
4468
5622
  const observeElementOffset = (instance, cb) => {
4469
5623
  const element = instance.scrollElement;
4470
5624
  if (!element) {
@@ -4474,33 +5628,46 @@ var useObserveElement = () => {
4474
5628
  if (!targetWindow) {
4475
5629
  return;
4476
5630
  }
4477
- let offset3 = 0;
4478
- const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : debounce(
4479
- targetWindow,
4480
- () => {
4481
- cb(offset3, false);
4482
- },
4483
- instance.options.isScrollingResetDelay
4484
- );
4485
- const createHandler = (isScrolling) => () => {
5631
+ let isScrolling = false;
5632
+ let idleTimer;
5633
+ const needsIdleFallback = !instance.options.useScrollendEvent || !supportsScrollend;
5634
+ const publish = (scrolling) => {
5635
+ isScrolling = scrolling;
4486
5636
  const { horizontal, isRtl } = instance.options;
4487
- offset3 = horizontal ? element.scrollLeft * (isRtl && -1 || 1) : element.scrollTop;
4488
- const zoom = store.getState().zoom;
4489
- offset3 = offset3 / zoom;
4490
- fallback();
4491
- cb(offset3, isScrolling);
5637
+ const physicalOffset = horizontal ? element.scrollLeft * (isRtl ? -1 : 1) : element.scrollTop;
5638
+ cb(physicalOffset / store.getState().zoom, isScrolling);
5639
+ };
5640
+ const clearIdleTimer = () => {
5641
+ if (idleTimer !== void 0) targetWindow.clearTimeout(idleTimer);
5642
+ idleTimer = void 0;
5643
+ };
5644
+ const createHandler = (scrolling) => () => {
5645
+ clearIdleTimer();
5646
+ publish(scrolling);
5647
+ if (scrolling && needsIdleFallback) {
5648
+ idleTimer = targetWindow.setTimeout(() => {
5649
+ idleTimer = void 0;
5650
+ publish(false);
5651
+ }, instance.options.isScrollingResetDelay);
5652
+ }
4492
5653
  };
4493
5654
  const handler = createHandler(true);
4494
5655
  const endHandler = createHandler(false);
4495
5656
  endHandler();
5657
+ const unsubscribe = store.subscribe((state, previous) => {
5658
+ if (state.zoom !== previous.zoom) publish(isScrolling);
5659
+ });
4496
5660
  element.addEventListener("scroll", handler, addEventListenerOptions);
4497
5661
  element.addEventListener("scrollend", endHandler, addEventListenerOptions);
4498
5662
  return () => {
5663
+ unsubscribe();
5664
+ clearIdleTimer();
4499
5665
  element.removeEventListener("scroll", handler);
4500
5666
  element.removeEventListener("scrollend", endHandler);
4501
5667
  };
4502
5668
  };
4503
5669
  return {
5670
+ observeElementRect: observeElementRect$1,
4504
5671
  observeElementOffset
4505
5672
  };
4506
5673
  };
@@ -4510,7 +5677,11 @@ var useScrollFn = () => {
4510
5677
  (_offset, canSmooth, instance) => {
4511
5678
  const zoom = store.getState().zoom;
4512
5679
  const offset3 = _offset * zoom;
4513
- elementScroll(offset3, canSmooth, instance);
5680
+ elementScroll(
5681
+ offset3,
5682
+ { ...canSmooth, adjustments: (canSmooth.adjustments ?? 0) * zoom },
5683
+ instance
5684
+ );
4514
5685
  },
4515
5686
  [store]
4516
5687
  );
@@ -4628,7 +5799,7 @@ function _toPrimitive(input, hint) {
4628
5799
  if (typeof input !== "object" || input === null) return input;
4629
5800
  var prim = input[Symbol.toPrimitive];
4630
5801
  if (prim !== void 0) {
4631
- var res = prim.call(input, hint || "default");
5802
+ var res = prim.call(input, hint);
4632
5803
  if (typeof res !== "object") return res;
4633
5804
  throw new TypeError("@@toPrimitive must return a primitive value.");
4634
5805
  }
@@ -6485,6 +7656,8 @@ var useViewportContainer = ({
6485
7656
  });
6486
7657
  const zoomRafRef = useRef(null);
6487
7658
  const lastPushedZoomRef = useRef(null);
7659
+ const initializedZoomRef = useRef(false);
7660
+ const appliedZoomRef = useRef(1);
6488
7661
  const updateTransform = useCallback(
6489
7662
  (zoomUpdate) => {
6490
7663
  if (!elementRef.current || !containerRef.current || !elementWrapperRef.current) {
@@ -6514,17 +7687,36 @@ var useViewportContainer = ({
6514
7687
  elementWrapperRef.current.style.height = `${naturalHeight * zoom2}px`;
6515
7688
  containerRef.current.scrollTop = translateY;
6516
7689
  containerRef.current.scrollLeft = translateX;
6517
- if (zoomUpdate && zoomRafRef.current === null) {
6518
- zoomRafRef.current = requestAnimationFrame(() => {
6519
- zoomRafRef.current = null;
6520
- const z = transformations.current.zoom;
6521
- lastPushedZoomRef.current = z;
6522
- updateZoom(() => z);
6523
- });
7690
+ appliedZoomRef.current = zoom2;
7691
+ if (zoomUpdate) {
7692
+ lastPushedZoomRef.current = zoom2;
7693
+ updateZoom(() => zoom2);
6524
7694
  }
6525
7695
  },
6526
7696
  [containerRef, elementRef, elementWrapperRef, updateZoom, store]
6527
7697
  );
7698
+ useLayoutEffect(() => {
7699
+ const viewport = containerRef.current;
7700
+ if (!viewport) return;
7701
+ return registerViewportZoom(viewport, (nextZoom) => {
7702
+ if (initializedZoomRef.current && appliedZoomRef.current === nextZoom && zoomRafRef.current === null)
7703
+ return;
7704
+ if (zoomRafRef.current !== null) {
7705
+ cancelAnimationFrame(zoomRafRef.current);
7706
+ zoomRafRef.current = null;
7707
+ }
7708
+ lastPushedZoomRef.current = null;
7709
+ const previousZoom = initializedZoomRef.current ? appliedZoomRef.current : store.getState().zoom;
7710
+ const ratio = previousZoom > 0 && Number.isFinite(previousZoom) ? nextZoom / previousZoom : 1;
7711
+ transformations.current = {
7712
+ zoom: nextZoom,
7713
+ translateX: viewport.scrollLeft * ratio,
7714
+ translateY: viewport.scrollTop * ratio
7715
+ };
7716
+ initializedZoomRef.current = true;
7717
+ updateTransform();
7718
+ });
7719
+ }, [containerRef, store, updateTransform]);
6528
7720
  useEffect(() => {
6529
7721
  if (USE_LAYOUT_ZOOM && gestureTransformAppliedRef.current && !isPinching)
6530
7722
  updateTransform();
@@ -6537,6 +7729,16 @@ var useViewportContainer = ({
6537
7729
  };
6538
7730
  }, []);
6539
7731
  useEffect(() => {
7732
+ if (!initializedZoomRef.current && containerRef.current) {
7733
+ initializedZoomRef.current = true;
7734
+ transformations.current = {
7735
+ zoom,
7736
+ translateX: containerRef.current.scrollLeft,
7737
+ translateY: containerRef.current.scrollTop
7738
+ };
7739
+ updateTransform();
7740
+ return;
7741
+ }
6540
7742
  if (transformations.current.zoom === zoom || !containerRef.current) {
6541
7743
  return;
6542
7744
  }
@@ -6549,7 +7751,7 @@ var useViewportContainer = ({
6549
7751
  zoomRafRef.current = null;
6550
7752
  }
6551
7753
  lastPushedZoomRef.current = null;
6552
- const prevZoom = transformations.current.zoom;
7754
+ const prevZoom = appliedZoomRef.current;
6553
7755
  if (!prevZoom || !Number.isFinite(prevZoom)) {
6554
7756
  transformations.current = {
6555
7757
  translateX: containerRef.current.scrollLeft,
@@ -6689,11 +7891,26 @@ var useViewportContainer = ({
6689
7891
  translateY: newTranslateY
6690
7892
  };
6691
7893
  newMemo.lastZoom = newZoom;
6692
- updateTransform(true);
7894
+ if (zoomRafRef.current === null) {
7895
+ zoomRafRef.current = requestAnimationFrame(() => {
7896
+ zoomRafRef.current = null;
7897
+ updateTransform(true);
7898
+ });
7899
+ }
6693
7900
  return newMemo;
6694
7901
  },
6695
7902
  onPinchStart: () => setIsPinching(true),
6696
- onPinchEnd: () => setIsPinching(false)
7903
+ onPinchEnd: () => {
7904
+ const pendingFrame = zoomRafRef.current !== null;
7905
+ if (zoomRafRef.current !== null) {
7906
+ cancelAnimationFrame(zoomRafRef.current);
7907
+ zoomRafRef.current = null;
7908
+ }
7909
+ setIsPinching(false);
7910
+ if (pendingFrame || gestureTransformAppliedRef.current) {
7911
+ updateTransform(pendingFrame);
7912
+ }
7913
+ }
6697
7914
  },
6698
7915
  {
6699
7916
  target: containerRef
@@ -6741,15 +7958,34 @@ var Pages = ({
6741
7958
  virtualizerOptions = DEFAULT_VIRTUALIZER_OPTIONS,
6742
7959
  initialOffset,
6743
7960
  onOffsetChange,
7961
+ copyOptions,
6744
7962
  ...props
6745
7963
  }) => {
6746
7964
  const [tempItems, setTempItems] = useState([]);
6747
7965
  const viewports = usePdf((state) => state.viewports);
7966
+ const selection = usePdf((state) => state.selection);
6748
7967
  const numPages = usePdf((state) => state.pdfDocumentProxy.numPages);
7968
+ const initialPage = usePdf((state) => state.initialPage);
6749
7969
  const isPinching = usePdf((state) => state.isPinching);
6750
7970
  const elementWrapperRef = useRef(null);
6751
7971
  const elementRef = useRef(null);
6752
7972
  const containerRef = useRef(null);
7973
+ const copyOptionsRef = useRef(copyOptions);
7974
+ copyOptionsRef.current = copyOptions;
7975
+ useEffect(() => {
7976
+ const container = containerRef.current;
7977
+ if (!container) return;
7978
+ const disconnect = selection.connect(container);
7979
+ const unregisterCopy = registerPdfCopy(
7980
+ container,
7981
+ () => copyOptionsRef.current ?? {},
7982
+ (options) => selection.getSnapshot() ? selection.getText(options) ?? void 0 : null
7983
+ );
7984
+ return () => {
7985
+ unregisterCopy();
7986
+ disconnect();
7987
+ };
7988
+ }, [selection]);
6753
7989
  useViewportContainer({
6754
7990
  elementRef,
6755
7991
  elementWrapperRef,
@@ -6757,7 +7993,7 @@ var Pages = ({
6757
7993
  });
6758
7994
  const setVirtualizer = usePdf((state) => state.setVirtualizer);
6759
7995
  const { scrollToFn } = useScrollFn();
6760
- const { observeElementOffset } = useObserveElement();
7996
+ const { observeElementOffset, observeElementRect } = useObserveElement();
6761
7997
  const viewportsRef = useRef(viewports);
6762
7998
  viewportsRef.current = viewports;
6763
7999
  const estimateSize = useCallback(
@@ -6769,15 +8005,22 @@ var Pages = ({
6769
8005
  []
6770
8006
  // Stable — reads from ref
6771
8007
  );
8008
+ const [startingOffset] = useState(
8009
+ () => initialOffset ?? viewports.slice(0, initialPage - 1).reduce(
8010
+ (offset3, viewport) => offset3 + viewport.height + EXTRA_HEIGHT + gap,
8011
+ 0
8012
+ )
8013
+ );
6772
8014
  const virtualizer = useVirtualizer({
6773
8015
  count: numPages || 0,
6774
8016
  getScrollElement: () => containerRef.current,
6775
8017
  estimateSize,
6776
8018
  observeElementOffset,
8019
+ observeElementRect,
6777
8020
  overscan: virtualizerOptions?.overscan ?? 0,
6778
8021
  scrollToFn,
6779
8022
  gap,
6780
- initialOffset,
8023
+ initialOffset: startingOffset,
6781
8024
  // Never trust scrollend alone to reset isScrolling: a single missed
6782
8025
  // event (a documented browser flake — TanStack flipped this default to
6783
8026
  // false in later 3.x) would pin isScrolling=true forever, and every
@@ -6785,6 +8028,21 @@ var Pages = ({
6785
8028
  // false keeps the isScrollingResetDelay debounce active as a fallback.
6786
8029
  useScrollendEvent: false
6787
8030
  });
8031
+ const previousViewports = useRef(viewports);
8032
+ useLayoutEffect(() => {
8033
+ if (isPinching) return;
8034
+ const previous = previousViewports.current;
8035
+ previousViewports.current = viewports;
8036
+ if (previous === viewports) return;
8037
+ let resized = false;
8038
+ for (let index = 0; index < viewports.length; index++) {
8039
+ if (previous[index]?.height !== viewports[index]?.height) {
8040
+ virtualizer.resizeItem(index, viewports[index].height + EXTRA_HEIGHT);
8041
+ resized = true;
8042
+ }
8043
+ }
8044
+ if (resized) setTempItems([]);
8045
+ }, [viewports, virtualizer, isPinching]);
6788
8046
  useEffect(() => {
6789
8047
  if (onOffsetChange && virtualizer.scrollOffset)
6790
8048
  onOffsetChange(virtualizer.scrollOffset);
@@ -6845,6 +8103,7 @@ var Pages = ({
6845
8103
  Primitive.div,
6846
8104
  {
6847
8105
  ref: containerRef,
8106
+ tabIndex: 0,
6848
8107
  ...props,
6849
8108
  style: {
6850
8109
  display: "flex",
@@ -6931,6 +8190,164 @@ async function mapConcurrent(items, concurrency, map, isCancelled = () => false)
6931
8190
  return results;
6932
8191
  }
6933
8192
 
8193
+ // src/lib/page-resources.ts
8194
+ var PageResources = class {
8195
+ constructor(document2, firstPage, rotation, onError, concurrency = 16) {
8196
+ this.document = document2;
8197
+ this.rotation = rotation;
8198
+ this.onError = onError;
8199
+ this.concurrency = concurrency;
8200
+ this.pages.set(firstPage.pageNumber, firstPage);
8201
+ const viewport = this.viewportFor(firstPage);
8202
+ this.viewports = Array.from({ length: document2.numPages }, () => viewport);
8203
+ if (document2.numPages === 1) this.completePages = [firstPage];
8204
+ }
8205
+ pages = /* @__PURE__ */ new Map();
8206
+ requests = /* @__PURE__ */ new Map();
8207
+ queue = [];
8208
+ active = 0;
8209
+ disposed = false;
8210
+ listeners = /* @__PURE__ */ new Set();
8211
+ notification;
8212
+ viewports;
8213
+ completePages;
8214
+ viewportUpdates = /* @__PURE__ */ new Map();
8215
+ viewportFor(page) {
8216
+ return page.getViewport({
8217
+ scale: 1,
8218
+ rotation: this.rotation + (page.rotate || 0)
8219
+ });
8220
+ }
8221
+ get(pageNumber) {
8222
+ return this.pages.get(pageNumber);
8223
+ }
8224
+ getSnapshot = () => ({
8225
+ viewports: this.viewports,
8226
+ pageProxies: this.completePages
8227
+ });
8228
+ subscribe = (listener) => {
8229
+ this.listeners.add(listener);
8230
+ return () => {
8231
+ this.listeners.delete(listener);
8232
+ };
8233
+ };
8234
+ /** Visible pages move ahead of the background queue; in-flight requests are shared. */
8235
+ load = (pageNumber) => {
8236
+ if (this.disposed)
8237
+ return Promise.reject(new Error("Document page loading was cancelled"));
8238
+ if (!Number.isInteger(pageNumber) || pageNumber < 1 || pageNumber > this.document.numPages)
8239
+ return Promise.reject(
8240
+ new RangeError(`Page ${pageNumber} does not exist`)
8241
+ );
8242
+ const page = this.pages.get(pageNumber);
8243
+ if (page) return Promise.resolve(page);
8244
+ const existing = this.requests.get(pageNumber);
8245
+ if (existing) {
8246
+ const queued = this.queue.indexOf(pageNumber);
8247
+ if (queued > 0) {
8248
+ this.queue.splice(queued, 1);
8249
+ this.queue.unshift(pageNumber);
8250
+ }
8251
+ return existing.promise;
8252
+ }
8253
+ const request = this.createRequest(pageNumber);
8254
+ this.queue.unshift(pageNumber);
8255
+ this.pump();
8256
+ return request.promise;
8257
+ };
8258
+ createRequest(pageNumber) {
8259
+ let resolve;
8260
+ let reject;
8261
+ const promise = new Promise((res, rej) => {
8262
+ resolve = res;
8263
+ reject = rej;
8264
+ });
8265
+ const request = { promise, resolve, reject };
8266
+ this.requests.set(pageNumber, request);
8267
+ return request;
8268
+ }
8269
+ /** Start after the reader mounts, so unrelated page metadata cannot delay its first render. */
8270
+ start = () => {
8271
+ if (this.disposed) return;
8272
+ for (let pageNumber = 1; pageNumber <= this.document.numPages; pageNumber++) {
8273
+ if (this.pages.has(pageNumber) || this.requests.has(pageNumber)) continue;
8274
+ const request = this.createRequest(pageNumber);
8275
+ void request.promise.catch(() => {
8276
+ });
8277
+ this.queue.push(pageNumber);
8278
+ }
8279
+ this.pump();
8280
+ };
8281
+ loadAll = async () => {
8282
+ if (this.disposed) throw new Error("Document page loading was cancelled");
8283
+ if (this.completePages) return this.completePages;
8284
+ this.start();
8285
+ await Promise.all(
8286
+ Array.from({ length: this.document.numPages }, (_, index) => {
8287
+ const pageNumber = index + 1;
8288
+ return this.pages.get(pageNumber) ?? this.requests.get(pageNumber).promise;
8289
+ })
8290
+ );
8291
+ return this.completePages;
8292
+ };
8293
+ pump() {
8294
+ while (!this.disposed && this.active < this.concurrency && this.queue.length) {
8295
+ const pageNumber = this.queue.shift();
8296
+ const request = this.requests.get(pageNumber);
8297
+ this.active++;
8298
+ void Promise.resolve().then(() => {
8299
+ if (this.disposed)
8300
+ throw new Error("Document page loading was cancelled");
8301
+ return this.document.getPage(pageNumber);
8302
+ }).then((page) => {
8303
+ if (this.disposed) return;
8304
+ const viewport = this.viewportFor(page);
8305
+ this.pages.set(pageNumber, page);
8306
+ this.viewportUpdates.set(pageNumber - 1, viewport);
8307
+ if (this.pages.size === this.document.numPages) {
8308
+ this.completePages = Array.from(
8309
+ { length: this.document.numPages },
8310
+ (_, index) => this.pages.get(index + 1)
8311
+ );
8312
+ }
8313
+ this.requests.delete(pageNumber);
8314
+ request.resolve(page);
8315
+ this.notify();
8316
+ }).catch((error) => {
8317
+ if (!this.disposed) {
8318
+ this.requests.delete(pageNumber);
8319
+ request.reject(error);
8320
+ this.onError(error);
8321
+ }
8322
+ }).finally(() => {
8323
+ this.active--;
8324
+ this.pump();
8325
+ });
8326
+ }
8327
+ }
8328
+ notify() {
8329
+ if (this.notification !== void 0) return;
8330
+ this.notification = setTimeout(() => {
8331
+ this.notification = void 0;
8332
+ if (this.disposed) return;
8333
+ this.viewports = this.viewports.slice();
8334
+ for (const [index, viewport] of this.viewportUpdates)
8335
+ this.viewports[index] = viewport;
8336
+ this.viewportUpdates.clear();
8337
+ for (const listener of this.listeners) listener();
8338
+ }, 0);
8339
+ }
8340
+ dispose = () => {
8341
+ this.disposed = true;
8342
+ clearTimeout(this.notification);
8343
+ this.listeners.clear();
8344
+ this.queue = [];
8345
+ for (const request of this.requests.values())
8346
+ request.reject(new Error("Document page loading was cancelled"));
8347
+ this.requests.clear();
8348
+ };
8349
+ };
8350
+
6934
8351
  // src/lib/pdfjs.ts
6935
8352
  var pdfJsPromise = null;
6936
8353
  var loadPdfJs = () => {
@@ -7056,6 +8473,8 @@ var usePDFDocumentContext = ({
7056
8473
  onError,
7057
8474
  source,
7058
8475
  initialRotation = 0,
8476
+ progressive = false,
8477
+ initialPage = 1,
7059
8478
  isZoomFitWidth,
7060
8479
  zoom = 1,
7061
8480
  zoomOptions,
@@ -7078,7 +8497,32 @@ var usePDFDocumentContext = ({
7078
8497
  darkModeColorsRef.current = darkModeColors;
7079
8498
  useEffect(() => {
7080
8499
  let isDisposed = false;
8500
+ let pageResources;
7081
8501
  const generateViewports = async (pdf) => {
8502
+ const firstPageNumber = Number.isFinite(initialPage) ? Math.max(1, Math.min(pdf.numPages, Math.trunc(initialPage))) : 1;
8503
+ if (progressive) {
8504
+ const firstPage = await pdf.getPage(firstPageNumber);
8505
+ if (isDisposed) return;
8506
+ pageResources = new PageResources(pdf, firstPage, rotation, (error) => {
8507
+ if (isDisposed) return;
8508
+ console.error("Error generating PDF viewports", error);
8509
+ onErrorRef.current?.({ error, phase: "viewport-generation", source });
8510
+ });
8511
+ setInitialState({
8512
+ isZoomFitWidth,
8513
+ viewports: pageResources.getSnapshot().viewports,
8514
+ pageProxies: pageResources.getSnapshot().pageProxies ?? [],
8515
+ pdfDocumentProxy: pdf,
8516
+ zoom,
8517
+ zoomOptions,
8518
+ pageResources,
8519
+ initialPage: firstPageNumber,
8520
+ colorScheme: colorSchemeRef.current,
8521
+ darkModeColors: darkModeColorsRef.current,
8522
+ renderColorMapRef
8523
+ });
8524
+ return;
8525
+ }
7082
8526
  const pageProxies = [];
7083
8527
  const viewports = await mapConcurrent(
7084
8528
  Array.from({ length: pdf.numPages }, (_, index) => index),
@@ -7104,6 +8548,7 @@ var usePDFDocumentContext = ({
7104
8548
  pdfDocumentProxy: pdf,
7105
8549
  zoom,
7106
8550
  zoomOptions,
8551
+ initialPage: firstPageNumber,
7107
8552
  colorScheme: colorSchemeRef.current,
7108
8553
  darkModeColors: darkModeColorsRef.current,
7109
8554
  renderColorMapRef
@@ -7166,6 +8611,7 @@ var usePDFDocumentContext = ({
7166
8611
  });
7167
8612
  return () => {
7168
8613
  isDisposed = true;
8614
+ pageResources?.dispose();
7169
8615
  void loadingTask?.destroy();
7170
8616
  };
7171
8617
  };
@@ -7207,6 +8653,28 @@ var ColorSchemeSync = ({
7207
8653
  ]);
7208
8654
  return null;
7209
8655
  };
8656
+ var PageResourcesSync = ({ resources }) => {
8657
+ const store = PDFStore.useContext();
8658
+ useEffect(() => {
8659
+ if (!resources) return;
8660
+ const update = () => {
8661
+ const snapshot = resources.getSnapshot();
8662
+ store.setState((state) => ({
8663
+ viewports: snapshot.viewports,
8664
+ pageProxies: snapshot.pageProxies ?? state.pageProxies,
8665
+ pagesLoaded: Boolean(snapshot.pageProxies)
8666
+ }));
8667
+ };
8668
+ const unsubscribe = resources.subscribe(update);
8669
+ update();
8670
+ const timer = setTimeout(resources.start, 0);
8671
+ return () => {
8672
+ clearTimeout(timer);
8673
+ unsubscribe();
8674
+ };
8675
+ }, [resources, store]);
8676
+ return null;
8677
+ };
7210
8678
  var Root = forwardRef(
7211
8679
  ({
7212
8680
  children,
@@ -7218,6 +8686,8 @@ var Root = forwardRef(
7218
8686
  zoom,
7219
8687
  zoomOptions,
7220
8688
  documentOptions,
8689
+ progressive,
8690
+ initialPage,
7221
8691
  colorScheme,
7222
8692
  darkModeColors,
7223
8693
  ...props
@@ -7230,6 +8700,8 @@ var Root = forwardRef(
7230
8700
  zoom,
7231
8701
  zoomOptions,
7232
8702
  documentOptions,
8703
+ progressive,
8704
+ initialPage,
7233
8705
  colorScheme,
7234
8706
  darkModeColors
7235
8707
  });
@@ -7245,6 +8717,7 @@ var Root = forwardRef(
7245
8717
  };
7246
8718
  }, [documentId]);
7247
8719
  return /* @__PURE__ */ jsx(Primitive.div, { ref, ...props, children: initialState ? /* @__PURE__ */ jsxs(PDFStore.Provider, { initialValue: initialState, children: [
8720
+ /* @__PURE__ */ jsx(PageResourcesSync, { resources: initialState.pageResources }),
7248
8721
  /* @__PURE__ */ jsx(
7249
8722
  ColorSchemeSync,
7250
8723
  {
@@ -7275,6 +8748,8 @@ function acquireDocumentText(pages) {
7275
8748
  const page = pages[index];
7276
8749
  const reader = page.streamTextContent().getReader();
7277
8750
  const chunks = [];
8751
+ const lineBreaks = [];
8752
+ let offset3 = 0;
7278
8753
  let complete = false;
7279
8754
  try {
7280
8755
  while (true) {
@@ -7284,9 +8759,13 @@ function acquireDocumentText(pages) {
7284
8759
  complete = true;
7285
8760
  break;
7286
8761
  }
7287
- chunks.push(
7288
- value.items.map((item) => "str" in item ? item.str : "").join("")
7289
- );
8762
+ for (const item of value.items) {
8763
+ if (!("str" in item)) continue;
8764
+ chunks.push(item.str);
8765
+ offset3 += item.str.length;
8766
+ if (item.hasEOL && lineBreaks.at(-1) !== offset3)
8767
+ lineBreaks.push(offset3);
8768
+ }
7290
8769
  }
7291
8770
  } finally {
7292
8771
  if (!complete)
@@ -7294,7 +8773,11 @@ function acquireDocumentText(pages) {
7294
8773
  });
7295
8774
  reader.releaseLock();
7296
8775
  }
7297
- result[index] = { pageNumber: page.pageNumber, text: chunks.join("") };
8776
+ result[index] = {
8777
+ pageNumber: page.pageNumber,
8778
+ text: chunks.join(""),
8779
+ ...lineBreaks.length ? { lineBreaks } : {}
8780
+ };
7298
8781
  }
7299
8782
  };
7300
8783
  const created = {
@@ -7339,7 +8822,10 @@ var Search = ({
7339
8822
  }) => {
7340
8823
  const [result, setResult] = useState(null);
7341
8824
  const [attempt, setAttempt] = useState(0);
8825
+ const document2 = usePdf((state) => state.pdfDocumentProxy);
7342
8826
  const proxies = usePdf((state) => state.pageProxies);
8827
+ const pagesLoaded = usePdf((state) => state.pagesLoaded);
8828
+ const loadPages = usePdf((state) => state.loadPdfPageProxies);
7343
8829
  const setTextContent = usePdf((state) => state.setTextContent);
7344
8830
  const retry = useCallback(() => {
7345
8831
  setResult(null);
@@ -7348,22 +8834,27 @@ var Search = ({
7348
8834
  useEffect(() => {
7349
8835
  let disposed = false;
7350
8836
  setResult(null);
7351
- const task = acquireDocumentText(proxies);
7352
- void task.promise.then((text) => {
7353
- if (disposed) return;
8837
+ let task;
8838
+ void loadPages().then((pages) => {
8839
+ if (disposed || !pagesLoaded) return;
8840
+ task = acquireDocumentText(pages);
8841
+ return task.promise;
8842
+ }).then((text) => {
8843
+ if (disposed || !text) return;
7354
8844
  setTextContent(text);
7355
- setResult({ pages: proxies, status: "ready" });
8845
+ setResult({ document: document2, pages: proxies, status: "ready" });
7356
8846
  }).catch((error) => {
7357
8847
  if (disposed) return;
7358
8848
  console.error("Error extracting PDF text", error);
7359
- setResult({ pages: proxies, status: "error", error });
8849
+ setResult({ document: document2, pages: proxies, status: "error", error });
7360
8850
  });
7361
8851
  return () => {
7362
8852
  disposed = true;
7363
- task.release();
8853
+ task?.release();
7364
8854
  };
7365
- }, [proxies, setTextContent, attempt]);
7366
- if (!result || result.pages !== proxies) return loading;
8855
+ }, [document2, proxies, pagesLoaded, loadPages, setTextContent, attempt]);
8856
+ if (!result || result.document !== document2 || result.pages !== proxies)
8857
+ return loading;
7367
8858
  if (result.status === "error") {
7368
8859
  if (errorFallback) return errorFallback({ error: result.error, retry });
7369
8860
  return /* @__PURE__ */ jsxs("div", { role: "alert", children: [
@@ -7463,7 +8954,7 @@ var useThumbnail = (pageNumber, config = {}) => {
7463
8954
  isVisible
7464
8955
  };
7465
8956
  };
7466
- var Thumbnail = ({
8957
+ var LoadedThumbnail = ({
7467
8958
  pageNumber = 1,
7468
8959
  eager = false,
7469
8960
  ...props
@@ -7472,30 +8963,65 @@ var Thumbnail = ({
7472
8963
  isFirstPage: eager || pageNumber < 5
7473
8964
  });
7474
8965
  const { jumpToPage } = usePdfJump();
8966
+ const isCurrentPage = usePdf((state) => state.currentPage === pageNumber);
8967
+ const spacePressed = useRef(false);
7475
8968
  return /* @__PURE__ */ jsx("div", { ref: containerRef, style: { minHeight: "150px", minWidth: "10px" }, children: isVisible && /* @__PURE__ */ jsx(
7476
8969
  Primitive.canvas,
7477
8970
  {
8971
+ "aria-label": `Page ${pageNumber}`,
8972
+ "aria-current": isCurrentPage ? "page" : void 0,
7478
8973
  ...props,
7479
8974
  role: "button",
7480
8975
  tabIndex: 0,
7481
- onClick: (e) => {
7482
- if (props.onClick) {
7483
- props.onClick(e);
7484
- }
7485
- jumpToPage(pageNumber, { behavior: "auto" });
8976
+ onClick: (event) => {
8977
+ props.onClick?.(event);
8978
+ if (!event.defaultPrevented)
8979
+ jumpToPage(pageNumber, { behavior: "auto" });
7486
8980
  },
7487
- onKeyDown: (e) => {
7488
- if (props.onKeyDown) {
7489
- props.onKeyDown(e);
8981
+ onKeyDown: (event) => {
8982
+ props.onKeyDown?.(event);
8983
+ if (event.defaultPrevented) return;
8984
+ if (event.key === "Enter") {
8985
+ event.preventDefault();
8986
+ jumpToPage(pageNumber, { behavior: "auto" });
8987
+ } else if (event.key === " ") {
8988
+ event.preventDefault();
8989
+ spacePressed.current = true;
7490
8990
  }
7491
- if (e.key === "Enter") {
8991
+ },
8992
+ onKeyUp: (event) => {
8993
+ props.onKeyUp?.(event);
8994
+ if (event.key !== " ") return;
8995
+ const activate = spacePressed.current && !event.defaultPrevented;
8996
+ spacePressed.current = false;
8997
+ if (activate) {
8998
+ event.preventDefault();
7492
8999
  jumpToPage(pageNumber, { behavior: "auto" });
7493
9000
  }
7494
9001
  },
9002
+ onBlur: (event) => {
9003
+ spacePressed.current = false;
9004
+ props.onBlur?.(event);
9005
+ },
7495
9006
  ref: canvasRef
7496
9007
  }
7497
9008
  ) });
7498
9009
  };
9010
+ var Thumbnail = (props) => /* @__PURE__ */ jsx(
9011
+ PageLoadingBoundary,
9012
+ {
9013
+ pageNumber: props.pageNumber ?? 1,
9014
+ fallback: /* @__PURE__ */ jsx(
9015
+ "div",
9016
+ {
9017
+ "aria-label": `Loading page ${props.pageNumber ?? 1}`,
9018
+ "aria-busy": "true",
9019
+ style: { minHeight: 150 }
9020
+ }
9021
+ ),
9022
+ children: /* @__PURE__ */ jsx(LoadedThumbnail, { ...props })
9023
+ }
9024
+ );
7499
9025
  function VirtualizedThumbnails({
7500
9026
  children,
7501
9027
  count,
@@ -7508,6 +9034,9 @@ function VirtualizedThumbnails({
7508
9034
  const containerRef = useRef(null);
7509
9035
  useImperativeHandle(ref, () => containerRef.current);
7510
9036
  const [focusPage, setFocusPage] = useState(null);
9037
+ const focusPageLoaded = usePdf(
9038
+ (state) => focusPage === null ? false : state.isPageLoaded(focusPage)
9039
+ );
7511
9040
  const itemHeight = Number.isFinite(virtualize.itemHeight) ? Math.max(1, virtualize.itemHeight) : 150;
7512
9041
  const virtualizer = useVirtualizer({
7513
9042
  count,
@@ -7525,7 +9054,7 @@ function VirtualizedThumbnails({
7525
9054
  canvas.focus({ preventScroll: true });
7526
9055
  setFocusPage(null);
7527
9056
  }
7528
- }, [focusPage, items]);
9057
+ }, [focusPage, focusPageLoaded, items]);
7529
9058
  return /* @__PURE__ */ jsx(
7530
9059
  Primitive.div,
7531
9060
  {
@@ -7657,13 +9186,41 @@ var CurrentZoom = ({ ...props }) => {
7657
9186
 
7658
9187
  // src/lib/search.ts
7659
9188
  var normalizedPages = /* @__PURE__ */ new WeakMap();
7660
- function normalize(page) {
9189
+ function normalize(page, options) {
7661
9190
  let cached = normalizedPages.get(page);
7662
- if (!cached || cached.text !== page.text) {
7663
- cached = { text: page.text, lower: page.text.toLowerCase() };
9191
+ if (cached?.text !== page.text || cached.lineBreaks !== page.lineBreaks) {
9192
+ cached = {
9193
+ text: page.text,
9194
+ lineBreaks: page.lineBreaks,
9195
+ variants: /* @__PURE__ */ new Map()
9196
+ };
7664
9197
  normalizedPages.set(page, cached);
7665
9198
  }
7666
- return cached.lower;
9199
+ const key = (options.matchDiacritics === false ? 1 : 0) | (options.ignoreHyphenation ? 2 : 0);
9200
+ let result = cached.variants.get(key);
9201
+ if (!result) {
9202
+ result = normalizeSearchText(page.text, options, page.lineBreaks);
9203
+ cached.variants.set(key, result);
9204
+ }
9205
+ return result;
9206
+ }
9207
+ function createMatch(page, normalized, start, length, searchText, textSize, score) {
9208
+ const end = Math.min(normalized.text.length, start + length);
9209
+ const { start: matchIndex, end: matchEnd } = originalTextRange(
9210
+ normalized,
9211
+ start,
9212
+ end
9213
+ );
9214
+ const matchLength = matchEnd - matchIndex;
9215
+ return {
9216
+ pageNumber: page.pageNumber,
9217
+ text: page.text.slice(matchIndex, matchEnd + textSize),
9218
+ score,
9219
+ matchIndex,
9220
+ isExactMatch: score === 1,
9221
+ searchText,
9222
+ ...matchLength !== searchText.length ? { matchLength } : {}
9223
+ };
7667
9224
  }
7668
9225
  function createBoundedDistance(query, maxDistance) {
7669
9226
  const length = query.length;
@@ -7716,7 +9273,8 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
7716
9273
  const threshold = Math.max(0, Math.min(1, options.threshold ?? 0.7));
7717
9274
  const limit = Number.isFinite(options.limit) ? Math.max(0, Math.floor(options.limit)) : 10;
7718
9275
  const textSize = options.textSize ?? 100;
7719
- const query = searchText.toLowerCase();
9276
+ const query = normalizeSearchText(searchText, options).text;
9277
+ if (!query.trim()) return empty;
7720
9278
  const maxDistance = Math.floor(query.length * (1 - threshold));
7721
9279
  const distance = maxDistance > 0 ? createBoundedDistance(query, maxDistance) : null;
7722
9280
  const exactMatches = [];
@@ -7726,9 +9284,12 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
7726
9284
  let steps = 0;
7727
9285
  let deadline = cooperative ? performance.now() + budget : 0;
7728
9286
  for (const page of pages) {
7729
- const lower = normalize(page);
9287
+ const normalized = normalize(page, options);
9288
+ const lower = normalized.text;
7730
9289
  const exactStarts = [];
7731
9290
  let index = 0;
9291
+ let lastStart = -1;
9292
+ let lastEnd = -1;
7732
9293
  while (true) {
7733
9294
  if (cooperative && (++steps & 127) === 0 && performance.now() >= deadline) {
7734
9295
  yield;
@@ -7736,19 +9297,31 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
7736
9297
  }
7737
9298
  const matchIndex = lower.indexOf(query, index);
7738
9299
  if (matchIndex === -1) break;
7739
- exactCount++;
9300
+ const span = originalTextRange(
9301
+ normalized,
9302
+ matchIndex,
9303
+ matchIndex + query.length
9304
+ );
9305
+ index = matchIndex + query.length;
7740
9306
  if (maxDistance > 0) exactStarts.push(matchIndex);
9307
+ if (span.start === lastStart && span.end === lastEnd) continue;
9308
+ lastStart = span.start;
9309
+ lastEnd = span.end;
9310
+ exactCount++;
7741
9311
  if (exactMatches.length < limit) {
7742
- exactMatches.push({
7743
- pageNumber: page.pageNumber,
7744
- text: page.text.substr(matchIndex, searchText.length + textSize),
7745
- score: 1,
7746
- matchIndex,
7747
- isExactMatch: true,
7748
- searchText
7749
- });
9312
+ exactMatches.push(
9313
+ createMatch(
9314
+ page,
9315
+ normalized,
9316
+ matchIndex,
9317
+ query.length,
9318
+ searchText,
9319
+ textSize,
9320
+ 1
9321
+ )
9322
+ );
7750
9323
  }
7751
- index = matchIndex + searchText.length;
9324
+ index = matchIndex + query.length;
7752
9325
  }
7753
9326
  if (!(maxDistance > 0)) continue;
7754
9327
  index = 0;
@@ -7758,11 +9331,11 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
7758
9331
  yield;
7759
9332
  deadline = performance.now() + budget;
7760
9333
  }
7761
- while (exactIndex < exactStarts.length && exactStarts[exactIndex] + searchText.length <= index)
9334
+ while (exactIndex < exactStarts.length && exactStarts[exactIndex] + query.length <= index)
7762
9335
  exactIndex++;
7763
9336
  const exactStart = exactStarts[exactIndex];
7764
9337
  if (exactStart !== void 0 && index >= exactStart) {
7765
- index = exactStart + searchText.length;
9338
+ index = exactStart + query.length;
7766
9339
  continue;
7767
9340
  }
7768
9341
  const edits = distance(lower, index);
@@ -7772,14 +9345,15 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
7772
9345
  if (fuzzyMatches.length < limit || score > (fuzzyMatches[limit - 1]?.score ?? Infinity)) {
7773
9346
  insertMatch(
7774
9347
  fuzzyMatches,
7775
- {
7776
- pageNumber: page.pageNumber,
7777
- text: page.text.substr(index, query.length + textSize),
7778
- score,
7779
- matchIndex: index,
7780
- isExactMatch: false,
7781
- searchText
7782
- },
9348
+ createMatch(
9349
+ page,
9350
+ normalized,
9351
+ index,
9352
+ query.length,
9353
+ searchText,
9354
+ textSize,
9355
+ score
9356
+ ),
7783
9357
  limit
7784
9358
  );
7785
9359
  }
@@ -7885,32 +9459,37 @@ var useSearch = () => {
7885
9459
 
7886
9460
  // src/hooks/search/useSearchPosition.tsx
7887
9461
  async function calculateHighlightRects(pageProxy, textMatch) {
7888
- const matchLength = textMatch.searchText ? textMatch.searchText.length : textMatch.text.length;
7889
- if (matchLength === 0 || !Number.isFinite(textMatch.matchIndex) || textMatch.matchIndex < 0)
9462
+ const matchLength = textMatch.matchLength ?? (textMatch.searchText ? textMatch.searchText.length : textMatch.text.length);
9463
+ if (!Number.isFinite(matchLength) || matchLength <= 0 || !Number.isFinite(textMatch.matchIndex) || textMatch.matchIndex < 0)
7890
9464
  return [];
7891
9465
  const matchEnd = textMatch.matchIndex + matchLength;
7892
9466
  const reader = pageProxy.streamTextContent().getReader();
7893
9467
  const matchRects = [];
7894
9468
  const viewport = pageProxy.getViewport({ scale: 1 });
9469
+ const styles = {};
7895
9470
  let offset3 = 0;
7896
9471
  let foundEnd = false;
7897
9472
  try {
7898
9473
  read: while (true) {
7899
9474
  const { value, done } = await reader.read();
7900
9475
  if (done) break;
9476
+ Object.assign(styles, value.styles);
7901
9477
  for (const item of value.items) {
7902
9478
  if (!("str" in item) || item.str.length === 0) continue;
7903
9479
  const length = item.str.length;
7904
9480
  const start = Math.max(0, textMatch.matchIndex - offset3);
7905
9481
  const end = Math.min(length, matchEnd - offset3);
7906
- if (end > start) {
7907
- matchRects.push({
7908
- pageNumber: textMatch.pageNumber,
7909
- left: item.transform[4] + start * (item.width / length),
7910
- top: viewport.height - (item.transform[5] + item.height),
7911
- width: (end - start) * (item.width / length),
7912
- height: item.height
7913
- });
9482
+ if (end > start && item.str.slice(start, end).trim()) {
9483
+ matchRects.push(
9484
+ getItemRect(
9485
+ item,
9486
+ styles[item.fontName],
9487
+ viewport,
9488
+ start,
9489
+ end,
9490
+ textMatch.pageNumber
9491
+ )
9492
+ );
7914
9493
  }
7915
9494
  offset3 += length;
7916
9495
  if (offset3 >= matchEnd) {
@@ -7927,6 +9506,61 @@ async function calculateHighlightRects(pageProxy, textMatch) {
7927
9506
  }
7928
9507
  return mergeAdjacentRects(matchRects);
7929
9508
  }
9509
+ var measurementContext;
9510
+ function getTextFractions(item, style, start, end) {
9511
+ const length = item.str.length;
9512
+ let from = start / length;
9513
+ let to = end / length;
9514
+ if ((start > 0 || end < length) && style?.fontFamily && !style.vertical && typeof document !== "undefined") {
9515
+ if (measurementContext === void 0) {
9516
+ const canvas = document.createElement("canvas");
9517
+ canvas.width = canvas.height = 0;
9518
+ measurementContext = canvas.getContext("2d");
9519
+ }
9520
+ if (measurementContext) {
9521
+ measurementContext.font = `100px ${style.fontFamily}`;
9522
+ const width = measurementContext.measureText(item.str).width;
9523
+ if (width > 0) {
9524
+ from = start === 0 ? 0 : measurementContext.measureText(item.str.slice(0, start)).width / width;
9525
+ to = end === length ? 1 : measurementContext.measureText(item.str.slice(0, end)).width / width;
9526
+ }
9527
+ }
9528
+ }
9529
+ return item.dir === "rtl" ? [1 - to, 1 - from] : [from, to];
9530
+ }
9531
+ function getItemRect(item, style, viewport, start, end, pageNumber) {
9532
+ const angle = Math.atan2(item.transform[1], item.transform[0]) - (style?.vertical ? Math.PI / 2 : 0);
9533
+ const cosine = Math.cos(angle);
9534
+ const sine = Math.sin(angle);
9535
+ const height = Math.hypot(item.transform[2], item.transform[3]);
9536
+ const ascent = (style?.ascent ?? (style?.descent ? 1 + style.descent : 1)) * height;
9537
+ const width = style?.vertical ? item.height : item.width;
9538
+ const [startFraction, endFraction] = getTextFractions(
9539
+ item,
9540
+ style,
9541
+ start,
9542
+ end
9543
+ );
9544
+ const from = startFraction * width;
9545
+ const to = endFraction * width;
9546
+ let left = Infinity;
9547
+ let top = Infinity;
9548
+ let right = -Infinity;
9549
+ let bottom = -Infinity;
9550
+ for (const along of [from, to]) {
9551
+ for (const above of [ascent, ascent - height]) {
9552
+ const [x, y] = viewport.convertToViewportPoint(
9553
+ item.transform[4] + along * cosine - above * sine,
9554
+ item.transform[5] + along * sine + above * cosine
9555
+ );
9556
+ left = Math.min(left, x);
9557
+ top = Math.min(top, y);
9558
+ right = Math.max(right, x);
9559
+ bottom = Math.max(bottom, y);
9560
+ }
9561
+ }
9562
+ return { pageNumber, left, top, width: right - left, height: bottom - top };
9563
+ }
7930
9564
  function mergeAdjacentRects(rects) {
7931
9565
  if (rects.length <= 1) return rects;
7932
9566
  const merged = [];
@@ -7935,10 +9569,13 @@ function mergeAdjacentRects(rects) {
7935
9569
  for (let i = 1; i < rects.length; i++) {
7936
9570
  const next = rects[i];
7937
9571
  if (!next) continue;
7938
- if (Math.abs(current.top - next.top) < 2 && Math.abs(current.height - next.height) < 2) {
9572
+ if (Math.abs(current.top - next.top) < 2 && Math.abs(current.height - next.height) < 2 && Math.max(current.left, next.left) <= Math.min(current.left + current.width, next.left + next.width) + 2) {
7939
9573
  current = {
7940
9574
  ...current,
7941
- width: next.left + next.width - current.left
9575
+ left: Math.min(current.left, next.left),
9576
+ top: Math.min(current.top, next.top),
9577
+ width: Math.max(current.left + current.width, next.left + next.width) - Math.min(current.left, next.left),
9578
+ height: Math.max(current.top + current.height, next.top + next.height) - Math.min(current.top, next.top)
7942
9579
  };
7943
9580
  } else {
7944
9581
  merged.push(current);
@@ -7951,5 +9588,24 @@ function mergeAdjacentRects(rects) {
7951
9588
 
7952
9589
  // src/hooks/usePageRendered.ts
7953
9590
  var usePageRendered = (pageNumber) => usePdf((state) => !!state.renderedPages[pageNumber]);
9591
+ function usePdfSelection() {
9592
+ const controller = usePdf((state) => state.selection);
9593
+ const selection = useSyncExternalStore(
9594
+ controller.subscribe,
9595
+ controller.getSnapshot,
9596
+ controller.getSnapshot
9597
+ );
9598
+ return useMemo(
9599
+ () => ({
9600
+ selection,
9601
+ setSelection: controller.setSelection,
9602
+ clearSelection: controller.clear,
9603
+ getText: controller.getText,
9604
+ getSelection: controller.getSelection,
9605
+ getSelectionAsync: controller.getSelectionAsync
9606
+ }),
9607
+ [controller, selection]
9608
+ );
9609
+ }
7954
9610
 
7955
- export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, AnnotationsStoreProvider, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, DEFAULT_DARK_MODE_COLORS, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, createDarkModeColorMap, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePageAnnotations, usePageRendered, usePdf, usePdfJump, useSearch, useSelectionDimensions };
9611
+ export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, AnnotationsStoreProvider, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, DEFAULT_DARK_MODE_COLORS, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, createDarkModeColorMap, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePageAnnotations, usePageRendered, usePdf, usePdfJump, usePdfSelection, useSearch, useSelectionDimensions };