UNPKG

postcss-include-media

Version:

PostCSS plugin to output @media definitions from include-media format.

259 lines (248 loc) 9.7 kB
'use strict'; // 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