├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── index.test-d.ts ├── package.json └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | tab_width = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [{*.json,*.json.example,*.gyp,*.yml,*.yaml}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [{*.py,*.asm}] 17 | indent_style = space 18 | 19 | [*.py] 20 | indent_size = 4 21 | 22 | [{*.asm,*.s}] 23 | indent_style = space 24 | indent_size = 8 25 | 26 | [*.md] 27 | trim_trailing_whitespace = false 28 | 29 | # Ideal settings - some plugins might support these. 30 | [*.js] 31 | quote_type = single 32 | 33 | [{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}] 34 | curly_bracket_next_line = false 35 | spaces_around_operators = true 36 | spaces_around_brackets = outside 37 | # close enough to 1TB 38 | indent_brace_style = K&R 39 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node.js ${{ matrix.node-version }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: 13 | - 22 14 | - 20 15 | - 18 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm install 22 | - run: npm test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.4.0 2 | 3 | - Changed: Invalid conversions now return `null` instead of `undefined` 4 | - Changed: Moved to XO standard 5 | - Fixed: a few details in package.json 6 | - Fixed: readme output regarding wrapped hue values ([#21](https://github.com/MoOx/color-string/pull/21)) 7 | 8 | # 0.3.0 9 | 10 | - Fixed: HSL alpha channel ([#16](https://github.com/harthur/color-string/pull/16)) 11 | - Fixed: ability to parse signed number ([#15](https://github.com/harthur/color-string/pull/15)) 12 | - Removed: component.json 13 | - Removed: browser build 14 | - Added: license field to package.json ([#17](https://github.com/harthur/color-string/pull/17)) 15 | 16 | --- 17 | 18 | Check out commit logs for earlier releases 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Heather Arthur 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # color-string 2 | 3 | > library for parsing and generating CSS color strings. 4 | 5 | ## Install 6 | 7 | ```sh 8 | npm install color-string 9 | ``` 10 | 11 | ## Usage 12 | 13 | ### Parsing 14 | 15 | ```js 16 | colorString.get('#FFF') // {model: 'rgb', value: [255, 255, 255, 1]} 17 | colorString.get('#FFFA') // {model: 'rgb', value: [255, 255, 255, 0.67]} 18 | colorString.get('#FFFFFFAA') // {model: 'rgb', value: [255, 255, 255, 0.67]} 19 | colorString.get('hsl(360, 100%, 50%)') // {model: 'hsl', value: [0, 100, 50, 1]} 20 | colorString.get('hsl(360 100% 50%)') // {model: 'hsl', value: [0, 100, 50, 1]} 21 | colorString.get('hwb(60, 3%, 60%)') // {model: 'hwb', value: [60, 3, 60, 1]} 22 | 23 | colorString.get.rgb('#FFF') // [255, 255, 255, 1] 24 | colorString.get.rgb('blue') // [0, 0, 255, 1] 25 | colorString.get.rgb('rgba(200, 60, 60, 0.3)') // [200, 60, 60, 0.3] 26 | colorString.get.rgb('rgba(200 60 60 / 0.3)') // [200, 60, 60, 0.3] 27 | colorString.get.rgb('rgba(200 60 60 / 30%)') // [200, 60, 60, 0.3] 28 | colorString.get.rgb('rgb(200, 200, 200)') // [200, 200, 200, 1] 29 | colorString.get.rgb('rgb(200 200 200)') // [200, 200, 200, 1] 30 | 31 | colorString.get.hsl('hsl(360, 100%, 50%)') // [0, 100, 50, 1] 32 | colorString.get.hsl('hsl(360 100% 50%)') // [0, 100, 50, 1] 33 | colorString.get.hsl('hsla(360, 60%, 50%, 0.4)') // [0, 60, 50, 0.4] 34 | colorString.get.hsl('hsl(360 60% 50% / 0.4)') // [0, 60, 50, 0.4] 35 | 36 | colorString.get.hwb('hwb(60, 3%, 60%)') // [60, 3, 60, 1] 37 | colorString.get.hwb('hwb(60, 3%, 60%, 0.6)') // [60, 3, 60, 0.6] 38 | 39 | colorString.get.rgb('invalid color string') // null 40 | ``` 41 | 42 | ### Generation 43 | 44 | ```js 45 | colorString.to.hex(255, 255, 255) // "#FFFFFF" 46 | colorString.to.hex(0, 0, 255, 0.4) // "#0000FF66" 47 | colorString.to.hex(0, 0, 255, 0.4) // "#0000FF66" 48 | colorString.to.rgb(255, 255, 255) // "rgb(255, 255, 255)" 49 | colorString.to.rgb(0, 0, 255, 0.4) // "rgba(0, 0, 255, 0.4)" 50 | colorString.to.rgb(0, 0, 255, 0.4) // "rgba(0, 0, 255, 0.4)" 51 | colorString.to.rgb.percent(0, 0, 255) // "rgb(0%, 0%, 100%)" 52 | colorString.to.keyword(255, 255, 0) // "yellow" 53 | colorString.to.hsl(360, 100, 100) // "hsl(360, 100%, 100%)" 54 | colorString.to.hwb(50, 3, 15) // "hwb(50, 3%, 15%)" 55 | ``` 56 | 57 | ## License 58 | 59 | MIT 60 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export type Model = 'rgb' | 'hsl' | 'hwb'; 2 | 3 | export type ColorString = { 4 | get: { 5 | (color: string): {model: Model; value: number[]} | null; 6 | rgb: (color: string) => number[] | null; 7 | hsl: (color: string) => number[] | null; 8 | hwb: (color: string) => number[] | null; 9 | }; 10 | to: { 11 | hex: (r: number, g: number, b: number, a?: number) => string | null; 12 | rgb: { 13 | (r: number, g: number, b: number, a?: number): string | null; 14 | percent: (r: number, g: number, b: number, a?: number) => string | null; 15 | }; 16 | keyword: (r: number, g: number, b: number, a?: number) => string | null; 17 | hsl: (h: number, s: number, l: number, a?: number) => string | null; 18 | hwb: (h: number, w: number, b: number, a?: number) => string | null; 19 | }; 20 | }; 21 | 22 | declare const colorString: ColorString; 23 | export default colorString; 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import colorNames from 'color-name'; 2 | 3 | const reverseNames = Object.create(null); 4 | 5 | // Create a list of reverse color names 6 | for (const name in colorNames) { 7 | if (Object.hasOwn(colorNames, name)) { 8 | reverseNames[colorNames[name]] = name; 9 | } 10 | } 11 | 12 | const cs = { 13 | to: {}, 14 | get: {}, 15 | }; 16 | 17 | cs.get = function (string) { 18 | const prefix = string.slice(0, 3).toLowerCase(); 19 | let value; 20 | let model; 21 | switch (prefix) { 22 | case 'hsl': { 23 | value = cs.get.hsl(string); 24 | model = 'hsl'; 25 | break; 26 | } 27 | 28 | case 'hwb': { 29 | value = cs.get.hwb(string); 30 | model = 'hwb'; 31 | break; 32 | } 33 | 34 | default: { 35 | value = cs.get.rgb(string); 36 | model = 'rgb'; 37 | break; 38 | } 39 | } 40 | 41 | if (!value) { 42 | return null; 43 | } 44 | 45 | return {model, value}; 46 | }; 47 | 48 | cs.get.rgb = function (string) { 49 | if (!string) { 50 | return null; 51 | } 52 | 53 | const abbr = /^#([a-f\d]{3,4})$/i; 54 | const hex = /^#([a-f\d]{6})([a-f\d]{2})?$/i; 55 | const rgba = /^rgba?\(\s*([+-]?\d+)(?=[\s,])\s*(?:,\s*)?([+-]?\d+)(?=[\s,])\s*(?:,\s*)?([+-]?\d+)\s*(?:[,|/]\s*([+-]?[\d.]+)(%?)\s*)?\)$/; 56 | const per = /^rgba?\(\s*([+-]?[\d.]+)%\s*,?\s*([+-]?[\d.]+)%\s*,?\s*([+-]?[\d.]+)%\s*(?:[,|/]\s*([+-]?[\d.]+)(%?)\s*)?\)$/; 57 | const keyword = /^(\w+)$/; 58 | 59 | let rgb = [0, 0, 0, 1]; 60 | let match; 61 | let i; 62 | let hexAlpha; 63 | 64 | if (match = string.match(hex)) { 65 | hexAlpha = match[2]; 66 | match = match[1]; 67 | 68 | for (i = 0; i < 3; i++) { 69 | // https://jsperf.com/slice-vs-substr-vs-substring-methods-long-string/19 70 | const i2 = i * 2; 71 | rgb[i] = Number.parseInt(match.slice(i2, i2 + 2), 16); 72 | } 73 | 74 | if (hexAlpha) { 75 | rgb[3] = Number.parseInt(hexAlpha, 16) / 255; 76 | } 77 | } else if (match = string.match(abbr)) { 78 | match = match[1]; 79 | hexAlpha = match[3]; 80 | 81 | for (i = 0; i < 3; i++) { 82 | rgb[i] = Number.parseInt(match[i] + match[i], 16); 83 | } 84 | 85 | if (hexAlpha) { 86 | rgb[3] = Number.parseInt(hexAlpha + hexAlpha, 16) / 255; 87 | } 88 | } else if (match = string.match(rgba)) { 89 | for (i = 0; i < 3; i++) { 90 | rgb[i] = Number.parseInt(match[i + 1], 10); 91 | } 92 | 93 | if (match[4]) { 94 | rgb[3] = match[5] ? Number.parseFloat(match[4]) * 0.01 : Number.parseFloat(match[4]); 95 | } 96 | } else if (match = string.match(per)) { 97 | for (i = 0; i < 3; i++) { 98 | rgb[i] = Math.round(Number.parseFloat(match[i + 1]) * 2.55); 99 | } 100 | 101 | if (match[4]) { 102 | rgb[3] = match[5] ? Number.parseFloat(match[4]) * 0.01 : Number.parseFloat(match[4]); 103 | } 104 | } else if (match = string.match(keyword)) { 105 | if (match[1] === 'transparent') { 106 | return [0, 0, 0, 0]; 107 | } 108 | 109 | if (!Object.hasOwn(colorNames, match[1])) { 110 | return null; 111 | } 112 | 113 | rgb = colorNames[match[1]]; 114 | rgb[3] = 1; 115 | 116 | return rgb; 117 | } else { 118 | return null; 119 | } 120 | 121 | for (i = 0; i < 3; i++) { 122 | rgb[i] = clamp(rgb[i], 0, 255); 123 | } 124 | 125 | rgb[3] = clamp(rgb[3], 0, 1); 126 | 127 | return rgb; 128 | }; 129 | 130 | cs.get.hsl = function (string) { 131 | if (!string) { 132 | return null; 133 | } 134 | 135 | const hsl = /^hsla?\(\s*([+-]?(?:\d{0,3}\.)?\d+)(?:deg)?\s*,?\s*([+-]?[\d.]+)%\s*,?\s*([+-]?[\d.]+)%\s*(?:[,|/]\s*([+-]?(?=\.\d|\d)(?:0|[1-9]\d*)?(?:\.\d*)?(?:[eE][+-]?\d+)?)\s*)?\)$/; 136 | const match = string.match(hsl); 137 | 138 | if (match) { 139 | const alpha = Number.parseFloat(match[4]); 140 | const h = ((Number.parseFloat(match[1]) % 360) + 360) % 360; 141 | const s = clamp(Number.parseFloat(match[2]), 0, 100); 142 | const l = clamp(Number.parseFloat(match[3]), 0, 100); 143 | const a = clamp(Number.isNaN(alpha) ? 1 : alpha, 0, 1); 144 | 145 | return [h, s, l, a]; 146 | } 147 | 148 | return null; 149 | }; 150 | 151 | cs.get.hwb = function (string) { 152 | if (!string) { 153 | return null; 154 | } 155 | 156 | const hwb = /^hwb\(\s*([+-]?\d{0,3}(?:\.\d+)?)(?:deg)?\s*,\s*([+-]?[\d.]+)%\s*,\s*([+-]?[\d.]+)%\s*(?:,\s*([+-]?(?=\.\d|\d)(?:0|[1-9]\d*)?(?:\.\d*)?(?:[eE][+-]?\d+)?)\s*)?\)$/; 157 | const match = string.match(hwb); 158 | 159 | if (match) { 160 | const alpha = Number.parseFloat(match[4]); 161 | const h = ((Number.parseFloat(match[1]) % 360) + 360) % 360; 162 | const w = clamp(Number.parseFloat(match[2]), 0, 100); 163 | const b = clamp(Number.parseFloat(match[3]), 0, 100); 164 | const a = clamp(Number.isNaN(alpha) ? 1 : alpha, 0, 1); 165 | return [h, w, b, a]; 166 | } 167 | 168 | return null; 169 | }; 170 | 171 | cs.to.hex = function (...rgba) { 172 | return ( 173 | '#' + 174 | hexDouble(rgba[0]) + 175 | hexDouble(rgba[1]) + 176 | hexDouble(rgba[2]) + 177 | (rgba[3] < 1 178 | ? (hexDouble(Math.round(rgba[3] * 255))) 179 | : '') 180 | ); 181 | }; 182 | 183 | cs.to.rgb = function (...rgba) { 184 | return rgba.length < 4 || rgba[3] === 1 185 | ? 'rgb(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ')' 186 | : 'rgba(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ', ' + rgba[3] + ')'; 187 | }; 188 | 189 | cs.to.rgb.percent = function (...rgba) { 190 | const r = Math.round(rgba[0] / 255 * 100); 191 | const g = Math.round(rgba[1] / 255 * 100); 192 | const b = Math.round(rgba[2] / 255 * 100); 193 | 194 | return rgba.length < 4 || rgba[3] === 1 195 | ? 'rgb(' + r + '%, ' + g + '%, ' + b + '%)' 196 | : 'rgba(' + r + '%, ' + g + '%, ' + b + '%, ' + rgba[3] + ')'; 197 | }; 198 | 199 | cs.to.hsl = function (...hsla) { 200 | return hsla.length < 4 || hsla[3] === 1 201 | ? 'hsl(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%)' 202 | : 'hsla(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%, ' + hsla[3] + ')'; 203 | }; 204 | 205 | // Hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax 206 | // (hwb have alpha optional & 1 is default value) 207 | cs.to.hwb = function (...hwba) { 208 | let a = ''; 209 | if (hwba.length >= 4 && hwba[3] !== 1) { 210 | a = ', ' + hwba[3]; 211 | } 212 | 213 | return 'hwb(' + hwba[0] + ', ' + hwba[1] + '%, ' + hwba[2] + '%' + a + ')'; 214 | }; 215 | 216 | cs.to.keyword = function (...rgb) { 217 | return reverseNames[rgb.slice(0, 3)]; 218 | }; 219 | 220 | // Helpers 221 | function clamp(number_, min, max) { 222 | return Math.min(Math.max(min, number_), max); 223 | } 224 | 225 | function hexDouble(number_) { 226 | const string_ = Math.round(number_).toString(16).toUpperCase(); 227 | return (string_.length < 2) ? '0' + string_ : string_; 228 | } 229 | 230 | export default cs; 231 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectType} from 'tsd'; 2 | import colorString, {type Model} from './index.js'; 3 | 4 | type GetColorResult = {model: Model; value: number[]} | null; 5 | type GetSpecificTypeResult = number[] | null; 6 | 7 | type ToColorResult = string | null; 8 | 9 | expectType(colorString.get('#FFF')); 10 | expectType(colorString.get('#FFFA')); 11 | expectType(colorString.get('hsl(360, 100%, 50%)')); 12 | expectType(colorString.get('hsl(360 100% 50%)')); 13 | expectType(colorString.get('hwb(60, 3%, 60%)')); 14 | 15 | expectType(colorString.get.rgb('#FFF')); 16 | expectType(colorString.get.rgb('blue')); 17 | expectType(colorString.get.rgb('#FFF')); 18 | expectType(colorString.get.rgb('blue')); 19 | expectType(colorString.get.rgb('rgba(200, 60, 60, 0.3)')); 20 | expectType(colorString.get.rgb('rgba(200 60 60 / 0.3)')); 21 | expectType(colorString.get.rgb('rgba(200 60 60 / 30%)')); 22 | expectType(colorString.get.rgb('rgb(200, 200, 200)')); 23 | expectType(colorString.get.rgb('rgb(200 200 200)')); 24 | 25 | expectType(colorString.get.hsl('hsl(360, 100%, 50%)')); 26 | expectType(colorString.get.hsl('hsl(360 100% 50%)')); 27 | expectType(colorString.get.hsl('hsla(360, 60%, 50%, 0.4)')); 28 | expectType(colorString.get.hsl('hsl(360 60% 50% / 0.4)')); 29 | 30 | expectType(colorString.get.hwb('hwb(60, 3%, 60%)')); 31 | expectType(colorString.get.hwb('hwb(60, 3%, 60%, 0.6)')); 32 | 33 | expectType(colorString.get.rgb('invalid color string')); 34 | 35 | expectType(colorString.to.hex(255, 255, 255)); 36 | expectType(colorString.to.hex(0, 0, 255, 0.4)); 37 | expectType(colorString.to.hex(0, 0, 255, 0.4)); 38 | expectType(colorString.to.rgb(255, 255, 255)); 39 | expectType(colorString.to.rgb(0, 0, 255, 0.4)); 40 | expectType(colorString.to.rgb(0, 0, 255, 0.4)); 41 | expectType(colorString.to.rgb.percent(0, 0, 255)); 42 | expectType(colorString.to.keyword(255, 255, 0)); 43 | expectType(colorString.to.hsl(360, 100, 100)); 44 | expectType(colorString.to.hwb(50, 3, 15)); 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "color-string", 3 | "description": "Parser and generator for CSS color strings", 4 | "version": "2.0.1", 5 | "author": "Heather Arthur ", 6 | "contributors": [ 7 | "Maxime Thirouin", 8 | "Dyma Ywanov ", 9 | "Josh Junon" 10 | ], 11 | "repository": "Qix-/color-string", 12 | "type": "module", 13 | "exports": "./index.js", 14 | "types": "./index.d.ts", 15 | "engines": { 16 | "node": ">=18" 17 | }, 18 | "scripts": { 19 | "test": "xo && tsd && node test.js" 20 | }, 21 | "license": "MIT", 22 | "files": [ 23 | "index.js", 24 | "index.d.ts" 25 | ], 26 | "xo": { 27 | "rules": { 28 | "no-cond-assign": 0, 29 | "operator-linebreak": 0, 30 | "@typescript-eslint/ban-types": 0 31 | } 32 | }, 33 | "dependencies": { 34 | "color-name": "^2.0.0" 35 | }, 36 | "devDependencies": { 37 | "tsd": "^0.31.2", 38 | "xo": "^0.60.0" 39 | }, 40 | "keywords": [ 41 | "color", 42 | "colour", 43 | "rgb", 44 | "css" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import string from './index.js'; 3 | 4 | function normalizeAlpha(result) { 5 | if (result.model === 'rgb' && result.value.length >= 4) { 6 | result.value[3] = result.value[3].toFixed(2); 7 | } else if (result.length >= 4) { 8 | result[3] = result[3].toFixed(2); 9 | } 10 | 11 | return result; 12 | } 13 | 14 | assert.deepEqual(string.get.rgb('#fef'), [255, 238, 255, 1]); 15 | assert.deepEqual(string.get.rgb('#fffFEF'), [255, 255, 239, 1]); 16 | assert.deepEqual(string.get.rgb('rgb(244, 233, 100)'), [244, 233, 100, 1]); 17 | assert.deepEqual(string.get.rgb('rgb(244 233 100)'), [244, 233, 100, 1]); 18 | assert.deepEqual(string.get.rgb('rgb(100%, 30%, 90%)'), [255, 77, 229, 1]); 19 | assert.deepEqual(string.get.rgb('rgb(100% 30% 90%)'), [255, 77, 229, 1]); 20 | assert.deepEqual(string.get.rgb('transparent'), [0, 0, 0, 0]); 21 | assert.deepEqual(string.get.hsl('hsl(240, 100%, 50.5%)'), [240, 100, 50.5, 1]); 22 | assert.deepEqual(string.get.hsl('hsl(240 100% 50.5%)'), [240, 100, 50.5, 1]); 23 | assert.deepEqual(string.get.hsl('hsl(240deg, 100%, 50.5%)'), [240, 100, 50.5, 1]); 24 | assert.deepEqual(string.get.hsl('hsl(240deg 100% 50.5%)'), [240, 100, 50.5, 1]); 25 | assert.deepEqual(string.get.hwb('hwb(240, 100%, 50.5%)'), [240, 100, 50.5, 1]); 26 | assert.deepEqual(string.get.hwb('hwb(240deg, 100%, 50.5%)'), [240, 100, 50.5, 1]); 27 | 28 | // Generic .get() 29 | assert.deepEqual(string.get('invalid'), null); 30 | assert.deepEqual(string.get('#fef'), {model: 'rgb', value: [255, 238, 255, 1]}); 31 | assert.deepEqual(string.get('#fffFEF'), {model: 'rgb', value: [255, 255, 239, 1]}); 32 | assert.deepEqual(string.get('#fffFEFff'), {model: 'rgb', value: [255, 255, 239, 1]}); 33 | assert.deepEqual(string.get('#fffFEF00'), {model: 'rgb', value: [255, 255, 239, 0]}); 34 | assert.deepEqual(normalizeAlpha(string.get('#fffFEFa9')), {model: 'rgb', value: [255, 255, 239, '0.66']}); 35 | assert.deepEqual(string.get('rgb(244, 233, 100)'), {model: 'rgb', value: [244, 233, 100, 1]}); 36 | assert.deepEqual(string.get('rgb(244 233 100)'), {model: 'rgb', value: [244, 233, 100, 1]}); 37 | assert.deepEqual(string.get('rgb(100%, 30%, 90%)'), {model: 'rgb', value: [255, 77, 229, 1]}); 38 | assert.deepEqual(string.get('rgb(100% 30% 90%)'), {model: 'rgb', value: [255, 77, 229, 1]}); 39 | assert.deepEqual(string.get('transparent'), {model: 'rgb', value: [0, 0, 0, 0]}); 40 | assert.deepEqual(string.get('hsl(240, 100%, 50.5%)'), {model: 'hsl', value: [240, 100, 50.5, 1]}); 41 | assert.deepEqual(string.get('hsl(-480, 100%, 50.5%)'), {model: 'hsl', value: [240, 100, 50.5, 1]}); 42 | assert.deepEqual(string.get('hsl(240 100% 50.5%)'), {model: 'hsl', value: [240, 100, 50.5, 1]}); 43 | assert.deepEqual(string.get('hsl(240deg, 100%, 50.5%)'), {model: 'hsl', value: [240, 100, 50.5, 1]}); 44 | assert.deepEqual(string.get('hsl(240deg 100% 50.5%)'), {model: 'hsl', value: [240, 100, 50.5, 1]}); 45 | assert.deepEqual(string.get('hwb(240, 100%, 50.5%)'), {model: 'hwb', value: [240, 100, 50.5, 1]}); 46 | assert.deepEqual(string.get('hwb(240deg, 100%, 50.5%)'), {model: 'hwb', value: [240, 100, 50.5, 1]}); 47 | 48 | // Invalid generic .get() calls 49 | assert.deepEqual(string.get('hsla(250, 100%, 50%, 50%)'), null); 50 | assert.deepEqual(string.get('hsl(250 100% 50% / 50%)'), null); 51 | assert.deepEqual(string.get('rgba(250, 100%, 50%, 50%)'), null); 52 | assert.deepEqual(string.get('333333'), null); 53 | assert.strictEqual(string.get('#1'), null); 54 | assert.strictEqual(string.get('#f'), null); 55 | assert.strictEqual(string.get('#4f'), null); 56 | assert.strictEqual(string.get('#45ab4'), null); 57 | assert.strictEqual(string.get('#45ab45e'), null); 58 | assert.strictEqual(string.get('rgb()'), null); 59 | assert.strictEqual(string.get('rgb(10)'), null); 60 | assert.strictEqual(string.get('rgb(10, 2)'), null); 61 | assert.strictEqual(string.get('rgb(10, 2, 2348723dskjfs)'), null); 62 | assert.strictEqual(string.get('rgb(10%)'), null); 63 | assert.strictEqual(string.get('rgb(10%, 2%)'), null); 64 | assert.strictEqual(string.get('rgb(10%, 2%, 2348723%dskjfs)'), null); 65 | assert.strictEqual(string.get('rgb(10%, 2%, 2348723dskjfs%)'), null); 66 | assert.strictEqual(string.get('rgb(10$,3)'), null); 67 | assert.strictEqual(string.get('rgba(10, 3)'), null); 68 | 69 | // With sign 70 | assert.deepEqual(string.get.rgb('rgb(-244, +233, -100)'), [0, 233, 0, 1]); 71 | assert.deepEqual(string.get.rgb('rgb(-244 +233 -100)'), [0, 233, 0, 1]); 72 | assert.deepEqual(string.get.hsl('hsl(+240, 100%, 50.5%)'), [240, 100, 50.5, 1]); 73 | assert.deepEqual(string.get.hsl('hsl(+240 100% 50.5%)'), [240, 100, 50.5, 1]); 74 | assert.deepEqual(string.get.rgb('rgba(200, +20, -233, -0.0)'), [200, 20, 0, 0]); 75 | assert.deepEqual(string.get.rgb('rgba(200 +20 -233 / -0.0)'), [200, 20, 0, 0]); 76 | assert.deepEqual(string.get.rgb('rgba(200, +20, -233, -0.0)'), [200, 20, 0, 0]); 77 | assert.deepEqual(string.get.rgb('rgba(200 +20 -233 / -0.0)'), [200, 20, 0, 0]); 78 | assert.deepEqual(string.get.hsl('hsla(+200, 100%, 50%, -0.2)'), [200, 100, 50, 0]); 79 | assert.deepEqual(string.get.hsl('hsla(+200, 100%, 50%, -1e-7)'), [200, 100, 50, 0]); 80 | assert.deepEqual(string.get.hsl('hsl(+200 100% 50% / -0.2)'), [200, 100, 50, 0]); 81 | assert.deepEqual(string.get.hsl('hsl(+200 100% 50% / -1e-7)'), [200, 100, 50, 0]); 82 | assert.deepEqual(string.get.hsl('hsl(+200 100% 50% / -2.e7)'), [200, 100, 50, 0]); 83 | assert.deepEqual(string.get.hsl('hsl(+200 100% 50% / +1e7)'), [200, 100, 50, 1]); 84 | assert.deepEqual(string.get.hsl('hsl(+200 100% 50% / 127.88e4)'), [200, 100, 50, 1]); 85 | assert.deepEqual(string.get.hsl('hsl(+200 100% 50% / 0.2e3)'), [200, 100, 50, 1]); 86 | assert.deepEqual(string.get.hsl('hsl(+200 100% 50% / .1e-4)'), [200, 100, 50, 1e-5]); 87 | assert.deepEqual(string.get.hsl('hsla(-10.0, 100%, 50%, -0.2)'), [350, 100, 50, 0]); 88 | assert.deepEqual(string.get.hsl('hsl(-10.0 100% 50% / -0.2)'), [350, 100, 50, 0]); 89 | assert.deepEqual(string.get.hsl('hsla(.5, 100%, 50%, -0.2)'), [0.5, 100, 50, 0]); 90 | assert.deepEqual(string.get.hsl('hsl(.5 100% 50% / -0.2)'), [0.5, 100, 50, 0]); 91 | assert.deepEqual(string.get.hwb('hwb(+240, 100%, 50.5%)'), [240, 100, 50.5, 1]); 92 | assert.deepEqual(string.get.hwb('hwb(-240deg, 100%, 50.5%)'), [120, 100, 50.5, 1]); 93 | assert.deepEqual(string.get.hwb('hwb(-240deg, 100%, 50.5%, +0.6)'), [120, 100, 50.5, 0.6]); 94 | assert.deepEqual(string.get.hwb('hwb(-240deg, 100%, 50.5%, +1e-7)'), [120, 100, 50.5, 1e-7]); 95 | assert.deepEqual(string.get.hwb('hwb(-240deg, 100%, 50.5%, -2.e7)'), [120, 100, 50.5, 0]); 96 | assert.deepEqual(string.get.hwb('hwb(-240deg, 100%, 50.5%, +1e7)'), [120, 100, 50.5, 1]); 97 | assert.deepEqual(string.get.hwb('hwb(-240deg, 100%, 50.5%, +1e7)'), [120, 100, 50.5, 1]); 98 | assert.deepEqual(string.get.hwb('hwb(-240deg, 100%, 50.5%, 127.88e4)'), [120, 100, 50.5, 1]); 99 | assert.deepEqual(string.get.hwb('hwb(-240deg, 100%, 50.5%, 0.2e3)'), [120, 100, 50.5, 1]); 100 | assert.deepEqual(string.get.hwb('hwb(-240deg, 100%, 50.5%, .1e-4)'), [120, 100, 50.5, 1e-5]); 101 | assert.deepEqual(string.get.hwb('hwb(10.0deg, 100%, 50.5%)'), [10, 100, 50.5, 1]); 102 | assert.deepEqual(string.get.hwb('hwb(-.5, 100%, 50.5%)'), [359.5, 100, 50.5, 1]); 103 | assert.deepEqual(string.get.hwb('hwb(-10.0deg, 100%, 50.5%, +0.6)'), [350, 100, 50.5, 0.6]); 104 | 105 | // Subsequent return values should not change array 106 | assert.deepEqual(string.get.rgb('blue'), [0, 0, 255, 1]); 107 | assert.deepEqual(string.get.rgb('blue'), [0, 0, 255, 1]); 108 | 109 | // Alpha 110 | assert.deepEqual(normalizeAlpha(string.get.rgb('#fffa')), [255, 255, 255, '0.67']); 111 | assert.deepEqual(string.get.rgb('#c814e933'), [200, 20, 233, 0.2]); 112 | assert.deepEqual(string.get.rgb('#c814e900'), [200, 20, 233, 0]); 113 | assert.deepEqual(string.get.rgb('#c814e9ff'), [200, 20, 233, 1]); 114 | assert.deepEqual(string.get.rgb('rgba(200, 20, 233, 0.2)'), [200, 20, 233, 0.2]); 115 | assert.deepEqual(string.get.rgb('rgba(200 20 233 / 0.2)'), [200, 20, 233, 0.2]); 116 | assert.deepEqual(string.get.rgb('rgba(200 20 233 / 20%)'), [200, 20, 233, 0.2]); 117 | assert.deepEqual(string.get.rgb('rgba(200, 20, 233, 0)'), [200, 20, 233, 0]); 118 | assert.deepEqual(string.get.rgb('rgba(200 20 233 / 0)'), [200, 20, 233, 0]); 119 | assert.deepEqual(string.get.rgb('rgba(200 20 233 / 0%)'), [200, 20, 233, 0]); 120 | assert.deepEqual(string.get.rgb('rgba(100%, 30%, 90%, 0.2)'), [255, 77, 229, 0.2]); 121 | assert.deepEqual(string.get.rgb('rgba(100% 30% 90% / 0.2)'), [255, 77, 229, 0.2]); 122 | assert.deepEqual(string.get.rgb('rgba(100% 30% 90% / 20%)'), [255, 77, 229, 0.2]); 123 | assert.deepEqual(string.get.hsl('hsla(200, 20%, 33%, 0.2)'), [200, 20, 33, 0.2]); 124 | assert.deepEqual(string.get.hsl('hsla(200, 20%, 33%, 1e-7)'), [200, 20, 33, 1e-7]); 125 | assert.deepEqual(string.get.hsl('hsl(200 20% 33% / 0.2)'), [200, 20, 33, 0.2]); 126 | assert.deepEqual(string.get.hsl('hsl(200 20% 33% / 1e-7)'), [200, 20, 33, 1e-7]); 127 | assert.deepEqual(string.get.hwb('hwb(200, 20%, 33%, 0.2)'), [200, 20, 33, 0.2]); 128 | assert.deepEqual(string.get.hwb('hwb(200, 20%, 33%, 1e-7)'), [200, 20, 33, 1e-7]); 129 | 130 | // No alpha 131 | assert.deepEqual(string.get.rgb('#fef'), [255, 238, 255, 1]); 132 | assert.deepEqual(string.get.rgb('rgba(200, 20, 233)'), [200, 20, 233, 1]); 133 | assert.deepEqual(string.get.rgb('rgba(200 20 233)'), [200, 20, 233, 1]); 134 | assert.deepEqual(string.get.hsl('hsl(240, 100%, 50.5%)'), [240, 100, 50.5, 1]); 135 | assert.deepEqual(string.get.hsl('hsl(240 100% 50.5%)'), [240, 100, 50.5, 1]); 136 | assert.deepEqual(string.get.rgb('rgba(0, 0, 0, 0)'), [0, 0, 0, 0]); 137 | assert.deepEqual(string.get.rgb('rgba(0 0 0 / 0)'), [0, 0, 0, 0]); 138 | assert.deepEqual(string.get.hsl('hsla(0, 0%, 0%, 0)'), [0, 0, 0, 0]); 139 | assert.deepEqual(string.get.hsl('hsl(0 0% 0% / 0)'), [0, 0, 0, 0]); 140 | assert.deepEqual(string.get.hsl('hsl(0deg 0% 0% / 0)'), [0, 0, 0, 0]); 141 | assert.deepEqual(string.get.hwb('hwb(400, 10%, 200%, 0)'), [40, 10, 100, 0]); 142 | 143 | // Range 144 | assert.deepEqual(string.get.rgb('rgba(300, 600, 100, 3)'), [255, 255, 100, 1]); 145 | assert.deepEqual(string.get.rgb('rgba(300 600 100 / 3)'), [255, 255, 100, 1]); 146 | assert.deepEqual(string.get.rgb('rgba(8000%, 100%, 333%, 88)'), [255, 255, 255, 1]); 147 | assert.deepEqual(string.get.rgb('rgba(8000% 100% 333% / 88)'), [255, 255, 255, 1]); 148 | assert.deepEqual(string.get.hsl('hsla(400, 10%, 200%, 10)'), [40, 10, 100, 1]); 149 | assert.deepEqual(string.get.hsl('hsl(400 10% 200% / 10)'), [40, 10, 100, 1]); 150 | assert.deepEqual(string.get.hwb('hwb(400, 10%, 200%, 10)'), [40, 10, 100, 1]); 151 | 152 | // Invalid 153 | assert.strictEqual(string.get.rgb('yellowblue'), null); 154 | assert.strictEqual(string.get.rgb('hsl(100, 10%, 10%)'), null); 155 | assert.strictEqual(string.get.rgb('hsl(100 10% 10%)'), null); 156 | assert.strictEqual(string.get.rgb('hwb(100, 10%, 10%)'), null); 157 | assert.strictEqual(string.get.rgb('rgb(123, 255, 9)1234'), null); 158 | assert.strictEqual(string.get.rgb('rgb(123 255 9)1234'), null); 159 | assert.strictEqual(string.get.rgb('333333'), null); 160 | assert.strictEqual(string.get.rgb('1'), null); 161 | assert.strictEqual(string.get.rgb('1892371923879'), null); 162 | assert.strictEqual(string.get.rgb('444'), null); 163 | assert.strictEqual(string.get.rgb('#1'), null); 164 | assert.strictEqual(string.get.rgb('#f'), null); 165 | assert.strictEqual(string.get.rgb('#4f'), null); 166 | assert.strictEqual(string.get.rgb('#45ab4'), null); 167 | assert.strictEqual(string.get.rgb('#45ab45e'), null); 168 | assert.strictEqual(string.get.hsl('hsl(41, 50%, 45%)1234'), null); 169 | assert.strictEqual(string.get.hsl('hsl(41 50% 45%)1234'), null); 170 | assert.strictEqual(string.get.hsl('hsl(41 50% 45% / 3)1234'), null); 171 | assert.strictEqual(string.get.hsl('hsl(41 50% 45% / 1e)'), null); 172 | assert.strictEqual(string.get.hsl('hsl(41 50% 45% / e)'), null); 173 | assert.strictEqual(string.get.hsl('hsl(41 50% 45% / 0e-)'), null); 174 | assert.strictEqual(string.get.hsl('hsl(41 50% 45% / 0e+)'), null); 175 | assert.strictEqual(string.get.hsl('hsl(41 50% 45% / +000e33)'), null); 176 | assert.strictEqual(string.get.hwb('hwb(240, 100%, 1e'), null); 177 | assert.strictEqual(string.get.hwb('hwb(240, 100%, e'), null); 178 | assert.strictEqual(string.get.hwb('hwb(240, 100%, 0e-'), null); 179 | assert.strictEqual(string.get.hwb('hwb(240, 100%, 0e+'), null); 180 | assert.strictEqual(string.get.hwb('hwb(240, 100%, +000e33'), null); 181 | 182 | // Generators 183 | assert.equal(string.to.hex(255, 10, 35), '#FF0A23'); 184 | assert.equal(string.to.hex(255, 10, 35, 1), '#FF0A23'); 185 | 186 | assert.equal(string.to.rgb(255, 10, 35), 'rgb(255, 10, 35)'); 187 | assert.equal(string.to.rgb(255, 10, 35, 0.3), 'rgba(255, 10, 35, 0.3)'); 188 | 189 | assert.equal(string.to.rgb.percent(255, 10, 35), 'rgb(100%, 4%, 14%)'); 190 | 191 | assert.equal(string.to.rgb.percent(255, 10, 35), 'rgb(100%, 4%, 14%)'); 192 | assert.equal(string.to.rgb.percent(255, 10, 35, 0.3), 'rgba(100%, 4%, 14%, 0.3)'); 193 | 194 | assert.equal(string.to.hsl(280, 40, 60), 'hsl(280, 40%, 60%)'); 195 | assert.equal(string.to.hsl(280, 40, 60, 0.3), 'hsla(280, 40%, 60%, 0.3)'); 196 | 197 | assert.equal(string.to.hwb(280, 40, 60), 'hwb(280, 40%, 60%)'); 198 | assert.equal(string.to.hwb(280, 40, 60, 0.3), 'hwb(280, 40%, 60%, 0.3)'); 199 | 200 | assert.equal(string.to.keyword(255, 255, 0), 'yellow'); 201 | assert.equal(string.to.keyword(100, 255, 0), undefined); 202 | 203 | // Make sure .get() doesn't return object prototype values (regression test, #44) 204 | Object.keys(Object.getOwnPropertyDescriptors(Object.prototype)) 205 | .map(property => assert.deepStrictEqual([property, string.get(property)], [property, null])); 206 | 207 | // Make sure writing decimal values as hex doesn't cause bizarre output (regression test, #25) 208 | assert.equal(string.to.hex(44.2, 83.8, 44), '#2C542C'); 209 | --------------------------------------------------------------------------------