Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/twbs/rfs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMartijn Cuppens <martijn.cuppens@gmail.com>2019-08-28 19:05:56 +0300
committerGitHub <noreply@github.com>2019-08-28 19:05:56 +0300
commit89f7a63ee4826b646ec659709cc170b8c45cddbc (patch)
treeaab7f3991699ffe9f423a0599b9e1c75442e6d4d /lib
parent7a15c1a4e41b1fd46b3f26be3529abc1e2d65a4d (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 'lib')
-rw-r--r--lib/rfs.js163
1 files changed, 163 insertions, 0 deletions
diff --git a/lib/rfs.js b/lib/rfs.js
new file mode 100644
index 0000000..3cae1ee
--- /dev/null
+++ b/lib/rfs.js
@@ -0,0 +1,163 @@
+const postcss = require('postcss');
+const valueParser = require('postcss-value-parser');
+
+const BREAKPOINT_ERROR = 'breakpoint option is invalid, it should be set in `px`, `rem` or `em`.';
+const BREAKPOINT_UNIT_ERROR = 'breakpointUnit option is invalid, it should be `px`, `rem` or `em`.';
+const BASE_RFS_ERROR = 'baseValue option is invalid, it should be set in `px` or `rem`.';
+
+module.exports = class {
+ constructor(opts) {
+ const defaultOptions = {
+ baseValue: 20,
+ unit: 'rem',
+ breakpoint: 1200,
+ breakpointUnit: 'px',
+ factor: 10,
+ twoDimensional: false,
+ unitPrecision: 5,
+ remValue: 16,
+ functionName: 'rfs',
+ enableRfs: true,
+ mode: 'min-media-query'
+ };
+
+ this.opts = Object.assign(defaultOptions, opts);
+
+ if (typeof this.opts.baseValue !== 'number') {
+ if (this.opts.baseValue.endsWith('px')) {
+ this.opts.baseValue = parseFloat(this.opts.baseValue);
+ } else if (this.opts.baseValue.endsWith('rem')) {
+ this.opts.baseValue = parseFloat(this.opts.baseValue) / this.opts.remValue;
+ } else {
+ console.error(BASE_RFS_ERROR);
+ }
+ }
+
+ if (typeof this.opts.breakpoint !== 'number') {
+ if (this.opts.breakpoint.endsWith('px')) {
+ this.opts.breakpoint = parseFloat(this.opts.breakpoint);
+ } else if (this.opts.breakpoint.endsWith('em')) {
+ this.opts.breakpoint = parseFloat(this.opts.breakpoint) * this.opts.remValue;
+ } else {
+ console.error(BREAKPOINT_ERROR);
+ }
+ }
+
+ if (!['px', 'rem', 'em'].includes(this.opts.breakpointUnit)) {
+ console.error(BREAKPOINT_UNIT_ERROR);
+ }
+ }
+
+ toFixed(number, precision) {
+ const multiplier = Math.pow(10, precision + 1);
+ const wholeNumber = Math.floor(number * multiplier);
+
+ return Math.round(wholeNumber / 10) * 10 / multiplier;
+ }
+
+ renderValue(value) {
+ // Do not add unit if value is 0
+ if (value === 0) {
+ return value;
+ }
+
+ // Render value in desired unit
+ if (this.opts.unit === 'rem') {
+ return `${this.toFixed(value / this.opts.remValue, this.opts.unitPrecision)}rem`;
+ }
+
+ return `${this.toFixed(value, this.opts.unitPrecision)}px`;
+ }
+
+ process(declarationValue, fluid) {
+ const parsed = valueParser(declarationValue);
+
+ // Function walk() will visit all the of the nodes in the tree,
+ // invoking the callback for each.
+ parsed.walk(node => {
+ // Since we only want to transform rfs() values,
+ // we can ignore anything else.
+ if (node.type !== 'function' && node.value !== this.opts.functionName) {
+ return;
+ }
+
+ node.nodes.filter(node => node.type === 'word').forEach(node => {
+ node.value = node.value.replace(/^(-?\d*\.?\d+)(.*)/g, (match, value, unit) => {
+ value = parseFloat(value);
+
+ // Return value if it's not a number or px/rem value
+ if (isNaN(value) || !['px', 'rem'].includes(unit)) {
+ return match;
+ }
+
+ // Multiply by remValue if value is in rem
+ if (unit === 'rem') {
+ value *= this.opts.remValue;
+ }
+
+ // Only add responsive function if needed
+ if (!fluid || this.opts.baseValue >= Math.abs(value) || this.opts.factor <= 1 || !this.opts.enableRfs) {
+ return this.renderValue(value);
+ }
+
+ // Calculate base and difference
+ let baseValue = this.opts.baseValue + ((Math.abs(value) - this.opts.baseValue) / this.opts.factor);
+ const diff = Math.abs(value) - baseValue;
+
+ // Divide by remValue if needed
+ if (this.opts.unit === 'rem') {
+ baseValue /= this.opts.remValue;
+ }
+
+ const viewportUnit = this.opts.twoDimensional ? 'vmin' : 'vw';
+ if (value > 0) {
+ return `calc(${this.toFixed(baseValue, this.opts.unitPrecision)}${this.opts.unit} + ${this.toFixed(diff * 100 / this.opts.breakpoint, this.opts.unitPrecision)}${viewportUnit})`;
+ }
+
+ return `calc(-${this.toFixed(baseValue, this.opts.unitPrecision)}${this.opts.unit} - ${this.toFixed(diff * 100 / this.opts.breakpoint, this.opts.unitPrecision)}${viewportUnit})`;
+ });
+ });
+
+ // Now we will transform the existing rgba() function node
+ // into a word node with the hex value
+ node.type = 'word';
+ node.value = valueParser.stringify(node.nodes);
+ });
+
+ return parsed.toString();
+ }
+
+ // Return the value without `rfs()` function
+ // eg. `4px rfs(32px)` => `.25rem 2rem`
+ value(value) {
+ return this.process(value, false);
+ }
+
+ // Convert `rfs()` function to fluid css
+ // eg. `4px rfs(32px)` => `.25rem calc(1.325rem + 0.9vw)`
+ fluidValue(value) {
+ return this.process(value, true);
+ }
+
+ renderMediaQuery() {
+ const mediaQuery = {
+ name: 'media'
+ };
+
+ const dimPrefix = (this.opts.mode === 'min-media-query') ? 'min' : 'max';
+ const breakpoint = (this.opts.breakpointUnit === 'px') ? this.opts.breakpoint : this.opts.breakpoint / this.opts.remValue;
+
+ if (this.opts.twoDimensional) {
+ mediaQuery.params = `(${dimPrefix}-width: ${breakpoint}${this.opts.breakpointUnit}), (${dimPrefix}-height: ${breakpoint}${this.opts.breakpointUnit})`;
+ } else {
+ mediaQuery.params = `(${dimPrefix}-width: ${breakpoint}${this.opts.breakpointUnit})`;
+ }
+
+ return postcss.atRule(mediaQuery);
+ }
+
+ getOptions() {
+ return this.opts;
+ }
+};
+