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

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;
@@ -27422,7 +27521,7 @@ var Viewer = ({
27422
27521
  {
27423
27522
  ref: horizontalScrollRef,
27424
27523
  horizontal: true,
27425
- scrollEnabled: !gestureScrollLockActive && documentSurfaceWidth > windowWidth,
27524
+ scrollEnabled: !gestureScrollLockActive && !pageScrubActive && documentSurfaceWidth > windowWidth,
27426
27525
  showsHorizontalScrollIndicator: false,
27427
27526
  onScroll: (event) => {
27428
27527
  horizontalScrollOffsetRef.current = event.nativeEvent.contentOffset?.x ?? 0;
@@ -27438,7 +27537,7 @@ var Viewer = ({
27438
27537
  style: { width: documentSurfaceWidth },
27439
27538
  contentContainerStyle: styles4.singleContent,
27440
27539
  showsVerticalScrollIndicator: false,
27441
- scrollEnabled: resolvedViewerScrollEnabled,
27540
+ scrollEnabled: pageScrubResolvedScrollEnabled,
27442
27541
  onLayout: () => captureViewerFrame(listRef.current),
27443
27542
  onContentSizeChange: (_, height) => {
27444
27543
  viewerContentHeightRef.current = height;
@@ -27511,7 +27610,7 @@ var Viewer = ({
27511
27610
  {
27512
27611
  ref: horizontalScrollRef,
27513
27612
  horizontal: true,
27514
- scrollEnabled: !gestureScrollLockActive && documentSurfaceWidth > windowWidth,
27613
+ scrollEnabled: !gestureScrollLockActive && !pageScrubActive && documentSurfaceWidth > windowWidth,
27515
27614
  showsHorizontalScrollIndicator: false,
27516
27615
  onScroll: (event) => {
27517
27616
  horizontalScrollOffsetRef.current = event.nativeEvent.contentOffset?.x ?? 0;
@@ -27534,7 +27633,7 @@ var Viewer = ({
27534
27633
  renderItem,
27535
27634
  onViewableItemsChanged,
27536
27635
  viewabilityConfig: { itemVisiblePercentThreshold: 60 },
27537
- scrollEnabled: resolvedViewerScrollEnabled,
27636
+ scrollEnabled: pageScrubResolvedScrollEnabled,
27538
27637
  onLayout: () => {
27539
27638
  captureViewerFrame(listRef.current);
27540
27639
  restoreOrientationScrollOffset();
@@ -29422,6 +29521,11 @@ var resolveRightSheetHeight = ({
29422
29521
  }) => showingNotes ? Math.min(440, windowHeight * 0.56) : Math.min(640, windowHeight * 0.72);
29423
29522
  var supportsPageThumbnails = (documentType) => documentType !== "text" && documentType !== "epub";
29424
29523
 
29524
+ // components/thumbnailClipping.ts
29525
+ var shouldRemoveThumbnailClipping = ({
29526
+ useNativePreview
29527
+ }) => !useNativePreview;
29528
+
29425
29529
  // components/RightSheet.tsx
29426
29530
  var import_jsx_runtime12 = require("react/jsx-runtime");
29427
29531
  var THUMBNAILS_INITIAL_NUM_TO_RENDER = 4;
@@ -29937,7 +30041,9 @@ var RightSheet = ({
29937
30041
  windowSize: THUMBNAILS_WINDOW_SIZE,
29938
30042
  maxToRenderPerBatch: THUMBNAILS_MAX_TO_RENDER_PER_BATCH,
29939
30043
  updateCellsBatchingPeriod: THUMBNAILS_UPDATE_CELLS_BATCHING_PERIOD,
29940
- removeClippedSubviews: true,
30044
+ removeClippedSubviews: shouldRemoveThumbnailClipping({
30045
+ useNativePreview
30046
+ }),
29941
30047
  viewabilityConfig: { itemVisiblePercentThreshold: 20 },
29942
30048
  onViewableItemsChanged: onThumbnailsViewableItemsChanged,
29943
30049
  renderItem: renderThumbnailItem
@@ -31884,22 +31990,14 @@ var styles16 = import_react_native16.StyleSheet.create({
31884
31990
  var import_react14 = require("react");
31885
31991
  var import_react_native17 = require("react-native");
31886
31992
  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
31993
  var import_jsx_runtime20 = require("react/jsx-runtime");
31898
31994
  var clampPercent = (value) => Math.max(0, Math.min(100, Math.round(value)));
31899
31995
  function ProgressPill({
31900
31996
  documentType,
31901
31997
  onPress,
31902
- onOpenPageJump
31998
+ onOpenPageJump,
31999
+ onNavigateToPage,
32000
+ onScrubbingChange
31903
32001
  }) {
31904
32002
  const {
31905
32003
  currentPage,
@@ -31911,18 +32009,193 @@ function ProgressPill({
31911
32009
  } = (0, import_core14.useViewerStore)();
31912
32010
  const isDark = uiTheme === "dark";
31913
32011
  const offsets = resolveMobileChromeOffsets(usePapyrusSafeAreaInsets());
32012
+ const { height: windowHeight } = (0, import_react_native17.useWindowDimensions)();
32013
+ const trackHeight = Math.max(
32014
+ 180,
32015
+ Math.min(520, windowHeight - offsets.progress - offsets.bottom - 132)
32016
+ );
32017
+ const thumbHeight = 44;
32018
+ const trackYRef = (0, import_react14.useRef)(0);
32019
+ const scrubberRef = (0, import_react14.useRef)(null);
32020
+ const currentPageRef = (0, import_react14.useRef)(currentPage);
32021
+ currentPageRef.current = currentPage;
32022
+ const isScrubbingRef = (0, import_react14.useRef)(false);
32023
+ const hasMovedRef = (0, import_react14.useRef)(false);
32024
+ const longPressTriggeredRef = (0, import_react14.useRef)(false);
32025
+ const longPressTimerRef = (0, import_react14.useRef)(null);
32026
+ const pendingReleasedScrubPageRef = (0, import_react14.useRef)(null);
32027
+ const [scrubPreviewPage, setScrubPreviewPage] = (0, import_react14.useState)(null);
32028
+ const onNavigateToPageRef = (0, import_react14.useRef)(onNavigateToPage);
32029
+ const onPressRef = (0, import_react14.useRef)(onPress);
32030
+ const onOpenPageJumpRef = (0, import_react14.useRef)(onOpenPageJump);
32031
+ const onScrubbingChangeRef = (0, import_react14.useRef)(onScrubbingChange);
32032
+ onScrubbingChangeRef.current = onScrubbingChange;
32033
+ const scrubToPositionRef = (0, import_react14.useRef)(() => {
32034
+ });
32035
+ const scrubNavigationControllerRef = (0, import_react14.useRef)(
32036
+ createPageScrubberNavigationController((page) => {
32037
+ onNavigateToPageRef.current?.(page);
32038
+ })
32039
+ );
32040
+ const scrubberResponderRef = (0, import_react14.useRef)(null);
32041
+ const touchPolicy = resolvePageScrubberTouchPolicy();
32042
+ const displayedPage = scrubPreviewPage ?? currentPage;
31914
32043
  const label = (0, import_react14.useMemo)(() => {
31915
32044
  const total = Math.max(pageCount, 1);
31916
- const percent = clampPercent(currentPage / total * 100);
32045
+ const percent = clampPercent(displayedPage / total * 100);
31917
32046
  if (documentType === "pdf") {
31918
- return `${currentPage}/${pageCount || 0}`;
32047
+ return `${displayedPage}/${pageCount || 0}`;
31919
32048
  }
31920
32049
  if (documentType === "epub") {
31921
- return `Cap. ${currentPage} \xB7 ${percent}%`;
32050
+ return `Cap. ${displayedPage} \xB7 ${percent}%`;
31922
32051
  }
31923
32052
  return `${percent}%`;
31924
- }, [currentPage, documentType, pageCount]);
31925
- if (!mobileChromeVisible || !mobileProgressPillVisible) return null;
32053
+ }, [displayedPage, documentType, pageCount]);
32054
+ const thumbTop = resolvePageScrubberThumbTop({
32055
+ currentPage,
32056
+ trackHeight,
32057
+ thumbHeight,
32058
+ pageCount
32059
+ });
32060
+ const thumbTopRef = (0, import_react14.useRef)(new import_react_native17.Animated.Value(thumbTop));
32061
+ const thumbTopSnapshotRef = (0, import_react14.useRef)(thumbTop);
32062
+ thumbTopSnapshotRef.current = thumbTop;
32063
+ const scrubberMetricsRef = (0, import_react14.useRef)({ trackHeight, thumbHeight, pageCount });
32064
+ scrubberMetricsRef.current = { trackHeight, thumbHeight, pageCount };
32065
+ (0, import_react14.useEffect)(() => {
32066
+ onNavigateToPageRef.current = onNavigateToPage;
32067
+ }, [onNavigateToPage]);
32068
+ (0, import_react14.useEffect)(() => {
32069
+ onPressRef.current = onPress;
32070
+ onOpenPageJumpRef.current = onOpenPageJump;
32071
+ }, [onOpenPageJump, onPress]);
32072
+ (0, import_react14.useEffect)(() => {
32073
+ const pendingPage = pendingReleasedScrubPageRef.current;
32074
+ if (pendingPage !== null && currentPage === pendingPage) {
32075
+ pendingReleasedScrubPageRef.current = null;
32076
+ setScrubPreviewPage(null);
32077
+ }
32078
+ }, [currentPage]);
32079
+ (0, import_react14.useEffect)(() => {
32080
+ if (!isScrubbingRef.current) {
32081
+ thumbTopRef.current.setValue(thumbTop);
32082
+ }
32083
+ }, [thumbTop, thumbTopRef]);
32084
+ (0, import_react14.useEffect)(
32085
+ () => () => {
32086
+ if (longPressTimerRef.current !== null) {
32087
+ clearTimeout(longPressTimerRef.current);
32088
+ }
32089
+ },
32090
+ []
32091
+ );
32092
+ if (!shouldRenderPageScrubber({
32093
+ mobileChromeVisible,
32094
+ mobileProgressPillVisible,
32095
+ isScrubbing: isScrubbingRef.current
32096
+ })) {
32097
+ return null;
32098
+ }
32099
+ const scrubToPosition = (position) => {
32100
+ const { trackHeight: trackHeight2, thumbHeight: thumbHeight2, pageCount: pageCount2 } = scrubberMetricsRef.current;
32101
+ const visualPosition = resolvePageScrubberThumbTopFromPosition({
32102
+ position,
32103
+ trackHeight: trackHeight2,
32104
+ thumbHeight: thumbHeight2
32105
+ });
32106
+ thumbTopRef.current.setValue(visualPosition);
32107
+ const nextPage = resolvePageScrubberPage({
32108
+ position,
32109
+ trackHeight: trackHeight2,
32110
+ thumbHeight: thumbHeight2,
32111
+ pageCount: pageCount2
32112
+ });
32113
+ if (nextPage === null) return;
32114
+ scrubNavigationControllerRef.current.update(nextPage);
32115
+ setScrubPreviewPage(nextPage);
32116
+ };
32117
+ scrubToPositionRef.current = scrubToPosition;
32118
+ if (scrubberResponderRef.current === null) {
32119
+ scrubberResponderRef.current = import_react_native17.PanResponder.create({
32120
+ onStartShouldSetPanResponder: () => touchPolicy.claimOnStart,
32121
+ onStartShouldSetPanResponderCapture: () => touchPolicy.captureOnStart,
32122
+ onMoveShouldSetPanResponder: () => true,
32123
+ onMoveShouldSetPanResponderCapture: () => touchPolicy.captureOnMove,
32124
+ onPanResponderTerminationRequest: () => false,
32125
+ onPanResponderGrant: () => {
32126
+ isScrubbingRef.current = true;
32127
+ onScrubbingChangeRef.current?.(true);
32128
+ hasMovedRef.current = false;
32129
+ longPressTriggeredRef.current = false;
32130
+ scrubNavigationControllerRef.current.begin();
32131
+ pendingReleasedScrubPageRef.current = null;
32132
+ setScrubPreviewPage(currentPageRef.current);
32133
+ if (longPressTimerRef.current !== null) {
32134
+ clearTimeout(longPressTimerRef.current);
32135
+ }
32136
+ longPressTimerRef.current = onOpenPageJumpRef.current ? setTimeout(() => {
32137
+ longPressTimerRef.current = null;
32138
+ if (!hasMovedRef.current) {
32139
+ longPressTriggeredRef.current = true;
32140
+ onOpenPageJumpRef.current?.();
32141
+ }
32142
+ }, 500) : null;
32143
+ },
32144
+ onPanResponderMove: (_, gestureState) => {
32145
+ if (Math.abs(gestureState.dy) <= 4) return;
32146
+ hasMovedRef.current = true;
32147
+ if (longPressTimerRef.current !== null) {
32148
+ clearTimeout(longPressTimerRef.current);
32149
+ longPressTimerRef.current = null;
32150
+ }
32151
+ scrubToPositionRef.current(gestureState.moveY - trackYRef.current);
32152
+ },
32153
+ onPanResponderRelease: () => {
32154
+ if (longPressTimerRef.current !== null) {
32155
+ clearTimeout(longPressTimerRef.current);
32156
+ longPressTimerRef.current = null;
32157
+ }
32158
+ isScrubbingRef.current = false;
32159
+ onScrubbingChangeRef.current?.(false);
32160
+ const releasedPage = scrubNavigationControllerRef.current.release();
32161
+ const releaseAction = resolvePageScrubberReleaseAction({
32162
+ hasMoved: hasMovedRef.current,
32163
+ pendingPage: releasedPage
32164
+ });
32165
+ if (releaseAction.kind === "navigate") {
32166
+ pendingReleasedScrubPageRef.current = releaseAction.page;
32167
+ if (!onNavigateToPageRef.current) {
32168
+ setScrubPreviewPage(null);
32169
+ }
32170
+ } else {
32171
+ scrubNavigationControllerRef.current.cancel();
32172
+ pendingReleasedScrubPageRef.current = null;
32173
+ setScrubPreviewPage(null);
32174
+ if (!longPressTriggeredRef.current) onPressRef.current();
32175
+ }
32176
+ hasMovedRef.current = false;
32177
+ longPressTriggeredRef.current = false;
32178
+ },
32179
+ onPanResponderTerminate: () => {
32180
+ if (longPressTimerRef.current !== null) {
32181
+ clearTimeout(longPressTimerRef.current);
32182
+ longPressTimerRef.current = null;
32183
+ }
32184
+ isScrubbingRef.current = false;
32185
+ onScrubbingChangeRef.current?.(false);
32186
+ scrubNavigationControllerRef.current.cancel();
32187
+ pendingReleasedScrubPageRef.current = null;
32188
+ setScrubPreviewPage(null);
32189
+ hasMovedRef.current = false;
32190
+ longPressTriggeredRef.current = false;
32191
+ thumbTopRef.current.setValue(thumbTopSnapshotRef.current);
32192
+ }
32193
+ });
32194
+ }
32195
+ const responderPanHandlers = scrubberResponderRef.current?.panHandlers ?? {};
32196
+ const responderTarget = touchPolicy.responderTarget;
32197
+ const scrubberPanHandlers = responderTarget === "track" ? responderPanHandlers : {};
32198
+ const pillPanHandlers = responderTarget === "pill" ? responderPanHandlers : {};
31926
32199
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
31927
32200
  import_react_native17.View,
31928
32201
  {
@@ -31931,32 +32204,45 @@ function ProgressPill({
31931
32204
  styles17.frame,
31932
32205
  {
31933
32206
  top: offsets.progress,
31934
- left: 0,
31935
- right: 0,
31936
- paddingLeft: offsets.left + MOBILE_CHROME_METRICS.progressHorizontalOffset,
31937
- paddingRight: offsets.right
32207
+ right: offsets.right
31938
32208
  }
31939
32209
  ],
31940
32210
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
31941
- import_react_native17.Pressable,
32211
+ import_react_native17.View,
31942
32212
  {
31943
- ...getProgressPillInteraction(onPress, onOpenPageJump),
31944
- style: [
31945
- styles17.pill,
31946
- isDark && styles17.pillDark,
31947
- { borderColor: `${accentColor}33` }
31948
- ],
31949
- testID: "papyrus-progress-pill",
32213
+ ref: scrubberRef,
32214
+ style: [styles17.scrubber, { height: trackHeight }],
32215
+ ...scrubberPanHandlers,
32216
+ onLayout: () => {
32217
+ scrubberRef.current?.measureInWindow((_x, y) => {
32218
+ trackYRef.current = y;
32219
+ });
32220
+ },
31950
32221
  children: [
31951
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.View, { style: styles17.iconHit, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
31952
- IconPageNav,
32222
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.View, { style: [styles17.track, isDark && styles17.trackDark] }),
32223
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.Animated.View, { style: [styles17.thumb, { top: thumbTopRef.current }], children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
32224
+ import_react_native17.View,
31953
32225
  {
31954
- size: 20,
31955
- color: isDark ? "#f8fafc" : "#111827",
31956
- strokeWidth: 1.8
32226
+ style: [
32227
+ styles17.pill,
32228
+ isDark && styles17.pillDark,
32229
+ { borderColor: `${accentColor}33` }
32230
+ ],
32231
+ ...pillPanHandlers,
32232
+ testID: "papyrus-progress-pill",
32233
+ children: [
32234
+ /* @__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 }) }),
32235
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native17.View, { style: styles17.iconHit, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
32236
+ IconPageNav,
32237
+ {
32238
+ size: 20,
32239
+ color: isDark ? "#f8fafc" : "#111827",
32240
+ strokeWidth: 1.8
32241
+ }
32242
+ ) })
32243
+ ]
31957
32244
  }
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 }) })
32245
+ ) })
31960
32246
  ]
31961
32247
  }
31962
32248
  )
@@ -31970,9 +32256,31 @@ var styles17 = import_react_native17.StyleSheet.create({
31970
32256
  left: 0,
31971
32257
  right: 0,
31972
32258
  bottom: "auto",
31973
- alignItems: "flex-start",
32259
+ alignItems: "flex-end",
31974
32260
  zIndex: 18
31975
32261
  },
32262
+ scrubber: {
32263
+ width: 132,
32264
+ position: "relative",
32265
+ marginRight: 4
32266
+ },
32267
+ track: {
32268
+ position: "absolute",
32269
+ top: 0,
32270
+ right: 30,
32271
+ bottom: 0,
32272
+ width: 3,
32273
+ borderRadius: 2,
32274
+ backgroundColor: "rgba(15,23,42,0.16)"
32275
+ },
32276
+ trackDark: {
32277
+ backgroundColor: "rgba(248,250,252,0.22)"
32278
+ },
32279
+ thumb: {
32280
+ position: "absolute",
32281
+ right: 0,
32282
+ height: 44
32283
+ },
31976
32284
  pill: {
31977
32285
  minWidth: 92,
31978
32286
  borderRadius: 999,
@@ -32447,6 +32755,7 @@ function ReadingShell({
32447
32755
  const [settingsOpen, setSettingsOpen] = (0, import_react16.useState)(false);
32448
32756
  const [searchResultsOpen, setSearchResultsOpen] = (0, import_react16.useState)(false);
32449
32757
  const [pageJumpOpen, setPageJumpOpen] = (0, import_react16.useState)(false);
32758
+ const [pageScrubActive, setPageScrubActive] = (0, import_react16.useState)(false);
32450
32759
  const isDark = uiTheme === "dark";
32451
32760
  import_react16.default.useEffect(() => {
32452
32761
  const showSubscription = import_react_native20.Keyboard.addListener("keyboardDidShow", () => {
@@ -32523,15 +32832,24 @@ function ReadingShell({
32523
32832
  showPageNavigationControls: false
32524
32833
  }
32525
32834
  ),
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 }) }),
32835
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native20.View, { style: styles20.viewerStage, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
32836
+ Viewer_default,
32837
+ {
32838
+ engine,
32839
+ ...viewerProps,
32840
+ pageScrubActive
32841
+ }
32842
+ ) }),
32527
32843
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
32528
32844
  ProgressPill,
32529
32845
  {
32530
32846
  documentType,
32847
+ onNavigateToPage: navigateToPage,
32531
32848
  onPress: () => openDestination(
32532
32849
  documentType === "pdf" ? "pages" : documentType === "epub" ? "contents" : "progress"
32533
32850
  ),
32534
- onOpenPageJump: () => setPageJumpOpen(true)
32851
+ onOpenPageJump: () => setPageJumpOpen(true),
32852
+ onScrubbingChange: setPageScrubActive
32535
32853
  }
32536
32854
  ),
32537
32855
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(