Last active
January 25, 2024 00:10
-
-
Save ardeshireshghi/9500d88ec7ed5878f48cf1ebbdfefdda to your computer and use it in GitHub Desktop.
This function takes the CSS selector text and calculates specificity. It does not take into account inline style and important specificity rules and only relies on the `selectorText`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function updateSpecificity(selector, currentSpecificity) { | |
const newSpecificity = [...currentSpecificity]; | |
if (isSelectorId(selector)) { | |
newSpecificity[0] += 1; | |
return newSpecificity; | |
} | |
if ( | |
isSelectorClass(selector) || | |
isSelectorPseudoClass(selector) || | |
isSelectorAttribute(selector) | |
) { | |
if (selector !== ':not') { | |
newSpecificity[1] += 1; | |
} | |
return newSpecificity; | |
} | |
// Otherwise it is either type selector (e.g div, li) or non-class Psuedo element like :before | |
newSpecificity[2] += 1; | |
return newSpecificity; | |
} | |
function cssSelectorSpecificity(selectorText) { | |
let currentSelector = selectorText; | |
let specificity = [0, 0, 0]; | |
let match; | |
do { | |
match = currentSelector.match(/([\.\#]?[\w-]+|\[.+\]|\:{1,2}[\w-]+)/); | |
if (match && match.length > 1) { | |
const selector = match[1]; | |
specificity = updateSpecificity(selector, specificity); | |
currentSelector = currentSelector.replace(match[1], ""); | |
} | |
} while (match); | |
return specificity; | |
} | |
const pseudoClasses = [ | |
"active", | |
"checked", | |
"disabled", | |
"empty", | |
"enabled", | |
"first-child", | |
"first-of-type", | |
"focus", | |
"hover", | |
"in-range", | |
"invalid", | |
"last-child", | |
"last-of-type", | |
"link", | |
"has", | |
"not", | |
"nth-child", | |
"nth-last-child", | |
"nth-last-of-type", | |
"nth-of-type", | |
"only-child", | |
"only-of-type", | |
"optional", | |
"out-of-range", | |
"placeholder-shown", | |
"read-only", | |
"read-write", | |
"required", | |
"root", | |
"target", | |
"valid", | |
"visited", | |
]; | |
const isSelectorId = (selector) => selector.startsWith("#"); | |
const isSelectorClass = (selector) => selector.startsWith("."); | |
const isSelectorPseudoClass = (selector) => | |
selector.startsWith(":") && | |
pseudoClasses.includes(selector.replace(/:/g, "")); | |
const isSelectorAttribute = (selector) => | |
selector.startsWith("[") && selector.endsWith("]"); | |
// Test with some selectors | |
const listOfSelectors = ` | |
nav > a:hover::before | |
ul#primary-nav li.active | |
#heading nav ul li.disabled | |
#heading nav ul li.disabled .ardeshir .foo:not(.bar):hover | |
#heading nav ul li.disabled .ardeshir .foo:not(.bar):has(.rt) | |
`; | |
listOfSelectors | |
.split("\n") | |
.filter(Boolean) | |
.forEach((selector) => { | |
console.log(selector, cssSelectorSpecificity(selector)); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment