1

Given this code:

const colors = {
  WHITE: "#FFF",
};
const style = {
  backgroundColor: colors.WHITE,
};

Throughout my code when I access style.backgroundColor I want the value to be "#FFF" as expected. But at a specific point I would like to know the reference key path as a string. For instance, I want the value to be "colors.WHITE". Is there a way to do that?

Stephani Bishop
  • 1,261
  • 1
  • 13
  • 20
  • 2
    No. The expression `colors.WHITE` is evaluated when your `style` object is built, and after that the only thing left is the value of that expression (`"#FFF"`). If you want to "remember" the entry in your `colors` object, store that as a separate property. – Pointy Aug 27 '22 at 16:53
  • Best you could do is `as const` for them, and then you'll be able to type-check that `colors.WHITE` is the same as `style.backgroundColor` – CertainPerformance Aug 27 '22 at 16:54

1 Answers1

0

I understand that it might be toooo much, but you can try this:

type ComputeRange<
    N extends number,
    Result extends Array<unknown> = [],
    > =
    (Result['length'] extends N
        ? Result[number]
        : ComputeRange<N, [...Result, Result['length']]>
    )

type HexNumber = `${ComputeRange<10>}`
type HexString = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'

type Hex = `${HexNumber}` | HexString;

type StringLength<
    Str extends string,
    Acc extends string[] = []
    > =
    (Str extends `${infer S}${infer Rest}`
        ? StringLength<Rest, [...Acc, S]>
        : Acc['length'])

type ValidateLength<
    Str extends string,
    Length extends number
    > =
    (StringLength<Str> extends Length
        ? Str
        : never)

type ValidateHex<
    Color extends string,
    Cache extends string = '',
    > =
    Color extends `${infer A}${infer Rest}`
    ? (A extends ''
        ? Cache
        : (A extends Hex
            ? ValidateHex<Rest, `${Cache}${A}`>
            : never)
    ) : Cache


type ExtractHash<T extends string> = T extends `#${infer Rest}` ? Rest : never


type ValidateMap<T extends Record<string, string>> = {
    [Prop in keyof T]: T[Prop] & ValidateHex<T[Prop]> & ValidateLength<T[Prop], 6 | 3>
}

type AddHash<T extends Record<string, string>> = { [Prop in keyof T]: `#${T[Prop]}` }


const color = <
    Key extends string,
    Value extends string,
    ColorMap extends Record<Key, Value>
>(dictionary: AddHash<ValidateMap<ColorMap>>) => dictionary


const colorTheme = color({
    white: '#ffffff',
    gray: '#e2e2e2',
    green: '#ffx' // expected error

})

colorTheme.gray // '#e2e2e2'

Playground

Pros:

TS will validate your hex value

Cons:

You need to create dummy runtime function for inference validation.

If you are interested in this solution I will provide some explanation.

Meanwhile, you can check my article and related answer