Smart PDF pan/select + reactive spread toggle

- In select mode, only defer to the text/annotation layer when the
  pointer is actually on text or a link; otherwise start panning.
  Lets PDF pages be dragged on blank space while keeping text
  selection. Shift and explicit 'pan' mode still force-pan.
- Make the 'spread' attribute reactive: extract #computeSpreads(),
  add #setSpread() which re-groups while preserving the current page.
This commit is contained in:
2026-08-13 12:37:13 -04:00
parent d016b27752
commit d9b1d5d18f
+32 -7
View File
@@ -23,7 +23,7 @@ const getViewport = (doc, viewport) => {
}; };
export class FixedLayout extends HTMLElement { export class FixedLayout extends HTMLElement {
static observedAttributes = ["zoom", "interaction-mode"]; static observedAttributes = ["zoom", "interaction-mode", "spread"];
#root = this.attachShadow({ mode: "closed" }); #root = this.attachShadow({ mode: "closed" });
#observer = new ResizeObserver(() => this.#onResize()); #observer = new ResizeObserver(() => this.#onResize());
#spreads; #spreads;
@@ -121,6 +121,10 @@ export class FixedLayout extends HTMLElement {
} }
break; break;
} }
case "spread": {
this.#setSpread(value == null ? undefined : value);
break;
}
} }
} }
@@ -492,8 +496,14 @@ export class FixedLayout extends HTMLElement {
#handleMouseDown(event) { #handleMouseDown(event) {
if (event.button !== 0) return; if (event.button !== 0) return;
if (this.#isPDF && this.#interactionMode === "select" && !event.shiftKey) if (this.#isPDF && this.#interactionMode === "select" && !event.shiftKey) {
return false; const t = event.realTarget;
if (
t?.closest?.(".textLayer span") ||
t?.closest?.(".annotationLayer a")
)
return false;
}
this.#dragState.startX = event.clientX; this.#dragState.startX = event.clientX;
this.#dragState.startY = event.clientY; this.#dragState.startY = event.clientY;
@@ -643,6 +653,7 @@ export class FixedLayout extends HTMLElement {
}); });
mouseEvent.sourceIframe = frameId; mouseEvent.sourceIframe = frameId;
mouseEvent.sourceFrame = frame; mouseEvent.sourceFrame = frame;
mouseEvent.realTarget = event.target;
const dragStarted = this.#handleMouseDown(mouseEvent); const dragStarted = this.#handleMouseDown(mouseEvent);
if (dragStarted) event.preventDefault(); if (dragStarted) event.preventDefault();
}); });
@@ -831,11 +842,15 @@ export class FixedLayout extends HTMLElement {
this.spread = rendition?.spread; this.spread = rendition?.spread;
this.defaultViewport = rendition?.viewport; this.defaultViewport = rendition?.viewport;
const rtl = book.dir === "rtl"; this.rtl = book.dir === "rtl";
const ltr = !rtl; this.#computeSpreads();
this.rtl = rtl; }
if (rendition?.spread === "none") #computeSpreads() {
const { book } = this;
const rtl = this.rtl;
const ltr = !rtl;
if (this.spread === "none")
this.#spreads = book.sections.map((section) => ({ center: section })); this.#spreads = book.sections.map((section) => ({ center: section }));
else else
this.#spreads = book.sections.reduce( this.#spreads = book.sections.reduce(
@@ -873,6 +888,16 @@ export class FixedLayout extends HTMLElement {
); );
} }
#setSpread(value) {
this.spread = value;
const currentSection =
this.book?.sections[this.index] ?? this.book?.sections[0];
this.#computeSpreads();
const resolved = currentSection ? this.getSpreadOf(currentSection) : null;
if (resolved) this.goToSpread(resolved.index, resolved.side, "page");
else this.#render();
}
get index() { get index() {
const spread = this.#spreads[this.#index]; const spread = this.#spreads[this.#index];
const section = const section =