UNPKG

@luminati-io/css-mediaquery

Version:

Parses and determines if a given CSS Media Query matches a set of values.

161 lines (129 loc) 5.1 kB
/* Copyright (c) 2014, Yahoo! Inc. All rights reserved. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ 'use strict'; exports.match = matchQuery; exports.parse = parseQuery; // ----------------------------------------------------------------------------- var RE_MEDIA_QUERY = /(?:(only|not)?\s*([^\s\(\)]+)(?:\s*and)?\s*)?(.+)?/i, RE_MQ_EXPRESSION = /\(\s*([^\s\:\)]+)\s*(?:\:\s*([^\s\)]+))?\s*\)/, RE_MQ_FEATURE = /^(?:(min|max)-)?(.+)/, RE_LENGTH_UNIT = /(em|rem|px|cm|mm|in|pt|pc)?$/, RE_RESOLUTION_UNIT = /(dpi|dpcm|dppx)?$/; function matchQuery(mediaQuery, values) { return parseQuery(mediaQuery).some(function (query) { var inverse = query.inverse; // Either the parsed or specified `type` is "all", or the types must be // equal for a match. var typeMatch = query.type === 'all' || values.type === query.type; // Quit early when `type` doesn't match, but take "not" into account. if ((typeMatch && inverse) || !(typeMatch || inverse)) { return false; } // if types match and no expressions, it's a match if (!query.expressions.length) return true; var expressionsMatch = query.expressions.every(function (expression) { var feature = expression.feature, modifier = expression.modifier, expValue = expression.value, value = values[feature]; // Missing or falsy values don't match. if (!value) { return false; } switch (feature) { case 'orientation': case 'scan': return value.toLowerCase() === expValue.toLowerCase(); case 'width': case 'height': case 'device-width': case 'device-height': expValue = toPx(expValue); value = toPx(value); break; case 'resolution': expValue = toDpi(expValue); value = toDpi(value); break; case 'aspect-ratio': case 'device-aspect-ratio': case /* Deprecated */ 'device-pixel-ratio': expValue = toDecimal(expValue); value = toDecimal(value); break; case 'grid': case 'color': case 'color-index': case 'monochrome': expValue = parseInt(expValue, 10) || 1; value = parseInt(value, 10) || 0; break; } switch (modifier) { case 'min': return value >= expValue; case 'max': return value <= expValue; default : return value === expValue; } }); return (expressionsMatch && !inverse) || (!expressionsMatch && inverse); }); } function parseQuery(mediaQuery) { return mediaQuery.split(',').map(function (query) { query = query.trim(); var captures = query.match(RE_MEDIA_QUERY), modifier = captures[1], type = captures[2], expressions = captures[3] || '', parsed = {}; parsed.inverse = !!modifier && modifier.toLowerCase() === 'not'; parsed.type = type ? type.toLowerCase() : 'all'; // Split expressions into a list. expressions = expressions.match(/\([^\)]+\)/g) || []; parsed.expressions = expressions.map(function (expression) { var captures = expression.match(RE_MQ_EXPRESSION), feature = captures[1].toLowerCase().match(RE_MQ_FEATURE); return { modifier: feature[1], feature : feature[2], value : captures[2] }; }); return parsed; }); } // -- Utilities ---------------------------------------------------------------- function toDecimal(ratio) { var decimal = Number(ratio), numbers; if (!decimal) { numbers = ratio.match(/^(\d+)\s*\/\s*(\d+)$/); decimal = numbers[1] / numbers[2]; } return decimal; } function toDpi(resolution) { var value = parseFloat(resolution), units = String(resolution).match(RE_RESOLUTION_UNIT)[1]; switch (units) { case 'dpcm': return value / 2.54; case 'dppx': return value * 96; default : return value; } } function toPx(length) { var value = parseFloat(length), units = String(length).match(RE_LENGTH_UNIT)[1]; switch (units) { case 'em' : return value * 16; case 'rem': return value * 16; case 'cm' : return value * 96 / 2.54; case 'mm' : return value * 96 / 2.54 / 10; case 'in' : return value * 96; case 'pt' : return value * 72; case 'pc' : return value * 72 / 12; default : return value; } }