@papyrus-sdk/ui-react-native 0.2.26 → 0.2.28

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
@@ -25279,6 +25279,12 @@ var resolvePageTapChromeVisibility = ({
25279
25279
  return !chromeVisible;
25280
25280
  };
25281
25281
 
25282
+ // components/selectionContentInteraction.ts
25283
+ var shouldDismissSelectionOnContentInteraction = ({
25284
+ selectionActive,
25285
+ interaction
25286
+ }) => selectionActive && (interaction === "tap" || interaction === "scroll");
25287
+
25282
25288
  // components/DedicatedAndroidPdfViewer.tsx
25283
25289
  import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
25284
25290
  var TEXT_MARKUP_TOOLS2 = /* @__PURE__ */ new Set(["highlight", "underline", "squiggly", "strikeout"]);
@@ -25459,6 +25465,13 @@ function DedicatedAndroidPdfViewer({
25459
25465
  setSelectedAnnotation(event.nativeEvent.id);
25460
25466
  },
25461
25467
  onTap: () => {
25468
+ if (shouldDismissSelectionOnContentInteraction({
25469
+ selectionActive: selectionRef.current !== null,
25470
+ interaction: "tap"
25471
+ })) {
25472
+ setSelection(null);
25473
+ selectionRef.current = null;
25474
+ }
25462
25475
  const nextVisible = resolvePageTapChromeVisibility({
25463
25476
  chromeVisible: chromeVisibleRef.current,
25464
25477
  selectionActive: selectionRef.current !== null,
@@ -25485,66 +25498,61 @@ function DedicatedAndroidPdfViewer({
25485
25498
  }
25486
25499
  },
25487
25500
  onScroll: (event) => {
25501
+ if (shouldDismissSelectionOnContentInteraction({
25502
+ selectionActive: selectionRef.current !== null,
25503
+ interaction: "scroll"
25504
+ })) {
25505
+ setSelection(null);
25506
+ selectionRef.current = null;
25507
+ }
25488
25508
  trackMobileChromeByOffset(event.nativeEvent.offsetY, "native.scroll");
25489
25509
  }
25490
25510
  }
25491
25511
  ),
25492
- selection && /* @__PURE__ */ jsxs4(Fragment, { children: [
25512
+ selection && /* @__PURE__ */ jsx5(Fragment, { children: /* @__PURE__ */ jsx5(View3, { style: styles3.selectionToolbar, pointerEvents: "box-none", children: /* @__PURE__ */ jsxs4(View3, { style: styles3.toolbarContent, children: [
25493
25513
  /* @__PURE__ */ jsx5(
25494
25514
  Pressable2,
25495
25515
  {
25496
- style: styles3.overlay,
25497
25516
  onPress: () => {
25498
- setSelection(null);
25499
- selectionRef.current = null;
25500
- }
25517
+ void copySelection();
25518
+ },
25519
+ style: styles3.toolbarButton,
25520
+ accessibilityRole: "button",
25521
+ accessibilityLabel: "Copy selected text",
25522
+ children: /* @__PURE__ */ jsx5(IconCopy, { size: 20, color: "#fff", strokeWidth: 2 })
25501
25523
  }
25502
25524
  ),
25503
- /* @__PURE__ */ jsx5(View3, { style: styles3.selectionToolbar, pointerEvents: "box-none", children: /* @__PURE__ */ jsxs4(View3, { style: styles3.toolbarContent, children: [
25504
- /* @__PURE__ */ jsx5(
25505
- Pressable2,
25506
- {
25507
- onPress: () => {
25508
- void copySelection();
25509
- },
25510
- style: styles3.toolbarButton,
25511
- accessibilityRole: "button",
25512
- accessibilityLabel: "Copy selected text",
25513
- children: /* @__PURE__ */ jsx5(IconCopy, { size: 20, color: "#fff", strokeWidth: 2 })
25514
- }
25515
- ),
25516
- /* @__PURE__ */ jsx5(
25517
- Pressable2,
25518
- {
25519
- onPress: () => {
25520
- applySelection("highlight");
25521
- },
25522
- style: styles3.toolbarButton,
25523
- children: /* @__PURE__ */ jsx5(IconHighlight, { size: 22, color: "#fbbf24" })
25524
- }
25525
- ),
25526
- /* @__PURE__ */ jsx5(
25527
- Pressable2,
25528
- {
25529
- onPress: () => {
25530
- applySelection("underline");
25531
- },
25532
- style: styles3.toolbarButton,
25533
- children: /* @__PURE__ */ jsx5(IconUnderline, { size: 22, color: "#60a5fa" })
25534
- }
25535
- ),
25536
- /* @__PURE__ */ jsx5(
25537
- Pressable2,
25538
- {
25539
- onPress: () => {
25540
- applySelection("comment");
25541
- },
25542
- style: styles3.toolbarButton,
25543
- children: /* @__PURE__ */ jsx5(IconCommentBubble, { size: 22, color: "#fff" })
25544
- }
25545
- )
25546
- ] }) })
25547
- ] })
25525
+ /* @__PURE__ */ jsx5(
25526
+ Pressable2,
25527
+ {
25528
+ onPress: () => {
25529
+ applySelection("highlight");
25530
+ },
25531
+ style: styles3.toolbarButton,
25532
+ children: /* @__PURE__ */ jsx5(IconHighlight, { size: 22, color: "#fbbf24" })
25533
+ }
25534
+ ),
25535
+ /* @__PURE__ */ jsx5(
25536
+ Pressable2,
25537
+ {
25538
+ onPress: () => {
25539
+ applySelection("underline");
25540
+ },
25541
+ style: styles3.toolbarButton,
25542
+ children: /* @__PURE__ */ jsx5(IconUnderline, { size: 22, color: "#60a5fa" })
25543
+ }
25544
+ ),
25545
+ /* @__PURE__ */ jsx5(
25546
+ Pressable2,
25547
+ {
25548
+ onPress: () => {
25549
+ applySelection("comment");
25550
+ },
25551
+ style: styles3.toolbarButton,
25552
+ children: /* @__PURE__ */ jsx5(IconCommentBubble, { size: 22, color: "#fff" })
25553
+ }
25554
+ )
25555
+ ] }) }) })
25548
25556
  ] });
25549
25557
  }
25550
25558
  var styles3 = StyleSheet3.create({
@@ -25584,10 +25592,6 @@ var styles3 = StyleSheet3.create({
25584
25592
  alignItems: "center",
25585
25593
  justifyContent: "center"
25586
25594
  },
25587
- overlay: {
25588
- ...StyleSheet3.absoluteFillObject,
25589
- zIndex: 40
25590
- },
25591
25595
  toolbarButtonText: {
25592
25596
  color: "#fff",
25593
25597
  fontSize: 11,
@@ -25742,6 +25746,87 @@ function createScrollPerfSession({
25742
25746
  return { begin, track, end };
25743
25747
  }
25744
25748
 
25749
+ // components/pageScrubberModel.ts
25750
+ var shouldRenderPageScrubber = ({
25751
+ mobileChromeVisible,
25752
+ mobileProgressPillVisible,
25753
+ isScrubbing
25754
+ }) => mobileProgressPillVisible && (mobileChromeVisible || isScrubbing);
25755
+ var shouldEnableViewerScrollForPageScrub = ({
25756
+ viewerScrollEnabled,
25757
+ isScrubbing
25758
+ }) => viewerScrollEnabled && !isScrubbing;
25759
+ var resolvePageScrubberTouchPolicy = () => ({
25760
+ responderTarget: "pill",
25761
+ claimOnStart: true,
25762
+ captureOnStart: true,
25763
+ captureOnMove: true,
25764
+ childConsumesTouch: false
25765
+ });
25766
+ var resolvePageScrubberReleaseAction = ({
25767
+ hasMoved,
25768
+ pendingPage
25769
+ }) => hasMoved && pendingPage !== null ? { kind: "navigate", page: pendingPage } : { kind: "open" };
25770
+ var createPageScrubberNavigationController = (onRelease) => {
25771
+ let active = false;
25772
+ let pendingPage = null;
25773
+ return {
25774
+ begin: () => {
25775
+ active = true;
25776
+ pendingPage = null;
25777
+ },
25778
+ update: (page) => {
25779
+ if (active) pendingPage = page;
25780
+ },
25781
+ release: () => {
25782
+ active = false;
25783
+ const page = pendingPage;
25784
+ pendingPage = null;
25785
+ if (page !== null) onRelease(page);
25786
+ return page;
25787
+ },
25788
+ cancel: () => {
25789
+ active = false;
25790
+ pendingPage = null;
25791
+ }
25792
+ };
25793
+ };
25794
+ var resolvePageScrubberThumbTopFromPosition = ({
25795
+ position,
25796
+ trackHeight,
25797
+ thumbHeight
25798
+ }) => Math.max(0, Math.min(trackHeight - thumbHeight, position - thumbHeight / 2));
25799
+ var resolvePageScrubberThumbTop = ({
25800
+ currentPage,
25801
+ trackHeight,
25802
+ thumbHeight,
25803
+ pageCount
25804
+ }) => {
25805
+ if (pageCount <= 1) return 0;
25806
+ const travel = Math.max(0, trackHeight - thumbHeight);
25807
+ const ratio = Math.max(
25808
+ 0,
25809
+ Math.min(1, (currentPage - 1) / Math.max(1, pageCount - 1))
25810
+ );
25811
+ return ratio * travel;
25812
+ };
25813
+ var resolvePageScrubberPage = ({
25814
+ position,
25815
+ trackHeight,
25816
+ thumbHeight,
25817
+ pageCount
25818
+ }) => {
25819
+ if (!Number.isFinite(pageCount) || pageCount <= 0) return null;
25820
+ if (pageCount === 1) return 1;
25821
+ const travel = Math.max(0, trackHeight - thumbHeight);
25822
+ const thumbTop = Math.max(0, Math.min(travel, position - thumbHeight / 2));
25823
+ const ratio = travel === 0 ? 0 : thumbTop / travel;
25824
+ return Math.max(
25825
+ 1,
25826
+ Math.min(pageCount, Math.round(ratio * (pageCount - 1)) + 1)
25827
+ );
25828
+ };
25829
+
25745
25830
  // components/viewerNavigation.ts
25746
25831
  var resolveViewerScrollTarget = (pageIndex, isDouble) => ({
25747
25832
  pageIndex,
@@ -25801,7 +25886,8 @@ var Viewer = ({
25801
25886
  maxToRenderPerBatch,
25802
25887
  removeClippedSubviews,
25803
25888
  useDedicatedAndroidPdfViewer,
25804
- viewerMode
25889
+ viewerMode,
25890
+ pageScrubActive = false
25805
25891
  }) => {
25806
25892
  const pageCount = useViewerStore4((state) => state.pageCount);
25807
25893
  const currentPage = useViewerStore4((state) => state.currentPage);
@@ -25876,6 +25962,8 @@ var Viewer = ({
25876
25962
  const selectionDragActiveRef = useRef4(false);
25877
25963
  const [gestureScrollLockActive, setGestureScrollLockActive] = useState4(false);
25878
25964
  const gestureScrollLockActiveRef = useRef4(false);
25965
+ const pageScrubActiveRef = useRef4(pageScrubActive);
25966
+ pageScrubActiveRef.current = pageScrubActive;
25879
25967
  const pinchPreviewScale = useRef4(new Animated.Value(1)).current;
25880
25968
  const pinchPreviewFocalX = useRef4(new Animated.Value(0)).current;
25881
25969
  const pinchPreviewFocalY = useRef4(new Animated.Value(0)).current;
@@ -26390,16 +26478,20 @@ var Viewer = ({
26390
26478
  selectionDragActive,
26391
26479
  gestureScrollLockActive
26392
26480
  });
26481
+ const pageScrubResolvedScrollEnabled = shouldEnableViewerScrollForPageScrub({
26482
+ viewerScrollEnabled: resolvedViewerScrollEnabled,
26483
+ isScrubbing: pageScrubActive
26484
+ });
26393
26485
  const setViewerScrollEnabledNative = useCallback3((enabled) => {
26394
26486
  const scrollNode = listRef.current;
26395
26487
  scrollNode?.setNativeProps?.({ scrollEnabled: enabled });
26396
26488
  }, []);
26397
26489
  const syncViewerScrollEnabled = useCallback3(
26398
- (nextSelectionDragActive = selectionDragActiveRef.current, nextGestureScrollLockActive = gestureScrollLockActiveRef.current) => {
26490
+ (nextSelectionDragActive = selectionDragActiveRef.current, nextGestureScrollLockActive = gestureScrollLockActiveRef.current, nextPageScrubActive = pageScrubActiveRef.current) => {
26399
26491
  setViewerScrollEnabledNative(
26400
26492
  shouldEnableViewerScroll({
26401
26493
  selectionDragActive: nextSelectionDragActive,
26402
- gestureScrollLockActive: nextGestureScrollLockActive
26494
+ gestureScrollLockActive: nextGestureScrollLockActive || nextPageScrubActive
26403
26495
  })
26404
26496
  );
26405
26497
  },
@@ -26672,6 +26764,13 @@ var Viewer = ({
26672
26764
  gestureScrollLockActiveRef.current
26673
26765
  );
26674
26766
  }, [selectionDragActive, syncViewerScrollEnabled]);
26767
+ useEffect4(() => {
26768
+ syncViewerScrollEnabled(
26769
+ selectionDragActiveRef.current,
26770
+ gestureScrollLockActiveRef.current,
26771
+ pageScrubActive
26772
+ );
26773
+ }, [pageScrubActive, syncViewerScrollEnabled]);
26675
26774
  useEffect4(() => {
26676
26775
  const pendingRestore = pendingPinchAnchorRestoreRef.current;
26677
26776
  if (!pendingRestore) return;
@@ -26857,6 +26956,17 @@ var Viewer = ({
26857
26956
  },
26858
26957
  [scaledListLayoutMetrics]
26859
26958
  );
26959
+ const scrollToPendingTarget = useCallback3(
26960
+ (animated) => {
26961
+ const target = pendingScrollTargetRef.current;
26962
+ if (target === null || listRef.current === null) return;
26963
+ listRef.current.scrollToOffset({
26964
+ offset: Math.max(0, getFallbackOffsetForIndex(target.listIndex)),
26965
+ animated
26966
+ });
26967
+ },
26968
+ [getFallbackOffsetForIndex]
26969
+ );
26860
26970
  const restoreOrientationScrollOffset = useCallback3(() => {
26861
26971
  if (pendingOrientationRestoreWidthRef.current !== windowWidth || pageCount <= 0 || isSingle || isWebView) {
26862
26972
  return;
@@ -26887,6 +26997,11 @@ var Viewer = ({
26887
26997
  }
26888
26998
  restoreOrientationScrollOffset();
26889
26999
  }, [restoreOrientationScrollOffset, windowWidth]);
27000
+ useEffect4(() => {
27001
+ if (pendingScrollTargetRef.current !== null) {
27002
+ scrollToPendingTarget(false);
27003
+ }
27004
+ }, [layoutRevision, scrollToPendingTarget]);
26890
27005
  const getItemLayout = useCallback3(
26891
27006
  (_, index) => {
26892
27007
  if (scaledListLayoutMetrics.itemCount === 0) {
@@ -26957,15 +27072,12 @@ var Viewer = ({
26957
27072
  },
26958
27073
  "scrollToPageSignal.flatList"
26959
27074
  );
26960
- listRef.current?.scrollToIndex({
26961
- index: target.listIndex,
26962
- animated: true,
26963
- viewPosition: 0
26964
- });
27075
+ scrollToPendingTarget(true);
26965
27076
  }, [
26966
27077
  clearPendingScrollRetry,
26967
27078
  clearPendingScrollTarget,
26968
27079
  ensurePageDimensions,
27080
+ scrollToPendingTarget,
26969
27081
  scrollToPageSignal,
26970
27082
  pageCount,
26971
27083
  setDocumentStateTracked,
@@ -27241,7 +27353,7 @@ var Viewer = ({
27241
27353
  {
27242
27354
  ref: horizontalScrollRef,
27243
27355
  horizontal: true,
27244
- scrollEnabled: !gestureScrollLockActive && documentSurfaceWidth > windowWidth,
27356
+ scrollEnabled: !gestureScrollLockActive && !pageScrubActive && documentSurfaceWidth > windowWidth,
27245
27357
  showsHorizontalScrollIndicator: false,
27246
27358
  onScroll: (event) => {
27247
27359
  horizontalScrollOffsetRef.current = event.nativeEvent.contentOffset?.x ?? 0;
@@ -27257,7 +27369,7 @@ var Viewer = ({
27257
27369
  style: { width: documentSurfaceWidth },
27258
27370
  contentContainerStyle: styles4.singleContent,
27259
27371
  showsVerticalScrollIndicator: false,
27260
- scrollEnabled: resolvedViewerScrollEnabled,
27372
+ scrollEnabled: pageScrubResolvedScrollEnabled,
27261
27373
  onLayout: () => captureViewerFrame(listRef.current),
27262
27374
  onContentSizeChange: (_, height) => {
27263
27375
  viewerContentHeightRef.current = height;
@@ -27330,7 +27442,7 @@ var Viewer = ({
27330
27442
  {
27331
27443
  ref: horizontalScrollRef,
27332
27444
  horizontal: true,
27333
- scrollEnabled: !gestureScrollLockActive && documentSurfaceWidth > windowWidth,
27445
+ scrollEnabled: !gestureScrollLockActive && !pageScrubActive && documentSurfaceWidth > windowWidth,
27334
27446
  showsHorizontalScrollIndicator: false,
27335
27447
  onScroll: (event) => {
27336
27448
  horizontalScrollOffsetRef.current = event.nativeEvent.contentOffset?.x ?? 0;
@@ -27353,13 +27465,15 @@ var Viewer = ({
27353
27465
  renderItem,
27354
27466
  onViewableItemsChanged,
27355
27467
  viewabilityConfig: { itemVisiblePercentThreshold: 60 },
27356
- scrollEnabled: resolvedViewerScrollEnabled,
27468
+ scrollEnabled: pageScrubResolvedScrollEnabled,
27357
27469
  onLayout: () => {
27358
27470
  captureViewerFrame(listRef.current);
27359
27471
  restoreOrientationScrollOffset();
27472
+ scrollToPendingTarget(false);
27360
27473
  },
27361
27474
  onContentSizeChange: (_, height) => {
27362
27475
  viewerContentHeightRef.current = height;
27476
+ scrollToPendingTarget(false);
27363
27477
  },
27364
27478
  onScrollToIndexFailed: ({ index, averageItemLength }) => {
27365
27479
  const dataLength = isDouble ? rows.length : pages.length;
@@ -27448,13 +27562,13 @@ var styles4 = StyleSheet4.create({
27448
27562
  var Viewer_default = Viewer;
27449
27563
 
27450
27564
  // components/Topbar.tsx
27451
- import { useState as useState6 } from "react";
27565
+ import { useState as useState7 } from "react";
27452
27566
  import {
27453
27567
  View as View7,
27454
27568
  Text as Text3,
27455
27569
  Pressable as Pressable5,
27456
27570
  StyleSheet as StyleSheet7,
27457
- useWindowDimensions as useWindowDimensions3
27571
+ useWindowDimensions as useWindowDimensions4
27458
27572
  } from "react-native";
27459
27573
  import { useViewerStore as useViewerStore5 } from "@papyrus-sdk/core";
27460
27574
 
@@ -27488,7 +27602,7 @@ var resolveMobileChromeOffsets = (insets) => ({
27488
27602
  });
27489
27603
 
27490
27604
  // components/PageJumpModal.tsx
27491
- import { useEffect as useEffect5, useState as useState5 } from "react";
27605
+ import { useEffect as useEffect6, useState as useState6 } from "react";
27492
27606
  import { Pressable as Pressable4, StyleSheet as StyleSheet6, Text as Text2, TextInput, View as View6 } from "react-native";
27493
27607
 
27494
27608
  // components/pageJumpModel.ts
@@ -27500,15 +27614,17 @@ var resolvePageJumpTarget = (value, pageCount) => {
27500
27614
  };
27501
27615
 
27502
27616
  // components/NativeSheet.tsx
27503
- import React7 from "react";
27617
+ import React7, { useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
27504
27618
  import {
27619
+ Animated as Animated2,
27505
27620
  FlatList as FlatList2,
27506
27621
  Modal,
27507
27622
  Pressable as Pressable3,
27508
27623
  ScrollView as ScrollView2,
27509
27624
  StyleSheet as StyleSheet5,
27510
27625
  Text,
27511
- View as View5
27626
+ View as View5,
27627
+ useWindowDimensions as useWindowDimensions3
27512
27628
  } from "react-native";
27513
27629
 
27514
27630
  // components/nativeSheetLayout.ts
@@ -27598,25 +27714,73 @@ function NativeSheet({
27598
27714
  }) {
27599
27715
  const insets = usePapyrusSafeAreaInsets();
27600
27716
  const palette = getNativeSheetPalette(Boolean(isDark));
27717
+ const { height: windowHeight } = useWindowDimensions3();
27718
+ const [rendered, setRendered] = useState5(visible);
27719
+ const renderedRef = useRef5(visible);
27720
+ const motion = useRef5(new Animated2.Value(visible ? 0 : 1)).current;
27721
+ const backdropOpacity = useRef5(new Animated2.Value(visible ? 1 : 0)).current;
27722
+ useEffect5(() => {
27723
+ motion.stopAnimation();
27724
+ backdropOpacity.stopAnimation();
27725
+ if (visible) {
27726
+ renderedRef.current = true;
27727
+ setRendered(true);
27728
+ motion.setValue(1);
27729
+ backdropOpacity.setValue(0);
27730
+ Animated2.parallel([
27731
+ Animated2.spring(motion, {
27732
+ toValue: 0,
27733
+ damping: 22,
27734
+ stiffness: 220,
27735
+ mass: 0.8,
27736
+ useNativeDriver: true
27737
+ }),
27738
+ Animated2.timing(backdropOpacity, {
27739
+ toValue: 1,
27740
+ duration: 180,
27741
+ useNativeDriver: true
27742
+ })
27743
+ ]).start();
27744
+ return;
27745
+ }
27746
+ if (!renderedRef.current) return;
27747
+ Animated2.parallel([
27748
+ Animated2.timing(motion, {
27749
+ toValue: 1,
27750
+ duration: 180,
27751
+ useNativeDriver: true
27752
+ }),
27753
+ Animated2.timing(backdropOpacity, {
27754
+ toValue: 0,
27755
+ duration: 140,
27756
+ useNativeDriver: true
27757
+ })
27758
+ ]).start(({ finished }) => {
27759
+ if (!finished) return;
27760
+ renderedRef.current = false;
27761
+ setRendered(false);
27762
+ });
27763
+ }, [backdropOpacity, motion, visible]);
27764
+ if (!rendered) return null;
27601
27765
  return /* @__PURE__ */ jsx8(
27602
27766
  Modal,
27603
27767
  {
27604
- animationType: "fade",
27768
+ animationType: "none",
27605
27769
  transparent: true,
27606
- visible,
27770
+ visible: rendered,
27607
27771
  onRequestClose: onClose,
27608
27772
  children: /* @__PURE__ */ jsxs6(View5, { style: styles5.root, children: [
27609
- /* @__PURE__ */ jsx8(
27773
+ /* @__PURE__ */ jsx8(Animated2.View, { style: [styles5.backdrop, { opacity: backdropOpacity }], children: /* @__PURE__ */ jsx8(
27610
27774
  Pressable3,
27611
27775
  {
27612
27776
  accessibilityRole: "button",
27613
27777
  accessibilityLabel: closeAccessibilityLabel,
27614
- style: styles5.backdrop,
27778
+ style: StyleSheet5.absoluteFill,
27615
27779
  onPress: onClose
27616
27780
  }
27617
- ),
27781
+ ) }),
27618
27782
  /* @__PURE__ */ jsxs6(
27619
- View5,
27783
+ Animated2.View,
27620
27784
  {
27621
27785
  style: [
27622
27786
  styles5.sheet,
@@ -27627,7 +27791,17 @@ function NativeSheet({
27627
27791
  backgroundColor: palette.backgroundColor,
27628
27792
  borderTopColor: palette.borderColor
27629
27793
  },
27630
- sheetStyle
27794
+ sheetStyle,
27795
+ {
27796
+ transform: [
27797
+ {
27798
+ translateY: motion.interpolate({
27799
+ inputRange: [0, 1],
27800
+ outputRange: [0, windowHeight]
27801
+ })
27802
+ }
27803
+ ]
27804
+ }
27631
27805
  ],
27632
27806
  children: [
27633
27807
  showHeader ? /* @__PURE__ */ jsxs6(View5, { style: styles5.header, children: [
@@ -27735,9 +27909,9 @@ function PageJumpModal({
27735
27909
  onClose,
27736
27910
  onConfirm
27737
27911
  }) {
27738
- const [value, setValue] = useState5(`${currentPage}`);
27912
+ const [value, setValue] = useState6(`${currentPage}`);
27739
27913
  const palette = getReaderSheetPalette(isDark);
27740
- useEffect5(() => {
27914
+ useEffect6(() => {
27741
27915
  if (visible) setValue(`${currentPage}`);
27742
27916
  }, [currentPage, visible]);
27743
27917
  const confirmJump = () => {
@@ -27855,10 +28029,10 @@ var Topbar = ({
27855
28029
  accentColor,
27856
28030
  mobileChromeVisible
27857
28031
  } = useViewerStore5();
27858
- const [jumpModalOpen, setJumpModalOpen] = useState6(false);
28032
+ const [jumpModalOpen, setJumpModalOpen] = useState7(false);
27859
28033
  const isDark = uiTheme === "dark";
27860
28034
  const navIconColor = isDark ? "#e5e7eb" : "#111827";
27861
- const { width, height } = useWindowDimensions3();
28035
+ const { width, height } = useWindowDimensions4();
27862
28036
  const isLandscape = width > height;
27863
28037
  const insets = usePapyrusSafeAreaInsets();
27864
28038
  const navigateToPage = (targetPage) => {
@@ -28179,14 +28353,14 @@ var styles7 = StyleSheet7.create({
28179
28353
  var Topbar_default = Topbar;
28180
28354
 
28181
28355
  // components/ToolDock.tsx
28182
- import React10, { useEffect as useEffect6, useMemo as useMemo4, useRef as useRef5, useState as useState7 } from "react";
28356
+ import React10, { useEffect as useEffect7, useMemo as useMemo4, useRef as useRef6, useState as useState8 } from "react";
28183
28357
  import {
28184
28358
  View as View8,
28185
28359
  Text as Text4,
28186
28360
  Pressable as Pressable6,
28187
28361
  StyleSheet as StyleSheet8,
28188
28362
  ScrollView as ScrollView3,
28189
- useWindowDimensions as useWindowDimensions4
28363
+ useWindowDimensions as useWindowDimensions5
28190
28364
  } from "react-native";
28191
28365
  import { useViewerStore as useViewerStore6 } from "@papyrus-sdk/core";
28192
28366
 
@@ -28419,18 +28593,18 @@ var OpacitySlider = React10.memo(function OpacitySlider2({
28419
28593
  isDark,
28420
28594
  onCommit
28421
28595
  }) {
28422
- const [trackWidth, setTrackWidth] = useState7(0);
28423
- const [draftValue, setDraftValue] = useState7(value);
28424
- const dragValueRef = useRef5(draftValue);
28425
- const isDraggingRef = useRef5(false);
28426
- const frameRef = useRef5(null);
28427
- useEffect6(() => {
28596
+ const [trackWidth, setTrackWidth] = useState8(0);
28597
+ const [draftValue, setDraftValue] = useState8(value);
28598
+ const dragValueRef = useRef6(draftValue);
28599
+ const isDraggingRef = useRef6(false);
28600
+ const frameRef = useRef6(null);
28601
+ useEffect7(() => {
28428
28602
  if (!isDraggingRef.current) {
28429
28603
  dragValueRef.current = value;
28430
28604
  setDraftValue(value);
28431
28605
  }
28432
28606
  }, [value]);
28433
- useEffect6(() => {
28607
+ useEffect7(() => {
28434
28608
  return () => {
28435
28609
  if (frameRef.current !== null) {
28436
28610
  cancelAnimationFrame(frameRef.current);
@@ -28529,10 +28703,10 @@ var ToolDock = () => {
28529
28703
  undoAnnotations,
28530
28704
  redoAnnotations
28531
28705
  } = useViewerStore6();
28532
- const [paletteExpanded, setPaletteExpanded] = useState7(false);
28533
- const [extrasExpanded, setExtrasExpanded] = useState7(false);
28706
+ const [paletteExpanded, setPaletteExpanded] = useState8(false);
28707
+ const [extrasExpanded, setExtrasExpanded] = useState8(false);
28534
28708
  const isDark = uiTheme === "dark";
28535
- const { width: windowWidth } = useWindowDimensions4();
28709
+ const { width: windowWidth } = useWindowDimensions5();
28536
28710
  const t = getStrings(locale);
28537
28711
  const panelColor = isDark ? "rgba(24,24,27,0.92)" : "rgba(255,255,255,0.96)";
28538
28712
  const borderColor = isDark ? "rgba(71,85,105,0.48)" : "rgba(229,231,235,0.88)";
@@ -29255,10 +29429,10 @@ var ToolDock_default = ToolDock;
29255
29429
  // components/RightSheet.tsx
29256
29430
  import {
29257
29431
  useCallback as useCallback4,
29258
- useEffect as useEffect7,
29432
+ useEffect as useEffect8,
29259
29433
  useMemo as useMemo5,
29260
- useRef as useRef6,
29261
- useState as useState8
29434
+ useRef as useRef7,
29435
+ useState as useState9
29262
29436
  } from "react";
29263
29437
  import {
29264
29438
  Dimensions,
@@ -29281,6 +29455,11 @@ var resolveRightSheetHeight = ({
29281
29455
  }) => showingNotes ? Math.min(440, windowHeight * 0.56) : Math.min(640, windowHeight * 0.72);
29282
29456
  var supportsPageThumbnails = (documentType) => documentType !== "text" && documentType !== "epub";
29283
29457
 
29458
+ // components/thumbnailClipping.ts
29459
+ var shouldRemoveThumbnailClipping = ({
29460
+ useNativePreview
29461
+ }) => !useNativePreview;
29462
+
29284
29463
  // components/RightSheet.tsx
29285
29464
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
29286
29465
  var THUMBNAILS_INITIAL_NUM_TO_RENDER = 4;
@@ -29316,10 +29495,10 @@ var PageThumbnail = ({
29316
29495
  shouldRenderPreview,
29317
29496
  onPress
29318
29497
  }) => {
29319
- const viewRef = useRef6(null);
29320
- const [layoutReady, setLayoutReady] = useState8(false);
29321
- const [previewUri, setPreviewUri] = useState8(null);
29322
- useEffect7(() => {
29498
+ const viewRef = useRef7(null);
29499
+ const [layoutReady, setLayoutReady] = useState9(false);
29500
+ const [previewUri, setPreviewUri] = useState9(null);
29501
+ useEffect8(() => {
29323
29502
  if (!layoutReady || !useNativePreview || !shouldRenderPreview) return;
29324
29503
  const viewTag = findNodeHandle2(viewRef.current);
29325
29504
  if (!viewTag) return;
@@ -29334,7 +29513,7 @@ var PageThumbnail = ({
29334
29513
  useNativePreview,
29335
29514
  zoom
29336
29515
  ]);
29337
- useEffect7(() => {
29516
+ useEffect8(() => {
29338
29517
  if (!useImagePreview || !shouldRenderPreview) {
29339
29518
  setPreviewUri(null);
29340
29519
  return;
@@ -29464,10 +29643,10 @@ var RightSheet = ({
29464
29643
  locale,
29465
29644
  accentColor
29466
29645
  } = useViewerStore7();
29467
- const [pagesMode, setPagesMode] = useState8(
29646
+ const [pagesMode, setPagesMode] = useState9(
29468
29647
  documentType === "pdf" || documentType === "comic" ? "thumbnails" : "summary"
29469
29648
  );
29470
- useEffect7(() => {
29649
+ useEffect8(() => {
29471
29650
  setPagesMode(
29472
29651
  documentType === "pdf" || documentType === "comic" ? "thumbnails" : "summary"
29473
29652
  );
@@ -29491,11 +29670,11 @@ var RightSheet = ({
29491
29670
  );
29492
29671
  const useNativePreview = renderTarget !== "webview" && hasNativePageView;
29493
29672
  const useImagePreview = documentType === "comic" && renderTarget === "webview" && typeof engine.getPagePreview === "function";
29494
- const thumbnailDimensionsCacheRef = useRef6(/* @__PURE__ */ new Map());
29495
- const thumbnailDimensionsPendingRef = useRef6(/* @__PURE__ */ new Set());
29496
- const thumbnailRefreshTimeoutRef = useRef6(null);
29497
- const [thumbnailLayoutRevision, setThumbnailLayoutRevision] = useState8(0);
29498
- const [visibleThumbnailPages, setVisibleThumbnailPages] = useState8(() => /* @__PURE__ */ new Set());
29673
+ const thumbnailDimensionsCacheRef = useRef7(/* @__PURE__ */ new Map());
29674
+ const thumbnailDimensionsPendingRef = useRef7(/* @__PURE__ */ new Set());
29675
+ const thumbnailRefreshTimeoutRef = useRef7(null);
29676
+ const [thumbnailLayoutRevision, setThumbnailLayoutRevision] = useState9(0);
29677
+ const [visibleThumbnailPages, setVisibleThumbnailPages] = useState9(() => /* @__PURE__ */ new Set());
29499
29678
  const resolvedThumbsInitialCount = useMemo5(
29500
29679
  () => resolvePositiveInt2(
29501
29680
  thumbsInitialCount,
@@ -29565,7 +29744,7 @@ var RightSheet = ({
29565
29744
  },
29566
29745
  [frameWidth, thumbnailLayoutRevision]
29567
29746
  );
29568
- useEffect7(
29747
+ useEffect8(
29569
29748
  () => () => {
29570
29749
  if (thumbnailRefreshTimeoutRef.current) {
29571
29750
  clearTimeout(thumbnailRefreshTimeoutRef.current);
@@ -29573,11 +29752,11 @@ var RightSheet = ({
29573
29752
  },
29574
29753
  []
29575
29754
  );
29576
- useEffect7(() => {
29755
+ useEffect8(() => {
29577
29756
  if (sidebarRightOpen) return;
29578
29757
  setVisibleThumbnailPages(/* @__PURE__ */ new Set());
29579
29758
  }, [sidebarRightOpen]);
29580
- useEffect7(() => {
29759
+ useEffect8(() => {
29581
29760
  if (!sidebarRightOpen || sidebarRightTab !== "pages") return;
29582
29761
  if (activeMobileDestination === "pages") {
29583
29762
  setPagesMode("thumbnails");
@@ -29585,7 +29764,7 @@ var RightSheet = ({
29585
29764
  }
29586
29765
  setPagesMode("summary");
29587
29766
  }, [activeMobileDestination, sidebarRightOpen, sidebarRightTab]);
29588
- useEffect7(() => {
29767
+ useEffect8(() => {
29589
29768
  if (!sidebarRightOpen || sidebarRightTab !== "pages") return;
29590
29769
  if (pagesMode !== "thumbnails" || !supportsThumbnails) return;
29591
29770
  if (pageCount <= 0) return;
@@ -29796,7 +29975,9 @@ var RightSheet = ({
29796
29975
  windowSize: THUMBNAILS_WINDOW_SIZE,
29797
29976
  maxToRenderPerBatch: THUMBNAILS_MAX_TO_RENDER_PER_BATCH,
29798
29977
  updateCellsBatchingPeriod: THUMBNAILS_UPDATE_CELLS_BATCHING_PERIOD,
29799
- removeClippedSubviews: true,
29978
+ removeClippedSubviews: shouldRemoveThumbnailClipping({
29979
+ useNativePreview
29980
+ }),
29800
29981
  viewabilityConfig: { itemVisiblePercentThreshold: 20 },
29801
29982
  onViewableItemsChanged: onThumbnailsViewableItemsChanged,
29802
29983
  renderItem: renderThumbnailItem
@@ -30199,7 +30380,7 @@ var styles9 = StyleSheet9.create({
30199
30380
  var RightSheet_default = RightSheet;
30200
30381
 
30201
30382
  // components/AnnotationEditor.tsx
30202
- import { useEffect as useEffect8, useState as useState9 } from "react";
30383
+ import { useEffect as useEffect9, useState as useState10 } from "react";
30203
30384
  import {
30204
30385
  Modal as Modal2,
30205
30386
  View as View10,
@@ -30222,10 +30403,10 @@ var AnnotationEditor = () => {
30222
30403
  } = useViewerStore8();
30223
30404
  const annotation = annotations.find((ann) => ann.id === selectedAnnotationId);
30224
30405
  const isEditable = annotation && (annotation.type === "text" || annotation.type === "comment");
30225
- const [draft, setDraft] = useState9("");
30406
+ const [draft, setDraft] = useState10("");
30226
30407
  const isDark = uiTheme === "dark";
30227
30408
  const t = getStrings(locale);
30228
- useEffect8(() => {
30409
+ useEffect9(() => {
30229
30410
  if (isEditable) {
30230
30411
  setDraft(annotation?.content ?? "");
30231
30412
  }
@@ -30352,7 +30533,7 @@ var styles10 = StyleSheet10.create({
30352
30533
  var AnnotationEditor_default = AnnotationEditor;
30353
30534
 
30354
30535
  // components/BottomBar.tsx
30355
- import { View as View11, Pressable as Pressable9, StyleSheet as StyleSheet11, useWindowDimensions as useWindowDimensions5 } from "react-native";
30536
+ import { View as View11, Pressable as Pressable9, StyleSheet as StyleSheet11, useWindowDimensions as useWindowDimensions6 } from "react-native";
30356
30537
  import { useViewerStore as useViewerStore9 } from "@papyrus-sdk/core";
30357
30538
 
30358
30539
  // components/bottomBarModel.ts
@@ -30420,7 +30601,7 @@ var BottomBar = ({
30420
30601
  } = useViewerStore9();
30421
30602
  const isDark = uiTheme === "dark";
30422
30603
  const t = getStrings(locale);
30423
- const { width, height } = useWindowDimensions5();
30604
+ const { width, height } = useWindowDimensions6();
30424
30605
  const isLandscape = width > height;
30425
30606
  const offsets = resolveMobileChromeOffsets(usePapyrusSafeAreaInsets());
30426
30607
  const iconColor = (active) => {
@@ -31208,7 +31389,7 @@ var styles12 = StyleSheet12.create({
31208
31389
  var SettingsSheet_default = SettingsSheet;
31209
31390
 
31210
31391
  // components/CoverPreview.tsx
31211
- import { useEffect as useEffect9, useMemo as useMemo6, useRef as useRef7, useState as useState10 } from "react";
31392
+ import { useEffect as useEffect10, useMemo as useMemo6, useRef as useRef8, useState as useState11 } from "react";
31212
31393
  import {
31213
31394
  ActivityIndicator,
31214
31395
  StyleSheet as StyleSheet13,
@@ -31266,18 +31447,18 @@ var CoverPreview = ({
31266
31447
  onLoadEnd,
31267
31448
  onError
31268
31449
  }) => {
31269
- const [engine, setEngine] = useState10(null);
31270
- const [layoutReady, setLayoutReady] = useState10(false);
31271
- const [loaded, setLoaded] = useState10(false);
31272
- const [loading, setLoading] = useState10(false);
31273
- const [error, setError] = useState10(null);
31274
- const viewRef = useRef7(null);
31450
+ const [engine, setEngine] = useState11(null);
31451
+ const [layoutReady, setLayoutReady] = useState11(false);
31452
+ const [loaded, setLoaded] = useState11(false);
31453
+ const [loading, setLoading] = useState11(false);
31454
+ const [error, setError] = useState11(null);
31455
+ const viewRef = useRef8(null);
31275
31456
  const resolvedType = useMemo6(() => inferDocumentType(source, type), [source, type]);
31276
31457
  const isPdf = resolvedType === "pdf";
31277
31458
  const hasNativePageView = Boolean(UIManager2.getViewManagerConfig?.("PapyrusPageView"));
31278
31459
  const canRender = !isPdf || hasNativePageView;
31279
31460
  const shouldRender = Boolean(visible);
31280
- useEffect9(() => {
31461
+ useEffect10(() => {
31281
31462
  if (!shouldRender) {
31282
31463
  if (!keepAlive) {
31283
31464
  setEngine(null);
@@ -31290,7 +31471,7 @@ var CoverPreview = ({
31290
31471
  setEngine(new MobileDocumentEngine());
31291
31472
  }
31292
31473
  }, [shouldRender, keepAlive, engine, canRender]);
31293
- useEffect9(() => {
31474
+ useEffect10(() => {
31294
31475
  if (!engine || !shouldRender || !canRender) return;
31295
31476
  let active = true;
31296
31477
  setLoading(true);
@@ -31317,13 +31498,13 @@ var CoverPreview = ({
31317
31498
  active = false;
31318
31499
  };
31319
31500
  }, [engine, shouldRender, canRender, source, type, pageIndex, isPdf, onLoadStart, onLoadEnd, onError]);
31320
- useEffect9(() => {
31501
+ useEffect10(() => {
31321
31502
  if (!engine || !isPdf || !loaded || !layoutReady || !shouldRender || !canRender) return;
31322
31503
  const viewTag = findNodeHandle3(viewRef.current);
31323
31504
  if (!viewTag) return;
31324
31505
  engine.renderPage(pageIndex, viewTag, renderScale);
31325
31506
  }, [engine, isPdf, loaded, layoutReady, shouldRender, canRender, pageIndex, renderScale]);
31326
- useEffect9(() => {
31507
+ useEffect10(() => {
31327
31508
  return () => {
31328
31509
  engine?.destroy();
31329
31510
  };
@@ -31390,7 +31571,7 @@ var styles13 = StyleSheet13.create({
31390
31571
  var CoverPreview_default = CoverPreview;
31391
31572
 
31392
31573
  // components/ReadingShell.tsx
31393
- import React16, { useState as useState12 } from "react";
31574
+ import React16, { useState as useState14 } from "react";
31394
31575
  import { Keyboard as Keyboard2, View as View19, StyleSheet as StyleSheet20 } from "react-native";
31395
31576
  import { useViewerStore as useViewerStore17 } from "@papyrus-sdk/core";
31396
31577
 
@@ -31754,25 +31935,24 @@ var styles16 = StyleSheet16.create({
31754
31935
  });
31755
31936
 
31756
31937
  // components/ProgressPill.tsx
31757
- import { useMemo as useMemo7 } from "react";
31758
- import { Pressable as Pressable12, StyleSheet as StyleSheet17, Text as Text12, View as View17 } from "react-native";
31938
+ import { useEffect as useEffect11, useMemo as useMemo7, useRef as useRef9, useState as useState12 } from "react";
31939
+ import {
31940
+ Animated as Animated3,
31941
+ PanResponder as PanResponder2,
31942
+ StyleSheet as StyleSheet17,
31943
+ Text as Text12,
31944
+ View as View17,
31945
+ useWindowDimensions as useWindowDimensions7
31946
+ } from "react-native";
31759
31947
  import { useViewerStore as useViewerStore14 } from "@papyrus-sdk/core";
31760
-
31761
- // components/progressPillInteraction.ts
31762
- var getProgressPillInteraction = (onPress, onLongPress) => ({
31763
- onPress,
31764
- onLongPress,
31765
- accessibilityLabel: "Open document navigation",
31766
- accessibilityRole: "button"
31767
- });
31768
-
31769
- // components/ProgressPill.tsx
31770
31948
  import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
31771
31949
  var clampPercent = (value) => Math.max(0, Math.min(100, Math.round(value)));
31772
31950
  function ProgressPill({
31773
31951
  documentType,
31774
31952
  onPress,
31775
- onOpenPageJump
31953
+ onOpenPageJump,
31954
+ onNavigateToPage,
31955
+ onScrubbingChange
31776
31956
  }) {
31777
31957
  const {
31778
31958
  currentPage,
@@ -31784,18 +31964,193 @@ function ProgressPill({
31784
31964
  } = useViewerStore14();
31785
31965
  const isDark = uiTheme === "dark";
31786
31966
  const offsets = resolveMobileChromeOffsets(usePapyrusSafeAreaInsets());
31967
+ const { height: windowHeight } = useWindowDimensions7();
31968
+ const trackHeight = Math.max(
31969
+ 180,
31970
+ Math.min(520, windowHeight - offsets.progress - offsets.bottom - 132)
31971
+ );
31972
+ const thumbHeight = 44;
31973
+ const trackYRef = useRef9(0);
31974
+ const scrubberRef = useRef9(null);
31975
+ const currentPageRef = useRef9(currentPage);
31976
+ currentPageRef.current = currentPage;
31977
+ const isScrubbingRef = useRef9(false);
31978
+ const hasMovedRef = useRef9(false);
31979
+ const longPressTriggeredRef = useRef9(false);
31980
+ const longPressTimerRef = useRef9(null);
31981
+ const pendingReleasedScrubPageRef = useRef9(null);
31982
+ const [scrubPreviewPage, setScrubPreviewPage] = useState12(null);
31983
+ const onNavigateToPageRef = useRef9(onNavigateToPage);
31984
+ const onPressRef = useRef9(onPress);
31985
+ const onOpenPageJumpRef = useRef9(onOpenPageJump);
31986
+ const onScrubbingChangeRef = useRef9(onScrubbingChange);
31987
+ onScrubbingChangeRef.current = onScrubbingChange;
31988
+ const scrubToPositionRef = useRef9(() => {
31989
+ });
31990
+ const scrubNavigationControllerRef = useRef9(
31991
+ createPageScrubberNavigationController((page) => {
31992
+ onNavigateToPageRef.current?.(page);
31993
+ })
31994
+ );
31995
+ const scrubberResponderRef = useRef9(null);
31996
+ const touchPolicy = resolvePageScrubberTouchPolicy();
31997
+ const displayedPage = scrubPreviewPage ?? currentPage;
31787
31998
  const label = useMemo7(() => {
31788
31999
  const total = Math.max(pageCount, 1);
31789
- const percent = clampPercent(currentPage / total * 100);
32000
+ const percent = clampPercent(displayedPage / total * 100);
31790
32001
  if (documentType === "pdf") {
31791
- return `${currentPage}/${pageCount || 0}`;
32002
+ return `${displayedPage}/${pageCount || 0}`;
31792
32003
  }
31793
32004
  if (documentType === "epub") {
31794
- return `Cap. ${currentPage} \xB7 ${percent}%`;
32005
+ return `Cap. ${displayedPage} \xB7 ${percent}%`;
31795
32006
  }
31796
32007
  return `${percent}%`;
31797
- }, [currentPage, documentType, pageCount]);
31798
- if (!mobileChromeVisible || !mobileProgressPillVisible) return null;
32008
+ }, [displayedPage, documentType, pageCount]);
32009
+ const thumbTop = resolvePageScrubberThumbTop({
32010
+ currentPage,
32011
+ trackHeight,
32012
+ thumbHeight,
32013
+ pageCount
32014
+ });
32015
+ const thumbTopRef = useRef9(new Animated3.Value(thumbTop));
32016
+ const thumbTopSnapshotRef = useRef9(thumbTop);
32017
+ thumbTopSnapshotRef.current = thumbTop;
32018
+ const scrubberMetricsRef = useRef9({ trackHeight, thumbHeight, pageCount });
32019
+ scrubberMetricsRef.current = { trackHeight, thumbHeight, pageCount };
32020
+ useEffect11(() => {
32021
+ onNavigateToPageRef.current = onNavigateToPage;
32022
+ }, [onNavigateToPage]);
32023
+ useEffect11(() => {
32024
+ onPressRef.current = onPress;
32025
+ onOpenPageJumpRef.current = onOpenPageJump;
32026
+ }, [onOpenPageJump, onPress]);
32027
+ useEffect11(() => {
32028
+ const pendingPage = pendingReleasedScrubPageRef.current;
32029
+ if (pendingPage !== null && currentPage === pendingPage) {
32030
+ pendingReleasedScrubPageRef.current = null;
32031
+ setScrubPreviewPage(null);
32032
+ }
32033
+ }, [currentPage]);
32034
+ useEffect11(() => {
32035
+ if (!isScrubbingRef.current) {
32036
+ thumbTopRef.current.setValue(thumbTop);
32037
+ }
32038
+ }, [thumbTop, thumbTopRef]);
32039
+ useEffect11(
32040
+ () => () => {
32041
+ if (longPressTimerRef.current !== null) {
32042
+ clearTimeout(longPressTimerRef.current);
32043
+ }
32044
+ },
32045
+ []
32046
+ );
32047
+ if (!shouldRenderPageScrubber({
32048
+ mobileChromeVisible,
32049
+ mobileProgressPillVisible,
32050
+ isScrubbing: isScrubbingRef.current
32051
+ })) {
32052
+ return null;
32053
+ }
32054
+ const scrubToPosition = (position) => {
32055
+ const { trackHeight: trackHeight2, thumbHeight: thumbHeight2, pageCount: pageCount2 } = scrubberMetricsRef.current;
32056
+ const visualPosition = resolvePageScrubberThumbTopFromPosition({
32057
+ position,
32058
+ trackHeight: trackHeight2,
32059
+ thumbHeight: thumbHeight2
32060
+ });
32061
+ thumbTopRef.current.setValue(visualPosition);
32062
+ const nextPage = resolvePageScrubberPage({
32063
+ position,
32064
+ trackHeight: trackHeight2,
32065
+ thumbHeight: thumbHeight2,
32066
+ pageCount: pageCount2
32067
+ });
32068
+ if (nextPage === null) return;
32069
+ scrubNavigationControllerRef.current.update(nextPage);
32070
+ setScrubPreviewPage(nextPage);
32071
+ };
32072
+ scrubToPositionRef.current = scrubToPosition;
32073
+ if (scrubberResponderRef.current === null) {
32074
+ scrubberResponderRef.current = PanResponder2.create({
32075
+ onStartShouldSetPanResponder: () => touchPolicy.claimOnStart,
32076
+ onStartShouldSetPanResponderCapture: () => touchPolicy.captureOnStart,
32077
+ onMoveShouldSetPanResponder: () => true,
32078
+ onMoveShouldSetPanResponderCapture: () => touchPolicy.captureOnMove,
32079
+ onPanResponderTerminationRequest: () => false,
32080
+ onPanResponderGrant: () => {
32081
+ isScrubbingRef.current = true;
32082
+ onScrubbingChangeRef.current?.(true);
32083
+ hasMovedRef.current = false;
32084
+ longPressTriggeredRef.current = false;
32085
+ scrubNavigationControllerRef.current.begin();
32086
+ pendingReleasedScrubPageRef.current = null;
32087
+ setScrubPreviewPage(currentPageRef.current);
32088
+ if (longPressTimerRef.current !== null) {
32089
+ clearTimeout(longPressTimerRef.current);
32090
+ }
32091
+ longPressTimerRef.current = onOpenPageJumpRef.current ? setTimeout(() => {
32092
+ longPressTimerRef.current = null;
32093
+ if (!hasMovedRef.current) {
32094
+ longPressTriggeredRef.current = true;
32095
+ onOpenPageJumpRef.current?.();
32096
+ }
32097
+ }, 500) : null;
32098
+ },
32099
+ onPanResponderMove: (_, gestureState) => {
32100
+ if (Math.abs(gestureState.dy) <= 4) return;
32101
+ hasMovedRef.current = true;
32102
+ if (longPressTimerRef.current !== null) {
32103
+ clearTimeout(longPressTimerRef.current);
32104
+ longPressTimerRef.current = null;
32105
+ }
32106
+ scrubToPositionRef.current(gestureState.moveY - trackYRef.current);
32107
+ },
32108
+ onPanResponderRelease: () => {
32109
+ if (longPressTimerRef.current !== null) {
32110
+ clearTimeout(longPressTimerRef.current);
32111
+ longPressTimerRef.current = null;
32112
+ }
32113
+ isScrubbingRef.current = false;
32114
+ onScrubbingChangeRef.current?.(false);
32115
+ const releasedPage = scrubNavigationControllerRef.current.release();
32116
+ const releaseAction = resolvePageScrubberReleaseAction({
32117
+ hasMoved: hasMovedRef.current,
32118
+ pendingPage: releasedPage
32119
+ });
32120
+ if (releaseAction.kind === "navigate") {
32121
+ pendingReleasedScrubPageRef.current = releaseAction.page;
32122
+ if (!onNavigateToPageRef.current) {
32123
+ setScrubPreviewPage(null);
32124
+ }
32125
+ } else {
32126
+ scrubNavigationControllerRef.current.cancel();
32127
+ pendingReleasedScrubPageRef.current = null;
32128
+ setScrubPreviewPage(null);
32129
+ if (!longPressTriggeredRef.current) onPressRef.current();
32130
+ }
32131
+ hasMovedRef.current = false;
32132
+ longPressTriggeredRef.current = false;
32133
+ },
32134
+ onPanResponderTerminate: () => {
32135
+ if (longPressTimerRef.current !== null) {
32136
+ clearTimeout(longPressTimerRef.current);
32137
+ longPressTimerRef.current = null;
32138
+ }
32139
+ isScrubbingRef.current = false;
32140
+ onScrubbingChangeRef.current?.(false);
32141
+ scrubNavigationControllerRef.current.cancel();
32142
+ pendingReleasedScrubPageRef.current = null;
32143
+ setScrubPreviewPage(null);
32144
+ hasMovedRef.current = false;
32145
+ longPressTriggeredRef.current = false;
32146
+ thumbTopRef.current.setValue(thumbTopSnapshotRef.current);
32147
+ }
32148
+ });
32149
+ }
32150
+ const responderPanHandlers = scrubberResponderRef.current?.panHandlers ?? {};
32151
+ const responderTarget = touchPolicy.responderTarget;
32152
+ const scrubberPanHandlers = responderTarget === "track" ? responderPanHandlers : {};
32153
+ const pillPanHandlers = responderTarget === "pill" ? responderPanHandlers : {};
31799
32154
  return /* @__PURE__ */ jsx20(
31800
32155
  View17,
31801
32156
  {
@@ -31804,32 +32159,45 @@ function ProgressPill({
31804
32159
  styles17.frame,
31805
32160
  {
31806
32161
  top: offsets.progress,
31807
- left: 0,
31808
- right: 0,
31809
- paddingLeft: offsets.left + MOBILE_CHROME_METRICS.progressHorizontalOffset,
31810
- paddingRight: offsets.right
32162
+ right: offsets.right
31811
32163
  }
31812
32164
  ],
31813
32165
  children: /* @__PURE__ */ jsxs17(
31814
- Pressable12,
32166
+ View17,
31815
32167
  {
31816
- ...getProgressPillInteraction(onPress, onOpenPageJump),
31817
- style: [
31818
- styles17.pill,
31819
- isDark && styles17.pillDark,
31820
- { borderColor: `${accentColor}33` }
31821
- ],
31822
- testID: "papyrus-progress-pill",
32168
+ ref: scrubberRef,
32169
+ style: [styles17.scrubber, { height: trackHeight }],
32170
+ ...scrubberPanHandlers,
32171
+ onLayout: () => {
32172
+ scrubberRef.current?.measureInWindow((_x, y) => {
32173
+ trackYRef.current = y;
32174
+ });
32175
+ },
31823
32176
  children: [
31824
- /* @__PURE__ */ jsx20(View17, { style: styles17.iconHit, children: /* @__PURE__ */ jsx20(
31825
- IconPageNav,
32177
+ /* @__PURE__ */ jsx20(View17, { style: [styles17.track, isDark && styles17.trackDark] }),
32178
+ /* @__PURE__ */ jsx20(Animated3.View, { style: [styles17.thumb, { top: thumbTopRef.current }], children: /* @__PURE__ */ jsxs17(
32179
+ View17,
31826
32180
  {
31827
- size: 20,
31828
- color: isDark ? "#f8fafc" : "#111827",
31829
- strokeWidth: 1.8
32181
+ style: [
32182
+ styles17.pill,
32183
+ isDark && styles17.pillDark,
32184
+ { borderColor: `${accentColor}33` }
32185
+ ],
32186
+ ...pillPanHandlers,
32187
+ testID: "papyrus-progress-pill",
32188
+ children: [
32189
+ /* @__PURE__ */ jsx20(View17, { style: styles17.labelHit, children: /* @__PURE__ */ jsx20(Text12, { style: [styles17.label, isDark && styles17.labelDark], children: label }) }),
32190
+ /* @__PURE__ */ jsx20(View17, { style: styles17.iconHit, children: /* @__PURE__ */ jsx20(
32191
+ IconPageNav,
32192
+ {
32193
+ size: 20,
32194
+ color: isDark ? "#f8fafc" : "#111827",
32195
+ strokeWidth: 1.8
32196
+ }
32197
+ ) })
32198
+ ]
31830
32199
  }
31831
- ) }),
31832
- /* @__PURE__ */ jsx20(View17, { style: styles17.labelHit, children: /* @__PURE__ */ jsx20(Text12, { style: [styles17.label, isDark && styles17.labelDark], children: label }) })
32200
+ ) })
31833
32201
  ]
31834
32202
  }
31835
32203
  )
@@ -31843,9 +32211,31 @@ var styles17 = StyleSheet17.create({
31843
32211
  left: 0,
31844
32212
  right: 0,
31845
32213
  bottom: "auto",
31846
- alignItems: "flex-start",
32214
+ alignItems: "flex-end",
31847
32215
  zIndex: 18
31848
32216
  },
32217
+ scrubber: {
32218
+ width: 132,
32219
+ position: "relative",
32220
+ marginRight: 4
32221
+ },
32222
+ track: {
32223
+ position: "absolute",
32224
+ top: 0,
32225
+ right: 30,
32226
+ bottom: 0,
32227
+ width: 3,
32228
+ borderRadius: 2,
32229
+ backgroundColor: "rgba(15,23,42,0.16)"
32230
+ },
32231
+ trackDark: {
32232
+ backgroundColor: "rgba(248,250,252,0.22)"
32233
+ },
32234
+ thumb: {
32235
+ position: "absolute",
32236
+ right: 0,
32237
+ height: 44
32238
+ },
31849
32239
  pill: {
31850
32240
  minWidth: 92,
31851
32241
  borderRadius: 999,
@@ -31884,10 +32274,10 @@ var styles17 = StyleSheet17.create({
31884
32274
  });
31885
32275
 
31886
32276
  // components/SearchOverlay.tsx
31887
- import React15, { useEffect as useEffect10, useMemo as useMemo8, useState as useState11 } from "react";
32277
+ import React15, { useEffect as useEffect12, useMemo as useMemo8, useState as useState13 } from "react";
31888
32278
  import {
31889
32279
  Keyboard,
31890
- Pressable as Pressable13,
32280
+ Pressable as Pressable12,
31891
32281
  StyleSheet as StyleSheet18,
31892
32282
  Text as Text13,
31893
32283
  TextInput as TextInput3,
@@ -31915,19 +32305,19 @@ function SearchOverlay({
31915
32305
  uiTheme,
31916
32306
  accentColor
31917
32307
  } = useViewerStore15();
31918
- const [draft, setDraft] = useState11(searchQuery);
31919
- const [isSearching, setIsSearching] = useState11(false);
32308
+ const [draft, setDraft] = useState13(searchQuery);
32309
+ const [isSearching, setIsSearching] = useState13(false);
31920
32310
  const searchRequestIdRef = React15.useRef(0);
31921
32311
  const isDark = uiTheme === "dark";
31922
32312
  const offsets = resolveMobileChromeOffsets(usePapyrusSafeAreaInsets());
31923
32313
  const t = getStrings(locale);
31924
32314
  const searchService = useMemo8(() => new SearchService(engine), [engine]);
31925
- useEffect10(() => {
32315
+ useEffect12(() => {
31926
32316
  searchService.clearCache();
31927
32317
  }, [isLoaded, pageCount, searchService]);
31928
32318
  const currentCount = searchResults.length > 0 && activeSearchIndex >= 0 ? activeSearchIndex + 1 : 0;
31929
32319
  const targetLabel = documentType === "pdf" ? t.page : documentType === "epub" ? t.contents : t.progress;
31930
- useEffect10(() => {
32320
+ useEffect12(() => {
31931
32321
  if (!visible) return;
31932
32322
  setDraft(searchQuery);
31933
32323
  }, [searchQuery, visible]);
@@ -31980,7 +32370,7 @@ function SearchOverlay({
31980
32370
  )
31981
32371
  ] }),
31982
32372
  /* @__PURE__ */ jsx21(
31983
- Pressable13,
32373
+ Pressable12,
31984
32374
  {
31985
32375
  onPress: handleClose,
31986
32376
  style: [styles18.closeButton, isDark && styles18.closeButtonDark],
@@ -31991,7 +32381,7 @@ function SearchOverlay({
31991
32381
  ] }),
31992
32382
  /* @__PURE__ */ jsxs18(View18, { style: styles18.metaRow, children: [
31993
32383
  /* @__PURE__ */ jsx21(
31994
- Pressable13,
32384
+ Pressable12,
31995
32385
  {
31996
32386
  onPress: prevSearchResult,
31997
32387
  disabled: searchResults.length === 0,
@@ -32006,7 +32396,7 @@ function SearchOverlay({
32006
32396
  ),
32007
32397
  /* @__PURE__ */ jsx21(Text13, { style: [styles18.metaText, isDark && styles18.metaTextDark], children: isSearching ? t.searching : `${currentCount}/${searchResults.length} ${targetLabel}` }),
32008
32398
  /* @__PURE__ */ jsx21(
32009
- Pressable13,
32399
+ Pressable12,
32010
32400
  {
32011
32401
  onPress: nextSearchResult,
32012
32402
  disabled: searchResults.length === 0,
@@ -32022,7 +32412,7 @@ function SearchOverlay({
32022
32412
  ] }),
32023
32413
  /* @__PURE__ */ jsxs18(View18, { style: styles18.actionsRow, children: [
32024
32414
  /* @__PURE__ */ jsx21(
32025
- Pressable13,
32415
+ Pressable12,
32026
32416
  {
32027
32417
  onPress: handleSubmit,
32028
32418
  style: [styles18.primaryAction, { backgroundColor: accentColor }],
@@ -32032,7 +32422,7 @@ function SearchOverlay({
32032
32422
  }
32033
32423
  ),
32034
32424
  /* @__PURE__ */ jsx21(
32035
- Pressable13,
32425
+ Pressable12,
32036
32426
  {
32037
32427
  onPress: onOpenResults,
32038
32428
  disabled: searchResults.length === 0,
@@ -32200,7 +32590,7 @@ var styles18 = StyleSheet18.create({
32200
32590
  });
32201
32591
 
32202
32592
  // components/SearchResultsSheet.tsx
32203
- import { Pressable as Pressable14, StyleSheet as StyleSheet19, Text as Text14 } from "react-native";
32593
+ import { Pressable as Pressable13, StyleSheet as StyleSheet19, Text as Text14 } from "react-native";
32204
32594
  import { useViewerStore as useViewerStore16 } from "@papyrus-sdk/core";
32205
32595
  import { jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
32206
32596
  function SearchResultsSheet({
@@ -32251,7 +32641,7 @@ function SearchResultsSheet({
32251
32641
  /* @__PURE__ */ jsx22(NativeSheetScrollView, { contentContainerStyle: styles19.content, children: searchResults.length === 0 ? /* @__PURE__ */ jsx22(Text14, { style: [styles19.emptyText, { color: palette.mutedText }], children: t.noResults }) : searchResults.map((result, index) => {
32252
32642
  const isActive = index === activeSearchIndex;
32253
32643
  return /* @__PURE__ */ jsxs19(
32254
- Pressable14,
32644
+ Pressable13,
32255
32645
  {
32256
32646
  onPress: () => {
32257
32647
  setDocumentState({
@@ -32321,12 +32711,13 @@ function ReadingShell({
32321
32711
  uiTheme,
32322
32712
  accentColor
32323
32713
  } = useViewerStore17();
32324
- const [overflowOpen, setOverflowOpen] = useState12(false);
32325
- const [infoOpen, setInfoOpen] = useState12(false);
32326
- const [actionsOpen, setActionsOpen] = useState12(false);
32327
- const [settingsOpen, setSettingsOpen] = useState12(false);
32328
- const [searchResultsOpen, setSearchResultsOpen] = useState12(false);
32329
- const [pageJumpOpen, setPageJumpOpen] = useState12(false);
32714
+ const [overflowOpen, setOverflowOpen] = useState14(false);
32715
+ const [infoOpen, setInfoOpen] = useState14(false);
32716
+ const [actionsOpen, setActionsOpen] = useState14(false);
32717
+ const [settingsOpen, setSettingsOpen] = useState14(false);
32718
+ const [searchResultsOpen, setSearchResultsOpen] = useState14(false);
32719
+ const [pageJumpOpen, setPageJumpOpen] = useState14(false);
32720
+ const [pageScrubActive, setPageScrubActive] = useState14(false);
32330
32721
  const isDark = uiTheme === "dark";
32331
32722
  React16.useEffect(() => {
32332
32723
  const showSubscription = Keyboard2.addListener("keyboardDidShow", () => {
@@ -32403,15 +32794,24 @@ function ReadingShell({
32403
32794
  showPageNavigationControls: false
32404
32795
  }
32405
32796
  ),
32406
- /* @__PURE__ */ jsx23(View19, { style: styles20.viewerStage, children: /* @__PURE__ */ jsx23(Viewer_default, { engine, ...viewerProps }) }),
32797
+ /* @__PURE__ */ jsx23(View19, { style: styles20.viewerStage, children: /* @__PURE__ */ jsx23(
32798
+ Viewer_default,
32799
+ {
32800
+ engine,
32801
+ ...viewerProps,
32802
+ pageScrubActive
32803
+ }
32804
+ ) }),
32407
32805
  /* @__PURE__ */ jsx23(
32408
32806
  ProgressPill,
32409
32807
  {
32410
32808
  documentType,
32809
+ onNavigateToPage: navigateToPage,
32411
32810
  onPress: () => openDestination(
32412
32811
  documentType === "pdf" ? "pages" : documentType === "epub" ? "contents" : "progress"
32413
32812
  ),
32414
- onOpenPageJump: () => setPageJumpOpen(true)
32813
+ onOpenPageJump: () => setPageJumpOpen(true),
32814
+ onScrubbingChange: setPageScrubActive
32415
32815
  }
32416
32816
  ),
32417
32817
  /* @__PURE__ */ jsx23(
@@ -32433,7 +32833,11 @@ function ReadingShell({
32433
32833
  setSearchResultsOpen(false);
32434
32834
  closeMobileDestination();
32435
32835
  },
32436
- onOpenResults: () => setSearchResultsOpen(true)
32836
+ onOpenResults: () => {
32837
+ Keyboard2.dismiss();
32838
+ setSearchResultsOpen(true);
32839
+ closeMobileDestination();
32840
+ }
32437
32841
  }
32438
32842
  ),
32439
32843
  /* @__PURE__ */ jsx23(