Skip to content

Commit 6202e37

Browse files
committed
feat: attribute queries
1 parent 573a4d8 commit 6202e37

21 files changed

+383
-204
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ These are the features that are "done", in that they pass basic testing. More co
2828
- [ ] Filter
2929
- [x] Pseudo classes
3030
- [x] Media query
31-
- [ ] Attribute selectors
31+
- [x] Attribute selectors
3232
- [x] Container named queries
3333
- [x] Container media queries
3434
- [ ] Container attribute queries

cpp/HybridStyleRegistry.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace margelo::nitro::cssnitro {
3232
std::make_unique<ShadowTreeUpdateManager>();
3333
std::unordered_map<std::string, std::shared_ptr<reactnativecss::Computed<Styled>>> HybridStyleRegistry::computedMap_;
3434
std::unordered_map<std::string, std::shared_ptr<reactnativecss::Observable<std::vector<HybridStyleRule>>>> HybridStyleRegistry::styleRuleMap_;
35+
std::atomic<uint64_t> HybridStyleRegistry::nextStyleRuleId_{1};
3536

3637
// Constructor, Destructor, and Method Implementations
3738
HybridStyleRegistry::HybridStyleRegistry() : HybridObject("HybridStyleRegistry") {}
@@ -40,8 +41,18 @@ namespace margelo::nitro::cssnitro {
4041

4142
void HybridStyleRegistry::setClassname(const std::string &className,
4243
const std::vector<HybridStyleRule> &styleRules) {
44+
// Create a copy of the style rules to modify them
45+
auto rulesWithIds = styleRules;
46+
47+
// Assign unique IDs to any rules that don't have one
48+
for (auto &rule: rulesWithIds) {
49+
if (!rule.id.has_value()) {
50+
rule.id = std::to_string(nextStyleRuleId_++);
51+
}
52+
}
53+
4354
// Reverse the style rules, this way later on we can bail early if values are already set
44-
auto reversedRules = styleRules;
55+
auto reversedRules = rulesWithIds;
4556
std::reverse(reversedRules.begin(), reversedRules.end());
4657

4758
auto it = styleRuleMap_.find(className);
@@ -102,13 +113,14 @@ namespace margelo::nitro::cssnitro {
102113
const std::string &variableScope,
103114
const std::string &containerScope) {
104115
Declarations declarations;
105-
declarations.classNames = classNames;
106116
declarations.variableScope = variableScope;
107117

108118
std::regex whitespace{"\\s+"};
109119
std::sregex_token_iterator tokenIt(classNames.begin(), classNames.end(), whitespace, -1);
110120
std::sregex_token_iterator end;
111121

122+
std::vector<std::tuple<std::string, AttributeQuery>> attributeQueriesVec;
123+
112124
for (; tokenIt != end; ++tokenIt) {
113125
const std::string className = tokenIt->str();
114126
if (className.empty()) {
@@ -123,6 +135,12 @@ namespace margelo::nitro::cssnitro {
123135
const std::vector<HybridStyleRule> &styleRules = styleIt->second->get();
124136
bool hasVars = false;
125137
for (const auto &sr: styleRules) {
138+
// Check for attribute queries
139+
if (sr.aq.has_value() && sr.id.has_value()) {
140+
// The style rule id is already a string
141+
attributeQueriesVec.emplace_back(sr.id.value(), sr.aq.value());
142+
}
143+
126144
// Check for variables
127145
if (sr.v.has_value()) {
128146
hasVars = true;
@@ -153,6 +171,11 @@ namespace margelo::nitro::cssnitro {
153171
}
154172
}
155173

174+
// Set attributeQueries if we found any
175+
if (!attributeQueriesVec.empty()) {
176+
declarations.attributeQueries = std::move(attributeQueriesVec);
177+
}
178+
156179
return declarations;
157180
}
158181

@@ -161,7 +184,8 @@ namespace margelo::nitro::cssnitro {
161184
const std::function<void()> &rerender,
162185
const std::string &classNames,
163186
const std::string &variableScope,
164-
const std::string &containerScope) {
187+
const std::string &containerScope,
188+
const std::vector<std::string> &validAttributeQueries) {
165189
if (auto existing = computedMap_.find(componentId); existing != computedMap_.end()) {
166190
if (existing->second) {
167191
existing->second->dispose();
@@ -177,7 +201,8 @@ namespace margelo::nitro::cssnitro {
177201
rerender,
178202
*shadowUpdates_,
179203
variableScope,
180-
containerScope);
204+
containerScope,
205+
validAttributeQueries);
181206

182207
computedMap_[componentId] = computed;
183208

cpp/HybridStyleRegistry.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ namespace margelo::nitro::cssnitro {
5454
Styled
5555
registerComponent(const std::string &componentId, const std::function<void()> &rerender,
5656
const std::string &classNames, const std::string &variableScope,
57-
const std::string &containerScope) override;
57+
const std::string &containerScope,
58+
const std::vector<std::string> &validAttributeQueries) override;
5859

5960
void deregisterComponent(const std::string &componentId) override;
6061

@@ -88,6 +89,7 @@ namespace margelo::nitro::cssnitro {
8889
static std::unique_ptr<ShadowTreeUpdateManager> shadowUpdates_;
8990
static std::unordered_map<std::string, std::shared_ptr<reactnativecss::Computed<Styled>>> computedMap_;
9091
static std::unordered_map<std::string, std::shared_ptr<reactnativecss::Observable<std::vector<HybridStyleRule>>>> styleRuleMap_;
92+
static std::atomic<uint64_t> nextStyleRuleId_;
9193
};
9294

9395
} // namespace margelo::nitro::cssnitro

cpp/Rules.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,25 @@
1313
namespace margelo::nitro::cssnitro {
1414

1515
bool Rules::testRule(const HybridStyleRule &rule, reactnativecss::Effect::GetProxy &get,
16-
const std::string &componentId, const std::string &containerScope) {
17-
// Check pseudo-classes first (rule.p)
16+
const std::string &componentId, const std::string &containerScope,
17+
const std::vector<std::string> &validAttributeQueries) {
18+
// Check attribute queries (rule.aq)
19+
if (rule.aq.has_value()) {
20+
if (!rule.id.has_value()) {
21+
return false;
22+
}
23+
24+
// The rule ID is already a string, use it directly
25+
const std::string &ruleId = rule.id.value();
26+
27+
// Use std::find to search the vector
28+
if (std::find(validAttributeQueries.begin(), validAttributeQueries.end(), ruleId) ==
29+
validAttributeQueries.end()) {
30+
return false;
31+
}
32+
}
33+
34+
// Check pseudo-classes (rule.p)
1835
if (rule.p.has_value()) {
1936
if (!testPseudoClasses(rule.p.value(), componentId, get)) {
2037
return false;
@@ -40,8 +57,8 @@ namespace margelo::nitro::cssnitro {
4057
return true;
4158
}
4259

43-
bool Rules::testRule(const std::shared_ptr<AnyMap> &mediaMap,
44-
reactnativecss::Effect::GetProxy &get) {
60+
bool Rules::testVariableMedia(const std::shared_ptr<AnyMap> &mediaMap,
61+
reactnativecss::Effect::GetProxy &get) {
4562
if (!mediaMap) {
4663
return true;
4764
}

cpp/Rules.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ namespace margelo::nitro::cssnitro {
2727
class Rules {
2828
public:
2929
static bool testRule(const HybridStyleRule &rule, reactnativecss::Effect::GetProxy &get,
30-
const std::string &componentId, const std::string &containerScope);
30+
const std::string &componentId, const std::string &containerScope,
31+
const std::vector<std::string> &validAttributeQueries);
3132

3233
static bool
33-
testRule(const std::shared_ptr<AnyMap> &mediaMap, reactnativecss::Effect::GetProxy &get);
34+
testVariableMedia(const std::shared_ptr<AnyMap> &mediaMap,
35+
reactnativecss::Effect::GetProxy &get);
3436

3537
private:
3638
static bool

cpp/StyledComputedFactory.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ namespace margelo::nitro::cssnitro {
2525
const std::function<void()> &rerender,
2626
ShadowTreeUpdateManager &shadowUpdates,
2727
const std::string &variableScope,
28-
const std::string &containerScope) {
28+
const std::string &containerScope,
29+
const std::vector<std::string> &validAttributeQueries) {
2930
auto computed = reactnativecss::Computed<Styled>::create(
30-
[&styleRuleMap, classNames, componentId, &rerender, &shadowUpdates, variableScope, containerScope](
31+
[&styleRuleMap, classNames, componentId, &rerender, &shadowUpdates, variableScope, containerScope, validAttributeQueries](
3132
const Styled &prev,
3233
typename reactnativecss::Effect::GetProxy &get) {
3334
(void) prev;
@@ -74,7 +75,8 @@ namespace margelo::nitro::cssnitro {
7475
// Now process the sorted style rules
7576
for (const HybridStyleRule &styleRule: allStyleRules) {
7677
// Skip rule if its media conditions don't pass
77-
if (!Rules::testRule(styleRule, get, componentId, containerScope)) {
78+
if (!Rules::testRule(styleRule, get, componentId, containerScope,
79+
validAttributeQueries)) {
7880
continue;
7981
}
8082

cpp/StyledComputedFactory.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ namespace margelo::nitro::cssnitro {
2323
const std::function<void()> &rerender,
2424
ShadowTreeUpdateManager &shadowUpdates,
2525
const std::string &variableScope,
26-
const std::string &containerScope);
26+
const std::string &containerScope,
27+
const std::vector<std::string> &validAttributeQueries);
2728

2829
bool shouldRerender(const Styled &styled);
2930

cpp/VariableContext.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ namespace margelo::nitro::cssnitro {
275275
}
276276

277277
// Test the rule - if it doesn't pass, continue to next item
278-
if (!Rules::testRule(mediaMap, get)) {
278+
if (!Rules::testVariableMedia(mediaMap, get)) {
279279
continue;
280280
}
281281
// If it passes, fall through to return the "v" value

example/src/App.tsx

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,30 @@ StyleRegistry.addStyleSheet({
88
[
99
"text-red-500",
1010
[
11-
{ s: specificity({ className: 1 }), d: [{ color: "red" }] },
1211
{
13-
s: specificity({ className: 2 }),
14-
d: [{ color: "green" }],
15-
m: { orientation: ["=", "landscape"] },
12+
s: specificity({ className: 1 }),
13+
d: [
14+
{
15+
color: "red",
16+
transitionProperty: "all",
17+
transitionDuration: "5s",
18+
},
19+
],
1620
},
17-
],
18-
],
19-
[
20-
"text-[--test]",
21-
[
2221
{
23-
s: specificity({ className: 3 }),
24-
d: [{ color: ["fn", "var", "test"] }],
25-
p: { a: true },
22+
s: specificity({ className: 4 }),
23+
d: [{ color: "purple" }],
24+
aq: { a: [["true", "disabled"]] },
2625
},
2726
],
2827
],
2928
],
3029
});
3130

32-
StyleRegistry.setRootVariables({
33-
test: [{ v: "pink" }],
34-
});
35-
3631
export default function App() {
3732
return (
3833
<View style={styles.container}>
39-
<Text
40-
className="text-red-500 text-[--test]"
41-
onPress={() => {
42-
console.log("Pressed!");
43-
}}
44-
>
34+
<Text className="text-red-500" style={{ fontSize: 30 }}>
4535
Multiply: {multiply(3, 7)}
4636
</Text>
4737
</View>

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,5 +167,8 @@
167167
"languages": "kotlin-swift",
168168
"type": "nitro-module",
169169
"version": "0.54.3"
170+
},
171+
"dependencies": {
172+
"react-native-reanimated": "^4.1.3"
170173
}
171174
}

0 commit comments

Comments
 (0)