# `Noizu.Tessera.Component.Styles`
[🔗](https://github.com/the-robot-lives/tessera/blob/v0.2.0/lib/noizu/tessera/component/styles.ex#L1)

CSS helpers for the `styles` DSL (EXTENSION-CONTRACT.md §7 theming).

Pure functions over the recorded CSS text — deliberately NOT a CSS parser
or linter (Lit validates author CSS at runtime). Only what the theming
contract needs is extracted:

  * `custom_property_reads/2` — every `var(--tsr-<tag>-*)` read in a CSS
    string, with its fallback (`nil` when absent). Tag-scoped: un-prefixed
    globals and other tags' properties are not this element's contract
    surface and are left alone.
  * `missing_fallbacks/2` — the tag-scoped reads that lack a `var()`
    fallback (the contract requires one so an unthemed element renders).
  * `parts/1` — part names declared in template markup (`part="…"`).
  * `escape_css_template/1` — make CSS text safe inside a Lit `css```
    template literal (backslash, backtick and `${` escaped; the CSS bytes
    are preserved verbatim).

Known limits (Tier 1, thin by design): `var()` inside CSS strings
(`content: "var(…)"`) is scanned like real CSS, and shared style modules
(`{:shared, path}`) are JS-side — their reads are not scanned, so properties
read only inside a shared module must be declared by the consuming
component's own `styles` block.

# `custom_property_reads`

```elixir
@spec custom_property_reads(String.t(), String.t()) :: [
  %{name: String.t(), default: String.t() | nil}
]
```

Every `var(--tsr-<tag>-*)` read in `css`, as `%{name: name, default:
fallback_or_nil}` — sorted by name, first occurrence wins on duplicates.
Nested `var()` fallbacks are reported as their own reads too. Raises
`ArgumentError` on an unbalanced `var(`.

# `escape_css_template`

```elixir
@spec escape_css_template(String.t()) :: String.t()
```

Escape CSS text for inclusion in a Lit `css``` template literal: a raw
backtick would close the literal and a raw `${` would open a JS
interpolation — both are injection/corruption hazards in author CSS.
Backslash is escaped first so the escapes themselves are not re-escaped.

# `missing_fallbacks`

```elixir
@spec missing_fallbacks(String.t(), String.t()) :: [String.t()]
```

Names of the tag-scoped `var()` reads in `css` that carry no fallback.

# `parts`

```elixir
@spec parts(String.t() | nil) :: [String.t()]
```

Part names declared in template markup via `part="…"` / `part='…'`
(space-separated lists split), in first-appearance order, deduplicated.
`exportparts` and `data-part` are NOT part declarations.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
