sort-css-media-queries
Version:
The custom `sort` method (mobile-first / desktop-first) of CSS media queries for `postcss-sort-media-queries`, `css-mqpacker` or `pleeease` (which uses css-mqpacker) or, perhaps, something else ))
313 lines (265 loc) • 7.24 kB
JavaScript
// ----------------------------------------
// Private
// ----------------------------------------
const minMaxWidth =
/(!?\(\s*min(-device)?-width)(.|\n)+\(\s*max(-device)?-width|\(\s*width\s*>(=)?(.|\n)+\(\s*width\s*<(=)?|(!?\(.*<(=)?\s*width\s*<(=)?)/i;
const minWidth = /\(\s*min(-device)?-width|\(\s*width\s*>(=)?/i;
const maxMinWidth =
/(!?\(\s*max(-device)?-width)(.|\n)+\(\s*min(-device)?-width|\(\s*width\s*<(=)?(.|\n)+\(\s*width\s*>(=)?|(!?\(.*>(=)?\s*width\s*>(=)?)/i;
const maxWidth = /\(\s*max(-device)?-width|\(\s*width\s*<(=)?/i;
const isMinWidth = _testQuery(minMaxWidth, maxMinWidth, minWidth);
const isMaxWidth = _testQuery(maxMinWidth, minMaxWidth, maxWidth);
const minMaxHeight =
/(!?\(\s*min(-device)?-height)(.|\n)+\(\s*max(-device)?-height|\(\s*height\s*>(=)?(.|\n)+\(\s*height\s*<(=)?|(!?\(.*<(=)?\s*height\s*<(=)?)/i;
const minHeight = /\(\s*min(-device)?-height|\(\s*height\s*>(=)?/i;
const maxMinHeight =
/(!?\(\s*max(-device)?-height)(.|\n)+\(\s*min(-device)?-height|\(\s*height\s*<(=)?(.|\n)+\(\s*height\s*>(=)?|(!?\(.*>(=)?\s*height\s*>(=)?)/i;
const maxHeight = /\(\s*max(-device)?-height|\(\s*height\s*<(=)?/i;
const isMinHeight = _testQuery(minMaxHeight, maxMinHeight, minHeight);
const isMaxHeight = _testQuery(maxMinHeight, minMaxHeight, maxHeight);
const lessThan = /<(?!=)/;
const grtrThan = />(?!=)/;
const isPrint = /print/i;
const isPrintOnly = /^print$/i;
const maxValue = Number.MAX_VALUE;
/**
* Obtain the length of the media request in pixels.
* Copy from original source `function inspectLength (length)`
* {@link https://github.com/hail2u/node-css-mqpacker/blob/master/index.js#L58}
* @private
* @param {string} length
* @return {number}
*/
function _getQueryLength(query) {
let length = /(-?\d*\.?\d+)(ch|em|ex|px|rem)/.exec(query);
if (length === null && (isMinWidth(query) || isMinHeight(query))) {
length = /(\d)/.exec(query);
}
if (length === '0') {
return 0;
}
if (length === null) {
return maxValue;
}
let number = length[1];
const unit = length[2];
switch (unit) {
case 'ch':
number = parseFloat(number) * 8.8984375;
break;
case 'em':
case 'rem':
number = parseFloat(number) * 16;
break;
case 'ex':
number = parseFloat(number) * 8.296875;
break;
case 'px':
number = parseFloat(number);
break;
}
return +number;
}
/**
* Wrapper for creating test functions
* @private
* @param {RegExp} doubleTestTrue
* @param {RegExp} doubleTestFalse
* @param {RegExp} singleTest
* @return {Function}
*/
function _testQuery(doubleTestTrue, doubleTestFalse, singleTest) {
/**
* @param {string} query
* @return {boolean}
*/
return function (query) {
let result;
if (doubleTestTrue.test(query)) result = true;
else if (doubleTestFalse.test(query)) result = false;
else result = singleTest.test(query);
/** Not keyword inverts the whole query */
return query.includes('not') ? !result : result;
};
}
/**
* @private
* @param {string} a
* @param {string} b
* @return {number|null}
*/
function _testIsPrint(a, b) {
const isPrintA = isPrint.test(a);
const isPrintOnlyA = isPrintOnly.test(a);
const isPrintB = isPrint.test(b);
const isPrintOnlyB = isPrintOnly.test(b);
if (isPrintA && isPrintB) {
if (!isPrintOnlyA && isPrintOnlyB) {
return 1;
}
if (isPrintOnlyA && !isPrintOnlyB) {
return -1;
}
return a.localeCompare(b);
}
if (isPrintA) {
return 1;
}
if (isPrintB) {
return -1;
}
return null;
}
// ----------------------------------------
// Public
// ----------------------------------------
/**
* @param {Object} [configuration]
* @param {boolean} [configuration.unitlessMqAlwaysFirst]
* @returns {(function(string, string): number)|*}
*/
export default function createSort(configuration) {
const config = configuration || {};
const UNITLESS_MQ_ALWAYS_FIRST = config.unitlessMqAlwaysFirst;
/**
* Sorting an array with media queries
* according to the mobile-first methodology.
* @param {string} a
* @param {string} b
* @return {number} 1 / 0 / -1
*/
function sortCSSmq(a, b) {
const testIsPrint = _testIsPrint(a, b);
if (testIsPrint !== null) {
return testIsPrint;
}
const minA = isMinWidth(a) || isMinHeight(a);
const maxA = isMaxWidth(a) || isMaxHeight(a);
const minB = isMinWidth(b) || isMinHeight(b);
const maxB = isMaxWidth(b) || isMaxHeight(b);
if (UNITLESS_MQ_ALWAYS_FIRST && ((!minA && !maxA) || (!minB && !maxB))) {
if (!minA && !maxA && !minB && !maxB) {
return a.localeCompare(b);
}
return !minB && !maxB ? 1 : -1;
} else {
if (minA && maxB) {
return -1;
}
if (maxA && minB) {
return 1;
}
const lengthA = _getQueryLength(a);
const lengthB = _getQueryLength(b);
if (lengthA === maxValue && lengthB === maxValue) {
return a.localeCompare(b);
} else if (lengthA === maxValue) {
return 1;
} else if (lengthB === maxValue) {
return -1;
}
if (lengthA > lengthB) {
if (maxA) {
return -1;
}
return 1;
}
if (lengthA < lengthB) {
if (maxA) {
return 1;
}
return -1;
}
if (lengthA === lengthB) {
if (maxA && maxB) {
if (lessThan.test(a) && !lessThan.test(b)) {
return 1;
}
if (!lessThan.test(a) && lessThan.test(b)) {
return -1;
}
}
if (minA && minB) {
if (grtrThan.test(a) && !grtrThan.test(b)) {
return 1;
}
if (!grtrThan.test(a) && grtrThan.test(b)) {
return -1;
}
}
}
return a.localeCompare(b);
}
}
/**
* Sorting an array with media queries
* according to the desktop-first methodology.
* @param {string} a
* @param {string} b
* @return {number} 1 / 0 / -1
*/
sortCSSmq.desktopFirst = function (a, b) {
const testIsPrint = _testIsPrint(a, b);
if (testIsPrint !== null) {
return testIsPrint;
}
const minA = isMinWidth(a) || isMinHeight(a);
const maxA = isMaxWidth(a) || isMaxHeight(a);
const minB = isMinWidth(b) || isMinHeight(b);
const maxB = isMaxWidth(b) || isMaxHeight(b);
if (UNITLESS_MQ_ALWAYS_FIRST && ((!minA && !maxA) || (!minB && !maxB))) {
if (!minA && !maxA && !minB && !maxB) {
return a.localeCompare(b);
}
return !minB && !maxB ? 1 : -1;
} else {
if (minA && maxB) {
return 1;
}
if (maxA && minB) {
return -1;
}
const lengthA = _getQueryLength(a);
const lengthB = _getQueryLength(b);
if (lengthA === maxValue && lengthB === maxValue) {
return a.localeCompare(b);
} else if (lengthA === maxValue) {
return 1;
} else if (lengthB === maxValue) {
return -1;
}
if (lengthA > lengthB) {
if (maxA) {
return -1;
}
return 1;
}
if (lengthA < lengthB) {
if (maxA) {
return 1;
}
return -1;
}
if (lengthA === lengthB) {
if (maxA && maxB) {
if (lessThan.test(a) && !lessThan.test(b)) {
return 1;
}
if (!lessThan.test(a) && lessThan.test(b)) {
return -1;
}
}
if (minA && minB) {
if (grtrThan.test(a) && !grtrThan.test(b)) {
return 1;
}
if (!grtrThan.test(a) && grtrThan.test(b)) {
return -1;
}
}
}
return -a.localeCompare(b);
}
};
return sortCSSmq;
}