fix(reader): turn automatically when highlighting across pages (#4487)
* fix(reader): turn automatically when highlighting across pages (closes #1354) * refact: Refactor time retrieval to use Date.now() * fix(reader): rework auto page-turn as a corner-dwell gesture Rework the initial #1354 implementation into a deliberate corner-dwell gesture that works across platforms, and fix popup positioning for cross-page selections. Trigger: - While a text selection is active, hold any engagement signal — the pointer (web/desktop/iOS), the Android native touchmove, or the selection caret — inside a screen corner for 500ms to turn one page: bottom-right goes to the next page, top-left to the previous. - One turn per engagement: a signal must leave the corner and return to turn another page, so the user controls it one page at a time. - The corner is a quarter-ellipse of radius 15% of each axis, measured against the reading frame (the <foliate-view> rect) inset by the page content margins, so the zone lands on the text — not the margin/footer or a sidebar — and the pointer can actually reach it. Per-platform signals: - web/desktop/iOS: the iframe pointermove, mapped to window coordinates via the iframe element's on-screen rect. - Android: the selection caret (the only signal during a native handle drag, where the handles live in a separate window so their touches never reach the Activity) plus a throttled (~10/s) native touchmove added in MainActivity.dispatchTouchEvent for content drags. Android scroll-pin (#873): an active selection pins the container scroll, which reverted the turn; suspend the pin during the turn and re-anchor it to the page we land on. Popup positioning: getPosition decided which selection end was on-screen using window bounds, so a cross-page selection's off-screen start (which maps behind the sidebar but inside the window) read "in view" and pinned the popup off the visible page. Test visibility against the reading frame instead, and for a multi-page selection anchor to the last on-screen line. Also: logical view.prev()/next() (RTL-correct); skip in scrolled mode; pass contentInsets down to the annotator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Huang Xin <chrox.huang@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -292,9 +292,38 @@ export const getPosition = (
|
||||
),
|
||||
dir: 'down',
|
||||
} as Position;
|
||||
const startInView = pointIsInView(start.point);
|
||||
const endInView = pointIsInView(end.point);
|
||||
if (!startInView && !endInView) return { point: { x: 0, y: 0 } };
|
||||
// Decide which selection end is on-screen by testing its UNCLAMPED line-rect
|
||||
// midpoint against the READING FRAME (`rect`) — not the window. A cross-page
|
||||
// selection's off-screen start maps to a negative/sidebar x that is still
|
||||
// inside the window, so a window-based check would wrongly read it "in view"
|
||||
// and pin the popup off the visible page (#1354).
|
||||
const midX = (r: Rect) => (r.left + r.right) / 2;
|
||||
const midY = (r: Rect) => (r.top + r.bottom) / 2;
|
||||
const inFrame = (px: number, py: number) =>
|
||||
px > rect.left && px < rect.right && py > rect.top && py < rect.bottom;
|
||||
const startInView = inFrame(midX(first), midY(first));
|
||||
const endInView = inFrame(midX(last), midY(last));
|
||||
if (!startInView && !endInView) {
|
||||
// Multi-page selection: both ends are off the visible page, but the middle
|
||||
// may cross it. Anchor to the last on-screen line so the popup tracks the
|
||||
// visible part of the selection.
|
||||
const v = rects
|
||||
.map((r) => frameRect(frame, r, sx, sy))
|
||||
.filter((r) => inFrame(midX(r), midY(r)))
|
||||
.at(-1);
|
||||
if (v) {
|
||||
return {
|
||||
point: constrainPointWithinRect(
|
||||
{ x: midX(v) - rect.left, y: v.bottom - rect.top + 6 },
|
||||
rect,
|
||||
paddingPx,
|
||||
),
|
||||
dir: 'down',
|
||||
} as Position;
|
||||
}
|
||||
// Otherwise fall through and anchor to an end so the popup still shows
|
||||
// (the constrained points are always within the frame, never {0,0}).
|
||||
}
|
||||
if (!startInView) return end;
|
||||
if (!endInView) return start;
|
||||
return start.point.y > window.innerHeight - end.point.y ? start : end;
|
||||
|
||||
Reference in New Issue
Block a user