diff options
author | Martijn Cuppens <martijn.cuppens@gmail.com> | 2019-08-28 19:05:56 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-28 19:05:56 +0300 |
commit | 89f7a63ee4826b646ec659709cc170b8c45cddbc (patch) | |
tree | aab7f3991699ffe9f423a0599b9e1c75442e6d4d /postcss.js | |
parent | 7a15c1a4e41b1fd46b3f26be3529abc1e2d65a4d (diff) |
Support for every property (#144)
- Support for all properties
- Shorthand mixins for margins and paddings
- Support for custom properties
- Clearer way to declare `!important` rules: `@include rfs(1rem !important)` instead of `@include rfs(1rem, true)`
- Switched to mobile first approach, still possible to switch to the `max-width` media queries if needed
- Configuration variables are changed:
- Base font size -> Base value
- Font size unit -> Unit
- `responsive-font-size` property changed to `rfs()` function (see https://github.com/twbs/rfs/issues/116)
- Dropped `responsive-font-size` mixins
- Dropped Less 2 support since we had to work with lists
- Prevent generation of `/test/expected/main.css`
- Additional tests for new implementations
- Cleanup npm scripts examples
- Code examples in `README.md` are grouped by processor and collapsed
Diffstat (limited to 'postcss.js')
-rw-r--r-- | postcss.js | 275 |
1 files changed, 100 insertions, 175 deletions
@@ -1,7 +1,7 @@ /*! * PostCSS RFS plugin * - * Automated font-resizing + * Automated responsive values for for font sizes, paddings, margins and much more * * Licensed under MIT (https://github.com/twbs/rfs/blob/master/LICENSE) */ @@ -9,211 +9,136 @@ 'use strict'; const postcss = require('postcss'); +const RfsClass = require('./lib/rfs.js'); + +const DISABLE_RFS_SELECTOR = '.disable-rfs'; +const ENABLE_RFS_SELECTOR = '.enable-rfs'; module.exports = postcss.plugin('postcss-rfs', opts => { - const BREAKPOINT_ERROR = 'breakpoint option is invalid, it must be set in `px`, `rem` or `em`.'; - const BREAKPOINT_UNIT_ERROR = 'breakpointUnit option is invalid, it must be `px`, `rem` or `em`.'; - const BASE_FONT_SIZE_ERROR = 'baseFontSize option is invalid, it must be set in `px` or `rem`.'; - const DISABLE_RESPONSIVE_FONT_SIZE_SELECTOR = '.disable-responsive-font-size'; - const ENABLE_RESPONSIVE_FONT_SIZE_SELECTOR = '.enable-responsive-font-size'; - - const defaultOptions = { - baseFontSize: 20, - fontSizeUnit: 'rem', - breakpoint: '75rem', - breakpointUnit: 'px', - factor: 10, - twoDimensional: false, - unitPrecision: 5, - remValue: 16, - propList: ['responsive-font-size', 'rfs'], - class: false, - safariIframeResizeBugFix: false, - enableResponsiveFontSizes: true - }; + const rfs = new RfsClass(opts); - opts = Object.assign(defaultOptions, opts); - - if (typeof opts.baseFontSize !== 'number') { - if (opts.baseFontSize.endsWith('px')) { - opts.baseFontSize = parseFloat(opts.baseFontSize); - } else if (opts.baseFontSize.endsWith('rem')) { - opts.baseFontSize = parseFloat(opts.baseFontSize) / opts.remValue; - } else { - console.error(BASE_FONT_SIZE_ERROR); - } - } - - if (typeof opts.breakpoint !== 'number') { - if (opts.breakpoint.endsWith('px')) { - opts.breakpoint = parseFloat(opts.breakpoint); - } else if (opts.breakpoint.endsWith('em')) { - opts.breakpoint = parseFloat(opts.breakpoint) * opts.remValue; - } else { - console.error(BREAKPOINT_ERROR); - } - } + // Get the options merged with defaults + opts = rfs.getOptions(); + const mediaQuery = rfs.renderMediaQuery(); return css => { css.walkRules(rule => { - if (rule.selector.includes(DISABLE_RESPONSIVE_FONT_SIZE_SELECTOR)) { - return; - } - - rule.walkDecls(decl => { - // Skip if property is not in propList - if (!opts.propList.includes(decl.prop)) { - return; + const mediaQueryRules = []; + const extraBlocks = []; + const {parent} = rule; + let removeRule = false; + let dcRule; + let ecRule; + let ruleSelector = rule.selector; + + // Prepare rule to add to media query + if (opts.class === 'enable') { + const selectors = rule.selector.split(','); + let tempRuleSelector = ''; + + for (const selector of selectors) { + tempRuleSelector += `${ENABLE_RFS_SELECTOR} ${selector},\n`; + tempRuleSelector += `${selector + ENABLE_RFS_SELECTOR},\n`; } - // Set property to font-size - decl.prop = 'font-size'; - - // Skip if value is not in px or rem - if (isNaN(decl.value) && !new RegExp(/(\d*\.?\d+)(px|rem)/g).test(decl.value)) { - return; - } - - // Get the float value of the value - let value = parseFloat(decl.value); - - // Multiply by remValue if value is in rem - if (decl.value.includes('rem')) { - value *= opts.remValue; - } - - // Render value in desired unit - if (opts.fontSizeUnit === 'px') { - decl.value = `${toFixed(value, opts.unitPrecision)}px`; - } else if (opts.fontSizeUnit === 'rem') { - decl.value = `${toFixed(value / opts.remValue, opts.unitPrecision)}rem`; - } else { - console.error('fontSizeUnit option is not valid, it must be `px` or `rem`.'); - } + ruleSelector = tempRuleSelector.slice(0, -2); + } - // Only add media query if needed - if (opts.baseFontSize >= value || opts.factor <= 1 || !opts.enableResponsiveFontSizes) { - return; - } + const fluidRule = postcss.rule({ + selector: ruleSelector + }); - // Calculate font-size and font-size difference - let baseFontSize = opts.baseFontSize + ((value - opts.baseFontSize) / opts.factor); - const fontSizeDiff = value - baseFontSize; + // Disable classes + if (opts.class === 'disable') { + const selectors = rule.selector.split(','); + let ruleSelector = ''; - // Divide by remValue if needed - if (opts.fontSizeUnit === 'rem') { - baseFontSize /= opts.remValue; + for (const selector of selectors) { + ruleSelector += (opts.mode === 'max-media-query') ? `${selector},\n` : ''; + ruleSelector += `${DISABLE_RFS_SELECTOR} ${selector},\n`; + ruleSelector += `${selector + DISABLE_RFS_SELECTOR},\n`; } - const viewportUnit = opts.twoDimensional ? 'vmin' : 'vw'; - - value = `calc(${toFixed(baseFontSize, opts.unitPrecision)}${opts.fontSizeUnit} + ${toFixed(fontSizeDiff * 100 / opts.breakpoint, opts.unitPrecision)}${viewportUnit})`; - - const mediaQuery = postcss.atRule(renderMediaQuery(opts)); - let ruleSelector = rule.selector; - - // Prefix with .enable-responsive-font-size class if needed - if (opts.class === 'enable') { - const selectors = rule.selector.split(','); - let tempRuleSelector = ''; - - for (const selector of selectors) { - tempRuleSelector += `${ENABLE_RESPONSIVE_FONT_SIZE_SELECTOR} ${selector},\n`; - tempRuleSelector += `${selector + ENABLE_RESPONSIVE_FONT_SIZE_SELECTOR},\n`; - } - - ruleSelector = tempRuleSelector.slice(0, -2); - } + ruleSelector = ruleSelector.slice(0, -2); - const mediaQueryRule = postcss.rule({ + dcRule = postcss.rule({ selector: ruleSelector, source: rule.source }); + } - mediaQueryRule.append(decl.clone({value})); + rule.walkDecls(decl => { + // Check if the selector doesn't contain the disabled selector + // Check if value contains rfs() function + if (!rule.selector.includes(DISABLE_RFS_SELECTOR) && new RegExp(opts.functionName + '(.*)', 'g').test(decl.value)) { + const value = rfs.value(decl.value); + const fluidValue = rfs.fluidValue(decl.value); + decl.value = value; + + if (value !== fluidValue) { + const defaultValue = (opts.mode === 'min-media-query') ? ((opts.class === 'enable') ? value : fluidValue) : value; + const mediaQueryValue = (opts.mode === 'min-media-query') ? value : fluidValue; + decl.value = defaultValue; + + fluidRule.append(decl.clone({value: mediaQueryValue})); + mediaQueryRules.push(fluidRule); + + // Disable classes + if (opts.class === 'disable') { + const declOpts = (opts.mode === 'max-media-query') ? {} : {value}; + dcRule.append(decl.clone(declOpts)); + extraBlocks.push(dcRule); + } else if (opts.class === 'enable' && opts.mode === 'min-media-query') { + if (ecRule === undefined) { + ecRule = postcss.rule({ + selector: ruleSelector, + source: parent.source + }); + } + + ecRule.append(decl.clone({value: fluidValue})); + extraBlocks.push(ecRule); + } + + // Remove declaration if needed + if (opts.class === 'disable' && opts.mode === 'max-media-query') { + if (decl.prev() || decl.next()) { + decl.remove(); + } else { + removeRule = true; + } + } + } + } + }); + if (mediaQueryRules.length !== 0) { // Safari iframe resize bug: https://github.com/twbs/rfs/issues/14 if (opts.safariIframeResizeBugFix) { - mediaQueryRule.append(postcss.decl({ + rule.append({ prop: 'min-width', value: '0vw' - })); + }); } - mediaQuery.append(mediaQueryRule); - rule.parent.insertAfter(rule, mediaQuery.clone()); + const fluidMediaQuery = mediaQuery.clone(); - // Disable classes - if (opts.class === 'disable') { - const selectors = rule.selector.split(','); - let ruleSelector = ''; - - for (const selector of selectors) { - ruleSelector += `${selector},\n`; - ruleSelector += `${DISABLE_RESPONSIVE_FONT_SIZE_SELECTOR} ${selector},\n`; - ruleSelector += `${selector + DISABLE_RESPONSIVE_FONT_SIZE_SELECTOR},\n`; - } + mediaQueryRules.forEach(mediaQueryRule => { + fluidMediaQuery.append(mediaQueryRule); + }); - ruleSelector = ruleSelector.slice(0, -2); + parent.insertAfter(rule, fluidMediaQuery); - const dcRule = postcss.rule({ - selector: ruleSelector, - source: rule.source + if (extraBlocks.length > 0) { + extraBlocks.forEach(disableBlock => { + parent.insertAfter(rule, disableBlock); }); - - dcRule.append(decl.clone()); - rule.parent.insertAfter(rule, dcRule); - - if (decl.prev() || decl.next()) { - decl.remove(); - } else { - decl.parent.remove(); - } } - }); - }); - }; - function renderMediaQuery(opts) { - const mediaQuery = { - name: 'media' - }; - - switch (opts.breakpointUnit) { - case 'em': - case 'rem': { - const breakpoint = opts.breakpoint / opts.remValue; - - if (opts.twoDimensional) { - mediaQuery.params = `(max-width: ${breakpoint}${opts.breakpointUnit}), (max-height: ${breakpoint}${opts.breakpointUnit})`; - } else { - mediaQuery.params = `(max-width: ${breakpoint}${opts.breakpointUnit})`; + if (removeRule) { + rule.remove(); } - - break; } - - case 'px': - if (opts.twoDimensional) { - mediaQuery.params = `(max-width: ${opts.breakpoint}px), (max-height: ${opts.breakpoint}px)`; - } else { - mediaQuery.params = `(max-width: ${opts.breakpoint}px)`; - } - - break; - - default: - console.error(BREAKPOINT_UNIT_ERROR); - break; - } - - return mediaQuery; - } - - function toFixed(number, precision) { - const multiplier = Math.pow(10, precision + 1); - const wholeNumber = Math.floor(number * multiplier); - - return Math.round(wholeNumber / 10) * 10 / multiplier; - } + }); + }; }); |