@readium/navigator-html-injectables 2.1.1 → 2.2.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.
@@ -0,0 +1,226 @@
1
+ import { Locator, LocatorLocations, LocatorText } from "@readium/shared";
2
+ import { Comms } from "../../comms/comms";
3
+ import { ReadiumWindow, deselect, findFirstVisibleLocator } from "../../helpers/dom";
4
+ import { ModuleName } from "../ModuleLibrary";
5
+ import { Snapper } from "./Snapper";
6
+ import { rangeFromLocator } from "../../helpers/locator";
7
+ import { forceWebkitRecalc } from "../../helpers/document";
8
+
9
+ export class WebPubSnapper extends Snapper {
10
+ static readonly moduleName: ModuleName = "webpub_snapper";
11
+
12
+ private wnd!: ReadiumWindow;
13
+ private comms!: Comms;
14
+ private resizeObserver!: ResizeObserver;
15
+
16
+ private initialScrollHandled = false;
17
+ private isScrolling = false;
18
+ private lastScrollTop = 0;
19
+ private isResizing = false;
20
+ private resizeDebounce: number | null = null;
21
+
22
+ private doc() {
23
+ return this.wnd.document.scrollingElement as HTMLElement;
24
+ }
25
+
26
+ private reportProgress() {
27
+ if (!this.comms.ready) return;
28
+
29
+ const scrollTop = Math.ceil(this.doc().scrollTop);
30
+ const scrollHeight = this.doc().scrollHeight;
31
+ const viewportHeight = this.wnd.innerHeight;
32
+ const progress = Math.max(0, Math.min(1, scrollTop / scrollHeight));
33
+ const viewportEnd = Math.max(0, Math.min(1, (scrollTop + viewportHeight) / scrollHeight));
34
+
35
+ this.comms.send("progress", {
36
+ start: progress,
37
+ end: viewportEnd
38
+ });
39
+ }
40
+
41
+ private handleScroll = () => {
42
+ if (!this.comms.ready) return;
43
+
44
+ // Filter resize events
45
+ if (this.isResizing) {
46
+ return;
47
+ }
48
+
49
+ // Filter initial scroll event
50
+ if (!this.initialScrollHandled) {
51
+ this.lastScrollTop = this.doc().scrollTop;
52
+ this.initialScrollHandled = true;
53
+ this.reportProgress();
54
+ return;
55
+ }
56
+
57
+ if (!this.isScrolling) {
58
+ this.isScrolling = true;
59
+ this.wnd.requestAnimationFrame(() => {
60
+ this.reportProgress();
61
+
62
+ const currentScrollTop = this.doc().scrollTop;
63
+ const deltaY = currentScrollTop - this.lastScrollTop;
64
+ this.lastScrollTop = currentScrollTop;
65
+
66
+ this.comms.send("scroll", deltaY);
67
+
68
+ this.isScrolling = false;
69
+ });
70
+ }
71
+ };
72
+
73
+ mount(wnd: ReadiumWindow, comms: Comms): boolean {
74
+ this.wnd = wnd;
75
+ this.comms = comms;
76
+
77
+ this.initialScrollHandled = false;
78
+ this.lastScrollTop = 0;
79
+ this.isResizing = false;
80
+ if (this.resizeDebounce) {
81
+ this.wnd.clearTimeout(this.resizeDebounce);
82
+ this.resizeDebounce = null;
83
+ }
84
+
85
+ // Set up resize handling
86
+ this.resizeObserver = new ResizeObserver(() => {
87
+ if (this.resizeDebounce) {
88
+ this.wnd.clearTimeout(this.resizeDebounce);
89
+ }
90
+
91
+ this.isResizing = true;
92
+ this.resizeDebounce = this.wnd.setTimeout(() => {
93
+ this.isResizing = false;
94
+ this.resizeDebounce = null;
95
+ this.reportProgress();
96
+ }, 50);
97
+ });
98
+ this.resizeObserver.observe(wnd.document.body);
99
+
100
+ wnd.addEventListener("scroll", this.handleScroll, { passive: true });
101
+
102
+ comms.register("force_webkit_recalc", WebPubSnapper.moduleName, () => {
103
+ forceWebkitRecalc(this.wnd);
104
+
105
+ // We absolutely must do this because overflown content
106
+ // won’t be rendered if we do not trigger scroll…
107
+ // Only the content at the start of the document,
108
+ // whose height is the viewport height, will be rendered.
109
+ const currentScroll = this.doc().scrollTop;
110
+ if (currentScroll > 1) {
111
+ this.doc().scrollTop = currentScroll - 1;
112
+ } else {
113
+ this.doc().scrollTop = currentScroll + 1;
114
+ }
115
+ this.doc().scrollTop = currentScroll;
116
+ });
117
+
118
+ comms.register("go_progression", WebPubSnapper.moduleName, (data, ack) => {
119
+ const position = data as number;
120
+
121
+ if (position < 0 || position > 1) {
122
+ comms.send("error", {
123
+ message: "go_progression must be given a position from 0.0 to 1.0"
124
+ });
125
+ ack(false);
126
+ return;
127
+ }
128
+
129
+ this.wnd.requestAnimationFrame(() => {
130
+ this.doc().scrollTop = this.doc().offsetHeight * position;
131
+ this.reportProgress();
132
+ deselect(this.wnd);
133
+ ack(true);
134
+ });
135
+ });
136
+
137
+ this.comms.register("go_id", WebPubSnapper.moduleName, (data, ack) => {
138
+ const element = wnd.document.getElementById(data as string);
139
+ if(!element) {
140
+ ack(false);
141
+ return;
142
+ }
143
+ this.wnd.requestAnimationFrame(() => {
144
+ this.doc().scrollTop = element.getBoundingClientRect().top + wnd.scrollY - wnd.innerHeight / 2;
145
+ this.reportProgress();
146
+ deselect(this.wnd);
147
+ ack(true);
148
+ });
149
+ });
150
+
151
+ comms.register("go_text", WebPubSnapper.moduleName, (data, ack) => {
152
+ let cssSelector = undefined;
153
+ if(Array.isArray(data)) {
154
+ if(data.length > 1)
155
+ cssSelector = (data as unknown[])[1] as string;
156
+ data = data[0];
157
+ }
158
+ const text = LocatorText.deserialize(data);
159
+ const r = rangeFromLocator(this.wnd.document, new Locator({
160
+ href: wnd.location.href,
161
+ type: "text/html",
162
+ text,
163
+ locations: cssSelector ? new LocatorLocations({
164
+ otherLocations: new Map([
165
+ ["cssSelector", cssSelector]
166
+ ])
167
+ }) : undefined
168
+ }));
169
+ if(!r) {
170
+ ack(false);
171
+ return;
172
+ }
173
+ this.wnd.requestAnimationFrame(() => {
174
+ this.doc().scrollTop = r.getBoundingClientRect().top + wnd.scrollY - wnd.innerHeight / 2;
175
+ this.reportProgress();
176
+ deselect(this.wnd);
177
+ ack(true);
178
+ });
179
+ });
180
+
181
+ comms.register("go_start", WebPubSnapper.moduleName, (_, ack) => {
182
+ if (this.doc().scrollTop === 0) return ack(false);
183
+ this.doc().scrollTop = 0;
184
+ this.reportProgress();
185
+ ack(true);
186
+ });
187
+
188
+ comms.register("go_end", WebPubSnapper.moduleName, (_, ack) => {
189
+ if (this.doc().scrollTop === this.doc().scrollHeight - this.doc().offsetHeight) return ack(false);
190
+ this.doc().scrollTop = this.doc().scrollHeight - this.doc().offsetHeight;
191
+ this.reportProgress();
192
+ ack(true);
193
+ });
194
+
195
+ comms.register("unfocus", WebPubSnapper.moduleName, (_, ack) => {
196
+ deselect(this.wnd);
197
+ ack(true);
198
+ });
199
+
200
+ comms.register([
201
+ "go_next",
202
+ "go_prev",
203
+ ], WebPubSnapper.moduleName, (_, ack) => ack(false));
204
+ comms.register("focus", WebPubSnapper.moduleName, (_, ack) => {
205
+ this.reportProgress();
206
+ ack(true);
207
+ });
208
+
209
+ comms.register("first_visible_locator", WebPubSnapper.moduleName, (_, ack) => {
210
+ const locator = findFirstVisibleLocator(wnd, true);
211
+ comms.send("first_visible_locator", locator.serialize());
212
+ ack(true);
213
+ });
214
+
215
+ comms.log("WebPubSnapper Mounted");
216
+ return true;
217
+ }
218
+
219
+ unmount(wnd: ReadiumWindow, comms: Comms): boolean {
220
+ comms.unregisterAll(WebPubSnapper.moduleName);
221
+ this.resizeObserver.disconnect();
222
+ if (this.handleScroll) wnd.removeEventListener("scroll", this.handleScroll);
223
+ comms.log("WebPubSnapper Unmounted");
224
+ return true;
225
+ }
226
+ }
@@ -15,7 +15,7 @@ export interface ReadiumWindow extends Window {
15
15
  getCssSelector: typeof getCssSelector;
16
16
  };
17
17
  navigator: Navigator & {
18
- epubReadingSystem: EPUBReadingSystem;
18
+ epubReadingSystem?: EPUBReadingSystem;
19
19
  };
20
20
  }
21
21
  export declare function deselect(wnd: ReadiumWindow): void;
@@ -1,5 +1,6 @@
1
1
  import { ModuleDerived } from "./Module";
2
- export type ModuleName = "setup" | "snapper" | "column_snapper" | "scroll_snapper" | "fixed_setup" | "decorator" | "reflowable_setup" | "peripherals";
2
+ export type ModuleName = "setup" | "snapper" | "column_snapper" | "scroll_snapper" | "webpub_snapper" | "fixed_setup" | "decorator" | "reflowable_setup" | "peripherals" | "webpub_setup";
3
3
  export declare const FXLModules: ModuleName[];
4
4
  export declare const ReflowableModules: ModuleName[];
5
+ export declare const WebPubModules: ModuleName[];
5
6
  export declare const ModuleLibrary: Map<string, ModuleDerived>;
@@ -0,0 +1,10 @@
1
+ import { Comms } from "../../comms/comms";
2
+ import { ReadiumWindow } from "../../helpers/dom";
3
+ import { Module } from "../Module";
4
+ export declare class WebPubSetup extends Module {
5
+ static readonly moduleName = "webpub_setup";
6
+ private comms;
7
+ wndOnErr(event: ErrorEvent): void;
8
+ mount(wnd: ReadiumWindow, comms: Comms): boolean;
9
+ unmount(wnd: ReadiumWindow, comms: Comms): boolean;
10
+ }
@@ -0,0 +1,20 @@
1
+ import { Comms } from "../../comms/comms";
2
+ import { ReadiumWindow } from "../../helpers/dom";
3
+ import { ModuleName } from "../ModuleLibrary";
4
+ import { Snapper } from "./Snapper";
5
+ export declare class WebPubSnapper extends Snapper {
6
+ static readonly moduleName: ModuleName;
7
+ private wnd;
8
+ private comms;
9
+ private resizeObserver;
10
+ private initialScrollHandled;
11
+ private isScrolling;
12
+ private lastScrollTop;
13
+ private isResizing;
14
+ private resizeDebounce;
15
+ private doc;
16
+ private reportProgress;
17
+ private handleScroll;
18
+ mount(wnd: ReadiumWindow, comms: Comms): boolean;
19
+ unmount(wnd: ReadiumWindow, comms: Comms): boolean;
20
+ }