From 29eb2e7fadc21f77d01f921c826a45a6b5aa9c2a Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:24:57 -0700 Subject: [PATCH 1/3] Default text color should be theme aware --- .../attributedstring/TextAttributes.cpp | 291 ++++++++++++++++++ vnext/overrides.json | 7 +- 2 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 vnext/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/attributedstring/TextAttributes.cpp diff --git a/vnext/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/attributedstring/TextAttributes.cpp b/vnext/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/attributedstring/TextAttributes.cpp new file mode 100644 index 00000000000..6ea8bf94cfa --- /dev/null +++ b/vnext/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/attributedstring/TextAttributes.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TextAttributes.h" + +#include +#include +#include +#include +#include + +#include + +namespace facebook::react { + +void TextAttributes::apply(TextAttributes textAttributes) { + // Color + foregroundColor = textAttributes.foregroundColor + ? textAttributes.foregroundColor + : foregroundColor; + backgroundColor = textAttributes.backgroundColor + ? textAttributes.backgroundColor + : backgroundColor; + opacity = + !std::isnan(textAttributes.opacity) ? textAttributes.opacity : opacity; + + // Font + fontFamily = !textAttributes.fontFamily.empty() ? textAttributes.fontFamily + : fontFamily; + fontSize = + !std::isnan(textAttributes.fontSize) ? textAttributes.fontSize : fontSize; + fontSizeMultiplier = !std::isnan(textAttributes.fontSizeMultiplier) + ? textAttributes.fontSizeMultiplier + : fontSizeMultiplier; + fontWeight = textAttributes.fontWeight.has_value() ? textAttributes.fontWeight + : fontWeight; + fontStyle = textAttributes.fontStyle.has_value() ? textAttributes.fontStyle + : fontStyle; + fontVariant = textAttributes.fontVariant.has_value() + ? textAttributes.fontVariant + : fontVariant; + allowFontScaling = textAttributes.allowFontScaling.has_value() + ? textAttributes.allowFontScaling + : allowFontScaling; + maxFontSizeMultiplier = !std::isnan(textAttributes.maxFontSizeMultiplier) + ? textAttributes.maxFontSizeMultiplier + : maxFontSizeMultiplier; + dynamicTypeRamp = textAttributes.dynamicTypeRamp.has_value() + ? textAttributes.dynamicTypeRamp + : dynamicTypeRamp; + letterSpacing = !std::isnan(textAttributes.letterSpacing) + ? textAttributes.letterSpacing + : letterSpacing; + textTransform = textAttributes.textTransform.has_value() + ? textAttributes.textTransform + : textTransform; + + // Paragraph Styles + lineHeight = !std::isnan(textAttributes.lineHeight) + ? textAttributes.lineHeight + : lineHeight; + alignment = textAttributes.alignment.has_value() ? textAttributes.alignment + : alignment; + baseWritingDirection = textAttributes.baseWritingDirection.has_value() + ? textAttributes.baseWritingDirection + : baseWritingDirection; + lineBreakStrategy = textAttributes.lineBreakStrategy.has_value() + ? textAttributes.lineBreakStrategy + : lineBreakStrategy; + lineBreakMode = textAttributes.lineBreakMode.has_value() + ? textAttributes.lineBreakMode + : lineBreakMode; + + // Decoration + textDecorationColor = textAttributes.textDecorationColor + ? textAttributes.textDecorationColor + : textDecorationColor; + textDecorationLineType = textAttributes.textDecorationLineType.has_value() + ? textAttributes.textDecorationLineType + : textDecorationLineType; + textDecorationStyle = textAttributes.textDecorationStyle.has_value() + ? textAttributes.textDecorationStyle + : textDecorationStyle; + + // Shadow + textShadowOffset = textAttributes.textShadowOffset.has_value() + ? textAttributes.textShadowOffset.value() + : textShadowOffset; + textShadowRadius = !std::isnan(textAttributes.textShadowRadius) + ? textAttributes.textShadowRadius + : textShadowRadius; + textShadowColor = textAttributes.textShadowColor + ? textAttributes.textShadowColor + : textShadowColor; + + // Special + isHighlighted = textAttributes.isHighlighted.has_value() + ? textAttributes.isHighlighted + : isHighlighted; + // TextAttributes "inherits" the isPressable value from ancestors, so this + // only applies the current node's value for isPressable if it is truthy. + isPressable = + textAttributes.isPressable.has_value() && *textAttributes.isPressable + ? textAttributes.isPressable + : isPressable; + layoutDirection = textAttributes.layoutDirection.has_value() + ? textAttributes.layoutDirection + : layoutDirection; + accessibilityRole = textAttributes.accessibilityRole.has_value() + ? textAttributes.accessibilityRole + : accessibilityRole; + role = textAttributes.role.has_value() ? textAttributes.role : role; +} + +#pragma mark - Operators + +bool TextAttributes::operator==(const TextAttributes& rhs) const { + return std::tie( + foregroundColor, + backgroundColor, + fontFamily, + fontWeight, + fontStyle, + fontVariant, + allowFontScaling, + dynamicTypeRamp, + alignment, + baseWritingDirection, + lineBreakStrategy, + textDecorationColor, + textDecorationLineType, + textDecorationStyle, + textShadowOffset, + textShadowColor, + isHighlighted, + isPressable, + layoutDirection, + accessibilityRole, + role, + textTransform) == + std::tie( + rhs.foregroundColor, + rhs.backgroundColor, + rhs.fontFamily, + rhs.fontWeight, + rhs.fontStyle, + rhs.fontVariant, + rhs.allowFontScaling, + rhs.dynamicTypeRamp, + rhs.alignment, + rhs.baseWritingDirection, + rhs.lineBreakStrategy, + rhs.textDecorationColor, + rhs.textDecorationLineType, + rhs.textDecorationStyle, + rhs.textShadowOffset, + rhs.textShadowColor, + rhs.isHighlighted, + rhs.isPressable, + rhs.layoutDirection, + rhs.accessibilityRole, + rhs.role, + rhs.textTransform) && + floatEquality(maxFontSizeMultiplier, rhs.maxFontSizeMultiplier) && + floatEquality(opacity, rhs.opacity) && + floatEquality(fontSize, rhs.fontSize) && + floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) && + floatEquality(letterSpacing, rhs.letterSpacing) && + floatEquality(lineHeight, rhs.lineHeight) && + floatEquality(textShadowRadius, rhs.textShadowRadius); +} + +TextAttributes TextAttributes::defaultTextAttributes() { + static auto textAttributes = [] { + auto defaultAttrs = TextAttributes{}; + // Non-obvious (can be different among platforms) default text attributes. + defaultAttrs.foregroundColor = facebook::react::Color { // [Windows] override default color to not be black + .m_color = {}, + .m_platformColor = {"TextFillColorPrimary"}, + }; + defaultAttrs.backgroundColor = clearColor(); + defaultAttrs.fontSize = 14.0; + defaultAttrs.fontSizeMultiplier = 1.0; + return defaultAttrs; + }(); + return textAttributes; +} + +#pragma mark - DebugStringConvertible + +#if RN_DEBUG_STRING_CONVERTIBLE +SharedDebugStringConvertibleList TextAttributes::getDebugProps() const { + const auto& textAttributes = TextAttributes::defaultTextAttributes(); + return { + // Color + debugStringConvertibleItem( + "backgroundColor", backgroundColor, textAttributes.backgroundColor), + debugStringConvertibleItem( + "foregroundColor", foregroundColor, textAttributes.foregroundColor), + debugStringConvertibleItem("opacity", opacity, textAttributes.opacity), + + // Font + debugStringConvertibleItem( + "fontFamily", fontFamily, textAttributes.fontFamily), + debugStringConvertibleItem("fontSize", fontSize, textAttributes.fontSize), + debugStringConvertibleItem( + "fontSizeMultiplier", + fontSizeMultiplier, + textAttributes.fontSizeMultiplier), + debugStringConvertibleItem( + "fontWeight", fontWeight, textAttributes.fontWeight), + debugStringConvertibleItem( + "fontStyle", fontStyle, textAttributes.fontStyle), + debugStringConvertibleItem( + "fontVariant", fontVariant, textAttributes.fontVariant), + debugStringConvertibleItem( + "allowFontScaling", + allowFontScaling, + textAttributes.allowFontScaling), + debugStringConvertibleItem( + "maxFontSizeMultiplier", + maxFontSizeMultiplier, + textAttributes.maxFontSizeMultiplier), + debugStringConvertibleItem( + "dynamicTypeRamp", dynamicTypeRamp, textAttributes.dynamicTypeRamp), + debugStringConvertibleItem( + "letterSpacing", letterSpacing, textAttributes.letterSpacing), + + // Paragraph Styles + debugStringConvertibleItem( + "lineHeight", lineHeight, textAttributes.lineHeight), + debugStringConvertibleItem( + "alignment", alignment, textAttributes.alignment), + debugStringConvertibleItem( + "writingDirection", + baseWritingDirection, + textAttributes.baseWritingDirection), + debugStringConvertibleItem( + "lineBreakStrategyIOS", + lineBreakStrategy, + textAttributes.lineBreakStrategy), + debugStringConvertibleItem( + "lineBreakModeIOS", lineBreakMode, textAttributes.lineBreakMode), + + // Decoration + debugStringConvertibleItem( + "textDecorationColor", + textDecorationColor, + textAttributes.textDecorationColor), + debugStringConvertibleItem( + "textDecorationLineType", + textDecorationLineType, + textAttributes.textDecorationLineType), + debugStringConvertibleItem( + "textDecorationStyle", + textDecorationStyle, + textAttributes.textDecorationStyle), + + // Shadow + debugStringConvertibleItem( + "textShadowOffset", + textShadowOffset, + textAttributes.textShadowOffset), + debugStringConvertibleItem( + "textShadowRadius", + textShadowRadius, + textAttributes.textShadowRadius), + debugStringConvertibleItem( + "textShadowColor", textShadowColor, textAttributes.textShadowColor), + + // Special + debugStringConvertibleItem( + "isHighlighted", isHighlighted, textAttributes.isHighlighted), + debugStringConvertibleItem( + "isPressable", isPressable, textAttributes.isPressable), + debugStringConvertibleItem( + "layoutDirection", layoutDirection, textAttributes.layoutDirection), + debugStringConvertibleItem( + "accessibilityRole", + accessibilityRole, + textAttributes.accessibilityRole), + debugStringConvertibleItem("role", role, textAttributes.role), + }; +} +#endif + +} // namespace facebook::react diff --git a/vnext/overrides.json b/vnext/overrides.json index 9c5530b4622..120cd1d3a37 100644 --- a/vnext/overrides.json +++ b/vnext/overrides.json @@ -205,6 +205,12 @@ "baseFile": "packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp", "baseHash": "0722acd9ba007eac349a9445b2fcf0cb932cd00d" }, + { + "type": "patch", + "file": "ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/attributedstring/TextAttributes.cpp", + "baseFile": "packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp", + "baseHash": "1fc7b68dff2d3a875737659af4a90efe3aa6dcfa" + }, { "type": "patch", "file": "ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp", @@ -260,7 +266,6 @@ "issue": 15827 }, { - "type": "patch", "type": "derived", "file": "ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/core/EventDispatcher.cpp", "baseFile": "packages/react-native/ReactCommon/react/renderer/core/EventDispatcher.cpp", From dbc7dbb94fc4c662f0a3ebd567dc03bc2cbffdd3 Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:25:21 -0700 Subject: [PATCH 2/3] Change files --- ...ative-windows-72a30276-e1e3-4730-89f0-11257a31621d.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-72a30276-e1e3-4730-89f0-11257a31621d.json diff --git a/change/react-native-windows-72a30276-e1e3-4730-89f0-11257a31621d.json b/change/react-native-windows-72a30276-e1e3-4730-89f0-11257a31621d.json new file mode 100644 index 00000000000..38e25e936b7 --- /dev/null +++ b/change/react-native-windows-72a30276-e1e3-4730-89f0-11257a31621d.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Default text color should be theme aware", + "packageName": "react-native-windows", + "email": "30809111+acoates-ms@users.noreply.github.com", + "dependentChangeType": "patch" +} From 8485fa7e02d0669c6ecf3deb22744afbd789ec84 Mon Sep 17 00:00:00 2001 From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:05:20 -0700 Subject: [PATCH 3/3] snapshots --- .../__snapshots__/HomeUIADump.test.ts.snap | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap index e9a8b5c28a4..f9d83216e65 100644 --- a/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap +++ b/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap @@ -4191,7 +4191,7 @@ exports[`Home UIA Tree Dump Native Perf Benchmark 1`] = ` }, "Comment": "Native Perf Benchmark", "Offset": "0, 0, 0", - "Size": "966, 77", + "Size": "966, 78", "Visual Type": "SpriteVisual", "__Children": [ { @@ -4937,12 +4937,12 @@ exports[`Home UIA Tree Dump Pressable 1`] = ` }, { "Offset": "16, 45, 0", - "Size": "934, 17", + "Size": "934, 16", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "934, 17", + "Size": "934, 16", "Visual Type": "SpriteVisual", }, ], @@ -5099,12 +5099,12 @@ exports[`Home UIA Tree Dump ScrollView 1`] = ` }, { "Offset": "16, 45, 0", - "Size": "934, 16", + "Size": "934, 17", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "934, 16", + "Size": "934, 17", "Visual Type": "SpriteVisual", }, ], @@ -5678,7 +5678,7 @@ exports[`Home UIA Tree Dump Switch 1`] = ` }, "Comment": "Switch", "Offset": "0, 0, 0", - "Size": "966, 78", + "Size": "966, 77", "Visual Type": "SpriteVisual", "__Children": [ { @@ -5759,7 +5759,7 @@ exports[`Home UIA Tree Dump Text 1`] = ` }, "Comment": "Text", "Offset": "0, 0, 0", - "Size": "966, 77", + "Size": "966, 78", "Visual Type": "SpriteVisual", "__Children": [ { @@ -6169,12 +6169,12 @@ exports[`Home UIA Tree Dump TransferProperties 1`] = ` "__Children": [ { "Offset": "16, 16, 0", - "Size": "144, 25", + "Size": "144, 24", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "144, 25", + "Size": "144, 24", "Visual Type": "SpriteVisual", }, ], @@ -6331,12 +6331,12 @@ exports[`Home UIA Tree Dump TransparentHitTestExample 1`] = ` "__Children": [ { "Offset": "16, 16, 0", - "Size": "214, 24", + "Size": "214, 25", "Visual Type": "SpriteVisual", "__Children": [ { "Offset": "0, 0, 0", - "Size": "214, 24", + "Size": "214, 25", "Visual Type": "SpriteVisual", }, ],