@readium/navigator 2.6.1 → 2.7.1

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.
Files changed (125) hide show
  1. package/LICENSE +28 -0
  2. package/dist/Navigator.js +1 -0
  3. package/dist/_virtual/index.js +1 -0
  4. package/dist/_virtual/preload-helper.js +1 -0
  5. package/dist/audio/AudioNavigator.js +1 -0
  6. package/dist/audio/AudioPoolManager.js +1 -0
  7. package/dist/audio/engine/PreservePitchProcessor.js +215 -0
  8. package/dist/audio/engine/PreservePitchWorklet.js +1 -0
  9. package/dist/audio/engine/WebAudioEngine.js +1 -0
  10. package/dist/audio/preferences/AudioDefaults.js +1 -0
  11. package/dist/audio/preferences/AudioPreferences.js +1 -0
  12. package/dist/audio/preferences/AudioPreferencesEditor.js +1 -0
  13. package/dist/audio/preferences/AudioSettings.js +1 -0
  14. package/dist/audio/protection/AudioNavigatorProtector.js +1 -0
  15. package/dist/css/Properties.js +1 -0
  16. package/dist/dom/_readium_cssSelectorGenerator.js +1 -0
  17. package/dist/dom/_readium_executionCleanup.js +14 -0
  18. package/dist/dom/_readium_executionPrevention.js +66 -0
  19. package/dist/dom/_readium_webpubExecution.js +5 -0
  20. package/dist/epub/EpubNavigator.js +1 -0
  21. package/dist/epub/css/Properties.js +1 -0
  22. package/dist/epub/css/ReadiumCSS.js +1 -0
  23. package/dist/epub/frame/FrameBlobBuilder.js +1 -0
  24. package/dist/epub/frame/FrameComms.js +1 -0
  25. package/dist/epub/frame/FrameManager.js +1 -0
  26. package/dist/epub/frame/FramePoolManager.js +1 -0
  27. package/dist/epub/fxl/FXLCoordinator.js +1 -0
  28. package/dist/epub/fxl/FXLFrameManager.js +1 -0
  29. package/dist/epub/fxl/FXLFramePoolManager.js +1 -0
  30. package/dist/epub/fxl/FXLPeripherals.js +4 -0
  31. package/dist/epub/fxl/FXLPeripheralsDebug.js +1 -0
  32. package/dist/epub/fxl/FXLSpreader.js +1 -0
  33. package/dist/epub/preferences/EpubDefaults.js +1 -0
  34. package/dist/epub/preferences/EpubPreferences.js +1 -0
  35. package/dist/epub/preferences/EpubPreferencesEditor.js +1 -0
  36. package/dist/epub/preferences/EpubSettings.js +1 -0
  37. package/dist/helpers/dimensions.js +1 -0
  38. package/dist/helpers/dist/sML.js +1 -0
  39. package/dist/helpers/lineLength.js +1 -0
  40. package/dist/helpers/minify.js +1 -0
  41. package/dist/helpers/scriptMode.js +1 -0
  42. package/dist/index.js +1 -11006
  43. package/dist/injection/Injector.js +1 -0
  44. package/dist/injection/epubInjectables.js +1 -0
  45. package/dist/injection/webpubInjectables.js +1 -0
  46. package/dist/{ReadiumCSS-after-D7unrNI9.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/ReadiumCSS-after.css.js} +2 -5
  47. package/dist/{ReadiumCSS-before-DwBLxUVH.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/ReadiumCSS-before.css.js} +2 -5
  48. package/dist/{ReadiumCSS-default-AIAk8uwU.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/ReadiumCSS-default.css.js} +2 -5
  49. package/dist/{ReadiumCSS-ebpaj_fonts_patch-Dt2XliTg.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/ReadiumCSS-ebpaj_fonts_patch.css.js} +2 -5
  50. package/dist/{ReadiumCSS-after-XUKPAxfT.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/cjk-horizontal/ReadiumCSS-after.css.js} +2 -5
  51. package/dist/{ReadiumCSS-before-8FMq19-x.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/cjk-horizontal/ReadiumCSS-before.css.js} +2 -5
  52. package/dist/{ReadiumCSS-default-N65xNiIp.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/cjk-horizontal/ReadiumCSS-default.css.js} +2 -5
  53. package/dist/{ReadiumCSS-after-ClF4TBzj.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/cjk-vertical/ReadiumCSS-after.css.js} +2 -5
  54. package/dist/{ReadiumCSS-before-BNTwR8Qm.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/cjk-vertical/ReadiumCSS-before.css.js} +2 -5
  55. package/dist/{ReadiumCSS-default-BesyZHRU.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/cjk-vertical/ReadiumCSS-default.css.js} +2 -5
  56. package/dist/{ReadiumCSS-after-d5mC4cme.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/rtl/ReadiumCSS-after.css.js} +2 -5
  57. package/dist/{ReadiumCSS-before-CG-KmDa3.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/rtl/ReadiumCSS-before.css.js} +2 -5
  58. package/dist/{ReadiumCSS-default-BhdLiyWp.js → node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/rtl/ReadiumCSS-default.css.js} +2 -5
  59. package/dist/node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/dist/webPub/ReadiumCSS-webPub.css.js +274 -0
  60. package/dist/node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/vars/colors.json.js +1 -0
  61. package/dist/node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/vars/experiments.json.js +1 -0
  62. package/dist/node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/vars/fontStacks.json.js +1 -0
  63. package/dist/node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/vars/i18n.json.js +1 -0
  64. package/dist/node_modules/.pnpm/@readium_css@2.0.5/node_modules/@readium/css/css/vars/settings.json.js +1 -0
  65. package/dist/node_modules/.pnpm/path-browserify@1.0.1/node_modules/path-browserify/index.js +1 -0
  66. package/dist/peripherals/KeyboardConditionBridge.js +1 -0
  67. package/dist/peripherals/KeyboardPeripherals.js +1 -0
  68. package/dist/preferences/Preference.js +1 -0
  69. package/dist/preferences/Types.js +1 -0
  70. package/dist/preferences/guards.js +1 -0
  71. package/dist/protection/AutomationDetector.js +1 -0
  72. package/dist/protection/ContextMenuProtector.js +1 -0
  73. package/dist/protection/CopyProtector.js +1 -0
  74. package/dist/protection/DevToolsDetector.js +1 -0
  75. package/dist/protection/DragAndDropProtector.js +1 -0
  76. package/dist/protection/IframeEmbeddingDetector.js +1 -0
  77. package/dist/protection/NavigatorProtector.js +1 -0
  78. package/dist/protection/PrintProtector.js +15 -0
  79. package/dist/protection/utils/WorkerConsole.js +12 -0
  80. package/dist/protection/utils/console.js +1 -0
  81. package/dist/protection/utils/match.js +1 -0
  82. package/dist/protection/utils/platform.js +1 -0
  83. package/dist/webpub/WebPubBlobBuilder.js +2 -0
  84. package/dist/webpub/WebPubFrameManager.js +1 -0
  85. package/dist/webpub/WebPubFramePoolManager.js +1 -0
  86. package/dist/webpub/WebPubNavigator.js +1 -0
  87. package/dist/webpub/css/Properties.js +1 -0
  88. package/dist/webpub/css/WebPubCSS.js +1 -0
  89. package/dist/webpub/preferences/WebPubDefaults.js +1 -0
  90. package/dist/webpub/preferences/WebPubPreferences.js +1 -0
  91. package/dist/webpub/preferences/WebPubPreferencesEditor.js +1 -0
  92. package/dist/webpub/preferences/WebPubSettings.js +1 -0
  93. package/package.json +16 -14
  94. package/src/Navigator.ts +4 -1
  95. package/src/audio/AudioNavigator.ts +8 -8
  96. package/src/decorations/index.ts +24 -127
  97. package/src/epub/EpubNavigator.ts +149 -20
  98. package/src/epub/frame/FrameManager.ts +8 -3
  99. package/src/epub/frame/FramePoolManager.ts +5 -2
  100. package/src/epub/fxl/FXLCoordinator.ts +1 -1
  101. package/src/epub/fxl/FXLFrameManager.ts +1 -1
  102. package/src/epub/preferences/EpubDefaults.ts +1 -1
  103. package/src/epub/preferences/EpubSettings.ts +1 -1
  104. package/src/protection/DevToolsDetector.ts +1 -1
  105. package/src/webpub/WebPubFrameManager.ts +9 -3
  106. package/src/webpub/WebPubFramePoolManager.ts +5 -2
  107. package/src/webpub/WebPubNavigator.ts +100 -19
  108. package/src/webpub/preferences/WebPubDefaults.ts +1 -1
  109. package/src/webpub/preferences/WebPubSettings.ts +1 -1
  110. package/types/src/Navigator.d.ts +3 -1
  111. package/types/src/decorations/index.d.ts +5 -59
  112. package/types/src/epub/EpubNavigator.d.ts +13 -3
  113. package/types/src/epub/frame/FrameManager.d.ts +2 -1
  114. package/types/src/epub/frame/FramePoolManager.d.ts +2 -1
  115. package/types/src/epub/fxl/FXLFramePoolManager.d.ts +1 -1
  116. package/types/src/webpub/WebPubFrameManager.d.ts +2 -1
  117. package/types/src/webpub/WebPubFramePoolManager.d.ts +2 -1
  118. package/types/src/webpub/WebPubNavigator.d.ts +12 -3
  119. package/dist/ar-DyHX_uy2.js +0 -7
  120. package/dist/da-Dct0PS3E.js +0 -7
  121. package/dist/fr-C5HEel98.js +0 -7
  122. package/dist/index.umd.cjs +0 -5615
  123. package/dist/it-DFOBoXGy.js +0 -7
  124. package/dist/pt_PT-Di3sVjze.js +0 -7
  125. package/dist/sv-BfzAFsVN.js +0 -7
@@ -1,9 +1,9 @@
1
- import { Layout, Link, Locator, LocatorText, Profile, Publication, ReadingProgression } from "@readium/shared";
1
+ import { Layout, Link, Locator, LocatorText, Profile, Publication, ReadingProgression, Timeline, TimelineItem, getCssSelector, getHtmlId } from "@readium/shared";
2
2
  import { Configurable, ConfigurableSettings, LineLengths, ProgressionRange, VisualNavigator, VisualNavigatorViewport } from "../index.ts";
3
3
  import { FramePoolManager } from "./frame/FramePoolManager.ts";
4
4
  import { FXLFramePoolManager } from "./fxl/FXLFramePoolManager.ts";
5
- import { CommsEventKey, ContextMenuEvent, DecorationActivatedEvent, FXLModules, ModuleLibrary, ModuleName, ReflowableModules, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent, KeyboardPeripheralEvent } from "@readium/navigator-html-injectables";
6
- import { Decoration, DecorationActivationEvent, DecorationObserver, DecorableNavigator, DecoratorConfig, decorationsEqual, resolveDecorationForWire, BUILTIN_DECORATION_TYPES } from "../decorations/index.ts";
5
+ import { CommsEventKey, ContextMenuEvent, DecorationActivatedEvent, DecorationPointerEnterData, DecorationPointerLeaveData, FXLModules, ModuleLibrary, ModuleName, ReflowableModules, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent, KeyboardPeripheralEvent } from "@readium/navigator-html-injectables";
6
+ import { Decoration, OnDecorationActivatedEvent, OnDecorationPointerEnterEvent, OnDecorationPointerLeaveEvent, DecorationObserver, DecorableNavigator, DecoratorConfig, decorationsEqual, resolveDecorationForWire, supportsDecorationStyle as canRenderDecorationStyle, DecorationStyleType } from "../decorations/index.ts";
7
7
  import * as path from "path-browserify";
8
8
  import { FXLFrameManager } from "./fxl/FXLFrameManager.ts";
9
9
  import { FrameManager } from "./frame/FrameManager.ts";
@@ -36,6 +36,7 @@ export interface EpubNavigatorConfiguration {
36
36
  export interface EpubNavigatorListeners {
37
37
  frameLoaded: (wnd: Window) => void;
38
38
  positionChanged: (locator: Locator) => void;
39
+ timelineItemChanged: (item: TimelineItem | undefined) => void;
39
40
  tap: (e: FrameClickEvent) => boolean; // Return true to prevent handling here
40
41
  click: (e: FrameClickEvent) => boolean; // Return true to prevent handling here
41
42
  zoom: (scale: number) => void;
@@ -53,6 +54,7 @@ export interface EpubNavigatorListeners {
53
54
  const defaultListeners = (listeners: EpubNavigatorListeners): EpubNavigatorListeners => ({
54
55
  frameLoaded: listeners.frameLoaded || (() => {}),
55
56
  positionChanged: listeners.positionChanged || (() => {}),
57
+ timelineItemChanged: listeners.timelineItemChanged || (() => {}),
56
58
  tap: listeners.tap || (() => false),
57
59
  click: listeners.click || (() => false),
58
60
  zoom: listeners.zoom || (() => {}),
@@ -73,6 +75,8 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
73
75
  private framePool!: FramePoolManager | FXLFramePoolManager;
74
76
  private positions!: Locator[];
75
77
  private currentLocation!: Locator;
78
+ private _currentTimelineItem: TimelineItem | undefined;
79
+ private _timelineAugmented = false;
76
80
  private lastLocationInView: Locator | undefined;
77
81
  private currentProgression: ReadingProgression;
78
82
  private _layout: Layout;
@@ -99,7 +103,9 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
99
103
 
100
104
  private _decorations: Map<string, Decoration[]> = new Map();
101
105
  private _decorationObservers: Map<string, Set<DecorationObserver>> = new Map();
106
+ private _decorationHoveredDecorations: Map<string, Decoration> = new Map();
102
107
  private _decorationActivationState: Map<string, boolean> = new Map();
108
+ private _decorationHoverState: Map<string, boolean> = new Map();
103
109
  private _decorationActivationConsumed = false;
104
110
 
105
111
  private reflowViewport: VisualNavigatorViewport = {
@@ -270,7 +276,11 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
270
276
  cssProperties,
271
277
  this._injector,
272
278
  this._contentProtection,
273
- this._keyboardPeripherals
279
+ this._keyboardPeripherals,
280
+ (href) => this.pub.timeline.segmentsForHref(href)
281
+ .flatMap(item => item.references)
282
+ .map(ref => { const h = ref.indexOf('#'); return h >= 0 ? ref.slice(h + 1) : ''; })
283
+ .filter(Boolean)
274
284
  );
275
285
  }
276
286
 
@@ -436,6 +446,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
436
446
  case "_pong":
437
447
  this.listeners.frameLoaded(this._cframes[0]!.iframe.contentWindow!);
438
448
  this.listeners.positionChanged(this.currentLocation);
449
+ this._notifyTimelineChange(this.currentLocation);
439
450
  if (sourceFrame) {
440
451
  const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
441
452
  const i = frames.indexOf(sourceFrame);
@@ -456,6 +467,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
456
467
  text: loc?.text
457
468
  });
458
469
  this.listeners.positionChanged(this.currentLocation);
470
+ this._notifyTimelineChange(this.currentLocation);
459
471
  break;
460
472
  case "text_selected": {
461
473
  const selection = data as BasicTextSelection;
@@ -476,6 +488,12 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
476
488
  if (handled) this._decorationActivationConsumed = true;
477
489
  break;
478
490
  }
491
+ case "decoration_pointer_enter":
492
+ this._handleDecorationPointerEnter(data as DecorationPointerEnterData);
493
+ break;
494
+ case "decoration_pointer_leave":
495
+ this._handleDecorationPointerLeave(data as DecorationPointerLeaveData);
496
+ break;
479
497
  case "click":
480
498
  case "tap":
481
499
  if (this._decorationActivationConsumed) {
@@ -645,9 +663,8 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
645
663
 
646
664
  // DecorableNavigator
647
665
 
648
- public supportsDecorationStyle(styleTypeId: string): boolean {
649
- return BUILTIN_DECORATION_TYPES.has(styleTypeId) ||
650
- !!this._decoratorConfig.decorationTemplates?.[styleTypeId];
666
+ public supportsDecorationStyle(styleTypeId: DecorationStyleType | string): boolean {
667
+ return canRenderDecorationStyle(styleTypeId, this._decoratorConfig.decorationTemplates);
651
668
  }
652
669
 
653
670
  public registerDecorationObserver(group: string, observer: DecorationObserver): void {
@@ -655,19 +672,34 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
655
672
  this._decorationObservers.set(group, new Set());
656
673
  this._decorationObservers.get(group)!.add(observer);
657
674
 
658
- // Store activation state and send to current frames
659
- this._decorationActivationState.set(group, true);
660
- this._sendDecorationActivationToFrames(group, true);
675
+ // Enable activation if the observer handles it
676
+ if (observer.onDecorationActivated) {
677
+ this._decorationActivationState.set(group, true);
678
+ this._sendDecorationActivationToFrames(group, true);
679
+ }
680
+
681
+ // Enable hover if the observer declares hover callbacks
682
+ if (observer.onDecorationPointerEnter || observer.onDecorationPointerLeave) {
683
+ this._decorationHoverState.set(group, true);
684
+ this._sendDecorationHoverToFrames(group, true);
685
+ }
661
686
  }
662
687
 
663
688
  public unregisterDecorationObserver(observer: DecorationObserver): void {
664
689
  this._decorationObservers.forEach((set, group) => {
665
- if (set.has(observer)) {
666
- set.delete(observer);
667
- if (set.size === 0) {
668
- this._decorationActivationState.delete(group);
669
- this._sendDecorationActivationToFrames(group, false);
670
- }
690
+ if (!set.has(observer)) return;
691
+ set.delete(observer);
692
+
693
+ const stillActivatable = [...set].some(o => o.onDecorationActivated);
694
+ if (this._decorationActivationState.has(group) && !stillActivatable) {
695
+ this._decorationActivationState.delete(group);
696
+ this._sendDecorationActivationToFrames(group, false);
697
+ }
698
+
699
+ const stillHoverable = [...set].some(o => o.onDecorationPointerEnter || o.onDecorationPointerLeave);
700
+ if (this._decorationHoverState.has(group) && !stillHoverable) {
701
+ this._decorationHoverState.delete(group);
702
+ this._sendDecorationHoverToFrames(group, false);
671
703
  }
672
704
  });
673
705
  }
@@ -679,6 +711,13 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
679
711
  });
680
712
  }
681
713
 
714
+ private _sendDecorationHoverToFrames(group: string, hoverable: boolean): void {
715
+ const frames = this._cframes.filter(f => !!f) as (FrameManager | FXLFrameManager)[];
716
+ frames.forEach(f => {
717
+ if (f.msg) f.msg.send("decoration_hoverable", { group, hoverable });
718
+ });
719
+ }
720
+
682
721
  public applyDecorations(decorations: Decoration[], group: string): void {
683
722
  const previous = this._decorations.get(group) ?? [];
684
723
  const prevById = new Map(previous.map(d => [d.id, d]));
@@ -706,6 +745,10 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
706
745
  if (activatable !== undefined) {
707
746
  this._sendDecorationActivationToFrames(group, activatable);
708
747
  }
748
+ const hoverable = this._decorationHoverState.get(group);
749
+ if (hoverable !== undefined) {
750
+ this._sendDecorationHoverToFrames(group, hoverable);
751
+ }
709
752
  }
710
753
 
711
754
  private _sendDecorationOps(
@@ -751,6 +794,9 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
751
794
  for (const [group, activatable] of this._decorationActivationState) {
752
795
  frame.msg.send("decoration_activatable", { group, activatable });
753
796
  }
797
+ for (const [group, hoverable] of this._decorationHoverState) {
798
+ frame.msg.send("decoration_hoverable", { group, hoverable });
799
+ }
754
800
  }
755
801
 
756
802
  private _reapplyDecorationsToCurrentFrames(): void {
@@ -769,13 +815,36 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
769
815
  const decoration = (this._decorations.get(data.group) ?? []).find(d => d.id === data.decorationId);
770
816
  if (!decoration) return false;
771
817
 
772
- const event: DecorationActivationEvent = { decoration, group: data.group, rect: data.rect, point: data.point };
818
+ const event: OnDecorationActivatedEvent = { decoration, group: data.group, rect: data.rect, point: data.point };
773
819
  let anyHandled = false;
774
820
  for (const obs of observers)
775
- if (obs.onDecorationActivated(event)) anyHandled = true;
821
+ if (obs.onDecorationActivated?.(event)) anyHandled = true;
776
822
  return anyHandled;
777
823
  }
778
824
 
825
+ private _handleDecorationPointerEnter(data: DecorationPointerEnterData): void {
826
+ const observers = this._decorationObservers.get(data.group);
827
+ if (!observers || observers.size === 0) return;
828
+ const decoration = (this._decorations.get(data.group) ?? []).find(d => d.id === data.decorationId);
829
+ if (!decoration) return;
830
+ this._decorationHoveredDecorations.set(data.group, decoration);
831
+ const event: OnDecorationPointerEnterEvent = { decoration, group: data.group, rect: data.rect, point: data.point };
832
+ for (const obs of observers)
833
+ obs.onDecorationPointerEnter?.(event);
834
+ }
835
+
836
+ private _handleDecorationPointerLeave(data: DecorationPointerLeaveData): void {
837
+ const observers = this._decorationObservers.get(data.group);
838
+ if (!observers || observers.size === 0) return;
839
+ const decoration = (this._decorations.get(data.group) ?? []).find(d => d.id === data.decorationId)
840
+ ?? this._decorationHoveredDecorations.get(data.group);
841
+ this._decorationHoveredDecorations.delete(data.group);
842
+ if (!decoration) return;
843
+ const event: OnDecorationPointerLeaveEvent = { decoration, group: data.group, rect: data.rect, point: data.point };
844
+ for (const obs of observers)
845
+ obs.onDecorationPointerLeave?.(event);
846
+ }
847
+
779
848
  // End of Decoration
780
849
 
781
850
  public async destroy() {
@@ -790,7 +859,9 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
790
859
  await this.framePool?.destroy();
791
860
  this._decorations.clear();
792
861
  this._decorationObservers.clear();
862
+ this._decorationHoveredDecorations.clear();
793
863
  this._decorationActivationState.clear();
864
+ this._decorationHoverState.clear();
794
865
  }
795
866
 
796
867
  private async changeResource(relative: number): Promise<boolean> {
@@ -829,6 +900,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
829
900
  // positionChanged via _pong; on back-then-forward to an
830
901
  // already-loaded frame no _pong fires, so dispatch explicitly here.
831
902
  this.listeners.positionChanged(this.currentLocation);
903
+ this._notifyTimelineChange(this.currentLocation);
832
904
  return true;
833
905
  }
834
906
 
@@ -931,6 +1003,10 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
931
1003
  this.lastLocationInView = nearestPositions.last;
932
1004
  this.updateViewport(progression);
933
1005
  this.listeners.positionChanged(this.currentLocation);
1006
+ const locatorForTimeline = progression.fragmentId
1007
+ ? this.currentLocation.copyWithLocations({ fragments: [`#${progression.fragmentId}`] })
1008
+ : this.currentLocation;
1009
+ this._notifyTimelineChange(locatorForTimeline);
934
1010
  await this.framePool.update(this.pub, this.currentLocation, this.determineModules());
935
1011
  }
936
1012
 
@@ -1033,9 +1109,54 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
1033
1109
  return this.pub;
1034
1110
  }
1035
1111
 
1112
+ get timeline(): Timeline {
1113
+ const t = this.pub.timeline;
1114
+ if (!this._timelineAugmented && this.positions?.length) {
1115
+ const positions = this.positions;
1116
+ // We have no way to tell which position within an href corresponds to which
1117
+ // toc entry (fragment ids aren't resolvable against the positions list). So
1118
+ // only the first *leaf* entry for a given href without a fragment match gets
1119
+ // the resource's start position; every other leaf entry sharing that href is
1120
+ // left without a position rather than being given one we can't actually derive.
1121
+ // Container items (chapters with their own toc children) are skipped entirely
1122
+ // here — otherwise the chapter itself would claim the one derivable slot before
1123
+ // any of its displayed children get a turn.
1124
+ const assignedHrefs = new Set<string>();
1125
+ t.augment((item, link) => {
1126
+ if (item.children?.length) return {};
1127
+ const hashIndex = link.href.indexOf('#');
1128
+ const bare = hashIndex >= 0 ? link.href.slice(0, hashIndex) : link.href;
1129
+ const fragment = hashIndex >= 0 ? link.href.slice(hashIndex + 1) : undefined;
1130
+ const entries = positions.filter(p => p.href === bare);
1131
+ if (!entries.length) return {};
1132
+ const atFragment = fragment
1133
+ ? entries.find(p => p.locations.fragments[0] === fragment)
1134
+ : undefined;
1135
+ let candidate: Locator | undefined;
1136
+ if (atFragment) {
1137
+ candidate = atFragment;
1138
+ } else if (!assignedHrefs.has(bare)) {
1139
+ assignedHrefs.add(bare);
1140
+ candidate = entries.reduce((min, p) =>
1141
+ (p.locations.position ?? Infinity) < (min.locations.position ?? Infinity) ? p : min
1142
+ );
1143
+ } else {
1144
+ candidate = undefined;
1145
+ }
1146
+ if (!candidate) return {};
1147
+ return {
1148
+ position: candidate.locations.position,
1149
+ scroll: atFragment?.locations.progression,
1150
+ };
1151
+ });
1152
+ this._timelineAugmented = true;
1153
+ }
1154
+ return t;
1155
+ }
1156
+
1036
1157
  private async loadLocator(locator: Locator, cb: (ok: boolean) => void) {
1037
1158
  let done = false;
1038
- let cssSelector = (typeof locator.locations.getCssSelector === "function") && locator.locations.getCssSelector();
1159
+ let cssSelector = getCssSelector(locator.locations);
1039
1160
  if(locator.text?.highlight) {
1040
1161
  done = await new Promise<boolean>((res, _) => {
1041
1162
  // Attempt to go to a highlighted piece of text in the resource
@@ -1067,7 +1188,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
1067
1188
  // This sanity check has to be performed because we're still passing non-locator class
1068
1189
  // locator objects to this function. This is not good and should eventually be forbidden
1069
1190
  // or the locator should be deserialized sometime before this function.
1070
- const hid = (typeof locator.locations.htmlId === "function") && locator.locations.htmlId();
1191
+ const hid = getHtmlId(locator.locations);
1071
1192
  if(hid)
1072
1193
  done = await new Promise<boolean>((res, _) => {
1073
1194
  // Attempt to go to an HTML ID in the resource
@@ -1113,4 +1234,12 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
1113
1234
  public goLink(link: Link, animated: boolean, cb: (ok: boolean) => void): void {
1114
1235
  return this.go(link.locator, animated, cb);
1115
1236
  }
1237
+
1238
+ private _notifyTimelineChange(locator: Locator): void {
1239
+ const item = this.timeline.locate(locator);
1240
+ if (item !== this._currentTimelineItem) {
1241
+ this._currentTimelineItem = item;
1242
+ this.listeners.timelineItemChanged(item);
1243
+ }
1244
+ }
1116
1245
  }
@@ -1,7 +1,7 @@
1
1
  import { Loader, ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { FrameComms } from "./FrameComms.ts";
3
- import type { ReadiumWindow } from "../../../../navigator-html-injectables/types/src/helpers/dom";
4
- import { sML } from "@readium/navigator-html-injectables";
3
+ import type { ReadiumWindow } from "@readium/navigator-html-injectables";
4
+ import { sML } from "@readium/helpers";
5
5
  import type { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../../Navigator.ts";
6
6
  import { KeyboardConditionBridge } from "../../peripherals/KeyboardConditionBridge.ts";
7
7
 
@@ -21,7 +21,8 @@ export class FrameManager {
21
21
  constructor(
22
22
  source: string,
23
23
  contentProtectionConfig: IContentProtectionConfig = {},
24
- keyboardPeripheralsConfig: IKeyboardPeripheralsConfig = []
24
+ keyboardPeripheralsConfig: IKeyboardPeripheralsConfig = [],
25
+ private readonly timelineFragmentIds: string[] = []
25
26
  ) {
26
27
  this.frame = document.createElement("iframe");
27
28
  this.frame.sandbox.value = "allow-same-origin allow-scripts";
@@ -141,6 +142,10 @@ export class FrameManager {
141
142
  // Apply content protection synchronously
142
143
  this.applyContentProtection();
143
144
 
145
+ // Send timeline fragment IDs so the snapper can set up observers
146
+ if (this.timelineFragmentIds.length > 0)
147
+ this.comms?.send("timeline_entries", this.timelineFragmentIds);
148
+
144
149
  const remove = () => {
145
150
  this.frame.style.removeProperty("visibility");
146
151
  this.frame.style.removeProperty("aria-hidden");
@@ -21,6 +21,7 @@ export class FramePoolManager {
21
21
  private readonly injector: Injector | null = null;
22
22
  private readonly contentProtectionConfig: IContentProtectionConfig;
23
23
  private readonly keyboardPeripheralsConfig: IKeyboardPeripheralsConfig;
24
+ private readonly getFragmentIds: (href: string) => string[];
24
25
 
25
26
  constructor(
26
27
  container: HTMLElement,
@@ -28,7 +29,8 @@ export class FramePoolManager {
28
29
  cssProperties?: { [key: string]: string },
29
30
  injector?: Injector | null,
30
31
  contentProtectionConfig?: IContentProtectionConfig,
31
- keyboardPeripheralsConfig?: IKeyboardPeripheralsConfig
32
+ keyboardPeripheralsConfig?: IKeyboardPeripheralsConfig,
33
+ getFragmentIds?: (href: string) => string[]
32
34
  ) {
33
35
  this.container = container;
34
36
  this.positions = positions;
@@ -36,6 +38,7 @@ export class FramePoolManager {
36
38
  this.injector = injector ?? null;
37
39
  this.contentProtectionConfig = contentProtectionConfig || {};
38
40
  this.keyboardPeripheralsConfig = keyboardPeripheralsConfig || [];
41
+ this.getFragmentIds = getFragmentIds ?? (() => []);
39
42
  }
40
43
 
41
44
  async destroy() {
@@ -171,7 +174,7 @@ export class FramePoolManager {
171
174
  }
172
175
 
173
176
  // Create <iframe>
174
- const fm = new FrameManager(this.blobs.get(href)!, this.contentProtectionConfig, this.keyboardPeripheralsConfig);
177
+ const fm = new FrameManager(this.blobs.get(href)!, this.contentProtectionConfig, this.keyboardPeripheralsConfig, this.getFragmentIds(href));
175
178
  if(href !== newHref) await fm.hide(); // Avoid unecessary hide
176
179
  this.container.appendChild(fm.iframe);
177
180
  await fm.load(modules);
@@ -1,4 +1,4 @@
1
- import { sML } from "@readium/navigator-html-injectables";
1
+ import { sML } from "@readium/helpers";
2
2
 
3
3
  export interface Point {
4
4
  X: number;
@@ -2,7 +2,7 @@ import { Loader, ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { Page, ReadingProgression } from "@readium/shared";
3
3
  import { FrameComms } from "../frame/FrameComms.ts";
4
4
  import { FXLPeripherals } from "./FXLPeripherals.ts";
5
- import type { ReadiumWindow } from "../../../../navigator-html-injectables/types/src/helpers/dom";
5
+ import type { ReadiumWindow } from "@readium/navigator-html-injectables";
6
6
  import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../../Navigator.ts";
7
7
  import { KeyboardConditionBridge } from "../../peripherals/KeyboardConditionBridge.ts";
8
8
 
@@ -19,7 +19,7 @@ import {
19
19
  withFallback
20
20
  } from "../../preferences/guards.ts";
21
21
 
22
- import { sMLWithRequest } from "@readium/navigator-html-injectables";
22
+ import { sMLWithRequest } from "@readium/helpers";
23
23
 
24
24
  export interface IEpubDefaults {
25
25
  backgroundColor?: string | null,
@@ -3,7 +3,7 @@ import { ExperimentKey, TextAlignment } from "../../preferences/Types.ts";
3
3
  import { EpubDefaults } from "./EpubDefaults.ts";
4
4
  import { EpubPreferences } from "./EpubPreferences.ts";
5
5
 
6
- import { sMLWithRequest } from "@readium/navigator-html-injectables";
6
+ import { sMLWithRequest } from "@readium/helpers";
7
7
 
8
8
  export interface IEpubSettings {
9
9
  backgroundColor?: string | null,
@@ -1,4 +1,4 @@
1
- import { sML } from "@readium/navigator-html-injectables";
1
+ import { sML } from "@readium/helpers";
2
2
  import { WorkerConsole } from "./utils/WorkerConsole.ts";
3
3
  import { log, table, clear } from "./utils/console.ts";
4
4
  import { isBrave } from "./utils/platform.ts";
@@ -1,7 +1,7 @@
1
1
  import { Loader, ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { FrameComms } from "../epub/frame/FrameComms.ts";
3
- import type { ReadiumWindow } from "../../../navigator-html-injectables/types/src/helpers/dom";
4
- import { sML } from "@readium/navigator-html-injectables";
3
+ import type { ReadiumWindow } from "@readium/navigator-html-injectables";
4
+ import { sML } from "@readium/helpers";
5
5
  import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator.ts";
6
6
  import { KeyboardConditionBridge } from "../peripherals/KeyboardConditionBridge.ts";
7
7
 
@@ -20,7 +20,8 @@ export class WebPubFrameManager {
20
20
  constructor(
21
21
  source: string,
22
22
  contentProtectionConfig: IContentProtectionConfig = {},
23
- keyboardPeripheralsConfig: IKeyboardPeripheralsConfig = []
23
+ keyboardPeripheralsConfig: IKeyboardPeripheralsConfig = [],
24
+ private readonly timelineFragmentIds: string[] = []
24
25
  ) {
25
26
  this.frame = document.createElement("iframe");
26
27
  this.frame.classList.add("readium-navigator-iframe");
@@ -138,6 +139,11 @@ export class WebPubFrameManager {
138
139
  this.comms?.send("focus", undefined, () => {
139
140
  // Apply content protection synchronously
140
141
  this.applyContentProtection();
142
+
143
+ // Send timeline fragment IDs so the snapper can set up observers
144
+ if (this.timelineFragmentIds.length > 0)
145
+ this.comms?.send("timeline_entries", this.timelineFragmentIds);
146
+
141
147
  const remove = () => {
142
148
  this.frame.style.removeProperty("visibility");
143
149
  this.frame.style.removeProperty("aria-hidden");
@@ -17,19 +17,22 @@ export class WebPubFramePoolManager {
17
17
  private readonly injector?: Injector | null = null;
18
18
  private readonly contentProtectionConfig: IContentProtectionConfig;
19
19
  private readonly keyboardPeripheralsConfig: IKeyboardPeripheralsConfig;
20
+ private readonly getFragmentIds: (href: string) => string[];
20
21
 
21
22
  constructor(
22
23
  container: HTMLElement,
23
24
  cssProperties?: { [key: string]: string },
24
25
  injector?: Injector | null,
25
26
  contentProtectionConfig: IContentProtectionConfig = {},
26
- keyboardPeripheralsConfig: IKeyboardPeripheralsConfig = []
27
+ keyboardPeripheralsConfig: IKeyboardPeripheralsConfig = [],
28
+ getFragmentIds?: (href: string) => string[]
27
29
  ) {
28
30
  this.container = container;
29
31
  this.currentCssProperties = cssProperties;
30
32
  this.injector = injector;
31
33
  this.contentProtectionConfig = contentProtectionConfig;
32
34
  this.keyboardPeripheralsConfig = [...keyboardPeripheralsConfig];
35
+ this.getFragmentIds = getFragmentIds ?? (() => []);
33
36
  }
34
37
 
35
38
  async destroy() {
@@ -154,7 +157,7 @@ export class WebPubFramePoolManager {
154
157
  this.blobs.set(href, blobURL);
155
158
  }
156
159
 
157
- const fm = new WebPubFrameManager(this.blobs.get(href)!, this.contentProtectionConfig, this.keyboardPeripheralsConfig);
160
+ const fm = new WebPubFrameManager(this.blobs.get(href)!, this.contentProtectionConfig, this.keyboardPeripheralsConfig, this.getFragmentIds(href));
158
161
  if(href !== newHref) await fm.hide();
159
162
  this.container.appendChild(fm.iframe);
160
163
  await fm.load(modules);