When it comes to TypeScript, there isn't a direct equivalent for the HexColor
type. The limitation of union types is that they can only handle around 100,000 members, making it difficult to encompass all possible combinations beyond that threshold. While a three-character string of Hex
characters fits within this limit (223 < 11,000), anything four characters or longer exceeds it (224 > 200,000).
There has been ongoing discussion about introducing support for regular-expression validated string types in TypeScript, as seen in the open issue at microsoft/TypeScript#41160. If this feature were to be implemented, representing a HexColor
type would become feasible. In the absence of such functionality currently within the language, an alternate approach must be taken.
At present, the closest approximation involves forsaking a specific HexColor
type and opting for a generic approach with HexColor<T>
, serving as a constraint on a literal type input T
. The aim is to ensure that T extends HexColor<T>
if and only if T
could have been a valid HexColor
. Furthermore, the concept involves defining HexColor<T>
to write a generic utility function hexColor()
, accepting only valid hex color inputs and returning the input itself. Instead of declaring something like const y: HexColor = "012345"
, you'd use const y = hexColor("012345")
. This general methodology follows:
type HexColor<T> = ⋯;
const hexColor = <T extends HexColor<T>>(h: T) => h;
const y = hexColor("012345"); // okay
const n = hexColor("012GFE"); // error, "021GFE" is not "0210FE"
An example showcases one way to define HexColor<T>
, generating a six-character string literal where each character mirrors the corresponding Hex
character from
T</code if available, or defaults to <code>"0"
otherwise. For instance, "021GFE" becomes "0210FE" due to an invalid "G", while "ABCDEFA" translates to "ABCDEF" by trimming surplus characters.
This phenomenon encapsulates a tail-recursive conditional type mechanism. The L
type parameter acts as an accumulator maintaining a tuple's length equal to processed characters count so far. Conversely, the A
type parameter serves as an accumulated output tracker. By subdividing T
into its first component F
and the remaining string R
, recursion ensues based on these redefined parameters.
The subsequent demonstration subtly varies in presenting the hexColor
helper function compared to previous descriptions:
const hexColor = <T extends string>(
h: T extends HexColor<T> ? T : HexColor<T>) => h;
To circumvent circularity errors inherent in
T extends HexColor<T></code, the workaround entails restricting <code>T
to
string
initially before embracing
HexColor<T></code within <code>h
's type definition. Despite its unconventional appearance, the
T extends HexColor<T> ? T : HexColor<T>
conditional type adeptly infers string literals for evaluation against
HexColor<T></code, yielding either <code>T
or
HexColor<T></code based on success or failure.</p>
<p>A validation test then verifies this operational workflow:</p>
<pre><code>const y1 = hexColor("012345"); // okay
const y2 = hexColor("ABCDEF"); // okay
const y3 = hexColor("00ff00"); // okay
const n1 = hexColor("ABCDE"); // error!
// Argument of type "ABCDE" is not assignable to parameter of type "ABCDE0".
const n2 = hexColor("ABCDEFA"); // error!
// Argument of type "ABCDEFA" is not assignable to parameter of type "ABCDEF".
const n3 = hexColor("012GFE"); // error!
// Argument of type "012GFE" is not assignable to parameter of type "0120FE".
const n4 = hexColor("whatever"); // error!
// Argument of type "whatever" is not assignable to parameter of type "00a0e0".
In conclusion, successful calls yield expected outcomes, whereas faulty ones prompt informative error messages detailing the nearest valid input. Although clarity may waver for entirely incorrect entries such as "whatever," the system strives to deliver interpretative feedback beneficial to perplexed developers.
Explore Code on Playground