fix: Stop text selection jumping near page tops
Some checks are pending
Build / build (push) Waiting to run

When you held a finger on the text and dragged a selection handle near
the top of a page in a paginated book, the page could slide sideways
and the highlighted words kept flashing between different spots. The
phone's built-in web view tried to scroll the page to follow the
finger, the reader refused that scroll, and the two kept fighting over
where the page should sit.

This change made the reader remember where the page was when the
selection started and put it back whenever the web view tried to move
it sideways, until the selection was cleared. Books read in scrolling
mode and books with vertical text were left alone, because following
the finger is the expected behaviour there.
This commit is contained in:
Chmouel Boudjnah 2026-10-06 15:45:34 +02:00
parent 9f98d75ab9
commit 5aa45ee299

View file

@ -5,7 +5,14 @@ import org.readium.r2.shared.ExperimentalReadiumApi
import kotlin.coroutines.cancellation.CancellationException
/**
* Keeps the start handle of a selection on the word that was selected.
* Keeps selection handles on the passage being selected.
*
* Near the top of a paginated column, Chromium tries to auto-scroll the
* document horizontally while a handle is dragged. Readium refuses native
* scrolling during selection, so the browser and the view repeatedly
* disagree about the offset and the range flashes between different words.
* Keep the document on its selected page too, until the selection clears.
* Scrolled and vertical-text books retain their normal selection scrolling.
*
* Long-pressing the *first* word of a paragraph selects it correctly, but
* the WebView paints the start handle on the paragraph's last automatic
@ -85,6 +92,31 @@ internal object SelectionHandleFix {
const val SCRIPT: String = """
(function () {
var state = window.__liseurLead || (window.__liseurLead = {});
if (!state.scrollGuardInstalled) {
state.scrollGuardInstalled = true;
state.selectionX = null;
document.addEventListener("selectionchange", function () {
var selection = window.getSelection();
if (!selection || selection.isCollapsed) {
state.selectionX = null;
return;
}
var root = document.documentElement;
var view = root.style.getPropertyValue("--USER__view").trim();
var scroll = root.style.getPropertyValue("--USER__scroll").trim();
if (view === "readium-scroll-on" || scroll === "readium-scroll-on" ||
getComputedStyle(root).writingMode.indexOf("vertical") === 0) {
state.selectionX = null;
return;
}
if (state.selectionX === null) state.selectionX = window.scrollX;
}, true);
window.addEventListener("scroll", function () {
if (state.selectionX !== null && window.scrollX !== state.selectionX) {
document.scrollingElement.scrollLeft = state.selectionX;
}
});
}
var installed = false;
var tok = state.token ||
(state.token = "l" + Math.random().toString(36).slice(2, 10));