Skip to content
Draft
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 @@ -57,7 +57,7 @@ internal class FabricUIManagerBinding : HybridClassBase() {

external fun findNextFocusableElement(parentTag: Int, focusedTag: Int, direction: Int): Int

external fun getRelativeAncestorList(rootTag: Int, childTag: Int): IntArray
external fun getRelativeAncestorList(rootTag: Int, childTag: Int): IntArray?

external fun stopSurface(surfaceId: Int)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,25 +343,18 @@ jintArray FabricUIManagerBinding::getRelativeAncestorList(
return nullptr;
}

ShadowNode::AncestorList ancestorList =
childShadowNode->getFamily().getAncestors(*rootShadowNode);
std::optional<std::vector<Tag>> ancestorTags =
FocusOrderingHelper::getRelativeAncestorTags(
*rootShadowNode, *childShadowNode);

if (ancestorList.empty() || ancestorList.size() < 2) {
if (!ancestorTags.has_value()) {
return nullptr;
}

// ignore the first ancestor as it is the rootShadowNode itself
std::vector<int> ancestorTags;
for (auto it = std::next(ancestorList.begin()); it != ancestorList.end();
++it) {
auto& ancestor = *it;
ancestorTags.push_back(ancestor.first.get().getTag());
}

jintArray result = env->NewIntArray(static_cast<jint>(ancestorTags.size()));
jintArray result = env->NewIntArray(static_cast<jint>(ancestorTags->size()));

env->SetIntArrayRegion(
result, 0, static_cast<jint>(ancestorTags.size()), ancestorTags.data());
result, 0, static_cast<jint>(ancestorTags->size()), ancestorTags->data());

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,25 @@ std::optional<FocusDirection> FocusOrderingHelper::resolveFocusDirection(

return std::nullopt;
}

std::optional<std::vector<Tag>> FocusOrderingHelper::getRelativeAncestorTags(
const ShadowNode& rootShadowNode,
const ShadowNode& childShadowNode) {
ShadowNode::AncestorList ancestorList =
childShadowNode.getFamily().getAncestors(rootShadowNode);

if (ancestorList.empty()) {
return std::nullopt;
}

// ignore the first ancestor as it is the rootShadowNode itself
std::vector<Tag> ancestorTags;
for (auto it = std::next(ancestorList.begin()); it != ancestorList.end();
++it) {
auto& ancestor = *it;
ancestorTags.push_back(ancestor.first.get().getTag());
}

return ancestorTags;
}
} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <react/renderer/uimanager/UIManager.h>
#include "FabricUIManagerBinding.h"

#include <optional>
#include <vector>

namespace facebook::react {

Expand Down Expand Up @@ -36,5 +40,14 @@ class FocusOrderingHelper {
Tag tag);

static std::optional<FocusDirection> resolveFocusDirection(int direction);

/*
* The tags of the nodes between `rootShadowNode` and `childShadowNode`,
* outermost first: empty for a direct child of `rootShadowNode`, and
* `std::nullopt` when `childShadowNode` does not descend from it.
*/
static std::optional<std::vector<Tag>> getRelativeAncestorTags(
const ShadowNode &rootShadowNode,
const ShadowNode &childShadowNode);
};
} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.
*/

#include <react/fabric/FocusOrderingHelper.h>

#include <gtest/gtest.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/element/ComponentBuilder.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>

#include <memory>
#include <vector>

namespace facebook::react {

class FocusOrderingHelperTest : public ::testing::Test {
protected:
ComponentBuilder builder_;
std::shared_ptr<ViewShadowNode> contentContainer_;
std::shared_ptr<ViewShadowNode> directChild_;
std::shared_ptr<ViewShadowNode> nestedChild_;
std::shared_ptr<ViewShadowNode> detachedNode_;

FocusOrderingHelperTest() : builder_(simpleComponentBuilder()) {
// clang-format off
auto element =
Element<ViewShadowNode>()
.reference(contentContainer_)
.tag(1)
.children({
Element<ViewShadowNode>()
.reference(directChild_)
.tag(2),
Element<ViewShadowNode>()
.tag(3)
.children({
Element<ViewShadowNode>()
.tag(4)
.children({
Element<ViewShadowNode>()
.reference(nestedChild_)
.tag(5)
})
})
});
// clang-format on

builder_.build(element);
builder_.build(Element<ViewShadowNode>().reference(detachedNode_).tag(6));
}
};

TEST_F(FocusOrderingHelperTest, testDirectChildHasNoAncestorsBelowTheRoot) {
auto ancestorTags = FocusOrderingHelper::getRelativeAncestorTags(
*contentContainer_, *directChild_);

ASSERT_TRUE(ancestorTags.has_value());
EXPECT_TRUE(ancestorTags->empty());
}

TEST_F(
FocusOrderingHelperTest,
testNestedChildListsItsAncestorsBelowTheRootOutermostFirst) {
auto ancestorTags = FocusOrderingHelper::getRelativeAncestorTags(
*contentContainer_, *nestedChild_);

ASSERT_TRUE(ancestorTags.has_value());
EXPECT_EQ(std::vector<Tag>({3, 4}), *ancestorTags);
}

TEST_F(FocusOrderingHelperTest, testNodeOutsideTheRootHasNoAncestorList) {
auto ancestorTags = FocusOrderingHelper::getRelativeAncestorTags(
*contentContainer_, *detachedNode_);

EXPECT_FALSE(ancestorTags.has_value());
}

TEST_F(FocusOrderingHelperTest, testRootHasNoAncestorListRelativeToItself) {
auto ancestorTags = FocusOrderingHelper::getRelativeAncestorTags(
*contentContainer_, *contentContainer_);

EXPECT_FALSE(ancestorTags.has_value());
}

} // namespace facebook::react
12 changes: 9 additions & 3 deletions packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,10 @@ const examples: Array<RNTesterModuleExample> = [
},
},
{
name: 'removeClippedSubviews',
title: '<ScrollView> Remove Clipped Subviews\n',
description:
'When true, offscreen child views (whose overflow value is hidden) are removed from their native backing superview when offscreen.',
'When true, offscreen child views (whose overflow value is hidden) are removed from their native backing superview when offscreen. The rows are focusable, so Tab on a hardware keyboard should reach every row either way.',
render(): React.Node {
return <RemoveClippedSubviews />;
},
Expand Down Expand Up @@ -844,7 +845,9 @@ const RemoveClippedSubviews = () => {
style={[styles.scrollView, {height: 200}]}
removeClippedSubviews={removeClippedSubviews}
nestedScrollEnabled>
{ITEMS.map(createItemRow)}
{ITEMS.map((msg, index) => (
<Item key={index} msg={msg} focusable={true} />
))}
</ScrollView>
<Button
label={'removeClippedSubviews: ' + removeClippedSubviews.toString()}
Expand Down Expand Up @@ -1476,12 +1479,15 @@ function ChildrenWithTouchEventsOverflowingContainerHorizontal() {
}

class Item extends React.PureComponent<{
focusable?: boolean,
msg?: string,
style?: ViewStyleProp,
}> {
render(): $FlowFixMe {
return (
<View style={[styles.item, this.props.style]}>
<View
focusable={this.props.focusable}
style={[styles.item, this.props.style]}>
<Text>{this.props.msg}</Text>
</View>
);
Expand Down
1 change: 1 addition & 0 deletions scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,7 @@ class facebook::react::FabricUIManagerBinding : public jni::HybridClass<facebook

class facebook::react::FocusOrderingHelper {
public static std::optional<facebook::react::FocusDirection> resolveFocusDirection(int direction);
public static std::optional<std::vector<facebook::react::Tag>> getRelativeAncestorTags(const facebook::react::ShadowNode& rootShadowNode, const facebook::react::ShadowNode& childShadowNode);
public static std::shared_ptr<const facebook::react::ShadowNode> findShadowNodeByTagRecursively(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, facebook::react::Tag tag);
public static void traverseAndUpdateNextFocusableElement(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& focusedShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& currNode, facebook::react::FocusDirection focusDirection, const facebook::react::UIManager& uimanager, facebook::react::Rect sourceRect, std::optional<facebook::react::Rect>& nextRect, std::shared_ptr<const facebook::react::ShadowNode>& nextNode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1969,6 +1969,7 @@ class facebook::react::FabricUIManagerBinding : public jni::HybridClass<facebook

class facebook::react::FocusOrderingHelper {
public static std::optional<facebook::react::FocusDirection> resolveFocusDirection(int direction);
public static std::optional<std::vector<facebook::react::Tag>> getRelativeAncestorTags(const facebook::react::ShadowNode& rootShadowNode, const facebook::react::ShadowNode& childShadowNode);
public static std::shared_ptr<const facebook::react::ShadowNode> findShadowNodeByTagRecursively(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, facebook::react::Tag tag);
public static void traverseAndUpdateNextFocusableElement(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& focusedShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& currNode, facebook::react::FocusDirection focusDirection, const facebook::react::UIManager& uimanager, facebook::react::Rect sourceRect, std::optional<facebook::react::Rect>& nextRect, std::shared_ptr<const facebook::react::ShadowNode>& nextNode);
}
Expand Down
1 change: 1 addition & 0 deletions scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,7 @@ class facebook::react::FabricUIManagerBinding : public jni::HybridClass<facebook

class facebook::react::FocusOrderingHelper {
public static std::optional<facebook::react::FocusDirection> resolveFocusDirection(int direction);
public static std::optional<std::vector<facebook::react::Tag>> getRelativeAncestorTags(const facebook::react::ShadowNode& rootShadowNode, const facebook::react::ShadowNode& childShadowNode);
public static std::shared_ptr<const facebook::react::ShadowNode> findShadowNodeByTagRecursively(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, facebook::react::Tag tag);
public static void traverseAndUpdateNextFocusableElement(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& focusedShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& currNode, facebook::react::FocusDirection focusDirection, const facebook::react::UIManager& uimanager, facebook::react::Rect sourceRect, std::optional<facebook::react::Rect>& nextRect, std::shared_ptr<const facebook::react::ShadowNode>& nextNode);
}
Expand Down
1 change: 1 addition & 0 deletions scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,7 @@ class facebook::react::FabricUIManagerBinding : public jni::HybridClass<facebook

class facebook::react::FocusOrderingHelper {
public static std::optional<facebook::react::FocusDirection> resolveFocusDirection(int direction);
public static std::optional<std::vector<facebook::react::Tag>> getRelativeAncestorTags(const facebook::react::ShadowNode& rootShadowNode, const facebook::react::ShadowNode& childShadowNode);
public static std::shared_ptr<const facebook::react::ShadowNode> findShadowNodeByTagRecursively(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, facebook::react::Tag tag);
public static void traverseAndUpdateNextFocusableElement(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& focusedShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& currNode, facebook::react::FocusDirection focusDirection, const facebook::react::UIManager& uimanager, facebook::react::Rect sourceRect, std::optional<facebook::react::Rect>& nextRect, std::shared_ptr<const facebook::react::ShadowNode>& nextNode);
}
Expand Down
Loading