Skip to content

Instantly share code, notes, and snippets.

@ardeshireshghi
Last active January 25, 2024 00:10
Show Gist options
  • Save ardeshireshghi/9500d88ec7ed5878f48cf1ebbdfefdda to your computer and use it in GitHub Desktop.
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`
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