@readium/navigator-html-injectables 1.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.
Files changed (77) hide show
  1. package/README.MD +13 -0
  2. package/dist/index.js +3144 -0
  3. package/dist/index.umd.cjs +69 -0
  4. package/package.json +58 -0
  5. package/src/Loader.ts +85 -0
  6. package/src/comms/comms.ts +168 -0
  7. package/src/comms/index.ts +3 -0
  8. package/src/comms/keys.ts +42 -0
  9. package/src/comms/mid.ts +5 -0
  10. package/src/helpers/animation.ts +5 -0
  11. package/src/helpers/css.ts +16 -0
  12. package/src/helpers/document.ts +43 -0
  13. package/src/helpers/dom.ts +126 -0
  14. package/src/helpers/locator.ts +67 -0
  15. package/src/helpers/rect.ts +301 -0
  16. package/src/helpers/scrollSnapperHelper.ts +43 -0
  17. package/src/index.ts +3 -0
  18. package/src/modules/Decorator.ts +462 -0
  19. package/src/modules/Module.ts +23 -0
  20. package/src/modules/ModuleLibrary.ts +44 -0
  21. package/src/modules/Peripherals.ts +148 -0
  22. package/src/modules/index.ts +3 -0
  23. package/src/modules/setup/FixedSetup.ts +73 -0
  24. package/src/modules/setup/ReflowableSetup.ts +65 -0
  25. package/src/modules/setup/Setup.ts +131 -0
  26. package/src/modules/snapper/ColumnSnapper.ts +468 -0
  27. package/src/modules/snapper/ScrollSnapper.ts +168 -0
  28. package/src/modules/snapper/Snapper.ts +49 -0
  29. package/src/vendor/approx-string-match/LICENSE +21 -0
  30. package/src/vendor/approx-string-match/README.MD +1 -0
  31. package/src/vendor/approx-string-match/index.ts +362 -0
  32. package/src/vendor/hypothesis/README.MD +1 -0
  33. package/src/vendor/hypothesis/anchoring/api-types.ts +309 -0
  34. package/src/vendor/hypothesis/anchoring/html.ts +134 -0
  35. package/src/vendor/hypothesis/anchoring/match-quote.ts +163 -0
  36. package/src/vendor/hypothesis/anchoring/placeholder.ts +59 -0
  37. package/src/vendor/hypothesis/anchoring/text-range.ts +327 -0
  38. package/src/vendor/hypothesis/anchoring/trim-range.ts +220 -0
  39. package/src/vendor/hypothesis/anchoring/types.ts +377 -0
  40. package/src/vendor/hypothesis/anchoring/xpath.ts +164 -0
  41. package/src/vendor/hypothesis/tsconfig.json +33 -0
  42. package/src/vendor/hypothesis/types/shared.ts +40 -0
  43. package/types/src/Loader.d.ts +33 -0
  44. package/types/src/comms/comms.d.ts +40 -0
  45. package/types/src/comms/index.d.ts +3 -0
  46. package/types/src/comms/keys.d.ts +2 -0
  47. package/types/src/comms/mid.d.ts +1 -0
  48. package/types/src/helpers/animation.d.ts +1 -0
  49. package/types/src/helpers/css.d.ts +4 -0
  50. package/types/src/helpers/document.d.ts +8 -0
  51. package/types/src/helpers/dom.d.ts +15 -0
  52. package/types/src/helpers/locator.d.ts +2 -0
  53. package/types/src/helpers/rect.d.ts +10 -0
  54. package/types/src/helpers/scrollSnapperHelper.d.ts +5 -0
  55. package/types/src/index.d.ts +3 -0
  56. package/types/src/modules/Decorator.d.ts +43 -0
  57. package/types/src/modules/Module.d.ts +11 -0
  58. package/types/src/modules/ModuleLibrary.d.ts +5 -0
  59. package/types/src/modules/Peripherals.d.ts +39 -0
  60. package/types/src/modules/ReflowablePeripherals.d.ts +37 -0
  61. package/types/src/modules/index.d.ts +3 -0
  62. package/types/src/modules/setup/FixedSetup.d.ts +9 -0
  63. package/types/src/modules/setup/ReflowableSetup.d.ts +9 -0
  64. package/types/src/modules/setup/Setup.d.ts +17 -0
  65. package/types/src/modules/snapper/ColumnSnapper.d.ts +41 -0
  66. package/types/src/modules/snapper/ScrollSnapper.d.ts +16 -0
  67. package/types/src/modules/snapper/Snapper.d.ts +11 -0
  68. package/types/src/vendor/approx-string-match/index.d.ts +54 -0
  69. package/types/src/vendor/hypothesis/anchoring/api-types.d.ts +266 -0
  70. package/types/src/vendor/hypothesis/anchoring/html.d.ts +17 -0
  71. package/types/src/vendor/hypothesis/anchoring/match-quote.d.ts +30 -0
  72. package/types/src/vendor/hypothesis/anchoring/placeholder.d.ts +32 -0
  73. package/types/src/vendor/hypothesis/anchoring/text-range.d.ts +103 -0
  74. package/types/src/vendor/hypothesis/anchoring/trim-range.d.ts +17 -0
  75. package/types/src/vendor/hypothesis/anchoring/types.d.ts +102 -0
  76. package/types/src/vendor/hypothesis/anchoring/xpath.d.ts +15 -0
  77. package/types/src/vendor/hypothesis/types/shared.d.ts +32 -0
@@ -0,0 +1,43 @@
1
+ import { ReadiumWindow } from "./dom";
2
+
3
+ export function isRTL(wnd: ReadiumWindow): boolean {
4
+ return wnd.document.body.dir.toLowerCase() === "rtl";
5
+ }
6
+
7
+ export function getColumnCountPerScreen(wnd: ReadiumWindow) {
8
+ return parseInt(
9
+ wnd.getComputedStyle(
10
+ wnd.document.documentElement
11
+ ).getPropertyValue("column-count")
12
+ );
13
+ }
14
+
15
+ /**
16
+ * Having an odd number of columns when displaying two columns per screen causes snapping and page
17
+ * turning issues. To fix this, we insert a blank virtual column at the end of the resource.
18
+ */
19
+ export function appendVirtualColumnIfNeeded(wnd: ReadiumWindow) {
20
+ const id = "readium-virtual-page";
21
+ let virtualCol = wnd.document.getElementById(id);
22
+ if (getColumnCountPerScreen(wnd) !== 2) {
23
+ if (virtualCol) {
24
+ virtualCol.remove();
25
+ }
26
+ } else {
27
+ const documentWidth = wnd.document.scrollingElement!.scrollWidth;
28
+ const colCount = documentWidth / wnd.innerWidth;
29
+ const hasOddColCount = (Math.round(colCount * 2) / 2) % 1 > 0.1;
30
+ if (hasOddColCount) {
31
+ if (virtualCol)
32
+ virtualCol.remove();
33
+ else {
34
+ virtualCol = wnd.document.createElement("div");
35
+ virtualCol.setAttribute("id", id);
36
+ virtualCol.dataset.readium = "true";
37
+ virtualCol.style.breakBefore = "column";
38
+ virtualCol.innerHTML = "​"; // zero-width space
39
+ wnd.document.body.appendChild(virtualCol);
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,126 @@
1
+ // (From swift-toolkit @ https://github.com/hypothesis/client/blob/main/src/annotator/highlighter.ts)
2
+
3
+ import { Locator, LocatorLocations, LocatorText } from "@readium/shared";
4
+ import type { getCssSelector } from "css-selector-generator";
5
+
6
+ type BlockedEventData = [0, Function, any[], any[]] | [1, Event, EventTarget];
7
+
8
+ // This is what is injected into the HTML documents
9
+ export interface ReadiumWindow extends Window {
10
+ _readium_blockEvents: boolean;
11
+ _readium_blockedEvents: BlockedEventData[];
12
+ _readium_eventBlocker: EventListenerOrEventListenerObject;
13
+ _readium_cssSelectorGenerator: {
14
+ getCssSelector: typeof getCssSelector;
15
+ };
16
+ }
17
+
18
+ export function deselect(wnd: ReadiumWindow) {
19
+ const currentSelection = wnd.getSelection();
20
+ if(currentSelection) currentSelection.removeAllRanges();
21
+ }
22
+
23
+ const interactiveTags = [
24
+ "a",
25
+ "audio",
26
+ "button",
27
+ "canvas",
28
+ "details",
29
+ "input",
30
+ "label",
31
+ "option",
32
+ "select",
33
+ "submit",
34
+ "textarea",
35
+ "video",
36
+ ];
37
+
38
+ // See https://github.com/JayPanoz/architecture/tree/touch-handling/misc/touch-handling
39
+ export function nearestInteractiveElement(element: Element): Element | null {
40
+ if (interactiveTags.indexOf(element.nodeName.toLowerCase()) !== -1) {
41
+ return element;
42
+ }
43
+
44
+ // Checks whether the element is editable by the user.
45
+ if (
46
+ element.hasAttribute("contenteditable") &&
47
+ element.getAttribute("contenteditable")?.toLowerCase() !== "false"
48
+ ) {
49
+ return element;
50
+ }
51
+
52
+ // Checks parents recursively because the touch might be for example on an <em> inside a <a>.
53
+ if (element.parentElement) {
54
+ return nearestInteractiveElement(element.parentElement);
55
+ }
56
+
57
+ return null;
58
+ }
59
+
60
+ /// Returns the `Locator` object to the first block element that is visible on
61
+ /// the screen.
62
+ export function findFirstVisibleLocator(wnd: ReadiumWindow, scrolling: boolean) {
63
+ const element = findElement(wnd, wnd.document.body, scrolling);
64
+ return new Locator({
65
+ href: "#",
66
+ type: "application/xhtml+xml",
67
+ locations: new LocatorLocations({
68
+ otherLocations: new Map([
69
+ ["cssSelector", wnd._readium_cssSelectorGenerator.getCssSelector(element)]
70
+ ])
71
+ }),
72
+ text: new LocatorText({
73
+ highlight: element.textContent || undefined
74
+ }),
75
+ });
76
+ }
77
+
78
+ function findElement(wnd: ReadiumWindow, rootElement: Element, scrolling: boolean): Element {
79
+ for (var i = 0; i < rootElement.children.length; i++) {
80
+ const child = rootElement.children[i];
81
+ if (!shouldIgnoreElement(child) && isElementVisible(wnd, child, scrolling)) {
82
+ return findElement(wnd, child, scrolling);
83
+ }
84
+ }
85
+ return rootElement;
86
+ }
87
+
88
+ function isElementVisible(wnd: ReadiumWindow, element: Element, scrolling: boolean) {
89
+ // if (readium.isFixedLayout) return true;
90
+
91
+ if (element === document.body || element === document.documentElement) {
92
+ return true;
93
+ }
94
+ if (!document || !document.documentElement || !document.body) {
95
+ return false;
96
+ }
97
+
98
+ const rect = element.getBoundingClientRect();
99
+ if (scrolling) {
100
+ return rect.bottom > 0 && rect.top < wnd.innerHeight;
101
+ } else {
102
+ return rect.right > 0 && rect.left < wnd.innerWidth;
103
+ }
104
+ }
105
+
106
+ function shouldIgnoreElement(element: Element) {
107
+ const elStyle = getComputedStyle(element);
108
+ if (elStyle) {
109
+ const display = elStyle.getPropertyValue("display");
110
+ if (display != "block") {
111
+ return true;
112
+ }
113
+ // Cannot be relied upon, because web browser engine reports invisible when out of view in
114
+ // scrolled columns!
115
+ // const visibility = elStyle.getPropertyValue("visibility");
116
+ // if (visibility === "hidden") {
117
+ // return false;
118
+ // }
119
+ const opacity = elStyle.getPropertyValue("opacity");
120
+ if (opacity === "0") {
121
+ return true;
122
+ }
123
+ }
124
+
125
+ return false;
126
+ }
@@ -0,0 +1,67 @@
1
+ import { Locator } from "@readium/shared";
2
+ import { TextQuoteAnchor } from "../vendor/hypothesis/anchoring/types";
3
+
4
+ // Based on the kotlin-toolkit code
5
+ export function rangeFromLocator(doc: Document, locator: Locator) {
6
+ try {
7
+ let locations = locator.locations;
8
+ let text = locator.text;
9
+ if (text && text.highlight) {
10
+ let root;
11
+ if (locations && locations.getCssSelector()) {
12
+ root = doc.querySelector(locations.getCssSelector()!);
13
+ }
14
+ if (!root) {
15
+ root = doc.body;
16
+ }
17
+
18
+ let anchor = new TextQuoteAnchor(root, text.highlight, {
19
+ prefix: text.before,
20
+ suffix: text.after,
21
+ });
22
+ try {
23
+ return anchor.toRange();
24
+ } catch (error) {
25
+ // We don't watch to "crash" when the quote is not found
26
+ console.warn("Quote not found:", anchor);
27
+ return null;
28
+ }
29
+ }
30
+
31
+ if (locations) {
32
+ var element = null;
33
+
34
+ if (!element && locations.getCssSelector()) {
35
+ element = doc.querySelector(locations.getCssSelector()!);
36
+ }
37
+
38
+ if (!element && locations.fragments) {
39
+ for (const htmlId of locations.fragments) {
40
+ element = doc.getElementById(htmlId);
41
+ if (element) {
42
+ break;
43
+ }
44
+ }
45
+ }
46
+
47
+ if (element) {
48
+ let range = doc.createRange();
49
+
50
+ // This is a special case where the node is
51
+ // a single element with no children. Not sure
52
+ // yet how effective this is yet, may remove in future.
53
+ if(element.childNodes.length === 0) {
54
+ range.selectNodeContents(element);
55
+ return range;
56
+ }
57
+
58
+ range.setStartBefore(element);
59
+ range.setEndAfter(element);
60
+ return range;
61
+ }
62
+ }
63
+ } catch (e) {
64
+ console.error(e);
65
+ }
66
+ return null;
67
+ }
@@ -0,0 +1,301 @@
1
+ // Mostly copied from the mobile toolkit and adjusted for typescript
2
+ // https://github.com/readium/swift-toolkit/blob/develop/Sources/Navigator/EPUB/Scripts/src/rect.js
3
+
4
+ export interface Rect {
5
+ bottom: number;
6
+ height: number;
7
+ left: number;
8
+ right: number;
9
+ top: number;
10
+ width: number;
11
+ }
12
+
13
+ export function getClientRectsNoOverlap(
14
+ range: Range,
15
+ doNotMergeHorizontallyAlignedRects: boolean
16
+ ) {
17
+ let clientRects = range.getClientRects();
18
+
19
+ // Try falling back to the client rects of the common ancestor of the range if it's an HTML Element
20
+ if(!clientRects.length)
21
+ if(range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE)
22
+ clientRects = (range.commonAncestorContainer as HTMLElement).getClientRects();
23
+
24
+ const tolerance = 1;
25
+ const originalRects: Rect[] = [];
26
+ for (const rangeClientRect of clientRects) {
27
+ originalRects.push({
28
+ bottom: rangeClientRect.bottom,
29
+ height: rangeClientRect.height,
30
+ left: rangeClientRect.left,
31
+ right: rangeClientRect.right,
32
+ top: rangeClientRect.top,
33
+ width: rangeClientRect.width,
34
+ });
35
+ }
36
+ const mergedRects = mergeTouchingRects(
37
+ originalRects,
38
+ tolerance,
39
+ doNotMergeHorizontallyAlignedRects
40
+ );
41
+ const noContainedRects = removeContainedRects(mergedRects, tolerance);
42
+ const newRects = replaceOverlapingRects(noContainedRects);
43
+ const minArea = 2 * 2;
44
+ for (let j = newRects.length - 1; j >= 0; j--) {
45
+ const rect = newRects[j];
46
+ const bigEnough = rect.width * rect.height > minArea;
47
+ if (!bigEnough) {
48
+ if (newRects.length > 1) {
49
+ // log("CLIENT RECT: remove small");
50
+ newRects.splice(j, 1);
51
+ } else {
52
+ // log("CLIENT RECT: remove small, but keep otherwise empty!");
53
+ break;
54
+ }
55
+ }
56
+ }
57
+ // log(`CLIENT RECT: reduced ${originalRects.length} --> ${newRects.length}`);
58
+ return newRects;
59
+ }
60
+
61
+ function mergeTouchingRects(
62
+ rects: Rect[],
63
+ tolerance: number,
64
+ doNotMergeHorizontallyAlignedRects: boolean
65
+ ): Rect[] {
66
+ for (let i = 0; i < rects.length; i++) {
67
+ for (let j = i + 1; j < rects.length; j++) {
68
+ const rect1 = rects[i];
69
+ const rect2 = rects[j];
70
+ if (rect1 === rect2) {
71
+ // log("mergeTouchingRects rect1 === rect2 ??!");
72
+ continue;
73
+ }
74
+ const rectsLineUpVertically =
75
+ almostEqual(rect1.top, rect2.top, tolerance) &&
76
+ almostEqual(rect1.bottom, rect2.bottom, tolerance);
77
+ const rectsLineUpHorizontally =
78
+ almostEqual(rect1.left, rect2.left, tolerance) &&
79
+ almostEqual(rect1.right, rect2.right, tolerance);
80
+ const horizontalAllowed = !doNotMergeHorizontallyAlignedRects;
81
+ const aligned =
82
+ (rectsLineUpHorizontally && horizontalAllowed) ||
83
+ (rectsLineUpVertically && !rectsLineUpHorizontally);
84
+ const canMerge = aligned && rectsTouchOrOverlap(rect1, rect2, tolerance);
85
+ if (canMerge) {
86
+ /*log(
87
+ `CLIENT RECT: merging two into one, VERTICAL: ${rectsLineUpVertically} HORIZONTAL: ${rectsLineUpHorizontally} (${doNotMergeHorizontallyAlignedRects})`
88
+ );*/
89
+ const newRects = rects.filter((rect) => {
90
+ return rect !== rect1 && rect !== rect2;
91
+ });
92
+ const replacementClientRect = getBoundingRect(rect1, rect2);
93
+ newRects.push(replacementClientRect);
94
+ return mergeTouchingRects(
95
+ newRects,
96
+ tolerance,
97
+ doNotMergeHorizontallyAlignedRects
98
+ );
99
+ }
100
+ }
101
+ }
102
+ return rects;
103
+ }
104
+
105
+ function getBoundingRect(rect1: Rect, rect2: Rect) {
106
+ const left = Math.min(rect1.left, rect2.left);
107
+ const right = Math.max(rect1.right, rect2.right);
108
+ const top = Math.min(rect1.top, rect2.top);
109
+ const bottom = Math.max(rect1.bottom, rect2.bottom);
110
+ return {
111
+ bottom,
112
+ height: bottom - top,
113
+ left,
114
+ right,
115
+ top,
116
+ width: right - left,
117
+ };
118
+ }
119
+
120
+ function removeContainedRects(rects: Rect[], tolerance: number) {
121
+ const rectsToKeep = new Set(rects);
122
+ for (const rect of rects) {
123
+ const bigEnough = rect.width > 1 && rect.height > 1;
124
+ if (!bigEnough) {
125
+ // log("CLIENT RECT: remove tiny");
126
+ rectsToKeep.delete(rect);
127
+ continue;
128
+ }
129
+ for (const possiblyContainingRect of rects) {
130
+ if (rect === possiblyContainingRect) {
131
+ continue;
132
+ }
133
+ if (!rectsToKeep.has(possiblyContainingRect)) {
134
+ continue;
135
+ }
136
+ if (rectContains(possiblyContainingRect, rect, tolerance)) {
137
+ // log("CLIENT RECT: remove contained");
138
+ rectsToKeep.delete(rect);
139
+ break;
140
+ }
141
+ }
142
+ }
143
+ return Array.from(rectsToKeep);
144
+ }
145
+
146
+ function rectContains(rect1: Rect, rect2: Rect, tolerance: number) {
147
+ return (
148
+ rectContainsPoint(rect1, rect2.left, rect2.top, tolerance) &&
149
+ rectContainsPoint(rect1, rect2.right, rect2.top, tolerance) &&
150
+ rectContainsPoint(rect1, rect2.left, rect2.bottom, tolerance) &&
151
+ rectContainsPoint(rect1, rect2.right, rect2.bottom, tolerance)
152
+ );
153
+ }
154
+
155
+ export function rectContainsPoint(rect: Rect, x: number, y: number, tolerance: number) {
156
+ return (
157
+ (rect.left < x || almostEqual(rect.left, x, tolerance)) &&
158
+ (rect.right > x || almostEqual(rect.right, x, tolerance)) &&
159
+ (rect.top < y || almostEqual(rect.top, y, tolerance)) &&
160
+ (rect.bottom > y || almostEqual(rect.bottom, y, tolerance))
161
+ );
162
+ }
163
+
164
+ function replaceOverlapingRects(rects: Rect[]): Rect[] {
165
+ for (let i = 0; i < rects.length; i++) {
166
+ for (let j = i + 1; j < rects.length; j++) {
167
+ const rect1 = rects[i];
168
+ const rect2 = rects[j];
169
+ if (rect1 === rect2) {
170
+ // log("replaceOverlapingRects rect1 === rect2 ??!");
171
+ continue;
172
+ }
173
+ if (rectsTouchOrOverlap(rect1, rect2, -1)) {
174
+ let toAdd = [];
175
+ let toRemove: Rect;
176
+ const subtractRects1 = rectSubtract(rect1, rect2);
177
+ if (subtractRects1.length === 1) {
178
+ toAdd = subtractRects1;
179
+ toRemove = rect1;
180
+ } else {
181
+ const subtractRects2 = rectSubtract(rect2, rect1);
182
+ if (subtractRects1.length < subtractRects2.length) {
183
+ toAdd = subtractRects1;
184
+ toRemove = rect1;
185
+ } else {
186
+ toAdd = subtractRects2;
187
+ toRemove = rect2;
188
+ }
189
+ }
190
+ // log(`CLIENT RECT: overlap, cut one rect into ${toAdd.length}`);
191
+ const newRects = rects.filter((rect) => {
192
+ return rect !== toRemove;
193
+ });
194
+ Array.prototype.push.apply(newRects, toAdd);
195
+ return replaceOverlapingRects(newRects);
196
+ }
197
+ }
198
+ }
199
+ return rects;
200
+ }
201
+
202
+ function rectSubtract(rect1: Rect, rect2: Rect) {
203
+ const rectIntersected = rectIntersect(rect2, rect1);
204
+ if (rectIntersected.height === 0 || rectIntersected.width === 0) {
205
+ return [rect1];
206
+ }
207
+ const rects = [];
208
+ {
209
+ const rectA = {
210
+ bottom: rect1.bottom,
211
+ height: 0,
212
+ left: rect1.left,
213
+ right: rectIntersected.left,
214
+ top: rect1.top,
215
+ width: 0,
216
+ };
217
+ rectA.width = rectA.right - rectA.left;
218
+ rectA.height = rectA.bottom - rectA.top;
219
+ if (rectA.height !== 0 && rectA.width !== 0) {
220
+ rects.push(rectA);
221
+ }
222
+ }
223
+ {
224
+ const rectB = {
225
+ bottom: rectIntersected.top,
226
+ height: 0,
227
+ left: rectIntersected.left,
228
+ right: rectIntersected.right,
229
+ top: rect1.top,
230
+ width: 0,
231
+ };
232
+ rectB.width = rectB.right - rectB.left;
233
+ rectB.height = rectB.bottom - rectB.top;
234
+ if (rectB.height !== 0 && rectB.width !== 0) {
235
+ rects.push(rectB);
236
+ }
237
+ }
238
+ {
239
+ const rectC = {
240
+ bottom: rect1.bottom,
241
+ height: 0,
242
+ left: rectIntersected.left,
243
+ right: rectIntersected.right,
244
+ top: rectIntersected.bottom,
245
+ width: 0,
246
+ };
247
+ rectC.width = rectC.right - rectC.left;
248
+ rectC.height = rectC.bottom - rectC.top;
249
+ if (rectC.height !== 0 && rectC.width !== 0) {
250
+ rects.push(rectC);
251
+ }
252
+ }
253
+ {
254
+ const rectD = {
255
+ bottom: rect1.bottom,
256
+ height: 0,
257
+ left: rectIntersected.right,
258
+ right: rect1.right,
259
+ top: rect1.top,
260
+ width: 0,
261
+ };
262
+ rectD.width = rectD.right - rectD.left;
263
+ rectD.height = rectD.bottom - rectD.top;
264
+ if (rectD.height !== 0 && rectD.width !== 0) {
265
+ rects.push(rectD);
266
+ }
267
+ }
268
+ return rects;
269
+ }
270
+
271
+ function rectIntersect(rect1: Rect, rect2: Rect) {
272
+ const maxLeft = Math.max(rect1.left, rect2.left);
273
+ const minRight = Math.min(rect1.right, rect2.right);
274
+ const maxTop = Math.max(rect1.top, rect2.top);
275
+ const minBottom = Math.min(rect1.bottom, rect2.bottom);
276
+ return {
277
+ bottom: minBottom,
278
+ height: Math.max(0, minBottom - maxTop),
279
+ left: maxLeft,
280
+ right: minRight,
281
+ top: maxTop,
282
+ width: Math.max(0, minRight - maxLeft),
283
+ };
284
+ }
285
+
286
+ function rectsTouchOrOverlap(rect1: Rect, rect2: Rect, tolerance: number) {
287
+ return (
288
+ (rect1.left < rect2.right ||
289
+ (tolerance >= 0 && almostEqual(rect1.left, rect2.right, tolerance))) &&
290
+ (rect2.left < rect1.right ||
291
+ (tolerance >= 0 && almostEqual(rect2.left, rect1.right, tolerance))) &&
292
+ (rect1.top < rect2.bottom ||
293
+ (tolerance >= 0 && almostEqual(rect1.top, rect2.bottom, tolerance))) &&
294
+ (rect2.top < rect1.bottom ||
295
+ (tolerance >= 0 && almostEqual(rect2.top, rect1.bottom, tolerance)))
296
+ );
297
+ }
298
+
299
+ function almostEqual(a: number, b: number, tolerance: number) {
300
+ return Math.abs(a - b) <= tolerance;
301
+ }
@@ -0,0 +1,43 @@
1
+ const iframeAnchors = {
2
+ top: "js-iframe-reader-top-anchor",
3
+ bottom: "js-iframe-reader-bottom-anchor",
4
+ };
5
+
6
+ export class AnchorObserver extends HTMLElement {
7
+ connectedCallback() {
8
+ this.setAttribute("aria-hidden", "true");
9
+ this.style.setProperty("display", "block", "important");
10
+ this.style.setProperty("padding", "0", "important");
11
+ this.style.setProperty("margin", "0", "important");
12
+ this.style.setProperty("visibility", "hidden", "important");
13
+ }
14
+ }
15
+
16
+ export function helperCreateAnchorElements(iframeElement: HTMLElement) {
17
+ const body = iframeElement.getElementsByTagName("body")[0];
18
+ const anchorTop = document.createElement("anchor-observer");
19
+ const anchorBottom = document.createElement("anchor-observer");
20
+ anchorTop.dataset.readium = "true";
21
+ anchorBottom.dataset.readium = "true";
22
+
23
+ anchorTop?.setAttribute("id", iframeAnchors.top);
24
+ anchorTop?.style.setProperty("height", "115px", "important");
25
+
26
+ anchorBottom?.setAttribute("id", iframeAnchors.bottom);
27
+ anchorBottom?.style.setProperty("height", "80px", "important");
28
+
29
+ body?.insertBefore(anchorTop, body.firstChild);
30
+ body?.appendChild(anchorBottom);
31
+ }
32
+
33
+ export function helperRemoveAnchorElements(iframeElement: HTMLElement) {
34
+ const body = iframeElement.getElementsByTagName("body")[0];
35
+ const anchorTop = body?.querySelector(`#${iframeAnchors.top}`);
36
+ const anchorBottom = body?.querySelector(`#${iframeAnchors.bottom}`);
37
+ if (anchorTop) {
38
+ anchorTop.parentElement?.removeChild(anchorTop);
39
+ }
40
+ if (anchorBottom) {
41
+ anchorBottom.parentElement?.removeChild(anchorBottom);
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './comms';
2
+ export * from './modules';
3
+ export * from './Loader';