@wistia/kaleidoscope 0.0.0-beta.1e8dd2d4.d35c078
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/README.md +31 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +449 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @wistia/kaleidoscope
|
|
2
|
+
|
|
3
|
+
TODO: describe what this package does.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
yarn add @wistia/kaleidoscope
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Development
|
|
12
|
+
|
|
13
|
+
This package lives in the [vhs monorepo](https://github.com/wistia/vhs). From the repo root:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
yarn vp run @wistia/kaleidoscope#build # build once
|
|
17
|
+
yarn workspace @wistia/kaleidoscope build:watch # rebuild on change, pushes to yalc consumers
|
|
18
|
+
yarn workspace @wistia/kaleidoscope test:watch
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Releasing
|
|
22
|
+
|
|
23
|
+
Add a changeset describing your change, then merge. The release PR opened on `main` publishes to npm when merged.
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
yarn changeset
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
Unlicensed — Copyright (c) Wistia, Inc. and its affiliates. All rights reserved.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/types.d.ts
|
|
3
|
+
/** Red, green and blue channels, each in the 0–255 range. */
|
|
4
|
+
type RgbTuple = [red: number, green: number, blue: number];
|
|
5
|
+
/** Red, green and blue channels (0–255) plus an alpha channel (0–1). */
|
|
6
|
+
type RgbaTuple = [red: number, green: number, blue: number, alpha: number];
|
|
7
|
+
/** RGB channels (0–255) with an optional alpha channel (0–1). */
|
|
8
|
+
type ColorTuple = [red: number, green: number, blue: number, alpha?: number];
|
|
9
|
+
/** Any object carrying RGB channels (0–255) and an optional alpha channel (0–1). */
|
|
10
|
+
type RgbaObject = {
|
|
11
|
+
readonly a?: number;
|
|
12
|
+
readonly b: number;
|
|
13
|
+
readonly g: number;
|
|
14
|
+
readonly r: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Anything that can be read as a color:
|
|
18
|
+
*
|
|
19
|
+
* - a hex string — `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa` (the `#` is optional)
|
|
20
|
+
* - an `rgb()`/`rgba()` string in either legacy comma syntax (`rgb(41, 73, 229)`) or
|
|
21
|
+
* modern space syntax (`rgb(41 73 229 / 50%)`), with numbers or percentages
|
|
22
|
+
* - an `[r, g, b]` or `[r, g, b, a]` tuple
|
|
23
|
+
* - an object with `r`, `g`, `b` and optionally `a` — including another `Color`
|
|
24
|
+
*
|
|
25
|
+
* CSS named colors (`red`, `transparent`, …) are not supported.
|
|
26
|
+
*/
|
|
27
|
+
type ColorInput = Color | ColorTuple | RgbaObject | string;
|
|
28
|
+
/** Hue (0–360), saturation (0–100) and lightness (0–100). */
|
|
29
|
+
type Hsl = {
|
|
30
|
+
readonly hue: number;
|
|
31
|
+
readonly lightness: number;
|
|
32
|
+
readonly saturation: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* CIELAB: perceptual lightness (0–100), plus the green–red (`a`) and blue–yellow (`b`)
|
|
36
|
+
* axes, which are unbounded but in practice fall within ±128.
|
|
37
|
+
*/
|
|
38
|
+
type Lab = {
|
|
39
|
+
readonly a: number;
|
|
40
|
+
readonly b: number;
|
|
41
|
+
readonly lightness: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The color space a blend interpolates through.
|
|
45
|
+
*
|
|
46
|
+
* - `lab` — perceptually uniform; the most even-looking scales, at the cost of some speed
|
|
47
|
+
* - `lrgb` — linear RGB; avoids the muddy midpoints of naive RGB blending
|
|
48
|
+
* - `rgb` — naive channel-wise RGB; fastest, and what CSS `color-mix()` does in sRGB
|
|
49
|
+
*/
|
|
50
|
+
type InterpolationMode = "lab" | "lrgb" | "rgb";
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/Color.d.ts
|
|
53
|
+
/**
|
|
54
|
+
* An immutable sRGB color with transformation methods like lighten, darken, tint, shade.
|
|
55
|
+
* Also includes methods to calculate contrast ratio against another color, relative luminance, etc.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* new Color('#2949e5').shade(0.8).toHex(); // '#0a1339'
|
|
60
|
+
* new Color('#2949e5').withAlpha(0.5).toRgba(); // 'rgba(41, 73, 229, 0.5)'
|
|
61
|
+
* new Color('#2949e5').contrast('#ffffff'); // 6.6
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare class Color {
|
|
65
|
+
/**
|
|
66
|
+
* Read `input` into a color, returning `null` when it cannot be parsed.
|
|
67
|
+
*/
|
|
68
|
+
static parse(input: ColorInput): Color | null;
|
|
69
|
+
static fromHsl(hsl: Hsl, alpha?: number): Color;
|
|
70
|
+
static fromLab(lab: Lab, alpha?: number): Color;
|
|
71
|
+
/** Red channel, 0–255. Kept unrounded so that chained operations don't accumulate error. */
|
|
72
|
+
readonly r: number;
|
|
73
|
+
/** Green channel, 0–255. Kept unrounded so that chained operations don't accumulate error. */
|
|
74
|
+
readonly g: number;
|
|
75
|
+
/** Blue channel, 0–255. Kept unrounded so that chained operations don't accumulate error. */
|
|
76
|
+
readonly b: number;
|
|
77
|
+
/** Alpha channel, 0–1. */
|
|
78
|
+
readonly a: number;
|
|
79
|
+
/**
|
|
80
|
+
* @param input - any supported color representation. Input that cannot be read as a
|
|
81
|
+
* color resolves to brand blue rather than failing, so a malformed value degrades to
|
|
82
|
+
* a usable color instead of interrupting a render.
|
|
83
|
+
*/
|
|
84
|
+
constructor(input: ColorInput);
|
|
85
|
+
/** `#rrggbb`. Alpha is dropped — use {@link Color.toHexWithAlpha} to keep it. */
|
|
86
|
+
toHex(): string;
|
|
87
|
+
/** `#rrggbbaa`. */
|
|
88
|
+
toHexWithAlpha(): string;
|
|
89
|
+
/** `rgb(41, 73, 229)`. */
|
|
90
|
+
toRgb(): string;
|
|
91
|
+
/** `rgba(41, 73, 229, 0.5)`. */
|
|
92
|
+
toRgba(): string;
|
|
93
|
+
/** Rounded channels, ready to hand to another color library. */
|
|
94
|
+
toRgbaTuple(): RgbaTuple;
|
|
95
|
+
toHsl(): Hsl;
|
|
96
|
+
toLab(): Lab;
|
|
97
|
+
/** Hex when fully opaque, `rgba()` otherwise, so the alpha is never silently lost. */
|
|
98
|
+
toString(): string;
|
|
99
|
+
/**
|
|
100
|
+
* WCAG relative luminance, from 0 (black) to 1 (white). Alpha is ignored — luminance
|
|
101
|
+
* is only meaningful once a color has been composited onto a background.
|
|
102
|
+
*/
|
|
103
|
+
luminance(): number;
|
|
104
|
+
/**
|
|
105
|
+
* WCAG contrast ratio against `other`, from 1 (identical) to 21 (black on white),
|
|
106
|
+
* reported to one decimal place.
|
|
107
|
+
*
|
|
108
|
+
* @see {@link colorContrastRatiosByShape} for the ratio each kind of content needs
|
|
109
|
+
*/
|
|
110
|
+
contrast(other: ColorInput): number;
|
|
111
|
+
/**
|
|
112
|
+
* Raise perceptual lightness. Each unit of `amount` moves the color 18 points up the
|
|
113
|
+
* Lab lightness axis, which stays visually even across hues in a way that nudging RGB
|
|
114
|
+
* channels does not.
|
|
115
|
+
*/
|
|
116
|
+
lighten(amount?: number): Color;
|
|
117
|
+
/** Lower perceptual lightness. The inverse of {@link Color.lighten}. */
|
|
118
|
+
darken(amount?: number): Color;
|
|
119
|
+
/** Mix `ratio` (0–1) of white in. */
|
|
120
|
+
tint(ratio: number, mode?: InterpolationMode): Color;
|
|
121
|
+
/** Mix `ratio` (0–1) of black in. */
|
|
122
|
+
shade(ratio: number, mode?: InterpolationMode): Color;
|
|
123
|
+
/**
|
|
124
|
+
* Mix towards `other`, where `ratio` 0 keeps this color and 1 returns `other`. Alpha is
|
|
125
|
+
* always interpolated linearly, whichever `mode` the channels travel through.
|
|
126
|
+
*/
|
|
127
|
+
blend(other: ColorInput, ratio?: number, mode?: InterpolationMode): Color;
|
|
128
|
+
/** Replace the alpha channel with `alpha` (0–1). */
|
|
129
|
+
withAlpha(alpha: number): Color;
|
|
130
|
+
/**
|
|
131
|
+
* Replace HSL lightness with `lightness` (0–100), leaving hue and saturation alone.
|
|
132
|
+
* Use this to hit an absolute lightness; use {@link Color.lighten} to step relative to
|
|
133
|
+
* where the color already is.
|
|
134
|
+
*/
|
|
135
|
+
withLightness(lightness: number): Color;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/colorContrastRatiosByShape.d.ts
|
|
139
|
+
declare const colorContrastRatiosByShape: {
|
|
140
|
+
nonText: number;
|
|
141
|
+
largeText: number;
|
|
142
|
+
paragraphText: number;
|
|
143
|
+
smallText: number;
|
|
144
|
+
};
|
|
145
|
+
//#endregion
|
|
146
|
+
export { Color, type ColorInput, type ColorTuple, type Hsl, type InterpolationMode, type Lab, type RgbTuple, type RgbaObject, type RgbaTuple, colorContrastRatiosByShape };
|
|
147
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/Color.ts","../src/colorContrastRatiosByShape.ts"],"mappings":";;;KAIY,YAAY,aAAa,eAAe;;KAGxC,aAAa,aAAa,eAAe,cAAc;;KAGvD,cAAc,aAAa,eAAe,cAAc;;KAGxD;WACD;WACA;WACA;WACA;;;;;;;;;;;;;KAcC,aAAa,QAAQ,aAAa;;KAGlC;WACD;WACA;WACA;;;;;;KAOC;WACD;WACA;WACA;;;;;;;;;KAUC;;;;;;;;;;;;;;cCTC;;;;SAIJ,MAAM,OAAO,aAAa;SAK1B,QAAQ,KAAK,KAAK,iBAAY;SAK9B,QAAQ,KAAK,KAAK,iBAAY;;WAM5B;;WAGA;;WAGA;;WAGA;;;;;;EAOT,YAAY,OAAO;;EASnB;;EAKA;;EAKA;;EAKA;;EAKA,eAAe;EAIf,SAAS;EAIT,SAAS;;EAKT;;;;;EAQA;;;;;;;EAUA,SAAS,OAAO;;;;;;EAahB,QAAQ,kBAAa;;EAMrB,OAAO,kBAAa;;EAKpB,KAAK,eAAe,OAAM,oBAAiD;;EAK3E,MAAM,eAAe,OAAM,oBAAiD;;;;;EAQ5E,MACE,OAAO,YACP,gBACA,OAAM,oBACL;;EA4BH,UAAU,gBAAgB;;;;;;EAS1B,cAAc,oBAAoB;;;;cC9NvB;EACX;EACA;EACA;EACA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
* @license @wistia/kaleidoscope v0.0.0-beta.1e8dd2d4.d35c078
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2026, Wistia, Inc. and its affiliates.
|
|
6
|
+
*
|
|
7
|
+
* This source code is unlicensed, all rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
//#region src/private/clamp.ts
|
|
11
|
+
/** Restrict `value` to the inclusive range between `min` and `max`. */
|
|
12
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/private/colorSpace.ts
|
|
15
|
+
const D65_WHITE_POINT = {
|
|
16
|
+
x: .95047,
|
|
17
|
+
y: 1,
|
|
18
|
+
z: 1.08883
|
|
19
|
+
};
|
|
20
|
+
const LAB_EPSILON = 216 / 24389;
|
|
21
|
+
const LAB_KAPPA = 24389 / 27;
|
|
22
|
+
const LAB_LINEAR_LIGHTNESS_THRESHOLD = 8;
|
|
23
|
+
/** Gamma-decode a 0–255 sRGB channel into linear light. */
|
|
24
|
+
const linearize = (channel) => {
|
|
25
|
+
const normalized = channel / 255;
|
|
26
|
+
return normalized <= .04045 ? normalized / 12.92 : ((normalized + .055) / 1.055) ** 2.4;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Gamma-encode linear light back into a 0–255 sRGB channel. The sign is carried through
|
|
30
|
+
* the exponent so that out-of-gamut Lab values produce a negative channel rather than NaN.
|
|
31
|
+
* Inverse of linearize.
|
|
32
|
+
*/
|
|
33
|
+
const compand = (linear) => {
|
|
34
|
+
const magnitude = Math.abs(linear);
|
|
35
|
+
return (magnitude <= .0031308 ? magnitude * 12.92 : 1.055 * magnitude ** (1 / 2.4) - .055) * Math.sign(linear) * 255;
|
|
36
|
+
};
|
|
37
|
+
const pivot = (value) => value > LAB_EPSILON ? Math.cbrt(value) : (LAB_KAPPA * value + 16) / 116;
|
|
38
|
+
const hueToChannel = (start, end, givenHue) => {
|
|
39
|
+
let hue = givenHue;
|
|
40
|
+
if (hue < 0) hue += 1;
|
|
41
|
+
if (hue > 1) hue -= 1;
|
|
42
|
+
if (hue < 1 / 6) return start + (end - start) * 6 * hue;
|
|
43
|
+
if (hue < 1 / 2) return end;
|
|
44
|
+
if (hue < 2 / 3) return start + (end - start) * (2 / 3 - hue) * 6;
|
|
45
|
+
return start;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* WCAG relative luminance (0–1).
|
|
49
|
+
*
|
|
50
|
+
* @see https://www.w3.org/WAI/GL/wiki/Relative_luminance
|
|
51
|
+
*/
|
|
52
|
+
const relativeLuminance = (red, green, blue) => .2126 * linearize(red) + .7152 * linearize(green) + .0722 * linearize(blue);
|
|
53
|
+
const rgbToHsl = (red, green, blue) => {
|
|
54
|
+
const r = red / 255;
|
|
55
|
+
const g = green / 255;
|
|
56
|
+
const b = blue / 255;
|
|
57
|
+
const max = Math.max(r, g, b);
|
|
58
|
+
const min = Math.min(r, g, b);
|
|
59
|
+
const lightness = (max + min) / 2;
|
|
60
|
+
const delta = max - min;
|
|
61
|
+
if (delta === 0) return {
|
|
62
|
+
hue: 0,
|
|
63
|
+
lightness: lightness * 100,
|
|
64
|
+
saturation: 0
|
|
65
|
+
};
|
|
66
|
+
const saturation = lightness > .5 ? delta / (2 - max - min) : delta / (max + min);
|
|
67
|
+
let hue;
|
|
68
|
+
if (max === r) hue = (g - b) / delta + (g < b ? 6 : 0);
|
|
69
|
+
else if (max === g) hue = (b - r) / delta + 2;
|
|
70
|
+
else hue = (r - g) / delta + 4;
|
|
71
|
+
return {
|
|
72
|
+
hue: hue / 6 * 360,
|
|
73
|
+
lightness: lightness * 100,
|
|
74
|
+
saturation: saturation * 100
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const hslToRgb = ({ hue, lightness, saturation }) => {
|
|
78
|
+
const h = hue / 360;
|
|
79
|
+
const s = saturation / 100;
|
|
80
|
+
const l = lightness / 100;
|
|
81
|
+
const end = l < .5 ? l * (1 + s) : l + s - l * s;
|
|
82
|
+
const start = 2 * l - end;
|
|
83
|
+
return [
|
|
84
|
+
hueToChannel(start, end, h + 1 / 3) * 255,
|
|
85
|
+
hueToChannel(start, end, h) * 255,
|
|
86
|
+
hueToChannel(start, end, h - 1 / 3) * 255
|
|
87
|
+
];
|
|
88
|
+
};
|
|
89
|
+
const rgbToLab = (red, green, blue) => {
|
|
90
|
+
const r = linearize(red);
|
|
91
|
+
const g = linearize(green);
|
|
92
|
+
const b = linearize(blue);
|
|
93
|
+
const x = (.4124564 * r + .3575761 * g + .1804375 * b) / D65_WHITE_POINT.x;
|
|
94
|
+
const y = (.2126729 * r + .7151522 * g + .072175 * b) / D65_WHITE_POINT.y;
|
|
95
|
+
const z = (.0193339 * r + .119192 * g + .9503041 * b) / D65_WHITE_POINT.z;
|
|
96
|
+
const fx = pivot(x);
|
|
97
|
+
const fy = pivot(y);
|
|
98
|
+
const fz = pivot(z);
|
|
99
|
+
return {
|
|
100
|
+
a: 500 * (fx - fy),
|
|
101
|
+
b: 200 * (fy - fz),
|
|
102
|
+
lightness: 116 * fy - 16
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
const labToRgb = ({ a, b, lightness }) => {
|
|
106
|
+
const fy = (lightness + 16) / 116;
|
|
107
|
+
const fx = fy + a / 500;
|
|
108
|
+
const fz = fy - b / 200;
|
|
109
|
+
const x = (fx ** 3 > LAB_EPSILON ? fx ** 3 : (116 * fx - 16) / LAB_KAPPA) * D65_WHITE_POINT.x;
|
|
110
|
+
const y = (lightness > LAB_LINEAR_LIGHTNESS_THRESHOLD ? fy ** 3 : lightness / LAB_KAPPA) * D65_WHITE_POINT.y;
|
|
111
|
+
const z = (fz ** 3 > LAB_EPSILON ? fz ** 3 : (116 * fz - 16) / LAB_KAPPA) * D65_WHITE_POINT.z;
|
|
112
|
+
return [
|
|
113
|
+
compand(3.2404542 * x - 1.5371385 * y - .4985314 * z),
|
|
114
|
+
compand(-.969266 * x + 1.8760108 * y + .041556 * z),
|
|
115
|
+
compand(.0556434 * x - .2040259 * y + 1.0572252 * z)
|
|
116
|
+
];
|
|
117
|
+
};
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/private/parseColor.ts
|
|
120
|
+
const HEX_PATTERN = /^#?([\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/i;
|
|
121
|
+
const RGB_FUNCTION_PATTERN = /^rgba?\(([^)]*)\)$/i;
|
|
122
|
+
const NUMBER_PATTERN = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
|
|
123
|
+
const PERCENTAGE_PATTERN = /^[+-]?(?:\d+\.?\d*|\.\d+)%$/;
|
|
124
|
+
const SEPARATOR_PATTERN = /[\s,]+/;
|
|
125
|
+
const MAX_CHANNEL = 255;
|
|
126
|
+
const HEX_RADIX$1 = 16;
|
|
127
|
+
const toChannel = (value) => clamp(value, 0, MAX_CHANNEL);
|
|
128
|
+
const toAlpha = (value) => clamp(value, 0, 1);
|
|
129
|
+
/**
|
|
130
|
+
* Read a numeric token, where a percentage is taken as a fraction of `percentageOf`. A
|
|
131
|
+
* channel is `128` or `50%` (of 255); an alpha is `0.5` or `50%` (of 1).
|
|
132
|
+
*/
|
|
133
|
+
const parseNumericToken = (token, percentageOf) => {
|
|
134
|
+
if (token === void 0) return null;
|
|
135
|
+
if (PERCENTAGE_PATTERN.test(token)) return Number(token.slice(0, -1)) / 100 * percentageOf;
|
|
136
|
+
if (NUMBER_PATTERN.test(token)) return Number(token);
|
|
137
|
+
return null;
|
|
138
|
+
};
|
|
139
|
+
const parseChannelToken = (token) => {
|
|
140
|
+
const value = parseNumericToken(token, MAX_CHANNEL);
|
|
141
|
+
return value === null ? null : toChannel(value);
|
|
142
|
+
};
|
|
143
|
+
const parseAlphaToken = (token) => {
|
|
144
|
+
const value = parseNumericToken(token, 1);
|
|
145
|
+
return value === null ? null : toAlpha(value);
|
|
146
|
+
};
|
|
147
|
+
const parseHex = (value) => {
|
|
148
|
+
const digits = HEX_PATTERN.exec(value)?.[1];
|
|
149
|
+
if (digits === void 0) return null;
|
|
150
|
+
const expanded = digits.length <= 4 ? digits.replaceAll(/./g, "$&$&") : digits;
|
|
151
|
+
return [
|
|
152
|
+
Number.parseInt(expanded.slice(0, 2), HEX_RADIX$1),
|
|
153
|
+
Number.parseInt(expanded.slice(2, 4), HEX_RADIX$1),
|
|
154
|
+
Number.parseInt(expanded.slice(4, 6), HEX_RADIX$1),
|
|
155
|
+
expanded.length === 8 ? Number.parseInt(expanded.slice(6, 8), HEX_RADIX$1) / MAX_CHANNEL : 1
|
|
156
|
+
];
|
|
157
|
+
};
|
|
158
|
+
const parseRgbFunction = (value) => {
|
|
159
|
+
const body = RGB_FUNCTION_PATTERN.exec(value)?.[1];
|
|
160
|
+
if (body === void 0) return null;
|
|
161
|
+
const [channelSource, alphaSource, ...extraSources] = body.split("/");
|
|
162
|
+
if (channelSource === void 0 || extraSources.length > 0) return null;
|
|
163
|
+
const tokens = channelSource.trim().split(SEPARATOR_PATTERN).filter(Boolean);
|
|
164
|
+
const alphaTokens = (alphaSource ?? "").trim().split(SEPARATOR_PATTERN).filter(Boolean);
|
|
165
|
+
if (alphaTokens.length > 1) return null;
|
|
166
|
+
const [redToken, greenToken, blueToken, legacyAlphaToken, ...extraTokens] = tokens;
|
|
167
|
+
if (extraTokens.length > 0) return null;
|
|
168
|
+
if (alphaSource !== void 0 && legacyAlphaToken !== void 0) return null;
|
|
169
|
+
const red = parseChannelToken(redToken);
|
|
170
|
+
const green = parseChannelToken(greenToken);
|
|
171
|
+
const blue = parseChannelToken(blueToken);
|
|
172
|
+
if (red === null || green === null || blue === null) return null;
|
|
173
|
+
const alphaToken = alphaTokens[0] ?? legacyAlphaToken;
|
|
174
|
+
if (alphaToken === void 0) return [
|
|
175
|
+
red,
|
|
176
|
+
green,
|
|
177
|
+
blue,
|
|
178
|
+
1
|
|
179
|
+
];
|
|
180
|
+
const alpha = parseAlphaToken(alphaToken);
|
|
181
|
+
return alpha === null ? null : [
|
|
182
|
+
red,
|
|
183
|
+
green,
|
|
184
|
+
blue,
|
|
185
|
+
alpha
|
|
186
|
+
];
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Read any supported color representation into RGBA channels, or `null` when the input
|
|
190
|
+
* is not a color this package understands.
|
|
191
|
+
*/
|
|
192
|
+
const parseColor = (input) => {
|
|
193
|
+
if (typeof input === "string") {
|
|
194
|
+
const trimmed = input.trim();
|
|
195
|
+
return parseHex(trimmed) ?? parseRgbFunction(trimmed);
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(input)) {
|
|
198
|
+
const [red, green, blue, alpha = 1] = input;
|
|
199
|
+
if (![
|
|
200
|
+
red,
|
|
201
|
+
green,
|
|
202
|
+
blue,
|
|
203
|
+
alpha
|
|
204
|
+
].every((value) => Number.isFinite(value))) return null;
|
|
205
|
+
return [
|
|
206
|
+
toChannel(red),
|
|
207
|
+
toChannel(green),
|
|
208
|
+
toChannel(blue),
|
|
209
|
+
toAlpha(alpha)
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
const { r, g, b, a = 1 } = input;
|
|
213
|
+
if (![
|
|
214
|
+
r,
|
|
215
|
+
g,
|
|
216
|
+
b,
|
|
217
|
+
a
|
|
218
|
+
].every((value) => Number.isFinite(value))) return null;
|
|
219
|
+
return [
|
|
220
|
+
toChannel(r),
|
|
221
|
+
toChannel(g),
|
|
222
|
+
toChannel(b),
|
|
223
|
+
toAlpha(a)
|
|
224
|
+
];
|
|
225
|
+
};
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/Color.ts
|
|
228
|
+
const HEX_RADIX = 16;
|
|
229
|
+
/** Lab lightness covered by one unit of `lighten()`/`darken()` */
|
|
230
|
+
const LAB_LIGHTNESS_STEP = 18;
|
|
231
|
+
const DEFAULT_INTERPOLATION_MODE = "lrgb";
|
|
232
|
+
const WHITE = "#ffffff";
|
|
233
|
+
const BLACK = "#000000";
|
|
234
|
+
/** Wistia brand blue (`#2949e5`) — what an unreadable input resolves to. */
|
|
235
|
+
const FALLBACK_CHANNELS = [
|
|
236
|
+
41,
|
|
237
|
+
73,
|
|
238
|
+
229,
|
|
239
|
+
1
|
|
240
|
+
];
|
|
241
|
+
const toHexPair = (channel) => Math.round(channel).toString(HEX_RADIX).padStart(2, "0");
|
|
242
|
+
/**
|
|
243
|
+
* Truncated rather than rounded, so a ratio can never climb across a WCAG threshold on
|
|
244
|
+
* its way to one decimal — 4.4501 reports as 4.4, not as a passing 4.5.
|
|
245
|
+
*/
|
|
246
|
+
const truncateToTenth = (ratio) => Math.floor(ratio * 10) / 10;
|
|
247
|
+
const interpolate = (from, to, ratio) => from + (to - from) * ratio;
|
|
248
|
+
/**
|
|
249
|
+
* Interpolate through linear light rather than gamma-encoded sRGB, which keeps blends
|
|
250
|
+
* between saturated colors from sagging through a muddy midpoint.
|
|
251
|
+
*/
|
|
252
|
+
const interpolateLinear = (from, to, ratio) => Math.sqrt(from ** 2 * (1 - ratio) + to ** 2 * ratio);
|
|
253
|
+
/**
|
|
254
|
+
* An immutable sRGB color with transformation methods like lighten, darken, tint, shade.
|
|
255
|
+
* Also includes methods to calculate contrast ratio against another color, relative luminance, etc.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```ts
|
|
259
|
+
* new Color('#2949e5').shade(0.8).toHex(); // '#0a1339'
|
|
260
|
+
* new Color('#2949e5').withAlpha(0.5).toRgba(); // 'rgba(41, 73, 229, 0.5)'
|
|
261
|
+
* new Color('#2949e5').contrast('#ffffff'); // 6.6
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
var Color = class Color {
|
|
265
|
+
/**
|
|
266
|
+
* Read `input` into a color, returning `null` when it cannot be parsed.
|
|
267
|
+
*/
|
|
268
|
+
static parse(input) {
|
|
269
|
+
const channels = parseColor(input);
|
|
270
|
+
return channels === null ? null : new Color(channels);
|
|
271
|
+
}
|
|
272
|
+
static fromHsl(hsl, alpha = 1) {
|
|
273
|
+
const [red, green, blue] = hslToRgb(hsl);
|
|
274
|
+
return new Color([
|
|
275
|
+
red,
|
|
276
|
+
green,
|
|
277
|
+
blue,
|
|
278
|
+
alpha
|
|
279
|
+
]);
|
|
280
|
+
}
|
|
281
|
+
static fromLab(lab, alpha = 1) {
|
|
282
|
+
const [red, green, blue] = labToRgb(lab);
|
|
283
|
+
return new Color([
|
|
284
|
+
red,
|
|
285
|
+
green,
|
|
286
|
+
blue,
|
|
287
|
+
alpha
|
|
288
|
+
]);
|
|
289
|
+
}
|
|
290
|
+
/** Red channel, 0–255. Kept unrounded so that chained operations don't accumulate error. */
|
|
291
|
+
r;
|
|
292
|
+
/** Green channel, 0–255. Kept unrounded so that chained operations don't accumulate error. */
|
|
293
|
+
g;
|
|
294
|
+
/** Blue channel, 0–255. Kept unrounded so that chained operations don't accumulate error. */
|
|
295
|
+
b;
|
|
296
|
+
/** Alpha channel, 0–1. */
|
|
297
|
+
a;
|
|
298
|
+
/**
|
|
299
|
+
* @param input - any supported color representation. Input that cannot be read as a
|
|
300
|
+
* color resolves to brand blue rather than failing, so a malformed value degrades to
|
|
301
|
+
* a usable color instead of interrupting a render.
|
|
302
|
+
*/
|
|
303
|
+
constructor(input) {
|
|
304
|
+
const [red, green, blue, alpha] = parseColor(input) ?? FALLBACK_CHANNELS;
|
|
305
|
+
this.r = red;
|
|
306
|
+
this.g = green;
|
|
307
|
+
this.b = blue;
|
|
308
|
+
this.a = alpha;
|
|
309
|
+
}
|
|
310
|
+
/** `#rrggbb`. Alpha is dropped — use {@link Color.toHexWithAlpha} to keep it. */
|
|
311
|
+
toHex() {
|
|
312
|
+
return `#${toHexPair(this.r)}${toHexPair(this.g)}${toHexPair(this.b)}`;
|
|
313
|
+
}
|
|
314
|
+
/** `#rrggbbaa`. */
|
|
315
|
+
toHexWithAlpha() {
|
|
316
|
+
return `${this.toHex()}${toHexPair(this.a * 255)}`;
|
|
317
|
+
}
|
|
318
|
+
/** `rgb(41, 73, 229)`. */
|
|
319
|
+
toRgb() {
|
|
320
|
+
return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`;
|
|
321
|
+
}
|
|
322
|
+
/** `rgba(41, 73, 229, 0.5)`. */
|
|
323
|
+
toRgba() {
|
|
324
|
+
return `rgba(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)}, ${this.a})`;
|
|
325
|
+
}
|
|
326
|
+
/** Rounded channels, ready to hand to another color library. */
|
|
327
|
+
toRgbaTuple() {
|
|
328
|
+
return [
|
|
329
|
+
Math.round(this.r),
|
|
330
|
+
Math.round(this.g),
|
|
331
|
+
Math.round(this.b),
|
|
332
|
+
this.a
|
|
333
|
+
];
|
|
334
|
+
}
|
|
335
|
+
toHsl() {
|
|
336
|
+
return rgbToHsl(this.r, this.g, this.b);
|
|
337
|
+
}
|
|
338
|
+
toLab() {
|
|
339
|
+
return rgbToLab(this.r, this.g, this.b);
|
|
340
|
+
}
|
|
341
|
+
/** Hex when fully opaque, `rgba()` otherwise, so the alpha is never silently lost. */
|
|
342
|
+
toString() {
|
|
343
|
+
return this.a === 1 ? this.toHex() : this.toRgba();
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* WCAG relative luminance, from 0 (black) to 1 (white). Alpha is ignored — luminance
|
|
347
|
+
* is only meaningful once a color has been composited onto a background.
|
|
348
|
+
*/
|
|
349
|
+
luminance() {
|
|
350
|
+
return relativeLuminance(this.r, this.g, this.b);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* WCAG contrast ratio against `other`, from 1 (identical) to 21 (black on white),
|
|
354
|
+
* reported to one decimal place.
|
|
355
|
+
*
|
|
356
|
+
* @see {@link colorContrastRatiosByShape} for the ratio each kind of content needs
|
|
357
|
+
*/
|
|
358
|
+
contrast(other) {
|
|
359
|
+
const ownLuminance = this.luminance();
|
|
360
|
+
const otherLuminance = new Color(other).luminance();
|
|
361
|
+
const lighter = Math.max(ownLuminance, otherLuminance);
|
|
362
|
+
const darker = Math.min(ownLuminance, otherLuminance);
|
|
363
|
+
return truncateToTenth((lighter + .05) / (darker + .05));
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Raise perceptual lightness. Each unit of `amount` moves the color 18 points up the
|
|
367
|
+
* Lab lightness axis, which stays visually even across hues in a way that nudging RGB
|
|
368
|
+
* channels does not.
|
|
369
|
+
*/
|
|
370
|
+
lighten(amount = 1) {
|
|
371
|
+
const { a, b, lightness } = this.toLab();
|
|
372
|
+
return Color.fromLab({
|
|
373
|
+
a,
|
|
374
|
+
b,
|
|
375
|
+
lightness: lightness + LAB_LIGHTNESS_STEP * amount
|
|
376
|
+
}, this.a);
|
|
377
|
+
}
|
|
378
|
+
/** Lower perceptual lightness. The inverse of {@link Color.lighten}. */
|
|
379
|
+
darken(amount = 1) {
|
|
380
|
+
return this.lighten(-amount);
|
|
381
|
+
}
|
|
382
|
+
/** Mix `ratio` (0–1) of white in. */
|
|
383
|
+
tint(ratio, mode = DEFAULT_INTERPOLATION_MODE) {
|
|
384
|
+
return this.blend(WHITE, ratio, mode);
|
|
385
|
+
}
|
|
386
|
+
/** Mix `ratio` (0–1) of black in. */
|
|
387
|
+
shade(ratio, mode = DEFAULT_INTERPOLATION_MODE) {
|
|
388
|
+
return this.blend(BLACK, ratio, mode);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Mix towards `other`, where `ratio` 0 keeps this color and 1 returns `other`. Alpha is
|
|
392
|
+
* always interpolated linearly, whichever `mode` the channels travel through.
|
|
393
|
+
*/
|
|
394
|
+
blend(other, ratio = .5, mode = DEFAULT_INTERPOLATION_MODE) {
|
|
395
|
+
const target = new Color(other);
|
|
396
|
+
const amount = clamp(ratio, 0, 1);
|
|
397
|
+
const alpha = interpolate(this.a, target.a, amount);
|
|
398
|
+
if (mode === "lab") {
|
|
399
|
+
const from = this.toLab();
|
|
400
|
+
const to = target.toLab();
|
|
401
|
+
return Color.fromLab({
|
|
402
|
+
a: interpolate(from.a, to.a, amount),
|
|
403
|
+
b: interpolate(from.b, to.b, amount),
|
|
404
|
+
lightness: interpolate(from.lightness, to.lightness, amount)
|
|
405
|
+
}, alpha);
|
|
406
|
+
}
|
|
407
|
+
const blendChannel = mode === "lrgb" ? interpolateLinear : interpolate;
|
|
408
|
+
return new Color([
|
|
409
|
+
blendChannel(this.r, target.r, amount),
|
|
410
|
+
blendChannel(this.g, target.g, amount),
|
|
411
|
+
blendChannel(this.b, target.b, amount),
|
|
412
|
+
alpha
|
|
413
|
+
]);
|
|
414
|
+
}
|
|
415
|
+
/** Replace the alpha channel with `alpha` (0–1). */
|
|
416
|
+
withAlpha(alpha) {
|
|
417
|
+
return new Color([
|
|
418
|
+
this.r,
|
|
419
|
+
this.g,
|
|
420
|
+
this.b,
|
|
421
|
+
alpha
|
|
422
|
+
]);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Replace HSL lightness with `lightness` (0–100), leaving hue and saturation alone.
|
|
426
|
+
* Use this to hit an absolute lightness; use {@link Color.lighten} to step relative to
|
|
427
|
+
* where the color already is.
|
|
428
|
+
*/
|
|
429
|
+
withLightness(lightness) {
|
|
430
|
+
const { hue, saturation } = this.toHsl();
|
|
431
|
+
return Color.fromHsl({
|
|
432
|
+
hue,
|
|
433
|
+
lightness: clamp(lightness, 0, 100),
|
|
434
|
+
saturation
|
|
435
|
+
}, this.a);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/colorContrastRatiosByShape.ts
|
|
440
|
+
const colorContrastRatiosByShape = {
|
|
441
|
+
nonText: 3,
|
|
442
|
+
largeText: 3,
|
|
443
|
+
paragraphText: 4.5,
|
|
444
|
+
smallText: 5.5
|
|
445
|
+
};
|
|
446
|
+
//#endregion
|
|
447
|
+
export { Color, colorContrastRatiosByShape };
|
|
448
|
+
|
|
449
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["HEX_RADIX"],"sources":["../src/private/clamp.ts","../src/private/colorSpace.ts","../src/private/parseColor.ts","../src/Color.ts","../src/colorContrastRatiosByShape.ts"],"sourcesContent":["/** Restrict `value` to the inclusive range between `min` and `max`. */\nexport const clamp = (value: number, min: number, max: number): number =>\n Math.min(Math.max(value, min), max);\n","// oxlint-disable id-length -- single-letter names mirror the color-space formulas\nimport type { Hsl, Lab, RgbTuple } from '../types';\n\nconst D65_WHITE_POINT = { x: 0.95047, y: 1, z: 1.08883 };\n\n// CIE standard constants: 216/24389 and 24389/27. Their product, 8, is the L* below\n// which the Lab transfer function stays linear.\nconst LAB_EPSILON = 216 / 24389;\nconst LAB_KAPPA = 24389 / 27;\nconst LAB_LINEAR_LIGHTNESS_THRESHOLD = 8;\n\n/** Gamma-decode a 0–255 sRGB channel into linear light. */\nconst linearize = (channel: number): number => {\n const normalized = channel / 255;\n return normalized <= 0.04045 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;\n};\n\n/**\n * Gamma-encode linear light back into a 0–255 sRGB channel. The sign is carried through\n * the exponent so that out-of-gamut Lab values produce a negative channel rather than NaN.\n * Inverse of linearize.\n */\nconst compand = (linear: number): number => {\n const magnitude = Math.abs(linear);\n const encoded =\n magnitude <= 0.0031308 ? magnitude * 12.92 : 1.055 * magnitude ** (1 / 2.4) - 0.055;\n return encoded * Math.sign(linear) * 255;\n};\n\nconst pivot = (value: number): number =>\n value > LAB_EPSILON ? Math.cbrt(value) : (LAB_KAPPA * value + 16) / 116;\n\nconst hueToChannel = (start: number, end: number, givenHue: number): number => {\n let hue = givenHue;\n if (hue < 0) {\n hue += 1;\n }\n if (hue > 1) {\n hue -= 1;\n }\n if (hue < 1 / 6) {\n return start + (end - start) * 6 * hue;\n }\n if (hue < 1 / 2) {\n return end;\n }\n if (hue < 2 / 3) {\n return start + (end - start) * (2 / 3 - hue) * 6;\n }\n return start;\n};\n\n/**\n * WCAG relative luminance (0–1).\n *\n * @see https://www.w3.org/WAI/GL/wiki/Relative_luminance\n */\nexport const relativeLuminance = (red: number, green: number, blue: number): number =>\n 0.2126 * linearize(red) + 0.7152 * linearize(green) + 0.0722 * linearize(blue);\n\nexport const rgbToHsl = (red: number, green: number, blue: number): Hsl => {\n const r = red / 255;\n const g = green / 255;\n const b = blue / 255;\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const lightness = (max + min) / 2;\n const delta = max - min;\n\n if (delta === 0) {\n return { hue: 0, lightness: lightness * 100, saturation: 0 };\n }\n\n const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);\n\n let hue: number;\n if (max === r) {\n hue = (g - b) / delta + (g < b ? 6 : 0);\n } else if (max === g) {\n hue = (b - r) / delta + 2;\n } else {\n hue = (r - g) / delta + 4;\n }\n\n return { hue: (hue / 6) * 360, lightness: lightness * 100, saturation: saturation * 100 };\n};\n\nexport const hslToRgb = ({ hue, lightness, saturation }: Hsl): RgbTuple => {\n const h = hue / 360;\n const s = saturation / 100;\n const l = lightness / 100;\n const end = l < 0.5 ? l * (1 + s) : l + s - l * s;\n const start = 2 * l - end;\n\n return [\n hueToChannel(start, end, h + 1 / 3) * 255,\n hueToChannel(start, end, h) * 255,\n hueToChannel(start, end, h - 1 / 3) * 255,\n ];\n};\n\nexport const rgbToLab = (red: number, green: number, blue: number): Lab => {\n const r = linearize(red);\n const g = linearize(green);\n const b = linearize(blue);\n\n const x = (0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / D65_WHITE_POINT.x;\n const y = (0.2126729 * r + 0.7151522 * g + 0.072175 * b) / D65_WHITE_POINT.y;\n const z = (0.0193339 * r + 0.119192 * g + 0.9503041 * b) / D65_WHITE_POINT.z;\n\n const fx = pivot(x);\n const fy = pivot(y);\n const fz = pivot(z);\n\n return { a: 500 * (fx - fy), b: 200 * (fy - fz), lightness: 116 * fy - 16 };\n};\n\nexport const labToRgb = ({ a, b, lightness }: Lab): RgbTuple => {\n const fy = (lightness + 16) / 116;\n const fx = fy + a / 500;\n const fz = fy - b / 200;\n\n const x = (fx ** 3 > LAB_EPSILON ? fx ** 3 : (116 * fx - 16) / LAB_KAPPA) * D65_WHITE_POINT.x;\n const y =\n (lightness > LAB_LINEAR_LIGHTNESS_THRESHOLD ? fy ** 3 : lightness / LAB_KAPPA) *\n D65_WHITE_POINT.y;\n const z = (fz ** 3 > LAB_EPSILON ? fz ** 3 : (116 * fz - 16) / LAB_KAPPA) * D65_WHITE_POINT.z;\n\n return [\n compand(3.2404542 * x - 1.5371385 * y - 0.4985314 * z),\n compand(-0.969266 * x + 1.8760108 * y + 0.041556 * z),\n compand(0.0556434 * x - 0.2040259 * y + 1.0572252 * z),\n ];\n};\n","// oxlint-disable id-length -- r/g/b/a are the canonical names for color channels\nimport type { ColorInput, RgbaTuple } from '../types';\nimport { clamp } from './clamp';\n\nconst HEX_PATTERN = /^#?([\\da-f]{3,4}|[\\da-f]{6}|[\\da-f]{8})$/i;\nconst RGB_FUNCTION_PATTERN = /^rgba?\\(([^)]*)\\)$/i;\nconst NUMBER_PATTERN = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)$/;\nconst PERCENTAGE_PATTERN = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)%$/;\nconst SEPARATOR_PATTERN = /[\\s,]+/;\n\nconst MAX_CHANNEL = 255;\nconst HEX_RADIX = 16;\n\nconst toChannel = (value: number): number => clamp(value, 0, MAX_CHANNEL);\nconst toAlpha = (value: number): number => clamp(value, 0, 1);\n\n/**\n * Read a numeric token, where a percentage is taken as a fraction of `percentageOf`. A\n * channel is `128` or `50%` (of 255); an alpha is `0.5` or `50%` (of 1).\n */\nconst parseNumericToken = (token: string | undefined, percentageOf: number): number | null => {\n if (token === undefined) {\n return null;\n }\n if (PERCENTAGE_PATTERN.test(token)) {\n return (Number(token.slice(0, -1)) / 100) * percentageOf;\n }\n if (NUMBER_PATTERN.test(token)) {\n return Number(token);\n }\n return null;\n};\n\nconst parseChannelToken = (token: string | undefined): number | null => {\n const value = parseNumericToken(token, MAX_CHANNEL);\n return value === null ? null : toChannel(value);\n};\n\nconst parseAlphaToken = (token: string | undefined): number | null => {\n const value = parseNumericToken(token, 1);\n return value === null ? null : toAlpha(value);\n};\n\nconst parseHex = (value: string): RgbaTuple | null => {\n const match = HEX_PATTERN.exec(value);\n const digits = match?.[1];\n if (digits === undefined) {\n return null;\n }\n\n // #rgb and #rgba are shorthand for each digit repeated: #1a2 -> #11aa22\n const expanded = digits.length <= 4 ? digits.replaceAll(/./g, '$&$&') : digits;\n\n return [\n Number.parseInt(expanded.slice(0, 2), HEX_RADIX),\n Number.parseInt(expanded.slice(2, 4), HEX_RADIX),\n Number.parseInt(expanded.slice(4, 6), HEX_RADIX),\n expanded.length === 8 ? Number.parseInt(expanded.slice(6, 8), HEX_RADIX) / MAX_CHANNEL : 1,\n ];\n};\n\nconst parseRgbFunction = (value: string): RgbaTuple | null => {\n const match = RGB_FUNCTION_PATTERN.exec(value);\n const body = match?.[1];\n if (body === undefined) {\n return null;\n }\n\n // Modern syntax puts alpha behind a slash: rgb(41 73 229 / 50%)\n const [channelSource, alphaSource, ...extraSources] = body.split('/');\n if (channelSource === undefined || extraSources.length > 0) {\n return null;\n }\n\n const tokens = channelSource.trim().split(SEPARATOR_PATTERN).filter(Boolean);\n const alphaTokens = (alphaSource ?? '').trim().split(SEPARATOR_PATTERN).filter(Boolean);\n if (alphaTokens.length > 1) {\n return null;\n }\n\n const [redToken, greenToken, blueToken, legacyAlphaToken, ...extraTokens] = tokens;\n if (extraTokens.length > 0) {\n return null;\n }\n if (alphaSource !== undefined && legacyAlphaToken !== undefined) {\n return null;\n }\n\n const red = parseChannelToken(redToken);\n const green = parseChannelToken(greenToken);\n const blue = parseChannelToken(blueToken);\n if (red === null || green === null || blue === null) {\n return null;\n }\n\n const alphaToken = alphaTokens[0] ?? legacyAlphaToken;\n if (alphaToken === undefined) {\n return [red, green, blue, 1];\n }\n\n const alpha = parseAlphaToken(alphaToken);\n return alpha === null ? null : [red, green, blue, alpha];\n};\n\n/**\n * Read any supported color representation into RGBA channels, or `null` when the input\n * is not a color this package understands.\n */\nexport const parseColor = (input: ColorInput): RgbaTuple | null => {\n if (typeof input === 'string') {\n const trimmed = input.trim();\n return parseHex(trimmed) ?? parseRgbFunction(trimmed);\n }\n\n if (Array.isArray(input)) {\n const [red, green, blue, alpha = 1] = input;\n if (![red, green, blue, alpha].every((value) => Number.isFinite(value))) {\n return null;\n }\n return [toChannel(red), toChannel(green), toChannel(blue), toAlpha(alpha)];\n }\n\n const { r, g, b, a = 1 } = input;\n if (![r, g, b, a].every((value) => Number.isFinite(value))) {\n return null;\n }\n return [toChannel(r), toChannel(g), toChannel(b), toAlpha(a)];\n};\n","import type { ColorInput, Hsl, InterpolationMode, Lab, RgbaTuple } from './types';\n// oxlint-disable id-length -- r/g/b/a are the canonical names for color channels\nimport { clamp } from './private/clamp';\nimport { hslToRgb, labToRgb, relativeLuminance, rgbToHsl, rgbToLab } from './private/colorSpace';\nimport { parseColor } from './private/parseColor';\n\nconst HEX_RADIX = 16;\n\n/** Lab lightness covered by one unit of `lighten()`/`darken()` */\nconst LAB_LIGHTNESS_STEP = 18;\n\nconst DEFAULT_INTERPOLATION_MODE: InterpolationMode = 'lrgb';\n\nconst WHITE = '#ffffff';\nconst BLACK = '#000000';\n\n/** Wistia brand blue (`#2949e5`) — what an unreadable input resolves to. */\nconst FALLBACK_CHANNELS: RgbaTuple = [41, 73, 229, 1];\n\nconst toHexPair = (channel: number): string =>\n Math.round(channel).toString(HEX_RADIX).padStart(2, '0');\n\n/**\n * Truncated rather than rounded, so a ratio can never climb across a WCAG threshold on\n * its way to one decimal — 4.4501 reports as 4.4, not as a passing 4.5.\n */\nconst truncateToTenth = (ratio: number): number => Math.floor(ratio * 10) / 10;\n\nconst interpolate = (from: number, to: number, ratio: number): number => from + (to - from) * ratio;\n\n/**\n * Interpolate through linear light rather than gamma-encoded sRGB, which keeps blends\n * between saturated colors from sagging through a muddy midpoint.\n */\nconst interpolateLinear = (from: number, to: number, ratio: number): number =>\n Math.sqrt(from ** 2 * (1 - ratio) + to ** 2 * ratio);\n\n/**\n * An immutable sRGB color with transformation methods like lighten, darken, tint, shade.\n * Also includes methods to calculate contrast ratio against another color, relative luminance, etc.\n *\n * @example\n * ```ts\n * new Color('#2949e5').shade(0.8).toHex(); // '#0a1339'\n * new Color('#2949e5').withAlpha(0.5).toRgba(); // 'rgba(41, 73, 229, 0.5)'\n * new Color('#2949e5').contrast('#ffffff'); // 6.6\n * ```\n */\nexport class Color {\n /**\n * Read `input` into a color, returning `null` when it cannot be parsed.\n */\n static parse(input: ColorInput): Color | null {\n const channels = parseColor(input);\n return channels === null ? null : new Color(channels);\n }\n\n static fromHsl(hsl: Hsl, alpha = 1): Color {\n const [red, green, blue] = hslToRgb(hsl);\n return new Color([red, green, blue, alpha]);\n }\n\n static fromLab(lab: Lab, alpha = 1): Color {\n const [red, green, blue] = labToRgb(lab);\n return new Color([red, green, blue, alpha]);\n }\n\n /** Red channel, 0–255. Kept unrounded so that chained operations don't accumulate error. */\n readonly r: number;\n\n /** Green channel, 0–255. Kept unrounded so that chained operations don't accumulate error. */\n readonly g: number;\n\n /** Blue channel, 0–255. Kept unrounded so that chained operations don't accumulate error. */\n readonly b: number;\n\n /** Alpha channel, 0–1. */\n readonly a: number;\n\n /**\n * @param input - any supported color representation. Input that cannot be read as a\n * color resolves to brand blue rather than failing, so a malformed value degrades to\n * a usable color instead of interrupting a render.\n */\n constructor(input: ColorInput) {\n const [red, green, blue, alpha] = parseColor(input) ?? FALLBACK_CHANNELS;\n this.r = red;\n this.g = green;\n this.b = blue;\n this.a = alpha;\n }\n\n /** `#rrggbb`. Alpha is dropped — use {@link Color.toHexWithAlpha} to keep it. */\n toHex(): string {\n return `#${toHexPair(this.r)}${toHexPair(this.g)}${toHexPair(this.b)}`;\n }\n\n /** `#rrggbbaa`. */\n toHexWithAlpha(): string {\n return `${this.toHex()}${toHexPair(this.a * 255)}`;\n }\n\n /** `rgb(41, 73, 229)`. */\n toRgb(): string {\n return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`;\n }\n\n /** `rgba(41, 73, 229, 0.5)`. */\n toRgba(): string {\n return `rgba(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)}, ${this.a})`;\n }\n\n /** Rounded channels, ready to hand to another color library. */\n toRgbaTuple(): RgbaTuple {\n return [Math.round(this.r), Math.round(this.g), Math.round(this.b), this.a];\n }\n\n toHsl(): Hsl {\n return rgbToHsl(this.r, this.g, this.b);\n }\n\n toLab(): Lab {\n return rgbToLab(this.r, this.g, this.b);\n }\n\n /** Hex when fully opaque, `rgba()` otherwise, so the alpha is never silently lost. */\n toString(): string {\n return this.a === 1 ? this.toHex() : this.toRgba();\n }\n\n /**\n * WCAG relative luminance, from 0 (black) to 1 (white). Alpha is ignored — luminance\n * is only meaningful once a color has been composited onto a background.\n */\n luminance(): number {\n return relativeLuminance(this.r, this.g, this.b);\n }\n\n /**\n * WCAG contrast ratio against `other`, from 1 (identical) to 21 (black on white),\n * reported to one decimal place.\n *\n * @see {@link colorContrastRatiosByShape} for the ratio each kind of content needs\n */\n contrast(other: ColorInput): number {\n const ownLuminance = this.luminance();\n const otherLuminance = new Color(other).luminance();\n const lighter = Math.max(ownLuminance, otherLuminance);\n const darker = Math.min(ownLuminance, otherLuminance);\n return truncateToTenth((lighter + 0.05) / (darker + 0.05));\n }\n\n /**\n * Raise perceptual lightness. Each unit of `amount` moves the color 18 points up the\n * Lab lightness axis, which stays visually even across hues in a way that nudging RGB\n * channels does not.\n */\n lighten(amount = 1): Color {\n const { a, b, lightness } = this.toLab();\n return Color.fromLab({ a, b, lightness: lightness + LAB_LIGHTNESS_STEP * amount }, this.a);\n }\n\n /** Lower perceptual lightness. The inverse of {@link Color.lighten}. */\n darken(amount = 1): Color {\n return this.lighten(-amount);\n }\n\n /** Mix `ratio` (0–1) of white in. */\n tint(ratio: number, mode: InterpolationMode = DEFAULT_INTERPOLATION_MODE): Color {\n return this.blend(WHITE, ratio, mode);\n }\n\n /** Mix `ratio` (0–1) of black in. */\n shade(ratio: number, mode: InterpolationMode = DEFAULT_INTERPOLATION_MODE): Color {\n return this.blend(BLACK, ratio, mode);\n }\n\n /**\n * Mix towards `other`, where `ratio` 0 keeps this color and 1 returns `other`. Alpha is\n * always interpolated linearly, whichever `mode` the channels travel through.\n */\n blend(\n other: ColorInput,\n ratio = 0.5,\n mode: InterpolationMode = DEFAULT_INTERPOLATION_MODE,\n ): Color {\n const target = new Color(other);\n const amount = clamp(ratio, 0, 1);\n const alpha = interpolate(this.a, target.a, amount);\n\n if (mode === 'lab') {\n const from = this.toLab();\n const to = target.toLab();\n return Color.fromLab(\n {\n a: interpolate(from.a, to.a, amount),\n b: interpolate(from.b, to.b, amount),\n lightness: interpolate(from.lightness, to.lightness, amount),\n },\n alpha,\n );\n }\n\n const blendChannel = mode === 'lrgb' ? interpolateLinear : interpolate;\n return new Color([\n blendChannel(this.r, target.r, amount),\n blendChannel(this.g, target.g, amount),\n blendChannel(this.b, target.b, amount),\n alpha,\n ]);\n }\n\n /** Replace the alpha channel with `alpha` (0–1). */\n withAlpha(alpha: number): Color {\n return new Color([this.r, this.g, this.b, alpha]);\n }\n\n /**\n * Replace HSL lightness with `lightness` (0–100), leaving hue and saturation alone.\n * Use this to hit an absolute lightness; use {@link Color.lighten} to step relative to\n * where the color already is.\n */\n withLightness(lightness: number): Color {\n const { hue, saturation } = this.toHsl();\n return Color.fromHsl({ hue, lightness: clamp(lightness, 0, 100), saturation }, this.a);\n }\n}\n","export const colorContrastRatiosByShape = {\n nonText: 3.0, // 3.0:1 - https://www.w3.org/TR/WCAG21/#non-text-contrast\n largeText: 3.0, // 3.0:1 - https://www.w3.org/TR/WCAG21/#contrast-minimum\n paragraphText: 4.5, // 4.5:1 - https://www.w3.org/TR/WCAG21/#contrast-minimum\n smallText: 5.5, // We're making this up, but it should be more than the paragraph text\n};\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wistia/kaleidoscope",
|
|
3
|
+
"version": "0.0.0-beta.1e8dd2d4.d35c078",
|
|
4
|
+
"description": "Shared logic and token engine for Wistia's audience-facing design system",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
"./package.json": "./package.json",
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=24.0.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build:watch": "tsdown --watch",
|
|
24
|
+
"clean": "shx rm -rf ./dist && echo 'dist directory has been removed'",
|
|
25
|
+
"prepack": "vp run build",
|
|
26
|
+
"publint": "publint-check",
|
|
27
|
+
"publint:export": "check-export-map",
|
|
28
|
+
"test:ci": "vitest run --coverage",
|
|
29
|
+
"test:coverage": "yarn run test:ci && open ./coverage/index.html",
|
|
30
|
+
"test:watch": "vitest",
|
|
31
|
+
"typecheck": "tsc"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@repo/config": "workspace:*",
|
|
35
|
+
"@types/node": "^26.1.2",
|
|
36
|
+
"@vitest/coverage-istanbul": "^4.1.10",
|
|
37
|
+
"check-export-map": "^1.3.1",
|
|
38
|
+
"shx": "^0.4.0",
|
|
39
|
+
"tsdown": "^0.22.14",
|
|
40
|
+
"typescript-7": "npm:typescript@^7.0.2",
|
|
41
|
+
"vitest": "^4.1.10"
|
|
42
|
+
},
|
|
43
|
+
"author": "Wistia Engineering",
|
|
44
|
+
"license": "UNLICENSED",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/wistia/vhs.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/wistia/vhs/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/wistia/vhs#readme"
|
|
53
|
+
}
|