Spaces:
Configuration error
Configuration error
import { parse } from 'css-tree'; | |
function ensureSelectorList(node) { | |
if (node.type === 'Raw') { | |
return parse(node.value, { context: 'selectorList' }); | |
} | |
return node; | |
} | |
function maxSpecificity(a, b) { | |
for (let i = 0; i < 3; i++) { | |
if (a[i] !== b[i]) { | |
return a[i] > b[i] ? a : b; | |
} | |
} | |
return a; | |
} | |
function maxSelectorListSpecificity(selectorList) { | |
return ensureSelectorList(selectorList).children.reduce( | |
(result, node) => maxSpecificity(specificity(node), result), | |
[0, 0, 0] | |
); | |
} | |
// §16. Calculating a selector’s specificity | |
// https://www.w3.org/TR/selectors-4/#specificity-rules | |
function specificity(simpleSelector) { | |
let A = 0; | |
let B = 0; | |
let C = 0; | |
// A selector’s specificity is calculated for a given element as follows: | |
simpleSelector.children.forEach((node) => { | |
switch (node.type) { | |
// count the number of ID selectors in the selector (= A) | |
case 'IdSelector': | |
A++; | |
break; | |
// count the number of class selectors, attributes selectors, ... | |
case 'ClassSelector': | |
case 'AttributeSelector': | |
B++; | |
break; | |
// ... and pseudo-classes in the selector (= B) | |
case 'PseudoClassSelector': | |
switch (node.name.toLowerCase()) { | |
// The specificity of an :is(), :not(), or :has() pseudo-class is replaced | |
// by the specificity of the most specific complex selector in its selector list argument. | |
case 'not': | |
case 'has': | |
case 'is': | |
// :matches() is used before it was renamed to :is() | |
// https://github.com/w3c/csswg-drafts/issues/3258 | |
case 'matches': | |
// Older browsers support :is() functionality as prefixed pseudo-class :any() | |
// https://developer.mozilla.org/en-US/docs/Web/CSS/:is | |
case '-webkit-any': | |
case '-moz-any': { | |
const [a, b, c] = maxSelectorListSpecificity(node.children.first); | |
A += a; | |
B += b; | |
C += c; | |
break; | |
} | |
// Analogously, the specificity of an :nth-child() or :nth-last-child() selector | |
// is the specificity of the pseudo class itself (counting as one pseudo-class selector) | |
// plus the specificity of the most specific complex selector in its selector list argument (if any). | |
case 'nth-child': | |
case 'nth-last-child': { | |
const arg = node.children.first; | |
if (arg.type === 'Nth' && arg.selector) { | |
const [a, b, c] = maxSelectorListSpecificity(arg.selector); | |
A += a; | |
B += b + 1; | |
C += c; | |
} else { | |
B++; | |
} | |
break; | |
} | |
// The specificity of a :where() pseudo-class is replaced by zero. | |
case 'where': | |
break; | |
// The four Level 2 pseudo-elements (::before, ::after, ::first-line, and ::first-letter) may, | |
// for legacy reasons, be represented using the <pseudo-class-selector> grammar, | |
// with only a single ":" character at their start. | |
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos | |
case 'before': | |
case 'after': | |
case 'first-line': | |
case 'first-letter': | |
C++; | |
break; | |
default: | |
B++; | |
} | |
break; | |
// count the number of type selectors ... | |
case 'TypeSelector': | |
// ignore the universal selector | |
if (!node.name.endsWith('*')) { | |
C++; | |
} | |
break; | |
// ... and pseudo-elements in the selector (= C) | |
case 'PseudoElementSelector': | |
C++; | |
break; | |
} | |
}); | |
return [A, B, C]; | |
}; | |
export default specificity; | |