On the web, double-clicking a word and then dragging to extend the native selection also turned the page. The first click's deferred single-click timer fires 250ms later while the second click's button is still held during the drag, so it posts iframe-single-click and flips the page. A plain double-click escapes this because its fast second click updates lastClickTime in time. Track the mouse-button state in iframeEventHandlers and suppress the deferred single click while the button is held (a drag is in progress). A normal single click is unaffected: its button is already released by the time the deferred timer fires. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2.5 KiB
name, description, metadata
| name | description | metadata | ||||||
|---|---|---|---|---|---|---|---|---|
| dblclick-drag-pageturn-4524 | Web double-click-and-drag selection turned the page; deferred single-click fired mid-drag while button held |
|
#4524: on Readest Web, double-click a word then drag to extend the native selection also turned the page (a plain double-click did not). The user expects browser-native double-click-drag word-by-word selection without a page turn.
Root cause (src/app/reader/utils/iframeEventHandlers.ts handleClick):
the first click of a potential double-click schedules a deferred
postSingleClick() after DOUBLE_CLICK_INTERVAL_THRESHOLD_MS (250ms).
- Plain double-click: the 2nd
clickfires fast, updateslastClickTime, postsiframe-double-click; when the 1st click's timer fires, theDate.now() - lastClickTime >= 250check is now false → single-click suppressed → no page turn. - Double-click + drag: the user holds the button down on the 2nd click and
drags, so the 2nd
mouseup/clickis delayed past 250ms. At first-click+250mslastClickTimeis still the 1st click → check passes →iframe-single-clickposted while the button is still held →usePagination.handlePageFlipturns the page.
Fix: module-level isMouseDown flag (set in handleMousedown, cleared in
handleMouseup); the deferred postSingleClick() returns early when
isMouseDown is true (a drag is in progress). Cannot affect a normal single
click — isMouseDown is false by the time its deferred timer fires; only a
held button (drag) suppresses it.
Verification gotcha: reproduced live by dispatching synthetic
mousedown/mouseup/click to the reading iframe doc (found via deep shadow-DOM
walk; the foliate iframe sits in nested shadow roots, document.querySelectorAll('iframe')
returns 0). Watch iframe-single-click on window 'message' + the
.progress-info-label "N / M" page text. NOTE: back-to-back synthetic gestures
share the module's real setTimeout deferrals and lastClickTime, so a
follow-up "normal single click" repro can spuriously show no single-click — the
vitest unit test (fake timers) is the authoritative regression check, not
chained browser repros. Iframe listeners are attached once
(detail.doc.isEventListenersAdded), so a full page reload is required to pick
up edits — Fast Refresh won't re-bind them.
Test: src/__tests__/reader/utils/iframeEventHandlers.test.ts. Related:
foliate-touch-listener-capture-phase, progressbar-focus-ring-4397.