diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h index b9535f14e363..a7dd98139991 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h @@ -28,6 +28,9 @@ struct Color { int32_t getColor() const; std::size_t getUIColorHash() const; + // Returns the UndefinedColor sentinel (null underlying UIColor) on a miss, so + // callers can tell a miss from a name that resolves to transparent. Callers + // reaching into getUIColor() must null-check it. static Color createSemanticColor(std::vector &semanticItems); std::shared_ptr getUIColor() const diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm index 93ecb7e33190..e47deaa95cf6 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm @@ -237,7 +237,12 @@ int32_t ColorFromUIColor(const std::shared_ptr &uiColor) Color Color::createSemanticColor(std::vector &semanticItems) { - auto semanticColor = RCTPlatformColorFromSemanticItems(semanticItems); + UIColor *semanticColor = RCTPlatformColorFromSemanticItemsOrNil(semanticItems); + if (semanticColor == nil) { + // Undefined-color sentinel on a miss, distinct from a name that resolves to + // transparent (getColor() is still 0, preserving the old render). + return HostPlatformColor::UndefinedColor; + } return Color(wrapManagedObject(semanticColor)); } diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm index 377783f0f195..890db576eba9 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm @@ -8,9 +8,13 @@ #import "PlatformColorParser.h" #import +#import +#import +#import #import #import #import +#import #import #import @@ -51,13 +55,39 @@ return SharedColor(color); } +// nullopt only on a parse failure, so a fallback that resolves to transparent is +// still honored. +static std::optional fallbackColorFromString(const std::string &fallbackString) +{ + auto cssColor = parseCSSProperty(fallbackString); + if (std::holds_alternative(cssColor)) { + const auto &c = std::get(cssColor); + return colorFromRGBA(c.r, c.g, c.b, c.a); + } + return std::nullopt; +} + SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value) { if (value.hasType>()) { auto items = (std::unordered_map)value; if (items.find("semantic") != items.end() && items.at("semantic").hasType>()) { auto semanticItems = (std::vector)items.at("semantic"); - return SharedColor(Color::createSemanticColor(semanticItems)); + auto semanticColor = SharedColor(Color::createSemanticColor(semanticItems)); + // The sentinel (null UIColor) means a true miss; apply the fallback only + // then, not when a name resolves to transparent. + if (!semanticColor) { + if (items.find("fallback") != items.end() && items.at("fallback").hasType()) { + auto fallbackColor = fallbackColorFromString((std::string)items.at("fallback")); + // has_value(), not != 0, so a transparent fallback is kept. + if (fallbackColor.has_value()) { + return *fallbackColor; + } + } + // Miss with no usable fallback: clearColor, never leaking the sentinel. + return clearColor(); + } + return semanticColor; } else if ( items.find("dynamic") != items.end() && items.at("dynamic").hasType>()) { diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h index f487564c043d..ad021c1e413f 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h @@ -15,6 +15,15 @@ struct ColorComponents; struct Color; } // namespace facebook::react +NS_ASSUME_NONNULL_BEGIN + facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems(std::vector &semanticItems); UIColor *RCTPlatformColorFromSemanticItems(std::vector &semanticItems); +// Like RCTPlatformColorFromSemanticItems but returns nil on a miss, so callers +// can tell a miss from a name that resolves to transparent. +UIColor *_Nullable RCTPlatformColorFromSemanticItemsOrNil(std::vector &semanticItems); +// Precondition: `color` is a resolved color, never the miss sentinel, so the +// result stays _Nonnull. UIColor *RCTPlatformColorFromColor(const facebook::react::Color &color); + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm index 4a0a53e2b504..882f38805672 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm @@ -192,7 +192,7 @@ return _ColorComponentsFromUIColor(RCTPlatformColorFromSemanticItems(semanticItems)); } -UIColor *RCTPlatformColorFromSemanticItems(std::vector &semanticItems) +UIColor *_Nullable RCTPlatformColorFromSemanticItemsOrNil(std::vector &semanticItems) { for (const auto &semanticCString : semanticItems) { NSString *semanticNSString = _NSStringFromCString(semanticCString); @@ -206,7 +206,15 @@ } } - return UIColor.clearColor; + return nil; +} + +UIColor *RCTPlatformColorFromSemanticItems(std::vector &semanticItems) +{ + // Non-null contract (e.g. for RCTPlatformColorComponentsFromSemanticItems); + // callers needing to detect a miss use the OrNil variant. + UIColor *uiColor = RCTPlatformColorFromSemanticItemsOrNil(semanticItems); + return uiColor != nil ? uiColor : UIColor.clearColor; } UIColor *RCTPlatformColorFromColor(const facebook::react::Color &color)