From cf6e86e4aa6b121ab510f3bee27921d4ebc5250a Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Sat, 25 Apr 2026 17:37:12 -0400 Subject: [PATCH] fix(android): make EditText accessible with display: contents The decorator view uses style={{ display: 'contents' }} so it doesn't take layout space, but on Android Fabric this caused the inner EditText to be missing from the accessibility tree. Number-pad/phone-pad inputs silently rejected all keystrokes (the IME has nothing to commit text into). Default keyboard inputs happened to work because they go through a different commit path. The shadow node already removed the ForceFlattenView trait so a host view is created, but the child's Yoga-computed metrics weren't being propagated to the now-non-flat decorator. Override layout() to copy the child's metrics onto the decorator and zero out the child's origin (now relative to the decorator). Mirrors react-native-live-markdown's approach for the same setup. --- ...formerTextInputDecoratorViewShadowNode.cpp | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/cpp/TransformerTextInputDecoratorViewShadowNode.cpp b/cpp/TransformerTextInputDecoratorViewShadowNode.cpp index aa217cf..f912d87 100644 --- a/cpp/TransformerTextInputDecoratorViewShadowNode.cpp +++ b/cpp/TransformerTextInputDecoratorViewShadowNode.cpp @@ -1,5 +1,7 @@ #include "TransformerTextInputDecoratorViewShadowNode.h" +#include + namespace facebook::react { const char TransformerTextInputDecoratorViewComponentName[] = @@ -15,7 +17,28 @@ void TransformerTextInputDecoratorViewShadowNode::initialize() { void TransformerTextInputDecoratorViewShadowNode::layout( LayoutContext layoutContext) { - ConcreteViewShadowNode::layout(layoutContext); + YogaLayoutableShadowNode::layout(layoutContext); + + // Nodes with display: contents are skipped during Yoga layout and end up + // with zero-sized metrics. To get a proper host view (so input and + // accessibility wiring works on Android), copy the child's Yoga-computed + // metrics onto the decorator and zero out the child's origin since it is + // now relative to the decorator. + const auto &children = getChildren(); + react_native_assert( + children.size() == 1 && + "TransformerTextInputDecoratorView didn't receive exactly one child"); + + auto child = + std::static_pointer_cast(children[0]); + child->ensureUnsealed(); + auto mutableChild = std::const_pointer_cast(child); + + auto childMetrics = child->getLayoutMetrics(); + setLayoutMetrics(childMetrics); + + childMetrics.frame.origin = Point{}; + mutableChild->setLayoutMetrics(childMetrics); } } // namespace facebook::react