Skip to content

Commit 05aa239

Browse files
committed
feat: add PseudoClasses
1 parent ea6dbd9 commit 05aa239

File tree

9 files changed

+298
-50
lines changed

9 files changed

+298
-50
lines changed

cpp/HybridStyleRegistry.cpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "StyledComputedFactory.hpp"
66
#include "Environment.hpp"
77
#include "VariableContext.hpp"
8+
#include "PseudoClasses.hpp"
89

910
#include <regex>
1011
#include <string>
@@ -121,14 +122,33 @@ namespace margelo::nitro::cssnitro {
121122
const std::vector<HybridStyleRule> &styleRules = styleIt->second->get();
122123
bool hasVars = false;
123124
for (const auto &sr: styleRules) {
125+
// Check for variables
124126
if (sr.v.has_value()) {
125127
hasVars = true;
126-
break;
128+
}
129+
130+
// Check for pseudo-classes
131+
if (sr.p.has_value()) {
132+
const auto &pseudoClass = sr.p.value();
133+
134+
// Check if active pseudo-class is set
135+
if (pseudoClass.a.has_value()) {
136+
declarations.active = true;
137+
}
138+
139+
// Check if hover pseudo-class is set
140+
if (pseudoClass.h.has_value()) {
141+
declarations.hover = true;
142+
}
143+
144+
// Check if focus pseudo-class is set
145+
if (pseudoClass.f.has_value()) {
146+
declarations.focus = true;
147+
}
127148
}
128149
}
129150
if (hasVars) {
130151
declarations.variableScope = componentId;
131-
break;
132152
}
133153
}
134154

@@ -172,8 +192,8 @@ namespace margelo::nitro::cssnitro {
172192
}
173193

174194
void HybridStyleRegistry::updateComponentState(const std::string &componentId,
175-
UpdateComponentStateFns type) {
176-
// TODO: Implement this
195+
PseudoClassType type, bool value) {
196+
PseudoClasses::set(componentId, type, value);
177197
}
178198

179199
void HybridStyleRegistry::unlinkComponent(const std::string &componentId) {

cpp/HybridStyleRegistry.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ namespace margelo::nitro::cssnitro {
5858

5959
void deregisterComponent(const std::string &componentId) override;
6060

61-
void updateComponentState(const std::string &componentId,
62-
UpdateComponentStateFns type) override;
61+
void updateComponentState(const std::string &componentId, PseudoClassType type,
62+
bool value) override;
6363

6464
void unlinkComponent(const std::string &componentId) override;
6565

cpp/PseudoClasses.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#include "PseudoClasses.hpp"
2+
3+
namespace margelo::nitro::cssnitro {
4+
5+
// Initialize the static map
6+
std::unordered_map<std::string, PseudoClassState> PseudoClasses::states;
7+
8+
bool PseudoClasses::get(const std::string &key, PseudoClassType type,
9+
reactnativecss::Effect::GetProxy &get) {
10+
// Find or create the state for this key
11+
auto stateIt = states.find(key);
12+
if (stateIt == states.end()) {
13+
// Key doesn't exist, create it
14+
PseudoClassState newState;
15+
states[key] = newState;
16+
stateIt = states.find(key);
17+
}
18+
19+
auto &state = stateIt->second;
20+
21+
// Get the appropriate observable based on type
22+
std::optional<std::shared_ptr<reactnativecss::Observable<bool>>> *observablePtr = nullptr;
23+
24+
// Select the correct observable based on the PseudoClassType
25+
switch (type) {
26+
case PseudoClassType::ACTIVE:
27+
observablePtr = &state.active;
28+
break;
29+
case PseudoClassType::HOVER:
30+
observablePtr = &state.hover;
31+
break;
32+
case PseudoClassType::FOCUS:
33+
observablePtr = &state.focus;
34+
break;
35+
}
36+
37+
// Safety check - should never happen but guards against warnings
38+
if (observablePtr == nullptr) {
39+
return false;
40+
}
41+
42+
// If the observable doesn't exist, create it with default value of false
43+
if (!observablePtr->has_value()) {
44+
*observablePtr = reactnativecss::Observable<bool>::create(false);
45+
}
46+
47+
// Subscribe to the observable and return its value
48+
return get(*observablePtr->value());
49+
}
50+
51+
void PseudoClasses::set(const std::string &key, PseudoClassType type, bool value) {
52+
// Find or create the state for this key
53+
auto stateIt = states.find(key);
54+
if (stateIt == states.end()) {
55+
// Key doesn't exist, create it
56+
PseudoClassState newState;
57+
states[key] = newState;
58+
stateIt = states.find(key);
59+
}
60+
61+
auto &state = stateIt->second;
62+
63+
// Get the appropriate observable based on type
64+
std::optional<std::shared_ptr<reactnativecss::Observable<bool>>> *observablePtr = nullptr;
65+
66+
switch (type) {
67+
case PseudoClassType::ACTIVE:
68+
observablePtr = &state.active;
69+
break;
70+
case PseudoClassType::HOVER:
71+
observablePtr = &state.hover;
72+
break;
73+
case PseudoClassType::FOCUS:
74+
observablePtr = &state.focus;
75+
break;
76+
}
77+
78+
// Safety check - should never happen but guards against warnings
79+
if (observablePtr == nullptr) {
80+
return;
81+
}
82+
83+
// If the observable doesn't exist, create it
84+
if (!observablePtr->has_value()) {
85+
*observablePtr = reactnativecss::Observable<bool>::create(value);
86+
} else {
87+
// Update the existing observable
88+
observablePtr->value()->set(value);
89+
}
90+
}
91+
92+
void PseudoClasses::remove(const std::string &key) {
93+
// Remove the key from the map
94+
states.erase(key);
95+
}
96+
97+
} // namespace margelo::nitro::cssnitro

cpp/PseudoClasses.hpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <unordered_map>
5+
#include <memory>
6+
#include <optional>
7+
#include "Observable.hpp"
8+
#include "Effect.hpp"
9+
#include "HybridStyleRegistrySpec.hpp"
10+
11+
namespace margelo::nitro::cssnitro {
12+
13+
struct PseudoClassState {
14+
std::optional<std::shared_ptr<reactnativecss::Observable<bool>>> active;
15+
std::optional<std::shared_ptr<reactnativecss::Observable<bool>>> hover;
16+
std::optional<std::shared_ptr<reactnativecss::Observable<bool>>> focus;
17+
};
18+
19+
class PseudoClasses {
20+
private:
21+
static std::unordered_map<std::string, PseudoClassState> states;
22+
23+
public:
24+
/**
25+
* Get the value of a pseudo-class for a given key.
26+
* If the key or type doesn't exist, it will be created with a default value of false.
27+
*
28+
* @param key The component/element key
29+
* @param type The pseudo-class type (active, hover, or focus)
30+
* @param get The Effect::GetProxy for reactive dependencies
31+
* @return The current boolean value of the pseudo-class
32+
*/
33+
static bool get(const std::string &key, PseudoClassType type,
34+
reactnativecss::Effect::GetProxy &get);
35+
36+
/**
37+
* Set the value of a pseudo-class for a given key.
38+
*
39+
* @param key The component/element key
40+
* @param type The pseudo-class type (active, hover, or focus)
41+
* @param value The value to set (true or false)
42+
*/
43+
static void set(const std::string &key, PseudoClassType type, bool value);
44+
45+
/**
46+
* Remove a key and all its pseudo-class states from the map.
47+
*
48+
* @param key The component/element key to remove
49+
*/
50+
static void remove(const std::string &key);
51+
};
52+
53+
} // namespace margelo::nitro::cssnitro

cpp/Rules.hpp

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "HybridStyleRule.hpp"
1515
#include "Environment.hpp"
1616
#include "Helpers.hpp"
17+
#include "PseudoClasses.hpp"
1718
#include <NitroModules/AnyMap.hpp>
1819

1920
namespace margelo::nitro::cssnitro {
@@ -24,14 +25,24 @@ namespace margelo::nitro::cssnitro {
2425

2526
class Rules {
2627
public:
27-
static bool testRule(const HybridStyleRule &rule, reactnativecss::Effect::GetProxy &get) {
28-
// If m is not defined, return true
29-
if (!rule.m.has_value() || !rule.m.value()) {
30-
return true;
28+
static bool testRule(const HybridStyleRule &rule, reactnativecss::Effect::GetProxy &get,
29+
const std::string &componentId) {
30+
// Check pseudo-classes first (rule.p)
31+
if (rule.p.has_value()) {
32+
if (!testPseudoClasses(rule.p.value(), componentId, get)) {
33+
return false;
34+
}
3135
}
3236

33-
auto &mediaMap = *rule.m.value();
34-
return testMediaMap(mediaMap, get);
37+
// Check media queries (rule.m)
38+
if (rule.m.has_value() && rule.m.value()) {
39+
auto &mediaMap = *rule.m.value();
40+
if (!testMediaMap(mediaMap, get)) {
41+
return false;
42+
}
43+
}
44+
45+
return true;
3546
}
3647

3748
static bool
@@ -43,6 +54,43 @@ namespace margelo::nitro::cssnitro {
4354
}
4455

4556
private:
57+
/**
58+
* Test if pseudo-class conditions match.
59+
* Returns false if any pseudo-class doesn't match the expected state.
60+
*/
61+
static bool
62+
testPseudoClasses(const PseudoClass &pseudoClass, const std::string &componentId,
63+
reactnativecss::Effect::GetProxy &get) {
64+
// Check active state
65+
if (pseudoClass.a.has_value()) {
66+
bool expectedActive = pseudoClass.a.value();
67+
bool actualActive = PseudoClasses::get(componentId, PseudoClassType::ACTIVE, get);
68+
if (actualActive != expectedActive) {
69+
return false;
70+
}
71+
}
72+
73+
// Check hover state
74+
if (pseudoClass.h.has_value()) {
75+
bool expectedHover = pseudoClass.h.value();
76+
bool actualHover = PseudoClasses::get(componentId, PseudoClassType::HOVER, get);
77+
if (actualHover != expectedHover) {
78+
return false;
79+
}
80+
}
81+
82+
// Check focus state
83+
if (pseudoClass.f.has_value()) {
84+
bool expectedFocus = pseudoClass.f.value();
85+
bool actualFocus = PseudoClasses::get(componentId, PseudoClassType::FOCUS, get);
86+
if (actualFocus != expectedFocus) {
87+
return false;
88+
}
89+
}
90+
91+
return true;
92+
}
93+
4694
static bool testMediaMap(const AnyMap &mediaMap, reactnativecss::Effect::GetProxy &get) {
4795
// Get all keys to check if empty
4896
auto keys = mediaMap.getAllKeys();

cpp/StyledComputedFactory.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ namespace margelo::nitro::cssnitro {
7272
// Now process the sorted style rules
7373
for (const HybridStyleRule &styleRule: allStyleRules) {
7474
// Skip rule if its media conditions don't pass
75-
if (!Rules::testRule(styleRule, get)) {
75+
if (!Rules::testRule(styleRule, get, componentId)) {
7676
continue;
7777
}
7878

example/src/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,24 @@ StyleRegistry.addStyleSheet({
2222
{
2323
s: specificity({ className: 3 }),
2424
d: [{ color: ["fn", "var", "test"] }],
25+
p: { a: true },
2526
},
2627
],
2728
],
2829
],
2930
});
3031

32+
StyleRegistry.setRootVariables({
33+
test: [{ v: "pink" }],
34+
});
35+
3136
export default function App() {
3237
return (
3338
<View style={styles.container}>
3439
<Text
3540
className="text-red-500 text-[--test]"
3641
onPress={() => {
3742
console.log("Pressed!");
38-
StyleRegistry.setRootVariables({
39-
test: [{ v: "pink", m: { orientation: ["=", "portrait"] } }],
40-
});
4143
}}
4244
>
4345
Multiply: {multiply(3, 7)}

0 commit comments

Comments
 (0)