-
Notifications
You must be signed in to change notification settings - Fork 0
/
css.js
172 lines (150 loc) Β· 5.03 KB
/
css.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Adapted from https://stackoverflow.com/a/37958301
var ELEMENT_RE = /[\w-]+/g,
ID_RE = /#[\w-]+/g,
CLASS_RE = /\.[\w-]+/g,
ATTR_RE = /\[[^\]]+\]/g,
// :not() pseudo-class does not add to specificity, but its content does as if it was outside it
PSEUDO_CLASSES_RE = /\:(?!not)[\w-]+(\(.*\))?/g,
PSEUDO_ELEMENTS_RE = /\:\:?(after|before|first-letter|first-line|selection)/g;
// handles extraction of `cssRules` as an `Array` from a stylesheet or something that behaves the same
function getSheetRules(stylesheet) {
var sheet_media = stylesheet.media && stylesheet.media.mediaText;
// if this sheet is disabled skip it
if (stylesheet.disabled) {
return [];
}
// if this sheet's media is specified and doesn't match the viewport then skip it
if (
sheet_media &&
sheet_media.length &&
!window.matchMedia(sheet_media).matches
) {
return [];
}
// get the style rules of this sheet
return Array.from(stylesheet.cssRules);
}
function _find(string, re) {
var matches = string.match(re);
return matches ? matches.length : 0;
}
// calculates the specificity of a given `selector`
function calculateScore(selector) {
var score = [0, 0, 0],
parts = selector.split(' '),
part,
match;
// TODO: clean the ':not' part since the last ELEMENT_RE will pick it up
while (((part = parts.shift()), typeof part == 'string')) {
// find all pseudo-elements
match = _find(part, PSEUDO_ELEMENTS_RE);
score[2] += match;
// and remove them
match && (part = part.replace(PSEUDO_ELEMENTS_RE, ''));
// find all pseudo-classes
match = _find(part, PSEUDO_CLASSES_RE);
score[1] += match;
// and remove them
match && (part = part.replace(PSEUDO_CLASSES_RE, ''));
// find all attributes
match = _find(part, ATTR_RE);
score[1] += match;
// and remove them
match && (part = part.replace(ATTR_RE, ''));
// find all IDs
match = _find(part, ID_RE);
score[0] += match;
// and remove them
match && (part = part.replace(ID_RE, ''));
// find all classes
match = _find(part, CLASS_RE);
score[1] += match;
// and remove them
match && (part = part.replace(CLASS_RE, ''));
// find all elements
score[2] += _find(part, ELEMENT_RE);
}
return parseInt(score.join(''), 10);
}
// get the highest possible specificity score an element can get from a given rule's selectorText
function getSpecificityScore(element, selector_text) {
var selectors = selector_text.split(','),
selector,
score,
result = 0;
while ((selector = selectors.shift())) {
if (matchesSelector(element, selector)) {
score = calculateScore(selector);
result = score > result ? score : result;
}
}
return result;
}
function sortBySpecificity(element, rules) {
// comparing function sorts CSSStyleRules according to specificity of their `selectorText`
return rules.sort((a, b) => {
return (
getSpecificityScore(element, b.selectorText) -
getSpecificityScore(element, a.selectorText)
);
});
}
// Find correct matchesSelector impl
function matchesSelector(el, selector) {
var matcher =
el.matchesSelector ||
el.mozMatchesSelector ||
el.webkitMatchesSelector ||
el.oMatchesSelector ||
el.msMatchesSelector;
return matcher.call(el, selector);
}
//TODO: not supporting 2nd argument for selecting pseudo elements
//TODO: not supporting 3rd argument for checking author style sheets only
function getMatchedCSSRulesPolyfill(
element,
/*, pseudo, author_only*/
styleSheets = window.document.styleSheets
) {
var style_sheets,
sheet,
rules,
rule,
result = [];
// get stylesheets and convert to a regular Array
style_sheets = Array.from(styleSheets);
// assuming the browser hands us stylesheets in order of appearance
// we iterate them from the beginning to follow proper cascade order
while ((sheet = style_sheets.shift())) {
// get the style rules of this sheet
rules = getSheetRules(sheet);
// loop the rules in order of appearance
while ((rule = rules.shift())) {
// if this is an @import rule
if (rule.styleSheet) {
// insert the imported stylesheet's rules at the beginning of this stylesheet's rules
rules = getSheetRules(rule.styleSheet).concat(rules);
// and skip this rule
continue;
}
// if there's no stylesheet attribute BUT there IS a media attribute it's a media rule
else if (rule.media) {
// insert the contained rules of this media rule to the beginning of this stylesheet's rules
rules = getSheetRules(rule).concat(rules);
// and skip it
continue;
}
// check if this element matches this rule's selector
if (matchesSelector(element, rule.selectorText)) {
// push the rule to the results set
result.push(rule);
}
}
}
// sort according to specificity
return sortBySpecificity(element, result);
}
export const getMatchedCSSRules =
typeof window.getMatchedCSSRules === 'function'
? window.getMatchedCSSRules
: getMatchedCSSRulesPolyfill;