This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,99 @@
load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_preprocessor_flags_for_build_mode")
load(
"//tools/build_defs/oss:rn_defs.bzl",
"ANDROID",
"APPLE",
"CXX",
"YOGA_CXX_TARGET",
"fb_xplat_cxx_test",
"get_apple_compiler_flags",
"get_apple_inspector_flags",
"react_native_xplat_target",
"rn_xplat_cxx_library",
"subdir_glob",
)
APPLE_COMPILER_FLAGS = get_apple_compiler_flags()
rn_xplat_cxx_library(
name = "androidtextinput",
srcs = glob(
["androidtextinput/**/*.cpp"],
exclude = glob(["tests/**/*.cpp"]),
),
headers = glob(
["androidtextinput/**/*.h"],
exclude = glob(["tests/**/*.h"]),
),
header_namespace = "",
exported_headers = subdir_glob(
[
("", "*.h"),
("androidtextinput", "*.h"),
],
prefix = "react/components/androidtextinput",
),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
cxx_tests = [":tests"],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"],
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
force_static = True,
platforms = (ANDROID, APPLE, CXX),
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
visibility = ["PUBLIC"],
deps = [
"//xplat/fbsystrace:fbsystrace",
"//xplat/folly:container_evicting_cache_map",
"//xplat/folly:headers_only",
"//xplat/folly:memory",
"//xplat/folly:molly",
"//xplat/third-party/glog:glog",
YOGA_CXX_TARGET,
react_native_xplat_target("utils:utils"),
react_native_xplat_target("fabric/attributedstring:attributedstring"),
react_native_xplat_target("fabric/core:core"),
react_native_xplat_target("fabric/debug:debug"),
react_native_xplat_target("fabric/graphics:graphics"),
react_native_xplat_target("fabric/textlayoutmanager:textlayoutmanager"),
react_native_xplat_target("fabric/components/text:text"),
react_native_xplat_target("fabric/components/view:view"),
react_native_xplat_target("fabric/components/image:image"),
react_native_xplat_target("fabric/uimanager:uimanager"),
react_native_xplat_target("fabric/imagemanager:imagemanager"),
],
)
fb_xplat_cxx_test(
name = "tests",
srcs = glob(["tests/**/*.cpp"]),
headers = glob(["tests/**/*.h"]),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
contacts = ["oncall+react_native@xmail.facebook.com"],
platforms = (
# `Apple` and `Android` flavors are disabled because the module depends on `textlayoutmanager` which requires real an Emulator/Simulator to run.
# At the same time, the code of tests does not rely on the simulator capabilities and it would be wasteful to add `fbandroid_use_instrumentation_test = True`.
# (Beware of this option though.)
# ANDROID,
# APPLE,
CXX
),
deps = [
":androidtextinput",
"//xplat/folly:molly",
"//xplat/third-party/gmock:gtest",
],
)

View File

@ -0,0 +1,185 @@
/*
* Copyright (c) Facebook, Inc. and its 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/core/ConcreteComponentDescriptor.h>
#include "AndroidTextInputShadowNode.h"
#include <yoga/CompactValue.h>
#include <yoga/YGEnums.h>
#include <yoga/YGValue.h>
namespace facebook {
namespace react {
/*
* Descriptor for <AndroidTextInput> component.
*/
class AndroidTextInputComponentDescriptor final
: public ConcreteComponentDescriptor<AndroidTextInputShadowNode> {
public:
AndroidTextInputComponentDescriptor(
ComponentDescriptorParameters const &parameters)
: ConcreteComponentDescriptor<AndroidTextInputShadowNode>(parameters) {
// Every single `AndroidTextInputShadowNode` will have a reference to
// a shared `TextLayoutManager`.
textLayoutManager_ = std::make_shared<TextLayoutManager>(contextContainer_);
}
virtual State::Shared createInitialState(
ShadowNodeFragment const &fragment,
ShadowNodeFamily::Shared const &family) const override {
int surfaceId = family->getSurfaceId();
float defaultThemePaddingStart = NAN;
float defaultThemePaddingEnd = NAN;
float defaultThemePaddingTop = NAN;
float defaultThemePaddingBottom = NAN;
if (surfaceIdToThemePaddingMap_.find(surfaceId) !=
surfaceIdToThemePaddingMap_.end()) {
YGStyle::Edges theme = surfaceIdToThemePaddingMap_[surfaceId];
defaultThemePaddingStart = ((YGValue)theme[YGEdgeStart]).value;
defaultThemePaddingEnd = ((YGValue)theme[YGEdgeEnd]).value;
defaultThemePaddingTop = ((YGValue)theme[YGEdgeTop]).value;
defaultThemePaddingBottom = ((YGValue)theme[YGEdgeBottom]).value;
}
return std::make_shared<AndroidTextInputShadowNode::ConcreteState>(
std::make_shared<AndroidTextInputState const>(AndroidTextInputState(
0,
{},
{},
{},
{},
{},
textLayoutManager_,
defaultThemePaddingStart,
defaultThemePaddingEnd,
defaultThemePaddingTop,
defaultThemePaddingBottom)),
family);
}
protected:
void adopt(UnsharedShadowNode shadowNode) const override {
assert(std::dynamic_pointer_cast<AndroidTextInputShadowNode>(shadowNode));
auto textInputShadowNode =
std::static_pointer_cast<AndroidTextInputShadowNode>(shadowNode);
// `ParagraphShadowNode` uses `TextLayoutManager` to measure text content
// and communicate text rendering metrics to mounting layer.
textInputShadowNode->setTextLayoutManager(textLayoutManager_);
textInputShadowNode->setContextContainer(
const_cast<ContextContainer *>(getContextContainer().get()));
// Get theme padding from cache, or set it from State.
// In theory, the Java ViewManager for TextInput should need to set state
// *exactly once* per surface to communicate the correct default padding,
// which will be cached here in C++.
// TODO T63008435: can this feature be removed entirely?
// TODO: figure out RTL/start/end/left/right stuff here
int surfaceId = textInputShadowNode->getSurfaceId();
const AndroidTextInputState &state = textInputShadowNode->getStateData();
if (surfaceIdToThemePaddingMap_.find(surfaceId) ==
surfaceIdToThemePaddingMap_.end() &&
!isnan(state.defaultThemePaddingStart)) {
YGStyle::Edges result;
result[YGEdgeStart] =
(YGValue){state.defaultThemePaddingStart, YGUnitPoint};
result[YGEdgeEnd] = (YGValue){state.defaultThemePaddingEnd, YGUnitPoint};
result[YGEdgeTop] = (YGValue){state.defaultThemePaddingTop, YGUnitPoint};
result[YGEdgeBottom] =
(YGValue){state.defaultThemePaddingBottom, YGUnitPoint};
surfaceIdToThemePaddingMap_.emplace(std::make_pair(surfaceId, result));
}
if (surfaceIdToThemePaddingMap_.find(surfaceId) !=
surfaceIdToThemePaddingMap_.end()) {
YGStyle::Edges theme = surfaceIdToThemePaddingMap_[surfaceId];
// 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.
YGStyle::Edges result =
textInputShadowNode->getConcreteProps().yogaStyle.padding();
bool changedPadding = false;
if (!textInputShadowNode->getConcreteProps().hasPadding &&
!textInputShadowNode->getConcreteProps().hasPaddingStart &&
!textInputShadowNode->getConcreteProps().hasPaddingLeft &&
!textInputShadowNode->getConcreteProps().hasPaddingHorizontal) {
changedPadding = true;
result[YGEdgeStart] = theme[YGEdgeStart];
}
if (!textInputShadowNode->getConcreteProps().hasPadding &&
!textInputShadowNode->getConcreteProps().hasPaddingEnd &&
!textInputShadowNode->getConcreteProps().hasPaddingRight &&
!textInputShadowNode->getConcreteProps().hasPaddingHorizontal) {
changedPadding = true;
result[YGEdgeEnd] = theme[YGEdgeEnd];
}
if (!textInputShadowNode->getConcreteProps().hasPadding &&
!textInputShadowNode->getConcreteProps().hasPaddingTop &&
!textInputShadowNode->getConcreteProps().hasPaddingVertical) {
changedPadding = true;
result[YGEdgeTop] = theme[YGEdgeTop];
}
if (!textInputShadowNode->getConcreteProps().hasPadding &&
!textInputShadowNode->getConcreteProps().hasPaddingBottom &&
!textInputShadowNode->getConcreteProps().hasPaddingVertical) {
changedPadding = true;
result[YGEdgeBottom] = theme[YGEdgeBottom];
}
// 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 ((textInputShadowNode->getConcreteProps().hasPadding ||
textInputShadowNode->getConcreteProps().hasPaddingLeft ||
textInputShadowNode->getConcreteProps().hasPaddingHorizontal) &&
!textInputShadowNode->getConcreteProps().hasPaddingStart) {
result[YGEdgeStart] = YGValueUndefined;
}
if ((textInputShadowNode->getConcreteProps().hasPadding ||
textInputShadowNode->getConcreteProps().hasPaddingRight ||
textInputShadowNode->getConcreteProps().hasPaddingHorizontal) &&
!textInputShadowNode->getConcreteProps().hasPaddingEnd) {
result[YGEdgeEnd] = YGValueUndefined;
}
// 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) {
// Set new props on node
const_cast<AndroidTextInputProps &>(
textInputShadowNode->getConcreteProps())
.yogaStyle.padding() = result;
// Communicate new props to Yoga part of the node
textInputShadowNode->updateYogaProps();
}
}
textInputShadowNode->dirtyLayout();
textInputShadowNode->enableMeasurement();
ConcreteComponentDescriptor::adopt(shadowNode);
}
private:
SharedTextLayoutManager textLayoutManager_;
mutable better::map<int, YGStyle::Edges> surfaceIdToThemePaddingMap_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,179 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AndroidTextInputEventEmitter.h"
namespace facebook {
namespace react {
void AndroidTextInputEventEmitter::onBlur(
AndroidTextInputOnBlurStruct event) const {
dispatchEvent("blur", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
return payload;
});
}
void AndroidTextInputEventEmitter::onFocus(
AndroidTextInputOnFocusStruct event) const {
dispatchEvent("focus", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
return payload;
});
}
void AndroidTextInputEventEmitter::onChange(
AndroidTextInputOnChangeStruct event) const {
dispatchEvent("change", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
payload.setProperty(runtime, "eventCount", event.eventCount);
payload.setProperty(runtime, "text", event.text);
return payload;
});
}
void AndroidTextInputEventEmitter::onChangeText(
AndroidTextInputOnChangeTextStruct event) const {
dispatchEvent(
"changeText", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
payload.setProperty(runtime, "eventCount", event.eventCount);
payload.setProperty(runtime, "text", event.text);
return payload;
});
}
void AndroidTextInputEventEmitter::onContentSizeChange(
AndroidTextInputOnContentSizeChangeStruct event) const {
dispatchEvent(
"contentSizeChange", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
{
auto contentSize = jsi::Object(runtime);
contentSize.setProperty(runtime, "width", event.contentSize.width);
contentSize.setProperty(runtime, "height", event.contentSize.height);
payload.setProperty(runtime, "contentSize", contentSize);
}
return payload;
});
}
void AndroidTextInputEventEmitter::onTextInput(
AndroidTextInputOnTextInputStruct event) const {
dispatchEvent("textInput", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
payload.setProperty(runtime, "text", event.text);
payload.setProperty(runtime, "previousText", event.previousText);
{
auto range = jsi::Object(runtime);
range.setProperty(runtime, "start", event.range.start);
range.setProperty(runtime, "end", event.range.end);
payload.setProperty(runtime, "range", range);
}
return payload;
});
}
void AndroidTextInputEventEmitter::onEndEditing(
AndroidTextInputOnEndEditingStruct event) const {
dispatchEvent(
"endEditing", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
payload.setProperty(runtime, "text", event.text);
return payload;
});
}
void AndroidTextInputEventEmitter::onSelectionChange(
AndroidTextInputOnSelectionChangeStruct event) const {
dispatchEvent(
"selectionChange", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
{
auto selection = jsi::Object(runtime);
selection.setProperty(runtime, "start", event.selection.start);
selection.setProperty(runtime, "end", event.selection.end);
payload.setProperty(runtime, "selection", selection);
}
return payload;
});
}
void AndroidTextInputEventEmitter::onSubmitEditing(
AndroidTextInputOnSubmitEditingStruct event) const {
dispatchEvent(
"submitEditing", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
payload.setProperty(runtime, "text", event.text);
return payload;
});
}
void AndroidTextInputEventEmitter::onKeyPress(
AndroidTextInputOnKeyPressStruct event) const {
dispatchEvent("keyPress", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
payload.setProperty(runtime, "key", event.key);
return payload;
});
}
void AndroidTextInputEventEmitter::onScroll(
AndroidTextInputOnScrollStruct event) const {
dispatchEvent("scroll", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "target", event.target);
payload.setProperty(
runtime, "responderIgnoreScroll", event.responderIgnoreScroll);
{
auto contentInset = jsi::Object(runtime);
contentInset.setProperty(runtime, "top", event.contentInset.top);
contentInset.setProperty(runtime, "bottom", event.contentInset.bottom);
contentInset.setProperty(runtime, "left", event.contentInset.left);
contentInset.setProperty(runtime, "right", event.contentInset.right);
payload.setProperty(runtime, "contentInset", contentInset);
}
{
auto contentOffset = jsi::Object(runtime);
contentOffset.setProperty(runtime, "x", event.contentOffset.x);
contentOffset.setProperty(runtime, "y", event.contentOffset.y);
payload.setProperty(runtime, "contentOffset", contentOffset);
}
{
auto contentSize = jsi::Object(runtime);
contentSize.setProperty(runtime, "width", event.contentSize.width);
contentSize.setProperty(runtime, "height", event.contentSize.height);
payload.setProperty(runtime, "contentSize", contentSize);
}
{
auto layoutMeasurement = jsi::Object(runtime);
layoutMeasurement.setProperty(
runtime, "width", event.layoutMeasurement.width);
layoutMeasurement.setProperty(
runtime, "height", event.layoutMeasurement.height);
payload.setProperty(runtime, "layoutMeasurement", layoutMeasurement);
}
{
auto velocity = jsi::Object(runtime);
velocity.setProperty(runtime, "x", event.velocity.x);
velocity.setProperty(runtime, "y", event.velocity.y);
payload.setProperty(runtime, "velocity", velocity);
}
return payload;
});
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,148 @@
/*
* Copyright (c) Facebook, Inc. and its 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/components/view/ViewEventEmitter.h>
namespace facebook {
namespace react {
struct AndroidTextInputOnBlurStruct {
int target;
};
struct AndroidTextInputOnFocusStruct {
int target;
};
struct AndroidTextInputOnChangeStruct {
int target;
int eventCount;
std::string text;
};
struct AndroidTextInputOnChangeTextStruct {
int target;
int eventCount;
std::string text;
};
struct AndroidTextInputOnContentSizeChangeContentSizeStruct {
double width;
double height;
};
struct AndroidTextInputOnContentSizeChangeStruct {
int target;
AndroidTextInputOnContentSizeChangeContentSizeStruct contentSize;
};
struct AndroidTextInputOnTextInputRangeStruct {
double start;
double end;
};
struct AndroidTextInputOnTextInputStruct {
int target;
std::string text;
std::string previousText;
AndroidTextInputOnTextInputRangeStruct range;
};
struct AndroidTextInputOnEndEditingStruct {
int target;
std::string text;
};
struct AndroidTextInputOnSelectionChangeSelectionStruct {
double start;
double end;
};
struct AndroidTextInputOnSelectionChangeStruct {
int target;
AndroidTextInputOnSelectionChangeSelectionStruct selection;
};
struct AndroidTextInputOnSubmitEditingStruct {
int target;
std::string text;
};
struct AndroidTextInputOnKeyPressStruct {
int target;
std::string key;
};
struct AndroidTextInputOnScrollContentInsetStruct {
double top;
double bottom;
double left;
double right;
};
struct AndroidTextInputOnScrollContentOffsetStruct {
double x;
double y;
};
struct AndroidTextInputOnScrollContentSizeStruct {
double width;
double height;
};
struct AndroidTextInputOnScrollLayoutMeasurementStruct {
double width;
double height;
};
struct AndroidTextInputOnScrollVelocityStruct {
double x;
double y;
};
struct AndroidTextInputOnScrollStruct {
int target;
bool responderIgnoreScroll;
AndroidTextInputOnScrollContentInsetStruct contentInset;
AndroidTextInputOnScrollContentOffsetStruct contentOffset;
AndroidTextInputOnScrollContentSizeStruct contentSize;
AndroidTextInputOnScrollLayoutMeasurementStruct layoutMeasurement;
AndroidTextInputOnScrollVelocityStruct velocity;
};
class AndroidTextInputEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void onBlur(AndroidTextInputOnBlurStruct value) const;
void onFocus(AndroidTextInputOnFocusStruct value) const;
void onChange(AndroidTextInputOnChangeStruct value) const;
void onChangeText(AndroidTextInputOnChangeTextStruct value) const;
void onContentSizeChange(
AndroidTextInputOnContentSizeChangeStruct value) const;
void onTextInput(AndroidTextInputOnTextInputStruct value) const;
void onEndEditing(AndroidTextInputOnEndEditingStruct value) const;
void onSelectionChange(AndroidTextInputOnSelectionChangeStruct value) const;
void onSubmitEditing(AndroidTextInputOnSubmitEditingStruct value) const;
void onKeyPress(AndroidTextInputOnKeyPressStruct value) const;
void onScroll(AndroidTextInputOnScrollStruct value) const;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,370 @@
/*
* Copyright (c) Facebook, Inc. and its 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/components/image/conversions.h>
#include <react/core/propsConversions.h>
#include <react/graphics/conversions.h>
namespace facebook {
namespace react {
static bool hasValue(
const RawProps &rawProps,
bool defaultValue,
const char *name,
const char *prefix,
const char *suffix) {
auto rawValue = rawProps.at(name, prefix, suffix);
// 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 AndroidTextInputProps &sourceProps,
const RawProps &rawProps)
: ViewProps(sourceProps, rawProps),
BaseTextProps(sourceProps, rawProps),
autoCompleteType(convertRawProp(
rawProps,
"autoCompleteType",
sourceProps.autoCompleteType,
{})),
returnKeyLabel(convertRawProp(
rawProps,
"returnKeyLabel",
sourceProps.returnKeyLabel,
{})),
numberOfLines(convertRawProp(
rawProps,
"numberOfLines",
sourceProps.numberOfLines,
{0})),
disableFullscreenUI(convertRawProp(
rawProps,
"disableFullscreenUI",
sourceProps.disableFullscreenUI,
{false})),
textBreakStrategy(convertRawProp(
rawProps,
"textBreakStrategy",
sourceProps.textBreakStrategy,
{})),
underlineColorAndroid(convertRawProp(
rawProps,
"underlineColorAndroid",
sourceProps.underlineColorAndroid,
{})),
inlineImageLeft(convertRawProp(
rawProps,
"inlineImageLeft",
sourceProps.inlineImageLeft,
{})),
inlineImagePadding(convertRawProp(
rawProps,
"inlineImagePadding",
sourceProps.inlineImagePadding,
{0})),
importantForAutofill(convertRawProp(
rawProps,
"importantForAutofill",
sourceProps.importantForAutofill,
{})),
showSoftInputOnFocus(convertRawProp(
rawProps,
"showSoftInputOnFocus",
sourceProps.showSoftInputOnFocus,
{false})),
autoCapitalize(convertRawProp(
rawProps,
"autoCapitalize",
sourceProps.autoCapitalize,
{})),
autoCorrect(convertRawProp(
rawProps,
"autoCorrect",
sourceProps.autoCorrect,
{false})),
autoFocus(convertRawProp(
rawProps,
"autoFocus",
sourceProps.autoFocus,
{false})),
allowFontScaling(convertRawProp(
rawProps,
"allowFontScaling",
sourceProps.allowFontScaling,
{false})),
maxFontSizeMultiplier(convertRawProp(
rawProps,
"maxFontSizeMultiplier",
sourceProps.maxFontSizeMultiplier,
{0.0})),
editable(
convertRawProp(rawProps, "editable", sourceProps.editable, {false})),
keyboardType(convertRawProp(
rawProps,
"keyboardType",
sourceProps.keyboardType,
{})),
returnKeyType(convertRawProp(
rawProps,
"returnKeyType",
sourceProps.returnKeyType,
{})),
maxLength(
convertRawProp(rawProps, "maxLength", sourceProps.maxLength, {0})),
multiline(convertRawProp(
rawProps,
"multiline",
sourceProps.multiline,
{false})),
placeholder(
convertRawProp(rawProps, "placeholder", sourceProps.placeholder, {})),
placeholderTextColor(convertRawProp(
rawProps,
"placeholderTextColor",
sourceProps.placeholderTextColor,
{})),
secureTextEntry(convertRawProp(
rawProps,
"secureTextEntry",
sourceProps.secureTextEntry,
{false})),
selectionColor(convertRawProp(
rawProps,
"selectionColor",
sourceProps.selectionColor,
{})),
selection(
convertRawProp(rawProps, "selection", sourceProps.selection, {})),
value(convertRawProp(rawProps, "value", sourceProps.value, {})),
defaultValue(convertRawProp(
rawProps,
"defaultValue",
sourceProps.defaultValue,
{})),
selectTextOnFocus(convertRawProp(
rawProps,
"selectTextOnFocus",
sourceProps.selectTextOnFocus,
{false})),
blurOnSubmit(convertRawProp(
rawProps,
"blurOnSubmit",
sourceProps.blurOnSubmit,
{false})),
caretHidden(convertRawProp(
rawProps,
"caretHidden",
sourceProps.caretHidden,
{false})),
contextMenuHidden(convertRawProp(
rawProps,
"contextMenuHidden",
sourceProps.contextMenuHidden,
{false})),
textShadowColor(convertRawProp(
rawProps,
"textShadowColor",
sourceProps.textShadowColor,
{})),
textShadowRadius(convertRawProp(
rawProps,
"textShadowRadius",
sourceProps.textShadowRadius,
{0.0})),
textDecorationLine(convertRawProp(
rawProps,
"textDecorationLine",
sourceProps.textDecorationLine,
{})),
fontStyle(
convertRawProp(rawProps, "fontStyle", sourceProps.fontStyle, {})),
textShadowOffset(convertRawProp(
rawProps,
"textShadowOffset",
sourceProps.textShadowOffset,
{})),
lineHeight(convertRawProp(
rawProps,
"lineHeight",
sourceProps.lineHeight,
{0.0})),
textTransform(convertRawProp(
rawProps,
"textTransform",
sourceProps.textTransform,
{})),
color(convertRawProp(rawProps, "color", sourceProps.color, {0})),
letterSpacing(convertRawProp(
rawProps,
"letterSpacing",
sourceProps.letterSpacing,
{0.0})),
fontSize(
convertRawProp(rawProps, "fontSize", sourceProps.fontSize, {0.0})),
textAlign(
convertRawProp(rawProps, "textAlign", sourceProps.textAlign, {})),
includeFontPadding(convertRawProp(
rawProps,
"includeFontPadding",
sourceProps.includeFontPadding,
{false})),
fontWeight(
convertRawProp(rawProps, "fontWeight", sourceProps.fontWeight, {})),
fontFamily(
convertRawProp(rawProps, "fontFamily", sourceProps.fontFamily, {})),
textAlignVertical(convertRawProp(
rawProps,
"textAlignVertical",
sourceProps.textAlignVertical,
{})),
cursorColor(
convertRawProp(rawProps, "cursorColor", sourceProps.cursorColor, {})),
mostRecentEventCount(convertRawProp(
rawProps,
"mostRecentEventCount",
sourceProps.mostRecentEventCount,
{0})),
text(convertRawProp(rawProps, "text", sourceProps.text, {})),
paragraphAttributes(
convertRawProp(rawProps, sourceProps.paragraphAttributes, {})),
// See AndroidTextInputComponentDescriptor for usage
// TODO T63008435: can these, and this feature, be removed entirely?
hasPadding(hasValue(rawProps, sourceProps.hasPadding, "", "padding", "")),
hasPaddingHorizontal(hasValue(
rawProps,
sourceProps.hasPaddingHorizontal,
"Horizontal",
"padding",
"")),
hasPaddingVertical(hasValue(
rawProps,
sourceProps.hasPaddingVertical,
"Vertical",
"padding",
"")),
hasPaddingLeft(hasValue(
rawProps,
sourceProps.hasPaddingLeft,
"Left",
"padding",
"")),
hasPaddingTop(
hasValue(rawProps, sourceProps.hasPaddingTop, "Top", "padding", "")),
hasPaddingRight(hasValue(
rawProps,
sourceProps.hasPaddingRight,
"Right",
"padding",
"")),
hasPaddingBottom(hasValue(
rawProps,
sourceProps.hasPaddingBottom,
"Bottom",
"padding",
"")),
hasPaddingStart(hasValue(
rawProps,
sourceProps.hasPaddingStart,
"Start",
"padding",
"")),
hasPaddingEnd(
hasValue(rawProps, sourceProps.hasPaddingEnd, "End", "padding", "")) {
}
// TODO T53300085: support this in codegen; this was hand-written
folly::dynamic AndroidTextInputProps::getDynamic() const {
folly::dynamic props = folly::dynamic::object();
props["autoCompleteType"] = autoCompleteType;
props["returnKeyLabel"] = returnKeyLabel;
props["numberOfLines"] = numberOfLines;
props["disableFullscreenUI"] = disableFullscreenUI;
props["textBreakStrategy"] = textBreakStrategy;
props["underlineColorAndroid"] = toDynamic(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["editable"] = editable;
props["keyboardType"] = keyboardType;
props["returnKeyType"] = returnKeyType;
props["maxLength"] = maxLength;
props["multiline"] = multiline;
props["placeholder"] = placeholder;
props["placeholderTextColor"] = toDynamic(placeholderTextColor);
props["secureTextEntry"] = secureTextEntry;
props["selectionColor"] = toDynamic(selectionColor);
props["selection"] = toDynamic(selection);
props["value"] = value;
props["defaultValue"] = defaultValue;
props["selectTextOnFocus"] = selectTextOnFocus;
props["blurOnSubmit"] = blurOnSubmit;
props["caretHidden"] = caretHidden;
props["contextMenuHidden"] = contextMenuHidden;
props["textShadowColor"] = toDynamic(textShadowColor);
props["textShadowRadius"] = textShadowRadius;
props["textDecorationLine"] = textDecorationLine;
props["fontStyle"] = fontStyle;
props["textShadowOffset"] = toDynamic(textShadowOffset);
props["lineHeight"] = lineHeight;
props["textTransform"] = textTransform;
props["color"] = color;
props["letterSpacing"] = letterSpacing;
props["fontSize"] = fontSize;
props["textAlign"] = textAlign;
props["includeFontPadding"] = includeFontPadding;
props["fontWeight"] = fontWeight;
props["fontFamily"] = fontFamily;
props["textAlignVertical"] = textAlignVertical;
props["cursorColor"] = toDynamic(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
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,183 @@
/*
* Copyright (c) Facebook, Inc. and its 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/components/text/BaseTextProps.h>
#include <react/core/Props.h>
#include <react/graphics/Color.h>
#include <react/graphics/Geometry.h>
#include <react/attributedstring/TextAttributes.h>
#include <react/attributedstring/conversions.h>
#include <react/components/text/BaseTextProps.h>
#include <react/components/view/ViewProps.h>
#include <react/core/propsConversions.h>
#include <react/graphics/Color.h>
#include <react/imagemanager/primitives.h>
#include <cinttypes>
#include <vector>
namespace facebook {
namespace react {
struct AndroidTextInputSelectionStruct {
int start;
int end;
};
static inline void fromRawValue(
const RawValue &value,
AndroidTextInputSelectionStruct &result) {
auto map = (better::map<std::string, RawValue>)value;
auto start = map.find("start");
if (start != map.end()) {
fromRawValue(start->second, result.start);
}
auto end = map.find("end");
if (end != map.end()) {
fromRawValue(end->second, result.end);
}
}
static inline std::string toString(
const AndroidTextInputSelectionStruct &value) {
return "[Object AndroidTextInputSelectionStruct]";
}
struct AndroidTextInputTextShadowOffsetStruct {
double width;
double height;
};
static inline void fromRawValue(
const RawValue &value,
AndroidTextInputTextShadowOffsetStruct &result) {
auto map = (better::map<std::string, RawValue>)value;
auto width = map.find("width");
if (width != map.end()) {
fromRawValue(width->second, result.width);
}
auto height = map.find("height");
if (height != map.end()) {
fromRawValue(height->second, result.height);
}
}
static inline std::string toString(
const AndroidTextInputTextShadowOffsetStruct &value) {
return "[Object AndroidTextInputTextShadowOffsetStruct]";
}
#ifdef ANDROID
inline folly::dynamic toDynamic(
const AndroidTextInputTextShadowOffsetStruct &value) {
folly::dynamic dynamicValue = folly::dynamic::object();
dynamicValue["width"] = value.width;
dynamicValue["height"] = value.height;
return dynamicValue;
}
inline folly::dynamic toDynamic(const AndroidTextInputSelectionStruct &value) {
folly::dynamic dynamicValue = folly::dynamic::object();
dynamicValue["start"] = value.start;
dynamicValue["end"] = value.end;
return dynamicValue;
}
#endif
class AndroidTextInputProps final : public ViewProps, public BaseTextProps {
public:
AndroidTextInputProps() = default;
AndroidTextInputProps(
const AndroidTextInputProps &sourceProps,
const RawProps &rawProps);
folly::dynamic getDynamic() const;
#pragma mark - Props
const std::string autoCompleteType{};
const std::string returnKeyLabel{};
const int numberOfLines{0};
const bool disableFullscreenUI{false};
const std::string textBreakStrategy{};
const SharedColor underlineColorAndroid{};
const std::string inlineImageLeft{};
const int inlineImagePadding{0};
const std::string importantForAutofill{};
const bool showSoftInputOnFocus{false};
const std::string autoCapitalize{};
const bool autoCorrect{false};
const bool autoFocus{false};
const bool allowFontScaling{false};
const Float maxFontSizeMultiplier{0.0};
const bool editable{false};
const std::string keyboardType{};
const std::string returnKeyType{};
const int maxLength{0};
const bool multiline{false};
const std::string placeholder{};
const SharedColor placeholderTextColor{};
const bool secureTextEntry{false};
const SharedColor selectionColor{};
const AndroidTextInputSelectionStruct selection{};
const std::string value{};
const std::string defaultValue{};
const bool selectTextOnFocus{false};
const bool blurOnSubmit{false};
const bool caretHidden{false};
const bool contextMenuHidden{false};
const SharedColor textShadowColor{};
const Float textShadowRadius{0.0};
const std::string textDecorationLine{};
const std::string fontStyle{};
const AndroidTextInputTextShadowOffsetStruct textShadowOffset{};
const Float lineHeight{0.0};
const std::string textTransform{};
const int color{0};
const Float letterSpacing{0.0};
const Float fontSize{0.0};
const std::string textAlign{};
const bool includeFontPadding{false};
const std::string fontWeight{};
const std::string fontFamily{};
const std::string textAlignVertical{};
const SharedColor cursorColor{};
const int mostRecentEventCount{0};
const std::string text{};
/*
* Contains all prop values that affect visual representation of the
* paragraph.
*/
ParagraphAttributes const paragraphAttributes{};
/**
* 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?
*/
const bool hasPadding{};
const bool hasPaddingHorizontal{};
const bool hasPaddingVertical{};
const bool hasPaddingLeft{};
const bool hasPaddingTop{};
const bool hasPaddingRight{};
const bool hasPaddingBottom{};
const bool hasPaddingStart{};
const bool hasPaddingEnd{};
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const;
#endif
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,197 @@
/*
* Copyright (c) Facebook, Inc. and its 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 <fbjni/fbjni.h>
#include <react/attributedstring/AttributedStringBox.h>
#include <react/attributedstring/TextAttributes.h>
#include <react/components/text/BaseTextShadowNode.h>
#include <react/core/LayoutConstraints.h>
#include <react/core/LayoutContext.h>
#include <react/core/conversions.h>
#include <react/jni/ReadableNativeMap.h>
using namespace facebook::jni;
namespace facebook {
namespace react {
extern const char AndroidTextInputComponentName[] = "AndroidTextInput";
void AndroidTextInputShadowNode::setContextContainer(
ContextContainer *contextContainer) {
ensureUnsealed();
contextContainer_ = contextContainer;
}
AttributedString AndroidTextInputShadowNode::getAttributedString() const {
// Use BaseTextShadowNode to get attributed string from children
auto childTextAttributes = TextAttributes::defaultTextAttributes();
childTextAttributes.apply(getConcreteProps().textAttributes);
auto attributedString = AttributedString{};
auto attachments = BaseTextShadowNode::Attachments{};
BaseTextShadowNode::buildAttributedString(
childTextAttributes, *this, attributedString, attachments);
// 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);
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(fragment);
}
return attributedString;
}
// For measurement purposes, we want to make sure that there's at least a
// single character in the string so that the measured height is greater
// than zero. Otherwise, empty TextInputs with no placeholder don't
// display at all.
AttributedString AndroidTextInputShadowNode::getPlaceholderAttributedString()
const {
// Return placeholder text, since text and children are empty.
auto textAttributedString = AttributedString{};
auto fragment = AttributedString::Fragment{};
fragment.string = getConcreteProps().placeholder;
if (fragment.string.empty()) {
fragment.string = " ";
}
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.apply(getConcreteProps().textAttributes);
// If there's no text, it's possible that this Fragment isn't actually
// appended to the AttributedString (see implementation of appendFragment)
fragment.textAttributes = textAttributes;
fragment.parentShadowView = ShadowView(*this);
textAttributedString.appendFragment(fragment);
return textAttributedString;
}
void AndroidTextInputShadowNode::setTextLayoutManager(
SharedTextLayoutManager textLayoutManager) {
ensureUnsealed();
textLayoutManager_ = textLayoutManager;
}
AttributedString AndroidTextInputShadowNode::getMostRecentAttributedString()
const {
auto const &state = getStateData();
auto reactTreeAttributedString = getAttributedString();
// 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.attributedString
: reactTreeAttributedString);
}
void AndroidTextInputShadowNode::updateStateIfNeeded() {
ensureUnsealed();
auto reactTreeAttributedString = getAttributedString();
auto const &state = getStateData();
assert(textLayoutManager_);
assert(
(!state.layoutManager || state.layoutManager == textLayoutManager_) &&
"`StateData` refers to a different `TextLayoutManager`");
// 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 (state.reactTreeAttributedString == reactTreeAttributedString &&
state.layoutManager == textLayoutManager_) {
return;
}
// Store default TextAttributes in state.
// In the case where the TextInput is completely empty (no value, no
// defaultValue, no placeholder, no children) there are therefore no fragments
// in the AttributedString, and when State is updated, it needs some way to
// reconstruct a Fragment with default TextAttributes.
auto defaultTextAttributes = TextAttributes::defaultTextAttributes();
defaultTextAttributes.apply(getConcreteProps().textAttributes);
auto newEventCount =
(state.reactTreeAttributedString == reactTreeAttributedString
? 0
: getConcreteProps().mostRecentEventCount);
auto newAttributedString = getMostRecentAttributedString();
// 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.
setStateData(AndroidTextInputState{newEventCount,
newAttributedString,
reactTreeAttributedString,
getConcreteProps().paragraphAttributes,
defaultTextAttributes,
ShadowView(*this),
textLayoutManager_,
state.defaultThemePaddingStart,
state.defaultThemePaddingEnd,
state.defaultThemePaddingTop,
state.defaultThemePaddingBottom});
}
#pragma mark - LayoutableShadowNode
Size AndroidTextInputShadowNode::measure(
LayoutConstraints layoutConstraints) const {
// 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();
if (attributedString.isEmpty()) {
attributedString = getPlaceholderAttributedString();
}
if (attributedString.isEmpty()) {
return {0, 0};
}
return textLayoutManager_
->measure(
AttributedStringBox{attributedString},
getConcreteProps().paragraphAttributes,
layoutConstraints)
.size;
}
void AndroidTextInputShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded();
ConcreteViewShadowNode::layout(layoutContext);
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) Facebook, Inc. and its 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/components/view/ConcreteViewShadowNode.h>
#include <react/utils/ContextContainer.h>
#include <react/attributedstring/AttributedString.h>
namespace facebook {
namespace react {
extern const char AndroidTextInputComponentName[];
/*
* `ShadowNode` for <AndroidTextInput> component.
*/
class AndroidTextInputShadowNode : public ConcreteViewShadowNode<
AndroidTextInputComponentName,
AndroidTextInputProps,
AndroidTextInputEventEmitter,
AndroidTextInputState> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
void setContextContainer(ContextContainer *contextContainer);
/*
* Returns a `AttributedString` which represents text content of the node.
*/
AttributedString getAttributedString() const;
AttributedString getPlaceholderAttributedString() const;
/*
* Associates a shared TextLayoutManager with the node.
* `ParagraphShadowNode` uses the manager to measure text content
* and construct `ParagraphState` objects.
*/
void setTextLayoutManager(SharedTextLayoutManager textLayoutManager);
#pragma mark - LayoutableShadowNode
Size measure(LayoutConstraints layoutConstraints) const override;
void layout(LayoutContext layoutContext) override;
private:
ContextContainer *contextContainer_{};
/**
* Get the most up-to-date attributed string for measurement and State.
*/
AttributedString getMostRecentAttributedString() const;
/*
* Creates a `State` object (with `AttributedText` and
* `TextLayoutManager`) if needed.
*/
void updateStateIfNeeded();
SharedTextLayoutManager textLayoutManager_;
/*
* Cached attributed string that represents the content of the subtree started
* from the node.
*/
mutable folly::Optional<AttributedString> cachedAttributedString_{};
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) Facebook, Inc. and its 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/components/text/conversions.h>
#include <react/debug/debugStringConvertibleUtils.h>
namespace facebook {
namespace react {
#ifdef ANDROID
folly::dynamic AndroidTextInputState::getDynamic() const {
// Java doesn't need all fields, so we don't pass them along.
folly::dynamic newState = folly::dynamic::object();
newState["mostRecentEventCount"] = mostRecentEventCount;
newState["attributedString"] = toDynamic(attributedString);
newState["paragraphAttributes"] = toDynamic(paragraphAttributes);
newState["hash"] = newState["attributedString"]["hash"];
newState["hasThemeData"] = !isnan(defaultThemePaddingStart);
return newState;
}
#endif
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,185 @@
/*
* Copyright (c) Facebook, Inc. and its 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/attributedstring/AttributedString.h>
#include <react/attributedstring/ParagraphAttributes.h>
#include <react/textlayoutmanager/TextLayoutManager.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#endif
namespace facebook {
namespace react {
/*
* State for <Paragraph> component.
* Represents what to render and how to render.
*/
class AndroidTextInputState final {
public:
int64_t mostRecentEventCount{0};
/*
* All content of <TextInput> component represented as an `AttributedString`.
*/
AttributedString attributedString{};
/*
* 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{};
/**
* Default TextAttributes used if we need to construct a new Fragment.
* Only used if text is inserted into an AttributedString with no existing
* Fragments.
*/
TextAttributes defaultTextAttributes;
/**
* Default parent ShadowView used if we need to construct a new Fragment.
* Only used if text is inserted into an AttributedString with no existing
* Fragments.
*/
ShadowView defaultParentShadowView;
/*
* `TextLayoutManager` provides a connection to platform-specific
* text rendering infrastructure which is capable to render the
* `AttributedString`.
*/
SharedTextLayoutManager layoutManager{};
/**
* Communicates Android theme padding back to the ShadowNode / Component
* Descriptor for layout.
*/
float defaultThemePaddingStart{NAN};
float defaultThemePaddingEnd{NAN};
float defaultThemePaddingTop{NAN};
float defaultThemePaddingBottom{NAN};
#ifdef ANDROID
AttributedString updateAttributedString(
TextAttributes const &defaultTextAttributes,
ShadowView const &defaultParentShadowView,
AttributedString const &original,
folly::dynamic const &data) {
if (data["textChanged"].empty()) {
return original;
}
// TODO: parse other attributes besides just string?
// on the other hand, not much should be driven from Java
// TODO: it'd be really nice to treat these as operational transforms
// instead of having to pass the whole string across.
// Unfortunately we don't have a good way of communicating from Java to C++
// *which* version of the State changes should be applied to; and if there's
// a conflict, we don't have any recourse of any way to bail out of a
// commit.
auto str = AttributedString{};
int i = 0;
folly::dynamic fragments = data["textChanged"]["fragments"];
for (auto const &fragment : original.getFragments()) {
str.appendFragment(AttributedString::Fragment{
fragments.size() > i ? fragments[i]["string"].getString() : "",
fragment.textAttributes,
fragment.parentShadowView});
i++;
}
if (fragments.size() > original.getFragments().size()) {
for (; i < fragments.size(); i++) {
str.appendFragment(
AttributedString::Fragment{fragments[i]["string"].getString(),
defaultTextAttributes,
defaultParentShadowView});
}
}
return str;
}
AndroidTextInputState(
int64_t mostRecentEventCount,
AttributedString const &attributedString,
AttributedString const &reactTreeAttributedString,
ParagraphAttributes const &paragraphAttributes,
TextAttributes const &defaultTextAttributes,
ShadowView const &defaultParentShadowView,
SharedTextLayoutManager const &layoutManager,
float defaultThemePaddingStart,
float defaultThemePaddingEnd,
float defaultThemePaddingTop,
float defaultThemePaddingBottom)
: mostRecentEventCount(mostRecentEventCount),
attributedString(attributedString),
reactTreeAttributedString(reactTreeAttributedString),
paragraphAttributes(paragraphAttributes),
defaultTextAttributes(defaultTextAttributes),
defaultParentShadowView(defaultParentShadowView),
layoutManager(layoutManager),
defaultThemePaddingStart(defaultThemePaddingStart),
defaultThemePaddingEnd(defaultThemePaddingEnd),
defaultThemePaddingTop(defaultThemePaddingTop),
defaultThemePaddingBottom(defaultThemePaddingBottom) {}
AndroidTextInputState() = default;
AndroidTextInputState(
AndroidTextInputState const &previousState,
folly::dynamic const &data)
: mostRecentEventCount(data.getDefault(
"mostRecentEventCount",
previousState.mostRecentEventCount)
.getInt()),
attributedString(updateAttributedString(
previousState.defaultTextAttributes,
previousState.defaultParentShadowView,
previousState.attributedString,
data)),
reactTreeAttributedString(previousState.reactTreeAttributedString),
paragraphAttributes(previousState.paragraphAttributes),
defaultTextAttributes(previousState.defaultTextAttributes),
defaultParentShadowView(previousState.defaultParentShadowView),
layoutManager(previousState.layoutManager),
defaultThemePaddingStart(data.getDefault(
"themePaddingStart",
previousState.defaultThemePaddingStart)
.getDouble()),
defaultThemePaddingEnd(data.getDefault(
"themePaddingEnd",
previousState.defaultThemePaddingEnd)
.getDouble()),
defaultThemePaddingTop(data.getDefault(
"themePaddingTop",
previousState.defaultThemePaddingTop)
.getDouble()),
defaultThemePaddingBottom(
data.getDefault(
"themePaddingBottom",
previousState.defaultThemePaddingBottom)
.getDouble()){};
folly::dynamic getDynamic() const;
#endif
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,99 @@
load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_preprocessor_flags_for_build_mode")
load(
"//tools/build_defs/oss:rn_defs.bzl",
"ANDROID",
"APPLE",
"CXX",
"YOGA_CXX_TARGET",
"fb_xplat_cxx_test",
"get_apple_compiler_flags",
"get_apple_inspector_flags",
"react_native_xplat_target",
"rn_xplat_cxx_library",
"subdir_glob",
)
APPLE_COMPILER_FLAGS = get_apple_compiler_flags()
rn_xplat_cxx_library(
name = "iostextinput",
srcs = glob(
["**/*.cpp"],
exclude = glob(["tests/**/*.cpp"]),
),
headers = glob(
["**/*.h"],
exclude = glob(["tests/**/*.h"]),
),
header_namespace = "",
exported_headers = subdir_glob(
[
("", "*.h"),
],
# TODO(shergin) T26519801 Figure out better directories structure
prefix = "react/components/iostextinput",
),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
cxx_tests = [":tests"],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_labels = ["supermodule:ios/default/public.react_native.infra"],
fbobjc_preprocessor_flags = get_preprocessor_flags_for_build_mode() + get_apple_inspector_flags(),
force_static = True,
platforms = (ANDROID, APPLE, CXX),
preprocessor_flags = [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
visibility = ["PUBLIC"],
deps = [
"//xplat/fbsystrace:fbsystrace",
"//xplat/folly:container_evicting_cache_map",
"//xplat/folly:headers_only",
"//xplat/folly:memory",
"//xplat/folly:molly",
"//xplat/third-party/glog:glog",
YOGA_CXX_TARGET,
react_native_xplat_target("utils:utils"),
react_native_xplat_target("fabric/attributedstring:attributedstring"),
react_native_xplat_target("fabric/core:core"),
react_native_xplat_target("fabric/debug:debug"),
react_native_xplat_target("fabric/graphics:graphics"),
react_native_xplat_target("fabric/textlayoutmanager:textlayoutmanager"),
react_native_xplat_target("fabric/components/text:text"),
react_native_xplat_target("fabric/components/view:view"),
react_native_xplat_target("fabric/components/image:image"),
react_native_xplat_target("fabric/uimanager:uimanager"),
react_native_xplat_target("fabric/imagemanager:imagemanager"),
],
)
fb_xplat_cxx_test(
name = "tests",
srcs = glob(["tests/**/*.cpp"]),
headers = glob(["tests/**/*.h"]),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
contacts = ["oncall+react_native@xmail.facebook.com"],
platforms = (
# `Apple` and `Android` flavors are disabled because the module depends on `textlayoutmanager` which requires real an Emulator/Simulator to run.
# At the same time, the code of tests does not rely on the simulator capabilities and it would be wasteful to add `fbandroid_use_instrumentation_test = True`.
# (Beware of this option though.)
# ANDROID,
# APPLE,
CXX
),
deps = [
":iostextinput",
"//xplat/folly:molly",
"//xplat/third-party/gmock:gtest",
],
)

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Facebook, Inc. and its 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/components/iostextinput/TextInputShadowNode.h>
#include <react/core/ConcreteComponentDescriptor.h>
namespace facebook {
namespace react {
/*
* Descriptor for <TextInput> component.
*/
class TextInputComponentDescriptor final
: public ConcreteComponentDescriptor<TextInputShadowNode> {
public:
TextInputComponentDescriptor(ComponentDescriptorParameters const &parameters)
: ConcreteComponentDescriptor<TextInputShadowNode>(parameters) {
textLayoutManager_ =
std::make_shared<TextLayoutManager const>(contextContainer_);
}
protected:
void adopt(UnsharedShadowNode shadowNode) const override {
ConcreteComponentDescriptor::adopt(shadowNode);
assert(std::dynamic_pointer_cast<TextInputShadowNode>(shadowNode));
auto concreteShadowNode =
std::static_pointer_cast<TextInputShadowNode>(shadowNode);
concreteShadowNode->setTextLayoutManager(textLayoutManager_);
concreteShadowNode->dirtyLayout();
concreteShadowNode->enableMeasurement();
}
private:
TextLayoutManager::Shared textLayoutManager_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) Facebook, Inc. and its 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 {
namespace react {
static jsi::Value textInputMetricsPayload(
jsi::Runtime &runtime,
TextInputMetrics const &textInputMetrics) {
auto payload = jsi::Object(runtime);
payload.setProperty(
runtime,
"text",
jsi::String::createFromUtf8(runtime, textInputMetrics.text));
payload.setProperty(runtime, "eventCount", textInputMetrics.eventCount);
{
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 keyPressMetricsPayload(
jsi::Runtime &runtime,
KeyPressMetrics const &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 {
key = keyPressMetrics.text.front();
}
}
payload.setProperty(
runtime, "key", jsi::String::createFromUtf8(runtime, key));
return payload;
};
void TextInputEventEmitter::onFocus(
TextInputMetrics const &textInputMetrics) const {
dispatchTextInputEvent("focus", textInputMetrics);
}
void TextInputEventEmitter::onBlur(
TextInputMetrics const &textInputMetrics) const {
dispatchTextInputEvent("blur", textInputMetrics);
}
void TextInputEventEmitter::onChange(
TextInputMetrics const &textInputMetrics) const {
dispatchTextInputEvent("change", textInputMetrics);
}
void TextInputEventEmitter::onChangeText(
TextInputMetrics const &textInputMetrics) const {
dispatchTextInputEvent("changeText", textInputMetrics);
}
void TextInputEventEmitter::onContentSizeChange(
TextInputMetrics const &textInputMetrics) const {
dispatchTextInputEvent("contentSizeChange", textInputMetrics);
}
void TextInputEventEmitter::onSelectionChange(
TextInputMetrics const &textInputMetrics) const {
dispatchTextInputEvent("selectionChange", textInputMetrics);
}
void TextInputEventEmitter::onEndEditing(
TextInputMetrics const &textInputMetrics) const {
dispatchTextInputEvent("endEditing", textInputMetrics);
}
void TextInputEventEmitter::onSubmitEditing(
TextInputMetrics const &textInputMetrics) const {
dispatchTextInputEvent("submitEditing", textInputMetrics);
}
void TextInputEventEmitter::onKeyPress(
KeyPressMetrics const &keyPressMetrics) const {
dispatchEvent(
"keyPress",
[keyPressMetrics](jsi::Runtime &runtime) {
return keyPressMetricsPayload(runtime, keyPressMetrics);
},
EventPriority::AsynchronousBatched);
}
void TextInputEventEmitter::dispatchTextInputEvent(
std::string const &name,
TextInputMetrics const &textInputMetrics,
EventPriority priority) const {
dispatchEvent(
name,
[textInputMetrics](jsi::Runtime &runtime) {
return textInputMetricsPayload(runtime, textInputMetrics);
},
priority);
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) Facebook, Inc. and its 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/attributedstring/AttributedString.h>
#include <react/components/view/ViewEventEmitter.h>
namespace facebook {
namespace react {
class TextInputMetrics {
public:
std::string text;
AttributedString::Range selectionRange;
// ScrollView-like metrics
Size contentSize;
Point contentOffset;
EdgeInsets contentInset;
Size containerSize;
int eventCount;
};
class KeyPressMetrics {
public:
std::string text;
int eventCount;
};
class TextInputEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void onFocus(TextInputMetrics const &textInputMetrics) const;
void onBlur(TextInputMetrics const &textInputMetrics) const;
void onChange(TextInputMetrics const &textInputMetrics) const;
void onChangeText(TextInputMetrics const &textInputMetrics) const;
void onContentSizeChange(TextInputMetrics const &textInputMetrics) const;
void onSelectionChange(TextInputMetrics const &textInputMetrics) const;
void onEndEditing(TextInputMetrics const &textInputMetrics) const;
void onSubmitEditing(TextInputMetrics const &textInputMetrics) const;
void onKeyPress(KeyPressMetrics const &textInputMetrics) const;
private:
void dispatchTextInputEvent(
std::string const &name,
TextInputMetrics const &textInputMetrics,
EventPriority priority = EventPriority::AsynchronousBatched) const;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) Facebook, Inc. and its 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/attributedstring/conversions.h>
#include <react/components/iostextinput/propsConversions.h>
#include <react/core/propsConversions.h>
#include <react/graphics/conversions.h>
namespace facebook {
namespace react {
TextInputProps::TextInputProps(
TextInputProps const &sourceProps,
RawProps const &rawProps)
: ViewProps(sourceProps, rawProps),
BaseTextProps(sourceProps, rawProps),
traits(convertRawProp(rawProps, sourceProps.traits, {})),
paragraphAttributes(
convertRawProp(rawProps, sourceProps.paragraphAttributes, {})),
defaultValue(convertRawProp(
rawProps,
"defaultValue",
sourceProps.defaultValue,
{})),
placeholder(
convertRawProp(rawProps, "placeholder", sourceProps.placeholder, {})),
placeholderTextColor(convertRawProp(
rawProps,
"placeholderTextColor",
sourceProps.placeholderTextColor,
{})),
maxLength(
convertRawProp(rawProps, "maxLength", sourceProps.maxLength, {})),
cursorColor(
convertRawProp(rawProps, "cursorColor", sourceProps.cursorColor, {})),
selectionColor(convertRawProp(
rawProps,
"selectionColor",
sourceProps.selectionColor,
{})),
underlineColorAndroid(convertRawProp(
rawProps,
"underlineColorAndroid",
sourceProps.underlineColorAndroid,
{})),
text(convertRawProp(rawProps, "text", sourceProps.text, {})),
mostRecentEventCount(convertRawProp(
rawProps,
"mostRecentEventCount",
sourceProps.mostRecentEventCount,
{})){};
TextAttributes TextInputProps::getEffectiveTextAttributes() const {
auto result = TextAttributes::defaultTextAttributes();
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;
}
ParagraphAttributes TextInputProps::getEffectiveParagraphAttributes() const {
auto result = paragraphAttributes;
if (!traits.multiline) {
result.maximumNumberOfLines = 1;
}
return result;
}
#ifdef ANDROID
folly::dynamic TextInputProps::getDynamic() const {
folly::dynamic props = folly::dynamic::object();
props["value"] = value;
return props;
}
#endif
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) Facebook, Inc. and its 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/attributedstring/ParagraphAttributes.h>
#include <react/attributedstring/TextAttributes.h>
#include <react/components/iostextinput/conversions.h>
#include <react/components/iostextinput/primitives.h>
#include <react/components/text/BaseTextProps.h>
#include <react/components/view/ViewProps.h>
#include <react/core/Props.h>
#include <react/core/propsConversions.h>
#include <react/graphics/Color.h>
#include <react/imagemanager/primitives.h>
#include <vector>
namespace facebook {
namespace react {
class TextInputProps final : public ViewProps, public BaseTextProps {
public:
TextInputProps() = default;
TextInputProps(TextInputProps const &sourceProps, RawProps const &rawProps);
#pragma mark - Props
TextInputTraits const traits{};
ParagraphAttributes const paragraphAttributes{};
std::string const defaultValue{};
std::string const placeholder{};
SharedColor const placeholderTextColor{};
int maxLength{};
/*
* Tint colors
*/
SharedColor const cursorColor{};
SharedColor const selectionColor{};
// TODO: Rename to `tintColor` and make universal.
SharedColor const underlineColorAndroid{};
/*
* "Private" (only used by TextInput.js) props
*/
std::string const text{};
int const mostRecentEventCount{0};
/*
* Accessors
*/
TextAttributes getEffectiveTextAttributes() const;
ParagraphAttributes getEffectiveParagraphAttributes() const;
#ifdef ANDROID
folly::dynamic getDynamic() const;
#endif
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) Facebook, Inc. and its 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"
#include <react/attributedstring/AttributedStringBox.h>
#include <react/attributedstring/TextAttributes.h>
#include <react/core/LayoutConstraints.h>
#include <react/core/LayoutContext.h>
#include <react/core/conversions.h>
namespace facebook {
namespace react {
extern char const TextInputComponentName[] = "TextInput";
AttributedStringBox TextInputShadowNode::attributedStringBoxToMeasure() const {
bool hasMeaningfulState =
getState() && getState()->getRevision() != State::initialRevisionValue;
if (hasMeaningfulState) {
auto attributedStringBox = getStateData().attributedStringBox;
if (attributedStringBox.getMode() ==
AttributedStringBox::Mode::OpaquePointer ||
!attributedStringBox.getValue().isEmpty()) {
return getStateData().attributedStringBox;
}
}
auto attributedString =
hasMeaningfulState ? AttributedString{} : getAttributedString();
if (attributedString.isEmpty()) {
auto placeholder = getConcreteProps().placeholder;
// Note: `zero-width space` is insufficient in some cases (e.g. when we need
// to measure the "hight" of the font).
auto string = !placeholder.empty() ? placeholder : "I";
auto textAttributes = getConcreteProps().getEffectiveTextAttributes();
attributedString.appendFragment({string, textAttributes, {}});
}
return AttributedStringBox{attributedString};
}
AttributedString TextInputShadowNode::getAttributedString() const {
auto textAttributes = getConcreteProps().getEffectiveTextAttributes();
auto attributedString = AttributedString{};
attributedString.appendFragment(
AttributedString::Fragment{getConcreteProps().text, textAttributes});
auto attachments = Attachments{};
BaseTextShadowNode::buildAttributedString(
textAttributes, *this, attributedString, attachments);
return attributedString;
}
void TextInputShadowNode::setTextLayoutManager(
TextLayoutManager::Shared const &textLayoutManager) {
ensureUnsealed();
textLayoutManager_ = textLayoutManager;
}
void TextInputShadowNode::updateStateIfNeeded() {
ensureUnsealed();
auto reactTreeAttributedString = getAttributedString();
auto const &state = getStateData();
assert(textLayoutManager_);
assert(
(!state.layoutManager || state.layoutManager == textLayoutManager_) &&
"`StateData` refers to a different `TextLayoutManager`");
if (state.reactTreeAttributedString == reactTreeAttributedString &&
state.layoutManager == textLayoutManager_) {
return;
}
auto newState = TextInputState{};
newState.attributedStringBox = AttributedStringBox{reactTreeAttributedString};
newState.paragraphAttributes = getConcreteProps().paragraphAttributes;
newState.reactTreeAttributedString = reactTreeAttributedString;
newState.layoutManager = textLayoutManager_;
newState.mostRecentEventCount = getConcreteProps().mostRecentEventCount;
setStateData(std::move(newState));
}
#pragma mark - LayoutableShadowNode
Size TextInputShadowNode::measure(LayoutConstraints layoutConstraints) const {
return textLayoutManager_
->measure(
attributedStringBoxToMeasure(),
getConcreteProps().getEffectiveParagraphAttributes(),
layoutConstraints)
.size;
}
void TextInputShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded();
ConcreteViewShadowNode::layout(layoutContext);
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) Facebook, Inc. and its 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/attributedstring/AttributedString.h>
#include <react/components/iostextinput/TextInputEventEmitter.h>
#include <react/components/iostextinput/TextInputProps.h>
#include <react/components/iostextinput/TextInputState.h>
#include <react/components/text/BaseTextShadowNode.h>
#include <react/components/view/ConcreteViewShadowNode.h>
#include <react/textlayoutmanager/TextLayoutManager.h>
#include <react/utils/ContextContainer.h>
namespace facebook {
namespace react {
extern const char TextInputComponentName[];
/*
* `ShadowNode` for <TextInput> component.
*/
class TextInputShadowNode : public ConcreteViewShadowNode<
TextInputComponentName,
TextInputProps,
TextInputEventEmitter,
TextInputState>,
public BaseTextShadowNode {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::TextKind);
return traits;
}
/*
* Returns a `AttributedString` which represents text content of the node.
*/
AttributedString getAttributedString() const;
/*
* Associates a shared `TextLayoutManager` with the node.
* `TextInputShadowNode` uses the manager to measure text content
* and construct `TextInputState` objects.
*/
void setTextLayoutManager(TextLayoutManager::Shared const &textLayoutManager);
#pragma mark - LayoutableShadowNode
Size measure(LayoutConstraints layoutConstraints) const override;
void layout(LayoutContext layoutContext) override;
private:
/*
* Creates a `State` object if needed.
*/
void updateStateIfNeeded();
/*
* 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;
TextLayoutManager::Shared textLayoutManager_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,12 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TextInputState.h"
namespace facebook {
namespace react {} // namespace react
} // namespace facebook

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) Facebook, Inc. and its 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/attributedstring/AttributedStringBox.h>
#include <react/attributedstring/ParagraphAttributes.h>
#include <react/textlayoutmanager/TextLayoutManager.h>
#ifdef ANDROID
#include <folly/dynamic.h>
#endif
namespace facebook {
namespace react {
/*
* State for <TextInput> component.
*/
class TextInputState final {
public:
/*
* 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;
/*
* `TextLayoutManager` provides a connection to platform-specific
* text rendering infrastructure which is capable to render the
* `AttributedString`.
*/
SharedTextLayoutManager layoutManager;
size_t mostRecentEventCount{0};
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,210 @@
/*
* Copyright (c) Facebook, Inc. and its 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/components/iostextinput/primitives.h>
#include <react/core/propsConversions.h>
namespace facebook {
namespace react {
inline void fromRawValue(
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;
}
abort();
}
inline void fromRawValue(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;
}
abort();
}
inline void fromRawValue(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;
}
abort();
}
inline void fromRawValue(
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;
}
abort();
}
inline void fromRawValue(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 == "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 == "url") {
result = KeyboardType::URL;
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;
}
abort();
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,209 @@
/*
* Copyright (c) Facebook, Inc. and its 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 <string>
#include <better/optional.h>
namespace facebook {
namespace 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,
};
/*
* Controls features of text inputs.
*/
class TextInputTraits final {
public:
/*
* iOS & Android
* Default value: `false`.
*/
bool multiline{false};
/*
* iOS & Android
* Default value: `None`.
*/
AutocapitalizationType autocapitalizationType{AutocapitalizationType::None};
/*
* Can be empty (`null` in JavaScript) which means `default`.
* iOS & Android
* Default value: `empty` (`null`).
*/
better::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`).
*/
better::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 & Android
* Default value: `false`.
*/
bool blurOnSubmit{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};
/*
* 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 (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{};
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) Facebook, Inc. and its 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/components/iostextinput/primitives.h>
#include <react/core/propsConversions.h>
namespace facebook {
namespace react {
static TextInputTraits convertRawProp(
RawProps const &rawProps,
TextInputTraits const &sourceTraits,
TextInputTraits const &defaultTraits) {
auto traits = TextInputTraits{};
traits.multiline = convertRawProp(
rawProps, "multiline", sourceTraits.multiline, defaultTraits.multiline);
traits.autocapitalizationType = convertRawProp(
rawProps,
"autoCapitalize",
sourceTraits.autocapitalizationType,
defaultTraits.autocapitalizationType);
traits.autoCorrect = convertRawProp(
rawProps,
"autoCorrect",
sourceTraits.autoCorrect,
defaultTraits.autoCorrect);
traits.contextMenuHidden = convertRawProp(
rawProps,
"contextMenuHidden",
sourceTraits.contextMenuHidden,
defaultTraits.contextMenuHidden);
traits.editable = convertRawProp(
rawProps, "editable", sourceTraits.editable, defaultTraits.editable);
traits.enablesReturnKeyAutomatically = convertRawProp(
rawProps,
"enablesReturnKeyAutomatically",
sourceTraits.enablesReturnKeyAutomatically,
defaultTraits.enablesReturnKeyAutomatically);
traits.keyboardAppearance = convertRawProp(
rawProps,
"keyboardAppearance",
sourceTraits.keyboardAppearance,
defaultTraits.keyboardAppearance);
traits.spellCheck = convertRawProp(
rawProps,
"spellCheck",
sourceTraits.spellCheck,
defaultTraits.spellCheck);
traits.caretHidden = convertRawProp(
rawProps,
"caretHidden",
sourceTraits.caretHidden,
defaultTraits.caretHidden);
traits.clearButtonMode = convertRawProp(
rawProps,
"clearButtonMode",
sourceTraits.clearButtonMode,
defaultTraits.clearButtonMode);
traits.scrollEnabled = convertRawProp(
rawProps,
"scrollEnabled",
sourceTraits.scrollEnabled,
defaultTraits.scrollEnabled);
traits.secureTextEntry = convertRawProp(
rawProps,
"secureTextEntry",
sourceTraits.secureTextEntry,
defaultTraits.secureTextEntry);
traits.blurOnSubmit = convertRawProp(
rawProps,
"blurOnSubmit",
sourceTraits.blurOnSubmit,
defaultTraits.blurOnSubmit);
traits.clearTextOnFocus = convertRawProp(
rawProps,
"clearTextOnFocus",
sourceTraits.clearTextOnFocus,
defaultTraits.clearTextOnFocus);
traits.keyboardType = convertRawProp(
rawProps,
"keyboardType",
sourceTraits.keyboardType,
defaultTraits.keyboardType);
traits.returnKeyType = convertRawProp(
rawProps,
"returnKeyType",
sourceTraits.returnKeyType,
defaultTraits.returnKeyType);
traits.selectTextOnFocus = convertRawProp(
rawProps,
"selectTextOnFocus",
sourceTraits.selectTextOnFocus,
defaultTraits.selectTextOnFocus);
traits.textContentType = convertRawProp(
rawProps,
"textContentType",
sourceTraits.textContentType,
defaultTraits.textContentType);
traits.passwordRules = convertRawProp(
rawProps,
"passwordRules",
sourceTraits.passwordRules,
defaultTraits.passwordRules);
return traits;
}
} // namespace react
} // namespace facebook