@rookcom/monarch-tokens 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +7 -2
- package/dist/appearance.d.ts +36 -0
- package/dist/appearance.d.ts.map +1 -0
- package/dist/appearance.js +42 -0
- package/dist/appearance.js.map +1 -0
- package/dist/color.d.ts +42 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +121 -0
- package/dist/color.js.map +1 -0
- package/dist/contrast-report.json +1333 -0
- package/dist/css/base.css +31 -0
- package/dist/css/compat.css +149 -0
- package/dist/css/monarch.css +429 -0
- package/dist/css/products/app.css +85 -0
- package/dist/css/products/atlas.css +85 -0
- package/dist/css/products/base.css +85 -0
- package/dist/css/products/calendar.css +85 -0
- package/dist/css/products/chat.css +85 -0
- package/dist/css/products/developers.css +85 -0
- package/dist/css/products/dossier.css +85 -0
- package/dist/css/products/dots.css +85 -0
- package/dist/css/products/friends.css +85 -0
- package/dist/css/products/gtm.css +85 -0
- package/dist/css/products/id.css +85 -0
- package/dist/css/products/intellect.css +85 -0
- package/dist/css/products/links.css +85 -0
- package/dist/css/products/people.css +85 -0
- package/dist/css/products/pulse.css +85 -0
- package/dist/css/products/ranker.css +85 -0
- package/dist/css/products/recruit.css +85 -0
- package/dist/css/products/signal.css +85 -0
- package/dist/css/products/stride.css +85 -0
- package/dist/css/products/tickets.css +85 -0
- package/dist/css.d.ts +36 -0
- package/dist/css.d.ts.map +1 -0
- package/dist/css.js +242 -0
- package/dist/css.js.map +1 -0
- package/dist/generated/products.d.ts +111 -0
- package/dist/generated/products.d.ts.map +1 -0
- package/dist/generated/products.js +26 -0
- package/dist/generated/products.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/primitives.d.ts +190 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +210 -0
- package/dist/primitives.js.map +1 -0
- package/dist/tailwind.d.ts +275 -0
- package/dist/tailwind.d.ts.map +1 -0
- package/dist/tailwind.js +179 -0
- package/dist/tailwind.js.map +1 -0
- package/dist/themes.d.ts +74 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +292 -0
- package/dist/themes.js.map +1 -0
- package/dist/tokens.json +2455 -0
- package/package.json +60 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @rookcom/monarch-tokens
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Packages move to the public npm registry as `@rookcom/monarch-*` (were `@rookhq/monarch-*` on GitHub Packages). Apps install with plain `npm ci`; no registry token is needed. Releases are published from CI with npm trusted publishing and provenance.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- a2d52a3: Tokens: contrast is now measured on 8-bit colour as browsers paint it, so no pair passes in maths and fails on screen (Rook Blue `info` was 4.48:1 in the browser).
|
|
12
|
+
|
|
13
|
+
Conformance: recognises the new components and patterns as Monarch primitives; `monarch-ignore-next-line` skips comment-only lines so it can sit next to `eslint-disable-next-line`; 4-digit numbers like `Order #1042` are no longer reported as colours.
|
package/README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @rookcom/monarch-tokens
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Framework-independent Rook design tokens. Exports: `./css` (all themes),
|
|
4
|
+
`./css/base`, `./css/compat` (legacy shadcn names, deprecated),
|
|
5
|
+
`./css/products/<id>`, `./tailwind` (Tailwind 3 preset), `./appearance`
|
|
6
|
+
(pre-paint boot script), `./tokens.json`, `./contrast-report.json`, and a TS
|
|
7
|
+
API (`themes`, `resolveTheme`, `contrast`, `brand`, `products`).
|
|
8
|
+
Docs: ../../docs/guides/theming.md
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving the viewer's appearance into the attributes Monarch CSS reads.
|
|
3
|
+
*
|
|
4
|
+
* Themes switch on attributes only (see css.ts). This module turns a stored
|
|
5
|
+
* preference plus the OS settings into those attributes. `appearanceScript`
|
|
6
|
+
* returns an inline script for <head> that runs before first paint, so there is
|
|
7
|
+
* no flash of the wrong theme.
|
|
8
|
+
*/
|
|
9
|
+
export type ThemePreference = 'light' | 'dark' | 'system';
|
|
10
|
+
export type ContrastPreference = 'standard' | 'more' | 'system';
|
|
11
|
+
export interface Appearance {
|
|
12
|
+
readonly theme: 'light' | 'dark';
|
|
13
|
+
readonly contrast: 'standard' | 'more';
|
|
14
|
+
}
|
|
15
|
+
export interface Environment {
|
|
16
|
+
readonly prefersDark: boolean;
|
|
17
|
+
readonly prefersMoreContrast: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function resolveAppearance(theme: ThemePreference, contrast: ContrastPreference, env: Environment): Appearance;
|
|
20
|
+
/** Applies an appearance to an element (normally <html>). */
|
|
21
|
+
export declare function applyAppearance(el: HTMLElement, a: Appearance): void;
|
|
22
|
+
export interface AppearanceScriptOptions {
|
|
23
|
+
/** Product ID from the Monarch registry, set as data-mn-product. */
|
|
24
|
+
readonly product?: string;
|
|
25
|
+
/** Cookie holding light|dark|system. Rook apps use `rook_theme`. */
|
|
26
|
+
readonly themeCookie?: string;
|
|
27
|
+
/** Cookie holding standard|more|system. */
|
|
28
|
+
readonly contrastCookie?: string;
|
|
29
|
+
readonly defaultTheme?: ThemePreference;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Inline script source for <head>. Options are validated and JSON-encoded, so
|
|
33
|
+
* the output is safe to inject with dangerouslySetInnerHTML.
|
|
34
|
+
*/
|
|
35
|
+
export declare function appearanceScript(options?: AppearanceScriptOptions): string;
|
|
36
|
+
//# sourceMappingURL=appearance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appearance.d.ts","sourceRoot":"","sources":["../src/appearance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAC1D,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEhE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;CACvC;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,kBAAkB,EAC5B,GAAG,EAAE,WAAW,GACf,UAAU,CAKZ;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,UAAU,GAAG,IAAI,CAKpE;AAED,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,2CAA2C;IAC3C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,eAAe,CAAC;CACzC;AAKD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,MAAM,CAiB9E"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving the viewer's appearance into the attributes Monarch CSS reads.
|
|
3
|
+
*
|
|
4
|
+
* Themes switch on attributes only (see css.ts). This module turns a stored
|
|
5
|
+
* preference plus the OS settings into those attributes. `appearanceScript`
|
|
6
|
+
* returns an inline script for <head> that runs before first paint, so there is
|
|
7
|
+
* no flash of the wrong theme.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveAppearance(theme, contrast, env) {
|
|
10
|
+
return {
|
|
11
|
+
theme: theme === 'system' ? (env.prefersDark ? 'dark' : 'light') : theme,
|
|
12
|
+
contrast: contrast === 'system' ? (env.prefersMoreContrast ? 'more' : 'standard') : contrast,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** Applies an appearance to an element (normally <html>). */
|
|
16
|
+
export function applyAppearance(el, a) {
|
|
17
|
+
el.setAttribute('data-mn-theme', a.theme);
|
|
18
|
+
el.setAttribute('data-mn-contrast', a.contrast);
|
|
19
|
+
// `.dark` is kept because every Rook app's Tailwind `dark:` variant uses it.
|
|
20
|
+
el.classList.toggle('dark', a.theme === 'dark');
|
|
21
|
+
}
|
|
22
|
+
const ID = /^[a-z][a-z0-9-]{1,31}$/;
|
|
23
|
+
const COOKIE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
24
|
+
/**
|
|
25
|
+
* Inline script source for <head>. Options are validated and JSON-encoded, so
|
|
26
|
+
* the output is safe to inject with dangerouslySetInnerHTML.
|
|
27
|
+
*/
|
|
28
|
+
export function appearanceScript(options = {}) {
|
|
29
|
+
const { product, themeCookie = 'rook_theme', contrastCookie = 'mn_contrast', defaultTheme = 'light', } = options;
|
|
30
|
+
if (product !== undefined && !ID.test(product))
|
|
31
|
+
throw new Error(`Invalid product id: ${product}`);
|
|
32
|
+
if (!COOKIE.test(themeCookie) || !COOKIE.test(contrastCookie))
|
|
33
|
+
throw new Error('Invalid cookie name');
|
|
34
|
+
const cfg = JSON.stringify({
|
|
35
|
+
p: product ?? null,
|
|
36
|
+
t: themeCookie,
|
|
37
|
+
c: contrastCookie,
|
|
38
|
+
d: defaultTheme,
|
|
39
|
+
});
|
|
40
|
+
return `(function(){try{var o=${cfg};var e=document.documentElement;function k(n){var m=document.cookie.match(new RegExp('(?:^|; )'+n+'=([^;]*)'));return m?decodeURIComponent(m[1]):null}var t=k(o.t)||o.d,c=k(o.c)||'system',q=function(s){return window.matchMedia&&window.matchMedia(s).matches};if(t==='system')t=q('(prefers-color-scheme: dark)')?'dark':'light';if(t!=='dark')t='light';if(c==='system')c=q('(prefers-contrast: more)')?'more':'standard';if(c!=='more')c='standard';e.setAttribute('data-mn-theme',t);e.setAttribute('data-mn-contrast',c);e.classList.toggle('dark',t==='dark');if(o.p)e.setAttribute('data-mn-product',o.p)}catch(_){}})();`;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=appearance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appearance.js","sourceRoot":"","sources":["../src/appearance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,MAAM,UAAU,iBAAiB,CAC/B,KAAsB,EACtB,QAA4B,EAC5B,GAAgB;IAEhB,OAAO;QACL,KAAK,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK;QACxE,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ;KAC7F,CAAC;AACJ,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,eAAe,CAAC,EAAe,EAAE,CAAa;IAC5D,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1C,EAAE,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;IAChD,6EAA6E;IAC7E,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;AAClD,CAAC;AAYD,MAAM,EAAE,GAAG,wBAAwB,CAAC;AACpC,MAAM,MAAM,GAAG,uBAAuB,CAAC;AAEvC;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAmC,EAAE;IACpE,MAAM,EACJ,OAAO,EACP,WAAW,GAAG,YAAY,EAC1B,cAAc,GAAG,aAAa,EAC9B,YAAY,GAAG,OAAO,GACvB,GAAG,OAAO,CAAC;IACZ,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,OAAO,EAAE,CAAC,CAAC;IAClG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;QACzB,CAAC,EAAE,OAAO,IAAI,IAAI;QAClB,CAAC,EAAE,WAAW;QACd,CAAC,EAAE,cAAc;QACjB,CAAC,EAAE,YAAY;KAChB,CAAC,CAAC;IACH,OAAO,yBAAyB,GAAG,omBAAomB,CAAC;AAC1oB,CAAC"}
|
package/dist/color.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour math for Monarch tokens.
|
|
3
|
+
*
|
|
4
|
+
* All colours are authored in HSL because every Rook app already stores its
|
|
5
|
+
* tokens as HSL channels (`354 71% 52%`). Contrast is computed with the WCAG 2.x
|
|
6
|
+
* relative-luminance formula, which is what WCAG 2.2 AA is measured against.
|
|
7
|
+
*/
|
|
8
|
+
export interface Hsl {
|
|
9
|
+
/** Hue, 0–360. */
|
|
10
|
+
readonly h: number;
|
|
11
|
+
/** Saturation, 0–100. */
|
|
12
|
+
readonly s: number;
|
|
13
|
+
/** Lightness, 0–100. */
|
|
14
|
+
readonly l: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function hsl(h: number, s: number, l: number): Hsl;
|
|
17
|
+
export declare function clamp(value: number, min: number, max: number): number;
|
|
18
|
+
/** `354 71% 52%`, the channel format shadcn-style `hsl(var(--x) / a)` expects. */
|
|
19
|
+
export declare function toChannels(c: Hsl): string;
|
|
20
|
+
/** `hsl(354 71% 52%)`. */
|
|
21
|
+
export declare function toCss(c: Hsl): string;
|
|
22
|
+
/** sRGB in 0–1. */
|
|
23
|
+
export declare function toRgb(c: Hsl): [number, number, number];
|
|
24
|
+
export declare function toHex(c: Hsl): string;
|
|
25
|
+
/** Parses `#rrggbb` into HSL. Used for registry entries authored as hex. */
|
|
26
|
+
export declare function fromHex(hex: string): Hsl;
|
|
27
|
+
export declare function relativeLuminance(c: Hsl): number;
|
|
28
|
+
/** WCAG contrast ratio, 1–21. */
|
|
29
|
+
export declare function contrast(a: Hsl, b: Hsl): number;
|
|
30
|
+
/**
|
|
31
|
+
* Moves lightness in `direction` until `color` reaches `minRatio` against every
|
|
32
|
+
* colour in `against`. Hue and saturation are preserved so the brand stays
|
|
33
|
+
* recognisable. Throws if no lightness satisfies the target, so an impossible
|
|
34
|
+
* theme fails the build instead of shipping silently.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ensureContrast(color: Hsl, against: readonly Hsl[], minRatio: number, direction: 'darken' | 'lighten'): Hsl;
|
|
37
|
+
/** Picks whichever candidate foreground has the higher contrast on `bg`. */
|
|
38
|
+
export declare function bestForeground(bg: Hsl, candidates: readonly Hsl[]): {
|
|
39
|
+
color: Hsl;
|
|
40
|
+
ratio: number;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=color.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,GAAG;IAClB,kBAAkB;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,GAAG,CAExD;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAErE;AAOD,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAEzC;AAED,0BAA0B;AAC1B,wBAAgB,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAEpC;AAED,mBAAmB;AACnB,wBAAgB,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAOtD;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAWpC;AAED,4EAA4E;AAC5E,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAoBxC;AAWD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAShD;AAED,iCAAiC;AACjC,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,MAAM,CAK/C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,SAAS,GAAG,EAAE,EACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,QAAQ,GAAG,SAAS,GAC9B,GAAG,CAYL;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS,GAAG,EAAE,GAAG;IAAE,KAAK,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAQjG"}
|
package/dist/color.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour math for Monarch tokens.
|
|
3
|
+
*
|
|
4
|
+
* All colours are authored in HSL because every Rook app already stores its
|
|
5
|
+
* tokens as HSL channels (`354 71% 52%`). Contrast is computed with the WCAG 2.x
|
|
6
|
+
* relative-luminance formula, which is what WCAG 2.2 AA is measured against.
|
|
7
|
+
*/
|
|
8
|
+
export function hsl(h, s, l) {
|
|
9
|
+
return { h: ((h % 360) + 360) % 360, s: clamp(s, 0, 100), l: clamp(l, 0, 100) };
|
|
10
|
+
}
|
|
11
|
+
export function clamp(value, min, max) {
|
|
12
|
+
return Math.min(max, Math.max(min, value));
|
|
13
|
+
}
|
|
14
|
+
/** Rounds to 1 decimal so generated CSS is stable and readable. */
|
|
15
|
+
function round1(n) {
|
|
16
|
+
return Math.round(n * 10) / 10;
|
|
17
|
+
}
|
|
18
|
+
/** `354 71% 52%`, the channel format shadcn-style `hsl(var(--x) / a)` expects. */
|
|
19
|
+
export function toChannels(c) {
|
|
20
|
+
return `${round1(c.h)} ${round1(c.s)}% ${round1(c.l)}%`;
|
|
21
|
+
}
|
|
22
|
+
/** `hsl(354 71% 52%)`. */
|
|
23
|
+
export function toCss(c) {
|
|
24
|
+
return `hsl(${toChannels(c)})`;
|
|
25
|
+
}
|
|
26
|
+
/** sRGB in 0–1. */
|
|
27
|
+
export function toRgb(c) {
|
|
28
|
+
const s = c.s / 100;
|
|
29
|
+
const l = c.l / 100;
|
|
30
|
+
const k = (n) => (n + c.h / 30) % 12;
|
|
31
|
+
const a = s * Math.min(l, 1 - l);
|
|
32
|
+
const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
33
|
+
return [f(0), f(8), f(4)];
|
|
34
|
+
}
|
|
35
|
+
export function toHex(c) {
|
|
36
|
+
return ('#' +
|
|
37
|
+
toRgb(c)
|
|
38
|
+
.map((v) => Math.round(v * 255)
|
|
39
|
+
.toString(16)
|
|
40
|
+
.padStart(2, '0'))
|
|
41
|
+
.join(''));
|
|
42
|
+
}
|
|
43
|
+
/** Parses `#rrggbb` into HSL. Used for registry entries authored as hex. */
|
|
44
|
+
export function fromHex(hex) {
|
|
45
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
|
|
46
|
+
if (!m?.[1])
|
|
47
|
+
throw new Error(`Invalid hex colour: ${hex}`);
|
|
48
|
+
const n = parseInt(m[1], 16);
|
|
49
|
+
const r = ((n >> 16) & 255) / 255;
|
|
50
|
+
const g = ((n >> 8) & 255) / 255;
|
|
51
|
+
const b = (n & 255) / 255;
|
|
52
|
+
const max = Math.max(r, g, b);
|
|
53
|
+
const min = Math.min(r, g, b);
|
|
54
|
+
const l = (max + min) / 2;
|
|
55
|
+
const d = max - min;
|
|
56
|
+
let h = 0;
|
|
57
|
+
let s = 0;
|
|
58
|
+
if (d !== 0) {
|
|
59
|
+
s = d / (1 - Math.abs(2 * l - 1));
|
|
60
|
+
if (max === r)
|
|
61
|
+
h = 60 * (((g - b) / d) % 6);
|
|
62
|
+
else if (max === g)
|
|
63
|
+
h = 60 * ((b - r) / d + 2);
|
|
64
|
+
else
|
|
65
|
+
h = 60 * ((r - g) / d + 4);
|
|
66
|
+
}
|
|
67
|
+
return hsl(h, s * 100, l * 100);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Browsers paint 8-bit channels, so contrast is measured on the quantised
|
|
71
|
+
* colour. Otherwise a pair can pass at 4.504:1 in floating point and fail at
|
|
72
|
+
* 4.48:1 on screen, which is what axe measures.
|
|
73
|
+
*/
|
|
74
|
+
function quantise(v) {
|
|
75
|
+
return Math.round(v * 255) / 255;
|
|
76
|
+
}
|
|
77
|
+
export function relativeLuminance(c) {
|
|
78
|
+
const [r, g, b] = toRgb(c)
|
|
79
|
+
.map(quantise)
|
|
80
|
+
.map((v) => (v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)));
|
|
81
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
82
|
+
}
|
|
83
|
+
/** WCAG contrast ratio, 1–21. */
|
|
84
|
+
export function contrast(a, b) {
|
|
85
|
+
const la = relativeLuminance(a);
|
|
86
|
+
const lb = relativeLuminance(b);
|
|
87
|
+
const [hi, lo] = la > lb ? [la, lb] : [lb, la];
|
|
88
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Moves lightness in `direction` until `color` reaches `minRatio` against every
|
|
92
|
+
* colour in `against`. Hue and saturation are preserved so the brand stays
|
|
93
|
+
* recognisable. Throws if no lightness satisfies the target, so an impossible
|
|
94
|
+
* theme fails the build instead of shipping silently.
|
|
95
|
+
*/
|
|
96
|
+
export function ensureContrast(color, against, minRatio, direction) {
|
|
97
|
+
const step = direction === 'darken' ? -0.5 : 0.5;
|
|
98
|
+
let current = color;
|
|
99
|
+
for (let i = 0; i < 220; i++) {
|
|
100
|
+
if (against.every((bg) => contrast(current, bg) >= minRatio))
|
|
101
|
+
return current;
|
|
102
|
+
const next = hsl(current.h, current.s, current.l + step);
|
|
103
|
+
if (next.l === current.l)
|
|
104
|
+
break;
|
|
105
|
+
current = next;
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`Cannot reach ${minRatio}:1 for ${toChannels(color)} against ${against.map(toChannels).join(', ')}`);
|
|
108
|
+
}
|
|
109
|
+
/** Picks whichever candidate foreground has the higher contrast on `bg`. */
|
|
110
|
+
export function bestForeground(bg, candidates) {
|
|
111
|
+
let best;
|
|
112
|
+
for (const c of candidates) {
|
|
113
|
+
const ratio = contrast(c, bg);
|
|
114
|
+
if (!best || ratio > best.ratio)
|
|
115
|
+
best = { color: c, ratio };
|
|
116
|
+
}
|
|
117
|
+
if (!best)
|
|
118
|
+
throw new Error('bestForeground needs at least one candidate');
|
|
119
|
+
return best;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color.js","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"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"}
|