import { Rgba } from '@nextblock-cms/utils'; /** WCAG 2.1 AA minimum contrast for body copy. */ export declare const BODY_TEXT_CONTRAST_THRESHOLD = 4.5; /** WCAG 2.1 AA minimum contrast for large text. */ export declare const LARGE_TEXT_CONTRAST_THRESHOLD = 3; /** WCAG's "large text" floor for regular weights: 18pt === 24px. */ export declare const LARGE_TEXT_MIN_PX = 24; /** WCAG's "large text" floor for bold weights: 14pt === 18.66px. */ export declare const LARGE_TEXT_BOLD_MIN_PX = 18.66; /** * Heading levels we are willing to treat as large text when the document does * not carry an explicit font size. * * This is a deliberate approximation and we would rather say so than pretend * otherwise: the only way to know a run's *rendered* size is to measure it in * the DOM, which would force a layout for every coloured run on every rebuild. * So an explicit `font-size` on the run wins when it exists, and otherwise we * assume h1-h3 clear 24px in the editor's typography while h4-h6 do not and are * held to the stricter body threshold. */ export declare const LARGE_TEXT_MAX_HEADING_LEVEL = 3; /** Stable class name on the hint decoration, so integrators can restyle it. */ export declare const LOW_CONTRAST_HINT_CLASS = "nb-low-contrast-hint"; export interface LowContrastVerdict { flag: boolean; ratio: number; } export interface LargeTextInput { bold: boolean; fontSize: string | null; headingLevel: number | null; } export interface LowContrastHintAttrsInput { backdrop: string; foreground: string; ratio: number; threshold: number; } /** * The index signature is what makes this structurally assignable to * ProseMirror's `DecorationAttrs`, which is an open bag of DOM attributes. */ export interface LowContrastHintAttrs { [attribute: string]: string; 'aria-label': string; class: string; style: string; title: string; } /** * The contrast bar a run has to clear. * * WCAG does not define a formula linking the body and large-text minimums — it * just publishes the pairs (4.5, 3) for AA and (7, 4.5) for AAA. Rather than * hardcode a table we scale a caller-supplied body threshold by the AA ratio, * which reproduces 3 exactly for the default 4.5 and stays proportional if an * integrator raises the bar. */ export declare function contrastThresholdFor(isLargeText: boolean, bodyThreshold?: number): number; /** * Decide whether a run of text is unreadable against the editing surface. * * `background` is expected to be opaque because the caller has already * flattened the DOM's background stack; any alpha on it is ignored rather than * guessed at. A colour we cannot parse is never flagged — a false positive on a * value we do not understand would put a black chip behind perfectly readable * text — and this function never throws, because it runs inside a ProseMirror * transaction handler where an exception would break editing entirely. */ export declare function shouldFlagLowContrast(foreground: string, background: string, isLargeText: boolean, bodyThreshold?: number): LowContrastVerdict; /** * Flatten a translucent colour onto the surface it is painted over. * * Contrast is a property of what the eye actually receives, so a 10%-opacity * black on white is effectively a pale grey and needs a *dark* chip, not the * light one its raw RGB would suggest. */ export declare function flattenColorOver(color: Rgba, background: Rgba): Rgba; /** * Pick the chip colour to paint behind an unreadable run. * * Contrast is symmetric, so the colour that would be readable *on* this text is * also the colour this text is readable *on*: `readableTextColor` therefore * doubles as a backdrop picker, giving pale text a black chip and dark text a * white one. Pass the flattened colour from {@link flattenColorOver} — the raw * value would mis-classify translucent text. * * The chip is fully opaque on purpose. Softening it with alpha would blend it * back towards the surface and re-introduce exactly the contrast loss we are * fixing; the rounded shape and dashed outline are what mark it as an * affordance, not transparency. */ export declare function pickBackdropColor(foreground: Rgba): string; /** * Flatten a stack of background layers, nearest-to-the-text first, onto a * fallback surface. * * The DOM walk collects `background-color` from the text's element and its * ancestors; the first opaque layer terminates the stack and everything nearer * is composited over it. When nothing opaque is found the caller's fallback * stands in. */ export declare function flattenBackgroundLayers(layers: readonly Rgba[], fallback: Rgba): Rgba; /** * Last-resort guess at the editing surface when every ancestor is transparent * all the way up (which happens inside portals and some print stylesheets). * * Hardcoding white here would break the dark and 'vibrant' CMS themes, where * the *dark* author colours are the invisible ones. The default text colour is * a reliable proxy for the theme: light default text means a dark page. */ export declare function inferSurfaceFromTextColor(textColor: Rgba): Rgba; /** * Pull one declaration out of an inline `style` attribute. * * The editor's `PreserveAllAttributesExtension` and `SpanNode` keep raw `style` * strings on nodes and on the `textStyle` mark, so pasted markup can carry a * colour that never reaches a typed Tiptap attribute. Later declarations win, * matching the cascade. This is a pragmatic split rather than a real CSS parser: * a value containing a semicolon (a `url(data:...;base64,...)` background, say) * will not be extracted, which for our purposes means "no colour found" and is * safely ignored. */ export declare function extractStyleDeclaration(style: string | null | undefined, property: string): string | null; /** * Convert a CSS font size to pixels. * * `rem` and `em` are resolved against a 16px root, which is the browser default * and what this workspace's Tailwind config assumes; we accept that this is an * approximation rather than measuring the cascade, because it only ever moves a * run between the 4.5:1 and 3:1 bars. */ export declare function parseFontSizePx(value: string | null | undefined): number | null; /** Whether a run qualifies for WCAG's relaxed large-text threshold. */ export declare function isLargeTextRun(input: LargeTextInput): boolean; /** Trim a contrast ratio to the shortest exact decimal, for display. */ export declare function formatContrastRatio(ratio: number): string; /** * Build the DOM attributes for a hint decoration. * * The chip deliberately keeps the author's real colour on top: we are making * their text legible, not overriding their choice, and an author who cannot see * the text also cannot guess why it suddenly looks different — hence the * `title`/`aria-label` quoting the measured ratio and saying, in as many words, * that this is an editing aid and is not saved. */ export declare function buildLowContrastHintAttrs(input: LowContrastHintAttrsInput): LowContrastHintAttrs;