Comparing version 2.1.1 to 3.0.0-0
{ | ||
"name": "colorizr", | ||
"version": "2.1.1", | ||
"version": "3.0.0-0", | ||
"description": "Manipulate colors like a boss", | ||
@@ -20,50 +20,68 @@ "author": "Gil Barbara <gilbarbara@gmail.com>", | ||
}, | ||
"main": "lib/index.js", | ||
"module": "esm/index.js", | ||
"main": "./dist/index.js", | ||
"module": "./dist/index.mjs", | ||
"exports": { | ||
".": { | ||
"import": "./dist/index.mjs", | ||
"require": "./dist/index.js" | ||
} | ||
}, | ||
"files": [ | ||
"lib", | ||
"esm", | ||
"dist", | ||
"src" | ||
], | ||
"types": "lib/index.d.ts", | ||
"types": "dist/index.d.ts", | ||
"sideEffects": false, | ||
"devDependencies": { | ||
"@gilbarbara/eslint-config": "^0.2.6", | ||
"@gilbarbara/prettier-config": "^0.1.0", | ||
"@gilbarbara/tsconfig": "^0.1.1", | ||
"@size-limit/preset-small-lib": "^8.0.0", | ||
"@types/jest": "^28.1.6", | ||
"husky": "^8.0.1", | ||
"@arethetypeswrong/cli": "^0.16.4", | ||
"@gilbarbara/eslint-config": "^0.8.2", | ||
"@gilbarbara/prettier-config": "^1.0.0", | ||
"@gilbarbara/tsconfig": "^0.2.3", | ||
"@size-limit/preset-small-lib": "^11.1.6", | ||
"@types/node": "^22.8.6", | ||
"@vitest/coverage-v8": "^2.1.4", | ||
"del-cli": "^6.0.0", | ||
"husky": "^9.1.6", | ||
"is-ci-cli": "^2.2.0", | ||
"jest": "^28.1.3", | ||
"jest-watch-typeahead": "^2.0.0", | ||
"repo-tools": "^0.2.2", | ||
"rimraf": "^3.0.2", | ||
"size-limit": "^8.0.0", | ||
"ts-jest": "^28.0.7", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.7.4", | ||
"repo-tools": "^0.3.1", | ||
"size-limit": "^11.1.6", | ||
"ts-node": "^10.9.2", | ||
"tsup": "^8.3.5", | ||
"typescript": "^5.6.3", | ||
"vite-tsconfig-paths": "^5.0.1", | ||
"vitest": "^2.1.4", | ||
"watch-run": "^1.2.5" | ||
}, | ||
"scripts": { | ||
"build": "npm run clean && npm run build:cjs && npm run build:esm", | ||
"build:cjs": "tsc", | ||
"build:esm": "tsc -m es6 --outDir esm", | ||
"watch:cjs": "npm run build:cjs -- -w", | ||
"watch:esm": "npm run build:esm -- -w", | ||
"clean": "rimraf lib && rimraf esm", | ||
"lint": "eslint --ext .ts,.tsx src test", | ||
"typecheck": "tsc --noEmit -p test/tsconfig.json", | ||
"build": "npm run clean && tsup", | ||
"watch": "tsup --watch", | ||
"clean": "del dist/*", | ||
"lint": "eslint --fix src test", | ||
"typecheck": "tsc -p test/tsconfig.json", | ||
"typevalidation": "attw -P", | ||
"test": "is-ci \"test:coverage\" \"test:watch\"", | ||
"test:coverage": "jest --coverage --bail", | ||
"test:watch": "jest --watchAll --verbose", | ||
"test:coverage": "vitest run --coverage", | ||
"test:watch": "vitest watch", | ||
"format": "prettier \"**/*.{js,jsx,json,yml,yaml,css,less,scss,ts,tsx,md,graphql,mdx}\" --write", | ||
"validate": "npm run lint && npm run typecheck && npm run test:coverage && npm run build && npm run size", | ||
"validate": "npm run lint && npm run typecheck && npm run test:coverage && npm run build && npm run size && npm run typevalidation", | ||
"size": "size-limit", | ||
"prepublishOnly": "npm run validate", | ||
"prepare": "husky install" | ||
"prepare": "husky" | ||
}, | ||
"tsup": { | ||
"dts": true, | ||
"entry": [ | ||
"src/index.ts" | ||
], | ||
"format": [ | ||
"cjs", | ||
"esm" | ||
], | ||
"sourcemap": true, | ||
"splitting": false | ||
}, | ||
"eslintConfig": { | ||
"extends": [ | ||
"@gilbarbara/eslint-config" | ||
"@gilbarbara/eslint-config/base", | ||
"@gilbarbara/eslint-config/vitest" | ||
], | ||
@@ -77,12 +95,12 @@ "rules": { | ||
{ | ||
"name": "lib", | ||
"path": "./lib/index.js", | ||
"limit": "8 kB" | ||
"name": "commonjs", | ||
"path": "./dist/index.js", | ||
"limit": "7.1 kB" | ||
}, | ||
{ | ||
"name": "esm", | ||
"path": "./esm/index.js", | ||
"limit": "6 kB" | ||
"path": "./dist/index.mjs", | ||
"limit": "7 kB" | ||
} | ||
] | ||
} |
648
README.md
@@ -5,9 +5,9 @@ # Colorizr | ||
Color conversion, manipulation, comparison, and analysis. | ||
Color conversion, generation, manipulation, comparison, and analysis. | ||
## Highlights | ||
- 🏖 **Easy to use**: Works with HSL and RGB, including CSS strings | ||
- 🏖 **Easy to use**: Works with Hex, HSL, OkLab, OkLCH, and RGB. | ||
- ♿️ **Accessibility:** WCAG analysis and comparison. | ||
- 🛠 **Small:** Less than 6k (gzipped) and zero dependencies. | ||
- 🛠 **Small:** Less than 7k (gzipped) and tree-shakable. | ||
- 🟦 **Modern:** Written in Typescript. | ||
@@ -31,3 +31,3 @@ | ||
Or you can create an instance to access all methods: | ||
Or you can create an instance to use all the methods for the selected color: | ||
@@ -37,20 +37,25 @@ ```typescript | ||
const colorizr = new Colorizr('#ff0044'); | ||
const colorInstance = new Colorizr('#ff0044'); | ||
colorInstance.luminance; // 0.2168 | ||
colorInstance.chroma; // 1 | ||
colorInstance.opacity; // 1 | ||
``` | ||
## Methods | ||
## API | ||
> String inputs accept css values: hex, rgb(a), hsl(a) and named colors. | ||
> String inputs accept css values: hex, hsl, oklab, oklch, rgb, and named colors. | ||
**brightnessDifference(left: string, right: string): number** | ||
_get the brightness difference between 2 colors_ | ||
- [Info](#Info) | ||
- [Manipulators](#Manipulators) | ||
- [Utilities](#Utilities) | ||
- [Converters](#Converters) | ||
- [Generators](#Generators) | ||
- [Comparison](#Comparison) | ||
- [Validators](#Validators) | ||
- [Class](#Class) | ||
```typescript | ||
import { brightnessDifference } from 'colorizr'; | ||
### Info | ||
brightnessDifference('#fff', 'rgb(255, 0, 68)'); // 171.003 | ||
``` | ||
**chroma(input: string): number** | ||
_get the chroma of a color_ | ||
Get the chroma of a color. | ||
@@ -64,4 +69,227 @@ ```typescript | ||
**luminance(input: string): number** | ||
Get the relative brightness according to the [WCAG definition](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef). | ||
Normalized to `0` for black and `1` for white. | ||
```typescript | ||
import { luminance } from 'colorizr'; | ||
luminance('#ff0044'); // 0.2168 | ||
``` | ||
**name(input: string): string** | ||
Get the name of a color. Return the hex code if it can't be named. | ||
```typescript | ||
import { name } from 'colorizr'; | ||
name('#ffc0cb', 10); // pink | ||
name('rgb(176 224 230)'); // 'powderblue' | ||
name('hsl(344 100 50)'); // #ff0044 | ||
``` | ||
**opacity(input: string): string** | ||
Get the opacity of a color. | ||
```typescript | ||
import { opacity } from 'colorizr'; | ||
opacity('#ff0044'); 1 | ||
opacity('rgb(255 0 68 / 90%)'); 0.9 | ||
opacity('hsl(344 100 50 / 60%)'); 0.6 | ||
``` | ||
### Manipulators | ||
**lighten(input: string, alpha: number): string** | ||
Get a color with increased lightness. | ||
```typescript | ||
import { lighten } from 'colorizr'; | ||
lighten('#ff0044', 10); // #ff3369 | ||
``` | ||
**darken(input: string, alpha = 10): string** | ||
Get a color with decreased lightness. | ||
```typescript | ||
import { darken } from 'colorizr'; | ||
darken('#ff0044', 10); // #cc0036 | ||
``` | ||
**saturate(input: string, amount: number): string** | ||
Get a color with increased saturation. | ||
```typescript | ||
import { saturate } from 'colorizr'; | ||
saturate('#ff0044', 10); // #ff0044 (already at the maximum) | ||
saturate('pink', 10); // #ffc0cb | ||
``` | ||
**desaturate(input: string, alpha: number): string** | ||
Get a color with decreased saturation. | ||
```typescript | ||
import { desaturate } from 'colorizr'; | ||
desaturate('#ff0044', 10); // #f20d4a | ||
``` | ||
**invert(input: string): string** | ||
Invert the color. | ||
```typescript | ||
import { invert } from 'colorizr'; | ||
invert('#07e'); // '#0077ee' | ||
invert('#f058'); // '#ff005588' | ||
``` | ||
**rotate(input: string, degrees: number): string** | ||
Get a color with a hue rotated by the specified degrees. | ||
```typescript | ||
import { rotate } from 'colorizr'; | ||
rotate('#ff0044', 30); // #ff3b00 | ||
``` | ||
**opacify(input: string, alpha: number, format?: ColorType): string** | ||
Add opacity to a color | ||
```typescript | ||
import { opacify } from 'colorizr'; | ||
opacify('hsl(344, 100, 50)', 10); // rgba(255, 0, 68, 0.9) | ||
opacify('#ff0044', 50); // hsla(344, 100%, 50%, 0.5) | ||
``` | ||
**transparentize(input: string, alpha: number, format?: ColorType): string** | ||
Increase/decrease the color opacity. | ||
```typescript | ||
import { transparentize } from 'colorizr'; | ||
transparentize('hsl(344, 100, 50)', 10); // rgba(255, 0, 68, 0.9) | ||
transparentize('#ff0044', 50, 'hsl'); // hsla(344, 100%, 50%, 0.5) | ||
``` | ||
### Utilities | ||
**addAlphaToHex(input: string, alpha: number): string** | ||
Add an alpha value to a hex string | ||
```typescript | ||
import { addAlphaToHex } from 'colorizr'; | ||
addAlphaToHex('#ff0044', 0.9); // '#ff0044e6' | ||
addAlphaToHex('#ff0044cc', 0.9); // '#ff0044e6' | ||
``` | ||
**convertAlphaToHex(input: number): string** | ||
Convert an alpha value to a hex value. | ||
```typescript | ||
import { convertAlphaToHex } from 'colorizr'; | ||
convertAlphaToHex(0.5); // '80' | ||
``` | ||
**extractAlphaFromHex(input: string): number** | ||
Extract the alpha value from a hex string | ||
```typescript | ||
import { extractAlphaFromHex } from 'colorizr'; | ||
convertAlphaToHex('#ff004480'); // 0.5 | ||
``` | ||
**extractColorParts(input: string): ExtractColorPartsReturn** | ||
Extract the color parts from a CSS color string. | ||
Hex colors are not supported. | ||
```typescript | ||
type ExtractColorPartsReturn = { | ||
alpha?: number; | ||
model: 'hsl' | 'oklab' | 'oklch' | 'rgb'; | ||
} & Record<string, number>; | ||
extractColorParts('rgb(255 0 68)') // { model: 'rgb', r: 255, g: 0, b: 68 } | ||
extractColorParts('hsl(344 100% 50% / 90%)') // { alpha: 0.9, model: 'hsl', h: 344, g: 100, l: 50 } | ||
``` | ||
**formatCSS(input: HSL | RGB, options?: FormatOptions): string** | ||
Get a css string from a color object. | ||
```typescript | ||
import { formatCSS } from 'colorizr'; | ||
formatCSS({ h: 344, s: 100, l: 50 }, { format: 'rgb' }); // 'rgb(255, 0, 68)' | ||
formatCSS({ r: 255, g: 0, b: 68 }, { alpha: 0.5, format: 'hsl' }); // 'hsla(344, 100%, 50%, 0.5)' | ||
``` | ||
**formatHex(input: string): string** | ||
Format a short hex string of 3 (or 4) digits into 6 (or 8) digits. | ||
```typescript | ||
import { formatHex } from 'colorizr'; | ||
formatHex('#07e'); // '#0077ee' | ||
formatHex('#f058'); // '#ff005588' | ||
``` | ||
**parseCSS(input: string, format?: ColorType): string | HSL | RGB** | ||
Parse a css string to hex, HSL, OKLAB, OKLCH, or RGB. | ||
If the format isn't set, it will return the same format as the input. | ||
```typescript | ||
import { parseCSS } from 'colorizr'; | ||
parseCSS('hsl(344 100% 50%)'); // { h: 344, l: 50, s: 100 } | ||
parseCSS('#ff0044', 'hsl'); // { h: 344, l: 50, s: 100 } | ||
``` | ||
**random(): string** | ||
Get a random color. | ||
```typescript | ||
import { random } from 'colorizr'; | ||
random(); // '#b385e0' | ||
``` | ||
**removeAlphaFromHex(input: string): string** | ||
Remove the alpha value from a hex string | ||
```typescript | ||
import { removeAlphaFromHex } from 'colorizr'; | ||
removeAlphaFromHex('#ff0044cc'); // '#ff0044' | ||
``` | ||
**textColor(input: string): string** | ||
Get a contrasting color (black or white) for the input color. | ||
```typescript | ||
import { textColor } from 'colorizr'; | ||
textColor('#ff0044'); // #ffffff | ||
textColor('#fff800'); // #000000 | ||
``` | ||
### Comparison | ||
**brightnessDifference(left: string, right: string): number** | ||
Get the brightness difference between the two colors. | ||
```typescript | ||
import { brightnessDifference } from 'colorizr'; | ||
brightnessDifference('#fff', 'rgb(255, 0, 68)'); // 171.003 | ||
``` | ||
**colorDifference(left: string, right: string): number** | ||
_get the color difference between 2 colors_ | ||
Get the color difference between the two colors. | ||
@@ -75,3 +303,3 @@ ```typescript | ||
**compare(left: string, right: string): Analysis** | ||
_get the WCAG analysis for two colors_ | ||
Get the WCAG analysis between two colors. | ||
@@ -83,3 +311,3 @@ ```typescript | ||
{ | ||
({ | ||
"brightnessDifference": 171.003, | ||
@@ -93,7 +321,7 @@ "colorDifference": 442, | ||
"normalAAA": false, | ||
} | ||
}) | ||
``` | ||
**contrast(left: string, right: string): number** | ||
_get the WCAG contrast ratio between 2 colors_ | ||
Get the WCAG contrast ratio between two colors. | ||
@@ -105,62 +333,90 @@ ```typescript | ||
``` | ||
### Generators | ||
**darken(input: string, amount = 10): string** | ||
_get a color with decreased lightness_ | ||
**palette(input: string, options?: PaletteOptions): string[]** | ||
Generate a palette of colors. | ||
```typescript | ||
import { darken } from 'colorizr'; | ||
import { palette } from 'colorizr'; | ||
darken('#ff0044', 10); // #cc0036 | ||
palette('#ff0044'); | ||
// ['#ff0044', '#ff7700', '#88ff00', '#00ff77', '#0088ff', '#7700ff']; | ||
palette('#ff0044', { type: 'monochromatic' }); | ||
// ['#ff99b4', '#ff5582', '#ff1150', '#cc0036', '#880024', '#440012'] | ||
``` | ||
**desaturate(input: string, amount: number): string** | ||
_get a color with decreased saturation_ | ||
**scheme(input: string, type: Scheme): string[]** | ||
Get a color scheme. | ||
```typescript | ||
import { desaturate } from 'colorizr'; | ||
import { scheme } from 'colorizr'; | ||
desaturate('#ff0044', 10); // #f20d4a | ||
const complementary = scheme('rgb(255, 0, 68)'); // ['#ff0044', '#00ffbb'] | ||
const triadic = scheme('#ff0044', 'triadic'); // ['#ff0044', '#44ff00', '#0044ff'] | ||
``` | ||
**fade(input: string, amount: number = 10, output?: ColorTypes = 'rgb'): string** | ||
_get a transparent color_ | ||
**swatch(input: string, variant?: 'up' | 'down'): string[]** | ||
Generate a color swatch with ten shades. | ||
The `variant` can be used to generate a lighter or darker swatch. | ||
```typescript | ||
import { fade } from 'colorizr'; | ||
import { swatch } from 'colorizr'; | ||
fade('hsl(344, 100, 50)', 10); // rgba(255, 0, 68, 0.9) | ||
fade('#ff0044', 50, 'hsl'); // hsla(344, 100%, 50%, 0.5) | ||
const colors = swatch('#ff0044'); | ||
/* [ | ||
"#ffccda", | ||
"#ff99b4", | ||
"#ff668f", | ||
"#ff3369", | ||
"#ff0044", | ||
"#cc0036", | ||
"#990029", | ||
"#66001b", | ||
"#33000e", | ||
"#1a0007", | ||
] */ | ||
``` | ||
**formatCSS(input: HSL | RGB, options?: FormatOptions): string** | ||
_get the css string for a color model object_ | ||
### Converters | ||
**convert(input: string, format: ColorType): string** | ||
Convert a color string from one format to another. | ||
```typescript | ||
import { formatCSS } from 'colorizr'; | ||
import { convert } from 'colorizr'; | ||
formatCSS({ h: 344, s: 100, l: 50 }, { model: 'rgb' }); // 'rgb(255, 0, 68)' | ||
formatCSS({ r: 255, g: 0, b: 68 }, { alpha: 0.5, model: 'hsl' }); // 'hsla(344, 100%, 50%, 0.5)' | ||
convert('#ff0044', 'hsl') // hsl(344 100% 50%) | ||
convert('rgb(255 0 68)', 'oklch') // oklch(63.269% 0.25404 19.90218) | ||
``` | ||
**formatHex(input: string): string** | ||
_format a short hex string of 3 (or 4) digits into 6 (or 8) digits._ | ||
**hex2hsl(input: string): HSL** | ||
Convert HEX to HSL. | ||
```typescript | ||
import { formatHex } from 'colorizr'; | ||
import { hex2hsl } from 'colorizr'; | ||
formatHex('#07e'); // '#0077ee' | ||
formatHex('#f058'); // '#ff005588' | ||
hex2hsl('#ff0044'); // { h: 344, s: 100, l: 50 } | ||
``` | ||
**hex2hsl(input: string): HSL** | ||
_convert a hex string into an HSL object_ | ||
**hex2oklab(input: string, precision?: number): LAB** | ||
Convert HEX to OKLAB. | ||
```typescript | ||
import { hex2hsl } from 'colorizr'; | ||
import { hex2oklab } from 'colorizr'; | ||
hex2hsl('#ff0044'); // { h: 344, s: 100, l: 50 } | ||
hex2oklab('#ff0044'); // { l: 0.63269, a: 0.23887, b: 0.08648 } | ||
``` | ||
**hex2oklch(input: string, precision?: number): LCH** | ||
Convert HEX to OKLCH. | ||
```typescript | ||
import { hex2oklch } from 'colorizr'; | ||
hex2oklch('#ff0044'); // { l: 0.63269, c: 0.25404, h: 19.90218 } | ||
``` | ||
**hex2rgb(input: string): RGB** | ||
_convert a hex string into an RGB object_ | ||
Convert HEX to RGB. | ||
@@ -173,4 +429,4 @@ ```typescript | ||
**hsl2hex(input: HSL): string** | ||
_convert an HSL object into a hex string_ | ||
**hsl2hex(input: HSL | ColorTupple): string** | ||
Convert HSL to HEX. | ||
@@ -181,102 +437,117 @@ ```typescript | ||
hsl2hex({ h: 344, s: 100, l: 50 }); // '#ff0044' | ||
hsl2hex([344, 100, 50]); // '#ff0044' | ||
``` | ||
**hsl2rgb(input: HSL): RGB** | ||
_convert an HSL object into an RGB object_ | ||
**hsl2oklab(input: HSL | ColorTupple, precision?: number): LAB** | ||
Convert HSL to OKLAB. | ||
```typescript | ||
import { hsl2oklab } from 'colorizr'; | ||
hsl2oklab({ h: 344, s: 100, l: 50 }); // { l: 0.63269, a: 0.23887, b: 0.08648 } | ||
hsl2oklab([344, 100, 50]); // { l: 0.63269, a: 0.23887, b: 0.08648 } | ||
``` | ||
**hsl2oklch(input: HSL | ColorTupple, precision?: number): string** | ||
Convert HSL to OKLCH. | ||
```typescript | ||
import { hsl2oklch } from 'colorizr'; | ||
hsl2oklch({ h: 344, s: 100, l: 50 }); // { l: 0.63269, c: 0.25404, h: 19.90218 } | ||
hsl2oklch([344, 100, 50]); // { l: 0.63269, c: 0.25404, h: 19.90218 } | ||
``` | ||
**hsl2rgb(input: HSL | ColorTupple): RGB** | ||
Convert HSL to RGB. | ||
```typescript | ||
import { hsl2rgb } from 'colorizr'; | ||
hsl2rgb({ h: 344, s: 100, l: 50 }); // { r: 255, g: 0, b: 68 } | ||
hsl2rgb([344, 100, 50]); // { r: 255, g: 0, b: 68 } | ||
``` | ||
**isValidColor(input: any): boolean** | ||
_check if the input can be parsed correctly_ | ||
**oklab2hex(input: LAB | ColorTupple): string** | ||
Convert OKLAB to HEX. | ||
```typescript | ||
import { isValidColor } from 'colorizr'; | ||
import { oklab2hex } from 'colorizr'; | ||
isValidColor('#f04'); // true | ||
isValidColor('#ff0044'); // true | ||
isValidColor('#ff004400'); // true | ||
isValidColor('rgb(100, 255, 0)'); // true | ||
isValidColor('hsla(344, 100%, 50%)'); // true | ||
isValidColor('blue'); // true | ||
isValidColor('aliceblue'); // true | ||
isValidColor('#mmff00'); // false | ||
isValidColor('blue-ish'); // false | ||
oklab2hex({ l: 0.63269, a: 0.23887, b: 0.08648 }); // '#ff0044' | ||
oklab2hex([0.63269, 0.23887, 0.08648]); // '#ff0044' | ||
``` | ||
**isValidHex(input: any): boolean** | ||
_check if the input is a valid hex_ | ||
**oklab2hsl(input: LAB | ColorTupple): HSL** | ||
Convert OKLAB to HSL. | ||
```typescript | ||
import { isValidHex } from 'colorizr'; | ||
import { oklab2hsl } from 'colorizr'; | ||
isValidHex('#f04'); // true | ||
oklab2hsl({ l: 0.63269, a: 0.23887, b: 0.08648 }); // { h: 344, s: 100, l: 50 } | ||
oklab2hsl([0.63269, 0.23887, 0.08648]); // { h: 344, s: 100, l: 50 } | ||
``` | ||
**lighten(input: string, amount: number): string** | ||
_get a color with increased lightness_ | ||
**oklab2oklch(input: LAB | ColorTupple, precision?: number): LCH** | ||
Convert OKLAB to OKLCH. | ||
```typescript | ||
import { lighten } from 'colorizr'; | ||
import { oklab2oklch } from 'colorizr'; | ||
lighten('#ff0044', 10); // #ff3369 | ||
oklab2oklch({ l: 0.63269, a: 0.23887, b: 0.08648 }); // { l: 0.63269, c: 0.25404, h: 19.90218 } | ||
oklab2oklch([0.63269, 0.23887, 0.08648]); // { l: 0.63269, c: 0.25404, h: 19.90218 } | ||
``` | ||
**luminance(input: string): number** | ||
_get the relative brightness according to the [WCAG definition](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef). Normalized to `0` for black and `1` for white._ | ||
**oklab2rgb(input: LAB | ColorTupple, precision: number = 0): RGB** | ||
Convert OKLAB to RGB. | ||
```typescript | ||
import { luminance } from 'colorizr'; | ||
import { oklab2rgb } from 'colorizr'; | ||
luminance('#ff0044'); // 0.2168 | ||
oklab2rgb({ l: 0.63269, a: 0.23887, b: 0.08648 }); // { r: 255, g: 0, b: 68 } | ||
oklab2rgb([0.63269, 0.23887, 0.08648]); // { r: 255, g: 0, b: 68 } | ||
``` | ||
**name(input: string): string** | ||
_get the named color. return the hex code if it can't be named_ | ||
**oklch2hex(input: LCH | ColorTupple): string** | ||
Convert OKLCH to HEX. | ||
```typescript | ||
import { name } from 'colorizr'; | ||
import { oklch2hex } from 'colorizr'; | ||
name('#ffc0cb', 10); // pink | ||
name('rgb(176, 224, 230)'); // 'powderblue' | ||
name('hsl(344, 100, 50)'); // #ff0044 | ||
oklch2hex({ l: 0.63269, c: 0.25404, h: 19.90218 }); // '#ff0044' | ||
oklch2hex([0.63269, 0.25404, 19.90218]); // '#ff0044' | ||
``` | ||
**palette(input: string, options?: PaletteOptions): string[]** | ||
_get a palette for a color_ | ||
**oklch2hsl(input: LCH | ColorTupple): HSL** | ||
Convert OKLCH to HSL. | ||
```typescript | ||
import { palette } from 'colorizr'; | ||
import { oklch2hsl } from 'colorizr'; | ||
palette('#ff0044'); | ||
// ['#ff0044', '#ff7700', '#88ff00', '#00ff77', '#0088ff', '#7700ff']; | ||
palette('#ff0044', { type: 'monochromatic' }); | ||
// ['#ff99b4', '#ff5582', '#ff1150', '#cc0036', '#880024', '#440012'] | ||
oklch2hsl({ l: 0.63269, c: 0.25404, h: 19.90218 }); // { h: 344, s: 100, l: 50 } | ||
oklch2hsl([0.63269, 0.25404, 19.90218]); // { h: 344, s: 100, l: 50 } | ||
``` | ||
**parseCSS(input: string, output: ColorTypes = 'hex'): string | HSL | RGB** | ||
_parse a css string to hex, hsl, or RGB_ | ||
**oklch2oklab(input: LCH | ColorTupple, precision?: number): LAB** | ||
Convert OKLCH to OKLAB. | ||
```typescript | ||
import { parseCSS } from 'colorizr'; | ||
import { oklch2oklab } from 'colorizr'; | ||
parseCSS('hsl(270 60% 70%)'); // '#b385e0' | ||
parseCSS('#ff0044', 'hsl'); // { h: 344, l: 50, s: 100 } | ||
oklch2oklab({ l: 0.63269, c: 0.25404, h: 19.90218 }); // { l: 0.63269, a: 0.23887, b: 0.08648 } | ||
oklch2oklab([0.63269, 0.25404, 19.90218]); // { l: 0.63269, a: 0.23887, b: 0.08648 } | ||
``` | ||
**random(): string** | ||
_get a random color_ | ||
**oklch2rgb(input: LCH | ColorTupple, precision: number = 0): RGB** | ||
Convert OKLCH to RGB. | ||
```typescript | ||
import { random } from 'colorizr'; | ||
import { oklch2rgb } from 'colorizr'; | ||
random(); // '#b385e0' | ||
oklch2rgb({ l: 0.63269, c: 0.25404, h: 19.90218 }); // { r: 255, g: 0, b: 68 } | ||
oklch2rgb([0.63269, 0.25404, 19.90218]); // { r: 255, g: 0, b: 68 } | ||
``` | ||
**rgb2hex(input: RGB | RGBArray): string** | ||
_convert an RGB object into a hex string_ | ||
**rgb2hex(input: RGB | ColorTupple): string** | ||
Convert RGB to HEX. | ||
@@ -286,8 +557,8 @@ ```typescript | ||
rgb2hex({ r: 255, g: 55, b: 75 }); // '#ff374b' | ||
rgb2hex({ r: 255, g: 0, b: 68 }); // '#ff0044' | ||
rgb2hex([255, 0, 68]); // '#ff0044' | ||
``` | ||
**rgb2hsl(input: RGB | RGBArray): HSL** | ||
_convert an RGB object into an HSL object_ | ||
**rgb2hsl(input: RGB | ColorTupple): HSL** | ||
Convert RGB to HSL. | ||
@@ -297,47 +568,97 @@ ```typescript | ||
rgb2hsl({ r: 255, g: 55, b: 75 }); // { h: 354, s: 100, l: 60.78 } | ||
rgb2hsl({ r: 255, g: 0, b: 68 }); // { h: 344, s: 100, l: 50 } | ||
rgb2hsl([255, 0, 68]); // { h: 344, s: 100, l: 50 } | ||
``` | ||
**rotate(input: string, degrees = 15): string** _get a color with changed hue_ | ||
**rgb2oklab(input: RGB | ColorTupple, precision: number): LAB** | ||
Convert RGB to OKLAB. | ||
```typescript | ||
import { rotate } from 'colorizr'; | ||
import { rgb2oklab } from 'colorizr'; | ||
rotate('#ff0044', 30); // #ff3b00 | ||
rgb2oklab({ r: 255, g: 0, b: 68 }); // { l: 0.63269, a: 0.23887, b: 0.08648 } | ||
rgb2oklab([255, 0, 68]); // { l: 0.63269, a: 0.23887, b: 0.08648 } | ||
``` | ||
**saturate(input: string, amount: number): string** | ||
_get a color with increased saturation_ | ||
**rgb2oklch(input: RGB | ColorTupple, precision: number): LCH** | ||
Convert RGB to OKLCH. | ||
```typescript | ||
import { saturate } from 'colorizr'; | ||
import { rgb2oklch } from 'colorizr'; | ||
saturate('#ff0044', 10); // #ff0044 (already at the maximum) | ||
saturate('pink', 10); // #ffc0cb | ||
rgb2oklch({ r: 255, g: 0, b: 68 }); // { l: 0.63269, c: 0.25404, h: 19.90218 } | ||
rgb2oklch([255, 0, 68]); // { l: 0.63269, c: 0.25404, h: 19.90218 } | ||
``` | ||
**scheme(input: string, type: Scheme): string[]** | ||
_get the scheme for a color_ | ||
### Validators | ||
**isValidColor(input: any): boolean** | ||
Check if the input is a valid color. | ||
```typescript | ||
import { scheme } from 'colorizr'; | ||
import { isValidColor } from 'colorizr'; | ||
const complementary = scheme('rgb(255, 0, 68)'); // ['#ff0044', '#00ffbb'] | ||
const triadic = scheme('#ff0044', 'triadic'); // ['#ff0044', '#44ff00', '#0044ff'] | ||
isValidColor('#ff0044'); // true | ||
isValidColor('#ff004400'); // true | ||
isValidColor('hsl(136 100% 50%)'); // true | ||
isValidColor('hsla(344, 100%, 50%, 0.4)'); // true | ||
isValidColor('oklab(70.622% 0.1374 0.14283)'); // true | ||
isValidColor('oklch(47.642% 0.29956 274.93693)'); // true | ||
isValidColor('rgb(255 230 109)'); // true | ||
isValidColor('blue'); // true | ||
isValidColor('aliceblue'); // true | ||
isValidColor('#mmff00'); // false | ||
isValidColor('blue-ish'); // false | ||
``` | ||
**textColor(input: string): string** | ||
_get a contrasting color to use with the text_ | ||
**isHex(input: unknown): boolean** | ||
Check if the input is a valid hex color. | ||
```typescript | ||
import { textColor } from 'colorizr'; | ||
import { isHex } from 'colorizr'; | ||
textColor('#ff0044'); // #ffffff | ||
textColor('#fff800'); // #000000 | ||
isHex('#f04'); // true | ||
isHex('#ff0044'); // true | ||
isHex('#ff0044cc'); // true | ||
``` | ||
## Instance API | ||
**isHSL(input: unknown): boolean** | ||
Check if the input is a valid HSL object. | ||
```typescript | ||
import { isHSL } from 'colorizr'; | ||
isHSL({ h: 344, s: 100, l: 50 }); // true | ||
``` | ||
**isLAB(input: unknown): boolean** | ||
Check if the input is a valid LAB color. | ||
```typescript | ||
import { isLAB } from 'colorizr'; | ||
isLAB({ l: 0.63269, a: 0.23887, b: 0.08648 }); // true | ||
``` | ||
**isLHC(input: unknown): boolean** | ||
Check if the input is a valid LCH color. | ||
```typescript | ||
import { isLHC } from 'colorizr'; | ||
isLHC({ l: 0.63269, c: 0.25404, h: 19.90218 }); // true | ||
``` | ||
**isRGB(input: unknown): boolean** | ||
Check if the input is a valid RGB color. | ||
```typescript | ||
import { isRGB } from 'colorizr'; | ||
isRGB({ r: 255, g: 0, b: 68 }); // true | ||
``` | ||
### Class | ||
```typescript | ||
import Colorizr from 'Colorizr'; | ||
@@ -352,63 +673,94 @@ | ||
### Getters | ||
#### Getters | ||
**colorizr.hex** | ||
_returns the hex_ | ||
Get the hex code. | ||
**colorizr.hsl** | ||
_returns the HSL object_ | ||
Get the HSL object. | ||
**colorizr.oklab** | ||
Get the OKLAB object. | ||
**colorizr.oklch** | ||
Get the OKLCH object. | ||
**colorizr.rgb** | ||
_returns the RGB object_ | ||
Get the RGB object. | ||
**colorizr.hue** | ||
_returns the color hue, between 0 and 360_ | ||
Get the hue (0-360). | ||
**colorizr.saturation** | ||
_returns the color saturation, between 0 and 100_ | ||
Get the saturation (0-100). | ||
**colorizr.lightness** | ||
_returns the color lightness, between 0 and 100_ | ||
Get the lightness (0-100). | ||
**colorizr.red** | ||
_returns the color red level, between 0 and 255_ | ||
Get the red level (0-255). | ||
**colorizr.green** | ||
_returns the color green level, between 0 and 255_ | ||
Get the green level (0-255). | ||
**colorizr.blue** | ||
_returns the color blue level, between 0 and 255_ | ||
Get the blue level (0-255). | ||
**colorizr.luminance** | ||
**colorizr.chroma** | ||
Get the chroma (0-1). | ||
**colorizr.luminance** | ||
Get the luminance (0-1). | ||
**colorizr.opacity** | ||
Get the opacity (0-1). | ||
**colorizr.css** | ||
Get the css string of the same time as the input. | ||
**colorizr.textColor** | ||
Get a contrasting color (black or white). | ||
### Manipulation | ||
#### Manipulation | ||
**colorizr.lighten(percentage = 10)** | ||
**colorizr.lighten(percentage: number)** | ||
Get a lighter color. | ||
**colorizr.darken(percentage = 10)** | ||
**colorizr.darken(percentage: number)** | ||
Get a darker color. | ||
**colorizr.saturate(percentage = 10)** | ||
**colorizr.desaturate(percentage: number)** | ||
Get a desaturated color. | ||
**colorizr.saturate(percentage = 10)** | ||
**colorizr.saturate(percentage: number)** | ||
Get a saturated color. | ||
**colorizr.rotate(degrees = 15)** | ||
**colorizr.rotate(degrees: number)** | ||
Get a color with a hue rotated. | ||
**colorizr.invert()** | ||
**colorizr.invert()** | ||
Get the inverted color. | ||
**colorizr.fade(percentage = 10)** | ||
**colorizr.transparentize(percentage: number)** | ||
Get a faded color. | ||
### Comparison | ||
#### Comparison | ||
**colorizr.compare(color: string)** | ||
_returns an object with the analysis (check the compare output above)_ | ||
Returns an object with the analysis (check the compare format above) | ||
## References | ||
#### Fortmatting | ||
**colorizr.format(type: ColorType, precision?: number)** | ||
Returns the formatted color with the type | ||
## Credits / References | ||
[color.js](https://github.com/color-js/color.js) | ||
[chroma-js](https://gka.github.io/chroma.js/) | ||
[calculating-color-contrast](https://24ways.org/2010/calculating-color-contrast/) | ||
[Colour Contrast Check](https://snook.ca/technical/colour_contrast/colour.html) | ||
[Contrast Checker](https://webaim.org/resources/contrastchecker/) | ||
[Converting Color Spaces in typescript](https://css-tricks.com/converting-color-spaces-in-typescript/) | ||
[Converting Color Spaces in typescript](https://css-tricks.com/converting-color-spaces-in-typescript/) | ||
[A perceptual color space for image processing](https://bottosson.github.io/posts/oklab/) | ||
@@ -1,19 +0,26 @@ | ||
import hex2rgb from './hex2rgb'; | ||
import { invariant, isString, messages, round } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import { MESSAGES, PRECISION } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { round } from '~/modules/utils'; | ||
import { isString } from '~/modules/validators'; | ||
import parseCSS from '~/parse-css'; | ||
/** | ||
* Get the brightness difference between 2 colors. | ||
*/ | ||
export default function brightnessDifference(left: string, right: string): number { | ||
invariant(isString(left), messages.left); | ||
invariant(isString(right), messages.right); | ||
export default function brightnessDifference( | ||
left: string, | ||
right: string, | ||
precision = PRECISION, | ||
): number { | ||
invariant(isString(left), MESSAGES.left); | ||
invariant(isString(right), MESSAGES.right); | ||
const RGBLeft = hex2rgb(parseCSS(left)); | ||
const RGBRight = hex2rgb(parseCSS(right)); | ||
const RGBLeft = parseCSS(left, 'rgb'); | ||
const RGBRight = parseCSS(right, 'rgb'); | ||
const rightY = (RGBRight.r * 299 + RGBRight.g * 587 + RGBRight.b * 114) / 1000; | ||
const leftY = (RGBLeft.r * 299 + RGBLeft.g * 587 + RGBLeft.b * 114) / 1000; | ||
const brightnessLeft = (RGBLeft.r * 299 + RGBLeft.g * 587 + RGBLeft.b * 114) / 1000; | ||
const brightnessRight = (RGBRight.r * 299 + RGBRight.g * 587 + RGBRight.b * 114) / 1000; | ||
return round(Math.abs(rightY - leftY), 4); | ||
return round(Math.abs(brightnessRight - brightnessLeft), precision); | ||
} |
@@ -1,5 +0,8 @@ | ||
import hex2rgb from './hex2rgb'; | ||
import { invariant, isString, messages, round } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { round } from '~/modules/utils'; | ||
import { isString } from '~/modules/validators'; | ||
import parseCSS from '~/parse-css'; | ||
/** | ||
@@ -9,5 +12,5 @@ * Get the chroma of a color. | ||
export default function chroma(input: string): number { | ||
invariant(isString(input), messages.inputString); | ||
invariant(isString(input), MESSAGES.inputString); | ||
const { r, g, b } = hex2rgb(parseCSS(input)); | ||
const { r, g, b } = parseCSS(input, 'rgb'); | ||
@@ -14,0 +17,0 @@ const max = Math.max(r, g, b); |
@@ -1,5 +0,7 @@ | ||
import hex2rgb from './hex2rgb'; | ||
import { invariant, isString, messages } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { isString } from '~/modules/validators'; | ||
import parseCSS from '~/parse-css'; | ||
/** | ||
@@ -9,7 +11,7 @@ * Get the difference between 2 colors. | ||
export default function colorDifference(left: string, right: string): number { | ||
invariant(isString(left), messages.left); | ||
invariant(isString(right), messages.right); | ||
invariant(isString(left), MESSAGES.left); | ||
invariant(isString(right), MESSAGES.right); | ||
const RGBLeft = hex2rgb(parseCSS(left)); | ||
const RGBRight = hex2rgb(parseCSS(right)); | ||
const RGBLeft = parseCSS(left, 'rgb'); | ||
const RGBRight = parseCSS(right, 'rgb'); | ||
@@ -16,0 +18,0 @@ return ( |
@@ -1,7 +0,10 @@ | ||
import getBrightnessDifference from './brightness-difference'; | ||
import getColorDifference from './color-difference'; | ||
import getContrast from './contrast'; | ||
import { invariant, isString, messages } from './modules/utils'; | ||
import { Analysis } from './types'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { isString } from '~/modules/validators'; | ||
import getBrightnessDifference from '~/brightness-difference'; | ||
import getColorDifference from '~/color-difference'; | ||
import getContrast from '~/contrast'; | ||
import { Analysis } from '~/types'; | ||
/** | ||
@@ -11,4 +14,4 @@ * Check 2 colors for WCAG compliance. | ||
export default function compare(left: string, right: string): Analysis { | ||
invariant(isString(left), messages.left); | ||
invariant(isString(right), messages.right); | ||
invariant(isString(left), MESSAGES.left); | ||
invariant(isString(right), MESSAGES.right); | ||
@@ -15,0 +18,0 @@ const colorThreshold = 500; |
@@ -1,5 +0,8 @@ | ||
import getLuminance from './luminance'; | ||
import { invariant, isString, messages, round } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { round } from '~/modules/utils'; | ||
import { isString } from '~/modules/validators'; | ||
import getLuminance from '~/luminance'; | ||
/** | ||
@@ -9,7 +12,7 @@ * Get the color contrast between 2 colors. | ||
export default function contrast(left: string, right: string): number { | ||
invariant(isString(left), messages.left); | ||
invariant(isString(right), messages.right); | ||
invariant(isString(left), MESSAGES.left); | ||
invariant(isString(right), MESSAGES.right); | ||
const LuminanceLeft = getLuminance(parseCSS(left)); | ||
const LuminanceRight = getLuminance(parseCSS(right)); | ||
const LuminanceLeft = getLuminance(left); | ||
const LuminanceRight = getLuminance(right); | ||
@@ -16,0 +19,0 @@ return round( |
@@ -1,8 +0,10 @@ | ||
import updater from './modules/updater'; | ||
import updater from '~/modules/updater'; | ||
import { Amount, ColorType } from '~/types'; | ||
/** | ||
* Decrease color lightness | ||
*/ | ||
export default function darken(input: string, amount = 10): string { | ||
return updater('l', '-')(input, amount); | ||
export default function darken(input: string, amount: Amount, format?: ColorType): string { | ||
return updater('l', '-', format)(input, amount); | ||
} |
@@ -1,8 +0,10 @@ | ||
import updater from './modules/updater'; | ||
import updater from '~/modules/updater'; | ||
import { Amount, ColorType } from '~/types'; | ||
/** | ||
* Decrease color saturation | ||
*/ | ||
export default function desaturate(input: string, amount = 10): string { | ||
return updater('s', '-')(input, amount); | ||
export default function desaturate(input: string, amount: Amount, format?: ColorType) { | ||
return updater('s', '-', format)(input, amount); | ||
} |
@@ -1,33 +0,95 @@ | ||
import hsl2rgb from './hsl2rgb'; | ||
import { invariant, isHSL, isNumber, isPlainObject, isRGB, messages } from './modules/utils'; | ||
import rgb2Hsl from './rgb2hsl'; | ||
import { FormatOptions, HSL, RGB } from './types'; | ||
import { MESSAGES, PRECISION } from '~/modules/constants'; | ||
import { convertAlphaToHex } from '~/modules/hex-utils'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { restrictValues, round } from '~/modules/utils'; | ||
import { isHex, isHSL, isLAB, isLCH, isValidColorModel } from '~/modules/validators'; | ||
export default function formatCSS(input: HSL | RGB, options: FormatOptions = {}): string { | ||
invariant(isPlainObject(input) && (isRGB(input) || isHSL(input)), messages.invalid); | ||
import * as converters from '~/converters'; | ||
import { Alpha, ColorModel, ColorType, HEX, HSL } from '~/types'; | ||
const { alpha, model = isRGB(input) ? 'rgb' : 'hsl' } = options; | ||
const prefix = `${model}${isNumber(alpha) ? 'a' : ''}`; | ||
let color = input; | ||
export interface FormatOptions { | ||
alpha?: Alpha; | ||
/** | ||
* The output color type. | ||
* @default 'rgb' | ||
*/ | ||
format?: ColorType; | ||
/** | ||
* The number of digits of the output. | ||
* @default 5 | ||
*/ | ||
precision?: number; | ||
/** | ||
* The separator between the values. | ||
* oklab and oklch always use space as a separator. | ||
* @default ' ' | ||
*/ | ||
separator?: string; | ||
} | ||
export default function formatCSS<T extends ColorModel | HEX>( | ||
input: T, | ||
options: FormatOptions = {}, | ||
): string { | ||
invariant(isHex(input) || isValidColorModel(input), MESSAGES.invalid); | ||
const { alpha, format = 'rgb', precision = PRECISION, separator: baseSeparator = ' ' } = options; | ||
let value: HSL; | ||
if (isHex(input)) { | ||
value = converters.hex2hsl(input); | ||
} else if (isHSL(input)) { | ||
value = input; | ||
} else if (isLAB(input)) { | ||
value = converters.oklab2hsl(input); | ||
} else if (isLCH(input)) { | ||
value = converters.oklch2hsl(input); | ||
} else { | ||
value = converters.rgb2hsl(input); | ||
} | ||
const opacity = alpha && alpha !== 1 ? `${round(alpha * 100)}%` : null; | ||
let params = []; | ||
let separator = baseSeparator; | ||
if (model === 'rgb') { | ||
if (isHSL(color)) { | ||
color = hsl2rgb(color); | ||
switch (format) { | ||
case 'hsl': { | ||
const { h, s, l } = value; | ||
params = [h, `${s}%`, `${l}%`]; | ||
break; | ||
} | ||
case 'oklab': { | ||
separator = ' '; | ||
const { l, a, b } = restrictValues(converters.hsl2oklab(value), precision); | ||
params = [Math.round(color.r), Math.round(color.g), Math.round(color.b)]; | ||
} else { | ||
if (isRGB(color)) { | ||
color = rgb2Hsl(color); | ||
params = [`${round(l * 100, precision)}%`, a, b]; | ||
break; | ||
} | ||
case 'oklch': { | ||
separator = ' '; | ||
const { l, c, h } = restrictValues(converters.hsl2oklch(value), precision); | ||
params = [Math.round(color.h), `${Math.round(color.s)}%`, `${Math.round(color.l)}%`]; | ||
} | ||
params = [`${round(l * 100, precision)}%`, c, h]; | ||
break; | ||
} | ||
case 'rgb': { | ||
const { r, g, b } = converters.hsl2rgb(value); | ||
if (isNumber(alpha)) { | ||
params.push(alpha); | ||
params = [r, g, b]; | ||
break; | ||
} | ||
default: { | ||
const hex = converters.hsl2hex(value); | ||
if (alpha && alpha !== 1) { | ||
return `${hex}${convertAlphaToHex(alpha)}`; | ||
} | ||
return hex; | ||
} | ||
} | ||
return `${prefix}(${params.join(', ')})`; | ||
return `${format}(${params.join(separator)}${opacity ? ` / ${opacity}` : ''})`; | ||
} |
@@ -1,23 +0,27 @@ | ||
import isValidHex from './is-valid-hex'; | ||
import { invariant, isString, messages } from './modules/utils'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { isHex } from '~/modules/validators'; | ||
export default function formatHex(input: string): string { | ||
invariant(isString(input), messages.inputString); | ||
import { HEX } from '~/types'; | ||
const color = input.replace('#', ''); | ||
let hex = color; | ||
export default function formatHex(input: string): HEX { | ||
invariant(isHex(input), MESSAGES.inputHex); | ||
let color = input.replace('#', ''); | ||
if (color.length === 3 || color.length === 4) { | ||
hex = ''; | ||
const values = [...color]; | ||
[...color].forEach(d => { | ||
hex += d + d; | ||
color = ''; | ||
values.forEach(d => { | ||
color += `${d}${d}`; | ||
}); | ||
} | ||
hex = `#${hex}`; | ||
const hex = `#${color}`; | ||
invariant(isValidHex(hex), 'invalid hex'); | ||
invariant(isHex(hex), 'invalid hex'); | ||
return hex; | ||
} |
220
src/index.ts
@@ -1,192 +0,36 @@ | ||
import chroma from './chroma'; | ||
import compare from './compare'; | ||
import darken from './darken'; | ||
import desaturate from './desaturate'; | ||
import fade from './fade'; | ||
import formatCSS from './format-css'; | ||
import lighten from './lighten'; | ||
import luminance from './luminance'; | ||
import parseColor from './modules/parse-color'; | ||
import { invariant } from './modules/utils'; | ||
import rotate from './rotate'; | ||
import saturate from './saturate'; | ||
import textColor from './text-color'; | ||
import { Analysis, HSL, Options, RGB, RGBArray } from './types'; | ||
import Colorizr from '~/colorizr'; | ||
class Colorizr { | ||
public hex: string; | ||
private readonly model: Options['model']; | ||
public hsl: HSL; | ||
public rgb: RGB; | ||
export { default as brightnessDifference } from '~/brightness-difference'; | ||
export { default as chroma } from '~/chroma'; | ||
export { default as colorDifference } from '~/color-difference'; | ||
export { default as compare } from '~/compare'; | ||
export { default as contrast } from '~/contrast'; | ||
export { default as convert } from '~/convert'; | ||
export { default as darken } from '~/darken'; | ||
export { default as desaturate } from '~/desaturate'; | ||
export { default as extractColorParts } from '~/extract-color-parts'; | ||
export { default as formatCSS } from '~/format-css'; | ||
export { default as formatHex } from '~/format-hex'; | ||
export { default as isValidColor } from '~/is-valid-color'; | ||
export { default as lighten } from '~/lighten'; | ||
export { default as luminance } from '~/luminance'; | ||
export { default as name } from '~/name'; | ||
export { default as palette } from '~/palette'; | ||
export { default as opacify } from '~/opacify'; | ||
export { default as opacity } from '~/opacity'; | ||
export { default as parseCSS } from '~/parse-css'; | ||
export { default as random } from '~/random'; | ||
export { default as rotate } from '~/rotate'; | ||
export { default as saturate } from '~/saturate'; | ||
export { default as scheme } from '~/scheme'; | ||
export { default as swatch } from '~/swatch'; | ||
export { default as textColor } from '~/text-color'; | ||
export { default as transparentize } from '~/transparentize'; | ||
constructor(color: string | HSL | RGB | RGBArray, options: Options = {}) { | ||
invariant(!!color, 'color is required'); | ||
export * from '~/converters'; | ||
export * from '~/types'; | ||
export * from '~/modules/hex-utils'; | ||
export { isHex, isHSL, isLAB, isLCH, isRGB } from '~/modules/validators'; | ||
const { model = 'rgb' } = options; | ||
const { hex, hsl, rgb } = parseColor(color); | ||
this.model = model; | ||
this.hex = hex; | ||
this.hsl = hsl; | ||
this.rgb = rgb; | ||
} | ||
/** | ||
* Get css string | ||
*/ | ||
get css(): string { | ||
return formatCSS(this.hsl, { model: this.model }); | ||
} | ||
/** | ||
* Get the red value | ||
*/ | ||
get red(): number { | ||
return Number(this.rgb.r); | ||
} | ||
/** | ||
* Get the green value | ||
*/ | ||
get green(): number { | ||
return Number(this.rgb.g); | ||
} | ||
/** | ||
* Get the blue value | ||
*/ | ||
get blue(): number { | ||
return Number(this.rgb.b); | ||
} | ||
/** | ||
* Get the hue value | ||
*/ | ||
get hue(): number { | ||
return Number(this.hsl.h); | ||
} | ||
/** | ||
* Get the saturation value | ||
*/ | ||
get saturation(): number { | ||
return Number(this.hsl.s); | ||
} | ||
/** | ||
* Get the lightness value | ||
*/ | ||
get lightness(): number { | ||
return Number(this.hsl.l); | ||
} | ||
/** | ||
* Get the luminance value | ||
*/ | ||
get luminance(): number { | ||
return luminance(this.hex); | ||
} | ||
/** | ||
* Get the chroma value | ||
*/ | ||
get chroma(): number { | ||
return chroma(this.hex); | ||
} | ||
/** | ||
* Get the contrasted color | ||
*/ | ||
get textColor(): string { | ||
return textColor(this.hex); | ||
} | ||
/** | ||
* Test 2 colors for compliance | ||
*/ | ||
public compare(input: string): Analysis { | ||
return compare(this.hex, input); | ||
} | ||
/** | ||
* Increase lightness | ||
*/ | ||
public lighten(percentage = 10): string { | ||
return lighten(this.hex, percentage); | ||
} | ||
/** | ||
* Decrease lightness | ||
*/ | ||
public darken(percentage = 10): string { | ||
return darken(this.hex, percentage); | ||
} | ||
/** | ||
* Increase saturation | ||
*/ | ||
public saturate(percentage = 10): string { | ||
return saturate(this.hex, percentage); | ||
} | ||
/** | ||
* Decrease saturation | ||
*/ | ||
public desaturate(percentage = 10): string { | ||
return desaturate(this.hex, percentage); | ||
} | ||
/** | ||
* Invert color | ||
*/ | ||
public invert(): string { | ||
return rotate(this.hex, 180); | ||
} | ||
/** | ||
* Rotate color | ||
*/ | ||
public rotate(degrees = 15): string { | ||
return rotate(this.hex, degrees); | ||
} | ||
/** | ||
* Fade color | ||
*/ | ||
public fade(percentage = 10): string { | ||
return fade(this.hex, percentage, this.model); | ||
} | ||
} | ||
export { default as brightnessDifference } from './brightness-difference'; | ||
export { default as chroma } from './chroma'; | ||
export { default as colorDifference } from './color-difference'; | ||
export { default as compare } from './compare'; | ||
export { default as contrast } from './contrast'; | ||
export { default as darken } from './darken'; | ||
export { default as desaturate } from './desaturate'; | ||
export { default as fade } from './fade'; | ||
export { default as formatCSS } from './format-css'; | ||
export { default as formatHex } from './format-hex'; | ||
export { default as hex2hsl } from './hex2hsl'; | ||
export { default as hex2rgb } from './hex2rgb'; | ||
export { default as hsl2hex } from './hsl2hex'; | ||
export { default as hsl2rgb } from './hsl2rgb'; | ||
export { default as isValidColor } from './is-valid-color'; | ||
export { default as isValidHex } from './is-valid-hex'; | ||
export { default as lighten } from './lighten'; | ||
export { default as luminance } from './luminance'; | ||
export { default as name } from './name'; | ||
export { default as palette } from './palette'; | ||
export { default as parseCSS } from './parse-css'; | ||
export { default as random } from './random'; | ||
export { default as rgb2hex } from './rgb2hex'; | ||
export { default as rgb2hsl } from './rgb2hsl'; | ||
export { default as rotate } from './rotate'; | ||
export { default as saturate } from './saturate'; | ||
export { default as scheme } from './scheme'; | ||
export { default as textColor } from './text-color'; | ||
export * from './types'; | ||
// eslint-disable-next-line unicorn/prefer-export-from | ||
export default Colorizr; |
@@ -1,4 +0,4 @@ | ||
import parseCSS from './parse-css'; | ||
import parseCSS from '~/parse-css'; | ||
export default function isValidColor(input: unknown): boolean { | ||
export default function isValidColor(input: string): boolean { | ||
try { | ||
@@ -5,0 +5,0 @@ parseCSS(input); |
@@ -1,8 +0,10 @@ | ||
import updater from './modules/updater'; | ||
import updater from '~/modules/updater'; | ||
import { Amount, ColorType } from '~/types'; | ||
/** | ||
* Increase color lightness | ||
*/ | ||
export default function lighten(input: string, amount = 10): string { | ||
return updater('l', '+')(input, amount); | ||
export default function lighten(input: string, amount: Amount, format?: ColorType) { | ||
return updater('l', '+', format)(input, amount); | ||
} |
@@ -1,5 +0,8 @@ | ||
import hex2rgb from './hex2rgb'; | ||
import { invariant, isString, messages, round } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { round } from '~/modules/utils'; | ||
import { isString } from '~/modules/validators'; | ||
import parseCSS from '~/parse-css'; | ||
/** | ||
@@ -9,5 +12,5 @@ * Get the luminance of a color. | ||
export default function luminance(input: string): number { | ||
invariant(isString(input), messages.inputString); | ||
invariant(isString(input), MESSAGES.inputString); | ||
const { r, g, b } = hex2rgb(parseCSS(input)); | ||
const { r, g, b } = parseCSS(input, 'rgb'); | ||
@@ -14,0 +17,0 @@ const rgb = [r / 255, g / 255, b / 255]; |
/** | ||
* CSS named colors | ||
*/ | ||
export type CSSColor = keyof typeof cssColors; | ||
export const cssColors = { | ||
@@ -30,3 +33,2 @@ aliceblue: '#f0f8ff', | ||
darkgray: '#a9a9a9', | ||
darkgrey: '#a9a9a9', | ||
darkgreen: '#006400', | ||
@@ -76,6 +78,6 @@ darkkhaki: '#bdb76b', | ||
lightcyan: '#e0ffff', | ||
lightgoldenrodyellow: '#FAFAD2', | ||
lightgoldenrodyellow: '#fafad2', | ||
lightgray: '#d3d3d3', | ||
lightgreen: '#90ee90', | ||
lightgrey: '#d3d3d3', | ||
lightgreen: '#90ee90', | ||
lightpink: '#ffb6c1', | ||
@@ -97,3 +99,3 @@ lightsalmon: '#ffa07a', | ||
mediumorchid: '#ba55d3', | ||
mediumpurple: '#9370d8', | ||
mediumpurple: '#9370db', | ||
mediumseagreen: '#3cb371', | ||
@@ -119,3 +121,3 @@ mediumslateblue: '#7b68ee', | ||
paleturquoise: '#afeeee', | ||
palevioletred: '#d87093', | ||
palevioletred: '#db7093', | ||
papayawhip: '#ffefd5', | ||
@@ -128,2 +130,3 @@ peachpuff: '#ffdab9', | ||
purple: '#800080', | ||
rebeccapurple: '#663399', | ||
red: '#ff0000', | ||
@@ -130,0 +133,0 @@ rosybrown: '#bc8f8f', |
@@ -1,2 +0,4 @@ | ||
import { invariant, isNumber, round } from './utils'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { round } from '~/modules/utils'; | ||
import { isNumber } from '~/modules/validators'; | ||
@@ -3,0 +5,0 @@ /** |
@@ -1,15 +0,14 @@ | ||
import { invariant, isHSL, isPlainObject, isRGB, isString, limit, messages } from './utils'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { addAlphaToHex } from '~/modules/hex-utils'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { addAlpha, limit } from '~/modules/utils'; | ||
import { isHex, isHSL, isLAB, isLCH, isPlainObject, isRGB, isString } from '~/modules/validators'; | ||
import hex2hsl from '../hex2hsl'; | ||
import hex2rgb from '../hex2rgb'; | ||
import hsl2hex from '../hsl2hex'; | ||
import hsl2rgb from '../hsl2rgb'; | ||
import isValidHex from '../is-valid-hex'; | ||
import parseCSS from '../parse-css'; | ||
import rgb2hex from '../rgb2hex'; | ||
import rgb2hsl from '../rgb2hsl'; | ||
import { Colors, HSL, PlainObject, RGB, RGBArray } from '../types'; | ||
import * as converters from '~/converters'; | ||
import extractColorParts from '~/extract-color-parts'; | ||
import parseCSS from '~/parse-css'; | ||
import { Colors, HSL, LAB, LCH, PlainObject, RGB } from '~/types'; | ||
export default function parseColor(color: string | HSL | RGB | RGBArray): Colors { | ||
invariant(!!color, messages.input); | ||
export default function parseColor(color: string | HSL | LAB | LCH | RGB): Colors { | ||
invariant(!!color, MESSAGES.input); | ||
@@ -19,33 +18,48 @@ const output: PlainObject = {}; | ||
if (isString(color)) { | ||
const hex = parseCSS(color) as string; | ||
const { alpha = 1 } = extractColorParts(color); | ||
const type = isHex(color) ? 'hex' : extractColorParts(color).model; | ||
invariant(isValidHex(hex), 'input is not valid'); | ||
output.hex = addAlphaToHex(parseCSS(color, 'hex'), alpha); | ||
output.hsl = addAlpha(parseCSS(color, 'hsl'), alpha); | ||
output.oklab = addAlpha(parseCSS(color, 'oklab'), alpha); | ||
output.oklch = addAlpha(parseCSS(color, 'oklch'), alpha); | ||
output.rgb = addAlpha(parseCSS(color, 'rgb'), alpha); | ||
output.hex = hex; | ||
output.rgb = hex2rgb(hex); | ||
output.hsl = hex2hsl(hex); | ||
} else if (Array.isArray(color)) { | ||
output.rgb = { | ||
r: limit(color[0], 'r'), | ||
g: limit(color[1], 'g'), | ||
b: limit(color[2], 'b'), | ||
}; | ||
output.alpha = alpha; | ||
output.type = type; | ||
} else if (isPlainObject(color)) { | ||
const { alpha = 1 } = color; | ||
output.hex = rgb2hex(output.rgb); | ||
output.hsl = rgb2hsl(output.rgb); | ||
} else if (isPlainObject(color)) { | ||
if (isHSL(color)) { | ||
output.hsl = { | ||
h: limit(color.h, 'h'), | ||
s: limit(color.s, 's'), | ||
l: limit(color.l, 'l'), | ||
h: limit(color.h, 'hsl', 'h'), | ||
s: limit(color.s, 'hsl', 's'), | ||
l: limit(color.l, 'hsl', 'l'), | ||
}; | ||
output.rgb = hsl2rgb(output.hsl); | ||
output.rgb = converters.hsl2rgb(output.hsl); | ||
output.oklab = converters.hsl2oklab(output.hsl); | ||
output.oklch = converters.hsl2oklch(output.hsl); | ||
output.type = 'hsl'; | ||
} else if (isLAB(color)) { | ||
output.hsl = converters.oklab2hsl(color); | ||
output.oklab = color; | ||
output.oklch = converters.oklab2oklch(color); | ||
output.rgb = converters.oklab2rgb(color); | ||
output.type = 'oklab'; | ||
} else if (isLCH(color)) { | ||
output.hsl = converters.oklch2hsl(color); | ||
output.oklab = converters.oklch2oklab(color); | ||
output.oklch = color; | ||
output.rgb = converters.oklch2rgb(color); | ||
output.type = 'oklch'; | ||
} else if (isRGB(color)) { | ||
output.rgb = { | ||
r: limit(color.r, 'r'), | ||
g: limit(color.g, 'g'), | ||
b: limit(color.b, 'b'), | ||
r: limit(color.r, 'rgb', 'r'), | ||
g: limit(color.g, 'rgb', 'g'), | ||
b: limit(color.b, 'rgb', 'b'), | ||
}; | ||
output.hsl = rgb2hsl(output.rgb); | ||
output.hsl = converters.rgb2hsl(output.rgb); | ||
output.oklab = converters.rgb2oklab(output.rgb); | ||
output.oklch = converters.rgb2oklch(output.rgb); | ||
output.type = 'rgb'; | ||
} else { | ||
@@ -55,5 +69,11 @@ throw new Error('invalid color'); | ||
output.hex = hsl2hex(output.hsl); | ||
output.hex = addAlphaToHex(converters.hsl2hex(output.hsl), alpha); | ||
output.hsl = addAlpha(output.hsl, alpha); | ||
output.oklab = addAlpha(output.oklab, alpha); | ||
output.oklch = addAlpha(output.oklch, alpha); | ||
output.rgb = addAlpha(output.rgb, alpha); | ||
output.alpha = alpha; | ||
} else { | ||
throw new Error(messages.input); | ||
throw new Error(MESSAGES.input); | ||
} | ||
@@ -60,0 +80,0 @@ |
@@ -1,6 +0,10 @@ | ||
import { constrain, invariant, isNumber, isString, messages } from './utils'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { clamp } from '~/modules/utils'; | ||
import { isHex, isNamedColor, isNumber, isString } from '~/modules/validators'; | ||
import hex2hsl from '../hex2hsl'; | ||
import parseCSS from '../parse-css'; | ||
import { shift } from '../shift'; | ||
import extractColorParts from '~/extract-color-parts'; | ||
import formatCSS from '~/format-css'; | ||
import parseCSS from '~/parse-css'; | ||
import { ColorModelKeys, ColorType } from '~/types'; | ||
@@ -10,14 +14,22 @@ /** | ||
*/ | ||
export default function updater(type: 'h' | 's' | 'l', sign: '+' | '-') { | ||
export default function updater( | ||
key: ColorModelKeys<'hsl'>, | ||
operator: '+' | '-', | ||
format?: ColorType, | ||
) { | ||
return (input: string, amount: number) => { | ||
invariant(isString(input), messages.inputString); | ||
invariant(isNumber(amount), messages.amount); | ||
invariant(isString(input), MESSAGES.inputString); | ||
invariant(isNumber(amount), MESSAGES.alpha); | ||
const hex = parseCSS(input); | ||
const hsl = hex2hsl(hex); | ||
const color = parseCSS(input, 'hsl'); | ||
const output = isHex(input) || isNamedColor(input) ? 'hex' : extractColorParts(input).model; | ||
return shift(hex, { | ||
[type]: constrain(hsl[type], amount, [0, 100], sign), | ||
}); | ||
return formatCSS( | ||
{ | ||
...color, | ||
[key]: clamp(color[key] + (operator === '+' ? amount : -amount), 0, 100), | ||
}, | ||
{ format: format ?? output }, | ||
); | ||
}; | ||
} |
@@ -1,29 +0,60 @@ | ||
import { HSL, PlainObject, RGB, RGBArray } from '../types'; | ||
import { COLOR_KEYS, COLOR_MODELS, MESSAGES, PRECISION } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { | ||
isHSL, | ||
isLAB, | ||
isLCH, | ||
isNumber, | ||
isPlainObject, | ||
isRGB, | ||
isValidColorModel, | ||
} from '~/modules/validators'; | ||
export const HSLKeys = ['h', 's', 'l']; | ||
export const RGBKeys = ['r', 'g', 'b']; | ||
import { | ||
Alpha, | ||
ColorModel, | ||
ColorModelKey, | ||
ColorModelKeys, | ||
ConverterParameters, | ||
LAB, | ||
LCH, | ||
PlainObject, | ||
} from '~/types'; | ||
/** | ||
* Constrain value into the range | ||
*/ | ||
export function constrain(input: number, amount: number, range: number[], sign: string): number { | ||
invariant(arguments.length === 4, 'All parameters are required'); | ||
export function addAlpha<T extends ColorModel>(input: any, alpha?: Alpha): T { | ||
invariant(isValidColorModel(input), MESSAGES.invalid); | ||
const [min, max] = range; | ||
let value = expr(input + sign + amount); | ||
let value = alpha; | ||
if (value < min) { | ||
value = min; | ||
} else if (value > max) { | ||
value = max; | ||
if (!value) { | ||
return input; | ||
} | ||
return Math.abs(value); | ||
/* c8 ignore next 3 */ | ||
if (value > 1) { | ||
value /= 100; | ||
} | ||
if (value === 1) { | ||
return input; | ||
} | ||
return { ...input, alpha: value }; | ||
} | ||
/** | ||
* Constrain an angle | ||
* Clamp a value between a min and max | ||
* @param value | ||
* @param [min=0] - The minimum value | ||
* @param [max=100] - The maximum value | ||
*/ | ||
export function clamp(value: number, min = 0, max = 100) { | ||
return Math.min(Math.max(value, min), max); | ||
} | ||
/** | ||
* Constrain the degrees between 0 and 360 | ||
*/ | ||
export function constrainDegrees(input: number, amount: number): number { | ||
invariant(isNumber(input), 'input is required'); | ||
invariant(isNumber(input), MESSAGES.inputNumber); | ||
@@ -44,211 +75,130 @@ let value = input + amount; | ||
/** | ||
* Parse math string expressions | ||
* Limit values per type. | ||
*/ | ||
export function expr(input: string): number { | ||
const chars = [...input]; | ||
const n: string[] = []; | ||
const op: string[] = []; | ||
export function limit<TModel extends Extract<ColorModelKey, 'hsl' | 'rgb'>>( | ||
input: number, | ||
model: TModel, | ||
key: ColorModelKeys<TModel>, | ||
): number { | ||
invariant(isNumber(input), 'Input is not a number'); | ||
invariant(COLOR_MODELS.includes(model), `Invalid model${model ? `: ${model}` : ''}`); | ||
invariant(COLOR_KEYS[model].includes(key), `Invalid key${key ? `: ${key}` : ''}`); | ||
let parsed; | ||
let index = 0; | ||
let last = true; | ||
switch (model) { | ||
case 'hsl': { | ||
invariant(COLOR_KEYS.hsl.includes(key), 'Invalid key'); | ||
n[index] = ''; | ||
if (['s', 'l'].includes(key)) { | ||
return clamp(input); | ||
} | ||
// Parse the string | ||
for (const char of chars) { | ||
if (Number.isNaN(parseInt(char, 10)) && char !== '.' && !last) { | ||
op[index] = char; | ||
index++; | ||
n[index] = ''; | ||
last = true; | ||
} else { | ||
n[index] += char; | ||
last = false; | ||
return clamp(input, 0, 360); | ||
} | ||
} | ||
case 'rgb': { | ||
invariant(COLOR_KEYS.rgb.includes(key), 'Invalid key'); | ||
// Calculate the expression | ||
parsed = parseFloat(n[0]); | ||
for (const [o, element] of op.entries()) { | ||
const value = parseFloat(n[o + 1]); | ||
switch (element) { | ||
case '+': | ||
parsed += value; | ||
break; | ||
case '-': | ||
parsed -= value; | ||
break; | ||
case '*': | ||
parsed *= value; | ||
break; | ||
case '/': | ||
parsed /= value; | ||
break; | ||
default: | ||
break; | ||
return clamp(input, 0, 255); | ||
} | ||
} | ||
return parsed; | ||
} | ||
export function invariant(condition: boolean, message: string): asserts condition { | ||
if (condition) { | ||
return; | ||
} | ||
/* istanbul ignore else */ | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (message === undefined) { | ||
throw new Error('invariant requires an error message argument'); | ||
/* c8 ignore next 3 */ | ||
default: { | ||
throw new Error('Invalid inputs'); | ||
} | ||
} | ||
let error; | ||
if (!message) { | ||
throw new Error( | ||
'Minified exception occurred; use the non-minified dev environment ' + | ||
'for the full error message and additional helpful warnings.', | ||
); | ||
} else { | ||
error = new Error(message); | ||
} | ||
error.name = 'colorizr'; | ||
throw error; | ||
} | ||
/** | ||
* Check if an object contains HSL values | ||
* Parse the input parameters | ||
*/ | ||
export function isHSL(input: any): input is HSL { | ||
if (!isPlainObject(input)) { | ||
return false; | ||
} | ||
export function parseInput<T extends ColorModel>( | ||
input: ConverterParameters<T>, | ||
model: ColorModelKey, | ||
): T { | ||
const keys = COLOR_KEYS[model]; | ||
const validator = { | ||
hsl: isHSL, | ||
oklab: isLAB, | ||
oklch: isLCH, | ||
rgb: isRGB, | ||
}; | ||
const entries = Object.entries(input); | ||
invariant(isPlainObject(input) || Array.isArray(input), MESSAGES.invalid); | ||
return ( | ||
!!entries.length && | ||
entries.every( | ||
([key, value]) => HSLKeys.includes(key) && value >= 0 && value <= (key === 'h' ? 360 : 100), | ||
) | ||
); | ||
} | ||
const value = Array.isArray(input) | ||
? ({ [keys[0]]: input[0], [keys[1]]: input[1], [keys[2]]: input[2] } as unknown as T) | ||
: input; | ||
/** | ||
* Check if the input is a number and not NaN | ||
*/ | ||
export function isNumber(input: any): input is number { | ||
return typeof input === 'number' && !Number.isNaN(input); | ||
invariant(validator[model](value), `invalid ${model} color`); | ||
return value; | ||
} | ||
/** | ||
* Check if the input is an object | ||
* Creates an object composed of the picked source properties. | ||
*/ | ||
export function isPlainObject(input: any): input is PlainObject { | ||
if (!input) { | ||
return false; | ||
export function pick(input: PlainObject, options: string[]): PlainObject { | ||
if (!Array.isArray(options)) { | ||
throw new TypeError('options must be an array'); | ||
} | ||
const { toString } = Object.prototype; | ||
const prototype = Object.getPrototypeOf(input); | ||
return options | ||
.filter(d => typeof input[d] !== 'undefined') | ||
.reduce((acc: PlainObject, d) => { | ||
acc[d] = input[d]; | ||
return ( | ||
toString.call(input) === '[object Object]' && | ||
(prototype === null || prototype === Object.getPrototypeOf({})) | ||
); | ||
return acc; | ||
}, {}); | ||
} | ||
/** | ||
* Check if an object contains RGB values. | ||
* Restrict the values to a certain number of digits. | ||
*/ | ||
export function isRGB(input: any): input is RGB { | ||
if (!isPlainObject(input)) { | ||
return false; | ||
export function restrictValues<T extends LAB | LCH>( | ||
input: T, | ||
precision: number = PRECISION, | ||
forcePrecision = true, | ||
): T { | ||
const output = new Map(Object.entries(input)); | ||
for (const [key, value] of output.entries()) { | ||
output.set(key, round(value, precision, forcePrecision)); | ||
} | ||
const entries = Object.entries(input); | ||
return ( | ||
!!entries.length && | ||
entries.every(([key, value]) => RGBKeys.includes(key) && value >= 0 && value <= 255) | ||
); | ||
return Object.fromEntries(output) as T; | ||
} | ||
/** | ||
* Check if an array contains RGB values. | ||
* Round decimal numbers. | ||
*/ | ||
export function isRGBArray(input: any): input is RGBArray { | ||
return Array.isArray(input) && input.length === 3 && input.every(d => d >= 0 && d <= 255); | ||
} | ||
/** | ||
* Check if the input is a string | ||
*/ | ||
export function isString(input: any): input is string { | ||
return typeof input === 'string'; | ||
} | ||
/** | ||
* Limit values per type. | ||
*/ | ||
export function limit(input: number, type: string): number { | ||
invariant(isNumber(input), 'Input is not a number'); | ||
/* istanbul ignore else */ | ||
if (RGBKeys.includes(type)) { | ||
return Math.max(Math.min(input, 255), 0); | ||
export function round(input: number, precision = 2, forcePrecision = true): number { | ||
if (!isNumber(input) || input === 0) { | ||
return 0; | ||
} | ||
if (['s', 'l'].includes(type)) { | ||
return Math.max(Math.min(input, 100), 0); | ||
} | ||
if (forcePrecision) { | ||
const factor = 10 ** precision; | ||
if (type === 'h') { | ||
return Math.max(Math.min(input, 360), 0); | ||
return Math.round(input * factor) / factor; | ||
} | ||
throw new Error('Invalid type'); | ||
} | ||
const absInput = Math.abs(input); | ||
export const messages = { | ||
amount: 'amount must be a number', | ||
left: 'left is required and must be a string', | ||
right: 'right is required and must be a string', | ||
input: 'input is required', | ||
inputString: 'input is required and must be a string', | ||
invalid: 'invalid input', | ||
options: 'invalid options', | ||
}; | ||
let digits = Math.abs(Math.ceil(Math.log(absInput) / Math.LN10)); | ||
/** | ||
* Creates an object composed of the picked source properties. | ||
*/ | ||
export function pick(input: PlainObject, options: string[]): PlainObject { | ||
if (!Array.isArray(options)) { | ||
throw new TypeError('options must be an array'); | ||
if (digits === 0) { | ||
digits = 2; | ||
} else if (digits > precision) { | ||
digits = precision; | ||
} | ||
return options | ||
.filter(d => typeof input[d] !== 'undefined') | ||
.reduce((acc: PlainObject, d) => { | ||
acc[d] = input[d]; | ||
let exponent = precision - (digits < 0 ? 0 : digits); | ||
return acc; | ||
}, {}); | ||
} | ||
if (exponent <= 1 && precision > 1) { | ||
exponent = 2; | ||
} else if (exponent > precision || exponent === 0) { | ||
exponent = precision; | ||
} | ||
/** | ||
* Round decimal numbers. | ||
*/ | ||
export function round(input: number, digits = 2): number { | ||
const factor = 10 ** digits; | ||
const factor = 10 ** exponent; | ||
return Math.round(input * factor) / factor; | ||
} |
@@ -1,13 +0,16 @@ | ||
import { cssColors } from './modules/css-colors'; | ||
import { invariant, isString, messages } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { cssColors } from '~/modules/css-colors'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { isString } from '~/modules/validators'; | ||
import parseCSS from '~/parse-css'; | ||
export default function name(input: string): string { | ||
invariant(isString(input), messages.inputString); | ||
invariant(isString(input), MESSAGES.inputString); | ||
const hex = parseCSS(input); | ||
const hex = parseCSS(input, 'hex'); | ||
const [color] = Object.entries(cssColors).find(([, value]) => value === hex) || []; | ||
return color || hex; | ||
return color ?? hex; | ||
} |
@@ -1,14 +0,24 @@ | ||
import hex2hsl from './hex2hsl'; | ||
import hsl2hex from './hsl2hex'; | ||
import { invariant, isPlainObject, isString, messages } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import rotate from './rotate'; | ||
import { PaletteOptions } from './types'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { isPlainObject, isString } from '~/modules/validators'; | ||
import hex2hsl from '~/converters/hex2hsl'; | ||
import hsl2hex from '~/converters/hsl2hex'; | ||
import parseCSS from '~/parse-css'; | ||
import rotate from '~/rotate'; | ||
import { HEX } from '~/types'; | ||
export interface PaletteOptions { | ||
lightness?: number; | ||
saturation?: number; | ||
size?: number; | ||
type?: string; | ||
} | ||
export default function palette(input: string, options: PaletteOptions = {}): string[] { | ||
invariant(isString(input), messages.inputString); | ||
invariant(isPlainObject(options), messages.options); | ||
invariant(isString(input), MESSAGES.inputString); | ||
invariant(isPlainObject(options), MESSAGES.options); | ||
const { lightness, saturation, size = 6, type } = options; | ||
const hsl = hex2hsl(parseCSS(input)); | ||
const hsl = hex2hsl(parseCSS(input, 'hex')); | ||
const output: string[] = []; | ||
@@ -29,8 +39,8 @@ | ||
output.push(hsl2hex({ ...hsl, l: lightness || hsl.l, s: saturation || hsl.s })); | ||
output.push(hsl2hex({ ...hsl, l: lightness ?? hsl.l, s: saturation ?? hsl.s })); | ||
for (let index = 1; index < size; index++) { | ||
const color = rotate(input, hsl.h + step * index); | ||
const color = rotate(input, hsl.h + step * index, 'hex') as HEX; | ||
output.push(hsl2hex({ ...hex2hsl(color), l: lightness || hsl.l, s: saturation || hsl.s })); | ||
output.push(hsl2hex({ ...hex2hsl(color), l: lightness ?? hsl.l, s: saturation ?? hsl.s })); | ||
} | ||
@@ -37,0 +47,0 @@ |
@@ -1,90 +0,146 @@ | ||
import hex2hsl from './hex2hsl'; | ||
import hex2rgb from './hex2rgb'; | ||
import hsl2hex from './hsl2hex'; | ||
import hsl2rgb from './hsl2rgb'; | ||
import isValidHex from './is-valid-hex'; | ||
import { cssColors } from './modules/css-colors'; | ||
import { invariant, isString, messages } from './modules/utils'; | ||
import rgb2hex from './rgb2hex'; | ||
import rgb2hsl from './rgb2hsl'; | ||
import { ColorTypes, Return } from './types'; | ||
import { MESSAGES, PRECISION } from '~/modules/constants'; | ||
import { CSSColor, cssColors } from '~/modules/css-colors'; | ||
import { convertAlphaToHex, extractAlphaFromHex, removeAlphaFromHex } from '~/modules/hex-utils'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { addAlpha, round } from '~/modules/utils'; | ||
import { isHex, isNamedColor, isString } from '~/modules/validators'; | ||
import * as converters from '~/converters'; | ||
import extractColorParts from '~/extract-color-parts'; | ||
import { ColorTuple, ColorType, HEX, HSL, LAB, LCH, RGB } from '~/types'; | ||
export type ParseCSSReturn<T extends ColorType> = T extends 'hsl' | ||
? HSL | ||
: T extends 'lab' | ||
? LAB | ||
: T extends 'lch' | ||
? LCH | ||
: T extends 'rgb' | ||
? RGB | ||
: T extends 'hex' | ||
? HEX | ||
: never; | ||
/** | ||
* Parse CSS color | ||
*/ | ||
export default function parseCSS<T extends ColorTypes = 'hex'>( | ||
input: unknown, | ||
output?: T, | ||
): Return<T> { | ||
invariant(isString(input), messages.inputString); | ||
export default function parseCSS<T extends ColorType>( | ||
input: string, | ||
format?: T, | ||
): ParseCSSReturn<T> { | ||
invariant(isString(input), MESSAGES.inputString); | ||
let result: any; | ||
const parsedInput = cssColors[input.toLowerCase() as keyof typeof cssColors] || input; | ||
const value = isNamedColor(input) ? cssColors[input.toLowerCase() as CSSColor] : input; | ||
if (isValidHex(parsedInput)) { | ||
const output = format ?? (isHex(value) ? 'hex' : extractColorParts(value).model); | ||
const colorParams = (params: Record<string, number>) => Object.values(params) as ColorTuple; | ||
if (isHex(value)) { | ||
const alpha = extractAlphaFromHex(value); | ||
switch (output) { | ||
case 'hsl': { | ||
result = hex2hsl(parsedInput); | ||
result = addAlpha(converters.hex2hsl(value), alpha); | ||
break; | ||
} | ||
case 'oklab': { | ||
result = addAlpha(converters.hex2oklab(value), alpha); | ||
break; | ||
} | ||
case 'oklch': { | ||
result = addAlpha(converters.hex2oklch(value), alpha); | ||
break; | ||
} | ||
case 'rgb': { | ||
result = hex2rgb(parsedInput); | ||
result = addAlpha(converters.hex2rgb(value), alpha); | ||
break; | ||
} | ||
default: { | ||
result = parsedInput; | ||
result = `${removeAlphaFromHex(value)}${alpha !== 1 ? convertAlphaToHex(alpha) : ''}`; | ||
break; | ||
} | ||
} | ||
} else { | ||
// TODO: improve the pattern to require 3 groups | ||
const matches = parsedInput.match( | ||
/(hsl|rgb)a?\((\d+)(?:,\s*|\s+)(\d+)%?(?:,\s*|\s+)(\d+)%?[^)]*\)/i, | ||
); | ||
invariant(Array.isArray(matches), 'invalid CSS string'); | ||
invariant(matches.length === 5, 'invalid CSS string'); | ||
return result as ParseCSSReturn<T>; | ||
} | ||
const [, model, hORr, sORg, lORb] = matches; | ||
let hex; | ||
let hsl; | ||
let rgb; | ||
switch (output) { | ||
case 'hsl': { | ||
const { alpha, model, ...color } = extractColorParts(value); | ||
if (model === 'hsl') { | ||
hsl = { | ||
h: parseInt(hORr, 10), | ||
s: parseInt(sORg, 10), | ||
l: parseInt(lORb, 10), | ||
}; | ||
hex = hsl2hex(hsl); | ||
rgb = hsl2rgb(hsl); | ||
} else { | ||
rgb = { | ||
r: parseInt(hORr, 10), | ||
g: parseInt(sORg, 10), | ||
b: parseInt(lORb, 10), | ||
}; | ||
hex = rgb2hex(rgb); | ||
hsl = rgb2hsl(rgb); | ||
if (['oklab', 'oklch'].includes(model) && color.l > 1) { | ||
color.l = round(color.l / 100, PRECISION); | ||
} | ||
result = addAlpha( | ||
model === 'hsl' ? color : converters[`${model}2hsl`](colorParams(color)), | ||
alpha, | ||
); | ||
break; | ||
} | ||
case 'oklab': { | ||
const { alpha, model, ...color } = extractColorParts(value); | ||
switch (output) { | ||
case 'hsl': { | ||
result = hsl; | ||
break; | ||
if (['oklab', 'oklch'].includes(model) && color.l > 1) { | ||
color.l = round(color.l / 100, PRECISION); | ||
} | ||
case 'rgb': { | ||
result = rgb; | ||
break; | ||
result = addAlpha( | ||
model === 'oklab' ? color : converters[`${model}2oklab`](colorParams(color)), | ||
alpha, | ||
); | ||
break; | ||
} | ||
case 'oklch': { | ||
const { alpha, model, ...color } = extractColorParts(value); | ||
if (['oklab', 'oklch'].includes(model) && color.l > 1) { | ||
color.l = round(color.l / 100, PRECISION); | ||
} | ||
case 'hex': | ||
default: { | ||
result = hex; | ||
break; | ||
result = addAlpha( | ||
model === 'oklch' ? color : converters[`${model}2oklch`](colorParams(color)), | ||
alpha, | ||
); | ||
break; | ||
} | ||
case 'rgb': { | ||
const { alpha, model, ...color } = extractColorParts(value); | ||
if (['oklab', 'oklch'].includes(model) && color.l > 1) { | ||
color.l /= 100; | ||
} | ||
result = addAlpha( | ||
model === 'rgb' ? color : converters[`${model}2rgb`](colorParams(color)), | ||
alpha, | ||
); | ||
break; | ||
} | ||
case 'hex': | ||
default: { | ||
const { alpha, model, ...color } = extractColorParts(value); | ||
let alphaPrefix = ''; | ||
if (['oklab', 'oklch'].includes(model) && color.l > 1) { | ||
color.l = round(color.l / 100, PRECISION); | ||
} | ||
if (alpha) { | ||
alphaPrefix = convertAlphaToHex(alpha); | ||
} | ||
result = `${converters[`${model}2hex`](colorParams(color))}${alphaPrefix}`; | ||
break; | ||
} | ||
} | ||
return result as Return<T>; | ||
return result as ParseCSSReturn<T>; | ||
} |
@@ -1,2 +0,2 @@ | ||
import hsl2hex from './hsl2hex'; | ||
import hsl2hex from '~/converters/hsl2hex'; | ||
@@ -3,0 +3,0 @@ /** |
@@ -1,19 +0,29 @@ | ||
import hex2hsl from './hex2hsl'; | ||
import { constrainDegrees, invariant, isNumber, isString, messages } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import { shift } from './shift'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { constrainDegrees } from '~/modules/utils'; | ||
import { isHex, isNamedColor, isNumber, isString } from '~/modules/validators'; | ||
import extractColorParts from '~/extract-color-parts'; | ||
import formatCSS from '~/format-css'; | ||
import parseCSS from '~/parse-css'; | ||
import { ColorType, Degrees } from '~/types'; | ||
/** | ||
* Change the color hue | ||
*/ | ||
export default function rotate(input: string, degrees = 15): string { | ||
invariant(isString(input), messages.inputString); | ||
export default function rotate(input: string, degrees: Degrees, format?: ColorType) { | ||
invariant(isString(input), MESSAGES.inputString); | ||
invariant(isNumber(degrees), 'degrees must be a number'); | ||
const hex = parseCSS(input); | ||
const { h } = hex2hsl(hex); | ||
const color = parseCSS(input, 'hsl'); | ||
return shift(hex, { | ||
h: constrainDegrees(h, degrees), | ||
}); | ||
const output = isHex(input) || isNamedColor(input) ? 'hex' : extractColorParts(input).model; | ||
return formatCSS( | ||
{ | ||
...color, | ||
h: constrainDegrees(color.h, degrees), | ||
}, | ||
{ format: format ?? output }, | ||
); | ||
} |
@@ -1,8 +0,10 @@ | ||
import updater from './modules/updater'; | ||
import updater from '~/modules/updater'; | ||
import { ColorType } from '~/types'; | ||
/** | ||
* Increase color saturation | ||
*/ | ||
export default function saturate(input: string, amount = 10): string { | ||
return updater('s', '+')(input, amount); | ||
export default function saturate(input: string, amount: number, format?: ColorType) { | ||
return updater('s', '+', format)(input, amount); | ||
} |
@@ -1,6 +0,9 @@ | ||
import { invariant, isString, messages } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import rotate from './rotate'; | ||
import { Scheme } from './types'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { isString } from '~/modules/validators'; | ||
import parseCSS from '~/parse-css'; | ||
import rotate from '~/rotate'; | ||
import { Scheme } from '~/types'; | ||
/** | ||
@@ -10,5 +13,5 @@ * Get the scheme for a color. | ||
export default function scheme(input: string, type: Scheme = 'complementary'): string[] { | ||
invariant(isString(input), messages.inputString); | ||
invariant(isString(input), MESSAGES.inputString); | ||
const hex = parseCSS(input); | ||
const hex = parseCSS(input, 'hex'); | ||
const output: string[] = []; | ||
@@ -15,0 +18,0 @@ |
@@ -1,15 +0,40 @@ | ||
import hex2rgb from './hex2rgb'; | ||
import { invariant, isString, messages } from './modules/utils'; | ||
import parseCSS from './parse-css'; | ||
import { MESSAGES } from '~/modules/constants'; | ||
import { invariant } from '~/modules/invariant'; | ||
import { isString } from '~/modules/validators'; | ||
import hex2rgb from '~/converters/hex2rgb'; | ||
import parseCSS from '~/parse-css'; | ||
interface Options { | ||
/** | ||
* The dark color to return if the input is light. | ||
* @default '#000000' | ||
*/ | ||
darkColor?: string; | ||
/** | ||
* The light color to return if the input is dark. | ||
* @default '#ffffff' | ||
*/ | ||
lightColor?: string; | ||
/** | ||
* The threshold to determine if the color is light or dark. | ||
* A number between 0 and 255. | ||
* @default 128 | ||
*/ | ||
threshold?: number; | ||
} | ||
/** | ||
* Get the contrasted color for a given hex. | ||
*/ | ||
export default function textColor(input: string): string { | ||
invariant(isString(input), messages.inputString); | ||
export default function textColor(input: string, options: Options = {}): string { | ||
const { darkColor = '#000000', lightColor = '#ffffff', threshold = 128 } = options; | ||
const { r, g, b } = hex2rgb(parseCSS(input)); | ||
invariant(isString(input), MESSAGES.inputString); | ||
invariant(threshold >= 0 && threshold <= 255, MESSAGES.threshold); | ||
const { r, g, b } = hex2rgb(parseCSS(input, 'hex')); | ||
const yiq = (r * 299 + g * 587 + b * 114) / 1000; | ||
return yiq >= 128 ? '#000000' : '#ffffff'; | ||
return yiq >= threshold ? darkColor : lightColor; | ||
} |
@@ -1,27 +0,46 @@ | ||
/* eslint-disable typescript-sort-keys/interface */ | ||
export interface Analysis { | ||
brightnessDifference: number; | ||
colorDifference: number; | ||
compliant: number; | ||
contrast: number; | ||
largeAA: boolean; | ||
largeAAA: boolean; | ||
normalAA: boolean; | ||
normalAAA: boolean; | ||
} | ||
/* eslint-disable @typescript-eslint/member-ordering */ | ||
export type ColorType = 'hex' | 'hsl' | 'oklab' | 'oklch' | 'rgb'; | ||
export type ColorModel = HSL | LAB | LCH | RGB; | ||
export type ColorModelKey = 'hsl' | 'oklab' | 'oklch' | 'rgb'; | ||
export type ColorModelKeys<TModel extends ColorModelKey> = TModel extends 'hsl' | ||
? keyof Omit<HSL, 'alpha'> | ||
: TModel extends 'oklab' | ||
? keyof Omit<LAB, 'alpha'> | ||
: TModel extends 'oklch' | ||
? keyof Omit<LCH, 'alpha'> | ||
: TModel extends 'rgb' | ||
? keyof Omit<RGB, 'alpha'> | ||
: never; | ||
export type ColorKeysTuple = [string, string, string]; | ||
export type ColorTuple = [number, number, number]; | ||
export type ColorTupleWithRound = [number, number, number, boolean]; | ||
export type ConverterParameters<TModel extends ColorModel> = TModel | ColorTuple; | ||
export interface Colors { | ||
hex: string; | ||
alpha: Alpha; | ||
hex: HEX; | ||
hsl: HSL; | ||
oklab: LAB; | ||
oklch: LCH; | ||
rgb: RGB; | ||
type: ColorType; | ||
} | ||
export type ColorTypes = 'hex' | 'hsl' | 'rgb'; | ||
export type ColorModels = 'hsl' | 'rgb'; | ||
/* A number between 0 and 1 */ | ||
export type Alpha = number; | ||
export interface FormatOptions { | ||
alpha?: number; | ||
model?: ColorModels; | ||
} | ||
/* A number between 0 and 100 */ | ||
export type Amount = number; | ||
/* A number between 0 and 360 */ | ||
export type Degrees = number; | ||
/* | ||
Color types | ||
*/ | ||
export type HEX = `#${string}`; | ||
export type CSS = `('#' | 'hsl' | 'oklab' | 'oklch' | 'rgb')${string}`; | ||
export interface HSL { | ||
@@ -31,21 +50,19 @@ h: number; | ||
l: number; | ||
alpha?: Alpha; | ||
} | ||
export interface Options { | ||
model?: ColorModels; | ||
export interface LAB { | ||
l: number; | ||
a: number; | ||
b: number; | ||
alpha?: Alpha; | ||
} | ||
export interface PaletteOptions { | ||
lightness?: number; | ||
saturation?: number; | ||
size?: number; | ||
type?: string; | ||
export interface LCH { | ||
l: number; | ||
c: number; | ||
h: number; | ||
alpha?: Alpha; | ||
} | ||
export type PlainObject = Record<string, any>; | ||
type ReturnModel<T> = T extends 'rgb' ? RGB : HSL; | ||
export type Return<T> = T extends 'rgb' | 'hsl' ? ReturnModel<T> : string; | ||
export interface RGB { | ||
@@ -55,6 +72,18 @@ r: number; | ||
b: number; | ||
alpha?: Alpha; | ||
} | ||
export type RGBArray = [number, number, number]; | ||
export interface Analysis { | ||
brightnessDifference: number; | ||
colorDifference: number; | ||
compliant: number; | ||
contrast: number; | ||
largeAA: boolean; | ||
largeAAA: boolean; | ||
normalAA: boolean; | ||
normalAAA: boolean; | ||
} | ||
export type PlainObject<T = any> = Record<string, T>; | ||
export type Scheme = | ||
@@ -61,0 +90,0 @@ | 'analogous' |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
396739
5359
754
18
69
2
1