Skip to content
Open
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
8 changes: 8 additions & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -5161,6 +5161,7 @@ public abstract interface class com/facebook/react/viewmanagers/VirtualViewManag

public final class com/facebook/react/views/drawer/ReactDrawerLayout : androidx/drawerlayout/widget/DrawerLayout, com/facebook/react/uimanager/HasChildPressedStateDelay {
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
public fun addFocusables (Ljava/util/ArrayList;II)V
public fun getHasChildPressedStateDelay ()Ljava/lang/Boolean;
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
Expand Down Expand Up @@ -5515,6 +5516,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android
public fun onLayoutChange (Landroid/view/View;IIIIIIII)V
protected fun onMeasure (II)V
protected fun onOverScrolled (IIZZ)V
protected fun onRequestFocusInDescendants (ILandroid/graphics/Rect;)Z
protected fun onScrollChanged (IIII)V
protected fun onSizeChanged (IIII)V
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
Expand Down Expand Up @@ -5625,6 +5627,7 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
public fun <init> (Landroid/content/Context;Lcom/facebook/react/views/scroll/FpsListener;)V
public synthetic fun <init> (Landroid/content/Context;Lcom/facebook/react/views/scroll/FpsListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun abortAnimation ()V
public fun addFocusables (Ljava/util/ArrayList;II)V
public fun dispatchGenericMotionEvent (Landroid/view/MotionEvent;)Z
public fun draw (Landroid/graphics/Canvas;)V
public fun executeKeyEvent (Landroid/view/KeyEvent;)Z
Expand Down Expand Up @@ -5665,6 +5668,7 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
public fun onLayoutChange (Landroid/view/View;IIIIIIII)V
protected fun onMeasure (II)V
protected fun onOverScrolled (IIZZ)V
protected fun onRequestFocusInDescendants (ILandroid/graphics/Rect;)Z
protected fun onScrollChanged (IIII)V
protected fun onSizeChanged (IIII)V
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
Expand Down Expand Up @@ -5980,10 +5984,12 @@ public final class com/facebook/react/views/scroll/VirtualViewContainerState$Com

public class com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout : androidx/swiperefreshlayout/widget/SwipeRefreshLayout, com/facebook/react/uimanager/HasChildPressedStateDelay {
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
public fun addFocusables (Ljava/util/ArrayList;II)V
public fun canChildScrollUp ()Z
public fun getHasChildPressedStateDelay ()Ljava/lang/Boolean;
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
public fun onLayout (ZIIII)V
protected fun onRequestFocusInDescendants (ILandroid/graphics/Rect;)Z
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
public fun requestDisallowInterceptTouchEvent (Z)V
public fun setHasChildPressedStateDelay (Ljava/lang/Boolean;)V
Expand Down Expand Up @@ -6439,6 +6445,7 @@ public final class com/facebook/react/views/view/ReactDrawableHelper {
public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGroup, com/facebook/react/touch/ReactHitSlopView, com/facebook/react/touch/ReactInterceptingViewGroup, com/facebook/react/uimanager/HasElevatedDescendantCache, com/facebook/react/uimanager/ReactClippingViewGroup, com/facebook/react/uimanager/ReactOverflowViewWithInset, com/facebook/react/uimanager/ReactPointerEventsView {
public fun <init> (Landroid/content/Context;)V
public fun addChildrenForAccessibility (Ljava/util/ArrayList;)V
public fun addFocusables (Ljava/util/ArrayList;II)V
public final fun cleanUpAxOrderListener ()V
protected fun dispatchDraw (Landroid/graphics/Canvas;)V
public fun dispatchGenericMotionEvent (Landroid/view/MotionEvent;)Z
Expand All @@ -6464,6 +6471,7 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
protected fun onLayout (ZIIII)V
protected fun onMeasure (II)V
protected fun onRequestFocusInDescendants (ILandroid/graphics/Rect;)Z
protected fun onSizeChanged (IIII)V
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
public fun onViewAdded (Landroid/view/View;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ public void setImportantForAccessibility(
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
} else if (importantForAccessibility.equals("no-hide-descendants")) {
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
HiddenSubtreeFocusHelper.releaseHiddenKeyboardFocus(view);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.uimanager

import android.view.View
import android.view.ViewGroup

/**
* Keeps the descendants of a view that hides them from accessibility services out of keyboard and
* D-pad navigation too, so a hardware keyboard never lands on content a screen reader cannot reach.
* A tap or a `focus()` call still reaches them, and the view itself keeps the place the platform
* gives it in the focus search.
*/
internal object HiddenSubtreeFocusHelper {

@JvmStatic
fun hidesDescendants(view: View): Boolean =
view.importantForAccessibility == View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS

/**
* Removes the descendants [group] added to [views] from [firstAddedIndex] on while it hides them.
* A focused descendant stays, so that focus search can move on from it.
*/
@JvmStatic
fun removeHiddenFocusables(group: ViewGroup, views: ArrayList<View>, firstAddedIndex: Int) {
if (hidesDescendants(group)) {
views.subList(firstAddedIndex, views.size).removeAll { it !== group && !it.isFocused }
}
}

/**
* Moves keyboard focus off a descendant [view] now hides; touch focus stays where the user types.
*/
@JvmStatic
fun releaseHiddenKeyboardFocus(view: View) {
if (view is ViewGroup && hidesDescendants(view) && !view.isInTouchMode) {
view.findFocus()?.takeIf { it !== view }?.clearFocus()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.facebook.react.views.drawer

import android.annotation.SuppressLint
import android.graphics.Rect
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
Expand All @@ -21,6 +22,7 @@ import com.facebook.react.R
import com.facebook.react.bridge.ReactContext
import com.facebook.react.common.ReactConstants
import com.facebook.react.uimanager.HasChildPressedStateDelay
import com.facebook.react.uimanager.HiddenSubtreeFocusHelper
import com.facebook.react.uimanager.ReactAccessibilityDelegate.AccessibilityRole
import com.facebook.react.uimanager.events.NativeGestureUtil.notifyNativeGestureEnded
import com.facebook.react.uimanager.events.NativeGestureUtil.notifyNativeGestureStarted
Expand Down Expand Up @@ -67,6 +69,16 @@ public class ReactDrawerLayout(reactContext: ReactContext) :
override fun shouldDelayChildPressedState(): Boolean =
hasChildPressedStateDelay ?: super.shouldDelayChildPressedState()

override fun addFocusables(views: ArrayList<View>, direction: Int, focusableMode: Int) {
val firstAddedIndex = views.size
super.addFocusables(views, direction, focusableMode)
HiddenSubtreeFocusHelper.removeHiddenFocusables(this, views, firstAddedIndex)
}

override fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean =
!HiddenSubtreeFocusHelper.hidesDescendants(this) &&
super.onRequestFocusInDescendants(direction, previouslyFocusedRect)

override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
try {
if (super.onInterceptTouchEvent(ev)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.uimanager.BackgroundStyleApplicator
import com.facebook.react.uimanager.HasChildPressedStateDelay
import com.facebook.react.uimanager.HiddenSubtreeFocusHelper
import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.LengthPercentageType
import com.facebook.react.uimanager.MeasureSpecAssertions
Expand Down Expand Up @@ -565,6 +566,16 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
}

override fun addFocusables(views: ArrayList<View>, direction: Int, focusableMode: Int) {
val firstAddedIndex = views.size
addPageFocusables(views, direction, focusableMode)
HiddenSubtreeFocusHelper.removeHiddenFocusables(this, views, firstAddedIndex)
}

override fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean =
!HiddenSubtreeFocusHelper.hidesDescendants(this) &&
super.onRequestFocusInDescendants(direction, previouslyFocusedRect)

private fun addPageFocusables(views: ArrayList<View>, direction: Int, focusableMode: Int) {
if (pagingEnabled && !pagedArrowScrolling) {
// Only add elements within the current page to list of focusables
val candidateViews = ArrayList<View>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<37e2e57a137f899bb94e78bd265de562>>
* @generated SignedSource<<71b6e6eea33dce97af22540ffdbeec49>>
*/

/**
Expand Down Expand Up @@ -39,6 +39,7 @@ import com.facebook.react.common.ReactConstants
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.uimanager.BackgroundStyleApplicator
import com.facebook.react.uimanager.HasChildPressedStateDelay
import com.facebook.react.uimanager.HiddenSubtreeFocusHelper
import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.LengthPercentageType
import com.facebook.react.uimanager.MeasureSpecAssertions
Expand Down Expand Up @@ -461,6 +462,16 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
maintainVisibleContentPositionHelper?.stop()
}

override fun addFocusables(views: ArrayList<View>, direction: Int, focusableMode: Int) {
val firstAddedIndex = views.size
super.addFocusables(views, direction, focusableMode)
HiddenSubtreeFocusHelper.removeHiddenFocusables(this, views, firstAddedIndex)
}

override fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean =
!HiddenSubtreeFocusHelper.hidesDescendants(this) &&
super.onRequestFocusInDescendants(direction, previouslyFocusedRect)

override fun focusSearch(focused: View, direction: Int): View? {
val nextFocus = super.focusSearch(focused, direction)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.facebook.react.common.ReactConstants
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.uimanager.BackgroundStyleApplicator
import com.facebook.react.uimanager.HasChildPressedStateDelay
import com.facebook.react.uimanager.HiddenSubtreeFocusHelper
import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.LengthPercentageType
import com.facebook.react.uimanager.MeasureSpecAssertions
Expand Down Expand Up @@ -453,6 +454,16 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
maintainVisibleContentPositionHelper?.stop()
}

override fun addFocusables(views: ArrayList<View>, direction: Int, focusableMode: Int) {
val firstAddedIndex = views.size
super.addFocusables(views, direction, focusableMode)
HiddenSubtreeFocusHelper.removeHiddenFocusables(this, views, firstAddedIndex)
}

override fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean =
!HiddenSubtreeFocusHelper.hidesDescendants(this) &&
super.onRequestFocusInDescendants(direction, previouslyFocusedRect)

override fun focusSearch(focused: View, direction: Int): View? {
val nextFocus = super.focusSearch(focused, direction)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@

package com.facebook.react.views.swiperefresh

import android.graphics.Rect
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.HasChildPressedStateDelay
import com.facebook.react.uimanager.HiddenSubtreeFocusHelper
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.events.NativeGestureUtil
import kotlin.math.abs
Expand Down Expand Up @@ -85,6 +88,16 @@ public open class ReactSwipeRefreshLayout(reactContext: ReactContext) :
override fun shouldDelayChildPressedState(): Boolean =
hasChildPressedStateDelay ?: super.shouldDelayChildPressedState()

public override fun addFocusables(views: ArrayList<View>, direction: Int, focusableMode: Int) {
val firstAddedIndex = views.size
super.addFocusables(views, direction, focusableMode)
HiddenSubtreeFocusHelper.removeHiddenFocusables(this, views, firstAddedIndex)
}

override fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean =
!HiddenSubtreeFocusHelper.hidesDescendants(this) &&
super.onRequestFocusInDescendants(direction, previouslyFocusedRect)

public override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (shouldInterceptTouchEvent(ev) && super.onInterceptTouchEvent(ev)) {
NativeGestureUtil.notifyNativeGestureStarted(this, ev)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderWidth
import com.facebook.react.uimanager.BackgroundStyleApplicator.setFeedbackUnderlay
import com.facebook.react.uimanager.BlendModeHelper.needsIsolatedLayer
import com.facebook.react.uimanager.HasElevatedDescendantCache
import com.facebook.react.uimanager.HiddenSubtreeFocusHelper
import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.LengthPercentageType
import com.facebook.react.uimanager.MeasureSpecAssertions.assertExplicitMeasureSpec
Expand Down Expand Up @@ -508,6 +509,16 @@ public open class ReactViewGroup public constructor(context: Context?) :
updateClippingToRect(clippingRect, excludedViews)
}

override fun addFocusables(views: ArrayList<View>, direction: Int, focusableMode: Int) {
val firstAddedIndex = views.size
super.addFocusables(views, direction, focusableMode)
HiddenSubtreeFocusHelper.removeHiddenFocusables(this, views, firstAddedIndex)
}

override fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean =
!HiddenSubtreeFocusHelper.hidesDescendants(this) &&
super.onRequestFocusInDescendants(direction, previouslyFocusedRect)

internal fun requestFocusFromJS() {
if (isAttachedToWindow) {
super.requestFocus(FOCUS_DOWN, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,14 @@ void FocusOrderingHelper::traverseAndUpdateNextFocusableElement(
}
}

// A view that hides its descendants from accessibility services keeps them
// out of keyboard navigation, as ReactViewGroup does for the views it hosts.
if (props != nullptr &&
props->importantForAccessibility ==
ImportantForAccessibility::NoHideDescendants) {
return;
}

for (auto& child : currNode->getChildren()) {
if (child->getTraits().check(ShadowNodeTraits::Trait::RootNodeKind)) {
continue;
Expand Down
Loading
Loading