@symbiote-native/engine 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/CMakeLists.txt +31 -1
- package/android/build.gradle +4 -1
- package/build/accessibility-info/index.android.js +21 -17
- package/build/accessibility-info/index.ios.js +2 -0
- package/build/accessibility-info/shared.d.ts +1 -1
- package/build/accessibility-props.d.ts +0 -11
- package/build/accessibility-props.js +30 -68
- package/build/alert/shared.d.ts +1 -1
- package/build/alert/shared.js +3 -7
- package/build/animated/graph.js +1 -1
- package/build/animated/leaf-lifecycle.js +2 -2
- package/build/app-state/index.d.ts +1 -1
- package/build/app-state/index.js +35 -27
- package/build/asset-source-resolver.d.ts +2 -0
- package/build/asset-source-resolver.js +13 -0
- package/build/back-handler/index.d.ts +7 -6
- package/build/back-handler/index.js +19 -16
- package/build/debug.js +8 -22
- package/build/dispatch.js +3 -9
- package/build/events/index.js +12 -3
- package/build/fabric-props.js +75 -180
- package/build/fabric.d.ts +2 -8
- package/build/fabric.js +27 -33
- package/build/host-access.d.ts +1 -128
- package/build/host-access.js +96 -205
- package/build/host-behavior.d.ts +1 -100
- package/build/host-behavior.js +125 -311
- package/build/image-loader.d.ts +4 -2
- package/build/image-loader.js +24 -48
- package/build/image-source-resolver.js +3 -7
- package/build/image-source-write.d.ts +1 -12
- package/build/image-source-write.js +28 -36
- package/build/imperative.d.ts +0 -28
- package/build/imperative.js +42 -92
- package/build/index.d.ts +3 -2
- package/build/index.js +30 -43
- package/build/invariant.d.ts +1 -0
- package/build/invariant.js +10 -0
- package/build/keyboard/index.js +11 -32
- package/build/linking/index.android.js +5 -3
- package/build/linking/shared.d.ts +1 -1
- package/build/linking/shared.js +16 -19
- package/build/mutation-buffer.d.ts +0 -177
- package/build/mutation-buffer.js +137 -308
- package/build/native-engine.d.ts +0 -102
- package/build/native-engine.js +38 -98
- package/build/native-events.d.ts +5 -0
- package/build/native-events.js +30 -18
- package/build/native-tree-host.d.ts +0 -21
- package/build/native-tree-host.js +14 -31
- package/build/node.d.ts +0 -212
- package/build/node.js +354 -774
- package/build/permissions-android/index.android.d.ts +59 -0
- package/build/permissions-android/index.android.js +47 -0
- package/build/permissions-android/index.d.ts +1 -115
- package/build/permissions-android/index.ios.d.ts +59 -0
- package/build/permissions-android/index.ios.js +31 -0
- package/build/permissions-android/index.js +3 -184
- package/build/permissions-android/shared.d.ts +63 -0
- package/build/permissions-android/shared.js +66 -0
- package/build/platform/index.android.js +3 -5
- package/build/platform/index.ios.js +3 -3
- package/build/platform/shared.d.ts +4 -0
- package/build/platform/shared.js +9 -0
- package/build/platform-color/index.android.d.ts +4 -0
- package/build/platform-color/index.android.js +7 -0
- package/build/platform-color/index.d.ts +2 -20
- package/build/platform-color/index.js +1 -45
- package/build/platform-color/shared.d.ts +21 -0
- package/build/platform-color/shared.js +41 -0
- package/build/post-commit.js +3 -8
- package/build/process-aspect-ratio.js +3 -7
- package/build/process-background-longhands.js +10 -19
- package/build/process-filter.js +11 -19
- package/build/process-font-variant.js +3 -7
- package/build/registry.d.ts +0 -33
- package/build/registry.js +22 -57
- package/build/report-error.js +4 -18
- package/build/settings/index.android.d.ts +6 -0
- package/build/settings/index.android.js +21 -0
- package/build/settings/index.d.ts +1 -8
- package/build/settings/index.ios.d.ts +8 -0
- package/build/settings/index.ios.js +122 -0
- package/build/settings/index.js +3 -122
- package/build/share/index.android.js +9 -32
- package/build/share/index.ios.js +14 -15
- package/build/share/shared.d.ts +4 -2
- package/build/share/shared.js +7 -10
- package/build/status-bar/index.android.js +1 -1
- package/build/status-bar/index.ios.js +4 -3
- package/build/structured-style.d.ts +0 -9
- package/build/structured-style.js +16 -31
- package/build/styles.js +3 -6
- package/build/surface.d.ts +0 -26
- package/build/surface.js +29 -76
- package/build/text-input-state.js +4 -8
- package/build/toast-android/index.android.d.ts +10 -0
- package/build/toast-android/index.android.js +108 -0
- package/build/toast-android/index.d.ts +1 -10
- package/build/toast-android/index.ios.d.ts +10 -0
- package/build/toast-android/index.ios.js +19 -0
- package/build/toast-android/index.js +3 -108
- package/build/touch-history.js +5 -11
- package/build/tree-host.d.ts +0 -270
- package/build/tree-host.js +63 -153
- package/build/view-config.js +17 -37
- package/cpp/SymbioteFabricProps.cpp +204 -120
- package/cpp/SymbioteFabricProps.h +5 -0
- package/cpp/SymbioteTree.cpp +20 -6
- package/package.json +2 -2
|
@@ -68,6 +68,7 @@ constexpr const char *kTextComponent = "RCTText";
|
|
|
68
68
|
constexpr const char *kAndroidSwitchComponent = "AndroidSwitch";
|
|
69
69
|
constexpr const char *kSinglelineTextInput = "RCTSinglelineTextInputView";
|
|
70
70
|
constexpr const char *kMultilineTextInput = "RCTMultilineTextInputView";
|
|
71
|
+
constexpr const char *kAndroidTextInput = "AndroidTextInput";
|
|
71
72
|
|
|
72
73
|
// Colour props must reach Fabric as platform ints. `fromRawValueShared.h` parses a CSS STRING only
|
|
73
74
|
// when `enableNativeCSSParsing()` is on, and it defaults to FALSE — an unparsed string falls
|
|
@@ -108,6 +109,8 @@ const std::unordered_set<std::string> kColorProps = {
|
|
|
108
109
|
"trackColorForTrue",
|
|
109
110
|
"trackColorForFalse",
|
|
110
111
|
"trackTintColor",
|
|
112
|
+
// ScrollView's Android fill below the content (`ScrollViewNativeComponent.js`: colorAttribute).
|
|
113
|
+
"endFillColor",
|
|
111
114
|
};
|
|
112
115
|
|
|
113
116
|
// ── COLOUR ───────────────────────────────────────────────────────────────────────────────────────
|
|
@@ -180,16 +183,36 @@ std::optional<uint32_t> parseCssColor(const std::string &text) {
|
|
|
180
183
|
* style uses, this runs per colour key per node per commit, and it is twenty lines.
|
|
181
184
|
*/
|
|
182
185
|
dynamic processColorValue(const dynamic &value) {
|
|
186
|
+
if (value.isObject()) {
|
|
187
|
+
// `DynamicColorIOS`: `PlatformColorParser.mm` reads each branch as an already-processed int,
|
|
188
|
+
// so RN's `processColorObject` processes them in JS. A string left here paints nothing.
|
|
189
|
+
const auto tuple = value.find("dynamic");
|
|
190
|
+
if (tuple == value.items().end() || !tuple->second.isObject()) return value;
|
|
191
|
+
dynamic branches = dynamic::object();
|
|
192
|
+
for (const auto &[branch, color] : tuple->second.items())
|
|
193
|
+
branches[branch] = processColorValue(color);
|
|
194
|
+
dynamic out = value;
|
|
195
|
+
out["dynamic"] = std::move(branches);
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
183
198
|
if (!value.isString()) return value;
|
|
184
199
|
const std::string &text = value.getString();
|
|
185
200
|
const std::optional<uint32_t> parsed = (!text.empty() && text[0] == '#')
|
|
186
201
|
? parseHexColor(std::string_view(text).substr(1))
|
|
187
202
|
: parseCssColor(text);
|
|
188
203
|
if (!parsed.has_value()) return value;
|
|
204
|
+
#ifdef ANDROID
|
|
205
|
+
// SIGNED on Android, as RN's `processColor` does (`normalizedColor | 0x0`): the props also reach
|
|
206
|
+
// Java ViewManagers as a Double, and Kotlin's `toInt()` SATURATES an unsigned 0xff...... to
|
|
207
|
+
// 0x7fffffff — every opaque background became translucent white while C++-read text colours
|
|
208
|
+
// stayed right.
|
|
209
|
+
return dynamic(static_cast<int64_t>(static_cast<int32_t>(*parsed)));
|
|
210
|
+
#else
|
|
189
211
|
// Widened rather than reinterpreted: RN's `processColor` yields an UNSIGNED 32-bit ARGB, and
|
|
190
212
|
// `fromRawValueShared` reads it back as `(int64_t)value` before shifting. A signed narrowing here
|
|
191
213
|
// would make every colour with alpha 0x80 or above negative.
|
|
192
214
|
return dynamic(static_cast<int64_t>(*parsed));
|
|
215
|
+
#endif
|
|
193
216
|
}
|
|
194
217
|
|
|
195
218
|
dynamic processValue(const std::string &key, const dynamic &value) {
|
|
@@ -530,6 +553,7 @@ const std::unordered_map<std::string, std::string> &enterKeyHintToReturnKeyType(
|
|
|
530
553
|
return *table;
|
|
531
554
|
}
|
|
532
555
|
|
|
556
|
+
#ifdef ANDROID
|
|
533
557
|
/** RN's W3C autocomplete -> Android `autoComplete` map, TextInput.js:828. */
|
|
534
558
|
const std::unordered_map<std::string, std::string> &autoCompleteWebToAndroid() {
|
|
535
559
|
static const auto *table = new std::unordered_map<std::string, std::string>{
|
|
@@ -566,7 +590,7 @@ const std::unordered_map<std::string, std::string> &autoCompleteWebToAndroid() {
|
|
|
566
590
|
};
|
|
567
591
|
return *table;
|
|
568
592
|
}
|
|
569
|
-
|
|
593
|
+
#else
|
|
570
594
|
/** RN's W3C autocomplete -> iOS `textContentType` map, TextInput.js:862. */
|
|
571
595
|
const std::unordered_map<std::string, std::string> &autoCompleteWebToTextContentType() {
|
|
572
596
|
static const auto *table = new std::unordered_map<std::string, std::string>{
|
|
@@ -609,6 +633,7 @@ const std::unordered_map<std::string, std::string> &autoCompleteWebToTextContent
|
|
|
609
633
|
};
|
|
610
634
|
return *table;
|
|
611
635
|
}
|
|
636
|
+
#endif
|
|
612
637
|
|
|
613
638
|
/** The string a key holds, or null when it is absent or is not a string. */
|
|
614
639
|
const std::string *stringAt(const dynamic &props, const char *key) {
|
|
@@ -632,6 +657,31 @@ std::optional<bool> boolAt(const dynamic &props, const char *key) {
|
|
|
632
657
|
return found->getBool();
|
|
633
658
|
}
|
|
634
659
|
|
|
660
|
+
/** Any value but `null` / absent — RN's `!= null`. */
|
|
661
|
+
const dynamic *presentAt(const dynamic &props, const char *key) {
|
|
662
|
+
const dynamic *found = props.get_ptr(key);
|
|
663
|
+
if (found == nullptr || found->isNull()) return nullptr;
|
|
664
|
+
return found;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* A colour an app authored, in ANY ColorValue shape RN accepts: a string, a number, or an opaque
|
|
669
|
+
* PlatformColor / DynamicColorIOS object. Reading only strings dropped every PlatformColor silently.
|
|
670
|
+
*/
|
|
671
|
+
const dynamic *colorAt(const dynamic &props, const char *key) {
|
|
672
|
+
return presentAt(props, key);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** `colorAt`, then JS truthiness, for RN's `if (color)` sites: `''`, `0` and `false` are absent. */
|
|
676
|
+
const dynamic *truthyColorAt(const dynamic &props, const char *key) {
|
|
677
|
+
const dynamic *found = colorAt(props, key);
|
|
678
|
+
if (found == nullptr) return nullptr;
|
|
679
|
+
if (found->isBool() && !found->getBool()) return nullptr;
|
|
680
|
+
if (found->isString() && found->getString().empty()) return nullptr;
|
|
681
|
+
if (found->isNumber() && found->asDouble() == 0) return nullptr;
|
|
682
|
+
return found;
|
|
683
|
+
}
|
|
684
|
+
|
|
635
685
|
/** A safe lookup: the mapped token, or null when the map has no entry. The caller owns the fallback. */
|
|
636
686
|
const std::string *mappedToken(
|
|
637
687
|
const std::unordered_map<std::string, std::string> &table,
|
|
@@ -671,8 +721,11 @@ std::string foldSubmitBehavior(
|
|
|
671
721
|
dynamic foldTextInputAliases(const dynamic &props, bool isMultiline) {
|
|
672
722
|
dynamic out = props;
|
|
673
723
|
|
|
724
|
+
// THE WEB SPELLING WINS (TextInput.js:919-937): `inputMode ? map[inputMode] : keyboardType`, and
|
|
725
|
+
// the same for `enterKeyHint` and `readOnly`. An unmapped token resolves to nothing, as RN's map
|
|
726
|
+
// lookup does, rather than falling back to the native prop.
|
|
674
727
|
const std::string *inputMode = stringAt(props, "inputMode");
|
|
675
|
-
if (inputMode != nullptr &&
|
|
728
|
+
if (inputMode != nullptr && !inputMode->empty()) {
|
|
676
729
|
// `search` is the ONE token RN resolves per platform (TextInput.js:815-825): iOS has a dedicated
|
|
677
730
|
// search keyboard whose return key is a magnifier, every other host falls back to the default.
|
|
678
731
|
if (*inputMode == "search") {
|
|
@@ -684,21 +737,41 @@ dynamic foldTextInputAliases(const dynamic &props, bool isMultiline) {
|
|
|
684
737
|
} else {
|
|
685
738
|
const std::string *mapped = mappedToken(inputModeToKeyboardType(), *inputMode);
|
|
686
739
|
if (mapped != nullptr) out["keyboardType"] = *mapped;
|
|
740
|
+
else out.erase("keyboardType");
|
|
687
741
|
}
|
|
688
742
|
}
|
|
689
743
|
|
|
690
744
|
const std::string *enterKeyHint = stringAt(props, "enterKeyHint");
|
|
691
|
-
if (enterKeyHint != nullptr &&
|
|
745
|
+
if (enterKeyHint != nullptr && !enterKeyHint->empty()) {
|
|
692
746
|
const std::string *mapped = mappedToken(enterKeyHintToReturnKeyType(), *enterKeyHint);
|
|
693
747
|
if (mapped != nullptr) out["returnKeyType"] = *mapped;
|
|
748
|
+
else out.erase("returnKeyType");
|
|
694
749
|
}
|
|
695
750
|
|
|
696
751
|
// The web spelling is the NEGATION of the native one. Getting it backwards makes every read-only
|
|
697
752
|
// field editable, silently.
|
|
698
753
|
const std::optional<bool> readOnly = boolAt(props, "readOnly");
|
|
699
|
-
if (readOnly.has_value()
|
|
700
|
-
|
|
754
|
+
if (readOnly.has_value()) out["editable"] = !*readOnly;
|
|
755
|
+
|
|
756
|
+
// TextInput.js:708-711 — `rows ?? numberOfLines`, and `focusable` resolved and ALWAYS sent:
|
|
757
|
+
// `tabIndex !== undefined ? !tabIndex : focusable !== false`.
|
|
758
|
+
const dynamic *rows = presentAt(props, "rows");
|
|
759
|
+
if (rows != nullptr) out["numberOfLines"] = *rows;
|
|
760
|
+
const dynamic *tabIndex = props.get_ptr("tabIndex");
|
|
761
|
+
if (tabIndex != nullptr) {
|
|
762
|
+
out["focusable"] = tabIndex->isNumber() ? tabIndex->asDouble() == 0 : tabIndex->isNull();
|
|
763
|
+
} else {
|
|
764
|
+
out["focusable"] = boolAt(props, "focusable") != std::optional<bool>(false);
|
|
701
765
|
}
|
|
766
|
+
// TextInput.js:904 — `allowFontScaling = true`.
|
|
767
|
+
if (props.get_ptr("allowFontScaling") == nullptr) out["allowFontScaling"] = true;
|
|
768
|
+
|
|
769
|
+
#ifdef ANDROID
|
|
770
|
+
// TextInput.js:728,734 — Android-only defaults: `autoCapitalize || 'sentences'`, `placeholder ?? ''`.
|
|
771
|
+
const std::string *capitalize = stringAt(props, "autoCapitalize");
|
|
772
|
+
if (capitalize == nullptr || capitalize->empty()) out["autoCapitalize"] = "sentences";
|
|
773
|
+
if (presentAt(props, "placeholder") == nullptr) out["placeholder"] = "";
|
|
774
|
+
#endif
|
|
702
775
|
|
|
703
776
|
out["submitBehavior"] = foldSubmitBehavior(
|
|
704
777
|
stringAt(props, "submitBehavior"), boolAt(props, "blurOnSubmit"), isMultiline);
|
|
@@ -713,24 +786,26 @@ dynamic foldTextInputAliases(const dynamic &props, bool isMultiline) {
|
|
|
713
786
|
}
|
|
714
787
|
}
|
|
715
788
|
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
719
|
-
// back to ITSELF (RN's `?? autoComplete`); one with no iOS entry leaves `textContentType` unset.
|
|
789
|
+
// TextInput.js:938-954, split per platform: Android sends the mapped token as `autoComplete` (a
|
|
790
|
+
// token with no entry falls back to ITSELF, `?? autoComplete`) and derives no `textContentType`;
|
|
791
|
+
// iOS sends NO `autoComplete` and derives `textContentType` when none is authored.
|
|
720
792
|
const std::string *autoComplete = stringAt(props, "autoComplete");
|
|
793
|
+
#ifdef ANDROID
|
|
721
794
|
if (autoComplete != nullptr) {
|
|
722
795
|
const std::string *android = mappedToken(autoCompleteWebToAndroid(), *autoComplete);
|
|
723
796
|
out["autoComplete"] = android != nullptr ? *android : *autoComplete;
|
|
724
|
-
if (out.get_ptr("textContentType") == nullptr) {
|
|
725
|
-
const std::string *ios = mappedToken(autoCompleteWebToTextContentType(), *autoComplete);
|
|
726
|
-
if (ios != nullptr) out["textContentType"] = *ios;
|
|
727
|
-
}
|
|
728
797
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
if (
|
|
732
|
-
|
|
798
|
+
#else
|
|
799
|
+
out.erase("autoComplete");
|
|
800
|
+
if (autoComplete != nullptr && presentAt(props, "textContentType") == nullptr) {
|
|
801
|
+
const std::string *ios = mappedToken(autoCompleteWebToTextContentType(), *autoComplete);
|
|
802
|
+
if (ios != nullptr) out["textContentType"] = *ios;
|
|
733
803
|
}
|
|
804
|
+
#endif
|
|
805
|
+
|
|
806
|
+
// `inputMode: 'none'` is how the web spells "focusable but no keyboard" — and it wins
|
|
807
|
+
// (`inputMode == null ? showSoftInputOnFocus : inputMode !== 'none'`).
|
|
808
|
+
if (inputMode != nullptr) out["showSoftInputOnFocus"] = *inputMode != "none";
|
|
734
809
|
|
|
735
810
|
// ANDROID ONLY, and that is F-76 rather than tidiness: iOS's `RCTSinglelineTextInputView`
|
|
736
811
|
// ViewConfig does not declare `underlineColorAndroid` at all, so RN's own
|
|
@@ -749,6 +824,8 @@ dynamic foldTextInputAliases(const dynamic &props, bool isMultiline) {
|
|
|
749
824
|
out.erase("enterKeyHint");
|
|
750
825
|
out.erase("readOnly");
|
|
751
826
|
out.erase("blurOnSubmit");
|
|
827
|
+
out.erase("rows");
|
|
828
|
+
out.erase("tabIndex");
|
|
752
829
|
return out;
|
|
753
830
|
}
|
|
754
831
|
|
|
@@ -1096,9 +1173,21 @@ dynamic foldRefreshWrapperProps(const dynamic &props, const IFirstChild &child)
|
|
|
1096
1173
|
* not know throws nothing, logs nothing and paints nothing, so the strip is only ever visible in a
|
|
1097
1174
|
* payload test.
|
|
1098
1175
|
*/
|
|
1099
|
-
dynamic foldScrollViewProps(
|
|
1176
|
+
dynamic foldScrollViewProps(
|
|
1177
|
+
const dynamic &props,
|
|
1178
|
+
bool isHorizontal,
|
|
1179
|
+
bool isWrapped,
|
|
1180
|
+
bool hasMomentumListener) {
|
|
1100
1181
|
dynamic out = props;
|
|
1101
1182
|
|
|
1183
|
+
// `ScrollView.js:1797-1808`. Android's ReactScrollView emits momentum events only with
|
|
1184
|
+
// `sendMomentumEvents` on; sticky headers need every scroll frame; snap edges default on.
|
|
1185
|
+
out["sendMomentumEvents"] = hasMomentumListener;
|
|
1186
|
+
const dynamic *sticky = props.get_ptr("stickyHeaderIndices");
|
|
1187
|
+
if (sticky != nullptr && sticky->isArray() && !sticky->empty()) out["scrollEventThrottle"] = 1;
|
|
1188
|
+
out["snapToStart"] = boolAt(props, "snapToStart") != std::optional<bool>(false);
|
|
1189
|
+
out["snapToEnd"] = boolAt(props, "snapToEnd") != std::optional<bool>(false);
|
|
1190
|
+
|
|
1102
1191
|
const dynamic *authored = props.get_ptr("style");
|
|
1103
1192
|
// WRAPPED IS A DIFFERENT COMPOSITION, not an extra one: on Android a RefreshControl becomes this
|
|
1104
1193
|
// node's PARENT and takes the LAYOUT half of the app's style with it, so composing `[base,
|
|
@@ -1112,7 +1201,8 @@ dynamic foldScrollViewProps(const dynamic &props, bool isHorizontal, bool isWrap
|
|
|
1112
1201
|
? splitScrollViewStyle(isHorizontal, authored).inner
|
|
1113
1202
|
: composeUnder(scrollViewBaseStyle(isHorizontal), authored);
|
|
1114
1203
|
|
|
1115
|
-
|
|
1204
|
+
// `ScrollView.js:1862` — defaulted only under the refresh wrap; elsewhere the authored value alone.
|
|
1205
|
+
if (isWrapped) out["nestedScrollEnabled"] = boolAt(props, "nestedScrollEnabled").value_or(true);
|
|
1116
1206
|
|
|
1117
1207
|
const std::optional<bool> authoredAxis = boolAt(props, "horizontal");
|
|
1118
1208
|
if (authoredAxis.has_value() && *authoredAxis != isHorizontal) {
|
|
@@ -1235,6 +1325,20 @@ dynamic foldScrollContentProps(
|
|
|
1235
1325
|
out["style"] = std::move(composed);
|
|
1236
1326
|
}
|
|
1237
1327
|
if (preserves) out["collapsableChildren"] = false;
|
|
1328
|
+
|
|
1329
|
+
// `ScrollView.js:1740-1745` — the scroller's clipping flag, forced off on Android while any header
|
|
1330
|
+
// sticks (clipping breaks sticky headers there).
|
|
1331
|
+
const dynamic *clipped = ownerProps != nullptr ? presentAt(*ownerProps, "removeClippedSubviews") : nullptr;
|
|
1332
|
+
#ifdef ANDROID
|
|
1333
|
+
const dynamic *sticky = ownerProps != nullptr ? ownerProps->get_ptr("stickyHeaderIndices") : nullptr;
|
|
1334
|
+
if (sticky != nullptr && sticky->isArray() && !sticky->empty()) {
|
|
1335
|
+
out["removeClippedSubviews"] = false;
|
|
1336
|
+
} else if (clipped != nullptr) {
|
|
1337
|
+
out["removeClippedSubviews"] = *clipped;
|
|
1338
|
+
}
|
|
1339
|
+
#else
|
|
1340
|
+
if (clipped != nullptr) out["removeClippedSubviews"] = *clipped;
|
|
1341
|
+
#endif
|
|
1238
1342
|
return out;
|
|
1239
1343
|
}
|
|
1240
1344
|
|
|
@@ -1479,8 +1583,8 @@ dynamic foldActivityIndicatorSpinnerProps(
|
|
|
1479
1583
|
out["animating"] = boolAt(props, "animating").value_or(true);
|
|
1480
1584
|
out["hidesWhenStopped"] = boolAt(props, "hidesWhenStopped").value_or(true);
|
|
1481
1585
|
|
|
1482
|
-
|
|
1483
|
-
if (color == nullptr
|
|
1586
|
+
// Any ColorValue the app wrote stands (RN defaults only an absent one).
|
|
1587
|
+
if (colorAt(props, "color") == nullptr) {
|
|
1484
1588
|
if (isAndroidProgressBar) out.erase("color");
|
|
1485
1589
|
else out["color"] = kSpinnerIosDefaultColor;
|
|
1486
1590
|
}
|
|
@@ -1519,38 +1623,6 @@ dynamic foldActivityIndicatorSpinnerProps(
|
|
|
1519
1623
|
* tint, the Android view style) read it off `propsOf(node)`, which this cannot reach. That
|
|
1520
1624
|
* separation is what makes the strip safe here and unsafe one layer up.
|
|
1521
1625
|
*/
|
|
1522
|
-
/**
|
|
1523
|
-
* Button's TITLE, as it is rendered: uppercase on Android, verbatim everywhere else
|
|
1524
|
-
* (`Button.js:352-353`).
|
|
1525
|
-
*
|
|
1526
|
-
* `#ifdef ANDROID` rather than a view-name branch, and for the reason `decelerationRate`'s constants
|
|
1527
|
-
* take one: there is no name to read. A raw text commits as `RCTRawText` on both platforms — unlike
|
|
1528
|
-
* `Switch`/`AndroidSwitch`, where the two platforms genuinely are two Fabric components and the wire
|
|
1529
|
-
* already says which. So the Android arm is unreachable headless, like `android_ripple`'s, and that
|
|
1530
|
-
* is a recorded gap rather than a hidden one.
|
|
1531
|
-
*
|
|
1532
|
-
* ASCII-only, deliberately, and it is upstream's own behaviour rather than a shortcut: RN calls
|
|
1533
|
-
* JavaScript's `String.prototype.toUpperCase`, which is full Unicode, so a Cyrillic or Greek label
|
|
1534
|
-
* uppercases there and would not here. Left ASCII because the alternative is dragging ICU into the
|
|
1535
|
-
* engine for a label that is uppercased only on Android, and a wrong-case label is a cosmetic
|
|
1536
|
-
* difference on one platform rather than a broken control. Recorded so it is a decision.
|
|
1537
|
-
*/
|
|
1538
|
-
dynamic foldButtonLabel(const dynamic &props) {
|
|
1539
|
-
#ifdef ANDROID
|
|
1540
|
-
const dynamic *text = props.get_ptr("text");
|
|
1541
|
-
if (text == nullptr || !text->isString()) return props;
|
|
1542
|
-
std::string upper = text->asString();
|
|
1543
|
-
for (char &character : upper)
|
|
1544
|
-
character = static_cast<char>(
|
|
1545
|
-
std::toupper(static_cast<unsigned char>(character)));
|
|
1546
|
-
dynamic out = props;
|
|
1547
|
-
out["text"] = std::move(upper);
|
|
1548
|
-
return out;
|
|
1549
|
-
#else
|
|
1550
|
-
return props;
|
|
1551
|
-
#endif
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
1626
|
/**
|
|
1555
1627
|
* Button's own `disabled`, which is three questions where a plain touchable asks one:
|
|
1556
1628
|
* `props.disabled ?? aria-disabled ?? accessibilityState.disabled` (`Button.js:331,337`).
|
|
@@ -1631,8 +1703,8 @@ dynamic foldButtonLabelStyle(
|
|
|
1631
1703
|
#else
|
|
1632
1704
|
style["color"] = kIosButtonBlue;
|
|
1633
1705
|
style["fontSize"] = kIosButtonFontSize;
|
|
1634
|
-
const dynamic *color = owner
|
|
1635
|
-
if (color != nullptr
|
|
1706
|
+
const dynamic *color = truthyColorAt(*owner, "color");
|
|
1707
|
+
if (color != nullptr) style["color"] = *color;
|
|
1636
1708
|
#endif
|
|
1637
1709
|
|
|
1638
1710
|
const std::optional<bool> disabled = buttonDisabled(*owner);
|
|
@@ -1655,9 +1727,6 @@ dynamic foldButtonLabelStyle(
|
|
|
1655
1727
|
}
|
|
1656
1728
|
|
|
1657
1729
|
#ifdef ANDROID
|
|
1658
|
-
// `TouchableNativeFeedback.js` via `Platform.Version >= 23` — foreground ripples need API 23.
|
|
1659
|
-
constexpr int kAndroidForegroundMinVersion = 23;
|
|
1660
|
-
|
|
1661
1730
|
// `Button.js:394-437`, one constant per literal so a value cannot drift silently.
|
|
1662
1731
|
constexpr int kAndroidButtonElevation = 4;
|
|
1663
1732
|
constexpr int kAndroidDisabledElevation = 0;
|
|
@@ -1665,26 +1734,6 @@ constexpr int kAndroidButtonBorderRadius = 2;
|
|
|
1665
1734
|
constexpr const char *kAndroidButtonBlue = "#2196F3";
|
|
1666
1735
|
constexpr const char *kAndroidDisabledBackground = "#dfdfdf";
|
|
1667
1736
|
|
|
1668
|
-
/**
|
|
1669
|
-
* The running device's API level, and the one line of the Android branch a HOST build cannot have.
|
|
1670
|
-
*
|
|
1671
|
-
* `android_get_device_api_level` is the NDK's, so it exists when `__ANDROID__` is defined — which the
|
|
1672
|
-
* real toolchain sets and the test host's `-DANDROID` does not. That split is the point rather than
|
|
1673
|
-
* a workaround: the rule's LOGIC becomes testable in the Android arm of the test host
|
|
1674
|
-
* (`tests/CMakeLists.txt`, `SYMBIOTE_PLATFORM_ANDROID`) while the query itself stays the device's.
|
|
1675
|
-
*
|
|
1676
|
-
* The host answers with the minimum RN supports, so the arm exercises the branch an app on a modern
|
|
1677
|
-
* device takes. The other branch is reachable only on a device old enough to need it, which is where
|
|
1678
|
-
* it always was.
|
|
1679
|
-
*/
|
|
1680
|
-
int androidApiLevel() {
|
|
1681
|
-
#ifdef __ANDROID__
|
|
1682
|
-
return android_get_device_api_level();
|
|
1683
|
-
#else
|
|
1684
|
-
return kAndroidForegroundMinVersion;
|
|
1685
|
-
#endif
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
1737
|
// RN's `TouchableNativeFeedback.SelectableBackground()` with no ripple radius, which is what its
|
|
1689
1738
|
// body falls back to when the app passes no `background` (`:343-348`), and what Button's own view
|
|
1690
1739
|
// gets because TNF clones onto it (`:339`).
|
|
@@ -1740,8 +1789,8 @@ dynamic foldButtonProps(
|
|
|
1740
1789
|
dynamic style = dynamic::object();
|
|
1741
1790
|
style["elevation"] = kAndroidButtonElevation;
|
|
1742
1791
|
style["borderRadius"] = kAndroidButtonBorderRadius;
|
|
1743
|
-
const
|
|
1744
|
-
style["backgroundColor"] = color != nullptr ? *color : kAndroidButtonBlue;
|
|
1792
|
+
const dynamic *color = truthyColorAt(authored, "color");
|
|
1793
|
+
style["backgroundColor"] = color != nullptr ? *color : dynamic(kAndroidButtonBlue);
|
|
1745
1794
|
if (buttonDisabled(authored).value_or(false)) {
|
|
1746
1795
|
style["elevation"] = kAndroidDisabledElevation;
|
|
1747
1796
|
style["backgroundColor"] = kAndroidDisabledBackground;
|
|
@@ -1808,24 +1857,27 @@ const std::array<const char *, 12> kPressableMachineKeys = {
|
|
|
1808
1857
|
* onto its OWN View (`Pressable.js:251`), which is why one node carries it and no inner view is
|
|
1809
1858
|
* needed.
|
|
1810
1859
|
*
|
|
1811
|
-
* The colour
|
|
1812
|
-
*
|
|
1813
|
-
*
|
|
1814
|
-
* documented "no tint".
|
|
1860
|
+
* The colour is converted HERE, as RN's `useAndroidRippleForView.js:58` does with `processColor`:
|
|
1861
|
+
* the dict is nested, so the top-level colour pass never sees it, and `ReactDrawableHelper.getColor`
|
|
1862
|
+
* reads it with `getInt` — a string there is a native type error, not a resolved colour. `null` is
|
|
1863
|
+
* the documented "no tint". A PlatformColor object passes through, as `processColor` leaves it.
|
|
1815
1864
|
*
|
|
1816
|
-
*
|
|
1817
|
-
* pressIn/pressMove and `Commands.setPressed` on pressIn/pressOut, which is what makes the ripple
|
|
1818
|
-
* originate at the touch point. Neither the old wrapper nor the behavior ever sent them.
|
|
1865
|
+
* `:57` — no ripple at all unless color, borderless or radius is set.
|
|
1819
1866
|
*/
|
|
1820
1867
|
#ifdef ANDROID
|
|
1821
1868
|
void applyAndroidRipple(dynamic &out, const dynamic &config) {
|
|
1869
|
+
const dynamic *color = colorAt(config, "color");
|
|
1870
|
+
const dynamic *borderless = presentAt(config, "borderless");
|
|
1871
|
+
const dynamic *radius = presentAt(config, "radius");
|
|
1872
|
+
if (color == nullptr && borderless == nullptr && radius == nullptr) return;
|
|
1873
|
+
|
|
1822
1874
|
dynamic background = dynamic::object();
|
|
1823
1875
|
background["type"] = "RippleAndroid";
|
|
1824
|
-
|
|
1825
|
-
background["color"] = color != nullptr ? dynamic(*color) : dynamic(nullptr);
|
|
1876
|
+
background["color"] = color != nullptr ? processColorValue(*color) : dynamic(nullptr);
|
|
1826
1877
|
background["borderless"] = boolAt(config, "borderless").value_or(false);
|
|
1827
|
-
|
|
1828
|
-
|
|
1878
|
+
if (radius != nullptr) background["rippleRadius"] = *radius;
|
|
1879
|
+
const dynamic *alpha = presentAt(config, "alpha");
|
|
1880
|
+
background["alpha"] = alpha != nullptr ? *alpha : dynamic(nullptr);
|
|
1829
1881
|
|
|
1830
1882
|
out[boolAt(config, "foreground").value_or(false) ? "nativeForegroundAndroid"
|
|
1831
1883
|
: "nativeBackgroundAndroid"] =
|
|
@@ -2042,12 +2094,13 @@ dynamic foldCloneOntoChild(
|
|
|
2042
2094
|
dynamic background = authored != nullptr && authored->isObject()
|
|
2043
2095
|
? *authored
|
|
2044
2096
|
: selectableItemBackground();
|
|
2045
|
-
// `
|
|
2046
|
-
//
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2097
|
+
// `TouchableNativeFeedback.Ripple(color)` leaves the colour a string; RN's factory runs it
|
|
2098
|
+
// through `processColor`, and Java reads it with `getInt` (see `applyAndroidRipple`).
|
|
2099
|
+
if (dynamic *rippleColor = background.get_ptr("color")) *rippleColor = processColorValue(*rippleColor);
|
|
2100
|
+
// `:402-405` — `canUseNativeForeground()` is `Platform.OS === 'android'`, true on this branch.
|
|
2101
|
+
out[boolAt(source, "useForeground").value_or(false) ? "nativeForegroundAndroid"
|
|
2102
|
+
: "nativeBackgroundAndroid"] =
|
|
2103
|
+
std::move(background);
|
|
2051
2104
|
}
|
|
2052
2105
|
#endif
|
|
2053
2106
|
return out;
|
|
@@ -2195,6 +2248,17 @@ dynamic resolveImageSources(const dynamic &props) {
|
|
|
2195
2248
|
dynamic foldImageProps(const dynamic &props, bool ariaHiddenIsTrue) {
|
|
2196
2249
|
dynamic out = props;
|
|
2197
2250
|
out["source"] = resolveImageSources(props);
|
|
2251
|
+
#ifdef ANDROID
|
|
2252
|
+
// `ReactImageView.setSource` ignores headers inside a source; only the top-level `headers` prop
|
|
2253
|
+
// reaches the request (`Image.android.js` lifts `source_[0].headers`). RN lifts them only when
|
|
2254
|
+
// `source_` is an array; a single object source's own headers were already dropped at write time
|
|
2255
|
+
// (`image-source-write.ts`), the one place its authored shape is still visible.
|
|
2256
|
+
const dynamic &sources = out["source"];
|
|
2257
|
+
if (sources.isArray() && !sources.empty() && sources.at(0).isObject()) {
|
|
2258
|
+
const dynamic *headers = sources.at(0).get_ptr("headers");
|
|
2259
|
+
if (headers != nullptr && headers->isObject()) out["headers"] = *headers;
|
|
2260
|
+
}
|
|
2261
|
+
#endif
|
|
2198
2262
|
|
|
2199
2263
|
// `ImageProps.js:195,202` — the size aliases are STYLE, not props, and an explicit style key wins.
|
|
2200
2264
|
// RN spells it `{width, height}, ...style`, so they go UNDER.
|
|
@@ -2259,7 +2323,16 @@ dynamic foldImageProps(const dynamic &props, bool ariaHiddenIsTrue) {
|
|
|
2259
2323
|
const dynamic *defaultSource = props.get_ptr("defaultSource");
|
|
2260
2324
|
if (defaultSource != nullptr && defaultSource->isArray() &&
|
|
2261
2325
|
defaultSource->size() > 0) {
|
|
2326
|
+
#ifdef ANDROID
|
|
2327
|
+
// `Image.android.js` sends `defaultSource_.uri`: `ReactImageManager.setDefaultSource` takes a
|
|
2328
|
+
// `String?`, and a map there is a native type error.
|
|
2329
|
+
const dynamic &first = defaultSource->at(0);
|
|
2330
|
+
const std::string *uri = first.isObject() ? stringAt(first, "uri") : nullptr;
|
|
2331
|
+
if (uri != nullptr) out["defaultSource"] = *uri;
|
|
2332
|
+
else out.erase("defaultSource");
|
|
2333
|
+
#else
|
|
2262
2334
|
out["defaultSource"] = defaultSource->at(0);
|
|
2335
|
+
#endif
|
|
2263
2336
|
}
|
|
2264
2337
|
|
|
2265
2338
|
for (const char *key : kImageConsumedKeys) out.erase(key);
|
|
@@ -2291,11 +2364,11 @@ dynamic foldSwitchProps(const dynamic &props, bool isAndroidSwitch) {
|
|
|
2291
2364
|
const bool isOn = boolAt(props, "value").value_or(false);
|
|
2292
2365
|
|
|
2293
2366
|
const dynamic *trackColor = props.get_ptr("trackColor");
|
|
2294
|
-
const
|
|
2295
|
-
const
|
|
2367
|
+
const dynamic *trackFalse = nullptr;
|
|
2368
|
+
const dynamic *trackTrue = nullptr;
|
|
2296
2369
|
if (trackColor != nullptr && trackColor->isObject()) {
|
|
2297
|
-
trackFalse =
|
|
2298
|
-
trackTrue =
|
|
2370
|
+
trackFalse = colorAt(*trackColor, "false");
|
|
2371
|
+
trackTrue = colorAt(*trackColor, "true");
|
|
2299
2372
|
}
|
|
2300
2373
|
|
|
2301
2374
|
const std::optional<bool> disabled = boolAt(props, "disabled");
|
|
@@ -2338,7 +2411,7 @@ dynamic foldSwitchProps(const dynamic &props, bool isAndroidSwitch) {
|
|
|
2338
2411
|
|
|
2339
2412
|
if (trackFalse != nullptr) out["trackColorForFalse"] = *trackFalse;
|
|
2340
2413
|
if (trackTrue != nullptr) out["trackColorForTrue"] = *trackTrue;
|
|
2341
|
-
const
|
|
2414
|
+
const dynamic *tint = isOn ? trackTrue : trackFalse;
|
|
2342
2415
|
if (tint != nullptr) out["trackTintColor"] = *tint;
|
|
2343
2416
|
// `:230` destructures the iOS colour names out of what reaches this view. They are keys it does
|
|
2344
2417
|
// not declare.
|
|
@@ -2359,7 +2432,7 @@ dynamic foldSwitchProps(const dynamic &props, bool isAndroidSwitch) {
|
|
|
2359
2432
|
// `StyleSheet.compose` exactly: `alignSelf` UNDER the app's style (an app that writes
|
|
2360
2433
|
// `alignSelf: 'stretch'` still wins), the pill OVER it.
|
|
2361
2434
|
const dynamic *authoredStyle = props.get_ptr("style");
|
|
2362
|
-
const
|
|
2435
|
+
const dynamic *iosBackground = colorAt(props, "ios_backgroundColor");
|
|
2363
2436
|
dynamic composed = dynamic::array();
|
|
2364
2437
|
dynamic intrinsic = dynamic::object();
|
|
2365
2438
|
// `:267` — a stock iOS switch keeps its intrinsic width instead of stretching to its
|
|
@@ -2383,7 +2456,7 @@ dynamic foldSwitchProps(const dynamic &props, bool isAndroidSwitch) {
|
|
|
2383
2456
|
out["accessibilityRole"] = "switch";
|
|
2384
2457
|
}
|
|
2385
2458
|
|
|
2386
|
-
const
|
|
2459
|
+
const dynamic *thumbColor = colorAt(props, "thumbColor");
|
|
2387
2460
|
if (thumbColor != nullptr) out["thumbTintColor"] = *thumbColor;
|
|
2388
2461
|
|
|
2389
2462
|
// None of the three authored names is a native prop, and leaving one in the payload is how a
|
|
@@ -2409,15 +2482,9 @@ dynamic fabricProps(
|
|
|
2409
2482
|
dynamic out = dynamic::object();
|
|
2410
2483
|
const dynamic *text = props.get_ptr("text");
|
|
2411
2484
|
if (text != nullptr) out["text"] = *text;
|
|
2412
|
-
//
|
|
2413
|
-
//
|
|
2414
|
-
//
|
|
2415
|
-
// is: rendered uppercase on Android and verbatim everywhere else (`Button.js:352-353`). That is
|
|
2416
|
-
// a user-agent choice about a control, so it belongs here and not in the app's string.
|
|
2417
|
-
//
|
|
2418
|
-
// Guarded on the tag rather than applied to every raw text, obviously — and the tag reaches a
|
|
2419
|
-
// raw text at all because `createRawText` now takes one, for this.
|
|
2420
|
-
if (tagName == "button-label") out = foldButtonLabel(out);
|
|
2485
|
+
// Button's Android uppercase is NOT here: RN uses JavaScript's full-Unicode `toUpperCase`, which
|
|
2486
|
+
// this host (no ICU) cannot match, so it runs in JS at the slot redirect
|
|
2487
|
+
// (`behaviors/button.ts`, `slotValueFor`).
|
|
2421
2488
|
return out;
|
|
2422
2489
|
}
|
|
2423
2490
|
|
|
@@ -2485,7 +2552,8 @@ dynamic fabricProps(
|
|
|
2485
2552
|
tagResolved = foldScrollViewProps(
|
|
2486
2553
|
*bag,
|
|
2487
2554
|
tagName == "horizontal-scroll-view",
|
|
2488
|
-
owner.tagName != nullptr && std::strcmp(owner.tagName, "refresh-control") == 0
|
|
2555
|
+
owner.tagName != nullptr && std::strcmp(owner.tagName, "refresh-control") == 0,
|
|
2556
|
+
self.hasMomentumListener);
|
|
2489
2557
|
bag = &tagResolved;
|
|
2490
2558
|
} else if (tagName == "refresh-control") {
|
|
2491
2559
|
tagResolved = foldRefreshWrapperProps(*bag, firstChild);
|
|
@@ -2559,8 +2627,12 @@ dynamic fabricProps(
|
|
|
2559
2627
|
}
|
|
2560
2628
|
|
|
2561
2629
|
dynamic valueFolded;
|
|
2562
|
-
|
|
2563
|
-
|
|
2630
|
+
// Android commits BOTH text-input tags as one `AndroidTextInput`, so there the TAG says which one
|
|
2631
|
+
// it is; the iOS names say it themselves.
|
|
2632
|
+
const bool isTextInput = component == kSinglelineTextInput ||
|
|
2633
|
+
component == kMultilineTextInput || component == kAndroidTextInput;
|
|
2634
|
+
const bool isMultilineTextInput = component == kMultilineTextInput ||
|
|
2635
|
+
(component == kAndroidTextInput && tagName == "text-input-multiline");
|
|
2564
2636
|
if (isTextInput &&
|
|
2565
2637
|
(bag->get_ptr("value") != nullptr || bag->get_ptr("defaultValue") != nullptr)) {
|
|
2566
2638
|
valueFolded = foldTextInputValue(*bag);
|
|
@@ -2572,7 +2644,7 @@ dynamic fabricProps(
|
|
|
2572
2644
|
// which is what makes this a rule rather than a mapping.
|
|
2573
2645
|
dynamic aliasResolved;
|
|
2574
2646
|
if (isTextInput) {
|
|
2575
|
-
aliasResolved = foldTextInputAliases(*bag,
|
|
2647
|
+
aliasResolved = foldTextInputAliases(*bag, isMultilineTextInput);
|
|
2576
2648
|
// `TextInput.js:583` — `props.accessible !== false`, handed to both the singleline and the
|
|
2577
2649
|
// multiline view (`:703`, `:772`). The same shape as the Text rule below, and found the same
|
|
2578
2650
|
// way: diffing one bench row's committed payload against React Native's own.
|
|
@@ -2602,10 +2674,22 @@ dynamic fabricProps(
|
|
|
2602
2674
|
const bool optedOut =
|
|
2603
2675
|
scaling != nullptr && scaling->isBool() && scaling->getBool() == false;
|
|
2604
2676
|
textDefaulted["allowFontScaling"] = !optedOut;
|
|
2605
|
-
// `Text.js:145` — `accessible !== false` on iOS
|
|
2606
|
-
//
|
|
2607
|
-
|
|
2677
|
+
// `Text.js:145-150` — `accessible !== false` on iOS; on Android an unset value follows the
|
|
2678
|
+
// press handlers (`onPress != null || onLongPress != null`), whose presence is a listener bit.
|
|
2679
|
+
#ifdef ANDROID
|
|
2680
|
+
textDefaulted["accessible"] =
|
|
2681
|
+
boolAt(textDefaulted, "accessible").value_or(self.hasPressOrLongPressListener);
|
|
2682
|
+
#else
|
|
2608
2683
|
textDefaulted["accessible"] = boolAt(textDefaulted, "accessible").value_or(true);
|
|
2684
|
+
#endif
|
|
2685
|
+
// `Text.js:152-163` — a pressable, enabled text with no role of its own is announced as a link.
|
|
2686
|
+
const bool isPressable =
|
|
2687
|
+
(self.hasPressOrLongPressListener || self.hasStartShouldSetResponder) &&
|
|
2688
|
+
buttonDisabled(props) != std::optional<bool>(true);
|
|
2689
|
+
if (isPressable && presentAt(textDefaulted, "accessibilityRole") == nullptr &&
|
|
2690
|
+
presentAt(props, "role") == nullptr) {
|
|
2691
|
+
textDefaulted["accessibilityRole"] = "link";
|
|
2692
|
+
}
|
|
2609
2693
|
// `Text.js:547` puts this in the component's own default STYLE, and its comment says why:
|
|
2610
2694
|
// "native components have historically acted like overflow: hidden ... to let client
|
|
2611
2695
|
// differentiate with overflow: 'visible'". Written at the TOP LEVEL rather than into the style
|
|
@@ -208,6 +208,11 @@ struct ISelf {
|
|
|
208
208
|
bool hasPressListener = false;
|
|
209
209
|
bool hasAnyPressListener = false;
|
|
210
210
|
bool underlayShown = false;
|
|
211
|
+
// ScrollView's `sendMomentumEvents`: whether the app wired momentumScrollBegin or -End.
|
|
212
|
+
bool hasMomentumListener = false;
|
|
213
|
+
// Text's pressability (`Text.js:145-163`): onPress or onLongPress, and onStartShouldSetResponder.
|
|
214
|
+
bool hasPressOrLongPressListener = false;
|
|
215
|
+
bool hasStartShouldSetResponder = false;
|
|
211
216
|
};
|
|
212
217
|
|
|
213
218
|
folly::dynamic fabricProps(
|