@sardine/colour 3.0.0 → 4.0.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/dist/index.mjs CHANGED
@@ -1,775 +1,1078 @@
1
- const namedCSSColours = /* @__PURE__ */ new Map([
2
- ["aliceblue", "#f0f8ff"],
3
- ["antiquewhite", "#faebd7"],
4
- ["aqua", "#00ffff"],
5
- ["aquamarine", "#7fffd4"],
6
- ["azure", "#f0ffff"],
7
- ["beige", "#f5f5dc"],
8
- ["bisque", "#ffe4c4"],
9
- ["black", "#000000"],
10
- ["blanchedalmond", "#ffebcd"],
11
- ["blue", "#0000ff"],
12
- ["blueviolet", "#8a2be2"],
13
- ["brown", "#a52a2a"],
14
- ["burlywood", "#deb887"],
15
- ["cadetblue", "#5f9ea0"],
16
- ["chartreuse", "#7fff00"],
17
- ["chocolate", "#d2691e"],
18
- ["coral", "#ff7f50"],
19
- ["cornflowerblue", "#6495ed"],
20
- ["cornsilk", "#fff8dc"],
21
- ["crimson", "#dc143c"],
22
- ["cyan", "#00ffff"],
23
- ["darkblue", "#00008b"],
24
- ["darkcyan", "#008b8b"],
25
- ["darkgoldenrod", "#b8860b"],
26
- ["darkgray", "#a9a9a9"],
27
- ["darkgreen", "#006400"],
28
- ["darkgrey", "#a9a9a9"],
29
- ["darkkhaki", "#bdb76b"],
30
- ["darkmagenta", "#8b008b"],
31
- ["darkolivegreen", "#556b2f"],
32
- ["darkorange", "#ff8c00"],
33
- ["darkorchid", "#9932cc"],
34
- ["darkred", "#8b0000"],
35
- ["darksalmon", "#e9967a"],
36
- ["darkseagreen", "#8fbc8f"],
37
- ["darkslateblue", "#483d8b"],
38
- ["darkslategray", "#2f4f4f"],
39
- ["darkslategrey", "#2f4f4f"],
40
- ["darkturquoise", "#00ced1"],
41
- ["darkviolet", "#9400d3"],
42
- ["deeppink", "#ff1493"],
43
- ["deepskyblue", "#00bfff"],
44
- ["dimgray", "#696969"],
45
- ["dimgrey", "#696969"],
46
- ["dodgerblue", "#1e90ff"],
47
- ["firebrick", "#b22222"],
48
- ["floralwhite", "#fffaf0"],
49
- ["forestgreen", "#228b22"],
50
- ["fuchsia", "#ff00ff"],
51
- ["gainsboro", "#dcdcdc"],
52
- ["ghostwhite", "#f8f8ff"],
53
- ["gold", "#ffd700"],
54
- ["goldenrod", "#daa520"],
55
- ["gray", "#808080"],
56
- ["green", "#008000"],
57
- ["greenyellow", "#adff2f"],
58
- ["grey", "#808080"],
59
- ["honeydew", "#f0fff0"],
60
- ["hotpink", "#ff69b4"],
61
- ["indianred", "#cd5c5c"],
62
- ["indigo", "#4b0082"],
63
- ["ivory", "#fffff0"],
64
- ["khaki", "#f0e68c"],
65
- ["lavender", "#e6e6fa"],
66
- ["lavenderblush", "#fff0f5"],
67
- ["lawngreen", "#7cfc00"],
68
- ["lemonchiffon", "#fffacd"],
69
- ["lightblue", "#add8e6"],
70
- ["lightcoral", "#f08080"],
71
- ["lightcyan", "#e0ffff"],
72
- ["lightgoldenrodyellow", "#fafad2"],
73
- ["lightgray", "#d3d3d3"],
74
- ["lightgreen", "#90ee90"],
75
- ["lightgrey", "#d3d3d3"],
76
- ["lightpink", "#ffb6c1"],
77
- ["lightsalmon", "#ffa07a"],
78
- ["lightseagreen", "#20b2aa"],
79
- ["lightskyblue", "#87cefa"],
80
- ["lightslategray", "#778899"],
81
- ["lightslategrey", "#778899"],
82
- ["lightsteelblue", "#b0c4de"],
83
- ["lightyellow", "#ffffe0"],
84
- ["lime", "#00ff00"],
85
- ["limegreen", "#32cd32"],
86
- ["linen", "#faf0e6"],
87
- ["magenta", "#ff00ff"],
88
- ["maroon", "#800000"],
89
- ["mediumaquamarine", "#66cdaa"],
90
- ["mediumblue", "#0000cd"],
91
- ["mediumorchid", "#ba55d3"],
92
- ["mediumpurple", "#9370db"],
93
- ["mediumseagreen", "#3cb371"],
94
- ["mediumslateblue", "#7b68ee"],
95
- ["mediumspringgreen", "#00fa9a"],
96
- ["mediumturquoise", "#48d1cc"],
97
- ["mediumvioletred", "#c71585"],
98
- ["midnightblue", "#191970"],
99
- ["mintcream", "#f5fffa"],
100
- ["mistyrose", "#ffe4e1"],
101
- ["moccasin", "#ffe4b5"],
102
- ["navajowhite", "#ffdead"],
103
- ["navy", "#000080"],
104
- ["oldlace", "#fdf5e6"],
105
- ["olive", "#808000"],
106
- ["olivedrab", "#6b8e23"],
107
- ["orange", "#ffa500"],
108
- ["orangered", "#ff4500"],
109
- ["orchid", "#da70d6"],
110
- ["palegoldenrod", "#eee8aa"],
111
- ["palegreen", "#98fb98"],
112
- ["paleturquoise", "#afeeee"],
113
- ["palevioletred", "#db7093"],
114
- ["papayawhip", "#ffefd5"],
115
- ["peachpuff", "#ffdab9"],
116
- ["peru", "#cd853f"],
117
- ["pink", "#ffc0cb"],
118
- ["plum", "#dda0dd"],
119
- ["powderblue", "#b0e0e6"],
120
- ["purple", "#800080"],
121
- ["rebeccapurple", "#663399"],
122
- ["red", "#ff0000"],
123
- ["rosybrown", "#bc8f8f"],
124
- ["royalblue", "#4169e1"],
125
- ["saddlebrown", "#8b4513"],
126
- ["salmon", "#fa8072"],
127
- ["sandybrown", "#f4a460"],
128
- ["seagreen", "#2e8b57"],
129
- ["seashell", "#fff5ee"],
130
- ["sienna", "#a0522d"],
131
- ["silver", "#c0c0c0"],
132
- ["skyblue", "#87ceeb"],
133
- ["slateblue", "#6a5acd"],
134
- ["slategray", "#708090"],
135
- ["slategrey", "#708090"],
136
- ["snow", "#fffafa"],
137
- ["springgreen", "#00ff7f"],
138
- ["steelblue", "#4682b4"],
139
- ["tan", "#d2b48c"],
140
- ["teal", "#008080"],
141
- ["thistle", "#d8bfd8"],
142
- ["tomato", "#ff6347"],
143
- ["transparent", "#00000000"],
144
- ["turquoise", "#40e0d0"],
145
- ["violet", "#ee82ee"],
146
- ["wheat", "#f5deb3"],
147
- ["white", "#ffffff"],
148
- ["whitesmoke", "#f5f5f5"],
149
- ["yellow", "#ffff00"],
150
- ["yellowgreen", "#9acd32"]
1
+ //#region src/util/namedCSSColours.ts
2
+ /**
3
+ * Named list from https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
4
+ */
5
+ var namedCSSColours = new Map([
6
+ ["aliceblue", "#f0f8ff"],
7
+ ["antiquewhite", "#faebd7"],
8
+ ["aqua", "#00ffff"],
9
+ ["aquamarine", "#7fffd4"],
10
+ ["azure", "#f0ffff"],
11
+ ["beige", "#f5f5dc"],
12
+ ["bisque", "#ffe4c4"],
13
+ ["black", "#000000"],
14
+ ["blanchedalmond", "#ffebcd"],
15
+ ["blue", "#0000ff"],
16
+ ["blueviolet", "#8a2be2"],
17
+ ["brown", "#a52a2a"],
18
+ ["burlywood", "#deb887"],
19
+ ["cadetblue", "#5f9ea0"],
20
+ ["chartreuse", "#7fff00"],
21
+ ["chocolate", "#d2691e"],
22
+ ["coral", "#ff7f50"],
23
+ ["cornflowerblue", "#6495ed"],
24
+ ["cornsilk", "#fff8dc"],
25
+ ["crimson", "#dc143c"],
26
+ ["cyan", "#00ffff"],
27
+ ["darkblue", "#00008b"],
28
+ ["darkcyan", "#008b8b"],
29
+ ["darkgoldenrod", "#b8860b"],
30
+ ["darkgray", "#a9a9a9"],
31
+ ["darkgreen", "#006400"],
32
+ ["darkgrey", "#a9a9a9"],
33
+ ["darkkhaki", "#bdb76b"],
34
+ ["darkmagenta", "#8b008b"],
35
+ ["darkolivegreen", "#556b2f"],
36
+ ["darkorange", "#ff8c00"],
37
+ ["darkorchid", "#9932cc"],
38
+ ["darkred", "#8b0000"],
39
+ ["darksalmon", "#e9967a"],
40
+ ["darkseagreen", "#8fbc8f"],
41
+ ["darkslateblue", "#483d8b"],
42
+ ["darkslategray", "#2f4f4f"],
43
+ ["darkslategrey", "#2f4f4f"],
44
+ ["darkturquoise", "#00ced1"],
45
+ ["darkviolet", "#9400d3"],
46
+ ["deeppink", "#ff1493"],
47
+ ["deepskyblue", "#00bfff"],
48
+ ["dimgray", "#696969"],
49
+ ["dimgrey", "#696969"],
50
+ ["dodgerblue", "#1e90ff"],
51
+ ["firebrick", "#b22222"],
52
+ ["floralwhite", "#fffaf0"],
53
+ ["forestgreen", "#228b22"],
54
+ ["fuchsia", "#ff00ff"],
55
+ ["gainsboro", "#dcdcdc"],
56
+ ["ghostwhite", "#f8f8ff"],
57
+ ["gold", "#ffd700"],
58
+ ["goldenrod", "#daa520"],
59
+ ["gray", "#808080"],
60
+ ["green", "#008000"],
61
+ ["greenyellow", "#adff2f"],
62
+ ["grey", "#808080"],
63
+ ["honeydew", "#f0fff0"],
64
+ ["hotpink", "#ff69b4"],
65
+ ["indianred", "#cd5c5c"],
66
+ ["indigo", "#4b0082"],
67
+ ["ivory", "#fffff0"],
68
+ ["khaki", "#f0e68c"],
69
+ ["lavender", "#e6e6fa"],
70
+ ["lavenderblush", "#fff0f5"],
71
+ ["lawngreen", "#7cfc00"],
72
+ ["lemonchiffon", "#fffacd"],
73
+ ["lightblue", "#add8e6"],
74
+ ["lightcoral", "#f08080"],
75
+ ["lightcyan", "#e0ffff"],
76
+ ["lightgoldenrodyellow", "#fafad2"],
77
+ ["lightgray", "#d3d3d3"],
78
+ ["lightgreen", "#90ee90"],
79
+ ["lightgrey", "#d3d3d3"],
80
+ ["lightpink", "#ffb6c1"],
81
+ ["lightsalmon", "#ffa07a"],
82
+ ["lightseagreen", "#20b2aa"],
83
+ ["lightskyblue", "#87cefa"],
84
+ ["lightslategray", "#778899"],
85
+ ["lightslategrey", "#778899"],
86
+ ["lightsteelblue", "#b0c4de"],
87
+ ["lightyellow", "#ffffe0"],
88
+ ["lime", "#00ff00"],
89
+ ["limegreen", "#32cd32"],
90
+ ["linen", "#faf0e6"],
91
+ ["magenta", "#ff00ff"],
92
+ ["maroon", "#800000"],
93
+ ["mediumaquamarine", "#66cdaa"],
94
+ ["mediumblue", "#0000cd"],
95
+ ["mediumorchid", "#ba55d3"],
96
+ ["mediumpurple", "#9370db"],
97
+ ["mediumseagreen", "#3cb371"],
98
+ ["mediumslateblue", "#7b68ee"],
99
+ ["mediumspringgreen", "#00fa9a"],
100
+ ["mediumturquoise", "#48d1cc"],
101
+ ["mediumvioletred", "#c71585"],
102
+ ["midnightblue", "#191970"],
103
+ ["mintcream", "#f5fffa"],
104
+ ["mistyrose", "#ffe4e1"],
105
+ ["moccasin", "#ffe4b5"],
106
+ ["navajowhite", "#ffdead"],
107
+ ["navy", "#000080"],
108
+ ["oldlace", "#fdf5e6"],
109
+ ["olive", "#808000"],
110
+ ["olivedrab", "#6b8e23"],
111
+ ["orange", "#ffa500"],
112
+ ["orangered", "#ff4500"],
113
+ ["orchid", "#da70d6"],
114
+ ["palegoldenrod", "#eee8aa"],
115
+ ["palegreen", "#98fb98"],
116
+ ["paleturquoise", "#afeeee"],
117
+ ["palevioletred", "#db7093"],
118
+ ["papayawhip", "#ffefd5"],
119
+ ["peachpuff", "#ffdab9"],
120
+ ["peru", "#cd853f"],
121
+ ["pink", "#ffc0cb"],
122
+ ["plum", "#dda0dd"],
123
+ ["powderblue", "#b0e0e6"],
124
+ ["purple", "#800080"],
125
+ ["rebeccapurple", "#663399"],
126
+ ["red", "#ff0000"],
127
+ ["rosybrown", "#bc8f8f"],
128
+ ["royalblue", "#4169e1"],
129
+ ["saddlebrown", "#8b4513"],
130
+ ["salmon", "#fa8072"],
131
+ ["sandybrown", "#f4a460"],
132
+ ["seagreen", "#2e8b57"],
133
+ ["seashell", "#fff5ee"],
134
+ ["sienna", "#a0522d"],
135
+ ["silver", "#c0c0c0"],
136
+ ["skyblue", "#87ceeb"],
137
+ ["slateblue", "#6a5acd"],
138
+ ["slategray", "#708090"],
139
+ ["slategrey", "#708090"],
140
+ ["snow", "#fffafa"],
141
+ ["springgreen", "#00ff7f"],
142
+ ["steelblue", "#4682b4"],
143
+ ["tan", "#d2b48c"],
144
+ ["teal", "#008080"],
145
+ ["thistle", "#d8bfd8"],
146
+ ["tomato", "#ff6347"],
147
+ ["transparent", "#00000000"],
148
+ ["turquoise", "#40e0d0"],
149
+ ["violet", "#ee82ee"],
150
+ ["wheat", "#f5deb3"],
151
+ ["white", "#ffffff"],
152
+ ["whitesmoke", "#f5f5f5"],
153
+ ["yellow", "#ffff00"],
154
+ ["yellowgreen", "#9acd32"]
151
155
  ]);
152
- const hexAnyRegex = /^#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})$/;
153
- const cssRGBARegex = /^rgba?\(\s*([-+]?\d*\.?\d+%?)\s*[,\s]\s*([-+]?\d*\.?\d+%?)\s*[,\s]\s*([-+]?\d*\.?\d+%?)\s*(?:[,/]\s*([-+]?\d*\.?\d+%?))?\s*\)$/i;
156
+ //#endregion
157
+ //#region src/util/regexers.ts
158
+ /** Any valid hexadecimal colour: 3, 4, 6, or 8 hex digits after `#` */
159
+ var hexAnyRegex = /^#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})$/;
160
+ /**
161
+ * Captures the following CSS RGB formats:
162
+ * - `rgb(0,0,0)`
163
+ * - `rgba(0, 0, 0, 0.4)`
164
+ * - `rgba(0,0,0,50%)`
165
+ * - `rgb(0 0 0)`
166
+ * - `rgba(0 0 0 / 0.4)`
167
+ * - `rgb(0 0 0 / 0.5)`
168
+ * - `rgb(0 0 0 / 50%)`
169
+ * - `rgb(50%, 25%, 100%)`
170
+ * - `rgba(50%, 25%, 100%, 0.8)`
171
+ * - `rgba(50%, 25%, 100%, 80%)`
172
+ */
173
+ var cssRGBARegex = /^rgba?\(\s*([-+]?\d*\.?\d+%?)\s*[,\s]\s*([-+]?\d*\.?\d+%?)\s*[,\s]\s*([-+]?\d*\.?\d+%?)\s*(?:[,/]\s*([-+]?\d*\.?\d+%?))?\s*\)$/i;
174
+ //#endregion
175
+ //#region src/assertions.ts
176
+ /**
177
+ * Determines whether a string represents a valid CSS RGB or RGBA colour value.
178
+ *
179
+ * Captures the following CSS RGB formats:
180
+ * - `rgb(0,0,0)`
181
+ * - `rgba(0, 0, 0, 0.4)`
182
+ * - `rgba(0,0,0,50%)`
183
+ * - `rgb(0 0 0)`
184
+ * - `rgba(0 0 0 / 0.4)`
185
+ * - `rgb(0 0 0 / 0.5)`
186
+ * - `rgb(0 0 0 / 50%)`
187
+ * - `rgb(50%, 25%, 100%)`
188
+ * - `rgba(50%, 25%, 100%, 0.8)`
189
+ * - `rgba(50%, 25%, 100%, 80%)`
190
+ *
191
+ * @param {string} colour - The string to test.
192
+ * @returns {boolean} `true` if the string represents a valid CSS RGB or RGBA colour value, `false` otherwise.
193
+ */
154
194
  function isCSSRGBColour(colour) {
155
- return cssRGBARegex.test(colour);
195
+ return cssRGBARegex.test(colour);
156
196
  }
197
+ /**
198
+ * Determines whether a string represents a valid hexadecimal colour value.
199
+ *
200
+ * @param {string} colour - The string to test.
201
+ * @returns {boolean} True if the string represents a valid hexadecimal colour value, false otherwise.
202
+ */
157
203
  function isHexColour(colour) {
158
- return hexAnyRegex.test(colour);
204
+ return hexAnyRegex.test(colour);
159
205
  }
206
+ /**
207
+ * Determines whether a string represents a valid named CSS colour.
208
+ *
209
+ * @param {NamedCSSColour} colour - The string to test.
210
+ * @returns {boolean} `true` if the string represents a valid named CSS colour, `false` otherwise.
211
+ */
160
212
  function isNamedCSSColour(colour) {
161
- return namedCSSColours.has(colour);
213
+ return namedCSSColours.has(colour);
162
214
  }
215
+ //#endregion
216
+ //#region src/util/index.ts
217
+ /**
218
+ * Calculates the Hue derivative
219
+ * @param x A numeric expression representing the cartesian x-coordinate.
220
+ * @param y A numeric expression representing the cartesian y-coordinate.
221
+ */
163
222
  function hue_d(x, y) {
164
- if (x === 0 && y === 0) {
165
- return 0;
166
- }
167
- const angle = Math.atan2(x, y) * (180 / Math.PI);
168
- if (angle >= 0) {
169
- return angle;
170
- }
171
- return angle + 360;
223
+ if (x === 0 && y === 0) return 0;
224
+ /** The angle in degrees */
225
+ const angle = Math.atan2(x, y) * (180 / Math.PI);
226
+ if (angle >= 0) return angle;
227
+ return angle + 360;
172
228
  }
229
+ /**
230
+ * Calculates the difference between two Hue derivatives
231
+ * @param HueHelper param
232
+ */
173
233
  function deltaHue_d({ C1, C2, h1_d, h2_d }) {
174
- if (C1 * C2 === 0) {
175
- return 0;
176
- }
177
- const delta = h2_d - h1_d;
178
- if (Math.abs(delta) <= 180) {
179
- return delta;
180
- }
181
- if (delta > 180) {
182
- return delta - 360;
183
- }
184
- return delta + 360;
234
+ if (C1 * C2 === 0) return 0;
235
+ const delta = h2_d - h1_d;
236
+ if (Math.abs(delta) <= 180) return delta;
237
+ if (delta > 180) return delta - 360;
238
+ return delta + 360;
185
239
  }
240
+ /**
241
+ * Calculates the mean between two Hue derivatives
242
+ * @param HueHelper param
243
+ */
186
244
  function meanHue_d({ C1, C2, h1_d, h2_d }) {
187
- if (C1 * C2 === 0) {
188
- return h2_d + h1_d;
189
- }
190
- const delta = Math.abs(h1_d - h2_d);
191
- if (delta <= 180) {
192
- return (h2_d + h1_d) / 2;
193
- }
194
- if (delta > 180 && h1_d + h2_d < 360) {
195
- return (h2_d + h1_d + 360) / 2;
196
- }
197
- return (h2_d + h1_d - 360) / 2;
198
- }
199
- const DEG_TO_RAD = Math.PI / 180;
200
- const toRadians = (n) => n * DEG_TO_RAD;
201
- const POW_25_7 = 25 ** 7;
202
- const bigSquare = (n) => Math.sqrt(n ** 7 / (n ** 7 + POW_25_7));
245
+ if (C1 * C2 === 0) return h2_d + h1_d;
246
+ const delta = Math.abs(h1_d - h2_d);
247
+ if (delta <= 180) return (h2_d + h1_d) / 2;
248
+ if (delta > 180 && h1_d + h2_d < 360) return (h2_d + h1_d + 360) / 2;
249
+ return (h2_d + h1_d - 360) / 2;
250
+ }
251
+ /**
252
+ * Converts a number in degrees to radians
253
+ * @param n Number in degrees to be converted
254
+ */
255
+ var DEG_TO_RAD = Math.PI / 180;
256
+ var toRadians = (n) => n * DEG_TO_RAD;
257
+ /**
258
+ * Calculates a recurring square root
259
+ * @param n Input number
260
+ */
261
+ var POW_25_7 = 25 ** 7;
262
+ var bigSquare = (n) => Math.sqrt(n ** 7 / (n ** 7 + POW_25_7));
263
+ /**
264
+ * Normalise black and white colorimetry as specified in IEC 61966-2-1
265
+ * It takes a RGB channel in the range [0 - 255] and returns a value between 0 and 1
266
+ * @param rgbValue number to be normalised
267
+ */
203
268
  function linearRGB(rgbValue, WCAG21) {
204
- const rgbRatio = rgbValue / 255;
205
- const threshold = WCAG21 ? 0.03928 : 0.04045;
206
- let linearValue;
207
- if (rgbRatio > threshold) {
208
- linearValue = ((rgbRatio + 0.055) / 1.055) ** 2.4;
209
- } else {
210
- linearValue = rgbRatio / 12.92;
211
- }
212
- return linearValue;
213
- }
214
- const LAB_DELTA = 6 / 29;
215
- const LAB_DELTA_CUBE = LAB_DELTA ** 3;
216
- const LAB_DELTA_SQ_3 = 3 * LAB_DELTA ** 2;
269
+ const rgbRatio = rgbValue / 255;
270
+ const threshold = WCAG21 ? .03928 : .04045;
271
+ let linearValue;
272
+ if (rgbRatio > threshold) linearValue = ((rgbRatio + .055) / 1.055) ** 2.4;
273
+ else linearValue = rgbRatio / 12.92;
274
+ return linearValue;
275
+ }
276
+ /**
277
+ * The division of the f function domain into two parts was done to prevent an infinite slope at n = 0
278
+ * @param n Number to be constrained
279
+ */
280
+ var LAB_DELTA = 6 / 29;
281
+ var LAB_DELTA_CUBE = LAB_DELTA ** 3;
282
+ var LAB_DELTA_SQ_3 = 3 * LAB_DELTA ** 2;
217
283
  function constrainLab(n) {
218
- if (n > LAB_DELTA_CUBE) {
219
- return Math.cbrt(n);
220
- }
221
- return n / LAB_DELTA_SQ_3 + 4 / 29;
284
+ if (n > LAB_DELTA_CUBE) return Math.cbrt(n);
285
+ return n / LAB_DELTA_SQ_3 + 4 / 29;
222
286
  }
287
+ /**
288
+ * Clamps a number between two values
289
+ * @param {number} value - The value to be clamped
290
+ * @param {number} min - The minimum value
291
+ * @param {number} max - The maximum value
292
+ * @returns {number} - A clamped value
293
+ */
223
294
  function clamp(value, min, max) {
224
- return Math.min(Math.max(value, min), max);
295
+ return Math.min(Math.max(value, min), max);
225
296
  }
226
- const NAMED_CSS_COLOUR_URL = "https://developer.mozilla.org/en-US/docs/Web/CSS/named-color";
297
+ /** MDN reference for CSS named colours — shared to avoid duplicating the URL string in the bundle */
298
+ var NAMED_CSS_COLOUR_URL = "https://developer.mozilla.org/en-US/docs/Web/CSS/named-color";
299
+ //#endregion
300
+ //#region src/CIEDE2000.ts
301
+ /**
302
+ * Measures the colour difference between two colours in the Lab space
303
+ *
304
+ * Math taken from:
305
+ *
306
+ * https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
307
+ * http://www2.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
308
+ * @param colour1 First colour to be compared
309
+ * @param colour2 First colour to be compared
310
+ */
227
311
  function ciede2000(colour1, colour2) {
228
- const lightness1 = colour1.L;
229
- const greenRed1 = colour1.a;
230
- const blueYellow1 = colour1.b;
231
- const lightness2 = colour2.L;
232
- const greenRed2 = colour2.a;
233
- const blueYellow2 = colour2.b;
234
- const luminanceWeight = 1;
235
- const chromaWeight = 1;
236
- const hueWeight = 1;
237
- const chroma1 = Math.sqrt(greenRed1 ** 2 + blueYellow1 ** 2);
238
- const chroma2 = Math.sqrt(greenRed2 ** 2 + blueYellow2 ** 2);
239
- const deltaLightness = lightness2 - lightness1;
240
- const meanChroma = (chroma1 + chroma2) / 2;
241
- const G = 0.5 * (1 - bigSquare(meanChroma));
242
- const greenRed1Prime = greenRed1 * (1 + G);
243
- const greenRed2Prime = greenRed2 * (1 + G);
244
- const chroma1Prime = Math.sqrt(greenRed1Prime ** 2 + blueYellow1 ** 2);
245
- const chroma2Prime = Math.sqrt(greenRed2Prime ** 2 + blueYellow2 ** 2);
246
- const meanChromaPrime = (chroma1Prime + chroma2Prime) / 2;
247
- const deltaChromaPrime = chroma2Prime - chroma1Prime;
248
- const hue1Prime = hue_d(blueYellow1, greenRed1Prime);
249
- const hue2Prime = hue_d(blueYellow2, greenRed2Prime);
250
- const deltaHuePrime = deltaHue_d({
251
- C1: chroma1,
252
- C2: chroma2,
253
- h1_d: hue1Prime,
254
- h2_d: hue2Prime
255
- });
256
- const deltaHue = 2 * Math.sqrt(chroma1Prime * chroma2Prime) * Math.sin(toRadians(deltaHuePrime) / 2);
257
- const meanHuePrime = meanHue_d({
258
- C1: chroma1,
259
- C2: chroma2,
260
- h1_d: hue1Prime,
261
- h2_d: hue2Prime
262
- });
263
- const meanLightness = (lightness1 + lightness2) / 2;
264
- const T = 1 - 0.17 * Math.cos(toRadians(meanHuePrime - 30)) + 0.24 * Math.cos(toRadians(2 * meanHuePrime)) + 0.32 * Math.cos(toRadians(3 * meanHuePrime + 6)) - 0.2 * Math.cos(toRadians(4 * meanHuePrime - 63));
265
- const SL = 1 + 0.015 * (meanLightness - 50) ** 2 / Math.sqrt(20 + (meanLightness - 50) ** 2);
266
- const SC = 0.045 * meanChromaPrime + 1;
267
- const SH = 1 + 0.015 * meanChromaPrime * T;
268
- const rotation = 30 * Math.exp(-(((meanHuePrime - 275) / 25) ** 2));
269
- const RT = -2 * bigSquare(meanChromaPrime) * Math.sin(toRadians(rotation * 2));
270
- const deltaE = Math.sqrt(
271
- (deltaLightness / (luminanceWeight * SL)) ** 2 + (deltaChromaPrime / (chromaWeight * SC)) ** 2 + (deltaHue / (hueWeight * SH)) ** 2 + RT * (deltaChromaPrime / (chromaWeight * SC)) * (deltaHue / (hueWeight * SH))
272
- );
273
- return deltaE;
312
+ /** Lightness for colour 1 */
313
+ const lightness1 = colour1.L;
314
+ /** green–red colour opponent for colour 1 */
315
+ const greenRed1 = colour1.a;
316
+ /** blue–yellow colour opponent for colour 1 */
317
+ const blueYellow1 = colour1.b;
318
+ /** Lightness for colour 2 */
319
+ const lightness2 = colour2.L;
320
+ /** green–red colour opponent for colour 2 */
321
+ const greenRed2 = colour2.a;
322
+ /** blue–yellow colour opponent for colour 2 */
323
+ const blueYellow2 = colour2.b;
324
+ /** Weighting factor for Luminance */
325
+ const luminanceWeight = 1;
326
+ /** Weighting factor for Chroma */
327
+ const chromaWeight = 1;
328
+ /** Weighting factor for Hue */
329
+ const hueWeight = 1;
330
+ /** Chroma for colour 1 */
331
+ const chroma1 = Math.sqrt(greenRed1 ** 2 + blueYellow1 ** 2);
332
+ /** Chroma for colour 2 */
333
+ const chroma2 = Math.sqrt(greenRed2 ** 2 + blueYellow2 ** 2);
334
+ /** Derivative of the Lightness difference */
335
+ const deltaLightness = lightness2 - lightness1;
336
+ const G = .5 * (1 - bigSquare((chroma1 + chroma2) / 2));
337
+ /** Derivative of greenRed1 */
338
+ const greenRed1Prime = greenRed1 * (1 + G);
339
+ /** Derivative of greenRed2 */
340
+ const greenRed2Prime = greenRed2 * (1 + G);
341
+ /** Derivative of chroma1 */
342
+ const chroma1Prime = Math.sqrt(greenRed1Prime ** 2 + blueYellow1 ** 2);
343
+ /** Derivative of chroma2 */
344
+ const chroma2Prime = Math.sqrt(greenRed2Prime ** 2 + blueYellow2 ** 2);
345
+ /** Derivative of Chroma mean */
346
+ const meanChromaPrime = (chroma1Prime + chroma2Prime) / 2;
347
+ /** Derivative of the mean difference of Chroma */
348
+ const deltaChromaPrime = chroma2Prime - chroma1Prime;
349
+ /** Derivative of colour 1 Hue */
350
+ const hue1Prime = hue_d(blueYellow1, greenRed1Prime);
351
+ /** Derivative of colour 2 Hue */
352
+ const hue2Prime = hue_d(blueYellow2, greenRed2Prime);
353
+ /** Hue difference */
354
+ const deltaHuePrime = deltaHue_d({
355
+ C1: chroma1,
356
+ C2: chroma2,
357
+ h1_d: hue1Prime,
358
+ h2_d: hue2Prime
359
+ });
360
+ const deltaHue = 2 * Math.sqrt(chroma1Prime * chroma2Prime) * Math.sin(toRadians(deltaHuePrime) / 2);
361
+ /** Derivative of mean hue */
362
+ const meanHuePrime = meanHue_d({
363
+ C1: chroma1,
364
+ C2: chroma2,
365
+ h1_d: hue1Prime,
366
+ h2_d: hue2Prime
367
+ });
368
+ /** Lightness Mean value*/
369
+ const meanLightness = (lightness1 + lightness2) / 2;
370
+ /** Compensation for neutral colours (the primed values in the L*C*h differences) */
371
+ const T = 1 - .17 * Math.cos(toRadians(meanHuePrime - 30)) + .24 * Math.cos(toRadians(2 * meanHuePrime)) + .32 * Math.cos(toRadians(3 * meanHuePrime + 6)) - .2 * Math.cos(toRadians(4 * meanHuePrime - 63));
372
+ /** Compensation for lightness */
373
+ const SL = 1 + .015 * (meanLightness - 50) ** 2 / Math.sqrt(20 + (meanLightness - 50) ** 2);
374
+ /** Compensation for chroma */
375
+ const SC = .045 * meanChromaPrime + 1;
376
+ /** Compensation for hue */
377
+ const SH = 1 + .015 * meanChromaPrime * T;
378
+ const rotation = 30 * Math.exp(-(((meanHuePrime - 275) / 25) ** 2));
379
+ /** A hue rotation term, to deal with the problematic blue region (hue angles in the neighborhood of 275°) */
380
+ const RT = -2 * bigSquare(meanChromaPrime) * Math.sin(toRadians(rotation * 2));
381
+ return Math.sqrt((deltaLightness / (luminanceWeight * SL)) ** 2 + (deltaChromaPrime / (chromaWeight * SC)) ** 2 + (deltaHue / (hueWeight * SH)) ** 2 + RT * (deltaChromaPrime / (chromaWeight * SC)) * (deltaHue / (hueWeight * SH)));
274
382
  }
383
+ //#endregion
384
+ //#region src/convertCSSRGBtoRGB.ts
385
+ /**
386
+ * Converts a CSS RGB/Alpha value to its numeric equivalent
387
+ * @param value - The value string (e.g., "50%", "127", "0.5")
388
+ * @param isAlpha - Whether this is an alpha channel value
389
+ * @returns The numeric value
390
+ */
275
391
  function convertCssValueToNumber(value, isAlpha = false) {
276
- if (value.endsWith("%")) {
277
- const percentage = Number.parseFloat(value.slice(0, -1));
278
- if (isAlpha) {
279
- return percentage / 100;
280
- }
281
- return Math.round(percentage / 100 * 255);
282
- }
283
- return Number.parseFloat(value);
392
+ if (value.endsWith("%")) {
393
+ const percentage = Number.parseFloat(value.slice(0, -1));
394
+ if (isAlpha) return percentage / 100;
395
+ return Math.round(percentage / 100 * 255);
396
+ }
397
+ return Number.parseFloat(value);
284
398
  }
285
399
  function convertCssRgbChannelValue(value) {
286
- if (!value) return void 0;
287
- return convertCssValueToNumber(value);
400
+ if (!value) return void 0;
401
+ return convertCssValueToNumber(value);
288
402
  }
289
403
  function convertCssAlphaChannelValue(value) {
290
- if (!value) return void 0;
291
- return convertCssValueToNumber(value, true);
404
+ if (!value) return void 0;
405
+ return convertCssValueToNumber(value, true);
292
406
  }
407
+ /**
408
+ * Converts CSS RGB colour format into RGB colour object.
409
+ * @param {string} colour - A CSS RGB colour in the format:
410
+ *
411
+ * - `rgb(0,0,0)`
412
+ * - `rgba(0,0,0,0.4)`
413
+ * - `rgb(0 0 0)`
414
+ * - `rgba(0 0 0 / 0.4)`
415
+ * - `rgb(50%, 25%, 100%)`
416
+ * - `rgba(50%, 25%, 100%, 0.8)`
417
+ * - `rgba(50%, 25%, 100%, 80%)`
418
+ *
419
+ * @returns {RGBColour} - RGB colour object.
420
+ */
293
421
  function convertCSSRGBtoRGB(colour) {
294
- const match = colour.match(cssRGBARegex);
295
- if (!match) {
296
- throw new Error(
297
- `convertCSSRGBtoRGB expects a valid CSS RGB string but got ${colour}`
298
- );
299
- }
300
- return {
301
- R: convertCssRgbChannelValue(match[1]),
302
- G: convertCssRgbChannelValue(match[2]),
303
- B: convertCssRgbChannelValue(match[3]),
304
- A: convertCssAlphaChannelValue(match[4])
305
- };
422
+ const match = colour.match(cssRGBARegex);
423
+ if (!match) throw new Error(`convertCSSRGBtoRGB expects a valid CSS RGB string but got ${colour}`);
424
+ return {
425
+ R: convertCssRgbChannelValue(match[1]),
426
+ G: convertCssRgbChannelValue(match[2]),
427
+ B: convertCssRgbChannelValue(match[3]),
428
+ A: convertCssAlphaChannelValue(match[4])
429
+ };
306
430
  }
431
+ //#endregion
432
+ //#region src/convertRGBtoHex.ts
433
+ /**
434
+ * Converts a colour in the RGB format to Hexadecimal.
435
+ * It accepts an option Alpha channel `A`
436
+ * @param {RGBColour} colour - An object representing RGB Colour.
437
+ * @param {number} colour.R - A number between 0 and 255 to describe the Red colour channel
438
+ * @param {number} colour.G - A number between 0 and 255 to describe the Green colour channel
439
+ * @param {number} colour.B - A number between 0 and 255 to describe the Blue colour channel
440
+ * @param {number} colour.A - An optional number between 0 and 1 to describe the Alpha colour channel
441
+ * @returns - An hexadecimal string
442
+ */
307
443
  function convertRGBtoHex({ R, G, B, A }) {
308
- const hex = (n) => {
309
- const value = clamp(n, 0, 255);
310
- return value.toString(16).padStart(2, "0");
311
- };
312
- return `#${hex(R)}${hex(G)}${hex(B)}${A ? hex(Math.round(A * 255)) : ""}`;
444
+ const hex = (n) => {
445
+ return clamp(n, 0, 255).toString(16).padStart(2, "0");
446
+ };
447
+ return `#${hex(R)}${hex(G)}${hex(B)}${A ? hex(Math.round(A * 255)) : ""}`;
313
448
  }
449
+ //#endregion
450
+ //#region src/convertCSSRGBtoHex.ts
451
+ /**
452
+ * Converts CSS RGB colour format into Hexadecimal.
453
+ * @param {string} colour - A CSS RGB colour in the format:
454
+ *
455
+ * - `rgb(0,0,0)`
456
+ * - `rgba(0,0,0,0.4)`
457
+ * - `rgb(0 0 0)`
458
+ * - `rgba(0 0 0 / 0.4)`
459
+ * - `rgb(50%, 25%, 100%)`
460
+ * - `rgba(50%, 25%, 100%, 0.8)`
461
+ * - `rgba(50%, 25%, 100%, 80%)`
462
+ *
463
+ * @returns {string} - An hexadecimal string
464
+ */
314
465
  function convertCSSRGBtoHex(colour) {
315
- const rgb = convertCSSRGBtoRGB(colour);
316
- return convertRGBtoHex(rgb);
466
+ return convertRGBtoHex(convertCSSRGBtoRGB(colour));
317
467
  }
468
+ //#endregion
469
+ //#region src/convertHextoRGB.ts
470
+ /**
471
+ * Converts an hexadecimal colour into RGB colour object.
472
+ * @param {string} hex - An hexadecimal colour in the format:
473
+ *
474
+ * - `#000`
475
+ * - `#102030`
476
+ * - `#ffff`
477
+ * - `#102030ff`
478
+ *
479
+ * @returns {RGBColour | null} RGB colour object, or `null` if the input is not a valid hexadecimal colour.
480
+ */
318
481
  function convertHextoRGB(hex) {
319
- if (typeof hex !== "string") {
320
- throw new Error(`convertHextoRGB expects a string but got a ${typeof hex}`);
321
- }
322
- if (!hexAnyRegex.test(hex)) {
323
- throw new Error(
324
- `convertHextoRGB expects a valid hexadecimal colour value but got ${hex}`
325
- );
326
- }
327
- switch (hex.length) {
328
- case 7:
329
- return {
330
- R: parseInt(hex.slice(1, 3), 16),
331
- G: parseInt(hex.slice(3, 5), 16),
332
- B: parseInt(hex.slice(5, 7), 16)
333
- };
334
- case 4:
335
- return {
336
- R: parseInt(hex.slice(1, 2).repeat(2), 16),
337
- G: parseInt(hex.slice(2, 3).repeat(2), 16),
338
- B: parseInt(hex.slice(3, 4).repeat(2), 16)
339
- };
340
- case 9:
341
- return {
342
- R: parseInt(hex.slice(1, 3), 16),
343
- G: parseInt(hex.slice(3, 5), 16),
344
- B: parseInt(hex.slice(5, 7), 16),
345
- A: Math.round(parseInt(hex.slice(7, 9), 16) / 255 * 100) / 100
346
- };
347
- default:
348
- return {
349
- R: parseInt(hex.slice(1, 2).repeat(2), 16),
350
- G: parseInt(hex.slice(2, 3).repeat(2), 16),
351
- B: parseInt(hex.slice(3, 4).repeat(2), 16),
352
- A: Math.round(parseInt(hex.slice(4, 5).repeat(2), 16) / 255 * 100) / 100
353
- };
354
- }
482
+ if (typeof hex !== "string" || !hexAnyRegex.test(hex)) return null;
483
+ switch (hex.length) {
484
+ case 7: return {
485
+ R: parseInt(hex.slice(1, 3), 16),
486
+ G: parseInt(hex.slice(3, 5), 16),
487
+ B: parseInt(hex.slice(5, 7), 16)
488
+ };
489
+ case 4: return {
490
+ R: parseInt(hex.slice(1, 2).repeat(2), 16),
491
+ G: parseInt(hex.slice(2, 3).repeat(2), 16),
492
+ B: parseInt(hex.slice(3, 4).repeat(2), 16)
493
+ };
494
+ case 9: return {
495
+ R: parseInt(hex.slice(1, 3), 16),
496
+ G: parseInt(hex.slice(3, 5), 16),
497
+ B: parseInt(hex.slice(5, 7), 16),
498
+ A: Math.round(parseInt(hex.slice(7, 9), 16) / 255 * 100) / 100
499
+ };
500
+ default: return {
501
+ R: parseInt(hex.slice(1, 2).repeat(2), 16),
502
+ G: parseInt(hex.slice(2, 3).repeat(2), 16),
503
+ B: parseInt(hex.slice(3, 4).repeat(2), 16),
504
+ A: Math.round(parseInt(hex.slice(4, 5).repeat(2), 16) / 255 * 100) / 100
505
+ };
506
+ }
355
507
  }
508
+ //#endregion
509
+ //#region src/convertHextoCSSRGB.ts
510
+ /**
511
+ * Converts an hexadecimal colour into CSS RGB/A colour.
512
+ * @param {string} hex - An hexadecimal colour in the format:
513
+ *
514
+ * - `#000`
515
+ * - `#102030`
516
+ * - `#ffff`
517
+ * - `#102030ff`
518
+ *
519
+ * @returns {string} either a CSS RGB or CSS RGBA string.
520
+ */
356
521
  function convertHextoCSSRGB(hex) {
357
- const rgb = convertHextoRGB(hex);
358
- if (rgb.A) {
359
- return `rgba(${rgb.R},${rgb.G},${rgb.B},${rgb.A})`;
360
- }
361
- return `rgb(${rgb.R},${rgb.G},${rgb.B})`;
522
+ const rgb = convertHextoRGB(hex);
523
+ if (!rgb) return null;
524
+ if (rgb.A) return `rgba(${rgb.R},${rgb.G},${rgb.B},${rgb.A})`;
525
+ return `rgb(${rgb.R},${rgb.G},${rgb.B})`;
362
526
  }
527
+ //#endregion
528
+ //#region src/convertHextoNamedCSSColour.ts
529
+ /**
530
+ * Converts a hexadecimal colour value to a named CSS colour.
531
+ *
532
+ * @param {string} colour - The hexadecimal colour value to convert.
533
+ * @returns {NamedCSSColour | undefined} The named CSS colour that corresponds to the given hexadecimal colour value, or undefined if no named CSS colour matches the given value.
534
+ */
363
535
  function convertHextoNamedCSSColour(colour) {
364
- for (const [name, hex] of namedCSSColours.entries()) {
365
- if (hex === colour) {
366
- return name;
367
- }
368
- }
369
- return void 0;
536
+ for (const [name, hex] of namedCSSColours.entries()) if (hex === colour) return name;
370
537
  }
538
+ //#endregion
539
+ //#region src/convertNamedCSSColourtoHex.ts
540
+ /**
541
+ * Converts a named CSS Colour in an hexadecimal one.
542
+ *
543
+ * List of colours sourced here:
544
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
545
+ * @param {NamedCSSColour} name - A named CSS colour
546
+ * @returns {string} - An hexadecimal string
547
+ */
371
548
  function convertNamedCSSColourtoHex(name) {
372
- return namedCSSColours.get(name);
549
+ return namedCSSColours.get(name);
373
550
  }
551
+ //#endregion
552
+ //#region src/convertNamedCSSColourtoRGB.ts
553
+ /**
554
+ * Converts a named CSS colour to an RGB colour object.
555
+ *
556
+ * @param {NamedCSSColour} colour - The named CSS colour to convert.
557
+ * @returns {RGBColour | undefined} An RGB colour object representing the named CSS colour, or undefined if the named CSS colour is not recognized.
558
+ */
374
559
  function convertNamedCSSColourtoRGB(colour) {
375
- const hex = convertNamedCSSColourtoHex(colour);
376
- if (!hex) {
377
- return void 0;
378
- }
379
- return convertHextoRGB(hex);
560
+ const hex = convertNamedCSSColourtoHex(colour);
561
+ if (!hex) return;
562
+ return convertHextoRGB(hex) || void 0;
380
563
  }
564
+ //#endregion
565
+ //#region src/convertRGBtoCSSRGB.ts
566
+ /**
567
+ * Converts an RGB colour object to a CSS RGB colour string.
568
+ *
569
+ * @param {RGBColour} colour - The RGB colour object to convert.
570
+ * @returns {string} The CSS RGB colour string in the format `rgb(R G B)` or `rgb(R G B / A)` if the alpha channel is present.
571
+ */
381
572
  function convertRGBtoCSSRGB({ R, G, B, A }) {
382
- return `rgb(${R} ${G} ${B}${A ? ` / ${A}` : ""})`;
573
+ return `rgb(${R} ${G} ${B}${A ? ` / ${A}` : ""})`;
383
574
  }
575
+ //#endregion
576
+ //#region src/convertRGBtoXYZ.ts
577
+ /**
578
+ * Converts sRGB colour space to XYZ.
579
+ * Math comes from https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
580
+ * @param {RGBColour} colour sRGB colour
581
+ * @return {XYZColour} XYZ colour
582
+ */
384
583
  function convertRGBtoXYZ(colour) {
385
- const { R, G, B } = colour;
386
- const _R = linearRGB(R) * 100;
387
- const _G = linearRGB(G) * 100;
388
- const _B = linearRGB(B) * 100;
389
- const X = _R * 0.4124 + _G * 0.3576 + _B * 0.1805;
390
- const Y = _R * 0.2126 + _G * 0.7152 + _B * 0.0722;
391
- const Z = _R * 0.0193 + _G * 0.1192 + _B * 0.9505;
392
- return { X, Y, Z };
584
+ const { R, G, B } = colour;
585
+ const _R = linearRGB(R) * 100;
586
+ const _G = linearRGB(G) * 100;
587
+ const _B = linearRGB(B) * 100;
588
+ return {
589
+ X: _R * .4124 + _G * .3576 + _B * .1805,
590
+ Y: _R * .2126 + _G * .7152 + _B * .0722,
591
+ Z: _R * .0193 + _G * .1192 + _B * .9505
592
+ };
393
593
  }
594
+ //#endregion
595
+ //#region src/convertXYZtoLab.ts
596
+ /**
597
+ * Converts XYZ colour space to Lab
598
+ * Math comes from https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[11]
599
+ * @param colour XYZ colour
600
+ * @return {LabColour} Lab colour
601
+ */
394
602
  function convertXYZtoLab(colour) {
395
- const { X, Y, Z } = colour;
396
- const _X = X / 95.047;
397
- const _Y = Y / 100;
398
- const _Z = Z / 108.883;
399
- const fX = constrainLab(_X);
400
- const fY = constrainLab(_Y);
401
- const fZ = constrainLab(_Z);
402
- const L = 116 * fY - 16;
403
- const a = 500 * (fX - fY);
404
- const b = 200 * (fY - fZ);
405
- return { L, a, b };
603
+ const { X, Y, Z } = colour;
604
+ const _X = X / 95.047;
605
+ const _Y = Y / 100;
606
+ const _Z = Z / 108.883;
607
+ const fX = constrainLab(_X);
608
+ const fY = constrainLab(_Y);
609
+ const fZ = constrainLab(_Z);
610
+ return {
611
+ L: 116 * fY - 16,
612
+ a: 500 * (fX - fY),
613
+ b: 200 * (fY - fZ)
614
+ };
406
615
  }
616
+ //#endregion
617
+ //#region src/convertRGBtoLab.ts
618
+ /**
619
+ * Indirectly converts RGB to Lab.
620
+ * First converts RGB to XYZ, then converts XYZ to Lab.
621
+ * @param {RGBColour} colour sRGB colour
622
+ * @return {LabColour} Lab colour
623
+ */
407
624
  function convertRGBtoLab(colour) {
408
- const XYZColour = convertRGBtoXYZ(colour);
409
- return convertXYZtoLab(XYZColour);
625
+ return convertXYZtoLab(convertRGBtoXYZ(colour));
410
626
  }
627
+ //#endregion
628
+ //#region src/convertRGBtoNamedCSSColour.ts
629
+ /**
630
+ * Converts an RGB colour object to a named CSS colour.
631
+ *
632
+ * @param {RGBColour} colour - The RGB colour object to convert.
633
+ * @returns {NamedCSSColour | undefined} The named CSS colour that corresponds to the given RGB colour, or undefined if no named CSS colour matches the given colour.
634
+ */
411
635
  function convertRGBtoNamedCSSColour(colour) {
412
- const hex = convertRGBtoHex(colour);
413
- return convertHextoNamedCSSColour(hex);
636
+ return convertHextoNamedCSSColour(convertRGBtoHex(colour));
414
637
  }
415
- const RGBdistance = (colour1, colour2) => {
416
- const c1 = convertRGBtoLab(colour1);
417
- const c2 = convertRGBtoLab(colour2);
418
- return ciede2000(c1, c2);
638
+ //#endregion
639
+ //#region src/RGBdistance.ts
640
+ /**
641
+ * Calculate the distance between two RGB colours using the CIEDE2000 colour-difference formula.
642
+ *
643
+ * The CIEDE2000 colour-difference formula is a standard method for calculating the perceptual difference
644
+ * between two colours in the CIELAB colour space. It takes into account human visual perception and the
645
+ * non-linearities in how we perceive colour differences.
646
+ *
647
+ * @param colour1 - The first colour to compare.
648
+ * @param colour2 - The second colour to compare.
649
+ * @returns The distance between the two colours. Smaller numbers (minimum 0) mean the colours are more similar,
650
+ * larger numbers (typically not exceeding 100 for visible colours) indicate more dissimilar colours.
651
+ */
652
+ var RGBdistance = (colour1, colour2) => {
653
+ return ciede2000(convertRGBtoLab(colour1), convertRGBtoLab(colour2));
419
654
  };
655
+ //#endregion
656
+ //#region src/findNearestRGBColour.ts
657
+ /**
658
+ * Finds the nearest RGB colour in a palette to the given RGB colour.
659
+ *
660
+ * @param {RGBColour} colour - The RGB colour to find the nearest match for.
661
+ * @param {RGBColour[]} palette - An array of RGB colours to search for the nearest match in.
662
+ * @returns {RGBColour} The RGB colour in the palette that is closest to the given colour.
663
+ * If the palette has fewer than 2 colours, or if it is undefined or null, the original colour is returned.
664
+ */
420
665
  function findNearestRGBColour(colour, palette) {
421
- if (!palette || palette.length < 2) {
422
- return colour;
423
- }
424
- let nearest = colour;
425
- let minDistance = Number.POSITIVE_INFINITY;
426
- for (const paletteColour of palette) {
427
- const distance = RGBdistance(colour, paletteColour);
428
- if (distance < minDistance) {
429
- minDistance = distance;
430
- nearest = paletteColour;
431
- }
432
- }
433
- return nearest;
666
+ if (!palette || palette.length < 2) return colour;
667
+ let nearest = colour;
668
+ let minDistance = Number.POSITIVE_INFINITY;
669
+ for (const paletteColour of palette) {
670
+ const distance = RGBdistance(colour, paletteColour);
671
+ if (distance < minDistance) {
672
+ minDistance = distance;
673
+ nearest = paletteColour;
674
+ }
675
+ }
676
+ return nearest;
434
677
  }
678
+ //#endregion
679
+ //#region src/findNearestColour.ts
680
+ /**
681
+ * Finds the nearest colour in a palette to a given colour.
682
+ *
683
+ * @param {string | NamedCSSColour} colour - The colour to match. It can be a hexadecimal colour, a CSS RGB colour, or a named CSS colour.
684
+ * @param {string[] | NamedCSSColour[]} palette - The palette of colours to search.
685
+ * @returns {string} The nearest colour in the palette to the given colour.
686
+ */
435
687
  function findNearestColour(colour, palette) {
436
- if (!palette || palette.length < 2) {
437
- return colour;
438
- }
439
- let baseColour;
440
- let colourType;
441
- const paletteRGB = [];
442
- if (isHexColour(colour)) {
443
- baseColour = convertHextoRGB(colour);
444
- colourType = "hex";
445
- } else if (isCSSRGBColour(colour)) {
446
- baseColour = convertCSSRGBtoRGB(colour);
447
- colourType = "cssRGB";
448
- } else if (isNamedCSSColour(colour)) {
449
- baseColour = convertNamedCSSColourtoRGB(colour);
450
- colourType = "namedCSS";
451
- }
452
- if (!baseColour) {
453
- return void 0;
454
- }
455
- for (const paletteColour of palette) {
456
- if (isHexColour(paletteColour)) {
457
- paletteRGB.push(convertHextoRGB(paletteColour));
458
- } else if (isCSSRGBColour(paletteColour)) {
459
- paletteRGB.push(convertCSSRGBtoRGB(paletteColour));
460
- } else if (isNamedCSSColour(paletteColour)) {
461
- const rgb = convertNamedCSSColourtoRGB(paletteColour);
462
- if (rgb) paletteRGB.push(rgb);
463
- }
464
- }
465
- if (paletteRGB.length < 2) {
466
- return colour;
467
- }
468
- const nearest = findNearestRGBColour(baseColour, paletteRGB);
469
- if (colourType === "hex") {
470
- return convertRGBtoHex(nearest);
471
- }
472
- if (colourType === "cssRGB") {
473
- return convertRGBtoCSSRGB(nearest);
474
- }
475
- return convertRGBtoNamedCSSColour(nearest);
688
+ if (!palette || palette.length < 2) return colour;
689
+ let baseColour;
690
+ let colourType;
691
+ const paletteRGB = [];
692
+ if (isHexColour(colour)) {
693
+ baseColour = convertHextoRGB(colour);
694
+ colourType = "hex";
695
+ } else if (isCSSRGBColour(colour)) {
696
+ baseColour = convertCSSRGBtoRGB(colour);
697
+ colourType = "cssRGB";
698
+ } else if (isNamedCSSColour(colour)) {
699
+ baseColour = convertNamedCSSColourtoRGB(colour);
700
+ colourType = "namedCSS";
701
+ }
702
+ if (!baseColour) return;
703
+ for (const paletteColour of palette) if (isHexColour(paletteColour)) {
704
+ const rgb = convertHextoRGB(paletteColour);
705
+ if (rgb) paletteRGB.push(rgb);
706
+ } else if (isCSSRGBColour(paletteColour)) paletteRGB.push(convertCSSRGBtoRGB(paletteColour));
707
+ else if (isNamedCSSColour(paletteColour)) {
708
+ const rgb = convertNamedCSSColourtoRGB(paletteColour);
709
+ if (rgb) paletteRGB.push(rgb);
710
+ }
711
+ if (paletteRGB.length < 2) return colour;
712
+ const nearest = findNearestRGBColour(baseColour, paletteRGB);
713
+ if (colourType === "hex") return convertRGBtoHex(nearest);
714
+ if (colourType === "cssRGB") return convertRGBtoCSSRGB(nearest);
715
+ return convertRGBtoNamedCSSColour(nearest);
476
716
  }
717
+ //#endregion
718
+ //#region src/findNearestCSSRGBColour.ts
719
+ /**
720
+ * Finds the nearest CSS RGB colour in a palette to a given CSS RGB colour.
721
+ *
722
+ * @param {string} colour - The CSS RGB colour to find the nearest match for. Alpha channel is ignored.
723
+ * @param {string[]} palette - An array of CSS RGB colours to search for the nearest match.
724
+ * @returns {string} The CSS RGB colour in the palette that is closest to the given colour.
725
+ * If the palette has fewer than 2 colours, or if it is undefined or null, the original colour is returned.
726
+ * The CSS RGB colour string is in the format `rgb(R G B)`.
727
+ */
477
728
  function findNearestCSSRGBColour(colour, palette) {
478
- if (!palette || palette.length < 2) {
479
- return colour;
480
- }
481
- const baseRGBColour = convertCSSRGBtoRGB(colour);
482
- const paletteRGBColours = palette.map(
483
- (hexColour) => convertCSSRGBtoRGB(hexColour)
484
- );
485
- const nearestRGBColour = findNearestRGBColour(
486
- baseRGBColour,
487
- paletteRGBColours
488
- );
489
- return convertRGBtoCSSRGB(nearestRGBColour);
729
+ if (!palette || palette.length < 2) return colour;
730
+ return convertRGBtoCSSRGB(findNearestRGBColour(convertCSSRGBtoRGB(colour), palette.map((hexColour) => convertCSSRGBtoRGB(hexColour))));
490
731
  }
732
+ //#endregion
733
+ //#region src/findNearestHexColour.ts
734
+ /**
735
+ * Finds the nearest hexadecimal colour in a palette to the given hex colour.
736
+ *
737
+ * @param {string} colour - The hexadecimal colour to find the nearest match for.
738
+ * @param {string[]} palette - An array of hexadecimal colours to search for the nearest match in.
739
+ * @returns {string} The hexadecimal colour in the palette that is closest to the given colour.
740
+ * If the palette has fewer than 2 colours, or if it is undefined or null, the original colour is returned.
741
+ */
491
742
  function findNearestHexColour(colour, palette) {
492
- if (!palette || palette.length < 2) {
493
- return colour;
494
- }
495
- const baseRGBColour = convertHextoRGB(colour);
496
- const paletteRGBColours = palette.map(
497
- (hexColour) => convertHextoRGB(hexColour)
498
- );
499
- const nearestRGBColour = findNearestRGBColour(
500
- baseRGBColour,
501
- paletteRGBColours
502
- );
503
- return convertRGBtoHex(nearestRGBColour);
743
+ if (!palette || palette.length < 2) return colour;
744
+ const baseRGBColour = convertHextoRGB(colour);
745
+ if (!baseRGBColour) return colour;
746
+ return convertRGBtoHex(findNearestRGBColour(baseRGBColour, palette.map((hexColour) => convertHextoRGB(hexColour)).filter((rgb) => rgb !== null)));
504
747
  }
748
+ //#endregion
749
+ //#region src/findNearestNamedCSSColour.ts
505
750
  function findNearestNamedCSSColour(colour, palette) {
506
- if (!palette || palette.length < 2) {
507
- return colour;
508
- }
509
- const baseRGBColour = convertNamedCSSColourtoRGB(colour);
510
- const paletteRGBColours = palette.map(
511
- (hexColour) => convertNamedCSSColourtoRGB(hexColour)
512
- );
513
- const filteredPaletteRGBColours = paletteRGBColours.filter(
514
- (paletteRGBColour) => paletteRGBColour !== void 0
515
- );
516
- if (!baseRGBColour || filteredPaletteRGBColours.length < 2) {
517
- return colour;
518
- }
519
- const nearestRGBColour = findNearestRGBColour(
520
- baseRGBColour,
521
- filteredPaletteRGBColours
522
- );
523
- return convertRGBtoNamedCSSColour(nearestRGBColour);
751
+ if (!palette || palette.length < 2) return colour;
752
+ const baseRGBColour = convertNamedCSSColourtoRGB(colour);
753
+ const filteredPaletteRGBColours = palette.map((hexColour) => convertNamedCSSColourtoRGB(hexColour)).filter((paletteRGBColour) => paletteRGBColour !== void 0);
754
+ if (!baseRGBColour || filteredPaletteRGBColours.length < 2) return colour;
755
+ return convertRGBtoNamedCSSColour(findNearestRGBColour(baseRGBColour, filteredPaletteRGBColours));
524
756
  }
757
+ //#endregion
758
+ //#region src/getSRGBLuminanceFromRGB.ts
759
+ /**
760
+ * Returns the relative luminance of a colour in the sRGB space.
761
+ *
762
+ * The calculations are compatible with WCAG 3.0 as it aligns with the sRGB spec
763
+ * and difference to WCAG 2.1 is minimal in a 8 bit channel.
764
+ *
765
+ * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
766
+ *
767
+ * https://www.w3.org/WAI/GL/wiki/Relative_luminance
768
+ * @param colour an hexadecimal colour
769
+ */
525
770
  function getSRGBLuminanceFromRGB({ R, G, B }, standard) {
526
- const isWCAG21 = standard === "WCAG2.1";
527
- const r = linearRGB(R, isWCAG21);
528
- const g = linearRGB(G, isWCAG21);
529
- const b = linearRGB(B, isWCAG21);
530
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
771
+ const isWCAG21 = standard === "WCAG2.1";
772
+ const r = linearRGB(R, isWCAG21);
773
+ const g = linearRGB(G, isWCAG21);
774
+ const b = linearRGB(B, isWCAG21);
775
+ return .2126 * r + .7152 * g + .0722 * b;
531
776
  }
777
+ //#endregion
778
+ //#region src/getSRGBLuminanceFromHex.ts
779
+ /**
780
+ * Returns the relative luminance of a colour in the sRGB space.
781
+ *
782
+ * The calculations are compatible with WCAG 3.0 as it aligns with the sRGB spec
783
+ * and difference to WCAG 2.1 is minimal in a 8 bit channel.
784
+ *
785
+ * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
786
+ *
787
+ * https://www.w3.org/WAI/GL/wiki/Relative_luminance
788
+ * @param colour an hexadecimal colour
789
+ */
532
790
  function getSRGBLuminanceFromHex(colour, standard) {
533
- const rgbColor = convertHextoRGB(colour);
534
- return getSRGBLuminanceFromRGB(rgbColor, standard);
791
+ const rgbColor = convertHextoRGB(colour);
792
+ if (!rgbColor) return null;
793
+ return getSRGBLuminanceFromRGB(rgbColor, standard);
535
794
  }
795
+ //#endregion
796
+ //#region src/util/calculateContrastRatio.ts
797
+ /**
798
+ * Calculate the contrast ratio between two colours
799
+ * @param luminance1 The luminance of the first colour
800
+ * @param luminance2 The luminance of the second colour
801
+ * @returns The contrast ratio between the two colours truncated to 3 decimal places
802
+ */
536
803
  function calculateContrastRatio(luminance1, luminance2) {
537
- const lighter = Math.max(luminance1, luminance2);
538
- const darker = Math.min(luminance1, luminance2);
539
- const ratio = (lighter + 0.05) / (darker + 0.05);
540
- return Math.floor(ratio * 1e3) / 1e3;
804
+ const lighter = Math.max(luminance1, luminance2);
805
+ const darker = Math.min(luminance1, luminance2);
806
+ const ratio = (lighter + .05) / (darker + .05);
807
+ return Math.floor(ratio * 1e3) / 1e3;
541
808
  }
809
+ //#endregion
810
+ //#region src/getContrastRatioFromHex.ts
811
+ /**
812
+ * Calculates the contrast ratio between two colours in hexadecimal format.
813
+ * @param colour1 The first colour in hexadecimal format
814
+ * @param colour2 The second colour in hexadecimal format
815
+ * @param standard The standard to evaluate the contrast ratio against, defaults to WCAG2.1
816
+ * @returns The contrast ratio between the two colours truncated to 3 decimal places
817
+ */
542
818
  function getContrastRatioFromHex(colour1, colour2, standard) {
543
- const luminance1 = getSRGBLuminanceFromHex(colour1, standard);
544
- const luminance2 = getSRGBLuminanceFromHex(colour2, standard);
545
- return calculateContrastRatio(luminance1, luminance2);
819
+ const luminance1 = getSRGBLuminanceFromHex(colour1, standard);
820
+ const luminance2 = getSRGBLuminanceFromHex(colour2, standard);
821
+ if (luminance1 === null || luminance2 === null) return null;
822
+ return calculateContrastRatio(luminance1, luminance2);
546
823
  }
824
+ //#endregion
825
+ //#region src/getContrastRatio.ts
826
+ /**
827
+ * Calculates the contrast ratio between two colours.
828
+ * Accepts CSS RGB colours (including percentage values), named CSS colours, or hexadecimal colours.
829
+ * @param colour1 The first colour (e.g., `#ff0000`, `rgb(255,0,0)`, `rgb(100%, 0%, 0%)`, or `red`)
830
+ * @param colour2 The second colour (e.g., `#00ff00`, `rgb(0,255,0)`, `rgb(0%, 100%, 0%)`, or `lime`)
831
+ * @param standard The standard to evaluate the contrast ratio against, defaults to WCAG2.1
832
+ * @returns The contrast ratio between the two colours truncated to 3 decimal places
833
+ */
547
834
  function getContrastRatio(colour1, colour2, standard) {
548
- let hexColour1;
549
- let hexColour2;
550
- if (colour1.startsWith("#")) {
551
- hexColour1 = colour1;
552
- } else if (colour1.startsWith("rgb")) {
553
- hexColour1 = convertCSSRGBtoHex(colour1);
554
- } else {
555
- const _hexColour1 = convertNamedCSSColourtoHex(colour1);
556
- if (_hexColour1 === void 0) {
557
- throw new Error(
558
- `getContrastRatio expects valid CSS named colours. ${colour1} is not a valid CSS named colour. See ${NAMED_CSS_COLOUR_URL}`
559
- );
560
- }
561
- hexColour1 = _hexColour1;
562
- }
563
- if (colour2.startsWith("#")) {
564
- hexColour2 = colour2;
565
- } else if (colour2.startsWith("rgb")) {
566
- hexColour2 = convertCSSRGBtoHex(colour2);
567
- } else {
568
- const _hexColour2 = convertNamedCSSColourtoHex(colour2);
569
- if (_hexColour2 === void 0) {
570
- throw new Error(
571
- `getContrastRatio expects valid CSS named colours. ${colour2} is not a valid CSS named colour. See ${NAMED_CSS_COLOUR_URL}`
572
- );
573
- }
574
- hexColour2 = _hexColour2;
575
- }
576
- return getContrastRatioFromHex(hexColour1, hexColour2, standard);
835
+ let hexColour1;
836
+ let hexColour2;
837
+ if (colour1.startsWith("#")) hexColour1 = colour1;
838
+ else if (colour1.startsWith("rgb")) hexColour1 = convertCSSRGBtoHex(colour1);
839
+ else {
840
+ const _hexColour1 = convertNamedCSSColourtoHex(colour1);
841
+ if (_hexColour1 === void 0) throw new Error(`getContrastRatio expects valid CSS named colours. ${colour1} is not a valid CSS named colour. See ${NAMED_CSS_COLOUR_URL}`);
842
+ hexColour1 = _hexColour1;
843
+ }
844
+ if (colour2.startsWith("#")) hexColour2 = colour2;
845
+ else if (colour2.startsWith("rgb")) hexColour2 = convertCSSRGBtoHex(colour2);
846
+ else {
847
+ const _hexColour2 = convertNamedCSSColourtoHex(colour2);
848
+ if (_hexColour2 === void 0) throw new Error(`getContrastRatio expects valid CSS named colours. ${colour2} is not a valid CSS named colour. See ${NAMED_CSS_COLOUR_URL}`);
849
+ hexColour2 = _hexColour2;
850
+ }
851
+ const result = getContrastRatioFromHex(hexColour1, hexColour2, standard);
852
+ if (result === null) throw new Error(`getContrastRatio expects valid hexadecimal colours but received ${hexColour1} or ${hexColour2}.`);
853
+ return result;
577
854
  }
855
+ //#endregion
856
+ //#region src/getContrastRatioFromCSSRGB.ts
857
+ /**
858
+ * Calculates the contrast ratio between two colours in CSSRGB format.
859
+ * @param colour1 The first colour in CSSRGB format
860
+ * @param colour2 The second colour in CSSRGB format
861
+ * @param standard The standard to evaluate the contrast ratio against, defaults to WCAG2.1
862
+ * @returns The contrast ratio between the two colours truncated to 3 decimal places
863
+ */
578
864
  function getContrastRatioFromCSSRGB(colour1, colour2, standard) {
579
- const rgbColour1 = convertCSSRGBtoRGB(colour1);
580
- const rgbColour2 = convertCSSRGBtoRGB(colour2);
581
- const luminance1 = getSRGBLuminanceFromRGB(rgbColour1, standard);
582
- const luminance2 = getSRGBLuminanceFromRGB(rgbColour2, standard);
583
- return calculateContrastRatio(luminance1, luminance2);
865
+ const rgbColour1 = convertCSSRGBtoRGB(colour1);
866
+ const rgbColour2 = convertCSSRGBtoRGB(colour2);
867
+ return calculateContrastRatio(getSRGBLuminanceFromRGB(rgbColour1, standard), getSRGBLuminanceFromRGB(rgbColour2, standard));
584
868
  }
869
+ //#endregion
870
+ //#region src/getContrastRatioFromNamedCSSColour.ts
871
+ /**
872
+ * Calculates the contrast ratio between two named CSS colours in.
873
+ *
874
+ * See list here https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
875
+ * @param colour1 The first named CSS colour
876
+ * @param colour2 The second named CSS colour
877
+ * @param standard The standard to evaluate the contrast ratio against, defaults to WCAG2.1
878
+ * @returns The contrast ratio between the two colours truncated to 3 decimal places
879
+ */
585
880
  function getContrastRatioFromNamedCSSColour(colour1, colour2, standard) {
586
- const hexColour1 = convertNamedCSSColourtoHex(colour1);
587
- const hexColour2 = convertNamedCSSColourtoHex(colour2);
588
- if (hexColour1 === void 0 || hexColour2 === void 0) {
589
- throw new Error(
590
- `getContrastRatioFromNamedCSSColour expects valid CSS named colours. ${colour1} or ${colour2} are not valid CSS named colours. See ${NAMED_CSS_COLOUR_URL}`
591
- );
592
- }
593
- return getContrastRatioFromHex(hexColour1, hexColour2, standard);
881
+ const hexColour1 = convertNamedCSSColourtoHex(colour1);
882
+ const hexColour2 = convertNamedCSSColourtoHex(colour2);
883
+ if (hexColour1 === void 0 || hexColour2 === void 0) throw new Error(`getContrastRatioFromNamedCSSColour expects valid CSS named colours. ${colour1} or ${colour2} are not valid CSS named colours. See ${NAMED_CSS_COLOUR_URL}`);
884
+ return getContrastRatioFromHex(hexColour1, hexColour2, standard);
594
885
  }
886
+ //#endregion
887
+ //#region src/isHexDarkColour.ts
888
+ /**
889
+ * Evaluates if a colour is dark by measuring the contrast ratio against black and white
890
+ * @param {string} colour - A colour in the hexadecimal format
891
+ * @param {"WCAG2.1" | "WCAG3.0"} standard - Evaluate agains "WCAG2.1" or "WCAG3.0"
892
+ * @returns {boolean} Returns either `true` or `false`
893
+ */
595
894
  function isHexDarkColour(colour, standard) {
596
- const colourLuminance = getSRGBLuminanceFromHex(colour, standard) + 0.05;
597
- const whiteContrast = 1.05 / colourLuminance;
598
- const blackContrast = colourLuminance / 0.05;
599
- return whiteContrast > blackContrast;
895
+ const lum = getSRGBLuminanceFromHex(colour, standard);
896
+ if (lum === null) return false;
897
+ const colourLuminance = lum + .05;
898
+ return 1.05 / colourLuminance > colourLuminance / .05;
600
899
  }
900
+ //#endregion
901
+ //#region src/isCSSNameDarkColour.ts
902
+ /**
903
+ * Evaluates if a named CSS colour is dark by measuring the contrast ratio against black and white
904
+ * @param {string} name - A named CSS colour, ie: `hotpink`
905
+ * @param {"WCAG2.1" | "WCAG3.0"} standard - Evaluate agains "WCAG2.1" or "WCAG3.0"
906
+ * @returns { boolean } Returns `true`, `false` or `undefined` if name is not a valid CSS named colour
907
+ */
601
908
  function isCSSNamedDarkColour(name, standard) {
602
- const hex = convertNamedCSSColourtoHex(name);
603
- if (hex) {
604
- return isHexDarkColour(hex, standard);
605
- }
606
- throw new Error(
607
- `${name} is not a valid colour format. isCSSNamedDarkColour only accepts CSS named colours. See ${NAMED_CSS_COLOUR_URL}`
608
- );
909
+ const hex = convertNamedCSSColourtoHex(name);
910
+ if (hex) return isHexDarkColour(hex, standard);
911
+ throw new Error(`${name} is not a valid colour format. isCSSNamedDarkColour only accepts CSS named colours. See ${NAMED_CSS_COLOUR_URL}`);
609
912
  }
913
+ //#endregion
914
+ //#region src/isCSSRGBDarkColour.ts
610
915
  function isCSSRGBDarkColour(colour, standard) {
611
- const rgb = convertCSSRGBtoRGB(colour);
612
- const colourLuminance = getSRGBLuminanceFromRGB(rgb, standard);
613
- const whiteContrast = 1.05 / colourLuminance;
614
- const blackContrast = colourLuminance / 0.05;
615
- return whiteContrast > blackContrast;
916
+ const colourLuminance = getSRGBLuminanceFromRGB(convertCSSRGBtoRGB(colour), standard);
917
+ return 1.05 / colourLuminance > colourLuminance / .05;
616
918
  }
919
+ //#endregion
920
+ //#region src/isDarkColour.ts
921
+ /**
922
+ * Evaluates if a colour is dark by measuring the contrast ratio against black and white.
923
+ * It accepts CSS RGB colours (including percentage values), named CSS or hexadecimal.
924
+ *
925
+ * If you know in advance the type of colour you want to evaluate consider using `isCSSNamedDarkColour`, `isCSSRGBDarkColour` or `isHexDarkColour` as they are smaller functions
926
+ * @param {string | NamedCSSColour} colour - A colour value, ie: `hotpink`, `#333222`, `rgb(12,34,45)`, or `rgb(50%, 25%, 100%)`
927
+ * @param {"WCAG2.1" | "WCAG3.0"} standard - Evaluate agains "WCAG2.1" or "WCAG3.0"
928
+ * @returns {boolean} Returns `true`, `false` or `undefined` if name is not a valid CSS named colour
929
+ */
617
930
  function isDarkColour(colour, standard) {
618
- try {
619
- if (colour.startsWith("#")) {
620
- return isHexDarkColour(colour, standard);
621
- }
622
- if (colour.startsWith("rgb")) {
623
- return isCSSRGBDarkColour(colour, standard);
624
- }
625
- return isCSSNamedDarkColour(colour, standard);
626
- } catch (_error) {
627
- throw new Error(
628
- `${colour} is not a valid colour format. isDarkColour accepts CSS RGB formats, ie rgb(0,0,0), rgba(255, 255, 255, 0.4), rgb(50%, 25%, 100%), and rgba(50%, 25%, 100%, 80%), hexadecimal and CSS named colours.`
629
- );
630
- }
631
- }
632
- const relativeContrast = (firstColour, secondColour) => {
633
- if (firstColour > secondColour) {
634
- return firstColour / secondColour;
635
- }
636
- return secondColour / firstColour;
931
+ try {
932
+ if (colour.startsWith("#")) return isHexDarkColour(colour, standard);
933
+ if (colour.startsWith("rgb")) return isCSSRGBDarkColour(colour, standard);
934
+ return isCSSNamedDarkColour(colour, standard);
935
+ } catch (_error) {
936
+ throw new Error(`${colour} is not a valid colour format. isDarkColour accepts CSS RGB formats, ie rgb(0,0,0), rgba(255, 255, 255, 0.4), rgb(50%, 25%, 100%), and rgba(50%, 25%, 100%, 80%), hexadecimal and CSS named colours.`);
937
+ }
938
+ }
939
+ //#endregion
940
+ //#region src/pickHexColourContrast.ts
941
+ var relativeContrast = (firstColour, secondColour) => {
942
+ if (firstColour > secondColour) return firstColour / secondColour;
943
+ return secondColour / firstColour;
637
944
  };
638
- const pickHexColourContrast = ({ backgroundColour, optionOneColour, optionTwoColour }, standard) => {
639
- const backgroundColourLuminance = getSRGBLuminanceFromHex(backgroundColour, standard) + 0.05;
640
- const optionOneColourLuminance = getSRGBLuminanceFromHex(optionOneColour, standard) + 0.05;
641
- const optionTwoColourLuminance = getSRGBLuminanceFromHex(optionTwoColour, standard) + 0.05;
642
- const optionOneContrast = relativeContrast(
643
- optionOneColourLuminance,
644
- backgroundColourLuminance
645
- );
646
- const optionTwoContrast = relativeContrast(
647
- optionTwoColourLuminance,
648
- backgroundColourLuminance
649
- );
650
- if (optionOneContrast > optionTwoContrast) {
651
- return optionOneColour;
652
- }
653
- return optionTwoColour;
945
+ var pickHexColourContrast = ({ backgroundColour, optionOneColour, optionTwoColour }, standard) => {
946
+ const bgLum = getSRGBLuminanceFromHex(backgroundColour, standard);
947
+ const opt1Lum = getSRGBLuminanceFromHex(optionOneColour, standard);
948
+ const opt2Lum = getSRGBLuminanceFromHex(optionTwoColour, standard);
949
+ if (bgLum === null || opt1Lum === null || opt2Lum === null) return null;
950
+ const backgroundColourLuminance = bgLum + .05;
951
+ const optionOneColourLuminance = opt1Lum + .05;
952
+ const optionTwoColourLuminance = opt2Lum + .05;
953
+ if (relativeContrast(optionOneColourLuminance, backgroundColourLuminance) > relativeContrast(optionTwoColourLuminance, backgroundColourLuminance)) return optionOneColour;
954
+ return optionTwoColour;
654
955
  };
956
+ //#endregion
957
+ //#region src/convertRGBtoHSV.ts
958
+ /**
959
+ * Converts an RGB colour to HSV.
960
+ * Conversion formula is based on the RGB to HSV conversion algorithm:
961
+ * https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
962
+ *
963
+ * https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
964
+ * - Hue (h) is calculated based on the maximum RGB value.
965
+ * - Saturation (s) is the chroma divided by the maximum RGB value.
966
+ * - Value (v) is the maximum RGB value.
967
+ * @param r Red channel (0-255)
968
+ * @param g Green channel (0-255)
969
+ * @param b Blue channel (0-255)
970
+ * @returns HSVColour object with h (hue), s (saturation), v (value)
971
+ */
655
972
  function convertRGBtoHSV(r, g, b) {
656
- const red = r / 255;
657
- const green = g / 255;
658
- const blue = b / 255;
659
- const max = Math.max(red, green, blue);
660
- const min = Math.min(red, green, blue);
661
- let hue = 0;
662
- let saturation = 0;
663
- const value = max;
664
- const delta = max - min;
665
- if (max === 0) {
666
- saturation = 0;
667
- } else {
668
- saturation = delta / max;
669
- }
670
- if (delta !== 0) {
671
- if (max === red) {
672
- hue = (green - blue) / delta % 6;
673
- } else if (max === green) {
674
- hue = (blue - red) / delta + 2;
675
- } else if (max === blue) {
676
- hue = (red - green) / delta + 4;
677
- }
678
- hue = hue * 60;
679
- if (hue < 0) {
680
- hue = hue + 360;
681
- }
682
- }
683
- return { h: hue, s: saturation, v: value };
973
+ const red = r / 255;
974
+ const green = g / 255;
975
+ const blue = b / 255;
976
+ const max = Math.max(red, green, blue);
977
+ const min = Math.min(red, green, blue);
978
+ let hue = 0;
979
+ let saturation = 0;
980
+ const value = max;
981
+ const delta = max - min;
982
+ if (max === 0) saturation = 0;
983
+ else saturation = delta / max;
984
+ if (delta !== 0) {
985
+ if (max === red) hue = (green - blue) / delta % 6;
986
+ else if (max === green) hue = (blue - red) / delta + 2;
987
+ else if (max === blue) hue = (red - green) / delta + 4;
988
+ hue = hue * 60;
989
+ if (hue < 0) hue = hue + 360;
990
+ }
991
+ return {
992
+ h: hue,
993
+ s: saturation,
994
+ v: value
995
+ };
684
996
  }
997
+ //#endregion
998
+ //#region src/sortHexColours.ts
999
+ /**
1000
+ * Helper to parse hex and get HSV + alpha
1001
+ * @param hex Hex color string
1002
+ * @returns Object with hue, saturation, value, and alpha
1003
+ */
685
1004
  function getColorInfo(hex) {
686
- const { R, G, B, A } = convertHextoRGB(hex);
687
- const a = typeof A === "number" ? A : 1;
688
- const { h, s, v } = convertRGBtoHSV(R, G, B);
689
- return { h, s, v, a };
1005
+ const rgb = convertHextoRGB(hex);
1006
+ if (!rgb) return null;
1007
+ const { R, G, B, A } = rgb;
1008
+ const a = typeof A === "number" ? A : 1;
1009
+ const { h, s, v } = convertRGBtoHSV(R, G, B);
1010
+ return {
1011
+ h,
1012
+ s,
1013
+ v,
1014
+ a
1015
+ };
690
1016
  }
1017
+ /**
1018
+ * Helper to get custom order for greyscale values
1019
+ * @param v HSV value (brightness) from 0 to 1
1020
+ * @returns Order priority: 0 for grey/other, 1 for black, 2 for white
1021
+ */
691
1022
  function getGreyscaleOrder(v) {
692
- if (v === 0) return 1;
693
- if (v === 1) return 2;
694
- return 0;
1023
+ if (v === 0) return 1;
1024
+ if (v === 1) return 2;
1025
+ return 0;
695
1026
  }
1027
+ /**
1028
+ * Sorts an array of hex colours by hue, then by saturation.
1029
+ * Colours with 0% saturation (greyscale) are shifted to the end.
1030
+ * Fully transparent colours (alpha = 0) are placed at the very end.
1031
+ * Accepts hex colours in #RRGGBB or #RRGGBBAA format.
1032
+ * @param hexColours Array of hex colour strings
1033
+ * @returns Sorted array of hex colour strings
1034
+ */
696
1035
  function sortHexColours(hexColours) {
697
- const cache = /* @__PURE__ */ new Map();
698
- for (const hex of hexColours) {
699
- if (!cache.has(hex)) {
700
- cache.set(hex, getColorInfo(hex));
701
- }
702
- }
703
- const normal = [];
704
- const greyscale = [];
705
- const transparent = [];
706
- for (const hex of hexColours) {
707
- const { s, a } = cache.get(hex);
708
- if (a === 0) {
709
- transparent.push(hex);
710
- } else if (s === 0) {
711
- greyscale.push(hex);
712
- } else {
713
- normal.push(hex);
714
- }
715
- }
716
- normal.sort((a, b) => {
717
- const ca = cache.get(a);
718
- const cb = cache.get(b);
719
- if (ca.h !== cb.h) return ca.h - cb.h;
720
- return cb.s - ca.s;
721
- });
722
- greyscale.sort((a, b) => {
723
- const ca = cache.get(a);
724
- const cb = cache.get(b);
725
- const orderA = getGreyscaleOrder(ca.v);
726
- const orderB = getGreyscaleOrder(cb.v);
727
- if (orderA !== orderB) {
728
- return orderA - orderB;
729
- }
730
- return ca.v - cb.v;
731
- });
732
- transparent.sort((a, b) => {
733
- const ca = cache.get(a);
734
- const cb = cache.get(b);
735
- return ca.v - cb.v;
736
- });
737
- return [...normal, ...greyscale, ...transparent];
738
- }
739
- export {
740
- RGBdistance,
741
- ciede2000,
742
- convertCSSRGBtoHex,
743
- convertCSSRGBtoRGB,
744
- convertHextoCSSRGB,
745
- convertHextoNamedCSSColour,
746
- convertHextoRGB,
747
- convertNamedCSSColourtoHex,
748
- convertNamedCSSColourtoRGB,
749
- convertRGBtoCSSRGB,
750
- convertRGBtoHex,
751
- convertRGBtoLab,
752
- convertRGBtoNamedCSSColour,
753
- convertRGBtoXYZ,
754
- convertXYZtoLab,
755
- findNearestCSSRGBColour,
756
- findNearestColour,
757
- findNearestHexColour,
758
- findNearestNamedCSSColour,
759
- findNearestRGBColour,
760
- getContrastRatio,
761
- getContrastRatioFromCSSRGB,
762
- getContrastRatioFromHex,
763
- getContrastRatioFromNamedCSSColour,
764
- getSRGBLuminanceFromHex,
765
- getSRGBLuminanceFromRGB,
766
- isCSSNamedDarkColour,
767
- isCSSRGBColour,
768
- isCSSRGBDarkColour,
769
- isDarkColour,
770
- isHexColour,
771
- isHexDarkColour,
772
- isNamedCSSColour,
773
- pickHexColourContrast,
774
- sortHexColours
775
- };
1036
+ const cache = /* @__PURE__ */ new Map();
1037
+ for (const hex of hexColours) if (!cache.has(hex)) {
1038
+ const info = getColorInfo(hex);
1039
+ if (info) cache.set(hex, info);
1040
+ }
1041
+ const normal = [];
1042
+ const greyscale = [];
1043
+ const transparent = [];
1044
+ for (const hex of hexColours) {
1045
+ const info = cache.get(hex);
1046
+ if (!info) continue;
1047
+ const { s, a } = info;
1048
+ if (a === 0) transparent.push(hex);
1049
+ else if (s === 0) greyscale.push(hex);
1050
+ else normal.push(hex);
1051
+ }
1052
+ normal.sort((a, b) => {
1053
+ const ca = cache.get(a);
1054
+ const cb = cache.get(b);
1055
+ if (ca.h !== cb.h) return ca.h - cb.h;
1056
+ return cb.s - ca.s;
1057
+ });
1058
+ greyscale.sort((a, b) => {
1059
+ const ca = cache.get(a);
1060
+ const cb = cache.get(b);
1061
+ const orderA = getGreyscaleOrder(ca.v);
1062
+ const orderB = getGreyscaleOrder(cb.v);
1063
+ if (orderA !== orderB) return orderA - orderB;
1064
+ return ca.v - cb.v;
1065
+ });
1066
+ transparent.sort((a, b) => {
1067
+ const ca = cache.get(a);
1068
+ const cb = cache.get(b);
1069
+ return ca.v - cb.v;
1070
+ });
1071
+ return [
1072
+ ...normal,
1073
+ ...greyscale,
1074
+ ...transparent
1075
+ ];
1076
+ }
1077
+ //#endregion
1078
+ export { RGBdistance, ciede2000, convertCSSRGBtoHex, convertCSSRGBtoRGB, convertHextoCSSRGB, convertHextoNamedCSSColour, convertHextoRGB, convertNamedCSSColourtoHex, convertNamedCSSColourtoRGB, convertRGBtoCSSRGB, convertRGBtoHex, convertRGBtoLab, convertRGBtoNamedCSSColour, convertRGBtoXYZ, convertXYZtoLab, findNearestCSSRGBColour, findNearestColour, findNearestHexColour, findNearestNamedCSSColour, findNearestRGBColour, getContrastRatio, getContrastRatioFromCSSRGB, getContrastRatioFromHex, getContrastRatioFromNamedCSSColour, getSRGBLuminanceFromHex, getSRGBLuminanceFromRGB, isCSSNamedDarkColour, isCSSRGBColour, isCSSRGBDarkColour, isDarkColour, isHexColour, isHexDarkColour, isNamedCSSColour, pickHexColourContrast, sortHexColours };