postcss-include-media
Version:
PostCSS plugin to output @media definitions from include-media format.
259 lines (248 loc) • 9.7 kB
JavaScript
// TODO write validation to confirm structure of required breakpoints.
/**
* Function to validate media expression shape
*
* @param {BreakpointType} breakpoints - object of breakpoints definitions
* @example { sm: '350px', md: '600px', };
*
* @returns {boolean}
*/
var validBreakpoints = function (breakpoints) { return Object.values(breakpoints).every(function (value) { return typeof value === 'string'; }); };
/**
* Function to validate media expression shape
*
* @param {MediaExpressionType} expressions - object of expressions definitions
* @example { screen: 'screen', print: 'print' };
*
* @returns {boolean}
*/
var validMediaExpressions = function (expressions) { return Object.values(expressions).every(function (value) { return typeof value === 'string'; }); };
/**
* Function to validate media expression shape
*
* @param {UnitIntervalType} unitIntervals - object of unitIntervals definitions
* @example { px: 1, em: 0.01 };
*
* @returns {boolean}
*/
var validUnitIntervals = function (unitIntervals) { return Object.values(unitIntervals).every(function (value) { return Number(value) === value; }); };
/**
* Function to validate include-media rule name
*
* @param {string} name - name of rule
* @example 'includeMedia';
*
* @returns {boolean}
*/
var validRuleName = function (name) { return typeof name === 'string'; };
/**
* Get dimension of an atRule params, based on a found operator
*
* @param {string} atRuleParams - Expression to extract dimension from
* @param {string} operator - Operator from `params`
*
* @return {string} - `width` or `height` (or potentially anything else)
*/
var getRuleDimension = function (atRuleParams, operator) {
var operatorIndex = atRuleParams.indexOf(operator);
var parsedDimension = atRuleParams.slice(1, operatorIndex - 1);
var dimension = 'width';
if (parsedDimension.length > 0) {
return parsedDimension;
}
return dimension;
};
var UNITS = ['px', 'cm', 'mm', '%', 'ch', 'pc', 'in', 'em', 'rem', 'pt', 'ex', 'vw', 'vh', 'vmin', 'vmax'];
var OPERATORS = ['>=', '>', '<=', '<', '≥', '≤'];
/**
* Creates a list of static expressions or media types
*
* @example - Creates a single media type (screen)
* $media-expressions: ('screen': 'screen');
*
* @example - Creates a static expression with logical disjunction (OR operator)
* const media-expressions = {
* 'retina2x': '(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)'
* };
*/
var defaultMediaExpressions = {
screen: 'screen',
print: 'print',
all: 'all',
handheld: 'handheld',
landscape: '(orientation: landscape)',
portrait: '(orientation: portrait)',
retina2x: '(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx)',
retina3x: '(-webkit-min-device-pixel-ratio: 3), (min-resolution: 350dpi), (min-resolution: 3dppx)',
};
/**
* Defines a number to be added or subtracted from each unit when declaring breakpoints with exclusive intervals
* @example - Interval for pixels is defined as `1` by default
* @include-media('>128px') {}
*
* Generates:
* @media (min-width: 129px) {}
*/
var defaultUnitIntervals = {
px: 1,
em: 0.01,
rem: 0.1,
'': 0,
};
/**
* Default breakpoints used in the original Sass include-media
*/
var defaultBreakpoints = {
phone: '320px',
tablet: '768px',
desktop: '1024px',
};
/**
* Used to caption the final units
* @param {string} breakpoint - the found breakpoint value
*
* @returns {string}
*/
var getUnitFromBreakpoint = function (breakpoint) {
var reg = new RegExp('([A-z%]+)');
var match = breakpoint.match(reg) || [''];
var unit = match[0];
return UNITS.includes(unit) ? unit : '';
};
/**
* Used to return the matching breakpoint value. OR returns the value after the operator
*
* @param {string} atRuleParams the raw atRule.params
* @param {string} operator the operator used to compare the trailing value
* @param {object} breakpoints - object of breakpoints definitions
*
* @returns {string}
*/
var captureBreakpoint = function (atRuleParams, operator, breakpoints) {
var operatorIndex = atRuleParams.indexOf(operator);
var rawValue = atRuleParams.slice(operatorIndex + operator.length).replace(/['"()]/g, '');
if (breakpoints[rawValue]) {
return breakpoints[rawValue];
}
return rawValue;
};
/**
* Get value of an expression, based on a found operator
*
* @param {string} breakpoint - the found breakpoint value
* @param {string} operator - Operator found in the `atRule.params`
* @param {UnitIntervalType} unitIntervals - The unit intervals to calculate the steps if > or <
*
* @return {number} - A numeric value
*/
var getRuleValue = function (breakpoint, operator, unitIntervals) {
var parsedValue = parseFloat(breakpoint);
var unit = getUnitFromBreakpoint(breakpoint);
var unitInterval = unitIntervals[unit];
if (operator === '>') {
return parsedValue + unitInterval;
}
else if (operator === '<') {
return parsedValue - unitInterval;
}
return parsedValue;
};
var captureOperator = function (string) {
var reg = new RegExp("([".concat(OPERATORS.join(''), "]+)"));
var actions = string.match(reg) || [''];
return actions[0];
};
var getMinMax = function (action) {
switch (action) {
case '>':
case '>=':
case '≥':
return 'min';
case '<':
case '<=':
case '≤':
return 'max';
default:
return '';
}
};
/**
* Used to return the matching breakpoint value. OR returns the value after the operator
*
* @param {string} atRuleParams the raw atRule.params
* @param {MediaExpressionType} mediaExpressions - object of mediaExpressions definitions
*
* @returns {string}
*/
var captureMediaExpression = function (atRuleParams, mediaExpressions) {
var cleanedParams = atRuleParams.replace(/['"()]/g, '');
if (mediaExpressions[cleanedParams]) {
return mediaExpressions[cleanedParams];
}
return '';
};
var AT_RULE_NAME = 'include-media';
var includeMediaPlugin = function (opts) {
if (opts === void 0) { opts = {}; }
var breakpoints = opts.breakpoints || defaultBreakpoints;
var mediaExpressions = opts.mediaExpressions || defaultMediaExpressions;
var unitIntervals = opts.unitIntervals || defaultUnitIntervals;
var ruleName = opts.ruleName || AT_RULE_NAME;
if (!validRuleName(ruleName)) {
throw new Error('Rule name must be a string.');
}
if (!validBreakpoints(breakpoints)) {
throw new Error('Breakpoints are not the valid structure, must be { key: String }');
}
if (!validMediaExpressions(mediaExpressions)) {
throw new Error('Media expressions are not the valid structure, must be { key: String }');
}
if (!validUnitIntervals(unitIntervals)) {
throw new Error('Unit Intervals are not the valid structure, must be { key: Number }');
}
return {
postcssPlugin: 'postcss-include-media',
Root: function (root, _a) {
var result = _a.result;
root.walkAtRules(function (atRule) {
var hasSpaceInName = ruleName.includes(' ');
var realName = hasSpaceInName ? ruleName.split(' ')[0] : ruleName;
var stripPreParams = new RegExp('^[^()]+', 'gm');
if (atRule.name === realName) {
// trim part of params in case of spaces between atRule and params (e.g. @include media (...) )
atRule.params = hasSpaceInName ? atRule.params.replace(stripPreParams, '') : atRule.params;
var newParams = "".concat(atRule.params).split(',').map(function (params) {
var matchedMediaExpression = captureMediaExpression(params, mediaExpressions);
if (matchedMediaExpression) {
return "".concat(matchedMediaExpression);
}
var operator = captureOperator(params);
var minMax = getMinMax(operator);
if (minMax === '') {
result.warn("You have not defined an operator \"".concat(operator, "\" for @include-media"));
return "".concat(params);
}
var dimension = getRuleDimension(params, operator);
var breakpoint = captureBreakpoint(params, operator, breakpoints);
if (isNaN(parseFloat(breakpoint))) {
result.warn("Not a valid breakpoint of \"".concat(breakpoint, "\" given to @include-media"));
return "(".concat(minMax, "-").concat(dimension, ": ").concat(breakpoint, ")");
}
var unitMeasure = getUnitFromBreakpoint(breakpoint);
var value = getRuleValue(breakpoint, operator, unitIntervals);
return "(".concat(minMax, "-").concat(dimension, ": ").concat(value).concat(unitMeasure, ")");
});
var newAtRule = atRule.clone();
newAtRule.params = newParams.join(' and '); // TODO make configurable
newAtRule.name = 'media';
newAtRule.raws.afterName = ' '; // adds space between rule and query.
atRule.replaceWith(newAtRule);
}
});
},
};
};
includeMediaPlugin.postcss = true;
module.exports = includeMediaPlugin;
//# sourceMappingURL=index.cjs.js.map
;