first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,254 @@
/*
* 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 "BaseTextInputProps.h"
#include <react/renderer/attributedstring/TextAttributes.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/components/image/conversions.h>
#include <react/renderer/components/textinput/baseConversions.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsMacros.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/imagemanager/primitives.h>
#include <limits>
namespace facebook::react {
BaseTextInputProps::BaseTextInputProps(
const PropsParserContext& context,
const BaseTextInputProps& sourceProps,
const RawProps& rawProps)
: ViewProps(context, sourceProps, rawProps),
BaseTextProps(context, sourceProps, rawProps),
paragraphAttributes(convertRawProp(
context,
rawProps,
sourceProps.paragraphAttributes,
{})),
defaultValue(convertRawProp(
context,
rawProps,
"defaultValue",
sourceProps.defaultValue,
{})),
placeholder(convertRawProp(
context,
rawProps,
"placeholder",
sourceProps.placeholder,
{})),
placeholderTextColor(convertRawProp(
context,
rawProps,
"placeholderTextColor",
sourceProps.placeholderTextColor,
{})),
cursorColor(convertRawProp(
context,
rawProps,
"cursorColor",
sourceProps.cursorColor,
{})),
selectionColor(convertRawProp(
context,
rawProps,
"selectionColor",
sourceProps.selectionColor,
{})),
selectionHandleColor(convertRawProp(
context,
rawProps,
"selectionHandleColor",
sourceProps.selectionHandleColor,
{})),
underlineColorAndroid(convertRawProp(
context,
rawProps,
"underlineColorAndroid",
sourceProps.underlineColorAndroid,
{})),
maxLength(convertRawProp(
context,
rawProps,
"maxLength",
sourceProps.maxLength,
{std::numeric_limits<int>::max()})),
text(convertRawProp(context, rawProps, "text", sourceProps.text, {})),
mostRecentEventCount(convertRawProp(
context,
rawProps,
"mostRecentEventCount",
sourceProps.mostRecentEventCount,
{})),
autoFocus(convertRawProp(
context,
rawProps,
"autoFocus",
sourceProps.autoFocus,
{})),
autoCapitalize(convertRawProp(
context,
rawProps,
"autoCapitalize",
sourceProps.autoCapitalize,
{})),
editable(convertRawProp(
context,
rawProps,
"editable",
sourceProps.editable,
{})),
readOnly(convertRawProp(
context,
rawProps,
"readOnly",
sourceProps.readOnly,
{})),
submitBehavior(convertRawProp(
context,
rawProps,
"submitBehavior",
sourceProps.submitBehavior,
{})),
multiline(convertRawProp(
context,
rawProps,
"multiline",
sourceProps.multiline,
{false})),
disableKeyboardShortcuts(convertRawProp(
context,
rawProps,
"disableKeyboardShortcuts",
sourceProps.disableKeyboardShortcuts,
{false})),
acceptDragAndDropTypes(convertRawProp(
context,
rawProps,
"acceptDragAndDropTypes",
sourceProps.acceptDragAndDropTypes,
{})) {}
void BaseTextInputProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
ViewProps::setProp(context, hash, propName, value);
BaseTextProps::setProp(context, hash, propName, value);
static auto defaults = BaseTextInputProps{};
// ParagraphAttributes has its own switch statement - to keep all
// of these fields together, and because there are some collisions between
// propnames parsed here and outside of ParagraphAttributes. For example,
// textBreakStrategy is duplicated.
// This code is also duplicated in ParagraphProps.
static auto paDefaults = ParagraphAttributes{};
switch (hash) {
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
maximumNumberOfLines,
"numberOfLines");
REBUILD_FIELD_SWITCH_CASE(
paDefaults, value, paragraphAttributes, ellipsizeMode, "ellipsizeMode");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
textBreakStrategy,
"textBreakStrategy");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
adjustsFontSizeToFit,
"adjustsFontSizeToFit");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
minimumFontSize,
"minimumFontSize");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
maximumFontSize,
"maximumFontSize");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
includeFontPadding,
"includeFontPadding");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
android_hyphenationFrequency,
"android_hyphenationFrequency");
REBUILD_FIELD_SWITCH_CASE(
paDefaults,
value,
paragraphAttributes,
textAlignVertical,
"textAlignVertical");
}
switch (hash) {
RAW_SET_PROP_SWITCH_CASE_BASIC(underlineColorAndroid);
RAW_SET_PROP_SWITCH_CASE_BASIC(autoFocus);
RAW_SET_PROP_SWITCH_CASE_BASIC(maxLength);
RAW_SET_PROP_SWITCH_CASE_BASIC(placeholder);
RAW_SET_PROP_SWITCH_CASE_BASIC(placeholderTextColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(selectionColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(selectionHandleColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(defaultValue);
RAW_SET_PROP_SWITCH_CASE_BASIC(cursorColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(text);
RAW_SET_PROP_SWITCH_CASE_BASIC(mostRecentEventCount);
RAW_SET_PROP_SWITCH_CASE_BASIC(autoCapitalize);
RAW_SET_PROP_SWITCH_CASE_BASIC(editable);
RAW_SET_PROP_SWITCH_CASE_BASIC(readOnly);
RAW_SET_PROP_SWITCH_CASE_BASIC(submitBehavior);
RAW_SET_PROP_SWITCH_CASE_BASIC(multiline);
RAW_SET_PROP_SWITCH_CASE_BASIC(disableKeyboardShortcuts);
RAW_SET_PROP_SWITCH_CASE_BASIC(acceptDragAndDropTypes);
}
}
TextAttributes BaseTextInputProps::getEffectiveTextAttributes(
Float fontSizeMultiplier) const {
auto result = TextAttributes::defaultTextAttributes();
result.fontSizeMultiplier = fontSizeMultiplier;
result.apply(textAttributes);
/*
* These props are applied to `View`, therefore they must not be a part of
* base text attributes.
*/
result.backgroundColor = clearColor();
result.opacity = 1;
return result;
}
SubmitBehavior BaseTextInputProps::getNonDefaultSubmitBehavior() const {
if (submitBehavior == SubmitBehavior::Default) {
return multiline ? SubmitBehavior::Newline : SubmitBehavior::BlurAndSubmit;
}
return submitBehavior;
}
} // namespace facebook::react

View File

@@ -0,0 +1,85 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/components/text/BaseTextProps.h>
#include <react/renderer/components/textinput/basePrimitives.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/Color.h>
#include <limits>
#include <string>
namespace facebook::react {
class BaseTextInputProps : public ViewProps, public BaseTextProps {
public:
BaseTextInputProps() = default;
BaseTextInputProps(
const PropsParserContext &context,
const BaseTextInputProps &sourceProps,
const RawProps &rawProps);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
SubmitBehavior getNonDefaultSubmitBehavior() const;
/*
* Accessors
*/
TextAttributes getEffectiveTextAttributes(Float fontSizeMultiplier) const;
#pragma mark - Props
/*
* Contains all prop values that affect visual representation of the
* paragraph.
*/
ParagraphAttributes paragraphAttributes{};
std::string defaultValue{};
std::string placeholder{};
SharedColor placeholderTextColor{};
/*
* Tint colors
*/
SharedColor cursorColor{};
SharedColor selectionColor{};
SharedColor selectionHandleColor{};
// TODO: Rename to `tintColor` and make universal.
SharedColor underlineColorAndroid{};
int maxLength = std::numeric_limits<int>::max();
/*
* "Private" (only used by TextInput.js) props
*/
std::string text{};
int mostRecentEventCount{0};
bool autoFocus{false};
std::string autoCapitalize{};
bool editable{true};
bool readOnly{false};
SubmitBehavior submitBehavior{SubmitBehavior::Default};
bool multiline{false};
bool disableKeyboardShortcuts{false};
std::optional<std::vector<std::string>> acceptDragAndDropTypes{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,227 @@
/*
* 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.
*/
#pragma once
#include <glog/logging.h>
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/components/text/BaseTextShadowNode.h>
#include <react/renderer/components/textinput/BaseTextInputProps.h>
#include <react/renderer/components/textinput/TextInputState.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/YogaLayoutableShadowNode.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
#include <react/renderer/textlayoutmanager/TextLayoutManagerExtended.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
/*
* Base `ShadowNode` for <TextInput> component.
*/
template <const char *concreteComponentName, typename ViewPropsT, typename ViewEventEmitterT, typename StateDataT>
class BaseTextInputShadowNode
: public ConcreteViewShadowNode<concreteComponentName, ViewPropsT, ViewEventEmitterT, StateDataT>,
public BaseTextShadowNode {
public:
using BaseShadowNode = ConcreteViewShadowNode<concreteComponentName, ViewPropsT, ViewEventEmitterT, StateDataT>;
using BaseShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits()
{
auto traits = BaseShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
traits.set(ShadowNodeTraits::Trait::BaselineYogaNode);
return traits;
}
/*
* Associates a shared `TextLayoutManager` with the node.
* `TextInputShadowNode` uses the manager to measure text content
* and construct `TextInputState` objects.
*/
void setTextLayoutManager(std::shared_ptr<const TextLayoutManager> textLayoutManager)
{
Sealable::ensureUnsealed();
textLayoutManager_ = std::move(textLayoutManager);
}
protected:
Size measureContent(const LayoutContext &layoutContext, const LayoutConstraints &layoutConstraints) const override
{
const auto &props = BaseShadowNode::getConcreteProps();
auto textConstraints = getTextConstraints(layoutConstraints);
TextLayoutContext textLayoutContext{
.pointScaleFactor = layoutContext.pointScaleFactor,
.surfaceId = BaseShadowNode::getSurfaceId(),
};
auto textSize = textLayoutManager_
->measure(
attributedStringBoxToMeasure(layoutContext),
props.paragraphAttributes,
textLayoutContext,
textConstraints)
.size;
return layoutConstraints.clamp(textSize);
}
void layout(LayoutContext layoutContext) override
{
updateStateIfNeeded(layoutContext);
BaseShadowNode::layout(layoutContext);
}
Float baseline(const LayoutContext &layoutContext, Size size) const override
{
const auto &props = BaseShadowNode::getConcreteProps();
auto attributedString = getAttributedString(layoutContext);
if (attributedString.isEmpty()) {
attributedString = getPlaceholderAttributedString(layoutContext);
}
// Yoga expects a baseline relative to the Node's border-box edge instead of
// the content, so we need to adjust by the padding and border widths, which
// have already been set by the time of baseline alignment
auto top = YGNodeLayoutGetBorder(&(YogaLayoutableShadowNode::yogaNode_), YGEdgeTop) +
YGNodeLayoutGetPadding(&(YogaLayoutableShadowNode::yogaNode_), YGEdgeTop);
AttributedStringBox attributedStringBox{attributedString};
if constexpr (TextLayoutManagerExtended::supportsLineMeasurement()) {
auto lines = TextLayoutManagerExtended(*textLayoutManager_)
.measureLines(attributedStringBox, props.paragraphAttributes, size);
return LineMeasurement::baseline(lines) + top;
} else {
LOG(WARNING) << "Baseline alignment is not supported by the current platform";
return top;
}
}
/*
* Determines the constraints to use while measure the underlying text
*/
LayoutConstraints getTextConstraints(const LayoutConstraints &layoutConstraints) const
{
if (BaseShadowNode::getConcreteProps().multiline) {
return layoutConstraints;
} else {
// A single line TextInput acts as a horizontal scroller of infinitely
// expandable text, so we want to measure the text as if it is allowed to
// infinitely expand horizontally, and later clamp to the constraints of
// the input.
return LayoutConstraints{
.minimumSize = layoutConstraints.minimumSize,
.maximumSize =
Size{
.width = std::numeric_limits<Float>::infinity(),
.height = layoutConstraints.maximumSize.height,
},
.layoutDirection = layoutConstraints.layoutDirection,
};
}
}
std::shared_ptr<const TextLayoutManager> textLayoutManager_;
private:
/*
* Creates a `State` object if needed.
*/
void updateStateIfNeeded(const LayoutContext &layoutContext)
{
Sealable::ensureUnsealed();
const auto &stateData = BaseShadowNode::getStateData();
const auto &reactTreeAttributedString = getAttributedString(layoutContext);
if (stateData.reactTreeAttributedString.isContentEqual(reactTreeAttributedString)) {
return;
}
const auto &props = BaseShadowNode::getConcreteProps();
BaseShadowNode::setStateData(
TextInputState{
AttributedStringBox{reactTreeAttributedString},
reactTreeAttributedString,
props.paragraphAttributes,
props.mostRecentEventCount});
}
/*
* Returns a `AttributedString` which represents text content of the node.
*/
AttributedString getAttributedString(const LayoutContext &layoutContext) const
{
const auto &props = BaseShadowNode::getConcreteProps();
const auto textAttributes = props.getEffectiveTextAttributes(layoutContext.fontSizeMultiplier);
AttributedString attributedString;
attributedString.appendFragment(
AttributedString::Fragment{
.string = props.text, .textAttributes = textAttributes, .parentShadowView = ShadowView(*this)});
auto attachments = BaseTextShadowNode::Attachments{};
BaseTextShadowNode::buildAttributedString(textAttributes, *this, attributedString, attachments);
attributedString.setBaseTextAttributes(textAttributes);
return attributedString;
}
/*
* Returns an `AttributedStringBox` which represents text content that should
* be used for measuring purposes. It might contain actual text value,
* placeholder value or some character that represents the size of the font.
*/
AttributedStringBox attributedStringBoxToMeasure(const LayoutContext &layoutContext) const
{
bool meaningfulState = BaseShadowNode::getState() &&
BaseShadowNode::getState()->getRevision() != State::initialRevisionValue &&
BaseShadowNode::getStateData().reactTreeAttributedString.getBaseTextAttributes().fontSizeMultiplier ==
layoutContext.fontSizeMultiplier;
if (meaningfulState) {
const auto &stateData = BaseShadowNode::getStateData();
auto attributedStringBox = stateData.attributedStringBox;
if (attributedStringBox.getMode() == AttributedStringBox::Mode::OpaquePointer ||
!attributedStringBox.getValue().isEmpty()) {
return stateData.attributedStringBox;
}
}
auto attributedString = meaningfulState ? AttributedString{} : getAttributedString(layoutContext);
if (attributedString.isEmpty()) {
attributedString = getPlaceholderAttributedString(layoutContext);
}
return AttributedStringBox{attributedString};
}
AttributedString getPlaceholderAttributedString(const LayoutContext &layoutContext) const
{
const auto &props = BaseShadowNode::getConcreteProps();
AttributedString attributedString;
attributedString.setBaseTextAttributes(props.getEffectiveTextAttributes(layoutContext.fontSizeMultiplier));
if (!props.placeholder.empty()) {
attributedString.appendFragment(
{.string = props.placeholder,
.textAttributes = attributedString.getBaseTextAttributes(),
.parentShadowView = {}});
}
return attributedString;
}
};
} // namespace facebook::react

View File

@@ -0,0 +1,39 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB rrc_textinput_SRC CONFIGURE_DEPENDS *.cpp platform/android/react/renderer/components/androidtextinput/*.cpp)
add_library(rrc_textinput OBJECT ${rrc_textinput_SRC})
target_include_directories(rrc_textinput PUBLIC . ${CMAKE_CURRENT_SOURCE_DIR}/platform/android/)
target_link_libraries(rrc_textinput
glog
folly_runtime
glog_init
jsi
react_debug
react_renderer_attributedstring
react_renderer_componentregistry
react_renderer_core
react_renderer_debug
react_renderer_graphics
react_renderer_imagemanager
react_renderer_mapbuffer
react_renderer_mounting
react_renderer_textlayoutmanager
react_renderer_uimanager
react_utils
rrc_image
rrc_text
rrc_view
yoga
)
target_compile_reactnative_options(rrc_textinput PRIVATE)
target_compile_options(rrc_textinput PRIVATE -Wpedantic)

View File

@@ -0,0 +1,199 @@
/*
* 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 "TextInputEventEmitter.h"
namespace facebook::react {
static jsi::Value textInputMetricsPayload(
jsi::Runtime& runtime,
const TextInputEventEmitter::Metrics& textInputMetrics,
bool includeSelectionState) {
auto payload = jsi::Object(runtime);
payload.setProperty(
runtime,
"text",
jsi::String::createFromUtf8(runtime, textInputMetrics.text));
payload.setProperty(runtime, "target", textInputMetrics.target);
payload.setProperty(runtime, "eventCount", textInputMetrics.eventCount);
if (includeSelectionState) {
auto selection = jsi::Object(runtime);
selection.setProperty(
runtime, "start", textInputMetrics.selectionRange.location);
selection.setProperty(
runtime,
"end",
textInputMetrics.selectionRange.location +
textInputMetrics.selectionRange.length);
payload.setProperty(runtime, "selection", selection);
}
return payload;
};
static jsi::Value textInputMetricsScrollPayload(
jsi::Runtime& runtime,
const TextInputEventEmitter::Metrics& textInputMetrics) {
auto payload = jsi::Object(runtime);
{
auto contentOffset = jsi::Object(runtime);
contentOffset.setProperty(runtime, "x", textInputMetrics.contentOffset.x);
contentOffset.setProperty(runtime, "y", textInputMetrics.contentOffset.y);
payload.setProperty(runtime, "contentOffset", contentOffset);
}
{
auto contentInset = jsi::Object(runtime);
contentInset.setProperty(runtime, "top", textInputMetrics.contentInset.top);
contentInset.setProperty(
runtime, "left", textInputMetrics.contentInset.left);
contentInset.setProperty(
runtime, "bottom", textInputMetrics.contentInset.bottom);
contentInset.setProperty(
runtime, "right", textInputMetrics.contentInset.right);
payload.setProperty(runtime, "contentInset", contentInset);
}
{
auto contentSize = jsi::Object(runtime);
contentSize.setProperty(
runtime, "width", textInputMetrics.contentSize.width);
contentSize.setProperty(
runtime, "height", textInputMetrics.contentSize.height);
payload.setProperty(runtime, "contentSize", contentSize);
}
{
auto layoutMeasurement = jsi::Object(runtime);
layoutMeasurement.setProperty(
runtime, "width", textInputMetrics.layoutMeasurement.width);
layoutMeasurement.setProperty(
runtime, "height", textInputMetrics.layoutMeasurement.height);
payload.setProperty(runtime, "layoutMeasurement", layoutMeasurement);
}
payload.setProperty(
runtime,
"zoomScale",
textInputMetrics.zoomScale != 0.0f ? textInputMetrics.zoomScale : 1);
return payload;
};
static jsi::Value textInputMetricsContentSizePayload(
jsi::Runtime& runtime,
const TextInputEventEmitter::Metrics& textInputMetrics) {
auto payload = jsi::Object(runtime);
{
auto contentSize = jsi::Object(runtime);
contentSize.setProperty(
runtime, "width", textInputMetrics.contentSize.width);
contentSize.setProperty(
runtime, "height", textInputMetrics.contentSize.height);
payload.setProperty(runtime, "contentSize", contentSize);
}
return payload;
};
static jsi::Value keyPressMetricsPayload(
jsi::Runtime& runtime,
const TextInputEventEmitter::KeyPressMetrics& keyPressMetrics) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "eventCount", keyPressMetrics.eventCount);
std::string key;
if (keyPressMetrics.text.empty()) {
key = "Backspace";
} else {
if (keyPressMetrics.text.front() == '\n') {
key = "Enter";
} else if (keyPressMetrics.text.front() == '\t') {
key = "Tab";
} else if (keyPressMetrics.text.front() == '\x1B') {
key = "Escape";
} else {
key = keyPressMetrics.text;
}
}
payload.setProperty(
runtime, "key", jsi::String::createFromUtf8(runtime, key));
return payload;
};
void TextInputEventEmitter::onFocus(const Metrics& textInputMetrics) const {
dispatchTextInputEvent("focus", textInputMetrics);
}
void TextInputEventEmitter::onBlur(const Metrics& textInputMetrics) const {
dispatchTextInputEvent("blur", textInputMetrics);
}
void TextInputEventEmitter::onChange(const Metrics& textInputMetrics) const {
dispatchTextInputEvent("change", textInputMetrics);
}
void TextInputEventEmitter::onContentSizeChange(
const Metrics& textInputMetrics) const {
dispatchTextInputContentSizeChangeEvent(
"contentSizeChange", textInputMetrics);
}
void TextInputEventEmitter::onSelectionChange(
const Metrics& textInputMetrics) const {
dispatchTextInputEvent("selectionChange", textInputMetrics, true);
}
void TextInputEventEmitter::onEndEditing(
const Metrics& textInputMetrics) const {
dispatchTextInputEvent("endEditing", textInputMetrics);
}
void TextInputEventEmitter::onSubmitEditing(
const Metrics& textInputMetrics) const {
dispatchTextInputEvent("submitEditing", textInputMetrics);
}
void TextInputEventEmitter::onKeyPress(
const KeyPressMetrics& keyPressMetrics) const {
dispatchEvent("keyPress", [keyPressMetrics](jsi::Runtime& runtime) {
return keyPressMetricsPayload(runtime, keyPressMetrics);
});
}
void TextInputEventEmitter::onScroll(const Metrics& textInputMetrics) const {
dispatchEvent("scroll", [textInputMetrics](jsi::Runtime& runtime) {
return textInputMetricsScrollPayload(runtime, textInputMetrics);
});
}
void TextInputEventEmitter::dispatchTextInputEvent(
const std::string& name,
const Metrics& textInputMetrics,
bool includeSelectionState) const {
dispatchEvent(
name, [includeSelectionState, textInputMetrics](jsi::Runtime& runtime) {
return textInputMetricsPayload(
runtime, textInputMetrics, includeSelectionState);
});
}
void TextInputEventEmitter::dispatchTextInputContentSizeChangeEvent(
const std::string& name,
const Metrics& textInputMetrics) const {
dispatchEvent(name, [textInputMetrics](jsi::Runtime& runtime) {
return textInputMetricsContentSizePayload(runtime, textInputMetrics);
});
}
} // namespace facebook::react

View File

@@ -0,0 +1,57 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/components/view/ViewEventEmitter.h>
namespace facebook::react {
class TextInputEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
struct Metrics {
std::string text;
AttributedString::Range selectionRange;
// ScrollView-like metrics
Size contentSize;
Point contentOffset;
EdgeInsets contentInset;
Size containerSize;
int eventCount;
Size layoutMeasurement;
Float zoomScale;
Tag target;
};
struct KeyPressMetrics {
std::string text;
int eventCount;
};
void onFocus(const Metrics &textInputMetrics) const;
void onBlur(const Metrics &textInputMetrics) const;
void onChange(const Metrics &textInputMetrics) const;
void onContentSizeChange(const Metrics &textInputMetrics) const;
void onSelectionChange(const Metrics &textInputMetrics) const;
void onEndEditing(const Metrics &textInputMetrics) const;
void onSubmitEditing(const Metrics &textInputMetrics) const;
void onKeyPress(const KeyPressMetrics &keyPressMetrics) const;
void onScroll(const Metrics &textInputMetrics) const;
private:
void dispatchTextInputEvent(
const std::string &name,
const Metrics &textInputMetrics,
bool includeSelectionState = false) const;
void dispatchTextInputContentSizeChangeEvent(const std::string &name, const Metrics &textInputMetrics) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,58 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
namespace facebook::react {
/*
* State for <TextInput> component.
*/
class TextInputState final {
public:
TextInputState() = default;
TextInputState(
AttributedStringBox attributedStringBox,
AttributedString reactTreeAttributedString,
ParagraphAttributes paragraphAttributes,
int64_t mostRecentEventCount)
: attributedStringBox(std::move(attributedStringBox)),
reactTreeAttributedString(std::move(reactTreeAttributedString)),
paragraphAttributes(std::move(paragraphAttributes)),
mostRecentEventCount(mostRecentEventCount)
{
}
/*
* All content of <TextInput> component.
*/
AttributedStringBox attributedStringBox;
/*
* All content of <TextInput> component represented as an `AttributedString`.
* This stores the previous computed *from the React tree*. This usually
* doesn't change as the TextInput contents are being updated. If it does
* change, we need to wipe out current contents of the TextInput and replace
* with the new value from the tree.
*/
AttributedString reactTreeAttributedString{};
/*
* Represents all visual attributes of a paragraph of text represented as
* a ParagraphAttributes.
*/
ParagraphAttributes paragraphAttributes;
int64_t mostRecentEventCount{0};
};
} // namespace facebook::react

View File

@@ -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.
*/
#pragma once
#include <react/renderer/components/textinput/basePrimitives.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/RawValue.h>
#include <string>
namespace facebook::react {
inline void fromRawValue(const PropsParserContext & /*context*/, const RawValue &value, SubmitBehavior &result)
{
auto string = static_cast<std::string>(value);
if (string == "newline") {
result = SubmitBehavior::Newline;
} else if (string == "submit") {
result = SubmitBehavior::Submit;
} else if (string == "blurAndSubmit") {
result = SubmitBehavior::BlurAndSubmit;
} else {
abort();
}
}
inline folly::dynamic toDynamic(const SubmitBehavior &value)
{
switch (value) {
case SubmitBehavior::Newline:
return "newline";
case SubmitBehavior::Submit:
return "submit";
case SubmitBehavior::BlurAndSubmit:
return "blurAndSubmit";
case SubmitBehavior::Default:
return {nullptr};
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,19 @@
/*
* 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.
*/
#pragma once
namespace facebook::react {
enum class SubmitBehavior {
Default,
Submit,
BlurAndSubmit,
Newline,
};
} // namespace facebook::react

View File

@@ -0,0 +1,153 @@
/*
* 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.
*/
#pragma once
#include "AndroidTextInputShadowNode.h"
#include "AndroidTextInputState.h"
#include <fbjni/fbjni.h>
#include <yoga/YGEnums.h>
#include <yoga/YGValue.h>
#include <unordered_map>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <AndroidTextInput> component.
*/
class AndroidTextInputComponentDescriptor final : public ConcreteComponentDescriptor<AndroidTextInputShadowNode> {
public:
AndroidTextInputComponentDescriptor(const ComponentDescriptorParameters &parameters)
: ConcreteComponentDescriptor<AndroidTextInputShadowNode>(parameters),
textLayoutManager_(std::make_shared<TextLayoutManager>(contextContainer_))
{
}
virtual State::Shared createInitialState(const Props::Shared &props, const ShadowNodeFamily::Shared &family)
const override
{
int surfaceId = family->getSurfaceId();
ThemePadding theme;
// TODO: figure out RTL/start/end/left/right stuff here
if (surfaceIdToThemePaddingMap_.find(surfaceId) != surfaceIdToThemePaddingMap_.end()) {
theme = surfaceIdToThemePaddingMap_[surfaceId];
} else {
const jni::global_ref<jobject> &fabricUIManager =
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
auto env = jni::Environment::current();
auto defaultTextInputPaddingArray = env->NewFloatArray(4);
static auto getThemeData =
jni::findClassStatic(UIManagerJavaDescriptor)->getMethod<jboolean(jint, jfloatArray)>("getThemeData");
if (getThemeData(fabricUIManager, surfaceId, defaultTextInputPaddingArray) != 0u) {
jfloat *defaultTextInputPadding = env->GetFloatArrayElements(defaultTextInputPaddingArray, nullptr);
theme.start = defaultTextInputPadding[0];
theme.end = defaultTextInputPadding[1];
theme.top = defaultTextInputPadding[2];
theme.bottom = defaultTextInputPadding[3];
surfaceIdToThemePaddingMap_.emplace(std::make_pair(surfaceId, theme));
env->ReleaseFloatArrayElements(defaultTextInputPaddingArray, defaultTextInputPadding, JNI_ABORT);
}
env->DeleteLocalRef(defaultTextInputPaddingArray);
}
return std::make_shared<AndroidTextInputShadowNode::ConcreteState>(
std::make_shared<const AndroidTextInputState>(AndroidTextInputState({}, {}, {}, 0)), family);
}
protected:
void adopt(ShadowNode &shadowNode) const override
{
auto &textInputShadowNode = static_cast<AndroidTextInputShadowNode &>(shadowNode);
// `TextInputShadowNode` uses `TextLayoutManager` to measure text content
// and communicate text rendering metrics to mounting layer.
textInputShadowNode.setTextLayoutManager(textLayoutManager_);
int surfaceId = textInputShadowNode.getSurfaceId();
if (surfaceIdToThemePaddingMap_.find(surfaceId) != surfaceIdToThemePaddingMap_.end()) {
const auto &theme = surfaceIdToThemePaddingMap_[surfaceId];
auto &textInputProps = textInputShadowNode.getConcreteProps();
// Override padding
// Node is still unsealed during adoption, before layout is complete
// TODO: T62959168 account for RTL and paddingLeft when setting default
// paddingStart, and vice-versa with paddingRight/paddingEnd.
// For now this assumes no RTL.
auto &style = const_cast<yoga::Style &>(textInputProps.yogaStyle);
bool changedPadding = false;
if (!textInputProps.hasPadding && !textInputProps.hasPaddingStart && !textInputProps.hasPaddingLeft &&
!textInputProps.hasPaddingHorizontal) {
changedPadding = true;
style.setPadding(yoga::Edge::Start, yoga::StyleLength::points(theme.start));
}
if (!textInputProps.hasPadding && !textInputProps.hasPaddingEnd && !textInputProps.hasPaddingRight &&
!textInputProps.hasPaddingHorizontal) {
changedPadding = true;
style.setPadding(yoga::Edge::End, yoga::StyleLength::points(theme.end));
}
if (!textInputProps.hasPadding && !textInputProps.hasPaddingTop && !textInputProps.hasPaddingVertical) {
changedPadding = true;
style.setPadding(yoga::Edge::Top, yoga::StyleLength::points(theme.top));
}
if (!textInputProps.hasPadding && !textInputProps.hasPaddingBottom && !textInputProps.hasPaddingVertical) {
changedPadding = true;
style.setPadding(yoga::Edge::Bottom, yoga::StyleLength::points(theme.bottom));
}
// If the TextInput initially does not have paddingLeft or paddingStart, a
// paddingStart may be set from the theme. If that happens, when there's a
// paddingLeft update, we must explicitly unset paddingStart... (same with
// paddingEnd)
// TODO: support RTL
if ((textInputProps.hasPadding || textInputProps.hasPaddingLeft || textInputProps.hasPaddingHorizontal) &&
!textInputProps.hasPaddingStart) {
style.setPadding(yoga::Edge::Start, yoga::StyleLength::undefined());
}
if ((textInputProps.hasPadding || textInputProps.hasPaddingRight || textInputProps.hasPaddingHorizontal) &&
!textInputProps.hasPaddingEnd) {
style.setPadding(yoga::Edge::End, yoga::StyleLength::undefined());
}
// Note that this is expensive: on every adopt, we need to set the Yoga
// props again, which normally only happens during prop parsing. Every
// commit, state update, etc, will incur this cost.
if (changedPadding) {
// Communicate new props to Yoga part of the node
textInputShadowNode.updateYogaProps();
}
}
textInputShadowNode.dirtyLayout();
ConcreteComponentDescriptor::adopt(shadowNode);
}
private:
struct ThemePadding {
float start{};
float end{};
float top{};
float bottom{};
};
// TODO T68526882: Unify with Binding::UIManagerJavaDescriptor
constexpr static auto UIManagerJavaDescriptor = "com/facebook/react/fabric/FabricUIManager";
const std::shared_ptr<TextLayoutManager> textLayoutManager_;
mutable std::unordered_map<int, ThemePadding> surfaceIdToThemePaddingMap_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/components/view/ViewEventEmitter.h>
namespace facebook::react {
// This emitter exists only as a placeholder and is not used for communication
// with JS.
//
// See:
// - EventEmitterWrapper::invokeEvent for the Android event emitter dispatch
// - ReactTextInputManager.java for the text input events used on Android
class AndroidTextInputEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
};
} // namespace facebook::react

View File

@@ -0,0 +1,636 @@
/*
* 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 "AndroidTextInputProps.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/components/image/conversions.h>
#include <react/renderer/components/textinput/baseConversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/core/propsConversions.h>
#include <react/utils/FloatComparison.h>
namespace facebook::react {
static bool
hasValue(const RawProps& rawProps, bool defaultValue, const char* name) {
auto rawValue = rawProps.at(name, nullptr, nullptr);
// No change to prop - use default
if (rawValue == nullptr) {
return defaultValue;
}
// Value passed from JS
if (rawValue->hasValue()) {
return true;
}
// Null/undefined passed in, indicating that we should use the default
// platform value - thereby resetting this
return false;
}
AndroidTextInputProps::AndroidTextInputProps(
const PropsParserContext &context,
const AndroidTextInputProps &sourceProps,
const RawProps &rawProps)
: BaseTextInputProps(context, sourceProps, rawProps),
autoComplete(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.autoComplete : convertRawProp(
context,
rawProps,
"autoComplete",
sourceProps.autoComplete,
{})),
returnKeyLabel(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.autoComplete : convertRawProp(context, rawProps,
"returnKeyLabel",
sourceProps.returnKeyLabel,
{})),
numberOfLines(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.numberOfLines : convertRawProp(context, rawProps,
"numberOfLines",
sourceProps.numberOfLines,
{0})),
disableFullscreenUI(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.disableFullscreenUI : convertRawProp(context, rawProps,
"disableFullscreenUI",
sourceProps.disableFullscreenUI,
{false})),
textBreakStrategy(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.textBreakStrategy : convertRawProp(context, rawProps,
"textBreakStrategy",
sourceProps.textBreakStrategy,
{})),
inlineImageLeft(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.inlineImageLeft : convertRawProp(context, rawProps,
"inlineImageLeft",
sourceProps.inlineImageLeft,
{})),
inlineImagePadding(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.inlineImagePadding : convertRawProp(context, rawProps,
"inlineImagePadding",
sourceProps.inlineImagePadding,
{0})),
importantForAutofill(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.importantForAutofill : convertRawProp(context, rawProps,
"importantForAutofill",
sourceProps.importantForAutofill,
{})),
showSoftInputOnFocus(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.showSoftInputOnFocus : convertRawProp(context, rawProps,
"showSoftInputOnFocus",
sourceProps.showSoftInputOnFocus,
{false})),
autoCorrect(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.autoCorrect : convertRawProp(context, rawProps,
"autoCorrect",
sourceProps.autoCorrect,
{false})),
allowFontScaling(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.allowFontScaling : convertRawProp(context, rawProps,
"allowFontScaling",
sourceProps.allowFontScaling,
{false})),
maxFontSizeMultiplier(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.maxFontSizeMultiplier : convertRawProp(context, rawProps,
"maxFontSizeMultiplier",
sourceProps.maxFontSizeMultiplier,
{0.0})),
keyboardType(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.keyboardType : convertRawProp(context, rawProps,
"keyboardType",
sourceProps.keyboardType,
{})),
returnKeyType(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.returnKeyType : convertRawProp(context, rawProps,
"returnKeyType",
sourceProps.returnKeyType,
{})),
secureTextEntry(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.secureTextEntry : convertRawProp(context, rawProps,
"secureTextEntry",
sourceProps.secureTextEntry,
{false})),
value(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.value : convertRawProp(context, rawProps, "value", sourceProps.value, {})),
selectTextOnFocus(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.selectTextOnFocus : convertRawProp(context, rawProps,
"selectTextOnFocus",
sourceProps.selectTextOnFocus,
{false})),
caretHidden(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.caretHidden : convertRawProp(context, rawProps,
"caretHidden",
sourceProps.caretHidden,
{false})),
contextMenuHidden(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.contextMenuHidden : convertRawProp(context, rawProps,
"contextMenuHidden",
sourceProps.contextMenuHidden,
{false})),
textShadowColor(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.textShadowColor : convertRawProp(context, rawProps,
"textShadowColor",
sourceProps.textShadowColor,
{})),
textShadowRadius(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.textShadowRadius : convertRawProp(context, rawProps,
"textShadowRadius",
sourceProps.textShadowRadius,
{0.0})),
textDecorationLine(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.textDecorationLine : convertRawProp(context, rawProps,
"textDecorationLine",
sourceProps.textDecorationLine,
{})),
fontStyle(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.fontStyle :
convertRawProp(context, rawProps, "fontStyle", sourceProps.fontStyle, {})),
textShadowOffset(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.textShadowOffset : convertRawProp(context, rawProps,
"textShadowOffset",
sourceProps.textShadowOffset,
{})),
lineHeight(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.lineHeight : convertRawProp(context, rawProps,
"lineHeight",
sourceProps.lineHeight,
{0.0})),
textTransform(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.textTransform : convertRawProp(context, rawProps,
"textTransform",
sourceProps.textTransform,
{})),
color(0 /*convertRawProp(context, rawProps, "color", sourceProps.color, {0})*/),
letterSpacing(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.letterSpacing : convertRawProp(context, rawProps,
"letterSpacing",
sourceProps.letterSpacing,
{0.0})),
fontSize(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.fontSize :
convertRawProp(context, rawProps, "fontSize", sourceProps.fontSize, {0.0})),
textAlign(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.textAlign :
convertRawProp(context, rawProps, "textAlign", sourceProps.textAlign, {})),
includeFontPadding(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.includeFontPadding : convertRawProp(context, rawProps,
"includeFontPadding",
sourceProps.includeFontPadding,
{false})),
fontWeight(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.fontWeight :
convertRawProp(context, rawProps, "fontWeight", sourceProps.fontWeight, {})),
fontFamily(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.fontFamily :
convertRawProp(context, rawProps, "fontFamily", sourceProps.fontFamily, {})),
// See AndroidTextInputComponentDescriptor for usage
// TODO T63008435: can these, and this feature, be removed entirely?
hasPadding(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.hasPadding : hasValue(rawProps, sourceProps.hasPadding, "padding")),
hasPaddingHorizontal(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.hasPaddingHorizontal : hasValue(
rawProps,
sourceProps.hasPaddingHorizontal,
"paddingHorizontal")),
hasPaddingVertical(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.hasPaddingVertical : hasValue(
rawProps,
sourceProps.hasPaddingVertical,
"paddingVertical")),
hasPaddingLeft(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.hasPaddingLeft : hasValue(
rawProps,
sourceProps.hasPaddingLeft,
"paddingLeft")),
hasPaddingTop(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.hasPaddingTop :
hasValue(rawProps, sourceProps.hasPaddingTop, "paddingTop")),
hasPaddingRight(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.hasPaddingRight : hasValue(
rawProps,
sourceProps.hasPaddingRight,
"paddingRight")),
hasPaddingBottom(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.hasPaddingBottom : hasValue(
rawProps,
sourceProps.hasPaddingBottom,
"paddingBottom")),
hasPaddingStart(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.hasPaddingStart : hasValue(
rawProps,
sourceProps.hasPaddingStart,
"paddingStart")),
hasPaddingEnd(ReactNativeFeatureFlags::enableCppPropsIteratorSetter()? sourceProps.hasPaddingEnd :
hasValue(rawProps, sourceProps.hasPaddingEnd, "paddingEnd")) {
}
void AndroidTextInputProps::setProp(
const PropsParserContext& context,
RawPropsPropNameHash hash,
const char* propName,
const RawValue& value) {
// All Props structs setProp methods must always, unconditionally,
// call all super::setProp methods, since multiple structs may
// reuse the same values.
BaseTextInputProps::setProp(context, hash, propName, value);
static auto defaults = AndroidTextInputProps{};
switch (hash) {
RAW_SET_PROP_SWITCH_CASE_BASIC(autoComplete);
RAW_SET_PROP_SWITCH_CASE_BASIC(returnKeyLabel);
RAW_SET_PROP_SWITCH_CASE_BASIC(numberOfLines);
RAW_SET_PROP_SWITCH_CASE_BASIC(disableFullscreenUI);
RAW_SET_PROP_SWITCH_CASE_BASIC(textBreakStrategy);
RAW_SET_PROP_SWITCH_CASE_BASIC(inlineImageLeft);
RAW_SET_PROP_SWITCH_CASE_BASIC(inlineImagePadding);
RAW_SET_PROP_SWITCH_CASE_BASIC(importantForAutofill);
RAW_SET_PROP_SWITCH_CASE_BASIC(showSoftInputOnFocus);
RAW_SET_PROP_SWITCH_CASE_BASIC(autoCorrect);
RAW_SET_PROP_SWITCH_CASE_BASIC(allowFontScaling);
RAW_SET_PROP_SWITCH_CASE_BASIC(maxFontSizeMultiplier);
RAW_SET_PROP_SWITCH_CASE_BASIC(keyboardType);
RAW_SET_PROP_SWITCH_CASE_BASIC(returnKeyType);
RAW_SET_PROP_SWITCH_CASE_BASIC(secureTextEntry);
RAW_SET_PROP_SWITCH_CASE_BASIC(selectTextOnFocus);
RAW_SET_PROP_SWITCH_CASE_BASIC(caretHidden);
RAW_SET_PROP_SWITCH_CASE_BASIC(contextMenuHidden);
RAW_SET_PROP_SWITCH_CASE_BASIC(textShadowColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(textShadowRadius);
RAW_SET_PROP_SWITCH_CASE_BASIC(textDecorationLine);
RAW_SET_PROP_SWITCH_CASE_BASIC(fontStyle);
RAW_SET_PROP_SWITCH_CASE_BASIC(textShadowOffset);
RAW_SET_PROP_SWITCH_CASE_BASIC(lineHeight);
RAW_SET_PROP_SWITCH_CASE_BASIC(textTransform);
// RAW_SET_PROP_SWITCH_CASE_BASIC(color);
RAW_SET_PROP_SWITCH_CASE_BASIC(letterSpacing);
RAW_SET_PROP_SWITCH_CASE_BASIC(fontSize);
RAW_SET_PROP_SWITCH_CASE_BASIC(textAlign);
RAW_SET_PROP_SWITCH_CASE_BASIC(includeFontPadding);
RAW_SET_PROP_SWITCH_CASE_BASIC(fontWeight);
RAW_SET_PROP_SWITCH_CASE_BASIC(fontFamily);
case CONSTEXPR_RAW_PROPS_KEY_HASH("value"): {
fromRawValue(context, value, this->value, {});
return;
}
// Paddings are not parsed at this level of the component (they're parsed in
// ViewProps) but we do need to know if they're present or not. See
// AndroidTextInputComponentDescriptor for usage
// TODO T63008435: can these, and this feature, be removed entirely?
case CONSTEXPR_RAW_PROPS_KEY_HASH("padding"): {
hasPadding = value.hasValue();
return;
}
case CONSTEXPR_RAW_PROPS_KEY_HASH("paddingHorizontal"): {
hasPaddingHorizontal = value.hasValue();
return;
}
case CONSTEXPR_RAW_PROPS_KEY_HASH("paddingVertical"): {
hasPaddingVertical = value.hasValue();
return;
}
case CONSTEXPR_RAW_PROPS_KEY_HASH("paddingLeft"): {
hasPaddingLeft = value.hasValue();
return;
}
case CONSTEXPR_RAW_PROPS_KEY_HASH("paddingRight"): {
hasPaddingRight = value.hasValue();
return;
}
case CONSTEXPR_RAW_PROPS_KEY_HASH("paddingTop"): {
hasPaddingTop = value.hasValue();
return;
}
case CONSTEXPR_RAW_PROPS_KEY_HASH("paddingBottom"): {
hasPaddingBottom = value.hasValue();
return;
}
case CONSTEXPR_RAW_PROPS_KEY_HASH("paddingStart"): {
hasPaddingStart = value.hasValue();
return;
}
case CONSTEXPR_RAW_PROPS_KEY_HASH("paddingEnd"): {
hasPaddingEnd = value.hasValue();
return;
}
}
}
// TODO T53300085: support this in codegen; this was hand-written
folly::dynamic AndroidTextInputProps::getDynamic() const {
folly::dynamic props = folly::dynamic::object();
props["autoComplete"] = autoComplete;
props["returnKeyLabel"] = returnKeyLabel;
props["numberOfLines"] = numberOfLines;
props["disableFullscreenUI"] = disableFullscreenUI;
props["textBreakStrategy"] = textBreakStrategy;
props["underlineColorAndroid"] = toAndroidRepr(underlineColorAndroid);
props["inlineImageLeft"] = inlineImageLeft;
props["inlineImagePadding"] = inlineImagePadding;
props["importantForAutofill"] = importantForAutofill;
props["showSoftInputOnFocus"] = showSoftInputOnFocus;
props["autoCapitalize"] = autoCapitalize;
props["autoCorrect"] = autoCorrect;
props["autoFocus"] = autoFocus;
props["allowFontScaling"] = allowFontScaling;
props["maxFontSizeMultiplier"] = maxFontSizeMultiplier;
props["keyboardType"] = keyboardType;
props["returnKeyType"] = returnKeyType;
props["maxLength"] = maxLength;
props["multiline"] = multiline;
props["placeholder"] = placeholder;
props["placeholderTextColor"] = toAndroidRepr(placeholderTextColor);
props["secureTextEntry"] = secureTextEntry;
props["selectionColor"] = toAndroidRepr(selectionColor);
props["selectionHandleColor"] = toAndroidRepr(selectionHandleColor);
props["value"] = value;
props["defaultValue"] = defaultValue;
props["selectTextOnFocus"] = selectTextOnFocus;
props["submitBehavior"] = toDynamic(submitBehavior);
props["caretHidden"] = caretHidden;
props["contextMenuHidden"] = contextMenuHidden;
props["textShadowColor"] = toAndroidRepr(textShadowColor);
props["textShadowRadius"] = textShadowRadius;
props["textDecorationLine"] = textDecorationLine;
props["fontStyle"] = fontStyle;
props["textShadowOffset"] = toDynamic(textShadowOffset);
props["lineHeight"] = lineHeight;
props["textTransform"] = textTransform;
props["color"] = toAndroidRepr(color);
props["letterSpacing"] = letterSpacing;
props["fontSize"] = fontSize;
props["textAlign"] = textAlign;
props["includeFontPadding"] = includeFontPadding;
props["fontWeight"] = fontWeight;
props["fontFamily"] = fontFamily;
props["cursorColor"] = toAndroidRepr(cursorColor);
props["mostRecentEventCount"] = mostRecentEventCount;
props["text"] = text;
props["hasPadding"] = hasPadding;
props["hasPaddingHorizontal"] = hasPaddingHorizontal;
props["hasPaddingVertical"] = hasPaddingVertical;
props["hasPaddingStart"] = hasPaddingStart;
props["hasPaddingEnd"] = hasPaddingEnd;
props["hasPaddingLeft"] = hasPaddingLeft;
props["hasPaddingRight"] = hasPaddingRight;
props["hasPaddingTop"] = hasPaddingTop;
props["hasPaddingBottom"] = hasPaddingBottom;
return props;
}
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
// TODO: codegen these
SharedDebugStringConvertibleList AndroidTextInputProps::getDebugProps() const {
return {};
}
#endif
ComponentName AndroidTextInputProps::getDiffPropsImplementationTarget() const {
return "TextInput";
}
folly::dynamic AndroidTextInputProps::getDiffProps(
const Props* prevProps) const {
static const auto defaultProps = AndroidTextInputProps();
const AndroidTextInputProps* oldProps = prevProps == nullptr
? &defaultProps
: static_cast<const AndroidTextInputProps*>(prevProps);
folly::dynamic result = ViewProps::getDiffProps(oldProps);
// Base text input paragraph props
if (paragraphAttributes.maximumNumberOfLines !=
oldProps->paragraphAttributes.maximumNumberOfLines) {
result["numberOfLines"] = paragraphAttributes.maximumNumberOfLines;
}
if (paragraphAttributes.ellipsizeMode !=
oldProps->paragraphAttributes.ellipsizeMode) {
result["ellipsizeMode"] = toString(paragraphAttributes.ellipsizeMode);
}
if (paragraphAttributes.textBreakStrategy !=
oldProps->paragraphAttributes.textBreakStrategy) {
result["textBreakStrategy"] =
toString(paragraphAttributes.textBreakStrategy);
}
if (paragraphAttributes.adjustsFontSizeToFit !=
oldProps->paragraphAttributes.adjustsFontSizeToFit) {
result["adjustsFontSizeToFit"] = paragraphAttributes.adjustsFontSizeToFit;
}
if (!floatEquality(
paragraphAttributes.minimumFontSize,
oldProps->paragraphAttributes.minimumFontSize)) {
result["minimumFontSize"] = paragraphAttributes.minimumFontSize;
}
if (!floatEquality(
paragraphAttributes.maximumFontSize,
oldProps->paragraphAttributes.maximumFontSize)) {
result["maximumFontSize"] = paragraphAttributes.maximumFontSize;
}
if (paragraphAttributes.includeFontPadding !=
oldProps->paragraphAttributes.includeFontPadding) {
result["includeFontPadding"] = paragraphAttributes.includeFontPadding;
}
if (paragraphAttributes.android_hyphenationFrequency !=
oldProps->paragraphAttributes.android_hyphenationFrequency) {
result["android_hyphenationFrequency"] =
toString(paragraphAttributes.android_hyphenationFrequency);
}
if (paragraphAttributes.textAlignVertical !=
oldProps->paragraphAttributes.textAlignVertical) {
if (!paragraphAttributes.textAlignVertical.has_value()) {
result["textAlignVertical"] = nullptr;
} else {
result["textAlignVertical"] =
toString(*paragraphAttributes.textAlignVertical);
}
}
// Base text input props
if (defaultValue != oldProps->defaultValue) {
result["defaultValue"] = defaultValue;
}
if (placeholder != oldProps->placeholder) {
result["placeholder"] = placeholder;
}
if (placeholderTextColor != oldProps->placeholderTextColor) {
result["placeholderTextColor"] = *placeholderTextColor;
}
if (cursorColor != oldProps->cursorColor) {
result["cursorColor"] = *cursorColor;
}
if (selectionColor != oldProps->selectionColor) {
result["selectionColor"] = *selectionColor;
}
if (selectionHandleColor != oldProps->selectionHandleColor) {
result["selectionHandleColor"] = *selectionHandleColor;
}
if (underlineColorAndroid != oldProps->underlineColorAndroid) {
result["underlineColorAndroid"] = *underlineColorAndroid;
}
if (maxLength != oldProps->maxLength) {
result["maxLength"] = maxLength;
}
if (text != oldProps->text) {
result["text"] = text;
}
if (mostRecentEventCount != oldProps->mostRecentEventCount) {
result["mostRecentEventCount"] = mostRecentEventCount;
}
if (autoFocus != oldProps->autoFocus) {
result["autoFocus"] = autoFocus;
}
if (autoCapitalize != oldProps->autoCapitalize) {
result["autoCapitalize"] = autoCapitalize;
}
if (editable != oldProps->editable) {
result["editable"] = editable;
}
if (readOnly != oldProps->readOnly) {
result["readOnly"] = readOnly;
}
if (submitBehavior != oldProps->submitBehavior) {
result["submitBehavior"] = toDynamic(submitBehavior);
}
if (multiline != oldProps->multiline) {
result["multiline"] = multiline;
}
if (disableKeyboardShortcuts != oldProps->disableKeyboardShortcuts) {
result["disableKeyboardShortcuts"] = disableKeyboardShortcuts;
}
if (acceptDragAndDropTypes != oldProps->acceptDragAndDropTypes) {
result["acceptDragAndDropTypes"] = acceptDragAndDropTypes.has_value()
? toDynamic(acceptDragAndDropTypes.value())
: nullptr;
}
// Android text input props
if (autoComplete != oldProps->autoComplete) {
result["autoComplete"] = autoComplete;
}
if (returnKeyLabel != oldProps->returnKeyLabel) {
result["returnKeyLabel"] = returnKeyLabel;
}
if (numberOfLines != oldProps->numberOfLines) {
result["numberOfLines"] = numberOfLines;
}
if (disableFullscreenUI != oldProps->disableFullscreenUI) {
result["disableFullscreenUI"] = disableFullscreenUI;
}
if (textBreakStrategy != oldProps->textBreakStrategy) {
result["textBreakStrategy"] = textBreakStrategy;
}
if (inlineImageLeft != oldProps->inlineImageLeft) {
result["inlineImageLeft"] = inlineImageLeft;
}
if (inlineImagePadding != oldProps->inlineImagePadding) {
result["inlineImagePadding"] = inlineImagePadding;
}
if (importantForAutofill != oldProps->importantForAutofill) {
result["importantForAutofill"] = importantForAutofill;
}
if (showSoftInputOnFocus != oldProps->showSoftInputOnFocus) {
result["showSoftInputOnFocus"] = showSoftInputOnFocus;
}
if (autoCorrect != oldProps->autoCorrect) {
result["autoCorrect"] = autoCorrect;
}
if (allowFontScaling != oldProps->allowFontScaling) {
result["allowFontScaling"] = allowFontScaling;
}
if (maxFontSizeMultiplier != oldProps->maxFontSizeMultiplier) {
result["maxFontSizeMultiplier"] = maxFontSizeMultiplier;
}
if (keyboardType != oldProps->keyboardType) {
result["keyboardType"] = keyboardType;
}
if (returnKeyType != oldProps->returnKeyType) {
result["returnKeyType"] = returnKeyType;
}
if (secureTextEntry != oldProps->secureTextEntry) {
result["secureTextEntry"] = secureTextEntry;
}
if (value != oldProps->value) {
result["value"] = value;
}
if (selectTextOnFocus != oldProps->selectTextOnFocus) {
result["selectTextOnFocus"] = selectTextOnFocus;
}
if (caretHidden != oldProps->caretHidden) {
result["caretHidden"] = caretHidden;
}
if (contextMenuHidden != oldProps->contextMenuHidden) {
result["contextMenuHidden"] = contextMenuHidden;
}
if (textShadowColor != oldProps->textShadowColor) {
result["textShadowColor"] = *textShadowColor;
}
if (textShadowRadius != oldProps->textShadowRadius) {
result["textShadowRadius"] = textShadowRadius;
}
if (textDecorationLine != oldProps->textDecorationLine) {
result["textDecorationLine"] = textDecorationLine;
}
if (fontStyle != oldProps->fontStyle) {
result["fontStyle"] = fontStyle;
}
if (textShadowOffset != oldProps->textShadowOffset) {
result["textShadowOffset"] = toDynamic(textShadowOffset);
}
if (lineHeight != oldProps->lineHeight) {
result["lineHeight"] = lineHeight;
}
if (textTransform != oldProps->textTransform) {
result["textTransform"] = textTransform;
}
if (letterSpacing != oldProps->letterSpacing) {
result["letterSpacing"] = letterSpacing;
}
if (fontSize != oldProps->fontSize) {
result["fontSize"] = fontSize;
}
if (textAlign != oldProps->textAlign) {
result["textAlign"] = textAlign;
}
if (includeFontPadding != oldProps->includeFontPadding) {
result["includeFontPadding"] = includeFontPadding;
}
if (fontWeight != oldProps->fontWeight) {
result["fontWeight"] = fontWeight;
}
if (fontFamily != oldProps->fontFamily) {
result["fontFamily"] = fontFamily;
}
return result;
}
} // namespace facebook::react

View File

@@ -0,0 +1,137 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/core/Props.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/attributedstring/TextAttributes.h>
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/components/textinput/BaseTextInputProps.h>
#include <react/renderer/components/textinput/basePrimitives.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/imagemanager/primitives.h>
#include <unordered_map>
namespace facebook::react {
struct AndroidTextInputTextShadowOffsetStruct {
double width;
double height;
};
inline static bool operator==(
const AndroidTextInputTextShadowOffsetStruct &lhs,
const AndroidTextInputTextShadowOffsetStruct &rhs)
{
return lhs.width == rhs.width && lhs.height == rhs.height;
}
static inline void
fromRawValue(const PropsParserContext &context, const RawValue &value, AndroidTextInputTextShadowOffsetStruct &result)
{
auto map = (std::unordered_map<std::string, RawValue>)value;
auto width = map.find("width");
if (width != map.end()) {
fromRawValue(context, width->second, result.width);
}
auto height = map.find("height");
if (height != map.end()) {
fromRawValue(context, height->second, result.height);
}
}
static inline std::string toString(const AndroidTextInputTextShadowOffsetStruct &value)
{
return "[Object AndroidTextInputTextShadowOffsetStruct]";
}
inline folly::dynamic toDynamic(const AndroidTextInputTextShadowOffsetStruct &value)
{
folly::dynamic dynamicValue = folly::dynamic::object();
dynamicValue["width"] = value.width;
dynamicValue["height"] = value.height;
return dynamicValue;
}
class AndroidTextInputProps final : public BaseTextInputProps {
public:
AndroidTextInputProps() = default;
AndroidTextInputProps(
const PropsParserContext &context,
const AndroidTextInputProps &sourceProps,
const RawProps &rawProps);
void
setProp(const PropsParserContext &context, RawPropsPropNameHash hash, const char *propName, const RawValue &value);
folly::dynamic getDynamic() const;
#pragma mark - Props
std::string autoComplete{};
std::string returnKeyLabel{};
int numberOfLines{0};
bool disableFullscreenUI{false};
std::string textBreakStrategy{};
std::string inlineImageLeft{};
int inlineImagePadding{0};
std::string importantForAutofill{};
bool showSoftInputOnFocus{false};
bool autoCorrect{false};
bool allowFontScaling{false};
Float maxFontSizeMultiplier{0.0};
std::string keyboardType{};
std::string returnKeyType{};
bool secureTextEntry{false};
std::string value{};
bool selectTextOnFocus{false};
bool caretHidden{false};
bool contextMenuHidden{false};
SharedColor textShadowColor{};
Float textShadowRadius{0.0};
std::string textDecorationLine{};
std::string fontStyle{};
AndroidTextInputTextShadowOffsetStruct textShadowOffset{};
Float lineHeight{0.0};
std::string textTransform{};
SharedColor color{0};
Float letterSpacing{0.0};
Float fontSize{0.0};
std::string textAlign{};
bool includeFontPadding{false};
std::string fontWeight{};
std::string fontFamily{};
/**
* Auxiliary information to detect if these props are set or not.
* See AndroidTextInputComponentDescriptor for usage.
* TODO T63008435: can these, and this feature, be removed entirely?
*/
bool hasPadding{};
bool hasPaddingHorizontal{};
bool hasPaddingVertical{};
bool hasPaddingLeft{};
bool hasPaddingTop{};
bool hasPaddingRight{};
bool hasPaddingBottom{};
bool hasPaddingStart{};
bool hasPaddingEnd{};
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const override;
#endif
ComponentName getDiffPropsImplementationTarget() const override;
folly::dynamic getDiffProps(const Props *prevProps) const override;
};
} // namespace facebook::react

View File

@@ -0,0 +1,235 @@
/*
* 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 "AndroidTextInputShadowNode.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/attributedstring/TextAttributes.h>
#include <react/renderer/components/text/BaseTextShadowNode.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/core/conversions.h>
#include <react/renderer/textlayoutmanager/TextLayoutContext.h>
namespace facebook::react {
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
extern const char AndroidTextInputComponentName[] = "AndroidTextInput";
void AndroidTextInputShadowNode::setTextLayoutManager(
std::shared_ptr<const TextLayoutManager> textLayoutManager) {
ensureUnsealed();
textLayoutManager_ = std::move(textLayoutManager);
}
Size AndroidTextInputShadowNode::measureContent(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const {
auto textConstraints = getTextConstraints(layoutConstraints);
TextLayoutContext textLayoutContext{
.pointScaleFactor = layoutContext.pointScaleFactor,
.surfaceId = getSurfaceId(),
};
if (getStateData().cachedAttributedStringId != 0) {
auto textSize = textLayoutManager_
->measureCachedSpannableById(
getStateData().cachedAttributedStringId,
getConcreteProps().paragraphAttributes,
textLayoutContext,
textConstraints)
.size;
return layoutConstraints.clamp(textSize);
}
// Layout is called right after measure.
// Measure is marked as `const`, and `layout` is not; so State can be
// updated during layout, but not during `measure`. If State is out-of-date
// in layout, it's too late: measure will have already operated on old
// State. Thus, we use the same value here that we *will* use in layout to
// update the state.
AttributedString attributedString =
getMostRecentAttributedString(layoutContext);
if (attributedString.isEmpty()) {
attributedString = getPlaceholderAttributedString(layoutContext);
}
auto textSize = textLayoutManager_
->measure(
AttributedStringBox{attributedString},
getConcreteProps().paragraphAttributes,
textLayoutContext,
textConstraints)
.size;
return layoutConstraints.clamp(textSize);
}
void AndroidTextInputShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded(layoutContext);
ConcreteViewShadowNode::layout(layoutContext);
}
Float AndroidTextInputShadowNode::baseline(
const LayoutContext& layoutContext,
Size size) const {
AttributedString attributedString =
getMostRecentAttributedString(layoutContext);
if (attributedString.isEmpty()) {
attributedString = getPlaceholderAttributedString(layoutContext);
}
// Yoga expects a baseline relative to the Node's border-box edge instead of
// the content, so we need to adjust by the padding and border widths, which
// have already been set by the time of baseline alignment
auto top = YGNodeLayoutGetBorder(&yogaNode_, YGEdgeTop) +
YGNodeLayoutGetPadding(&yogaNode_, YGEdgeTop);
AttributedStringBox attributedStringBox{attributedString};
return LineMeasurement::baseline(textLayoutManager_->measureLines(
attributedStringBox,
getConcreteProps().paragraphAttributes,
size)) +
top;
}
LayoutConstraints AndroidTextInputShadowNode::getTextConstraints(
const LayoutConstraints& layoutConstraints) const {
if (getConcreteProps().multiline) {
return layoutConstraints;
} else {
// A single line TextInput acts as a horizontal scroller of infinitely
// expandable text, so we want to measure the text as if it is allowed to
// infinitely expand horizontally, and later clamp to the constraints of the
// input.
return LayoutConstraints{
.minimumSize = layoutConstraints.minimumSize,
.maximumSize =
Size{
.width = std::numeric_limits<Float>::infinity(),
.height = layoutConstraints.maximumSize.height,
},
.layoutDirection = layoutConstraints.layoutDirection,
};
}
}
void AndroidTextInputShadowNode::updateStateIfNeeded(
const LayoutContext& layoutContext) {
ensureUnsealed();
const auto& stateData = getStateData();
auto reactTreeAttributedString = getAttributedString(layoutContext);
// Tree is often out of sync with the value of the TextInput.
// This is by design - don't change the value of the TextInput in the State,
// and therefore in Java, unless the tree itself changes.
if (stateData.reactTreeAttributedString == reactTreeAttributedString) {
return;
}
// If props event counter is less than what we already have in state, skip it
const auto& props = BaseShadowNode::getConcreteProps();
if (props.mostRecentEventCount < stateData.mostRecentEventCount) {
return;
}
// Even if we're here and updating state, it may be only to update the layout
// manager If that is the case, make sure we don't update text: pass in the
// current attributedString unchanged, and pass in zero for the "event count"
// so no changes are applied There's no way to prevent a state update from
// flowing to Java, so we just ensure it's a noop in those cases.
auto newEventCount = stateData.reactTreeAttributedString.isContentEqual(
reactTreeAttributedString)
? 0
: props.mostRecentEventCount;
auto newAttributedString = getMostRecentAttributedString(layoutContext);
setStateData(
AndroidTextInputState{
AttributedStringBox(newAttributedString),
reactTreeAttributedString,
props.paragraphAttributes,
newEventCount});
}
AttributedString AndroidTextInputShadowNode::getAttributedString(
const LayoutContext& layoutContext) const {
// Use BaseTextShadowNode to get attributed string from children
auto childTextAttributes = TextAttributes::defaultTextAttributes();
childTextAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
childTextAttributes.apply(getConcreteProps().textAttributes);
// Don't propagate the background color of the TextInput onto the attributed
// string. Android tries to render shadow of the background alongside the
// shadow of the text which results in weird artifacts.
childTextAttributes.backgroundColor = HostPlatformColor::UndefinedColor;
auto attributedString = AttributedString{};
auto attachments = BaseTextShadowNode::Attachments{};
BaseTextShadowNode::buildAttributedString(
childTextAttributes, *this, attributedString, attachments);
attributedString.setBaseTextAttributes(childTextAttributes);
// BaseTextShadowNode only gets children. We must detect and prepend text
// value attributes manually.
if (!getConcreteProps().text.empty()) {
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.apply(getConcreteProps().textAttributes);
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
auto fragment = AttributedString::Fragment{};
fragment.string = getConcreteProps().text;
fragment.textAttributes = textAttributes;
// If the TextInput opacity is 0 < n < 1, the opacity of the TextInput and
// text value's background will stack. This is a hack/workaround to prevent
// that effect.
fragment.textAttributes.backgroundColor = clearColor();
fragment.parentShadowView = ShadowView(*this);
attributedString.prependFragment(std::move(fragment));
}
return attributedString;
}
AttributedString AndroidTextInputShadowNode::getMostRecentAttributedString(
const LayoutContext& layoutContext) const {
const auto& state = getStateData();
auto reactTreeAttributedString = getAttributedString(layoutContext);
// Sometimes the treeAttributedString will only differ from the state
// not by inherent properties (string or prop attributes), but by the frame of
// the parent which has changed Thus, we can't directly compare the entire
// AttributedString
bool treeAttributedStringChanged =
!state.reactTreeAttributedString.compareTextAttributesWithoutFrame(
reactTreeAttributedString);
return (
!treeAttributedStringChanged ? state.attributedStringBox.getValue()
: reactTreeAttributedString);
}
AttributedString AndroidTextInputShadowNode::getPlaceholderAttributedString(
const LayoutContext& layoutContext) const {
const auto& props = BaseShadowNode::getConcreteProps();
AttributedString attributedString;
attributedString.setBaseTextAttributes(
props.getEffectiveTextAttributes(layoutContext.fontSizeMultiplier));
if (!props.placeholder.empty()) {
attributedString.appendFragment(
{.string = props.placeholder,
.textAttributes = attributedString.getBaseTextAttributes(),
.parentShadowView = {}});
}
return attributedString;
}
} // namespace facebook::react

View File

@@ -0,0 +1,83 @@
/*
* 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.
*/
#pragma once
#include "AndroidTextInputEventEmitter.h"
#include "AndroidTextInputProps.h"
#include "AndroidTextInputState.h"
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/utils/ContextContainer.h>
namespace facebook::react {
extern const char AndroidTextInputComponentName[];
/*
* `ShadowNode` for <AndroidTextInput> component.
*/
class AndroidTextInputShadowNode final : public ConcreteViewShadowNode<
AndroidTextInputComponentName,
AndroidTextInputProps,
AndroidTextInputEventEmitter,
AndroidTextInputState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits()
{
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
traits.set(ShadowNodeTraits::Trait::BaselineYogaNode);
return traits;
}
/*
* Associates a shared TextLayoutManager with the node.
* `TextInputShadowNode` uses the manager to measure text content
* and construct `TextInputState` objects.
*/
void setTextLayoutManager(std::shared_ptr<const TextLayoutManager> textLayoutManager);
protected:
Size measureContent(const LayoutContext &layoutContext, const LayoutConstraints &layoutConstraints) const override;
void layout(LayoutContext layoutContext) override;
Float baseline(const LayoutContext &layoutContext, Size size) const override;
std::shared_ptr<const TextLayoutManager> textLayoutManager_;
/*
* Determines the constraints to use while measure the underlying text
*/
LayoutConstraints getTextConstraints(const LayoutConstraints &layoutConstraints) const;
private:
/*
* Creates a `State` object (with `AttributedText` and
* `TextLayoutManager`) if needed.
*/
void updateStateIfNeeded(const LayoutContext &layoutContext);
/*
* Returns a `AttributedString` which represents text content of the node.
*/
AttributedString getAttributedString(const LayoutContext &layoutContext) const;
/**
* Get the most up-to-date attributed string for measurement and State.
*/
AttributedString getMostRecentAttributedString(const LayoutContext &layoutContext) const;
AttributedString getPlaceholderAttributedString(const LayoutContext &layoutContext) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,42 @@
/*
* 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 "AndroidTextInputState.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/components/text/ParagraphState.h>
namespace facebook::react {
folly::dynamic AndroidTextInputState::getDynamic() const {
LOG(FATAL) << "TextInputState state should only be read using MapBuffer";
}
MapBuffer AndroidTextInputState::getMapBuffer() const {
auto builder = MapBufferBuilder();
// If we have a `cachedAttributedStringId` we know that we're (1) not trying
// to set a new string, so we don't need to pass it along; (2) setState was
// called from Java to trigger a relayout with a `cachedAttributedStringId`,
// so Java has all up-to-date information and we should pass an empty map
// through.
if (cachedAttributedStringId == 0) {
// TODO truncation
builder.putInt(
TX_STATE_KEY_MOST_RECENT_EVENT_COUNT,
static_cast<int32_t>(mostRecentEventCount));
auto attStringMapBuffer = toMapBuffer(attributedStringBox.getValue());
builder.putMapBuffer(TX_STATE_KEY_ATTRIBUTED_STRING, attStringMapBuffer);
auto paMapBuffer = toMapBuffer(paragraphAttributes);
builder.putMapBuffer(TX_STATE_KEY_PARAGRAPH_ATTRIBUTES, paMapBuffer);
builder.putInt(TX_STATE_KEY_HASH, attStringMapBuffer.getInt(AS_KEY_HASH));
}
return builder.build();
}
} // namespace facebook::react

View File

@@ -0,0 +1,79 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
namespace facebook::react {
/*
* State for <TextInput> component.
*/
class AndroidTextInputState final {
public:
AndroidTextInputState() = default;
AndroidTextInputState(
AttributedStringBox attributedStringBox,
AttributedString reactTreeAttributedString,
ParagraphAttributes paragraphAttributes,
int64_t mostRecentEventCount)
: attributedStringBox(std::move(attributedStringBox)),
reactTreeAttributedString(std::move(reactTreeAttributedString)),
paragraphAttributes(std::move(paragraphAttributes)),
mostRecentEventCount(mostRecentEventCount)
{
}
AndroidTextInputState(const AndroidTextInputState &previousState, const folly::dynamic &data)
: attributedStringBox(previousState.attributedStringBox),
reactTreeAttributedString(previousState.reactTreeAttributedString),
paragraphAttributes(previousState.paragraphAttributes),
mostRecentEventCount(data.getDefault("mostRecentEventCount", previousState.mostRecentEventCount).getInt()),
cachedAttributedStringId(data.getDefault("opaqueCacheId", previousState.cachedAttributedStringId).getInt())
{
}
folly::dynamic getDynamic() const;
MapBuffer getMapBuffer() const;
/*
* All content of <TextInput> component.
*/
AttributedStringBox attributedStringBox;
/*
* All content of <TextInput> component represented as an `AttributedString`.
* This stores the previous computed *from the React tree*. This usually
* doesn't change as the TextInput contents are being updated. If it does
* change, we need to wipe out current contents of the TextInput and replace
* with the new value from the tree.
*/
AttributedString reactTreeAttributedString{};
/*
* Represents all visual attributes of a paragraph of text represented as
* a ParagraphAttributes.
*/
ParagraphAttributes paragraphAttributes;
int64_t mostRecentEventCount{0};
/**
* Stores an opaque cache ID used on the Java side to refer to a specific
* AttributedString for measurement purposes only.
*/
int64_t cachedAttributedStringId{0};
};
} // namespace facebook::react

View File

@@ -0,0 +1,39 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/components/iostextinput/TextInputShadowNode.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
namespace facebook::react {
/*
* Descriptor for <TextInput> component.
*/
class TextInputComponentDescriptor final : public ConcreteComponentDescriptor<TextInputShadowNode> {
public:
TextInputComponentDescriptor(const ComponentDescriptorParameters &parameters)
: ConcreteComponentDescriptor<TextInputShadowNode>(parameters),
textLayoutManager_(std::make_shared<TextLayoutManager>(contextContainer_))
{
}
protected:
void adopt(ShadowNode &shadowNode) const override
{
ConcreteComponentDescriptor::adopt(shadowNode);
auto &concreteShadowNode = static_cast<TextInputShadowNode &>(shadowNode);
concreteShadowNode.setTextLayoutManager(textLayoutManager_);
}
private:
const std::shared_ptr<TextLayoutManager> textLayoutManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,55 @@
/*
* 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 "TextInputProps.h"
#include <react/renderer/attributedstring/conversions.h>
#include <react/renderer/components/iostextinput/propsConversions.h>
#include <react/renderer/components/textinput/baseConversions.h>
#include <react/renderer/core/graphicsConversions.h>
#include <react/renderer/core/propsConversions.h>
namespace facebook::react {
TextInputProps::TextInputProps(
const PropsParserContext& context,
const TextInputProps& sourceProps,
const RawProps& rawProps)
: BaseTextInputProps(context, sourceProps, rawProps),
traits(convertRawProp(context, rawProps, sourceProps.traits, {})),
selection(convertRawProp(
context,
rawProps,
"selection",
sourceProps.selection,
std::optional<Selection>())),
inputAccessoryViewID(convertRawProp(
context,
rawProps,
"inputAccessoryViewID",
sourceProps.inputAccessoryViewID,
{})),
inputAccessoryViewButtonLabel(convertRawProp(
context,
rawProps,
"inputAccessoryViewButtonLabel",
sourceProps.inputAccessoryViewButtonLabel,
{})),
onKeyPressSync(convertRawProp(
context,
rawProps,
"onKeyPressSync",
sourceProps.onKeyPressSync,
{})),
onChangeSync(convertRawProp(
context,
rawProps,
"onChangeSync",
sourceProps.onChangeSync,
{})) {};
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/attributedstring/TextAttributes.h>
#include <react/renderer/components/iostextinput/conversions.h>
#include <react/renderer/components/iostextinput/primitives.h>
#include <react/renderer/components/textinput/BaseTextInputProps.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
#include <react/renderer/imagemanager/primitives.h>
#include <vector>
namespace facebook::react {
class TextInputProps final : public BaseTextInputProps {
public:
TextInputProps() = default;
TextInputProps(const PropsParserContext &context, const TextInputProps &sourceProps, const RawProps &rawProps);
#pragma mark - Props
const TextInputTraits traits{};
/*
* "Private" (only used by TextInput.js) props
*/
std::optional<Selection> selection{};
const std::string inputAccessoryViewID{};
const std::string inputAccessoryViewButtonLabel{};
bool onKeyPressSync{false};
bool onChangeSync{false};
};
} // namespace facebook::react

View File

@@ -0,0 +1,14 @@
/*
* 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 "TextInputShadowNode.h"
namespace facebook::react {
extern const char TextInputComponentName[] = "TextInput";
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/components/iostextinput/TextInputProps.h>
#include <react/renderer/components/textinput/BaseTextInputShadowNode.h>
#include <react/renderer/components/textinput/TextInputEventEmitter.h>
#include <react/renderer/components/textinput/TextInputState.h>
namespace facebook::react {
extern const char TextInputComponentName[];
/*
* `ShadowNode` for <TextInput> component.
*/
class TextInputShadowNode final
: public BaseTextInputShadowNode<TextInputComponentName, TextInputProps, TextInputEventEmitter, TextInputState> {
public:
using BaseTextInputShadowNode::BaseTextInputShadowNode;
};
} // namespace facebook::react

View File

@@ -0,0 +1,211 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/components/iostextinput/primitives.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
namespace facebook::react {
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, AutocapitalizationType &result)
{
auto string = (std::string)value;
if (string == "none") {
result = AutocapitalizationType::None;
return;
}
if (string == "words") {
result = AutocapitalizationType::Words;
return;
}
if (string == "sentences") {
result = AutocapitalizationType::Sentences;
return;
}
if (string == "characters") {
result = AutocapitalizationType::Characters;
return;
}
result = AutocapitalizationType::None;
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, KeyboardAppearance &result)
{
auto string = (std::string)value;
if (string == "default") {
result = KeyboardAppearance::Default;
return;
}
if (string == "light") {
result = KeyboardAppearance::Light;
return;
}
if (string == "dark") {
result = KeyboardAppearance::Dark;
return;
}
result = KeyboardAppearance::Default;
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, ReturnKeyType &result)
{
auto string = (std::string)value;
if (string == "default") {
result = ReturnKeyType::Default;
return;
}
if (string == "done") {
result = ReturnKeyType::Done;
return;
}
if (string == "go") {
result = ReturnKeyType::Go;
return;
}
if (string == "next") {
result = ReturnKeyType::Next;
return;
}
if (string == "search") {
result = ReturnKeyType::Search;
return;
}
if (string == "send") {
result = ReturnKeyType::Send;
return;
}
// Android-only
if (string == "none") {
result = ReturnKeyType::None;
return;
}
if (string == "previous") {
result = ReturnKeyType::Previous;
return;
}
// iOS-only
if (string == "emergency-call") {
result = ReturnKeyType::EmergencyCall;
return;
}
if (string == "google") {
result = ReturnKeyType::Google;
return;
}
if (string == "join") {
result = ReturnKeyType::Join;
return;
}
if (string == "route") {
result = ReturnKeyType::Route;
return;
}
if (string == "yahoo") {
result = ReturnKeyType::Yahoo;
return;
}
if (string == "continue") {
result = ReturnKeyType::Continue;
return;
}
result = ReturnKeyType::Default;
}
inline void
fromRawValue(const PropsParserContext &context, const RawValue &value, TextInputAccessoryVisibilityMode &result)
{
auto string = (std::string)value;
if (string == "never") {
result = TextInputAccessoryVisibilityMode::Never;
return;
}
if (string == "while-editing") {
result = TextInputAccessoryVisibilityMode::WhileEditing;
return;
}
if (string == "unless-editing") {
result = TextInputAccessoryVisibilityMode::UnlessEditing;
return;
}
if (string == "always") {
result = TextInputAccessoryVisibilityMode::Always;
return;
}
result = TextInputAccessoryVisibilityMode::Never;
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, KeyboardType &result)
{
auto string = (std::string)value;
if (string == "default") {
result = KeyboardType::Default;
return;
}
if (string == "email-address") {
result = KeyboardType::EmailAddress;
return;
}
if (string == "numeric") {
result = KeyboardType::Numeric;
return;
}
if (string == "phone-pad") {
result = KeyboardType::PhonePad;
return;
}
if (string == "number-pad") {
result = KeyboardType::NumberPad;
return;
}
if (string == "url") {
result = KeyboardType::URL;
return;
}
if (string == "decimal-pad") {
result = KeyboardType::DecimalPad;
return;
}
// iOS-only
if (string == "ascii-capable") {
result = KeyboardType::ASCIICapable;
return;
}
if (string == "numbers-and-punctuation") {
result = KeyboardType::NumbersAndPunctuation;
return;
}
if (string == "name-phone-pad") {
result = KeyboardType::NamePhonePad;
return;
}
if (string == "twitter") {
result = KeyboardType::Twitter;
return;
}
if (string == "web-search") {
result = KeyboardType::WebSearch;
return;
}
if (string == "ascii-capable-number-pad") {
result = KeyboardType::ASCIICapableNumberPad;
return;
}
// Android-only
if (string == "visible-password") {
result = KeyboardType::VisiblePassword;
return;
}
result = KeyboardType::Default;
}
} // namespace facebook::react

View File

@@ -0,0 +1,222 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/components/textinput/basePrimitives.h>
#include <optional>
#include <string>
#include <vector>
namespace facebook::react {
// iOS & Android.
enum class AutocapitalizationType {
None,
Words,
Sentences,
Characters,
};
// iOS-only
enum class KeyboardAppearance {
Default,
Light,
Dark,
};
enum class ReturnKeyType {
// Universal
Default,
Done,
Go,
Next,
Search,
Send,
// Android-only
None,
Previous,
// iOS-only
EmergencyCall,
Google,
Join,
Route,
Yahoo,
Continue,
};
// iOS-only
enum class TextInputAccessoryVisibilityMode {
Never,
WhileEditing,
UnlessEditing,
Always,
};
enum class KeyboardType {
// Universal
Default,
EmailAddress,
Numeric,
PhonePad,
NumberPad,
DecimalPad,
// iOS-only
ASCIICapable,
NumbersAndPunctuation,
URL,
NamePhonePad,
Twitter,
WebSearch,
ASCIICapableNumberPad,
// Android-only
VisiblePassword,
};
class Selection final {
public:
int start{0};
int end{0};
};
/*
* Controls features of text inputs.
*/
class TextInputTraits final {
public:
/*
* iOS & Android
* Default value: `Sentences`.
*/
AutocapitalizationType autocapitalizationType{AutocapitalizationType::Sentences};
/*
* Can be empty (`null` in JavaScript) which means `default`.
* iOS & Android
* Default value: `empty` (`null`).
*/
std::optional<bool> autoCorrect{};
/*
* iOS & Android
* Default value: `false`.
*/
bool contextMenuHidden{false};
/*
* iOS & Android
* Default value: `true`.
*/
bool editable{true};
/*
* iOS-only (implemented only on iOS for now)
* If `true`, will automatically disable return key when text widget has
* zero-length contents, and will automatically enable when text widget has
* non-zero-length contents.
* Default value: `false`.
*/
bool enablesReturnKeyAutomatically{false};
/*
* Some values iOS- or Android-only (inherently particular-OS-specific)
* Default value: `Default`.
*/
KeyboardAppearance keyboardAppearance{KeyboardAppearance::Default};
/*
* Controls the annotation of misspelled words for a text input.
* iOS-only (implemented only on iOS for now)
* Can be empty (`null` in JavaScript) which means `default`.
* Default value: `empty` (`null`).
*/
std::optional<bool> spellCheck{};
/*
* iOS & Android
* Default value: `false`.
*/
bool caretHidden{false};
/*
* Controls the visibility of a `Clean` button.
* iOS-only (implemented only on iOS for now)
* Default value: `Never`.
*/
TextInputAccessoryVisibilityMode clearButtonMode{TextInputAccessoryVisibilityMode::Never};
/*
* iOS-only (implemented only on iOS for now)
* Default value: `true`.
*/
bool scrollEnabled{true};
/*
* iOS & Android
* Default value: `false`.
*/
bool secureTextEntry{false};
/*
* iOS-only (implemented only on iOS for now)
* Default value: `false`.
*/
bool clearTextOnFocus{false};
/*
* Some values iOS- or Android-only (inherently particular-OS-specific)
* Default value: `Default`.
*/
KeyboardType keyboardType{KeyboardType::Default};
/*
* iOS & Android
* Default value: `true`.
*/
bool showSoftInputOnFocus{true};
/*
* Some values iOS- or Android-only (inherently particular-OS-specific)
* Default value: `Default`.
*/
ReturnKeyType returnKeyType{ReturnKeyType::Default};
/*
* iOS & Android
* Default value: `false`.
*/
bool selectTextOnFocus{false};
/*
* iOS-only
* Default value: `empty` (`null`).
*/
std::vector<std::string> dataDetectorTypes{};
/*
* iOS-only (inherently iOS-specific)
* Default value: `<empty string>` (default content type).
*/
std::string textContentType{};
/*
* iOS-only (inherently iOS-specific)
* Default value: `<empty string>` (no rules).
*/
std::string passwordRules{};
/*
* If `false`, the iOS system will not insert an extra space after a paste
* operation neither delete one or two spaces after a cut or delete operation.
* iOS-only (inherently iOS-specific)
* Can be empty (`null` in JavaScript) which means `default`.
* Default value: `empty` (`null`).
*/
std::optional<bool> smartInsertDelete{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,104 @@
/*
* 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.
*/
#pragma once
#include <react/debug/react_native_expect.h>
#include <react/renderer/components/iostextinput/primitives.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
namespace facebook::react {
static TextInputTraits convertRawProp(
const PropsParserContext &context,
const RawProps &rawProps,
const TextInputTraits &sourceTraits,
const TextInputTraits &defaultTraits)
{
auto traits = TextInputTraits{};
traits.autocapitalizationType = convertRawProp(
context, rawProps, "autoCapitalize", sourceTraits.autocapitalizationType, defaultTraits.autocapitalizationType);
traits.autoCorrect =
convertRawProp(context, rawProps, "autoCorrect", sourceTraits.autoCorrect, defaultTraits.autoCorrect);
traits.contextMenuHidden = convertRawProp(
context, rawProps, "contextMenuHidden", sourceTraits.contextMenuHidden, defaultTraits.contextMenuHidden);
traits.editable = convertRawProp(context, rawProps, "editable", sourceTraits.editable, defaultTraits.editable);
traits.enablesReturnKeyAutomatically = convertRawProp(
context,
rawProps,
"enablesReturnKeyAutomatically",
sourceTraits.enablesReturnKeyAutomatically,
defaultTraits.enablesReturnKeyAutomatically);
traits.keyboardAppearance = convertRawProp(
context, rawProps, "keyboardAppearance", sourceTraits.keyboardAppearance, defaultTraits.keyboardAppearance);
traits.spellCheck =
convertRawProp(context, rawProps, "spellCheck", sourceTraits.spellCheck, defaultTraits.spellCheck);
traits.caretHidden =
convertRawProp(context, rawProps, "caretHidden", sourceTraits.caretHidden, defaultTraits.caretHidden);
traits.clearButtonMode =
convertRawProp(context, rawProps, "clearButtonMode", sourceTraits.clearButtonMode, defaultTraits.clearButtonMode);
traits.scrollEnabled =
convertRawProp(context, rawProps, "scrollEnabled", sourceTraits.scrollEnabled, defaultTraits.scrollEnabled);
traits.secureTextEntry =
convertRawProp(context, rawProps, "secureTextEntry", sourceTraits.secureTextEntry, defaultTraits.secureTextEntry);
traits.clearTextOnFocus = convertRawProp(
context, rawProps, "clearTextOnFocus", sourceTraits.clearTextOnFocus, defaultTraits.clearTextOnFocus);
traits.keyboardType =
convertRawProp(context, rawProps, "keyboardType", sourceTraits.keyboardType, defaultTraits.keyboardType);
traits.showSoftInputOnFocus = convertRawProp(
context, rawProps, "showSoftInputOnFocus", sourceTraits.showSoftInputOnFocus, defaultTraits.showSoftInputOnFocus);
traits.returnKeyType =
convertRawProp(context, rawProps, "returnKeyType", sourceTraits.returnKeyType, defaultTraits.returnKeyType);
traits.selectTextOnFocus = convertRawProp(
context, rawProps, "selectTextOnFocus", sourceTraits.selectTextOnFocus, defaultTraits.selectTextOnFocus);
traits.textContentType =
convertRawProp(context, rawProps, "textContentType", sourceTraits.textContentType, defaultTraits.textContentType);
traits.passwordRules =
convertRawProp(context, rawProps, "passwordRules", sourceTraits.passwordRules, defaultTraits.passwordRules);
traits.smartInsertDelete = convertRawProp(
context, rawProps, "smartInsertDelete", sourceTraits.smartInsertDelete, defaultTraits.smartInsertDelete);
traits.dataDetectorTypes = convertRawProp(
context, rawProps, "dataDetectorTypes", sourceTraits.dataDetectorTypes, defaultTraits.dataDetectorTypes);
return traits;
}
inline void fromRawValue(const PropsParserContext &context, const RawValue &value, Selection &result)
{
if (value.hasType<std::unordered_map<std::string, int>>()) {
auto map = (std::unordered_map<std::string, int>)value;
for (const auto &pair : map) {
if (pair.first == "start") {
result.start = pair.second;
} else if (pair.first == "end") {
result.end = pair.second;
} else {
LOG(ERROR) << "Unsupported Selection map key: " << pair.first;
react_native_expect(false);
}
}
return;
}
react_native_expect(value.hasType<std::vector<int>>());
if (value.hasType<std::vector<int>>()) {
auto array = (std::vector<int>)value;
react_native_expect(array.size() == 2);
if (array.size() >= 2) {
result = {array.at(0), array.at(1)};
} else {
result = {0, 0};
LOG(ERROR) << "Unsupported Selection vector size: " << array.size();
}
} else {
LOG(ERROR) << "Unsupported Selection type";
}
}
} // namespace facebook::react