├── .eslintrc ├── .gitignore ├── src ├── regexes │ ├── transparent.js │ ├── hex.js │ ├── utils │ │ └── index.js │ ├── rgb4-number.js │ ├── rgb3-number.js │ ├── rgb4-percentage.js │ ├── rgb3-percentage.js │ ├── hsl3.js │ ├── hsl4.js │ └── index.js ├── index.d.ts ├── index.js └── utils │ └── index.js ├── .editorconfig ├── .github └── workflows │ ├── codecov.yml │ ├── nodejs.yml │ └── codeql.yml ├── rollup.config.js ├── LICENSE ├── package.json ├── CHANGELOG.md ├── README.md └── test ├── 01-regexes.js └── 00-parse.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "rollup" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | 5 | .nyc_output/ 6 | coverage/ 7 | -------------------------------------------------------------------------------- /src/regexes/transparent.js: -------------------------------------------------------------------------------- 1 | const pattern = /^transparent$/; 2 | export default new RegExp(pattern, 'i'); 3 | -------------------------------------------------------------------------------- /src/regexes/hex.js: -------------------------------------------------------------------------------- 1 | const pattern = /^#([a-f0-9]{3,4}|[a-f0-9]{4}(?:[a-f0-9]{2}){1,2})\b$/; 2 | export default new RegExp(pattern, 'i'); 3 | -------------------------------------------------------------------------------- /src/regexes/utils/index.js: -------------------------------------------------------------------------------- 1 | const float = '-?\\d*(?:\\.\\d+)'; 2 | 3 | export const number = `(${float}?)`; 4 | export const percentage = `(${float}?%)`; 5 | export const numberOrPercentage = `(${float}?%?)`; 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.js] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "parse-css-color" { 2 | export = parseCSSColor 3 | 4 | export interface Result { 5 | readonly alpha: number 6 | readonly type: string 7 | readonly values: number[] 8 | } 9 | 10 | function parseCSSColor(str: string): Result | null 11 | } 12 | -------------------------------------------------------------------------------- /src/regexes/rgb4-number.js: -------------------------------------------------------------------------------- 1 | import { number, numberOrPercentage } from './utils'; 2 | 3 | const pattern = `^ 4 | rgba?\\( 5 | \\s*${number} 6 | \\s+${number} 7 | \\s+${number} 8 | \\s*(?:\\s*\\/\\s*${numberOrPercentage}\\s*)? 9 | \\) 10 | $ 11 | `.replace(/\n|\s/g, ''); 12 | 13 | export default new RegExp(pattern); 14 | -------------------------------------------------------------------------------- /src/regexes/rgb3-number.js: -------------------------------------------------------------------------------- 1 | import { number, numberOrPercentage } from './utils'; 2 | 3 | const pattern = `^ 4 | rgba?\\( 5 | \\s*${number}\\s*, 6 | \\s*${number}\\s*, 7 | \\s*${number}\\s* 8 | (?:,\\s*${numberOrPercentage}\\s*)? 9 | \\) 10 | $ 11 | `.replace(/\n|\s/g, ''); 12 | 13 | export default new RegExp(pattern); 14 | -------------------------------------------------------------------------------- /src/regexes/rgb4-percentage.js: -------------------------------------------------------------------------------- 1 | import { percentage, numberOrPercentage } from './utils'; 2 | 3 | const pattern = `^ 4 | rgba?\\( 5 | \\s*${percentage} 6 | \\s+${percentage} 7 | \\s+${percentage} 8 | \\s*(?:\\s*\\/\\s*${numberOrPercentage}\\s*)? 9 | \\) 10 | $ 11 | `.replace(/\n|\s/g, ''); 12 | 13 | export default new RegExp(pattern); 14 | -------------------------------------------------------------------------------- /src/regexes/rgb3-percentage.js: -------------------------------------------------------------------------------- 1 | import { percentage, numberOrPercentage } from './utils'; 2 | 3 | const pattern = `^ 4 | rgba?\\( 5 | \\s*${percentage}\\s*, 6 | \\s*${percentage}\\s*, 7 | \\s*${percentage}\\s* 8 | (?:,\\s*${numberOrPercentage}\\s*)? 9 | \\) 10 | $ 11 | `.replace(/\n|\s/g, ''); 12 | 13 | export default new RegExp(pattern); 14 | -------------------------------------------------------------------------------- /src/regexes/hsl3.js: -------------------------------------------------------------------------------- 1 | import { percentage, numberOrPercentage } from './utils'; 2 | 3 | const pattern = `^ 4 | hsla?\\( 5 | \\s*(-?\\d*(?:\\.\\d+)?(?:deg|rad|turn)?)\\s*, 6 | \\s*${percentage}\\s*, 7 | \\s*${percentage}\\s* 8 | (?:,\\s*${numberOrPercentage}\\s*)? 9 | \\) 10 | $ 11 | `.replace(/\n|\s/g, ''); 12 | 13 | export default new RegExp(pattern); 14 | -------------------------------------------------------------------------------- /src/regexes/hsl4.js: -------------------------------------------------------------------------------- 1 | import { percentage, numberOrPercentage } from './utils'; 2 | 3 | const pattern = `^ 4 | hsla?\\( 5 | \\s*(-?\\d*(?:\\.\\d+)?(?:deg|rad|turn)?)\\s* 6 | \\s+${percentage} 7 | \\s+${percentage} 8 | \\s*(?:\\s*\\/\\s*${numberOrPercentage}\\s*)? 9 | \\) 10 | $ 11 | `.replace(/\n|\s/g, ''); 12 | 13 | export default new RegExp(pattern); 14 | -------------------------------------------------------------------------------- /src/regexes/index.js: -------------------------------------------------------------------------------- 1 | export { default as hexRe } from './hex'; 2 | 3 | export { default as hsl3Re } from './hsl3'; 4 | export { default as hsl4Re } from './hsl4'; 5 | 6 | export { default as rgb3NumberRe } from './rgb3-number'; 7 | export { default as rgb3PercentageRe } from './rgb3-percentage'; 8 | export { default as rgb4NumberRe } from './rgb4-number'; 9 | export { default as rgb4PercentageRe } from './rgb4-percentage'; 10 | 11 | export { default as transparentRe } from './transparent'; 12 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Codecov 2 | on: push 3 | jobs: 4 | coverage: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - uses: actions/setup-node@v3 9 | with: 10 | node-version: 16 11 | - run: npm ci 12 | - run: npm run coverage 13 | - name: Upload coverage to Codecov 14 | uses: codecov/codecov-action@v2 15 | with: 16 | token: ${{ secrets.CODECOV_TOKEN }} 17 | name: codecov-umbrella 18 | file: ./coverage/lcov.info 19 | fail_ci_if_error: true 20 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm run build --if-present 29 | - run: npm test 30 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "58 23 * * 5" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ javascript ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v2 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v2 40 | with: 41 | category: "/language:${{ matrix.language }}" 42 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import buble from '@rollup/plugin-buble'; 4 | import replace from '@rollup/plugin-replace'; 5 | import copy from 'rollup-plugin-copy'; 6 | 7 | import { terser } from 'rollup-plugin-terser'; 8 | 9 | import pkg from './package.json'; 10 | 11 | export default [ 12 | { 13 | input: 'src/index.js', 14 | output: { 15 | file: pkg.browser, 16 | format: 'umd', 17 | name: 'parseCssColor' 18 | }, 19 | plugins: [resolve(), commonjs(), buble(), replace({ __VERSION__: `v${pkg.version}` }), terser()] 20 | }, 21 | { 22 | input: 'src/index.js', 23 | external: ['color-name', 'hex-rgb'], 24 | plugins: [ 25 | resolve(), 26 | commonjs(), 27 | replace({ __VERSION__: `v${pkg.version}` }), 28 | copy({ 29 | targets: [ 30 | { 31 | src: 'src/index.d.ts', 32 | dest: 'dist' 33 | } 34 | ] 35 | }) 36 | ], 37 | output: [ 38 | { file: pkg.main, format: 'cjs' }, 39 | { file: pkg.module, format: 'es' } 40 | ] 41 | } 42 | ]; 43 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * parse-css-color 3 | * @version __VERSION__ 4 | * @link http://github.com/noeldelgado/parse-css-color/ 5 | * @license MIT 6 | */ 7 | import colorName from 'color-name'; 8 | 9 | import { 10 | hexRe, 11 | hsl3Re, 12 | hsl4Re, 13 | rgb3NumberRe, 14 | rgb3PercentageRe, 15 | rgb4NumberRe, 16 | rgb4PercentageRe, 17 | transparentRe 18 | } from './regexes'; 19 | import { getHEX, getRGB, getHSL } from './utils'; 20 | 21 | const parseCSSColor = (str) => { 22 | if (typeof str !== 'string') return null; 23 | 24 | const hex = hexRe.exec(str); 25 | if (hex) return getHEX(hex[0]); 26 | 27 | const hsl = hsl4Re.exec(str) || hsl3Re.exec(str); 28 | if (hsl) return getHSL(hsl); 29 | 30 | const rgb = 31 | rgb4NumberRe.exec(str) || 32 | rgb4PercentageRe.exec(str) || 33 | rgb3NumberRe.exec(str) || 34 | rgb3PercentageRe.exec(str); 35 | if (rgb) return getRGB(rgb); 36 | 37 | if (transparentRe.exec(str)) return getRGB([null, 0, 0, 0, 0]); 38 | 39 | const cn = colorName[str.toLowerCase()]; 40 | if (cn) return getRGB([null, cn[0], cn[1], cn[2], 1]); 41 | 42 | return null; 43 | }; 44 | 45 | export default parseCSSColor; 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Noel Delgado (pixelia.me) 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 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import hex2Rgb from 'hex-rgb'; 2 | 3 | const clamp = (num, min, max) => Math.min(Math.max(min, num), max); 4 | 5 | /* 500 => 255, -10 => 0, 128 => 128 */ 6 | const parseRGB = (num) => { 7 | let n = num; 8 | if (typeof n !== 'number') n = n.endsWith('%') ? (parseFloat(n) * 255) / 100 : parseFloat(n); 9 | return clamp(Math.round(n), 0, 255); 10 | }; 11 | 12 | /* 200 => 100, -100 => 0, 50 => 50 */ 13 | const parsePercentage = (percentage) => clamp(parseFloat(percentage), 0, 100); 14 | 15 | /* '50%' => 5.0, 200 => 1, -10 => 0 */ 16 | function parseAlpha(alpha) { 17 | let a = alpha; 18 | if (typeof a !== 'number') a = a.endsWith('%') ? parseFloat(a) / 100 : parseFloat(a); 19 | return clamp(a, 0, 1); 20 | } 21 | 22 | export function getHEX(hex) { 23 | const [r, g, b, a] = hex2Rgb(hex, { format: 'array' }); 24 | return getRGB([null, ...[r, g, b, a]]); 25 | } 26 | 27 | export function getHSL([, h, s, l, a = 1]) { 28 | let hh = h; 29 | if (hh.endsWith('turn')) { 30 | hh = (parseFloat(hh) * 360) / 1; 31 | } else if (hh.endsWith('rad')) { 32 | hh = Math.round((parseFloat(hh) * 180) / Math.PI); 33 | } else { 34 | hh = parseFloat(hh); 35 | } 36 | return { 37 | type: 'hsl', 38 | values: [hh, parsePercentage(s), parsePercentage(l)], 39 | alpha: parseAlpha(a === null ? 1 : a) 40 | }; 41 | } 42 | 43 | export function getRGB([, r, g, b, a = 1]) { 44 | return { 45 | type: 'rgb', 46 | values: [r, g, b].map(parseRGB), 47 | alpha: parseAlpha(a === null ? 1 : a) 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-css-color", 3 | "version": "0.2.1", 4 | "description": "parse a css color string", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.esm.js", 7 | "browser": "dist/index.umd.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist" 11 | ], 12 | "scripts": { 13 | "dev": "rollup -c -w", 14 | "test": "mocha --require esm", 15 | "build": "rollup -c", 16 | "coverage": "nyc npm run test && nyc report --reporter=lcov", 17 | "prepublish": "npm run build" 18 | }, 19 | "keywords": [ 20 | "parse", 21 | "css", 22 | "color", 23 | "string", 24 | "colour", 25 | "rgb", 26 | "rgba", 27 | "hsl", 28 | "hsla", 29 | "hex", 30 | "hexa", 31 | "alpha" 32 | ], 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/noeldelgado/parse-css-color.git" 36 | }, 37 | "author": "Noel Delgado (https://pixelia.me/)", 38 | "license": "MIT", 39 | "dependencies": { 40 | "color-name": "^1.1.4", 41 | "hex-rgb": "^4.1.0" 42 | }, 43 | "devDependencies": { 44 | "@rollup/plugin-buble": "0.21.3", 45 | "@rollup/plugin-commonjs": "11.1.0", 46 | "@rollup/plugin-node-resolve": "7.1.3", 47 | "@rollup/plugin-replace": "2.3.2", 48 | "eslint": "7.0.0", 49 | "eslint-config-rollup": "2.0.0", 50 | "esm": "3.2.25", 51 | "mocha": "8.4.0", 52 | "nyc": "^15.1.0", 53 | "rollup": "2.8.2", 54 | "rollup-plugin-copy": "3.4.0", 55 | "rollup-plugin-terser": "7.0.2" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [0.2.1] - [#16](https://github.com/noeldelgado/parse-css-color/pull/16) - 2022-04-07 6 | ### Fixed 7 | - index.d.ts [#15](https://github.com/noeldelgado/parse-css-color/pull/15) 8 | 9 | ### Added 10 | - README: badges(librariesio, lgtm) [f36810e](https://github.com/noeldelgado/parse-css-color/commit/f36810ec81da9352e078a2ebe5dcdee4635e8735) 11 | 12 | ## [0.2.0] - [#12](https://github.com/noeldelgado/parse-css-color/pull/12) - 2022-03-16 13 | ### Added 14 | - Type definitions [c0ec124](https://github.com/noeldelgado/parse-css-color/commit/c0ec124f398036f4a03d8ede41fd2d83e3e142c7) 15 | 16 | ## [0.1.2] - [#2](https://github.com/noeldelgado/parse-css-color/pull/2) - 2020-05-19 17 | ### Added 18 | - unpkg CDN [bc542b9](https://github.com/noeldelgado/parse-css-color/commit/bc542b962c0eb04127c6d48fde5a2daec9f31589) 19 | - jsDelivr CDN [5fb8022](https://github.com/noeldelgado/parse-css-color/commit/5fb8022aa323bc86298a32bd9fbb85c403885804) 20 | - more input tests: 21 | - empty, undefined, null [57e4b08](https://github.com/noeldelgado/parse-css-color/commit/57e4b088c8eaa2a7c59911ab599211f7e958e1c8) 22 | - false, true, 0, 1 [f4b1032](https://github.com/noeldelgado/parse-css-color/commit/f4b103227c741b80ba7e1d9338f8ec422ce8d8f5) 23 | 24 | ### Fixed 25 | - return null if input is not string [8c203de](https://github.com/noeldelgado/parse-css-color/commit/8c203ded32e1e287a2c8ba84b926f18722887ca7) 26 | 27 | ## [0.1.1] - [#1](https://github.com/noeldelgado/parse-css-color/pull/1) - 2020-05-19 28 | ### Added 29 | - add related projects [9d229e6](https://github.com/noeldelgado/parse-css-color/commit/9d229e6dcd7b20801a522b30c7b419e4d3619352) [c81fe20](https://github.com/noeldelgado/parse-css-color/commit/c81fe2060bb2da0bb13a6091a5d7ec24d984184c) 30 | - add CHANGELOG [29d92b4](https://github.com/noeldelgado/parse-css-color/commit/29d92b4ef93e6fd3a337afb20058eae24b5c7712) 31 | - add currentColor and inherit fail tests [fd85e7b](https://github.com/noeldelgado/parse-css-color/commit/fd85e7b70ca425f06d364fe3350cfc418fa5874e) 32 | 33 | ### Changed 34 | - move distributed files to dist folder instead of lib (for npm) [1af0a5d](https://github.com/noeldelgado/parse-css-color/commit/1af0a5d7b3c86620c6e3bd52df0b560099d23392) 35 | - rename digit/number [5f75605](https://github.com/noeldelgado/parse-css-color/commit/5f75605c9c8135a8855e234dd1b28f052ce86f7c) 36 | 37 | ### Removed 38 | - remove lib files from source code [d6591d3](https://github.com/noeldelgado/parse-css-color/commit/d6591d38824e68d0f3768cc61f8b4ba79384f35c) 39 | 40 | ## [0.1.0] - 2020-05-18 41 | ### Added 42 | - support for: 43 | - #RGB|A, #RRGGBB|AA 44 | - RGB|A module level 3 and 4 45 | - HSL|A module level 3 and 4 46 | - color keywords 47 | - transparent 48 | 49 | [Unreleased]: https://github.com/noeldelgado/parse-css-color/compare/v0.2.1...HEAD 50 | [0.2.1]: https://github.com/noeldelgado/parse-css-color/compare/v0.2.0...v0.2.1 51 | [0.2.0]: https://github.com/noeldelgado/parse-css-color/compare/v0.1.2...v0.2.0 52 | [0.1.2]: https://github.com/noeldelgado/parse-css-color/compare/v0.1.1...v0.1.2 53 | [0.1.1]: https://github.com/noeldelgado/parse-css-color/compare/v0.1.0...v0.1.1 54 | [0.1.0]: https://github.com/noeldelgado/parse-css-color/releases/tag/v0.1.0 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parse-css-color 2 | [![NPM Version][npm-image]][npm-url] 3 | ![](https://img.badgesize.io/noeldelgado/parse-css-color/master/src/index.js.svg?compression=gzip) 4 | [![License][license-img]][license-url] 5 | [![codecov][codecov-image]][codecov-url] 6 | [![Known Vulnerabilities][snyk-img]][snyk-url] 7 | [![Libraries.io dependency status for latest release][librariesio-img]][librariesio-url] 8 | [![Total alerts][lgtm-image]][lgtm-url] 9 | [![Language grade: JavaScript][lgtm-grade-image]][lgtm-grade-url] 10 | 11 | Parse a CSS color string. 12 | 13 | ### Supports 14 | * \ 15 | * Hexadecimal RGB value: #RGB #RRGGBB 16 | * #RGBA #RRGGBBAA (4 and 8-digit hexadecimal RGBA notation) 17 | * RGB/A color module level 3 and 4 (number, percentage) 18 | * HSL/A color module level 3 and 4 (number, deg, rad, turn) 19 | * \ 20 | * One of the [pre-defined color keywords](https://www.w3.org/wiki/CSS/Properties/color/keywords). 21 | * transparent 22 | * Shorthand for transparent black, rgba(0,0,0,0). 23 | 24 | ### Does not support 25 | * currentColor 26 | * inherit 27 | 28 | ## Installation 29 | 30 | **NPM** 31 | 32 | ```sh 33 | npm i parse-css-color 34 | ``` 35 | 36 | Or as a ` 42 | ``` 43 | 44 | **jsDelivr CDN** 45 | 46 | ```html 47 | 48 | ``` 49 | 50 | ## Usage 51 | ```js 52 | import parse from 'parse-css-color' 53 | 54 | // HEX/A 55 | parse('#00f') 56 | //> { type: 'rgb', values: [0, 0, 255], alpha: 1 } 57 | parse('#00f8') 58 | //> { type: 'rgb', values: [0, 0, 255], alpha: 0.5333333333333333 } 59 | parse('#0000FF80') 60 | //> { type: 'rgb', values: [0, 0, 255], alpha: 0.5019607843137255 } 61 | parse('#00g') 62 | //> null 63 | 64 | // HSL/A 65 | parse('hsl(270deg 60% 70% / 25%)') 66 | //> { type: 'hsl', values: [270, 60, 70], alpha: 0.25 } 67 | parse('hsl(4.71239rad 260% -70% / 0.5)') // clipped to 68 | //> { type: 'hsl', values: [270, 100, 0], alpha: 0.5 } 69 | parse('hsla(.75turn, 60%, 70%, 50%)') 70 | //> { type: 'hsl', values: [270, 60, 70], alpha: 0.5 } 71 | parse('hsla(100deg 0 0 / 0)') // error: missing percetanges 72 | //> null 73 | 74 | // RGB/A 75 | parse('rgb(255 0 0 / 0.5)') 76 | //> { type: 'rgb', values: [255, 0, 0], alpha: 0.5 } 77 | parse('rgb(500 -100 0 / 200%)') // clipped to 78 | //> { type: 'rgb', values: [255, 0, 0], alpha: 1 } 79 | parse('rgba(255, 0, 255, 20%)') 80 | //> { type: 'rgb', values: [255, 0, 255], alpha: 0.2 } 81 | parse('rgba(100% 255 100% / 0)') // error: mixed percetange with integer 82 | //> null 83 | ``` 84 | See [tests](https://github.com/noeldelgado/parse-css-color/tree/master/test) for more cases. 85 | 86 | ## Dev 87 | ```sh 88 | npm install # install dependencies 89 | npm test # run the tests (append `-- -w`) to watch 90 | npm run dev # watch for changes and rebuild 91 | ``` 92 | 93 | ## Related 94 | - [mix-css-color](https://github.com/noeldelgado/mix-css-color) - Mix two CSS colors together in variable proportion. Opacity is included in the calculations. 95 | - [values.js](https://github.com/noeldelgado/values.js) - Get the tints and shades of a CSS color. 96 | 97 | ## License 98 | MIT © [Noel Delgado](http://pixelia.me/) 99 | 100 | [npm-image]: https://img.shields.io/npm/v/parse-css-color.svg?logo=npm&label=NPM 101 | [npm-url]: https://www.npmjs.com/package/parse-css-color 102 | [license-img]: https://img.shields.io/npm/l/parse-css-color 103 | [license-url]: https://github.com/noeldelgado/parse-css-color/blob/master/LICENSE 104 | [codecov-image]: https://codecov.io/gh/noeldelgado/parse-css-color/branch/master/graph/badge.svg 105 | [codecov-url]: https://codecov.io/gh/noeldelgado/parse-css-color 106 | [snyk-img]: https://snyk.io/test/npm/parse-css-color/badge.svg 107 | [snyk-url]: https://snyk.io/test/npm/parse-css-color 108 | [librariesio-img]: https://img.shields.io/librariesio/release/npm/parse-css-color.svg?logo=librariesdotio 109 | [librariesio-url]: https://libraries.io/npm/parse-css-color 110 | [lgtm-image]: https://img.shields.io/lgtm/alerts/g/noeldelgado/parse-css-color.svg?logo=lgtm&logoWidth=18 111 | [lgtm-url]: https://lgtm.com/projects/g/noeldelgado/parse-css-color/alerts/ 112 | [lgtm-grade-image]: https://img.shields.io/lgtm/grade/javascript/g/noeldelgado/parse-css-color.svg?logo=lgtm&logoWidth=18 113 | [lgtm-grade-url]: https://lgtm.com/projects/g/noeldelgado/parse-css-color/context:javascript 114 | -------------------------------------------------------------------------------- /test/01-regexes.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert').strict; 2 | 3 | const { 4 | hexRe, 5 | hsl3Re, 6 | hsl4Re, 7 | rgb3NumberRe, 8 | rgb3PercentageRe, 9 | rgb4NumberRe, 10 | rgb4PercentageRe, 11 | transparentRe 12 | } = require('../src/regexes'); 13 | 14 | const { ok, equal } = assert; 15 | 16 | const okTest = (pattern, arr) => 17 | arr.forEach((str) => it(`${str} should pass`, () => ok(pattern.test(str)))); 18 | 19 | const failTest = (pattern, arr) => 20 | arr.forEach((str) => it(`${str} should fail`, () => equal(pattern.test(str), false))); 21 | 22 | describe('regexes', () => { 23 | describe('regexes/hexRe', () => { 24 | const r = hexRe; 25 | okTest(r, [ 26 | '#00f', 27 | '#00ff', 28 | '#00f8', 29 | '#00f0', 30 | '#0000FF', 31 | '#0000ff', 32 | '#0000FFFF', 33 | '#0000ffff', 34 | '#0000FF80' 35 | ]); 36 | failTest(r, [ 37 | '0', 38 | 'ff', 39 | '00ff', 40 | '00ff0', 41 | '00ff00', 42 | '#00ff0', 43 | '#00g', 44 | '#z00', 45 | ' #000', 46 | '#000 ', 47 | ' #000 ', 48 | 'red', 49 | 'a#000', 50 | 'a #000', 51 | '#000 a' 52 | ]); 53 | }); 54 | 55 | describe('regexes/hsl3Re (level 3)', () => { 56 | const r = hsl3Re; 57 | okTest(r, [ 58 | 'hsl(240, 100%, 50%)', 59 | 'hsl(240 , 100%, 50%)', 60 | 'hsl(240, 100% , 50%)', 61 | 'hsl(240,100%,50%)', 62 | 'hsl( 240 , 100% , 50% )', 63 | 'hsl( 240,100%,50% )', 64 | 'hsl( 240 , 0% , 0% )', 65 | 'hsl(-240, 100%, 50%)', 66 | 67 | 'hsla(240, 100%, 50%, 1)', 68 | 'hsla(240, 100% , 50%, 0.5)', 69 | 'hsla(240, 100% , 50%, 0)', 70 | 'hsla(0, 100%, 50%, 50%)', 71 | 'hsla(240 , 100%, 50%, 50%)', 72 | 'hsla(240, 100% , 50%, 50%)', 73 | 'hsla(240, 100%, 50% , 50%)', 74 | 'hsla( 240, 100%, 50% , 50% )', 75 | 'hsla(240,100%,50%,50%)', 76 | 'hsla( 240 , 100% , 50% , 0)', 77 | 'hsla( 240 , 0% , 0% , 1 )', 78 | 'hsla(240, 100%, 50%, 50)', 79 | 'hsla(240, 100%, 50%, 200%)' 80 | ]); 81 | failTest(r, [ 82 | 'hsl()', 83 | ' hsl(240, 100%, 50%)', 84 | 'hsl(240, 100%, 50%) ', 85 | ' hsl(240, 100%, 50%) ', 86 | 'hsl(240%, 100%, 50%)', 87 | 'hsl(240, 100, 50%)', 88 | 'hsl(240, 100%, 50)', 89 | 'hsl(240, 100, 50)', 90 | 'hsl(240%, 100, 50)', 91 | 92 | 'hsla()', 93 | ' hsla(240, 100% , 50%, 50%)', 94 | 'hsla(240, 100% , 50%, 50%) ', 95 | ' hsla(240, 100% , 50%, 50%) ', 96 | 'hsla(240%, 100% , 50%, 50%)', 97 | 'hsla(240, 100, 50%, 50%)', 98 | 'hsla(240, 100%, 50, 50%)' 99 | ]); 100 | }); 101 | 102 | describe('regexes/hsl4Re (level 4)', () => { 103 | const r = hsl4Re; 104 | okTest(r, [ 105 | 'hsl(240deg 100% 50%)', 106 | 'hsl( 240 100% 50% / 100% )', 107 | 'hsl( 240turn 100% 50% / 50%)', 108 | 'hsla( 240rad 100% 50% / 0% )', 109 | 'hsl(100 100% 100%)', 110 | 'hsl(100 100% 100% / 0.5)' 111 | ]); 112 | failTest(r, [ 113 | 'hsl()', 114 | 'hsl(100)', 115 | 'hsl(100 100%)', 116 | 'hsl(100 100% 100)', 117 | 'hsl(100de 100% 100%)' 118 | ]); 119 | }); 120 | 121 | describe('regexes/rgb3NumberRe digits (level 3)', () => { 122 | const r = rgb3NumberRe; 123 | okTest(r, [ 124 | 'rgb(0, 0, 255)', 125 | 'rgba(0, 0, 255, 1)', 126 | 'rgb(500, -100, 255, 1)', 127 | 'rgba(-100, 300, 0, 0.5)' 128 | ]); 129 | failTest(r, [ 130 | 'rgn(255, 255, 255)', 131 | 'rgb(255 255 255)', 132 | 'rgba(300%, 255, 281, 100%)', 133 | 'rgba(300, 255%, 281, 1)' 134 | ]); 135 | }); 136 | 137 | describe('regexes/rgb3PercentageRe percentage (level 3)', () => { 138 | const r = rgb3PercentageRe; 139 | okTest(r, [ 140 | 'rgb(0%, 0%, 100%)', 141 | 'rgba(0%, 0%, 100%, 100%)', 142 | 'rgba(0%, 0%, 100%, 1)', 143 | 'rgba(0%, 0%, 100%, 0.5)' 144 | ]); 145 | }); 146 | 147 | describe('regexes/rgb4NumberRe digits (level 4)', () => { 148 | const r = rgb4NumberRe; 149 | okTest(r, [ 150 | 'rgb(0 0 255)', 151 | 'rgb(0 0 255 / 100%)', 152 | 'rgb(0 0 255 / 50%)', 153 | 'rgb(0 0 255 / 0%)', 154 | 'rgb(0 0 255 / 0)', 155 | 'rgb(0 0 255 / 0.5)', 156 | 'rgb(0 0 255 / 1)' 157 | ]); 158 | }); 159 | 160 | describe('regexes/rgb4PercentageRe percentage (level 4)', () => { 161 | const r = rgb4PercentageRe; 162 | okTest(r, [ 163 | 'rgb(0% 0% 100% / 100%)', 164 | 'rgb(0% 0% 100% / 50%)', 165 | 'rgb(0% 0% 100% / 1)', 166 | 'rgb(0% 0% 100% / 0.5)', 167 | 'rgb(0% 0% 100% / 0)' 168 | ]); 169 | }); 170 | 171 | describe('regexs/transparentRe', () => { 172 | const r = transparentRe; 173 | okTest(r, ['transparent', 'tRansParent']); 174 | failTest(r, [' transparent', 'transParent ']); 175 | }); 176 | }); 177 | -------------------------------------------------------------------------------- /test/00-parse.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert').strict; 2 | 3 | const parseCssColor = require('..'); 4 | 5 | const { deepEqual, equal } = assert; 6 | 7 | const parseOk = (colorString, m, v, a) => { 8 | it(`${colorString} => type: ${m}, values: ${v}, alpha: ${a}`, () => { 9 | const { type, values, alpha } = parseCssColor(colorString); 10 | equal(type, m); 11 | deepEqual(values, v); 12 | equal(alpha, a); 13 | }); 14 | }; 15 | 16 | const parseFail = (colorString) => { 17 | it(`${colorString} should be null`, () => { 18 | const res = parseCssColor(colorString); 19 | equal(res, null); 20 | }); 21 | }; 22 | 23 | describe('parse()', () => { 24 | parseOk('transparent', 'rgb', [0, 0, 0], 0); 25 | parseFail(' transparent'); 26 | parseFail('transparent '); 27 | describe('non-case sensitive TrANSParent', () => parseOk('TrANSParent', 'rgb', [0, 0, 0], 0)); 28 | parseFail(); 29 | parseFail(''); 30 | parseFail(undefined); // eslint-disable-line 31 | parseFail(null); 32 | parseFail(false); 33 | parseFail(true); 34 | parseFail(0); 35 | parseFail(1); 36 | parseFail('currentColor'); 37 | parseFail('inherit'); 38 | }); 39 | 40 | describe('parse()', () => { 41 | parseOk('#000', 'rgb', [0, 0, 0], 1); 42 | parseOk('#00f', 'rgb', [0, 0, 255], 1); 43 | parseOk('#00ff', 'rgb', [0, 0, 255], 1); 44 | parseOk('#00f8', 'rgb', [0, 0, 255], 0.5333333333333333); 45 | parseOk('#00f0', 'rgb', [0, 0, 255], 0); 46 | parseOk('#0000FF', 'rgb', [0, 0, 255], 1); 47 | parseOk('#0000FFFF', 'rgb', [0, 0, 255], 1); 48 | parseOk('#0000FF80', 'rgb', [0, 0, 255], 0.5019607843137255); 49 | parseFail('#00g'); 50 | }); 51 | 52 | describe('parse()', () => { 53 | const type = 'hsl'; 54 | describe('hsl level 4', () => { 55 | parseOk('hsl(270 60% 50% / .15)', type, [270, 60, 50], 0.15); 56 | parseOk('hsl(270 60% 50% / 15%)', type, [270, 60, 50], 0.15); 57 | parseOk('hsl(0deg 0% 0%)', type, [0, 0, 0], 1); 58 | parseOk('hsl(-520deg 200% -100%)', type, [-520, 100, 0], 1); 59 | parseOk('hsl(-520deg 200% -100% / 200%)', type, [-520, 100, 0], 1); 60 | parseOk('hsl(-520deg 200% -100% / -200%)', type, [-520, 100, 0], 0); 61 | parseOk('hsl(-520deg 200% -100% / 2)', type, [-520, 100, 0], 1); 62 | parseOk('hsl(0 0% 0% / 0)', type, [0, 0, 0], 0); 63 | parseOk('hsl(360deg -10% 0% / 200)', type, [360, 0, 0], 1); 64 | describe('deg', () => parseOk('hsl(270deg 60% 70%)', type, [270, 60, 70], 1)); 65 | describe('rad', () => parseOk('hsl(4.71239rad 60% 70%)', type, [270, 60, 70], 1)); 66 | describe('turn', () => parseOk('hsl(.75turn 60% 70%)', type, [270, 60, 70], 1)); 67 | describe('deg', () => parseOk('hsl(270deg 60% 70% / 0.25)', type, [270, 60, 70], 0.25)); 68 | describe('rad', () => parseOk('hsl(4.71239rad 60% 70% / 0.5)', type, [270, 60, 70], 0.5)); 69 | describe('turn', () => parseOk('hsl(.75turn 60% 70% / 50%)', type, [270, 60, 70], 0.5)); 70 | // s|l without percentage 71 | parseFail('hsla(100deg 0 0 / 0)'); 72 | parseFail('hsl(0deg 0% 0 / 0)'); 73 | parseFail('hsla(100 255 100% / 0)'); 74 | }); 75 | describe('hsl level 3', () => { 76 | parseOk('hsl(270, 60%, 50%, .15)', type, [270, 60, 50], 0.15); 77 | parseOk('hsl(270, 60%, 50%, 15%)', type, [270, 60, 50], 0.15); 78 | parseOk('hsl(270,60%,70%)', type, [270, 60, 70], 1); 79 | parseOk('hsl(270, 60%, 70%)', type, [270, 60, 70], 1); 80 | parseOk('hsl(270 60% 70%)', type, [270, 60, 70], 1); 81 | describe('deg', () => parseOk('hsl(270deg, 60%, 70%)', type, [270, 60, 70], 1)); 82 | describe('rad', () => parseOk('hsl(4.71239rad, 60%, 70%)', type, [270, 60, 70], 1)); 83 | describe('turn', () => parseOk('hsl(.75turn, 60%, 70%)', type, [270, 60, 70], 1)); 84 | describe('deg', () => parseOk('hsl(270deg, 60%, 70%, 1)', type, [270, 60, 70], 1)); 85 | describe('rad', () => parseOk('hsl(4.71239rad, 60%, 70%, 0.5)', type, [270, 60, 70], 0.5)); 86 | describe('turn', () => parseOk('hsl(.75turn, 60%, 70%, 50%)', type, [270, 60, 70], 0.5)); 87 | parseOk('hsl(240, 100%, 50%)', type, [240, 100, 50], 1); 88 | parseOk('hsl(240 , 100%, 50%)', type, [240, 100, 50], 1); 89 | parseOk('hsl(240, 100% , 50%)', type, [240, 100, 50], 1); 90 | parseOk('hsl(240,100%,50%)', type, [240, 100, 50], 1); 91 | parseOk('hsl( 240 , 100% , 50% )', type, [240, 100, 50], 1); 92 | parseOk('hsl( 240,100%,50% )', type, [240, 100, 50], 1); 93 | parseOk('hsl( 240 , 0% , 0% )', type, [240, 0, 0], 1); 94 | parseOk('hsl(-240, 100%, 50%)', type, [-240, 100, 50], 1); 95 | }); 96 | }); 97 | 98 | describe('parse()', () => { 99 | const type = 'rgb'; 100 | describe('rgb level 4', () => { 101 | parseOk('rgb(0 0 0)', type, [0, 0, 0], 1); 102 | parseOk('rgb(300 256 -100)', type, [255, 255, 0], 1); 103 | parseOk('rgb(300 256 -100 / -20)', type, [255, 255, 0], 0); 104 | parseOk('rgba(300 256 -100 / -20)', type, [255, 255, 0], 0); 105 | parseOk('rgba(300 256 -100 / -20)', type, [255, 255, 0], 0); 106 | parseOk('rgb(0 0 0 / 0)', type, [0, 0, 0], 0); 107 | parseOk('rgb( 0 0 0 / 100)', type, [0, 0, 0], 1); 108 | parseOk('rgb(0 0 0 / 20%)', type, [0, 0, 0], 0.2); 109 | parseOk('rgb(0 0 0 / -200%)', type, [0, 0, 0], 0); 110 | parseOk('rgb(300% 0% 0% / 1)', type, [255, 0, 0], 1); 111 | parseOk('rgb(100% 0% 0% / 200%)', type, [255, 0, 0], 1); 112 | parseOk('rgba(255, 0, 153.6, 1)', type, [255, 0, 154], 1); 113 | parseOk('rgb(0% 42.35% 33.33%)', type, [0, 108, 85], 1); 114 | parseOk('rgb(41.2% 69.88% 96.64%)', type, [105, 178, 246], 1); 115 | // parseOk('rgba(1e2, .5e1, .5e0, +.25e2%)', type, [100, 5, 0], 0.25); 116 | // mixed % with # 117 | parseFail('rgb(100% 0 0 / 0)'); 118 | parseFail('rgba(100% 0 0 / 0)'); 119 | parseFail('rgba(100% 255 100% / 0)'); 120 | }); 121 | describe('rgb level 3', () => { 122 | parseOk('rgb(0, 0, 0, 0)', type, [0, 0, 0], 0); 123 | parseOk('rgba(0, 0, 0, 0)', type, [0, 0, 0], 0); 124 | parseOk('rgba( 0, 0, 0)', type, [0, 0, 0], 1); 125 | parseOk('rgb(-100, -20, -1, -2)', type, [0, 0, 0], 0); 126 | parseOk('rgb(400, 300 , 600, 2)', type, [255, 255, 255], 1); 127 | parseOk('rgba(0%, 0% , 0%, 0)', type, [0, 0, 0], 0); 128 | parseOk('rgba(-100 , 0, 0, 0)', type, [0, 0, 0], 0); 129 | parseOk('rgba(-100, -10, -0, -100)', type, [0, 0, 0], 0); 130 | parseOk('rgba(500, 400, 281, 100)', type, [255, 255, 255], 1); 131 | parseOk('rgb(500, 400, 281, 100)', type, [255, 255, 255], 1); 132 | // mixed % with # 133 | parseFail('rgba(300%, 255, 281, 100%)'); 134 | parseFail('rgba(300%, 255, 281, 1)'); 135 | parseFail('rgb(300%, 255, 281)'); 136 | }); 137 | }); 138 | 139 | describe('parse()', () => { 140 | const type = 'rgb'; 141 | parseOk('red', type, [255, 0, 0], 1); 142 | parseOk('aqua', type, [0, 255, 255], 1); 143 | parseOk('cyan', type, [0, 255, 255], 1); 144 | parseOk('rebeccapurple', type, [102, 51, 153], 1); 145 | parseOk('tomato', type, [255, 99, 71], 1); 146 | parseOk('mediumaquamarine', type, [102, 205, 170], 1); 147 | describe('non-case sensitive Red', () => parseOk('Red', type, [255, 0, 0], 1)); 148 | describe('non-case sensitive darkSlateGray', () => 149 | parseOk('darkSlateGray', type, [47, 79, 79], 1)); 150 | parseFail('Unnamed'); 151 | parseFail('pinkk'); 152 | parseFail('superblue'); 153 | parseFail('yellowgreenwhite'); 154 | }); 155 | --------------------------------------------------------------------------------