@@ -276,64 +276,23 @@ function PressableDisabled() {
276276 ) ;
277277}
278278
279- /**
280- * Repro for: Pressable does not consume native touches on iOS/iPadOS.
281- *
282- * Topology: NativeTouchReceiver (UIView with touchesEnded:) is the PARENT.
283- * Pressable is the CHILD and the UIKit hit-tested target.
284- *
285- * Bug: tapping the Pressable makes it the JS responder (onPress fires), but
286- * the touch also bubbles up the UIKit responder chain to NativeTouchReceiver
287- * because RCTSurfaceTouchHandler has cancelsTouchesInView=NO — so
288- * touchesEnded: is never cancelled on the ancestor, and onNativeTouch fires too.
289- *
290- * Expected after preventNativePropagation fix:
291- * preventNativePropagation={false} → both onPress AND onNativeTouch fire (bug)
292- * preventNativePropagation={true} → only onPress fires (fixed)
293- */
294- function PressableBlockNativeResponderExample ( ) {
279+ function PressablePreventNativePropagationExample ( ) {
295280 const [ log , setLog ] = useState < Array < string >> ( [ ] ) ;
296281
297- function emit ( msg : string ) {
298- setLog ( prev => [ msg , ...prev ] . slice ( 0 , 10 ) ) ;
299- }
300-
301282 return (
302283 < View >
303- < Text style = { blockNativeStyles . description } >
304- Tap the Pressable button inside each row.{ '\n\n' }
305- < Text style = { blockNativeStyles . bold } > [default]</ Text >
306- { ' — BUG: both Pressable.onPress (JS) and NativeTouchReceiver.onNativeTouch' }
307- { ' (UIKit responder chain) fire for the same tap.\n' }
308- < Text style = { blockNativeStyles . bold } > [blocked]</ Text >
309- { ' — FIXED: only Pressable.onPress fires.' }
310- </ Text >
311-
312- < View style = { blockNativeStyles . logBox } >
313- { log . length === 0 ? (
314- < Text style = { blockNativeStyles . logPlaceholder } >
315- events will appear here
316- </ Text >
317- ) : (
318- log . map ( ( line , i ) => (
319- < Text key = { i } style = { blockNativeStyles . logLine } >
320- { line }
321- </ Text >
322- ) )
323- ) }
324- </ View >
325-
326284 < Text style = { blockNativeStyles . sectionHeader } >
327- { 'Default ( preventNativePropagation={false})' }
285+ { 'preventNativePropagation={false} (default )' }
328286 </ Text >
329287 < RNTNativeTouchReceiver
330288 style = { blockNativeStyles . receiver }
331289 onNativeTouch = { ( ) =>
332- emit ( '[default] NativeTouchReceiver.touchesEnded: ← native leaked' )
290+ setLog ( prev => [ ... prev , ' NativeTouchReceiver.onNativeTouch' ] )
333291 } >
334292 < Pressable
335293 style = { blockNativeStyles . pressable }
336- onPress = { ( ) => emit ( '[default] Pressable.onPress ✓' ) } >
294+ onPressIn = { ( ) => setLog ( [ ] ) }
295+ onPress = { ( ) => setLog ( prev => [ ...prev , 'Pressable.onPress' ] ) } >
337296 < Text style = { blockNativeStyles . pressableText } > Tap me</ Text >
338297 </ Pressable >
339298 </ RNTNativeTouchReceiver >
@@ -344,31 +303,39 @@ function PressableBlockNativeResponderExample() {
344303 < RNTNativeTouchReceiver
345304 style = { blockNativeStyles . receiver }
346305 onNativeTouch = { ( ) =>
347- emit (
348- '[blocked] NativeTouchReceiver.touchesEnded: ← UNEXPECTED, fix not working' ,
349- )
306+ setLog ( prev => [ ...prev , 'NativeTouchReceiver.onNativeTouch' ] )
350307 } >
351308 < Pressable
352309 style = { blockNativeStyles . pressable }
353310 preventNativePropagation = { true }
354- onPress = { ( ) => emit ( '[blocked] Pressable.onPress ✓' ) } >
311+ onPressIn = { ( ) => setLog ( [ ] ) }
312+ onPress = { ( ) => setLog ( prev => [ ...prev , 'Pressable.onPress' ] ) } >
355313 < Text style = { blockNativeStyles . pressableText } > Tap me</ Text >
356314 </ Pressable >
357315 </ RNTNativeTouchReceiver >
316+
317+ < LogBox lines = { log } />
318+ </ View >
319+ ) ;
320+ }
321+
322+ function LogBox ( { lines} : { lines : Array < string > } ) {
323+ return (
324+ < View style = { blockNativeStyles . logBox } >
325+ { lines . length === 0 ? (
326+ < Text style = { blockNativeStyles . logPlaceholder } > tap to see events</ Text >
327+ ) : (
328+ lines . map ( ( line , i ) => (
329+ < Text key = { i } style = { blockNativeStyles . logLine } >
330+ { line }
331+ </ Text >
332+ ) )
333+ ) }
358334 </ View >
359335 ) ;
360336}
361337
362338const blockNativeStyles = StyleSheet . create ( {
363- description : {
364- fontSize : 13 ,
365- color : '#333' ,
366- marginBottom : 10 ,
367- lineHeight : 19 ,
368- } ,
369- bold : {
370- fontWeight : '700' ,
371- } ,
372339 logBox : {
373340 backgroundColor : '#1a1a1a' ,
374341 borderRadius : 6 ,
@@ -803,19 +770,13 @@ const examples = [
803770 } ,
804771 } ,
805772 {
806- title : 'preventNativePropagation — press leaks to native parent (iOS repro) ' ,
807- name : 'block -native-responder ' ,
773+ title : 'preventNativePropagation ',
774+ name : 'prevent - native - propagation ',
808775 description :
809- ( 'Repro for: Pressable does not consume the native touch on iOS/iPadOS. ' +
810- 'A Pressable sits inside a NativeTouchReceiver (plain UIView with ' +
811- 'touchesEnded: overridden). Tapping the Pressable makes it the JS ' +
812- 'responder (onPress fires), but the touch also bubbles up the UIKit ' +
813- 'responder chain so the parent NativeTouchReceiver fires too. ' +
814- 'With preventNativePropagation={true} and the fix applied, only ' +
815- 'Pressable.onPress should fire.' ) as string ,
776+ ' Pressable inside a native UIView parent . Without preventNativePropagation the touch leaks up the UIKit responder chain to the parent . ' as string,
816777 platform : ' ios ',
817778 render : function ( ) : React . Node {
818- return < PressableBlockNativeResponderExample /> ;
779+ return < PressablePreventNativePropagationExample /> ;
819780 } ,
820781 } ,
821782 ...PressableExampleFbInternal . examples ,
0 commit comments