├── .editorconfig ├── .gitignore ├── .travis.yml ├── compositor.json ├── index.js ├── lib ├── contains-immutable-prefix.js ├── contains-mutation-from-source.js ├── get-css-classes-from-ast.js ├── get-mutations.js ├── get-warning-string.js └── has-mutation.js ├── license ├── media └── logo.png ├── package.json ├── readme.md └── test ├── fixtures ├── app.css ├── basscss-mutations.css ├── basscss.css ├── bootstrap-mutations.css ├── bootstrap.css └── vendor.css ├── get-css-classes-test.js ├── get-mutations-test.js └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - '4' 5 | - '0.12' 6 | -------------------------------------------------------------------------------- /compositor.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "johnotander/immutable-css", 3 | "version": "0.1.3", 4 | "libraries": { 5 | "xv": "^1.1.2" 6 | }, 7 | "title": "", 8 | "branch": "", 9 | "style": { 10 | "name": "Default", 11 | "fontFamily": "-apple-system, BlinkMacSystemFont, sans-serif", 12 | "fontWeight": 400, 13 | "bold": 600, 14 | "lineHeight": 1.5, 15 | "typeScale": [ 16 | 72, 17 | 48, 18 | 24, 19 | 20, 20 | 16, 21 | 14, 22 | 12 23 | ], 24 | "heading": { 25 | "fontFamily": null, 26 | "fontStyle": null, 27 | "fontWeight": 600, 28 | "lineHeight": 1.25, 29 | "textTransform": null, 30 | "letterSpacing": null, 31 | "h0": {}, 32 | "h1": {}, 33 | "h2": {}, 34 | "h3": {}, 35 | "h4": {}, 36 | "h5": {}, 37 | "h6": {} 38 | }, 39 | "alternativeText": {}, 40 | "space": [ 41 | 0, 42 | 8, 43 | 16, 44 | 32, 45 | 48, 46 | 64, 47 | 96 48 | ], 49 | "layout": { 50 | "maxWidth": 1024, 51 | "centered": false 52 | }, 53 | "colors": { 54 | "text": "#111", 55 | "background": "#fff", 56 | "inverted": "#fff", 57 | "primary": "#08e", 58 | "secondary": "#0e8", 59 | "highlight": "#e08", 60 | "border": "#ddd", 61 | "muted": "#eee" 62 | }, 63 | "border": { 64 | "width": 1, 65 | "radius": 2 66 | }, 67 | "link": {}, 68 | "button": { 69 | "hover": { 70 | "boxShadow": "inset 0 0 0 999px rgba(0, 0, 0, .125)" 71 | } 72 | }, 73 | "input": {}, 74 | "body": { 75 | "margin": 0 76 | } 77 | }, 78 | "content": [ 79 | { 80 | "component": "nav/AbsoluteNav", 81 | "links": [ 82 | { 83 | "href": "http://immutablecss.com", 84 | "text": "Home" 85 | }, 86 | { 87 | "href": "https://github.com/johnotander/immutable-css", 88 | "text": "GitHub" 89 | }, 90 | { 91 | "href": "https://npmjs.com/package/immutable-css", 92 | "text": "npm" 93 | } 94 | ] 95 | }, 96 | { 97 | "component": "header/BannerHeader", 98 | "heading": "immutable-css", 99 | "subhead": "A CSS linter for immutable selectors.", 100 | "links": [ 101 | { 102 | "text": "Tweet", 103 | "href": "https://twitter.com/intent/tweet?text=immutable-css%253A%2520A%2520CSS%2520linter%2520for%2520immutable%2520selectors.&url=http%253A%252F%252Fimmutablecss.com" 104 | } 105 | ], 106 | "text": "v1.1.2" 107 | }, 108 | { 109 | "component": "article/BasicArticle", 110 | "text": "

<h1 align="center">\n <img width="360" src="https://rawgit.com/johnotander/immutable-css/master/media/logo.png" alt="immutable-css">\n</h1>\n\n

\n

\"Build \"js-standard-style\"

\n

Best practices suggest avoiding overriding styles from vendor libraries to prevent unwanted side effects. Base library styles should not be altered – or as Harry Roberts describes, base styles should be treated as Immutable CSS.

\n

See the interactive web app.

\n

Installation

\n
npm install --save immutable-css\n
\n

Usage

\n

immutableCss.processFiles(immutableSourceCss, customCss, options) takes two stylesheet paths, and ensures the custom CSS doesn't override any selectors contained within the immutable source.\nThis is typically best when comparing vendor CSS (Bootstrap, Tachyons, Basscss, etc.) to your app's customizations.

\n
var immutableCss = require('immutable-css')\n\nimmutableCss.processFiles('css/vendor.css', 'css/app.css')\n// => [...]\n
\n

immutableCss.processGlob(cssGlob, options) takes a glob that matches CSS files and ensures that no stylesheet overrides selectors contained within another.\nThis is useful to ensure that CSS partials aren't mixing concerns by mutating selectors contained within another file.

\n
var immutableCss = require('immutable-css')\n\nimmutableCss.processGlob('src/css/**/*.css', { verbose: true })\n
\n

Using with PostCSS

\n

Immutable CSS detects mutations among files by leveraging PostCSS sourcemaps. It is also best used as a PostCSS plugin in tandem with postcss-import and postcss-reporter.

\n
var fs = require('fs')\nvar postcss = require('postcss')\nvar import = require('postcss-import')\nvar reporter = require('postcss-reporter')\nvar immutableCss = require('immutable-css')\n\nvar css = fs.readFileSync('styles.css', 'utf8')\n\nvar mutations = postcss([import(), immutableCss(), reporter()])\n                  .process(css, { from: 'styles.css' })\n
\n

Using with Gulp

\n
var gulp = require('gulp')\nvar postcss = require('gulp-postcss')\nvar import = require('postcss-import')\nvar reporter = require('postcss-reporter')\nvar immutableCss = require('immutable-css')\n\ngulp.task('immutable', function () {\n  var processors = [\n    import,\n    immutableCss,\n    // If you want Immutable CSS to halt the gulp pipline if there are any warnings\n    // then set throwError to true\n    reporter({clearMessages: true, throwError: false})\n  ]\n\n  gulp.src('assets/css/base.css')\n    .pipe(postcss(processors))\n    .pipe(gulp.dest('dist/css'))\n})\n
\n

Input

\n
@import 'basscss';\n\n.button {}\n.left {}\n.something-else {}\n
\n

Output

\n
⚠  .button was mutated 2 times\n[line 93, col 1]: /css/basscss.css\n[line 3, col 1]: /css/custom.css\n[immutable-css]\n⚠  .left was mutated 2 times\n[line 291, col 1]: /css/basscss.css\n[line 4, col 1]: /css/custom.css\n[immutable-css]\n
\n

Options

\n\n

Using the callback

\n

Immutable CSS accepts an optional callback, which returns the mutations hash. The key is the mutated class name, the value is an array of mutating filenames.

\n
postcss([\n  import(),\n  immutableCss({ ignoredClasses: ['.button'] }, function(mutations) {\n    console.log(mutations)\n    // => { '.foobar': [] }\n  })\n]).process(css, { from: cssFile })\n
\n

Using the immutable-css-cli

\n
npm i -g immutable-css-cli\n
\n
immutable-css css/main.css\n⚠  .button was mutated 2 times\n[line 93, col 1]: /css/_basscss.css\n[line 11, col 1]: /css/_custom.css\n[immutable-css]\n⚠  .left was mutated 2 times\n[line 291, col 1]: /css/_basscss.css\n[line 15, col 1]: /css/_custom.css\n[immutable-css]\n
\n

https://github.com/johnotander/immutable-css-cli

\n

Dependencies

\n\n

Related Reading

\n\n

License

\n

MIT

\n

Contributing

\n
    \n
  1. Fork it
  2. \n
  3. Create your feature branch (git checkout -b my-new-feature)
  4. \n
  5. Commit your changes (git commit -am 'Add some feature')
  6. \n
  7. Push to the branch (git push origin my-new-feature)
  8. \n
  9. Create new Pull Request
  10. \n
\n

Crafted with <3 by @jxnblk & @4lpine.

\n
\n
\n

This package was initially generated with yeoman and the p generator.

\n
\n", 111 | "html": "

<h1 align="center">\n <img width="360" src="https://rawgit.com/johnotander/immutable-css/master/media/logo.png" alt="immutable-css">\n</h1>\n\n

\n

\"Build \"js-standard-style\"

\n

Best practices suggest avoiding overriding styles from vendor libraries to prevent unwanted side effects. Base library styles should not be altered – or as Harry Roberts describes, base styles should be treated as Immutable CSS.

\n

See the interactive web app.

\n

Installation

\n
npm install --save immutable-css\n
\n

Usage

\n

immutableCss.processFiles(immutableSourceCss, customCss, options) takes two stylesheet paths, and ensures the custom CSS doesn't override any selectors contained within the immutable source.\nThis is typically best when comparing vendor CSS (Bootstrap, Tachyons, Basscss, etc.) to your app's customizations.

\n
var immutableCss = require('immutable-css')\n\nimmutableCss.processFiles('css/vendor.css', 'css/app.css')\n// => [...]\n
\n

immutableCss.processGlob(cssGlob, options) takes a glob that matches CSS files and ensures that no stylesheet overrides selectors contained within another.\nThis is useful to ensure that CSS partials aren't mixing concerns by mutating selectors contained within another file.

\n
var immutableCss = require('immutable-css')\n\nimmutableCss.processGlob('src/css/**/*.css', { verbose: true })\n
\n

Using with PostCSS

\n

Immutable CSS detects mutations among files by leveraging PostCSS sourcemaps. It is also best used as a PostCSS plugin in tandem with postcss-import and postcss-reporter.

\n
var fs = require('fs')\nvar postcss = require('postcss')\nvar import = require('postcss-import')\nvar reporter = require('postcss-reporter')\nvar immutableCss = require('immutable-css')\n\nvar css = fs.readFileSync('styles.css', 'utf8')\n\nvar mutations = postcss([import(), immutableCss(), reporter()])\n                  .process(css, { from: 'styles.css' })\n
\n

Using with Gulp

\n
var gulp = require('gulp')\nvar postcss = require('gulp-postcss')\nvar import = require('postcss-import')\nvar reporter = require('postcss-reporter')\nvar immutableCss = require('immutable-css')\n\ngulp.task('immutable', function () {\n  var processors = [\n    import,\n    immutableCss,\n    // If you want Immutable CSS to halt the gulp pipline if there are any warnings\n    // then set throwError to true\n    reporter({clearMessages: true, throwError: false})\n  ]\n\n  gulp.src('assets/css/base.css')\n    .pipe(postcss(processors))\n    .pipe(gulp.dest('dist/css'))\n})\n
\n

Input

\n
@import 'basscss';\n\n.button {}\n.left {}\n.something-else {}\n
\n

Output

\n
⚠  .button was mutated 2 times\n[line 93, col 1]: /css/basscss.css\n[line 3, col 1]: /css/custom.css\n[immutable-css]\n⚠  .left was mutated 2 times\n[line 291, col 1]: /css/basscss.css\n[line 4, col 1]: /css/custom.css\n[immutable-css]\n
\n

Options

\n\n

Using the callback

\n

Immutable CSS accepts an optional callback, which returns the mutations hash. The key is the mutated class name, the value is an array of mutating filenames.

\n
postcss([\n  import(),\n  immutableCss({ ignoredClasses: ['.button'] }, function(mutations) {\n    console.log(mutations)\n    // => { '.foobar': [] }\n  })\n]).process(css, { from: cssFile })\n
\n

Using the immutable-css-cli

\n
npm i -g immutable-css-cli\n
\n
immutable-css css/main.css\n⚠  .button was mutated 2 times\n[line 93, col 1]: /css/_basscss.css\n[line 11, col 1]: /css/_custom.css\n[immutable-css]\n⚠  .left was mutated 2 times\n[line 291, col 1]: /css/_basscss.css\n[line 15, col 1]: /css/_custom.css\n[immutable-css]\n
\n

https://github.com/johnotander/immutable-css-cli

\n

Dependencies

\n\n

Related Reading

\n\n

License

\n

MIT

\n

Contributing

\n
    \n
  1. Fork it
  2. \n
  3. Create your feature branch (git checkout -b my-new-feature)
  4. \n
  5. Commit your changes (git commit -am 'Add some feature')
  6. \n
  7. Push to the branch (git push origin my-new-feature)
  8. \n
  9. Create new Pull Request
  10. \n
\n

Crafted with <3 by @jxnblk & @4lpine.

\n
\n
\n

This package was initially generated with yeoman and the p generator.

\n
\n" 112 | }, 113 | { 114 | "component": "footer/BasicFooter", 115 | "links": [ 116 | { 117 | "href": "https://github.com/johnotander/immutable-css", 118 | "text": "GitHub" 119 | }, 120 | { 121 | "href": "https://github.com/johnotander", 122 | "text": "johnotander" 123 | } 124 | ] 125 | } 126 | ] 127 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var glob = require('glob') 5 | var isCss = require('is-css') 6 | var postcss = require('postcss') 7 | var fileExists = require('file-exists') 8 | var getCssClasses = require('get-css-classes') 9 | var extendOptions = require('extend-options') 10 | 11 | var hasMutation = require('./lib/has-mutation') 12 | var getWarningString = require('./lib/get-warning-string') 13 | var containsMutationFromSource = require('./lib/contains-mutation-from-source') 14 | 15 | var immutableCss = postcss.plugin('immutable-css', function (opts, cb) { 16 | return function immutableCss (root, result) { 17 | var mutationsMap = {} 18 | 19 | if (typeof opts === 'function') { 20 | cb = opts 21 | opts = {} 22 | } 23 | 24 | cb = cb || function () {} 25 | opts = extendOptions({ 26 | immutableClasses: [], 27 | immutablePrefixes: [], 28 | ignoredClasses: [], 29 | strict: false 30 | }, opts || {}) 31 | 32 | root.walkRules(function (rule) { 33 | rule.selectors.forEach(function (selector) { 34 | getCssClasses(selector).forEach(function (klass) { 35 | mutationsMap[klass] = mutationsMap[klass] || [] 36 | 37 | var klassSource = rule.source.input.from 38 | var klassLine = rule.source.start.line 39 | var klassColumn = rule.source.start.column 40 | 41 | if (containsMutationFromSource(klassSource, mutationsMap[klass]) && !opts.strict) { 42 | return 43 | } 44 | 45 | mutationsMap[klass].push({ 46 | selector: klass, 47 | line: klassLine, 48 | column: klassColumn, 49 | rule: rule 50 | }) 51 | }) 52 | }) 53 | }) 54 | 55 | Object.keys(mutationsMap).forEach(function (mutationClass) { 56 | if (hasMutation(mutationClass, mutationsMap, opts)) { 57 | result.warn(getWarningString(mutationsMap[mutationClass])) 58 | } else { 59 | delete mutationsMap[mutationClass] 60 | } 61 | }) 62 | 63 | cb(mutationsMap) 64 | } 65 | }) 66 | 67 | module.exports = immutableCss 68 | 69 | module.exports.processFiles = function processFiles (immutableCssFile, customCssFile, options) { 70 | if (isValidFile(immutableCssFile) && isValidFile(customCssFile)) { 71 | var immutableCssSrc = fs.readFileSync(immutableCssFile, 'utf8') 72 | var customCssSrc = fs.readFileSync(customCssFile, 'utf8') 73 | 74 | // Concatenate the files with source maps. 75 | var immutableRoot = postcss.parse(immutableCssSrc, { from: immutableCssFile }) 76 | var customRoot = postcss.parse(customCssSrc, { from: customCssFile }) 77 | immutableRoot.append(customRoot) 78 | 79 | return immutableCss.process(immutableRoot, options).messages 80 | } else { 81 | console.error('immutable-css expected two CSS files') 82 | } 83 | } 84 | 85 | module.exports.processGlob = function processGlob (cssGlob, options) { 86 | var files = glob.sync(cssGlob) 87 | var root = null 88 | 89 | files.forEach(function (file) { 90 | if (isValidFile(file)) { 91 | var css = fs.readFileSync(file, 'utf8') 92 | var newRoot = postcss.parse(css, { from: file }) 93 | 94 | if (root) { 95 | root.append(newRoot) 96 | } else { 97 | root = newRoot 98 | } 99 | } else { 100 | console.error(file + ' is an invalid file') 101 | } 102 | }) 103 | 104 | return immutableCss.process(root, options).messages 105 | } 106 | 107 | function isValidFile (file) { 108 | return isCss(file) && fileExists(file) 109 | } 110 | -------------------------------------------------------------------------------- /lib/contains-immutable-prefix.js: -------------------------------------------------------------------------------- 1 | module.exports = function containsImmutablePrefix (mutationClass, opts) { 2 | return opts.immutablePrefixes.some(function (prefix) { 3 | return prefix.test(mutationClass) 4 | }) 5 | } 6 | -------------------------------------------------------------------------------- /lib/contains-mutation-from-source.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function containsMutationFromSource (source, mutations) { 4 | return mutations.some(function (mutation) { 5 | return mutation.rule.source.input.from === source 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /lib/get-css-classes-from-ast.js: -------------------------------------------------------------------------------- 1 | var getCssClasses = require('get-css-classes') 2 | 3 | module.exports = function getCssClassesFromAst (cssObject) { 4 | cssClasses = {} 5 | 6 | cssObject.walkRules(function (rule) { 7 | rule.selectors.forEach(function (selector) { 8 | getCssClasses(selector, { keepPseudos: true }).forEach(function (cssClass) { 9 | cssClasses[cssClass] = true 10 | }) 11 | }) 12 | }) 13 | 14 | return cssClasses 15 | } 16 | -------------------------------------------------------------------------------- /lib/get-mutations.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This module is no longer used for any internal immutable-css functionality 4 | // however, it is currently still being used for the immutablecss.com web app. 5 | // So, we will leave this in for now. 6 | 7 | var postcss = require('postcss') 8 | var getCssClasses = require('get-css-classes') 9 | var getCssClassesFromAst = require('./get-css-classes-from-ast') 10 | 11 | var ignoredSelectors, immutableSelectors, immutableClasses 12 | 13 | module.exports = function getMutations (immutableCss, customCss, options) { 14 | options = options || {} 15 | ignoredSelectors = options.ignoredSelectors || [] 16 | immutableSelectors = options.immutableSelectors || [] 17 | 18 | var immutableAst = postcss.parse(immutableCss) 19 | var customAst = postcss.parse(customCss) 20 | 21 | immutableClasses = getCssClassesFromAst(immutableAst) 22 | 23 | var immutableErrors = [] 24 | customAst.walkRules(function (rule) { 25 | rule.selectors.forEach(function (selector) { 26 | getCssClasses(selector).forEach(function (classSelector) { 27 | if (hasMutation(classSelector)) { 28 | immutableErrors.push({ 29 | selector: classSelector, 30 | line: rule.source.start.line, 31 | column: rule.source.start.column, 32 | rule: rule 33 | }) 34 | } 35 | }) 36 | }) 37 | }) 38 | 39 | return immutableErrors 40 | } 41 | 42 | function hasMutation (classSelector) { 43 | return (immutableClasses[classSelector] || immutableSelectors.indexOf(classSelector) != -1) && 44 | ignoredSelectors.indexOf(classSelector) == -1 45 | } 46 | -------------------------------------------------------------------------------- /lib/get-warning-string.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function getWarningString (mutations) { 4 | var warning = mutations[0].selector + ' was mutated ' + mutations.length + ' times\n' 5 | 6 | mutations.forEach(function (mutation) { 7 | warning += '[line ' + mutation.line + ', col ' + mutation.column + ']: ' + mutation.rule.source.input.from + '\n' 8 | }) 9 | 10 | return warning 11 | } 12 | -------------------------------------------------------------------------------- /lib/has-mutation.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var containsImmutablePrefix = require('./contains-immutable-prefix') 4 | 5 | module.exports = function hasMutation (mutationClass, mutationsMap, opts) { 6 | return (mutationsMap[mutationClass].length > 1 || // class has been seen 7 | opts.immutableClasses.indexOf(mutationClass) !== -1 || // is contained in the immutable classes array 8 | containsImmutablePrefix(mutationClass, opts)) && // is an immutable prefix 9 | opts.ignoredClasses.indexOf(mutationClass) === -1 // is not in the ignored classes array 10 | } 11 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) John Otander (johnotander.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johno/immutable-css/1179af19d5b2509992c8be5ee86bed3ef9470560/media/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "immutable-css", 3 | "description": "A CSS linter for immutable selectors.", 4 | "author": "John Otander", 5 | "version": "1.1.2", 6 | "main": "index.js", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "mocha test" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/johnotander/immutable-css.git" 16 | }, 17 | "keywords": [ 18 | "css", 19 | "immutable", 20 | "mutability", 21 | "immutablecss", 22 | "immutability", 23 | "mutation", 24 | "lint", 25 | "linter" 26 | ], 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/johnotander/immutable-css/issues" 30 | }, 31 | "homepage": "https://github.com/johnotander/immutable-css", 32 | "dependencies": { 33 | "extend-options": "0.0.1", 34 | "file-exists": "^0.1.1", 35 | "get-css-classes": "1.1.0", 36 | "glob": "^5.0.14", 37 | "has-class-selector": "1.0.0", 38 | "is-css": "^1.0.0", 39 | "meow": "^3.3.0", 40 | "postcss": "^5.0.10", 41 | "specificity": "^0.1.4" 42 | }, 43 | "devDependencies": { 44 | "mocha": "*", 45 | "postcss-import": "^7.1.0", 46 | "postcss-reporter": "^1.3.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | immutable-css 3 |

4 | 5 | [![Build Status](https://secure.travis-ci.org/johnotander/immutable-css.png?branch=master)](https://travis-ci.org/johnotander/immutable-css) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 6 | 7 | Best practices suggest avoiding overriding styles from vendor libraries to prevent unwanted side effects. Base library styles should not be altered – or as Harry Roberts describes, base styles should be treated as [Immutable CSS](http://csswizardry.com/2015/03/immutable-css/). 8 | 9 | See the interactive [web app](http://immutablecss.com). 10 | 11 | ## Installation 12 | 13 | ```bash 14 | npm install --save immutable-css 15 | ``` 16 | 17 | ## Usage 18 | 19 | `immutableCss.processFiles(immutableSourceCss, customCss, options)` takes two stylesheet paths, and ensures the custom CSS doesn't override any selectors contained within the immutable source. 20 | This is typically best when comparing vendor CSS ([Bootstrap](http://getbootstrap.com), [Tachyons](http://tachyons.io), [Basscss](http://basscss.com), etc.) to your app's customizations. 21 | 22 | ```js 23 | var immutableCss = require('immutable-css') 24 | 25 | immutableCss.processFiles('css/vendor.css', 'css/app.css') 26 | // => [...] 27 | 28 | ``` 29 | 30 | `immutableCss.processGlob(cssGlob, options)` takes a glob that matches CSS files and ensures that no stylesheet overrides selectors contained within another. 31 | This is useful to ensure that CSS partials aren't mixing concerns by mutating selectors contained within another file. 32 | 33 | ```js 34 | var immutableCss = require('immutable-css') 35 | 36 | immutableCss.processGlob('src/css/**/*.css', { verbose: true }) 37 | ``` 38 | 39 | ### Using with [PostCSS](https://github.com/postcss/postcss) 40 | 41 | Immutable CSS detects mutations among files by leveraging PostCSS sourcemaps. It is also best used as a PostCSS plugin in tandem with `postcss-import` and `postcss-reporter`. 42 | 43 | ```js 44 | var fs = require('fs') 45 | var postcss = require('postcss') 46 | var import = require('postcss-import') 47 | var reporter = require('postcss-reporter') 48 | var immutableCss = require('immutable-css') 49 | 50 | var css = fs.readFileSync('styles.css', 'utf8') 51 | 52 | var mutations = postcss([import(), immutableCss(), reporter()]) 53 | .process(css, { from: 'styles.css' }) 54 | ``` 55 | 56 | ### Using with [Gulp](http://gulpjs.com) 57 | 58 | ```js 59 | var gulp = require('gulp') 60 | var postcss = require('gulp-postcss') 61 | var import = require('postcss-import') 62 | var reporter = require('postcss-reporter') 63 | var immutableCss = require('immutable-css') 64 | 65 | gulp.task('immutable', function () { 66 | var processors = [ 67 | import, 68 | immutableCss, 69 | // If you want Immutable CSS to halt the gulp pipline if there are any warnings 70 | // then set throwError to true 71 | reporter({clearMessages: true, throwError: false}) 72 | ] 73 | 74 | gulp.src('assets/css/base.css') 75 | .pipe(postcss(processors)) 76 | .pipe(gulp.dest('dist/css')) 77 | }) 78 | ``` 79 | 80 | #### Input 81 | 82 | ```css 83 | @import 'basscss'; 84 | 85 | .button {} 86 | .left {} 87 | .something-else {} 88 | ``` 89 | 90 | #### Output 91 | 92 | ```sh 93 | ⚠ .button was mutated 2 times 94 | [line 93, col 1]: /css/basscss.css 95 | [line 3, col 1]: /css/custom.css 96 | [immutable-css] 97 | ⚠ .left was mutated 2 times 98 | [line 291, col 1]: /css/basscss.css 99 | [line 4, col 1]: /css/custom.css 100 | [immutable-css] 101 | ``` 102 | 103 | ### Options 104 | 105 | * `strict` (Boolean): Whether class mutations are allowed in the same file. Default: `false`. 106 | * `ignoredClasses` (Array): List of classes to ignore for mutation violations.
Ex: `['.some-mutable-class']` 107 | * `immutableClasses` (Array): List of classes to check against.
Ex: `['.button', '.foobar']` 108 | * `immutablePrefixes` (Array): List of prefix regexes that are immutable.
Ex: `[/\.u\-/, /\.util\-/]` 109 | * `callback` (Function): Callback that receives a mutations object.
Ex: `function (mutations) { console.log(mutations) }` 110 | * `verbose` (Boolean): Whether mutations are logged (defaults to true with PostCSS). 111 | 112 | #### Using the callback 113 | 114 | Immutable CSS accepts an optional callback, which returns the mutations hash. The key is the mutated class name, the value is an array of mutating filenames. 115 | 116 | ```js 117 | postcss([ 118 | import(), 119 | immutableCss({ ignoredClasses: ['.button'] }, function(mutations) { 120 | console.log(mutations) 121 | // => { '.foobar': [] } 122 | }) 123 | ]).process(css, { from: cssFile }) 124 | ``` 125 | 126 | ### Using the [immutable-css-cli](https://github.com/johnotander/immutable-css-cli) 127 | 128 | ```sh 129 | npm i -g immutable-css-cli 130 | ``` 131 | 132 | ```sh 133 | immutable-css css/main.css 134 | ⚠ .button was mutated 2 times 135 | [line 93, col 1]: /css/_basscss.css 136 | [line 11, col 1]: /css/_custom.css 137 | [immutable-css] 138 | ⚠ .left was mutated 2 times 139 | [line 291, col 1]: /css/_basscss.css 140 | [line 15, col 1]: /css/_custom.css 141 | [immutable-css] 142 | ``` 143 | 144 | 145 | 146 | ## Dependencies 147 | 148 | * 149 | * 150 | * 151 | 152 | ## Related Reading 153 | 154 | * 155 | * 156 | * 157 | * 158 | * 159 | 160 | ## License 161 | 162 | MIT 163 | 164 | ## Contributing 165 | 166 | 1. Fork it 167 | 2. Create your feature branch (`git checkout -b my-new-feature`) 168 | 3. Commit your changes (`git commit -am 'Add some feature'`) 169 | 4. Push to the branch (`git push origin my-new-feature`) 170 | 5. Create new Pull Request 171 | 172 | Crafted with <3 by [@jxnblk](https://twitter.com/jxnblk) & [@4lpine](https://twitter.com/4lpine). 173 | 174 | *** 175 | 176 | > This package was initially generated with [yeoman](http://yeoman.io) and the [p generator](https://github.com/johnotander/generator-p.git). 177 | -------------------------------------------------------------------------------- /test/fixtures/app.css: -------------------------------------------------------------------------------- 1 | .bar:before { 2 | content: 'baz'; 3 | } 4 | 5 | .awesome:before { 6 | color: green; 7 | } 8 | 9 | :root { 10 | color: purple; 11 | } 12 | 13 | .bar.awesome { 14 | color: hotpink; 15 | } 16 | 17 | .sibling + .foo { 18 | color: yellow; 19 | } 20 | 21 | .foobar > .awesome { 22 | color: tomato; 23 | } 24 | 25 | a.awesome { 26 | color: rebeccapurple; 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/basscss-mutations.css: -------------------------------------------------------------------------------- 1 | @import './test/fixtures/basscss'; 2 | 3 | .this-is-not-a-mutation { 4 | color: green; 5 | } 6 | 7 | input { 8 | border: none; 9 | } 10 | 11 | .button { 12 | color: blue; 13 | } 14 | 15 | .left { 16 | float: right /* :) */ 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/basscss.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Basscss v5.0.1 4 | 5 | Low-level CSS toolkit 6 | http://basscss.com 7 | 8 | */ 9 | 10 | body, 11 | button { 12 | margin: 0; 13 | } 14 | 15 | button, 16 | input, 17 | select, 18 | textarea { 19 | font-family: inherit; 20 | font-size: 100%; 21 | } 22 | 23 | img { 24 | max-width: 100%; 25 | } 26 | 27 | svg { 28 | max-height: 100%; 29 | } 30 | 31 | /* Basscss Base Forms */ 32 | 33 | input, 34 | select, 35 | textarea, 36 | fieldset { 37 | font-size: 1rem; 38 | margin-top: 0; 39 | margin-bottom: .5rem; 40 | } 41 | 42 | input[type=text], 43 | input[type=datetime], 44 | input[type=datetime-local], 45 | input[type=email], 46 | input[type=month], 47 | input[type=number], 48 | input[type=password], 49 | input[type=search], 50 | input[type=tel], 51 | input[type=time], 52 | input[type=url], 53 | input[type=week] { 54 | box-sizing: border-box; 55 | height: 2.25rem; 56 | padding: .5rem .5rem; 57 | vertical-align: middle; 58 | -webkit-appearance: none; 59 | } 60 | 61 | select { 62 | box-sizing: border-box; 63 | line-height: 1.75; 64 | padding: .5rem .5rem; 65 | } 66 | 67 | select:not([multiple]) { 68 | height: 2.25rem; 69 | vertical-align: middle; 70 | } 71 | 72 | textarea { 73 | box-sizing: border-box; 74 | line-height: 1.75; 75 | padding: .5rem .5rem; 76 | } 77 | 78 | .fieldset-reset { 79 | padding: 0; 80 | margin-left: 0; 81 | margin-right: 0; 82 | border: 0; 83 | } 84 | 85 | .fieldset-reset legend { 86 | padding: 0; 87 | } 88 | 89 | 90 | 91 | /* Basscss Base Buttons */ 92 | 93 | button, 94 | .button { 95 | font-size: inherit; 96 | font-weight: bold; 97 | text-decoration: none; 98 | cursor: pointer; 99 | display: inline-block; 100 | box-sizing: border-box; 101 | line-height: 1.125rem; 102 | padding: .5rem 1rem; 103 | margin: 0; 104 | height: auto; 105 | border: 1px solid transparent; 106 | vertical-align: middle; 107 | -webkit-appearance: none; 108 | } 109 | 110 | ::-moz-focus-inner { 111 | border: 0; 112 | padding: 0; 113 | } 114 | 115 | .button:hover { 116 | text-decoration: none; 117 | } 118 | 119 | 120 | 121 | /* Basscss Base Tables */ 122 | 123 | table { 124 | border-collapse: separate; 125 | border-spacing: 0; 126 | max-width: 100%; 127 | width: 100%; 128 | } 129 | 130 | th { 131 | text-align: left; 132 | font-weight: bold; 133 | } 134 | 135 | th, 136 | td { 137 | padding: .25rem 1rem; 138 | line-height: inherit; 139 | } 140 | 141 | th { 142 | vertical-align: bottom; 143 | } 144 | 145 | td { 146 | vertical-align: top; 147 | } 148 | 149 | 150 | 151 | /* Basscss Base Typography */ 152 | 153 | body { 154 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 155 | line-height: 1.5; 156 | font-size: 100%; 157 | } 158 | 159 | h1, 160 | h2, 161 | h3, 162 | h4, 163 | h5, 164 | h6 { 165 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 166 | font-weight: bold; 167 | line-height: 1.25; 168 | margin-top: 1em; 169 | margin-bottom: .5em; 170 | } 171 | 172 | p, 173 | dl, 174 | ol, 175 | ul { 176 | font-size: 1rem; 177 | margin-top: 0; 178 | margin-bottom: 1rem; 179 | } 180 | 181 | ol, 182 | ul { 183 | padding-left: 2rem; 184 | } 185 | 186 | pre, 187 | code, 188 | samp { 189 | font-family: 'Source Code Pro', Consolas, monospace; 190 | font-size: inherit; 191 | } 192 | 193 | pre { 194 | margin-top: 0; 195 | margin-bottom: 1rem; 196 | overflow-x: scroll; 197 | } 198 | 199 | hr { 200 | margin-top: 2rem; 201 | margin-bottom: 2rem; 202 | } 203 | 204 | blockquote { 205 | margin-top: 2rem; 206 | margin-bottom: 2rem; 207 | margin-left: 0; 208 | padding-left: 1rem; 209 | padding-right: 1rem; 210 | } 211 | 212 | blockquote, 213 | blockquote p { 214 | font-size: 1.25rem; 215 | font-style: italic; 216 | } 217 | 218 | h1, 219 | .h1 { 220 | font-size: 2rem; 221 | } 222 | 223 | h2, 224 | .h2 { 225 | font-size: 1.5rem; 226 | } 227 | 228 | h3, 229 | .h3 { 230 | font-size: 1.25rem; 231 | } 232 | 233 | h4, 234 | .h4 { 235 | font-size: 1rem; 236 | } 237 | 238 | h5, 239 | .h5 { 240 | font-size: .875rem; 241 | } 242 | 243 | h6, 244 | .h6 { 245 | font-size: .75rem; 246 | } 247 | 248 | .list-reset { 249 | list-style: none; 250 | padding-left: 0; 251 | } 252 | 253 | 254 | 255 | /* Basscss Utility Layout */ 256 | 257 | .inline { 258 | display: inline; 259 | } 260 | 261 | .block { 262 | display: block; 263 | } 264 | 265 | .inline-block { 266 | display: inline-block; 267 | } 268 | 269 | .overflow-hidden { 270 | overflow: hidden; 271 | } 272 | 273 | .overflow-scroll { 274 | overflow: scroll; 275 | } 276 | 277 | .overflow-auto { 278 | overflow: auto; 279 | } 280 | 281 | .clearfix:before, 282 | .clearfix:after { 283 | content: " "; 284 | display: table; 285 | } 286 | 287 | .clearfix:after { 288 | clear: both; 289 | } 290 | 291 | .left { 292 | float: left; 293 | } 294 | 295 | .right { 296 | float: right; 297 | } 298 | 299 | .fit { 300 | max-width: 100%; 301 | } 302 | 303 | .half-width { 304 | width: 50%; 305 | } 306 | 307 | .full-width { 308 | width: 100%; 309 | } 310 | 311 | /* Basscss Utility Typography */ 312 | 313 | .bold { 314 | font-weight: bold; 315 | } 316 | 317 | .regular { 318 | font-weight: normal; 319 | } 320 | 321 | .italic { 322 | font-style: italic; 323 | } 324 | 325 | .caps { 326 | text-transform: uppercase; 327 | letter-spacing: .2em; 328 | } 329 | 330 | .left-align { 331 | text-align: left; 332 | } 333 | 334 | .center { 335 | text-align: center; 336 | } 337 | 338 | .right-align { 339 | text-align: right; 340 | } 341 | 342 | .justify { 343 | text-align: justify; 344 | } 345 | 346 | .nowrap { 347 | white-space: nowrap; 348 | } 349 | 350 | 351 | 352 | /* Basscss Utility White Space */ 353 | 354 | .m0 { 355 | margin: 0; 356 | } 357 | 358 | .mt0 { 359 | margin-top: 0; 360 | } 361 | 362 | .mr0 { 363 | margin-right: 0; 364 | } 365 | 366 | .mb0 { 367 | margin-bottom: 0; 368 | } 369 | 370 | .ml0 { 371 | margin-left: 0; 372 | } 373 | 374 | .m1 { 375 | margin: .5rem; 376 | } 377 | 378 | .mt1 { 379 | margin-top: .5rem; 380 | } 381 | 382 | .mr1 { 383 | margin-right: .5rem; 384 | } 385 | 386 | .mb1 { 387 | margin-bottom: .5rem; 388 | } 389 | 390 | .ml1 { 391 | margin-left: .5rem; 392 | } 393 | 394 | .m2 { 395 | margin: 1rem; 396 | } 397 | 398 | .mt2 { 399 | margin-top: 1rem; 400 | } 401 | 402 | .mr2 { 403 | margin-right: 1rem; 404 | } 405 | 406 | .mb2 { 407 | margin-bottom: 1rem; 408 | } 409 | 410 | .ml2 { 411 | margin-left: 1rem; 412 | } 413 | 414 | .m3 { 415 | margin: 2rem; 416 | } 417 | 418 | .mt3 { 419 | margin-top: 2rem; 420 | } 421 | 422 | .mr3 { 423 | margin-right: 2rem; 424 | } 425 | 426 | .mb3 { 427 | margin-bottom: 2rem; 428 | } 429 | 430 | .ml3 { 431 | margin-left: 2rem; 432 | } 433 | 434 | .m4 { 435 | margin: 4rem; 436 | } 437 | 438 | .mt4 { 439 | margin-top: 4rem; 440 | } 441 | 442 | .mr4 { 443 | margin-right: 4rem; 444 | } 445 | 446 | .mb4 { 447 | margin-bottom: 4rem; 448 | } 449 | 450 | .ml4 { 451 | margin-left: 4rem; 452 | } 453 | 454 | .mxn1 { 455 | margin-left: -.5rem; 456 | margin-right: -.5rem; 457 | } 458 | 459 | .mxn2 { 460 | margin-left: -1rem; 461 | margin-right: -1rem; 462 | } 463 | 464 | .mxn3 { 465 | margin-left: -2rem; 466 | margin-right: -2rem; 467 | } 468 | 469 | .mxn4 { 470 | margin-left: -4rem; 471 | margin-right: -4rem; 472 | } 473 | 474 | .mx-auto { 475 | margin-left: auto; 476 | margin-right: auto; 477 | } 478 | 479 | .p1 { 480 | padding: .5rem; 481 | } 482 | 483 | .py1 { 484 | padding-top: .5rem; 485 | padding-bottom: .5rem; 486 | } 487 | 488 | .px1 { 489 | padding-left: .5rem; 490 | padding-right: .5rem; 491 | } 492 | 493 | .p2 { 494 | padding: 1rem; 495 | } 496 | 497 | .py2 { 498 | padding-top: 1rem; 499 | padding-bottom: 1rem; 500 | } 501 | 502 | .px2 { 503 | padding-left: 1rem; 504 | padding-right: 1rem; 505 | } 506 | 507 | .p3 { 508 | padding: 2rem; 509 | } 510 | 511 | .py3 { 512 | padding-top: 2rem; 513 | padding-bottom: 2rem; 514 | } 515 | 516 | .px3 { 517 | padding-left: 2rem; 518 | padding-right: 2rem; 519 | } 520 | 521 | .p4 { 522 | padding: 4rem; 523 | } 524 | 525 | .py4 { 526 | padding-top: 4rem; 527 | padding-bottom: 4rem; 528 | } 529 | 530 | .px4 { 531 | padding-left: 4rem; 532 | padding-right: 4rem; 533 | } 534 | 535 | 536 | 537 | /* Basscss Utility Responsive States */ 538 | 539 | .sm-show, 540 | .md-show, 541 | .lg-show { 542 | display: none !important; 543 | } 544 | 545 | @media (min-width: 40em) { 546 | .sm-show { 547 | display: block !important; 548 | } 549 | } 550 | 551 | @media (min-width: 52em) { 552 | .md-show { 553 | display: block !important; 554 | } 555 | } 556 | 557 | @media (min-width: 64em) { 558 | .lg-show { 559 | display: block !important; 560 | } 561 | } 562 | 563 | @media (min-width: 40em) { 564 | .sm-hide { 565 | display: none !important; 566 | } 567 | } 568 | 569 | @media (min-width: 52em) { 570 | .md-hide { 571 | display: none !important; 572 | } 573 | } 574 | 575 | @media (min-width: 64em) { 576 | .lg-hide { 577 | display: none !important; 578 | } 579 | } 580 | 581 | .display-none { 582 | display: none !important; 583 | } 584 | 585 | .hide { 586 | position: absolute !important; 587 | height: 1px; 588 | width: 1px; 589 | overflow: hidden; 590 | clip: rect(1px, 1px, 1px, 1px); 591 | } 592 | 593 | /* Basscss Positions */ 594 | 595 | .relative { 596 | position: relative; 597 | } 598 | 599 | .absolute { 600 | position: absolute; 601 | } 602 | 603 | .fixed { 604 | position: fixed; 605 | } 606 | 607 | .top-0 { 608 | top: 0; 609 | } 610 | 611 | .right-0 { 612 | right: 0; 613 | } 614 | 615 | .bottom-0 { 616 | bottom: 0; 617 | } 618 | 619 | .left-0 { 620 | left: 0; 621 | } 622 | 623 | .z1 { 624 | z-index: 1; 625 | } 626 | 627 | .z2 { 628 | z-index: 2; 629 | } 630 | 631 | .z3 { 632 | z-index: 3; 633 | } 634 | 635 | .z4 { 636 | z-index: 4; 637 | } 638 | 639 | .absolute-center { 640 | top: 0; 641 | right: 0; 642 | bottom: 0; 643 | left: 0; 644 | margin: auto; 645 | display: table; 646 | } 647 | 648 | /* Basscss UI Utility Button Sizes */ 649 | 650 | .button-small { 651 | padding: .25rem .5rem; 652 | } 653 | 654 | .button-big { 655 | padding: 1rem 1.25rem; 656 | } 657 | 658 | .button-narrow { 659 | padding-left: .5rem; 660 | padding-right: .5rem; 661 | } 662 | 663 | 664 | 665 | /* Basscss Grid */ 666 | 667 | .container { 668 | max-width: 64em; 669 | margin-left: auto; 670 | margin-right: auto; 671 | } 672 | 673 | .col { 674 | float: left; 675 | box-sizing: border-box; 676 | } 677 | 678 | .col-right { 679 | float: right; 680 | box-sizing: border-box; 681 | } 682 | 683 | .col-1 { 684 | width: 8.333333333333332%; 685 | } 686 | 687 | .col-2 { 688 | width: 16.666666666666664%; 689 | } 690 | 691 | .col-3 { 692 | width: 25%; 693 | } 694 | 695 | .col-4 { 696 | width: 33.33333333333333%; 697 | } 698 | 699 | .col-5 { 700 | width: 41.66666666666667%; 701 | } 702 | 703 | .col-6 { 704 | width: 50%; 705 | } 706 | 707 | .col-7 { 708 | width: 58.333333333333336%; 709 | } 710 | 711 | .col-8 { 712 | width: 66.66666666666666%; 713 | } 714 | 715 | .col-9 { 716 | width: 75%; 717 | } 718 | 719 | .col-10 { 720 | width: 83.33333333333334%; 721 | } 722 | 723 | .col-11 { 724 | width: 91.66666666666666%; 725 | } 726 | 727 | .col-12 { 728 | width: 100%; 729 | } 730 | 731 | @media (min-width: 40em) { 732 | .sm-col { 733 | float: left; 734 | box-sizing: border-box; 735 | } 736 | 737 | .sm-col-right { 738 | float: right; 739 | box-sizing: border-box; 740 | } 741 | 742 | .sm-col-1 { 743 | width: 8.333333333333332%; 744 | } 745 | 746 | .sm-col-2 { 747 | width: 16.666666666666664%; 748 | } 749 | 750 | .sm-col-3 { 751 | width: 25%; 752 | } 753 | 754 | .sm-col-4 { 755 | width: 33.33333333333333%; 756 | } 757 | 758 | .sm-col-5 { 759 | width: 41.66666666666667%; 760 | } 761 | 762 | .sm-col-6 { 763 | width: 50%; 764 | } 765 | 766 | .sm-col-7 { 767 | width: 58.333333333333336%; 768 | } 769 | 770 | .sm-col-8 { 771 | width: 66.66666666666666%; 772 | } 773 | 774 | .sm-col-9 { 775 | width: 75%; 776 | } 777 | 778 | .sm-col-10 { 779 | width: 83.33333333333334%; 780 | } 781 | 782 | .sm-col-11 { 783 | width: 91.66666666666666%; 784 | } 785 | 786 | .sm-col-12 { 787 | width: 100%; 788 | } 789 | } 790 | 791 | @media (min-width: 52em) { 792 | .md-col { 793 | float: left; 794 | box-sizing: border-box; 795 | } 796 | 797 | .md-col-right { 798 | float: right; 799 | box-sizing: border-box; 800 | } 801 | 802 | .md-col-1 { 803 | width: 8.333333333333332%; 804 | } 805 | 806 | .md-col-2 { 807 | width: 16.666666666666664%; 808 | } 809 | 810 | .md-col-3 { 811 | width: 25%; 812 | } 813 | 814 | .md-col-4 { 815 | width: 33.33333333333333%; 816 | } 817 | 818 | .md-col-5 { 819 | width: 41.66666666666667%; 820 | } 821 | 822 | .md-col-6 { 823 | width: 50%; 824 | } 825 | 826 | .md-col-7 { 827 | width: 58.333333333333336%; 828 | } 829 | 830 | .md-col-8 { 831 | width: 66.66666666666666%; 832 | } 833 | 834 | .md-col-9 { 835 | width: 75%; 836 | } 837 | 838 | .md-col-10 { 839 | width: 83.33333333333334%; 840 | } 841 | 842 | .md-col-11 { 843 | width: 91.66666666666666%; 844 | } 845 | 846 | .md-col-12 { 847 | width: 100%; 848 | } 849 | } 850 | 851 | @media (min-width: 64em) { 852 | .lg-col { 853 | float: left; 854 | box-sizing: border-box; 855 | } 856 | 857 | .lg-col-right { 858 | float: right; 859 | box-sizing: border-box; 860 | } 861 | 862 | .lg-col-1 { 863 | width: 8.333333333333332%; 864 | } 865 | 866 | .lg-col-2 { 867 | width: 16.666666666666664%; 868 | } 869 | 870 | .lg-col-3 { 871 | width: 25%; 872 | } 873 | 874 | .lg-col-4 { 875 | width: 33.33333333333333%; 876 | } 877 | 878 | .lg-col-5 { 879 | width: 41.66666666666667%; 880 | } 881 | 882 | .lg-col-6 { 883 | width: 50%; 884 | } 885 | 886 | .lg-col-7 { 887 | width: 58.333333333333336%; 888 | } 889 | 890 | .lg-col-8 { 891 | width: 66.66666666666666%; 892 | } 893 | 894 | .lg-col-9 { 895 | width: 75%; 896 | } 897 | 898 | .lg-col-10 { 899 | width: 83.33333333333334%; 900 | } 901 | 902 | .lg-col-11 { 903 | width: 91.66666666666666%; 904 | } 905 | 906 | .lg-col-12 { 907 | width: 100%; 908 | } 909 | } 910 | 911 | 912 | 913 | /* 914 | * Basscss Flex Object 915 | */ 916 | 917 | .flex { 918 | display: -webkit-box; 919 | display: -webkit-flex; 920 | display: -ms-flexbox; 921 | display: flex; 922 | } 923 | 924 | .flex-column { 925 | -webkit-box-orient: vertical; 926 | -webkit-box-direction: normal; 927 | -webkit-flex-direction: column; 928 | -ms-flex-direction: column; 929 | flex-direction: column; 930 | } 931 | 932 | .flex-wrap { 933 | -webkit-flex-wrap: wrap; 934 | -ms-flex-wrap: wrap; 935 | flex-wrap: wrap; 936 | } 937 | 938 | .flex-center { 939 | -webkit-box-align: center; 940 | -webkit-align-items: center; 941 | -ms-flex-align: center; 942 | align-items: center; 943 | } 944 | 945 | .flex-baseline { 946 | -webkit-box-align: baseline; 947 | -webkit-align-items: baseline; 948 | -ms-flex-align: baseline; 949 | align-items: baseline; 950 | } 951 | 952 | .flex-stretch { 953 | -webkit-box-align: stretch; 954 | -webkit-align-items: stretch; 955 | -ms-flex-align: stretch; 956 | align-items: stretch; 957 | } 958 | 959 | .flex-start { 960 | -webkit-box-align: start; 961 | -webkit-align-items: flex-start; 962 | -ms-flex-align: start; 963 | align-items: flex-start; 964 | } 965 | 966 | .flex-end { 967 | -webkit-box-align: end; 968 | -webkit-align-items: flex-end; 969 | -ms-flex-align: end; 970 | align-items: flex-end; 971 | } 972 | 973 | .flex-first { 974 | -webkit-box-ordinal-group: 0; 975 | -webkit-order: -1; 976 | -ms-flex-order: -1; 977 | order: -1; 978 | } 979 | 980 | .flex-last { 981 | -webkit-box-ordinal-group: 1025; 982 | -webkit-order: 1024; 983 | -ms-flex-order: 1024; 984 | order: 1024; 985 | } 986 | 987 | .flex-auto { 988 | -webkit-box-flex: 1; 989 | -webkit-flex: 1 1 auto; 990 | -ms-flex: 1 1 auto; 991 | flex: 1 1 auto; 992 | } 993 | 994 | .flex-grow { 995 | -webkit-box-flex: 1; 996 | -webkit-flex: 1 0 auto; 997 | -ms-flex: 1 0 auto; 998 | flex: 1 0 auto; 999 | } 1000 | 1001 | .flex-none { 1002 | -webkit-box-flex: 0; 1003 | -webkit-flex: none; 1004 | -ms-flex: none; 1005 | flex: none; 1006 | } 1007 | 1008 | .flex > div { 1009 | box-sizing: border-box; 1010 | } 1011 | 1012 | @media (min-width: 40em) { 1013 | .sm-flex { 1014 | display: -webkit-box; 1015 | display: -webkit-flex; 1016 | display: -ms-flexbox; 1017 | display: flex; 1018 | } 1019 | 1020 | .sm-flex > div { 1021 | box-sizing: border-box; 1022 | } 1023 | } 1024 | 1025 | @media (min-width: 52em) { 1026 | .md-flex { 1027 | display: -webkit-box; 1028 | display: -webkit-flex; 1029 | display: -ms-flexbox; 1030 | display: flex; 1031 | } 1032 | 1033 | .md-flex > div { 1034 | box-sizing: border-box; 1035 | } 1036 | } 1037 | 1038 | @media (min-width: 64em) { 1039 | .lg-flex { 1040 | display: -webkit-box; 1041 | display: -webkit-flex; 1042 | display: -ms-flexbox; 1043 | display: flex; 1044 | } 1045 | 1046 | .lg-flex > div { 1047 | box-sizing: border-box; 1048 | } 1049 | } 1050 | 1051 | /* New */ 1052 | 1053 | /* Basscss Color Base */ 1054 | 1055 | /* 1056 | 1057 | COLOR VARIABLES 1058 | 1059 | - Cool 1060 | - Warm 1061 | - Gray Scale 1062 | 1063 | */ 1064 | 1065 | :root { 1066 | /* Cool */ 1067 | /* Warm */ 1068 | /* Gray scale */ 1069 | } 1070 | 1071 | body { 1072 | color: #222; 1073 | background-color: white; 1074 | } 1075 | 1076 | a { 1077 | color: #0074d9; 1078 | text-decoration: none; 1079 | } 1080 | 1081 | a:hover { 1082 | text-decoration: underline; 1083 | } 1084 | 1085 | pre, 1086 | code { 1087 | background-color: #ddd; 1088 | border-radius: 3px; 1089 | } 1090 | 1091 | hr { 1092 | border: 0; 1093 | border-bottom-style: solid; 1094 | border-bottom-width: 1px; 1095 | border-bottom-color: rgba(0, 0, 0, .125); 1096 | } 1097 | 1098 | .button { 1099 | color: white; 1100 | background-color: #0074d9; 1101 | border-radius: 3px; 1102 | -webkit-transition-duration: .05s; 1103 | transition-duration: .05s; 1104 | -webkit-transition-timing-function: ease-out; 1105 | transition-timing-function: ease-out; 1106 | -webkit-transition-property: box-shadow, background-color; 1107 | transition-property: box-shadow, background-color; 1108 | } 1109 | 1110 | .button:hover { 1111 | box-shadow: inset 0 0 0 20rem rgba(0, 0, 0, .0625); 1112 | } 1113 | 1114 | .button:focus { 1115 | outline: none; 1116 | border-color: rgba(0, 0, 0, .125); 1117 | box-shadow: 0 0 2px 1px rgba(0, 0, 0, .25); 1118 | } 1119 | 1120 | .button:active, 1121 | .button.is-active { 1122 | box-shadow: inset 0 0 0 20rem rgba(0, 0, 0, .125), 1123 | inset 0 3px 4px 0 rgba(0, 0, 0, .25), 1124 | 0 0 1px rgba(0, 0, 0, .125); 1125 | } 1126 | 1127 | .button:disabled, 1128 | .button.is-disabled { 1129 | opacity: .5; 1130 | } 1131 | 1132 | 1133 | 1134 | /* Basscss Color Forms */ 1135 | 1136 | .field-light { 1137 | background-color: white; 1138 | -webkit-transition: box-shadow .2s ease; 1139 | transition: box-shadow .2s ease; 1140 | border-style: solid; 1141 | border-width: 1px; 1142 | border-color: rgba(0, 0, 0, .125); 1143 | border-radius: 3px; 1144 | } 1145 | 1146 | .field-light:focus { 1147 | outline: none; 1148 | border-color: #0074d9; 1149 | box-shadow: 0 0 2px rgba(0, 116, 217, .5); 1150 | } 1151 | 1152 | .field-light:disabled { 1153 | color: #aaa; 1154 | background-color: rgba(0, 0, 0, .125); 1155 | } 1156 | 1157 | .field-light:read-only:not(select) { 1158 | background-color: rgba(0, 0, 0, .125); 1159 | } 1160 | 1161 | .field-light:invalid { 1162 | border-color: #ff4136; 1163 | } 1164 | 1165 | .field-light.is-success { 1166 | border-color: #2ecc40; 1167 | } 1168 | 1169 | .field-light.is-warning { 1170 | border-color: #ffdc00; 1171 | } 1172 | 1173 | .field-light.is-error { 1174 | border-color: #ff4136; 1175 | } 1176 | 1177 | .radio-light, 1178 | .checkbox-light { 1179 | -webkit-transition: box-shadow .2s ease; 1180 | transition: box-shadow .2s ease; 1181 | } 1182 | 1183 | .radio-light { 1184 | border-radius: 50%; 1185 | } 1186 | 1187 | .radio-light:focus, 1188 | .checkbox-light:focus { 1189 | outline: none; 1190 | box-shadow: 0 0 2px rgba(0, 116, 217, .5); 1191 | } 1192 | 1193 | 1194 | 1195 | /* Basscss Color Forms Dark */ 1196 | 1197 | .field-dark { 1198 | color: white; 1199 | background-color: rgba(0, 0, 0, .25); 1200 | border: 1px solid rgba(0, 0, 0, .0625); 1201 | border-radius: 3px; 1202 | } 1203 | 1204 | .field-dark::-webkit-input-placeholder { 1205 | color: rgba(255, 255, 255, .75); 1206 | } 1207 | 1208 | .field-dark::-moz-placeholder { 1209 | color: rgba(255, 255, 255, .75); 1210 | } 1211 | 1212 | .field-dark:-ms-input-placeholder { 1213 | color: rgba(255, 255, 255, .75); 1214 | } 1215 | 1216 | .field-dark::placeholder { 1217 | color: rgba(255, 255, 255, .75); 1218 | } 1219 | 1220 | .field-dark:focus { 1221 | outline: 0; 1222 | border: 1px solid rgba(255, 255, 255, .5); 1223 | } 1224 | 1225 | .field-dark:read-only:not(select) { 1226 | background-color: rgba(255, 255, 255, .25); 1227 | } 1228 | 1229 | .field-dark:invalid { 1230 | border-color: #ff4136; 1231 | } 1232 | 1233 | .field-dark.is-success { 1234 | border-color: #2ecc40; 1235 | } 1236 | 1237 | .field-dark.is-warning { 1238 | border-color: #ffdc00; 1239 | } 1240 | 1241 | .field-dark.is-error { 1242 | border-color: #ff4136; 1243 | } 1244 | 1245 | 1246 | 1247 | /* Basscss Input Range */ 1248 | 1249 | input[type=range] { 1250 | vertical-align: middle; 1251 | background-color: transparent; 1252 | } 1253 | 1254 | .range-light { 1255 | color: inherit; 1256 | -webkit-appearance: none; 1257 | padding-top: .5rem; 1258 | padding-bottom: .5rem; 1259 | } 1260 | 1261 | .range-light::-webkit-slider-thumb { 1262 | -webkit-appearance: none; 1263 | position: relative; 1264 | width: .5rem; 1265 | height: 1.25rem; 1266 | border-radius: 3px; 1267 | background-color: currentcolor; 1268 | cursor: pointer; 1269 | margin-top: -0.5rem; 1270 | } 1271 | 1272 | /* Touch screen friendly pseudo element */ 1273 | 1274 | .range-light::-webkit-slider-thumb:before { 1275 | content: ''; 1276 | display: block; 1277 | position: absolute; 1278 | top: -0.5rem; 1279 | left: -0.875rem; 1280 | width: 2.25rem; 1281 | height: 2.25rem; 1282 | opacity: 0; 1283 | } 1284 | 1285 | .range-light::-moz-range-thumb { 1286 | width: .5rem; 1287 | height: 1.25rem; 1288 | border-radius: 3px; 1289 | border-color: transparent; 1290 | border-width: 0; 1291 | background-color: currentcolor; 1292 | cursor: pointer; 1293 | } 1294 | 1295 | .range-light::-webkit-slider-runnable-track { 1296 | height: 0.25rem; 1297 | cursor: pointer; 1298 | border-radius: 3px; 1299 | background-color: rgba(0, 0, 0, .25); 1300 | } 1301 | 1302 | .range-light::-moz-range-track { 1303 | height: 0.25rem; 1304 | cursor: pointer; 1305 | border-radius: 3px; 1306 | background-color: rgba(0, 0, 0, .25); 1307 | } 1308 | 1309 | .range-light:focus { 1310 | outline: none; 1311 | } 1312 | 1313 | .range-light:focus::-webkit-slider-thumb { 1314 | outline: none; 1315 | border: 0; 1316 | box-shadow: 0 0 1px 2px currentcolor; 1317 | } 1318 | 1319 | .range-light:focus::-moz-range-thumb { 1320 | outline: none; 1321 | border: 0; 1322 | box-shadow: 0 0 1px 2px currentcolor; 1323 | } 1324 | 1325 | 1326 | 1327 | /* Basscss Color Tables */ 1328 | 1329 | .table-light th, 1330 | .table-light td { 1331 | border-bottom-style: solid; 1332 | border-bottom-width: 1px; 1333 | border-bottom-color: rgba(0, 0, 0, .125); 1334 | } 1335 | 1336 | .table-light tr:last-child td { 1337 | border-bottom: 0; 1338 | } 1339 | 1340 | 1341 | 1342 | /* Basscss Button Outline */ 1343 | 1344 | .button-outline { 1345 | position: relative; 1346 | z-index: 2; 1347 | color: inherit; 1348 | background-color: transparent; 1349 | border-radius: 3px; 1350 | border: 1px solid currentcolor; 1351 | -webkit-transition-duration: .1s; 1352 | transition-duration: .1s; 1353 | -webkit-transition-timing-function: ease-out; 1354 | transition-timing-function: ease-out; 1355 | -webkit-transition-property: box-shadow, background-color; 1356 | transition-property: box-shadow, background-color; 1357 | } 1358 | 1359 | .button-outline:before { 1360 | content: ''; 1361 | width: 100%; 1362 | height: 100%; 1363 | display: block; 1364 | position: absolute; 1365 | z-index: -1; 1366 | top: -1px; 1367 | left: -1px; 1368 | border: 1px solid transparent; 1369 | background-color: currentcolor; 1370 | border-radius: 3px; 1371 | -webkit-transition-duration: .1s; 1372 | transition-duration: .1s; 1373 | -webkit-transition-timing-function: ease-out; 1374 | transition-timing-function: ease-out; 1375 | -webkit-transition-property: opacity; 1376 | transition-property: opacity; 1377 | opacity: 0; 1378 | } 1379 | 1380 | .button-outline:hover { 1381 | box-shadow: none; 1382 | } 1383 | 1384 | .button-outline:hover:before { 1385 | opacity: .125; 1386 | } 1387 | 1388 | .button-outline:focus { 1389 | outline: none; 1390 | border: 1px solid currentcolor; 1391 | box-shadow: 0 0 3px 1px; 1392 | } 1393 | 1394 | .button-outline:active, 1395 | .button-outline.is-active { 1396 | box-shadow: inset 0 1px 5px 0, 0 0 1px; 1397 | } 1398 | 1399 | .button-outline:disabled, 1400 | .button-outline.is-disabled { 1401 | opacity: .5; 1402 | } 1403 | 1404 | 1405 | 1406 | /* New */ 1407 | 1408 | /* Basscss Button Transparent */ 1409 | 1410 | .button-transparent { 1411 | position: relative; 1412 | z-index: 2; 1413 | color: inherit; 1414 | background-color: transparent; 1415 | border-radius: 0; 1416 | border: 1px solid transparent; 1417 | -webkit-transition-duration: .1s; 1418 | transition-duration: .1s; 1419 | -webkit-transition-timing-function: ease-out; 1420 | transition-timing-function: ease-out; 1421 | -webkit-transition-property: box-shadow; 1422 | transition-property: box-shadow; 1423 | } 1424 | 1425 | .button-transparent:before { 1426 | content: ''; 1427 | width: 100%; 1428 | height: 100%; 1429 | display: block; 1430 | position: absolute; 1431 | z-index: -1; 1432 | top: -1px; 1433 | left: -1px; 1434 | border: 1px solid transparent; 1435 | background-color: currentcolor; 1436 | -webkit-transition-duration: .1s; 1437 | transition-duration: .1s; 1438 | -webkit-transition-timing-function: ease-out; 1439 | transition-timing-function: ease-out; 1440 | -webkit-transition-property: opacity; 1441 | transition-property: opacity; 1442 | opacity: 0; 1443 | } 1444 | 1445 | .button-transparent:hover { 1446 | box-shadow: none; 1447 | } 1448 | 1449 | .button-transparent:hover:before { 1450 | opacity: .0625; 1451 | opacity: .09375; 1452 | } 1453 | 1454 | .button-transparent:focus { 1455 | outline: none; 1456 | border-color: transparent; 1457 | box-shadow: 0 0 3px; 1458 | } 1459 | 1460 | .button-transparent:active:before, 1461 | .button-transparent.is-active:before { 1462 | opacity: .0625; 1463 | } 1464 | 1465 | .button-transparent:disabled, 1466 | .button-transparent.is-disabled { 1467 | opacity: .5; 1468 | } 1469 | 1470 | 1471 | 1472 | /* New */ 1473 | 1474 | /* Basscss Background Images */ 1475 | 1476 | .bg-cover { 1477 | background-size: cover; 1478 | } 1479 | 1480 | .bg-contain { 1481 | background-size: contain; 1482 | } 1483 | 1484 | .bg-center { 1485 | background-position: center; 1486 | } 1487 | 1488 | .bg-top { 1489 | background-position: top; 1490 | } 1491 | 1492 | .bg-right { 1493 | background-position: right; 1494 | } 1495 | 1496 | .bg-bottom { 1497 | background-position: bottom; 1498 | } 1499 | 1500 | .bg-left { 1501 | background-position: left; 1502 | } 1503 | 1504 | /* New */ 1505 | 1506 | /* Basscss Color Borders */ 1507 | 1508 | .border { 1509 | border-style: solid; 1510 | border-width: 1px; 1511 | border-color: rgba(0, 0, 0, .125); 1512 | } 1513 | 1514 | .border-top { 1515 | border-top-style: solid; 1516 | border-top-width: 1px; 1517 | border-top-color: rgba(0, 0, 0, .125); 1518 | } 1519 | 1520 | .border-right { 1521 | border-right-style: solid; 1522 | border-right-width: 1px; 1523 | border-right-color: rgba(0, 0, 0, .125); 1524 | } 1525 | 1526 | .border-bottom { 1527 | border-bottom-style: solid; 1528 | border-bottom-width: 1px; 1529 | border-bottom-color: rgba(0, 0, 0, .125); 1530 | } 1531 | 1532 | .border-left { 1533 | border-left-style: solid; 1534 | border-left-width: 1px; 1535 | border-left-color: rgba(0, 0, 0, .125); 1536 | } 1537 | 1538 | .rounded { 1539 | border-radius: 3px; 1540 | } 1541 | 1542 | .circle { 1543 | border-radius: 50%; 1544 | } 1545 | 1546 | .rounded-top { 1547 | border-radius: 3px 3px 0 0; 1548 | } 1549 | 1550 | .rounded-right { 1551 | border-radius: 0 3px 3px 0; 1552 | } 1553 | 1554 | .rounded-bottom { 1555 | border-radius: 0 0 3px 3px; 1556 | } 1557 | 1558 | .rounded-left { 1559 | border-radius: 3px 0 0 3px; 1560 | } 1561 | 1562 | .not-rounded { 1563 | border-radius: 0; 1564 | } 1565 | 1566 | 1567 | 1568 | /* Basscss Colors */ 1569 | 1570 | /* Color */ 1571 | 1572 | .black, 1573 | .dark-gray { 1574 | color: #222; 1575 | } 1576 | 1577 | .gray, 1578 | .mid-gray { 1579 | color: #aaa; 1580 | } 1581 | 1582 | .silver, 1583 | .light-gray { 1584 | color: #ddd; 1585 | } 1586 | 1587 | .white { 1588 | color: #fff; 1589 | } 1590 | 1591 | .aqua { 1592 | color: #7fdbff; 1593 | } 1594 | 1595 | .blue { 1596 | color: #0074d9; 1597 | } 1598 | 1599 | .navy { 1600 | color: #001f3f; 1601 | } 1602 | 1603 | .teal { 1604 | color: #39cccc; 1605 | } 1606 | 1607 | .green { 1608 | color: #2ecc40; 1609 | } 1610 | 1611 | .olive { 1612 | color: #3d9970; 1613 | } 1614 | 1615 | .lime { 1616 | color: #01ff70; 1617 | } 1618 | 1619 | .yellow { 1620 | color: #ffdc00; 1621 | } 1622 | 1623 | .orange { 1624 | color: #ff851b; 1625 | } 1626 | 1627 | .red { 1628 | color: #ff4136; 1629 | } 1630 | 1631 | .fuchsia { 1632 | color: #f012be; 1633 | } 1634 | 1635 | .purple { 1636 | color: #b10dc9; 1637 | } 1638 | 1639 | .maroon { 1640 | color: #85144b; 1641 | } 1642 | 1643 | /* Background Color */ 1644 | 1645 | .bg-black, 1646 | .bg-dark-gray { 1647 | background-color: #222; 1648 | } 1649 | 1650 | .bg-gray, 1651 | .bg-mid-gray { 1652 | background-color: #aaa; 1653 | } 1654 | 1655 | .bg-silver, 1656 | .bg-light-gray { 1657 | background-color: #ddd; 1658 | } 1659 | 1660 | .bg-white { 1661 | background-color: #fff; 1662 | } 1663 | 1664 | .bg-aqua { 1665 | background-color: #7fdbff; 1666 | } 1667 | 1668 | .bg-blue { 1669 | background-color: #0074d9; 1670 | } 1671 | 1672 | .bg-navy { 1673 | background-color: #001f3f; 1674 | } 1675 | 1676 | .bg-teal { 1677 | background-color: #39cccc; 1678 | } 1679 | 1680 | .bg-green { 1681 | background-color: #2ecc40; 1682 | } 1683 | 1684 | .bg-olive { 1685 | background-color: #3d9970; 1686 | } 1687 | 1688 | .bg-lime { 1689 | background-color: #01ff70; 1690 | } 1691 | 1692 | .bg-yellow { 1693 | background-color: #ffdc00; 1694 | } 1695 | 1696 | .bg-orange { 1697 | background-color: #ff851b; 1698 | } 1699 | 1700 | .bg-red { 1701 | background-color: #ff4136; 1702 | } 1703 | 1704 | .bg-fuchsia { 1705 | background-color: #f012be; 1706 | } 1707 | 1708 | .bg-purple { 1709 | background-color: #b10dc9; 1710 | } 1711 | 1712 | .bg-maroon { 1713 | background-color: #85144b; 1714 | } 1715 | 1716 | .bg-darken-1 { 1717 | background-color: rgba(0, 0, 0, .0625); 1718 | } 1719 | 1720 | .bg-darken-2 { 1721 | background-color: rgba(0, 0, 0, .125); 1722 | } 1723 | 1724 | .bg-darken-3 { 1725 | background-color: rgba(0, 0, 0, .25); 1726 | } 1727 | 1728 | .bg-darken-4 { 1729 | background-color: rgba(0, 0, 0, .5); 1730 | } 1731 | 1732 | /* Border Color */ 1733 | 1734 | .border-aqua { 1735 | border-color: #7fdbff; 1736 | } 1737 | 1738 | .border-blue { 1739 | border-color: #0074d9; 1740 | } 1741 | 1742 | .border-navy { 1743 | border-color: #001f3f; 1744 | } 1745 | 1746 | .border-teal { 1747 | border-color: #39cccc; 1748 | } 1749 | 1750 | .border-green { 1751 | border-color: #2ecc40; 1752 | } 1753 | 1754 | .border-olive { 1755 | border-color: #3d9970; 1756 | } 1757 | 1758 | .border-lime { 1759 | border-color: #01ff70; 1760 | } 1761 | 1762 | .border-yellow { 1763 | border-color: #ffdc00; 1764 | } 1765 | 1766 | .border-orange { 1767 | border-color: #ff851b; 1768 | } 1769 | 1770 | .border-red { 1771 | border-color: #ff4136; 1772 | } 1773 | 1774 | .border-fuchsia { 1775 | border-color: #f012be; 1776 | } 1777 | 1778 | .border-purple { 1779 | border-color: #b10dc9; 1780 | } 1781 | 1782 | .border-maroon { 1783 | border-color: #85144b; 1784 | } 1785 | 1786 | .border-black { 1787 | border-color: #222; 1788 | } 1789 | 1790 | .border-gray { 1791 | border-color: #aaa; 1792 | } 1793 | 1794 | .border-silver { 1795 | border-color: #ddd; 1796 | } 1797 | 1798 | .border-white { 1799 | border-color: #fff; 1800 | } 1801 | 1802 | .border-darken-1 { 1803 | border-color: rgba(0, 0, 0, .0625); 1804 | } 1805 | 1806 | .border-darken-2 { 1807 | border-color: rgba(0, 0, 0, .125); 1808 | } 1809 | 1810 | .border-darken-3 { 1811 | border-color: rgba(0, 0, 0, .25); 1812 | } 1813 | 1814 | .border-darken-4 { 1815 | border-color: rgba(0, 0, 0, .5); 1816 | } 1817 | 1818 | /* Opacity */ 1819 | 1820 | .muted { 1821 | opacity: .5; 1822 | } 1823 | -------------------------------------------------------------------------------- /test/fixtures/bootstrap-mutations.css: -------------------------------------------------------------------------------- 1 | .input-group { 2 | border: none; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/vendor.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | background-color: tomato; 3 | } 4 | 5 | .foo { 6 | color: purple; 7 | } 8 | 9 | .awesome .opossum { 10 | color: blue; 11 | } 12 | -------------------------------------------------------------------------------- /test/get-css-classes-test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var assert = require('assert') 3 | var getCssClassesFromAst = require('../lib/get-css-classes-from-ast') 4 | var postcss = require('postcss') 5 | 6 | function fixture(name) { 7 | return fs.readFileSync('test/fixtures/' + name, 'utf8').trim() 8 | } 9 | 10 | describe('get-css-classes-from-ast', function() { 11 | 12 | it('should return a hash of the selectors', function() { 13 | var css = postcss.parse(fixture('vendor.css')) 14 | assert.deepEqual(getCssClassesFromAst(css), { '.foo': true, '.awesome': true, '.opossum': true }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /test/get-mutations-test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var assert = require('assert') 3 | var getMutations = require('../lib/get-mutations') 4 | 5 | function fixture(name) { 6 | return fs.readFileSync('test/fixtures/' + name, 'utf8').trim() 7 | } 8 | 9 | describe('get-mutations', function() { 10 | 11 | it('should return an array of mutations', function() { 12 | assert.equal(getMutations(fixture('vendor.css'), fixture('app.css')).length, 5) 13 | }) 14 | 15 | it('should find basscss mutations when they exist', function() { 16 | assert.equal(getMutations(fixture('basscss.css'), fixture('basscss-mutations.css')).length, 2) 17 | }) 18 | 19 | it('should find bootstrap mutations when they exist', function() { 20 | assert.equal(getMutations(fixture('bootstrap.css'), fixture('bootstrap-mutations.css')).length, 1) 21 | }) 22 | 23 | it('should include immutable selectors that are passes as options', function() { 24 | assert.equal( 25 | getMutations( 26 | fixture('vendor.css'), 27 | fixture('app.css'), 28 | { immutableSelectors: ['.sibling'] } 29 | ).length, 6) 30 | }) 31 | 32 | it('should ignore the specified classes', function() { 33 | assert.equal( 34 | getMutations( 35 | fixture('basscss.css'), 36 | fixture('basscss-mutations.css'), 37 | { ignoredSelectors: ['.button'] } 38 | ).length, 1) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var assert = require('assert') 4 | var postcss = require('postcss') 5 | var postcssImport = require('postcss-import') 6 | var postcssReporter = require('postcss-reporter') 7 | var immutableCss = require('..') 8 | 9 | function fixture (name) { 10 | return fs.readFileSync('test/fixtures/' + name, 'utf8') 11 | } 12 | 13 | function test (input, mutations, opts, cb) { 14 | var messages = postcss([ postcssImport(), immutableCss(opts, cb), postcssReporter() ]) 15 | .process(fixture(input), { from: input }) 16 | .messages 17 | 18 | if (mutations) { 19 | assert.deepEqual(messages, mutations) 20 | } 21 | } 22 | 23 | describe('immutable-css', function () { 24 | 25 | it('should report the correct mutations', function () { 26 | test('basscss-mutations.css', mutations) 27 | }) 28 | 29 | it('should return the mutations object in the callback', function (done) { 30 | test('basscss-mutations.css', undefined, {}, function (classMap) { 31 | assert.equal(Object.keys(classMap).length, 2) 32 | done() 33 | }) 34 | }) 35 | 36 | it('should ignore the specified selectors', function (done) { 37 | test('basscss-mutations.css', undefined, { ignoredClasses: ['.button'] }, function (classMap) { 38 | assert.equal(Object.keys(classMap).length, 1) 39 | done() 40 | }) 41 | }) 42 | 43 | it('should ignore the specified prefixes', function (done) { 44 | test('basscss-mutations.css', undefined, { immutablePrefixes: [/\.right/] }, function (classMap) { 45 | assert.equal(Object.keys(classMap).length, 5) 46 | done() 47 | }) 48 | }) 49 | }) 50 | 51 | describe('immutable-css --strict', function () { 52 | it('does not allow class mutations in the same file', function (done) { 53 | test('vendor.css', undefined, { strict: true }, function (classMap) { 54 | assert.equal(Object.keys(classMap).length, 1) 55 | done() 56 | }) 57 | }) 58 | }) 59 | 60 | describe('immutable-css.processFiles', function () { 61 | 62 | it('reports the mutations', function () { 63 | var foundMutations = immutableCss.processFiles('test/fixtures/basscss.css', 'test/fixtures/basscss-mutations.css') 64 | assert.equal(foundMutations.length, mutations.length) 65 | }) 66 | }) 67 | 68 | describe('immutable-css.processGlob', function () { 69 | 70 | it('reports the mutations', function () { 71 | var foundMutations = immutableCss.processGlob('test/fixtures/**/*.css') 72 | assert.equal(foundMutations.length, 15) // Magic mutation number in test/fixtures 73 | }) 74 | }) 75 | 76 | var mutations = [{ 77 | plugin: 'immutable-css', 78 | text: '.button was mutated 2 times\n[line 93, col 1]: ' + __dirname + '/fixtures/basscss.css\n[line 11, col 1]: ' + path.resolve(__dirname, '..') + '/basscss-mutations.css\n', 79 | type: 'warning' 80 | }, { 81 | plugin: 'immutable-css', 82 | text: '.left was mutated 2 times\n[line 291, col 1]: ' + __dirname + '/fixtures/basscss.css\n[line 15, col 1]: ' + path.resolve(__dirname, '..') + '/basscss-mutations.css\n', 83 | type: 'warning' 84 | }] 85 | --------------------------------------------------------------------------------