@readium/navigator 2.10.2 → 2.11.0

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 (86) hide show
  1. package/dist/audio/AudioNavigator.js +1 -1
  2. package/dist/audio/AudioPoolManager.js +1 -1
  3. package/dist/audio/engine/WebAudioEngine.js +1 -1
  4. package/dist/divina/DivinaNavigator.js +1 -0
  5. package/dist/divina/DivinaPageManager.js +1 -0
  6. package/dist/divina/DivinaPagedPresenter.js +1 -0
  7. package/dist/divina/DivinaPeripherals.js +1 -0
  8. package/dist/divina/DivinaScrolledPresenter.js +1 -0
  9. package/dist/divina/DivinaSpreader.js +1 -0
  10. package/dist/divina/DivinaVariantSelector.js +1 -0
  11. package/dist/divina/preferences/DivinaDefaults.js +1 -0
  12. package/dist/divina/preferences/DivinaPreferences.js +1 -0
  13. package/dist/divina/preferences/DivinaPreferencesEditor.js +1 -0
  14. package/dist/divina/preferences/DivinaSettings.js +1 -0
  15. package/dist/divina/protection/DivinaNavigatorProtector.js +1 -0
  16. package/dist/epub/EpubNavigator.js +1 -1
  17. package/dist/epub/frame/FrameBlobBuilder.js +3 -1
  18. package/dist/epub/frame/FrameManager.js +1 -1
  19. package/dist/epub/frame/FramePoolManager.js +1 -1
  20. package/dist/epub/fxl/FXLFrameManager.js +1 -1
  21. package/dist/epub/fxl/FXLFramePoolManager.js +1 -1
  22. package/dist/epub/fxl/FXLPeripherals.js +2 -2
  23. package/dist/index.js +1 -1
  24. package/dist/peripherals/KeyboardConditionBridge.js +1 -1
  25. package/dist/peripherals/KeyboardPeripherals.js +1 -1
  26. package/dist/webpub/WebPubFrameManager.js +1 -1
  27. package/dist/webpub/WebPubFramePoolManager.js +1 -1
  28. package/dist/webpub/WebPubNavigator.js +1 -1
  29. package/package.json +5 -5
  30. package/src/audio/AudioNavigator.ts +107 -5
  31. package/src/audio/AudioPoolManager.ts +14 -8
  32. package/src/audio/engine/AudioEngine.ts +34 -1
  33. package/src/audio/engine/WebAudioEngine.ts +48 -3
  34. package/src/divina/DivinaNavigator.ts +704 -0
  35. package/src/divina/DivinaPageManager.ts +322 -0
  36. package/src/divina/DivinaPagedPresenter.ts +508 -0
  37. package/src/divina/DivinaPeripherals.ts +630 -0
  38. package/src/divina/DivinaScrolledPresenter.ts +516 -0
  39. package/src/divina/DivinaSpreader.ts +202 -0
  40. package/src/divina/DivinaVariantSelector.ts +77 -0
  41. package/src/divina/index.ts +8 -0
  42. package/src/divina/preferences/DivinaDefaults.ts +37 -0
  43. package/src/divina/preferences/DivinaPreferences.ts +77 -0
  44. package/src/divina/preferences/DivinaPreferencesEditor.ts +96 -0
  45. package/src/divina/preferences/DivinaSettings.ts +38 -0
  46. package/src/divina/preferences/index.ts +4 -0
  47. package/src/divina/protection/DivinaNavigatorProtector.ts +44 -0
  48. package/src/epub/EpubNavigator.ts +73 -7
  49. package/src/epub/frame/FrameBlobBuilder.ts +144 -39
  50. package/src/epub/frame/FrameManager.ts +7 -9
  51. package/src/epub/frame/FramePoolManager.ts +106 -48
  52. package/src/epub/fxl/FXLFrameManager.ts +69 -26
  53. package/src/epub/fxl/FXLFramePoolManager.ts +98 -53
  54. package/src/epub/fxl/FXLPeripherals.ts +22 -1
  55. package/src/index.ts +1 -0
  56. package/src/peripherals/KeyboardConditionBridge.ts +15 -5
  57. package/src/peripherals/KeyboardPeripherals.ts +13 -7
  58. package/src/webpub/WebPubFrameManager.ts +7 -9
  59. package/src/webpub/WebPubFramePoolManager.ts +5 -2
  60. package/src/webpub/WebPubNavigator.ts +44 -0
  61. package/types/src/audio/AudioNavigator.d.ts +29 -0
  62. package/types/src/audio/engine/AudioEngine.d.ts +27 -1
  63. package/types/src/audio/engine/WebAudioEngine.d.ts +14 -2
  64. package/types/src/divina/DivinaNavigator.d.ts +119 -0
  65. package/types/src/divina/DivinaPageManager.d.ts +66 -0
  66. package/types/src/divina/DivinaPagedPresenter.d.ts +122 -0
  67. package/types/src/divina/DivinaPeripherals.d.ts +121 -0
  68. package/types/src/divina/DivinaScrolledPresenter.d.ts +125 -0
  69. package/types/src/divina/DivinaSpreader.d.ts +65 -0
  70. package/types/src/divina/DivinaVariantSelector.d.ts +30 -0
  71. package/types/src/divina/index.d.ts +8 -0
  72. package/types/src/divina/preferences/DivinaDefaults.d.ts +18 -0
  73. package/types/src/divina/preferences/DivinaPreferences.d.ts +33 -0
  74. package/types/src/divina/preferences/DivinaPreferencesEditor.d.ts +20 -0
  75. package/types/src/divina/preferences/DivinaSettings.d.ts +26 -0
  76. package/types/src/divina/preferences/index.d.ts +4 -0
  77. package/types/src/divina/protection/DivinaNavigatorProtector.d.ts +14 -0
  78. package/types/src/epub/EpubNavigator.d.ts +7 -1
  79. package/types/src/epub/frame/FrameBlobBuilder.d.ts +19 -3
  80. package/types/src/epub/frame/FramePoolManager.d.ts +5 -1
  81. package/types/src/epub/fxl/FXLFrameManager.d.ts +2 -0
  82. package/types/src/epub/fxl/FXLFramePoolManager.d.ts +8 -2
  83. package/types/src/epub/fxl/FXLPeripherals.d.ts +3 -0
  84. package/types/src/index.d.ts +1 -0
  85. package/types/src/peripherals/KeyboardPeripherals.d.ts +1 -0
  86. package/types/src/webpub/WebPubNavigator.d.ts +2 -0
@@ -27,6 +27,30 @@ export interface Playback {
27
27
  offset?: number;
28
28
  buffered?: number;
29
29
  }
30
+ /**
31
+ * A consumer-supplied loader that feeds one resource to the playback element
32
+ * through Media Source Extensions. The engine constructs one per track via
33
+ * the [AudioMseLoaderFactory] and calls [start] immediately; the loader owns
34
+ * the element's `src` (a MediaSource object URL), all fetching, buffering and
35
+ * seek servicing until [destroy] is called. Container specifics (WebM, fMP4,
36
+ * byte ranges vs. segments…) are entirely up to the implementation.
37
+ */
38
+ export interface AudioMseLoader {
39
+ /** Attaches to the element and begins streaming. Called exactly once. */
40
+ start(): void;
41
+ /**
42
+ * Cancels in-flight fetches, detaches element listeners and releases the
43
+ * MediaSource object URL. The element itself must be left intact — the
44
+ * engine reuses it for the next track.
45
+ */
46
+ destroy(): void;
47
+ }
48
+ /**
49
+ * Creates the [AudioMseLoader] for a resource. [mimeType] is the resource's
50
+ * media type from the manifest, when known — needed to configure the
51
+ * SourceBuffer.
52
+ */
53
+ export type AudioMseLoaderFactory = (element: HTMLMediaElement, href: string, mimeType?: string) => AudioMseLoader;
30
54
  /**
31
55
  * An audio engine that plays audio resources from a publication.
32
56
  * @playback - The current [Playback] state.
@@ -52,8 +76,10 @@ export interface AudioEngine {
52
76
  * Changes the src of the primary media element without swapping it,
53
77
  * preserving the RemotePlayback session and all attached event listeners.
54
78
  * @param href The URL of the new audio resource.
79
+ * @param mimeType The media type of the resource, when known. Required for
80
+ * MSE-based loading to configure the SourceBuffer.
55
81
  */
56
- changeSrc(href: string): void;
82
+ changeSrc(href: string, mimeType?: string): void;
57
83
  /**
58
84
  * Plays the current audio resource.
59
85
  */
@@ -1,4 +1,4 @@
1
- import { AudioEngine, Playback } from "./AudioEngine.ts";
1
+ import { AudioEngine, AudioMseLoaderFactory, Playback } from "./AudioEngine.ts";
2
2
  type EventCallback = (data: any) => void;
3
3
  export declare class WebAudioEngine implements AudioEngine {
4
4
  readonly playback: Playback;
@@ -16,6 +16,10 @@ export declare class WebAudioEngine implements AudioEngine {
16
16
  private isStoppedValue;
17
17
  private worklet;
18
18
  private webAudioActive;
19
+ private readonly mseLoaderFactory;
20
+ private mseLoader;
21
+ /** The resource href currently loaded (element src is a blob: URL in MSE mode). */
22
+ private currentHref;
19
23
  private readonly boundOnCanPlayThrough;
20
24
  private readonly boundOnTimeUpdate;
21
25
  private readonly boundOnError;
@@ -33,6 +37,7 @@ export declare class WebAudioEngine implements AudioEngine {
33
37
  private readonly boundOnProgress;
34
38
  constructor(values: {
35
39
  playback: Playback;
40
+ mseLoaderFactory?: AudioMseLoaderFactory;
36
41
  });
37
42
  /**
38
43
  * Adds an event listener to the audio engine.
@@ -132,6 +137,8 @@ export declare class WebAudioEngine implements AudioEngine {
132
137
  */
133
138
  private activateWebAudio;
134
139
  get isWebAudioActive(): boolean;
140
+ /** True when this engine streams media through Media Source Extensions. */
141
+ get usesMse(): boolean;
135
142
  /**
136
143
  * Tears down the Web Audio graph and restores the media element to standalone
137
144
  * playback. Safe to call even if Web Audio was never activated.
@@ -145,10 +152,15 @@ export declare class WebAudioEngine implements AudioEngine {
145
152
  * the required headers), the graph is torn down and the src is reloaded
146
153
  * without CORS so playback continues — just without pitch correction.
147
154
  */
148
- changeSrc(href: string): void;
155
+ changeSrc(href: string, mimeType?: string): void;
149
156
  /**
150
157
  * Returns the HTML media element used for playback.
151
158
  */
152
159
  getMediaElement(): HTMLMediaElement;
160
+ /**
161
+ * Releases loader resources (in-flight fetches, MediaSource object URL).
162
+ * The media element itself is left intact.
163
+ */
164
+ destroy(): void;
153
165
  }
154
166
  export {};
@@ -0,0 +1,119 @@
1
+ import { Layout, Link, Locator, Publication, ReadingProgression, Timeline, TimelineItem } from "@readium/shared";
2
+ import { ContextMenuEvent, FrameClickEvent, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
3
+ import { IContentProtectionConfig, IKeyboardPeripheralsConfig, KeyboardPeripheralEventData, VisualNavigator, VisualNavigatorViewport } from "../Navigator.ts";
4
+ import { Configurable, ConfigurableSettings } from "../preferences/Configurable.ts";
5
+ import { IDivinaDefaults } from "./preferences/DivinaDefaults.ts";
6
+ import { DivinaPreferences, IDivinaPreferences } from "./preferences/DivinaPreferences.ts";
7
+ import { DivinaSettings } from "./preferences/DivinaSettings.ts";
8
+ import { DivinaPreferencesEditor } from "./preferences/DivinaPreferencesEditor.ts";
9
+ export interface DivinaNavigatorConfiguration {
10
+ preferences: IDivinaPreferences;
11
+ defaults: IDivinaDefaults;
12
+ contentProtection?: IContentProtectionConfig;
13
+ keyboardPeripherals?: IKeyboardPeripheralsConfig;
14
+ }
15
+ export interface DivinaNavigatorListeners {
16
+ positionChanged: (locator: Locator) => void;
17
+ timelineItemChanged: (item: TimelineItem | undefined) => void;
18
+ tap: (e: FrameClickEvent) => boolean;
19
+ click: (e: FrameClickEvent) => boolean;
20
+ zoom: (scale: number) => void;
21
+ miscPointer: (amount: number) => void;
22
+ scroll: (delta: number) => void;
23
+ customEvent: (key: string, data: unknown) => void;
24
+ handleLocator: (locator: Locator) => boolean;
25
+ contentProtection: (type: string, data: SuspiciousActivityEvent) => void;
26
+ contextMenu: (data: ContextMenuEvent) => void;
27
+ peripheral: (data: KeyboardPeripheralEventData) => void;
28
+ }
29
+ /**
30
+ * Navigator for Divina (visual narrative) publications: bitmap pages rendered
31
+ * as <img> elements with blob sources, in either a horizontal paged view with
32
+ * synthetic spreads (comics/manga) or a continuous vertical scroll (webtoons).
33
+ */
34
+ export declare class DivinaNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, DivinaPreferences> {
35
+ private readonly pub;
36
+ private readonly container;
37
+ private readonly listeners;
38
+ private positions;
39
+ private currentLocation;
40
+ private _destroyed;
41
+ private _isNavigating;
42
+ private _timelineAugmented;
43
+ private _wrappedTimeline;
44
+ private _currentTimelineItem;
45
+ private _preferences;
46
+ private _defaults;
47
+ private _settings;
48
+ private _preferencesEditor;
49
+ private readonly spreader;
50
+ private readonly pool;
51
+ private pagedPresenter;
52
+ private scrolledPresenter;
53
+ private readonly _contentProtection;
54
+ private readonly _keyboardPeripherals;
55
+ private readonly _navigatorProtector;
56
+ private _keyboardPeripheralsManager;
57
+ private readonly _keyboardConditionBridge;
58
+ private readonly _suspiciousActivityListener;
59
+ private readonly _keyboardPeripheralListener;
60
+ private resizeObserver;
61
+ constructor(container: HTMLElement, pub: Publication, listeners: Partial<DivinaNavigatorListeners>, positions?: Locator[], initialPosition?: Locator | undefined, configuration?: DivinaNavigatorConfiguration);
62
+ private get manifestScrolled();
63
+ get ownerWindow(): Window;
64
+ get publication(): Publication;
65
+ get timeline(): Timeline;
66
+ private _notifyTimelineChange;
67
+ get layout(): Layout;
68
+ get readingProgression(): ReadingProgression;
69
+ private get presenter();
70
+ load(): Promise<boolean | undefined>;
71
+ private synthesizePositions;
72
+ private createPresenter;
73
+ private destroyPresenter;
74
+ private handlePointer;
75
+ /** Paged mode: move by one spread and report the new position */
76
+ private changeSpread;
77
+ private setLocationToIndex;
78
+ /** Scrolled mode: debounced position reports from the scroll handler */
79
+ private updateLocationFromScroll;
80
+ private applyBackground;
81
+ /** Whether a CSS color is dark, via computed-style normalization; null if unparseable */
82
+ private isDarkColor;
83
+ resizeHandler(): void;
84
+ private completeLocator;
85
+ /**
86
+ * The reading order index for a href, tolerating representation
87
+ * mismatches: a TOC can use absolute URLs while the reading order is
88
+ * relative (or vice versa), and it can reference a page through one of
89
+ * its alternate variants rather than the main href. Both sides are
90
+ * resolved against the publication base, and each page matches on its
91
+ * own href or any of its alternates.
92
+ */
93
+ private findReadingOrderIndex;
94
+ go(locator: Locator, animated: boolean, cb: (ok: boolean) => void): void;
95
+ goLink(link: Link, animated: boolean, cb: (ok: boolean) => void): void;
96
+ goForward(animated: boolean, cb: (ok: boolean) => void): void;
97
+ goBackward(animated: boolean, cb: (ok: boolean) => void): void;
98
+ /**
99
+ * Scrolled mode: scroll by a fixed pixel amount (e.g. a line step for
100
+ * arrow keys) instead of the viewport-sized go* step. Falls back to a
101
+ * spread turn in paged mode.
102
+ */
103
+ scrollBy(px: number, animated: boolean, cb: (ok: boolean) => void): void;
104
+ zoomIn(): void;
105
+ zoomOut(): void;
106
+ zoomReset(): void;
107
+ get currentLocator(): Locator;
108
+ get viewport(): VisualNavigatorViewport;
109
+ get isScrollStart(): boolean;
110
+ get isScrollEnd(): boolean;
111
+ get canGoBackward(): boolean;
112
+ get canGoForward(): boolean;
113
+ get settings(): Readonly<DivinaSettings>;
114
+ get preferencesEditor(): DivinaPreferencesEditor;
115
+ private _applying;
116
+ submitPreferences(preferences: DivinaPreferences): Promise<void>;
117
+ private applyPreferences;
118
+ destroy(): Promise<void>;
119
+ }
@@ -0,0 +1,66 @@
1
+ import { Link, Page } from "@readium/shared";
2
+ import { DivinaPage } from "./DivinaSpreader.ts";
3
+ import { DivinaQuality } from "./DivinaVariantSelector.ts";
4
+ export declare function placeholderSVG(width: number, height: number, message: string, detail?: string): string;
5
+ export type DivinaPageMode = "paged" | "scrolled";
6
+ /**
7
+ * Manages the lifecycle of a single Divina page: an <img> element inside a
8
+ * wrapper <div>, with blob-based loading, an intrinsic-size SVG placeholder,
9
+ * and per-mode layout. The image-based equivalent of FXLFrameManager.
10
+ */
11
+ export declare class DivinaPageManager {
12
+ readonly page: DivinaPage;
13
+ readonly wrapper: HTMLDivElement;
14
+ readonly img: HTMLImageElement;
15
+ private readonly fetchBlob;
16
+ private readonly getQuality;
17
+ private readonly placeholder;
18
+ private objectURL;
19
+ private loadPromise;
20
+ private abortController;
21
+ private _loaded;
22
+ private destroyed;
23
+ private generation;
24
+ private mode;
25
+ constructor(page: DivinaPage, fetchBlob: (link: Link, signal?: AbortSignal) => Promise<Blob>, getQuality?: () => DivinaQuality);
26
+ get intrinsicWidth(): number;
27
+ get intrinsicHeight(): number;
28
+ get loaded(): boolean;
29
+ get element(): HTMLDivElement;
30
+ /**
31
+ * Configure the wrapper/image styles for the given presentation mode.
32
+ * Paged: the image is fitted (letterboxed) inside the slot-sized wrapper.
33
+ * Scrolled: the image dictates the height via its aspect ratio.
34
+ */
35
+ setMode(mode: DivinaPageMode): void;
36
+ /**
37
+ * Position the fitted image within its wrapper (paged mode only).
38
+ *
39
+ * @param position In a two-page spread, the side of the gutter this page
40
+ * sits on (pages butt against the gutter). With `half`, the half of a
41
+ * full-spread wrapper the page occupies.
42
+ * @param half The page is alone in a double-width (full-spread) slot but
43
+ * carries a left/right position hint — e.g. a shifted cover or the
44
+ * orphaned component of a spread — so it is fitted to and centered
45
+ * within its half of the spread.
46
+ */
47
+ fit(position: Page, half?: boolean): void;
48
+ /** Scrolled mode: constrain the display width of this page in the strip */
49
+ applyStripWidth(displayWidth: number): void;
50
+ /**
51
+ * Fetch the image as a blob and swap it in for the placeholder.
52
+ * Idempotent; concurrent calls await the same promise.
53
+ */
54
+ /**
55
+ * The most appropriate variant (main link or alternate) for the page's
56
+ * current display box and the user's quality preference.
57
+ */
58
+ private pickVariant;
59
+ load(): Promise<void>;
60
+ /**
61
+ * Release the blob and go back to the placeholder (memory reclamation),
62
+ * aborting any in-flight fetch.
63
+ */
64
+ unload(): void;
65
+ destroy(): void;
66
+ }
@@ -0,0 +1,122 @@
1
+ import { Publication } from "@readium/shared";
2
+ import { VisualNavigatorViewport } from "../Navigator.ts";
3
+ import { DivinaPageManager } from "./DivinaPageManager.ts";
4
+ import { DivinaManagerEventKey, DivinaPagedManager, DivinaPeripherals } from "./DivinaPeripherals.ts";
5
+ import { DivinaPage, DivinaSpreader } from "./DivinaSpreader.ts";
6
+ export type DivinaPresenterListener = (key: DivinaManagerEventKey, data: unknown) => void;
7
+ /**
8
+ * Horizontal paged presenter for Divina publications: a sliding spine of
9
+ * page slots with synthetic spreads, ported from the FXLFramePoolManager
10
+ * slider core but rendering <img> pages instead of iframes.
11
+ *
12
+ * Slot model: in double-page mode every spread occupies exactly two slots
13
+ * (single pages get a double-width slot), so slide indices are always even
14
+ * and always aligned with spread boundaries.
15
+ */
16
+ export declare class DivinaPagedPresenter implements DivinaPagedManager {
17
+ private readonly container;
18
+ private readonly pub;
19
+ private readonly spreader;
20
+ private readonly pool;
21
+ readonly bookElement: HTMLDivElement;
22
+ readonly spineElement: HTMLDivElement;
23
+ readonly peripherals: DivinaPeripherals;
24
+ width: number;
25
+ height: number;
26
+ currentSlide: number;
27
+ private transform;
28
+ private spread;
29
+ private orientationInternal;
30
+ private containerHeightCached;
31
+ private destroyed;
32
+ private loadTimers;
33
+ listener: DivinaPresenterListener;
34
+ constructor(container: HTMLElement, pub: Publication, spreader: DivinaSpreader, pool: Map<number, DivinaPageManager>, spreads: boolean);
35
+ get rtl(): boolean;
36
+ private get single();
37
+ get perPage(): 2 | 1;
38
+ get threshold(): number;
39
+ get portrait(): boolean;
40
+ get slength(): number;
41
+ /** Total slot count in the current mode */
42
+ get length(): number;
43
+ /**
44
+ * The x position of a slot boundary within the spine, snapped to device
45
+ * pixels. Fractional boundaries leave hairline gaps at the spread gutter
46
+ * (the background shows through between the two page images), varying
47
+ * with the window size.
48
+ */
49
+ private slotEdge;
50
+ private get offset();
51
+ get doNotDisturb(): boolean;
52
+ /** The reading-order index of the first page of the current spread */
53
+ get currentIndex(): number;
54
+ /** The pages currently (meant to be) visible */
55
+ get currentSpread(): DivinaPage[];
56
+ private updateDimensions;
57
+ private bookScale;
58
+ private bookTranslate;
59
+ private bookTransform;
60
+ updateBookStyle(initial?: boolean): void;
61
+ updateSpineStyle(animate: boolean, fast?: boolean): void;
62
+ /**
63
+ * Recompute every slot's width and height. Widths are derived from
64
+ * device-pixel-snapped slot boundaries so adjacent pages of a spread
65
+ * meet exactly at the gutter without subpixel seams.
66
+ */
67
+ private updateSlotSizes;
68
+ reAlign(index?: number): number;
69
+ /**
70
+ * Moves the spine to the position of the currently active slide.
71
+ * Always commits: the spine transform may have been written directly by
72
+ * the peripherals during a drag (snap-back relies on this), and reading
73
+ * style.transform back for comparison is unreliable under CSS Typed OM.
74
+ */
75
+ slideToCurrent(enableTransition?: boolean, fast?: boolean): void;
76
+ bounce(rtl?: boolean): void;
77
+ private onChange;
78
+ /**
79
+ * Go to the next spread/page.
80
+ * @returns whether moving forward was possible
81
+ */
82
+ next(): boolean;
83
+ /**
84
+ * Go to the previous spread/page.
85
+ * @returns whether moving backward was possible
86
+ */
87
+ prev(): boolean;
88
+ /**
89
+ * Go to the slide showing the given reading order item.
90
+ * @returns whether the slide changed
91
+ */
92
+ /**
93
+ * The slide showing the given item in the current mode: the item index
94
+ * itself in single mode, or the start slot of the item's spread in
95
+ * double-page mode (the item may occupy the spread's second slot).
96
+ */
97
+ private slideForItem;
98
+ goToItem(itemIndex: number, animated?: boolean): boolean;
99
+ /**
100
+ * Loads pages within the load window around the current position,
101
+ * unloads distant ones, and positions the current spread.
102
+ */
103
+ update(): Promise<void>;
104
+ /**
105
+ * Fit a page of the current spread within its slot. A lone portrait page
106
+ * with a left/right hint (shifted cover, orphaned spread half) is fitted
107
+ * into its half of the full-spread slot; everything else is fitted to the
108
+ * whole slot and anchored against the gutter (or centered).
109
+ */
110
+ private placePage;
111
+ /** Union of the current spread's image bounds, used for zoom/pan clamping */
112
+ get currentBounds(): DOMRect;
113
+ get viewport(): VisualNavigatorViewport;
114
+ /**
115
+ * When the container resizes, resize slider components as well
116
+ */
117
+ resizeHandler(slide?: boolean, fast?: boolean): void;
118
+ /** Toggle double-page spreads (from preferences) */
119
+ setSpreads(spreads: boolean): void;
120
+ deselect(): void;
121
+ destroy(): Promise<void>;
122
+ }
@@ -0,0 +1,121 @@
1
+ import { PanTracker } from "../epub/fxl/FXLPeripherals.ts";
2
+ export type DivinaManagerEventKey = "no_more" | "no_less" | "zoom" | "tap" | "click";
3
+ export interface DivinaPointerEvent {
4
+ x: number;
5
+ y: number;
6
+ clientX: number;
7
+ clientY: number;
8
+ doNotDisturb: boolean;
9
+ }
10
+ /**
11
+ * The narrow surface of DivinaPagedPresenter that the peripherals interact with.
12
+ * Mirrors what FXLPeripherals uses from FXLFramePoolManager.
13
+ */
14
+ export interface DivinaPagedManager {
15
+ readonly spineElement: HTMLDivElement;
16
+ readonly bookElement: HTMLDivElement;
17
+ readonly width: number;
18
+ readonly height: number;
19
+ readonly perPage: number;
20
+ readonly rtl: boolean;
21
+ readonly threshold: number;
22
+ readonly length: number;
23
+ readonly currentSlide: number;
24
+ readonly currentBounds: DOMRect;
25
+ listener(key: DivinaManagerEventKey, data: unknown): void;
26
+ updateBookStyle(initial?: boolean): void;
27
+ updateSpineStyle(animate: boolean, fast?: boolean): void;
28
+ slideToCurrent(enableTransition?: boolean, fast?: boolean): void;
29
+ deselect(): void;
30
+ }
31
+ /**
32
+ * Input handling for the paged Divina presenter. Ported from FXLPeripherals
33
+ * (touch pan/pinch-zoom/swipe state machines) with additions for content that
34
+ * lives in the host DOM instead of iframes: click/tap synthesis, mouse wheel
35
+ * page turning (one page per wheel burst, xbreader/bibi-like), ctrl+wheel and
36
+ * double-click zoom.
37
+ */
38
+ export declare class DivinaPeripherals {
39
+ private readonly manager;
40
+ private readonly coordinator;
41
+ dragState: number;
42
+ private minimumMoved;
43
+ pan: PanTracker;
44
+ private pinch;
45
+ private _scale;
46
+ get scale(): number;
47
+ private scaleDebouncer;
48
+ set scale(value: number);
49
+ private frameBounds;
50
+ private destroyed;
51
+ private mouseDown;
52
+ private clickTimer;
53
+ private lastTouchEnd;
54
+ private tapTimer;
55
+ private lastTapTime;
56
+ private lastTapX;
57
+ private lastTapY;
58
+ private wheelHot;
59
+ private wheelLastDir;
60
+ private wheelLastMag;
61
+ private wheelDecelCount;
62
+ private wheelLastEvent;
63
+ private wheelCooldownTimer;
64
+ constructor(manager: DivinaPagedManager);
65
+ private readonly btouchstartHandler;
66
+ private readonly btouchendHandler;
67
+ private readonly btouchmoveHandler;
68
+ private readonly bdblclickHandler;
69
+ private readonly bmousedownHandler;
70
+ private readonly bmouseupHandler;
71
+ private readonly bmousemoveHandler;
72
+ private readonly bwheelHandler;
73
+ observe(item: EventTarget): void;
74
+ unobserve(item: EventTarget): void;
75
+ destroy(): void;
76
+ private clearPan;
77
+ clearPinch(): void;
78
+ get isScaled(): boolean;
79
+ resetZoom(): void;
80
+ private emitPointer;
81
+ /**
82
+ * touchstart event handler (ported from FXLPeripherals)
83
+ */
84
+ touchstartHandler(e: TouchEvent): void;
85
+ private startTouch;
86
+ /**
87
+ * touchend event handler (ported from FXLPeripherals, adds tap synthesis)
88
+ */
89
+ touchendHandler(e: TouchEvent): void;
90
+ private moveFrame;
91
+ private dragTranslate;
92
+ private dragTransform;
93
+ /**
94
+ * touchmove event handler (ported from FXLPeripherals)
95
+ */
96
+ touchmoveHandler(e: TouchEvent): void;
97
+ /**
98
+ * Double click/tap: toggle zoom between 1x and 2x, centered on the point.
99
+ */
100
+ toggleZoom(clientX: number, clientY: number): void;
101
+ /** Zoom in/out by a keyboard step, clamped to [1, MAX_SCALE] */
102
+ zoomBy(delta: number): void;
103
+ zoomIn(): void;
104
+ zoomOut(): void;
105
+ dblclickHandler(e: MouseEvent): void;
106
+ private addTouch;
107
+ mousedownHandler(e: MouseEvent): void;
108
+ mouseupHandler(e: MouseEvent): void;
109
+ mousemoveHandler(e: MouseEvent): void;
110
+ /**
111
+ * Wheel handler: ctrl+wheel zooms (trackpad pinch on Chrome), otherwise a
112
+ * wheel burst turns exactly one page. A new page turn requires either
113
+ * 200ms of quiet (fresh gesture), a direction reversal, or a re-acceleration
114
+ * after a decelerating tail (deliberate consecutive flicks).
115
+ */
116
+ wheelHandler(e: WheelEvent): void;
117
+ /**
118
+ * Recalculate drag/swipe event and reposition the frame of the slider
119
+ */
120
+ private updateAfterDrag;
121
+ }
@@ -0,0 +1,125 @@
1
+ import { VisualNavigatorViewport } from "../Navigator.ts";
2
+ import { DivinaPageManager } from "./DivinaPageManager.ts";
3
+ import { DivinaSpreader } from "./DivinaSpreader.ts";
4
+ export type DivinaScrolledEventKey = "scroll" | "position" | "tap" | "click";
5
+ export type DivinaScrolledListener = (key: DivinaScrolledEventKey, data: unknown) => void;
6
+ /**
7
+ * Vertical scrolled ("webtoon") presenter for Divina publications: all pages
8
+ * stacked in a native scroll container with no gaps, constrained to a
9
+ * configurable strip width, based on xbreader but improved
10
+ *
11
+ * Page offsets are cached and looked up with binary search so per-frame
12
+ * scroll work stays constant regardless of publication size.
13
+ */
14
+ export declare class DivinaScrolledPresenter {
15
+ private readonly container;
16
+ private readonly spreader;
17
+ private readonly pool;
18
+ readonly scrollerElement: HTMLDivElement;
19
+ private stripWidth;
20
+ private destroyed;
21
+ private scrollRAF;
22
+ private restoreRAF;
23
+ private positionTimer;
24
+ private lastScrollTop;
25
+ /** Position within the strip as a fraction of total strip height (resize retention) */
26
+ private fraction;
27
+ private pageTops;
28
+ private pageHeights;
29
+ private stripHeightCached;
30
+ private viewportHeightCached;
31
+ private loadedIndices;
32
+ private lastWindowIndex;
33
+ private programmaticScroll;
34
+ private programmaticTimer;
35
+ private scrollTarget;
36
+ private animating;
37
+ private animRAF;
38
+ private animLastTime;
39
+ listener: DivinaScrolledListener;
40
+ constructor(container: HTMLElement, spreader: DivinaSpreader, pool: Map<number, DivinaPageManager>, stripWidth: number);
41
+ private readonly bscrollHandler;
42
+ private readonly bscrollendHandler;
43
+ private readonly banimStep;
44
+ private readonly buserTakeoverHandler;
45
+ private readonly bmousedownHandler;
46
+ private readonly bclickHandler;
47
+ private readonly btouchendHandler;
48
+ private readonly btouchstartHandler;
49
+ /** Display width of each page in the strip */
50
+ private get displayWidth();
51
+ /**
52
+ * Recomputes the cached page offsets and viewport metrics.
53
+ * Cheap relative to the per-frame layout reads it replaces, but still a
54
+ * forced layout: only call after actual layout changes.
55
+ */
56
+ private refreshLayoutCache;
57
+ /** The index of the last page whose top is at or above the given strip offset */
58
+ private indexAtOffset;
59
+ applyStripWidth(stripWidth: number): void;
60
+ private restoreFraction;
61
+ private updateFraction;
62
+ /** Top offset in pixels of the given page within the strip */
63
+ private pageTop;
64
+ private pageHeight;
65
+ /**
66
+ * The reading-order index of the current page: the last page whose top
67
+ * is above the middle of the viewport.
68
+ */
69
+ get currentIndex(): number;
70
+ /** Progression within the current page (0..1) */
71
+ get currentPageProgression(): number;
72
+ /**
73
+ * Marks the start of a programmatic scroll: "scroll" events are not
74
+ * reported to the host until the scroll settles (scrollend or timeout),
75
+ * so navigation doesn't masquerade as user scrolling.
76
+ */
77
+ private beginProgrammaticScroll;
78
+ private scrollendHandler;
79
+ /** Load the window around wherever the scroll settled */
80
+ private settleWindow;
81
+ /** Direct user input takes over any in-flight programmatic scroll */
82
+ private userTakeoverHandler;
83
+ private scrollHandler;
84
+ /** Load nearby pages, unload distant ones */
85
+ private updateWindow;
86
+ private touchStartY;
87
+ private lastTouchEnd;
88
+ private touchstartHandler;
89
+ private touchendHandler;
90
+ private mouseDownPos;
91
+ private mousedownHandler;
92
+ private clickHandler;
93
+ private emitPointer;
94
+ /**
95
+ * Scroll forward/backward by a fraction of the viewport.
96
+ * Repeated calls chain from the pending target, so rapid key presses
97
+ * (including autorepeat from a held key) extend one animation that keeps
98
+ * cruising instead of restarting; the target is clamped to stay within
99
+ * reach of the position so releasing the key stops promptly rather than
100
+ * replaying the queued distance.
101
+ * @returns whether scrolling was possible
102
+ */
103
+ next(animated?: boolean, stepPx?: number): boolean;
104
+ prev(animated?: boolean, stepPx?: number): boolean;
105
+ private scrollToTarget;
106
+ /**
107
+ * One frame of the owned scroll animation: exponential approach toward
108
+ * the (re)targetable scrollTarget, capped at a cruise speed. Under a held
109
+ * key the cap dominates and the scroll advances at constant speed; once
110
+ * input stops the exponential term eases it out into the target.
111
+ */
112
+ private animStep;
113
+ private stopAnimation;
114
+ /**
115
+ * Go to the top of a page, optionally at a progression within it.
116
+ * @returns whether the position changed
117
+ */
118
+ goToItem(itemIndex: number, animated?: boolean, progression?: number): boolean;
119
+ update(): Promise<void>;
120
+ get atStart(): boolean;
121
+ get atEnd(): boolean;
122
+ get viewport(): VisualNavigatorViewport;
123
+ resizeHandler(): void;
124
+ destroy(): Promise<void>;
125
+ }