vue-book-reader 1.2.2 → 1.2.4

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.
@@ -1,1028 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4
- const debounce = (f, wait2, immediate) => {
5
- let timeout;
6
- return (...args) => {
7
- const later = () => {
8
- timeout = null;
9
- f(...args);
10
- };
11
- if (timeout) clearTimeout(timeout);
12
- timeout = setTimeout(later, wait2);
13
- };
14
- };
15
- const lerp = (min, max, x) => x * (max - min) + min;
16
- const easeOutQuad = (x) => 1 - (1 - x) * (1 - x);
17
- const animate = (a, b, duration, ease, render) => new Promise((resolve) => {
18
- let start;
19
- const step = (now) => {
20
- if (document.hidden) {
21
- render(lerp(a, b, 1));
22
- return resolve();
23
- }
24
- start ??= now;
25
- const fraction = Math.min(1, (now - start) / duration);
26
- render(lerp(a, b, ease(fraction)));
27
- if (fraction < 1) requestAnimationFrame(step);
28
- else resolve();
29
- };
30
- if (document.hidden) {
31
- render(lerp(a, b, 1));
32
- return resolve();
33
- }
34
- requestAnimationFrame(step);
35
- });
36
- const uncollapse = (range) => {
37
- if (!(range == null ? void 0 : range.collapsed)) return range;
38
- const { endOffset, endContainer } = range;
39
- if (endContainer.nodeType === 1) {
40
- const node = endContainer.childNodes[endOffset];
41
- if ((node == null ? void 0 : node.nodeType) === 1) return node;
42
- return endContainer;
43
- }
44
- if (endOffset + 1 < endContainer.length) range.setEnd(endContainer, endOffset + 1);
45
- else if (endOffset > 1) range.setStart(endContainer, endOffset - 1);
46
- else return endContainer.parentNode;
47
- return range;
48
- };
49
- const makeRange = (doc, node, start, end = start) => {
50
- const range = doc.createRange();
51
- range.setStart(node, start);
52
- range.setEnd(node, end);
53
- return range;
54
- };
55
- const bisectNode = (doc, node, cb, start = 0, end = node.nodeValue.length) => {
56
- if (end - start === 1) {
57
- const result2 = cb(makeRange(doc, node, start), makeRange(doc, node, end));
58
- return result2 < 0 ? start : end;
59
- }
60
- const mid = Math.floor(start + (end - start) / 2);
61
- const result = cb(makeRange(doc, node, start, mid), makeRange(doc, node, mid, end));
62
- return result < 0 ? bisectNode(doc, node, cb, start, mid) : result > 0 ? bisectNode(doc, node, cb, mid, end) : mid;
63
- };
64
- const {
65
- SHOW_ELEMENT,
66
- SHOW_TEXT,
67
- SHOW_CDATA_SECTION,
68
- FILTER_ACCEPT,
69
- FILTER_REJECT,
70
- FILTER_SKIP
71
- } = NodeFilter;
72
- const filter = SHOW_ELEMENT | SHOW_TEXT | SHOW_CDATA_SECTION;
73
- const getBoundingClientRect = (target) => {
74
- let top = Infinity, right = -Infinity, left = Infinity, bottom = -Infinity;
75
- for (const rect of target.getClientRects()) {
76
- left = Math.min(left, rect.left);
77
- top = Math.min(top, rect.top);
78
- right = Math.max(right, rect.right);
79
- bottom = Math.max(bottom, rect.bottom);
80
- }
81
- return new DOMRect(left, top, right - left, bottom - top);
82
- };
83
- const getVisibleRange = (doc, start, end, mapRect) => {
84
- const acceptNode = (node) => {
85
- var _a, _b;
86
- const name = (_a = node.localName) == null ? void 0 : _a.toLowerCase();
87
- if (name === "script" || name === "style") return FILTER_REJECT;
88
- if (node.nodeType === 1) {
89
- const { left, right } = mapRect(node.getBoundingClientRect());
90
- if (right < start || left > end) return FILTER_REJECT;
91
- if (left >= start && right <= end) return FILTER_ACCEPT;
92
- } else {
93
- if (!((_b = node.nodeValue) == null ? void 0 : _b.trim())) return FILTER_SKIP;
94
- const range2 = doc.createRange();
95
- range2.selectNodeContents(node);
96
- const { left, right } = mapRect(range2.getBoundingClientRect());
97
- if (right >= start && left <= end) return FILTER_ACCEPT;
98
- }
99
- return FILTER_SKIP;
100
- };
101
- const walker = doc.createTreeWalker(doc.body, filter, { acceptNode });
102
- const nodes = [];
103
- for (let node = walker.nextNode(); node; node = walker.nextNode())
104
- nodes.push(node);
105
- const from = nodes[0] ?? doc.body;
106
- const to = nodes[nodes.length - 1] ?? from;
107
- const startOffset = from.nodeType === 1 ? 0 : bisectNode(doc, from, (a, b) => {
108
- const p = mapRect(getBoundingClientRect(a));
109
- const q = mapRect(getBoundingClientRect(b));
110
- if (p.right < start && q.left > start) return 0;
111
- return q.left > start ? -1 : 1;
112
- });
113
- const endOffset = to.nodeType === 1 ? 0 : bisectNode(doc, to, (a, b) => {
114
- const p = mapRect(getBoundingClientRect(a));
115
- const q = mapRect(getBoundingClientRect(b));
116
- if (p.right < end && q.left > end) return 0;
117
- return q.left > end ? -1 : 1;
118
- });
119
- const range = doc.createRange();
120
- range.setStart(from, startOffset);
121
- range.setEnd(to, endOffset);
122
- return range;
123
- };
124
- const selectionIsBackward = (sel) => {
125
- const range = document.createRange();
126
- range.setStart(sel.anchorNode, sel.anchorOffset);
127
- range.setEnd(sel.focusNode, sel.focusOffset);
128
- return range.collapsed;
129
- };
130
- const setSelectionTo = (target, collapse) => {
131
- let range;
132
- if (target.startContainer) range = target.cloneRange();
133
- else if (target.nodeType) {
134
- range = document.createRange();
135
- range.selectNode(target);
136
- }
137
- if (range) {
138
- const sel = range.startContainer.ownerDocument.defaultView.getSelection();
139
- if (sel) {
140
- sel.removeAllRanges();
141
- if (collapse === -1) range.collapse(true);
142
- else if (collapse === 1) range.collapse();
143
- sel.addRange(range);
144
- }
145
- }
146
- };
147
- const getDirection = (doc) => {
148
- const { defaultView } = doc;
149
- const { writingMode, direction } = defaultView.getComputedStyle(doc.body);
150
- const vertical = writingMode === "vertical-rl" || writingMode === "vertical-lr";
151
- const rtl = doc.body.dir === "rtl" || direction === "rtl" || doc.documentElement.dir === "rtl";
152
- return { vertical, rtl };
153
- };
154
- const getBackground = (doc) => {
155
- const bodyStyle = doc.defaultView.getComputedStyle(doc.body);
156
- return bodyStyle.backgroundColor === "rgba(0, 0, 0, 0)" && bodyStyle.backgroundImage === "none" ? doc.defaultView.getComputedStyle(doc.documentElement).background : bodyStyle.background;
157
- };
158
- const makeMarginals = (length, part) => Array.from({ length }, () => {
159
- const div = document.createElement("div");
160
- const child = document.createElement("div");
161
- div.append(child);
162
- child.setAttribute("part", part);
163
- return div;
164
- });
165
- const setStylesImportant = (el, styles) => {
166
- const { style } = el;
167
- for (const [k, v] of Object.entries(styles)) style.setProperty(k, v, "important");
168
- };
169
- class View {
170
- #observer = new ResizeObserver(() => this.expand());
171
- #element = document.createElement("div");
172
- #iframe = document.createElement("iframe");
173
- #contentRange = document.createRange();
174
- #overlayer;
175
- #vertical = false;
176
- #rtl = false;
177
- #column = true;
178
- #size;
179
- #layout = {};
180
- constructor({ container, onExpand }) {
181
- this.container = container;
182
- this.onExpand = onExpand;
183
- this.#iframe.setAttribute("part", "filter");
184
- this.#element.append(this.#iframe);
185
- Object.assign(this.#element.style, {
186
- boxSizing: "content-box",
187
- position: "relative",
188
- overflow: "hidden",
189
- flex: "0 0 auto",
190
- width: "100%",
191
- height: "100%",
192
- display: "flex",
193
- justifyContent: "center",
194
- alignItems: "center"
195
- });
196
- Object.assign(this.#iframe.style, {
197
- overflow: "hidden",
198
- border: "0",
199
- display: "none",
200
- width: "100%",
201
- height: "100%"
202
- });
203
- this.#iframe.setAttribute("sandbox", "allow-same-origin allow-scripts");
204
- this.#iframe.setAttribute("scrolling", "no");
205
- }
206
- get element() {
207
- return this.#element;
208
- }
209
- get document() {
210
- return this.#iframe.contentDocument;
211
- }
212
- async load(src, afterLoad, beforeRender) {
213
- if (typeof src !== "string") throw new Error(`${src} is not string`);
214
- return new Promise((resolve) => {
215
- this.#iframe.addEventListener("load", () => {
216
- const doc = this.document;
217
- afterLoad == null ? void 0 : afterLoad(doc);
218
- this.#iframe.style.display = "block";
219
- const { vertical, rtl } = getDirection(doc);
220
- const background = getBackground(doc);
221
- this.#iframe.style.display = "none";
222
- this.#vertical = vertical;
223
- this.#rtl = rtl;
224
- this.#contentRange.selectNodeContents(doc.body);
225
- const layout = beforeRender == null ? void 0 : beforeRender({ vertical, rtl, background });
226
- this.#iframe.style.display = "block";
227
- this.render(layout);
228
- this.#observer.observe(doc.body);
229
- doc.fonts.ready.then(() => this.expand());
230
- resolve();
231
- }, { once: true });
232
- this.#iframe.src = src;
233
- });
234
- }
235
- render(layout) {
236
- if (!layout) return;
237
- this.#column = layout.flow !== "scrolled";
238
- this.#layout = layout;
239
- if (this.#column) this.columnize(layout);
240
- else this.scrolled(layout);
241
- }
242
- scrolled({ gap, columnWidth }) {
243
- const vertical = this.#vertical;
244
- const doc = this.document;
245
- setStylesImportant(doc.documentElement, {
246
- "box-sizing": "border-box",
247
- "padding": vertical ? `${gap}px 0` : `0 ${gap}px`,
248
- "column-width": "auto",
249
- "height": "auto",
250
- "width": "auto"
251
- });
252
- setStylesImportant(doc.body, {
253
- [vertical ? "max-height" : "max-width"]: `${columnWidth}px`,
254
- "margin": "auto"
255
- });
256
- this.setImageSize();
257
- this.expand();
258
- }
259
- columnize({ width, height, gap, columnWidth }) {
260
- const vertical = this.#vertical;
261
- this.#size = vertical ? height : width;
262
- const doc = this.document;
263
- setStylesImportant(doc.documentElement, {
264
- "box-sizing": "border-box",
265
- "column-width": `${Math.trunc(columnWidth)}px`,
266
- "column-gap": `${gap}px`,
267
- "column-fill": "auto",
268
- ...vertical ? { "width": `${width}px` } : { "height": `${height}px` },
269
- "padding": vertical ? `${gap / 2}px 0` : `0 ${gap / 2}px`,
270
- "overflow": "hidden",
271
- // force wrap long words
272
- "overflow-wrap": "break-word",
273
- // reset some potentially problematic props
274
- "position": "static",
275
- "border": "0",
276
- "margin": "0",
277
- "max-height": "none",
278
- "max-width": "none",
279
- "min-height": "none",
280
- "min-width": "none",
281
- // fix glyph clipping in WebKit
282
- "-webkit-line-box-contain": "block glyphs replaced"
283
- });
284
- setStylesImportant(doc.body, {
285
- "max-height": "none",
286
- "max-width": "none",
287
- "margin": "0"
288
- });
289
- this.setImageSize();
290
- this.expand();
291
- }
292
- setImageSize() {
293
- const { width, height, margin } = this.#layout;
294
- const vertical = this.#vertical;
295
- const doc = this.document;
296
- for (const el of doc.body.querySelectorAll("img, svg, video")) {
297
- const { maxHeight, maxWidth } = doc.defaultView.getComputedStyle(el);
298
- setStylesImportant(el, {
299
- "max-height": vertical ? maxHeight !== "none" && maxHeight !== "0px" ? maxHeight : "100%" : `${height - margin * 2}px`,
300
- "max-width": vertical ? `${width - margin * 2}px` : maxWidth !== "none" && maxWidth !== "0px" ? maxWidth : "100%",
301
- "object-fit": "contain",
302
- "page-break-inside": "avoid",
303
- "break-inside": "avoid",
304
- "box-sizing": "border-box"
305
- });
306
- }
307
- }
308
- expand() {
309
- const { documentElement } = this.document;
310
- if (this.#column) {
311
- const side = this.#vertical ? "height" : "width";
312
- const otherSide = this.#vertical ? "width" : "height";
313
- const contentRect = this.#contentRange.getBoundingClientRect();
314
- const rootRect = documentElement.getBoundingClientRect();
315
- const contentStart = this.#vertical ? 0 : this.#rtl ? rootRect.right - contentRect.right : contentRect.left - rootRect.left;
316
- const contentSize = contentStart + contentRect[side];
317
- const pageCount = Math.ceil(contentSize / this.#size);
318
- const expandedSize = pageCount * this.#size;
319
- this.#element.style.padding = "0";
320
- this.#iframe.style[side] = `${expandedSize}px`;
321
- this.#element.style[side] = `${expandedSize + this.#size * 2}px`;
322
- this.#iframe.style[otherSide] = "100%";
323
- this.#element.style[otherSide] = "100%";
324
- documentElement.style[side] = `${this.#size}px`;
325
- if (this.#overlayer) {
326
- this.#overlayer.element.style.margin = "0";
327
- this.#overlayer.element.style.left = this.#vertical ? "0" : `${this.#size}px`;
328
- this.#overlayer.element.style.top = this.#vertical ? `${this.#size}px` : "0";
329
- this.#overlayer.element.style[side] = `${expandedSize}px`;
330
- this.#overlayer.redraw();
331
- }
332
- } else {
333
- const side = this.#vertical ? "width" : "height";
334
- const otherSide = this.#vertical ? "height" : "width";
335
- const contentSize = documentElement.getBoundingClientRect()[side];
336
- const expandedSize = contentSize;
337
- const { margin } = this.#layout;
338
- const padding = this.#vertical ? `0 ${margin}px` : `${margin}px 0`;
339
- this.#element.style.padding = padding;
340
- this.#iframe.style[side] = `${expandedSize}px`;
341
- this.#element.style[side] = `${expandedSize}px`;
342
- this.#iframe.style[otherSide] = "100%";
343
- this.#element.style[otherSide] = "100%";
344
- if (this.#overlayer) {
345
- this.#overlayer.element.style.margin = padding;
346
- this.#overlayer.element.style.left = "0";
347
- this.#overlayer.element.style.top = "0";
348
- this.#overlayer.element.style[side] = `${expandedSize}px`;
349
- this.#overlayer.redraw();
350
- }
351
- }
352
- this.onExpand();
353
- }
354
- set overlayer(overlayer) {
355
- this.#overlayer = overlayer;
356
- this.#element.append(overlayer.element);
357
- }
358
- get overlayer() {
359
- return this.#overlayer;
360
- }
361
- destroy() {
362
- if (this.document) this.#observer.unobserve(this.document.body);
363
- }
364
- }
365
- class Paginator extends HTMLElement {
366
- static observedAttributes = [
367
- "flow",
368
- "gap",
369
- "margin",
370
- "max-inline-size",
371
- "max-block-size",
372
- "max-column-count"
373
- ];
374
- #root = this.attachShadow({ mode: "closed" });
375
- #observer = new ResizeObserver(() => this.render());
376
- #top;
377
- #background;
378
- #container;
379
- #header;
380
- #footer;
381
- #view;
382
- #vertical = false;
383
- #rtl = false;
384
- #margin = 0;
385
- #index = -1;
386
- #anchor = 0;
387
- // anchor view to a fraction (0-1), Range, or Element
388
- #justAnchored = false;
389
- #locked = false;
390
- // while true, prevent any further navigation
391
- #styles;
392
- #styleMap = /* @__PURE__ */ new WeakMap();
393
- #mediaQuery = matchMedia("(prefers-color-scheme: dark)");
394
- #mediaQueryListener;
395
- #scrollBounds;
396
- #touchState;
397
- #touchScrolled;
398
- #lastVisibleRange;
399
- constructor() {
400
- super();
401
- this.#root.innerHTML = `<style>
402
- :host {
403
- display: block;
404
- container-type: size;
405
- }
406
- :host, #top {
407
- box-sizing: border-box;
408
- position: relative;
409
- overflow: hidden;
410
- width: 100%;
411
- height: 100%;
412
- }
413
- #top {
414
- --_gap: 7%;
415
- --_margin: 48px;
416
- --_max-inline-size: 720px;
417
- --_max-block-size: 1440px;
418
- --_max-column-count: 2;
419
- --_max-column-count-portrait: 1;
420
- --_max-column-count-spread: var(--_max-column-count);
421
- --_half-gap: calc(var(--_gap) / 2);
422
- --_max-width: calc(var(--_max-inline-size) * var(--_max-column-count-spread));
423
- --_max-height: var(--_max-block-size);
424
- display: grid;
425
- grid-template-columns:
426
- minmax(var(--_half-gap), 1fr)
427
- var(--_half-gap)
428
- minmax(0, calc(var(--_max-width) - var(--_gap)))
429
- var(--_half-gap)
430
- minmax(var(--_half-gap), 1fr);
431
- grid-template-rows:
432
- minmax(var(--_margin), 1fr)
433
- minmax(0, var(--_max-height))
434
- minmax(var(--_margin), 1fr);
435
- &.vertical {
436
- --_max-column-count-spread: var(--_max-column-count-portrait);
437
- --_max-width: var(--_max-block-size);
438
- --_max-height: calc(var(--_max-inline-size) * var(--_max-column-count-spread));
439
- }
440
- @container (orientation: portrait) {
441
- & {
442
- --_max-column-count-spread: var(--_max-column-count-portrait);
443
- }
444
- &.vertical {
445
- --_max-column-count-spread: var(--_max-column-count);
446
- }
447
- }
448
- }
449
- #background {
450
- grid-column: 1 / -1;
451
- grid-row: 1 / -1;
452
- }
453
- #container {
454
- grid-column: 2 / 5;
455
- grid-row: 2;
456
- overflow: hidden;
457
- }
458
- :host([flow="scrolled"]) #container {
459
- grid-column: 1 / -1;
460
- grid-row: 1 / -1;
461
- overflow: auto;
462
- }
463
- #header {
464
- grid-column: 3 / 4;
465
- grid-row: 1;
466
- }
467
- #footer {
468
- grid-column: 3 / 4;
469
- grid-row: 3;
470
- align-self: end;
471
- }
472
- #header, #footer {
473
- display: grid;
474
- height: var(--_margin);
475
- }
476
- :is(#header, #footer) > * {
477
- display: flex;
478
- align-items: center;
479
- min-width: 0;
480
- }
481
- :is(#header, #footer) > * > * {
482
- width: 100%;
483
- overflow: hidden;
484
- white-space: nowrap;
485
- text-overflow: ellipsis;
486
- text-align: center;
487
- font-size: .75em;
488
- opacity: .6;
489
- }
490
- </style>
491
- <div id="top">
492
- <div id="background" part="filter"></div>
493
- <div id="header"></div>
494
- <div id="container"></div>
495
- <div id="footer"></div>
496
- </div>
497
- `;
498
- this.#top = this.#root.getElementById("top");
499
- this.#background = this.#root.getElementById("background");
500
- this.#container = this.#root.getElementById("container");
501
- this.#header = this.#root.getElementById("header");
502
- this.#footer = this.#root.getElementById("footer");
503
- this.#observer.observe(this.#container);
504
- this.#container.addEventListener("scroll", () => this.dispatchEvent(new Event("scroll")));
505
- this.#container.addEventListener("scroll", debounce(() => {
506
- if (this.scrolled) {
507
- if (this.#justAnchored) this.#justAnchored = false;
508
- else this.#afterScroll("scroll");
509
- }
510
- }, 250));
511
- const opts = { passive: false };
512
- this.addEventListener("touchstart", this.#onTouchStart.bind(this), opts);
513
- this.addEventListener("touchmove", this.#onTouchMove.bind(this), opts);
514
- this.addEventListener("touchend", this.#onTouchEnd.bind(this));
515
- this.addEventListener("load", ({ detail: { doc } }) => {
516
- doc.addEventListener("touchstart", this.#onTouchStart.bind(this), opts);
517
- doc.addEventListener("touchmove", this.#onTouchMove.bind(this), opts);
518
- doc.addEventListener("touchend", this.#onTouchEnd.bind(this));
519
- });
520
- this.addEventListener("relocate", ({ detail }) => {
521
- if (detail.reason === "selection") setSelectionTo(this.#anchor, 0);
522
- else if (detail.reason === "navigation") {
523
- if (this.#anchor === 1) setSelectionTo(detail.range, 1);
524
- else if (typeof this.#anchor === "number")
525
- setSelectionTo(detail.range, -1);
526
- else setSelectionTo(this.#anchor, -1);
527
- }
528
- });
529
- const checkPointerSelection = debounce((range, sel) => {
530
- if (!sel.rangeCount) return;
531
- const selRange = sel.getRangeAt(0);
532
- const backward = selectionIsBackward(sel);
533
- if (backward && selRange.compareBoundaryPoints(Range.START_TO_START, range) < 0)
534
- this.prev();
535
- else if (!backward && selRange.compareBoundaryPoints(Range.END_TO_END, range) > 0)
536
- this.next();
537
- }, 700);
538
- this.addEventListener("load", ({ detail: { doc } }) => {
539
- let isPointerSelecting = false;
540
- doc.addEventListener("pointerdown", () => isPointerSelecting = true);
541
- doc.addEventListener("pointerup", () => isPointerSelecting = false);
542
- let isKeyboardSelecting = false;
543
- doc.addEventListener("keydown", () => isKeyboardSelecting = true);
544
- doc.addEventListener("keyup", () => isKeyboardSelecting = false);
545
- doc.addEventListener("selectionchange", () => {
546
- if (this.scrolled) return;
547
- const range = this.#lastVisibleRange;
548
- if (!range) return;
549
- const sel = doc.getSelection();
550
- if (!sel.rangeCount) return;
551
- if (isPointerSelecting && sel.type === "Range")
552
- checkPointerSelection(range, sel);
553
- else if (isKeyboardSelecting) {
554
- const selRange = sel.getRangeAt(0).cloneRange();
555
- const backward = selectionIsBackward(sel);
556
- if (!backward) selRange.collapse();
557
- this.#scrollToAnchor(selRange);
558
- }
559
- });
560
- doc.addEventListener("focusin", (e) => this.scrolled ? null : (
561
- // NOTE: `requestAnimationFrame` is needed in WebKit
562
- requestAnimationFrame(() => this.#scrollToAnchor(e.target))
563
- ));
564
- });
565
- this.#mediaQueryListener = () => {
566
- if (!this.#view) return;
567
- this.#background.style.background = getBackground(this.#view.document);
568
- };
569
- this.#mediaQuery.addEventListener("change", this.#mediaQueryListener);
570
- }
571
- attributeChangedCallback(name, _, value) {
572
- switch (name) {
573
- case "flow":
574
- this.render();
575
- break;
576
- case "gap":
577
- case "margin":
578
- case "max-block-size":
579
- case "max-column-count":
580
- this.#top.style.setProperty("--_" + name, value);
581
- break;
582
- case "max-inline-size":
583
- this.#top.style.setProperty("--_" + name, value);
584
- this.render();
585
- break;
586
- }
587
- }
588
- open(book) {
589
- var _a;
590
- this.bookDir = book.dir;
591
- this.sections = book.sections;
592
- (_a = book.transformTarget) == null ? void 0 : _a.addEventListener("data", ({ detail }) => {
593
- if (detail.type !== "text/css") return;
594
- const w = innerWidth;
595
- const h = innerHeight;
596
- detail.data = Promise.resolve(detail.data).then((data) => data.replace(new RegExp("(?<=[{\\s;])-epub-", "gi"), "").replace(/(\d*\.?\d+)vw/gi, (_, d) => parseFloat(d) * w / 100 + "px").replace(/(\d*\.?\d+)vh/gi, (_, d) => parseFloat(d) * h / 100 + "px").replace(/page-break-(after|before|inside)\s*:/gi, (_, x) => `-webkit-column-break-${x}:`).replace(/break-(after|before|inside)\s*:\s*(avoid-)?page/gi, (_, x, y) => `break-${x}: ${y ?? ""}column`));
597
- });
598
- }
599
- #createView() {
600
- if (this.#view) {
601
- this.#view.destroy();
602
- this.#container.removeChild(this.#view.element);
603
- }
604
- this.#view = new View({
605
- container: this,
606
- onExpand: () => this.#scrollToAnchor(this.#anchor)
607
- });
608
- this.#container.append(this.#view.element);
609
- return this.#view;
610
- }
611
- #beforeRender({ vertical, rtl, background }) {
612
- this.#vertical = vertical;
613
- this.#rtl = rtl;
614
- this.#top.classList.toggle("vertical", vertical);
615
- this.#background.style.background = background;
616
- const { width, height } = this.#container.getBoundingClientRect();
617
- const size = vertical ? height : width;
618
- const style = getComputedStyle(this.#top);
619
- const maxInlineSize = parseFloat(style.getPropertyValue("--_max-inline-size"));
620
- const maxColumnCount = parseInt(style.getPropertyValue("--_max-column-count-spread"));
621
- const margin = parseFloat(style.getPropertyValue("--_margin"));
622
- this.#margin = margin;
623
- const g = parseFloat(style.getPropertyValue("--_gap")) / 100;
624
- const gap = -g / (g - 1) * size;
625
- const flow = this.getAttribute("flow");
626
- if (flow === "scrolled") {
627
- this.setAttribute("dir", vertical ? "rtl" : "ltr");
628
- this.#top.style.padding = "0";
629
- const columnWidth2 = maxInlineSize;
630
- this.heads = null;
631
- this.feet = null;
632
- this.#header.replaceChildren();
633
- this.#footer.replaceChildren();
634
- return { flow, margin, gap, columnWidth: columnWidth2 };
635
- }
636
- const divisor = Math.min(maxColumnCount, Math.ceil(size / maxInlineSize));
637
- const columnWidth = size / divisor - gap;
638
- this.setAttribute("dir", rtl ? "rtl" : "ltr");
639
- const marginalDivisor = vertical ? Math.min(2, Math.ceil(width / maxInlineSize)) : divisor;
640
- const marginalStyle = {
641
- gridTemplateColumns: `repeat(${marginalDivisor}, 1fr)`,
642
- gap: `${gap}px`,
643
- direction: this.bookDir === "rtl" ? "rtl" : "ltr"
644
- };
645
- Object.assign(this.#header.style, marginalStyle);
646
- Object.assign(this.#footer.style, marginalStyle);
647
- const heads = makeMarginals(marginalDivisor, "head");
648
- const feet = makeMarginals(marginalDivisor, "foot");
649
- this.heads = heads.map((el) => el.children[0]);
650
- this.feet = feet.map((el) => el.children[0]);
651
- this.#header.replaceChildren(...heads);
652
- this.#footer.replaceChildren(...feet);
653
- return { height, width, margin, gap, columnWidth };
654
- }
655
- render() {
656
- if (!this.#view) return;
657
- this.#view.render(this.#beforeRender({
658
- vertical: this.#vertical,
659
- rtl: this.#rtl
660
- }));
661
- this.#scrollToAnchor(this.#anchor);
662
- }
663
- get scrolled() {
664
- return this.getAttribute("flow") === "scrolled";
665
- }
666
- get scrollProp() {
667
- const { scrolled } = this;
668
- return this.#vertical ? scrolled ? "scrollLeft" : "scrollTop" : scrolled ? "scrollTop" : "scrollLeft";
669
- }
670
- get sideProp() {
671
- const { scrolled } = this;
672
- return this.#vertical ? scrolled ? "width" : "height" : scrolled ? "height" : "width";
673
- }
674
- get size() {
675
- return this.#container.getBoundingClientRect()[this.sideProp];
676
- }
677
- get viewSize() {
678
- return this.#view.element.getBoundingClientRect()[this.sideProp];
679
- }
680
- get start() {
681
- return Math.abs(this.#container[this.scrollProp]);
682
- }
683
- get end() {
684
- return this.start + this.size;
685
- }
686
- get page() {
687
- return Math.floor((this.start + this.end) / 2 / this.size);
688
- }
689
- get pages() {
690
- return Math.round(this.viewSize / this.size);
691
- }
692
- scrollBy(dx, dy) {
693
- const delta = this.#vertical ? dy : dx;
694
- const element = this.#container;
695
- const { scrollProp } = this;
696
- const [offset, a, b] = this.#scrollBounds;
697
- const rtl = this.#rtl;
698
- const min = rtl ? offset - b : offset - a;
699
- const max = rtl ? offset + a : offset + b;
700
- element[scrollProp] = Math.max(min, Math.min(
701
- max,
702
- element[scrollProp] + delta
703
- ));
704
- }
705
- snap(vx, vy) {
706
- const velocity = this.#vertical ? vy : vx;
707
- const [offset, a, b] = this.#scrollBounds;
708
- const { start, end, pages, size } = this;
709
- const min = Math.abs(offset) - a;
710
- const max = Math.abs(offset) + b;
711
- const d = velocity * (this.#rtl ? -size : size);
712
- const page = Math.floor(
713
- Math.max(min, Math.min(max, (start + end) / 2 + (isNaN(d) ? 0 : d))) / size
714
- );
715
- this.#scrollToPage(page, "snap").then(() => {
716
- const dir = page <= 0 ? -1 : page >= pages - 1 ? 1 : null;
717
- if (dir) return this.#goTo({
718
- index: this.#adjacentIndex(dir),
719
- anchor: dir < 0 ? () => 1 : () => 0
720
- });
721
- });
722
- }
723
- #onTouchStart(e) {
724
- const touch = e.changedTouches[0];
725
- this.#touchState = {
726
- x: touch == null ? void 0 : touch.screenX,
727
- y: touch == null ? void 0 : touch.screenY,
728
- t: e.timeStamp,
729
- vx: 0,
730
- xy: 0
731
- };
732
- }
733
- #onTouchMove(e) {
734
- const state = this.#touchState;
735
- if (state.pinched) return;
736
- state.pinched = globalThis.visualViewport.scale > 1;
737
- if (this.scrolled || state.pinched) return;
738
- if (e.touches.length > 1) {
739
- if (this.#touchScrolled) e.preventDefault();
740
- return;
741
- }
742
- e.preventDefault();
743
- const touch = e.changedTouches[0];
744
- const x = touch.screenX, y = touch.screenY;
745
- const dx = state.x - x, dy = state.y - y;
746
- const dt = e.timeStamp - state.t;
747
- state.x = x;
748
- state.y = y;
749
- state.t = e.timeStamp;
750
- state.vx = dx / dt;
751
- state.vy = dy / dt;
752
- this.#touchScrolled = true;
753
- this.scrollBy(dx, dy);
754
- }
755
- #onTouchEnd() {
756
- this.#touchScrolled = false;
757
- if (this.scrolled) return;
758
- requestAnimationFrame(() => {
759
- if (globalThis.visualViewport.scale === 1)
760
- this.snap(this.#touchState.vx, this.#touchState.vy);
761
- });
762
- }
763
- // allows one to process rects as if they were LTR and horizontal
764
- #getRectMapper() {
765
- if (this.scrolled) {
766
- const size = this.viewSize;
767
- const margin = this.#margin;
768
- return this.#vertical ? ({ left, right }) => ({ left: size - right - margin, right: size - left - margin }) : ({ top, bottom }) => ({ left: top + margin, right: bottom + margin });
769
- }
770
- const pxSize = this.pages * this.size;
771
- return this.#rtl ? ({ left, right }) => ({ left: pxSize - right, right: pxSize - left }) : this.#vertical ? ({ top, bottom }) => ({ left: top, right: bottom }) : (f) => f;
772
- }
773
- async #scrollToRect(rect, reason) {
774
- if (this.scrolled) {
775
- const offset2 = this.#getRectMapper()(rect).left - this.#margin;
776
- return this.#scrollTo(offset2, reason);
777
- }
778
- const offset = this.#getRectMapper()(rect).left;
779
- return this.#scrollToPage(Math.floor(offset / this.size) + (this.#rtl ? -1 : 1), reason);
780
- }
781
- async #scrollTo(offset, reason, smooth) {
782
- const element = this.#container;
783
- const { scrollProp, size } = this;
784
- if (element[scrollProp] === offset) {
785
- this.#scrollBounds = [offset, this.atStart ? 0 : size, this.atEnd ? 0 : size];
786
- this.#afterScroll(reason);
787
- return;
788
- }
789
- if (this.scrolled && this.#vertical) offset = -offset;
790
- if ((reason === "snap" || smooth) && this.hasAttribute("animated")) return animate(
791
- element[scrollProp],
792
- offset,
793
- 300,
794
- easeOutQuad,
795
- (x) => element[scrollProp] = x
796
- ).then(() => {
797
- this.#scrollBounds = [offset, this.atStart ? 0 : size, this.atEnd ? 0 : size];
798
- this.#afterScroll(reason);
799
- });
800
- else {
801
- element[scrollProp] = offset;
802
- this.#scrollBounds = [offset, this.atStart ? 0 : size, this.atEnd ? 0 : size];
803
- this.#afterScroll(reason);
804
- }
805
- }
806
- async #scrollToPage(page, reason, smooth) {
807
- const offset = this.size * (this.#rtl ? -page : page);
808
- return this.#scrollTo(offset, reason, smooth);
809
- }
810
- async scrollToAnchor(anchor, select) {
811
- return this.#scrollToAnchor(anchor, select ? "selection" : "navigation");
812
- }
813
- async #scrollToAnchor(anchor, reason = "anchor") {
814
- var _a, _b;
815
- this.#anchor = anchor;
816
- const rects = (_b = (_a = uncollapse(anchor)) == null ? void 0 : _a.getClientRects) == null ? void 0 : _b.call(_a);
817
- if (rects) {
818
- const rect = Array.from(rects).find((r) => r.width > 0 && r.height > 0) || rects[0];
819
- if (!rect) return;
820
- await this.#scrollToRect(rect, reason);
821
- return;
822
- }
823
- if (this.scrolled) {
824
- await this.#scrollTo(anchor * this.viewSize, reason);
825
- return;
826
- }
827
- const { pages } = this;
828
- if (!pages) return;
829
- const textPages = pages - 2;
830
- const newPage = Math.round(anchor * (textPages - 1));
831
- await this.#scrollToPage(newPage + 1, reason);
832
- }
833
- #getVisibleRange() {
834
- if (this.scrolled) return getVisibleRange(
835
- this.#view.document,
836
- this.start + this.#margin,
837
- this.end - this.#margin,
838
- this.#getRectMapper()
839
- );
840
- const size = this.#rtl ? -this.size : this.size;
841
- return getVisibleRange(
842
- this.#view.document,
843
- this.start - size,
844
- this.end - size,
845
- this.#getRectMapper()
846
- );
847
- }
848
- #afterScroll(reason) {
849
- const range = this.#getVisibleRange();
850
- this.#lastVisibleRange = range;
851
- if (reason !== "selection" && reason !== "navigation" && reason !== "anchor")
852
- this.#anchor = range;
853
- else this.#justAnchored = true;
854
- const index = this.#index;
855
- const detail = { reason, range, index };
856
- if (this.scrolled) detail.fraction = this.start / this.viewSize;
857
- else if (this.pages > 0) {
858
- const { page, pages } = this;
859
- this.#header.style.visibility = page > 1 ? "visible" : "hidden";
860
- detail.fraction = (page - 1) / (pages - 2);
861
- detail.size = 1 / (pages - 2);
862
- }
863
- this.dispatchEvent(new CustomEvent("relocate", { detail }));
864
- }
865
- async #display(promise) {
866
- var _a, _b;
867
- const { index, src, anchor, onLoad, select } = await promise;
868
- this.#index = index;
869
- const hasFocus = (_b = (_a = this.#view) == null ? void 0 : _a.document) == null ? void 0 : _b.hasFocus();
870
- if (src) {
871
- const view = this.#createView();
872
- const afterLoad = (doc) => {
873
- if (doc.head) {
874
- const $styleBefore = doc.createElement("style");
875
- doc.head.prepend($styleBefore);
876
- const $style = doc.createElement("style");
877
- doc.head.append($style);
878
- this.#styleMap.set(doc, [$styleBefore, $style]);
879
- }
880
- onLoad == null ? void 0 : onLoad({ doc, index });
881
- };
882
- const beforeRender = this.#beforeRender.bind(this);
883
- await view.load(src, afterLoad, beforeRender);
884
- this.dispatchEvent(new CustomEvent("create-overlayer", {
885
- detail: {
886
- doc: view.document,
887
- index,
888
- attach: (overlayer) => view.overlayer = overlayer
889
- }
890
- }));
891
- this.#view = view;
892
- }
893
- await this.scrollToAnchor((typeof anchor === "function" ? anchor(this.#view.document) : anchor) ?? 0, select);
894
- if (hasFocus) this.focusView();
895
- }
896
- #canGoToIndex(index) {
897
- return index >= 0 && index <= this.sections.length - 1;
898
- }
899
- async #goTo({ index, anchor, select }) {
900
- if (index === this.#index) await this.#display({ index, anchor, select });
901
- else {
902
- const oldIndex = this.#index;
903
- const onLoad = (detail) => {
904
- var _a, _b;
905
- (_b = (_a = this.sections[oldIndex]) == null ? void 0 : _a.unload) == null ? void 0 : _b.call(_a);
906
- this.setStyles(this.#styles);
907
- this.dispatchEvent(new CustomEvent("load", { detail }));
908
- };
909
- await this.#display(Promise.resolve(this.sections[index].load()).then((src) => ({ index, src, anchor, onLoad, select })).catch((e) => {
910
- console.warn(e);
911
- console.warn(new Error(`Failed to load section ${index}`));
912
- return {};
913
- }));
914
- }
915
- }
916
- async goTo(target) {
917
- if (this.#locked) return;
918
- const resolved = await target;
919
- if (this.#canGoToIndex(resolved.index)) return this.#goTo(resolved);
920
- }
921
- #scrollPrev(distance) {
922
- if (!this.#view) return true;
923
- if (this.scrolled) {
924
- if (this.start > 0) return this.#scrollTo(
925
- Math.max(0, this.start - (distance ?? this.size)),
926
- null,
927
- true
928
- );
929
- return true;
930
- }
931
- if (this.atStart) return;
932
- const page = this.page - 1;
933
- return this.#scrollToPage(page, "page", true).then(() => page <= 0);
934
- }
935
- #scrollNext(distance) {
936
- if (!this.#view) return true;
937
- if (this.scrolled) {
938
- if (this.viewSize - this.end > 2) return this.#scrollTo(
939
- Math.min(this.viewSize, distance ? this.start + distance : this.end),
940
- null,
941
- true
942
- );
943
- return true;
944
- }
945
- if (this.atEnd) return;
946
- const page = this.page + 1;
947
- const pages = this.pages;
948
- return this.#scrollToPage(page, "page", true).then(() => page >= pages - 1);
949
- }
950
- get atStart() {
951
- return this.#adjacentIndex(-1) == null && this.page <= 1;
952
- }
953
- get atEnd() {
954
- return this.#adjacentIndex(1) == null && this.page >= this.pages - 2;
955
- }
956
- #adjacentIndex(dir) {
957
- var _a;
958
- for (let index = this.#index + dir; this.#canGoToIndex(index); index += dir)
959
- if (((_a = this.sections[index]) == null ? void 0 : _a.linear) !== "no") return index;
960
- }
961
- async #turnPage(dir, distance) {
962
- if (this.#locked) return;
963
- this.#locked = true;
964
- const prev = dir === -1;
965
- const shouldGo = await (prev ? this.#scrollPrev(distance) : this.#scrollNext(distance));
966
- if (shouldGo) await this.#goTo({
967
- index: this.#adjacentIndex(dir),
968
- anchor: prev ? () => 1 : () => 0
969
- });
970
- if (shouldGo || !this.hasAttribute("animated")) await wait(100);
971
- this.#locked = false;
972
- }
973
- prev(distance) {
974
- return this.#turnPage(-1, distance);
975
- }
976
- next(distance) {
977
- return this.#turnPage(1, distance);
978
- }
979
- prevSection() {
980
- return this.goTo({ index: this.#adjacentIndex(-1) });
981
- }
982
- nextSection() {
983
- return this.goTo({ index: this.#adjacentIndex(1) });
984
- }
985
- firstSection() {
986
- const index = this.sections.findIndex((section) => section.linear !== "no");
987
- return this.goTo({ index });
988
- }
989
- lastSection() {
990
- const index = this.sections.findLastIndex((section) => section.linear !== "no");
991
- return this.goTo({ index });
992
- }
993
- getContents() {
994
- if (this.#view) return [{
995
- index: this.#index,
996
- overlayer: this.#view.overlayer,
997
- doc: this.#view.document
998
- }];
999
- return [];
1000
- }
1001
- setStyles(styles) {
1002
- var _a, _b, _c, _d, _e;
1003
- this.#styles = styles;
1004
- const $$styles = this.#styleMap.get((_a = this.#view) == null ? void 0 : _a.document);
1005
- if (!$$styles) return;
1006
- const [$beforeStyle, $style] = $$styles;
1007
- if (Array.isArray(styles)) {
1008
- const [beforeStyle, style] = styles;
1009
- $beforeStyle.textContent = beforeStyle;
1010
- $style.textContent = style;
1011
- } else $style.textContent = styles;
1012
- requestAnimationFrame(() => this.#background.style.background = getBackground(this.#view.document));
1013
- (_e = (_d = (_c = (_b = this.#view) == null ? void 0 : _b.document) == null ? void 0 : _c.fonts) == null ? void 0 : _d.ready) == null ? void 0 : _e.then(() => this.#view.expand());
1014
- }
1015
- focusView() {
1016
- this.#view.document.defaultView.focus();
1017
- }
1018
- destroy() {
1019
- var _a, _b;
1020
- this.#observer.unobserve(this);
1021
- this.#view.destroy();
1022
- this.#view = null;
1023
- (_b = (_a = this.sections[this.#index]) == null ? void 0 : _a.unload) == null ? void 0 : _b.call(_a);
1024
- this.#mediaQuery.removeEventListener("change", this.#mediaQueryListener);
1025
- }
1026
- }
1027
- customElements.define("foliate-paginator", Paginator);
1028
- exports.Paginator = Paginator;