Pārlūkot izejas kodu

feat: `hexToRgb()`, `rgbToHex()`, `lightenColor()`, `darkenColor()`

Sv443 8 mēneši atpakaļ
vecāks
revīzija
3b8aa884c1
5 mainītis faili ar 205 papildinājumiem un 3 dzēšanām
  1. 5 0
      .changeset/mean-guests-design.md
  2. 16 3
      .eslintrc.cjs
  3. 5 0
      README-summary.md
  4. 103 0
      README.md
  5. 76 0
      lib/colors.ts

+ 5 - 0
.changeset/mean-guests-design.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": minor
+---
+
+Added color manipulation functions `hexToRgb()`, `rgbToHex()`, `lightenColor()` and `darkenColor()`

+ 16 - 3
.eslintrc.cjs

@@ -26,12 +26,17 @@ module.exports = {
     indent: [
       "error",
       2,
-      { ignoredNodes: ["VariableDeclaration[declarations.length=0]"] },
+      {
+        ignoredNodes: ["VariableDeclaration[declarations.length=0]"],
+      },
     ],
     "@typescript-eslint/no-non-null-assertion": "off",
     "@typescript-eslint/no-unused-vars": [
       "warn",
-      { ignoreRestSiblings: true, argsIgnorePattern: "^_" },
+      {
+        ignoreRestSiblings: true,
+        argsIgnorePattern: "^_",
+      },
     ],
     "@typescript-eslint/ban-ts-comment": "off",
     "@typescript-eslint/ban-types": [
@@ -43,7 +48,15 @@ module.exports = {
         extendDefaults: true,
       },
     ],
-    "@typescript-eslint/explicit-function-return-type": "error",
+    "@typescript-eslint/explicit-function-return-type": [
+      "error",
+      {
+        allowConciseArrowFunctionExpressionsStartingWithVoid: true,
+        allowExpressions: true,
+        allowFunctionsWithoutTypeParameters: true,
+        allowIIFEs: true,
+      }
+    ],
     "comma-dangle": ["error", "only-multiline"],
     "no-misleading-character-class": "off",
   },

+ 5 - 0
README-summary.md

@@ -63,6 +63,11 @@ or view the documentation of previous major releases:
     - [`tr.addLanguage()`](https://github.com/Sv443-Network/UserUtils#traddlanguage) - add a language and its translations
     - [`tr.setLanguage()`](https://github.com/Sv443-Network/UserUtils#trsetlanguage) - set the currently active language for translations
     - [`tr.getLanguage()`](https://github.com/Sv443-Network/UserUtils#trgetlanguage) - returns the currently active language
+- **Colors:**
+    - [`hexToRgb()`](https://github.com/Sv443-Network/UserUtils#hextorgb) - convert a hex color string to an RGB number tuple
+    - [`rgbToHex()`](https://github.com/Sv443-Network/UserUtils#rgbtohex) - convert RGB numbers to a hex color string
+    - [`lightenColor()`](https://github.com/Sv443-Network/UserUtils#lightencolor) - lighten a CSS color string (hex, rgb or rgba) by a given percentage
+    - [`darkenColor()`](https://github.com/Sv443-Network/UserUtils#darkencolor) - darken a CSS color string (hex, rgb or rgba) by a given percentage
 - **Utility types for TypeScript:**
     - [`Stringifiable`](https://github.com/Sv443-Network/UserUtils#stringifiable) - any value that is a string or can be converted to one (implicitly or explicitly)
     - [`NonEmptyArray`](https://github.com/Sv443-Network/UserUtils#nonemptyarray) - any array that should have at least one item

+ 103 - 0
README.md

@@ -65,6 +65,11 @@ View the documentation of previous major releases:
     - [`tr.addLanguage()`](#traddlanguage) - add a language and its translations
     - [`tr.setLanguage()`](#trsetlanguage) - set the currently active language for translations
     - [`tr.getLanguage()`](#trgetlanguage) - returns the currently active language
+  - [**Colors:**](#colors)
+    - [`hexToRgb()`](#hextorgb) - convert a hex color string to an RGB number tuple
+    - [`rgbToHex()`](#rgbtohex) - convert RGB numbers to a hex color string
+    - [`lightenColor()`](#lightencolor) - lighten a CSS color string (hex, rgb or rgba) by a given percentage
+    - [`darkenColor()`](#darkencolor) - darken a CSS color string (hex, rgb or rgba) by a given percentage
   - [**Utility types for TypeScript:**](#utility-types)
     - [`Stringifiable`](#stringifiable) - any value that is a string or can be converted to one (implicitly or explicitly)
     - [`NonEmptyArray`](#nonemptyarray) - any array that should have at least one item
@@ -2074,6 +2079,104 @@ If no language has been set yet, it will return undefined.
 
 <br><br>
 
+## Colors:
+The color functions are used to manipulate and convert colors in various formats.  
+
+### hexToRgb()
+Usage:  
+```ts
+hexToRgb(hex: string): [red: number, green: number, blue: number]
+```  
+  
+Converts a hex color string to an RGB color tuple array.  
+Accepts the formats `#RRGGBB` and `#RGB`, with or without the hash symbol.  
+  
+<details><summary><b>Example - click to view</b></summary>
+
+```ts
+import { hexToRgb } from "@sv443-network/userutils";
+
+hexToRgb("#ff0000"); // [255, 0, 0]
+hexToRgb("0032ef");  // [0, 50, 239]
+hexToRgb("#0f0");    // [0, 255, 0]
+```
+
+</details>
+
+<br>
+
+### rgbToHex()
+Usage:  
+```ts
+rgbToHex(red: number, green: number, blue: number, withHash?: boolean, upperCase?: boolean): string
+```  
+  
+Converts RGB color values to a hex color string.  
+The `withHash` parameter determines if the hash symbol should be included in the output (true by default).  
+The `upperCase` parameter determines if the output should be in uppercase (false by default).  
+  
+<details><summary><b>Example - click to view</b></summary>
+
+```ts
+import { rgbToHex } from "@sv443-network/userutils";
+
+rgbToHex(255, 0, 0);             // "#ff0000"
+rgbToHex(255, 0, 0, false);      // "ff0000"
+rgbToHex(255, 0, 0, true, true); // "#FF0000"
+```
+
+</details>
+
+<br>
+
+### lightenColor()
+Usage:  
+```ts
+lightenColor(color: string, percent: number): string
+```  
+  
+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).  
+Throws an error if the color format is invalid or not supported.  
+  
+<details><summary><b>Example - click to view</b></summary>
+
+```ts
+import { lightenColor } from "@sv443-network/userutils";
+
+lightenColor("#ff0000", 20);              // "#ff3333"
+lightenColor("rgb(0, 255, 0)", 50);       // "rgb(128, 255, 128)"
+lightenColor("rgba(0, 255, 0, 0.5)", 50); // "rgba(128, 255, 128, 0.5)"
+```
+
+</details>
+
+<br>
+
+### darkenColor()
+Usage:  
+```ts
+darkenColor(color: string, percent: number): string
+```  
+  
+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).  
+Throws an error if the color format is invalid or not supported.  
+  
+<details><summary><b>Example - click to view</b></summary>
+
+```ts
+import { darkenColor } from "@sv443-network/userutils";
+
+darkenColor("#ff0000", 20);              // "#cc0000"
+darkenColor("rgb(0, 255, 0)", 50);       // "rgb(0, 128, 0)"
+darkenColor("rgba(0, 255, 0, 0.5)", 50); // "rgba(0, 128, 0, 0.5)"
+```
+
+</details>
+
+<br><br>
+
 <!-- #SECTION Utility types -->
 ## Utility types:
 UserUtils also offers some utility types that can be used in TypeScript projects.  

+ 76 - 0
lib/colors.ts

@@ -0,0 +1,76 @@
+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);
+
+  const r = (bigint >> 16) & 255;
+  const g = (bigint >> 8) & 255;
+  const b = (bigint & 255);
+
+  return [clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255)];
+}
+
+/** Converts RGB values to a hex color string */
+export function rgbToHex(red: number, green: number, blue: number, withHash = true, upperCase = false): string {
+  const toHexVal = (n: number) =>
+    clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0")[(upperCase ? "toUpperCase" : "toLowerCase")]();
+  return `${withHash ? "#" : ""}${toHexVal(red)}${toHexVal(green)}${toHexVal(blue)}`;
+}
+
+/**
+ * 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).
+ * @returns Returns the new color value in the same format as the input
+ * @throws Throws if the color format is invalid or not supported
+ */
+export function lightenColor(color: string, percent: number): string {
+  return darkenColor(color, percent * -1);
+}
+
+/**
+ * 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).
+ * @returns Returns the new color value in the same format as the input
+ * @throws Throws if the color format is invalid or not supported
+ */
+export function darkenColor(color: string, percent: number): string {
+  color = color.trim();
+
+  const darkenRgb = (r: number, g: number, b: number, percent: number): [number, number, number] => {
+    r = Math.max(0, Math.min(255, r - (r * percent / 100)));
+    g = Math.max(0, Math.min(255, g - (g * percent / 100)));
+    b = Math.max(0, Math.min(255, b - (b * percent / 100)));
+    return [r, g, b];
+  };
+
+  let r: number, g: number, b: number, a: number | undefined;
+
+  const isHexCol = color.match(/^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/);
+
+  if(isHexCol)
+    [r, g, b] = hexToRgb(color);
+  else if(color.startsWith("rgb")) {
+    const rgbValues = color.match(/\d+(\.\d+)?/g)?.map(Number);
+    if (rgbValues)
+      [r, g, b, a] = rgbValues as [number, number, number, number?];
+    else
+      throw new Error("Invalid RGB/RGBA color format");
+  }
+  else
+    throw new Error("Unsupported color format");
+
+  [r, g, b] = darkenRgb(r, g, b, percent);
+
+  const upperCase = color.match(/[A-F]/) !== null;
+
+  if(isHexCol)
+    return rgbToHex(r, g, b, color.startsWith("#"), upperCase);
+  else if(color.startsWith("rgba"))
+    return `rgba(${r}, ${g}, ${b}, ${a})`;
+  else if(color.startsWith("rgb"))
+    return `rgb(${r}, ${g}, ${b})`;
+  else
+    throw new Error("Unsupported color format");
+}