Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

better-color-tools

Package Overview
Dependencies
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

better-color-tools - npm Package Compare versions

Comparing version 0.0.1 to 0.0.2

CODE_OF_CONDUCT.md

7

CHANGELOG.md
# better-color-tools
## 0.0.2
### Patch Changes
- 181de13: Improve HSL accuracy by standardizing to 2 decimal places for all 3 values (before, hue had 3; saturation & lightness had 1)
## 0.0.1
### Patch Changes
- ce992b2: Fixed hex and HSL bugs

44

dist/index.js

@@ -141,10 +141,30 @@ import NP from 'number-precision';

const hsl = [0, 0, 0, 1];
const values = strVal
.replace(/hsl\s*\(/i, '')
let [h, s, l, a] = strVal
.replace(/hsla?\s*\(/i, '')
.replace(/\)\s*$/, '')
.split(',');
hsl[0] = parseFloat(values[0]);
hsl[1] = values[1].includes('%') ? parseFloat(values[1]) / 100 : parseFloat(values[1]);
hsl[2] = values[2].includes('%') ? parseFloat(values[2]) / 100 : parseFloat(values[2]);
hsl[3] = parseFloat(values[3] || '1');
hsl[0] = parseFloat(h);
if ((s.includes('%') && !l.includes('%')) || (!s.includes('%') && l.includes('%')))
throw new Error(`Mix of % and normalized (0–1) values in "${strVal}", prefer one or the other.`);
if (s.includes('%')) {
const sVal = parseFloat(s) / 100;
const lVal = parseFloat(l) / 100;
if (sVal < 0 || sVal > 100)
throw new Error(`Saturation out of bounds for "${strVal}", must be between 0% and 100%`);
if (lVal < 0 || lVal > 100)
throw new Error(`Lightness out of bounds for "${strVal}", must be between 0% and 100%`);
hsl[1] = sVal;
hsl[2] = lVal;
}
else {
const sVal = parseFloat(s);
const lVal = parseFloat(l);
if (sVal < 0 || sVal > 1)
throw new Error(`Saturation out of bounds for "${strVal}", must be between 0 and 1 (or be a %)`);
if (lVal < 0 || lVal > 1)
throw new Error(`Lightness out of bounds for "${strVal}", must be between 0 and 1 (or be a %)`);
hsl[1] = sVal;
hsl[2] = lVal;
}
hsl[3] = parseFloat(a || '1');
const color = hslToRGB(hsl);

@@ -158,8 +178,4 @@ return validate(color);

throw new Error(`Unable to parse color "${rawColor}"`);
const color = [
parseInt(values[0], 10),
parseInt(values[1], 10),
parseInt(values[2], 10),
parseFloat((values[3] || '1').trim()), // a
];
const [r, g, b, a] = values;
const color = [parseInt(r, 10), parseInt(g, 10), parseInt(b, 10), parseFloat((a || '1').trim())];
return validate(color);

@@ -268,3 +284,3 @@ }

if (M == m)
return [H, S, NP.round(L / 255, 3), A];
return [H, S, NP.round(L / 255, 4), A];
// if any other color, calculate hue & saturation

@@ -293,3 +309,3 @@ const C = M - m;

}
return [NP.round(H, 3), NP.round(S, 3), NP.round(L / 255, 3), NP.round(A, 3)];
return [NP.round(H, 2), NP.round(S, 4), NP.round(L / 255, 4), A];
}

@@ -296,0 +312,0 @@ export default {

{
"name": "better-color-tools",
"description": "Better color manipulation for Sass and JavaScript / TypeScript.",
"version": "0.0.1",
"version": "0.0.2",
"author": {

@@ -28,9 +28,2 @@ "name": "Drew Powers",

"types": "./dist/index.d.ts",
"scripts": {
"build": "rm -rf dist && tsc",
"changeset": "changeset",
"dev": "tsc -w",
"lint": "eslint \"**/*.{js,ts}\"",
"test": "mocha --parallel"
},
"dependencies": {

@@ -52,3 +45,11 @@ "number-precision": "^1.5.1"

"typescript": "^4.5.2"
},
"scripts": {
"build": "rm -rf dist && tsc",
"changeset": "changeset",
"dev": "tsc -w",
"lint": "eslint \"**/*.{js,ts}\"",
"pretest": "npm run build",
"test": "mocha --parallel"
}
}
}

@@ -59,4 +59,7 @@ # better-color-tools

// mix color (0 = pure color1, 0.5 = even blend, 1 = pure color2)
$mix: color.mix(#1a7f37, #cf222e, 0.4);
$mix: color.mix(#1a7f37, #cf222e, 0); // 100% color 1, 0% color 2
$mix: color.mix(#1a7f37, #cf222e, 0.25); // 75%, 25%
$mix: color.mix(#1a7f37, #cf222e, 0.5); // 50%, 50%
$mix: color.mix(#1a7f37, #cf222e, 0.75); // 25%, 75%
$mix: color.mix(#1a7f37, #cf222e, 1); // 0%, 100%
```

@@ -66,4 +69,4 @@

⚠️ Still in development. It’s important to note that Sass’ new [`color.scale()`][sass-color-scale] tool is pretty advanced, and is actually good way to lighten / darken colors (previous methods have been lacking). Right now Sass’ `color.scale()` produces
better results than this library, and I’m not happy with that 🙂.
⚠️ Still in development. It’s important to note that Sass’ new [`color.scale()`][sass-color-scale] utility is now a fantastic way to lighten / darken colors (previous attempts had been lacking). `color.scale()` produces better results than this library,
currently, and I’m not happy with that 🙂.

@@ -73,7 +76,9 @@ ```scss

// 0 = current color, 1 = white, -1 = black
$lighter: color.lighten(#cf222e, 0.25);
$lighter: color.lighten(#cf222e, 0); // 0% lighter (original color)
$lighter: color.lighten(#cf222e, 0.25); // 25% lighter
$lighter: color.lighten(#cf222e, 1); // 100% lighter (pure white)
// 0 = current color, 1 = black, -1 = white
$darker: color.darken(#cf222e, 0.25);
$darker: color.darken(#cf222e, 0); // 0% darker (original color)
$darker: color.darken(#cf222e, 0.25); // 25% darker
$darker: color.darken(#cf222e, 1); // 100% darker (pure black)
```

@@ -87,7 +92,12 @@

_Note: you’ll see `0xcf222e` in the examples which is just another way of writing `'#cf222e'`. It’s just replacing the `#` with `0x`. Use what you prefer!_
```ts
import color from 'better-color-tools';
// mix color (0.5 = even blend, 0 = color 1, 1 = color 2)
const mix = color.mix('#1a7f37', '#cf222e', 0.4);
const mix = color.mix(0x1a7f37, 0xcf222e, 0); // 100% color 1, 0% color 2
const mix = color.mix(0x1a7f37, 0xcf222e, 0.25); // 75%, 25%
const mix = color.mix(0x1a7f37, 0xcf222e, 0.5); // 50%, 50%
const mix = color.mix(0x1a7f37, 0xcf222e, 0.75); // 25%, 75%
const mix = color.mix(0x1a7f37, 0xcf222e, 1); // 0%, 100%
```

@@ -102,7 +112,9 @@

// 1 = white, 0 = current color, -1 = black
const lighter = color.lighten('#cf222e', 0.5);
color.lighten(0xcf222e, 0); // 0% lighter (original color)
color.lighten(0xcf222e, 0.25); // 25% lighter
color.lighten(0xcf222e, 1); // 100% lighter (pure white)
// 1 = black, 0 = current color, -1 = white
const darker = color.darken('#cf222e', 0.5);
color.darken(0xcf222e, 0); // 0% darker (original color)
color.darken(0xcf222e, 0.25); // 25% darker
color.darken(0xcf222e, 1); // 100% darker (pure black)
```

@@ -114,21 +126,19 @@

It’s in HSL handling where approaches differ. Because HSL is a smaller color space than RGB, in order to use it, it **requires some use of decimals.** So any library that rounds out-of-the-box will yield different results.
It’s in HSL handling where approaches differ. Because HSL is a smaller color space than RGB, in order to use it, it **requires at least 1 decimal place.** So any library that rounds out-of-the-box will produce inaccurate results (compare this library to
[color-convert] converting from RGB -> HSL and back again):
Compare this library to [color-convert], converting from RGB -> HSL -> RGB
```ts
color.from(color.from([167, 214, 65]).hsl).rgbVal; // ✅ [167, 214, 65]
convert.hsl.rgb(convert.rgb.hsl(167, 214, 65)); // ❌ [168, 215, 66]
```
The reason, again, is rounding by default. This is a [known limitation of HSL][hsl], so many libraries can disable rounding with overrides, but in addition to that not being default behavior it also produces noisy results:
```ts
const original = [167, 214, 65];
color.from(color.from(original).hsl).rgb; // ✅ [167, 214, 65]
convert.hsl.rgb(convert.rgb.hsl(...original)); // ❌ [168, 215, 66]
color.from([167, 214, 65]).hsl; // hsl(78.93, 64.5%, 54.71%)
convert.rgb.hsl.raw([167, 214, 65]); // hsl(78.9261744966443, 64.50216450216452%, 54.70588235294118%)
```
2 things are the cause of the difference:
This library takes the opinion that **HSL should have RGB precision by default.** So this library generates values that support infinite conversions without quality loss that are still readable.
1. **JavaScript’s rounding errors.** Many implementations are borrowed from other languages that don’t have JavaScript’s “[bad math][number-precision].” This implementation was written with JavaScript in mind and minimizes decimal calculations through
tricks like normalizing to `255` rather than `1.`
2. **No rounding by default.** As stated before, **HSL requires decimal places** to produce the full RGB color space. When a library rounds by default it will always make HSL conversions inaccurate. This is a known limitation, so libraries like
color-convert will allow you to use decimals. But that generates numbers like `hsl(78.9261744966443, 64.50216450216452%, 54.70588235294118%)`. Compare that to better-color-tools: `hsl(78.926, 64.5%, 54.7%)`. Why do you have to choose between accuracy
and utility?
#### Usage

@@ -155,5 +165,5 @@

// convert color to hsl
color.from('#C4432B').hsl; // 'hsl(9.412, 64%, 46.9%, 1)'
color.from(0xc4432b).hsl; // 'hsl(9.412, 64%, 46.9%, 1)'
color.from('#C4432B').hslVal; // [9.412, 0.64, 0.469, 1]
color.from('#C4432B').hsl; // 'hsl(9.41, 64.02%, 46.86%, 1)'
color.from(0xc4432b).hsl; // 'hsl(9.41, 64.02%, 46.86%, 1)'
color.from('#C4432B').hslVal; // [9.41, 0.6402, 0.4686, 1]

@@ -164,6 +174,12 @@ // convert color names to hex

## TODO / Roadmap
- Adding color spaces like [Adobe](https://en.wikipedia.org/wiki/Adobe_RGB_color_space) and [Rec 709](https://en.wikipedia.org/wiki/Rec._709) to allow color mixing and lightening/darkening to use different perceptual color algorithms
- This library currently only supports 8-bit RGB (web & apps); is 16-bit useful? (create an issue!)
[color-convert]: https://github.com/Qix-/color-convert
[computer-color]: https://www.youtube.com/watch?v=LKnqECcg6Gw&vl=en
[hsl]: https://en.wikipedia.org/wiki/HSL_and_HSV#Disadvantages
[number-precision]: https://github.com/nefe/number-precision
[sass-color]: https://sass-lang.com/documentation/modules/color
[sass-color-scale]: https://sass-lang.com/documentation/modules/color#scale

@@ -143,10 +143,24 @@ import NP from 'number-precision';

const hsl: HSL = [0, 0, 0, 1];
const values = strVal
.replace(/hsl\s*\(/i, '')
let [h, s, l, a] = strVal
.replace(/hsla?\s*\(/i, '')
.replace(/\)\s*$/, '')
.split(',');
hsl[0] = parseFloat(values[0]);
hsl[1] = values[1].includes('%') ? parseFloat(values[1]) / 100 : parseFloat(values[1]);
hsl[2] = values[2].includes('%') ? parseFloat(values[2]) / 100 : parseFloat(values[2]);
hsl[3] = parseFloat(values[3] || '1');
hsl[0] = parseFloat(h);
if ((s.includes('%') && !l.includes('%')) || (!s.includes('%') && l.includes('%'))) throw new Error(`Mix of % and normalized (0–1) values in "${strVal}", prefer one or the other.`);
if (s.includes('%')) {
const sVal = parseFloat(s) / 100;
const lVal = parseFloat(l) / 100;
if (sVal < 0 || sVal > 100) throw new Error(`Saturation out of bounds for "${strVal}", must be between 0% and 100%`);
if (lVal < 0 || lVal > 100) throw new Error(`Lightness out of bounds for "${strVal}", must be between 0% and 100%`);
hsl[1] = sVal;
hsl[2] = lVal;
} else {
const sVal = parseFloat(s);
const lVal = parseFloat(l);
if (sVal < 0 || sVal > 1) throw new Error(`Saturation out of bounds for "${strVal}", must be between 0 and 1 (or be a %)`);
if (lVal < 0 || lVal > 1) throw new Error(`Lightness out of bounds for "${strVal}", must be between 0 and 1 (or be a %)`);
hsl[1] = sVal;
hsl[2] = lVal;
}
hsl[3] = parseFloat(a || '1');
const color = hslToRGB(hsl);

@@ -159,8 +173,4 @@ return validate(color);

if (values.length != 3 && values.length != 4) throw new Error(`Unable to parse color "${rawColor}"`);
const color: RGBA = [
parseInt(values[0], 10), // r
parseInt(values[1], 10), // g
parseInt(values[2], 10), // b
parseFloat((values[3] || '1').trim()), // a
];
const [r, g, b, a] = values;
const color: RGBA = [parseInt(r, 10), parseInt(g, 10), parseInt(b, 10), parseFloat((a || '1').trim())];
return validate(color);

@@ -271,3 +281,3 @@ }

// if white/black/gray, exit early
if (M == m) return [H, S, NP.round(L / 255, 3), A];
if (M == m) return [H, S, NP.round(L / 255, 4), A];

@@ -300,3 +310,3 @@ // if any other color, calculate hue & saturation

return [NP.round(H, 3), NP.round(S, 3), NP.round(L / 255, 3), NP.round(A, 3)];
return [NP.round(H, 2), NP.round(S, 4), NP.round(L / 255, 4), A];
}

@@ -303,0 +313,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc