Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Template literal types for strongly typed string values #131

Open
ndugger opened this issue May 21, 2021 · 6 comments
Open

Template literal types for strongly typed string values #131

ndugger opened this issue May 21, 2021 · 6 comments

Comments

@ndugger
Copy link

ndugger commented May 21, 2021

I'm working on a library that's essentially typescript functions that generate valid CSS. Having just discovered template literal types, I figured out that you can essentially have strongly typed CSS strings. I think this could help tighten up csstype's property types even further for things like CSS units, colors, functions, etc.

Some examples:

export type PCT = `${ number }%`

export function pct(amount: number): PCT {
    return `${ amount }%`
}
export type NumberOrPercent = number | PCT

export type HEX = `#${ string }`
export type RGB = `rgb(${ NumberOrPercent } ${ NumberOrPercent } ${ NumberOrPercent } / ${ NumberOrPercent })`

export type Color = HEX | RGB

export function rgb(r: NumberOrPercent, g: NumberOrPercent, b: NumberOrPercent, alpha: NumberOrPercent): RGB {
    return `rgb(${ r } ${ g } ${ b } / ${ alpha })`
}

export type ColorContrastFn = `color-contrast(${ Color } vs ${ Color })`

export function colorContrast(base: Color, compare: Color): ColorContrastFn {
    return `color-contrast(${ base } vs ${ compare })`
}

With the above types, you restrict the types of strings that are allowed to be passed into other interpolated string types to form strongly typed CSS.

// These are all valid:
colorContrast('#FFF', '#000')
colorContrast('rgb(255 255 255 / 1)', '#000')
colorContrast(rgb(255, 0, 0), 'rgb(255 0 0 / 1)')
// These are type errors:
colorContrast('asdf', '12345')
colorContrast('flex-start', 'flex-end')

This means that the Properties interface could become more strict to be something like:

interface Properties {
    backgroundColor: Color
    color: Color
}

Is this something that sounds useful for these type definitions? Is it something that's doable based on how the type definitions are even being generated?

@ndugger
Copy link
Author

ndugger commented May 21, 2021

I should have experimented a bit more, because I just bumped up against Expression produces a union type that is too complex to represent., I guess I have to simplify my own string types now, so there are definitely issues with this approach that I wasn't aware of.

@frenic
Copy link
Owner

frenic commented May 21, 2021

Template string literals will be used to some extent but we need to ensure the performance and be able to support TS < 4.1 with a separate definition file (without template string literals).

@ndugger
Copy link
Author

ndugger commented May 28, 2021

After spending several hours on this myself, I've gotten it to work, but the types degrade quickly in cases of the newly proposed color functions, where you could pass the value of color-contrast() as the parameter of another color-contrast() call, which template literals does not support well or at all.

There is a TS proposal that is suggesting adding regex types for additional string validation, I think this body of work in CSSType is probably best left until that is [hopefully] added in, otherwise you can only be exact up to a certain point before needing to degrade your type specificity.

@frenic
Copy link
Owner

frenic commented Jun 3, 2021

Thank you for digging into this! I think even with regex validation of string value types will be hard to work with. Image the error message you will receive when doing a minor mistake, that the browser even in several cases will forgive. But we'll see when, and if, something like that will be implemented in TS.

@buschtoens
Copy link

While I agree that strongly-typing <color> is not really feasible yet, strongly-typing any <dimension> and <percentage> value should be realistically possible, as the syntax is fairly strict for them.

Dimensions

A <dimension> is a <number> with a unit attached to it, for example 45deg, 100ms, or 10px. The attached unit identifier is case insensitive. There is never a space or any other characters between a the number and the unit identifier: i.e. 1 cm is not valid.

CSS uses dimensions to specify:

Percentages

The <percentage> data type consists of a <number> followed by the percentage sign (%). Optionally, it may be preceded by a single + or - sign, although negative values are not valid for all properties. As with all CSS dimensions, there is no space between the symbol and the number.

Would you accept a pull request? Do you have any further thoughts?

@buschtoens
Copy link

Also, what's the rationale for not exporting the DataType namespace?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants