Procházet zdrojové kódy

feat: color functions changes

Sv443 před 7 měsíci
rodič
revize
54ee0ce340

+ 5 - 0
.changeset/empty-walls-complain.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": patch
+---
+
+Consolidated behavior of `lightenColor()` and `darkenColor()` when using non-number values

+ 5 - 0
.changeset/healthy-llamas-exist.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": major
+---
+
+Changed `hexToRgb()` and `rgbToHex()` to support hex color codes with an alpha channel

+ 5 - 0
.changeset/silly-mayflies-explode.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": minor
+---
+
+Added parameter `upperCase` (false by default) to `lightenColor()` and `darkenColor()`

+ 19 - 12
README.md

@@ -2154,20 +2154,23 @@ The color functions are used to manipulate and convert colors in various formats
 ### hexToRgb()
 ### hexToRgb()
 Usage:  
 Usage:  
 ```ts
 ```ts
-hexToRgb(hex: string): [red: number, green: number, blue: number]
+hexToRgb(hex: string): [red: number, green: number, blue: number, alpha?: number]
 ```  
 ```  
   
   
-Converts a hex color string to an RGB color tuple array.  
-Accepts the formats `#RRGGBB` and `#RGB`, with or without the hash symbol.  
+Converts a hex color string to an RGB or RGBA color tuple array.  
+The values of R, G and B will be in the range of 0-255, while the alpha value will be in the range of 0-1.  
+Accepts the formats `#RRGGBB`, `#RRGGBBAA`, `#RGB` and `#RGBA`, with or without the hash symbol.  
   
   
 <details><summary><b>Example - click to view</b></summary>
 <details><summary><b>Example - click to view</b></summary>
 
 
 ```ts
 ```ts
 import { hexToRgb } from "@sv443-network/userutils";
 import { hexToRgb } from "@sv443-network/userutils";
 
 
-hexToRgb("#ff0000"); // [255, 0, 0]
-hexToRgb("0032ef");  // [0, 50, 239]
-hexToRgb("#0f0");    // [0, 255, 0]
+hexToRgb("#aaff85aa"); // [170, 255, 133, 0.6666666666666666]
+hexToRgb("#ff0000");   // [255, 0, 0, undefined]
+hexToRgb("0032ef");    // [0, 50, 239, undefined]
+hexToRgb("#0f0");      // [0, 255, 0, undefined]
+hexToRgb("0f0f");      // [0, 255, 0, 1]
 ```
 ```
 </details>
 </details>
 
 
@@ -2176,7 +2179,7 @@ hexToRgb("#0f0");    // [0, 255, 0]
 ### rgbToHex()
 ### rgbToHex()
 Usage:  
 Usage:  
 ```ts
 ```ts
-rgbToHex(red: number, green: number, blue: number, withHash?: boolean, upperCase?: boolean): string
+rgbToHex(red: number, green: number, blue: number, alpha?: number, withHash?: boolean, upperCase?: boolean): string
 ```  
 ```  
   
   
 Converts RGB color values to a hex color string.  
 Converts RGB color values to a hex color string.  
@@ -2188,9 +2191,9 @@ The `upperCase` parameter determines if the output should be in uppercase (false
 ```ts
 ```ts
 import { rgbToHex } from "@sv443-network/userutils";
 import { rgbToHex } from "@sv443-network/userutils";
 
 
-rgbToHex(255, 0, 0);             // "#ff0000"
-rgbToHex(255, 0, 0, false);      // "ff0000"
-rgbToHex(255, 0, 0, true, true); // "#FF0000"
+rgbToHex(255, 0, 0);                        // "#ff0000" (with hash symbol, lowercase)
+rgbToHex(255, 0, 0, 0.5, false);            // "ff000080" (with alpha, no hash symbol, lowercase)
+rgbToHex(255, 0, 0, undefined, true, true); // "#FF0000" (no alpha, with hash symbol, uppercase)
 ```
 ```
 </details>
 </details>
 
 
@@ -2199,11 +2202,12 @@ rgbToHex(255, 0, 0, true, true); // "#FF0000"
 ### lightenColor()
 ### lightenColor()
 Usage:  
 Usage:  
 ```ts
 ```ts
-lightenColor(color: string, percent: number): string
+lightenColor(color: string, percent: number, upperCase?: boolean): string
 ```  
 ```  
   
   
 Lightens a CSS color value (in hex, RGB or RGBA format) by a given percentage.  
 Lightens a CSS color value (in hex, RGB or RGBA format) by a given percentage.  
 Will not exceed the maximum range (00-FF or 0-255).  
 Will not exceed the maximum range (00-FF or 0-255).  
+If the `upperCase` parameter is set to true (default is false), the output will be in uppercase.  
 Throws an error if the color format is invalid or not supported.  
 Throws an error if the color format is invalid or not supported.  
   
   
 <details><summary><b>Example - click to view</b></summary>
 <details><summary><b>Example - click to view</b></summary>
@@ -2212,6 +2216,7 @@ Throws an error if the color format is invalid or not supported.
 import { lightenColor } from "@sv443-network/userutils";
 import { lightenColor } from "@sv443-network/userutils";
 
 
 lightenColor("#ff0000", 20);              // "#ff3333"
 lightenColor("#ff0000", 20);              // "#ff3333"
+lightenColor("#ff0000", 20, true);        // "#FF3333"
 lightenColor("rgb(0, 255, 0)", 50);       // "rgb(128, 255, 128)"
 lightenColor("rgb(0, 255, 0)", 50);       // "rgb(128, 255, 128)"
 lightenColor("rgba(0, 255, 0, 0.5)", 50); // "rgba(128, 255, 128, 0.5)"
 lightenColor("rgba(0, 255, 0, 0.5)", 50); // "rgba(128, 255, 128, 0.5)"
 ```
 ```
@@ -2222,11 +2227,12 @@ lightenColor("rgba(0, 255, 0, 0.5)", 50); // "rgba(128, 255, 128, 0.5)"
 ### darkenColor()
 ### darkenColor()
 Usage:  
 Usage:  
 ```ts
 ```ts
-darkenColor(color: string, percent: number): string
+darkenColor(color: string, percent: number, upperCase?: boolean): string
 ```  
 ```  
   
   
 Darkens a CSS color value (in hex, RGB or RGBA format) by a given percentage.  
 Darkens a CSS color value (in hex, RGB or RGBA format) by a given percentage.  
 Will not exceed the maximum range (00-FF or 0-255).  
 Will not exceed the maximum range (00-FF or 0-255).  
+If the `upperCase` parameter is set to true (default is false), the output will be in uppercase.  
 Throws an error if the color format is invalid or not supported.  
 Throws an error if the color format is invalid or not supported.  
   
   
 <details><summary><b>Example - click to view</b></summary>
 <details><summary><b>Example - click to view</b></summary>
@@ -2235,6 +2241,7 @@ Throws an error if the color format is invalid or not supported.
 import { darkenColor } from "@sv443-network/userutils";
 import { darkenColor } from "@sv443-network/userutils";
 
 
 darkenColor("#ff0000", 20);              // "#cc0000"
 darkenColor("#ff0000", 20);              // "#cc0000"
+darkenColor("#ff0000", 20, true);        // "#CC0000"
 darkenColor("rgb(0, 255, 0)", 50);       // "rgb(0, 128, 0)"
 darkenColor("rgb(0, 255, 0)", 50);       // "rgb(0, 128, 0)"
 darkenColor("rgba(0, 255, 0, 0.5)", 50); // "rgba(0, 128, 0, 0.5)"
 darkenColor("rgba(0, 255, 0, 0.5)", 50); // "rgba(0, 128, 0, 0.5)"
 ```
 ```

+ 31 - 23
lib/colors.ts

@@ -1,41 +1,52 @@
 import { clamp } from "./math.js";
 import { clamp } from "./math.js";
 
 
-/** Converts a hex color string to a tuple of RGB values */
-export function hexToRgb(hex: string): [red: number, green: number, blue: number] {
-  hex = hex.trim();
-  const bigint = parseInt(hex.startsWith("#") ? hex.slice(1) : hex, 16);
+/**
+ * Converts a hex color string in the format `#RRGGBB`, `#RRGGBBAA` (or even `RRGGBB` and `RGB`) to a tuple.  
+ * @returns Returns a tuple array where R, G and B are an integer from 0-255 and alpha is a float from 0 to 1, or undefined if no alpha channel exists.
+ */
+export function hexToRgb(hex: string): [red: number, green: number, blue: number, alpha?: number] {
+  hex = (hex.startsWith("#") ? hex.slice(1) : hex).trim();
+  const a = hex.length === 8 || hex.length === 4 ? parseInt(hex.slice(-(hex.length / 4)), 16) / (hex.length === 8 ? 255 : 15) : undefined;
+
+  if(!isNaN(Number(a)))
+    hex = hex.slice(0, -(hex.length / 4));
+
+  if(hex.length === 3 || hex.length === 4)
+    hex = hex.split("").map(c => c + c).join("");
+
+  const bigint = parseInt(hex, 16);
 
 
   const r = (bigint >> 16) & 255;
   const r = (bigint >> 16) & 255;
   const g = (bigint >> 8) & 255;
   const g = (bigint >> 8) & 255;
-  const b = (bigint & 255);
+  const b = bigint & 255;
 
 
-  return [clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255)];
+  return [clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255), typeof a === "number" ? clamp(a, 0, 1) : undefined];
 }
 }
 
 
-/** Converts RGB values to a hex color string */
-export function rgbToHex(red: number, green: number, blue: number, withHash = true, upperCase = false): string {
+/** Converts RGB or RGBA number values to a hex color string in the format `#RRGGBB` or `#RRGGBBAA` */
+export function rgbToHex(red: number, green: number, blue: number, alpha?: number, withHash = true, upperCase = false): string {
   const toHexVal = (n: number) =>
   const toHexVal = (n: number) =>
     clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0")[(upperCase ? "toUpperCase" : "toLowerCase")]();
     clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0")[(upperCase ? "toUpperCase" : "toLowerCase")]();
-  return `${withHash ? "#" : ""}${toHexVal(red)}${toHexVal(green)}${toHexVal(blue)}`;
+  return `${withHash ? "#" : ""}${toHexVal(red)}${toHexVal(green)}${toHexVal(blue)}${alpha ? toHexVal(alpha * 255) : ""}`;
 }
 }
 
 
 /**
 /**
- * Lightens a CSS color value (in hex, RGB or RGBA format) by a given percentage.  
+ * Lightens a CSS color value (in #HEX, rgb() or rgba() format) by a given percentage.  
  * Will not exceed the maximum range (00-FF or 0-255).
  * Will not exceed the maximum range (00-FF or 0-255).
  * @returns Returns the new color value in the same format as the input
  * @returns Returns the new color value in the same format as the input
  * @throws Throws if the color format is invalid or not supported
  * @throws Throws if the color format is invalid or not supported
  */
  */
-export function lightenColor(color: string, percent: number): string {
-  return darkenColor(color, percent * -1);
+export function lightenColor(color: string, percent: number, upperCase = false): string {
+  return darkenColor(color, percent * -1, upperCase);
 }
 }
 
 
 /**
 /**
- * Darkens a CSS color value (in hex, RGB or RGBA format) by a given percentage.  
+ * Darkens a CSS color value (in #HEX, rgb() or rgba() format) by a given percentage.  
  * Will not exceed the maximum range (00-FF or 0-255).
  * Will not exceed the maximum range (00-FF or 0-255).
  * @returns Returns the new color value in the same format as the input
  * @returns Returns the new color value in the same format as the input
  * @throws Throws if the color format is invalid or not supported
  * @throws Throws if the color format is invalid or not supported
  */
  */
-export function darkenColor(color: string, percent: number): string {
+export function darkenColor(color: string, percent: number, upperCase = false): string {
   color = color.trim();
   color = color.trim();
 
 
   const darkenRgb = (r: number, g: number, b: number, percent: number): [number, number, number] => {
   const darkenRgb = (r: number, g: number, b: number, percent: number): [number, number, number] => {
@@ -47,28 +58,25 @@ export function darkenColor(color: string, percent: number): string {
 
 
   let r: number, g: number, b: number, a: number | undefined;
   let r: number, g: number, b: number, a: number | undefined;
 
 
-  const isHexCol = color.match(/^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/);
+  const isHexCol = color.match(/^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/);
 
 
   if(isHexCol)
   if(isHexCol)
-    [r, g, b] = hexToRgb(color);
+    [r, g, b, a] = hexToRgb(color);
   else if(color.startsWith("rgb")) {
   else if(color.startsWith("rgb")) {
     const rgbValues = color.match(/\d+(\.\d+)?/g)?.map(Number);
     const rgbValues = color.match(/\d+(\.\d+)?/g)?.map(Number);
-    if (rgbValues)
-      [r, g, b, a] = rgbValues as [number, number, number, number?];
-    else
+    if(!rgbValues)
       throw new Error("Invalid RGB/RGBA color format");
       throw new Error("Invalid RGB/RGBA color format");
+    [r, g, b, a] = rgbValues as [number, number, number, number?];
   }
   }
   else
   else
     throw new Error("Unsupported color format");
     throw new Error("Unsupported color format");
 
 
   [r, g, b] = darkenRgb(r, g, b, percent);
   [r, g, b] = darkenRgb(r, g, b, percent);
 
 
-  const upperCase = color.match(/[A-F]/) !== null;
-
   if(isHexCol)
   if(isHexCol)
-    return rgbToHex(r, g, b, color.startsWith("#"), upperCase);
+    return rgbToHex(r, g, b, a, color.startsWith("#"), upperCase);
   else if(color.startsWith("rgba"))
   else if(color.startsWith("rgba"))
-    return `rgba(${r}, ${g}, ${b}, ${a})`;
+    return `rgba(${r}, ${g}, ${b}, ${a ?? NaN})`;
   else if(color.startsWith("rgb"))
   else if(color.startsWith("rgb"))
     return `rgb(${r}, ${g}, ${b})`;
     return `rgb(${r}, ${g}, ${b})`;
   else
   else