Skip to content

Commit 26eb36c

Browse files
Abbondanzometa-codesync[bot]
authored andcommitted
PlatformColor lazy fallback: honor a raw-color fallback on iOS and macOS (native) (#57556)
Summary: Pull Request resolved: #57556 An implementation for the RFC in react-native-community/discussions-and-proposals#1008 This is the first diff in a stack that adds an optional trailing `{fallback: '<raw color string>'}` argument to `PlatformColor(...)`. The fallback is carried lazily to native and parsed only when every supplied color token fails to resolve. This diff wires up the iOS and macOS native color resolvers to honor that fallback; a later diff adds the JS argument that emits it, so on its own this change is a no-op for existing call sites. The fallback is supported on the new architecture (Fabric) only. To distinguish a genuine miss (no token resolves) from a token that legitimately resolves to a transparent color, the semantic-color resolver gains an `OrNil` variant: - `RCTPlatformColorFromSemanticItemsOrNil` returns `nil` on a miss instead of collapsing to `clearColor`. `RCTPlatformColorFromSemanticItems` keeps its existing non-null contract by wrapping the `OrNil` variant. - `Color::createSemanticColor` returns the undefined-color sentinel (a null underlying `UIColor`) on a miss, so `PlatformColorParser.mm` applies the fallback only on a true miss and otherwise renders transparent exactly as before. - The fallback string is parsed with the shared CSS color parser (`parseCSSProperty<CSSColor>`), so `transparent`/`rgba(0,0,0,0)` are honored rather than mistaken for a parse failure. This spans the `xplat` iOS and macOS graphics resolvers. Changelog: [Internal] - PlatformColor: iOS/macOS native support for a lazy raw-color fallback Reviewed By: javache, cipolleschi Differential Revision: D111837102 fbshipit-source-id: 20fd1d6e10b1ce953de3644752051843b51b230f
1 parent 2ab916f commit 26eb36c

5 files changed

Lines changed: 59 additions & 4 deletions

File tree

packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ struct Color {
2828
int32_t getColor() const;
2929
std::size_t getUIColorHash() const;
3030

31+
// Returns the UndefinedColor sentinel (null underlying UIColor) on a miss, so
32+
// callers can tell a miss from a name that resolves to transparent. Callers
33+
// reaching into getUIColor() must null-check it.
3134
static Color createSemanticColor(std::vector<std::string> &semanticItems);
3235

3336
std::shared_ptr<void> getUIColor() const

packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,12 @@ int32_t ColorFromUIColor(const std::shared_ptr<void> &uiColor)
237237

238238
Color Color::createSemanticColor(std::vector<std::string> &semanticItems)
239239
{
240-
auto semanticColor = RCTPlatformColorFromSemanticItems(semanticItems);
240+
UIColor *semanticColor = RCTPlatformColorFromSemanticItemsOrNil(semanticItems);
241+
if (semanticColor == nil) {
242+
// Undefined-color sentinel on a miss, distinct from a name that resolves to
243+
// transparent (getColor() is still 0, preserving the old render).
244+
return HostPlatformColor::UndefinedColor;
245+
}
241246
return Color(wrapManagedObject(semanticColor));
242247
}
243248

packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88
#import "PlatformColorParser.h"
99

1010
#import <react/renderer/core/RawValue.h>
11+
#import <react/renderer/css/CSSColor.h>
12+
#import <react/renderer/css/CSSValueParser.h>
13+
#import <react/renderer/graphics/Color.h>
1114
#import <react/renderer/graphics/HostPlatformColor.h>
1215
#import <react/renderer/graphics/RCTPlatformColorUtils.h>
1316
#import <react/utils/ManagedObjectWrapper.h>
17+
#import <optional>
1418
#import <string>
1519
#import <unordered_map>
1620

@@ -51,13 +55,39 @@
5155
return SharedColor(color);
5256
}
5357

58+
// nullopt only on a parse failure, so a fallback that resolves to transparent is
59+
// still honored.
60+
static std::optional<SharedColor> fallbackColorFromString(const std::string &fallbackString)
61+
{
62+
auto cssColor = parseCSSProperty<CSSColor>(fallbackString);
63+
if (std::holds_alternative<CSSColor>(cssColor)) {
64+
const auto &c = std::get<CSSColor>(cssColor);
65+
return colorFromRGBA(c.r, c.g, c.b, c.a);
66+
}
67+
return std::nullopt;
68+
}
69+
5470
SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value)
5571
{
5672
if (value.hasType<std::unordered_map<std::string, RawValue>>()) {
5773
auto items = (std::unordered_map<std::string, RawValue>)value;
5874
if (items.find("semantic") != items.end() && items.at("semantic").hasType<std::vector<std::string>>()) {
5975
auto semanticItems = (std::vector<std::string>)items.at("semantic");
60-
return SharedColor(Color::createSemanticColor(semanticItems));
76+
auto semanticColor = SharedColor(Color::createSemanticColor(semanticItems));
77+
// The sentinel (null UIColor) means a true miss; apply the fallback only
78+
// then, not when a name resolves to transparent.
79+
if (!semanticColor) {
80+
if (items.find("fallback") != items.end() && items.at("fallback").hasType<std::string>()) {
81+
auto fallbackColor = fallbackColorFromString((std::string)items.at("fallback"));
82+
// has_value(), not != 0, so a transparent fallback is kept.
83+
if (fallbackColor.has_value()) {
84+
return *fallbackColor;
85+
}
86+
}
87+
// Miss with no usable fallback: clearColor, never leaking the sentinel.
88+
return clearColor();
89+
}
90+
return semanticColor;
6191
} else if (
6292
items.find("dynamic") != items.end() &&
6393
items.at("dynamic").hasType<std::unordered_map<std::string, RawValue>>()) {

packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ struct ColorComponents;
1515
struct Color;
1616
} // namespace facebook::react
1717

18+
NS_ASSUME_NONNULL_BEGIN
19+
1820
facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems(std::vector<std::string> &semanticItems);
1921
UIColor *RCTPlatformColorFromSemanticItems(std::vector<std::string> &semanticItems);
22+
// Like RCTPlatformColorFromSemanticItems but returns nil on a miss, so callers
23+
// can tell a miss from a name that resolves to transparent.
24+
UIColor *_Nullable RCTPlatformColorFromSemanticItemsOrNil(std::vector<std::string> &semanticItems);
25+
// Precondition: `color` is a resolved color, never the miss sentinel, so the
26+
// result stays _Nonnull.
2027
UIColor *RCTPlatformColorFromColor(const facebook::react::Color &color);
28+
29+
NS_ASSUME_NONNULL_END

packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@
192192
return _ColorComponentsFromUIColor(RCTPlatformColorFromSemanticItems(semanticItems));
193193
}
194194

195-
UIColor *RCTPlatformColorFromSemanticItems(std::vector<std::string> &semanticItems)
195+
UIColor *_Nullable RCTPlatformColorFromSemanticItemsOrNil(std::vector<std::string> &semanticItems)
196196
{
197197
for (const auto &semanticCString : semanticItems) {
198198
NSString *semanticNSString = _NSStringFromCString(semanticCString);
@@ -206,7 +206,15 @@
206206
}
207207
}
208208

209-
return UIColor.clearColor;
209+
return nil;
210+
}
211+
212+
UIColor *RCTPlatformColorFromSemanticItems(std::vector<std::string> &semanticItems)
213+
{
214+
// Non-null contract (e.g. for RCTPlatformColorComponentsFromSemanticItems);
215+
// callers needing to detect a miss use the OrNil variant.
216+
UIColor *uiColor = RCTPlatformColorFromSemanticItemsOrNil(semanticItems);
217+
return uiColor != nil ? uiColor : UIColor.clearColor;
210218
}
211219

212220
UIColor *RCTPlatformColorFromColor(const facebook::react::Color &color)

0 commit comments

Comments
 (0)