UNPKG

8.92 kBJavaScriptView Raw
1import { formatMuiErrorMessage as _formatMuiErrorMessage } from "@material-ui/utils";
2
3/* eslint-disable no-use-before-define */
4
5/**
6 * Returns a number whose value is limited to the given range.
7 *
8 * @param {number} value The value to be clamped
9 * @param {number} min The lower boundary of the output range
10 * @param {number} max The upper boundary of the output range
11 * @returns {number} A number in the range [min, max]
12 */
13function clamp(value, min = 0, max = 1) {
14 if (process.env.NODE_ENV !== 'production') {
15 if (value < min || value > max) {
16 console.error(`Material-UI: The value provided ${value} is out of range [${min}, ${max}].`);
17 }
18 }
19
20 return Math.min(Math.max(min, value), max);
21}
22/**
23 * Converts a color from CSS hex format to CSS rgb format.
24 *
25 * @param {string} color - Hex color, i.e. #nnn or #nnnnnn
26 * @returns {string} A CSS rgb color string
27 */
28
29
30export function hexToRgb(color) {
31 color = color.substr(1);
32 const re = new RegExp(`.{1,${color.length >= 6 ? 2 : 1}}`, 'g');
33 let colors = color.match(re);
34
35 if (colors && colors[0].length === 1) {
36 colors = colors.map(n => n + n);
37 }
38
39 return colors ? `rgb${colors.length === 4 ? 'a' : ''}(${colors.map((n, index) => {
40 return index < 3 ? parseInt(n, 16) : Math.round(parseInt(n, 16) / 255 * 1000) / 1000;
41 }).join(', ')})` : '';
42}
43
44function intToHex(int) {
45 const hex = int.toString(16);
46 return hex.length === 1 ? `0${hex}` : hex;
47}
48/**
49 * Converts a color from CSS rgb format to CSS hex format.
50 *
51 * @param {string} color - RGB color, i.e. rgb(n, n, n)
52 * @returns {string} A CSS rgb color string, i.e. #nnnnnn
53 */
54
55
56export function rgbToHex(color) {
57 // Idempotent
58 if (color.indexOf('#') === 0) {
59 return color;
60 }
61
62 const {
63 values
64 } = decomposeColor(color);
65 return `#${values.map(n => intToHex(n)).join('')}`;
66}
67/**
68 * Converts a color from hsl format to rgb format.
69 *
70 * @param {string} color - HSL color values
71 * @returns {string} rgb color values
72 */
73
74export function hslToRgb(color) {
75 color = decomposeColor(color);
76 const {
77 values
78 } = color;
79 const h = values[0];
80 const s = values[1] / 100;
81 const l = values[2] / 100;
82 const a = s * Math.min(l, 1 - l);
83
84 const f = (n, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
85
86 let type = 'rgb';
87 const rgb = [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
88
89 if (color.type === 'hsla') {
90 type += 'a';
91 rgb.push(values[3]);
92 }
93
94 return recomposeColor({
95 type,
96 values: rgb
97 });
98}
99/**
100 * Returns an object with the type and values of a color.
101 *
102 * Note: Does not support rgb % values.
103 *
104 * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
105 * @returns {object} - A MUI color object: {type: string, values: number[]}
106 */
107
108export function decomposeColor(color) {
109 // Idempotent
110 if (color.type) {
111 return color;
112 }
113
114 if (color.charAt(0) === '#') {
115 return decomposeColor(hexToRgb(color));
116 }
117
118 const marker = color.indexOf('(');
119 const type = color.substring(0, marker);
120
121 if (['rgb', 'rgba', 'hsl', 'hsla'].indexOf(type) === -1) {
122 throw new Error(process.env.NODE_ENV !== "production" ? `Material-UI: Unsupported \`${color}\` color.
123We support the following formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla().` : _formatMuiErrorMessage(3, color));
124 }
125
126 let values = color.substring(marker + 1, color.length - 1).split(',');
127 values = values.map(value => parseFloat(value));
128 return {
129 type,
130 values
131 };
132}
133/**
134 * Converts a color object with type and values to a string.
135 *
136 * @param {object} color - Decomposed color
137 * @param {string} color.type - One of: 'rgb', 'rgba', 'hsl', 'hsla'
138 * @param {array} color.values - [n,n,n] or [n,n,n,n]
139 * @returns {string} A CSS color string
140 */
141
142export function recomposeColor(color) {
143 const {
144 type
145 } = color;
146 let {
147 values
148 } = color;
149
150 if (type.indexOf('rgb') !== -1) {
151 // Only convert the first 3 values to int (i.e. not alpha)
152 values = values.map((n, i) => i < 3 ? parseInt(n, 10) : n);
153 } else if (type.indexOf('hsl') !== -1) {
154 values[1] = `${values[1]}%`;
155 values[2] = `${values[2]}%`;
156 }
157
158 return `${type}(${values.join(', ')})`;
159}
160/**
161 * Calculates the contrast ratio between two colors.
162 *
163 * Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
164 *
165 * @param {string} foreground - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
166 * @param {string} background - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
167 * @returns {number} A contrast ratio value in the range 0 - 21.
168 */
169
170export function getContrastRatio(foreground, background) {
171 const lumA = getLuminance(foreground);
172 const lumB = getLuminance(background);
173 return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
174}
175/**
176 * The relative brightness of any point in a color space,
177 * normalized to 0 for darkest black and 1 for lightest white.
178 *
179 * Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
180 *
181 * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
182 * @returns {number} The relative brightness of the color in the range 0 - 1
183 */
184
185export function getLuminance(color) {
186 color = decomposeColor(color);
187 let rgb = color.type === 'hsl' ? decomposeColor(hslToRgb(color)).values : color.values;
188 rgb = rgb.map(val => {
189 val /= 255; // normalized
190
191 return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
192 }); // Truncate at 3 digits
193
194 return Number((0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3));
195}
196/**
197 * Darken or lighten a color, depending on its luminance.
198 * Light colors are darkened, dark colors are lightened.
199 *
200 * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
201 * @param {number} coefficient=0.15 - multiplier in the range 0 - 1
202 * @returns {string} A CSS color string. Hex input values are returned as rgb
203 */
204
205export function emphasize(color, coefficient = 0.15) {
206 return getLuminance(color) > 0.5 ? darken(color, coefficient) : lighten(color, coefficient);
207}
208let warnedOnce = false;
209/**
210 * Set the absolute transparency of a color.
211 * Any existing alpha values are overwritten.
212 *
213 * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
214 * @param {number} value - value to set the alpha channel to in the range 0 -1
215 * @returns {string} A CSS color string. Hex input values are returned as rgb
216 *
217 * @deprecated
218 * Use `import { alpha } from '@material-ui/core/styles'` instead.
219 */
220
221export function fade(color, value) {
222 if (process.env.NODE_ENV !== 'production') {
223 if (!warnedOnce) {
224 warnedOnce = true;
225 console.error(['Material-UI: The `fade` color utility was renamed to `alpha` to better describe its functionality.', '', "You should use `import { alpha } from '@material-ui/core/styles'`"].join('\n'));
226 }
227 }
228
229 return alpha(color, value);
230}
231/**
232 * Set the absolute transparency of a color.
233 * Any existing alpha value is overwritten.
234 *
235 * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
236 * @param {number} value - value to set the alpha channel to in the range 0-1
237 * @returns {string} A CSS color string. Hex input values are returned as rgb
238 */
239
240export function alpha(color, value) {
241 color = decomposeColor(color);
242 value = clamp(value);
243
244 if (color.type === 'rgb' || color.type === 'hsl') {
245 color.type += 'a';
246 }
247
248 color.values[3] = value;
249 return recomposeColor(color);
250}
251/**
252 * Darkens a color.
253 *
254 * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
255 * @param {number} coefficient - multiplier in the range 0 - 1
256 * @returns {string} A CSS color string. Hex input values are returned as rgb
257 */
258
259export function darken(color, coefficient) {
260 color = decomposeColor(color);
261 coefficient = clamp(coefficient);
262
263 if (color.type.indexOf('hsl') !== -1) {
264 color.values[2] *= 1 - coefficient;
265 } else if (color.type.indexOf('rgb') !== -1) {
266 for (let i = 0; i < 3; i += 1) {
267 color.values[i] *= 1 - coefficient;
268 }
269 }
270
271 return recomposeColor(color);
272}
273/**
274 * Lightens a color.
275 *
276 * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
277 * @param {number} coefficient - multiplier in the range 0 - 1
278 * @returns {string} A CSS color string. Hex input values are returned as rgb
279 */
280
281export function lighten(color, coefficient) {
282 color = decomposeColor(color);
283 coefficient = clamp(coefficient);
284
285 if (color.type.indexOf('hsl') !== -1) {
286 color.values[2] += (100 - color.values[2]) * coefficient;
287 } else if (color.type.indexOf('rgb') !== -1) {
288 for (let i = 0; i < 3; i += 1) {
289 color.values[i] += (255 - color.values[i]) * coefficient;
290 }
291 }
292
293 return recomposeColor(color);
294}
\No newline at end of file