@readium/navigator-html-injectables 2.3.1 → 2.4.0-beta.2

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.
@@ -36,26 +36,50 @@ export class ColumnSnapper extends Snapper {
36
36
  private lastTurnTime: number = 0;
37
37
  private wnd!: ReadiumWindow;
38
38
  private comms!: Comms;
39
+ private rtl = false;
39
40
  private doc() { return this.wnd.document.scrollingElement as HTMLElement; }
40
41
  private scrollOffset() {
41
42
  // The reason we do this is because when the document is transformed (translate3d),
42
43
  // the scrollLeft value is 0 because... reasons. So we have to use the cached value
43
44
  // from this.alreadyScrollLeft instead.
44
- return (this.doc().scrollLeft > 0) ? this.doc().scrollLeft : this.alreadyScrollLeft;
45
+ // Note: for RTL, scrollLeft is negative, so we check !== 0, not > 0.
46
+ const sl = this.doc().scrollLeft;
47
+ return (sl !== 0) ? sl : this.alreadyScrollLeft;
45
48
  }
46
49
 
47
50
  snapOffset(offset: number) {
48
- const value = offset + (isRTL(this.wnd) ? -1 : 1);
51
+ const value = offset + (this.rtl ? -1 : 1);
49
52
  return value - (value % this.wnd.innerWidth);
50
53
  }
51
54
 
55
+ /**
56
+ * Snap a non-negative normalized offset (distance from document start) to
57
+ * the nearest page boundary. Works identically regardless of reading direction.
58
+ */
59
+ private snapNormOffset(offset: number): number {
60
+ const value = offset + 1;
61
+ return value - (value % this.wnd.innerWidth);
62
+ }
63
+
64
+ /**
65
+ * Normalized scroll position: distance from document start, always in
66
+ * [0, scrollWidth - innerWidth] regardless of reading direction.
67
+ * scrollLeft may be negative depending on the browser — Math.abs() normalizes it.
68
+ */
69
+ private normScroll(): number {
70
+ const raw = this.doc().scrollLeft || this.alreadyScrollLeft;
71
+ return this.rtl ? Math.abs(raw) : Math.max(0, this.wnd.scrollX > 0 ? this.wnd.scrollX : raw);
72
+ }
73
+
52
74
  reportProgress() {
53
- // Contrary to ScrollTop, Android slightly adds to scrollX
54
- // So we do not need to round it up
55
- const scrollX = this.wnd.scrollX;
56
75
  const scrollWidth = this.cachedScrollWidth;
57
- const progress = Math.max(0, Math.min(1, scrollX / scrollWidth));
58
- const viewportEnd = Math.max(0, Math.min(1, (scrollX + this.wnd.innerWidth) / scrollWidth));
76
+ const viewportWidth = this.wnd.innerWidth;
77
+ const norm = this.normScroll();
78
+ const scrollable = Math.max(1, scrollWidth - viewportWidth);
79
+
80
+ // progress 0 = document start, 1 = document end — same regardless of direction.
81
+ const progress = Math.max(0, Math.min(1, norm / scrollable));
82
+ const viewportEnd = Math.max(0, Math.min(1, (norm + viewportWidth) / scrollWidth));
59
83
  this.comms.send("progress", {
60
84
  start: progress,
61
85
  end: viewportEnd
@@ -69,11 +93,17 @@ export class ColumnSnapper extends Snapper {
69
93
  if(this.overscroll !== 0 || this.shakeTimeout !== 0) return;
70
94
  const doc = this.doc();
71
95
 
72
- doc.classList.add((isRTL(this.wnd) ? "readium-bounce-l" : "readium-bounce-r"));
96
+ // Bounce toward the boundary that was hit.
97
+ // LTR: end is on the right → bounce-r. RTL: start is on the right (norm≈0),
98
+ // end is on the left → bounce direction flips based on which boundary we're at.
99
+ const atStart = this.normScroll() < 5;
100
+ const bounceClass = this.rtl
101
+ ? (atStart ? "readium-bounce-r" : "readium-bounce-l")
102
+ : "readium-bounce-r";
103
+ doc.classList.add(bounceClass);
73
104
  const curScrollLeft = this.scrollOffset();
74
105
  this.shakeTimeout = this.wnd.setTimeout(() => {
75
- doc.classList.remove("readium-bounce-l");
76
- doc.classList.remove("readium-bounce-r");
106
+ doc.classList.remove(bounceClass);
77
107
  this.shakeTimeout = 0;
78
108
  this.doc().scrollLeft = curScrollLeft;
79
109
  }, 150);
@@ -95,33 +125,36 @@ export class ColumnSnapper extends Snapper {
95
125
 
96
126
  // Snaps the current offset to the page width.
97
127
  snapCurrentOffset(smooth=false, noprogress=false) {
98
- const startX = this.wnd.scrollX > 0 ? this.wnd.scrollX : this.alreadyScrollLeft;
99
128
  const doc = this.doc();
100
- const cdo = this.dragOffset();
101
129
  const columnCount = getColumnCountPerScreen(this.wnd);
102
- const currentOffset = Math.min(Math.max(0, startX), this.cachedScrollWidth);
103
- const factor = isRTL(this.wnd) ? -1 : 1;
104
- const hurdle =
105
- // The hurdle to overcome in order to change pages
106
- factor *
107
- (this.wnd.innerWidth / 3) *
108
- ((factor * cdo) > 0 ? 2 : 1);
130
+ const maxScroll = this.cachedScrollWidth - this.wnd.innerWidth;
109
131
 
110
- const so = this.snapOffset(currentOffset + hurdle);
132
+ // Work in normalized (non-negative, distance-from-start) coordinates
133
+ // so all arithmetic is direction-agnostic.
134
+ const currentNorm = Math.min(Math.max(0, this.normScroll()), maxScroll);
135
+
136
+ // Forward drag: LTR = drag left (cdo > 0), RTL = drag right (cdo < 0).
137
+ // Normalize so that positive = moving forward in reading order.
138
+ const cdo = this.dragOffset();
139
+ const normalizedCdo = this.rtl ? -cdo : cdo;
140
+ const hurdle = (this.wnd.innerWidth / 3) * (normalizedCdo > 0 ? 2 : 1);
111
141
 
112
- const direction = so > this.scrollOffset() ? "right" : "left";
142
+ const snappedNorm = Math.min(maxScroll, Math.max(0, this.snapNormOffset(currentNorm + hurdle)));
113
143
 
114
- this.checkSuspiciousSnap(direction, Math.abs(so - this.scrollOffset()));
144
+ // Convert back to signed scrollLeft for the actual DOM write.
145
+ const so = this.rtl ? -snappedNorm : snappedNorm;
146
+ const currentScrollLeft = this.rtl ? -currentNorm : currentNorm;
115
147
 
116
- if(smooth && so !== this.scrollOffset()) { // Smooth snapping
148
+ const direction = so > currentScrollLeft ? "right" : "left";
149
+ this.checkSuspiciousSnap(direction, Math.abs(so - currentScrollLeft));
150
+
151
+ if(smooth && so !== currentScrollLeft) { // Smooth snapping
117
152
  this.snappingCancelled = false;
118
153
  const position = (start: number, end: number, elapsed: number, period: number) => {
119
- if (elapsed > period) {
120
- return end;
121
- }
154
+ if (elapsed > period) return end;
122
155
  return start + (end - start) * easeInOutQuad(elapsed / period);
123
- }
124
- const period = /*Math.abs(startX - (this.useTransform ? currentOffset : 0)) < 10 ? 1 : */SNAP_DURATION * columnCount; // TODO revamp
156
+ };
157
+ const period = SNAP_DURATION * columnCount;
125
158
  let startTime: number;
126
159
  const step = (timestamp: number) => {
127
160
  if(this.snappingCancelled) return;
@@ -130,7 +163,7 @@ export class ColumnSnapper extends Snapper {
130
163
  const elapsed = timestamp - startTime;
131
164
 
132
165
  const lpos = position(this.overscroll, 0, elapsed, period);
133
- const spos = position(startX, so, elapsed, period);
166
+ const spos = position(currentScrollLeft, so, elapsed, period);
134
167
  doc.scrollLeft = spos;
135
168
  if(this.overscroll !== 0)
136
169
  doc.style.transform = `translate3d(${-lpos}px, 0px, 0px)`;
@@ -143,7 +176,7 @@ export class ColumnSnapper extends Snapper {
143
176
  doc.scrollLeft = so;
144
177
  if(!noprogress) this.reportProgress();
145
178
  }
146
- }
179
+ };
147
180
  this.wnd.requestAnimationFrame(step);
148
181
  } else { // Instant snapping
149
182
  doc.style.removeProperty("transform");
@@ -193,18 +226,24 @@ export class ColumnSnapper extends Snapper {
193
226
  if(this.touchState === ScrollTouchState.MOVE) {
194
227
  // Get the horizontal drag distance
195
228
  const dragOffset = this.dragOffset();
196
- const scrollOffset = this.scrollOffset();
197
- // this.cachedScrollWidth = this.doc().scrollWidth!;
229
+
230
+ // Normalize: distance from document start, forward drag is positive.
231
+ const norm = this.normScroll();
232
+ const maxScroll = this.cachedScrollWidth - this.wnd.innerWidth;
233
+ const normalizedDrag = this.rtl ? -dragOffset : dragOffset;
234
+
198
235
  if(this.cachedScrollWidth <= this.wnd.innerWidth) {
199
236
  // Only a single page, meaning any swipe triggers next/prev
200
237
  this.reportProgress();
201
- if(dragOffset > 5) this.comms.send("no_more", undefined);
202
- if(dragOffset < -5) this.comms.send("no_less", undefined);
203
- } else if(scrollOffset < 5 && dragOffset < 5) {
238
+ if(normalizedDrag > 5) this.comms.send("no_more", undefined);
239
+ if(normalizedDrag < -5) this.comms.send("no_less", undefined);
240
+ } else if(norm < 5 && normalizedDrag < 5) {
241
+ // At document start, tried to go backward
204
242
  this.alreadyScrollLeft = 0;
205
243
  this.comms.send("no_less", undefined);
206
- } else if((this.cachedScrollWidth - scrollOffset - this.wnd.innerWidth) < 5 && dragOffset > 5) {
207
- this.alreadyScrollLeft = this.cachedScrollWidth;
244
+ } else if((maxScroll - norm) < 5 && normalizedDrag > 5) {
245
+ // At document end, tried to go forward
246
+ this.alreadyScrollLeft = this.rtl ? -maxScroll : maxScroll;
208
247
  this.comms.send("no_more", undefined);
209
248
  }
210
249
 
@@ -235,16 +274,22 @@ export class ColumnSnapper extends Snapper {
235
274
 
236
275
  const dro = this.dragOffset();
237
276
  const newpos = this.alreadyScrollLeft + dro;
238
- if(newpos < 0) {
277
+
278
+ // For RTL, scrollLeft is in [-(scrollWidth - innerWidth), 0].
279
+ // For LTR, scrollLeft is in [0, scrollWidth - innerWidth].
280
+ const minScrollLeft = this.rtl ? -(this.cachedScrollWidth - this.wnd.innerWidth) : 0;
281
+ const maxScrollLeft = this.rtl ? 0 : (this.cachedScrollWidth - this.wnd.innerWidth);
282
+
283
+ if(newpos < minScrollLeft) {
239
284
  this.overscroll = newpos;
240
285
  this.doc().style.transform = `translate3d(${-this.overscroll}px, 0px, 0px)`;
241
- } else if((newpos + this.wnd.innerWidth) > this.cachedScrollWidth) {
286
+ } else if(newpos > maxScrollLeft) {
242
287
  this.overscroll = newpos;
243
288
  this.doc().style.transform = `translate3d(${-newpos}px, 0px, 0px)`;
244
289
  } else {
245
290
  this.overscroll = 0;
246
291
  this.doc().style.removeProperty("transform");
247
- this.doc().scrollLeft = this.alreadyScrollLeft + dro;
292
+ this.doc().scrollLeft = newpos;
248
293
  }
249
294
  }
250
295
  private readonly onTouchMover = this.onTouchMove.bind(this);
@@ -283,6 +328,7 @@ export class ColumnSnapper extends Snapper {
283
328
  mount(wnd: ReadiumWindow, comms: Comms): boolean {
284
329
  this.wnd = wnd;
285
330
  this.comms = comms;
331
+ this.rtl = isRTL(wnd);
286
332
  if(!super.mount(wnd, comms)) return false;
287
333
 
288
334
  wnd.navigator.epubReadingSystem && (wnd.navigator.epubReadingSystem.layoutStyle = "paginated");
@@ -371,10 +417,19 @@ export class ColumnSnapper extends Snapper {
371
417
  ack(true);
372
418
  });
373
419
 
420
+ // LTR only — snapOffset only works with non-negative values.
374
421
  const scrollToOffset = (offset: number): boolean => {
375
422
  const oldScrollLeft = this.doc().scrollLeft;
376
- this.doc().scrollLeft = this.snapOffset(offset); // TODO assert if never undefined (same for rest of !)
423
+ this.doc().scrollLeft = this.snapOffset(offset);
424
+ return oldScrollLeft !== this.doc().scrollLeft;
425
+ }
377
426
 
427
+ // RTL: offset is normalized (non-negative, 0 = start, scrollable = end).
428
+ // Snaps to page boundary and sets scrollLeft = -snappedNorm.
429
+ const rtlScrollToNorm = (norm: number): boolean => {
430
+ const oldScrollLeft = this.doc().scrollLeft;
431
+ const snapped = this.snapNormOffset(Math.max(0, Math.min(this.cachedScrollWidth - wnd.innerWidth, norm)));
432
+ this.doc().scrollLeft = -snapped;
378
433
  return oldScrollLeft !== this.doc().scrollLeft;
379
434
  }
380
435
 
@@ -393,10 +448,13 @@ export class ColumnSnapper extends Snapper {
393
448
  }
394
449
  this.wnd.requestAnimationFrame(() => {
395
450
  this.cachedScrollWidth = this.doc().scrollWidth!;
396
- const documentWidth = this.cachedScrollWidth;
397
- const factor = isRTL(wnd) ? -1 : 1;
398
- const offset = documentWidth * position * factor;
399
- this.doc().scrollLeft = this.snapOffset(offset);
451
+ const scrollable = this.cachedScrollWidth - wnd.innerWidth;
452
+ const norm = scrollable * position;
453
+ if (this.rtl) {
454
+ this.doc().scrollLeft = -this.snapNormOffset(norm);
455
+ } else {
456
+ this.doc().scrollLeft = this.snapOffset(norm);
457
+ }
400
458
  this.reportProgress();
401
459
  deselect(this.wnd);
402
460
  ack(true);
@@ -410,7 +468,11 @@ export class ColumnSnapper extends Snapper {
410
468
  return;
411
469
  }
412
470
  this.wnd.requestAnimationFrame(() => {
413
- this.doc().scrollLeft = this.snapOffset(element.getBoundingClientRect().left + wnd.scrollX);
471
+ if (this.rtl) {
472
+ this.doc().scrollLeft = -this.snapNormOffset(element.getBoundingClientRect().left + wnd.scrollX);
473
+ } else {
474
+ this.doc().scrollLeft = this.snapOffset(element.getBoundingClientRect().left + wnd.scrollX);
475
+ }
414
476
  this.reportProgress();
415
477
  deselect(this.wnd);
416
478
  ack(true);
@@ -441,7 +503,11 @@ export class ColumnSnapper extends Snapper {
441
503
  return;
442
504
  }
443
505
  this.wnd.requestAnimationFrame(() => {
444
- this.doc().scrollLeft = this.snapOffset(r.getBoundingClientRect().left + wnd.scrollX);
506
+ if (this.rtl) {
507
+ this.doc().scrollLeft = -this.snapNormOffset(r.getBoundingClientRect().left + wnd.scrollX);
508
+ } else {
509
+ this.doc().scrollLeft = this.snapOffset(r.getBoundingClientRect().left + wnd.scrollX);
510
+ }
445
511
  this.reportProgress();
446
512
  deselect(this.wnd);
447
513
  ack(true);
@@ -449,12 +515,18 @@ export class ColumnSnapper extends Snapper {
449
515
  });
450
516
 
451
517
  comms.register("go_end", ColumnSnapper.moduleName, (_, ack) => {
452
- const factor = isRTL(wnd) ? -1 : 1;
453
518
  this.wnd.requestAnimationFrame(() => {
454
519
  this.cachedScrollWidth = this.doc().scrollWidth!;
455
- const final = this.cachedScrollWidth * factor;
456
- if(this.doc().scrollLeft === final) return ack(false);
457
- this.doc().scrollLeft = this.snapOffset(final);
520
+ let snappedFinal: number;
521
+ if (this.rtl) {
522
+ // RTL: end is the leftmost position, using normalized snapping first
523
+ snappedFinal = -this.snapNormOffset(this.cachedScrollWidth - wnd.innerWidth);
524
+ } else {
525
+ // LTR: end is scrollWidth (browser clamps to scrollWidth - innerWidth)
526
+ snappedFinal = this.snapOffset(this.cachedScrollWidth);
527
+ }
528
+ if(this.doc().scrollLeft === snappedFinal) return ack(false);
529
+ this.doc().scrollLeft = snappedFinal;
458
530
  this.reportProgress();
459
531
  deselect(this.wnd);
460
532
  ack(true);
@@ -474,11 +546,15 @@ export class ColumnSnapper extends Snapper {
474
546
  comms.register("go_prev", ColumnSnapper.moduleName, (_, ack) => {
475
547
  this.wnd.requestAnimationFrame(() => {
476
548
  this.cachedScrollWidth = this.doc().scrollWidth!;
477
- const offset = wnd.scrollX - wnd.innerWidth;
478
- const minOffset = isRTL(wnd) ? - (this.cachedScrollWidth - wnd.innerWidth) : 0;
479
- const change = scrollToOffset(Math.max(offset, minOffset));
549
+ let change: boolean;
550
+ if (this.rtl) {
551
+ // RTL: go_prev = toward document start = decreasing norm = scrollLeft toward 0
552
+ change = rtlScrollToNorm(this.normScroll() - wnd.innerWidth);
553
+ } else {
554
+ change = scrollToOffset(wnd.scrollX - wnd.innerWidth);
555
+ }
556
+ this.reportProgress();
480
557
  if(change) {
481
- this.reportProgress();
482
558
  deselect(this.wnd);
483
559
  this.checkSuspiciousSnap("left", this.wnd.innerWidth);
484
560
  }
@@ -489,11 +565,15 @@ export class ColumnSnapper extends Snapper {
489
565
  comms.register("go_next", ColumnSnapper.moduleName, (_, ack) => {
490
566
  this.wnd.requestAnimationFrame(() => {
491
567
  this.cachedScrollWidth = this.doc().scrollWidth!;
492
- const offset = wnd.scrollX + wnd.innerWidth;
493
- const maxOffset = isRTL(wnd) ? 0 : this.cachedScrollWidth - wnd.innerWidth;
494
- const change = scrollToOffset(Math.min(offset, maxOffset));
568
+ let change: boolean;
569
+ if (this.rtl) {
570
+ // RTL: go_next = toward document end = increasing norm = scrollLeft more negative
571
+ change = rtlScrollToNorm(this.normScroll() + wnd.innerWidth);
572
+ } else {
573
+ change = scrollToOffset(wnd.scrollX + wnd.innerWidth);
574
+ }
575
+ this.reportProgress();
495
576
  if(change) {
496
- this.reportProgress();
497
577
  deselect(this.wnd);
498
578
  this.checkSuspiciousSnap("right", this.wnd.innerWidth);
499
579
  }
@@ -1,5 +1,5 @@
1
- import { ReadiumWindow } from "./helpers/dom";
2
- import { ModuleName } from "./modules";
1
+ import { ReadiumWindow } from "./helpers/dom.ts";
2
+ import { ModuleName } from "./modules/index.ts";
3
3
  /**
4
4
  * The Module loader. Handles initialization of the HTML injectables
5
5
  * in the target Window, which could be an IFrame, or the current window).
@@ -1,5 +1,6 @@
1
1
  import { ReadiumWindow } from "./dom.ts";
2
2
  export declare function isRTL(wnd: ReadiumWindow): boolean;
3
+ export declare function isVerticalLR(wnd: ReadiumWindow): boolean;
3
4
  export declare function getColumnCountPerScreen(wnd: ReadiumWindow): number;
4
5
  /**
5
6
  * Returns the "content height" of an element, which is its clientHeight
@@ -1,5 +1,5 @@
1
1
  import { ModuleDerived } from "./Module.ts";
2
- export type ModuleName = "setup" | "snapper" | "column_snapper" | "scroll_snapper" | "webpub_snapper" | "fixed_setup" | "decorator" | "reflowable_setup" | "peripherals" | "webpub_setup" | "print_protection";
2
+ export type ModuleName = "setup" | "snapper" | "column_snapper" | "scroll_snapper" | "cjk_vertical_snapper" | "webpub_snapper" | "fixed_setup" | "decorator" | "reflowable_setup" | "peripherals" | "webpub_setup" | "print_protection";
3
3
  export declare const FXLModules: ModuleName[];
4
4
  export declare const ReflowableModules: ModuleName[];
5
5
  export declare const WebPubModules: ModuleName[];
@@ -0,0 +1,58 @@
1
+ import { Comms } from "../../comms/comms.ts";
2
+ import { ReadiumWindow } from "../../helpers/dom.ts";
3
+ import { ModuleName } from "../ModuleLibrary.ts";
4
+ import { Snapper } from "./Snapper.ts";
5
+ import { BaseSuspiciousActivityEvent } from "../Peripherals.ts";
6
+ export interface SuspiciousCJKScrollingEvent extends BaseSuspiciousActivityEvent {
7
+ type: "suspicious_scrolling";
8
+ scrollDelta: number;
9
+ scrollDirection: "left" | "right";
10
+ targetElement: {
11
+ tagName: string;
12
+ } | null;
13
+ }
14
+ /**
15
+ * Snapper for CJK vertical writing (writing-mode: vertical-rl).
16
+ *
17
+ * In vertical-rl, content overflows on the X axis, flowing from right (start)
18
+ * to left (end). This snapper is the horizontal counterpart of ScrollSnapper:
19
+ * it tracks scrollLeft / scrollWidth / innerWidth and reports progress
20
+ * accordingly.
21
+ *
22
+ * Progress convention:
23
+ * scrollLeft = 0 (right edge, document start) → progress 0
24
+ * scrollLeft = ±(scrollWidth − clientWidth) → progress 1 (document end, left edge)
25
+ *
26
+ * All modern browsers use negative scrollLeft for vertical-rl (range: [−scrollable, 0]).
27
+ * Read it with Math.abs() to get a non-negative offset from document start.
28
+ * Write absolute positions as negative values (go_progression, go_end).
29
+ * Write relative adjustments as viewport-coordinate deltas (go_id, go_text) — this
30
+ * is sign-agnostic and works regardless of browser convention.
31
+ *
32
+ * go_next / go_prev always return false so that the navigator advances to the
33
+ * next or previous spine item — this snapper intentionally provides free
34
+ * horizontal scroll without intra-resource page fragmentation.
35
+ */
36
+ export declare class CJKVerticalSnapper extends Snapper {
37
+ static readonly moduleName: ModuleName;
38
+ private wnd;
39
+ private comms;
40
+ private resizeObserver;
41
+ private patternAnalyzer;
42
+ private lastScrollTime;
43
+ private isScrollProtectionEnabled;
44
+ private initialScrollHandled;
45
+ private isScrolling;
46
+ private lastScrollLeft;
47
+ private isResizing;
48
+ private resizeDebounce;
49
+ private verticalLR;
50
+ private doc;
51
+ /** Total horizontally scrollable distance (magnitude). */
52
+ private scrollable;
53
+ private reportProgress;
54
+ private handleScroll;
55
+ private enableScrollProtection;
56
+ mount(wnd: ReadiumWindow, comms: Comms): boolean;
57
+ unmount(wnd: ReadiumWindow, comms: Comms): boolean;
58
+ }
@@ -19,9 +19,21 @@ export declare class ColumnSnapper extends Snapper {
19
19
  private lastTurnTime;
20
20
  private wnd;
21
21
  private comms;
22
+ private rtl;
22
23
  private doc;
23
24
  private scrollOffset;
24
25
  snapOffset(offset: number): number;
26
+ /**
27
+ * Snap a non-negative normalized offset (distance from document start) to
28
+ * the nearest page boundary. Works identically regardless of reading direction.
29
+ */
30
+ private snapNormOffset;
31
+ /**
32
+ * Normalized scroll position: distance from document start, always in
33
+ * [0, scrollWidth - innerWidth] regardless of reading direction.
34
+ * scrollLeft may be negative depending on the browser — Math.abs() normalizes it.
35
+ */
36
+ private normScroll;
25
37
  reportProgress(): void;
26
38
  private shakeTimeout;
27
39
  shake(): void;