@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.
- package/dist/index.js +818 -658
- package/dist/index.umd.cjs +22 -11
- package/package.json +1 -1
- package/src/Loader.ts +3 -3
- package/src/helpers/document.ts +10 -1
- package/src/modules/ModuleLibrary.ts +4 -0
- package/src/modules/snapper/CJKVerticalSnapper.ts +333 -0
- package/src/modules/snapper/ColumnSnapper.ts +139 -59
- package/types/src/Loader.d.ts +2 -2
- package/types/src/helpers/document.d.ts +1 -0
- package/types/src/modules/ModuleLibrary.d.ts +1 -1
- package/types/src/modules/snapper/CJKVerticalSnapper.d.ts +58 -0
- package/types/src/modules/snapper/ColumnSnapper.d.ts +12 -0
|
@@ -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
|
-
|
|
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 + (
|
|
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
|
|
58
|
-
const
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
142
|
+
const snappedNorm = Math.min(maxScroll, Math.max(0, this.snapNormOffset(currentNorm + hurdle)));
|
|
113
143
|
|
|
114
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
197
|
-
//
|
|
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(
|
|
202
|
-
if(
|
|
203
|
-
} else if(
|
|
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((
|
|
207
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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);
|
|
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
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
456
|
-
if(this.
|
|
457
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
}
|
package/types/src/Loader.d.ts
CHANGED
|
@@ -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;
|