@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.
@@ -20483,6 +20483,67 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20483
20483
  <!-- @papyrus-comic-runtime:end -->
20484
20484
  <script>
20485
20485
  (function () {
20486
+ /* @papyrus-epub-reader:start */
20487
+ (function(root){
20488
+ function mode(value){return value==='chapter-scroll'?'chapter-scroll':'paged';}
20489
+ function validCfi(value){return typeof value==='string' && value.length<=4096 && /^epubcfi\([^\n<>]+\)$/.test(value);}
20490
+ function options(value){return {manager:'default',flow:mode(value)==='paged'?'paginated':'scrolled-doc',spread:'none',width:'100%',height:'100%',allowScriptedContent:false};}
20491
+ function gesture(start,end,state){
20492
+ if(!start || !end || state.selected || state.interactive || state.multitouch)return null;
20493
+ var dx=end.x-start.x,dy=end.y-start.y,elapsed=end.time-start.time;
20494
+ 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';
20495
+ if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
20496
+ return null;
20497
+ }
20498
+ function location(raw,count,readingMode,geometry){
20499
+ var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
20500
+ var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
20501
+ var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
20502
+ var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
20503
+ var atStart=Boolean(raw.atStart),atEnd=Boolean(raw.atEnd);
20504
+ // Chromium may round a column offset down by a fraction of a CSS pixel.
20505
+ // Keep epub.js authoritative outside a 1px boundary, and for RTL layouts.
20506
+ if(visualPage!==null && total>0 && geometry && geometry.direction==='ltr' && geometry.pageWidth>0 && Number.isFinite(geometry.offset)){
20507
+ var boundary=Math.round(geometry.offset/geometry.pageWidth);
20508
+ if(Math.abs(geometry.offset-boundary*geometry.pageWidth)<=1){
20509
+ visualPage=Math.min(total,Math.max(1,boundary+1));
20510
+ atStart=chapter===(geometry.firstIndex ?? 0)+1 && visualPage===1;
20511
+ atEnd=chapter===(geometry.lastIndex ?? count-1)+1 && visualPage===total;
20512
+ }
20513
+ }
20514
+ var fraction=total?Math.max(0,(visualPage-1)/total):0;
20515
+ 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};
20516
+ }
20517
+ function installResizeAnchor(manager,readCfi){
20518
+ var original=manager.resize,anchor=null;
20519
+ function resize(width,height,target){
20520
+ if(validCfi(target))anchor=target;
20521
+ if(!anchor){var current=readCfi();if(validCfi(current))anchor=current;}
20522
+ // epub.js accepts a logical target as its third argument. Keep it across
20523
+ // keyboard/dialog reflows instead of adopting each new viewport start.
20524
+ return original.call(this,width,height,anchor || target);
20525
+ }
20526
+ manager.resize=resize;
20527
+ return {cfi:function(){return anchor;},invalidate:function(){anchor=null;},destroy:function(){if(manager.resize===resize)manager.resize=original;anchor=null;}};
20528
+ }
20529
+ function install(contents,config){
20530
+ var doc=contents.document,win=contents.window,start=null,blocked=false;
20531
+ 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;}
20532
+ function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
20533
+ function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
20534
+ function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
20535
+ 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();}
20536
+ function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
20537
+ 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);}
20538
+ function cancel(){start=null;blocked=true;}
20539
+ function link(e){if(config.current() && e.target?.closest?.('a') && config.interact)config.interact();}
20540
+ 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);
20541
+ return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);doc.removeEventListener('click',link);};
20542
+ }
20543
+ root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,installResizeAnchor:installResizeAnchor,install:install};
20544
+ })(typeof window==='undefined'?globalThis:window);
20545
+
20546
+ /* @papyrus-epub-reader:end */
20486
20547
  /* @papyrus-epub-annotations:start */
20487
20548
  (function (root, factory) {
20488
20549
  if (typeof module === 'object' && module.exports) module.exports = factory();
@@ -20561,6 +20622,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20561
20622
  var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
20562
20623
  var style = annotation.markupStyle || annotation.type;
20563
20624
  var opacity = Number.isFinite(annotation.opacity) ? Math.max(0,Math.min(1,annotation.opacity)) : 0.35;
20625
+ if(style === 'highlight')opacity=Math.min(0.35,opacity);
20564
20626
  rects.forEach(function(rect) {
20565
20627
  var mark = doc.createElement('div');
20566
20628
  var bodyRect=doc.body.getBoundingClientRect();
@@ -20573,8 +20635,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20573
20635
  if (hasNote) {
20574
20636
  var last = rects[rects.length-1], button = doc.createElement('button');
20575
20637
  button.textContent = '●'; button.setAttribute('aria-label',options.labels().comment || '');
20576
- 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';
20577
- var key = Math.floor(last.top/44);
20638
+ var pageWidth=Math.max(44, options.pageWidth ? options.pageWidth() : win.innerWidth);
20639
+ var bodyRect=doc.body.getBoundingClientRect(), column=Math.max(0,Math.floor((last.left-bodyRect.left)/pageWidth));
20640
+ 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';
20641
+ var key = column+":"+Math.floor(last.top/44);
20578
20642
  var neighbors=noteGroups.get(key);
20579
20643
  if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
20580
20644
  neighbors.ids.push(annotation.id);
@@ -20595,10 +20659,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20595
20659
  doc.addEventListener('click',tapMarkup);
20596
20660
  var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
20597
20661
  if (observer) observer.observe(doc.body);
20598
- win.addEventListener('resize',schedule); doc.addEventListener('load',schedule,true);
20662
+ win.addEventListener('resize',schedule);win.addEventListener('scroll',schedule); doc.addEventListener('load',schedule,true);
20599
20663
  if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
20600
20664
  schedule();
20601
- 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();}};
20665
+ 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();}};
20602
20666
  }
20603
20667
  return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20604
20668
  });
@@ -20611,6 +20675,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20611
20675
  let currentType = null;
20612
20676
  let book = null;
20613
20677
  let rendition = null;
20678
+ let epubResizeAnchor = null;
20614
20679
  let documentSessionId = null;
20615
20680
  let annotationDocumentId;
20616
20681
  let annotationLabels = {};
@@ -21023,6 +21088,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21023
21088
  };
21024
21089
 
21025
21090
  const clearViewer = () => {
21091
+ if(epubResizeAnchor){epubResizeAnchor.destroy();epubResizeAnchor=null;}
21026
21092
  epubAnnotationRenderers.forEach(renderer => renderer.destroy());
21027
21093
  epubAnnotationRenderers.clear();
21028
21094
  if (epubInteractionCleanup) {
@@ -21679,26 +21745,57 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21679
21745
  pageCount,
21680
21746
  });
21681
21747
 
21748
+ if (book.locations?.generate) {
21749
+ const activeBook=book;
21750
+ activeBook.locations.generate(150).then(() => {
21751
+ if (isCurrentLoad() && activeBook===book && epubLocation) {
21752
+ epubLocation.progress=activeBook.locations.percentageFromCfi(epubLocation.cfi);
21753
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
21754
+ }
21755
+ }).catch(() => {});
21756
+ }
21757
+ return renderEpubRendition(loadId,generation,null);
21758
+ };
21759
+
21760
+ const renderEpubRendition = async (loadId,generation,target) => {
21761
+ const diagnosticPayload={loadId};
21762
+ const isCurrentLoad=()=>generation===epubLoadGeneration;
21763
+ if(epubInteractionCleanup)epubInteractionCleanup();
21764
+ if(rendition)rendition.destroy();
21682
21765
  clearViewer();
21683
- rendition = book.renderTo(viewer, {
21684
- manager: 'continuous',
21685
- allowScriptedContent: false,
21686
- width: '100%',
21687
- height: '100%',
21688
- flow: 'scrolled-continuous',
21689
- spread: 'none',
21690
- });
21766
+ rendition = book.renderTo(viewer, window.PapyrusEpubReader.options(epubViewMode === 'single' ? 'paged' : 'chapter-scroll'));
21691
21767
  sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
21692
21768
 
21693
21769
  if (rendition && rendition.hooks && rendition.hooks.content) {
21694
21770
  const session = documentSessionId;
21695
21771
  const activeRendition = rendition;
21696
21772
  rendition.hooks.content.register((contents) => {
21773
+ const removeGestureListeners = window.PapyrusEpubReader.install(contents, {
21774
+ mode: () => epubViewMode === 'single' ? 'paged' : 'chapter-scroll',
21775
+ rtl: () => book.package?.metadata?.direction === 'rtl' || book.spine?.direction === 'rtl',
21776
+ blocked: () => Boolean(nativeMenuSelection && String(nativeMenuSelection.contents?.window?.getSelection() || '').trim()),
21777
+ annotationHit: point => epubAnnotationRenderers.get(contents)?.hit(point.x,point.y),
21778
+ current: () => session === documentSessionId && activeRendition === rendition,
21779
+ interact: () => epubResizeAnchor?.invalidate(),
21780
+ tap: () => sendEvent('EPUB_CONTENT_TAP',{documentSessionId:session}),
21781
+ turn: direction => {
21782
+ epubNavigationQueue = epubNavigationQueue.catch(()=>{}).then(() => {
21783
+ if(session !== documentSessionId || activeRendition !== rendition)return;
21784
+ epubResizeAnchor?.invalidate();
21785
+ return direction<0?activeRendition.prev():activeRendition.next();
21786
+ });
21787
+ epubNavigationQueue.catch(error => sendEvent('EPUB_NAVIGATION_ERROR',{message:String(error)}));
21788
+ },
21789
+ });
21790
+ const gestureCleanup = () => {removeGestureListeners();epubSelectionCleanups.delete(gestureCleanup);};
21791
+ epubSelectionCleanups.add(gestureCleanup);
21792
+ contents.__papyrusGestureCleanup=gestureCleanup;
21697
21793
  const href = spineItems[contents.sectionIndex]?.href;
21698
21794
  if (href && window.PapyrusEpubAnnotations) {
21699
21795
  const renderer = window.PapyrusEpubAnnotations.install(contents, {
21700
21796
  href, get documentId() { return annotationDocumentId; },
21701
21797
  isCurrent: () => session === documentSessionId && activeRendition === rendition,
21798
+ pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
21702
21799
  annotations: () => epubAnnotations, labels: () => annotationLabels,
21703
21800
  onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
21704
21801
  });
@@ -21717,8 +21814,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21717
21814
  typeof selectionWindow.getSelection === 'function'
21718
21815
  ) {
21719
21816
  let selectionTimer = null;
21720
- let lastSelectionKey = '';
21721
- let lastSelectionAt = 0;
21817
+
21722
21818
  const reportSelection = () => {
21723
21819
  selectionTimer = null;
21724
21820
  if (session !== documentSessionId || activeRendition !== rendition) return;
@@ -21733,11 +21829,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21733
21829
  try { cfi = contents.cfiFromRange(range); } catch (_) { return; }
21734
21830
  const anchor = window.PapyrusEpubAnnotations?.selection(contents, cfi, href, annotationDocumentId);
21735
21831
  if (!anchor) return;
21736
- const key = anchor.cfiRange + ':' + anchor.quote;
21737
- const now = Date.now();
21738
- if (key === lastSelectionKey && now - lastSelectionAt < 750) return;
21739
- lastSelectionKey = key;
21740
- lastSelectionAt = now;
21832
+ if (nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21741
21833
  const payload = {documentSessionId: session, text: anchor.quote,
21742
21834
  pageIndex: contents.sectionIndex + 1, anchor};
21743
21835
  nativeMenuSelection = {contents, payload};
@@ -21752,6 +21844,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21752
21844
  // The content document still emits selectionchange, so resolve its
21753
21845
  // live Range to a CFI before the native menu action can fire.
21754
21846
  if (text) reportSelection();
21847
+ else if (nativeMenuSelection?.contents === contents) nativeMenuSelection = null;
21755
21848
  };
21756
21849
  selectionDocument.addEventListener('selectionchange', handleSelectionChange);
21757
21850
  const selectionCleanup = () => {
@@ -21774,6 +21867,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21774
21867
  typeof rendition.hooks.unloaded.register === 'function'
21775
21868
  ) {
21776
21869
  rendition.hooks.unloaded.register((view) => {
21870
+ view?.contents?.__papyrusGestureCleanup?.();
21777
21871
  const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
21778
21872
  if (typeof cleanup === 'function') cleanup();
21779
21873
  const renderer = epubAnnotationRenderers.get(view?.contents);
@@ -21789,21 +21883,37 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21789
21883
  if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21790
21884
  const href = spineItems[contents.sectionIndex]?.href;
21791
21885
  const anchor = href && window.PapyrusEpubAnnotations?.selection(contents,cfiRange,href,annotationDocumentId);
21792
- if (!anchor) return;
21886
+ if (!anchor || nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21793
21887
  epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(true);
21794
21888
  const payload = {documentSessionId: selectionSession, text: anchor.quote, pageIndex: contents.sectionIndex+1, anchor};
21795
21889
  nativeMenuSelection = {contents, payload};
21796
21890
  sendEvent('EPUB_TEXT_SELECTED', payload);
21797
21891
  });
21798
- ['rendered','resized','layout'].forEach(name => rendition.on(name, () => {
21892
+ ['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
21799
21893
  if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
21800
21894
  }));
21801
21895
  rendition.on('relocated', (location) => {
21896
+ if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21802
21897
  const start = location && location.start;
21803
21898
  let index = start && Number.isInteger(start.index) ? start.index : -1;
21804
21899
  if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
21805
21900
  if (index < 0 || index >= spineItems.length) return;
21806
21901
  epubScrollDiagnostics && epubScrollDiagnostics.relocated(location);
21902
+ let pageGeometry;
21903
+ const manager=rendition.manager,layout=manager?.layout;
21904
+ if(epubViewMode==='single' && layout?.divisor===1 && !manager?.settings?.fullsize && manager?.settings?.direction==='ltr'){
21905
+ const visible=manager.visible?.();
21906
+ const view=visible?.find(view=>view.index===index);
21907
+ if(view && manager.container){
21908
+ pageGeometry={direction:'ltr',firstIndex:book.spine.first()?.index,lastIndex:book.spine.last()?.index,pageWidth:layout.pageWidth,offset:manager.container.getBoundingClientRect().left-view.position().left};
21909
+ }
21910
+ }
21911
+ epubLocation=window.PapyrusEpubReader.location(location,spineItems.length,epubViewMode==='single'?'paged':'chapter-scroll',pageGeometry);
21912
+ if(epubLocation){
21913
+ epubLocation.cfi=epubResizeAnchor?.cfi() || epubLocation.cfi;
21914
+ if(book.locations?.length?.()>0)epubLocation.progress=book.locations.percentageFromCfi(epubLocation.cfi);
21915
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
21916
+ }
21807
21917
  const nextPage = index + 1;
21808
21918
  if (nextPage === currentPage) return;
21809
21919
  currentPage = nextPage;
@@ -21811,21 +21921,23 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21811
21921
  });
21812
21922
  }
21813
21923
 
21924
+ applyEpubZoom();
21814
21925
  sendEpubDiagnostic('epub.display.start', diagnosticPayload);
21815
21926
  await withEpubStageTimeout(
21816
- displayEpubPage(0),
21927
+ rendition.display(target || spineItems[0]?.href),
21817
21928
  'rendition.display',
21818
21929
  diagnosticPayload,
21819
21930
  );
21820
21931
  if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
21821
21932
  sendEpubDiagnostic('epub.display.end', diagnosticPayload);
21933
+ const resizeRendition=rendition;
21934
+ epubResizeAnchor=window.PapyrusEpubReader.installResizeAnchor(resizeRendition.manager,()=>resizeRendition.location?.start?.cfi);
21822
21935
  if (
21823
21936
  rendition &&
21824
21937
  rendition.manager &&
21825
21938
  typeof rendition.manager.on === 'function' &&
21826
21939
  typeof rendition.on === 'function'
21827
21940
  ) {
21828
- let touchStart = null;
21829
21941
  let lastScrollOffset = -1;
21830
21942
  const manager = rendition.manager;
21831
21943
  epubScrollDiagnostics = createEpubScrollDiagnostics(manager);
@@ -21900,48 +22012,19 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21900
22012
  return result;
21901
22013
  };
21902
22014
  }
21903
- const getTouchPoint = (event) => {
21904
- const touch =
21905
- (event && event.changedTouches && event.changedTouches[0]) ||
21906
- (event && event.touches && event.touches[0]);
21907
- if (!touch) return null;
21908
- const x = Number(touch.screenX ?? touch.clientX);
21909
- const y = Number(touch.screenY ?? touch.clientY);
21910
- return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
21911
- };
21912
- const handleScroll = (event) => {
22015
+ const handleScroll = event => {
21913
22016
  const offsetY = Math.max(0, Number(event && event.top) || 0);
21914
22017
  epubScrollDiagnostics && epubScrollDiagnostics.scroll(event);
21915
- if (Math.abs(offsetY - lastScrollOffset) < 1) return;
21916
- lastScrollOffset = offsetY;
21917
- sendEvent('VIEWER_SCROLL', { offsetY });
22018
+ if (Math.abs(offsetY-lastScrollOffset)<1) return;
22019
+ lastScrollOffset=offsetY;
22020
+ sendEvent('VIEWER_SCROLL',{offsetY});
21918
22021
  };
21919
- const handleTouchStart = (event) => {
21920
- touchStart = getTouchPoint(event);
21921
- epubScrollDiagnostics && epubScrollDiagnostics.touchStart(event);
21922
- };
21923
- const handleTouchMove = (event) => {
21924
- epubScrollDiagnostics && epubScrollDiagnostics.touchMove(event);
21925
- };
21926
- const handleTouchEnd = (event) => {
21927
- const end = getTouchPoint(event);
21928
- const start = touchStart;
21929
- touchStart = null;
21930
- const selectedText = event?.target?.ownerDocument?.defaultView?.getSelection?.()?.toString().trim() || '';
21931
- if (!selectedText && start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
21932
- sendEvent('VIEWER_TAP', {});
21933
- }
21934
- epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
21935
- };
21936
-
21937
22022
  manager.on('scroll', handleScroll);
21938
22023
  const handleScrolled = (event) => {
21939
22024
  epubScrollDiagnostics && epubScrollDiagnostics.settled(event);
21940
22025
  };
21941
22026
  manager.on('scrolled', handleScrolled);
21942
- rendition.on('touchstart', handleTouchStart);
21943
- rendition.on('touchmove', handleTouchMove);
21944
- rendition.on('touchend', handleTouchEnd);
22027
+
21945
22028
  epubInteractionCleanup = () => {
21946
22029
  epubSelectionCleanups.forEach((cleanup) => cleanup());
21947
22030
  epubSelectionCleanups.clear();
@@ -21950,16 +22033,9 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21950
22033
  if (originalEnqueue) queue.enqueue = originalEnqueue;
21951
22034
  if (typeof manager.off === 'function') manager.off('scroll', handleScroll);
21952
22035
  if (typeof manager.off === 'function') manager.off('scrolled', handleScrolled);
21953
- if (typeof rendition.off === 'function') {
21954
- rendition.off('touchstart', handleTouchStart);
21955
- rendition.off('touchmove', handleTouchMove);
21956
- rendition.off('touchend', handleTouchEnd);
21957
- }
21958
22036
  epubScrollDiagnostics = null;
21959
22037
  };
21960
22038
  }
21961
- applyEpubZoom();
21962
-
21963
22039
  const outline = await withEpubStageTimeout(
21964
22040
  buildOutline(),
21965
22041
  'outline',
@@ -21973,6 +22049,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21973
22049
  return { pageCount, outline };
21974
22050
  };
21975
22051
 
22052
+ let epubNavigationQueue = Promise.resolve();
22053
+ let epubNavigationRequest = 0;
21976
22054
  const displayEpubPage = async (pageIndex) => {
21977
22055
  if (!rendition) return;
21978
22056
  const item = spineItems[pageIndex];
@@ -21982,9 +22060,19 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21982
22060
  target,
21983
22061
  pageIndex,
21984
22062
  });
21985
- await rendition.display(target);
21986
- currentPage = pageIndex + 1;
21987
- sendState();
22063
+ const activeRendition = rendition;
22064
+ const session = documentSessionId;
22065
+ const request = ++epubNavigationRequest;
22066
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22067
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22068
+ epubResizeAnchor?.invalidate();
22069
+ await activeRendition.display(target);
22070
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22071
+ currentPage = pageIndex + 1;
22072
+ sendState();
22073
+ });
22074
+ epubNavigationQueue = navigation;
22075
+ await navigation;
21988
22076
  };
21989
22077
 
21990
22078
  const getTextContent = async (pageIndex) => {
@@ -22086,6 +22174,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22086
22174
  };
22087
22175
 
22088
22176
  let nativeMenuSelection = null;
22177
+ let epubViewMode = "single";
22178
+ let epubLocation = null;
22089
22179
 
22090
22180
  const handleCommand = async (message) => {
22091
22181
  const { id, kind, payload } = message;
@@ -22098,6 +22188,11 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22098
22188
  epubAnnotations = [];
22099
22189
  annotationDocumentId = undefined;
22100
22190
  zoom = 1.0;
22191
+ epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
22192
+ epubLocation = null;
22193
+ nativeMenuSelection = null;
22194
+ epubNavigationRequest += 1;
22195
+ epubNavigationQueue = Promise.resolve();
22101
22196
 
22102
22197
  if (currentType === 'text') {
22103
22198
  const result = await loadText(
@@ -22152,17 +22247,24 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22152
22247
  epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22153
22248
  } else {
22154
22249
  const annotation = epubAnnotations.find(a => a.id === payload.annotationId);
22155
- if (!annotation) throw new Error('Annotation not available');
22250
+ if (!annotation || (annotation.anchor.documentId && annotation.anchor.documentId !== annotationDocumentId)) throw new Error('Annotation not available for this document');
22156
22251
  // Navigation uses the CFI, never a synthetic percentage/page rectangle.
22157
22252
  const navigationSession = documentSessionId;
22158
22253
  const activeRendition = rendition;
22159
22254
  // Open the confirmed chapter first, resolve its quote, then navigate its actual range.
22160
- await activeRendition.display(annotation.anchor.href);
22161
- if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22162
- const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22163
- const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22164
- if(!range)throw new Error('EPUB anchor unresolved');
22165
- await activeRendition.display(contents.cfiFromRange(range));
22255
+ epubNavigationRequest += 1;
22256
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22257
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22258
+ epubResizeAnchor?.invalidate();
22259
+ await activeRendition.display(annotation.anchor.href);
22260
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22261
+ const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22262
+ const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22263
+ if(!range)throw new Error('EPUB anchor unresolved');
22264
+ await activeRendition.display(contents.cfiFromRange(range));
22265
+ });
22266
+ epubNavigationQueue = navigation;
22267
+ await navigation;
22166
22268
  }
22167
22269
  sendResponse(id,true,null); return;
22168
22270
  }
@@ -22189,12 +22291,55 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22189
22291
  return;
22190
22292
  }
22191
22293
 
22294
+ if (kind === 'epub-turn-page') {
22295
+ if (epubViewMode !== 'single') {sendResponse(id,true,null); return;}
22296
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB navigation');
22297
+ const active = rendition;
22298
+ epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
22299
+ if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
22300
+ epubResizeAnchor?.invalidate();
22301
+ return payload.direction < 0 ? active.prev() : active.next();
22302
+ });
22303
+ await epubNavigationQueue;
22304
+ sendResponse(id,true,null); return;
22305
+ }
22306
+
22307
+ if (kind === 'set-epub-view-mode') {
22308
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB layout session');
22309
+ const next=payload.epubMode ? (payload.epubMode==='paged'?'single':'continuous') : (payload.viewMode==='single'?'single':'continuous');
22310
+ {
22311
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22312
+ if(payload.documentSessionId!==documentSessionId || next===epubViewMode)return;
22313
+ const target=epubResizeAnchor?.cfi() || epubLocation?.cfi;
22314
+ epubViewMode=next;
22315
+ await renderEpubRendition('layout',epubLoadGeneration,target);
22316
+ });
22317
+ await epubNavigationQueue;
22318
+ }
22319
+ sendResponse(id,true,null);return;
22320
+ }
22321
+ if(kind === 'epub-go-to-location'){
22322
+ if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
22323
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
22324
+ await epubNavigationQueue;sendResponse(id,true,null);return;
22325
+ }
22326
+
22192
22327
  if (kind === 'set-zoom') {
22193
- zoom = Math.max(0.5, Math.min(4.0, payload.zoom || 1.0));
22194
- applyZoom();
22195
- sendState();
22196
- sendResponse(id, true, { zoom });
22197
- return;
22328
+ const requestedZoom=Math.max(0.5,Math.min(4.0,payload.zoom || 1.0));
22329
+ if(currentType === 'epub') {
22330
+ const session=documentSessionId;
22331
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22332
+ if(session!==documentSessionId)return;
22333
+ const active=rendition;
22334
+ const cfi=epubResizeAnchor?.cfi() || epubLocation?.cfi;
22335
+ zoom=requestedZoom;applyEpubZoom();
22336
+ // Let the content CSS update before redisplaying the logical anchor.
22337
+ await new Promise(resolve=>(window.requestAnimationFrame || (callback=>setTimeout(callback,0)))(resolve));
22338
+ if(cfi && active===rendition && session===documentSessionId)await active.display(cfi);
22339
+ });
22340
+ await epubNavigationQueue;
22341
+ } else {zoom=requestedZoom;applyZoom();}
22342
+ sendState();sendResponse(id,true,{zoom});return;
22198
22343
  }
22199
22344
 
22200
22345
  if (kind === 'set-rotation') {