@papyrus-sdk/ui-react-native 0.2.35-beta.9 → 0.2.35

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
@@ -20517,6 +20517,67 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20517
20517
  <!-- @papyrus-comic-runtime:end -->
20518
20518
  <script>
20519
20519
  (function () {
20520
+ /* @papyrus-epub-reader:start */
20521
+ (function(root){
20522
+ function mode(value){return value==='chapter-scroll'?'chapter-scroll':'paged';}
20523
+ function validCfi(value){return typeof value==='string' && value.length<=4096 && /^epubcfi\\([^\\n<>]+\\)$/.test(value);}
20524
+ function options(value){return {manager:'default',flow:mode(value)==='paged'?'paginated':'scrolled-doc',spread:'none',width:'100%',height:'100%',allowScriptedContent:false};}
20525
+ function gesture(start,end,state){
20526
+ if(!start || !end || state.selected || state.interactive || state.multitouch)return null;
20527
+ var dx=end.x-start.x,dy=end.y-start.y,elapsed=end.time-start.time;
20528
+ if(state.mode==='paged' && Math.abs(dx)>=48 && Math.abs(dx)>Math.abs(dy)*1.5 && elapsed>=0 && elapsed<1500)return (dx<0)!==Boolean(state.rtl)?'next':'prev';
20529
+ if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
20530
+ return null;
20531
+ }
20532
+ function location(raw,count,readingMode,geometry){
20533
+ var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
20534
+ var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
20535
+ var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
20536
+ var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
20537
+ var atStart=Boolean(raw.atStart),atEnd=Boolean(raw.atEnd);
20538
+ // Chromium may round a column offset down by a fraction of a CSS pixel.
20539
+ // Keep epub.js authoritative outside a 1px boundary, and for RTL layouts.
20540
+ if(visualPage!==null && total>0 && geometry && geometry.direction==='ltr' && geometry.pageWidth>0 && Number.isFinite(geometry.offset)){
20541
+ var boundary=Math.round(geometry.offset/geometry.pageWidth);
20542
+ if(Math.abs(geometry.offset-boundary*geometry.pageWidth)<=1){
20543
+ visualPage=Math.min(total,Math.max(1,boundary+1));
20544
+ atStart=chapter===(geometry.firstIndex ?? 0)+1 && visualPage===1;
20545
+ atEnd=chapter===(geometry.lastIndex ?? count-1)+1 && visualPage===total;
20546
+ }
20547
+ }
20548
+ var fraction=total?Math.max(0,(visualPage-1)/total):0;
20549
+ return {version:1,cfi:start.cfi,href:start.href||'',chapter:chapter,chapterCount:count,visualPage:visualPage,visualPageCount:total,progress:Math.min(1,Math.max(0,(chapter-1+fraction)/Math.max(1,count))),mode:mode(readingMode),atStart:atStart,atEnd:atEnd};
20550
+ }
20551
+ function installResizeAnchor(manager,readCfi){
20552
+ var original=manager.resize,anchor=null;
20553
+ function resize(width,height,target){
20554
+ if(validCfi(target))anchor=target;
20555
+ if(!anchor){var current=readCfi();if(validCfi(current))anchor=current;}
20556
+ // epub.js accepts a logical target as its third argument. Keep it across
20557
+ // keyboard/dialog reflows instead of adopting each new viewport start.
20558
+ return original.call(this,width,height,anchor || target);
20559
+ }
20560
+ manager.resize=resize;
20561
+ return {cfi:function(){return anchor;},invalidate:function(){anchor=null;},destroy:function(){if(manager.resize===resize)manager.resize=original;anchor=null;}};
20562
+ }
20563
+ function install(contents,config){
20564
+ var doc=contents.document,win=contents.window,start=null,blocked=false;
20565
+ function point(e){var t=e.changedTouches&&e.changedTouches[0]||e.touches&&e.touches[0];return t?{x:t.clientX,y:t.clientY,time:Date.now()}:null;}
20566
+ function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
20567
+ function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
20568
+ function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
20569
+ function invalidateScroll(finish){if(config.current() && start && finish && config.mode()==='chapter-scroll' && !blocked && !selected() && Math.abs(finish.y-start.y)>12 && Math.abs(finish.y-start.y)>Math.abs(finish.x-start.x) && config.interact)config.interact();}
20570
+ function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
20571
+ function end(e){var finish=point(e);invalidateScroll(finish);var action=gesture(start,finish,{mode:config.mode(),rtl:config.rtl(),selected:selected(),interactive:blocked||interactive(e.target)||Boolean(config.annotationHit && config.annotationHit(point(e))),multitouch:blocked});start=null;if(!config.current())return;if(action==='tap')config.tap();else if(action)config.turn(action==='next'?1:-1);}
20572
+ function cancel(){start=null;blocked=true;}
20573
+ function link(e){if(config.current() && e.target?.closest?.('a') && config.interact)config.interact();}
20574
+ doc.addEventListener('touchstart',begin,{passive:true});doc.addEventListener('touchmove',move,{passive:true});doc.addEventListener('touchend',end,{passive:true});doc.addEventListener('touchcancel',cancel,{passive:true});doc.addEventListener('click',link);
20575
+ return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);doc.removeEventListener('click',link);};
20576
+ }
20577
+ root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,installResizeAnchor:installResizeAnchor,install:install};
20578
+ })(typeof window==='undefined'?globalThis:window);
20579
+
20580
+ /* @papyrus-epub-reader:end */
20520
20581
  /* @papyrus-epub-annotations:start */
20521
20582
  (function (root, factory) {
20522
20583
  if (typeof module === 'object' && module.exports) module.exports = factory();
@@ -20595,6 +20656,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20595
20656
  var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
20596
20657
  var style = annotation.markupStyle || annotation.type;
20597
20658
  var opacity = Number.isFinite(annotation.opacity) ? Math.max(0,Math.min(1,annotation.opacity)) : 0.35;
20659
+ if(style === 'highlight')opacity=Math.min(0.35,opacity);
20598
20660
  rects.forEach(function(rect) {
20599
20661
  var mark = doc.createElement('div');
20600
20662
  var bodyRect=doc.body.getBoundingClientRect();
@@ -20607,8 +20669,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20607
20669
  if (hasNote) {
20608
20670
  var last = rects[rects.length-1], button = doc.createElement('button');
20609
20671
  button.textContent = '\u25CF'; button.setAttribute('aria-label',options.labels().comment || '');
20610
- button.style.cssText = 'position:absolute;pointer-events:auto;border:0;background:transparent;color:'+color+';width:44px;height:44px;left:'+Math.max(0,Math.min(doc.documentElement.clientWidth-44,last.right-doc.body.getBoundingClientRect().left))+'px;top:'+(Math.floor(last.top/44)*44-doc.body.getBoundingClientRect().top)+'px';
20611
- var key = Math.floor(last.top/44);
20672
+ var pageWidth=Math.max(44, options.pageWidth ? options.pageWidth() : win.innerWidth);
20673
+ var bodyRect=doc.body.getBoundingClientRect(), column=Math.max(0,Math.floor((last.left-bodyRect.left)/pageWidth));
20674
+ button.style.cssText = 'position:absolute;pointer-events:auto;border:0;background:transparent;color:'+color+';width:44px;height:44px;left:'+((column+1)*pageWidth-44)+'px;top:'+(Math.floor(last.top/44)*44-doc.body.getBoundingClientRect().top)+'px';
20675
+ var key = column+":"+Math.floor(last.top/44);
20612
20676
  var neighbors=noteGroups.get(key);
20613
20677
  if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
20614
20678
  neighbors.ids.push(annotation.id);
@@ -20629,10 +20693,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20629
20693
  doc.addEventListener('click',tapMarkup);
20630
20694
  var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
20631
20695
  if (observer) observer.observe(doc.body);
20632
- win.addEventListener('resize',schedule); doc.addEventListener('load',schedule,true);
20696
+ win.addEventListener('resize',schedule);win.addEventListener('scroll',schedule); doc.addEventListener('load',schedule,true);
20633
20697
  if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
20634
20698
  schedule();
20635
- return {schedule:schedule, range:function(anchor){return resolveRange(contents,anchor);}, destroy:function(){alive=false;if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',schedule);doc.removeEventListener('load',schedule,true);doc.removeEventListener('click',tapMarkup);overlay.remove();doc.body.style.position=originalPosition;registry.clear();}};
20699
+ return {schedule:schedule, hit:function(x,y){return hitRegions.some(function(item){return item.rects.some(function(r){return x>=r.left && x<=r.right && y>=r.top && y<=r.bottom;});});}, range:function(anchor){return resolveRange(contents,anchor);}, destroy:function(){alive=false;if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',schedule);win.removeEventListener('scroll',schedule);doc.removeEventListener('load',schedule,true);doc.removeEventListener('click',tapMarkup);overlay.remove();doc.body.style.position=originalPosition;registry.clear();}};
20636
20700
  }
20637
20701
  return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20638
20702
  });
@@ -20645,6 +20709,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20645
20709
  let currentType = null;
20646
20710
  let book = null;
20647
20711
  let rendition = null;
20712
+ let epubResizeAnchor = null;
20648
20713
  let documentSessionId = null;
20649
20714
  let annotationDocumentId;
20650
20715
  let annotationLabels = {};
@@ -21057,6 +21122,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21057
21122
  };
21058
21123
 
21059
21124
  const clearViewer = () => {
21125
+ if(epubResizeAnchor){epubResizeAnchor.destroy();epubResizeAnchor=null;}
21060
21126
  epubAnnotationRenderers.forEach(renderer => renderer.destroy());
21061
21127
  epubAnnotationRenderers.clear();
21062
21128
  if (epubInteractionCleanup) {
@@ -21713,26 +21779,57 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21713
21779
  pageCount,
21714
21780
  });
21715
21781
 
21782
+ if (book.locations?.generate) {
21783
+ const activeBook=book;
21784
+ activeBook.locations.generate(150).then(() => {
21785
+ if (isCurrentLoad() && activeBook===book && epubLocation) {
21786
+ epubLocation.progress=activeBook.locations.percentageFromCfi(epubLocation.cfi);
21787
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
21788
+ }
21789
+ }).catch(() => {});
21790
+ }
21791
+ return renderEpubRendition(loadId,generation,null);
21792
+ };
21793
+
21794
+ const renderEpubRendition = async (loadId,generation,target) => {
21795
+ const diagnosticPayload={loadId};
21796
+ const isCurrentLoad=()=>generation===epubLoadGeneration;
21797
+ if(epubInteractionCleanup)epubInteractionCleanup();
21798
+ if(rendition)rendition.destroy();
21716
21799
  clearViewer();
21717
- rendition = book.renderTo(viewer, {
21718
- manager: 'continuous',
21719
- allowScriptedContent: false,
21720
- width: '100%',
21721
- height: '100%',
21722
- flow: 'scrolled-continuous',
21723
- spread: 'none',
21724
- });
21800
+ rendition = book.renderTo(viewer, window.PapyrusEpubReader.options(epubViewMode === 'single' ? 'paged' : 'chapter-scroll'));
21725
21801
  sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
21726
21802
 
21727
21803
  if (rendition && rendition.hooks && rendition.hooks.content) {
21728
21804
  const session = documentSessionId;
21729
21805
  const activeRendition = rendition;
21730
21806
  rendition.hooks.content.register((contents) => {
21807
+ const removeGestureListeners = window.PapyrusEpubReader.install(contents, {
21808
+ mode: () => epubViewMode === 'single' ? 'paged' : 'chapter-scroll',
21809
+ rtl: () => book.package?.metadata?.direction === 'rtl' || book.spine?.direction === 'rtl',
21810
+ blocked: () => Boolean(nativeMenuSelection && String(nativeMenuSelection.contents?.window?.getSelection() || '').trim()),
21811
+ annotationHit: point => epubAnnotationRenderers.get(contents)?.hit(point.x,point.y),
21812
+ current: () => session === documentSessionId && activeRendition === rendition,
21813
+ interact: () => epubResizeAnchor?.invalidate(),
21814
+ tap: () => sendEvent('EPUB_CONTENT_TAP',{documentSessionId:session}),
21815
+ turn: direction => {
21816
+ epubNavigationQueue = epubNavigationQueue.catch(()=>{}).then(() => {
21817
+ if(session !== documentSessionId || activeRendition !== rendition)return;
21818
+ epubResizeAnchor?.invalidate();
21819
+ return direction<0?activeRendition.prev():activeRendition.next();
21820
+ });
21821
+ epubNavigationQueue.catch(error => sendEvent('EPUB_NAVIGATION_ERROR',{message:String(error)}));
21822
+ },
21823
+ });
21824
+ const gestureCleanup = () => {removeGestureListeners();epubSelectionCleanups.delete(gestureCleanup);};
21825
+ epubSelectionCleanups.add(gestureCleanup);
21826
+ contents.__papyrusGestureCleanup=gestureCleanup;
21731
21827
  const href = spineItems[contents.sectionIndex]?.href;
21732
21828
  if (href && window.PapyrusEpubAnnotations) {
21733
21829
  const renderer = window.PapyrusEpubAnnotations.install(contents, {
21734
21830
  href, get documentId() { return annotationDocumentId; },
21735
21831
  isCurrent: () => session === documentSessionId && activeRendition === rendition,
21832
+ pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
21736
21833
  annotations: () => epubAnnotations, labels: () => annotationLabels,
21737
21834
  onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
21738
21835
  });
@@ -21751,8 +21848,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21751
21848
  typeof selectionWindow.getSelection === 'function'
21752
21849
  ) {
21753
21850
  let selectionTimer = null;
21754
- let lastSelectionKey = '';
21755
- let lastSelectionAt = 0;
21851
+
21756
21852
  const reportSelection = () => {
21757
21853
  selectionTimer = null;
21758
21854
  if (session !== documentSessionId || activeRendition !== rendition) return;
@@ -21767,11 +21863,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21767
21863
  try { cfi = contents.cfiFromRange(range); } catch (_) { return; }
21768
21864
  const anchor = window.PapyrusEpubAnnotations?.selection(contents, cfi, href, annotationDocumentId);
21769
21865
  if (!anchor) return;
21770
- const key = anchor.cfiRange + ':' + anchor.quote;
21771
- const now = Date.now();
21772
- if (key === lastSelectionKey && now - lastSelectionAt < 750) return;
21773
- lastSelectionKey = key;
21774
- lastSelectionAt = now;
21866
+ if (nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21775
21867
  const payload = {documentSessionId: session, text: anchor.quote,
21776
21868
  pageIndex: contents.sectionIndex + 1, anchor};
21777
21869
  nativeMenuSelection = {contents, payload};
@@ -21786,6 +21878,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21786
21878
  // The content document still emits selectionchange, so resolve its
21787
21879
  // live Range to a CFI before the native menu action can fire.
21788
21880
  if (text) reportSelection();
21881
+ else if (nativeMenuSelection?.contents === contents) nativeMenuSelection = null;
21789
21882
  };
21790
21883
  selectionDocument.addEventListener('selectionchange', handleSelectionChange);
21791
21884
  const selectionCleanup = () => {
@@ -21808,6 +21901,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21808
21901
  typeof rendition.hooks.unloaded.register === 'function'
21809
21902
  ) {
21810
21903
  rendition.hooks.unloaded.register((view) => {
21904
+ view?.contents?.__papyrusGestureCleanup?.();
21811
21905
  const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
21812
21906
  if (typeof cleanup === 'function') cleanup();
21813
21907
  const renderer = epubAnnotationRenderers.get(view?.contents);
@@ -21823,21 +21917,37 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21823
21917
  if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21824
21918
  const href = spineItems[contents.sectionIndex]?.href;
21825
21919
  const anchor = href && window.PapyrusEpubAnnotations?.selection(contents,cfiRange,href,annotationDocumentId);
21826
- if (!anchor) return;
21920
+ if (!anchor || nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21827
21921
  epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(true);
21828
21922
  const payload = {documentSessionId: selectionSession, text: anchor.quote, pageIndex: contents.sectionIndex+1, anchor};
21829
21923
  nativeMenuSelection = {contents, payload};
21830
21924
  sendEvent('EPUB_TEXT_SELECTED', payload);
21831
21925
  });
21832
- ['rendered','resized','layout'].forEach(name => rendition.on(name, () => {
21926
+ ['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
21833
21927
  if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
21834
21928
  }));
21835
21929
  rendition.on('relocated', (location) => {
21930
+ if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21836
21931
  const start = location && location.start;
21837
21932
  let index = start && Number.isInteger(start.index) ? start.index : -1;
21838
21933
  if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
21839
21934
  if (index < 0 || index >= spineItems.length) return;
21840
21935
  epubScrollDiagnostics && epubScrollDiagnostics.relocated(location);
21936
+ let pageGeometry;
21937
+ const manager=rendition.manager,layout=manager?.layout;
21938
+ if(epubViewMode==='single' && layout?.divisor===1 && !manager?.settings?.fullsize && manager?.settings?.direction==='ltr'){
21939
+ const visible=manager.visible?.();
21940
+ const view=visible?.find(view=>view.index===index);
21941
+ if(view && manager.container){
21942
+ pageGeometry={direction:'ltr',firstIndex:book.spine.first()?.index,lastIndex:book.spine.last()?.index,pageWidth:layout.pageWidth,offset:manager.container.getBoundingClientRect().left-view.position().left};
21943
+ }
21944
+ }
21945
+ epubLocation=window.PapyrusEpubReader.location(location,spineItems.length,epubViewMode==='single'?'paged':'chapter-scroll',pageGeometry);
21946
+ if(epubLocation){
21947
+ epubLocation.cfi=epubResizeAnchor?.cfi() || epubLocation.cfi;
21948
+ if(book.locations?.length?.()>0)epubLocation.progress=book.locations.percentageFromCfi(epubLocation.cfi);
21949
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
21950
+ }
21841
21951
  const nextPage = index + 1;
21842
21952
  if (nextPage === currentPage) return;
21843
21953
  currentPage = nextPage;
@@ -21845,21 +21955,23 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21845
21955
  });
21846
21956
  }
21847
21957
 
21958
+ applyEpubZoom();
21848
21959
  sendEpubDiagnostic('epub.display.start', diagnosticPayload);
21849
21960
  await withEpubStageTimeout(
21850
- displayEpubPage(0),
21961
+ rendition.display(target || spineItems[0]?.href),
21851
21962
  'rendition.display',
21852
21963
  diagnosticPayload,
21853
21964
  );
21854
21965
  if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
21855
21966
  sendEpubDiagnostic('epub.display.end', diagnosticPayload);
21967
+ const resizeRendition=rendition;
21968
+ epubResizeAnchor=window.PapyrusEpubReader.installResizeAnchor(resizeRendition.manager,()=>resizeRendition.location?.start?.cfi);
21856
21969
  if (
21857
21970
  rendition &&
21858
21971
  rendition.manager &&
21859
21972
  typeof rendition.manager.on === 'function' &&
21860
21973
  typeof rendition.on === 'function'
21861
21974
  ) {
21862
- let touchStart = null;
21863
21975
  let lastScrollOffset = -1;
21864
21976
  const manager = rendition.manager;
21865
21977
  epubScrollDiagnostics = createEpubScrollDiagnostics(manager);
@@ -21934,48 +22046,19 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21934
22046
  return result;
21935
22047
  };
21936
22048
  }
21937
- const getTouchPoint = (event) => {
21938
- const touch =
21939
- (event && event.changedTouches && event.changedTouches[0]) ||
21940
- (event && event.touches && event.touches[0]);
21941
- if (!touch) return null;
21942
- const x = Number(touch.screenX ?? touch.clientX);
21943
- const y = Number(touch.screenY ?? touch.clientY);
21944
- return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
21945
- };
21946
- const handleScroll = (event) => {
22049
+ const handleScroll = event => {
21947
22050
  const offsetY = Math.max(0, Number(event && event.top) || 0);
21948
22051
  epubScrollDiagnostics && epubScrollDiagnostics.scroll(event);
21949
- if (Math.abs(offsetY - lastScrollOffset) < 1) return;
21950
- lastScrollOffset = offsetY;
21951
- sendEvent('VIEWER_SCROLL', { offsetY });
22052
+ if (Math.abs(offsetY-lastScrollOffset)<1) return;
22053
+ lastScrollOffset=offsetY;
22054
+ sendEvent('VIEWER_SCROLL',{offsetY});
21952
22055
  };
21953
- const handleTouchStart = (event) => {
21954
- touchStart = getTouchPoint(event);
21955
- epubScrollDiagnostics && epubScrollDiagnostics.touchStart(event);
21956
- };
21957
- const handleTouchMove = (event) => {
21958
- epubScrollDiagnostics && epubScrollDiagnostics.touchMove(event);
21959
- };
21960
- const handleTouchEnd = (event) => {
21961
- const end = getTouchPoint(event);
21962
- const start = touchStart;
21963
- touchStart = null;
21964
- const selectedText = event?.target?.ownerDocument?.defaultView?.getSelection?.()?.toString().trim() || '';
21965
- if (!selectedText && start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
21966
- sendEvent('VIEWER_TAP', {});
21967
- }
21968
- epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
21969
- };
21970
-
21971
22056
  manager.on('scroll', handleScroll);
21972
22057
  const handleScrolled = (event) => {
21973
22058
  epubScrollDiagnostics && epubScrollDiagnostics.settled(event);
21974
22059
  };
21975
22060
  manager.on('scrolled', handleScrolled);
21976
- rendition.on('touchstart', handleTouchStart);
21977
- rendition.on('touchmove', handleTouchMove);
21978
- rendition.on('touchend', handleTouchEnd);
22061
+
21979
22062
  epubInteractionCleanup = () => {
21980
22063
  epubSelectionCleanups.forEach((cleanup) => cleanup());
21981
22064
  epubSelectionCleanups.clear();
@@ -21984,16 +22067,9 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21984
22067
  if (originalEnqueue) queue.enqueue = originalEnqueue;
21985
22068
  if (typeof manager.off === 'function') manager.off('scroll', handleScroll);
21986
22069
  if (typeof manager.off === 'function') manager.off('scrolled', handleScrolled);
21987
- if (typeof rendition.off === 'function') {
21988
- rendition.off('touchstart', handleTouchStart);
21989
- rendition.off('touchmove', handleTouchMove);
21990
- rendition.off('touchend', handleTouchEnd);
21991
- }
21992
22070
  epubScrollDiagnostics = null;
21993
22071
  };
21994
22072
  }
21995
- applyEpubZoom();
21996
-
21997
22073
  const outline = await withEpubStageTimeout(
21998
22074
  buildOutline(),
21999
22075
  'outline',
@@ -22007,6 +22083,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22007
22083
  return { pageCount, outline };
22008
22084
  };
22009
22085
 
22086
+ let epubNavigationQueue = Promise.resolve();
22087
+ let epubNavigationRequest = 0;
22010
22088
  const displayEpubPage = async (pageIndex) => {
22011
22089
  if (!rendition) return;
22012
22090
  const item = spineItems[pageIndex];
@@ -22016,9 +22094,19 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22016
22094
  target,
22017
22095
  pageIndex,
22018
22096
  });
22019
- await rendition.display(target);
22020
- currentPage = pageIndex + 1;
22021
- sendState();
22097
+ const activeRendition = rendition;
22098
+ const session = documentSessionId;
22099
+ const request = ++epubNavigationRequest;
22100
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22101
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22102
+ epubResizeAnchor?.invalidate();
22103
+ await activeRendition.display(target);
22104
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22105
+ currentPage = pageIndex + 1;
22106
+ sendState();
22107
+ });
22108
+ epubNavigationQueue = navigation;
22109
+ await navigation;
22022
22110
  };
22023
22111
 
22024
22112
  const getTextContent = async (pageIndex) => {
@@ -22120,6 +22208,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22120
22208
  };
22121
22209
 
22122
22210
  let nativeMenuSelection = null;
22211
+ let epubViewMode = "single";
22212
+ let epubLocation = null;
22123
22213
 
22124
22214
  const handleCommand = async (message) => {
22125
22215
  const { id, kind, payload } = message;
@@ -22132,6 +22222,11 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22132
22222
  epubAnnotations = [];
22133
22223
  annotationDocumentId = undefined;
22134
22224
  zoom = 1.0;
22225
+ epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
22226
+ epubLocation = null;
22227
+ nativeMenuSelection = null;
22228
+ epubNavigationRequest += 1;
22229
+ epubNavigationQueue = Promise.resolve();
22135
22230
 
22136
22231
  if (currentType === 'text') {
22137
22232
  const result = await loadText(
@@ -22186,17 +22281,24 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22186
22281
  epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22187
22282
  } else {
22188
22283
  const annotation = epubAnnotations.find(a => a.id === payload.annotationId);
22189
- if (!annotation) throw new Error('Annotation not available');
22284
+ if (!annotation || (annotation.anchor.documentId && annotation.anchor.documentId !== annotationDocumentId)) throw new Error('Annotation not available for this document');
22190
22285
  // Navigation uses the CFI, never a synthetic percentage/page rectangle.
22191
22286
  const navigationSession = documentSessionId;
22192
22287
  const activeRendition = rendition;
22193
22288
  // Open the confirmed chapter first, resolve its quote, then navigate its actual range.
22194
- await activeRendition.display(annotation.anchor.href);
22195
- if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22196
- const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22197
- const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22198
- if(!range)throw new Error('EPUB anchor unresolved');
22199
- await activeRendition.display(contents.cfiFromRange(range));
22289
+ epubNavigationRequest += 1;
22290
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22291
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22292
+ epubResizeAnchor?.invalidate();
22293
+ await activeRendition.display(annotation.anchor.href);
22294
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22295
+ const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22296
+ const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22297
+ if(!range)throw new Error('EPUB anchor unresolved');
22298
+ await activeRendition.display(contents.cfiFromRange(range));
22299
+ });
22300
+ epubNavigationQueue = navigation;
22301
+ await navigation;
22200
22302
  }
22201
22303
  sendResponse(id,true,null); return;
22202
22304
  }
@@ -22223,12 +22325,55 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22223
22325
  return;
22224
22326
  }
22225
22327
 
22328
+ if (kind === 'epub-turn-page') {
22329
+ if (epubViewMode !== 'single') {sendResponse(id,true,null); return;}
22330
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB navigation');
22331
+ const active = rendition;
22332
+ epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
22333
+ if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
22334
+ epubResizeAnchor?.invalidate();
22335
+ return payload.direction < 0 ? active.prev() : active.next();
22336
+ });
22337
+ await epubNavigationQueue;
22338
+ sendResponse(id,true,null); return;
22339
+ }
22340
+
22341
+ if (kind === 'set-epub-view-mode') {
22342
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB layout session');
22343
+ const next=payload.epubMode ? (payload.epubMode==='paged'?'single':'continuous') : (payload.viewMode==='single'?'single':'continuous');
22344
+ {
22345
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22346
+ if(payload.documentSessionId!==documentSessionId || next===epubViewMode)return;
22347
+ const target=epubResizeAnchor?.cfi() || epubLocation?.cfi;
22348
+ epubViewMode=next;
22349
+ await renderEpubRendition('layout',epubLoadGeneration,target);
22350
+ });
22351
+ await epubNavigationQueue;
22352
+ }
22353
+ sendResponse(id,true,null);return;
22354
+ }
22355
+ if(kind === 'epub-go-to-location'){
22356
+ if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
22357
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
22358
+ await epubNavigationQueue;sendResponse(id,true,null);return;
22359
+ }
22360
+
22226
22361
  if (kind === 'set-zoom') {
22227
- zoom = Math.max(0.5, Math.min(4.0, payload.zoom || 1.0));
22228
- applyZoom();
22229
- sendState();
22230
- sendResponse(id, true, { zoom });
22231
- return;
22362
+ const requestedZoom=Math.max(0.5,Math.min(4.0,payload.zoom || 1.0));
22363
+ if(currentType === 'epub') {
22364
+ const session=documentSessionId;
22365
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22366
+ if(session!==documentSessionId)return;
22367
+ const active=rendition;
22368
+ const cfi=epubResizeAnchor?.cfi() || epubLocation?.cfi;
22369
+ zoom=requestedZoom;applyEpubZoom();
22370
+ // Let the content CSS update before redisplaying the logical anchor.
22371
+ await new Promise(resolve=>(window.requestAnimationFrame || (callback=>setTimeout(callback,0)))(resolve));
22372
+ if(cfi && active===rendition && session===documentSessionId)await active.display(cfi);
22373
+ });
22374
+ await epubNavigationQueue;
22375
+ } else {zoom=requestedZoom;applyZoom();}
22376
+ sendState();sendResponse(id,true,{zoom});return;
22232
22377
  }
22233
22378
 
22234
22379
  if (kind === 'set-rotation') {
@@ -23391,6 +23536,7 @@ var IconToolStrikeout = ({
23391
23536
  ),
23392
23537
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Rect, { x: "10.9", y: "17.1", width: "14.2", height: "2.4", rx: "1.2", fill: color })
23393
23538
  ] });
23539
+ var IconStrikeout = ({ size = 22, color = defaultColor, strokeWidth = 1.8 }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Path, { d: "M17 5c-1-1-3-2-5-2-3 0-5 2-5 4 0 2 2 3 5 4M17 15c0 2-2 4-5 4-2 0-4-1-5-2M4 12h16", stroke: color, strokeWidth, strokeLinecap: "round", strokeLinejoin: "round" }) });
23394
23540
  var IconCopy = ({
23395
23541
  size = 20,
23396
23542
  color = defaultColor,
@@ -23455,6 +23601,39 @@ var IconToolNote = ({
23455
23601
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Line, { x1: "15", y1: "14.8", x2: "21.6", y2: "14.8", stroke: color, strokeWidth: "1.8", strokeLinecap: "round" }),
23456
23602
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Line, { x1: "15", y1: "18.2", x2: "19.2", y2: "18.2", stroke: color, strokeWidth: "1.8", strokeLinecap: "round" })
23457
23603
  ] });
23604
+ var IconHighlight = ({
23605
+ size = 24,
23606
+ color = "#fbbf24"
23607
+ }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.default, { width: size, height: size, viewBox: "0 0 24 24", fill: "none", children: [
23608
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
23609
+ import_react_native_svg.Path,
23610
+ {
23611
+ d: "M4 15h16v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-3Z",
23612
+ fill: color,
23613
+ opacity: "0.85"
23614
+ }
23615
+ ),
23616
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
23617
+ import_react_native_svg.Path,
23618
+ {
23619
+ d: "M6 5h12v10H6V5Z",
23620
+ stroke: color,
23621
+ strokeWidth: "2",
23622
+ strokeLinecap: "round",
23623
+ strokeLinejoin: "round",
23624
+ fill: "none"
23625
+ }
23626
+ ),
23627
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
23628
+ import_react_native_svg.Path,
23629
+ {
23630
+ d: "M8 8h8M8 11h5",
23631
+ stroke: color,
23632
+ strokeWidth: "2",
23633
+ strokeLinecap: "round"
23634
+ }
23635
+ )
23636
+ ] });
23458
23637
  var IconUnderline = ({
23459
23638
  size = 24,
23460
23639
  color = "#60a5fa",
@@ -25883,8 +26062,38 @@ var styles = import_react_native.StyleSheet.create({
25883
26062
  var arePageRendererPropsEqual = (previous, next) => previous.documentId === next.documentId && previous.engine === next.engine && previous.pageIndex === next.pageIndex && previous.scale === next.scale && previous.PageViewComponent === next.PageViewComponent && previous.availableWidth === next.availableWidth && previous.maxPageWidth === next.maxPageWidth && previous.maxPageHeight === next.maxPageHeight && previous.pageViewportWidth === next.pageViewportWidth && previous.horizontalPadding === next.horizontalPadding && previous.spacing === next.spacing && previous.onSelectionDragActiveChange === next.onSelectionDragActiveChange && previous.gestureScrollLockActive === next.gestureScrollLockActive && previous.lastPinchEndedAt === next.lastPinchEndedAt && previous.requestSelectionVerticalAutoscroll === next.requestSelectionVerticalAutoscroll;
25884
26063
  var PageRenderer_default = (0, import_react2.memo)(PageRenderer, arePageRendererPropsEqual);
25885
26064
 
26065
+ // components/PapyrusSafeArea.tsx
26066
+ var import_react3 = __toESM(require("react"));
26067
+ var import_react_native_safe_area_context = require("react-native-safe-area-context");
26068
+ var import_jsx_runtime4 = require("react/jsx-runtime");
26069
+ var papyrusSafeAreaContext = import_react_native_safe_area_context.SafeAreaInsetsContext;
26070
+ var ZERO_SAFE_AREA_INSETS = {
26071
+ top: 0,
26072
+ bottom: 0,
26073
+ left: 0,
26074
+ right: 0
26075
+ };
26076
+ var usePapyrusSafeAreaInsets = () => (0, import_react3.useContext)(papyrusSafeAreaContext) ?? ZERO_SAFE_AREA_INSETS;
26077
+ function PapyrusSafeAreaBoundary({
26078
+ children
26079
+ }) {
26080
+ const insets = (0, import_react3.useContext)(papyrusSafeAreaContext);
26081
+ if (insets) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
26082
+ return import_react3.default.createElement(import_react_native_safe_area_context.SafeAreaProvider, null, children);
26083
+ }
26084
+
26085
+ // components/epubReadingLocation.ts
26086
+ function parseEpubReadingLocation(raw) {
26087
+ if (!raw || typeof raw !== "object") return null;
26088
+ const p = raw;
26089
+ if (p.version !== 1 || typeof p.cfi !== "string" || p.cfi.length > 4096 || !/^epubcfi\([^\n<>]+\)$/.test(p.cfi) || typeof p.href !== "string" || !Number.isSafeInteger(p.chapter) || p.chapter < 1 || !Number.isSafeInteger(p.chapterCount) || p.chapterCount < p.chapter || !Number.isFinite(p.progress) || p.progress < 0 || p.progress > 1 || typeof p.atStart !== "boolean" || typeof p.atEnd !== "boolean") return null;
26090
+ if (p.mode === "chapter-scroll") return p.visualPage === null && p.visualPageCount === null ? p : null;
26091
+ if (p.mode !== "paged" || !Number.isSafeInteger(p.visualPage) || (p.visualPage ?? 0) < 1 || !Number.isSafeInteger(p.visualPageCount) || (p.visualPageCount ?? 0) < (p.visualPage ?? 0)) return null;
26092
+ return p;
26093
+ }
26094
+
25886
26095
  // components/WebViewViewer.tsx
25887
- var import_react3 = require("react");
26096
+ var import_react4 = require("react");
25888
26097
  var import_react_native2 = require("react-native");
25889
26098
  var import_clipboard3 = __toESM(require("@react-native-clipboard/clipboard"));
25890
26099
  var import_react_native_webview = __toESM(require("react-native-webview"));
@@ -25952,7 +26161,7 @@ function parseEpubAnnotationEvent(raw, session, documentId) {
25952
26161
  }
25953
26162
 
25954
26163
  // components/WebViewViewer.tsx
25955
- var import_jsx_runtime4 = require("react/jsx-runtime");
26164
+ var import_jsx_runtime5 = require("react/jsx-runtime");
25956
26165
  var runtimeAsset = require_runtime();
25957
26166
  var resolveRuntimeSource = (asset) => {
25958
26167
  if (typeof asset === "string") {
@@ -25972,6 +26181,7 @@ var resolveRuntimeSource = (asset) => {
25972
26181
  };
25973
26182
  var WebViewViewer = ({
25974
26183
  engine,
26184
+ documentType,
25975
26185
  documentId,
25976
26186
  onDefineSelection,
25977
26187
  defineSelectionMode,
@@ -25979,19 +26189,20 @@ var WebViewViewer = ({
25979
26189
  onScrollOffset,
25980
26190
  onTap
25981
26191
  }) => {
25982
- const webViewRef = (0, import_react3.useRef)(null);
25983
- const { pageTheme, annotations, isLoaded, locale, annotationColor, annotationOpacity, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = (0, import_core4.useViewerStore)();
25984
- const [selection, setSelection] = (0, import_react3.useState)(null);
26192
+ const webViewRef = (0, import_react4.useRef)(null);
26193
+ const insets = usePapyrusSafeAreaInsets();
26194
+ const { pageTheme, epubReadingMode, setDocumentState, annotations, isLoaded, locale, annotationColor, annotationOpacity, addAnnotation, beginAnnotationDraft, setSelectedAnnotation } = (0, import_core4.useViewerStore)();
26195
+ const [selection, setSelection] = (0, import_react4.useState)(null);
25985
26196
  const t = getStrings(locale);
25986
26197
  const bridgeEngine = engine;
25987
- const runtimeSource = (0, import_react3.useMemo)(
26198
+ const runtimeSource = (0, import_react4.useMemo)(
25988
26199
  () => resolveRuntimeSource(
25989
26200
  bridgeEngine.getWebViewRuntimeSource?.() ?? runtimeAsset
25990
26201
  ),
25991
26202
  [bridgeEngine]
25992
26203
  );
25993
- const [runtimeHtml, setRuntimeHtml] = (0, import_react3.useState)(null);
25994
- (0, import_react3.useEffect)(() => {
26204
+ const [runtimeHtml, setRuntimeHtml] = (0, import_react4.useState)(null);
26205
+ (0, import_react4.useEffect)(() => {
25995
26206
  let active = true;
25996
26207
  if ("html" in runtimeSource) {
25997
26208
  setRuntimeHtml(runtimeSource.html);
@@ -26012,7 +26223,7 @@ var WebViewViewer = ({
26012
26223
  active = false;
26013
26224
  };
26014
26225
  }, [runtimeSource]);
26015
- const webViewSource = (0, import_react3.useMemo)(() => {
26226
+ const webViewSource = (0, import_react4.useMemo)(() => {
26016
26227
  if (runtimeHtml !== null) {
26017
26228
  return {
26018
26229
  html: runtimeHtml,
@@ -26023,13 +26234,13 @@ var WebViewViewer = ({
26023
26234
  return runtimeSource;
26024
26235
  }, [runtimeHtml, runtimeSource]);
26025
26236
  const runtimeConfig = bridgeEngine.getWebViewRuntimeConfig?.();
26026
- const runtimeConfigScript = (0, import_react3.useMemo)(() => {
26237
+ const runtimeConfigScript = (0, import_react4.useMemo)(() => {
26027
26238
  if (!runtimeConfig) return void 0;
26028
26239
  return `window.__PAPYRUS_RUNTIME_CONFIG__=${JSON.stringify(
26029
26240
  runtimeConfig
26030
26241
  )};true;`;
26031
26242
  }, [runtimeConfig]);
26032
- (0, import_react3.useEffect)(() => {
26243
+ (0, import_react4.useEffect)(() => {
26033
26244
  bridgeEngine.attachWebView?.({
26034
26245
  postMessage: (message) => {
26035
26246
  if (__DEV__) {
@@ -26043,10 +26254,10 @@ var WebViewViewer = ({
26043
26254
  bridgeEngine.detachWebView?.();
26044
26255
  };
26045
26256
  }, [bridgeEngine]);
26046
- (0, import_react3.useEffect)(() => {
26257
+ (0, import_react4.useEffect)(() => {
26047
26258
  setSelection(null);
26048
26259
  }, [engine, documentId]);
26049
- (0, import_react3.useEffect)(() => {
26260
+ (0, import_react4.useEffect)(() => {
26050
26261
  if (!isLoaded) return;
26051
26262
  let active = true;
26052
26263
  void bridgeEngine.syncEpubAnnotations?.(annotations, documentId, { comment: t.annotationNote }).catch(() => {
@@ -26061,6 +26272,10 @@ var WebViewViewer = ({
26061
26272
  (note !== void 0 ? beginAnnotationDraft : addAnnotation)((0, import_core4.createContextualAnnotation)({ id: `annotation-${Date.now()}-${Math.random().toString(36).slice(2)}`, anchor: selected.anchor, pageIndex: selected.pageIndex, style, color: annotationColor, opacity: annotationOpacity, note }));
26062
26273
  setSelection(null);
26063
26274
  };
26275
+ (0, import_react4.useEffect)(() => {
26276
+ if (!isLoaded) return;
26277
+ webViewRef.current?.postMessage(JSON.stringify({ id: "epub-layout", kind: "set-epub-view-mode", payload: { epubMode: epubReadingMode, documentSessionId: bridgeEngine.getWebViewDocumentSessionId?.() } }));
26278
+ }, [isLoaded, epubReadingMode, bridgeEngine]);
26064
26279
  const sendSelectionCommand = (key) => {
26065
26280
  webViewRef.current?.postMessage(JSON.stringify({
26066
26281
  type: "epub-selection-action",
@@ -26084,6 +26299,11 @@ var WebViewViewer = ({
26084
26299
  let annotationRaw = raw;
26085
26300
  try {
26086
26301
  const message = JSON.parse(raw);
26302
+ if (message.type === "event" && message.name === "EPUB_LOCATION" && message.payload?.documentSessionId === bridgeEngine.getWebViewDocumentSessionId?.()) {
26303
+ const location = parseEpubReadingLocation(message.payload.location);
26304
+ if (location) setDocumentState({ epubLocation: location });
26305
+ }
26306
+ if (message.type === "event" && message.name === "EPUB_CONTENT_TAP" && message.payload?.documentSessionId === bridgeEngine.getWebViewDocumentSessionId?.()) onTap?.();
26087
26307
  if (message.type === "event" && message.name === "EPUB_SELECTION_ACTION") {
26088
26308
  action = message.payload?.action;
26089
26309
  annotationRaw = JSON.stringify({ ...message, name: "EPUB_TEXT_SELECTED" });
@@ -26147,7 +26367,7 @@ var WebViewViewer = ({
26147
26367
  console.warn("[Papyrus WebView] error", event.nativeEvent);
26148
26368
  }
26149
26369
  };
26150
- const themeOverlayStyle = (0, import_react3.useMemo)(() => {
26370
+ const themeOverlayStyle = (0, import_react4.useMemo)(() => {
26151
26371
  switch (pageTheme) {
26152
26372
  case "sepia":
26153
26373
  return styles2.themeSepia;
@@ -26159,7 +26379,7 @@ var WebViewViewer = ({
26159
26379
  return styles2.themeNone;
26160
26380
  }
26161
26381
  }, [pageTheme]);
26162
- const allowingReadAccessToURL = (0, import_react3.useMemo)(() => {
26382
+ const allowingReadAccessToURL = (0, import_react4.useMemo)(() => {
26163
26383
  if (!runtimeSource || typeof runtimeSource !== "object") return void 0;
26164
26384
  if ("uri" in runtimeSource && runtimeSource.uri) {
26165
26385
  return runtimeSource.uri;
@@ -26172,8 +26392,8 @@ var WebViewViewer = ({
26172
26392
  maxWidth: resolvedMaxPageWidth,
26173
26393
  alignSelf: "center"
26174
26394
  } : void 0;
26175
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.container, children: [
26176
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
26395
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native2.View, { style: [styles2.container, documentType === "epub" && { paddingTop: insets.top, paddingBottom: insets.bottom }], children: [
26396
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
26177
26397
  import_react_native_webview.default,
26178
26398
  {
26179
26399
  ref: webViewRef,
@@ -26197,7 +26417,7 @@ var WebViewViewer = ({
26197
26417
  style: [styles2.webview, cappedWebViewStyle]
26198
26418
  }
26199
26419
  ),
26200
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
26420
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
26201
26421
  import_react_native2.View,
26202
26422
  {
26203
26423
  pointerEvents: "none",
@@ -26299,22 +26519,22 @@ function getNearbyPdfNotes(annotations, id) {
26299
26519
  // components/PdfNoteChooser.tsx
26300
26520
  var import_react_native3 = require("react-native");
26301
26521
  var import_core5 = require("@papyrus-sdk/core");
26302
- var import_jsx_runtime5 = require("react/jsx-runtime");
26522
+ var import_jsx_runtime6 = require("react/jsx-runtime");
26303
26523
  function PdfNoteChooser({ notes, title, cancel, onSelect, onClose }) {
26304
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Modal, { visible: notes.length > 1, transparent: true, animationType: "none", onRequestClose: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.View, { style: styles3.backdrop, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.sheet, accessibilityViewIsModal: true, children: [
26305
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Text, { style: styles3.title, children: title }),
26306
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.FlatList, { data: notes, keyExtractor: (a) => a.id, renderItem: ({ item, index }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Pressable, { accessibilityRole: "button", style: styles3.row, onPress: () => onSelect(item.id), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.Text, { children: [
26524
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Modal, { visible: notes.length > 1, transparent: true, animationType: "none", onRequestClose: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.View, { style: styles3.backdrop, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native3.View, { style: styles3.sheet, accessibilityViewIsModal: true, children: [
26525
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Text, { style: styles3.title, children: title }),
26526
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.FlatList, { data: notes, keyExtractor: (a) => a.id, renderItem: ({ item, index }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Pressable, { accessibilityRole: "button", style: styles3.row, onPress: () => onSelect(item.id), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native3.Text, { children: [
26307
26527
  index + 1,
26308
26528
  ". ",
26309
26529
  (0, import_core5.getAnnotationNote)(item) || (0, import_core5.getAnnotationQuote)(item)
26310
26530
  ] }) }) }),
26311
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Pressable, { accessibilityRole: "button", style: styles3.row, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.Text, { children: cancel }) })
26531
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Pressable, { accessibilityRole: "button", style: styles3.row, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native3.Text, { children: cancel }) })
26312
26532
  ] }) }) });
26313
26533
  }
26314
26534
  var styles3 = import_react_native3.StyleSheet.create({ backdrop: { flex: 1, backgroundColor: "#0008", justifyContent: "center", padding: 24 }, sheet: { backgroundColor: "#fff", borderRadius: 16, padding: 16, maxHeight: "75%" }, title: { fontSize: 18, fontWeight: "600", marginBottom: 12 }, row: { minHeight: 44, padding: 12 } });
26315
26535
 
26316
26536
  // components/DedicatedAndroidPdfViewer.tsx
26317
- var import_react4 = require("react");
26537
+ var import_react5 = require("react");
26318
26538
  var import_react_native4 = require("react-native");
26319
26539
  var import_core6 = require("@papyrus-sdk/core");
26320
26540
  var import_engine_native2 = require("@papyrus-sdk/engine-native");
@@ -26335,7 +26555,7 @@ var resolvePageTapChromeVisibility = ({
26335
26555
  };
26336
26556
 
26337
26557
  // components/DedicatedAndroidPdfViewer.tsx
26338
- var import_jsx_runtime6 = require("react/jsx-runtime");
26558
+ var import_jsx_runtime7 = require("react/jsx-runtime");
26339
26559
  var TEXT_MARKUP_TOOLS2 = /* @__PURE__ */ new Set(["highlight", "underline", "squiggly", "strikeout"]);
26340
26560
  var MOBILE_CHROME_HIDE_DELTA = 28;
26341
26561
  var MOBILE_CHROME_SHOW_DELTA = 22;
@@ -26364,7 +26584,7 @@ function DedicatedAndroidPdfViewer({
26364
26584
  const searchResults = (0, import_core6.useViewerStore)((state) => state.searchResults);
26365
26585
  const annotationNavigationRequest = (0, import_core6.useViewerStore)((state) => state.annotationNavigationRequest);
26366
26586
  const annotations = (0, import_core6.useViewerStore)((state) => state.annotations);
26367
- const [pendingNotes, setPendingNotes] = (0, import_react4.useState)([]);
26587
+ const [pendingNotes, setPendingNotes] = (0, import_react5.useState)([]);
26368
26588
  const handleAnnotationTap = (id) => {
26369
26589
  const nearby = getNearbyPdfNotes(annotations, id);
26370
26590
  if (nearby.length < 2) {
@@ -26392,30 +26612,30 @@ function DedicatedAndroidPdfViewer({
26392
26612
  maxWidth: resolvedMaxPageWidth,
26393
26613
  alignSelf: "center"
26394
26614
  } : void 0;
26395
- const [selection, setSelection] = (0, import_react4.useState)(null);
26396
- const selectionRef = (0, import_react4.useRef)(null);
26397
- const lastVisiblePagesKeyRef = (0, import_react4.useRef)("");
26398
- const lastScrollOffsetYRef = (0, import_react4.useRef)(0);
26399
- const scrollDownAccumRef = (0, import_react4.useRef)(0);
26400
- const scrollUpAccumRef = (0, import_react4.useRef)(0);
26401
- const pendingChromeShowTimeoutRef = (0, import_react4.useRef)(null);
26402
- const chromeVisibleRef = (0, import_react4.useRef)(true);
26403
- const lastZoomChangedAtRef = (0, import_react4.useRef)(null);
26404
- (0, import_react4.useEffect)(() => {
26615
+ const [selection, setSelection] = (0, import_react5.useState)(null);
26616
+ const selectionRef = (0, import_react5.useRef)(null);
26617
+ const lastVisiblePagesKeyRef = (0, import_react5.useRef)("");
26618
+ const lastScrollOffsetYRef = (0, import_react5.useRef)(0);
26619
+ const scrollDownAccumRef = (0, import_react5.useRef)(0);
26620
+ const scrollUpAccumRef = (0, import_react5.useRef)(0);
26621
+ const pendingChromeShowTimeoutRef = (0, import_react5.useRef)(null);
26622
+ const chromeVisibleRef = (0, import_react5.useRef)(true);
26623
+ const lastZoomChangedAtRef = (0, import_react5.useRef)(null);
26624
+ (0, import_react5.useEffect)(() => {
26405
26625
  chromeVisibleRef.current = mobileChromeVisible;
26406
26626
  }, [mobileChromeVisible]);
26407
- const clearPendingChromeShow = (0, import_react4.useCallback)(() => {
26627
+ const clearPendingChromeShow = (0, import_react5.useCallback)(() => {
26408
26628
  if (pendingChromeShowTimeoutRef.current) {
26409
26629
  clearTimeout(pendingChromeShowTimeoutRef.current);
26410
26630
  pendingChromeShowTimeoutRef.current = null;
26411
26631
  }
26412
26632
  }, []);
26413
- const setMobileChromeVisible = (0, import_react4.useCallback)((visible, reason) => {
26633
+ const setMobileChromeVisible = (0, import_react5.useCallback)((visible, reason) => {
26414
26634
  if (chromeVisibleRef.current === visible) return;
26415
26635
  chromeVisibleRef.current = visible;
26416
26636
  setDocumentState({ mobileChromeVisible: visible });
26417
26637
  }, [setDocumentState]);
26418
- const trackMobileChromeByOffset = (0, import_react4.useCallback)((offsetY, reasonPrefix) => {
26638
+ const trackMobileChromeByOffset = (0, import_react5.useCallback)((offsetY, reasonPrefix) => {
26419
26639
  const safeOffset = Math.max(0, offsetY);
26420
26640
  const delta = safeOffset - lastScrollOffsetYRef.current;
26421
26641
  lastScrollOffsetYRef.current = safeOffset;
@@ -26451,12 +26671,12 @@ function DedicatedAndroidPdfViewer({
26451
26671
  }
26452
26672
  }
26453
26673
  }, [clearPendingChromeShow, setMobileChromeVisible]);
26454
- (0, import_react4.useEffect)(() => {
26674
+ (0, import_react5.useEffect)(() => {
26455
26675
  return () => {
26456
26676
  clearPendingChromeShow();
26457
26677
  };
26458
26678
  }, [clearPendingChromeShow]);
26459
- const applySelection = (0, import_react4.useCallback)((type) => {
26679
+ const applySelection = (0, import_react5.useCallback)((type) => {
26460
26680
  const sel = selectionRef.current;
26461
26681
  if (!sel || !sel.rects || sel.rects.length === 0) return;
26462
26682
  const bounds = sel.rects.reduce((acc, r) => ({
@@ -26478,12 +26698,12 @@ function DedicatedAndroidPdfViewer({
26478
26698
  setSelection(null);
26479
26699
  selectionRef.current = null;
26480
26700
  }, [addAnnotation, beginAnnotationDraft, annotationColor, documentId]);
26481
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native4.View, { style: styles4.container, children: [
26482
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
26701
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: styles4.container, children: [
26702
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
26483
26703
  setPendingNotes([]);
26484
26704
  setSelectedAnnotation(id);
26485
26705
  } }),
26486
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
26706
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
26487
26707
  import_engine_native2.PapyrusPdfDocumentView,
26488
26708
  {
26489
26709
  style: [styles4.viewer, cappedViewerStyle],
@@ -26597,7 +26817,7 @@ var styles4 = import_react_native4.StyleSheet.create({
26597
26817
  });
26598
26818
 
26599
26819
  // components/DedicatedIosPdfViewer.tsx
26600
- var import_react5 = require("react");
26820
+ var import_react6 = require("react");
26601
26821
  var import_react_native5 = require("react-native");
26602
26822
  var import_core7 = require("@papyrus-sdk/core");
26603
26823
  var import_engine_native3 = require("@papyrus-sdk/engine-native");
@@ -26694,7 +26914,7 @@ var resolveNativePdfVisiblePages = (pages) => (pages ?? []).flatMap((page) => {
26694
26914
  });
26695
26915
 
26696
26916
  // components/DedicatedIosPdfViewer.tsx
26697
- var import_jsx_runtime7 = require("react/jsx-runtime");
26917
+ var import_jsx_runtime8 = require("react/jsx-runtime");
26698
26918
  var MOBILE_CHROME_HIDE_DELTA2 = 28;
26699
26919
  var MOBILE_CHROME_SHOW_DELTA2 = 22;
26700
26920
  var MOBILE_CHROME_SHOW_DELAY_MS2 = 180;
@@ -26726,7 +26946,7 @@ function DedicatedIosPdfViewer({
26726
26946
  const inkStrokeWidth = (0, import_core7.useViewerStore)((state) => state.inkStrokeWidth);
26727
26947
  const annotationNavigationRequest = (0, import_core7.useViewerStore)((state) => state.annotationNavigationRequest);
26728
26948
  const annotations = (0, import_core7.useViewerStore)((state) => state.annotations);
26729
- const [pendingNotes, setPendingNotes] = (0, import_react5.useState)([]);
26949
+ const [pendingNotes, setPendingNotes] = (0, import_react6.useState)([]);
26730
26950
  const handleAnnotationTap = (id) => {
26731
26951
  const nearby = getNearbyPdfNotes(annotations, id);
26732
26952
  if (nearby.length < 2) {
@@ -26762,16 +26982,16 @@ function DedicatedIosPdfViewer({
26762
26982
  supportsNativeEditMenu
26763
26983
  );
26764
26984
  const resolvedMaxPageWidth = typeof maxPageWidth === "number" && Number.isFinite(maxPageWidth) && maxPageWidth > 0 ? maxPageWidth : void 0;
26765
- const chromeVisibleRef = (0, import_react5.useRef)(mobileChromeVisible);
26766
- const lastScrollOffsetYRef = (0, import_react5.useRef)(0);
26767
- const scrollDownAccumRef = (0, import_react5.useRef)(0);
26768
- const scrollUpAccumRef = (0, import_react5.useRef)(0);
26769
- const pendingChromeShowTimeoutRef = (0, import_react5.useRef)(null);
26770
- const lastZoomChangedAtRef = (0, import_react5.useRef)(null);
26771
- const lastVisiblePagesKeyRef = (0, import_react5.useRef)("");
26772
- const [selection, setSelection] = (0, import_react5.useState)(null);
26773
- const selectionRef = (0, import_react5.useRef)(null);
26774
- (0, import_react5.useEffect)(() => {
26985
+ const chromeVisibleRef = (0, import_react6.useRef)(mobileChromeVisible);
26986
+ const lastScrollOffsetYRef = (0, import_react6.useRef)(0);
26987
+ const scrollDownAccumRef = (0, import_react6.useRef)(0);
26988
+ const scrollUpAccumRef = (0, import_react6.useRef)(0);
26989
+ const pendingChromeShowTimeoutRef = (0, import_react6.useRef)(null);
26990
+ const lastZoomChangedAtRef = (0, import_react6.useRef)(null);
26991
+ const lastVisiblePagesKeyRef = (0, import_react6.useRef)("");
26992
+ const [selection, setSelection] = (0, import_react6.useState)(null);
26993
+ const selectionRef = (0, import_react6.useRef)(null);
26994
+ (0, import_react6.useEffect)(() => {
26775
26995
  const pickerActive = supportsNativeEditMenu && activeTool === "ink";
26776
26996
  setDocumentState({ nativeInkToolPickerActive: pickerActive });
26777
26997
  return () => {
@@ -26780,15 +27000,15 @@ function DedicatedIosPdfViewer({
26780
27000
  }
26781
27001
  };
26782
27002
  }, [activeTool, setDocumentState]);
26783
- (0, import_react5.useEffect)(() => {
27003
+ (0, import_react6.useEffect)(() => {
26784
27004
  chromeVisibleRef.current = mobileChromeVisible;
26785
27005
  }, [mobileChromeVisible]);
26786
- const clearPendingChromeShow = (0, import_react5.useCallback)(() => {
27006
+ const clearPendingChromeShow = (0, import_react6.useCallback)(() => {
26787
27007
  if (!pendingChromeShowTimeoutRef.current) return;
26788
27008
  clearTimeout(pendingChromeShowTimeoutRef.current);
26789
27009
  pendingChromeShowTimeoutRef.current = null;
26790
27010
  }, []);
26791
- const setMobileChromeVisible = (0, import_react5.useCallback)(
27011
+ const setMobileChromeVisible = (0, import_react6.useCallback)(
26792
27012
  (visible) => {
26793
27013
  if (chromeVisibleRef.current === visible) return;
26794
27014
  chromeVisibleRef.current = visible;
@@ -26796,14 +27016,14 @@ function DedicatedIosPdfViewer({
26796
27016
  },
26797
27017
  [setDocumentState]
26798
27018
  );
26799
- const updateSelection = (0, import_react5.useCallback)(
27019
+ const updateSelection = (0, import_react6.useCallback)(
26800
27020
  (nextSelection) => {
26801
27021
  selectionRef.current = nextSelection;
26802
27022
  setSelection(nextSelection);
26803
27023
  },
26804
27024
  []
26805
27025
  );
26806
- const handleTextSelectionChange = (0, import_react5.useCallback)(
27026
+ const handleTextSelectionChange = (0, import_react6.useCallback)(
26807
27027
  (event) => {
26808
27028
  const nextSelection = resolveNativePdfTextSelection(event.nativeEvent);
26809
27029
  updateSelection(nextSelection);
@@ -26816,7 +27036,7 @@ function DedicatedIosPdfViewer({
26816
27036
  },
26817
27037
  [onTextSelected, setSelectedAnnotation, updateSelection]
26818
27038
  );
26819
- const defineSelection = (0, import_react5.useCallback)(() => {
27039
+ const defineSelection = (0, import_react6.useCallback)(() => {
26820
27040
  const currentSelection = selectionRef.current;
26821
27041
  if (!currentSelection) return;
26822
27042
  onDefineSelection?.({
@@ -26825,7 +27045,7 @@ function DedicatedIosPdfViewer({
26825
27045
  });
26826
27046
  updateSelection(null);
26827
27047
  }, [onDefineSelection, updateSelection]);
26828
- const handleNativeDefineSelection = (0, import_react5.useCallback)(
27048
+ const handleNativeDefineSelection = (0, import_react6.useCallback)(
26829
27049
  (event) => {
26830
27050
  const text = event.nativeEvent?.text;
26831
27051
  const pageIndex = event.nativeEvent?.pageIndex;
@@ -26837,7 +27057,7 @@ function DedicatedIosPdfViewer({
26837
27057
  },
26838
27058
  [onDefineSelection, updateSelection]
26839
27059
  );
26840
- const handleInkToolPickerVisibilityChange = (0, import_react5.useCallback)(
27060
+ const handleInkToolPickerVisibilityChange = (0, import_react6.useCallback)(
26841
27061
  (event) => {
26842
27062
  handleNativeInkPickerVisibility(
26843
27063
  event.nativeEvent?.visible === true,
@@ -26846,7 +27066,7 @@ function DedicatedIosPdfViewer({
26846
27066
  },
26847
27067
  []
26848
27068
  );
26849
- const trackMobileChromeByOffset = (0, import_react5.useCallback)(
27069
+ const trackMobileChromeByOffset = (0, import_react6.useCallback)(
26850
27070
  (offsetY) => {
26851
27071
  if (pageCount <= 0) return;
26852
27072
  const safeOffset = Math.max(0, offsetY);
@@ -26882,11 +27102,11 @@ function DedicatedIosPdfViewer({
26882
27102
  },
26883
27103
  [clearPendingChromeShow, pageCount, setMobileChromeVisible]
26884
27104
  );
26885
- (0, import_react5.useEffect)(
27105
+ (0, import_react6.useEffect)(
26886
27106
  () => () => clearPendingChromeShow(),
26887
27107
  [clearPendingChromeShow]
26888
27108
  );
26889
- const handleVisiblePagesChange = (0, import_react5.useCallback)(
27109
+ const handleVisiblePagesChange = (0, import_react6.useCallback)(
26890
27110
  (event) => {
26891
27111
  const pages = resolveNativePdfVisiblePages(
26892
27112
  event.nativeEvent?.pages
@@ -26901,13 +27121,13 @@ function DedicatedIosPdfViewer({
26901
27121
  },
26902
27122
  [setDocumentState]
26903
27123
  );
26904
- const handleScroll = (0, import_react5.useCallback)(
27124
+ const handleScroll = (0, import_react6.useCallback)(
26905
27125
  (event) => {
26906
27126
  trackMobileChromeByOffset(event.nativeEvent?.offsetY ?? 0);
26907
27127
  },
26908
27128
  [trackMobileChromeByOffset]
26909
27129
  );
26910
- const handleTap = (0, import_react5.useCallback)(() => {
27130
+ const handleTap = (0, import_react6.useCallback)(() => {
26911
27131
  setSelectedAnnotation(null);
26912
27132
  const nextVisible = resolvePageTapChromeVisibility({
26913
27133
  chromeVisible: chromeVisibleRef.current,
@@ -26917,7 +27137,7 @@ function DedicatedIosPdfViewer({
26917
27137
  });
26918
27138
  if (nextVisible !== null) setMobileChromeVisible(nextVisible);
26919
27139
  }, [activeTool, selectionActive, setMobileChromeVisible, setSelectedAnnotation]);
26920
- const handleDeleteAnnotation = (0, import_react5.useCallback)(
27140
+ const handleDeleteAnnotation = (0, import_react6.useCallback)(
26921
27141
  (annotationId) => {
26922
27142
  deleteAnnotationAndClearSelection(
26923
27143
  annotationId,
@@ -26932,12 +27152,12 @@ function DedicatedIosPdfViewer({
26932
27152
  maxWidth: resolvedMaxPageWidth,
26933
27153
  alignSelf: "center"
26934
27154
  } : void 0;
26935
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native5.View, { style: styles5.container, children: [
26936
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
27155
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.container, children: [
27156
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PdfNoteChooser, { notes: pendingNotes, title: t.annotationNote, cancel: t.cancel, onClose: () => setPendingNotes([]), onSelect: (id) => {
26937
27157
  setPendingNotes([]);
26938
27158
  setSelectedAnnotation(id);
26939
27159
  } }),
26940
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
27160
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
26941
27161
  import_engine_native3.PapyrusPdfDocumentView,
26942
27162
  {
26943
27163
  style: [styles5.viewer, cappedViewerStyle],
@@ -27006,22 +27226,22 @@ function DedicatedIosPdfViewer({
27006
27226
  onInkToolPickerVisibilityChange: handleInkToolPickerVisibilityChange
27007
27227
  }
27008
27228
  ),
27009
- selection && !supportsNativeEditMenu && onDefineSelection && shouldShowDefineSelection(selection.text, defineSelectionMode) && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native5.View, { style: styles5.selectionFallback, pointerEvents: "box-none", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
27229
+ selection && !supportsNativeEditMenu && onDefineSelection && shouldShowDefineSelection(selection.text, defineSelectionMode) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.View, { style: styles5.selectionFallback, pointerEvents: "box-none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27010
27230
  import_react_native5.Pressable,
27011
27231
  {
27012
27232
  onPress: defineSelection,
27013
27233
  style: styles5.fallbackButton,
27014
27234
  accessibilityRole: "button",
27015
27235
  accessibilityLabel: t.define,
27016
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native5.Text, { style: styles5.fallbackButtonText, children: t.define })
27236
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.Text, { style: styles5.fallbackButtonText, children: t.define })
27017
27237
  }
27018
27238
  ) }),
27019
- fallbackDeleteAnnotation && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
27239
+ fallbackDeleteAnnotation && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27020
27240
  import_react_native5.View,
27021
27241
  {
27022
27242
  style: styles5.annotationDeleteFallback,
27023
27243
  pointerEvents: "box-none",
27024
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
27244
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27025
27245
  import_react_native5.Pressable,
27026
27246
  {
27027
27247
  onPress: () => handleDeleteAnnotation(fallbackDeleteAnnotation.id),
@@ -27029,7 +27249,7 @@ function DedicatedIosPdfViewer({
27029
27249
  accessibilityRole: "button",
27030
27250
  accessibilityLabel: t.deleteAnnotation,
27031
27251
  hitSlop: 8,
27032
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native5.Text, { style: styles5.annotationDeleteButtonText, children: t.deleteAnnotation })
27252
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.Text, { style: styles5.annotationDeleteButtonText, children: t.deleteAnnotation })
27033
27253
  }
27034
27254
  )
27035
27255
  }
@@ -27092,7 +27312,7 @@ var styles5 = import_react_native5.StyleSheet.create({
27092
27312
  });
27093
27313
 
27094
27314
  // components/NativePdfDocumentViewer.tsx
27095
- var import_jsx_runtime8 = require("react/jsx-runtime");
27315
+ var import_jsx_runtime9 = require("react/jsx-runtime");
27096
27316
  function NativePdfDocumentViewer({
27097
27317
  engine,
27098
27318
  documentId,
@@ -27105,7 +27325,7 @@ function NativePdfDocumentViewer({
27105
27325
  import_react_native6.Platform.OS
27106
27326
  );
27107
27327
  if (implementation === "android") {
27108
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27328
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
27109
27329
  DedicatedAndroidPdfViewer,
27110
27330
  {
27111
27331
  engine,
@@ -27118,7 +27338,7 @@ function NativePdfDocumentViewer({
27118
27338
  );
27119
27339
  }
27120
27340
  if (implementation === "ios") {
27121
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
27341
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
27122
27342
  DedicatedIosPdfViewer,
27123
27343
  {
27124
27344
  engine,
@@ -27172,26 +27392,6 @@ var resolveMobileChromeOffsets = (insets) => ({
27172
27392
  right: MOBILE_CHROME_METRICS.screenPadding + Math.max(0, insets.right)
27173
27393
  });
27174
27394
 
27175
- // components/PapyrusSafeArea.tsx
27176
- var import_react6 = __toESM(require("react"));
27177
- var import_react_native_safe_area_context = require("react-native-safe-area-context");
27178
- var import_jsx_runtime9 = require("react/jsx-runtime");
27179
- var papyrusSafeAreaContext = import_react_native_safe_area_context.SafeAreaInsetsContext;
27180
- var ZERO_SAFE_AREA_INSETS = {
27181
- top: 0,
27182
- bottom: 0,
27183
- left: 0,
27184
- right: 0
27185
- };
27186
- var usePapyrusSafeAreaInsets = () => (0, import_react6.useContext)(papyrusSafeAreaContext) ?? ZERO_SAFE_AREA_INSETS;
27187
- function PapyrusSafeAreaBoundary({
27188
- children
27189
- }) {
27190
- const insets = (0, import_react6.useContext)(papyrusSafeAreaContext);
27191
- if (insets) return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children });
27192
- return import_react6.default.createElement(import_react_native_safe_area_context.SafeAreaProvider, null, children);
27193
- }
27194
-
27195
27395
  // components/NativeTextDocumentViewer.tsx
27196
27396
  var import_jsx_runtime10 = require("react/jsx-runtime");
27197
27397
  function NativeTextDocumentViewer({
@@ -27740,6 +27940,7 @@ var scrollViewerListToOffset = (list, offset, animated) => {
27740
27940
  list.scrollToOffset?.({ offset, animated });
27741
27941
  };
27742
27942
  var Viewer = ({
27943
+ documentType,
27743
27944
  documentId,
27744
27945
  engine,
27745
27946
  maxPageWidth,
@@ -29267,14 +29468,8 @@ var Viewer = ({
29267
29468
  },
29268
29469
  [mobilePerf, perfEnabled, scrollPerfSession]
29269
29470
  );
29270
- const handleWebViewScroll = (0, import_react9.useCallback)(
29271
- (_offsetY) => {
29272
- if (!chromeVisibleRef.current) {
29273
- setMobileChromeVisible(true, "scroll.webview.recover");
29274
- }
29275
- },
29276
- [setMobileChromeVisible]
29277
- );
29471
+ const handleWebViewScroll = (0, import_react9.useCallback)((_offsetY) => {
29472
+ }, []);
29278
29473
  const handlePageTap = (0, import_react9.useCallback)(() => {
29279
29474
  const nextVisible = resolvePageTapChromeVisibility({
29280
29475
  chromeVisible: chromeVisibleRef.current,
@@ -29496,6 +29691,7 @@ var Viewer = ({
29496
29691
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native9.View, { style: [styles8.container, isDark && styles8.containerDark], children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
29497
29692
  WebViewViewer_default,
29498
29693
  {
29694
+ documentType,
29499
29695
  engine,
29500
29696
  documentId,
29501
29697
  onDefineSelection,
@@ -30042,6 +30238,7 @@ var styles9 = import_react_native11.StyleSheet.create({
30042
30238
  // components/PageJumpModal.tsx
30043
30239
  var import_jsx_runtime14 = require("react/jsx-runtime");
30044
30240
  function PageJumpModal({
30241
+ documentType,
30045
30242
  visible,
30046
30243
  currentPage,
30047
30244
  pageCount,
@@ -30068,8 +30265,8 @@ function PageJumpModal({
30068
30265
  isDark,
30069
30266
  maxHeight: 300,
30070
30267
  showHeader: true,
30071
- title: "Ir para p\xE1gina",
30072
- closeAccessibilityLabel: "Close page jump",
30268
+ title: documentType === "epub" ? "Ir para cap\xEDtulo" : "Ir para p\xE1gina",
30269
+ closeAccessibilityLabel: "Fechar navega\xE7\xE3o",
30073
30270
  sheetStyle: {
30074
30271
  paddingHorizontal: 18,
30075
30272
  paddingBottom: 18,
@@ -30097,7 +30294,7 @@ function PageJumpModal({
30097
30294
  }
30098
30295
  ],
30099
30296
  onSubmitEditing: confirmJump,
30100
- accessibilityLabel: "Page jump input",
30297
+ accessibilityLabel: documentType === "epub" ? "N\xFAmero do cap\xEDtulo" : "N\xFAmero da p\xE1gina",
30101
30298
  testID: "papyrus-page-jump-input"
30102
30299
  }
30103
30300
  ),
@@ -30152,6 +30349,7 @@ var styles10 = import_react_native12.StyleSheet.create({
30152
30349
  var import_jsx_runtime15 = require("react/jsx-runtime");
30153
30350
  var Topbar = ({
30154
30351
  engine,
30352
+ documentType,
30155
30353
  onOpenSettings,
30156
30354
  onOpenOverflow,
30157
30355
  title,
@@ -30172,6 +30370,12 @@ var Topbar = ({
30172
30370
  locale
30173
30371
  } = (0, import_core12.useViewerStore)();
30174
30372
  const inkSession = useNativeInkSession();
30373
+ const chromeVisible = mobileChromeVisible || inkSession.active;
30374
+ const chromeProgress = useNativeInkMotion(chromeVisible, documentType === "epub");
30375
+ const chromeMotion = (0, import_react_native_reanimated2.useAnimatedStyle)(() => {
30376
+ "worklet";
30377
+ return { opacity: chromeProgress.value };
30378
+ });
30175
30379
  const t = getStrings(locale);
30176
30380
  const progress = useNativeInkMotion(inkSession.active, inkSession.supported);
30177
30381
  const settingsMotion = (0, import_react_native_reanimated2.useAnimatedStyle)(() => {
@@ -30227,26 +30431,30 @@ var Topbar = ({
30227
30431
  children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconSettings, { size: MOBILE_CHROME_METRICS.iconSize, color: isDark ? "#e5e7eb" : "#111827" })
30228
30432
  }
30229
30433
  );
30230
- if (!mobileChromeVisible && !inkSession.active) return null;
30434
+ if (!chromeVisible && documentType !== "epub") return null;
30231
30435
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
30232
30436
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
30233
- import_react_native13.View,
30437
+ import_react_native_reanimated2.default.View,
30234
30438
  {
30235
30439
  style: [
30236
30440
  styles11.chromeFrame,
30441
+ chromeMotion,
30237
30442
  {
30238
30443
  paddingTop: insets.top,
30239
30444
  paddingLeft: MOBILE_CHROME_METRICS.screenPadding + insets.left,
30240
30445
  paddingRight: MOBILE_CHROME_METRICS.screenPadding + insets.right
30241
30446
  }
30242
30447
  ],
30243
- pointerEvents: "box-none",
30448
+ pointerEvents: chromeVisible ? "box-none" : "none",
30449
+ accessibilityElementsHidden: !chromeVisible,
30450
+ importantForAccessibility: chromeVisible ? "auto" : "no-hide-descendants",
30244
30451
  testID: "papyrus-floating-top-controls",
30245
30452
  children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
30246
30453
  import_react_native13.View,
30247
30454
  {
30248
30455
  style: [
30249
30456
  styles11.container,
30457
+ import_react_native13.Platform.OS === "ios" && styles11.containerIos,
30250
30458
  isDark && styles11.containerDark,
30251
30459
  isLandscape && styles11.containerLandscape
30252
30460
  ],
@@ -30386,6 +30594,7 @@ var Topbar = ({
30386
30594
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
30387
30595
  PageJumpModal,
30388
30596
  {
30597
+ documentType,
30389
30598
  visible: jumpModalOpen,
30390
30599
  currentPage,
30391
30600
  pageCount,
@@ -30403,7 +30612,8 @@ var styles11 = import_react_native13.StyleSheet.create({
30403
30612
  maxWidth: MOBILE_CHROME_METRICS.maxFloatingWidth,
30404
30613
  marginTop: 10,
30405
30614
  paddingHorizontal: 14,
30406
- paddingVertical: 10,
30615
+ height: MOBILE_CHROME_METRICS.topbarHeight,
30616
+ paddingVertical: 0,
30407
30617
  backgroundColor: "rgba(255,255,255,0.88)",
30408
30618
  borderWidth: 1,
30409
30619
  borderColor: "rgba(255,255,255,0.72)",
@@ -30420,6 +30630,10 @@ var styles11 = import_react_native13.StyleSheet.create({
30420
30630
  backgroundColor: "rgba(15,17,21,0.88)",
30421
30631
  borderColor: "rgba(71,85,105,0.48)"
30422
30632
  },
30633
+ containerIos: {
30634
+ height: MOBILE_CHROME_METRICS.topbarHeight,
30635
+ paddingVertical: 0
30636
+ },
30423
30637
  containerLandscape: {
30424
30638
  maxWidth: void 0,
30425
30639
  marginTop: 0
@@ -32556,10 +32770,10 @@ var import_react_native16 = require("react-native");
32556
32770
  var import_core15 = require("@papyrus-sdk/core");
32557
32771
  var import_jsx_runtime18 = require("react/jsx-runtime");
32558
32772
  var COLORS = ["#fbbf24", "#fb7185", "#60a5fa", "#34d399", "#c084fc"];
32559
- var AnnotationEditor = () => {
32773
+ var AnnotationEditor = ({ documentId }) => {
32560
32774
  const { annotations, annotationDraft, clearAnnotationDraft, addAnnotation, selectedAnnotationId, updateAnnotation, removeAnnotation, setSelectedAnnotation, uiTheme, locale, accentColor } = (0, import_core15.useViewerStore)();
32561
32775
  const annotation = annotationDraft?.id === selectedAnnotationId ? annotationDraft : annotations.find((a) => a.id === selectedAnnotationId);
32562
- const supported = annotation && annotation.type !== "ink";
32776
+ const supported = annotation && annotation.type !== "ink" && (!documentId || !annotation.anchor?.documentId || annotation.anchor.documentId === documentId);
32563
32777
  const [editing, setEditing] = (0, import_react16.useState)(false);
32564
32778
  const [draft, setDraft] = (0, import_react16.useState)("");
32565
32779
  const [color, setColor] = (0, import_react16.useState)(COLORS[0]);
@@ -32624,14 +32838,34 @@ var AnnotationEditor = () => {
32624
32838
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native16.Animated.View, { accessibilityViewIsModal: true, style: [styles14.card, { backgroundColor: dark ? "#17191f" : "#fff", marginBottom: Math.max(16, insets.bottom), opacity: progress, transform: [{ translateY: progress.interpolate({ inputRange: [0, 1], outputRange: [12, 0] }) }] }], children: [
32625
32839
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native16.View, { style: styles14.header, children: [
32626
32840
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: [styles14.title, textStyle], children: editing ? t.editNote : t.annotationNote }),
32627
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: t.close, style: styles14.button, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: textStyle, children: "\xD7" }) })
32841
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Pressable, { onPress: close, accessibilityRole: "button", accessibilityLabel: t.close, style: [styles14.button, { borderRadius: 22, backgroundColor: dark ? "#242936" : "#f1f5f9" }], children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconClose, { size: 20, color: textStyle.color }) })
32628
32842
  ] }),
32629
32843
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native16.ScrollView, { style: styles14.content, keyboardShouldPersistTaps: "handled", children: [
32630
32844
  quote ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: [styles14.quote, textStyle], selectable: true, children: quote }) : null,
32631
32845
  editing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
32632
32846
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.TextInput, { accessibilityLabel: t.editNote, value: draft, onChangeText: setDraft, multiline: true, autoFocus: true, placeholder: t.notePlaceholder, placeholderTextColor: dark ? "#9ca3af" : "#6b7280", style: [styles14.input, textStyle, { borderColor: dark ? "#374151" : "#d1d5db" }] }),
32633
32847
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.View, { style: styles14.options, children: COLORS.map((value) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Pressable, { accessibilityRole: "button", accessibilityLabel: `${t.annotationColor} ${value}`, accessibilityState: { selected: color === value }, onPress: () => setColor(value), style: [styles14.color, { backgroundColor: value, borderColor: color === value ? accentColor : "transparent" }] }, value)) }),
32634
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.View, { style: styles14.options, children: [["highlight", t.annotationHighlight], ["underline", t.annotationUnderline], ["strikeout", t.annotationStrikeout], ["none", t.annotationIndicatorOnly]].map(([style, label]) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Pressable, { onPress: () => setMarkup(style), accessibilityRole: "button", accessibilityState: { selected: markup === style }, style: [styles14.button, markup === style && { borderColor: accentColor, borderWidth: 1 }], children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: textStyle, children: label }) }, style)) })
32848
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.View, { style: styles14.markupOptions, children: [
32849
+ ["highlight", t.annotationHighlight, IconHighlight],
32850
+ ["underline", t.annotationUnderline, IconUnderline],
32851
+ ["strikeout", t.annotationStrikeout, IconStrikeout],
32852
+ ["none", t.annotationIndicatorOnly, IconComment]
32853
+ ].map(([style, label, Icon]) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
32854
+ import_react_native16.Pressable,
32855
+ {
32856
+ onPress: () => setMarkup(style),
32857
+ accessibilityRole: "button",
32858
+ accessibilityLabel: label,
32859
+ accessibilityState: { selected: markup === style },
32860
+ testID: `annotation-style-${style}`,
32861
+ style: [styles14.markupButton, { backgroundColor: dark ? "#242936" : "#f1f5f9", borderColor: markup === style ? accentColor : "transparent" }],
32862
+ children: [
32863
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.View, { accessible: false, importantForAccessibility: "no-hide-descendants", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Icon, { size: 22, color: markup === style ? accentColor : textStyle.color }) }),
32864
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: [styles14.markupLabel, textStyle], children: style === "none" ? locale === "pt-BR" ? "Indicador" : "Indicator" : label })
32865
+ ]
32866
+ },
32867
+ style
32868
+ )) })
32635
32869
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_native16.Text, { style: [styles14.note, textStyle], selectable: true, children: (0, import_core15.getAnnotationNote)(annotation) || t.notePlaceholder })
32636
32870
  ] }),
32637
32871
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_native16.View, { style: styles14.actions, children: [
@@ -32654,7 +32888,7 @@ var AnnotationEditor = () => {
32654
32888
  ] })
32655
32889
  ] }) });
32656
32890
  };
32657
- var styles14 = import_react_native16.StyleSheet.create({ host: { flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0,0,0,.18)", paddingHorizontal: 12 }, card: { borderRadius: 20, padding: 16, maxHeight: "72%", elevation: 12 }, header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, title: { fontSize: 17, fontWeight: "700" }, content: { flexGrow: 0 }, quote: { fontSize: 14, lineHeight: 21, borderLeftWidth: 3, borderLeftColor: "#9ca3af", paddingLeft: 10, marginVertical: 12 }, note: { fontSize: 16, lineHeight: 24, paddingVertical: 12 }, input: { minHeight: 112, maxHeight: 240, borderWidth: 1, borderRadius: 12, padding: 12, fontSize: 16, textAlignVertical: "top" }, button: { minWidth: 44, minHeight: 44, paddingHorizontal: 10, borderRadius: 10, alignItems: "center", justifyContent: "center" }, actions: { flexDirection: "row", alignItems: "center", flexWrap: "wrap", marginTop: 12 }, options: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginTop: 12 }, color: { width: 44, height: 44, borderRadius: 22, borderWidth: 3 } });
32891
+ var styles14 = import_react_native16.StyleSheet.create({ host: { flex: 1, justifyContent: "flex-end", backgroundColor: "rgba(0,0,0,.18)", paddingHorizontal: 12 }, card: { borderRadius: 20, padding: 16, maxHeight: "72%", elevation: 12 }, header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, title: { fontSize: 17, fontWeight: "700" }, content: { flexGrow: 0 }, quote: { fontSize: 14, lineHeight: 21, borderLeftWidth: 3, borderLeftColor: "#9ca3af", paddingLeft: 10, marginVertical: 12 }, note: { fontSize: 16, lineHeight: 24, paddingVertical: 12 }, input: { minHeight: 112, maxHeight: 240, borderWidth: 1, borderRadius: 12, padding: 12, fontSize: 16, textAlignVertical: "top" }, button: { minWidth: 44, minHeight: 44, paddingHorizontal: 10, borderRadius: 10, alignItems: "center", justifyContent: "center" }, actions: { flexDirection: "row", alignItems: "center", flexWrap: "wrap", marginTop: 12 }, markupOptions: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginTop: 12 }, markupButton: { flex: 1, minWidth: 64, minHeight: 64, borderWidth: 1, borderRadius: 12, paddingHorizontal: 4, paddingVertical: 8, alignItems: "center", justifyContent: "center", gap: 6 }, markupLabel: { fontSize: 11, lineHeight: 15, textAlign: "center" }, options: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginTop: 12 }, color: { width: 44, height: 44, borderRadius: 22, borderWidth: 3 } });
32658
32892
  var AnnotationEditor_default = AnnotationEditor;
32659
32893
 
32660
32894
  // components/BottomBar.tsx
@@ -32717,10 +32951,19 @@ var BottomBar = ({
32717
32951
  documentType,
32718
32952
  onOpenInfo,
32719
32953
  onOpenSettings,
32720
- onOpenDestination
32954
+ onOpenDestination,
32955
+ onTurnPage,
32956
+ onNavigateToPage,
32957
+ onOpenPages
32721
32958
  }) => {
32722
32959
  const {
32723
32960
  activeMobileDestination,
32961
+ viewMode,
32962
+ epubReadingMode,
32963
+ epubLocation,
32964
+ comicLayoutMode,
32965
+ currentPage,
32966
+ pageCount,
32724
32967
  mobileDockVisible,
32725
32968
  setDocumentState,
32726
32969
  uiTheme,
@@ -32735,7 +32978,7 @@ var BottomBar = ({
32735
32978
  const supportsInkChrome = documentType === "pdf" && inkSession.supported;
32736
32979
  const drawing = supportsInkChrome && inkSession.active;
32737
32980
  const dockVisible = mobileChromeVisible && mobileDockVisible && !drawing;
32738
- const dockProgress = useNativeInkMotion(dockVisible, supportsInkChrome);
32981
+ const dockProgress = useNativeInkMotion(dockVisible, supportsInkChrome || documentType === "epub");
32739
32982
  const dockMotion = (0, import_react_native_reanimated3.useAnimatedStyle)(() => {
32740
32983
  "worklet";
32741
32984
  return {
@@ -32757,6 +33000,15 @@ var BottomBar = ({
32757
33000
  activeMobileDestination,
32758
33001
  toolDockOpen
32759
33002
  });
33003
+ const paginated = documentType !== "text" && (documentType === "comic" ? comicLayoutMode === "single" : documentType === "epub" ? epubReadingMode === "paged" : viewMode === "single");
33004
+ if (paginated) {
33005
+ layout.leftSlots.push(...layout.rightSlots.filter((slot) => slot.key === "search"));
33006
+ layout.rightSlots = [];
33007
+ }
33008
+ const turnPage = (direction) => {
33009
+ if (onTurnPage) onTurnPage(direction);
33010
+ else onNavigateToPage?.(currentPage + direction);
33011
+ };
32760
33012
  const slotMeta = {
32761
33013
  annotate: {
32762
33014
  label: t.tools,
@@ -32817,7 +33069,7 @@ var BottomBar = ({
32817
33069
  onPress: onOpenSettings
32818
33070
  }
32819
33071
  };
32820
- if (!dockVisible && !supportsInkChrome) return null;
33072
+ if (!dockVisible && !supportsInkChrome && documentType !== "epub") return null;
32821
33073
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
32822
33074
  import_react_native_reanimated3.default.View,
32823
33075
  {
@@ -32882,7 +33134,11 @@ var BottomBar = ({
32882
33134
  isDark && styles15.islandDark
32883
33135
  ],
32884
33136
  testID: "papyrus-floating-bottom-dock",
32885
- children: layout.rightSlots.map((slot) => {
33137
+ children: paginated ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
33138
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native17.Pressable, { accessibilityRole: "button", accessibilityLabel: locale === "pt-BR" ? "P\xE1gina anterior" : "Previous page", disabled: !dockVisible || (documentType === "epub" ? epubLocation?.atStart : !onTurnPage && currentPage <= 1), onPress: () => turnPage(-1), style: styles15.iconOnlyItem, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(IconChevronLeft, { size: MOBILE_CHROME_METRICS.iconSize, color: iconColor(false) }) }),
33139
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native17.Pressable, { accessibilityRole: "button", accessibilityLabel: locale === "pt-BR" ? "Ir para p\xE1gina" : "Go to page", onPress: onOpenPages, style: [styles15.iconOnlyItem, { minWidth: 56 }], children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native17.Text, { style: { color: iconColor(false), fontWeight: "700", fontSize: 13, lineHeight: 18, includeFontPadding: false }, children: documentType === "epub" ? epubLocation?.visualPage ? `Cap. ${epubLocation.chapter} \xB7 ${epubLocation.visualPage}/${epubLocation.visualPageCount}` : `Cap. ${currentPage}` : `${currentPage}/${pageCount}` }) }),
33140
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_native17.Pressable, { accessibilityRole: "button", accessibilityLabel: locale === "pt-BR" ? "Pr\xF3xima p\xE1gina" : "Next page", disabled: !dockVisible || (documentType === "epub" ? epubLocation?.atEnd : !onTurnPage && currentPage >= pageCount), onPress: () => turnPage(1), style: styles15.iconOnlyItem, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(IconChevronRight, { size: MOBILE_CHROME_METRICS.iconSize, color: iconColor(false) }) })
33141
+ ] }) : layout.rightSlots.map((slot) => {
32886
33142
  const meta = slotMeta[slot.key];
32887
33143
  const Icon = meta.icon;
32888
33144
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
@@ -32945,8 +33201,9 @@ var styles15 = import_react_native17.StyleSheet.create({
32945
33201
  island: {
32946
33202
  flexDirection: "row",
32947
33203
  alignItems: "center",
32948
- paddingVertical: 8,
32949
- paddingHorizontal: 10,
33204
+ height: MOBILE_CHROME_METRICS.bottomBarVisualHeight,
33205
+ paddingVertical: 0,
33206
+ paddingHorizontal: 6,
32950
33207
  backgroundColor: "rgba(255,255,255,0.9)",
32951
33208
  borderWidth: 1,
32952
33209
  borderColor: "rgba(255,255,255,0.72)",
@@ -32972,6 +33229,8 @@ var styles15 = import_react_native17.StyleSheet.create({
32972
33229
  borderColor: "rgba(71,85,105,0.48)"
32973
33230
  },
32974
33231
  iconOnlyItem: {
33232
+ minWidth: 44,
33233
+ height: 44,
32975
33234
  alignItems: "center",
32976
33235
  justifyContent: "center",
32977
33236
  paddingHorizontal: MOBILE_CHROME_METRICS.bottomBarItemPaddingHorizontal,
@@ -33048,10 +33307,12 @@ var SettingsSheet = ({
33048
33307
  engine,
33049
33308
  visible,
33050
33309
  onClose,
33051
- documentType = "pdf"
33310
+ documentType = "pdf",
33311
+ onOpenInfo
33052
33312
  }) => {
33053
33313
  const {
33054
33314
  viewMode,
33315
+ epubReadingMode,
33055
33316
  uiTheme,
33056
33317
  zoom,
33057
33318
  setDocumentState,
@@ -33063,12 +33324,16 @@ var SettingsSheet = ({
33063
33324
  comicReadingDirection
33064
33325
  } = (0, import_core17.useViewerStore)();
33065
33326
  const isDark = uiTheme === "dark";
33066
- const isPaged = viewMode === "single";
33327
+ const isPaged = documentType === "epub" ? epubReadingMode === "paged" : viewMode === "single";
33067
33328
  const isDouble = viewMode === "double";
33068
33329
  const t = getStrings(locale);
33069
33330
  const { height: windowHeight } = (0, import_react_native18.useWindowDimensions)();
33070
33331
  const sheetMaxHeight = getSettingsSheetMaxHeight(windowHeight);
33071
33332
  const handleTransition = (mode) => {
33333
+ if (documentType === "epub") {
33334
+ setDocumentState({ epubReadingMode: mode === "paged" ? "paged" : "chapter-scroll" });
33335
+ return;
33336
+ }
33072
33337
  if (mode === "paged") {
33073
33338
  setDocumentState({ viewMode: "single" });
33074
33339
  return;
@@ -33113,6 +33378,7 @@ var SettingsSheet = ({
33113
33378
  contentContainerStyle: styles16.sheetContent,
33114
33379
  showsVerticalScrollIndicator: false,
33115
33380
  children: [
33381
+ onOpenInfo ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native18.Pressable, { accessibilityRole: "button", onPress: onOpenInfo, style: { minHeight: 44, justifyContent: "center", marginBottom: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native18.Text, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: t.info }) }) : null,
33116
33382
  (documentType === "text" || documentType === "epub") && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native18.View, { style: styles16.section, children: [
33117
33383
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_native18.Text, { style: [styles16.sectionTitle, isDark && styles16.sectionTitleDark], children: locale === "pt-BR" ? "Tamanho da fonte" : "Font size" }),
33118
33384
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native18.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [
@@ -33291,7 +33557,7 @@ var SettingsSheet = ({
33291
33557
  isDark && styles16.optionTextDark,
33292
33558
  (!isPaged || isDouble) && styles16.optionTextActive
33293
33559
  ],
33294
- children: t.continuous
33560
+ children: documentType === "epub" && locale === "pt-BR" ? "Rolagem por cap\xEDtulo" : t.continuous
33295
33561
  }
33296
33562
  )
33297
33563
  }
@@ -33321,7 +33587,7 @@ var SettingsSheet = ({
33321
33587
  )
33322
33588
  ] })
33323
33589
  ] }),
33324
- documentType !== "comic" && documentType !== "text" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native18.View, { style: styles16.section, children: [
33590
+ documentType === "pdf" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_native18.View, { style: styles16.section, children: [
33325
33591
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
33326
33592
  import_react_native18.Text,
33327
33593
  {
@@ -34199,6 +34465,7 @@ var styles20 = import_react_native22.StyleSheet.create({
34199
34465
  // components/ProgressPill.tsx
34200
34466
  var import_react18 = require("react");
34201
34467
  var import_react_native23 = require("react-native");
34468
+ var import_react_native_reanimated4 = __toESM(require("react-native-reanimated"));
34202
34469
  var import_core21 = require("@papyrus-sdk/core");
34203
34470
 
34204
34471
  // components/progressPillMode.ts
@@ -34218,9 +34485,11 @@ var import_jsx_runtime25 = require("react/jsx-runtime");
34218
34485
  var clampPercent = (value) => Math.max(0, Math.min(100, Math.round(value)));
34219
34486
  function ProgressPill({
34220
34487
  documentType,
34488
+ navigationInDock = false,
34221
34489
  onPress,
34222
34490
  onOpenPageJump,
34223
34491
  onNavigateToPage,
34492
+ onTurnPage,
34224
34493
  onScrubbingChange
34225
34494
  }) {
34226
34495
  const {
@@ -34230,12 +34499,20 @@ function ProgressPill({
34230
34499
  textLength,
34231
34500
  comicLayoutMode,
34232
34501
  viewMode,
34502
+ epubReadingMode,
34503
+ epubLocation,
34233
34504
  locale,
34234
34505
  uiTheme,
34235
34506
  accentColor,
34236
34507
  mobileChromeVisible,
34237
34508
  mobileProgressPillVisible
34238
34509
  } = (0, import_core21.useViewerStore)();
34510
+ const chromeVisible = mobileChromeVisible && mobileProgressPillVisible;
34511
+ const chromeProgress = useNativeInkMotion(chromeVisible, documentType === "epub");
34512
+ const chromeMotion = (0, import_react_native_reanimated4.useAnimatedStyle)(() => {
34513
+ "worklet";
34514
+ return { opacity: chromeProgress.value };
34515
+ });
34239
34516
  const t = getStrings(locale);
34240
34517
  const isDark = uiTheme === "dark";
34241
34518
  const offsets = resolveMobileChromeOffsets(usePapyrusSafeAreaInsets());
@@ -34280,10 +34557,10 @@ function ProgressPill({
34280
34557
  return `${displayedPage}/${pageCount || 0}`;
34281
34558
  }
34282
34559
  if (documentType === "epub") {
34283
- return `Cap. ${displayedPage} \xB7 ${percent}%`;
34560
+ return `Cap. ${displayedPage} \xB7 ${scrubPreviewPage === null && epubLocation ? clampPercent(epubLocation.progress * 100) : percent}%`;
34284
34561
  }
34285
34562
  return `${percent}%`;
34286
- }, [displayedPage, documentType, pageCount]);
34563
+ }, [displayedPage, documentType, pageCount, epubLocation, scrubPreviewPage]);
34287
34564
  const thumbTop = resolvePageScrubberThumbTop({
34288
34565
  currentPage,
34289
34566
  trackHeight,
@@ -34325,7 +34602,7 @@ function ProgressPill({
34325
34602
  },
34326
34603
  []
34327
34604
  );
34328
- if (!shouldRenderPageScrubber({
34605
+ if (!(documentType === "epub" && mobileProgressPillVisible) && !shouldRenderPageScrubber({
34329
34606
  mobileChromeVisible,
34330
34607
  mobileProgressPillVisible,
34331
34608
  isScrubbing: isScrubbingRef.current
@@ -34356,16 +34633,17 @@ function ProgressPill({
34356
34633
  }
34357
34634
  );
34358
34635
  }
34359
- const progressViewMode = documentType === "comic" ? comicLayoutMode === "single" ? "single" : "continuous" : viewMode;
34636
+ const progressViewMode = documentType === "comic" ? comicLayoutMode === "single" ? "single" : "continuous" : documentType === "epub" ? epubReadingMode === "paged" ? "single" : "continuous" : viewMode;
34360
34637
  const pillMode = resolveProgressPillMode(progressViewMode, currentPage, pageCount);
34638
+ if (pillMode.kind === "navigation" && navigationInDock) return null;
34361
34639
  if (pillMode.kind === "navigation") {
34362
- const previousDisabled = pillMode.previousPage === null || !onNavigateToPage;
34363
- const nextDisabled = pillMode.nextPage === null || !onNavigateToPage;
34640
+ const previousDisabled = !onTurnPage && (pillMode.previousPage === null || !onNavigateToPage);
34641
+ const nextDisabled = !onTurnPage && (pillMode.nextPage === null || !onNavigateToPage);
34364
34642
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
34365
34643
  import_react_native23.View,
34366
34644
  {
34367
34645
  pointerEvents: "box-none",
34368
- style: [styles21.frame, styles21.singleFrame, { top: offsets.progress, right: offsets.right }],
34646
+ style: [{ position: "absolute", zIndex: 18, bottom: offsets.bottom + MOBILE_CHROME_METRICS.bottomBarVisualHeight + 12, left: offsets.left, right: offsets.right, alignItems: "center" }],
34369
34647
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_react_native23.View, { style: [styles21.singlePill, isDark && styles21.pillDark], children: [
34370
34648
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
34371
34649
  import_react_native23.Pressable,
@@ -34374,7 +34652,7 @@ function ProgressPill({
34374
34652
  accessibilityLabel: t.previousPage,
34375
34653
  accessibilityState: { disabled: previousDisabled },
34376
34654
  disabled: previousDisabled,
34377
- onPress: () => pillMode.previousPage !== null && onNavigateToPageRef.current?.(pillMode.previousPage),
34655
+ onPress: () => onTurnPage ? onTurnPage(-1) : pillMode.previousPage !== null && onNavigateToPageRef.current?.(pillMode.previousPage),
34378
34656
  style: styles21.pageArrowHit,
34379
34657
  testID: "papyrus-progress-page-previous",
34380
34658
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native23.Text, { style: [styles21.pageArrow, isDark && styles21.labelDark, previousDisabled && styles21.pageArrowDisabled], children: "\u2039" })
@@ -34402,7 +34680,7 @@ function ProgressPill({
34402
34680
  accessibilityLabel: t.nextPage,
34403
34681
  accessibilityState: { disabled: nextDisabled },
34404
34682
  disabled: nextDisabled,
34405
- onPress: () => pillMode.nextPage !== null && onNavigateToPageRef.current?.(pillMode.nextPage),
34683
+ onPress: () => onTurnPage ? onTurnPage(1) : pillMode.nextPage !== null && onNavigateToPageRef.current?.(pillMode.nextPage),
34406
34684
  style: styles21.pageArrowHit,
34407
34685
  testID: "papyrus-progress-page-next",
34408
34686
  children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native23.Text, { style: [styles21.pageArrow, isDark && styles21.labelDark, nextDisabled && styles21.pageArrowDisabled], children: "\u203A" })
@@ -34546,11 +34824,14 @@ function ProgressPill({
34546
34824
  }
34547
34825
  const responderPanHandlers = scrubberResponderRef.current?.panHandlers ?? {};
34548
34826
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
34549
- import_react_native23.View,
34827
+ import_react_native_reanimated4.default.View,
34550
34828
  {
34551
- pointerEvents: "box-none",
34829
+ pointerEvents: chromeVisible ? "box-none" : "none",
34830
+ accessibilityElementsHidden: !chromeVisible,
34831
+ importantForAccessibility: chromeVisible ? "auto" : "no-hide-descendants",
34552
34832
  style: [
34553
34833
  styles21.frame,
34834
+ chromeMotion,
34554
34835
  resolvePageScrubberOverlayStyle(),
34555
34836
  {
34556
34837
  top: offsets.progress,
@@ -35279,15 +35560,10 @@ function ReadingShell({
35279
35560
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35280
35561
  Topbar_default,
35281
35562
  {
35563
+ documentType,
35282
35564
  engine,
35283
35565
  title,
35284
- onOpenOverflow: () => {
35285
- closeMobileDestination();
35286
- setInfoOpen(false);
35287
- setActionsOpen(false);
35288
- setSettingsOpen(false);
35289
- setOverflowOpen(true);
35290
- },
35566
+ onOpenSettings: () => openDestination("display"),
35291
35567
  onOpenPageJump: () => setPageJumpOpen(true),
35292
35568
  showPageNavigationControls: false
35293
35569
  }
@@ -35295,6 +35571,7 @@ function ReadingShell({
35295
35571
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native26.View, { style: styles24.viewerStage, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35296
35572
  Viewer_default,
35297
35573
  {
35574
+ documentType,
35298
35575
  engine,
35299
35576
  ...viewerProps,
35300
35577
  pageScrubActive
@@ -35303,7 +35580,13 @@ function ReadingShell({
35303
35580
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35304
35581
  ProgressPill,
35305
35582
  {
35583
+ navigationInDock: true,
35306
35584
  documentType,
35585
+ onTurnPage: documentType === "epub" ? (direction) => {
35586
+ const paginatedEngine = engine;
35587
+ void paginatedEngine.turnEpubPage?.(direction).catch(() => {
35588
+ });
35589
+ } : void 0,
35307
35590
  onNavigateToPage: navigateToPage,
35308
35591
  onPress: () => openDestination(
35309
35592
  documentType === "pdf" ? "pages" : documentType === "epub" ? "contents" : "progress"
@@ -35315,6 +35598,12 @@ function ReadingShell({
35315
35598
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35316
35599
  BottomBar_default,
35317
35600
  {
35601
+ onNavigateToPage: navigateToPage,
35602
+ onOpenPages: () => setPageJumpOpen(true),
35603
+ onTurnPage: documentType === "epub" ? (direction) => {
35604
+ const paginatedEngine = engine;
35605
+ void paginatedEngine.turnEpubPage?.(direction).catch((error) => console.warn("[Papyrus] page navigation failed", error));
35606
+ } : void 0,
35318
35607
  documentType,
35319
35608
  onOpenDestination: openDestination,
35320
35609
  onOpenInfo: () => openDestination("info"),
@@ -35394,6 +35683,7 @@ function ReadingShell({
35394
35683
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35395
35684
  SettingsSheet_default,
35396
35685
  {
35686
+ onOpenInfo: () => openDestination("info"),
35397
35687
  engine,
35398
35688
  documentType,
35399
35689
  visible: settingsOpen,
@@ -35406,6 +35696,7 @@ function ReadingShell({
35406
35696
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
35407
35697
  PageJumpModal,
35408
35698
  {
35699
+ documentType,
35409
35700
  visible: pageJumpOpen,
35410
35701
  currentPage,
35411
35702
  pageCount,