3 Object.defineProperty(exports, "__esModule", {
6 exports.parseMediaFeature = parseMediaFeature;
7 exports.parseMediaQuery = parseMediaQuery;
8 exports.parseMediaList = parseMediaList;
10 var _Node = require('./nodes/Node');
12 var _Node2 = _interopRequireDefault(_Node);
14 var _Container = require('./nodes/Container');
16 var _Container2 = _interopRequireDefault(_Container);
18 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21 * Parses a media feature expression, e.g. `max-width: 10px`, `(color)`
\r
23 * @param {string} string - the source expression string, can be inside parens
\r
24 * @param {Number} index - the index of `string` in the overall input
\r
26 * @return {Array} an array of Nodes, the first element being a media feature,
\r
27 * the secont - its value (may be missing)
\r
30 function parseMediaFeature(string) {
31 var index = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
38 var lastModeIndex = 0;
39 var mediaFeature = '';
41 var mediaFeatureValue = null;
42 var indexLocal = index;
44 var stringNormalized = string;
45 // Strip trailing parens (if any), and correct the starting index
46 if (string[0] === '(' && string[string.length - 1] === ')') {
47 stringNormalized = string.substring(1, string.length - 1);
51 for (var i = 0; i < stringNormalized.length; i++) {
52 var character = stringNormalized[i];
54 // If entering/exiting a string
55 if (character === '\'' || character === '"') {
56 if (modesEntered[lastModeIndex].isCalculationEnabled === true) {
59 isCalculationEnabled: false,
63 } else if (modesEntered[lastModeIndex].mode === 'string' && modesEntered[lastModeIndex].character === character && stringNormalized[i - 1] !== '\\') {
69 // If entering/exiting interpolation
70 if (character === '{') {
72 mode: 'interpolation',
73 isCalculationEnabled: true
76 } else if (character === '}') {
81 // If a : is met outside of a string, function call or interpolation, than
82 // this : separates a media feature and a value
83 if (modesEntered[lastModeIndex].mode === 'normal' && character === ':') {
84 var mediaFeatureValueStr = stringNormalized.substring(i + 1);
87 before: /^(\s*)/.exec(mediaFeatureValueStr)[1],
88 after: /(\s*)$/.exec(mediaFeatureValueStr)[1],
89 value: mediaFeatureValueStr.trim()
92 mediaFeatureValue.sourceIndex = mediaFeatureValue.before.length + i + 1 + indexLocal;
95 sourceIndex: i + indexLocal,
96 after: mediaFeatureValue.before,
101 mediaFeature += character;
104 // Forming a media feature node
106 type: 'media-feature',
107 before: /^(\s*)/.exec(mediaFeature)[1],
108 after: /(\s*)$/.exec(mediaFeature)[1],
109 value: mediaFeature.trim()
111 mediaFeature.sourceIndex = mediaFeature.before.length + indexLocal;
112 result.push(mediaFeature);
114 if (colon !== null) {
115 colon.before = mediaFeature.after;
119 if (mediaFeatureValue !== null) {
120 result.push(mediaFeatureValue);
127 * Parses a media query, e.g. `screen and (color)`, `only tv`
\r
129 * @param {string} string - the source media query string
\r
130 * @param {Number} index - the index of `string` in the overall input
\r
132 * @return {Array} an array of Nodes and Containers
\r
135 function parseMediaQuery(string) {
136 var index = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
140 // How many timies the parser entered parens/curly braces
142 // Has any keyword, media type, media feature expression or interpolation
143 // ('element' hereafter) started
144 var insideSomeValue = false;
147 function resetNode() {
157 for (var i = 0; i < string.length; i++) {
158 var character = string[i];
159 // If not yet entered any element
160 if (!insideSomeValue) {
161 if (character.search(/\s/) !== -1) {
163 // Don't form 'after' yet; will do it later
164 node.before += character;
166 // Not a whitespace - entering an element
168 if (character === '(') {
169 node.type = 'media-feature-expression';
172 node.value = character;
173 node.sourceIndex = index + i;
174 insideSomeValue = true;
177 // Already in the middle of some alement
178 node.value += character;
180 // Here parens just increase localLevel and don't trigger a start of
181 // a media feature expression (since they can't be nested)
182 // Interpolation start
183 if (character === '{' || character === '(') {
186 // Interpolation/function call/media feature expression end
187 if (character === ')' || character === '}') {
192 // If exited all parens/curlies and the next symbol
193 if (insideSomeValue && localLevel === 0 && (character === ')' || i === string.length - 1 || string[i + 1].search(/\s/) !== -1)) {
194 if (['not', 'only', 'and'].indexOf(node.value) !== -1) {
195 node.type = 'keyword';
197 // if it's an expression, parse its contents
198 if (node.type === 'media-feature-expression') {
199 node.nodes = parseMediaFeature(node.value, node.sourceIndex);
201 result.push(Array.isArray(node.nodes) ? new _Container2.default(node) : new _Node2.default(node));
203 insideSomeValue = false;
207 // Now process the result array - to specify undefined types of the nodes
208 // and specify the `after` prop
209 for (var _i = 0; _i < result.length; _i++) {
212 result[_i - 1].after = node.before;
215 // Node types. Might not be set because contains interpolation/function
216 // calls or fully consists of them
217 if (node.type === undefined) {
219 // only `and` can follow an expression
220 if (result[_i - 1].type === 'media-feature-expression') {
221 node.type = 'keyword';
224 // Anything after 'only|not' is a media type
225 if (result[_i - 1].value === 'not' || result[_i - 1].value === 'only') {
226 node.type = 'media-type';
229 // Anything after 'and' is an expression
230 if (result[_i - 1].value === 'and') {
231 node.type = 'media-feature-expression';
235 if (result[_i - 1].type === 'media-type') {
236 // if it is the last element - it might be an expression
237 // or 'and' depending on what is after it
238 if (!result[_i + 1]) {
239 node.type = 'media-feature-expression';
241 node.type = result[_i + 1].type === 'media-feature-expression' ? 'keyword' : 'media-feature-expression';
247 // `screen`, `fn( ... )`, `#{ ... }`. Not an expression, since then
248 // its type would have been set by now
249 if (!result[_i + 1]) {
250 node.type = 'media-type';
254 // `screen and` or `#{...} (max-width: 10px)`
255 if (result[_i + 1] && (result[_i + 1].type === 'media-feature-expression' || result[_i + 1].type === 'keyword')) {
256 node.type = 'media-type';
259 if (result[_i + 2]) {
260 // `screen and (color) ...`
261 if (result[_i + 2].type === 'media-feature-expression') {
262 node.type = 'media-type';
263 result[_i + 1].type = 'keyword';
266 // `only screen and ...`
267 if (result[_i + 2].type === 'keyword') {
268 node.type = 'keyword';
269 result[_i + 1].type = 'media-type';
273 if (result[_i + 3]) {
274 // `screen and (color) ...`
275 if (result[_i + 3].type === 'media-feature-expression') {
276 node.type = 'keyword';
277 result[_i + 1].type = 'media-type';
278 result[_i + 2].type = 'keyword';
289 * Parses a media query list. Takes a possible `url()` at the start into
\r
290 * account, and divides the list into media queries that are parsed separately
\r
292 * @param {string} string - the source media query list string
\r
294 * @return {Array} an array of Nodes/Containers
\r
297 function parseMediaList(string) {
299 var interimIndex = 0;
302 // Check for a `url(...)` part (if it is contents of an @import rule)
303 var doesHaveUrl = /^(\s*)url\s*\(/.exec(string);
304 if (doesHaveUrl !== null) {
305 var i = doesHaveUrl[0].length;
306 var parenthesesLv = 1;
307 while (parenthesesLv > 0) {
308 var character = string[i];
309 if (character === '(') {
312 if (character === ')') {
317 result.unshift(new _Node2.default({
319 value: string.substring(0, i).trim(),
320 sourceIndex: doesHaveUrl[1].length,
321 before: doesHaveUrl[1],
322 after: /^(\s*)/.exec(string.substring(i))[1]
327 // Start processing the media query list
328 for (var _i2 = interimIndex; _i2 < string.length; _i2++) {
329 var _character = string[_i2];
331 // Dividing the media query list into comma-separated media queries
332 // Only count commas that are outside of any parens
333 // (i.e., not part of function call params list, etc.)
334 if (_character === '(') {
337 if (_character === ')') {
340 if (levelLocal === 0 && _character === ',') {
341 var _mediaQueryString = string.substring(interimIndex, _i2);
342 var _spaceBefore = /^(\s*)/.exec(_mediaQueryString)[1];
343 result.push(new _Container2.default({
345 value: _mediaQueryString.trim(),
346 sourceIndex: interimIndex + _spaceBefore.length,
347 nodes: parseMediaQuery(_mediaQueryString, interimIndex),
348 before: _spaceBefore,
349 after: /(\s*)$/.exec(_mediaQueryString)[1]
351 interimIndex = _i2 + 1;
355 var mediaQueryString = string.substring(interimIndex);
356 var spaceBefore = /^(\s*)/.exec(mediaQueryString)[1];
357 result.push(new _Container2.default({
359 value: mediaQueryString.trim(),
360 sourceIndex: interimIndex + spaceBefore.length,
361 nodes: parseMediaQuery(mediaQueryString, interimIndex),
363 after: /(\s*)$/.exec(mediaQueryString)[1]