UNPKG

6.59 kBJavaScriptView Raw
1'use strict';
2
3var csstree = require('css-tree'),
4 List = csstree.List,
5 stable = require('stable'),
6 specificity = require('csso/lib/restructure/prepare/specificity');
7
8
9/**
10 * Flatten a CSS AST to a selectors list.
11 *
12 * @param {Object} cssAst css-tree AST to flatten
13 * @return {Array} selectors
14 */
15function flattenToSelectors(cssAst) {
16 var selectors = [];
17
18 csstree.walk(cssAst, {visit: 'Rule', enter: function(node) {
19 if (node.type !== 'Rule') {
20 return;
21 }
22
23 var atrule = this.atrule;
24 var rule = node;
25
26 node.prelude.children.each(function(selectorNode, selectorItem) {
27 var selector = {
28 item: selectorItem,
29 atrule: atrule,
30 rule: rule,
31 pseudos: []
32 };
33
34 selectorNode.children.each(function(selectorChildNode, selectorChildItem, selectorChildList) {
35 if (selectorChildNode.type === 'PseudoClassSelector' ||
36 selectorChildNode.type === 'PseudoElementSelector') {
37 selector.pseudos.push({
38 item: selectorChildItem,
39 list: selectorChildList
40 });
41 }
42 });
43
44 selectors.push(selector);
45 });
46 }});
47
48 return selectors;
49}
50
51/**
52 * Filter selectors by Media Query.
53 *
54 * @param {Array} selectors to filter
55 * @param {Array} useMqs Array with strings of media queries that should pass (<name> <expression>)
56 * @return {Array} Filtered selectors that match the passed media queries
57 */
58function filterByMqs(selectors, useMqs) {
59 return selectors.filter(function(selector) {
60 if (selector.atrule === null) {
61 return ~useMqs.indexOf('');
62 }
63
64 var mqName = selector.atrule.name;
65 var mqStr = mqName;
66 if (selector.atrule.expression &&
67 selector.atrule.expression.children.first().type === 'MediaQueryList') {
68 var mqExpr = csstree.generate(selector.atrule.expression);
69 mqStr = [mqName, mqExpr].join(' ');
70 }
71
72 return ~useMqs.indexOf(mqStr);
73 });
74}
75
76/**
77 * Filter selectors by the pseudo-elements and/or -classes they contain.
78 *
79 * @param {Array} selectors to filter
80 * @param {Array} usePseudos Array with strings of single or sequence of pseudo-elements and/or -classes that should pass
81 * @return {Array} Filtered selectors that match the passed pseudo-elements and/or -classes
82 */
83function filterByPseudos(selectors, usePseudos) {
84 return selectors.filter(function(selector) {
85 var pseudoSelectorsStr = csstree.generate({
86 type: 'Selector',
87 children: new List().fromArray(selector.pseudos.map(function(pseudo) {
88 return pseudo.item.data;
89 }))
90 });
91 return ~usePseudos.indexOf(pseudoSelectorsStr);
92 });
93}
94
95/**
96 * Remove pseudo-elements and/or -classes from the selectors for proper matching.
97 *
98 * @param {Array} selectors to clean
99 * @return {Array} Selectors without pseudo-elements and/or -classes
100 */
101function cleanPseudos(selectors) {
102 selectors.forEach(function(selector) {
103 selector.pseudos.forEach(function(pseudo) {
104 pseudo.list.remove(pseudo.item);
105 });
106 });
107}
108
109
110/**
111 * Compares two selector specificities.
112 * extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
113 *
114 * @param {Array} aSpecificity Specificity of selector A
115 * @param {Array} bSpecificity Specificity of selector B
116 * @return {Number} Score of selector specificity A compared to selector specificity B
117 */
118function compareSpecificity(aSpecificity, bSpecificity) {
119 for (var i = 0; i < 4; i += 1) {
120 if (aSpecificity[i] < bSpecificity[i]) {
121 return -1;
122 } else if (aSpecificity[i] > bSpecificity[i]) {
123 return 1;
124 }
125 }
126
127 return 0;
128}
129
130
131/**
132 * Compare two simple selectors.
133 *
134 * @param {Object} aSimpleSelectorNode Simple selector A
135 * @param {Object} bSimpleSelectorNode Simple selector B
136 * @return {Number} Score of selector A compared to selector B
137 */
138function compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode) {
139 var aSpecificity = specificity(aSimpleSelectorNode),
140 bSpecificity = specificity(bSimpleSelectorNode);
141 return compareSpecificity(aSpecificity, bSpecificity);
142}
143
144function _bySelectorSpecificity(selectorA, selectorB) {
145 return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data);
146}
147
148
149/**
150 * Sort selectors stably by their specificity.
151 *
152 * @param {Array} selectors to be sorted
153 * @return {Array} Stable sorted selectors
154 */
155function sortSelectors(selectors) {
156 return stable(selectors, _bySelectorSpecificity);
157}
158
159
160/**
161 * Convert a css-tree AST style declaration to CSSStyleDeclaration property.
162 *
163 * @param {Object} declaration css-tree style declaration
164 * @return {Object} CSSStyleDeclaration property
165 */
166function csstreeToStyleDeclaration(declaration) {
167 var propertyName = declaration.property,
168 propertyValue = csstree.generate(declaration.value),
169 propertyPriority = (declaration.important ? 'important' : '');
170 return {
171 name: propertyName,
172 value: propertyValue,
173 priority: propertyPriority
174 };
175}
176
177
178/**
179 * Gets the CSS string of a style element
180 *
181 * @param {Object} element style element
182 * @return {String|Array} CSS string or empty array if no styles are set
183 */
184function getCssStr(elem) {
185 return elem.content[0].text || elem.content[0].cdata || [];
186}
187
188/**
189 * Sets the CSS string of a style element
190 *
191 * @param {Object} element style element
192 * @param {String} CSS string to be set
193 * @return {Object} reference to field with CSS
194 */
195function setCssStr(elem, css) {
196 // in case of cdata field
197 if(elem.content[0].cdata) {
198 elem.content[0].cdata = css;
199 return elem.content[0].cdata;
200 }
201
202 // in case of text field + if nothing was set yet
203 elem.content[0].text = css;
204 return elem.content[0].text;
205}
206
207
208module.exports.flattenToSelectors = flattenToSelectors;
209
210module.exports.filterByMqs = filterByMqs;
211module.exports.filterByPseudos = filterByPseudos;
212module.exports.cleanPseudos = cleanPseudos;
213
214module.exports.compareSpecificity = compareSpecificity;
215module.exports.compareSimpleSelectorNode = compareSimpleSelectorNode;
216
217module.exports.sortSelectors = sortSelectors;
218
219module.exports.csstreeToStyleDeclaration = csstreeToStyleDeclaration;
220
221module.exports.getCssStr = getCssStr;
222module.exports.setCssStr = setCssStr;