@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.js CHANGED
@@ -21033,11 +21033,13 @@ module.exports = __toCommonJS(ui_react_native_exports);
21033
21033
  // components/Viewer.tsx
21034
21034
  var import_react3 = require("react");
21035
21035
  var import_react_native3 = require("react-native");
21036
+ var import_react_native_gesture_handler2 = require("react-native-gesture-handler");
21036
21037
  var import_core3 = require("@papyrus-sdk/core");
21037
21038
 
21038
21039
  // components/PageRenderer.tsx
21039
21040
  var import_react = require("react");
21040
21041
  var import_react_native = require("react-native");
21042
+ var import_react_native_gesture_handler = require("react-native-gesture-handler");
21041
21043
  var import_react_native_svg = __toESM(require("react-native-svg"));
21042
21044
  var import_core = require("@papyrus-sdk/core");
21043
21045
  var import_engine_native = require("@papyrus-sdk/engine-native");
@@ -21224,6 +21226,126 @@ var createScrollPerfMonitor = (scope, label = "scroll") => {
21224
21226
  };
21225
21227
  var logPerfEvent = logPerf;
21226
21228
 
21229
+ // gesture/pinchZoom.ts
21230
+ var DEFAULT_PINCH_ZOOM_BOUNDS = {
21231
+ minZoom: 0.5,
21232
+ maxZoom: 4
21233
+ };
21234
+ var PINCH_PRESS_SUPPRESSION_MS = 120;
21235
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
21236
+ var resolvePinchPreviewScale = (startZoom, previewZoom) => {
21237
+ const safeStartZoom = Math.max(1e-4, Math.abs(startZoom));
21238
+ return previewZoom / safeStartZoom;
21239
+ };
21240
+ var sanitizePinchPreviewScale = (value) => {
21241
+ if (!Number.isFinite(value) || value <= 0) {
21242
+ return 1;
21243
+ }
21244
+ return value;
21245
+ };
21246
+ var resolvePinchGestureZoom = (startZoom, scaleFactor, bounds = DEFAULT_PINCH_ZOOM_BOUNDS) => clamp(
21247
+ (Number.isFinite(startZoom) && startZoom > 0 ? startZoom : 1) * (Number.isFinite(scaleFactor) && scaleFactor > 0 ? scaleFactor : 1),
21248
+ bounds.minZoom,
21249
+ bounds.maxZoom
21250
+ );
21251
+ var resolveAnchoredViewportOffset = ({
21252
+ viewportOffset,
21253
+ startScrollOffset,
21254
+ startItemOffset,
21255
+ startItemLength,
21256
+ endItemOffset,
21257
+ endItemLength,
21258
+ viewportLength,
21259
+ endContentLength
21260
+ }) => {
21261
+ const safeViewportOffset = Number.isFinite(viewportOffset) ? viewportOffset : 0;
21262
+ const safeStartScrollOffset = Number.isFinite(startScrollOffset) ? startScrollOffset : 0;
21263
+ const safeStartItemOffset = Number.isFinite(startItemOffset) ? startItemOffset : 0;
21264
+ const safeStartItemLength = Number.isFinite(startItemLength) && startItemLength > 0 ? startItemLength : 1;
21265
+ const safeEndItemOffset = Number.isFinite(endItemOffset) ? endItemOffset : 0;
21266
+ const safeEndItemLength = Number.isFinite(endItemLength) && endItemLength > 0 ? endItemLength : 1;
21267
+ const safeViewportLength = Number.isFinite(viewportLength) && viewportLength > 0 ? viewportLength : 0;
21268
+ const safeEndContentLength = Number.isFinite(endContentLength) && endContentLength > 0 ? endContentLength : safeEndItemOffset + safeEndItemLength;
21269
+ const contentPoint = safeStartScrollOffset + safeViewportOffset - safeStartItemOffset;
21270
+ const normalizedPoint = clamp(contentPoint / safeStartItemLength, 0, 1);
21271
+ const anchoredContentPoint = safeEndItemOffset + normalizedPoint * safeEndItemLength;
21272
+ return clamp(
21273
+ anchoredContentPoint - safeViewportOffset,
21274
+ 0,
21275
+ Math.max(0, safeEndContentLength - safeViewportLength)
21276
+ );
21277
+ };
21278
+ var resolveClampedScrollOffset = (offset, contentLength, viewportLength) => {
21279
+ const safeOffset = Number.isFinite(offset) ? offset : 0;
21280
+ const safeContentLength = Number.isFinite(contentLength) && contentLength > 0 ? contentLength : 0;
21281
+ const safeViewportLength = Number.isFinite(viewportLength) && viewportLength > 0 ? viewportLength : 0;
21282
+ return clamp(
21283
+ safeOffset,
21284
+ 0,
21285
+ Math.max(0, safeContentLength - safeViewportLength)
21286
+ );
21287
+ };
21288
+ var shouldSuppressPressAfterPinch = (lastPinchEndedAt, now = Date.now(), windowMs = PINCH_PRESS_SUPPRESSION_MS) => {
21289
+ if (typeof lastPinchEndedAt !== "number") return false;
21290
+ const elapsedMs = now - lastPinchEndedAt;
21291
+ return elapsedMs >= 0 && elapsedMs < windowMs;
21292
+ };
21293
+
21294
+ // gesture/selectionInteraction.ts
21295
+ var clamp2 = (value, min, max) => Math.min(max, Math.max(min, value));
21296
+ var DRAG_SELECTION_TOOLS = /* @__PURE__ */ new Set([
21297
+ "highlight",
21298
+ "underline",
21299
+ "squiggly",
21300
+ "strikeout"
21301
+ ]);
21302
+ var resolveAxisAutoscroll = (coordinate, size, threshold, maxStep) => {
21303
+ if (size <= 0 || threshold <= 0 || maxStep <= 0) return 0;
21304
+ const startDistance = clamp2(coordinate, 0, size);
21305
+ if (startDistance < threshold) {
21306
+ const intensity = 1 - startDistance / threshold;
21307
+ return -Math.round(maxStep * intensity);
21308
+ }
21309
+ const endDistance = clamp2(size - coordinate, 0, size);
21310
+ if (endDistance < threshold) {
21311
+ const intensity = 1 - endDistance / threshold;
21312
+ return Math.round(maxStep * intensity);
21313
+ }
21314
+ return 0;
21315
+ };
21316
+ var shouldEnableViewerScroll = ({
21317
+ selectionDragActive,
21318
+ gestureScrollLockActive = false
21319
+ }) => !selectionDragActive && !gestureScrollLockActive;
21320
+ var shouldEnableSelectionDrag = ({
21321
+ activeTool,
21322
+ interactionMode
21323
+ }) => DRAG_SELECTION_TOOLS.has(activeTool) || activeTool === "select" && interactionMode === "select";
21324
+ var isToolDockToolSelected = ({
21325
+ activeTool,
21326
+ interactionMode,
21327
+ toolId
21328
+ }) => toolId === "select" ? activeTool === "select" && interactionMode === "select" : activeTool === toolId;
21329
+ var getToolDockDismissState = ({
21330
+ activeTool,
21331
+ interactionMode
21332
+ }) => ({
21333
+ toolDockOpen: false,
21334
+ activeTool,
21335
+ interactionMode: activeTool === "select" && interactionMode === "select" ? "pan" : interactionMode
21336
+ });
21337
+ var getSelectionEdgeAutoscroll = ({
21338
+ x,
21339
+ y,
21340
+ width,
21341
+ height,
21342
+ threshold,
21343
+ maxStep
21344
+ }) => ({
21345
+ dx: resolveAxisAutoscroll(x, width, threshold, maxStep),
21346
+ dy: resolveAxisAutoscroll(y, height, threshold, maxStep)
21347
+ });
21348
+
21227
21349
  // components/PageRenderer.tsx
21228
21350
  var import_jsx_runtime = require("react/jsx-runtime");
21229
21351
  var TEXT_MARKUP_TOOLS = /* @__PURE__ */ new Set([
@@ -21286,6 +21408,9 @@ var buildSquigglyPath = (segments = 16) => {
21286
21408
  return path;
21287
21409
  };
21288
21410
  var SQUIGGLY_PATH = buildSquigglyPath();
21411
+ var SELECTION_EDGE_THRESHOLD_PX = 48;
21412
+ var SELECTION_EDGE_MAX_STEP_PX = 24;
21413
+ var SELECTION_AUTOSCROLL_INTERVAL_MS = 16;
21289
21414
  var PageRenderer = ({
21290
21415
  engine,
21291
21416
  pageIndex,
@@ -21293,26 +21418,37 @@ var PageRenderer = ({
21293
21418
  PageViewComponent = import_engine_native.PapyrusPageView,
21294
21419
  availableWidth,
21295
21420
  horizontalPadding = 16,
21296
- spacing = 24
21421
+ spacing = 24,
21422
+ onSelectionDragActiveChange,
21423
+ gestureScrollLockActive = false,
21424
+ lastPinchEndedAt = null,
21425
+ onHorizontalScrollOffsetChange,
21426
+ horizontalScrollRestore = null,
21427
+ requestSelectionVerticalAutoscroll
21297
21428
  }) => {
21298
21429
  const viewRef = (0, import_react.useRef)(null);
21430
+ const pageScrollRef = (0, import_react.useRef)(null);
21299
21431
  const [layout, setLayout] = (0, import_react.useState)({ width: 0, height: 0 });
21300
21432
  const [pageSize, setPageSize] = (0, import_react.useState)(null);
21301
21433
  const { width: windowWidth } = (0, import_react_native.useWindowDimensions)();
21302
21434
  const isNative = import_react_native.Platform.OS === "android" || import_react_native.Platform.OS === "ios";
21303
21435
  const perfEnabled = isMobilePerfEnabled();
21304
21436
  const renderCountRef = (0, import_react.useRef)(0);
21305
- const setStateBurstRef = (0, import_react.useRef)(
21306
- createBurstMonitor("PageRenderer", "setDocumentState", 18, 700)
21307
- );
21437
+ const inkDrawingActiveRef = (0, import_react.useRef)(false);
21438
+ const horizontalScrollOffsetRef = (0, import_react.useRef)(0);
21439
+ const selectionDragActiveRef = (0, import_react.useRef)(false);
21440
+ const selectionDragPointRef = (0, import_react.useRef)(null);
21441
+ const lastAppliedHorizontalRestoreRef = (0, import_react.useRef)(null);
21442
+ const selectionAutoscrollIntervalRef = (0, import_react.useRef)(null);
21443
+ const rawTouchMoveLoggedAtRef = (0, import_react.useRef)(0);
21308
21444
  const zoom = (0, import_core.useViewerStore)((state) => state.zoom);
21309
21445
  const rotation = (0, import_core.useViewerStore)((state) => state.rotation);
21310
21446
  const pageTheme = (0, import_core.useViewerStore)((state) => state.pageTheme);
21311
21447
  const annotations = (0, import_core.useViewerStore)((state) => state.annotations);
21312
21448
  const annotationColor = (0, import_core.useViewerStore)((state) => state.annotationColor);
21313
21449
  const addAnnotation = (0, import_core.useViewerStore)((state) => state.addAnnotation);
21314
- const setDocumentState = (0, import_core.useViewerStore)((state) => state.setDocumentState);
21315
21450
  const activeTool = (0, import_core.useViewerStore)((state) => state.activeTool);
21451
+ const interactionMode = (0, import_core.useViewerStore)((state) => state.interactionMode);
21316
21452
  const accentColor = (0, import_core.useViewerStore)((state) => state.accentColor);
21317
21453
  const selectedAnnotationId = (0, import_core.useViewerStore)(
21318
21454
  (state) => state.selectedAnnotationId
@@ -21326,19 +21462,6 @@ var PageRenderer = ({
21326
21462
  const setSelectionActive = (0, import_core.useViewerStore)(
21327
21463
  (state) => state.setSelectionActive
21328
21464
  );
21329
- const setDocumentStateTracked = (0, import_react.useCallback)(
21330
- (state, reason) => {
21331
- if (perfEnabled) {
21332
- setStateBurstRef.current({
21333
- reason,
21334
- page: pageIndex + 1,
21335
- keys: Object.keys(state).join(",")
21336
- });
21337
- }
21338
- setDocumentState(state);
21339
- },
21340
- [pageIndex, perfEnabled, setDocumentState]
21341
- );
21342
21465
  const logSelectionPerf = (0, import_react.useCallback)(
21343
21466
  (event, payload) => {
21344
21467
  if (!perfEnabled) return;
@@ -21349,6 +21472,56 @@ var PageRenderer = ({
21349
21472
  },
21350
21473
  [pageIndex, perfEnabled]
21351
21474
  );
21475
+ const logGestureDebug = (0, import_react.useCallback)(
21476
+ (event, payload) => {
21477
+ if (!perfEnabled || !isNative) return;
21478
+ logPerfEvent("PageRenderer", `gesture.${event}`, {
21479
+ page: pageIndex + 1,
21480
+ activeTool,
21481
+ interactionMode,
21482
+ pinchActive: gestureScrollLockActive,
21483
+ gestureLockActive: gestureScrollLockActive,
21484
+ selectionEnabled: import_react_native.Platform.OS === "web" || isNative && shouldEnableSelectionDrag({
21485
+ activeTool,
21486
+ interactionMode
21487
+ }),
21488
+ zoom: Math.round(zoom * 100) / 100,
21489
+ ...payload
21490
+ });
21491
+ },
21492
+ [activeTool, interactionMode, isNative, pageIndex, perfEnabled, zoom]
21493
+ );
21494
+ const logRawTouchDebug = (0, import_react.useCallback)(
21495
+ (phase, event) => {
21496
+ if (!perfEnabled || !isNative) return;
21497
+ const touches = Array.isArray(event?.nativeEvent?.touches) ? event.nativeEvent.touches.length : 0;
21498
+ const changedTouches = Array.isArray(event?.nativeEvent?.changedTouches) ? event.nativeEvent.changedTouches.length : 0;
21499
+ if (phase === "move") {
21500
+ if (touches < 2 && !gestureScrollLockActive) return;
21501
+ const now = Date.now();
21502
+ if (now - rawTouchMoveLoggedAtRef.current < 120) return;
21503
+ rawTouchMoveLoggedAtRef.current = now;
21504
+ }
21505
+ logGestureDebug(`touch.${phase}`, {
21506
+ touches,
21507
+ changedTouches,
21508
+ target: event?.nativeEvent?.target ?? null,
21509
+ locationX: Math.round((event?.nativeEvent?.locationX ?? 0) * 100) / 100,
21510
+ locationY: Math.round((event?.nativeEvent?.locationY ?? 0) * 100) / 100,
21511
+ pageX: Math.round((event?.nativeEvent?.pageX ?? 0) * 100) / 100,
21512
+ pageY: Math.round((event?.nativeEvent?.pageY ?? 0) * 100) / 100
21513
+ });
21514
+ },
21515
+ [isNative, logGestureDebug, perfEnabled]
21516
+ );
21517
+ const setSelectionDragState = (0, import_react.useCallback)(
21518
+ (active) => {
21519
+ if (selectionDragActiveRef.current === active) return;
21520
+ selectionDragActiveRef.current = active;
21521
+ onSelectionDragActiveChange?.(active);
21522
+ },
21523
+ [onSelectionDragActiveChange]
21524
+ );
21352
21525
  const pageAnnotations = (0, import_react.useMemo)(
21353
21526
  () => annotations.filter((ann) => ann.pageIndex === pageIndex),
21354
21527
  [annotations, pageIndex]
@@ -21377,12 +21550,6 @@ var PageRenderer = ({
21377
21550
  const selectionRectRef = (0, import_react.useRef)(null);
21378
21551
  const selectionBoundsRef = (0, import_react.useRef)(null);
21379
21552
  const selectionBoundsStart = (0, import_react.useRef)(null);
21380
- const lastTapRef = (0, import_react.useRef)(
21381
- null
21382
- );
21383
- const pinchRef = (0, import_react.useRef)(null);
21384
- const isPinchingRef = (0, import_react.useRef)(false);
21385
- const pinchLogZoomRef = (0, import_react.useRef)(zoom);
21386
21553
  const [isInkDrawing, setIsInkDrawing] = (0, import_react.useState)(false);
21387
21554
  const [inkPoints, setInkPoints] = (0, import_react.useState)(
21388
21555
  []
@@ -21480,7 +21647,7 @@ var PageRenderer = ({
21480
21647
  createdAt: Date.now()
21481
21648
  });
21482
21649
  };
21483
- const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
21650
+ const clamp3 = (value, min, max) => Math.min(max, Math.max(min, value));
21484
21651
  const getBoundsFromRects = (rects) => {
21485
21652
  let minX = 1;
21486
21653
  let minY = 1;
@@ -21494,10 +21661,10 @@ var PageRenderer = ({
21494
21661
  });
21495
21662
  if (maxX <= minX || maxY <= minY) return null;
21496
21663
  return {
21497
- x: clamp(minX, 0, 1),
21498
- y: clamp(minY, 0, 1),
21499
- width: clamp(maxX - minX, 0, 1),
21500
- height: clamp(maxY - minY, 0, 1)
21664
+ x: clamp3(minX, 0, 1),
21665
+ y: clamp3(minY, 0, 1),
21666
+ width: clamp3(maxX - minX, 0, 1),
21667
+ height: clamp3(maxY - minY, 0, 1)
21501
21668
  };
21502
21669
  };
21503
21670
  (0, import_react.useEffect)(() => {
@@ -21505,11 +21672,30 @@ var PageRenderer = ({
21505
21672
  }, [inkPoints]);
21506
21673
  (0, import_react.useEffect)(() => {
21507
21674
  if (activeTool === "ink") return;
21675
+ inkDrawingActiveRef.current = false;
21508
21676
  setIsInkDrawing(false);
21509
21677
  setInkPoints([]);
21510
21678
  inkPointsRef.current = [];
21511
21679
  }, [activeTool]);
21512
- const clearSelection = () => {
21680
+ const pageViewportWidth = Math.max(
21681
+ 0,
21682
+ (availableWidth ?? windowWidth) - horizontalPadding * 2
21683
+ );
21684
+ const selectionEnabled = import_react_native.Platform.OS === "web" || isNative && shouldEnableSelectionDrag({
21685
+ activeTool,
21686
+ interactionMode
21687
+ });
21688
+ const inkEnabled = isNative && activeTool === "ink";
21689
+ const stopSelectionAutoscroll = (0, import_react.useCallback)(() => {
21690
+ if (selectionAutoscrollIntervalRef.current) {
21691
+ clearInterval(selectionAutoscrollIntervalRef.current);
21692
+ selectionAutoscrollIntervalRef.current = null;
21693
+ }
21694
+ }, []);
21695
+ const clearSelection = (0, import_react.useCallback)(() => {
21696
+ stopSelectionAutoscroll();
21697
+ selectionDragPointRef.current = null;
21698
+ setSelectionDragState(false);
21513
21699
  setSelectionRect(null);
21514
21700
  selectionRectRef.current = null;
21515
21701
  setSelectionRects([]);
@@ -21519,13 +21705,19 @@ var PageRenderer = ({
21519
21705
  setIsSelecting(false);
21520
21706
  selectionStart.current = null;
21521
21707
  selectionBoundsStart.current = null;
21522
- lastTapRef.current = null;
21523
21708
  setSelectionActive(false);
21524
- };
21709
+ }, [setSelectionActive, setSelectionDragState, stopSelectionAutoscroll]);
21525
21710
  (0, import_react.useEffect)(() => {
21526
21711
  if (activeTool === "select") return;
21527
21712
  clearSelection();
21528
21713
  }, [activeTool]);
21714
+ (0, import_react.useEffect)(
21715
+ () => () => {
21716
+ stopSelectionAutoscroll();
21717
+ setSelectionDragState(false);
21718
+ },
21719
+ [setSelectionDragState, stopSelectionAutoscroll]
21720
+ );
21529
21721
  const stopPressPropagation = (event) => {
21530
21722
  event.stopPropagation?.();
21531
21723
  };
@@ -21592,8 +21784,8 @@ var PageRenderer = ({
21592
21784
  if (!layout.width || !layout.height) return;
21593
21785
  const size = 26;
21594
21786
  const half = size / 2;
21595
- const left = clamp(x - half, 0, Math.max(0, layout.width - size));
21596
- const top = clamp(y - half, 0, Math.max(0, layout.height - size));
21787
+ const left = clamp3(x - half, 0, Math.max(0, layout.width - size));
21788
+ const top = clamp3(y - half, 0, Math.max(0, layout.height - size));
21597
21789
  const bounds = {
21598
21790
  x: left / layout.width,
21599
21791
  y: top / layout.height,
@@ -21602,50 +21794,193 @@ var PageRenderer = ({
21602
21794
  };
21603
21795
  await selectFromBounds(bounds);
21604
21796
  };
21605
- const getTouchDistance = (touches) => {
21606
- if (touches.length < 2) return 0;
21607
- const [a, b] = touches;
21608
- return Math.hypot(b.pageX - a.pageX, b.pageY - a.pageY);
21609
- };
21610
- const shouldHandlePinch = (touches) => isNative && touches.length === 2;
21611
- const handlePinchStart = (touches) => {
21612
- if (!shouldHandlePinch(touches)) return;
21613
- const distance = getTouchDistance(touches);
21614
- pinchRef.current = { distance, zoom };
21615
- pinchLogZoomRef.current = zoom;
21616
- logSelectionPerf("pinch.start", {
21617
- tool: activeTool,
21618
- distance: Math.round(distance * 100) / 100,
21619
- zoom: Math.round(zoom * 100) / 100
21797
+ const cancelSelectionDrag = (0, import_react.useCallback)(() => {
21798
+ stopSelectionAutoscroll();
21799
+ selectionDragPointRef.current = null;
21800
+ setSelectionDragState(false);
21801
+ setIsSelecting(false);
21802
+ selectionStart.current = null;
21803
+ selectionRectRef.current = null;
21804
+ setSelectionRect(null);
21805
+ }, [setSelectionDragState, stopSelectionAutoscroll]);
21806
+ const updateSelectionRectFromPoint = (0, import_react.useCallback)(
21807
+ (x, y) => {
21808
+ const start = selectionStart.current;
21809
+ if (!start || !layout.width || !layout.height) return;
21810
+ const left = Math.max(0, Math.min(start.x, x));
21811
+ const top = Math.max(0, Math.min(start.y, y));
21812
+ const right = Math.min(layout.width, Math.max(start.x, x));
21813
+ const bottom = Math.min(layout.height, Math.max(start.y, y));
21814
+ const rect = {
21815
+ x: left,
21816
+ y: top,
21817
+ width: right - left,
21818
+ height: bottom - top
21819
+ };
21820
+ selectionRectRef.current = rect;
21821
+ setSelectionRect(rect);
21822
+ },
21823
+ [layout.height, layout.width]
21824
+ );
21825
+ const applySelectionEdgeAutoscroll = (0, import_react.useCallback)(() => {
21826
+ const point = selectionDragPointRef.current;
21827
+ if (!point || !selectionStart.current || !layout.width || !layout.height) {
21828
+ stopSelectionAutoscroll();
21829
+ return;
21830
+ }
21831
+ const visibleX = point.x - horizontalScrollOffsetRef.current;
21832
+ const { dx } = getSelectionEdgeAutoscroll({
21833
+ x: visibleX,
21834
+ y: SELECTION_EDGE_THRESHOLD_PX,
21835
+ width: pageViewportWidth,
21836
+ height: SELECTION_EDGE_THRESHOLD_PX * 2,
21837
+ threshold: SELECTION_EDGE_THRESHOLD_PX,
21838
+ maxStep: SELECTION_EDGE_MAX_STEP_PX
21620
21839
  });
21621
- };
21622
- const handlePinchMove = (touches) => {
21623
- if (!shouldHandlePinch(touches) || !pinchRef.current) return;
21624
- const distance = getTouchDistance(touches);
21625
- if (!distance) return;
21626
- const scale2 = distance / pinchRef.current.distance;
21627
- const nextZoom = clamp(pinchRef.current.zoom * scale2, 0.5, 4);
21628
- setDocumentStateTracked({ zoom: nextZoom }, "pinchMove");
21629
- engine.setZoom(nextZoom);
21630
- if (Math.abs(nextZoom - pinchLogZoomRef.current) >= 0.12) {
21631
- pinchLogZoomRef.current = nextZoom;
21632
- logSelectionPerf("pinch.move", {
21633
- tool: activeTool,
21634
- distance: Math.round(distance * 100) / 100,
21635
- zoom: Math.round(nextZoom * 100) / 100
21636
- });
21840
+ let appliedDx = 0;
21841
+ if (dx !== 0 && pageViewportWidth > 0) {
21842
+ const maxOffsetX = Math.max(0, layout.width - pageViewportWidth);
21843
+ const nextOffsetX = clamp3(
21844
+ horizontalScrollOffsetRef.current + dx,
21845
+ 0,
21846
+ maxOffsetX
21847
+ );
21848
+ appliedDx = nextOffsetX - horizontalScrollOffsetRef.current;
21849
+ if (appliedDx !== 0) {
21850
+ horizontalScrollOffsetRef.current = nextOffsetX;
21851
+ pageScrollRef.current?.scrollTo({ x: nextOffsetX, animated: false });
21852
+ }
21637
21853
  }
21638
- };
21639
- const handlePinchEnd = () => {
21640
- if (isPinchingRef.current || pinchRef.current) {
21641
- logSelectionPerf("pinch.end", {
21642
- tool: activeTool,
21643
- zoom: Math.round(zoom * 100) / 100
21644
- });
21854
+ const appliedDy = requestSelectionVerticalAutoscroll?.(point.absoluteY) ?? 0;
21855
+ if (appliedDx === 0 && appliedDy === 0) {
21856
+ stopSelectionAutoscroll();
21857
+ return;
21645
21858
  }
21646
- pinchRef.current = null;
21647
- };
21859
+ const nextX = clamp3(point.x + appliedDx, 0, layout.width);
21860
+ const nextY = clamp3(point.y + appliedDy, 0, layout.height);
21861
+ selectionDragPointRef.current = {
21862
+ absoluteY: point.absoluteY,
21863
+ x: nextX,
21864
+ y: nextY
21865
+ };
21866
+ updateSelectionRectFromPoint(nextX, nextY);
21867
+ }, [
21868
+ layout.height,
21869
+ layout.width,
21870
+ pageViewportWidth,
21871
+ requestSelectionVerticalAutoscroll,
21872
+ stopSelectionAutoscroll,
21873
+ updateSelectionRectFromPoint
21874
+ ]);
21875
+ const ensureSelectionAutoscroll = (0, import_react.useCallback)(() => {
21876
+ if (selectionAutoscrollIntervalRef.current) return;
21877
+ selectionAutoscrollIntervalRef.current = setInterval(
21878
+ applySelectionEdgeAutoscroll,
21879
+ SELECTION_AUTOSCROLL_INTERVAL_MS
21880
+ );
21881
+ }, [applySelectionEdgeAutoscroll]);
21882
+ const beginSelectionDrag = (0, import_react.useCallback)(
21883
+ (x, y, absoluteY) => {
21884
+ if (!selectionEnabled || !layout.width || !layout.height || selectionRects.length > 0 || selectionBounds) {
21885
+ return;
21886
+ }
21887
+ const start = {
21888
+ x: clamp3(x, 0, layout.width),
21889
+ y: clamp3(y, 0, layout.height)
21890
+ };
21891
+ selectionStart.current = start;
21892
+ selectionDragPointRef.current = { absoluteY, ...start };
21893
+ setSelectionDragState(true);
21894
+ setIsSelecting(true);
21895
+ const rect = { x: start.x, y: start.y, width: 0, height: 0 };
21896
+ selectionRectRef.current = rect;
21897
+ setSelectionRect(rect);
21898
+ },
21899
+ [
21900
+ layout.height,
21901
+ layout.width,
21902
+ selectionBounds,
21903
+ selectionEnabled,
21904
+ selectionRects.length,
21905
+ setSelectionDragState
21906
+ ]
21907
+ );
21908
+ const updateSelectionDrag = (0, import_react.useCallback)(
21909
+ (x, y, absoluteY) => {
21910
+ if (!selectionEnabled || !selectionStart.current) return;
21911
+ const nextX = clamp3(x, 0, layout.width);
21912
+ const nextY = clamp3(y, 0, layout.height);
21913
+ selectionDragPointRef.current = {
21914
+ absoluteY,
21915
+ x: nextX,
21916
+ y: nextY
21917
+ };
21918
+ updateSelectionRectFromPoint(nextX, nextY);
21919
+ ensureSelectionAutoscroll();
21920
+ },
21921
+ [
21922
+ ensureSelectionAutoscroll,
21923
+ layout.height,
21924
+ layout.width,
21925
+ selectionEnabled,
21926
+ updateSelectionRectFromPoint
21927
+ ]
21928
+ );
21929
+ const finishSelectionDrag = (0, import_react.useCallback)(async () => {
21930
+ stopSelectionAutoscroll();
21931
+ selectionDragPointRef.current = null;
21932
+ setSelectionDragState(false);
21933
+ const rect = selectionRectRef.current;
21934
+ if (!selectionEnabled || !rect || !layout.width || !layout.height) {
21935
+ setIsSelecting(false);
21936
+ selectionStart.current = null;
21937
+ return;
21938
+ }
21939
+ setIsSelecting(false);
21940
+ selectionStart.current = null;
21941
+ const minSize = 6;
21942
+ if (rect.width < minSize || rect.height < minSize) {
21943
+ clearSelection();
21944
+ return;
21945
+ }
21946
+ const normalized = {
21947
+ x: rect.x / layout.width,
21948
+ y: rect.y / layout.height,
21949
+ width: rect.width / layout.width,
21950
+ height: rect.height / layout.height
21951
+ };
21952
+ await selectFromBounds(normalized);
21953
+ setSelectionRect(null);
21954
+ }, [
21955
+ clearSelection,
21956
+ layout.height,
21957
+ layout.width,
21958
+ selectionEnabled,
21959
+ setSelectionDragState,
21960
+ stopSelectionAutoscroll
21961
+ ]);
21962
+ const handleDoubleTap = (0, import_react.useCallback)(
21963
+ (x, y) => {
21964
+ if (shouldSuppressPressAfterPinch(lastPinchEndedAt)) {
21965
+ return;
21966
+ }
21967
+ if (!isNative || activeTool !== "select" || selectionRects.length > 0 || selectionBounds) {
21968
+ return;
21969
+ }
21970
+ void selectAtPoint(x, y);
21971
+ },
21972
+ [
21973
+ activeTool,
21974
+ isNative,
21975
+ lastPinchEndedAt,
21976
+ selectionBounds,
21977
+ selectionRects.length
21978
+ ]
21979
+ );
21648
21980
  const handlePress = (event) => {
21981
+ if (shouldSuppressPressAfterPinch(lastPinchEndedAt)) {
21982
+ return;
21983
+ }
21649
21984
  if (!layout.width || !layout.height) return;
21650
21985
  const { locationX, locationY } = event.nativeEvent;
21651
21986
  if (selectionRects.length > 0 || selectionBounds) {
@@ -21666,20 +22001,7 @@ var PageRenderer = ({
21666
22001
  return;
21667
22002
  }
21668
22003
  setSelectedAnnotation(null);
21669
- if (!isNative || activeTool === "ink") return;
21670
- const now = Date.now();
21671
- const lastTap = lastTapRef.current;
21672
- lastTapRef.current = { time: now, x: locationX, y: locationY };
21673
- if (!lastTap) return;
21674
- const timeDelta = now - lastTap.time;
21675
- const distance = Math.hypot(locationX - lastTap.x, locationY - lastTap.y);
21676
- if (timeDelta < 280 && distance < 24 && activeTool === "select") {
21677
- void selectAtPoint(locationX, locationY);
21678
- }
21679
22004
  };
21680
- const selectionEnabled = import_react_native.Platform.OS === "web" || isNative && (activeTool === "select" || TEXT_MARKUP_TOOLS.has(activeTool));
21681
- const inkEnabled = isNative && activeTool === "ink";
21682
- const pinchEnabled = isNative;
21683
22005
  const toNormalizedPoint = (x, y) => {
21684
22006
  if (!layout.width || !layout.height) return null;
21685
22007
  return {
@@ -21691,6 +22013,7 @@ var PageRenderer = ({
21691
22013
  const point = toNormalizedPoint(x, y);
21692
22014
  if (!point) return;
21693
22015
  clearSelection();
22016
+ inkDrawingActiveRef.current = true;
21694
22017
  setIsInkDrawing(true);
21695
22018
  setInkPoints([point]);
21696
22019
  inkPointsRef.current = [point];
@@ -21709,6 +22032,7 @@ var PageRenderer = ({
21709
22032
  const finishInkDrawing = () => {
21710
22033
  const points = inkPointsRef.current;
21711
22034
  if (points.length === 0) return;
22035
+ inkDrawingActiveRef.current = false;
21712
22036
  setIsInkDrawing(false);
21713
22037
  setInkPoints([]);
21714
22038
  inkPointsRef.current = [];
@@ -21731,31 +22055,18 @@ var PageRenderer = ({
21731
22055
  const panResponder = (0, import_react.useMemo)(
21732
22056
  () => import_react_native.PanResponder.create({
21733
22057
  onStartShouldSetPanResponder: (event) => {
22058
+ if (isNative) return false;
21734
22059
  const touches = event.nativeEvent.touches ?? [];
21735
- return pinchEnabled && shouldHandlePinch(touches) || selectionEnabled || inkEnabled;
22060
+ if (touches.length !== 1) return false;
22061
+ return inkEnabled;
21736
22062
  },
21737
22063
  onMoveShouldSetPanResponder: (event) => {
22064
+ if (isNative) return false;
21738
22065
  const touches = event.nativeEvent.touches ?? [];
21739
- return pinchEnabled && shouldHandlePinch(touches) || selectionEnabled || inkEnabled;
21740
- },
21741
- onStartShouldSetPanResponderCapture: (event) => {
21742
- const touches = event.nativeEvent.touches ?? [];
21743
- return pinchEnabled && shouldHandlePinch(touches);
21744
- },
21745
- onMoveShouldSetPanResponderCapture: (event) => {
21746
- const touches = event.nativeEvent.touches ?? [];
21747
- return pinchEnabled && shouldHandlePinch(touches);
22066
+ if (touches.length !== 1) return false;
22067
+ return selectionEnabled || inkEnabled;
21748
22068
  },
21749
22069
  onPanResponderGrant: (event) => {
21750
- const touches = event.nativeEvent.touches ?? [];
21751
- if (pinchEnabled && shouldHandlePinch(touches)) {
21752
- isPinchingRef.current = true;
21753
- setIsSelecting(false);
21754
- selectionStart.current = null;
21755
- handlePinchStart(touches);
21756
- return;
21757
- }
21758
- isPinchingRef.current = false;
21759
22070
  if (inkEnabled) {
21760
22071
  beginInkDrawing(
21761
22072
  event.nativeEvent.locationX,
@@ -21763,26 +22074,13 @@ var PageRenderer = ({
21763
22074
  );
21764
22075
  return;
21765
22076
  }
21766
- if (!selectionEnabled || !layout.width || !layout.height) return;
21767
- const { locationX, locationY } = event.nativeEvent;
21768
- selectionStart.current = { x: locationX, y: locationY };
21769
- setIsSelecting(true);
21770
- const rect = { x: locationX, y: locationY, width: 0, height: 0 };
21771
- selectionRectRef.current = rect;
21772
- setSelectionRect(rect);
22077
+ beginSelectionDrag(
22078
+ event.nativeEvent.locationX,
22079
+ event.nativeEvent.locationY,
22080
+ event.nativeEvent.pageY ?? event.nativeEvent.locationY
22081
+ );
21773
22082
  },
21774
- onPanResponderMove: (event, gestureState) => {
21775
- const touches = event.nativeEvent.touches ?? [];
21776
- if (pinchEnabled && (shouldHandlePinch(touches) || isPinchingRef.current)) {
21777
- if (shouldHandlePinch(touches)) {
21778
- if (!isPinchingRef.current) {
21779
- isPinchingRef.current = true;
21780
- handlePinchStart(touches);
21781
- }
21782
- handlePinchMove(touches);
21783
- }
21784
- return;
21785
- }
22083
+ onPanResponderMove: (event) => {
21786
22084
  if (inkEnabled) {
21787
22085
  pushInkPoint(
21788
22086
  event.nativeEvent.locationX,
@@ -21790,79 +22088,90 @@ var PageRenderer = ({
21790
22088
  );
21791
22089
  return;
21792
22090
  }
21793
- if (!selectionEnabled || !selectionStart.current) return;
21794
- const start = selectionStart.current;
21795
- const currentX = start.x + gestureState.dx;
21796
- const currentY = start.y + gestureState.dy;
21797
- const left = Math.max(0, Math.min(start.x, currentX));
21798
- const top = Math.max(0, Math.min(start.y, currentY));
21799
- const right = Math.min(layout.width, Math.max(start.x, currentX));
21800
- const bottom = Math.min(layout.height, Math.max(start.y, currentY));
21801
- const rect = {
21802
- x: left,
21803
- y: top,
21804
- width: right - left,
21805
- height: bottom - top
21806
- };
21807
- selectionRectRef.current = rect;
21808
- setSelectionRect(rect);
22091
+ updateSelectionDrag(
22092
+ event.nativeEvent.locationX,
22093
+ event.nativeEvent.locationY,
22094
+ event.nativeEvent.pageY ?? event.nativeEvent.locationY
22095
+ );
21809
22096
  },
21810
22097
  onPanResponderRelease: async () => {
21811
- if (isPinchingRef.current) {
21812
- isPinchingRef.current = false;
21813
- handlePinchEnd();
21814
- return;
21815
- }
21816
22098
  if (inkEnabled) {
21817
22099
  finishInkDrawing();
21818
22100
  return;
21819
22101
  }
21820
- const rect = selectionRectRef.current;
21821
- if (!selectionEnabled || !rect || !layout.width || !layout.height) {
21822
- setIsSelecting(false);
21823
- selectionStart.current = null;
21824
- return;
21825
- }
21826
- setIsSelecting(false);
21827
- selectionStart.current = null;
21828
- const minSize = 6;
21829
- if (rect.width < minSize || rect.height < minSize) {
21830
- clearSelection();
21831
- return;
21832
- }
21833
- const normalized = {
21834
- x: rect.x / layout.width,
21835
- y: rect.y / layout.height,
21836
- width: rect.width / layout.width,
21837
- height: rect.height / layout.height
21838
- };
21839
- await selectFromBounds(normalized);
21840
- setSelectionRect(null);
22102
+ await finishSelectionDrag();
21841
22103
  },
21842
22104
  onPanResponderTerminate: () => {
21843
- if (isPinchingRef.current) {
21844
- isPinchingRef.current = false;
21845
- handlePinchEnd();
21846
- return;
21847
- }
21848
22105
  if (inkEnabled) {
21849
22106
  finishInkDrawing();
21850
22107
  return;
21851
22108
  }
21852
- setIsSelecting(false);
21853
- selectionStart.current = null;
22109
+ cancelSelectionDrag();
21854
22110
  }
21855
22111
  }),
21856
22112
  [
21857
- selectionEnabled,
22113
+ beginSelectionDrag,
22114
+ cancelSelectionDrag,
22115
+ finishSelectionDrag,
22116
+ isNative,
21858
22117
  inkEnabled,
21859
- pinchEnabled,
21860
- layout.width,
21861
- layout.height,
21862
- annotationColor,
21863
- zoom
22118
+ beginInkDrawing,
22119
+ finishInkDrawing,
22120
+ pushInkPoint,
22121
+ selectionEnabled,
22122
+ updateSelectionDrag
22123
+ ]
22124
+ );
22125
+ const selectionGesture = (0, import_react.useMemo)(
22126
+ () => import_react_native_gesture_handler.Gesture.Pan().enabled(
22127
+ isNative && selectionEnabled && selectionRects.length === 0 && !selectionBounds
22128
+ ).maxPointers(1).minDistance(4).runOnJS(true).onStart((event) => {
22129
+ beginSelectionDrag(event.x, event.y, event.absoluteY);
22130
+ }).onUpdate((event) => {
22131
+ updateSelectionDrag(event.x, event.y, event.absoluteY);
22132
+ }).onEnd(() => {
22133
+ void finishSelectionDrag();
22134
+ }).onFinalize(() => {
22135
+ if (selectionDragActiveRef.current) {
22136
+ cancelSelectionDrag();
22137
+ }
22138
+ }),
22139
+ [
22140
+ beginSelectionDrag,
22141
+ cancelSelectionDrag,
22142
+ finishSelectionDrag,
22143
+ isNative,
22144
+ selectionBounds,
22145
+ selectionEnabled,
22146
+ selectionRects.length,
22147
+ updateSelectionDrag
21864
22148
  ]
21865
22149
  );
22150
+ const inkGesture = (0, import_react.useMemo)(
22151
+ () => import_react_native_gesture_handler.Gesture.Pan().enabled(isNative && inkEnabled).maxPointers(1).minDistance(0).runOnJS(true).onStart((event) => {
22152
+ beginInkDrawing(event.x, event.y);
22153
+ }).onUpdate((event) => {
22154
+ pushInkPoint(event.x, event.y);
22155
+ }).onEnd(() => {
22156
+ finishInkDrawing();
22157
+ }).onFinalize(() => {
22158
+ if (inkDrawingActiveRef.current) {
22159
+ finishInkDrawing();
22160
+ }
22161
+ }),
22162
+ [beginInkDrawing, finishInkDrawing, inkEnabled, isNative, pushInkPoint]
22163
+ );
22164
+ const doubleTapGesture = (0, import_react.useMemo)(
22165
+ () => import_react_native_gesture_handler.Gesture.Tap().enabled(isNative && activeTool === "select").numberOfTaps(2).maxDistance(24).maxDelay(280).maxDuration(250).runOnJS(true).onEnd((event, success) => {
22166
+ if (!success) return;
22167
+ handleDoubleTap(event.x, event.y);
22168
+ }),
22169
+ [activeTool, handleDoubleTap, isNative]
22170
+ );
22171
+ const contentGesture = (0, import_react.useMemo)(
22172
+ () => import_react_native_gesture_handler.Gesture.Simultaneous(selectionGesture, inkGesture, doubleTapGesture),
22173
+ [doubleTapGesture, inkGesture, selectionGesture]
22174
+ );
21866
22175
  const selectionBoundsPx = (0, import_react.useMemo)(() => {
21867
22176
  if (!selectionBounds || !layout.width || !layout.height) return null;
21868
22177
  return {
@@ -21886,8 +22195,8 @@ var PageRenderer = ({
21886
22195
  const minSize = 0.01;
21887
22196
  let next = { ...start };
21888
22197
  if (handle === "start") {
21889
- const newX = clamp(start.x + dx, 0, start.x + start.width - minSize);
21890
- const newY = clamp(start.y + dy, 0, start.y + start.height - minSize);
22198
+ const newX = clamp3(start.x + dx, 0, start.x + start.width - minSize);
22199
+ const newY = clamp3(start.y + dy, 0, start.y + start.height - minSize);
21891
22200
  next = {
21892
22201
  x: newX,
21893
22202
  y: newY,
@@ -21895,8 +22204,8 @@ var PageRenderer = ({
21895
22204
  height: start.y + start.height - newY
21896
22205
  };
21897
22206
  } else {
21898
- const maxX = clamp(start.x + start.width + dx, start.x + minSize, 1);
21899
- const maxY = clamp(start.y + start.height + dy, start.y + minSize, 1);
22207
+ const maxX = clamp3(start.x + start.width + dx, start.x + minSize, 1);
22208
+ const maxY = clamp3(start.y + start.height + dy, start.y + minSize, 1);
21900
22209
  next = {
21901
22210
  x: start.x,
21902
22211
  y: start.y,
@@ -21970,29 +22279,83 @@ var PageRenderer = ({
21970
22279
  const pageWidth = isNative ? baseWidth * zoom : baseWidth;
21971
22280
  const pageHeight = pageWidth / aspectRatio;
21972
22281
  const hasActiveSelection = selectionRects.length > 0 || !!selectionBounds || isSelecting;
21973
- const scrollEnabled = isNative && zoom > 1 && !hasActiveSelection && !isInkDrawing;
22282
+ const scrollEnabled = isNative && zoom > 1 && !hasActiveSelection && !isInkDrawing && !gestureScrollLockActive;
22283
+ (0, import_react.useEffect)(() => {
22284
+ if (!horizontalScrollRestore) return;
22285
+ if (lastAppliedHorizontalRestoreRef.current === horizontalScrollRestore.requestId) {
22286
+ return;
22287
+ }
22288
+ const nextOffsetX = resolveClampedScrollOffset(
22289
+ horizontalScrollRestore.offsetX,
22290
+ pageWidth,
22291
+ pageViewportWidth
22292
+ );
22293
+ lastAppliedHorizontalRestoreRef.current = horizontalScrollRestore.requestId;
22294
+ horizontalScrollOffsetRef.current = nextOffsetX;
22295
+ pageScrollRef.current?.scrollTo({ x: nextOffsetX, animated: false });
22296
+ onHorizontalScrollOffsetChange?.(pageIndex, nextOffsetX);
22297
+ }, [
22298
+ horizontalScrollRestore,
22299
+ onHorizontalScrollOffsetChange,
22300
+ pageIndex,
22301
+ pageViewportWidth,
22302
+ pageWidth
22303
+ ]);
21974
22304
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21975
22305
  import_react_native.ScrollView,
21976
22306
  {
22307
+ ref: pageScrollRef,
21977
22308
  horizontal: true,
21978
22309
  scrollEnabled,
21979
22310
  showsHorizontalScrollIndicator: false,
22311
+ onScroll: (event) => {
22312
+ const nextOffsetX = event.nativeEvent.contentOffset?.x ?? 0;
22313
+ horizontalScrollOffsetRef.current = nextOffsetX;
22314
+ },
22315
+ onScrollEndDrag: () => {
22316
+ onHorizontalScrollOffsetChange?.(
22317
+ pageIndex,
22318
+ horizontalScrollOffsetRef.current
22319
+ );
22320
+ },
22321
+ onMomentumScrollEnd: () => {
22322
+ onHorizontalScrollOffsetChange?.(
22323
+ pageIndex,
22324
+ horizontalScrollOffsetRef.current
22325
+ );
22326
+ },
22327
+ scrollEventThrottle: 16,
21980
22328
  contentContainerStyle: [
21981
22329
  styles.scrollContent,
21982
22330
  { paddingHorizontal: horizontalPadding }
21983
22331
  ],
21984
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22332
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_gesture_handler.GestureDetector, { gesture: contentGesture, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
21985
22333
  import_react_native.Pressable,
21986
22334
  {
21987
22335
  ...panResponder.panHandlers,
21988
22336
  style: [
21989
22337
  styles.container,
21990
- { width: pageWidth, height: pageHeight, marginBottom: spacing }
22338
+ {
22339
+ width: pageWidth,
22340
+ height: pageHeight,
22341
+ marginBottom: spacing
22342
+ }
21991
22343
  ],
21992
22344
  onLayout: handleLayout,
22345
+ onTouchStart: (event) => logRawTouchDebug("start", event),
22346
+ onTouchMove: (event) => logRawTouchDebug("move", event),
22347
+ onTouchEnd: (event) => logRawTouchDebug("end", event),
22348
+ onTouchCancel: (event) => logRawTouchDebug("cancel", event),
21993
22349
  onPress: handlePress,
21994
22350
  children: [
21995
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageViewComponent, { ref: viewRef, style: styles.page }),
22351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22352
+ PageViewComponent,
22353
+ {
22354
+ ref: viewRef,
22355
+ pointerEvents: "none",
22356
+ style: styles.page
22357
+ }
22358
+ ),
21996
22359
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21997
22360
  import_react_native.View,
21998
22361
  {
@@ -22129,7 +22492,9 @@ var PageRenderer = ({
22129
22492
  const isSelected = selectedAnnotationId === ann.id;
22130
22493
  const isText = ann.type === "comment" || ann.type === "text";
22131
22494
  const isInk = ann.type === "ink" && Array.isArray(ann.path) && ann.path.length > 1;
22132
- const isMarkup = TEXT_MARKUP_TOOLS.has(ann.type);
22495
+ const isMarkup = TEXT_MARKUP_TOOLS.has(
22496
+ ann.type
22497
+ );
22133
22498
  const rects = ann.rects && ann.rects.length > 0 ? ann.rects : [ann.rect];
22134
22499
  const hitTargetStyle = {
22135
22500
  left: `${ann.rect.x * 100}%`,
@@ -22169,7 +22534,10 @@ var PageRenderer = ({
22169
22534
  import_react_native.View,
22170
22535
  {
22171
22536
  pointerEvents: "none",
22172
- style: [styles.annotationLineContainer, rectStyle],
22537
+ style: [
22538
+ styles.annotationLineContainer,
22539
+ rectStyle
22540
+ ],
22173
22541
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22174
22542
  import_react_native.View,
22175
22543
  {
@@ -22188,7 +22556,10 @@ var PageRenderer = ({
22188
22556
  import_react_native.View,
22189
22557
  {
22190
22558
  pointerEvents: "none",
22191
- style: [styles.annotationLineContainer, rectStyle],
22559
+ style: [
22560
+ styles.annotationLineContainer,
22561
+ rectStyle
22562
+ ],
22192
22563
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22193
22564
  import_react_native.View,
22194
22565
  {
@@ -22421,7 +22792,7 @@ var PageRenderer = ({
22421
22792
  ) : null
22422
22793
  ]
22423
22794
  }
22424
- )
22795
+ ) })
22425
22796
  }
22426
22797
  );
22427
22798
  };
@@ -22648,7 +23019,7 @@ var styles = import_react_native.StyleSheet.create({
22648
23019
  borderRadius: 3
22649
23020
  }
22650
23021
  });
22651
- 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;
23022
+ 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;
22652
23023
  var PageRenderer_default = (0, import_react.memo)(PageRenderer, arePageRendererPropsEqual);
22653
23024
 
22654
23025
  // components/WebViewViewer.tsx
@@ -22800,6 +23171,8 @@ var MOBILE_CHROME_HIDE_DELTA = 28;
22800
23171
  var MOBILE_CHROME_SHOW_DELTA = 22;
22801
23172
  var MOBILE_CHROME_SHOW_DELAY_MS = 180;
22802
23173
  var MOBILE_CHROME_TOP_RESET = 16;
23174
+ var SELECTION_EDGE_THRESHOLD_PX2 = 48;
23175
+ var SELECTION_EDGE_MAX_STEP_PX2 = 24;
22803
23176
  var resolvePositiveInt = (value, fallback, min, max) => {
22804
23177
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
22805
23178
  const rounded = Math.round(value);
@@ -22857,6 +23230,26 @@ var Viewer = ({
22857
23230
  const scrollDownAccumRef = (0, import_react3.useRef)(0);
22858
23231
  const scrollUpAccumRef = (0, import_react3.useRef)(0);
22859
23232
  const [layoutRevision, setLayoutRevision] = (0, import_react3.useState)(0);
23233
+ const [selectionDragActive, setSelectionDragActive] = (0, import_react3.useState)(false);
23234
+ const selectionDragActiveRef = (0, import_react3.useRef)(false);
23235
+ const [gestureScrollLockActive, setGestureScrollLockActive] = (0, import_react3.useState)(false);
23236
+ const gestureScrollLockActiveRef = (0, import_react3.useRef)(false);
23237
+ const [pinchPreviewScale, setPinchPreviewScale] = (0, import_react3.useState)(1);
23238
+ const [lastPinchEndedAt, setLastPinchEndedAt] = (0, import_react3.useState)(null);
23239
+ const [horizontalScrollRestore, setHorizontalScrollRestore] = (0, import_react3.useState)(null);
23240
+ const pinchGestureActiveRef = (0, import_react3.useRef)(false);
23241
+ const pinchStartZoomRef = (0, import_react3.useRef)(1);
23242
+ const pinchPreviewZoomRef = (0, import_react3.useRef)(1);
23243
+ const pinchFocalPointRef = (0, import_react3.useRef)({ x: 0, y: 0 });
23244
+ const pinchUpdateLoggedAtRef = (0, import_react3.useRef)(0);
23245
+ const horizontalScrollOffsetsRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
23246
+ const pendingPinchAnchorRestoreRef = (0, import_react3.useRef)(
23247
+ null
23248
+ );
23249
+ const pinchAnchorRestoreFrameRef = (0, import_react3.useRef)(null);
23250
+ const nextHorizontalRestoreRequestIdRef = (0, import_react3.useRef)(0);
23251
+ const viewerFrameRef = (0, import_react3.useRef)({ y: 0, height: 0 });
23252
+ const viewerContentHeightRef = (0, import_react3.useRef)(0);
22860
23253
  const resolvedWindowSize = (0, import_react3.useMemo)(
22861
23254
  () => resolvePositiveInt(virtualWindowSize, FLATLIST_WINDOW_SIZE, 2, 30),
22862
23255
  [virtualWindowSize]
@@ -22955,6 +23348,9 @@ var Viewer = ({
22955
23348
  if (layoutRefreshTimeoutRef.current) {
22956
23349
  clearTimeout(layoutRefreshTimeoutRef.current);
22957
23350
  }
23351
+ if (pinchAnchorRestoreFrameRef.current !== null) {
23352
+ cancelAnimationFrame(pinchAnchorRestoreFrameRef.current);
23353
+ }
22958
23354
  clearPendingScrollRetry();
22959
23355
  clearPendingChromeShow();
22960
23356
  },
@@ -23121,6 +23517,513 @@ var Viewer = ({
23121
23517
  const columnGap = 12;
23122
23518
  const horizontalPadding = 16;
23123
23519
  const columnWidth = isDouble ? (windowWidth - horizontalPadding * 2 - columnGap) / 2 : windowWidth;
23520
+ const getPageWidthForZoom = (0, import_react3.useCallback)(
23521
+ (pageIndex, zoomValue) => {
23522
+ const safeZoom = Math.max(zoomValue, 0.25);
23523
+ const baseWidth = isDouble ? columnWidth * 0.92 : windowWidth * 0.92;
23524
+ return baseWidth * safeZoom;
23525
+ },
23526
+ [columnWidth, isDouble, windowWidth]
23527
+ );
23528
+ const getPageHeightForZoom = (0, import_react3.useCallback)(
23529
+ (pageIndex, zoomValue) => getPageWidthForZoom(pageIndex, zoomValue) / getPageAspectRatio(pageIndex),
23530
+ [getPageAspectRatio, getPageWidthForZoom]
23531
+ );
23532
+ const getPageViewportMetrics = (0, import_react3.useCallback)(
23533
+ (pageIndex) => {
23534
+ if (isDouble) {
23535
+ const isRight = pageIndex % 2 === 1;
23536
+ return {
23537
+ viewportWidth: columnWidth,
23538
+ horizontalPadding: 8,
23539
+ viewportOffsetX: horizontalPadding + (isRight ? columnWidth + columnGap : 0)
23540
+ };
23541
+ }
23542
+ return {
23543
+ viewportWidth: windowWidth,
23544
+ horizontalPadding: 16,
23545
+ viewportOffsetX: 0
23546
+ };
23547
+ },
23548
+ [columnGap, columnWidth, horizontalPadding, isDouble, windowWidth]
23549
+ );
23550
+ const getPageLayoutForZoom = (0, import_react3.useCallback)(
23551
+ (pageIndex, zoomValue) => {
23552
+ if (isSingle) {
23553
+ const pageHeight = getPageHeightForZoom(pageIndex, zoomValue);
23554
+ return {
23555
+ pageOffsetY: 18,
23556
+ pageHeight,
23557
+ totalContentHeight: 18 + pageHeight + 140
23558
+ };
23559
+ }
23560
+ if (isDouble) {
23561
+ let offsetY2 = LIST_TOP_PADDING;
23562
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
23563
+ const row = rows[rowIndex];
23564
+ const leftHeight = getPageHeightForZoom(row.left, zoomValue);
23565
+ const rightHeight = row.right === null ? leftHeight : getPageHeightForZoom(row.right, zoomValue);
23566
+ const rowLength = Math.max(leftHeight, rightHeight) + DOUBLE_PAGE_SPACING;
23567
+ if (row.left === pageIndex || row.right === pageIndex) {
23568
+ let totalContentHeight = LIST_TOP_PADDING;
23569
+ for (let totalRowIndex = 0; totalRowIndex < rows.length; totalRowIndex += 1) {
23570
+ const totalRow = rows[totalRowIndex];
23571
+ const totalLeftHeight = getPageHeightForZoom(
23572
+ totalRow.left,
23573
+ zoomValue
23574
+ );
23575
+ const totalRightHeight = totalRow.right === null ? totalLeftHeight : getPageHeightForZoom(totalRow.right, zoomValue);
23576
+ totalContentHeight += Math.max(totalLeftHeight, totalRightHeight) + DOUBLE_PAGE_SPACING;
23577
+ }
23578
+ totalContentHeight += LIST_BOTTOM_PADDING;
23579
+ return {
23580
+ pageOffsetY: offsetY2,
23581
+ pageHeight: getPageHeightForZoom(pageIndex, zoomValue),
23582
+ totalContentHeight
23583
+ };
23584
+ }
23585
+ offsetY2 += rowLength;
23586
+ }
23587
+ }
23588
+ let offsetY = LIST_TOP_PADDING;
23589
+ for (let currentPageIndex = 0; currentPageIndex < pageCount; currentPageIndex += 1) {
23590
+ const currentPageHeight = getPageHeightForZoom(
23591
+ currentPageIndex,
23592
+ zoomValue
23593
+ );
23594
+ if (currentPageIndex === pageIndex) {
23595
+ let totalContentHeight = LIST_TOP_PADDING;
23596
+ for (let totalPageIndex = 0; totalPageIndex < pageCount; totalPageIndex += 1) {
23597
+ totalContentHeight += getPageHeightForZoom(totalPageIndex, zoomValue) + CONTINUOUS_PAGE_SPACING;
23598
+ }
23599
+ totalContentHeight += LIST_BOTTOM_PADDING;
23600
+ return {
23601
+ pageOffsetY: offsetY,
23602
+ pageHeight: currentPageHeight,
23603
+ totalContentHeight
23604
+ };
23605
+ }
23606
+ offsetY += currentPageHeight + CONTINUOUS_PAGE_SPACING;
23607
+ }
23608
+ return {
23609
+ pageOffsetY: LIST_TOP_PADDING,
23610
+ pageHeight: getPageHeightForZoom(pageIndex, zoomValue),
23611
+ totalContentHeight: LIST_TOP_PADDING + getPageHeightForZoom(pageIndex, zoomValue) + CONTINUOUS_PAGE_SPACING + LIST_BOTTOM_PADDING
23612
+ };
23613
+ },
23614
+ [getPageHeightForZoom, isDouble, isSingle, pageCount, rows]
23615
+ );
23616
+ const resolvePinchAnchorPageIndex = (0, import_react3.useCallback)(
23617
+ (focalX, focalY, zoomValue, scrollOffsetY = lastScrollOffsetYRef.current) => {
23618
+ if (isSingle) {
23619
+ return Math.max(0, currentPage - 1);
23620
+ }
23621
+ const contentY = Math.max(0, scrollOffsetY + focalY);
23622
+ if (isDouble) {
23623
+ let offsetY2 = LIST_TOP_PADDING;
23624
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
23625
+ const row = rows[rowIndex];
23626
+ const leftHeight = getPageHeightForZoom(row.left, zoomValue);
23627
+ const rightHeight = row.right === null ? leftHeight : getPageHeightForZoom(row.right, zoomValue);
23628
+ const rowLength = Math.max(leftHeight, rightHeight) + DOUBLE_PAGE_SPACING;
23629
+ if (contentY <= offsetY2 + rowLength || rowIndex === rows.length - 1) {
23630
+ const isRight = row.right !== null && focalX > horizontalPadding + columnWidth + columnGap / 2;
23631
+ return isRight ? row.right : row.left;
23632
+ }
23633
+ offsetY2 += rowLength;
23634
+ }
23635
+ return rows[rows.length - 1]?.left ?? 0;
23636
+ }
23637
+ let offsetY = LIST_TOP_PADDING;
23638
+ for (let pageIndex = 0; pageIndex < pageCount; pageIndex += 1) {
23639
+ const pageLength = getPageHeightForZoom(pageIndex, zoomValue) + CONTINUOUS_PAGE_SPACING;
23640
+ if (contentY <= offsetY + pageLength || pageIndex === pageCount - 1) {
23641
+ return pageIndex;
23642
+ }
23643
+ offsetY += pageLength;
23644
+ }
23645
+ return Math.max(0, pageCount - 1);
23646
+ },
23647
+ [
23648
+ columnGap,
23649
+ columnWidth,
23650
+ currentPage,
23651
+ getPageHeightForZoom,
23652
+ horizontalPadding,
23653
+ isDouble,
23654
+ isSingle,
23655
+ pageCount,
23656
+ rows
23657
+ ]
23658
+ );
23659
+ const resolvedViewerScrollEnabled = shouldEnableViewerScroll({
23660
+ selectionDragActive,
23661
+ gestureScrollLockActive
23662
+ });
23663
+ const setViewerScrollEnabledNative = (0, import_react3.useCallback)((enabled) => {
23664
+ const scrollNode = listRef.current;
23665
+ scrollNode?.setNativeProps?.({ scrollEnabled: enabled });
23666
+ }, []);
23667
+ const syncViewerScrollEnabled = (0, import_react3.useCallback)(
23668
+ (nextSelectionDragActive = selectionDragActiveRef.current, nextGestureScrollLockActive = gestureScrollLockActiveRef.current) => {
23669
+ setViewerScrollEnabledNative(
23670
+ shouldEnableViewerScroll({
23671
+ selectionDragActive: nextSelectionDragActive,
23672
+ gestureScrollLockActive: nextGestureScrollLockActive
23673
+ })
23674
+ );
23675
+ },
23676
+ [setViewerScrollEnabledNative]
23677
+ );
23678
+ const handleGestureScrollLockChange = (0, import_react3.useCallback)(
23679
+ (active) => {
23680
+ if (gestureScrollLockActiveRef.current === active) return;
23681
+ gestureScrollLockActiveRef.current = active;
23682
+ setGestureScrollLockActive(active);
23683
+ syncViewerScrollEnabled(selectionDragActiveRef.current, active);
23684
+ },
23685
+ [syncViewerScrollEnabled]
23686
+ );
23687
+ const handlePinchPreviewScaleChange = (0, import_react3.useCallback)((scale) => {
23688
+ const nextScale = sanitizePinchPreviewScale(scale);
23689
+ setPinchPreviewScale((current) => {
23690
+ if (Math.abs(current - nextScale) < 5e-4) {
23691
+ return current;
23692
+ }
23693
+ return nextScale;
23694
+ });
23695
+ }, []);
23696
+ const resetViewerPinchPreview = (0, import_react3.useCallback)(() => {
23697
+ pinchPreviewZoomRef.current = pinchStartZoomRef.current;
23698
+ handlePinchPreviewScaleChange(1);
23699
+ }, [handlePinchPreviewScaleChange]);
23700
+ const beginViewerPinch = (0, import_react3.useCallback)(
23701
+ (focalX, focalY) => {
23702
+ pinchGestureActiveRef.current = true;
23703
+ pinchStartZoomRef.current = zoom;
23704
+ pinchPreviewZoomRef.current = zoom;
23705
+ pinchFocalPointRef.current = { x: focalX, y: focalY };
23706
+ pinchUpdateLoggedAtRef.current = 0;
23707
+ setLastPinchEndedAt(null);
23708
+ handlePinchPreviewScaleChange(1);
23709
+ handleGestureScrollLockChange(true);
23710
+ if (perfEnabled) {
23711
+ logPerfEvent("Viewer", "pinch.start", {
23712
+ zoom: Math.round(zoom * 100) / 100
23713
+ });
23714
+ }
23715
+ },
23716
+ [
23717
+ handleGestureScrollLockChange,
23718
+ handlePinchPreviewScaleChange,
23719
+ perfEnabled,
23720
+ zoom
23721
+ ]
23722
+ );
23723
+ const updateViewerPinch = (0, import_react3.useCallback)(
23724
+ (scaleFactor, focalX, focalY) => {
23725
+ if (!pinchGestureActiveRef.current) return;
23726
+ pinchFocalPointRef.current = { x: focalX, y: focalY };
23727
+ const nextZoom = resolvePinchGestureZoom(
23728
+ pinchStartZoomRef.current,
23729
+ scaleFactor
23730
+ );
23731
+ pinchPreviewZoomRef.current = nextZoom;
23732
+ handlePinchPreviewScaleChange(
23733
+ resolvePinchPreviewScale(pinchStartZoomRef.current, nextZoom)
23734
+ );
23735
+ if (!perfEnabled) return;
23736
+ const now = Date.now();
23737
+ if (now - pinchUpdateLoggedAtRef.current < 120) return;
23738
+ pinchUpdateLoggedAtRef.current = now;
23739
+ logPerfEvent("Viewer", "pinch.update", {
23740
+ scale: Math.round(scaleFactor * 1e3) / 1e3,
23741
+ nextZoom: Math.round(nextZoom * 100) / 100
23742
+ });
23743
+ },
23744
+ [handlePinchPreviewScaleChange, perfEnabled]
23745
+ );
23746
+ const finishViewerPinch = (0, import_react3.useCallback)(() => {
23747
+ if (!pinchGestureActiveRef.current) return;
23748
+ pinchGestureActiveRef.current = false;
23749
+ const focalX = pinchFocalPointRef.current.x;
23750
+ const focalY = pinchFocalPointRef.current.y;
23751
+ const viewerScrollOffsetY = lastScrollOffsetYRef.current;
23752
+ const finalZoom = resolvePinchGestureZoom(
23753
+ pinchPreviewZoomRef.current || pinchStartZoomRef.current,
23754
+ 1,
23755
+ DEFAULT_PINCH_ZOOM_BOUNDS
23756
+ );
23757
+ setLastPinchEndedAt(Date.now());
23758
+ if (Math.abs(finalZoom - zoom) >= 1e-3) {
23759
+ const anchorPageIndex = resolvePinchAnchorPageIndex(
23760
+ focalX,
23761
+ focalY,
23762
+ zoom,
23763
+ viewerScrollOffsetY
23764
+ );
23765
+ const { pageOffsetY: startPageOffsetY, pageHeight: startPageHeight } = getPageLayoutForZoom(anchorPageIndex, zoom);
23766
+ const startPageWidth = getPageWidthForZoom(anchorPageIndex, zoom);
23767
+ const {
23768
+ viewportWidth: pageViewportWidth,
23769
+ horizontalPadding: pageHorizontalPadding,
23770
+ viewportOffsetX
23771
+ } = getPageViewportMetrics(anchorPageIndex);
23772
+ const pageViewportContentWidth = Math.max(
23773
+ 0,
23774
+ pageViewportWidth - pageHorizontalPadding * 2
23775
+ );
23776
+ pendingPinchAnchorRestoreRef.current = {
23777
+ finalZoom,
23778
+ focalY,
23779
+ viewerScrollOffsetY,
23780
+ pageIndex: anchorPageIndex,
23781
+ startPageOffsetY,
23782
+ startPageHeight,
23783
+ startPageWidth,
23784
+ startPageScrollX: horizontalScrollOffsetsRef.current.get(anchorPageIndex) ?? 0,
23785
+ pageViewportWidth,
23786
+ pageHorizontalPadding,
23787
+ pageViewportContentOffsetX: Math.max(
23788
+ 0,
23789
+ Math.min(
23790
+ pageViewportContentWidth,
23791
+ focalX - viewportOffsetX - pageHorizontalPadding
23792
+ )
23793
+ )
23794
+ };
23795
+ setDocumentStateTracked({ zoom: finalZoom }, "pinch.viewerEnd");
23796
+ engine.setZoom(finalZoom);
23797
+ } else {
23798
+ pendingPinchAnchorRestoreRef.current = null;
23799
+ }
23800
+ resetViewerPinchPreview();
23801
+ handleGestureScrollLockChange(false);
23802
+ if (perfEnabled) {
23803
+ logPerfEvent("Viewer", "pinch.end", {
23804
+ finalZoom: Math.round(finalZoom * 100) / 100,
23805
+ page: pendingPinchAnchorRestoreRef.current?.pageIndex ?? null
23806
+ });
23807
+ }
23808
+ }, [
23809
+ engine,
23810
+ getPageLayoutForZoom,
23811
+ getPageViewportMetrics,
23812
+ getPageWidthForZoom,
23813
+ handleGestureScrollLockChange,
23814
+ perfEnabled,
23815
+ resetViewerPinchPreview,
23816
+ resolvePinchAnchorPageIndex,
23817
+ setDocumentStateTracked,
23818
+ zoom
23819
+ ]);
23820
+ const handlePageHorizontalScrollOffsetChange = (0, import_react3.useCallback)(
23821
+ (pageIndex, offsetX) => {
23822
+ horizontalScrollOffsetsRef.current.set(pageIndex, Math.max(0, offsetX));
23823
+ const pageWidth = getPageWidthForZoom(pageIndex, zoom);
23824
+ const { viewportWidth, horizontalPadding: horizontalPadding2 } = getPageViewportMetrics(pageIndex);
23825
+ const pageViewportWidth = Math.max(
23826
+ 0,
23827
+ viewportWidth - horizontalPadding2 * 2
23828
+ );
23829
+ const nextOffsetX = resolveClampedScrollOffset(
23830
+ offsetX,
23831
+ pageWidth,
23832
+ pageViewportWidth
23833
+ );
23834
+ setHorizontalScrollRestore((current) => {
23835
+ if (current && Math.abs(current.offsetX - nextOffsetX) < 0.5) {
23836
+ return current;
23837
+ }
23838
+ const requestId = nextHorizontalRestoreRequestIdRef.current + 1;
23839
+ nextHorizontalRestoreRequestIdRef.current = requestId;
23840
+ return {
23841
+ pageIndex,
23842
+ requestId,
23843
+ offsetX: nextOffsetX
23844
+ };
23845
+ });
23846
+ },
23847
+ [getPageViewportMetrics, getPageWidthForZoom, zoom]
23848
+ );
23849
+ const viewerPinchGesture = (0, import_react3.useMemo)(
23850
+ () => import_react_native_gesture_handler2.Gesture.Pinch().enabled(!isWebView && pageCount > 0).onTouchesDown((event) => {
23851
+ if ((event.allTouches?.length ?? 0) >= 2) {
23852
+ handleGestureScrollLockChange(true);
23853
+ }
23854
+ }).onTouchesUp((event) => {
23855
+ if ((event.allTouches?.length ?? 0) < 2 && !pinchGestureActiveRef.current) {
23856
+ handleGestureScrollLockChange(false);
23857
+ }
23858
+ }).runOnJS(true).onStart((event) => {
23859
+ beginViewerPinch(event.focalX, event.focalY);
23860
+ }).onUpdate((event) => {
23861
+ updateViewerPinch(event.scale, event.focalX, event.focalY);
23862
+ }).onEnd(() => {
23863
+ finishViewerPinch();
23864
+ }).onFinalize(() => {
23865
+ finishViewerPinch();
23866
+ resetViewerPinchPreview();
23867
+ handleGestureScrollLockChange(false);
23868
+ }),
23869
+ [
23870
+ beginViewerPinch,
23871
+ finishViewerPinch,
23872
+ handleGestureScrollLockChange,
23873
+ isWebView,
23874
+ pageCount,
23875
+ resetViewerPinchPreview,
23876
+ updateViewerPinch
23877
+ ]
23878
+ );
23879
+ (0, import_react3.useEffect)(() => {
23880
+ selectionDragActiveRef.current = selectionDragActive;
23881
+ syncViewerScrollEnabled(
23882
+ selectionDragActive,
23883
+ gestureScrollLockActiveRef.current
23884
+ );
23885
+ }, [selectionDragActive, syncViewerScrollEnabled]);
23886
+ (0, import_react3.useEffect)(() => {
23887
+ const pendingRestore = pendingPinchAnchorRestoreRef.current;
23888
+ if (!pendingRestore) return;
23889
+ if (Math.abs(pendingRestore.finalZoom - zoom) >= 1e-3) return;
23890
+ if (pinchAnchorRestoreFrameRef.current !== null) {
23891
+ cancelAnimationFrame(pinchAnchorRestoreFrameRef.current);
23892
+ }
23893
+ pinchAnchorRestoreFrameRef.current = requestAnimationFrame(() => {
23894
+ pinchAnchorRestoreFrameRef.current = null;
23895
+ const {
23896
+ pageOffsetY: endPageOffsetY,
23897
+ pageHeight: endPageHeight,
23898
+ totalContentHeight: endContentHeight
23899
+ } = getPageLayoutForZoom(pendingRestore.pageIndex, zoom);
23900
+ const viewerViewportHeight = viewerFrameRef.current.height;
23901
+ const nextScrollY = resolveAnchoredViewportOffset({
23902
+ viewportOffset: pendingRestore.focalY,
23903
+ startScrollOffset: pendingRestore.viewerScrollOffsetY,
23904
+ startItemOffset: pendingRestore.startPageOffsetY,
23905
+ startItemLength: pendingRestore.startPageHeight,
23906
+ endItemOffset: endPageOffsetY,
23907
+ endItemLength: endPageHeight,
23908
+ viewportLength: viewerViewportHeight,
23909
+ endContentLength: endContentHeight
23910
+ });
23911
+ if (isSingle) {
23912
+ listRef.current?.scrollTo?.({
23913
+ y: nextScrollY,
23914
+ animated: false
23915
+ });
23916
+ } else {
23917
+ listRef.current?.scrollToOffset({
23918
+ offset: nextScrollY,
23919
+ animated: false
23920
+ });
23921
+ }
23922
+ lastScrollOffsetYRef.current = nextScrollY;
23923
+ const endPageWidth = getPageWidthForZoom(pendingRestore.pageIndex, zoom);
23924
+ const pageViewportContentWidth = Math.max(
23925
+ 0,
23926
+ pendingRestore.pageViewportWidth - pendingRestore.pageHorizontalPadding * 2
23927
+ );
23928
+ const nextOffsetX = resolveAnchoredViewportOffset({
23929
+ viewportOffset: pendingRestore.pageViewportContentOffsetX,
23930
+ startScrollOffset: pendingRestore.startPageScrollX,
23931
+ startItemOffset: 0,
23932
+ startItemLength: pendingRestore.startPageWidth,
23933
+ endItemOffset: 0,
23934
+ endItemLength: endPageWidth,
23935
+ viewportLength: pageViewportContentWidth,
23936
+ endContentLength: endPageWidth
23937
+ });
23938
+ const requestId = nextHorizontalRestoreRequestIdRef.current + 1;
23939
+ nextHorizontalRestoreRequestIdRef.current = requestId;
23940
+ setHorizontalScrollRestore({
23941
+ pageIndex: pendingRestore.pageIndex,
23942
+ requestId,
23943
+ offsetX: nextOffsetX
23944
+ });
23945
+ pendingPinchAnchorRestoreRef.current = null;
23946
+ if (perfEnabled) {
23947
+ logPerfEvent("Viewer", "pinch.anchorRestore", {
23948
+ page: pendingRestore.pageIndex + 1,
23949
+ scrollY: Math.round(nextScrollY * 100) / 100,
23950
+ scrollX: Math.round(nextOffsetX * 100) / 100,
23951
+ zoom: Math.round(zoom * 100) / 100
23952
+ });
23953
+ }
23954
+ });
23955
+ return () => {
23956
+ if (pinchAnchorRestoreFrameRef.current !== null) {
23957
+ cancelAnimationFrame(pinchAnchorRestoreFrameRef.current);
23958
+ pinchAnchorRestoreFrameRef.current = null;
23959
+ }
23960
+ };
23961
+ }, [getPageLayoutForZoom, getPageWidthForZoom, isSingle, perfEnabled, zoom]);
23962
+ (0, import_react3.useEffect)(() => {
23963
+ if (zoom > 1) return;
23964
+ setHorizontalScrollRestore((current) => {
23965
+ if (!current || Math.abs(current.offsetX) < 0.5) {
23966
+ return current;
23967
+ }
23968
+ const requestId = nextHorizontalRestoreRequestIdRef.current + 1;
23969
+ nextHorizontalRestoreRequestIdRef.current = requestId;
23970
+ return {
23971
+ pageIndex: current.pageIndex,
23972
+ requestId,
23973
+ offsetX: 0
23974
+ };
23975
+ });
23976
+ }, [zoom]);
23977
+ const captureViewerFrame = (0, import_react3.useCallback)((node) => {
23978
+ const measurable = node;
23979
+ measurable?.measureInWindow?.((_, y, __, height) => {
23980
+ viewerFrameRef.current = { y, height };
23981
+ });
23982
+ }, []);
23983
+ const scrollViewerBy = (0, import_react3.useCallback)(
23984
+ (deltaY) => {
23985
+ if (!Number.isFinite(deltaY) || deltaY === 0) return 0;
23986
+ const viewportHeight = viewerFrameRef.current.height;
23987
+ const maxOffset = Math.max(
23988
+ 0,
23989
+ viewerContentHeightRef.current - viewportHeight
23990
+ );
23991
+ const nextOffset = Math.max(
23992
+ 0,
23993
+ Math.min(maxOffset, lastScrollOffsetYRef.current + deltaY)
23994
+ );
23995
+ const appliedDelta = nextOffset - lastScrollOffsetYRef.current;
23996
+ if (appliedDelta === 0) return 0;
23997
+ lastScrollOffsetYRef.current = nextOffset;
23998
+ if (isSingle) {
23999
+ listRef.current?.scrollTo?.({
24000
+ y: nextOffset,
24001
+ animated: false
24002
+ });
24003
+ return appliedDelta;
24004
+ }
24005
+ listRef.current?.scrollToOffset({ offset: nextOffset, animated: false });
24006
+ return appliedDelta;
24007
+ },
24008
+ [isSingle]
24009
+ );
24010
+ const handleSelectionVerticalAutoscroll = (0, import_react3.useCallback)(
24011
+ (absoluteY) => {
24012
+ const frame = viewerFrameRef.current;
24013
+ if (!Number.isFinite(absoluteY) || frame.height <= 0) return 0;
24014
+ const relativeY = absoluteY - frame.y;
24015
+ const { dy } = getSelectionEdgeAutoscroll({
24016
+ x: SELECTION_EDGE_THRESHOLD_PX2,
24017
+ y: relativeY,
24018
+ width: SELECTION_EDGE_THRESHOLD_PX2 * 2,
24019
+ height: frame.height,
24020
+ threshold: SELECTION_EDGE_THRESHOLD_PX2,
24021
+ maxStep: SELECTION_EDGE_MAX_STEP_PX2
24022
+ });
24023
+ return scrollViewerBy(dy);
24024
+ },
24025
+ [scrollViewerBy]
24026
+ );
23124
24027
  const listLayoutMetrics = (0, import_react3.useMemo)(() => {
23125
24028
  const offsets = [];
23126
24029
  const lengths = [];
@@ -23361,7 +24264,13 @@ var Viewer = ({
23361
24264
  pageIndex: row.left,
23362
24265
  availableWidth: columnWidth,
23363
24266
  horizontalPadding: 8,
23364
- spacing: DOUBLE_PAGE_SPACING
24267
+ spacing: DOUBLE_PAGE_SPACING,
24268
+ onSelectionDragActiveChange: setSelectionDragActive,
24269
+ gestureScrollLockActive,
24270
+ lastPinchEndedAt,
24271
+ onHorizontalScrollOffsetChange: handlePageHorizontalScrollOffsetChange,
24272
+ horizontalScrollRestore,
24273
+ requestSelectionVerticalAutoscroll: handleSelectionVerticalAutoscroll
23365
24274
  }
23366
24275
  ) }),
23367
24276
  row.right !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: { width: columnWidth }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -23371,7 +24280,13 @@ var Viewer = ({
23371
24280
  pageIndex: row.right,
23372
24281
  availableWidth: columnWidth,
23373
24282
  horizontalPadding: 8,
23374
- spacing: DOUBLE_PAGE_SPACING
24283
+ spacing: DOUBLE_PAGE_SPACING,
24284
+ onSelectionDragActiveChange: setSelectionDragActive,
24285
+ gestureScrollLockActive,
24286
+ lastPinchEndedAt,
24287
+ onHorizontalScrollOffsetChange: handlePageHorizontalScrollOffsetChange,
24288
+ horizontalScrollRestore,
24289
+ requestSelectionVerticalAutoscroll: handleSelectionVerticalAutoscroll
23375
24290
  }
23376
24291
  ) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: { width: columnWidth } })
23377
24292
  ] });
@@ -23381,114 +24296,170 @@ var Viewer = ({
23381
24296
  {
23382
24297
  engine,
23383
24298
  pageIndex: item,
23384
- spacing: CONTINUOUS_PAGE_SPACING
24299
+ spacing: CONTINUOUS_PAGE_SPACING,
24300
+ onSelectionDragActiveChange: setSelectionDragActive,
24301
+ gestureScrollLockActive,
24302
+ lastPinchEndedAt,
24303
+ onHorizontalScrollOffsetChange: handlePageHorizontalScrollOffsetChange,
24304
+ horizontalScrollRestore,
24305
+ requestSelectionVerticalAutoscroll: handleSelectionVerticalAutoscroll
23385
24306
  }
23386
24307
  );
23387
24308
  },
23388
- [columnWidth, engine, horizontalPadding, isDouble]
24309
+ [
24310
+ columnWidth,
24311
+ engine,
24312
+ handlePageHorizontalScrollOffsetChange,
24313
+ handleSelectionVerticalAutoscroll,
24314
+ gestureScrollLockActive,
24315
+ horizontalScrollRestore,
24316
+ horizontalPadding,
24317
+ isDouble,
24318
+ lastPinchEndedAt
24319
+ ]
23389
24320
  );
23390
24321
  if (isWebView) {
23391
24322
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(WebViewViewer_default, { engine }) });
23392
24323
  }
23393
24324
  if (isSingle) {
23394
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
23395
- import_react_native3.ScrollView,
24325
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_gesture_handler2.GestureDetector, { gesture: viewerPinchGesture, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
24326
+ import_react_native3.View,
23396
24327
  {
23397
- contentContainerStyle: styles3.singleContent,
23398
- showsVerticalScrollIndicator: false,
23399
- scrollEnabled: true,
23400
- onScroll: (event) => handleViewerScroll(event, "single"),
23401
- onScrollBeginDrag: perfEnabled ? () => {
23402
- scrollMonitorRef.current.begin("single.beginDrag");
23403
- } : void 0,
23404
- onMomentumScrollBegin: perfEnabled ? () => {
23405
- scrollMonitorRef.current.begin("single.momentumBegin");
23406
- } : void 0,
23407
- onScrollEndDrag: perfEnabled ? () => {
23408
- scrollMonitorRef.current.end("single.endDrag");
23409
- sampleMemory("Viewer", "single.endDrag", { pageCount });
23410
- } : void 0,
23411
- onMomentumScrollEnd: perfEnabled ? () => {
23412
- scrollMonitorRef.current.end("single.momentumEnd");
23413
- sampleMemory("Viewer", "single.momentumEnd", { pageCount });
23414
- } : void 0,
23415
- scrollEventThrottle: 16,
24328
+ style: [
24329
+ styles3.gestureSurface,
24330
+ { transform: [{ scale: pinchPreviewScale }] }
24331
+ ],
23416
24332
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
23417
- PageRenderer_default,
24333
+ import_react_native3.ScrollView,
23418
24334
  {
23419
- engine,
23420
- pageIndex: Math.max(0, currentPage - 1),
23421
- spacing: 32
24335
+ ref: (node) => {
24336
+ captureViewerFrame(node);
24337
+ listRef.current = node;
24338
+ },
24339
+ contentContainerStyle: styles3.singleContent,
24340
+ showsVerticalScrollIndicator: false,
24341
+ scrollEnabled: resolvedViewerScrollEnabled,
24342
+ onLayout: () => captureViewerFrame(listRef.current),
24343
+ onContentSizeChange: (_, height) => {
24344
+ viewerContentHeightRef.current = height;
24345
+ },
24346
+ onScroll: (event) => handleViewerScroll(event, "single"),
24347
+ onScrollBeginDrag: perfEnabled ? () => {
24348
+ scrollMonitorRef.current.begin("single.beginDrag");
24349
+ } : void 0,
24350
+ onMomentumScrollBegin: perfEnabled ? () => {
24351
+ scrollMonitorRef.current.begin("single.momentumBegin");
24352
+ } : void 0,
24353
+ onScrollEndDrag: perfEnabled ? () => {
24354
+ scrollMonitorRef.current.end("single.endDrag");
24355
+ sampleMemory("Viewer", "single.endDrag", { pageCount });
24356
+ } : void 0,
24357
+ onMomentumScrollEnd: perfEnabled ? () => {
24358
+ scrollMonitorRef.current.end("single.momentumEnd");
24359
+ sampleMemory("Viewer", "single.momentumEnd", {
24360
+ pageCount
24361
+ });
24362
+ } : void 0,
24363
+ scrollEventThrottle: 16,
24364
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
24365
+ PageRenderer_default,
24366
+ {
24367
+ engine,
24368
+ pageIndex: Math.max(0, currentPage - 1),
24369
+ spacing: 32,
24370
+ onSelectionDragActiveChange: setSelectionDragActive,
24371
+ gestureScrollLockActive,
24372
+ lastPinchEndedAt,
24373
+ onHorizontalScrollOffsetChange: handlePageHorizontalScrollOffsetChange,
24374
+ horizontalScrollRestore,
24375
+ requestSelectionVerticalAutoscroll: handleSelectionVerticalAutoscroll
24376
+ }
24377
+ )
23422
24378
  }
23423
24379
  )
23424
24380
  }
23425
- ) });
24381
+ ) }) });
23426
24382
  }
23427
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
23428
- import_react_native3.FlatList,
24383
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.container, isDark && styles3.containerDark], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_gesture_handler2.GestureDetector, { gesture: viewerPinchGesture, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
24384
+ import_react_native3.View,
23429
24385
  {
23430
- ref: listRef,
23431
- data: isDouble ? rows : pages,
23432
- initialNumToRender: FLATLIST_INITIAL_NUM_TO_RENDER,
23433
- windowSize: resolvedWindowSize,
23434
- maxToRenderPerBatch: resolvedMaxToRenderPerBatch,
23435
- updateCellsBatchingPeriod: FLATLIST_UPDATE_CELLS_BATCHING_PERIOD,
23436
- removeClippedSubviews: resolvedRemoveClippedSubviews,
23437
- getItemLayout,
23438
- keyExtractor,
23439
- contentContainerStyle: styles3.listContent,
23440
- renderItem,
23441
- onViewableItemsChanged,
23442
- viewabilityConfig: { itemVisiblePercentThreshold: 60 },
23443
- scrollEnabled: true,
23444
- onScrollToIndexFailed: ({ index, averageItemLength }) => {
23445
- const dataLength = isDouble ? rows.length : pages.length;
23446
- if (index < 0 || index >= dataLength) return;
23447
- pendingScrollIndexRef.current = index;
23448
- const offset = Math.max(0, getFallbackOffsetForIndex(index));
23449
- listRef.current?.scrollToOffset({ offset, animated: false });
23450
- if (!isDouble) {
23451
- ensurePageDimensions(index);
23452
- } else {
23453
- const row = rows[index];
23454
- if (row) {
23455
- ensurePageDimensions(row.left);
23456
- if (row.right !== null) {
23457
- ensurePageDimensions(row.right);
24386
+ style: [
24387
+ styles3.gestureSurface,
24388
+ { transform: [{ scale: pinchPreviewScale }] }
24389
+ ],
24390
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
24391
+ import_react_native3.FlatList,
24392
+ {
24393
+ ref: listRef,
24394
+ data: isDouble ? rows : pages,
24395
+ initialNumToRender: FLATLIST_INITIAL_NUM_TO_RENDER,
24396
+ windowSize: resolvedWindowSize,
24397
+ maxToRenderPerBatch: resolvedMaxToRenderPerBatch,
24398
+ updateCellsBatchingPeriod: FLATLIST_UPDATE_CELLS_BATCHING_PERIOD,
24399
+ removeClippedSubviews: resolvedRemoveClippedSubviews,
24400
+ getItemLayout,
24401
+ keyExtractor,
24402
+ contentContainerStyle: styles3.listContent,
24403
+ renderItem,
24404
+ onViewableItemsChanged,
24405
+ viewabilityConfig: { itemVisiblePercentThreshold: 60 },
24406
+ scrollEnabled: resolvedViewerScrollEnabled,
24407
+ onLayout: () => captureViewerFrame(listRef.current),
24408
+ onContentSizeChange: (_, height) => {
24409
+ viewerContentHeightRef.current = height;
24410
+ },
24411
+ onScrollToIndexFailed: ({ index, averageItemLength }) => {
24412
+ const dataLength = isDouble ? rows.length : pages.length;
24413
+ if (index < 0 || index >= dataLength) return;
24414
+ pendingScrollIndexRef.current = index;
24415
+ const offset = Math.max(0, getFallbackOffsetForIndex(index));
24416
+ listRef.current?.scrollToOffset({ offset, animated: false });
24417
+ if (!isDouble) {
24418
+ ensurePageDimensions(index);
24419
+ } else {
24420
+ const row = rows[index];
24421
+ if (row) {
24422
+ ensurePageDimensions(row.left);
24423
+ if (row.right !== null) {
24424
+ ensurePageDimensions(row.right);
24425
+ }
24426
+ }
23458
24427
  }
23459
- }
23460
- }
23461
- scheduleScrollRetry("onScrollToIndexFailed");
23462
- if (perfEnabled) {
23463
- logPerfEvent("Viewer", "scrollToIndexFailed", {
23464
- index,
23465
- averageItemLength,
23466
- fallbackOffset: offset,
23467
- fallbackSource: "cached-item-layout",
23468
- itemCount: dataLength,
23469
- retryAttempt: pendingScrollAttemptsRef.current
23470
- });
24428
+ scheduleScrollRetry("onScrollToIndexFailed");
24429
+ if (perfEnabled) {
24430
+ logPerfEvent("Viewer", "scrollToIndexFailed", {
24431
+ index,
24432
+ averageItemLength,
24433
+ fallbackOffset: offset,
24434
+ fallbackSource: "cached-item-layout",
24435
+ itemCount: dataLength,
24436
+ retryAttempt: pendingScrollAttemptsRef.current
24437
+ });
24438
+ }
24439
+ },
24440
+ onScroll: (event) => handleViewerScroll(event, "continuous"),
24441
+ onScrollBeginDrag: perfEnabled ? () => {
24442
+ scrollMonitorRef.current.begin("continuous.beginDrag");
24443
+ } : void 0,
24444
+ onMomentumScrollBegin: perfEnabled ? () => {
24445
+ scrollMonitorRef.current.begin("continuous.momentumBegin");
24446
+ } : void 0,
24447
+ onScrollEndDrag: perfEnabled ? () => {
24448
+ scrollMonitorRef.current.end("continuous.endDrag");
24449
+ sampleMemory("Viewer", "continuous.endDrag", { pageCount });
24450
+ } : void 0,
24451
+ onMomentumScrollEnd: perfEnabled ? () => {
24452
+ scrollMonitorRef.current.end("continuous.momentumEnd");
24453
+ sampleMemory("Viewer", "continuous.momentumEnd", {
24454
+ pageCount
24455
+ });
24456
+ } : void 0,
24457
+ scrollEventThrottle: 16,
24458
+ showsVerticalScrollIndicator: false
23471
24459
  }
23472
- },
23473
- onScroll: (event) => handleViewerScroll(event, "continuous"),
23474
- onScrollBeginDrag: perfEnabled ? () => {
23475
- scrollMonitorRef.current.begin("continuous.beginDrag");
23476
- } : void 0,
23477
- onMomentumScrollBegin: perfEnabled ? () => {
23478
- scrollMonitorRef.current.begin("continuous.momentumBegin");
23479
- } : void 0,
23480
- onScrollEndDrag: perfEnabled ? () => {
23481
- scrollMonitorRef.current.end("continuous.endDrag");
23482
- sampleMemory("Viewer", "continuous.endDrag", { pageCount });
23483
- } : void 0,
23484
- onMomentumScrollEnd: perfEnabled ? () => {
23485
- scrollMonitorRef.current.end("continuous.momentumEnd");
23486
- sampleMemory("Viewer", "continuous.momentumEnd", { pageCount });
23487
- } : void 0,
23488
- scrollEventThrottle: 16,
23489
- showsVerticalScrollIndicator: false
24460
+ )
23490
24461
  }
23491
- ) });
24462
+ ) }) });
23492
24463
  };
23493
24464
  var styles3 = import_react_native3.StyleSheet.create({
23494
24465
  container: {
@@ -23498,6 +24469,9 @@ var styles3 = import_react_native3.StyleSheet.create({
23498
24469
  containerDark: {
23499
24470
  backgroundColor: "#0f1115"
23500
24471
  },
24472
+ gestureSurface: {
24473
+ flex: 1
24474
+ },
23501
24475
  listContent: {
23502
24476
  paddingTop: LIST_TOP_PADDING,
23503
24477
  paddingBottom: LIST_BOTTOM_PADDING
@@ -24170,6 +25144,7 @@ var ToolDock = () => {
24170
25144
  setAnnotationColor,
24171
25145
  accentColor,
24172
25146
  activeTool,
25147
+ interactionMode,
24173
25148
  toolDockOpen,
24174
25149
  setDocumentState
24175
25150
  } = (0, import_core5.useViewerStore)();
@@ -24181,7 +25156,12 @@ var ToolDock = () => {
24181
25156
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
24182
25157
  import_react_native5.Pressable,
24183
25158
  {
24184
- onPress: () => setDocumentState({ toolDockOpen: false }),
25159
+ onPress: () => setDocumentState(
25160
+ getToolDockDismissState({
25161
+ activeTool,
25162
+ interactionMode
25163
+ })
25164
+ ),
24185
25165
  style: [styles5.closeButton, isDark && styles5.closeButtonDark],
24186
25166
  children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
24187
25167
  import_react_native5.Text,
@@ -24194,12 +25174,29 @@ var ToolDock = () => {
24194
25174
  )
24195
25175
  ] }),
24196
25176
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.View, { style: styles5.toolsRow, children: TOOL_OPTIONS.map((tool) => {
24197
- const isSelected = activeTool === tool.id;
25177
+ const isSelected = isToolDockToolSelected({
25178
+ toolId: tool.id,
25179
+ activeTool,
25180
+ interactionMode
25181
+ });
24198
25182
  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;
24199
25183
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
24200
25184
  import_react_native5.Pressable,
24201
25185
  {
24202
- onPress: () => setDocumentState({ activeTool: tool.id }),
25186
+ onPress: () => {
25187
+ if (tool.id === "select") {
25188
+ const shouldArmSelection = activeTool !== "select" || interactionMode !== "select";
25189
+ setDocumentState({
25190
+ activeTool: "select",
25191
+ interactionMode: shouldArmSelection ? "select" : "pan"
25192
+ });
25193
+ return;
25194
+ }
25195
+ setDocumentState({
25196
+ activeTool: tool.id,
25197
+ interactionMode: "pan"
25198
+ });
25199
+ },
24203
25200
  style: [
24204
25201
  styles5.toolButton,
24205
25202
  isDark && styles5.toolButtonDark,