From 45272e8a591c76ec1015b00f70e679197167d72a Mon Sep 17 00:00:00 2001 From: Eric Ferraiuolo Date: Tue, 7 Jan 2014 21:23:32 -0500 Subject: [PATCH] Implement media query `match()` function and expose `parse()` --- index.js | 86 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 065f90f..7232b46 100644 --- a/index.js +++ b/index.js @@ -6,22 +6,84 @@ See the accompanying LICENSE file for terms. 'use strict'; -module.exports = match; +exports.match = matchQuery; +exports.parse = parseQuery; // ----------------------------------------------------------------------------- -function match(mediaQuery, values) { - return parseQuery(mediaQuery); -} - -// -- Utilities ---------------------------------------------------------------- - 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 === 'all' || + values.type === query.type; + + // Quit early when `type` doesn't match, but take "not" into account. + if ((typeMatch && inverse) || !(typeMatch || inverse)) { + return false; + } + + 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': + 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) { var captures = query.match(RE_MEDIA_QUERY), @@ -30,9 +92,8 @@ function parseQuery(mediaQuery) { expressions = captures[3], parsed = {}; - parsed.only = !!modifier && modifier.toLowerCase() === 'only'; - parsed.not = !!modifier && modifier.toLowerCase() === 'not'; - parsed.type = type ? type.toLowerCase() : 'all'; + parsed.inverse = !!modifier && modifier.toLowerCase() === 'not'; + parsed.type = type ? type.toLowerCase() : 'all'; // Split expressions into a list. expressions = expressions.match(/\([^\)]+\)/g); @@ -42,9 +103,8 @@ function parseQuery(mediaQuery) { feature = captures[1].toLowerCase().match(RE_MQ_FEATURE); return { - feature : feature[0], modifier: feature[1], - property: feature[2], + feature : feature[2], value : captures[2] }; }); @@ -53,6 +113,8 @@ function parseQuery(mediaQuery) { }); } +// -- Utilities ---------------------------------------------------------------- + function toDecimal(ratio) { var decimal = Number(ratio), numbers;