-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwarnings.ts
More file actions
116 lines (103 loc) · 3.68 KB
/
Copy pathwarnings.ts
File metadata and controls
116 lines (103 loc) · 3.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* Contrast-warning dispatcher.
*
* Tokens memoize their resolution, but a long-lived process (e.g. a dev
* server with HMR) can re-resolve the same theme many times. The cache
* here dedupes warnings within a session with a soft cap to keep noise
* bounded.
*/
import { apcaContrast } from './contrast-solver';
import type { ResolvedContrast } from './contrast-solver';
import { contrastRatioFromLuminance } from './okhsl-color-math';
const CONTRAST_WARN_CACHE_LIMIT = 256;
const contrastWarnCache = new Set<string>();
/**
* Slack factor below the requested target before we emit a warning.
* The contrast solver overshoots to absorb rounding noise, so an actual
* value within ~2x that overshoot is effectively a pass.
*/
const CONTRAST_WARN_SLACK_WCAG = 0.98;
/** APCA Lc is on a 0–106 scale; allow a small absolute slack. */
const CONTRAST_WARN_SLACK_APCA = 1.5;
function schemeLabel(isDark: boolean, isHighContrast: boolean): string {
if (isDark && isHighContrast) return 'darkContrast';
if (isDark) return 'dark';
if (isHighContrast) return 'lightContrast';
return 'light';
}
function metricLabel(c: ResolvedContrast): string {
return c.metric === 'apca'
? `APCA Lc ${c.target.toFixed(1)}`
: `WCAG ${c.target.toFixed(2)}`;
}
function dedupe(key: string): boolean {
if (contrastWarnCache.has(key)) return true;
if (contrastWarnCache.size >= CONTRAST_WARN_CACHE_LIMIT) {
contrastWarnCache.clear();
}
contrastWarnCache.add(key);
return false;
}
/** Warn when the solver could not reach the requested contrast floor. */
export function warnContrastUnmet(
name: string,
isDark: boolean,
isHighContrast: boolean,
contrast: ResolvedContrast,
actual: number,
): void {
const slack =
contrast.metric === 'apca'
? contrast.target - CONTRAST_WARN_SLACK_APCA
: contrast.target * CONTRAST_WARN_SLACK_WCAG;
if (actual >= slack) return;
const scheme = schemeLabel(isDark, isHighContrast);
const key = `unmet|${name}|${scheme}|${contrast.metric}|${contrast.target.toFixed(
2,
)}|${actual.toFixed(2)}`;
if (dedupe(key)) return;
console.warn(
`glaze: color "${name}" cannot meet ${metricLabel(contrast)} in ` +
`${scheme} scheme (got ${actual.toFixed(2)}). ` +
`Try widening the tone window, lowering the contrast target, ` +
`or picking a base color further from this color's tone.`,
);
}
/**
* Verification (§10): a chromatic swatch inherits the gray tone's
* lightness but drifts in real luminance, so a contrast-floored color may
* land slightly under the contrast its tone implies. Emit an advisory
* warning when the actual measured contrast drifts below the target.
*/
export function warnContrastDrift(
name: string,
isDark: boolean,
isHighContrast: boolean,
contrast: ResolvedContrast,
yColor: number,
yBase: number,
): void {
const actual =
contrast.metric === 'apca'
? Math.abs(
contrast.polarity === 'bg'
? apcaContrast(yBase, yColor)
: apcaContrast(yColor, yBase),
)
: contrastRatioFromLuminance(yColor, yBase);
const slack =
contrast.metric === 'apca'
? contrast.target - CONTRAST_WARN_SLACK_APCA
: contrast.target * CONTRAST_WARN_SLACK_WCAG;
if (actual >= slack) return;
const scheme = schemeLabel(isDark, isHighContrast);
const key = `drift|${name}|${scheme}|${contrast.metric}|${contrast.target.toFixed(
2,
)}|${actual.toFixed(2)}`;
if (dedupe(key)) return;
console.warn(
`glaze: color "${name}" drifts below ${metricLabel(contrast)} in ` +
`${scheme} scheme (measured ${actual.toFixed(2)}). Chromatic luminance ` +
`differs from the gray tone; nudge the tone or saturation if the floor matters.`,
);
}