├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src ├── plugin.spec.ts └── plugin.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [{package.json,*.yml}] 10 | indent_style = space 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .nyc_output/ 3 | .vscode/ 4 | coverage/ 5 | dist/ 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.map 3 | *.spec.* 4 | .nyc_output/ 5 | .vscode/ 6 | coverage/ 7 | src/ 8 | tsconfig.json 9 | tslint.json 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "node" 5 | - "6" 6 | 7 | after_success: 8 | - npm install codecov 9 | - npm run codecov 10 | 11 | notifications: 12 | email: 13 | on_success: change 14 | on_failure: always 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.0 2 | - Upgrade to PostCSS 6 (still works with PostCSS 5). 3 | 4 | ## 1.0.0 5 | - Supports PostCSS 5.x. 6 | 7 | ## 0.0.7 8 | - Remove CodeClimate integration. 9 | 10 | ## 0.0.6 11 | - Move scripts to package.json. 12 | 13 | ## 0.0.5 14 | - Don't npm ignore .d.ts. 15 | 16 | ## 0.0.4 17 | - npm ignore scripts. 18 | 19 | ## 0.0.3 20 | - Reorganize files. 21 | 22 | ## 0.0.2 23 | - Fix npm package. 24 | 25 | ## 0.0.1 26 | - Supports PostCSS 4.x. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jed Mao 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-center 2 | 3 | 6 | 7 | [![NPM version](http://img.shields.io/npm/v/postcss-center.svg?style=flat)](https://www.npmjs.org/package/postcss-center) 8 | [![npm license](http://img.shields.io/npm/l/postcss-center.svg?style=flat-square)](https://www.npmjs.org/package/postcss-center) 9 | [![Travis Build Status](https://img.shields.io/travis/jedmao/postcss-center.svg)](https://travis-ci.org/jedmao/postcss-center) 10 | [![codecov](https://codecov.io/gh/jedmao/postcss-center/branch/master/graph/badge.svg)](https://codecov.io/gh/jedmao/postcss-center) 11 | [![Dependency Status](https://gemnasium.com/badges/github.com/jedmao/postcss-center.svg)](https://gemnasium.com/github.com/jedmao/postcss-center) 12 | 13 | [![npm](https://nodei.co/npm/postcss-center.svg?downloads=true)](https://nodei.co/npm/postcss-center/) 14 | 15 | [PostCSS](https://github.com/postcss/postcss) plugin to center elements. 16 | 17 | ## Introduction 18 | 19 | Centering elements in CSS [isn't exactly straight-forward](http://www.w3.org/Style/Examples/007/center.en.html), but we can change that! 20 | 21 | ```css 22 | .foo { 23 | top: center; 24 | left: center; 25 | } 26 | ``` 27 | 28 | Transpiles into: 29 | 30 | ```css 31 | .foo { 32 | position: absolute; 33 | top: 50%; 34 | left: 50%; 35 | margin-right: -50%; 36 | transform: translate(-50%, -50%) 37 | } 38 | ``` 39 | 40 | Of course, you don't have to include both `top` and `left`: 41 | 42 | ```css 43 | .foo { 44 | top: center; 45 | } 46 | ``` 47 | 48 | Transpiles into: 49 | 50 | ```css 51 | .foo { 52 | position: absolute; 53 | top: 50%; 54 | transform: translateY(-50%); 55 | } 56 | ``` 57 | 58 | Or... 59 | 60 | ```css 61 | .foo { 62 | left: center; 63 | } 64 | ``` 65 | 66 | Transpiles into: 67 | 68 | ```css 69 | .foo { 70 | position: absolute; 71 | left: 50%; 72 | margin-right: -50%; 73 | transform: translateX(-50%); 74 | } 75 | ``` 76 | 77 | That's about it! 78 | 79 | ### Conditions 80 | 81 | - If the value of `top` or `left` is not `center` it will be preserved. If both are not `center`, this plugin will do nothing! 82 | - If the rule already has a `position` it will only be preserved if its value is `relative` or `fixed`. All other values will be replaced with `absolute`. 83 | - If the rule has a `position` of `relative` or the value of `left` is not `center`, the `margin-right` declaration will not be inserted. 84 | 85 | ## Installation 86 | 87 | ``` 88 | $ npm install postcss-center 89 | ``` 90 | 91 | ## Usage 92 | 93 | ### JavaScript 94 | 95 | ```js 96 | postcss([ require('postcss-center') ]); 97 | ``` 98 | 99 | ### TypeScript 100 | 101 | ```ts 102 | import * as postcssCenter from 'postcss-center'; 103 | 104 | postcss([ postcssCenter ]); 105 | ``` 106 | 107 | ## Options 108 | 109 | None at this time. 110 | 111 | ## Testing 112 | 113 | Run the following command: 114 | 115 | ``` 116 | $ npm test 117 | ``` 118 | 119 | This will build scripts, run tests and generate a code coverage report. Anything less than 100% coverage will throw an error. 120 | 121 | ### Watching 122 | 123 | For much faster development cycles, run the following commands in 2 separate processes: 124 | 125 | ``` 126 | $ npm run build:watch 127 | ``` 128 | 129 | Compiles TypeScript source into the `./dist` folder and watches for changes. 130 | 131 | ``` 132 | $ npm run watch 133 | ``` 134 | 135 | Runs the tests in the `./dist` folder and watches for changes. 136 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-center", 3 | "version": "1.1.0", 4 | "description": "PostCSS plugin to center elements.", 5 | "main": "dist/plugin.js", 6 | "types": "dist/plugin.d.ts", 7 | "scripts": { 8 | "clean": "rimraf coverage dist *.log", 9 | "codecov": "codecov -f coverage/lcov.info", 10 | "build": "tsc", 11 | "build:watch": "tsc --watch", 12 | "prepublish": "npm test", 13 | "pretest": "npm run tslint && npm run clean && npm run build", 14 | "test": "nyc ava", 15 | "test:watch": "ava --watch", 16 | "tslint": "tslint --project tsconfig.json", 17 | "watch": "npm run test:watch" 18 | }, 19 | "ava": { 20 | "files": [ 21 | "dist/**/*.spec.js" 22 | ], 23 | "source": [ 24 | "dist/**/*.js" 25 | ] 26 | }, 27 | "nyc": { 28 | "lines": 100, 29 | "statements": 100, 30 | "functions": 100, 31 | "branches": 100, 32 | "include": [ 33 | "dist/**/*.js" 34 | ], 35 | "exclude": [ 36 | "dist/**/*.spec.js" 37 | ], 38 | "reporter": [ 39 | "lcov", 40 | "text" 41 | ], 42 | "cache": true, 43 | "all": true, 44 | "check-coverage": true 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "url": "git+https://github.com/jedmao/postcss-center.git" 49 | }, 50 | "keywords": [ 51 | "postcss", 52 | "postcss-plugin", 53 | "center", 54 | "top", 55 | "left" 56 | ], 57 | "author": "Jed Mao ", 58 | "license": "MIT", 59 | "bugs": { 60 | "url": "https://github.com/jedmao/postcss-center/issues" 61 | }, 62 | "homepage": "https://github.com/jedmao/postcss-center#readme", 63 | "dependencies": { 64 | "postcss": "^6.0.14" 65 | }, 66 | "devDependencies": { 67 | "@types/node": "^8.0.47", 68 | "ava": "^0.23.0", 69 | "nyc": "^11.3.0", 70 | "rimraf": "^2.6.2", 71 | "tslint": "^5.8.0", 72 | "typescript": "^2.6.1" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/plugin.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { TestContext } from 'ava'; 2 | import * as postcss from 'postcss'; 3 | 4 | import * as plugin from './plugin'; 5 | 6 | test('top: center; transpiles into expected declarations', macro, 7 | `foo { 8 | top: center; 9 | }`, 10 | `foo { 11 | position: absolute; 12 | top: 50%; 13 | transform: translateY(-50%); 14 | }` 15 | ); 16 | 17 | ['absolute', 'relative', 'fixed'].forEach(position => { 18 | test( 19 | `top: center; perserves location/value of position at top if ${position}`, 20 | macro, 21 | `foo { 22 | position: ${position}; 23 | bar: baz; 24 | top: center; 25 | }`, 26 | `foo { 27 | position: ${position}; 28 | bar: baz; 29 | top: 50%; 30 | transform: translateY(-50%); 31 | }` 32 | ); 33 | test( 34 | `top: center; perserves location/value of position at bottom if ${position}`, 35 | macro, 36 | `foo { 37 | top: center; 38 | bar: baz; 39 | position: ${position}; 40 | }`, 41 | `foo { 42 | top: 50%; 43 | transform: translateY(-50%); 44 | bar: baz; 45 | position: ${position}; 46 | }` 47 | ); 48 | }); 49 | 50 | test('top: center; removes position: static and inserts a new position', macro, 51 | `foo { 52 | top: center; 53 | position: static; 54 | }`, 55 | `foo { 56 | position: absolute; 57 | top: 50%; 58 | transform: translateY(-50%); 59 | }` 60 | ); 61 | 62 | test('left: center; transpiles into expected declarations', macro, 63 | `foo { 64 | left: center; 65 | }`, 66 | `foo { 67 | position: absolute; 68 | left: 50%; 69 | margin-right: -50%; 70 | transform: translateX(-50%); 71 | }` 72 | ); 73 | 74 | test('left: center; omits margin-right: -50% if position is relative', macro, 75 | `foo { 76 | position: relative; 77 | left: center; 78 | }`, 79 | `foo { 80 | position: relative; 81 | left: 50%; 82 | transform: translateX(-50%); 83 | }` 84 | ); 85 | 86 | test('top: center; left: center; transpiles into expected declarations', macro, 87 | `foo { 88 | top: center; 89 | left: center; 90 | }`, 91 | `foo { 92 | position: absolute; 93 | top: 50%; 94 | left: 50%; 95 | margin-right: -50%; 96 | transform: translate(-50%, -50%); 97 | }` 98 | ); 99 | 100 | test('top: 10px; left: 20px; passes through w/o modification', macro, 101 | `foo { 102 | top: 10px; 103 | left: 20px; 104 | }`, 105 | `foo { 106 | top: 10px; 107 | left: 20px; 108 | }` 109 | ); 110 | 111 | function macro( 112 | t: TestContext, 113 | input: string, 114 | expected?: string|RegExp 115 | ) { 116 | const processor = postcss([ plugin() ]); 117 | if (expected instanceof RegExp) { 118 | t.throws(() => { 119 | return processor.process(stripTabs(input)).css; 120 | }, expected); 121 | return; 122 | } 123 | t.is( 124 | processor.process(stripTabs(input)).css, 125 | stripTabs(expected) 126 | ); 127 | function stripTabs(input: string) { 128 | return input.replace(/\t/g, ''); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import * as postcss from 'postcss'; 2 | 3 | const PostCssCenter = postcss.plugin('postcss-center', () => { 4 | return root => { 5 | root.walkRules(rule => { 6 | let top: postcss.Declaration; 7 | rule.walkDecls('top', decl => { 8 | if (decl.value !== 'center') { 9 | return; 10 | } 11 | top = decl; 12 | decl.value = '50%'; 13 | }); 14 | 15 | let left: postcss.Declaration; 16 | rule.walkDecls('left', decl => { 17 | if (decl.value !== 'center') { 18 | return; 19 | } 20 | left = decl; 21 | decl.value = '50%'; 22 | }); 23 | 24 | if (!top && !left) { 25 | return; 26 | } 27 | 28 | let position: string; 29 | rule.walkDecls('position', decl => { 30 | if (/^(absolute|relative|fixed)$/.test(decl.value)) { 31 | position = decl.value; 32 | return; 33 | } 34 | decl.remove(); 35 | }); 36 | 37 | if (!position) { 38 | (top || left).cloneBefore({ 39 | prop: 'position', 40 | value: 'absolute' 41 | }); 42 | } 43 | 44 | let translate: string; 45 | if (top && left) { 46 | translate = '(-50%, -50%)'; 47 | } else if (top) { 48 | translate = 'Y(-50%)'; 49 | } else { 50 | translate = 'X(-50%)'; 51 | } 52 | (left || top).cloneAfter({ 53 | prop: 'transform', 54 | value: `translate${translate}` 55 | }); 56 | 57 | if (left && position !== 'relative') { 58 | left.cloneAfter({ 59 | prop: 'margin-right', 60 | value: '-50%' 61 | }); 62 | } 63 | }); 64 | }; 65 | }); 66 | 67 | export = PostCssCenter; 68 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "target": "es6", 6 | "declaration": true, 7 | "newLine": "LF", 8 | "outDir": "dist", 9 | "rootDir": "src", 10 | "sourceMap": true, 11 | "lib": [ 12 | "es2015" 13 | ] 14 | }, 15 | "include": [ 16 | "src/**/*.ts" 17 | ], 18 | "exclude": [ 19 | "node_modules" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": true, 5 | "eofline": true, 6 | "forin": true, 7 | "label-position": true, 8 | "max-line-length": [true, 120], 9 | "no-arg": true, 10 | "no-bitwise": true, 11 | "no-console": [true, 12 | "debug", 13 | "info", 14 | "time", 15 | "timeEnd", 16 | "trace" 17 | ], 18 | "no-construct": true, 19 | "no-debugger": true, 20 | "no-duplicate-variable": true, 21 | "no-empty": true, 22 | "no-eval": true, 23 | "no-string-literal": true, 24 | "no-trailing-whitespace": true, 25 | "one-line": [true, 26 | "check-open-brace", 27 | "check-catch", 28 | "check-else", 29 | "check-whitespace" 30 | ], 31 | "quotemark": [false], 32 | "radix": true, 33 | "semicolon": [true, 34 | "always" 35 | ], 36 | "triple-equals": [true, "allow-null-check"], 37 | "variable-name": false, 38 | "whitespace": [true, 39 | "check-branch", 40 | "check-decl", 41 | "check-operator", 42 | "check-type" 43 | ] 44 | } 45 | } 46 | --------------------------------------------------------------------------------