/*{ "order": 3, "description": "Utilities for writing CSS as JS objects, generating stylesheets, and working with CSS variables and themes via css(), vars, and varDefault()." }*/
/*#
# CSS
`tosijs` provides a collection of utilities for working with CSS rules that
help leverage CSS variables to produce highly maintainable and lightweight
code that is nonetheless easy to customize.
> The design goal of `css`, `vars`, and `varDefault` is to make working with
> CSS3 more intuitive, maintainable, easier, and expressive than using libraries
> like Tailwind.
The basic goal is to be able to implement some or all of our CSS very efficiently, compactly,
and reusably in Javascript because:
- Javascript quality tooling is really good, CSS quality tooling is terrible
- Having to write CSS in Javascript is *inevitable* so it might as well be consistent and painless
- It turns out you can get by with *much less* and generally *simpler* CSS this way
- You get some natural wins this way. E.g. writing two definitions of `body {}` is easy to do
and bad in CSS. In Javascript it's simply an error!
The `css` module attempts to implement all this the simplest and most obvious way possible,
providing syntax sugar to help with best-practices such as `css-variables` and the use of
`@media` queries to drive consistency, themes, and accessibility.
## css(styleMap: TosiStyleMap): string
A function that, given a `TosiStyleMap` renders CSS code. What is a TosiStyleMap?
It's kind of what you'd expect if you wanted to represent CSS as Javascript in
the most straightforward way possible. It allows for things like `@import`,
`@keyframes` and so forth, but knows just enough about CSS to help with things
like autocompletion of CSS rules (rendered as camelcase) so that, unlike me, it
can remind you that it's `whiteSpace` and not `whitespace`.
```
import {elements, css} from 'tosijs'
const {style} = elements
const myStyleMap = {
body: {
color: 'red'
},
button: {
borderRadius: 5
}
}
document.head.append(style(css(myStyleMap)))
```
There's a convenient `Stylesheet()` function that does all this and adds an id to the
resulting `
```
If a bare, non-zero **number** is assigned to a CSS property it will have 'px' suffixed
to it automatically. There are *no bare numeric*ele properties in CSS except `0`.
Why `px`? Well the other obvious options would be `rem` and `em` but `px` seems the
least surprising option.
`css` should render nested rules, such as `@keyframes` and `@media` correctly.
## Initializing CSS Variables
You can initialize CSS variables using `_` or `__` prefixes on property names.
One bar, turns the camelCase property-name into a --snake-case CSS variable
name, while two creates a default that can be overridden.
```
StyleSheet('my-theme', {
':root', {
_fooBar: 'red',
__bazBar: '10px'
}
})
```
Will produce:
```
```
```js
import { elements, vars } from 'tosijs'
const { div } = elements
window.CSS.registerProperty({
name: '--at-bar',
syntax: '',
inherits: true,
initialValue: 'green',
})
preview.append(
div(
{
style: {
_fooBar: 'red',
__bazBar: 'blue',
}
},
div(
{
style: { color: vars.fooBar },
},
'fooBar'
),
div(
{
style: { color: vars.bazBar },
},
'bazBar'
),
div(
{
style: { color: vars.atBar },
},
'atBar'
),
)
)
```
> ### @property and CSS.registerProperty() considered harmful
>
> This [new CSS feature}(https://developer.mozilla.org/en-US/docs/Web/CSS/@property)
> is well-intentioned but ill-considered. I advise
> against using it yourself until its serious flaws are addressed. The problem
> is that if someone registers a variable you're using or you register
> a variable someone else is using then your CSS may be broken. And
> you can't re-register a variable either.
> This is a bit like the problem
> that tosijs Component works around with tagNames, but in practice far more
> difficult to solve. It is impossible to tell if a given instance of
> a given variable name is an intentional reuse or a new separate variable.
> No-one intentionally defines two different components with the same tag.
## invertLuminance({[key: string]: any}) => {[key: string]: string}
Given a map of CSS properties (in camelCase) emit a map of those properties that
has color values with their luminance inverted.
const myStyleMap = {
':root': cssVars, // includes --font-size
'@media (prefers-color-scheme: dark)': {
':root': invertLuminance(cssVars) // omits --font-size
},
}
## vars
`vars` is a proxy object that will return a css variable string from
a camelCase property, e.g.
vars.camelCase // 'var(--camel-case)'
> **it isn't called `var`** because that's a reserved word!
### varDefault
`varDefault` is a proxy object just like `vars` except that it returns a
`function` that takes a property and renders it as a css variable reference
with a default, e.g
varDefault.borderColor('red') // `var(--border-color, red)`
## `getCssVar(variable: string, atElement = document.body): string`
`getCssVar()` obtains the css variable evaluated at the specified element
(an element defined at `:root` can be evaluated at `document.body`). You
can provide the name, e.g. `--foo-bar`, or "wrapped", e.g. `var(--foo-bar)`.
### Syntax Sugar for `calc(...)`
More importantly, `vars` allows you to conveniently perform calculations
on css (dimensional) variables by a percentage:
vars.camelSize50 // 'calc(var(--camel-size) * 0.5)'
vars.camelSize_50 // 'calc(var(--camel-size) * -0.5)'
### Computed Colors
> #### Notes
>
> `color()` and `color-mix()` are [now enjoy 91% support](https://caniuse.com/?search=color-mix) as of writing.
> See [color-mix()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) documentation.
> Where they meet your needs, I'd suggest using them.
>
> [contrast-color()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/contrast-color) is coming in Safari 26,
> but [currently enjoys 0% upport](https://caniuse.com/?search=contrast-color).
>
> **Note** although these look superficially like the `vars` syntax sugar for
> `calc()` on dimensional variables, they are in fact color calculations
> performed on colors *evaluated* on `document.body`, emitted into a dedicated
> `tosijs-computed-colors` stylesheet.
>
> **They ARE recomputed on theme change** — every registered computed color is
> recalculated whenever an observant stylesheet regenerates (see
> [StyleSheet](#stylesheetid-string-stylespec-xinstylesheet)), so a derived shade
> tracks its source variable automatically. (This documentation previously said
> the opposite; it was out of date.) Verified in-browser: changing `--brand`
> from `#3366cc` to `#cc3366` moves `--brand_20b` from `rgba(41,82,163,1)` to
> `rgba(163,41,82,1)` and the DOM restyles with it.
You can write:
```
const styleSpec = {
_lineHeight: 24,
_spacing: 5,
_buttonHeight: calc(`vars.lineHeight + vars.spacing200`)
)
```
And then render this as CSS and stick it into a StyleNode and it will work.
You *cannot* write:
```
const styleSpec = {
_background: '#fafafa',
_blockColor: vars.background_5b
}
```
Because `--background` isn't defined on `document.body` yet, so vars.background_5b
won't be able to tell what `--background` is going to be yet. So either you need to
do this in two stags (create a StyleNode that defines the base color `--background`
then define the computed colors and add this) OR use a `Color` instance:
```
const background = Color.fromCss('#fafafa')
initVars({
background: background.toHTML,
blockColor: background.brighten(-0.05).toHTML
})
```
Until browsers support color calculations the way they support dimension arithmetic with `calc()`
this is the miserable existence we all lead. That, or defining huge arrays of color
values that we mostly don't use and are often not exactly what we want. You choose!
> **New** color now supports CSS [named colors](https://developer.mozilla.org/en-US/docs/Web/CSS/named-color),
such as `black`, `red`, and `aliceblue`.
`vars` also allows you to perform color calculations on css (color)
variables:
#### Change luminance with `b` (for brighten) suffix
The scale value is treated as a percentage and moves the brightness
that far from its current value to 100% (if positive) or 0% (if negattive).
vars.textColor50b // increases the luminance of textColor
vars.textColor_50b // halves the luminance of textColor
#### Change saturation with `s` suffix
The scale value is treated as a percentage and moves the saturation
that far from its current value to 100% (if positive) or 0% (if negattive).
vars.textColor50s // increases the saturation of textColor
vars.textColor_50s // halves the saturation of textColor
#### Rotate hue with `h` suffix
vars.textColor30h // rotates the hue of textColor by 30°
vars.textColor_90h // rotates the hue of textColor by -90°
#### Set Opacity with `o` suffix
Unlike the other modifiers, `o` simply sets the opacity of the
resulting color to the value provided.
vars.textColor50o // textColor with opacity set to 0.5
## More to follow?
The more I use the `css` module, the more I like it and the more ideas I have
to make it even better, but I have a very tight size/complexity target
for `tosijs` so these new ideas really have to earn a spot. Perhaps the
feature I have come closest to adding and then decided against was providing
syntax-sugar for classs so that:
css({
_foo: {
color: 'red'
}
})
Would render:
.foo {
color: 'red'
}
But looking at the code I and others have written, the case for this is weak as most class
declarations are not just bare classes. This doesn't help with declarations
for `input.foo` or `.foo::after` or `.foo > *` and now there'd be things that
look different which violates the "principle of least surprise". So, no.
### Something to Declare
Where I am always looking to improve this module (and all of `tosijs`) is to
do a better job of **declaring** things to improve autocomplete behavior and
minimize casting and other Typescript antipatterns. E.g. adding a ton of
declarations to `elements` and `css` has done wonders to reduce the need for
stuff like `const nameElement = this.parts.nameField as unknown as HTMLInputElement`
and prevent css property typos without adding a single byte to the size of
the javascript payload.
## StyleSheet(id: string, styleSpec: TosiStyleSheet): HTMLStyleElement
Creates a `