@triabin/vue-book-reader
Version:
Forked from jinhuan138/vue-book-reader, add some features
784 lines (783 loc) • 29.8 kB
JavaScript
const A = (a) => new Promise((t) => setTimeout(t, a)), z = (a, t, e) => {
let i;
return (...s) => {
const n = () => {
i = null, a(...s);
};
i && clearTimeout(i), i = setTimeout(n, t);
};
}, L = (a, t, e) => e * (t - a) + a, B = (a) => 1 - (1 - a) * (1 - a), I = (a, t, e, i, s) => new Promise((n) => {
let o;
const r = (h) => {
o ??= h;
const l = Math.min(1, (h - o) / e);
s(L(a, t, i(l))), l < 1 ? requestAnimationFrame(r) : n();
};
requestAnimationFrame(r);
}), V = (a) => {
if (!(a != null && a.collapsed)) return a;
const { endOffset: t, endContainer: e } = a;
if (e.nodeType === 1) {
const i = e.childNodes[t];
return (i == null ? void 0 : i.nodeType) === 1 ? i : e;
}
if (t + 1 < e.length) a.setEnd(e, t + 1);
else if (t > 1) a.setStart(e, t - 1);
else return e.parentNode;
return a;
}, b = (a, t, e, i = e) => {
const s = a.createRange();
return s.setStart(t, e), s.setEnd(t, i), s;
}, C = (a, t, e, i = 0, s = t.nodeValue.length) => {
if (s - i === 1)
return e(b(a, t, i), b(a, t, s)) < 0 ? i : s;
const n = Math.floor(i + (s - i) / 2), o = e(b(a, t, i, n), b(a, t, n, s));
return o < 0 ? C(a, t, e, i, n) : o > 0 ? C(a, t, e, n, s) : n;
}, {
SHOW_ELEMENT: N,
SHOW_TEXT: O,
SHOW_CDATA_SECTION: F,
FILTER_ACCEPT: T,
FILTER_REJECT: k,
FILTER_SKIP: M
} = NodeFilter, D = N | O | F, E = (a) => {
let t = 1 / 0, e = -1 / 0, i = 1 / 0, s = -1 / 0;
for (const n of a.getClientRects())
i = Math.min(i, n.left), t = Math.min(t, n.top), e = Math.max(e, n.right), s = Math.max(s, n.bottom);
return new DOMRect(i, t, e - i, s - t);
}, P = (a, t, e, i) => {
const s = (d) => {
var m, g;
const u = (m = d.localName) == null ? void 0 : m.toLowerCase();
if (u === "script" || u === "style") return k;
if (d.nodeType === 1) {
const { left: f, right: y } = i(d.getBoundingClientRect());
if (y < t || f > e) return k;
if (f >= t && y <= e) return T;
} else {
if (!((g = d.nodeValue) != null && g.trim())) return M;
const f = a.createRange();
f.selectNodeContents(d);
const { left: y, right: w } = i(f.getBoundingClientRect());
if (w >= t && y <= e) return T;
}
return M;
}, n = a.createTreeWalker(a.body, D, { acceptNode: s }), o = [];
for (let d = n.nextNode(); d; d = n.nextNode())
o.push(d);
const r = o[0] ?? a.body, h = o[o.length - 1] ?? r, l = r.nodeType === 1 ? 0 : C(a, r, (d, u) => {
const m = i(E(d)), g = i(E(u));
return m.right < t && g.left > t ? 0 : g.left > t ? -1 : 1;
}), c = h.nodeType === 1 ? 0 : C(a, h, (d, u) => {
const m = i(E(d)), g = i(E(u));
return m.right < e && g.left > e ? 0 : g.left > e ? -1 : 1;
}), p = a.createRange();
return p.setStart(r, l), p.setEnd(h, c), p;
}, R = (a) => {
const t = document.createRange();
return t.setStart(a.anchorNode, a.anchorOffset), t.setEnd(a.focusNode, a.focusOffset), t.collapsed;
}, S = (a, t) => {
let e;
if (a.startContainer ? e = a.cloneRange() : a.nodeType && (e = document.createRange(), e.selectNode(a)), e) {
const i = e.startContainer.ownerDocument.defaultView.getSelection();
i.removeAllRanges(), t === -1 ? e.collapse(!0) : t === 1 && e.collapse(), i.addRange(e);
}
}, j = (a) => {
const { defaultView: t } = a, { writingMode: e, direction: i } = t.getComputedStyle(a.body), s = e === "vertical-rl" || e === "vertical-lr", n = a.body.dir === "rtl" || i === "rtl" || a.documentElement.dir === "rtl";
return { vertical: s, rtl: n };
}, W = (a) => {
const t = a.defaultView.getComputedStyle(a.body);
return t.backgroundColor === "rgba(0, 0, 0, 0)" && t.backgroundImage === "none" ? a.defaultView.getComputedStyle(a.documentElement).background : t.background;
}, $ = (a, t) => Array.from({ length: a }, () => {
const e = document.createElement("div"), i = document.createElement("div");
return e.append(i), i.setAttribute("part", t), e;
}), v = (a, t) => {
const { style: e } = a;
for (const [i, s] of Object.entries(t)) e.setProperty(i, s, "important");
};
class q {
#a = new ResizeObserver(() => this.expand());
#o = document.createElement("div");
#i = document.createElement("iframe");
#h = document.createRange();
#e;
#s = !1;
#c = !1;
#t = !0;
#n;
#r = {};
constructor({ container: t, onExpand: e }) {
this.container = t, this.onExpand = e, this.#i.setAttribute("part", "filter"), this.#o.append(this.#i), Object.assign(this.#o.style, {
boxSizing: "content-box",
position: "relative",
overflow: "hidden",
flex: "0 0 auto",
width: "100%",
height: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center"
}), Object.assign(this.#i.style, {
overflow: "hidden",
border: "0",
display: "none",
width: "100%",
height: "100%"
}), this.#i.setAttribute("sandbox", "allow-same-origin allow-scripts"), this.#i.setAttribute("scrolling", "no");
}
get element() {
return this.#o;
}
get document() {
return this.#i.contentDocument;
}
async load(t, e, i) {
if (typeof t != "string") throw new Error(`${t} is not string`);
return new Promise((s) => {
this.#i.addEventListener("load", () => {
const n = this.document;
e == null || e(n), this.#i.style.display = "block";
const { vertical: o, rtl: r } = j(n);
this.docBackground = W(n), n.body.style.background = "none";
const h = this.docBackground;
this.#i.style.display = "none", this.#s = o, this.#c = r, this.#h.selectNodeContents(n.body);
const l = i == null ? void 0 : i({ vertical: o, rtl: r, background: h });
this.#i.style.display = "block", this.render(l), this.#a.observe(n.body), n.fonts.ready.then(() => this.expand()), s();
}, { once: !0 }), this.#i.src = t;
});
}
render(t) {
!t || !this.document || (this.#t = t.flow !== "scrolled", this.#r = t, this.#t ? this.columnize(t) : this.scrolled(t));
}
scrolled({ margin: t, gap: e, columnWidth: i }) {
const s = this.#s, n = this.document;
v(n.documentElement, {
"box-sizing": "border-box",
padding: s ? `${t * 1.5}px ${e}px` : `0 ${e}px`,
"column-width": "auto",
height: "auto",
width: "auto"
}), v(n.body, {
[s ? "max-height" : "max-width"]: `${i}px`,
margin: "auto"
}), this.setImageSize(), this.expand();
}
columnize({ width: t, height: e, margin: i, gap: s, columnWidth: n }) {
const o = this.#s;
this.#n = o ? e : t;
const r = this.document;
v(r.documentElement, {
"box-sizing": "border-box",
"column-width": `${Math.trunc(n)}px`,
"column-gap": o ? `${i}px` : `${s}px`,
"column-fill": "auto",
...o ? { width: `${t}px` } : { height: `${e}px` },
padding: o ? `${i / 2}px ${s}px` : `0 ${s / 2}px`,
overflow: "hidden",
// force wrap long words
"overflow-wrap": "break-word",
// reset some potentially problematic props
position: "static",
border: "0",
margin: "0",
"max-height": "none",
"max-width": "none",
"min-height": "none",
"min-width": "none",
// fix glyph clipping in WebKit
"-webkit-line-box-contain": "block glyphs replaced"
}), v(r.body, {
"max-height": "none",
"max-width": "none",
margin: "0"
}), this.setImageSize(), this.expand();
}
setImageSize() {
const { width: t, height: e, margin: i } = this.#r, s = this.#s, n = this.document;
for (const o of n.body.querySelectorAll("img, svg, video")) {
const { maxHeight: r, maxWidth: h } = n.defaultView.getComputedStyle(o);
v(o, {
"max-height": s ? r !== "none" && r !== "0px" ? r : "100%" : `${e - i * 2}px`,
"max-width": s ? `${t - i * 2}px` : h !== "none" && h !== "0px" ? h : "100%",
"object-fit": "contain",
"page-break-inside": "avoid",
"break-inside": "avoid",
"box-sizing": "border-box"
});
}
}
expand() {
const { documentElement: t } = this.document;
if (this.#t) {
const e = this.#s ? "height" : "width", i = this.#s ? "width" : "height", s = this.#h.getBoundingClientRect(), n = t.getBoundingClientRect(), r = (this.#s ? 0 : this.#c ? n.right - s.right : s.left - n.left) + s[e], l = Math.ceil(r / this.#n) * this.#n;
this.#o.style.padding = "0", this.#i.style[e] = `${l}px`, this.#o.style[e] = `${l + this.#n * 2}px`, this.#i.style[i] = "100%", this.#o.style[i] = "100%", t.style[e] = `${this.#n}px`, this.#e && (this.#e.element.style.margin = "0", this.#e.element.style.left = this.#s ? "0" : `${this.#n}px`, this.#e.element.style.top = this.#s ? `${this.#n}px` : "0", this.#e.element.style[e] = `${l}px`, this.#e.redraw());
} else {
const e = this.#s ? "width" : "height", i = this.#s ? "height" : "width", n = t.getBoundingClientRect()[e], { margin: o, gap: r } = this.#r, h = this.#s ? `0 ${r}px` : `${o}px 0`;
this.#o.style.padding = h, this.#i.style[e] = `${n}px`, this.#o.style[e] = `${n}px`, this.#i.style[i] = "100%", this.#o.style[i] = "100%", this.#e && (this.#e.element.style.margin = h, this.#e.element.style.left = "0", this.#e.element.style.top = "0", this.#e.element.style[e] = `${n}px`, this.#e.redraw());
}
this.onExpand();
}
set overlayer(t) {
this.#e = t, this.#o.append(t.element);
}
get overlayer() {
return this.#e;
}
destroy() {
this.document && this.#a.unobserve(this.document.body);
}
}
class H extends HTMLElement {
static observedAttributes = [
"flow",
"gap",
"margin",
"max-inline-size",
"max-block-size",
"max-column-count"
];
#a = this.attachShadow({ mode: "closed" });
#o = new ResizeObserver(() => this.render());
#i;
#h;
#e;
#s;
#c;
#t;
#n = !1;
#r = !1;
#m = 0;
#d = -1;
#l = 0;
// anchor view to a fraction (0-1), Range, or Element
#E = !1;
#x = !1;
// while true, prevent any further navigation
#T;
#k = /* @__PURE__ */ new WeakMap();
#M = matchMedia("(prefers-color-scheme: dark)");
#S;
#g;
#v;
#C;
#P;
constructor() {
super(), this.#a.innerHTML = `<style>
:host {
display: block;
container-type: size;
}
:host, #top {
box-sizing: border-box;
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
}
#top {
--_gap: 7%;
--_margin: 48px;
--_max-inline-size: 100%;
--_max-block-size: 100%;
--_max-column-count: 2;
--_max-column-count-portrait: 1;
--_max-column-count-spread: var(--_max-column-count);
--_half-gap: calc(var(--_gap) / 2);
--_max-width: calc(var(--_max-inline-size) * var(--_max-column-count-spread));
--_max-height: var(--_max-block-size);
display: grid;
grid-template-columns:
minmax(var(--_half-gap), 1fr)
var(--_half-gap)
minmax(0, calc(var(--_max-width) - var(--_gap)))
var(--_half-gap)
minmax(var(--_half-gap), 1fr);
grid-template-rows:
minmax(var(--_margin), 1fr)
minmax(0, var(--_max-height))
minmax(var(--_margin), 1fr);
&.vertical {
--_max-column-count-spread: var(--_max-column-count-portrait);
--_max-width: var(--_max-block-size);
--_max-height: calc(var(--_max-inline-size) * var(--_max-column-count-spread));
}
(orientation: portrait) {
& {
--_max-column-count-spread: var(--_max-column-count-portrait);
}
&.vertical {
--_max-column-count-spread: var(--_max-column-count);
}
}
}
#background {
grid-column: 1 / -1;
grid-row: 1 / -1;
}
#container {
grid-column: 2 / 5;
grid-row: 2;
overflow: hidden;
}
:host([flow="scrolled"]) #container {
grid-column: 1 / -1;
grid-row: 1 / -1;
overflow: auto;
}
#header {
grid-column: 3 / 4;
grid-row: 1;
}
#footer {
grid-column: 3 / 4;
grid-row: 3;
align-self: end;
}
#header, #footer {
display: grid;
height: var(--_margin);
}
:is(#header, #footer) > * {
display: flex;
align-items: center;
min-width: 0;
}
:is(#header, #footer) > * > * {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
font-size: .75em;
opacity: .6;
}
</style>
<div id="top">
<div id="background" part="filter"></div>
<div id="header"></div>
<div id="container" part="container"></div>
<div id="footer"></div>
</div>
`, this.#i = this.#a.getElementById("top"), this.#h = this.#a.getElementById("background"), this.#e = this.#a.getElementById("container"), this.#s = this.#a.getElementById("header"), this.#c = this.#a.getElementById("footer"), this.#o.observe(this.#e), this.#e.addEventListener("scroll", () => this.dispatchEvent(new Event("scroll"))), this.#e.addEventListener("scroll", z(() => {
this.scrolled && (this.#E ? this.#E = !1 : this.#b("scroll"));
}, 250));
const t = { passive: !1 };
this.addEventListener("touchstart", this.#$.bind(this), t), this.addEventListener("touchmove", this.#A.bind(this), t), this.addEventListener("touchend", this.#L.bind(this)), this.addEventListener("load", ({ detail: { doc: i } }) => {
i.addEventListener("touchstart", this.#$.bind(this), t), i.addEventListener("touchmove", this.#A.bind(this), t), i.addEventListener("touchend", this.#L.bind(this));
}), this.addEventListener("relocate", ({ detail: i }) => {
i.reason === "selection" ? S(this.#l, 0) : i.reason === "navigation" && (this.#l === 1 ? S(i.range, 1) : typeof this.#l == "number" ? S(i.range, -1) : S(this.#l, -1));
});
const e = z((i, s) => {
if (!s.rangeCount) return;
const n = s.getRangeAt(0), o = R(s);
o && n.compareBoundaryPoints(Range.START_TO_START, i) < 0 ? this.prev() : !o && n.compareBoundaryPoints(Range.END_TO_END, i) > 0 && this.next();
}, 700);
this.addEventListener("load", ({ detail: { doc: i } }) => {
let s = !1;
i.addEventListener("pointerdown", () => s = !0), i.addEventListener("pointerup", () => s = !1);
let n = !1;
i.addEventListener("keydown", () => n = !0), i.addEventListener("keyup", () => n = !1), i.addEventListener("selectionchange", () => {
if (this.scrolled) return;
const o = this.#P;
if (!o) return;
const r = i.getSelection();
if (r.rangeCount) {
if (s && r.type === "Range")
e(o, r);
else if (n) {
const h = r.getRangeAt(0).cloneRange();
R(r) || h.collapse(), this.#y(h);
}
}
}), i.addEventListener("focusin", (o) => this.scrolled ? null : (
// NOTE: `requestAnimationFrame` is needed in WebKit
requestAnimationFrame(() => this.#y(o.target))
));
}), this.#S = () => {
this.#t && this.#_(this.#t.docBackground, this.columnCount);
}, this.#M.addEventListener("change", this.#S);
}
attributeChangedCallback(t, e, i) {
switch (t) {
case "flow":
this.render();
break;
case "gap":
case "margin":
case "max-block-size":
case "max-column-count":
this.#i.style.setProperty("--_" + t, i), this.render();
break;
case "max-inline-size":
this.#i.style.setProperty("--_" + t, i), this.render();
break;
}
}
open(t) {
var e;
this.bookDir = t.dir, this.sections = t.sections, (e = t.transformTarget) == null || e.addEventListener("data", ({ detail: i }) => {
if (i.type !== "text/css") return;
const s = innerWidth, n = innerHeight;
i.data = Promise.resolve(i.data).then((o) => o.replace(new RegExp("(?<=[{\\s;])-epub-", "gi"), "").replace(/(\d*\.?\d+)vw/gi, (r, h) => parseFloat(h) * s / 100 + "px").replace(/(\d*\.?\d+)vh/gi, (r, h) => parseFloat(h) * n / 100 + "px").replace(/page-break-(after|before|inside)\s*:/gi, (r, h) => `-webkit-column-break-${h}:`).replace(/break-(after|before|inside)\s*:\s*(avoid-)?page/gi, (r, h, l) => `break-${h}: ${l ?? ""}column`));
});
}
#N() {
return this.#t && (this.#t.destroy(), this.#e.removeChild(this.#t.element)), this.#t = new q({
container: this,
onExpand: () => this.#y(this.#l)
}), this.#e.append(this.#t.element), this.#t;
}
#_(t, e) {
var o;
const i = (o = this.#t) == null ? void 0 : o.document;
if (!i) return;
const n = i.defaultView.getComputedStyle(i.documentElement).getPropertyValue("--theme-bg-color");
if (t && n) {
const r = t.split(/\s(?=(?:url|rgb|hsl|#[0-9a-fA-F]{3,6}))/);
r[0] = n, t = r.join(" ");
}
/cover.*fixed|fixed.*cover/.test(t) && (t = t.replace("cover", "auto 100%").replace("fixed", "")), this.#h.innerHTML = "", this.#h.style.display = "grid", this.#h.style.gridTemplateColumns = `repeat(${e}, 1fr)`;
for (let r = 0; r < e; r++) {
const h = document.createElement("div");
h.style.background = t, h.style.width = "100%", h.style.height = "100%", this.#h.appendChild(h);
}
}
#R({ vertical: t, rtl: e, background: i }) {
this.#n = t, this.#r = e, this.#i.classList.toggle("vertical", t);
const { width: s, height: n } = this.#e.getBoundingClientRect(), o = t ? n : s, r = getComputedStyle(this.#i), h = parseFloat(r.getPropertyValue("--_max-inline-size")), l = parseInt(r.getPropertyValue("--_max-column-count-spread")), c = parseFloat(r.getPropertyValue("--_margin"));
this.#m = c;
const p = parseFloat(r.getPropertyValue("--_gap")) / 100, d = -p / (p - 1) * o, u = this.getAttribute("flow");
if (u === "scrolled") {
this.setAttribute("dir", t ? "rtl" : "ltr"), this.#i.style.padding = "0";
const x = h;
return this.heads = null, this.feet = null, this.#s.replaceChildren(), this.#c.replaceChildren(), { flow: u, margin: c, gap: d, columnWidth: x };
}
const m = Math.min(l, Math.ceil(o / h)), g = t ? o / m - c : o / m - d;
this.setAttribute("dir", e ? "rtl" : "ltr"), this.columnCount = m, this.#_(i, this.columnCount);
const f = t ? Math.min(2, Math.ceil(s / h)) : m, y = {
gridTemplateColumns: `repeat(${f}, 1fr)`,
gap: `${d}px`,
direction: this.bookDir === "rtl" ? "rtl" : "ltr"
};
Object.assign(this.#s.style, y), Object.assign(this.#c.style, y);
const w = $(f, "head"), _ = $(f, "foot");
return this.heads = w.map((x) => x.children[0]), this.feet = _.map((x) => x.children[0]), this.#s.replaceChildren(...w), this.#c.replaceChildren(..._), { height: n, width: s, margin: c, gap: d, columnWidth: g };
}
render() {
this.#t && (this.#t.render(this.#R({
vertical: this.#n,
rtl: this.#r
})), this.#y(this.#l));
}
get scrolled() {
return this.getAttribute("flow") === "scrolled";
}
get scrollProp() {
const { scrolled: t } = this;
return this.#n ? t ? "scrollLeft" : "scrollTop" : t ? "scrollTop" : "scrollLeft";
}
get sideProp() {
const { scrolled: t } = this;
return this.#n ? t ? "width" : "height" : t ? "height" : "width";
}
get size() {
return this.#e.getBoundingClientRect()[this.sideProp];
}
get viewSize() {
return this.#t.element.getBoundingClientRect()[this.sideProp];
}
get start() {
return Math.abs(this.#e[this.scrollProp]);
}
get end() {
return this.start + this.size;
}
get page() {
return Math.floor((this.start + this.end) / 2 / this.size);
}
get pages() {
return Math.round(this.viewSize / this.size);
}
// this is the current position of the container
get containerPosition() {
return this.#e[this.scrollProp];
}
// this is the new position of the containr
set containerPosition(t) {
this.#e[this.scrollProp] = t;
}
scrollBy(t, e) {
const i = this.#n ? e : t, [s, n, o] = this.#g, r = this.#r, h = r ? s - o : s - n, l = r ? s + n : s + o;
this.containerPosition = Math.max(h, Math.min(
l,
this.containerPosition + i
));
}
snap(t, e) {
const i = this.#n ? e : t, [s, n, o] = this.#g, { start: r, end: h, pages: l, size: c } = this, p = Math.abs(s) - n, d = Math.abs(s) + o, u = i * (this.#r ? -c : c), m = Math.floor(
Math.max(p, Math.min(d, (r + h) / 2 + (isNaN(u) ? 0 : u))) / c
);
this.#f(m, "snap").then(() => {
const g = m <= 0 ? -1 : m >= l - 1 ? 1 : null;
if (g) return this.#z({
index: this.#u(g),
anchor: g < 0 ? () => 1 : () => 0
});
});
}
#$(t) {
const e = t.changedTouches[0];
this.#v = {
x: e == null ? void 0 : e.screenX,
y: e == null ? void 0 : e.screenY,
t: t.timeStamp,
vx: 0,
xy: 0
};
}
#A(t) {
var p;
const e = this.#v;
if (e.pinched || (e.pinched = globalThis.visualViewport.scale > 1, this.scrolled || e.pinched)) return;
if (t.touches.length > 1) {
this.#C && t.preventDefault();
return;
}
const i = (p = this.#t) == null ? void 0 : p.document, s = i == null ? void 0 : i.getSelection();
if (s && s.rangeCount > 0 && !s.isCollapsed)
return;
t.preventDefault();
const n = t.changedTouches[0], o = n.screenX, r = n.screenY, h = e.x - o, l = e.y - r, c = t.timeStamp - e.t;
e.x = o, e.y = r, e.t = t.timeStamp, e.vx = h / c, e.vy = l / c, this.#C = !0, Math.abs(h) >= Math.abs(l) ? this.scrollBy(h, 0) : Math.abs(l) > Math.abs(h) && this.scrollBy(0, l);
}
#L() {
this.#C = !1, !this.scrolled && requestAnimationFrame(() => {
globalThis.visualViewport.scale === 1 && this.snap(this.#v.vx, this.#v.vy);
});
}
// allows one to process rects as if they were LTR and horizontal
#w() {
if (this.scrolled) {
const e = this.viewSize, i = this.#m;
return this.#n ? ({ left: s, right: n }) => ({ left: e - n - i, right: e - s - i }) : ({ top: s, bottom: n }) => ({ left: s + i, right: n + i });
}
const t = this.pages * this.size;
return this.#r ? ({ left: e, right: i }) => ({ left: t - i, right: t - e }) : this.#n ? ({ top: e, bottom: i }) => ({ left: e, right: i }) : (e) => e;
}
async #O(t, e) {
if (this.scrolled) {
const s = this.#w()(t).left - this.#m;
return this.#p(s, e);
}
const i = this.#w()(t).left;
return this.#f(Math.floor(i / this.size) + (this.#r ? -1 : 1), e);
}
async #p(t, e, i) {
const { size: s } = this;
if (this.containerPosition === t) {
this.#g = [t, this.atStart ? 0 : s, this.atEnd ? 0 : s], this.#b(e);
return;
}
if (this.scrolled && this.#n && (t = -t), (e === "snap" || i) && this.hasAttribute("animated")) return I(
this.containerPosition,
t,
300,
B,
(n) => this.containerPosition = n
).then(() => {
this.#g = [t, this.atStart ? 0 : s, this.atEnd ? 0 : s], this.#b(e);
});
this.containerPosition = t, this.#g = [t, this.atStart ? 0 : s, this.atEnd ? 0 : s], this.#b(e);
}
async #f(t, e, i) {
const s = this.size * (this.#r ? -t : t);
return this.#p(s, e, i);
}
async scrollToAnchor(t, e) {
return this.#y(t, e ? "selection" : "navigation");
}
async #y(t, e = "anchor") {
var r, h;
this.#l = t;
const i = (h = (r = V(t)) == null ? void 0 : r.getClientRects) == null ? void 0 : h.call(r);
if (i) {
const l = Array.from(i).find((c) => c.width > 0 && c.height > 0) || i[0];
if (!l) return;
await this.#O(l, e);
return;
}
if (this.scrolled) {
await this.#p(t * this.viewSize, e);
return;
}
const { pages: s } = this;
if (!s) return;
const n = s - 2, o = Math.round(t * (n - 1));
await this.#f(o + 1, e);
}
#F() {
if (this.scrolled) return P(
this.#t.document,
this.start + this.#m,
this.end - this.#m,
this.#w()
);
const t = this.#r ? -this.size : this.size;
return P(
this.#t.document,
this.start - t,
this.end - t,
this.#w()
);
}
#b(t) {
const e = this.#F();
this.#P = e, t !== "selection" && t !== "navigation" && t !== "anchor" ? this.#l = e : this.#E = !0;
const i = this.#d, s = { reason: t, range: e, index: i };
if (this.scrolled) s.fraction = this.start / this.viewSize;
else if (this.pages > 0) {
const { page: n, pages: o } = this;
this.#s.style.visibility = n > 1 ? "visible" : "hidden", s.fraction = (n - 1) / (o - 2), s.size = 1 / (o - 2);
}
this.dispatchEvent(new CustomEvent("relocate", { detail: s }));
}
async #B(t) {
var h, l;
const { index: e, src: i, anchor: s, onLoad: n, select: o } = await t;
this.#d = e;
const r = (l = (h = this.#t) == null ? void 0 : h.document) == null ? void 0 : l.hasFocus();
if (i) {
const c = this.#N(), p = (u) => {
if (u.head) {
const m = u.createElement("style");
u.head.prepend(m);
const g = u.createElement("style");
u.head.append(g), this.#k.set(u, [m, g]);
}
n == null || n({ doc: u, index: e });
}, d = this.#R.bind(this);
await c.load(i, p, d), this.dispatchEvent(new CustomEvent("create-overlayer", {
detail: {
doc: c.document,
index: e,
attach: (u) => c.overlayer = u
}
})), this.#t = c;
}
await this.scrollToAnchor((typeof s == "function" ? s(this.#t.document) : s) ?? 0, o), r && this.focusView();
}
#I(t) {
return t >= 0 && t <= this.sections.length - 1;
}
async #z({ index: t, anchor: e, select: i }) {
if (t === this.#d) await this.#B({ index: t, anchor: e, select: i });
else {
const s = this.#d, n = (o) => {
var r, h;
(h = (r = this.sections[s]) == null ? void 0 : r.unload) == null || h.call(r), this.setStyles(this.#T), this.dispatchEvent(new CustomEvent("load", { detail: o }));
};
await this.#B(Promise.resolve(this.sections[t].load()).then((o) => ({ index: t, src: o, anchor: e, onLoad: n, select: i })).catch((o) => (console.warn(o), console.warn(new Error(`Failed to load section ${t}`)), {})));
}
}
async goTo(t) {
if (this.#x) return;
const e = await t;
if (this.#I(e.index)) return this.#z(e);
}
#D(t) {
if (!this.#t) return !0;
if (this.scrolled)
return this.start > 0 ? this.#p(
Math.max(0, this.start - (t ?? this.size)),
null,
!0
) : !this.atStart;
if (this.atStart) return;
const e = this.page - 1;
return this.#f(e, "page", !0).then(() => e <= 0);
}
#j(t) {
if (!this.#t) return !0;
if (this.scrolled)
return this.viewSize - this.end > 2 ? this.#p(
Math.min(this.viewSize, t ? this.start + t : this.end),
null,
!0
) : !this.atEnd;
if (this.atEnd) return;
const e = this.page + 1, i = this.pages;
return this.#f(e, "page", !0).then(() => e >= i - 1);
}
get atStart() {
return this.#u(-1) == null && this.page <= 1;
}
get atEnd() {
return this.#u(1) == null && this.page >= this.pages - 2;
}
#u(t) {
var e;
for (let i = this.#d + t; this.#I(i); i += t)
if (((e = this.sections[i]) == null ? void 0 : e.linear) !== "no") return i;
}
async #V(t, e) {
if (this.#x) return;
this.#x = !0;
const i = t === -1, s = await (i ? this.#D(e) : this.#j(e));
s && await this.#z({
index: this.#u(t),
anchor: i ? () => 1 : () => 0
}), (s || !this.hasAttribute("animated")) && await A(100), this.#x = !1;
}
async prev(t) {
return await this.#V(-1, t);
}
async next(t) {
return await this.#V(1, t);
}
prevSection() {
return this.goTo({ index: this.#u(-1) });
}
nextSection() {
return this.goTo({ index: this.#u(1) });
}
firstSection() {
const t = this.sections.findIndex((e) => e.linear !== "no");
return this.goTo({ index: t });
}
lastSection() {
const t = this.sections.findLastIndex((e) => e.linear !== "no");
return this.goTo({ index: t });
}
getContents() {
return this.#t ? [{
index: this.#d,
overlayer: this.#t.overlayer,
doc: this.#t.document
}] : [];
}
setStyles(t) {
var n, o, r, h, l;
this.#T = t;
const e = this.#k.get((n = this.#t) == null ? void 0 : n.document);
if (!e) return;
const [i, s] = e;
if (Array.isArray(t)) {
const [c, p] = t;
i.textContent = c, s.textContent = p;
} else s.textContent = t;
requestAnimationFrame(() => {
this.#_(this.#t.docBackground, this.columnCount);
}), (l = (h = (r = (o = this.#t) == null ? void 0 : o.document) == null ? void 0 : r.fonts) == null ? void 0 : h.ready) == null || l.then(() => this.#t.expand());
}
focusView() {
this.#t.document.defaultView.focus();
}
destroy() {
var t, e;
this.#o.unobserve(this), this.#t.destroy(), this.#t = null, (e = (t = this.sections[this.#d]) == null ? void 0 : t.unload) == null || e.call(t), this.#M.removeEventListener("change", this.#S);
}
}
customElements.define("foliate-paginator", H);
export {
H as Paginator
};