@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.js CHANGED
@@ -25460,6 +25460,12 @@ var resolvePageTapChromeVisibility = ({
25460
25460
  return !chromeVisible;
25461
25461
  };
25462
25462
 
25463
+ // components/selectionContentInteraction.ts
25464
+ var shouldDismissSelectionOnContentInteraction = ({
25465
+ selectionActive,
25466
+ interaction
25467
+ }) => selectionActive && (interaction === "tap" || interaction === "scroll");
25468
+
25463
25469
  // components/DedicatedAndroidPdfViewer.tsx
25464
25470
  var import_jsx_runtime5 = require("react/jsx-runtime");
25465
25471
  var TEXT_MARKUP_TOOLS2 = /* @__PURE__ */ new Set(["highlight", "underline", "squiggly", "strikeout"]);
@@ -25640,6 +25646,13 @@ function DedicatedAndroidPdfViewer({
25640
25646
  setSelectedAnnotation(event.nativeEvent.id);
25641
25647
  },
25642
25648
  onTap: () => {
25649
+ if (shouldDismissSelectionOnContentInteraction({
25650
+ selectionActive: selectionRef.current !== null,
25651
+ interaction: "tap"
25652
+ })) {
25653
+ setSelection(null);
25654
+ selectionRef.current = null;
25655
+ }
25643
25656
  const nextVisible = resolvePageTapChromeVisibility({
25644
25657
  chromeVisible: chromeVisibleRef.current,
25645
25658
  selectionActive: selectionRef.current !== null,
@@ -25666,66 +25679,61 @@ function DedicatedAndroidPdfViewer({
25666
25679
  }
25667
25680
  },
25668
25681
  onScroll: (event) => {
25682
+ if (shouldDismissSelectionOnContentInteraction({
25683
+ selectionActive: selectionRef.current !== null,
25684
+ interaction: "scroll"
25685
+ })) {
25686
+ setSelection(null);
25687
+ selectionRef.current = null;
25688
+ }
25669
25689
  trackMobileChromeByOffset(event.nativeEvent.offsetY, "native.scroll");
25670
25690
  }
25671
25691
  }
25672
25692
  ),
25673
- selection && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
25693
+ selection && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.View, { style: styles3.selectionToolbar, pointerEvents: "box-none", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.toolbarContent, children: [
25674
25694
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
25675
25695
  import_react_native3.Pressable,
25676
25696
  {
25677
- style: styles3.overlay,
25678
25697
  onPress: () => {
25679
- setSelection(null);
25680
- selectionRef.current = null;
25681
- }
25698
+ void copySelection();
25699
+ },
25700
+ style: styles3.toolbarButton,
25701
+ accessibilityRole: "button",
25702
+ accessibilityLabel: "Copy selected text",
25703
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconCopy, { size: 20, color: "#fff", strokeWidth: 2 })
25682
25704
  }
25683
25705
  ),
25684
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.View, { style: styles3.selectionToolbar, pointerEvents: "box-none", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.toolbarContent, children: [
25685
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
25686
- import_react_native3.Pressable,
25687
- {
25688
- onPress: () => {
25689
- void copySelection();
25690
- },
25691
- style: styles3.toolbarButton,
25692
- accessibilityRole: "button",
25693
- accessibilityLabel: "Copy selected text",
25694
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconCopy, { size: 20, color: "#fff", strokeWidth: 2 })
25695
- }
25696
- ),
25697
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
25698
- import_react_native3.Pressable,
25699
- {
25700
- onPress: () => {
25701
- applySelection("highlight");
25702
- },
25703
- style: styles3.toolbarButton,
25704
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconHighlight, { size: 22, color: "#fbbf24" })
25705
- }
25706
- ),
25707
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
25708
- import_react_native3.Pressable,
25709
- {
25710
- onPress: () => {
25711
- applySelection("underline");
25712
- },
25713
- style: styles3.toolbarButton,
25714
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconUnderline, { size: 22, color: "#60a5fa" })
25715
- }
25716
- ),
25717
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
25718
- import_react_native3.Pressable,
25719
- {
25720
- onPress: () => {
25721
- applySelection("comment");
25722
- },
25723
- style: styles3.toolbarButton,
25724
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconCommentBubble, { size: 22, color: "#fff" })
25725
- }
25726
- )
25727
- ] }) })
25728
- ] })
25706
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
25707
+ import_react_native3.Pressable,
25708
+ {
25709
+ onPress: () => {
25710
+ applySelection("highlight");
25711
+ },
25712
+ style: styles3.toolbarButton,
25713
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconHighlight, { size: 22, color: "#fbbf24" })
25714
+ }
25715
+ ),
25716
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
25717
+ import_react_native3.Pressable,
25718
+ {
25719
+ onPress: () => {
25720
+ applySelection("underline");
25721
+ },
25722
+ style: styles3.toolbarButton,
25723
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconUnderline, { size: 22, color: "#60a5fa" })
25724
+ }
25725
+ ),
25726
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
25727
+ import_react_native3.Pressable,
25728
+ {
25729
+ onPress: () => {
25730
+ applySelection("comment");
25731
+ },
25732
+ style: styles3.toolbarButton,
25733
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconCommentBubble, { size: 22, color: "#fff" })
25734
+ }
25735
+ )
25736
+ ] }) }) })
25729
25737
  ] });
25730
25738
  }
25731
25739
  var styles3 = import_react_native3.StyleSheet.create({
@@ -25765,10 +25773,6 @@ var styles3 = import_react_native3.StyleSheet.create({
25765
25773
  alignItems: "center",
25766
25774
  justifyContent: "center"
25767
25775
  },
25768
- overlay: {
25769
- ...import_react_native3.StyleSheet.absoluteFillObject,
25770
- zIndex: 40
25771
- },
25772
25776
  toolbarButtonText: {
25773
25777
  color: "#fff",
25774
25778
  fontSize: 11,
@@ -25923,6 +25927,87 @@ function createScrollPerfSession({
25923
25927
  return { begin, track, end };
25924
25928
  }
25925
25929
 
25930
+ // components/pageScrubberModel.ts
25931
+ var shouldRenderPageScrubber = ({
25932
+ mobileChromeVisible,
25933
+ mobileProgressPillVisible,
25934
+ isScrubbing
25935
+ }) => mobileProgressPillVisible && (mobileChromeVisible || isScrubbing);
25936
+ var shouldEnableViewerScrollForPageScrub = ({
25937
+ viewerScrollEnabled,
25938
+ isScrubbing
25939
+ }) => viewerScrollEnabled && !isScrubbing;
25940
+ var resolvePageScrubberTouchPolicy = () => ({
25941
+ responderTarget: "pill",
25942
+ claimOnStart: true,
25943
+ captureOnStart: true,
25944
+ captureOnMove: true,
25945
+ childConsumesTouch: false
25946
+ });
25947
+ var resolvePageScrubberReleaseAction = ({
25948
+ hasMoved,
25949
+ pendingPage
25950
+ }) => hasMoved && pendingPage !== null ? { kind: "navigate", page: pendingPage } : { kind: "open" };
25951
+ var createPageScrubberNavigationController = (onRelease) => {
25952
+ let active = false;
25953
+ let pendingPage = null;
25954
+ return {
25955
+ begin: () => {
25956
+ active = true;
25957
+ pendingPage = null;
25958
+ },
25959
+ update: (page) => {
25960
+ if (active) pendingPage = page;
25961
+ },
25962
+ release: () => {
25963
+ active = false;
25964
+ const page = pendingPage;
25965
+ pendingPage = null;
25966
+ if (page !== null) onRelease(page);
25967
+ return page;
25968
+ },
25969
+ cancel: () => {
25970
+ active = false;
25971
+ pendingPage = null;
25972
+ }
25973
+ };
25974
+ };
25975
+ var resolvePageScrubberThumbTopFromPosition = ({
25976
+ position,
25977
+ trackHeight,
25978
+ thumbHeight
25979
+ }) => Math.max(0, Math.min(trackHeight - thumbHeight, position - thumbHeight / 2));
25980
+ var resolvePageScrubberThumbTop = ({
25981
+ currentPage,
25982
+ trackHeight,
25983
+ thumbHeight,
25984
+ pageCount
25985
+ }) => {
25986
+ if (pageCount <= 1) return 0;
25987
+ const travel = Math.max(0, trackHeight - thumbHeight);
25988
+ const ratio = Math.max(
25989
+ 0,
25990
+ Math.min(1, (currentPage - 1) / Math.max(1, pageCount - 1))
25991
+ );
25992
+ return ratio * travel;
25993
+ };
25994
+ var resolvePageScrubberPage = ({
25995
+ position,
25996
+ trackHeight,
25997
+ thumbHeight,
25998
+ pageCount
25999
+ }) => {
26000
+ if (!Number.isFinite(pageCount) || pageCount <= 0) return null;
26001
+ if (pageCount === 1) return 1;
26002
+ const travel = Math.max(0, trackHeight - thumbHeight);
26003
+ const thumbTop = Math.max(0, Math.min(travel, position - thumbHeight / 2));
26004
+ const ratio = travel === 0 ? 0 : thumbTop / travel;
26005
+ return Math.max(
26006
+ 1,
26007
+ Math.min(pageCount, Math.round(ratio * (pageCount - 1)) + 1)
26008
+ );
26009
+ };
26010
+
25926
26011
  // components/viewerNavigation.ts
25927
26012
  var resolveViewerScrollTarget = (pageIndex, isDouble) => ({
25928
26013
  pageIndex,
@@ -25982,7 +26067,8 @@ var Viewer = ({
25982
26067
  maxToRenderPerBatch,
25983
26068
  removeClippedSubviews,
25984
26069
  useDedicatedAndroidPdfViewer,
25985
- viewerMode
26070
+ viewerMode,
26071
+ pageScrubActive = false
25986
26072
  }) => {
25987
26073
  const pageCount = (0, import_core4.useViewerStore)((state) => state.pageCount);
25988
26074
  const currentPage = (0, import_core4.useViewerStore)((state) => state.currentPage);
@@ -26057,6 +26143,8 @@ var Viewer = ({
26057
26143
  const selectionDragActiveRef = (0, import_react5.useRef)(false);
26058
26144
  const [gestureScrollLockActive, setGestureScrollLockActive] = (0, import_react5.useState)(false);
26059
26145
  const gestureScrollLockActiveRef = (0, import_react5.useRef)(false);
26146
+ const pageScrubActiveRef = (0, import_react5.useRef)(pageScrubActive);
26147
+ pageScrubActiveRef.current = pageScrubActive;
26060
26148
  const pinchPreviewScale = (0, import_react5.useRef)(new import_react_native4.Animated.Value(1)).current;
26061
26149
  const pinchPreviewFocalX = (0, import_react5.useRef)(new import_react_native4.Animated.Value(0)).current;
26062
26150
  const pinchPreviewFocalY = (0, import_react5.useRef)(new import_react_native4.Animated.Value(0)).current;
@@ -26571,16 +26659,20 @@ var Viewer = ({
26571
26659
  selectionDragActive,
26572
26660
  gestureScrollLockActive
26573
26661
  });
26662
+ const pageScrubResolvedScrollEnabled = shouldEnableViewerScrollForPageScrub({
26663
+ viewerScrollEnabled: resolvedViewerScrollEnabled,
26664
+ isScrubbing: pageScrubActive
26665
+ });
26574
26666
  const setViewerScrollEnabledNative = (0, import_react5.useCallback)((enabled) => {
26575
26667
  const scrollNode = listRef.current;
26576
26668
  scrollNode?.setNativeProps?.({ scrollEnabled: enabled });
26577
26669
  }, []);
26578
26670
  const syncViewerScrollEnabled = (0, import_react5.useCallback)(
26579
- (nextSelectionDragActive = selectionDragActiveRef.current, nextGestureScrollLockActive = gestureScrollLockActiveRef.current) => {
26671
+ (nextSelectionDragActive = selectionDragActiveRef.current, nextGestureScrollLockActive = gestureScrollLockActiveRef.current, nextPageScrubActive = pageScrubActiveRef.current) => {
26580
26672
  setViewerScrollEnabledNative(
26581
26673
  shouldEnableViewerScroll({
26582
26674
  selectionDragActive: nextSelectionDragActive,
26583
- gestureScrollLockActive: nextGestureScrollLockActive
26675
+ gestureScrollLockActive: nextGestureScrollLockActive || nextPageScrubActive
26584
26676
  })
26585
26677
  );
26586
26678
  },
@@ -26853,6 +26945,13 @@ var Viewer = ({
26853
26945
  gestureScrollLockActiveRef.current
26854
26946
  );
26855
26947
  }, [selectionDragActive, syncViewerScrollEnabled]);
26948
+ (0, import_react5.useEffect)(() => {
26949
+ syncViewerScrollEnabled(
26950
+ selectionDragActiveRef.current,
26951
+ gestureScrollLockActiveRef.current,
26952
+ pageScrubActive
26953
+ );
26954
+ }, [pageScrubActive, syncViewerScrollEnabled]);
26856
26955
  (0, import_react5.useEffect)(() => {
26857
26956
  const pendingRestore = pendingPinchAnchorRestoreRef.current;
26858
26957
  if (!pendingRestore) return;
@@ -27038,6 +27137,17 @@ var Viewer = ({
27038
27137
  },
27039
27138
  [scaledListLayoutMetrics]
27040
27139
  );
27140
+ const scrollToPendingTarget = (0, import_react5.useCallback)(
27141
+ (animated) => {
27142
+ const target = pendingScrollTargetRef.current;
27143
+ if (target === null || listRef.current === null) return;
27144
+ listRef.current.scrollToOffset({
27145
+ offset: Math.max(0, getFallbackOffsetForIndex(target.listIndex)),
27146
+ animated
27147
+ });
27148
+ },
27149
+ [getFallbackOffsetForIndex]
27150
+ );
27041
27151
  const restoreOrientationScrollOffset = (0, import_react5.useCallback)(() => {
27042
27152
  if (pendingOrientationRestoreWidthRef.current !== windowWidth || pageCount <= 0 || isSingle || isWebView) {
27043
27153
  return;
@@ -27068,6 +27178,11 @@ var Viewer = ({
27068
27178
  }
27069
27179
  restoreOrientationScrollOffset();
27070
27180
  }, [restoreOrientationScrollOffset, windowWidth]);
27181
+ (0, import_react5.useEffect)(() => {
27182
+ if (pendingScrollTargetRef.current !== null) {
27183
+ scrollToPendingTarget(false);
27184
+ }
27185
+ }, [layoutRevision, scrollToPendingTarget]);
27071
27186
  const getItemLayout = (0, import_react5.useCallback)(
27072
27187
  (_, index) => {
27073
27188
  if (scaledListLayoutMetrics.itemCount === 0) {
@@ -27138,15 +27253,12 @@ var Viewer = ({
27138
27253
  },
27139
27254
  "scrollToPageSignal.flatList"
27140
27255
  );
27141
- listRef.current?.scrollToIndex({
27142
- index: target.listIndex,
27143
- animated: true,
27144
- viewPosition: 0
27145
- });
27256
+ scrollToPendingTarget(true);
27146
27257
  }, [
27147
27258
  clearPendingScrollRetry,
27148
27259
  clearPendingScrollTarget,
27149
27260
  ensurePageDimensions,
27261
+ scrollToPendingTarget,
27150
27262
  scrollToPageSignal,
27151
27263
  pageCount,
27152
27264
  setDocumentStateTracked,
@@ -27422,7 +27534,7 @@ var Viewer = ({
27422
27534
  {
27423
27535
  ref: horizontalScrollRef,
27424
27536
  horizontal: true,
27425
- scrollEnabled: !gestureScrollLockActive && documentSurfaceWidth > windowWidth,
27537
+ scrollEnabled: !gestureScrollLockActive && !pageScrubActive && documentSurfaceWidth > windowWidth,
27426
27538
  showsHorizontalScrollIndicator: false,
27427
27539
  onScroll: (event) => {
27428
27540
  horizontalScrollOffsetRef.current = event.nativeEvent.contentOffset?.x ?? 0;
@@ -27438,7 +27550,7 @@ var Viewer = ({
27438
27550
  style: { width: documentSurfaceWidth },
27439
27551
  contentContainerStyle: styles4.singleContent,
27440
27552
  showsVerticalScrollIndicator: false,
27441
- scrollEnabled: resolvedViewerScrollEnabled,
27553
+ scrollEnabled: pageScrubResolvedScrollEnabled,
27442
27554
  onLayout: () => captureViewerFrame(listRef.current),
27443
27555
  onContentSizeChange: (_, height) => {
27444
27556
  viewerContentHeightRef.current = height;
@@ -27511,7 +27623,7 @@ var Viewer = ({
27511
27623
  {
27512
27624
  ref: horizontalScrollRef,
27513
27625
  horizontal: true,
27514
- scrollEnabled: !gestureScrollLockActive && documentSurfaceWidth > windowWidth,
27626
+ scrollEnabled: !gestureScrollLockActive && !pageScrubActive && documentSurfaceWidth > windowWidth,
27515
27627
  showsHorizontalScrollIndicator: false,
27516
27628
  onScroll: (event) => {
27517
27629
  horizontalScrollOffsetRef.current = event.nativeEvent.contentOffset?.x ?? 0;
@@ -27534,13 +27646,15 @@ var Viewer = ({
27534
27646
  renderItem,
27535
27647
  onViewableItemsChanged,
27536
27648
  viewabilityConfig: { itemVisiblePercentThreshold: 60 },
27537
- scrollEnabled: resolvedViewerScrollEnabled,
27649
+ scrollEnabled: pageScrubResolvedScrollEnabled,
27538
27650
  onLayout: () => {
27539
27651
  captureViewerFrame(listRef.current);
27540
27652
  restoreOrientationScrollOffset();
27653
+ scrollToPendingTarget(false);
27541
27654
  },
27542
27655
  onContentSizeChange: (_, height) => {
27543
27656
  viewerContentHeightRef.current = height;
27657
+ scrollToPendingTarget(false);
27544
27658
  },
27545
27659
  onScrollToIndexFailed: ({ index, averageItemLength }) => {
27546
27660
  const dataLength = isDouble ? rows.length : pages.length;
@@ -27762,25 +27876,73 @@ function NativeSheet({
27762
27876
  }) {
27763
27877
  const insets = usePapyrusSafeAreaInsets();
27764
27878
  const palette = getNativeSheetPalette(Boolean(isDark));
27879
+ const { height: windowHeight } = (0, import_react_native5.useWindowDimensions)();
27880
+ const [rendered, setRendered] = (0, import_react7.useState)(visible);
27881
+ const renderedRef = (0, import_react7.useRef)(visible);
27882
+ const motion = (0, import_react7.useRef)(new import_react_native5.Animated.Value(visible ? 0 : 1)).current;
27883
+ const backdropOpacity = (0, import_react7.useRef)(new import_react_native5.Animated.Value(visible ? 1 : 0)).current;
27884
+ (0, import_react7.useEffect)(() => {
27885
+ motion.stopAnimation();
27886
+ backdropOpacity.stopAnimation();
27887
+ if (visible) {
27888
+ renderedRef.current = true;
27889
+ setRendered(true);
27890
+ motion.setValue(1);
27891
+ backdropOpacity.setValue(0);
27892
+ import_react_native5.Animated.parallel([
27893
+ import_react_native5.Animated.spring(motion, {
27894
+ toValue: 0,
27895
+ damping: 22,
27896
+ stiffness: 220,
27897
+ mass: 0.8,
27898
+ useNativeDriver: true
27899
+ }),
27900
+ import_react_native5.Animated.timing(backdropOpacity, {
27901
+ toValue: 1,
27902
+ duration: 180,
27903
+ useNativeDriver: true
27904
+ })
27905
+ ]).start();
27906
+ return;
27907
+ }
27908
+ if (!renderedRef.current) return;
27909
+ import_react_native5.Animated.parallel([
27910
+ import_react_native5.Animated.timing(motion, {
27911
+ toValue: 1,
27912
+ duration: 180,
27913
+ useNativeDriver: true
27914
+ }),
27915
+ import_react_native5.Animated.timing(backdropOpacity, {
27916
+ toValue: 0,
27917
+ duration: 140,
27918
+ useNativeDriver: true
27919
+ })
27920
+ ]).start(({ finished }) => {
27921
+ if (!finished) return;
27922
+ renderedRef.current = false;
27923
+ setRendered(false);
27924
+ });
27925
+ }, [backdropOpacity, motion, visible]);
27926
+ if (!rendered) return null;
27765
27927
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27766
27928
  import_react_native5.Modal,
27767
27929
  {
27768
- animationType: "fade",
27930
+ animationType: "none",
27769
27931
  transparent: true,
27770
- visible,
27932
+ visible: rendered,
27771
27933
  onRequestClose: onClose,
27772
27934
  children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.root, children: [
27773
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27935
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.Animated.View, { style: [styles5.backdrop, { opacity: backdropOpacity }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27774
27936
  import_react_native5.Pressable,
27775
27937
  {
27776
27938
  accessibilityRole: "button",
27777
27939
  accessibilityLabel: closeAccessibilityLabel,
27778
- style: styles5.backdrop,
27940
+ style: import_react_native5.StyleSheet.absoluteFill,
27779
27941
  onPress: onClose
27780
27942
  }
27781
- ),
27943
+ ) }),
27782
27944
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
27783
- import_react_native5.View,
27945
+ import_react_native5.Animated.View,
27784
27946
  {
27785
27947
  style: [
27786
27948
  styles5.sheet,
@@ -27791,7 +27953,17 @@ function NativeSheet({
27791
27953
  backgroundColor: palette.backgroundColor,
27792
27954
  borderTopColor: palette.borderColor
27793
27955
  },
27794
- sheetStyle
27956
+ sheetStyle,
27957
+ {
27958
+ transform: [
27959
+ {
27960
+ translateY: motion.interpolate({
27961
+ inputRange: [0, 1],
27962
+ outputRange: [0, windowHeight]
27963
+ })
27964
+ }
27965
+ ]
27966
+ }
27795
27967
  ],
27796
27968
  children: [
27797
27969
  showHeader ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.header, children: [
@@ -29422,6 +29594,11 @@ var resolveRightSheetHeight = ({
29422
29594
  }) => showingNotes ? Math.min(440, windowHeight * 0.56) : Math.min(640, windowHeight * 0.72);
29423
29595
  var supportsPageThumbnails = (documentType) => documentType !== "text" && documentType !== "epub";
29424
29596
 
29597
+ // components/thumbnailClipping.ts
29598
+ var shouldRemoveThumbnailClipping = ({
29599
+ useNativePreview
29600
+ }) => !useNativePreview;
29601
+
29425
29602
  // components/RightSheet.tsx
29426
29603
  var import_jsx_runtime12 = require("react/jsx-runtime");
29427
29604
  var THUMBNAILS_INITIAL_NUM_TO_RENDER = 4;
@@ -29937,7 +30114,9 @@ var RightSheet = ({
29937
30114
  windowSize: THUMBNAILS_WINDOW_SIZE,
29938
30115
  maxToRenderPerBatch: THUMBNAILS_MAX_TO_RENDER_PER_BATCH,
29939
30116
  updateCellsBatchingPeriod: THUMBNAILS_UPDATE_CELLS_BATCHING_PERIOD,
29940
- removeClippedSubviews: true,
30117
+ removeClippedSubviews: shouldRemoveThumbnailClipping({
30118
+ useNativePreview
30119
+ }),
29941
30120
  viewabilityConfig: { itemVisiblePercentThreshold: 20 },
29942
30121
  onViewableItemsChanged: onThumbnailsViewableItemsChanged,
29943
30122
  renderItem: renderThumbnailItem
@@ -31884,22 +32063,14 @@ var styles16 = import_react_native16.StyleSheet.create({
31884
32063
  var import_react14 = require("react");
31885
32064
  var import_react_native17 = require("react-native");
31886
32065
  var import_core14 = require("@papyrus-sdk/core");
31887
-
31888
- // components/progressPillInteraction.ts
31889
- var getProgressPillInteraction = (onPress, onLongPress) => ({
31890
- onPress,
31891
- onLongPress,
31892
- accessibilityLabel: "Open document navigation",
31893
- accessibilityRole: "button"
31894
- });
31895
-
31896
- // components/ProgressPill.tsx
31897
32066
  var import_jsx_runtime20 = require("react/jsx-runtime");
31898
32067
  var clampPercent = (value) => Math.max(0, Math.min(100, Math.round(value)));
31899
32068
  function ProgressPill({
31900
32069
  documentType,
31901
32070
  onPress,
31902
- onOpenPageJump
32071
+ onOpenPageJump,
32072
+ onNavigateToPage,
32073
+ onScrubbingChange
31903
32074
  }) {
31904
32075
  const {
31905
32076
  currentPage,
@@ -31911,18 +32082,193 @@ function ProgressPill({
31911
32082
  } = (0, import_core14.useViewerStore)();
31912
32083
  const isDark = uiTheme === "dark";
31913
32084
  const offsets = resolveMobileChromeOffsets(usePapyrusSafeAreaInsets());
32085
+ const { height: windowHeight } = (0, import_react_native17.useWindowDimensions)();
32086
+ const trackHeight = Math.max(
32087
+ 180,
32088
+ Math.min(520, windowHeight - offsets.progress - offsets.bottom - 132)
32089
+ );
32090
+ const thumbHeight = 44;
32091
+ const trackYRef = (0, import_react14.useRef)(0);
32092
+ const scrubberRef = (0, import_react14.useRef)(null);
32093
+ const currentPageRef = (0, import_react14.useRef)(currentPage);
32094
+ currentPageRef.current = currentPage;
32095
+ const isScrubbingRef = (0, import_react14.useRef)(false);
32096
+ const hasMovedRef = (0, import_react14.useRef)(false);
32097
+ const longPressTriggeredRef = (0, import_react14.useRef)(false);
32098
+ const longPressTimerRef = (0, import_react14.useRef)(null);
32099
+ const pendingReleasedScrubPageRef = (0, import_react14.useRef)(null);
32100
+ const [scrubPreviewPage, setScrubPreviewPage] = (0, import_react14.useState)(null);
32101
+ const onNavigateToPageRef = (0, import_react14.useRef)(onNavigateToPage);
32102
+ const onPressRef = (0, import_react14.useRef)(onPress);
32103
+ const onOpenPageJumpRef = (0, import_react14.useRef)(onOpenPageJump);
32104
+ const onScrubbingChangeRef = (0, import_react14.useRef)(onScrubbingChange);
32105
+ onScrubbingChangeRef.current = onScrubbingChange;
32106
+ const scrubToPositionRef = (0, import_react14.useRef)(() => {
32107
+ });
32108
+ const scrubNavigationControllerRef = (0, import_react14.useRef)(
32109
+ createPageScrubberNavigationController((page) => {
32110
+ onNavigateToPageRef.current?.(page);
32111
+ })
32112
+ );
32113
+ const scrubberResponderRef = (0, import_react14.useRef)(null);
32114
+ const touchPolicy = resolvePageScrubberTouchPolicy();
32115
+ const displayedPage = scrubPreviewPage ?? currentPage;
31914
32116
  const label = (0, import_react14.useMemo)(() => {
31915
32117
  const total = Math.max(pageCount, 1);
31916
- const percent = clampPercent(currentPage / total * 100);
32118
+ const percent = clampPercent(displayedPage / total * 100);
31917
32119
  if (documentType === "pdf") {
31918
- return `${currentPage}/${pageCount || 0}`;
32120
+ return `${displayedPage}/${pageCount || 0}`;
31919
32121
  }
31920
32122
  if (documentType === "epub") {
31921
- return `Cap. ${currentPage} \xB7 ${percent}%`;
32123
+ return `Cap. ${displayedPage} \xB7 ${percent}%`;
31922
32124
  }
31923
32125
  return `${percent}%`;
31924
- }, [currentPage, documentType, pageCount]);
31925
- if (!mobileChromeVisible || !mobileProgressPillVisible) return null;
32126
+ }, [displayedPage, documentType, pageCount]);
32127
+ const thumbTop = resolvePageScrubberThumbTop({
32128
+ currentPage,
32129
+ trackHeight,
32130
+ thumbHeight,
32131
+ pageCount
32132
+ });
32133
+ const thumbTopRef = (0, import_react14.useRef)(new import_react_native17.Animated.Value(thumbTop));
32134
+ const thumbTopSnapshotRef = (0, import_react14.useRef)(thumbTop);
32135
+ thumbTopSnapshotRef.current = thumbTop;
32136
+ const scrubberMetricsRef = (0, import_react14.useRef)({ trackHeight, thumbHeight, pageCount });
32137
+ scrubberMetricsRef.current = { trackHeight, thumbHeight, pageCount };
32138
+ (0, import_react14.useEffect)(() => {
32139
+ onNavigateToPageRef.current = onNavigateToPage;
32140
+ }, [onNavigateToPage]);
32141
+ (0, import_react14.useEffect)(() => {
32142
+ onPressRef.current = onPress;
32143
+ onOpenPageJumpRef.current = onOpenPageJump;
32144
+ }, [onOpenPageJump, onPress]);
32145
+ (0, import_react14.useEffect)(() => {
32146
+ const pendingPage = pendingReleasedScrubPageRef.current;
32147
+ if (pendingPage !== null && currentPage === pendingPage) {
32148
+ pendingReleasedScrubPageRef.current = null;
32149
+ setScrubPreviewPage(null);
32150
+ }
32151
+ }, [currentPage]);
32152
+ (0, import_react14.useEffect)(() => {
32153
+ if (!isScrubbingRef.current) {
32154
+ thumbTopRef.current.setValue(thumbTop);
32155
+ }
32156
+ }, [thumbTop, thumbTopRef]);
32157
+ (0, import_react14.useEffect)(
32158
+ () => () => {
32159
+ if (longPressTimerRef.current !== null) {
32160
+ clearTimeout(longPressTimerRef.current);
32161
+ }
32162
+ },
32163
+ []
32164
+ );
32165
+ if (!shouldRenderPageScrubber({
32166
+ mobileChromeVisible,
32167
+ mobileProgressPillVisible,
32168
+ isScrubbing: isScrubbingRef.current
32169
+ })) {
32170
+ return null;
32171
+ }
32172
+ const scrubToPosition = (position) => {
32173
+ const { trackHeight: trackHeight2, thumbHeight: thumbHeight2, pageCount: pageCount2 } = scrubberMetricsRef.current;
32174
+ const visualPosition = resolvePageScrubberThumbTopFromPosition({
32175
+ position,
32176
+ trackHeight: trackHeight2,
32177
+ thumbHeight: thumbHeight2
32178
+ });
32179
+ thumbTopRef.current.setValue(visualPosition);
32180
+ const nextPage = resolvePageScrubberPage({
32181
+ position,
32182
+ trackHeight: trackHeight2,
32183
+ thumbHeight: thumbHeight2,
32184
+ pageCount: pageCount2
32185
+ });
32186
+ if (nextPage === null) return;
32187
+ scrubNavigationControllerRef.current.update(nextPage);
32188
+ setScrubPreviewPage(nextPage);
32189
+ };
32190
+ scrubToPositionRef.current = scrubToPosition;
32191
+ if (scrubberResponderRef.current === null) {
32192
+ scrubberResponderRef.current = import_react_native17.PanResponder.create({
32193
+ onStartShouldSetPanResponder: () => touchPolicy.claimOnStart,
32194
+ onStartShouldSetPanResponderCapture: () => touchPolicy.captureOnStart,
32195
+ onMoveShouldSetPanResponder: () => true,
32196
+ onMoveShouldSetPanResponderCapture: () => touchPolicy.captureOnMove,
32197
+ onPanResponderTerminationRequest: () => false,
32198
+ onPanResponderGrant: () => {
32199
+ isScrubbingRef.current = true;
32200
+ onScrubbingChangeRef.current?.(true);
32201
+ hasMovedRef.current = false;
32202
+ longPressTriggeredRef.current = false;
32203
+ scrubNavigationControllerRef.current.begin();
32204
+ pendingReleasedScrubPageRef.current = null;
32205
+ setScrubPreviewPage(currentPageRef.current);
32206
+ if (longPressTimerRef.current !== null) {
32207
+ clearTimeout(longPressTimerRef.current);
32208
+ }
32209
+ longPressTimerRef.current = onOpenPageJumpRef.current ? setTimeout(() => {
32210
+ longPressTimerRef.current = null;
32211
+ if (!hasMovedRef.current) {
32212
+ longPressTriggeredRef.current = true;
32213
+ onOpenPageJumpRef.current?.();
32214
+ }
32215
+ }, 500) : null;
32216
+ },
32217
+ onPanResponderMove: (_, gestureState) => {
32218
+ if (Math.abs(gestureState.dy) <= 4) return;
32219
+ hasMovedRef.current = true;
32220
+ if (longPressTimerRef.current !== null) {
32221
+ clearTimeout(longPressTimerRef.current);
32222
+ longPressTimerRef.current = null;
32223
+ }
32224
+ scrubToPositionRef.current(gestureState.moveY - trackYRef.current);
32225
+ },
32226
+ onPanResponderRelease: () => {
32227
+ if (longPressTimerRef.current !== null) {
32228
+ clearTimeout(longPressTimerRef.current);
32229
+ longPressTimerRef.current = null;
32230
+ }
32231
+ isScrubbingRef.current = false;
32232
+ onScrubbingChangeRef.current?.(false);
32233
+ const releasedPage = scrubNavigationControllerRef.current.release();
32234
+ const releaseAction = resolvePageScrubberReleaseAction({
32235
+ hasMoved: hasMovedRef.current,
32236
+ pendingPage: releasedPage
32237
+ });
32238
+ if (releaseAction.kind === "navigate") {
32239
+ pendingReleasedScrubPageRef.current = releaseAction.page;
32240
+ if (!onNavigateToPageRef.current) {
32241
+ setScrubPreviewPage(null);
32242
+ }
32243
+ } else {
32244
+ scrubNavigationControllerRef.current.cancel();
32245
+ pendingReleasedScrubPageRef.current = null;
32246
+ setScrubPreviewPage(null);
32247
+ if (!longPressTriggeredRef.current) onPressRef.current();
32248
+ }
32249
+ hasMovedRef.current = false;
32250
+ longPressTriggeredRef.current = false;
32251
+ },
32252
+ onPanResponderTerminate: () => {
32253
+ if (longPressTimerRef.current !== null) {
32254
+ clearTimeout(longPressTimerRef.current);
32255
+ longPressTimerRef.current = null;
32256
+ }
32257
+ isScrubbingRef.current = false;
32258
+ onScrubbingChangeRef.current?.(false);
32259
+ scrubNavigationControllerRef.current.cancel();
32260
+ pendingReleasedScrubPageRef.current = null;
32261
+ setScrubPreviewPage(null);
32262
+ hasMovedRef.current = false;
32263
+ longPressTriggeredRef.current = false;
32264
+ thumbTopRef.current.setValue(thumbTopSnapshotRef.current);
32265
+ }
32266
+ });
32267
+ }
32268
+ const responderPanHandlers = scrubberResponderRef.current?.panHandlers ?? {};
32269
+ const responderTarget = touchPolicy.responderTarget;
32270
+ const scrubberPanHandlers = responderTarget === "track" ? responderPanHandlers : {};
32271
+ const pillPanHandlers = responderTarget === "pill" ? responderPanHandlers : {};
31926
32272
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
31927
32273
  import_react_native17.View,
31928
32274
  {
@@ -31931,32 +32277,45 @@ function ProgressPill({
31931
32277
  styles17.frame,
31932
32278
  {
31933
32279
  top: offsets.progress,
31934
- left: 0,
31935
- right: 0,
31936
- paddingLeft: offsets.left + MOBILE_CHROME_METRICS.progressHorizontalOffset,
31937
- paddingRight: offsets.right
32280
+ right: offsets.right
31938
32281
  }
31939
32282
  ],
31940
32283
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
31941
- import_react_native17.Pressable,
32284
+ import_react_native17.View,
31942
32285
  {
31943
- ...getProgressPillInteraction(onPress, onOpenPageJump),
31944
- style: [
31945
- styles17.pill,
31946
- isDark && styles17.pillDark,
31947
- { borderColor: `${accentColor}33` }
31948
- ],
31949
- testID: "papyrus-progress-pill",
32286
+ ref: scrubberRef,
32287
+ style: [styles17.scrubber, { height: trackHeight }],
32288
+ ...scrubberPanHandlers,
32289
+ onLayout: () => {
32290
+ scrubberRef.current?.measureInWindow((_x, y) => {
32291
+ trackYRef.current = y;
32292
+ });
32293
+ },
31950
32294
  children: [
31951
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.View, { style: styles17.iconHit, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
31952
- IconPageNav,
32295
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.View, { style: [styles17.track, isDark && styles17.trackDark] }),
32296
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.Animated.View, { style: [styles17.thumb, { top: thumbTopRef.current }], children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
32297
+ import_react_native17.View,
31953
32298
  {
31954
- size: 20,
31955
- color: isDark ? "#f8fafc" : "#111827",
31956
- strokeWidth: 1.8
32299
+ style: [
32300
+ styles17.pill,
32301
+ isDark && styles17.pillDark,
32302
+ { borderColor: `${accentColor}33` }
32303
+ ],
32304
+ ...pillPanHandlers,
32305
+ testID: "papyrus-progress-pill",
32306
+ children: [
32307
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.View, { style: styles17.labelHit, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.Text, { style: [styles17.label, isDark && styles17.labelDark], children: label }) }),
32308
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.View, { style: styles17.iconHit, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
32309
+ IconPageNav,
32310
+ {
32311
+ size: 20,
32312
+ color: isDark ? "#f8fafc" : "#111827",
32313
+ strokeWidth: 1.8
32314
+ }
32315
+ ) })
32316
+ ]
31957
32317
  }
31958
- ) }),
31959
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.View, { style: styles17.labelHit, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.Text, { style: [styles17.label, isDark && styles17.labelDark], children: label }) })
32318
+ ) })
31960
32319
  ]
31961
32320
  }
31962
32321
  )
@@ -31970,9 +32329,31 @@ var styles17 = import_react_native17.StyleSheet.create({
31970
32329
  left: 0,
31971
32330
  right: 0,
31972
32331
  bottom: "auto",
31973
- alignItems: "flex-start",
32332
+ alignItems: "flex-end",
31974
32333
  zIndex: 18
31975
32334
  },
32335
+ scrubber: {
32336
+ width: 132,
32337
+ position: "relative",
32338
+ marginRight: 4
32339
+ },
32340
+ track: {
32341
+ position: "absolute",
32342
+ top: 0,
32343
+ right: 30,
32344
+ bottom: 0,
32345
+ width: 3,
32346
+ borderRadius: 2,
32347
+ backgroundColor: "rgba(15,23,42,0.16)"
32348
+ },
32349
+ trackDark: {
32350
+ backgroundColor: "rgba(248,250,252,0.22)"
32351
+ },
32352
+ thumb: {
32353
+ position: "absolute",
32354
+ right: 0,
32355
+ height: 44
32356
+ },
31976
32357
  pill: {
31977
32358
  minWidth: 92,
31978
32359
  borderRadius: 999,
@@ -32447,6 +32828,7 @@ function ReadingShell({
32447
32828
  const [settingsOpen, setSettingsOpen] = (0, import_react16.useState)(false);
32448
32829
  const [searchResultsOpen, setSearchResultsOpen] = (0, import_react16.useState)(false);
32449
32830
  const [pageJumpOpen, setPageJumpOpen] = (0, import_react16.useState)(false);
32831
+ const [pageScrubActive, setPageScrubActive] = (0, import_react16.useState)(false);
32450
32832
  const isDark = uiTheme === "dark";
32451
32833
  import_react16.default.useEffect(() => {
32452
32834
  const showSubscription = import_react_native20.Keyboard.addListener("keyboardDidShow", () => {
@@ -32523,15 +32905,24 @@ function ReadingShell({
32523
32905
  showPageNavigationControls: false
32524
32906
  }
32525
32907
  ),
32526
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native20.View, { style: styles20.viewerStage, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Viewer_default, { engine, ...viewerProps }) }),
32908
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native20.View, { style: styles20.viewerStage, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
32909
+ Viewer_default,
32910
+ {
32911
+ engine,
32912
+ ...viewerProps,
32913
+ pageScrubActive
32914
+ }
32915
+ ) }),
32527
32916
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
32528
32917
  ProgressPill,
32529
32918
  {
32530
32919
  documentType,
32920
+ onNavigateToPage: navigateToPage,
32531
32921
  onPress: () => openDestination(
32532
32922
  documentType === "pdf" ? "pages" : documentType === "epub" ? "contents" : "progress"
32533
32923
  ),
32534
- onOpenPageJump: () => setPageJumpOpen(true)
32924
+ onOpenPageJump: () => setPageJumpOpen(true),
32925
+ onScrubbingChange: setPageScrubActive
32535
32926
  }
32536
32927
  ),
32537
32928
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
@@ -32553,7 +32944,11 @@ function ReadingShell({
32553
32944
  setSearchResultsOpen(false);
32554
32945
  closeMobileDestination();
32555
32946
  },
32556
- onOpenResults: () => setSearchResultsOpen(true)
32947
+ onOpenResults: () => {
32948
+ import_react_native20.Keyboard.dismiss();
32949
+ setSearchResultsOpen(true);
32950
+ closeMobileDestination();
32951
+ }
32557
32952
  }
32558
32953
  ),
32559
32954
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(