Skip to content

Commit ea6dbd9

Browse files
committed
feat: specificity sorting
1 parent 2b042c4 commit ea6dbd9

File tree

6 files changed

+137
-43
lines changed

6 files changed

+137
-43
lines changed

cpp/Specificity.hpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#pragma once
2+
3+
#include <tuple>
4+
5+
namespace margelo::nitro::cssnitro {
6+
7+
using SpecificityArray = std::tuple<double, double, double, double, double>;
8+
9+
class Specificity {
10+
public:
11+
/**
12+
* Sort function for comparing two specificity arrays.
13+
* Returns true if 'a' should come before 'b' in the sorted order.
14+
* Sorts in reverse order (larger values first).
15+
*
16+
* @param a First specificity array
17+
* @param b Second specificity array
18+
* @return true if a should come before b (a has higher specificity)
19+
*/
20+
static bool sort(const SpecificityArray &a, const SpecificityArray &b) {
21+
// Compare each index in order, higher values come first
22+
if (std::get<0>(a) != std::get<0>(b)) {
23+
return std::get<0>(a) > std::get<0>(b);
24+
}
25+
if (std::get<1>(a) != std::get<1>(b)) {
26+
return std::get<1>(a) > std::get<1>(b);
27+
}
28+
if (std::get<2>(a) != std::get<2>(b)) {
29+
return std::get<2>(a) > std::get<2>(b);
30+
}
31+
if (std::get<3>(a) != std::get<3>(b)) {
32+
return std::get<3>(a) > std::get<3>(b);
33+
}
34+
return std::get<4>(a) > std::get<4>(b);
35+
}
36+
};
37+
38+
} // namespace margelo::nitro::cssnitro
39+

cpp/StyledComputedFactory.cpp

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
#include "Rules.hpp"
55
#include "Helpers.hpp"
66
#include "StyleFunction.hpp"
7+
#include "Specificity.hpp"
78

89
#include <regex>
910
#include <variant>
1011
#include <vector>
1112
#include <string>
13+
#include <algorithm>
1214
#include <folly/dynamic.h>
1315
#include <NitroModules/AnyMap.hpp>
1416

@@ -35,6 +37,9 @@ namespace margelo::nitro::cssnitro {
3537
*/
3638
std::unordered_map<std::string, AnyValue> mergedStyles;
3739

40+
// Collect all style rules from all classNames
41+
std::vector<HybridStyleRule> allStyleRules;
42+
3843
std::regex whitespace{"\\s+"};
3944
std::sregex_token_iterator tokenIt(classNames.begin(), classNames.end(),
4045
whitespace, -1);
@@ -53,43 +58,53 @@ namespace margelo::nitro::cssnitro {
5358

5459
const std::vector<HybridStyleRule> &styleRules = get(*styleIt->second);
5560

56-
for (const HybridStyleRule &styleRule: styleRules) {
57-
// Skip rule if its media conditions don't pass
58-
if (!Rules::testRule(styleRule, get)) {
59-
continue;
60-
}
61+
// Add all style rules to the collection
62+
allStyleRules.insert(allStyleRules.end(), styleRules.begin(),
63+
styleRules.end());
64+
}
65+
66+
// Sort all style rules by specificity (highest specificity first)
67+
std::sort(allStyleRules.begin(), allStyleRules.end(),
68+
[](const HybridStyleRule &a, const HybridStyleRule &b) {
69+
return Specificity::sort(a.s, b.s);
70+
});
6171

62-
if (styleRule.d.has_value()) {
63-
const auto declarations = styleRule.d.value();
64-
const auto &dStyles = std::get<0>(std::get<0>(declarations));
65-
for (const auto &kv: dStyles->getMap()) {
66-
// Only set if key doesn't already exist
67-
if (mergedStyles.count(kv.first) == 0) {
68-
// if kv.second is an array with "fn" as the first key, resolve it
69-
if (dStyles->isArray(kv.first)) {
70-
const auto &arr = dStyles->getArray(kv.first);
71-
if (!arr.empty() &&
72-
std::holds_alternative<std::string>(arr[0]) &&
73-
std::get<std::string>(arr[0]) == "fn") {
74-
auto result = StyleFunction::resolveStyleFn(
75-
arr, get, variableScope);
76-
77-
// Skip if resolveStyleFn returns nullptr
78-
if (std::holds_alternative<std::monostate>(
79-
result)) {
80-
continue;
81-
}
82-
83-
mergedStyles[kv.first] = result;
72+
// Now process the sorted style rules
73+
for (const HybridStyleRule &styleRule: allStyleRules) {
74+
// Skip rule if its media conditions don't pass
75+
if (!Rules::testRule(styleRule, get)) {
76+
continue;
77+
}
78+
79+
if (styleRule.d.has_value()) {
80+
const auto declarations = styleRule.d.value();
81+
const auto &dStyles = std::get<0>(std::get<0>(declarations));
82+
for (const auto &kv: dStyles->getMap()) {
83+
// Only set if key doesn't already exist
84+
if (mergedStyles.count(kv.first) == 0) {
85+
// if kv.second is an array with "fn" as the first key, resolve it
86+
if (dStyles->isArray(kv.first)) {
87+
const auto &arr = dStyles->getArray(kv.first);
88+
if (!arr.empty() &&
89+
std::holds_alternative<std::string>(arr[0]) &&
90+
std::get<std::string>(arr[0]) == "fn") {
91+
auto result = StyleFunction::resolveStyleFn(
92+
arr, get, variableScope);
93+
94+
// Skip if resolveStyleFn returns nullptr
95+
if (std::holds_alternative<std::monostate>(
96+
result)) {
8497
continue;
8598
}
99+
100+
mergedStyles[kv.first] = result;
101+
continue;
86102
}
87-
mergedStyles[kv.first] = kv.second;
88103
}
104+
mergedStyles[kv.first] = kv.second;
89105
}
90-
// Ignore the other entries for now
91-
92106
}
107+
// Ignore the other entries for now
93108
}
94109
}
95110

example/src/App.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,38 @@
11
import { StyleSheet, View } from "react-native";
22

3-
import { multiply, StyleRegistry } from "react-native-css-nitro";
3+
import { multiply, specificity, StyleRegistry } from "react-native-css-nitro";
44
import { Text } from "react-native-css-nitro/components/Text";
55

66
StyleRegistry.addStyleSheet({
77
s: [
88
[
99
"text-red-500",
1010
[
11-
{ s: [], d: [{ color: "red" }] },
11+
{ s: specificity({ className: 1 }), d: [{ color: "red" }] },
1212
{
13-
s: [],
13+
s: specificity({ className: 2 }),
1414
d: [{ color: "green" }],
1515
m: { orientation: ["=", "landscape"] },
1616
},
1717
],
1818
],
19-
["text-[--test]", [{ s: [], d: [{ color: ["fn", "var", "test"] }] }]],
19+
[
20+
"text-[--test]",
21+
[
22+
{
23+
s: specificity({ className: 3 }),
24+
d: [{ color: ["fn", "var", "test"] }],
25+
},
26+
],
27+
],
2028
],
2129
});
2230

2331
export default function App() {
2432
return (
2533
<View style={styles.container}>
2634
<Text
27-
className="text-[--test] text-red-500"
35+
className="text-red-500 text-[--test]"
2836
onPress={() => {
2937
console.log("Pressed!");
3038
StyleRegistry.setRootVariables({

src/index.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { NitroModules } from 'react-native-nitro-modules';
2-
import type { CssNitro } from './CssNitro.nitro';
3-
import type { Math } from './specs/Math.nitro';
1+
import { NitroModules } from "react-native-nitro-modules";
42

5-
export { StyleRegistry } from './specs/StyleRegistry';
6-
export { useStyled } from './native/useStyled';
3+
import type { CssNitro } from "./CssNitro.nitro";
4+
import type { Math } from "./specs/Math.nitro";
5+
6+
export { StyleRegistry } from "./specs/StyleRegistry";
7+
export { useStyled } from "./native/useStyled";
8+
export * from "./native/specificity";
79

810
const CssNitroHybridObject =
9-
NitroModules.createHybridObject<CssNitro>('CssNitro');
11+
NitroModules.createHybridObject<CssNitro>("CssNitro");
1012

11-
const MathHybridObject = NitroModules.createHybridObject<Math>('Math');
13+
const MathHybridObject = NitroModules.createHybridObject<Math>("Math");
1214

1315
export function multiply(a: number, b: number): number {
1416
return CssNitroHybridObject.multiply(a, b);

src/native/specificity.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { SpecificityArray } from "../specs/StyleRegistry/HybridStyleRegistry.nitro";
2+
3+
const Specificity = {
4+
order: 0,
5+
className: 1,
6+
important: 2,
7+
inline: 3,
8+
pseudoElements: 4,
9+
pseudoClass: 1,
10+
// Id: 0, - We don't support ID yet
11+
// StyleSheet: 0, - We don't support multiple stylesheets
12+
};
13+
14+
const specificityKeys = new Set(Object.keys(Specificity));
15+
16+
export function specificity(
17+
options: Partial<typeof Specificity>,
18+
): SpecificityArray {
19+
const spec: SpecificityArray = [0, 0, 0, 0, 0];
20+
21+
for (const [key, value] of Object.entries(options)) {
22+
if (value && specificityKeys.has(key)) {
23+
const index = Specificity[key as keyof typeof Specificity];
24+
spec[index] ??= 0;
25+
spec[index] += value;
26+
}
27+
}
28+
29+
return spec;
30+
}

src/specs/StyleRegistry/HybridStyleRegistry.nitro.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,4 @@ interface HybridStyleRule {
131131
m?: AnyMap;
132132
}
133133

134-
type SpecificityArray = number[];
134+
export type SpecificityArray = [number, number, number, number, number];

0 commit comments

Comments
 (0)