# Functions

Import these from `@grafloria/renderer`.

## Functions

### `assertThemeContrast`

Throw with a readable diff when a theme does not conform. Use in tests/CI.

```ts
function assertThemeContrast(theme: Theme, textLevel: number = WCAG.AA_TEXT): void
```

### `auditThemeContrast`

Every contrast pair in a theme that a reader depends on.

`textLevel` defaults to AA (4.5:1). Pass `WCAG.AAA_TEXT` to hold a theme to
AAA — which is what the high-contrast themes are built and tested against.

```ts
function auditThemeContrast(theme: Theme, textLevel: number = WCAG.AA_TEXT): ContrastReport
```

### `clearStyles`

Drop every named style (tests, and hosts tearing a document down).

```ts
function clearStyles(): void
```

### `contrastRatio`

Contrast ratio between two colours, 1…21. `undefined` when either colour is
not one we can parse (see {@link parseColor}).

```ts
function contrastRatio(a: string, b: string): number | undefined
```

### `cssVarName`

`--grafloria-node-fill` — the custom property a token maps to.

```ts
function cssVarName(token: ThemeToken): string
```

### `defineStyle`

Define (or redefine) a named style. Definitions are copied, so later mutation
of the caller's object has no effect.

```ts
function defineStyle(name: string, style: NamedStyle): void
```

### `defineStyles`

Define several named styles at once.

```ts
function defineStyles(styles: Record<string, NamedStyle>): void
```

### `deriveTheme`

Auto-derive a theme from another one, then PROVE it conforms.

The generation step is deliberately dumb (flip lightness for 'dark', keep the
palette for 'high-contrast'); the value is in the second step, which walks the
SAME audit the caller can run and repairs every enforceable failure with
`ensureContrast`. The result is asserted before it is returned, so a derived
theme that could not be repaired is a loud error, never a subtly unreadable
diagram.

Derived alongside the surfaces: the STATE colours (selected / highlighted /
hovered / disabled / error), the SECONDARY text, and the semantic category
palette — i.e. everything a theme swap has to move together for the diagram to
stay coherent.

```ts
function deriveTheme(options: DeriveThemeOptions): Theme
```

### `ensureContrast`

Push `foreground` away from `background` until it clears `minimum`.

Walks in whichever direction the background is NOT (a light background darkens
the foreground, a dark one lightens it) in fixed steps, and takes the first
step that passes. Black/white are the endpoints, so on any real background
this terminates with a passing colour — that is why the derived themes can be
ASSERTED to conform rather than eyeballed.

Returns the input untouched when it already passes, or when either colour is
unparseable (nothing sensible to do — the audit will report it).

```ts
function ensureContrast(foreground: string, background: string, minimum: number): string
```

### `generateBaseStyleSheet`

The shared, theme-INDEPENDENT stylesheet. Identical for every renderer on the
page, so it is injected once and deduped by element id.

```ts
function generateBaseStyleSheet(): string
```

### `generateContrastPreferenceBlock`

`prefers-contrast: more`, in CSS.

The ColorModeController swaps in a whole high-contrast THEME when the host gave
it one. This block is what a host that did NOT still gets: thicker strokes. Weight is the half of contrast that colour cannot supply — a 1px hairline is
not an accessible border however dark it is — and it is the one thing we can
safely strengthen without knowing anything about the host's palette.

```ts
function generateContrastPreferenceBlock(instanceId: string): string
```

### `generateForcedColorsBlock`

The `@media (forced-colors: active)` variable override for one instance.

Emitted for every renderer, unconditionally: it costs one inert media block and
it is the FLOOR — it protects hosts that never supplied a high-contrast theme
and never touched `colorMode`. (The ColorModeController's high-contrast upgrade
is the ceiling: a better-looking, fully-checked theme when one exists.)

`forced-color-adjust: none` is deliberately NOT set: we are opting IN to the
user's colours, not out of them.

```ts
function generateForcedColorsBlock(instanceId: string): string
```

### `generateInstanceOverrideCSS`

Everything that must sit AFTER the theme's variable block, in cascade order. One string, one `<style>` element, one guaranteed ordering.

```ts
function generateInstanceOverrideCSS(
  bridge: TokenBridge | undefined,
  instanceId: string
): string
```

### `generateInstanceVarBlock`

One renderer's variable block: the ONLY place a theme's values are written. Scoped to that renderer's root, so a second diagram with a different theme
cannot be repainted by it.

```ts
function generateInstanceVarBlock(theme: Theme, instanceId: string): string
```

### `generateTokenBridgeBlock`

The bridge's CSS block for one instance, or `''` when there is nothing to map.

```ts
function generateTokenBridgeBlock(bridge: TokenBridge | undefined, instanceId: string): string
```

### `getStyle`

The raw definition, or undefined when the name was never defined.

DIAGRAM-FIRST, then process-global — see `ext/registry-scope.ts`. A diagram
that defined its own `critical` sees its own; one that did not still sees the
app-wide definition.

```ts
function getStyle(name: string): NamedStyle | undefined
```

### `getStyleRegistryVersion`

Bumped on every mutation — renderers key cache invalidation off this.

```ts
function getStyleRegistryVersion(): number
```

### `hasStyle`

```ts
function hasStyle(name: string): boolean
```

### `hslToRgb`

```ts
function hslToRgb({ h, s, l }: Hsl): Rgb
```

### `instanceScopeSelector`

`[data-grafloria-instance="grafloria-3"]` — selects one diagram's root (and scoped hosts).

```ts
function instanceScopeSelector(instanceId: string): string
```

### `isThemeRef`

True when a style value is a theme reference rather than a literal.

```ts
function isThemeRef(value: unknown): value is ThemeRef
```

### `lightnessOf`

HSL lightness of a colour, 0-1. Undefined for unparseable input.

```ts
function lightnessOf(hex: string): number | undefined
```

### `linkTypeKey`

A link's "type" for `theme.links[type]`: an explicit `type` metadata key when
the host sets one, else the path type (`smooth` / `orthogonal` / …), which is
the structural analogue of React Flow's `edge.type`.

```ts
function linkTypeKey(link: LinkModel): string
```

### `listStyles`

```ts
function listStyles(): string[]
```

### `meetsContrast`

Does this pair clear a threshold? Unparseable colours are NOT a pass.

```ts
function meetsContrast(a: string, b: string, minimum: number): boolean
```

### `muiBridge`

MUI (Material UI) with CSS-variable theming enabled
(`extendTheme` / `CssVarsProvider`, which publishes `--mui-palette-*`).

```ts
function muiBridge(prefix = '--mui'): TokenBridge
```

### `onStyleRegistryChange`

Subscribe to registry mutations. Returns the unsubscribe function.

```ts
function onStyleRegistryChange(listener: () => void): () => void
```

### `parseColor`

Parse the colour forms a theme can actually hold: `#rgb`, `#rgba`, `#rrggbb`,
`#rrggbbaa`, `rgb()/rgba()`.

`undefined` for anything else — a CSS system colour (`CanvasText`), a
`var(--x)`, a gradient spec. Callers must SKIP those rather than pretend a
ratio: a contrast claim about a value we cannot see is worse than no claim.

```ts
function parseColor(value: string): Rgb | undefined
```

### `readColorPreferences`

Read the three OS preferences right now.

```ts
function readColorPreferences(): ColorPreferences
```

### `relativeLuminance`

WCAG relative luminance (0 = black, 1 = white).

```ts
function relativeLuminance(color: Rgb): number
```

### `removeStyle`

```ts
function removeStyle(name: string): boolean
```

### `resolveBindableVars`

Every BINDABLE token of a theme, as `{ '--grafloria-colors-primary': '#2563eb', … }`.

This is the second half of the instance variable block (the first being the 33
chrome vars). Together they are the complete set of values a `themeRef` can
point at — which is exactly what makes a theme swap expressible as "rewrite
this instance's variables" instead of "rebuild every VNode".

```ts
function resolveBindableVars(theme: Theme): Record<string, string>
```

### `resolveLinkStyle`

Resolve a link's effective style. ONE ordered spread — see the header.

```ts
function resolveLinkStyle(
  link: LinkModel,
  theme: Theme,
  options: CascadeOptions = {}
): Partial<LinkStyle>
```

### `resolveNodeSelectionLook`

How this node shows it is selected, resolved through the cascade: its own
style, a named style, its type's theme defaults, the theme-wide default —
else 'both'. The renderer reads it for the ring and the CSS state class.

```ts
function resolveNodeSelectionLook(node: NodeModel, theme: Theme): NodeSelectionLook
```

### `resolveNodeStyle`

Resolve a node's effective style. ONE ordered spread — see the header.

```ts
function resolveNodeStyle(
  node: NodeModel,
  theme: Theme,
  options: CascadeOptions = {}
): Partial<NodeStyle>
```

### `resolveStyleClasses`

Merge a `styleClass` list into ONE partial style. Names are applied
left-to-right (later names win); unknown names are ignored.

```ts
function resolveStyleClasses<T extends NamedStyle>(styleClass: string | undefined): Partial<T>
```

### `resolveThemeFromPrefs`

THE resolution rule, as one pure function.

1. `colorMode` picks the light/dark AXIS ('system' → the OS's answer).
  2. An explicit contrast preference (`prefers-contrast: more`, or a forced-
     colors mode) then upgrades that axis to the high-contrast theme, when the
     caller supplied one. It is an UPGRADE, not a replacement: a user in dark
     mode who wants more contrast gets high-contrast DARK, not a light flash.

Note `forced-colors` ALSO gets a pure-CSS treatment (the variable block emits a
system-colour override under `@media (forced-colors: active)`), because the OS
palette must win even for a host that never passed a high-contrast theme. The
two are complementary: this picks the best THEME we have; that guarantees the
floor.

```ts
function resolveThemeFromPrefs(
  mode: ColorMode,
  prefs: ColorPreferences,
  themes: ThemeSet
): Theme
```

### `resolveThemeRef`

Resolve a token against a theme. `undefined` when the theme does not define it
— callers must treat that as "this property was never set" (the cascade layer
below it, or the stylesheet, then wins) rather than painting `undefined`.

```ts
function resolveThemeRef(token: string, theme: Theme): string | number | undefined
```

### `resolveThemeVars`

The whole theme as `{ '--grafloria-node-fill': '#ffffff', … }`. Also the natural shape for design-token export / Canvas-side lookups.

```ts
function resolveThemeVars(theme: Theme): Record<string, string>
```

### `rgbToHsl`

```ts
function rgbToHsl({ r, g, b }: Rgb): Hsl
```

### `shadcnBridge`

```ts
function shadcnBridge(options: { space?: 'hsl' | 'oklch' | 'raw' } = {}): TokenBridge
```

### `tailwindBridge`

Tailwind v4, whose theme IS a set of CSS variables (`--color-slate-200`, …). `scale` picks the neutral ramp so a host can stay on its own greys.

```ts
function tailwindBridge(options: { scale?: string; accent?: string } = {}): TokenBridge
```

### `themeRef`

Bind a property to a theme token.

node.setStyle({ fill: themeRef('category.critical') })

The `any` return is what lets a ThemeRef sit in a `fill?: string | Gradient | …`
slot: the engine's style types are the PUBLIC model contract and stay free of
any renderer type, so the renderer — the only thing that ever resolves a ref —
owns the marker and detects it structurally with {@link isThemeRef}.

```ts
function themeRef(token: ThemeToken | string): any
```

### `themeRefCssValue`

How a bound property is emitted in CSS mode when the target property accepts
`var()` (i.e. it is written into an inline CSS `style` string):
`var(--grafloria-category-critical, #b91c1c)`.

The literal FALLBACK matters: a theme that simply does not define the token
would otherwise make the declaration invalid at computed-value time and the
element would lose the property entirely (an SVG shape with no `fill` paints
black). With the fallback it degrades to the value the token had when the
VNode was built, which is the closest thing to "unchanged" available.

```ts
function themeRefCssValue(token: string, theme: Theme): string | undefined
```

### `themeRefToken`

The token a reference points at.

```ts
function themeRefToken(ref: ThemeRef): string
```

### `themeRefVar`

`node.selected.fill` → `--grafloria-node-selected-fill`.

Mechanical, not a lookup: the same rule reproduces every entry of THEME_VARS
(pinned by a test), so chrome tokens and caller tokens share ONE naming law
and nothing has to be registered ahead of time.

```ts
function themeRefVar(token: string): string
```

### `themeVar`

`var(--grafloria-node-fill)` — the reference the shared stylesheet is written in.

```ts
function themeVar(token: ThemeToken): string
```

### `themeVarValue`

Serialize one token's value for CSS (adds the unit for numeric bindings).

```ts
function themeVarValue(token: ThemeToken, theme: Theme): string
```

### `toHex`

`{r:255,g:0,b:0}` → `#ff0000`.

```ts
function toHex({ r, g, b }: Rgb): string
```

### `withLightness`

Same hue and saturation, new lightness. The primitive the dark-flip is built on.

```ts
function withLightness(hex: string, lightness: number): string
```
