Functions
Import these from @grafloria/renderer.
Functions
assertThemeContrast
Throw with a readable diff when a theme does not conform. Use in tests/CI.
tsfunction 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.
tsfunction auditThemeContrast(theme: Theme, textLevel: number = WCAG.AA_TEXT): ContrastReport
clearStyles
Drop every named style (tests, and hosts tearing a document down).
tsfunction clearStyles(): void
contrastRatio
Contrast ratio between two colours, 1…21. undefined when either colour is
not one we can parse (see {@link parseColor}).
tsfunction contrastRatio(a: string, b: string): number | undefined
cssVarName
--grafloria-node-fill — the custom property a token maps to.
tsfunction 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.
tsfunction defineStyle(name: string, style: NamedStyle): void
defineStyles
Define several named styles at once.
tsfunction 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.
tsfunction 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).
tsfunction 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.
tsfunction 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.
tsfunction 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.
tsfunction 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.
tsfunction 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.
tsfunction generateInstanceVarBlock(theme: Theme, instanceId: string): string
generateTokenBridgeBlock
The bridge's CSS block for one instance, or '' when there is nothing to map.
tsfunction 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.
tsfunction getStyle(name: string): NamedStyle | undefined
getStyleRegistryVersion
Bumped on every mutation — renderers key cache invalidation off this.
tsfunction getStyleRegistryVersion(): number
hasStyle
tsfunction hasStyle(name: string): boolean
hslToRgb
tsfunction hslToRgb({ h, s, l }: Hsl): Rgb
instanceScopeSelector
[data-grafloria-instance="grafloria-3"] — selects one diagram's root (and scoped hosts).
tsfunction instanceScopeSelector(instanceId: string): string
isThemeRef
True when a style value is a theme reference rather than a literal.
tsfunction isThemeRef(value: unknown): value is ThemeRef
lightnessOf
HSL lightness of a colour, 0-1. Undefined for unparseable input.
tsfunction 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.
tsfunction linkTypeKey(link: LinkModel): string
listStyles
tsfunction listStyles(): string[]
meetsContrast
Does this pair clear a threshold? Unparseable colours are NOT a pass.
tsfunction meetsContrast(a: string, b: string, minimum: number): boolean
muiBridge
MUI (Material UI) with CSS-variable theming enabled
(extendTheme / CssVarsProvider, which publishes --mui-palette-*).
tsfunction muiBridge(prefix = '--mui'): TokenBridge
onStyleRegistryChange
Subscribe to registry mutations. Returns the unsubscribe function.
tsfunction 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.
tsfunction parseColor(value: string): Rgb | undefined
readColorPreferences
Read the three OS preferences right now.
tsfunction readColorPreferences(): ColorPreferences
relativeLuminance
WCAG relative luminance (0 = black, 1 = white).
tsfunction relativeLuminance(color: Rgb): number
removeStyle
tsfunction 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".
tsfunction resolveBindableVars(theme: Theme): Record<string, string>
resolveLinkStyle
Resolve a link's effective style. ONE ordered spread — see the header.
tsfunction 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.
tsfunction resolveNodeSelectionLook(node: NodeModel, theme: Theme): NodeSelectionLook
resolveNodeStyle
Resolve a node's effective style. ONE ordered spread — see the header.
tsfunction 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.
tsfunction resolveStyleClasses<T extends NamedStyle>(styleClass: string | undefined): Partial<T>
resolveThemeFromPrefs
THE resolution rule, as one pure function.
colorModepicks the light/dark AXIS ('system' → the OS's answer).- 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.
tsfunction 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.
tsfunction 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.
tsfunction resolveThemeVars(theme: Theme): Record<string, string>
rgbToHsl
tsfunction rgbToHsl({ r, g, b }: Rgb): Hsl
shadcnBridge
tsfunction 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.
tsfunction 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}.
tsfunction 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.
tsfunction themeRefCssValue(token: string, theme: Theme): string | undefined
themeRefToken
The token a reference points at.
tsfunction 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.
tsfunction themeRefVar(token: string): string
themeVar
var(--grafloria-node-fill) — the reference the shared stylesheet is written in.
tsfunction themeVar(token: ThemeToken): string
themeVarValue
Serialize one token's value for CSS (adds the unit for numeric bindings).
tsfunction themeVarValue(token: ThemeToken, theme: Theme): string
toHex
{r:255,g:0,b:0} → #ff0000.
tsfunction toHex({ r, g, b }: Rgb): string
withLightness
Same hue and saturation, new lightness. The primitive the dark-flip is built on.
tsfunction withLightness(hex: string, lightness: number): string
Was this page helpful?