Skip to content

Instantly share code, notes, and snippets.

@samermurad
Last active January 15, 2026 17:07
Show Gist options
  • Select an option

  • Save samermurad/54ca688b977632350c75417aa80485b0 to your computer and use it in GitHub Desktop.

Select an option

Save samermurad/54ca688b977632350c75417aa80485b0 to your computer and use it in GitHub Desktop.

Revisions

  1. samermurad revised this gist Jan 15, 2026. 1 changed file with 22 additions and 7 deletions.
    29 changes: 22 additions & 7 deletions Color.ts
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,4 @@
    type HexString = `#${number}`;
    type RGB = [number, number, number];
    type RGBA = [number, number, number, number];
    type ColorInput = HexString | string | number | RGB | RGBA; // | {r: number, g: number; b: number, alpha?: number};


    class Color {
    class Color implements Iterable<any>{
    private _hex: string;
    private _rgba: RGBA;

    @@ -95,6 +89,7 @@ class Color {
    if (component <= 0) component = 0;
    else if (component <= 1) component = component * 255.0
    else if (component > 1) component = Math.min(component, 255);
    component = Math.round(component);
    // add it to string as hex
    str += component.toString(16).padStart(2, '0');
    }
    @@ -108,4 +103,24 @@ class Color {
    toString() {
    return this._hex;
    }

    [Symbol.iterator](): Iterator<number> {
    let currentIndex = 0;
    const rgba = this.rgba;
    const size = rgba.length;
    return {
    next(): IteratorResult<number> {
    if (currentIndex < size) {
    const nextData = currentIndex++
    return {
    value: rgba[nextData],
    done: false,
    }
    } else {
    return { done: true, value: undefined };
    }
    }

    }
    }
    }
  2. samermurad revised this gist Jan 15, 2026. 1 changed file with 19 additions and 21 deletions.
    40 changes: 19 additions & 21 deletions examples.ts
    Original file line number Diff line number Diff line change
    @@ -1,35 +1,33 @@
    try {
    console.log(Color.normalizeHexString('#f'));
    console.log(new Color('#f'));
    } catch (error) {
    console.log(error.message);
    }

    try {
    console.log(Color.normalizeHexString('#rr'));
    console.log(new Color('#rr'));
    } catch (error) {
    console.log(error.message);
    }
    console.log(Color.normalizeHexString('#ff'));
    console.log(Color.normalizeHexString('#ffa'));
    console.log(Color.normalizeHexString('#f01a'));
    console.log(Color.normalizeHexString('#fd01b'));
    console.log(Color.normalizeHexString('#68ff46'));
    console.log(Color.normalizeHexString('#405c91a'));
    console.log(Color.normalizeHexString('#405c91aa'));
    console.log(new Color('#ff'));
    console.log(new Color('#ffa'));
    console.log(new Color('#f01a'));
    console.log(new Color('#fd01b'));
    console.log(new Color('#68ff46'));
    console.log(new Color('#405c91a'));
    console.log(new Color('#405c91aa'));
    try {
    console.log(Color.normalizeHexString('#405c91aae'));
    console.log(new Color('#405c91aae'));
    } catch (error) {
    console.log(error.message);
    }

    console.log(Color.hexStringToComponents('#ff00c3'));
    console.log(Color.hexStringToComponents('#ff00c3a'));
    console.log(Color.hexStringToComponents('#ff00c3a'));
    console.log(Color.normalizeHexString('#ff00c3a'));
    console.log(Color.componentsToHexString([1, 0, 0.7647058823529411, 0.6666666666666666]));
    console.log(Color.componentsToHexString([1, 0, 0.7647058823529411]));
    console.log(Color.componentsToHexString([255, 0, 255 * 0.7647058823529411, 255 * 0.6666666666666666]));
    console.log(Color.hexNumberToComponents(0xff00c3a));
    console.log(Color.hexNumberToComponents(0xffaacca));

    console.log(new Color('#ff'))
    console.log(new Color('#ff00c3'));
    console.log(new Color('#ff00c3a'));
    console.log(new Color('#ff00c3a'));
    console.log(new Color('#ff00c3a'));
    console.log(new Color([1, 0, 0.7647058823529411, 0.6666666666666666]));
    console.log(new Color([1, 0, 0.7647058823529411]));
    console.log(new Color([255, 0, 255 * 0.7647058823529411, 255 * 0.6666666666666666]));
    console.log(new Color(0xff00c3a));
    console.log(new Color(0xffaacca));
  3. samermurad created this gist Jan 15, 2026.
    111 changes: 111 additions & 0 deletions Color.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,111 @@
    type HexString = `#${number}`;
    type RGB = [number, number, number];
    type RGBA = [number, number, number, number];
    type ColorInput = HexString | string | number | RGB | RGBA; // | {r: number, g: number; b: number, alpha?: number};


    class Color {
    private _hex: string;
    private _rgba: RGBA;


    get hex(): string {
    return this._hex;
    }
    get rgba(): RGBA {
    return this._rgba
    }

    constructor(input: ColorInput) {
    if (input === undefined) throw new Error('ColorInput must be a supported type: (HexString/HexNumber/RGB/RGBA/)');
    if (typeof input === 'string') {
    this._hex = Color.normalizeHexString(input);
    this._rgba = Color.hexStringToComponents(this._hex)
    } else if (typeof input === 'number') {
    this._rgba = Color.hexNumberToComponents(input);
    this._hex = Color.componentsToHexString(this._rgba);
    } else if (Array.isArray(input)) {
    if (!(input.length === 4 || input.length === 3)) throw new Error('must use RGB | RGBA as array input');
    this._hex = Color.componentsToHexString(input);
    this._rgba = Color.hexStringToComponents(this._hex);
    } else if (typeof input === 'object') {
    // defaults in invalid pink color
    const { r = 1, g = 0, b = 0.7647058823529411, a = 1 } = input;
    this._hex = Color.componentsToHexString([r,g,b,a]);
    this._rgba = Color.hexStringToComponents(this._hex)
    } else {
    this._hex = Color.normalizeHexString('#ff00c3a');
    this._rgba = Color.hexStringToComponents(this._hex);
    }
    }

    static normalizeHexString(hex: string): HexString {
    const hexNoPound = hex.replace('#', '');
    if (hexNoPound.length < 2 || hexNoPound.length > 8) throw new Error('Invalid hex value: ' + hex);
    let fixHex = '';
    switch (hexNoPound.length) {
    case 2:
    fixHex = `${hexNoPound}${hexNoPound}${hexNoPound}`;
    break;
    case 3: // 0xffff => 0xff_ff_ff
    case 4: // 0xffff => 0xff_ff_ff_ff
    fixHex = `${hexNoPound.split('').map(f => `${f}${f}`).join('')}`;
    break;
    case 5: {// 0xf_f_f_f_f
    const [r1, r2, g1, g2, b] = hexNoPound as any;
    fixHex = `${r1}${r2}${g1}${g2}${b}${b}`;
    break;
    }
    case 6:
    case 8:
    fixHex = hexNoPound // string is correct;
    /* no op*/
    break;
    case 7: {// 0xf_f_f_f_f_f_f
    const [r1, r2, g1, g2, b1, b2, a] = hexNoPound as any;
    fixHex = `${r1}${r2}${g1}${g2}${b1}${b2}${a}${a}`;
    break;
    }
    }
    if (isNaN(parseInt(fixHex, 16))) throw new Error(fixHex + ' is not a hex number.');
    return `#${fixHex}` as HexString;
    }
    static hexStringToComponents(hexStr: string) : RGBA {
    const normalizedHex = Color.normalizeHexString(hexStr).replace('#', '');
    const hex = parseInt(normalizedHex, 16);
    if (isNaN(hex)) throw new Error(hex + ' is not a hex number.');

    let rgba = [1, 1, 1, 1]; // r, g, b, a
    const size = (normalizedHex.length / 2);

    for (let i = size - 1; i >= 0; i--)
    rgba[(size - 1 - i)] = ((hex >> (i * 8)) & 0xff) / 255.0; // 24 / 16 / 8 / 0;
    const [r, g, b, a] = rgba;

    return [r, g, b, a];
    }
    static hexNumberToComponents(hex: number): RGBA {
    return Color.hexStringToComponents(hex.toString(16));
    }
    static componentsToHexString(components: RGB | RGBA): HexString {
    let str = '';
    for (let i = 0; i < components.length; i++) {
    let component = components[i];
    // normalize component value
    if (component <= 0) component = 0;
    else if (component <= 1) component = component * 255.0
    else if (component > 1) component = Math.min(component, 255);
    // add it to string as hex
    str += component.toString(16).padStart(2, '0');
    }
    return Color.normalizeHexString(str);
    }

    valueOf() {
    return this._hex;
    }

    toString() {
    return this._hex;
    }
    }
    35 changes: 35 additions & 0 deletions examples.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,35 @@
    try {
    console.log(Color.normalizeHexString('#f'));
    } catch (error) {
    console.log(error.message);
    }

    try {
    console.log(Color.normalizeHexString('#rr'));
    } catch (error) {
    console.log(error.message);
    }
    console.log(Color.normalizeHexString('#ff'));
    console.log(Color.normalizeHexString('#ffa'));
    console.log(Color.normalizeHexString('#f01a'));
    console.log(Color.normalizeHexString('#fd01b'));
    console.log(Color.normalizeHexString('#68ff46'));
    console.log(Color.normalizeHexString('#405c91a'));
    console.log(Color.normalizeHexString('#405c91aa'));
    try {
    console.log(Color.normalizeHexString('#405c91aae'));
    } catch (error) {
    console.log(error.message);
    }

    console.log(Color.hexStringToComponents('#ff00c3'));
    console.log(Color.hexStringToComponents('#ff00c3a'));
    console.log(Color.hexStringToComponents('#ff00c3a'));
    console.log(Color.normalizeHexString('#ff00c3a'));
    console.log(Color.componentsToHexString([1, 0, 0.7647058823529411, 0.6666666666666666]));
    console.log(Color.componentsToHexString([1, 0, 0.7647058823529411]));
    console.log(Color.componentsToHexString([255, 0, 255 * 0.7647058823529411, 255 * 0.6666666666666666]));
    console.log(Color.hexNumberToComponents(0xff00c3a));
    console.log(Color.hexNumberToComponents(0xffaacca));

    console.log(new Color('#ff'))