mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
Refactor FixedLayout zoom/pan from scroll-based to transform-based model
Replace the overflow:auto + scrollTo zoom approach with a CSS transform
model to fix the scroll-to-zoom cursor targeting bug in dual-pane layouts.
Root cause: flex centering (justify-content:center) and overflow:auto
conflict -- when content fits, flex positions it; when zoomed (overflow),
scrollLeft=0 jumps to the left edge. The zoom-to-cursor formula used
scrollLeft as reference without accounting for the centering offset,
causing systematic drift toward the right pane.
Changes:
- Host CSS: overflow:hidden, display:block, position:relative
- New #wrapper div with transform:translate(x,y) for pan positioning
- New #transform = { x, y, scale } as single source of truth
- #zoomByRatio(cx,cy,ratio) uses panzoom formula: x = cx - ratio*(cx - x)
- #render() centers via transform.x/y = (hostSize - contentSize) / 2
- #updateFrameScales() handles iframe size/scale separately from centering
- Simplified drag: tracks startTX/startTY, updates transform directly
- Simplified attributeChangedCallback: detects external vs internal zoom
changes and zooms from viewport center for external ones
- Remove all scrollLeft/scrollTop/scrollTo usage
- Remove marginBlock:auto, long-press drag detection, debug console.logs
- Preserve external API: zoom attribute, dragOffset, toggleMagnifier(),
load/relocate events
This commit is contained in:
+203
-328
@@ -1,32 +1,23 @@
|
|||||||
const parseViewport = (str) =>
|
const parseViewport = (str) =>
|
||||||
str
|
str
|
||||||
?.split(/[,;\s]/) // NOTE: technically, only the comma is valid
|
?.split(/[,;\s]/)
|
||||||
?.filter((x) => x)
|
?.filter((x) => x)
|
||||||
?.map((x) => x.split("=").map((x) => x.trim()));
|
?.map((x) => x.split("=").map((x) => x.trim()));
|
||||||
|
|
||||||
const getViewport = (doc, viewport) => {
|
const getViewport = (doc, viewport) => {
|
||||||
// use `viewBox` for SVG
|
|
||||||
if (doc.documentElement.localName === "svg") {
|
if (doc.documentElement.localName === "svg") {
|
||||||
const [, , width, height] =
|
const [, , width, height] =
|
||||||
doc.documentElement.getAttribute("viewBox")?.split(/\s/) ?? [];
|
doc.documentElement.getAttribute("viewBox")?.split(/\s/) ?? [];
|
||||||
return { width, height };
|
return { width, height };
|
||||||
}
|
}
|
||||||
|
|
||||||
// get `viewport` `meta` element
|
|
||||||
const meta = parseViewport(
|
const meta = parseViewport(
|
||||||
doc.querySelector('meta[name="viewport"]')?.getAttribute("content"),
|
doc.querySelector('meta[name="viewport"]')?.getAttribute("content"),
|
||||||
);
|
);
|
||||||
if (meta) return Object.fromEntries(meta);
|
if (meta) return Object.fromEntries(meta);
|
||||||
|
|
||||||
// fallback to book's viewport
|
|
||||||
if (typeof viewport === "string") return parseViewport(viewport);
|
if (typeof viewport === "string") return parseViewport(viewport);
|
||||||
if (viewport?.width && viewport.height) return viewport;
|
if (viewport?.width && viewport.height) return viewport;
|
||||||
|
|
||||||
// if no viewport (possibly with image directly in spine), get image size
|
|
||||||
const img = doc.querySelector("img");
|
const img = doc.querySelector("img");
|
||||||
if (img) return { width: img.naturalWidth, height: img.naturalHeight };
|
if (img) return { width: img.naturalWidth, height: img.naturalHeight };
|
||||||
|
|
||||||
// just show *something*, i guess...
|
|
||||||
console.warn(new Error("Missing viewport properties"));
|
console.warn(new Error("Missing viewport properties"));
|
||||||
return { width: 1000, height: 2000 };
|
return { width: 1000, height: 2000 };
|
||||||
};
|
};
|
||||||
@@ -45,6 +36,8 @@ export class FixedLayout extends HTMLElement {
|
|||||||
#center;
|
#center;
|
||||||
#side;
|
#side;
|
||||||
#zoom;
|
#zoom;
|
||||||
|
#wrapper;
|
||||||
|
#transform = { x: 0, y: 0, scale: 1 };
|
||||||
#zoomState = {
|
#zoomState = {
|
||||||
minScale: 0.1,
|
minScale: 0.1,
|
||||||
maxScale: 10,
|
maxScale: 10,
|
||||||
@@ -52,14 +45,10 @@ export class FixedLayout extends HTMLElement {
|
|||||||
};
|
};
|
||||||
#dragState = {
|
#dragState = {
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
isPotentialDrag: false,
|
|
||||||
dragTimeout: null,
|
|
||||||
pressDelay: 200, // milliseconds
|
|
||||||
startX: 0,
|
startX: 0,
|
||||||
startY: 0,
|
startY: 0,
|
||||||
scrollLeft: 0,
|
startTX: 0,
|
||||||
scrollTop: 0,
|
startTY: 0,
|
||||||
draggable: true,
|
|
||||||
};
|
};
|
||||||
dragOffset = { x: 0, y: 0 };
|
dragOffset = { x: 0, y: 0 };
|
||||||
#magnifier = {
|
#magnifier = {
|
||||||
@@ -68,6 +57,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
magnification: 2,
|
magnification: 2,
|
||||||
element: null,
|
element: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -76,54 +66,58 @@ export class FixedLayout extends HTMLElement {
|
|||||||
sheet.replaceSync(`:host {
|
sheet.replaceSync(`:host {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: block;
|
||||||
justify-content: center;
|
overflow: hidden;
|
||||||
align-items: center;
|
position: relative;
|
||||||
overflow: auto;
|
|
||||||
}`);
|
}`);
|
||||||
|
|
||||||
|
this.#wrapper = document.createElement("div");
|
||||||
|
this.#wrapper.style.cssText = `
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
`;
|
||||||
|
this.#root.appendChild(this.#wrapper);
|
||||||
|
|
||||||
this.#observer.observe(this);
|
this.#observer.observe(this);
|
||||||
// DEBUG: Log initialization
|
|
||||||
console.log("[FixedLayout Debug] ✓ FixedLayout initialized", {
|
|
||||||
hasZoomState: !!this.#zoomState,
|
|
||||||
hasDragState: !!this.#dragState,
|
|
||||||
hasMagnifier: !!this.#magnifier,
|
|
||||||
zoomState: this.#zoomState,
|
|
||||||
dragState: this.#dragState,
|
|
||||||
});
|
|
||||||
// NOTE: Event listeners will be attached to wrapper elements in #showSpread
|
|
||||||
console.log(
|
|
||||||
"[FixedLayout Debug] Event listeners will be attached to frame wrappers",
|
|
||||||
);
|
|
||||||
// NOTE: Event listeners will be attached to iframe documents in #createFrame
|
|
||||||
console.log(
|
|
||||||
"[FixedLayout Debug] Event listeners will be attached to iframe documents",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeChangedCallback(name, _, value) {
|
attributeChangedCallback(name, _, value) {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "zoom":
|
case "zoom": {
|
||||||
this.#zoom =
|
const newZoom =
|
||||||
value !== "fit-width" && value !== "fit-page"
|
value !== "fit-width" && value !== "fit-page"
|
||||||
? parseFloat(value)
|
? parseFloat(value)
|
||||||
: value;
|
: value;
|
||||||
this.#render();
|
|
||||||
|
if (typeof newZoom === "number" && !isNaN(newZoom)) {
|
||||||
|
if (this.#transform.scale === newZoom) return;
|
||||||
|
const rect = this.getBoundingClientRect();
|
||||||
|
const cx = rect.width / 2;
|
||||||
|
const cy = rect.height / 2;
|
||||||
|
this.#zoomByRatio(cx, cy, newZoom / this.#transform.scale);
|
||||||
|
} else {
|
||||||
|
this.#zoom = newZoom;
|
||||||
|
this.#render();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toggleMagnifier() {
|
|
||||||
console.log("[FixedLayout Debug] 🔍 Toggle magnifier:", {
|
|
||||||
current: this.#magnifier.enabled,
|
|
||||||
willBe: !this.#magnifier.enabled,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
get zoom() {
|
||||||
|
return this.#zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMagnifier() {
|
||||||
this.#magnifier.enabled = !this.#magnifier.enabled;
|
this.#magnifier.enabled = !this.#magnifier.enabled;
|
||||||
|
|
||||||
if (this.#magnifier.enabled) {
|
if (this.#magnifier.enabled) {
|
||||||
this.#createMagnifier();
|
this.#createMagnifier();
|
||||||
this.addEventListener("mousemove", this.#handleMagnifierMove.bind(this));
|
this.addEventListener("mousemove", this.#handleMagnifierMove.bind(this));
|
||||||
this.style.cursor = "crosshair";
|
this.style.cursor = "crosshair";
|
||||||
console.log("[FixedLayout Debug] ✅ Magnifier enabled");
|
|
||||||
} else {
|
} else {
|
||||||
this.#destroyMagnifier();
|
this.#destroyMagnifier();
|
||||||
this.removeEventListener(
|
this.removeEventListener(
|
||||||
@@ -131,16 +125,11 @@ export class FixedLayout extends HTMLElement {
|
|||||||
this.#handleMagnifierMove.bind(this),
|
this.#handleMagnifierMove.bind(this),
|
||||||
);
|
);
|
||||||
this.style.cursor = "";
|
this.style.cursor = "";
|
||||||
console.log("[FixedLayout Debug] ❌ Magnifier disabled");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#createMagnifier() {
|
#createMagnifier() {
|
||||||
if (this.#magnifier.element) {
|
if (this.#magnifier.element) return;
|
||||||
console.log("[FixedLayout Debug] ⏸️ Magnifier already exists");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("[FixedLayout Debug] 🔧 Creating magnifier element");
|
|
||||||
|
|
||||||
const magnifier = document.createElement("div");
|
const magnifier = document.createElement("div");
|
||||||
magnifier.className = "magnifier";
|
magnifier.className = "magnifier";
|
||||||
@@ -170,14 +159,12 @@ export class FixedLayout extends HTMLElement {
|
|||||||
magnifier.appendChild(lens);
|
magnifier.appendChild(lens);
|
||||||
this.#root.appendChild(magnifier);
|
this.#root.appendChild(magnifier);
|
||||||
this.#magnifier.element = magnifier;
|
this.#magnifier.element = magnifier;
|
||||||
console.log("[FixedLayout Debug] ✅ Magnifier element created");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#destroyMagnifier() {
|
#destroyMagnifier() {
|
||||||
if (this.#magnifier.element) {
|
if (this.#magnifier.element) {
|
||||||
this.#magnifier.element.remove();
|
this.#magnifier.element.remove();
|
||||||
this.#magnifier.element = null;
|
this.#magnifier.element = null;
|
||||||
console.log("[FixedLayout Debug] ✅ Magnifier destroyed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,239 +175,165 @@ export class FixedLayout extends HTMLElement {
|
|||||||
const x = event.clientX - rect.left;
|
const x = event.clientX - rect.left;
|
||||||
const y = event.clientY - rect.top;
|
const y = event.clientY - rect.top;
|
||||||
|
|
||||||
// Position magnifier
|
|
||||||
const magnifier = this.#magnifier.element;
|
const magnifier = this.#magnifier.element;
|
||||||
magnifier.style.display = "block";
|
magnifier.style.display = "block";
|
||||||
magnifier.style.left = `${x - this.#magnifier.size / 2}px`;
|
magnifier.style.left = `${x - this.#magnifier.size / 2}px`;
|
||||||
magnifier.style.top = `${y - this.#magnifier.size / 2}px`;
|
magnifier.style.top = `${y - this.#magnifier.size / 2}px`;
|
||||||
|
|
||||||
// Update lens (clone and zoom iframe content)
|
|
||||||
const lens = magnifier.querySelector(".magnifier-lens");
|
const lens = magnifier.querySelector(".magnifier-lens");
|
||||||
const frame = this.#center || this.#left || this.#right;
|
const frame = this.#center || this.#left || this.#right;
|
||||||
if (frame?.iframe) {
|
if (frame?.iframe) {
|
||||||
const iframe = frame.iframe;
|
const iframe = frame.iframe;
|
||||||
lens.innerHTML = "";
|
lens.innerHTML = "";
|
||||||
lens.appendChild(iframe.cloneNode(true));
|
lens.appendChild(iframe.cloneNode(true));
|
||||||
|
const scale = this.#transform.scale || 1;
|
||||||
lens.style.transform = `scale(${this.#magnifier.magnification})`;
|
lens.style.transform = `scale(${this.#magnifier.magnification})`;
|
||||||
|
|
||||||
const iframeRect = iframe.getBoundingClientRect();
|
const iframeRect = iframe.getBoundingClientRect();
|
||||||
const relX = (x - iframeRect.left) / (this.#zoom || 1);
|
const relX = (x - iframeRect.left) / scale;
|
||||||
const relY = (y - iframeRect.top) / (this.#zoom || 1);
|
const relY = (y - iframeRect.top) / scale;
|
||||||
|
|
||||||
lens.style.transformOrigin = `${-relX + this.#magnifier.size / 2}px ${-relY + this.#magnifier.size / 2}px`;
|
lens.style.transformOrigin = `${-relX + this.#magnifier.size / 2}px ${-relY + this.#magnifier.size / 2}px`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#handleWheel(event) {
|
|
||||||
console.log("[FixedLayout Debug] 🖱️ Wheel event:", {
|
#applyTransform() {
|
||||||
deltaY: event.deltaY,
|
this.#wrapper.style.transform = `translate(${this.#transform.x}px, ${this.#transform.y}px)`;
|
||||||
ctrlKey: event.ctrlKey,
|
}
|
||||||
metaKey: event.metaKey,
|
|
||||||
panelMode: this.hasAttribute("panel-mode"),
|
#updateFrameScales(scale) {
|
||||||
currentZoom: this.#zoom,
|
const left = this.#left ?? {};
|
||||||
});
|
const right = this.#center ?? this.#right ?? {};
|
||||||
// Don't interfere with panel mode
|
const { width: hostWidth, height: hostHeight } = this.getBoundingClientRect();
|
||||||
if (this.hasAttribute("panel-mode")) {
|
const portrait =
|
||||||
console.log("[FixedLayout Debug] ⏸️ Ignoring wheel - panel mode active");
|
this.spread !== "both" && this.spread !== "portrait" && hostHeight > hostWidth;
|
||||||
return;
|
const target = this.#side === "left" ? left : right;
|
||||||
|
const blankWidth = left.width ?? right.width ?? 0;
|
||||||
|
const blankHeight = left.height ?? right.height ?? 0;
|
||||||
|
|
||||||
|
const transform = (frame) => {
|
||||||
|
let { element, iframe, width, height, blank, onZoom } = frame;
|
||||||
|
if (!iframe) return;
|
||||||
|
if (onZoom) onZoom({ doc: frame.iframe.contentDocument, scale });
|
||||||
|
const iframeScale = onZoom ? scale : 1;
|
||||||
|
Object.assign(iframe.style, {
|
||||||
|
width: `${width * iframeScale}px`,
|
||||||
|
height: `${height * iframeScale}px`,
|
||||||
|
transform: onZoom ? "none" : `scale(${scale})`,
|
||||||
|
transformOrigin: "top left",
|
||||||
|
display: blank ? "none" : "block",
|
||||||
|
});
|
||||||
|
Object.assign(element.style, {
|
||||||
|
width: `${(width ?? blankWidth) * scale}px`,
|
||||||
|
height: `${(height ?? blankHeight) * scale}px`,
|
||||||
|
overflow: "hidden",
|
||||||
|
display: "block",
|
||||||
|
flexShrink: "0",
|
||||||
|
});
|
||||||
|
if (portrait && frame !== target) {
|
||||||
|
element.style.display = "none";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (this.#center) {
|
||||||
|
transform(this.#center);
|
||||||
|
} else {
|
||||||
|
transform(left);
|
||||||
|
transform(right);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Allow browser zoom with Ctrl/Cmd
|
#getContentSize() {
|
||||||
if (event.ctrlKey || event.metaKey) {
|
const left = this.#left ?? {};
|
||||||
console.log(
|
const right = this.#center ?? this.#right ?? {};
|
||||||
"[FixedLayout Debug] ⏸️ Ignoring wheel - browser zoom shortcut",
|
const { width: hostWidth, height: hostHeight } = this.getBoundingClientRect();
|
||||||
);
|
const portrait =
|
||||||
return;
|
this.spread !== "both" && this.spread !== "portrait" && hostHeight > hostWidth;
|
||||||
|
const target = this.#side === "left" ? left : right;
|
||||||
|
const blankWidth = left.width ?? right.width ?? 0;
|
||||||
|
const blankHeight = left.height ?? right.height ?? 0;
|
||||||
|
const scale = this.#transform.scale;
|
||||||
|
|
||||||
|
let contentWidth;
|
||||||
|
let contentHeight;
|
||||||
|
|
||||||
|
if (this.#center || portrait) {
|
||||||
|
const tw = (target.width ?? blankWidth) * scale;
|
||||||
|
const th = (target.height ?? blankHeight) * scale;
|
||||||
|
contentWidth = tw;
|
||||||
|
contentHeight = th;
|
||||||
|
} else {
|
||||||
|
const lw = (left.width ?? blankWidth) * scale;
|
||||||
|
const rw = (right.width ?? blankWidth) * scale;
|
||||||
|
const lh = (left.height ?? blankHeight) * scale;
|
||||||
|
const rh = (right.height ?? blankHeight) * scale;
|
||||||
|
contentWidth = lw + rw;
|
||||||
|
contentHeight = Math.max(lh, rh);
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
return { contentWidth, contentHeight };
|
||||||
|
}
|
||||||
|
|
||||||
const delta = event.deltaY;
|
#zoomByRatio(cx, cy, ratio) {
|
||||||
const rect = this.getBoundingClientRect();
|
const oldScale = this.#transform.scale;
|
||||||
|
|
||||||
// DETERMINE WHICH IFRAME SENT THE EVENT
|
|
||||||
const source = event.sourceIframe || "right"; // Default to right
|
|
||||||
const leftWidth = this.#left?.element?.offsetWidth || 0;
|
|
||||||
|
|
||||||
// ADJUST CURSOR POSITION BASED ON SOURCE IFRAME
|
|
||||||
const rawCursorX = event.clientX - rect.left;
|
|
||||||
const rawCursorY = event.clientY - rect.top;
|
|
||||||
|
|
||||||
// For both pages, calculate cursor position relative to the spread's viewport
|
|
||||||
// The cursor position is already relative to the container, not individual pages
|
|
||||||
const cursorX = rawCursorX;
|
|
||||||
const cursorY = rawCursorY;
|
|
||||||
// Current zoom level (default to fit-page if not set)
|
|
||||||
const oldScale =
|
|
||||||
this.#zoom === "fit-page" || this.#zoom === "fit-width"
|
|
||||||
? 1
|
|
||||||
: this.#zoom || 1;
|
|
||||||
console.log("[FixedLayout Debug] 📍 Cursor calculation:", {
|
|
||||||
source,
|
|
||||||
leftWidth,
|
|
||||||
rawCursorX,
|
|
||||||
cursorX,
|
|
||||||
rectLeft: rect.left,
|
|
||||||
clientX: event.clientX,
|
|
||||||
});
|
|
||||||
// Calculate new scale
|
|
||||||
const zoomFactor =
|
|
||||||
delta > 0 ? 1 - this.#zoomState.zoomStep : 1 + this.#zoomState.zoomStep;
|
|
||||||
const newScale = Math.min(
|
const newScale = Math.min(
|
||||||
this.#zoomState.maxScale,
|
this.#zoomState.maxScale,
|
||||||
Math.max(this.#zoomState.minScale, oldScale * zoomFactor),
|
Math.max(this.#zoomState.minScale, oldScale * ratio),
|
||||||
);
|
);
|
||||||
|
const actualRatio = newScale / oldScale;
|
||||||
|
|
||||||
const scaleChange = newScale / oldScale;
|
this.#transform.x = cx - actualRatio * (cx - this.#transform.x);
|
||||||
|
this.#transform.y = cy - actualRatio * (cy - this.#transform.y);
|
||||||
|
this.#transform.scale = newScale;
|
||||||
|
|
||||||
|
this.#zoom = newScale;
|
||||||
|
this.#updateFrameScales(newScale);
|
||||||
|
this.#applyTransform();
|
||||||
|
|
||||||
// Calculate new scroll position (same calculation for both pages)
|
|
||||||
const newScrollX =
|
|
||||||
(this.scrollLeft + this.dragOffset.x + cursorX) * scaleChange -
|
|
||||||
cursorX -
|
|
||||||
this.dragOffset.x;
|
|
||||||
const newScrollY =
|
|
||||||
(this.scrollTop + this.dragOffset.y + cursorY) * scaleChange -
|
|
||||||
cursorY -
|
|
||||||
this.dragOffset.y;
|
|
||||||
// Apply zoom
|
|
||||||
console.log("[FixedLayout Debug] 🔍 Applying zoom:", {
|
|
||||||
oldScale,
|
|
||||||
newScale,
|
|
||||||
cursorX,
|
|
||||||
cursorY,
|
|
||||||
dragOffsetX: this.dragOffset.x,
|
|
||||||
dragOffsetY: this.dragOffset.y,
|
|
||||||
scrollLeft: this.scrollLeft,
|
|
||||||
scrollTop: this.scrollTop,
|
|
||||||
newScrollX,
|
|
||||||
newScrollY,
|
|
||||||
source,
|
|
||||||
leftWidth,
|
|
||||||
});
|
|
||||||
// Apply zoom
|
|
||||||
this.setAttribute("zoom", newScale);
|
this.setAttribute("zoom", newScale);
|
||||||
this.scrollTo(newScrollX, newScrollY);
|
}
|
||||||
|
|
||||||
|
#handleWheel(event) {
|
||||||
|
if (this.hasAttribute("panel-mode")) return;
|
||||||
|
if (event.ctrlKey || event.metaKey) return;
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const rect = this.getBoundingClientRect();
|
||||||
|
const cx = event.clientX - rect.left;
|
||||||
|
const cy = event.clientY - rect.top;
|
||||||
|
|
||||||
|
const ratio =
|
||||||
|
event.deltaY > 0
|
||||||
|
? 1 - this.#zoomState.zoomStep
|
||||||
|
: 1 + this.#zoomState.zoomStep;
|
||||||
|
|
||||||
|
this.#zoomByRatio(cx, cy, ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
#handleMouseDown(event) {
|
#handleMouseDown(event) {
|
||||||
console.log("[FixedLayout Debug] 🖱️ Mouse down:", {
|
if (event.button !== 0) return;
|
||||||
button: event.button,
|
|
||||||
clientX: event.clientX,
|
|
||||||
clientY: event.clientY,
|
|
||||||
});
|
|
||||||
// Only left mouse button
|
|
||||||
if (event.button !== 0) {
|
|
||||||
console.log("[FixedLayout Debug] ⏸️ Ignoring - not left button");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store initial state
|
|
||||||
this.#dragState.startX = event.clientX;
|
this.#dragState.startX = event.clientX;
|
||||||
this.#dragState.startY = event.clientY;
|
this.#dragState.startY = event.clientY;
|
||||||
this.#dragState.scrollLeft = this.scrollLeft;
|
this.#dragState.startTX = this.#transform.x;
|
||||||
this.#dragState.scrollTop = this.scrollTop;
|
this.#dragState.startTY = this.#transform.y;
|
||||||
this.#dragState.isPotentialDrag = true;
|
this.#dragState.isDragging = true;
|
||||||
console.log(
|
this.style.cursor = "grabbing";
|
||||||
"[FixedLayout Debug] ⏱️ Potential drag started, timeout set for",
|
|
||||||
this.#dragState.pressDelay,
|
|
||||||
"ms",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Start timer to detect long press
|
|
||||||
this.#dragState.dragTimeout = setTimeout(() => {
|
|
||||||
// If we haven't moved much and haven't released yet, it's a drag
|
|
||||||
if (this.#dragState.isPotentialDrag) {
|
|
||||||
console.log("[FixedLayout Debug] ✋ DRAG MODE ACTIVATED (timeout)");
|
|
||||||
this.#dragState.isDragging = true;
|
|
||||||
this.#dragState.isPotentialDrag = false;
|
|
||||||
this.dragOffset.x = 0;
|
|
||||||
this.dragOffset.y = 0;
|
|
||||||
this.style.cursor = "grabbing";
|
|
||||||
}
|
|
||||||
}, this.#dragState.pressDelay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#handleMouseMove(event) {
|
#handleMouseMove(event) {
|
||||||
// Check if this is the start of a drag (moved during potential drag)
|
|
||||||
if (this.#dragState.isPotentialDrag) {
|
|
||||||
const dx = event.clientX - this.#dragState.startX;
|
|
||||||
const dy = event.clientY - this.#dragState.startY;
|
|
||||||
const moved = Math.sqrt(dx * dx + dy * dy);
|
|
||||||
|
|
||||||
// If moved more than 3 pixels, start dragging immediately
|
|
||||||
if (moved > 3) {
|
|
||||||
clearTimeout(this.#dragState.dragTimeout);
|
|
||||||
this.#dragState.isPotentialDrag = false;
|
|
||||||
this.#dragState.isDragging = true;
|
|
||||||
this.dragOffset.x = 0;
|
|
||||||
this.dragOffset.y = 0;
|
|
||||||
this.style.cursor = "grabbing";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle actual drag
|
|
||||||
if (!this.#dragState.isDragging) return;
|
if (!this.#dragState.isDragging) return;
|
||||||
|
|
||||||
const dx = event.clientX - this.#dragState.startX;
|
const dx = event.clientX - this.#dragState.startX;
|
||||||
const dy = event.clientY - this.#dragState.startY;
|
const dy = event.clientY - this.#dragState.startY;
|
||||||
if (this.#zoom === "fit-page") {
|
|
||||||
// Track how far user has dragged
|
|
||||||
this.dragOffset.x -= dx;
|
|
||||||
this.dragOffset.y -= dy;
|
|
||||||
this.#render();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
this.#zoom !== "fit-page" &&
|
|
||||||
this.#zoom !== "fit-width" &&
|
|
||||||
this.#zoom !== 1 &&
|
|
||||||
this.#dragState.draggable
|
|
||||||
) {
|
|
||||||
// Calculate new scroll position directly from mouse movement
|
|
||||||
const newScrollX = this.#dragState.scrollLeft - dx;
|
|
||||||
const newScrollY = this.#dragState.scrollTop - dy;
|
|
||||||
|
|
||||||
// Apply scroll (browser will automatically clamp to valid range)
|
this.#transform.x = this.#dragState.startTX + dx;
|
||||||
this.scrollLeft = newScrollX;
|
this.#transform.y = this.#dragState.startTY + dy;
|
||||||
this.scrollTop = newScrollY;
|
this.#applyTransform();
|
||||||
} else {
|
|
||||||
const reason = !this.#dragState.draggable
|
|
||||||
? "draggable is false"
|
|
||||||
: this.#zoom === "fit-page"
|
|
||||||
? "zoom is fit-page"
|
|
||||||
: this.#zoom === "fit-width"
|
|
||||||
? "zoom is fit-width"
|
|
||||||
: this.#zoom === 1
|
|
||||||
? "zoom is 1"
|
|
||||||
: "unknown";
|
|
||||||
console.log(`[FixedLayout Debug] ⛔ Drag ignored - ${reason}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#handleMouseUp(event) {
|
#handleMouseUp(event) {
|
||||||
console.log("[FixedLayout Debug] 🖱️ Mouse up:", {
|
if (!this.#dragState.isDragging) return;
|
||||||
wasDragging: this.#dragState.isDragging,
|
|
||||||
wasPotentialDrag: this.#dragState.isPotentialDrag,
|
|
||||||
hasTimeout: !!this.#dragState.dragTimeout,
|
|
||||||
isDraggingBeforeReset: this.#dragState.isDragging, // ADD THIS
|
|
||||||
});
|
|
||||||
// Clear any pending drag timeout
|
|
||||||
if (this.#dragState.dragTimeout) {
|
|
||||||
console.log("[FixedLayout Debug] ⏱️ Clearing drag timeout");
|
|
||||||
clearTimeout(this.#dragState.dragTimeout);
|
|
||||||
this.#dragState.dragTimeout = null;
|
|
||||||
}
|
|
||||||
// Reset drag state
|
|
||||||
const wasDragging = this.#dragState.isDragging;
|
|
||||||
this.#dragState.isDragging = false;
|
this.#dragState.isDragging = false;
|
||||||
this.#dragState.isPotentialDrag = false;
|
|
||||||
this.style.cursor = "";
|
this.style.cursor = "";
|
||||||
this.dragOffset.x = 0;
|
|
||||||
this.dragOffset.y = 0;
|
|
||||||
// NEW: Reset drag offset when drag completes
|
|
||||||
if (wasDragging) {
|
|
||||||
console.log("[FixedLayout Debug] ✅ Drag completed");
|
|
||||||
this.dragOffset.x = 0;
|
|
||||||
this.dragOffset.y = 0;
|
|
||||||
this.#render(); // Apply the reset
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async #createFrame({ index, src: srcOption }, frameId) {
|
async #createFrame({ index, src: srcOption }, frameId) {
|
||||||
@@ -436,12 +349,10 @@ export class FixedLayout extends HTMLElement {
|
|||||||
display: "none",
|
display: "none",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
});
|
});
|
||||||
// `allow-scripts` is needed for events because of WebKit bug
|
|
||||||
// https://bugs.webkit.org/show_bug.cgi?id=218086
|
|
||||||
iframe.setAttribute("sandbox", "allow-same-origin allow-scripts");
|
iframe.setAttribute("sandbox", "allow-same-origin allow-scripts");
|
||||||
iframe.setAttribute("scrolling", "no");
|
iframe.setAttribute("scrolling", "no");
|
||||||
iframe.setAttribute("part", "filter");
|
iframe.setAttribute("part", "filter");
|
||||||
this.#root.append(element);
|
this.#wrapper.append(element);
|
||||||
if (!src) return { blank: true, element, iframe, frameId };
|
if (!src) return { blank: true, element, iframe, frameId };
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
iframe.addEventListener(
|
iframe.addEventListener(
|
||||||
@@ -453,7 +364,6 @@ export class FixedLayout extends HTMLElement {
|
|||||||
);
|
);
|
||||||
const { width, height } = getViewport(doc, this.defaultViewport);
|
const { width, height } = getViewport(doc, this.defaultViewport);
|
||||||
|
|
||||||
// NEW: Attach event listeners INSIDE the iframe document
|
|
||||||
this.#attachEventListenersToIframe(doc, frameId, { element, iframe });
|
this.#attachEventListenersToIframe(doc, frameId, { element, iframe });
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
@@ -470,15 +380,19 @@ export class FixedLayout extends HTMLElement {
|
|||||||
iframe.src = src;
|
iframe.src = src;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#attachEventListenersToIframe(doc, frameId, frame) {
|
|
||||||
if (!doc) {
|
|
||||||
console.log("[FixedLayout Debug] ⏸️ No document to attach listeners");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
#attachEventListenersToIframe(doc, frameId, frame) {
|
||||||
"[FixedLayout Debug] 🔧 Attaching event listeners to iframe document",
|
const convertCoords = (e) => {
|
||||||
);
|
const iframeRect = frame.iframe.getBoundingClientRect();
|
||||||
|
const flRect = this.getBoundingClientRect();
|
||||||
|
const scaleX = iframeRect.width / (doc.documentElement.clientWidth || 1);
|
||||||
|
const scaleY = iframeRect.height / (doc.documentElement.clientHeight || 1);
|
||||||
|
return {
|
||||||
|
clientX: iframeRect.left - flRect.left + e.clientX * scaleX,
|
||||||
|
clientY: iframeRect.top - flRect.top + e.clientY * scaleY,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (!doc) return;
|
||||||
|
|
||||||
const images = doc.querySelectorAll("img");
|
const images = doc.querySelectorAll("img");
|
||||||
images.forEach((img) => {
|
images.forEach((img) => {
|
||||||
@@ -487,22 +401,18 @@ export class FixedLayout extends HTMLElement {
|
|||||||
img.style.webkitUserDrag = "none";
|
img.style.webkitUserDrag = "none";
|
||||||
img.style.WebkitUserDrag = "none";
|
img.style.WebkitUserDrag = "none";
|
||||||
});
|
});
|
||||||
console.log(
|
|
||||||
`[FixedLayout Debug] 🔒 Disabled native drag on ${images.length} images`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Attach wheel event listener for zoom to the iframe's document
|
|
||||||
doc.addEventListener(
|
doc.addEventListener(
|
||||||
"wheel",
|
"wheel",
|
||||||
(event) => {
|
(event) => {
|
||||||
// Re-target to FixedLayout element so handler can access scroll position
|
const { clientX, clientY } = convertCoords(event);
|
||||||
const fixedLayoutEvent = new WheelEvent("wheel", {
|
const fixedLayoutEvent = new WheelEvent("wheel", {
|
||||||
deltaX: event.deltaX,
|
deltaX: event.deltaX,
|
||||||
deltaY: event.deltaY,
|
deltaY: event.deltaY,
|
||||||
deltaZ: event.deltaZ,
|
deltaZ: event.deltaZ,
|
||||||
deltaMode: event.deltaMode,
|
deltaMode: event.deltaMode,
|
||||||
clientX: event.clientX,
|
clientX,
|
||||||
clientY: event.clientY,
|
clientY,
|
||||||
ctrlKey: event.ctrlKey,
|
ctrlKey: event.ctrlKey,
|
||||||
metaKey: event.metaKey,
|
metaKey: event.metaKey,
|
||||||
shiftKey: event.shiftKey,
|
shiftKey: event.shiftKey,
|
||||||
@@ -510,39 +420,21 @@ export class FixedLayout extends HTMLElement {
|
|||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TAG EVENT WITH SOURCE IFRAME
|
|
||||||
fixedLayoutEvent.sourceIframe = frameId;
|
fixedLayoutEvent.sourceIframe = frameId;
|
||||||
fixedLayoutEvent.sourceFrame = frame;
|
fixedLayoutEvent.sourceFrame = frame;
|
||||||
|
|
||||||
// Call our handler with the event
|
|
||||||
this.#handleWheel(fixedLayoutEvent);
|
this.#handleWheel(fixedLayoutEvent);
|
||||||
|
|
||||||
// Prevent default in iframe
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
},
|
},
|
||||||
{ passive: false },
|
{ passive: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[FixedLayout Debug] ✓ Wheel listener attached to iframe document",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Attach mouse drag support for pan to the iframe's document
|
|
||||||
doc.addEventListener("mousedown", (event) => {
|
doc.addEventListener("mousedown", (event) => {
|
||||||
console.log("[FixedLayout Debug] 🖱️ Mouse down inside iframe:", {
|
const { clientX, clientY } = convertCoords(event);
|
||||||
button: event.button,
|
|
||||||
clientX: event.clientX,
|
|
||||||
clientY: event.clientY,
|
|
||||||
target: event.target.tagName,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Re-target to FixedLayout element
|
|
||||||
const mouseEvent = new MouseEvent("mousedown", {
|
const mouseEvent = new MouseEvent("mousedown", {
|
||||||
button: event.button,
|
button: event.button,
|
||||||
clientX: event.clientX,
|
clientX,
|
||||||
clientY: event.clientY,
|
clientY,
|
||||||
ctrlKey: event.ctrlKey,
|
ctrlKey: event.ctrlKey,
|
||||||
metaKey: event.metaKey,
|
metaKey: event.metaKey,
|
||||||
shiftKey: event.shiftKey,
|
shiftKey: event.shiftKey,
|
||||||
@@ -550,8 +442,6 @@ export class FixedLayout extends HTMLElement {
|
|||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TAG EVENT WITH SOURCE IFRAME
|
|
||||||
mouseEvent.sourceIframe = frameId;
|
mouseEvent.sourceIframe = frameId;
|
||||||
mouseEvent.sourceFrame = frame;
|
mouseEvent.sourceFrame = frame;
|
||||||
this.#handleMouseDown(mouseEvent);
|
this.#handleMouseDown(mouseEvent);
|
||||||
@@ -559,10 +449,11 @@ export class FixedLayout extends HTMLElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
doc.addEventListener("mousemove", (event) => {
|
doc.addEventListener("mousemove", (event) => {
|
||||||
|
const { clientX, clientY } = convertCoords(event);
|
||||||
const mouseEvent = new MouseEvent("mousemove", {
|
const mouseEvent = new MouseEvent("mousemove", {
|
||||||
button: event.button,
|
button: event.button,
|
||||||
clientX: event.clientX,
|
clientX,
|
||||||
clientY: event.clientY,
|
clientY,
|
||||||
ctrlKey: event.ctrlKey,
|
ctrlKey: event.ctrlKey,
|
||||||
metaKey: event.metaKey,
|
metaKey: event.metaKey,
|
||||||
shiftKey: event.shiftKey,
|
shiftKey: event.shiftKey,
|
||||||
@@ -570,8 +461,6 @@ export class FixedLayout extends HTMLElement {
|
|||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TAG EVENT WITH SOURCE IFRAME
|
|
||||||
mouseEvent.sourceIframe = frameId;
|
mouseEvent.sourceIframe = frameId;
|
||||||
mouseEvent.sourceFrame = frame;
|
mouseEvent.sourceFrame = frame;
|
||||||
this.#handleMouseMove(mouseEvent);
|
this.#handleMouseMove(mouseEvent);
|
||||||
@@ -579,10 +468,11 @@ export class FixedLayout extends HTMLElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
doc.addEventListener("mouseup", (event) => {
|
doc.addEventListener("mouseup", (event) => {
|
||||||
|
const { clientX, clientY } = convertCoords(event);
|
||||||
const mouseEvent = new MouseEvent("mouseup", {
|
const mouseEvent = new MouseEvent("mouseup", {
|
||||||
button: event.button,
|
button: event.button,
|
||||||
clientX: event.clientX,
|
clientX,
|
||||||
clientY: event.clientY,
|
clientY,
|
||||||
ctrlKey: event.ctrlKey,
|
ctrlKey: event.ctrlKey,
|
||||||
metaKey: event.metaKey,
|
metaKey: event.metaKey,
|
||||||
shiftKey: event.shiftKey,
|
shiftKey: event.shiftKey,
|
||||||
@@ -590,18 +480,13 @@ export class FixedLayout extends HTMLElement {
|
|||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TAG EVENT WITH SOURCE IFRAME
|
|
||||||
mouseEvent.sourceIframe = frameId;
|
mouseEvent.sourceIframe = frameId;
|
||||||
mouseEvent.sourceFrame = frame;
|
mouseEvent.sourceFrame = frame;
|
||||||
this.#handleMouseUp(mouseEvent);
|
this.#handleMouseUp(mouseEvent);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[FixedLayout Debug] ✓ Mouse drag listeners attached to iframe document",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#render(side = this.#side) {
|
#render(side = this.#side) {
|
||||||
if (!side) return;
|
if (!side) return;
|
||||||
const left = this.#left ?? {};
|
const left = this.#left ?? {};
|
||||||
@@ -637,40 +522,19 @@ export class FixedLayout extends HTMLElement {
|
|||||||
),
|
),
|
||||||
)) || 1;
|
)) || 1;
|
||||||
|
|
||||||
const transform = (frame) => {
|
this.#transform.scale = scale;
|
||||||
let { element, iframe, width, height, blank, onZoom } = frame;
|
|
||||||
if (!iframe) return;
|
this.#updateFrameScales(scale);
|
||||||
if (onZoom) onZoom({ doc: frame.iframe.contentDocument, scale });
|
|
||||||
const iframeScale = onZoom ? scale : 1;
|
const { contentWidth, contentHeight } = this.#getContentSize();
|
||||||
Object.assign(iframe.style, {
|
this.#transform.x = (width - contentWidth) / 2;
|
||||||
width: `${width * iframeScale}px`,
|
this.#transform.y = (height - contentHeight) / 2;
|
||||||
height: `${height * iframeScale}px`,
|
|
||||||
transform: onZoom ? "none" : `scale(${scale})`,
|
this.#applyTransform();
|
||||||
transformOrigin: "top left",
|
|
||||||
display: blank ? "none" : "block",
|
|
||||||
});
|
|
||||||
Object.assign(element.style, {
|
|
||||||
width: `${(width ?? blankWidth) * scale}px`,
|
|
||||||
height: `${(height ?? blankHeight) * scale}px`,
|
|
||||||
overflow: "hidden",
|
|
||||||
display: "block",
|
|
||||||
flexShrink: "0",
|
|
||||||
marginBlock: "auto",
|
|
||||||
});
|
|
||||||
if (portrait && frame !== target) {
|
|
||||||
element.style.display = "none";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (this.#center) {
|
|
||||||
transform(this.#center);
|
|
||||||
} else {
|
|
||||||
transform(left);
|
|
||||||
transform(right);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async #showSpread({ left, right, center, side }) {
|
async #showSpread({ left, right, center, side }) {
|
||||||
this.#root.replaceChildren();
|
this.#wrapper.replaceChildren();
|
||||||
console.log("[FixedLayout Debug] 🧹 Old frame wrappers removed");
|
|
||||||
this.#left = null;
|
this.#left = null;
|
||||||
this.#right = null;
|
this.#right = null;
|
||||||
this.#center = null;
|
this.#center = null;
|
||||||
@@ -689,6 +553,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
this.#render();
|
this.#render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#goLeft() {
|
#goLeft() {
|
||||||
if (this.#center || this.#left?.blank) return;
|
if (this.#center || this.#left?.blank) return;
|
||||||
if (this.#portrait && this.#left?.element?.style?.display === "none") {
|
if (this.#portrait && this.#left?.element?.style?.display === "none") {
|
||||||
@@ -698,6 +563,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#goRight() {
|
#goRight() {
|
||||||
if (this.#center || this.#right?.blank) return;
|
if (this.#center || this.#right?.blank) return;
|
||||||
if (this.#portrait && this.#right?.element?.style?.display === "none") {
|
if (this.#portrait && this.#right?.element?.style?.display === "none") {
|
||||||
@@ -707,6 +573,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open(book) {
|
open(book) {
|
||||||
this.book = book;
|
this.book = book;
|
||||||
const { rendition } = book;
|
const { rendition } = book;
|
||||||
@@ -754,6 +621,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
[{}],
|
[{}],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get index() {
|
get index() {
|
||||||
const spread = this.#spreads[this.#index];
|
const spread = this.#spreads[this.#index];
|
||||||
const section =
|
const section =
|
||||||
@@ -763,6 +631,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
: (spread.right ?? spread.left));
|
: (spread.right ?? spread.left));
|
||||||
return this.book.sections.indexOf(section);
|
return this.book.sections.indexOf(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
#reportLocation(reason) {
|
#reportLocation(reason) {
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent("relocate", {
|
new CustomEvent("relocate", {
|
||||||
@@ -776,6 +645,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSpreadOf(section) {
|
getSpreadOf(section) {
|
||||||
const spreads = this.#spreads;
|
const spreads = this.#spreads;
|
||||||
for (let index = 0; index < spreads.length; index++) {
|
for (let index = 0; index < spreads.length; index++) {
|
||||||
@@ -785,6 +655,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
if (center === section) return { index, side: "center" };
|
if (center === section) return { index, side: "center" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async goToSpread(index, side, reason) {
|
async goToSpread(index, side, reason) {
|
||||||
if (index < 0 || index > this.#spreads.length - 1) return;
|
if (index < 0 || index > this.#spreads.length - 1) return;
|
||||||
if (index === this.#index) {
|
if (index === this.#index) {
|
||||||
@@ -808,10 +679,11 @@ export class FixedLayout extends HTMLElement {
|
|||||||
}
|
}
|
||||||
this.#reportLocation(reason);
|
this.#reportLocation(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
async select(target) {
|
async select(target) {
|
||||||
await this.goTo(target);
|
await this.goTo(target);
|
||||||
// TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async goTo(target) {
|
async goTo(target) {
|
||||||
const { book } = this;
|
const { book } = this;
|
||||||
const resolved = await target;
|
const resolved = await target;
|
||||||
@@ -820,6 +692,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
const { index, side } = this.getSpreadOf(section);
|
const { index, side } = this.getSpreadOf(section);
|
||||||
await this.goToSpread(index, side);
|
await this.goToSpread(index, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
async next() {
|
async next() {
|
||||||
const s = this.rtl ? this.#goLeft() : this.#goRight();
|
const s = this.rtl ? this.#goLeft() : this.#goRight();
|
||||||
if (!s)
|
if (!s)
|
||||||
@@ -829,6 +702,7 @@ export class FixedLayout extends HTMLElement {
|
|||||||
"page",
|
"page",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async prev() {
|
async prev() {
|
||||||
const s = this.rtl ? this.#goRight() : this.#goLeft();
|
const s = this.rtl ? this.#goRight() : this.#goLeft();
|
||||||
if (!s)
|
if (!s)
|
||||||
@@ -838,12 +712,13 @@ export class FixedLayout extends HTMLElement {
|
|||||||
"page",
|
"page",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getContents() {
|
getContents() {
|
||||||
return Array.from(this.#root.querySelectorAll("iframe"), (frame) => ({
|
return Array.from(this.#root.querySelectorAll("iframe"), (frame) => ({
|
||||||
doc: frame.contentDocument,
|
doc: frame.contentDocument,
|
||||||
// TODO: index, overlayer
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.#observer.unobserve(this);
|
this.#observer.unobserve(this);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user