fix(selection): collapse idle overlay so page touch drag gestures are not stolen - #2103
fix(selection): collapse idle overlay so page touch drag gestures are not stolen#2103guangzan wants to merge 1 commit into
Conversation
… not stolen A persistent full-viewport fixed inset-0 overlay layer makes Chrome claim horizontal touch pans on pages using `touch-action: manipulation`, firing pointercancel and breaking touch drag gestures (e.g. carousels) outside the toolbar. Collapse the overlay root to 0x0 while the toolbar is idle and restore the full-viewport layer only while the toolbar is visible.
🦋 Changeset detectedLatest commit: 1b9f4fc The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor trust score23/100 — New contributor This score estimates contributor familiarity with Outcome
Score breakdown
Signals used
Policy
Updated automatically when the PR changes or when a maintainer reruns the workflow. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1b9f4fc530
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| isSelectionToolbarVisible | ||
| ? `pointer-events-none fixed inset-0 ${SELECTION_CONTENT_OVERLAY_LAYERS.selectionOverlay}` | ||
| : "pointer-events-none fixed h-0 w-0", |
There was a problem hiding this comment.
Gate the expanded overlay on rendered toolbar state
If the toolbar is visible and then the selection toolbar is disabled, the site becomes disabled, or all toolbar features are turned off via synced config, isSelectionToolbarVisible can remain true while the child toolbar is no longer rendered by the condition below. In that state this branch still keeps a full-viewport fixed overlay with no visible toolbar to interact with, so the touch-drag stealing this patch is meant to avoid can persist until a later mouse-driven clear; collapse the root whenever the toolbar content cannot render, or clear the visible atom when those booleans become false.
Useful? React with 👍 / 👎.
Description
On mobile-emulated/touch devices, page-level touch drag gestures (e.g. carousels built with Embla/swiper) became impossible to drag when the extension was installed. This PR fixes the selection toolbar's overlay root so it no longer steals horizontal touch pans from the page.
Problem
Reproduction (Chrome DevTools mobile emulation, touch enabled):
https://ui.shadcn.com/docs/components/base/carouselbody { touch-action: manipulation }via DevTools<read-frog-selection>(selection content script)Result: the carousel cannot be dragged at all — the browser fires
pointercancelshortly after the first fewpointermoveevents, aborting the JS-driven drag.Either of these makes the carousel work again:
touch-action: manipulationfrombody<style>inside<read-frog-selection>Root Cause
The selection toolbar renders a persistent full-viewport overlay root:
This layer is mounted unconditionally, even while idle (no selection, no toolbar visible). It is
pointer-events: none, so it does not intercept clicks — butpointer-eventsdoes not remove an element from the browser's touch gesture arbitration:touch-action: manipulationonbody(=pan-x pan-y+ no double-tap zoom) makes the compositor decide gesture ownership immediately, without the double-tap delay window.fixedlayer (its owntouch-action: auto) sitting above the carousel, Chrome's gesture arbitration re-computes the allowed touch behaviors for the touch point. The carousel container'stouch-action: pan-ycontract (reserve horizontal pans for the script) is no longer honored — Chrome claims the horizontal pan as page scrolling and firespointercancelat the carousel, terminating its pointer-based drag.Removing the host's
<style>"fixes" it only because that<style>carries the entry CSS (WXT:hostreset + Tailwind); without it the overlay'sfixed inset-0/ z-index classes never apply, the layer collapses to a small static box and stops covering the viewport.Fix
Collapse the overlay root to
0x0(fixed h-0 w-0) while the toolbar is idle, and restore the full-viewportfixed inset-0layer only while the toolbar is visible (isSelectionToolbarVisible):getViewportRect()readswindow.visualViewportandviewportPointToHostPoint()only runs while the toolbar is visible (at which point the container has already been restored to full viewport).Evidence
Verified with the built extension in real Chrome (mobile emulation + CDP touch swipe on the shadcn carousel,
body { touch-action: manipulation }set), countingpointercancelevents per swipe:Also verified post-fix that selecting text still expands the overlay root back to
inset-0and the toolbar appears at the correct position with no React errors.Surgical isolation performed during debugging (for context, all with extension + manipulation):
pointercancel(culprit confirmed)pointercancel(chosen fix)touch-action: none/pan-yon the overlay → still canceled (so a CSS touch-action override does not work; geometry is what matters)Changes
src/entrypoints/selection.content/selection-toolbar/index.tsx: collapse/expand the overlay root based onisSelectionToolbarVisiblesrc/entrypoints/selection.content/selection-toolbar/__tests__/selection-toolbar.test.tsx: regression test assertingh-0 w-0while idle andinset-0while visible.changeset/selection-overlay-touch-gesture.md: patch changeset for@read-frog/extensionTest Plan
pnpm vitest run src/entrypoints/selection.content— 218/218 passedSKIP_FREE_API=trueper repo convention)