@exabytellc/utils
Version:
EB react utils to make everything a little easier!
427 lines (417 loc) • 12 kB
JavaScript
import ClassHelper from "../_helpers/ClassHelper";
import { validParam } from "../_helpers/handleError";
import { mapToObject } from "../list";
import { clamp, round, numLinearTo } from "../math";
import { padStart } from "../str";
import { isNull, isNum } from "../types";
import { Colors, HSLtoRGB, RGBToHSL } from "../colors";
export default class Color extends ClassHelper {
//========< variable & constructor
#r;
get r() { return this.#r }
#g;
get g() { return this.#g }
#b;
get b() { return this.#b }
#o;
get o() { return this.#o }
#s = 0;
get shade() { return this.#s; }
get avg() { return ((this.r + this.g + this.b) / 3) }
//========< variable & constructor
#hsl = null;
get hsl() {
if (!this.#hsl) this.#hsl = RGBToHSL(this.#r, this.#g, this.#b);
return this.#hsl;
}
//========< variable & constructor
constructor(...args) {
super();
let r = 0, g = 0, b = 0, o = 1, s = null; // Default values
if (args.length === 1) {
const c = Color.#parse(args[0]);
if (c) {
({ r, g, b, o, shade: s } = c); // Destructuring assignment
}
} else if (args.length >= 3) { // Ensure at least RGB values are provided
[r, g, b, o] = args; // Destructuring assignment
} else {
validParam(true, args);
}
// Perform any additional validation if necessary
this.#r = r ?? 255;
this.#g = g ?? 255;
this.#b = b ?? 255;
this.#o = o ?? 1;
this.#s = s ?? (((Math.max(this.#r, this.#g, this.#b) / 255) + (Math.min(this.#r, this.#g, this.#b) / 255)) / 2);
}
//========< parse
static #parseHex(hex, hasAlpha = false) {
const step = hasAlpha ? 2 : 1;
const components = [];
for (let i = 0; i < hex.length; i += step) {
const component = parseInt(hex.substring(i, i + step), 16);
components.push(component);
}
return new Color(...components);
}
static #parse(color) {
return this.handleTryCatch({
n: "#parse",
e: () => {
// Check if color is already a Color object
if (Color.is(color)) return color;
// Check if color is a named color
if (Colors?.[color]) {
return Colors?.[color];
}
// Parse hexadecimal color codes (6-digit)
if (color?.match(/^#([a-f0-9]{6})$/i)) { // #ff00ff
return Color.#parseHex(color.substring(1));
}
// Parse hexadecimal color codes (3-digit)
if (color?.match(/^#([a-f0-9]{3})$/i)) { // #f0f
const hex = color.substring(1).split('').map(c => c + c).join('');
return Color.#parseHex(hex);
}
// Parse hexadecimal color codes with alpha (8-digit)
if (color?.match(/^#([a-f0-9]{8})$/i)) { // #ff00ff00
return Color.#parseHex(color.substring(1), true);
}
// Parse hexadecimal color codes with alpha (4-digit)
if (color?.match(/^#([a-f0-9]{4})$/i)) { // #f00f
const hex = color.substring(1).split('').map(c => c + c).join('');
return Color.#parseHex(hex, true);
}
// Parse RGB and RGBA color codes
const rgbMatch = color.match(/^rgba?\(([^)]+)\)$/i);
if (rgbMatch) {
const [r, g, b, a] = rgbMatch[1].split(',').map(val => parseFloat(val.trim()));
if (isNaN(r) || isNaN(g) || isNaN(b) || r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
throw new Error('Invalid RGB values');
}
const alpha = isNaN(a) ? 1 : Math.min(Math.max(a, 0), 1);
return new Color(r, g, b, alpha);
}
// Parse HSL and HSLA color codes
const hslMatch = color.match(/^hsla?\(([^)]+)\)$/i);
if (hslMatch) {
const [h, s, l, a] = hslMatch[1].split(',').map(val =>
parseFloat(val.trim().replace('%', ''))
);
if (isNaN(h) || isNaN(s) || isNaN(l) || h < 0 || h > 360 || s < 0 || s > 100 || l < 0 || l > 100) {
throw new Error('Invalid HSL values');
}
const hue = h / 360;
const saturation = s / 100;
const lightness = l / 100;
const alpha = isNaN(a) ? 1 : clamp(a, 0, 1);
return new Color(...Object.values(HSLtoRGB(hue, saturation, lightness)), alpha);
}
throw new Error('Unknown color format');
}
});
}
//========< parse
static is(value, canBeNull = false) {
return (
canBeNull && isNull(value) ||
!isNull(value) && typeof value === "object" && value instanceof Color
);
}
//========< brightness
lighten(perc) {
return this.handleTryCatch({
n: "lighten",
c: !isNum(perc),
p: { perc },
e: () => {
return new Color(
clamp(this.#r + ((255 - this.#r) * perc), 0, 255),
clamp(this.#g + ((255 - this.#g) * perc), 0, 255),
clamp(this.#b + ((255 - this.#b) * perc), 0, 255),
this.#o
);
}
});
}
darken(perc) {
return this.handleTryCatch({
n: "darken",
c: !isNum(perc),
p: { perc },
e: () => {
return new Color(
clamp(this.#r - (this.#r * perc), 0, 255),
clamp(this.#g - (this.#g * perc), 0, 255),
clamp(this.#b - (this.#b * perc), 0, 255),
this.#o
);
}
});
}
//========< opacity
fadeOut(perc) {
return this.handleTryCatch({
n: "fadeOut",
c: !isNum(perc),
p: { perc },
e: () => {
return new Color(
this.#r,
this.#g,
this.#b,
clamp(this.#o - perc, 0, 1)
);
}
});
}
fadeIn(perc) {
return this.handleTryCatch({
n: "fadeIn",
c: !isNum(perc),
p: { perc },
e: () => {
return new Color(
this.#r,
this.#g,
this.#b,
clamp(this.#o + perc, 0, 1)
);
}
});
}
//========<
setShade(perc) {
return this.handleTryCatch({
n: "setShade",
c: !isNum(perc),
p: { perc },
e: () => {
var { h, s } = RGBToHSL(this.#r, this.#g, this.#b);
var { r, g, b } = HSLtoRGB(h, s, perc * 100);
// Return a new Color instance with adjusted RGB values and same opacity
return new Color(r, g, b, this.#o);
}
});
}
addShade(perc) {
return this.handleTryCatch({
n: "addShade",
c: !isNum(perc),
p: { perc },
e: () => {
var { h, s, l } = RGBToHSL(this.#r, this.#g, this.#b);
var { r, g, b } = HSLtoRGB(h, s, (l + (perc * 100)));
// Return a new Color instance with adjusted RGB values and same opacity
return new Color(r, g, b, this.#o);
}
});
}
//========< opacity
setFade(o = 1) {
return this.handleTryCatch({
n: "setFade",
c: !isNum(o),
p: { o },
e: () => {
return new Color(
this.#r,
this.#g,
this.#b,
round(clamp(o, 0, 1), 2)
);
}
});
}
addFade(o = 1) {
return this.handleTryCatch({
n: "addFade",
c: !isNum(o),
p: { o },
e: () => {
return new Color(
this.#r,
this.#g,
this.#b,
round(clamp(this.#o + o, 0, 1), 2)
);
}
});
}
//========< check neigbors
isNeighborColor(color, tolerance = 0.12) {
color = new Color(color);
return this.handleTryCatch({
n: "isNeighborColor",
c: !Color.is(color) || !isNum(tolerance),
p: { color, tolerance },
e: () => {
color = new Color(color);
tolerance = tolerance * 255;
return (
Math.abs(this.#r - color.rgba[0]) <= tolerance &&
Math.abs(this.#g - color.rgba[1]) <= tolerance &&
Math.abs(this.#b - color.rgba[2]) <= tolerance
);
}
});
}
//========< to string format
get HEX() {
return this.handleTryCatch({
n: "HEX",
e: () => {
return `#${padStart(this.#r.toString(16), 2)}${padStart(
this.#g.toString(16),
2
)}${padStart(this.#b.toString(16), 2)}`;
}
});
}
get HEXA() {
return this.handleTryCatch({
n: "HEXA",
e: () => {
return `#${padStart(this.#r.toString(16), 2)}${padStart(
this.#g.toString(16), 2)}${padStart(this.#b.toString(16), 2)}${padStart(
Math.round(this.#o * 255).toString(16), 2)}`;
}
});
}
get RGB() {
return this.handleTryCatch({
n: "RGB",
e: () => {
return `rgb(${round(this.#r, 1)},${round(this.#g, 1)},${round(this.#b, 1)})`;
}
});
}
get RGBA() {
return this.handleTryCatch({
n: "RGBA",
e: () => {
return `rgba(${round(this.#r, 1)},${round(this.#g, 1)},${round(this.#b, 1)},${round(this.#o, 2)})`;
}
});
}
//========< get contrast
get contrastHueColor() {
return new Color(
255 - this.#r,
255 - this.#g,
255 - this.#b,
this.#o
);
}
get contrastShadeColor() {
return this.addShade(this.#s < 0.5 ? 0.7 : -0.7);
}
get contrastBlackWhiteColor() {
let c = this.#s < 0.5 ? 255 : 0;
return new Color(c, c, c, this.#o);
}
//========< get contrast
get shades() {
const o = {
900: 0.1,
800: 0.2,
700: 0.3,
600: 0.4,
500: 0.5,
400: 0.6,
300: 0.7,
200: 0.8,
100: 0.9,
50: 0.95,
}
return mapToObject(o, (k, v) => {
return {
[k]: this.setShade(v)
}
})
}
get fades() {
const o = {
5: 0.05,
10: 0.1,
20: 0.2,
30: 0.3,
40: 0.4,
50: 0.5,
60: 0.6,
70: 0.7,
80: 0.8,
90: 0.9,
95: 0.95,
}
return mapToObject(o, (k, v) => {
return {
[k]: this.setFade(v)
}
})
}
//========< hue to color
hueToColor(perc, color) {
color = new Color(color);
return this.handleTryCatch({
n: "hueToColor",
c: !isNum(perc) || !Color.is(color),
p: { perc, color },
f: null,
e: () => {
let mHSL = this.hsl;
let cHSL = color.hsl;
let dH = Math.abs(cHSL.h - mHSL.h) * perc;
let dS = Math.abs(cHSL.s - mHSL.s) * perc;
let dL = Math.abs(cHSL.l - mHSL.l) * perc;
let rgb = HSLtoRGB(
numLinearTo(mHSL.h, dH, cHSL.h),
numLinearTo(mHSL.s, dS, cHSL.s),
numLinearTo(mHSL.l, dL, cHSL.l)
);
return new Color(rgb.r, rgb.g, rgb.b, this.#o);
}
});
}
//========< get text
scaleToColor(perc, color) {
color = new Color(color);
return this.handleTryCatch({
n: "scaleToColor",
c: !isNum(perc) || !Color.is(color),
p: { perc, color },
f: null,
e: () => {
return new Color(
Math.round(this.r + (perc * (color.r - this.r))),
Math.round(this.g + (perc * (color.g - this.g))),
Math.round(this.b + (perc * (color.b - this.b))),
this.o
);
}
});
}
//========< get text
mixColors(color, mixAmount = 0.5) {
function mixC(cA, cB) {
var nCA = cA * mixAmount;
var nCB = cB * (1 - mixAmount);
return parseInt(nCA + nCB);
}
color = new Color(color);
return this.handleTryCatch({
n: "mixColors",
c: !Color.is(color) || !isNum(mixAmount),
p: { color, mixAmount },
e: () => {
return new Color(
mixC(this.#r, color.r),
mixC(this.#g, color.g),
mixC(this.#b, color.b),
mixC(this.#o, color.o),
);
}
});
}
}