Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ package com.facebook.react.views.view

import android.view.View
import com.facebook.react.R
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.uimanager.PointerEvents
import com.facebook.react.uimanager.style.Overflow

/**
* Helper class for managing the important_for_interaction view tag. This tag determines how a view
Expand All @@ -28,6 +30,8 @@ internal object ImportantForInteractionHelper {
/** CSS pointer-events: auto heuristics. */
const val IMPORTANT_FOR_INTERACTION_AUTO_CSSPOINTEREVENTSAUTO: Int = 0x8

const val IMPORTANT_FOR_INTERACTION_DONT_CLIP_DESCENDANTS: Int = 0x10

/**
* Sets the important_for_interaction tag on a view based on the given [PointerEvents] value.
*
Expand All @@ -37,9 +41,11 @@ internal object ImportantForInteractionHelper {
*
* @param view The view to set the tag on
* @param pointerEvents The pointer events value to convert and set
* @param overflow The overflow value; descendants are not clipped when it is [Overflow.VISIBLE]
*/
@JvmStatic
fun setImportantForInteraction(view: View, pointerEvents: PointerEvents) {
fun setImportantForInteraction(view: View, pointerEvents: PointerEvents, overflow: Overflow) {
if (!ReactNativeFeatureFlags.syncAndroidClipBoundsWithOverflow()) return
val value =
when (pointerEvents) {
PointerEvents.AUTO -> IMPORTANT_FOR_INTERACTION_AUTO_CSSPOINTEREVENTSAUTO
Expand All @@ -49,7 +55,9 @@ internal object ImportantForInteractionHelper {
IMPORTANT_FOR_INTERACTION_AUTO_CSSPOINTEREVENTSAUTO or
IMPORTANT_FOR_INTERACTION_EXCLUDE_DESCENDANTS
PointerEvents.BOX_NONE -> IMPORTANT_FOR_INTERACTION_NO
}
} or
(if (overflow == Overflow.VISIBLE) IMPORTANT_FOR_INTERACTION_DONT_CLIP_DESCENDANTS
else 0)
view.setTag(R.id.important_for_interaction, value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ public open class ReactViewGroup public constructor(context: Context?) :

public override var hitSlopRect: Rect? = null
public override var pointerEvents: PointerEvents = PointerEvents.AUTO
set(value) {
field = value
ImportantForInteractionHelper.setImportantForInteraction(this, value, _overflow)
}

public var axOrderList: MutableList<String>? = null

Expand Down Expand Up @@ -175,9 +179,9 @@ public open class ReactViewGroup public constructor(context: Context?) :
allChildrenCount = 0
clippingRect = null
hitSlopRect = null
// pointerEvents setter reads _overflow, so _overflow must be assigned first.
_overflow = Overflow.VISIBLE
pointerEvents = PointerEvents.AUTO
ImportantForInteractionHelper.setImportantForInteraction(this, pointerEvents)
childrenLayoutChangeListener = null
onInterceptTouchEventListener = null
needsOffscreenAlphaCompositing = false
Expand Down Expand Up @@ -828,6 +832,7 @@ public open class ReactViewGroup public constructor(context: Context?) :
}
set(overflow) {
_overflow = Overflow.fromString(overflow)
ImportantForInteractionHelper.setImportantForInteraction(this, pointerEvents, _overflow)
invalidate()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,6 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
@ReactProp(name = ViewProps.POINTER_EVENTS)
public open fun setPointerEvents(view: ReactViewGroup, pointerEventsStr: String?) {
view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
ImportantForInteractionHelper.setImportantForInteraction(view, view.pointerEvents)
}

@ReactProp(name = "nativeBackgroundAndroid")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.views.view

import android.app.Activity
import android.content.Context
import android.view.View
import com.facebook.react.R
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
import com.facebook.react.uimanager.PointerEvents
import com.facebook.react.uimanager.style.Overflow
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner

/** Tests for [ImportantForInteractionHelper]. */
@RunWith(RobolectricTestRunner::class)
class ImportantForInteractionHelperTest {

private lateinit var context: Context

private val dontClip =
ImportantForInteractionHelper.IMPORTANT_FOR_INTERACTION_DONT_CLIP_DESCENDANTS
private val auto =
ImportantForInteractionHelper.IMPORTANT_FOR_INTERACTION_AUTO_CSSPOINTEREVENTSAUTO
private val no = ImportantForInteractionHelper.IMPORTANT_FOR_INTERACTION_NO
private val exclude = ImportantForInteractionHelper.IMPORTANT_FOR_INTERACTION_EXCLUDE_DESCENDANTS

@Before
fun setUp() {
ReactNativeFeatureFlagsForTests.setUp()
ReactNativeFeatureFlags.override(
object : ReactNativeFeatureFlagsDefaults() {
override fun syncAndroidClipBoundsWithOverflow(): Boolean = true
}
)
context = Robolectric.buildActivity(Activity::class.java).create().get()
}

@After
fun tearDown() {
ReactNativeFeatureFlags.dangerouslyReset()
}

private fun tagFor(pointerEvents: PointerEvents, overflow: Overflow): Int {
val view = View(context)
ImportantForInteractionHelper.setImportantForInteraction(view, pointerEvents, overflow)
return view.getTag(R.id.important_for_interaction) as Int
}

@Test
fun overflowMapping() {
assertThat(tagFor(PointerEvents.AUTO, Overflow.VISIBLE) and dontClip).isEqualTo(dontClip)
assertThat(tagFor(PointerEvents.AUTO, Overflow.HIDDEN) and dontClip).isEqualTo(0)
assertThat(tagFor(PointerEvents.AUTO, Overflow.SCROLL) and dontClip).isEqualTo(0)
}

@Test
fun pointerEventsMapping() {
assertThat(tagFor(PointerEvents.AUTO, Overflow.HIDDEN)).isEqualTo(auto)
assertThat(tagFor(PointerEvents.NONE, Overflow.HIDDEN)).isEqualTo(no or exclude)
assertThat(tagFor(PointerEvents.BOX_ONLY, Overflow.HIDDEN)).isEqualTo(auto or exclude)
assertThat(tagFor(PointerEvents.BOX_NONE, Overflow.HIDDEN)).isEqualTo(no)
}

@Test
fun tagRecomputesOnProperties() {
val rvg = ReactViewGroup(context)
fun tag() = rvg.getTag(R.id.important_for_interaction) as Int

// overflow defaults to VISIBLE
rvg.pointerEvents = PointerEvents.BOX_NONE
assertThat(tag()).isEqualTo(no or dontClip)

rvg.overflow = "hidden"
assertThat(tag()).isEqualTo(no)

rvg.pointerEvents = PointerEvents.AUTO
assertThat(tag()).isEqualTo(auto)
}
}
Loading