Repository navigation
fix(native): don't make every container a Pressable - #482
Open
YevheniiKotyrlo wants to merge 2 commits into
Open
YevheniiKotyrlo wants to merge 2 commits into
YevheniiKotyrlo wants to merge 2 commits into
Conversation
…king it pressable Registering a container — a size container from `container-name`, or a group a descendant selector names — wired `activeFamily` on the element, and that family's presence is what adds `onPress` and turns a `View` into a `Pressable`. So every container became a touch target: it claimed the JS responder, Android's `JSResponderHandler` then intercepted the touch and cancelled the native gestures of everything beneath it, and the element was announced as clickable and focusable. A container now tracks its `:active` in `containerActiveFamily`, which its queries read and which never adds press handlers. A container already pressed for its own reasons answers through that press; any other observes the touch events its subtree receives, which reach every ancestor without a responder claim.
…ends A container followed one boolean, so with two fingers inside a group, lifting one ended its `:active` while the other still rested there. Each container now follows the touches that went down inside it, and is active until the last of them ends. The touches still on screen prune that set on every event, so a touch that went down elsewhere never keeps it active, and a touch whose end never arrived — its target left the screen first — is dropped at the next touch event.
Contributor
Author
YevheniiKotyrlo
marked this pull request as ready for review
October 13, 2026 09:26
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Problem
Every element that declares a container — a
container-namebox, and everygroupa descendant selector names — renders as aPressable, whatever the selector asks of it.updateRuleswires the press family on each one:getStyledPropsanswersactiveFamilywithonPress/onPressIn/onPressOut, anduseNativeCssswaps aViewthat has anonPressfor aPressable. So the container:JSResponderHandlerthen intercepts the rest of the touch, so a nativeScrollViewbeneath it receivesACTION_CANCEL;Pressabledefaultsaccessibleandfocusabletotrue, so a bare<View className="group/item">dumps asclickable="true" focusable="true"and is a keyboard focus stop.Any class that registers a container triggers it —
@container/name,group,group/name— including a group that is only ever asked about an attribute (group-data-[state=on]:).Solution
A container's
:active, as its queries read it, gets its own family,containerActiveFamily, and nothing adds press handlers for it.onTouchStart/onTouchEnd/onTouchCancel. They bubble to every ancestor without a responder claim, so nothing beneath it loses a touch, and a native scroller that takes the gesture ends the state through itstouchcancel. It follows the touches that went down inside it and is active until the last of them ends; the touches still on screen prune that set on every event.onPressanswers through that press, as before, so its ownactive:and its descendants'group-active:change together.testContainerPseudoConditionreadscontainerActiveFamily.An element's own
:activerule still makes it aPressable. What changes for a plain group is that pressing a control inside it now activates the group, as a browser does::activematches the flat-tree ancestors of the element being activated. Before, the control won the responder and the group never saw the press.Tests
src/__tests__/native/container-activity.test.tsxrenders containers and reads the host element'sonStartShouldSetResponder— the prop aPressableadds to take part in responder negotiation — then fires the touch events a container now answers from, carrying thetouches/changedTouchesReact Native delivers.grouping.test.tsx's two:activecases fire touches instead of presses.On
main, 8 of those 13 cases fail: all three containers carryonStartShouldSetResponder, and no touch reaches a group's:active. The cases that pass onmainare the ones that must not change: a group with its ownonPress, and a touch that went down elsewhere, or whose end never arrived, keeping nothing active.Verification
On Ubuntu with the
.nvmrcNode, every pull-request job but the macOS build:yarn build·yarn lint0 ·yarn typecheck0 ·yarn test --maxWorkers=2 --coverage1344 passed ·node --test node/__tests__/*.mjs42 passed ·yarn example expo export --platform web· no unstaged files after either build ·commitlintclean. I did not run the iOS dev build.On an Android 16 emulator, the example app's
ScrollViewinside an@containerview does not move under a drag onmainand scrolls with this PR, landing on the same row as the same list inside a plain view; the pair is in this comment.No existing issue tracks this — searched the tracker for
group pressable,container pressable,group onPress,group clickable,group-active,container scroll,group scroll,responder,touch cancelandPressable container, with no relevant hits.Known limits
A touch whose view leaves the screen before the finger lifts never reaches JS — Fabric drops the event — so the containers it went down in stay active until the next touch reaches them.
Related
:activeancestor against the nearest container of any kind, which is why every container observes touches rather than only groups.pressablestate this change keeps from flipping on a container.Base
Cut from
a5002c5(main); it merges cleanly.