@papyrus-sdk/ui-react-native 0.2.10 → 0.2.12

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.mjs CHANGED
@@ -1,7 +1,22 @@
1
- var __getOwnPropNames = Object.getOwnPropertyNames;
2
- var __commonJS = (cb, mod) => function __require() {
3
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
4
- };
1
+ import {
2
+ DEFAULT_PINCH_ZOOM_BOUNDS,
3
+ resolveAnchoredViewportOffset,
4
+ resolveClampedScrollOffset,
5
+ resolvePinchGestureZoom,
6
+ resolvePinchPreviewScale,
7
+ sanitizePinchPreviewScale,
8
+ shouldSuppressPressAfterPinch
9
+ } from "./chunk-PE5U4ZWV.mjs";
10
+ import {
11
+ getSelectionEdgeAutoscroll,
12
+ getToolDockDismissState,
13
+ isToolDockToolSelected,
14
+ shouldEnableSelectionDrag,
15
+ shouldEnableViewerScroll
16
+ } from "./chunk-GCVUTXFR.mjs";
17
+ import {
18
+ __commonJS
19
+ } from "./chunk-ZD7AOCMD.mjs";
5
20
 
6
21
  // runtime/index.html
7
22
  var require_runtime = __commonJS({
@@ -21004,6 +21019,7 @@ import {
21004
21019
  View as View3,
21005
21020
  useWindowDimensions as useWindowDimensions2
21006
21021
  } from "react-native";
21022
+ import { Gesture as Gesture2, GestureDetector as GestureDetector2 } from "react-native-gesture-handler";
21007
21023
  import { useViewerStore as useViewerStore3 } from "@papyrus-sdk/core";
21008
21024
 
21009
21025
  // components/PageRenderer.tsx
@@ -21025,6 +21041,7 @@ import {
21025
21041
  findNodeHandle,
21026
21042
  useWindowDimensions
21027
21043
  } from "react-native";
21044
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
21028
21045
  import Svg, { Path as SvgPath } from "react-native-svg";
21029
21046
  import { useViewerStore } from "@papyrus-sdk/core";
21030
21047
  import {
@@ -21275,6 +21292,9 @@ var buildSquigglyPath = (segments = 16) => {
21275
21292
  return path;
21276
21293
  };
21277
21294
  var SQUIGGLY_PATH = buildSquigglyPath();
21295
+ var SELECTION_EDGE_THRESHOLD_PX = 48;
21296
+ var SELECTION_EDGE_MAX_STEP_PX = 24;
21297
+ var SELECTION_AUTOSCROLL_INTERVAL_MS = 16;
21278
21298
  var PageRenderer = ({
21279
21299
  engine,
21280
21300
  pageIndex,
@@ -21282,26 +21302,37 @@ var PageRenderer = ({
21282
21302
  PageViewComponent = PapyrusPageView,
21283
21303
  availableWidth,
21284
21304
  horizontalPadding = 16,
21285
- spacing = 24
21305
+ spacing = 24,
21306
+ onSelectionDragActiveChange,
21307
+ gestureScrollLockActive = false,
21308
+ lastPinchEndedAt = null,
21309
+ onHorizontalScrollOffsetChange,
21310
+ horizontalScrollRestore = null,
21311
+ requestSelectionVerticalAutoscroll
21286
21312
  }) => {
21287
21313
  const viewRef = useRef(null);
21314
+ const pageScrollRef = useRef(null);
21288
21315
  const [layout, setLayout] = useState({ width: 0, height: 0 });
21289
21316
  const [pageSize, setPageSize] = useState(null);
21290
21317
  const { width: windowWidth } = useWindowDimensions();
21291
21318
  const isNative = Platform.OS === "android" || Platform.OS === "ios";
21292
21319
  const perfEnabled = isMobilePerfEnabled();
21293
21320
  const renderCountRef = useRef(0);
21294
- const setStateBurstRef = useRef(
21295
- createBurstMonitor("PageRenderer", "setDocumentState", 18, 700)
21296
- );
21321
+ const inkDrawingActiveRef = useRef(false);
21322
+ const horizontalScrollOffsetRef = useRef(0);
21323
+ const selectionDragActiveRef = useRef(false);
21324
+ const selectionDragPointRef = useRef(null);
21325
+ const lastAppliedHorizontalRestoreRef = useRef(null);
21326
+ const selectionAutoscrollIntervalRef = useRef(null);
21327
+ const rawTouchMoveLoggedAtRef = useRef(0);
21297
21328
  const zoom = useViewerStore((state) => state.zoom);
21298
21329
  const rotation = useViewerStore((state) => state.rotation);
21299
21330
  const pageTheme = useViewerStore((state) => state.pageTheme);
21300
21331
  const annotations = useViewerStore((state) => state.annotations);
21301
21332
  const annotationColor = useViewerStore((state) => state.annotationColor);
21302
21333
  const addAnnotation = useViewerStore((state) => state.addAnnotation);
21303
- const setDocumentState = useViewerStore((state) => state.setDocumentState);
21304
21334
  const activeTool = useViewerStore((state) => state.activeTool);
21335
+ const interactionMode = useViewerStore((state) => state.interactionMode);
21305
21336
  const accentColor = useViewerStore((state) => state.accentColor);
21306
21337
  const selectedAnnotationId = useViewerStore(
21307
21338
  (state) => state.selectedAnnotationId
@@ -21315,19 +21346,6 @@ var PageRenderer = ({
21315
21346
  const setSelectionActive = useViewerStore(
21316
21347
  (state) => state.setSelectionActive
21317
21348
  );
21318
- const setDocumentStateTracked = useCallback(
21319
- (state, reason) => {
21320
- if (perfEnabled) {
21321
- setStateBurstRef.current({
21322
- reason,
21323
- page: pageIndex + 1,
21324
- keys: Object.keys(state).join(",")
21325
- });
21326
- }
21327
- setDocumentState(state);
21328
- },
21329
- [pageIndex, perfEnabled, setDocumentState]
21330
- );
21331
21349
  const logSelectionPerf = useCallback(
21332
21350
  (event, payload) => {
21333
21351
  if (!perfEnabled) return;
@@ -21338,6 +21356,56 @@ var PageRenderer = ({
21338
21356
  },
21339
21357
  [pageIndex, perfEnabled]
21340
21358
  );
21359
+ const logGestureDebug = useCallback(
21360
+ (event, payload) => {
21361
+ if (!perfEnabled || !isNative) return;
21362
+ logPerfEvent("PageRenderer", `gesture.${event}`, {
21363
+ page: pageIndex + 1,
21364
+ activeTool,
21365
+ interactionMode,
21366
+ pinchActive: gestureScrollLockActive,
21367
+ gestureLockActive: gestureScrollLockActive,
21368
+ selectionEnabled: Platform.OS === "web" || isNative && shouldEnableSelectionDrag({
21369
+ activeTool,
21370
+ interactionMode
21371
+ }),
21372
+ zoom: Math.round(zoom * 100) / 100,
21373
+ ...payload
21374
+ });
21375
+ },
21376
+ [activeTool, interactionMode, isNative, pageIndex, perfEnabled, zoom]
21377
+ );
21378
+ const logRawTouchDebug = useCallback(
21379
+ (phase, event) => {
21380
+ if (!perfEnabled || !isNative) return;
21381
+ const touches = Array.isArray(event?.nativeEvent?.touches) ? event.nativeEvent.touches.length : 0;
21382
+ const changedTouches = Array.isArray(event?.nativeEvent?.changedTouches) ? event.nativeEvent.changedTouches.length : 0;
21383
+ if (phase === "move") {
21384
+ if (touches < 2 && !gestureScrollLockActive) return;
21385
+ const now = Date.now();
21386
+ if (now - rawTouchMoveLoggedAtRef.current < 120) return;
21387
+ rawTouchMoveLoggedAtRef.current = now;
21388
+ }
21389
+ logGestureDebug(`touch.${phase}`, {
21390
+ touches,
21391
+ changedTouches,
21392
+ target: event?.nativeEvent?.target ?? null,
21393
+ locationX: Math.round((event?.nativeEvent?.locationX ?? 0) * 100) / 100,
21394
+ locationY: Math.round((event?.nativeEvent?.locationY ?? 0) * 100) / 100,
21395
+ pageX: Math.round((event?.nativeEvent?.pageX ?? 0) * 100) / 100,
21396
+ pageY: Math.round((event?.nativeEvent?.pageY ?? 0) * 100) / 100
21397
+ });
21398
+ },
21399
+ [isNative, logGestureDebug, perfEnabled]
21400
+ );
21401
+ const setSelectionDragState = useCallback(
21402
+ (active) => {
21403
+ if (selectionDragActiveRef.current === active) return;
21404
+ selectionDragActiveRef.current = active;
21405
+ onSelectionDragActiveChange?.(active);
21406
+ },
21407
+ [onSelectionDragActiveChange]
21408
+ );
21341
21409
  const pageAnnotations = useMemo(
21342
21410
  () => annotations.filter((ann) => ann.pageIndex === pageIndex),
21343
21411
  [annotations, pageIndex]
@@ -21366,12 +21434,6 @@ var PageRenderer = ({
21366
21434
  const selectionRectRef = useRef(null);
21367
21435
  const selectionBoundsRef = useRef(null);
21368
21436
  const selectionBoundsStart = useRef(null);
21369
- const lastTapRef = useRef(
21370
- null
21371
- );
21372
- const pinchRef = useRef(null);
21373
- const isPinchingRef = useRef(false);
21374
- const pinchLogZoomRef = useRef(zoom);
21375
21437
  const [isInkDrawing, setIsInkDrawing] = useState(false);
21376
21438
  const [inkPoints, setInkPoints] = useState(
21377
21439
  []
@@ -21494,11 +21556,30 @@ var PageRenderer = ({
21494
21556
  }, [inkPoints]);
21495
21557
  useEffect(() => {
21496
21558
  if (activeTool === "ink") return;
21559
+ inkDrawingActiveRef.current = false;
21497
21560
  setIsInkDrawing(false);
21498
21561
  setInkPoints([]);
21499
21562
  inkPointsRef.current = [];
21500
21563
  }, [activeTool]);
21501
- const clearSelection = () => {
21564
+ const pageViewportWidth = Math.max(
21565
+ 0,
21566
+ (availableWidth ?? windowWidth) - horizontalPadding * 2
21567
+ );
21568
+ const selectionEnabled = Platform.OS === "web" || isNative && shouldEnableSelectionDrag({
21569
+ activeTool,
21570
+ interactionMode
21571
+ });
21572
+ const inkEnabled = isNative && activeTool === "ink";
21573
+ const stopSelectionAutoscroll = useCallback(() => {
21574
+ if (selectionAutoscrollIntervalRef.current) {
21575
+ clearInterval(selectionAutoscrollIntervalRef.current);
21576
+ selectionAutoscrollIntervalRef.current = null;
21577
+ }
21578
+ }, []);
21579
+ const clearSelection = useCallback(() => {
21580
+ stopSelectionAutoscroll();
21581
+ selectionDragPointRef.current = null;
21582
+ setSelectionDragState(false);
21502
21583
  setSelectionRect(null);
21503
21584
  selectionRectRef.current = null;
21504
21585
  setSelectionRects([]);
@@ -21508,13 +21589,19 @@ var PageRenderer = ({
21508
21589
  setIsSelecting(false);
21509
21590
  selectionStart.current = null;
21510
21591
  selectionBoundsStart.current = null;
21511
- lastTapRef.current = null;
21512
21592
  setSelectionActive(false);
21513
- };
21593
+ }, [setSelectionActive, setSelectionDragState, stopSelectionAutoscroll]);
21514
21594
  useEffect(() => {
21515
21595
  if (activeTool === "select") return;
21516
21596
  clearSelection();
21517
21597
  }, [activeTool]);
21598
+ useEffect(
21599
+ () => () => {
21600
+ stopSelectionAutoscroll();
21601
+ setSelectionDragState(false);
21602
+ },
21603
+ [setSelectionDragState, stopSelectionAutoscroll]
21604
+ );
21518
21605
  const stopPressPropagation = (event) => {
21519
21606
  event.stopPropagation?.();
21520
21607
  };
@@ -21591,50 +21678,193 @@ var PageRenderer = ({
21591
21678
  };
21592
21679
  await selectFromBounds(bounds);
21593
21680
  };
21594
- const getTouchDistance = (touches) => {
21595
- if (touches.length < 2) return 0;
21596
- const [a, b] = touches;
21597
- return Math.hypot(b.pageX - a.pageX, b.pageY - a.pageY);
21598
- };
21599
- const shouldHandlePinch = (touches) => isNative && touches.length === 2;
21600
- const handlePinchStart = (touches) => {
21601
- if (!shouldHandlePinch(touches)) return;
21602
- const distance = getTouchDistance(touches);
21603
- pinchRef.current = { distance, zoom };
21604
- pinchLogZoomRef.current = zoom;
21605
- logSelectionPerf("pinch.start", {
21606
- tool: activeTool,
21607
- distance: Math.round(distance * 100) / 100,
21608
- zoom: Math.round(zoom * 100) / 100
21681
+ const cancelSelectionDrag = useCallback(() => {
21682
+ stopSelectionAutoscroll();
21683
+ selectionDragPointRef.current = null;
21684
+ setSelectionDragState(false);
21685
+ setIsSelecting(false);
21686
+ selectionStart.current = null;
21687
+ selectionRectRef.current = null;
21688
+ setSelectionRect(null);
21689
+ }, [setSelectionDragState, stopSelectionAutoscroll]);
21690
+ const updateSelectionRectFromPoint = useCallback(
21691
+ (x, y) => {
21692
+ const start = selectionStart.current;
21693
+ if (!start || !layout.width || !layout.height) return;
21694
+ const left = Math.max(0, Math.min(start.x, x));
21695
+ const top = Math.max(0, Math.min(start.y, y));
21696
+ const right = Math.min(layout.width, Math.max(start.x, x));
21697
+ const bottom = Math.min(layout.height, Math.max(start.y, y));
21698
+ const rect = {
21699
+ x: left,
21700
+ y: top,
21701
+ width: right - left,
21702
+ height: bottom - top
21703
+ };
21704
+ selectionRectRef.current = rect;
21705
+ setSelectionRect(rect);
21706
+ },
21707
+ [layout.height, layout.width]
21708
+ );
21709
+ const applySelectionEdgeAutoscroll = useCallback(() => {
21710
+ const point = selectionDragPointRef.current;
21711
+ if (!point || !selectionStart.current || !layout.width || !layout.height) {
21712
+ stopSelectionAutoscroll();
21713
+ return;
21714
+ }
21715
+ const visibleX = point.x - horizontalScrollOffsetRef.current;
21716
+ const { dx } = getSelectionEdgeAutoscroll({
21717
+ x: visibleX,
21718
+ y: SELECTION_EDGE_THRESHOLD_PX,
21719
+ width: pageViewportWidth,
21720
+ height: SELECTION_EDGE_THRESHOLD_PX * 2,
21721
+ threshold: SELECTION_EDGE_THRESHOLD_PX,
21722
+ maxStep: SELECTION_EDGE_MAX_STEP_PX
21609
21723
  });
21610
- };
21611
- const handlePinchMove = (touches) => {
21612
- if (!shouldHandlePinch(touches) || !pinchRef.current) return;
21613
- const distance = getTouchDistance(touches);
21614
- if (!distance) return;
21615
- const scale2 = distance / pinchRef.current.distance;
21616
- const nextZoom = clamp(pinchRef.current.zoom * scale2, 0.5, 4);
21617
- setDocumentStateTracked({ zoom: nextZoom }, "pinchMove");
21618
- engine.setZoom(nextZoom);
21619
- if (Math.abs(nextZoom - pinchLogZoomRef.current) >= 0.12) {
21620
- pinchLogZoomRef.current = nextZoom;
21621
- logSelectionPerf("pinch.move", {
21622
- tool: activeTool,
21623
- distance: Math.round(distance * 100) / 100,
21624
- zoom: Math.round(nextZoom * 100) / 100
21625
- });
21724
+ let appliedDx = 0;
21725
+ if (dx !== 0 && pageViewportWidth > 0) {
21726
+ const maxOffsetX = Math.max(0, layout.width - pageViewportWidth);
21727
+ const nextOffsetX = clamp(
21728
+ horizontalScrollOffsetRef.current + dx,
21729
+ 0,
21730
+ maxOffsetX
21731
+ );
21732
+ appliedDx = nextOffsetX - horizontalScrollOffsetRef.current;
21733
+ if (appliedDx !== 0) {
21734
+ horizontalScrollOffsetRef.current = nextOffsetX;
21735
+ pageScrollRef.current?.scrollTo({ x: nextOffsetX, animated: false });
21736
+ }
21626
21737
  }
21627
- };
21628
- const handlePinchEnd = () => {
21629
- if (isPinchingRef.current || pinchRef.current) {
21630
- logSelectionPerf("pinch.end", {
21631
- tool: activeTool,
21632
- zoom: Math.round(zoom * 100) / 100
21633
- });
21738
+ const appliedDy = requestSelectionVerticalAutoscroll?.(point.absoluteY) ?? 0;
21739
+ if (appliedDx === 0 && appliedDy === 0) {
21740
+ stopSelectionAutoscroll();
21741
+ return;
21634
21742
  }
21635
- pinchRef.current = null;
21636
- };
21743
+ const nextX = clamp(point.x + appliedDx, 0, layout.width);
21744
+ const nextY = clamp(point.y + appliedDy, 0, layout.height);
21745
+ selectionDragPointRef.current = {
21746
+ absoluteY: point.absoluteY,
21747
+ x: nextX,
21748
+ y: nextY
21749
+ };
21750
+ updateSelectionRectFromPoint(nextX, nextY);
21751
+ }, [
21752
+ layout.height,
21753
+ layout.width,
21754
+ pageViewportWidth,
21755
+ requestSelectionVerticalAutoscroll,
21756
+ stopSelectionAutoscroll,
21757
+ updateSelectionRectFromPoint
21758
+ ]);
21759
+ const ensureSelectionAutoscroll = useCallback(() => {
21760
+ if (selectionAutoscrollIntervalRef.current) return;
21761
+ selectionAutoscrollIntervalRef.current = setInterval(
21762
+ applySelectionEdgeAutoscroll,
21763
+ SELECTION_AUTOSCROLL_INTERVAL_MS
21764
+ );
21765
+ }, [applySelectionEdgeAutoscroll]);
21766
+ const beginSelectionDrag = useCallback(
21767
+ (x, y, absoluteY) => {
21768
+ if (!selectionEnabled || !layout.width || !layout.height || selectionRects.length > 0 || selectionBounds) {
21769
+ return;
21770
+ }
21771
+ const start = {
21772
+ x: clamp(x, 0, layout.width),
21773
+ y: clamp(y, 0, layout.height)
21774
+ };
21775
+ selectionStart.current = start;
21776
+ selectionDragPointRef.current = { absoluteY, ...start };
21777
+ setSelectionDragState(true);
21778
+ setIsSelecting(true);
21779
+ const rect = { x: start.x, y: start.y, width: 0, height: 0 };
21780
+ selectionRectRef.current = rect;
21781
+ setSelectionRect(rect);
21782
+ },
21783
+ [
21784
+ layout.height,
21785
+ layout.width,
21786
+ selectionBounds,
21787
+ selectionEnabled,
21788
+ selectionRects.length,
21789
+ setSelectionDragState
21790
+ ]
21791
+ );
21792
+ const updateSelectionDrag = useCallback(
21793
+ (x, y, absoluteY) => {
21794
+ if (!selectionEnabled || !selectionStart.current) return;
21795
+ const nextX = clamp(x, 0, layout.width);
21796
+ const nextY = clamp(y, 0, layout.height);
21797
+ selectionDragPointRef.current = {
21798
+ absoluteY,
21799
+ x: nextX,
21800
+ y: nextY
21801
+ };
21802
+ updateSelectionRectFromPoint(nextX, nextY);
21803
+ ensureSelectionAutoscroll();
21804
+ },
21805
+ [
21806
+ ensureSelectionAutoscroll,
21807
+ layout.height,
21808
+ layout.width,
21809
+ selectionEnabled,
21810
+ updateSelectionRectFromPoint
21811
+ ]
21812
+ );
21813
+ const finishSelectionDrag = useCallback(async () => {
21814
+ stopSelectionAutoscroll();
21815
+ selectionDragPointRef.current = null;
21816
+ setSelectionDragState(false);
21817
+ const rect = selectionRectRef.current;
21818
+ if (!selectionEnabled || !rect || !layout.width || !layout.height) {
21819
+ setIsSelecting(false);
21820
+ selectionStart.current = null;
21821
+ return;
21822
+ }
21823
+ setIsSelecting(false);
21824
+ selectionStart.current = null;
21825
+ const minSize = 6;
21826
+ if (rect.width < minSize || rect.height < minSize) {
21827
+ clearSelection();
21828
+ return;
21829
+ }
21830
+ const normalized = {
21831
+ x: rect.x / layout.width,
21832
+ y: rect.y / layout.height,
21833
+ width: rect.width / layout.width,
21834
+ height: rect.height / layout.height
21835
+ };
21836
+ await selectFromBounds(normalized);
21837
+ setSelectionRect(null);
21838
+ }, [
21839
+ clearSelection,
21840
+ layout.height,
21841
+ layout.width,
21842
+ selectionEnabled,
21843
+ setSelectionDragState,
21844
+ stopSelectionAutoscroll
21845
+ ]);
21846
+ const handleDoubleTap = useCallback(
21847
+ (x, y) => {
21848
+ if (shouldSuppressPressAfterPinch(lastPinchEndedAt)) {
21849
+ return;
21850
+ }
21851
+ if (!isNative || activeTool !== "select" || selectionRects.length > 0 || selectionBounds) {
21852
+ return;
21853
+ }
21854
+ void selectAtPoint(x, y);
21855
+ },
21856
+ [
21857
+ activeTool,
21858
+ isNative,
21859
+ lastPinchEndedAt,
21860
+ selectionBounds,
21861
+ selectionRects.length
21862
+ ]
21863
+ );
21637
21864
  const handlePress = (event) => {
21865
+ if (shouldSuppressPressAfterPinch(lastPinchEndedAt)) {
21866
+ return;
21867
+ }
21638
21868
  if (!layout.width || !layout.height) return;
21639
21869
  const { locationX, locationY } = event.nativeEvent;
21640
21870
  if (selectionRects.length > 0 || selectionBounds) {
@@ -21655,20 +21885,7 @@ var PageRenderer = ({
21655
21885
  return;
21656
21886
  }
21657
21887
  setSelectedAnnotation(null);
21658
- if (!isNative || activeTool === "ink") return;
21659
- const now = Date.now();
21660
- const lastTap = lastTapRef.current;
21661
- lastTapRef.current = { time: now, x: locationX, y: locationY };
21662
- if (!lastTap) return;
21663
- const timeDelta = now - lastTap.time;
21664
- const distance = Math.hypot(locationX - lastTap.x, locationY - lastTap.y);
21665
- if (timeDelta < 280 && distance < 24 && activeTool === "select") {
21666
- void selectAtPoint(locationX, locationY);
21667
- }
21668
21888
  };
21669
- const selectionEnabled = Platform.OS === "web" || isNative && (activeTool === "select" || TEXT_MARKUP_TOOLS.has(activeTool));
21670
- const inkEnabled = isNative && activeTool === "ink";
21671
- const pinchEnabled = isNative;
21672
21889
  const toNormalizedPoint = (x, y) => {
21673
21890
  if (!layout.width || !layout.height) return null;
21674
21891
  return {
@@ -21680,6 +21897,7 @@ var PageRenderer = ({
21680
21897
  const point = toNormalizedPoint(x, y);
21681
21898
  if (!point) return;
21682
21899
  clearSelection();
21900
+ inkDrawingActiveRef.current = true;
21683
21901
  setIsInkDrawing(true);
21684
21902
  setInkPoints([point]);
21685
21903
  inkPointsRef.current = [point];
@@ -21698,6 +21916,7 @@ var PageRenderer = ({
21698
21916
  const finishInkDrawing = () => {
21699
21917
  const points = inkPointsRef.current;
21700
21918
  if (points.length === 0) return;
21919
+ inkDrawingActiveRef.current = false;
21701
21920
  setIsInkDrawing(false);
21702
21921
  setInkPoints([]);
21703
21922
  inkPointsRef.current = [];
@@ -21720,31 +21939,18 @@ var PageRenderer = ({
21720
21939
  const panResponder = useMemo(
21721
21940
  () => PanResponder.create({
21722
21941
  onStartShouldSetPanResponder: (event) => {
21942
+ if (isNative) return false;
21723
21943
  const touches = event.nativeEvent.touches ?? [];
21724
- return pinchEnabled && shouldHandlePinch(touches) || selectionEnabled || inkEnabled;
21944
+ if (touches.length !== 1) return false;
21945
+ return inkEnabled;
21725
21946
  },
21726
21947
  onMoveShouldSetPanResponder: (event) => {
21948
+ if (isNative) return false;
21727
21949
  const touches = event.nativeEvent.touches ?? [];
21728
- return pinchEnabled && shouldHandlePinch(touches) || selectionEnabled || inkEnabled;
21729
- },
21730
- onStartShouldSetPanResponderCapture: (event) => {
21731
- const touches = event.nativeEvent.touches ?? [];
21732
- return pinchEnabled && shouldHandlePinch(touches);
21733
- },
21734
- onMoveShouldSetPanResponderCapture: (event) => {
21735
- const touches = event.nativeEvent.touches ?? [];
21736
- return pinchEnabled && shouldHandlePinch(touches);
21950
+ if (touches.length !== 1) return false;
21951
+ return selectionEnabled || inkEnabled;
21737
21952
  },
21738
21953
  onPanResponderGrant: (event) => {
21739
- const touches = event.nativeEvent.touches ?? [];
21740
- if (pinchEnabled && shouldHandlePinch(touches)) {
21741
- isPinchingRef.current = true;
21742
- setIsSelecting(false);
21743
- selectionStart.current = null;
21744
- handlePinchStart(touches);
21745
- return;
21746
- }
21747
- isPinchingRef.current = false;
21748
21954
  if (inkEnabled) {
21749
21955
  beginInkDrawing(
21750
21956
  event.nativeEvent.locationX,
@@ -21752,26 +21958,13 @@ var PageRenderer = ({
21752
21958
  );
21753
21959
  return;
21754
21960
  }
21755
- if (!selectionEnabled || !layout.width || !layout.height) return;
21756
- const { locationX, locationY } = event.nativeEvent;
21757
- selectionStart.current = { x: locationX, y: locationY };
21758
- setIsSelecting(true);
21759
- const rect = { x: locationX, y: locationY, width: 0, height: 0 };
21760
- selectionRectRef.current = rect;
21761
- setSelectionRect(rect);
21961
+ beginSelectionDrag(
21962
+ event.nativeEvent.locationX,
21963
+ event.nativeEvent.locationY,
21964
+ event.nativeEvent.pageY ?? event.nativeEvent.locationY
21965
+ );
21762
21966
  },
21763
- onPanResponderMove: (event, gestureState) => {
21764
- const touches = event.nativeEvent.touches ?? [];
21765
- if (pinchEnabled && (shouldHandlePinch(touches) || isPinchingRef.current)) {
21766
- if (shouldHandlePinch(touches)) {
21767
- if (!isPinchingRef.current) {
21768
- isPinchingRef.current = true;
21769
- handlePinchStart(touches);
21770
- }
21771
- handlePinchMove(touches);
21772
- }
21773
- return;
21774
- }
21967
+ onPanResponderMove: (event) => {
21775
21968
  if (inkEnabled) {
21776
21969
  pushInkPoint(
21777
21970
  event.nativeEvent.locationX,
@@ -21779,79 +21972,90 @@ var PageRenderer = ({
21779
21972
  );
21780
21973
  return;
21781
21974
  }
21782
- if (!selectionEnabled || !selectionStart.current) return;
21783
- const start = selectionStart.current;
21784
- const currentX = start.x + gestureState.dx;
21785
- const currentY = start.y + gestureState.dy;
21786
- const left = Math.max(0, Math.min(start.x, currentX));
21787
- const top = Math.max(0, Math.min(start.y, currentY));
21788
- const right = Math.min(layout.width, Math.max(start.x, currentX));
21789
- const bottom = Math.min(layout.height, Math.max(start.y, currentY));
21790
- const rect = {
21791
- x: left,
21792
- y: top,
21793
- width: right - left,
21794
- height: bottom - top
21795
- };
21796
- selectionRectRef.current = rect;
21797
- setSelectionRect(rect);
21975
+ updateSelectionDrag(
21976
+ event.nativeEvent.locationX,
21977
+ event.nativeEvent.locationY,
21978
+ event.nativeEvent.pageY ?? event.nativeEvent.locationY
21979
+ );
21798
21980
  },
21799
21981
  onPanResponderRelease: async () => {
21800
- if (isPinchingRef.current) {
21801
- isPinchingRef.current = false;
21802
- handlePinchEnd();
21803
- return;
21804
- }
21805
21982
  if (inkEnabled) {
21806
21983
  finishInkDrawing();
21807
21984
  return;
21808
21985
  }
21809
- const rect = selectionRectRef.current;
21810
- if (!selectionEnabled || !rect || !layout.width || !layout.height) {
21811
- setIsSelecting(false);
21812
- selectionStart.current = null;
21813
- return;
21814
- }
21815
- setIsSelecting(false);
21816
- selectionStart.current = null;
21817
- const minSize = 6;
21818
- if (rect.width < minSize || rect.height < minSize) {
21819
- clearSelection();
21820
- return;
21821
- }
21822
- const normalized = {
21823
- x: rect.x / layout.width,
21824
- y: rect.y / layout.height,
21825
- width: rect.width / layout.width,
21826
- height: rect.height / layout.height
21827
- };
21828
- await selectFromBounds(normalized);
21829
- setSelectionRect(null);
21986
+ await finishSelectionDrag();
21830
21987
  },
21831
21988
  onPanResponderTerminate: () => {
21832
- if (isPinchingRef.current) {
21833
- isPinchingRef.current = false;
21834
- handlePinchEnd();
21835
- return;
21836
- }
21837
21989
  if (inkEnabled) {
21838
21990
  finishInkDrawing();
21839
21991
  return;
21840
21992
  }
21841
- setIsSelecting(false);
21842
- selectionStart.current = null;
21993
+ cancelSelectionDrag();
21843
21994
  }
21844
21995
  }),
21845
21996
  [
21846
- selectionEnabled,
21997
+ beginSelectionDrag,
21998
+ cancelSelectionDrag,
21999
+ finishSelectionDrag,
22000
+ isNative,
21847
22001
  inkEnabled,
21848
- pinchEnabled,
21849
- layout.width,
21850
- layout.height,
21851
- annotationColor,
21852
- zoom
22002
+ beginInkDrawing,
22003
+ finishInkDrawing,
22004
+ pushInkPoint,
22005
+ selectionEnabled,
22006
+ updateSelectionDrag
22007
+ ]
22008
+ );
22009
+ const selectionGesture = useMemo(
22010
+ () => Gesture.Pan().enabled(
22011
+ isNative && selectionEnabled && selectionRects.length === 0 && !selectionBounds
22012
+ ).maxPointers(1).minDistance(4).runOnJS(true).onStart((event) => {
22013
+ beginSelectionDrag(event.x, event.y, event.absoluteY);
22014
+ }).onUpdate((event) => {
22015
+ updateSelectionDrag(event.x, event.y, event.absoluteY);
22016
+ }).onEnd(() => {
22017
+ void finishSelectionDrag();
22018
+ }).onFinalize(() => {
22019
+ if (selectionDragActiveRef.current) {
22020
+ cancelSelectionDrag();
22021
+ }
22022
+ }),
22023
+ [
22024
+ beginSelectionDrag,
22025
+ cancelSelectionDrag,
22026
+ finishSelectionDrag,
22027
+ isNative,
22028
+ selectionBounds,
22029
+ selectionEnabled,
22030
+ selectionRects.length,
22031
+ updateSelectionDrag
21853
22032
  ]
21854
22033
  );
22034
+ const inkGesture = useMemo(
22035
+ () => Gesture.Pan().enabled(isNative && inkEnabled).maxPointers(1).minDistance(0).runOnJS(true).onStart((event) => {
22036
+ beginInkDrawing(event.x, event.y);
22037
+ }).onUpdate((event) => {
22038
+ pushInkPoint(event.x, event.y);
22039
+ }).onEnd(() => {
22040
+ finishInkDrawing();
22041
+ }).onFinalize(() => {
22042
+ if (inkDrawingActiveRef.current) {
22043
+ finishInkDrawing();
22044
+ }
22045
+ }),
22046
+ [beginInkDrawing, finishInkDrawing, inkEnabled, isNative, pushInkPoint]
22047
+ );
22048
+ const doubleTapGesture = useMemo(
22049
+ () => Gesture.Tap().enabled(isNative && activeTool === "select").numberOfTaps(2).maxDistance(24).maxDelay(280).maxDuration(250).runOnJS(true).onEnd((event, success) => {
22050
+ if (!success) return;
22051
+ handleDoubleTap(event.x, event.y);
22052
+ }),
22053
+ [activeTool, handleDoubleTap, isNative]
22054
+ );
22055
+ const contentGesture = useMemo(
22056
+ () => Gesture.Simultaneous(selectionGesture, inkGesture, doubleTapGesture),
22057
+ [doubleTapGesture, inkGesture, selectionGesture]
22058
+ );
21855
22059
  const selectionBoundsPx = useMemo(() => {
21856
22060
  if (!selectionBounds || !layout.width || !layout.height) return null;
21857
22061
  return {
@@ -21959,29 +22163,83 @@ var PageRenderer = ({
21959
22163
  const pageWidth = isNative ? baseWidth * zoom : baseWidth;
21960
22164
  const pageHeight = pageWidth / aspectRatio;
21961
22165
  const hasActiveSelection = selectionRects.length > 0 || !!selectionBounds || isSelecting;
21962
- const scrollEnabled = isNative && zoom > 1 && !hasActiveSelection && !isInkDrawing;
22166
+ const scrollEnabled = isNative && zoom > 1 && !hasActiveSelection && !isInkDrawing && !gestureScrollLockActive;
22167
+ useEffect(() => {
22168
+ if (!horizontalScrollRestore) return;
22169
+ if (lastAppliedHorizontalRestoreRef.current === horizontalScrollRestore.requestId) {
22170
+ return;
22171
+ }
22172
+ const nextOffsetX = resolveClampedScrollOffset(
22173
+ horizontalScrollRestore.offsetX,
22174
+ pageWidth,
22175
+ pageViewportWidth
22176
+ );
22177
+ lastAppliedHorizontalRestoreRef.current = horizontalScrollRestore.requestId;
22178
+ horizontalScrollOffsetRef.current = nextOffsetX;
22179
+ pageScrollRef.current?.scrollTo({ x: nextOffsetX, animated: false });
22180
+ onHorizontalScrollOffsetChange?.(pageIndex, nextOffsetX);
22181
+ }, [
22182
+ horizontalScrollRestore,
22183
+ onHorizontalScrollOffsetChange,
22184
+ pageIndex,
22185
+ pageViewportWidth,
22186
+ pageWidth
22187
+ ]);
21963
22188
  return /* @__PURE__ */ jsx(
21964
22189
  ScrollView,
21965
22190
  {
22191
+ ref: pageScrollRef,
21966
22192
  horizontal: true,
21967
22193
  scrollEnabled,
21968
22194
  showsHorizontalScrollIndicator: false,
22195
+ onScroll: (event) => {
22196
+ const nextOffsetX = event.nativeEvent.contentOffset?.x ?? 0;
22197
+ horizontalScrollOffsetRef.current = nextOffsetX;
22198
+ },
22199
+ onScrollEndDrag: () => {
22200
+ onHorizontalScrollOffsetChange?.(
22201
+ pageIndex,
22202
+ horizontalScrollOffsetRef.current
22203
+ );
22204
+ },
22205
+ onMomentumScrollEnd: () => {
22206
+ onHorizontalScrollOffsetChange?.(
22207
+ pageIndex,
22208
+ horizontalScrollOffsetRef.current
22209
+ );
22210
+ },
22211
+ scrollEventThrottle: 16,
21969
22212
  contentContainerStyle: [
21970
22213
  styles.scrollContent,
21971
22214
  { paddingHorizontal: horizontalPadding }
21972
22215
  ],
21973
- children: /* @__PURE__ */ jsxs(
22216
+ children: /* @__PURE__ */ jsx(GestureDetector, { gesture: contentGesture, children: /* @__PURE__ */ jsxs(
21974
22217
  Pressable,
21975
22218
  {
21976
22219
  ...panResponder.panHandlers,
21977
22220
  style: [
21978
22221
  styles.container,
21979
- { width: pageWidth, height: pageHeight, marginBottom: spacing }
22222
+ {
22223
+ width: pageWidth,
22224
+ height: pageHeight,
22225
+ marginBottom: spacing
22226
+ }
21980
22227
  ],
21981
22228
  onLayout: handleLayout,
22229
+ onTouchStart: (event) => logRawTouchDebug("start", event),
22230
+ onTouchMove: (event) => logRawTouchDebug("move", event),
22231
+ onTouchEnd: (event) => logRawTouchDebug("end", event),
22232
+ onTouchCancel: (event) => logRawTouchDebug("cancel", event),
21982
22233
  onPress: handlePress,
21983
22234
  children: [
21984
- /* @__PURE__ */ jsx(PageViewComponent, { ref: viewRef, style: styles.page }),
22235
+ /* @__PURE__ */ jsx(
22236
+ PageViewComponent,
22237
+ {
22238
+ ref: viewRef,
22239
+ pointerEvents: "none",
22240
+ style: styles.page
22241
+ }
22242
+ ),
21985
22243
  /* @__PURE__ */ jsx(
21986
22244
  View,
21987
22245
  {
@@ -22118,7 +22376,9 @@ var PageRenderer = ({
22118
22376
  const isSelected = selectedAnnotationId === ann.id;
22119
22377
  const isText = ann.type === "comment" || ann.type === "text";
22120
22378
  const isInk = ann.type === "ink" && Array.isArray(ann.path) && ann.path.length > 1;
22121
- const isMarkup = TEXT_MARKUP_TOOLS.has(ann.type);
22379
+ const isMarkup = TEXT_MARKUP_TOOLS.has(
22380
+ ann.type
22381
+ );
22122
22382
  const rects = ann.rects && ann.rects.length > 0 ? ann.rects : [ann.rect];
22123
22383
  const hitTargetStyle = {
22124
22384
  left: `${ann.rect.x * 100}%`,
@@ -22158,7 +22418,10 @@ var PageRenderer = ({
22158
22418
  View,
22159
22419
  {
22160
22420
  pointerEvents: "none",
22161
- style: [styles.annotationLineContainer, rectStyle],
22421
+ style: [
22422
+ styles.annotationLineContainer,
22423
+ rectStyle
22424
+ ],
22162
22425
  children: /* @__PURE__ */ jsx(
22163
22426
  View,
22164
22427
  {
@@ -22177,7 +22440,10 @@ var PageRenderer = ({
22177
22440
  View,
22178
22441
  {
22179
22442
  pointerEvents: "none",
22180
- style: [styles.annotationLineContainer, rectStyle],
22443
+ style: [
22444
+ styles.annotationLineContainer,
22445
+ rectStyle
22446
+ ],
22181
22447
  children: /* @__PURE__ */ jsx(
22182
22448
  View,
22183
22449
  {
@@ -22410,7 +22676,7 @@ var PageRenderer = ({
22410
22676
  ) : null
22411
22677
  ]
22412
22678
  }
22413
- )
22679
+ ) })
22414
22680
  }
22415
22681
  );
22416
22682
  };
@@ -22637,7 +22903,7 @@ var styles = StyleSheet.create({
22637
22903
  borderRadius: 3
22638
22904
  }
22639
22905
  });
22640
- var arePageRendererPropsEqual = (previous, next) => previous.engine === next.engine && previous.pageIndex === next.pageIndex && previous.scale === next.scale && previous.PageViewComponent === next.PageViewComponent && previous.availableWidth === next.availableWidth && previous.horizontalPadding === next.horizontalPadding && previous.spacing === next.spacing;
22906
+ var arePageRendererPropsEqual = (previous, next) => previous.engine === next.engine && previous.pageIndex === next.pageIndex && previous.scale === next.scale && previous.PageViewComponent === next.PageViewComponent && previous.availableWidth === next.availableWidth && previous.horizontalPadding === next.horizontalPadding && previous.spacing === next.spacing && previous.onSelectionDragActiveChange === next.onSelectionDragActiveChange && previous.gestureScrollLockActive === next.gestureScrollLockActive && previous.lastPinchEndedAt === next.lastPinchEndedAt && previous.onHorizontalScrollOffsetChange === next.onHorizontalScrollOffsetChange && previous.horizontalScrollRestore?.requestId === next.horizontalScrollRestore?.requestId && previous.horizontalScrollRestore?.offsetX === next.horizontalScrollRestore?.offsetX && previous.requestSelectionVerticalAutoscroll === next.requestSelectionVerticalAutoscroll;
22641
22907
  var PageRenderer_default = memo(PageRenderer, arePageRendererPropsEqual);
22642
22908
 
22643
22909
  // components/WebViewViewer.tsx
@@ -22789,6 +23055,8 @@ var MOBILE_CHROME_HIDE_DELTA = 28;
22789
23055
  var MOBILE_CHROME_SHOW_DELTA = 22;
22790
23056
  var MOBILE_CHROME_SHOW_DELAY_MS = 180;
22791
23057
  var MOBILE_CHROME_TOP_RESET = 16;
23058
+ var SELECTION_EDGE_THRESHOLD_PX2 = 48;
23059
+ var SELECTION_EDGE_MAX_STEP_PX2 = 24;
22792
23060
  var resolvePositiveInt = (value, fallback, min, max) => {
22793
23061
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
22794
23062
  const rounded = Math.round(value);
@@ -22846,6 +23114,26 @@ var Viewer = ({
22846
23114
  const scrollDownAccumRef = useRef3(0);
22847
23115
  const scrollUpAccumRef = useRef3(0);
22848
23116
  const [layoutRevision, setLayoutRevision] = useState2(0);
23117
+ const [selectionDragActive, setSelectionDragActive] = useState2(false);
23118
+ const selectionDragActiveRef = useRef3(false);
23119
+ const [gestureScrollLockActive, setGestureScrollLockActive] = useState2(false);
23120
+ const gestureScrollLockActiveRef = useRef3(false);
23121
+ const [pinchPreviewScale, setPinchPreviewScale] = useState2(1);
23122
+ const [lastPinchEndedAt, setLastPinchEndedAt] = useState2(null);
23123
+ const [horizontalScrollRestore, setHorizontalScrollRestore] = useState2(null);
23124
+ const pinchGestureActiveRef = useRef3(false);
23125
+ const pinchStartZoomRef = useRef3(1);
23126
+ const pinchPreviewZoomRef = useRef3(1);
23127
+ const pinchFocalPointRef = useRef3({ x: 0, y: 0 });
23128
+ const pinchUpdateLoggedAtRef = useRef3(0);
23129
+ const horizontalScrollOffsetsRef = useRef3(/* @__PURE__ */ new Map());
23130
+ const pendingPinchAnchorRestoreRef = useRef3(
23131
+ null
23132
+ );
23133
+ const pinchAnchorRestoreFrameRef = useRef3(null);
23134
+ const nextHorizontalRestoreRequestIdRef = useRef3(0);
23135
+ const viewerFrameRef = useRef3({ y: 0, height: 0 });
23136
+ const viewerContentHeightRef = useRef3(0);
22849
23137
  const resolvedWindowSize = useMemo3(
22850
23138
  () => resolvePositiveInt(virtualWindowSize, FLATLIST_WINDOW_SIZE, 2, 30),
22851
23139
  [virtualWindowSize]
@@ -22944,6 +23232,9 @@ var Viewer = ({
22944
23232
  if (layoutRefreshTimeoutRef.current) {
22945
23233
  clearTimeout(layoutRefreshTimeoutRef.current);
22946
23234
  }
23235
+ if (pinchAnchorRestoreFrameRef.current !== null) {
23236
+ cancelAnimationFrame(pinchAnchorRestoreFrameRef.current);
23237
+ }
22947
23238
  clearPendingScrollRetry();
22948
23239
  clearPendingChromeShow();
22949
23240
  },
@@ -23110,6 +23401,513 @@ var Viewer = ({
23110
23401
  const columnGap = 12;
23111
23402
  const horizontalPadding = 16;
23112
23403
  const columnWidth = isDouble ? (windowWidth - horizontalPadding * 2 - columnGap) / 2 : windowWidth;
23404
+ const getPageWidthForZoom = useCallback2(
23405
+ (pageIndex, zoomValue) => {
23406
+ const safeZoom = Math.max(zoomValue, 0.25);
23407
+ const baseWidth = isDouble ? columnWidth * 0.92 : windowWidth * 0.92;
23408
+ return baseWidth * safeZoom;
23409
+ },
23410
+ [columnWidth, isDouble, windowWidth]
23411
+ );
23412
+ const getPageHeightForZoom = useCallback2(
23413
+ (pageIndex, zoomValue) => getPageWidthForZoom(pageIndex, zoomValue) / getPageAspectRatio(pageIndex),
23414
+ [getPageAspectRatio, getPageWidthForZoom]
23415
+ );
23416
+ const getPageViewportMetrics = useCallback2(
23417
+ (pageIndex) => {
23418
+ if (isDouble) {
23419
+ const isRight = pageIndex % 2 === 1;
23420
+ return {
23421
+ viewportWidth: columnWidth,
23422
+ horizontalPadding: 8,
23423
+ viewportOffsetX: horizontalPadding + (isRight ? columnWidth + columnGap : 0)
23424
+ };
23425
+ }
23426
+ return {
23427
+ viewportWidth: windowWidth,
23428
+ horizontalPadding: 16,
23429
+ viewportOffsetX: 0
23430
+ };
23431
+ },
23432
+ [columnGap, columnWidth, horizontalPadding, isDouble, windowWidth]
23433
+ );
23434
+ const getPageLayoutForZoom = useCallback2(
23435
+ (pageIndex, zoomValue) => {
23436
+ if (isSingle) {
23437
+ const pageHeight = getPageHeightForZoom(pageIndex, zoomValue);
23438
+ return {
23439
+ pageOffsetY: 18,
23440
+ pageHeight,
23441
+ totalContentHeight: 18 + pageHeight + 140
23442
+ };
23443
+ }
23444
+ if (isDouble) {
23445
+ let offsetY2 = LIST_TOP_PADDING;
23446
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
23447
+ const row = rows[rowIndex];
23448
+ const leftHeight = getPageHeightForZoom(row.left, zoomValue);
23449
+ const rightHeight = row.right === null ? leftHeight : getPageHeightForZoom(row.right, zoomValue);
23450
+ const rowLength = Math.max(leftHeight, rightHeight) + DOUBLE_PAGE_SPACING;
23451
+ if (row.left === pageIndex || row.right === pageIndex) {
23452
+ let totalContentHeight = LIST_TOP_PADDING;
23453
+ for (let totalRowIndex = 0; totalRowIndex < rows.length; totalRowIndex += 1) {
23454
+ const totalRow = rows[totalRowIndex];
23455
+ const totalLeftHeight = getPageHeightForZoom(
23456
+ totalRow.left,
23457
+ zoomValue
23458
+ );
23459
+ const totalRightHeight = totalRow.right === null ? totalLeftHeight : getPageHeightForZoom(totalRow.right, zoomValue);
23460
+ totalContentHeight += Math.max(totalLeftHeight, totalRightHeight) + DOUBLE_PAGE_SPACING;
23461
+ }
23462
+ totalContentHeight += LIST_BOTTOM_PADDING;
23463
+ return {
23464
+ pageOffsetY: offsetY2,
23465
+ pageHeight: getPageHeightForZoom(pageIndex, zoomValue),
23466
+ totalContentHeight
23467
+ };
23468
+ }
23469
+ offsetY2 += rowLength;
23470
+ }
23471
+ }
23472
+ let offsetY = LIST_TOP_PADDING;
23473
+ for (let currentPageIndex = 0; currentPageIndex < pageCount; currentPageIndex += 1) {
23474
+ const currentPageHeight = getPageHeightForZoom(
23475
+ currentPageIndex,
23476
+ zoomValue
23477
+ );
23478
+ if (currentPageIndex === pageIndex) {
23479
+ let totalContentHeight = LIST_TOP_PADDING;
23480
+ for (let totalPageIndex = 0; totalPageIndex < pageCount; totalPageIndex += 1) {
23481
+ totalContentHeight += getPageHeightForZoom(totalPageIndex, zoomValue) + CONTINUOUS_PAGE_SPACING;
23482
+ }
23483
+ totalContentHeight += LIST_BOTTOM_PADDING;
23484
+ return {
23485
+ pageOffsetY: offsetY,
23486
+ pageHeight: currentPageHeight,
23487
+ totalContentHeight
23488
+ };
23489
+ }
23490
+ offsetY += currentPageHeight + CONTINUOUS_PAGE_SPACING;
23491
+ }
23492
+ return {
23493
+ pageOffsetY: LIST_TOP_PADDING,
23494
+ pageHeight: getPageHeightForZoom(pageIndex, zoomValue),
23495
+ totalContentHeight: LIST_TOP_PADDING + getPageHeightForZoom(pageIndex, zoomValue) + CONTINUOUS_PAGE_SPACING + LIST_BOTTOM_PADDING
23496
+ };
23497
+ },
23498
+ [getPageHeightForZoom, isDouble, isSingle, pageCount, rows]
23499
+ );
23500
+ const resolvePinchAnchorPageIndex = useCallback2(
23501
+ (focalX, focalY, zoomValue, scrollOffsetY = lastScrollOffsetYRef.current) => {
23502
+ if (isSingle) {
23503
+ return Math.max(0, currentPage - 1);
23504
+ }
23505
+ const contentY = Math.max(0, scrollOffsetY + focalY);
23506
+ if (isDouble) {
23507
+ let offsetY2 = LIST_TOP_PADDING;
23508
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
23509
+ const row = rows[rowIndex];
23510
+ const leftHeight = getPageHeightForZoom(row.left, zoomValue);
23511
+ const rightHeight = row.right === null ? leftHeight : getPageHeightForZoom(row.right, zoomValue);
23512
+ const rowLength = Math.max(leftHeight, rightHeight) + DOUBLE_PAGE_SPACING;
23513
+ if (contentY <= offsetY2 + rowLength || rowIndex === rows.length - 1) {
23514
+ const isRight = row.right !== null && focalX > horizontalPadding + columnWidth + columnGap / 2;
23515
+ return isRight ? row.right : row.left;
23516
+ }
23517
+ offsetY2 += rowLength;
23518
+ }
23519
+ return rows[rows.length - 1]?.left ?? 0;
23520
+ }
23521
+ let offsetY = LIST_TOP_PADDING;
23522
+ for (let pageIndex = 0; pageIndex < pageCount; pageIndex += 1) {
23523
+ const pageLength = getPageHeightForZoom(pageIndex, zoomValue) + CONTINUOUS_PAGE_SPACING;
23524
+ if (contentY <= offsetY + pageLength || pageIndex === pageCount - 1) {
23525
+ return pageIndex;
23526
+ }
23527
+ offsetY += pageLength;
23528
+ }
23529
+ return Math.max(0, pageCount - 1);
23530
+ },
23531
+ [
23532
+ columnGap,
23533
+ columnWidth,
23534
+ currentPage,
23535
+ getPageHeightForZoom,
23536
+ horizontalPadding,
23537
+ isDouble,
23538
+ isSingle,
23539
+ pageCount,
23540
+ rows
23541
+ ]
23542
+ );
23543
+ const resolvedViewerScrollEnabled = shouldEnableViewerScroll({
23544
+ selectionDragActive,
23545
+ gestureScrollLockActive
23546
+ });
23547
+ const setViewerScrollEnabledNative = useCallback2((enabled) => {
23548
+ const scrollNode = listRef.current;
23549
+ scrollNode?.setNativeProps?.({ scrollEnabled: enabled });
23550
+ }, []);
23551
+ const syncViewerScrollEnabled = useCallback2(
23552
+ (nextSelectionDragActive = selectionDragActiveRef.current, nextGestureScrollLockActive = gestureScrollLockActiveRef.current) => {
23553
+ setViewerScrollEnabledNative(
23554
+ shouldEnableViewerScroll({
23555
+ selectionDragActive: nextSelectionDragActive,
23556
+ gestureScrollLockActive: nextGestureScrollLockActive
23557
+ })
23558
+ );
23559
+ },
23560
+ [setViewerScrollEnabledNative]
23561
+ );
23562
+ const handleGestureScrollLockChange = useCallback2(
23563
+ (active) => {
23564
+ if (gestureScrollLockActiveRef.current === active) return;
23565
+ gestureScrollLockActiveRef.current = active;
23566
+ setGestureScrollLockActive(active);
23567
+ syncViewerScrollEnabled(selectionDragActiveRef.current, active);
23568
+ },
23569
+ [syncViewerScrollEnabled]
23570
+ );
23571
+ const handlePinchPreviewScaleChange = useCallback2((scale) => {
23572
+ const nextScale = sanitizePinchPreviewScale(scale);
23573
+ setPinchPreviewScale((current) => {
23574
+ if (Math.abs(current - nextScale) < 5e-4) {
23575
+ return current;
23576
+ }
23577
+ return nextScale;
23578
+ });
23579
+ }, []);
23580
+ const resetViewerPinchPreview = useCallback2(() => {
23581
+ pinchPreviewZoomRef.current = pinchStartZoomRef.current;
23582
+ handlePinchPreviewScaleChange(1);
23583
+ }, [handlePinchPreviewScaleChange]);
23584
+ const beginViewerPinch = useCallback2(
23585
+ (focalX, focalY) => {
23586
+ pinchGestureActiveRef.current = true;
23587
+ pinchStartZoomRef.current = zoom;
23588
+ pinchPreviewZoomRef.current = zoom;
23589
+ pinchFocalPointRef.current = { x: focalX, y: focalY };
23590
+ pinchUpdateLoggedAtRef.current = 0;
23591
+ setLastPinchEndedAt(null);
23592
+ handlePinchPreviewScaleChange(1);
23593
+ handleGestureScrollLockChange(true);
23594
+ if (perfEnabled) {
23595
+ logPerfEvent("Viewer", "pinch.start", {
23596
+ zoom: Math.round(zoom * 100) / 100
23597
+ });
23598
+ }
23599
+ },
23600
+ [
23601
+ handleGestureScrollLockChange,
23602
+ handlePinchPreviewScaleChange,
23603
+ perfEnabled,
23604
+ zoom
23605
+ ]
23606
+ );
23607
+ const updateViewerPinch = useCallback2(
23608
+ (scaleFactor, focalX, focalY) => {
23609
+ if (!pinchGestureActiveRef.current) return;
23610
+ pinchFocalPointRef.current = { x: focalX, y: focalY };
23611
+ const nextZoom = resolvePinchGestureZoom(
23612
+ pinchStartZoomRef.current,
23613
+ scaleFactor
23614
+ );
23615
+ pinchPreviewZoomRef.current = nextZoom;
23616
+ handlePinchPreviewScaleChange(
23617
+ resolvePinchPreviewScale(pinchStartZoomRef.current, nextZoom)
23618
+ );
23619
+ if (!perfEnabled) return;
23620
+ const now = Date.now();
23621
+ if (now - pinchUpdateLoggedAtRef.current < 120) return;
23622
+ pinchUpdateLoggedAtRef.current = now;
23623
+ logPerfEvent("Viewer", "pinch.update", {
23624
+ scale: Math.round(scaleFactor * 1e3) / 1e3,
23625
+ nextZoom: Math.round(nextZoom * 100) / 100
23626
+ });
23627
+ },
23628
+ [handlePinchPreviewScaleChange, perfEnabled]
23629
+ );
23630
+ const finishViewerPinch = useCallback2(() => {
23631
+ if (!pinchGestureActiveRef.current) return;
23632
+ pinchGestureActiveRef.current = false;
23633
+ const focalX = pinchFocalPointRef.current.x;
23634
+ const focalY = pinchFocalPointRef.current.y;
23635
+ const viewerScrollOffsetY = lastScrollOffsetYRef.current;
23636
+ const finalZoom = resolvePinchGestureZoom(
23637
+ pinchPreviewZoomRef.current || pinchStartZoomRef.current,
23638
+ 1,
23639
+ DEFAULT_PINCH_ZOOM_BOUNDS
23640
+ );
23641
+ setLastPinchEndedAt(Date.now());
23642
+ if (Math.abs(finalZoom - zoom) >= 1e-3) {
23643
+ const anchorPageIndex = resolvePinchAnchorPageIndex(
23644
+ focalX,
23645
+ focalY,
23646
+ zoom,
23647
+ viewerScrollOffsetY
23648
+ );
23649
+ const { pageOffsetY: startPageOffsetY, pageHeight: startPageHeight } = getPageLayoutForZoom(anchorPageIndex, zoom);
23650
+ const startPageWidth = getPageWidthForZoom(anchorPageIndex, zoom);
23651
+ const {
23652
+ viewportWidth: pageViewportWidth,
23653
+ horizontalPadding: pageHorizontalPadding,
23654
+ viewportOffsetX
23655
+ } = getPageViewportMetrics(anchorPageIndex);
23656
+ const pageViewportContentWidth = Math.max(
23657
+ 0,
23658
+ pageViewportWidth - pageHorizontalPadding * 2
23659
+ );
23660
+ pendingPinchAnchorRestoreRef.current = {
23661
+ finalZoom,
23662
+ focalY,
23663
+ viewerScrollOffsetY,
23664
+ pageIndex: anchorPageIndex,
23665
+ startPageOffsetY,
23666
+ startPageHeight,
23667
+ startPageWidth,
23668
+ startPageScrollX: horizontalScrollOffsetsRef.current.get(anchorPageIndex) ?? 0,
23669
+ pageViewportWidth,
23670
+ pageHorizontalPadding,
23671
+ pageViewportContentOffsetX: Math.max(
23672
+ 0,
23673
+ Math.min(
23674
+ pageViewportContentWidth,
23675
+ focalX - viewportOffsetX - pageHorizontalPadding
23676
+ )
23677
+ )
23678
+ };
23679
+ setDocumentStateTracked({ zoom: finalZoom }, "pinch.viewerEnd");
23680
+ engine.setZoom(finalZoom);
23681
+ } else {
23682
+ pendingPinchAnchorRestoreRef.current = null;
23683
+ }
23684
+ resetViewerPinchPreview();
23685
+ handleGestureScrollLockChange(false);
23686
+ if (perfEnabled) {
23687
+ logPerfEvent("Viewer", "pinch.end", {
23688
+ finalZoom: Math.round(finalZoom * 100) / 100,
23689
+ page: pendingPinchAnchorRestoreRef.current?.pageIndex ?? null
23690
+ });
23691
+ }
23692
+ }, [
23693
+ engine,
23694
+ getPageLayoutForZoom,
23695
+ getPageViewportMetrics,
23696
+ getPageWidthForZoom,
23697
+ handleGestureScrollLockChange,
23698
+ perfEnabled,
23699
+ resetViewerPinchPreview,
23700
+ resolvePinchAnchorPageIndex,
23701
+ setDocumentStateTracked,
23702
+ zoom
23703
+ ]);
23704
+ const handlePageHorizontalScrollOffsetChange = useCallback2(
23705
+ (pageIndex, offsetX) => {
23706
+ horizontalScrollOffsetsRef.current.set(pageIndex, Math.max(0, offsetX));
23707
+ const pageWidth = getPageWidthForZoom(pageIndex, zoom);
23708
+ const { viewportWidth, horizontalPadding: horizontalPadding2 } = getPageViewportMetrics(pageIndex);
23709
+ const pageViewportWidth = Math.max(
23710
+ 0,
23711
+ viewportWidth - horizontalPadding2 * 2
23712
+ );
23713
+ const nextOffsetX = resolveClampedScrollOffset(
23714
+ offsetX,
23715
+ pageWidth,
23716
+ pageViewportWidth
23717
+ );
23718
+ setHorizontalScrollRestore((current) => {
23719
+ if (current && Math.abs(current.offsetX - nextOffsetX) < 0.5) {
23720
+ return current;
23721
+ }
23722
+ const requestId = nextHorizontalRestoreRequestIdRef.current + 1;
23723
+ nextHorizontalRestoreRequestIdRef.current = requestId;
23724
+ return {
23725
+ pageIndex,
23726
+ requestId,
23727
+ offsetX: nextOffsetX
23728
+ };
23729
+ });
23730
+ },
23731
+ [getPageViewportMetrics, getPageWidthForZoom, zoom]
23732
+ );
23733
+ const viewerPinchGesture = useMemo3(
23734
+ () => Gesture2.Pinch().enabled(!isWebView && pageCount > 0).onTouchesDown((event) => {
23735
+ if ((event.allTouches?.length ?? 0) >= 2) {
23736
+ handleGestureScrollLockChange(true);
23737
+ }
23738
+ }).onTouchesUp((event) => {
23739
+ if ((event.allTouches?.length ?? 0) < 2 && !pinchGestureActiveRef.current) {
23740
+ handleGestureScrollLockChange(false);
23741
+ }
23742
+ }).runOnJS(true).onStart((event) => {
23743
+ beginViewerPinch(event.focalX, event.focalY);
23744
+ }).onUpdate((event) => {
23745
+ updateViewerPinch(event.scale, event.focalX, event.focalY);
23746
+ }).onEnd(() => {
23747
+ finishViewerPinch();
23748
+ }).onFinalize(() => {
23749
+ finishViewerPinch();
23750
+ resetViewerPinchPreview();
23751
+ handleGestureScrollLockChange(false);
23752
+ }),
23753
+ [
23754
+ beginViewerPinch,
23755
+ finishViewerPinch,
23756
+ handleGestureScrollLockChange,
23757
+ isWebView,
23758
+ pageCount,
23759
+ resetViewerPinchPreview,
23760
+ updateViewerPinch
23761
+ ]
23762
+ );
23763
+ useEffect3(() => {
23764
+ selectionDragActiveRef.current = selectionDragActive;
23765
+ syncViewerScrollEnabled(
23766
+ selectionDragActive,
23767
+ gestureScrollLockActiveRef.current
23768
+ );
23769
+ }, [selectionDragActive, syncViewerScrollEnabled]);
23770
+ useEffect3(() => {
23771
+ const pendingRestore = pendingPinchAnchorRestoreRef.current;
23772
+ if (!pendingRestore) return;
23773
+ if (Math.abs(pendingRestore.finalZoom - zoom) >= 1e-3) return;
23774
+ if (pinchAnchorRestoreFrameRef.current !== null) {
23775
+ cancelAnimationFrame(pinchAnchorRestoreFrameRef.current);
23776
+ }
23777
+ pinchAnchorRestoreFrameRef.current = requestAnimationFrame(() => {
23778
+ pinchAnchorRestoreFrameRef.current = null;
23779
+ const {
23780
+ pageOffsetY: endPageOffsetY,
23781
+ pageHeight: endPageHeight,
23782
+ totalContentHeight: endContentHeight
23783
+ } = getPageLayoutForZoom(pendingRestore.pageIndex, zoom);
23784
+ const viewerViewportHeight = viewerFrameRef.current.height;
23785
+ const nextScrollY = resolveAnchoredViewportOffset({
23786
+ viewportOffset: pendingRestore.focalY,
23787
+ startScrollOffset: pendingRestore.viewerScrollOffsetY,
23788
+ startItemOffset: pendingRestore.startPageOffsetY,
23789
+ startItemLength: pendingRestore.startPageHeight,
23790
+ endItemOffset: endPageOffsetY,
23791
+ endItemLength: endPageHeight,
23792
+ viewportLength: viewerViewportHeight,
23793
+ endContentLength: endContentHeight
23794
+ });
23795
+ if (isSingle) {
23796
+ listRef.current?.scrollTo?.({
23797
+ y: nextScrollY,
23798
+ animated: false
23799
+ });
23800
+ } else {
23801
+ listRef.current?.scrollToOffset({
23802
+ offset: nextScrollY,
23803
+ animated: false
23804
+ });
23805
+ }
23806
+ lastScrollOffsetYRef.current = nextScrollY;
23807
+ const endPageWidth = getPageWidthForZoom(pendingRestore.pageIndex, zoom);
23808
+ const pageViewportContentWidth = Math.max(
23809
+ 0,
23810
+ pendingRestore.pageViewportWidth - pendingRestore.pageHorizontalPadding * 2
23811
+ );
23812
+ const nextOffsetX = resolveAnchoredViewportOffset({
23813
+ viewportOffset: pendingRestore.pageViewportContentOffsetX,
23814
+ startScrollOffset: pendingRestore.startPageScrollX,
23815
+ startItemOffset: 0,
23816
+ startItemLength: pendingRestore.startPageWidth,
23817
+ endItemOffset: 0,
23818
+ endItemLength: endPageWidth,
23819
+ viewportLength: pageViewportContentWidth,
23820
+ endContentLength: endPageWidth
23821
+ });
23822
+ const requestId = nextHorizontalRestoreRequestIdRef.current + 1;
23823
+ nextHorizontalRestoreRequestIdRef.current = requestId;
23824
+ setHorizontalScrollRestore({
23825
+ pageIndex: pendingRestore.pageIndex,
23826
+ requestId,
23827
+ offsetX: nextOffsetX
23828
+ });
23829
+ pendingPinchAnchorRestoreRef.current = null;
23830
+ if (perfEnabled) {
23831
+ logPerfEvent("Viewer", "pinch.anchorRestore", {
23832
+ page: pendingRestore.pageIndex + 1,
23833
+ scrollY: Math.round(nextScrollY * 100) / 100,
23834
+ scrollX: Math.round(nextOffsetX * 100) / 100,
23835
+ zoom: Math.round(zoom * 100) / 100
23836
+ });
23837
+ }
23838
+ });
23839
+ return () => {
23840
+ if (pinchAnchorRestoreFrameRef.current !== null) {
23841
+ cancelAnimationFrame(pinchAnchorRestoreFrameRef.current);
23842
+ pinchAnchorRestoreFrameRef.current = null;
23843
+ }
23844
+ };
23845
+ }, [getPageLayoutForZoom, getPageWidthForZoom, isSingle, perfEnabled, zoom]);
23846
+ useEffect3(() => {
23847
+ if (zoom > 1) return;
23848
+ setHorizontalScrollRestore((current) => {
23849
+ if (!current || Math.abs(current.offsetX) < 0.5) {
23850
+ return current;
23851
+ }
23852
+ const requestId = nextHorizontalRestoreRequestIdRef.current + 1;
23853
+ nextHorizontalRestoreRequestIdRef.current = requestId;
23854
+ return {
23855
+ pageIndex: current.pageIndex,
23856
+ requestId,
23857
+ offsetX: 0
23858
+ };
23859
+ });
23860
+ }, [zoom]);
23861
+ const captureViewerFrame = useCallback2((node) => {
23862
+ const measurable = node;
23863
+ measurable?.measureInWindow?.((_, y, __, height) => {
23864
+ viewerFrameRef.current = { y, height };
23865
+ });
23866
+ }, []);
23867
+ const scrollViewerBy = useCallback2(
23868
+ (deltaY) => {
23869
+ if (!Number.isFinite(deltaY) || deltaY === 0) return 0;
23870
+ const viewportHeight = viewerFrameRef.current.height;
23871
+ const maxOffset = Math.max(
23872
+ 0,
23873
+ viewerContentHeightRef.current - viewportHeight
23874
+ );
23875
+ const nextOffset = Math.max(
23876
+ 0,
23877
+ Math.min(maxOffset, lastScrollOffsetYRef.current + deltaY)
23878
+ );
23879
+ const appliedDelta = nextOffset - lastScrollOffsetYRef.current;
23880
+ if (appliedDelta === 0) return 0;
23881
+ lastScrollOffsetYRef.current = nextOffset;
23882
+ if (isSingle) {
23883
+ listRef.current?.scrollTo?.({
23884
+ y: nextOffset,
23885
+ animated: false
23886
+ });
23887
+ return appliedDelta;
23888
+ }
23889
+ listRef.current?.scrollToOffset({ offset: nextOffset, animated: false });
23890
+ return appliedDelta;
23891
+ },
23892
+ [isSingle]
23893
+ );
23894
+ const handleSelectionVerticalAutoscroll = useCallback2(
23895
+ (absoluteY) => {
23896
+ const frame = viewerFrameRef.current;
23897
+ if (!Number.isFinite(absoluteY) || frame.height <= 0) return 0;
23898
+ const relativeY = absoluteY - frame.y;
23899
+ const { dy } = getSelectionEdgeAutoscroll({
23900
+ x: SELECTION_EDGE_THRESHOLD_PX2,
23901
+ y: relativeY,
23902
+ width: SELECTION_EDGE_THRESHOLD_PX2 * 2,
23903
+ height: frame.height,
23904
+ threshold: SELECTION_EDGE_THRESHOLD_PX2,
23905
+ maxStep: SELECTION_EDGE_MAX_STEP_PX2
23906
+ });
23907
+ return scrollViewerBy(dy);
23908
+ },
23909
+ [scrollViewerBy]
23910
+ );
23113
23911
  const listLayoutMetrics = useMemo3(() => {
23114
23912
  const offsets = [];
23115
23913
  const lengths = [];
@@ -23350,7 +24148,13 @@ var Viewer = ({
23350
24148
  pageIndex: row.left,
23351
24149
  availableWidth: columnWidth,
23352
24150
  horizontalPadding: 8,
23353
- spacing: DOUBLE_PAGE_SPACING
24151
+ spacing: DOUBLE_PAGE_SPACING,
24152
+ onSelectionDragActiveChange: setSelectionDragActive,
24153
+ gestureScrollLockActive,
24154
+ lastPinchEndedAt,
24155
+ onHorizontalScrollOffsetChange: handlePageHorizontalScrollOffsetChange,
24156
+ horizontalScrollRestore,
24157
+ requestSelectionVerticalAutoscroll: handleSelectionVerticalAutoscroll
23354
24158
  }
23355
24159
  ) }),
23356
24160
  row.right !== null ? /* @__PURE__ */ jsx3(View3, { style: { width: columnWidth }, children: /* @__PURE__ */ jsx3(
@@ -23360,7 +24164,13 @@ var Viewer = ({
23360
24164
  pageIndex: row.right,
23361
24165
  availableWidth: columnWidth,
23362
24166
  horizontalPadding: 8,
23363
- spacing: DOUBLE_PAGE_SPACING
24167
+ spacing: DOUBLE_PAGE_SPACING,
24168
+ onSelectionDragActiveChange: setSelectionDragActive,
24169
+ gestureScrollLockActive,
24170
+ lastPinchEndedAt,
24171
+ onHorizontalScrollOffsetChange: handlePageHorizontalScrollOffsetChange,
24172
+ horizontalScrollRestore,
24173
+ requestSelectionVerticalAutoscroll: handleSelectionVerticalAutoscroll
23364
24174
  }
23365
24175
  ) }) : /* @__PURE__ */ jsx3(View3, { style: { width: columnWidth } })
23366
24176
  ] });
@@ -23370,114 +24180,170 @@ var Viewer = ({
23370
24180
  {
23371
24181
  engine,
23372
24182
  pageIndex: item,
23373
- spacing: CONTINUOUS_PAGE_SPACING
24183
+ spacing: CONTINUOUS_PAGE_SPACING,
24184
+ onSelectionDragActiveChange: setSelectionDragActive,
24185
+ gestureScrollLockActive,
24186
+ lastPinchEndedAt,
24187
+ onHorizontalScrollOffsetChange: handlePageHorizontalScrollOffsetChange,
24188
+ horizontalScrollRestore,
24189
+ requestSelectionVerticalAutoscroll: handleSelectionVerticalAutoscroll
23374
24190
  }
23375
24191
  );
23376
24192
  },
23377
- [columnWidth, engine, horizontalPadding, isDouble]
24193
+ [
24194
+ columnWidth,
24195
+ engine,
24196
+ handlePageHorizontalScrollOffsetChange,
24197
+ handleSelectionVerticalAutoscroll,
24198
+ gestureScrollLockActive,
24199
+ horizontalScrollRestore,
24200
+ horizontalPadding,
24201
+ isDouble,
24202
+ lastPinchEndedAt
24203
+ ]
23378
24204
  );
23379
24205
  if (isWebView) {
23380
24206
  return /* @__PURE__ */ jsx3(View3, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ jsx3(WebViewViewer_default, { engine }) });
23381
24207
  }
23382
24208
  if (isSingle) {
23383
- return /* @__PURE__ */ jsx3(View3, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ jsx3(
23384
- ScrollView2,
24209
+ return /* @__PURE__ */ jsx3(View3, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ jsx3(GestureDetector2, { gesture: viewerPinchGesture, children: /* @__PURE__ */ jsx3(
24210
+ View3,
23385
24211
  {
23386
- contentContainerStyle: styles3.singleContent,
23387
- showsVerticalScrollIndicator: false,
23388
- scrollEnabled: true,
23389
- onScroll: (event) => handleViewerScroll(event, "single"),
23390
- onScrollBeginDrag: perfEnabled ? () => {
23391
- scrollMonitorRef.current.begin("single.beginDrag");
23392
- } : void 0,
23393
- onMomentumScrollBegin: perfEnabled ? () => {
23394
- scrollMonitorRef.current.begin("single.momentumBegin");
23395
- } : void 0,
23396
- onScrollEndDrag: perfEnabled ? () => {
23397
- scrollMonitorRef.current.end("single.endDrag");
23398
- sampleMemory("Viewer", "single.endDrag", { pageCount });
23399
- } : void 0,
23400
- onMomentumScrollEnd: perfEnabled ? () => {
23401
- scrollMonitorRef.current.end("single.momentumEnd");
23402
- sampleMemory("Viewer", "single.momentumEnd", { pageCount });
23403
- } : void 0,
23404
- scrollEventThrottle: 16,
24212
+ style: [
24213
+ styles3.gestureSurface,
24214
+ { transform: [{ scale: pinchPreviewScale }] }
24215
+ ],
23405
24216
  children: /* @__PURE__ */ jsx3(
23406
- PageRenderer_default,
24217
+ ScrollView2,
23407
24218
  {
23408
- engine,
23409
- pageIndex: Math.max(0, currentPage - 1),
23410
- spacing: 32
24219
+ ref: (node) => {
24220
+ captureViewerFrame(node);
24221
+ listRef.current = node;
24222
+ },
24223
+ contentContainerStyle: styles3.singleContent,
24224
+ showsVerticalScrollIndicator: false,
24225
+ scrollEnabled: resolvedViewerScrollEnabled,
24226
+ onLayout: () => captureViewerFrame(listRef.current),
24227
+ onContentSizeChange: (_, height) => {
24228
+ viewerContentHeightRef.current = height;
24229
+ },
24230
+ onScroll: (event) => handleViewerScroll(event, "single"),
24231
+ onScrollBeginDrag: perfEnabled ? () => {
24232
+ scrollMonitorRef.current.begin("single.beginDrag");
24233
+ } : void 0,
24234
+ onMomentumScrollBegin: perfEnabled ? () => {
24235
+ scrollMonitorRef.current.begin("single.momentumBegin");
24236
+ } : void 0,
24237
+ onScrollEndDrag: perfEnabled ? () => {
24238
+ scrollMonitorRef.current.end("single.endDrag");
24239
+ sampleMemory("Viewer", "single.endDrag", { pageCount });
24240
+ } : void 0,
24241
+ onMomentumScrollEnd: perfEnabled ? () => {
24242
+ scrollMonitorRef.current.end("single.momentumEnd");
24243
+ sampleMemory("Viewer", "single.momentumEnd", {
24244
+ pageCount
24245
+ });
24246
+ } : void 0,
24247
+ scrollEventThrottle: 16,
24248
+ children: /* @__PURE__ */ jsx3(
24249
+ PageRenderer_default,
24250
+ {
24251
+ engine,
24252
+ pageIndex: Math.max(0, currentPage - 1),
24253
+ spacing: 32,
24254
+ onSelectionDragActiveChange: setSelectionDragActive,
24255
+ gestureScrollLockActive,
24256
+ lastPinchEndedAt,
24257
+ onHorizontalScrollOffsetChange: handlePageHorizontalScrollOffsetChange,
24258
+ horizontalScrollRestore,
24259
+ requestSelectionVerticalAutoscroll: handleSelectionVerticalAutoscroll
24260
+ }
24261
+ )
23411
24262
  }
23412
24263
  )
23413
24264
  }
23414
- ) });
24265
+ ) }) });
23415
24266
  }
23416
- return /* @__PURE__ */ jsx3(View3, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ jsx3(
23417
- FlatList,
24267
+ return /* @__PURE__ */ jsx3(View3, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ jsx3(GestureDetector2, { gesture: viewerPinchGesture, children: /* @__PURE__ */ jsx3(
24268
+ View3,
23418
24269
  {
23419
- ref: listRef,
23420
- data: isDouble ? rows : pages,
23421
- initialNumToRender: FLATLIST_INITIAL_NUM_TO_RENDER,
23422
- windowSize: resolvedWindowSize,
23423
- maxToRenderPerBatch: resolvedMaxToRenderPerBatch,
23424
- updateCellsBatchingPeriod: FLATLIST_UPDATE_CELLS_BATCHING_PERIOD,
23425
- removeClippedSubviews: resolvedRemoveClippedSubviews,
23426
- getItemLayout,
23427
- keyExtractor,
23428
- contentContainerStyle: styles3.listContent,
23429
- renderItem,
23430
- onViewableItemsChanged,
23431
- viewabilityConfig: { itemVisiblePercentThreshold: 60 },
23432
- scrollEnabled: true,
23433
- onScrollToIndexFailed: ({ index, averageItemLength }) => {
23434
- const dataLength = isDouble ? rows.length : pages.length;
23435
- if (index < 0 || index >= dataLength) return;
23436
- pendingScrollIndexRef.current = index;
23437
- const offset = Math.max(0, getFallbackOffsetForIndex(index));
23438
- listRef.current?.scrollToOffset({ offset, animated: false });
23439
- if (!isDouble) {
23440
- ensurePageDimensions(index);
23441
- } else {
23442
- const row = rows[index];
23443
- if (row) {
23444
- ensurePageDimensions(row.left);
23445
- if (row.right !== null) {
23446
- ensurePageDimensions(row.right);
24270
+ style: [
24271
+ styles3.gestureSurface,
24272
+ { transform: [{ scale: pinchPreviewScale }] }
24273
+ ],
24274
+ children: /* @__PURE__ */ jsx3(
24275
+ FlatList,
24276
+ {
24277
+ ref: listRef,
24278
+ data: isDouble ? rows : pages,
24279
+ initialNumToRender: FLATLIST_INITIAL_NUM_TO_RENDER,
24280
+ windowSize: resolvedWindowSize,
24281
+ maxToRenderPerBatch: resolvedMaxToRenderPerBatch,
24282
+ updateCellsBatchingPeriod: FLATLIST_UPDATE_CELLS_BATCHING_PERIOD,
24283
+ removeClippedSubviews: resolvedRemoveClippedSubviews,
24284
+ getItemLayout,
24285
+ keyExtractor,
24286
+ contentContainerStyle: styles3.listContent,
24287
+ renderItem,
24288
+ onViewableItemsChanged,
24289
+ viewabilityConfig: { itemVisiblePercentThreshold: 60 },
24290
+ scrollEnabled: resolvedViewerScrollEnabled,
24291
+ onLayout: () => captureViewerFrame(listRef.current),
24292
+ onContentSizeChange: (_, height) => {
24293
+ viewerContentHeightRef.current = height;
24294
+ },
24295
+ onScrollToIndexFailed: ({ index, averageItemLength }) => {
24296
+ const dataLength = isDouble ? rows.length : pages.length;
24297
+ if (index < 0 || index >= dataLength) return;
24298
+ pendingScrollIndexRef.current = index;
24299
+ const offset = Math.max(0, getFallbackOffsetForIndex(index));
24300
+ listRef.current?.scrollToOffset({ offset, animated: false });
24301
+ if (!isDouble) {
24302
+ ensurePageDimensions(index);
24303
+ } else {
24304
+ const row = rows[index];
24305
+ if (row) {
24306
+ ensurePageDimensions(row.left);
24307
+ if (row.right !== null) {
24308
+ ensurePageDimensions(row.right);
24309
+ }
24310
+ }
23447
24311
  }
23448
- }
23449
- }
23450
- scheduleScrollRetry("onScrollToIndexFailed");
23451
- if (perfEnabled) {
23452
- logPerfEvent("Viewer", "scrollToIndexFailed", {
23453
- index,
23454
- averageItemLength,
23455
- fallbackOffset: offset,
23456
- fallbackSource: "cached-item-layout",
23457
- itemCount: dataLength,
23458
- retryAttempt: pendingScrollAttemptsRef.current
23459
- });
24312
+ scheduleScrollRetry("onScrollToIndexFailed");
24313
+ if (perfEnabled) {
24314
+ logPerfEvent("Viewer", "scrollToIndexFailed", {
24315
+ index,
24316
+ averageItemLength,
24317
+ fallbackOffset: offset,
24318
+ fallbackSource: "cached-item-layout",
24319
+ itemCount: dataLength,
24320
+ retryAttempt: pendingScrollAttemptsRef.current
24321
+ });
24322
+ }
24323
+ },
24324
+ onScroll: (event) => handleViewerScroll(event, "continuous"),
24325
+ onScrollBeginDrag: perfEnabled ? () => {
24326
+ scrollMonitorRef.current.begin("continuous.beginDrag");
24327
+ } : void 0,
24328
+ onMomentumScrollBegin: perfEnabled ? () => {
24329
+ scrollMonitorRef.current.begin("continuous.momentumBegin");
24330
+ } : void 0,
24331
+ onScrollEndDrag: perfEnabled ? () => {
24332
+ scrollMonitorRef.current.end("continuous.endDrag");
24333
+ sampleMemory("Viewer", "continuous.endDrag", { pageCount });
24334
+ } : void 0,
24335
+ onMomentumScrollEnd: perfEnabled ? () => {
24336
+ scrollMonitorRef.current.end("continuous.momentumEnd");
24337
+ sampleMemory("Viewer", "continuous.momentumEnd", {
24338
+ pageCount
24339
+ });
24340
+ } : void 0,
24341
+ scrollEventThrottle: 16,
24342
+ showsVerticalScrollIndicator: false
23460
24343
  }
23461
- },
23462
- onScroll: (event) => handleViewerScroll(event, "continuous"),
23463
- onScrollBeginDrag: perfEnabled ? () => {
23464
- scrollMonitorRef.current.begin("continuous.beginDrag");
23465
- } : void 0,
23466
- onMomentumScrollBegin: perfEnabled ? () => {
23467
- scrollMonitorRef.current.begin("continuous.momentumBegin");
23468
- } : void 0,
23469
- onScrollEndDrag: perfEnabled ? () => {
23470
- scrollMonitorRef.current.end("continuous.endDrag");
23471
- sampleMemory("Viewer", "continuous.endDrag", { pageCount });
23472
- } : void 0,
23473
- onMomentumScrollEnd: perfEnabled ? () => {
23474
- scrollMonitorRef.current.end("continuous.momentumEnd");
23475
- sampleMemory("Viewer", "continuous.momentumEnd", { pageCount });
23476
- } : void 0,
23477
- scrollEventThrottle: 16,
23478
- showsVerticalScrollIndicator: false
24344
+ )
23479
24345
  }
23480
- ) });
24346
+ ) }) });
23481
24347
  };
23482
24348
  var styles3 = StyleSheet3.create({
23483
24349
  container: {
@@ -23487,6 +24353,9 @@ var styles3 = StyleSheet3.create({
23487
24353
  containerDark: {
23488
24354
  backgroundColor: "#0f1115"
23489
24355
  },
24356
+ gestureSurface: {
24357
+ flex: 1
24358
+ },
23490
24359
  listContent: {
23491
24360
  paddingTop: LIST_TOP_PADDING,
23492
24361
  paddingBottom: LIST_BOTTOM_PADDING
@@ -24166,6 +25035,7 @@ var ToolDock = () => {
24166
25035
  setAnnotationColor,
24167
25036
  accentColor,
24168
25037
  activeTool,
25038
+ interactionMode,
24169
25039
  toolDockOpen,
24170
25040
  setDocumentState
24171
25041
  } = useViewerStore5();
@@ -24177,7 +25047,12 @@ var ToolDock = () => {
24177
25047
  /* @__PURE__ */ jsx6(
24178
25048
  Pressable3,
24179
25049
  {
24180
- onPress: () => setDocumentState({ toolDockOpen: false }),
25050
+ onPress: () => setDocumentState(
25051
+ getToolDockDismissState({
25052
+ activeTool,
25053
+ interactionMode
25054
+ })
25055
+ ),
24181
25056
  style: [styles5.closeButton, isDark && styles5.closeButtonDark],
24182
25057
  children: /* @__PURE__ */ jsx6(
24183
25058
  Text2,
@@ -24190,12 +25065,29 @@ var ToolDock = () => {
24190
25065
  )
24191
25066
  ] }),
24192
25067
  /* @__PURE__ */ jsx6(View5, { style: styles5.toolsRow, children: TOOL_OPTIONS.map((tool) => {
24193
- const isSelected = activeTool === tool.id;
25068
+ const isSelected = isToolDockToolSelected({
25069
+ toolId: tool.id,
25070
+ activeTool,
25071
+ interactionMode
25072
+ });
24194
25073
  const label = tool.label === "note" ? t.note : tool.label === "select" ? t.select : tool.label === "underline" ? t.underline : tool.label === "squiggly" ? t.squiggly : tool.label === "strikeout" ? t.strikeout : tool.label === "ink" ? t.ink : t.highlight;
24195
25074
  return /* @__PURE__ */ jsx6(
24196
25075
  Pressable3,
24197
25076
  {
24198
- onPress: () => setDocumentState({ activeTool: tool.id }),
25077
+ onPress: () => {
25078
+ if (tool.id === "select") {
25079
+ const shouldArmSelection = activeTool !== "select" || interactionMode !== "select";
25080
+ setDocumentState({
25081
+ activeTool: "select",
25082
+ interactionMode: shouldArmSelection ? "select" : "pan"
25083
+ });
25084
+ return;
25085
+ }
25086
+ setDocumentState({
25087
+ activeTool: tool.id,
25088
+ interactionMode: "pan"
25089
+ });
25090
+ },
24199
25091
  style: [
24200
25092
  styles5.toolButton,
24201
25093
  isDark && styles5.toolButtonDark,