UNPKG

ipink-themejs

Version:

Theme for ui, 根据自定义的颜色值导出一套常用的Map合集, 字号、圆角、边框、字色、背景色、...

1,710 lines (1,680 loc) 69.7 kB
/** * Take input from [0, n] and return it as [0, 1] * @hidden */ function bound01(n, max) { if (isOnePointZero(n)) { n = '100%'; } const isPercent = isPercentage(n); n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n))); // Automatically convert percentage into number if (isPercent) { n = parseInt(String(n * max), 10) / 100; } // Handle floating point rounding errors if (Math.abs(n - max) < 0.000001) { return 1; } // Convert into [0, 1] range if it isn't already if (max === 360) { // If n is a hue given in degrees, // wrap around out-of-range values into [0, 360] range // then convert into [0, 1]. n = (n < 0 ? (n % max) + max : n % max) / parseFloat(String(max)); } else { // If n not a hue given in degrees // Convert into [0, 1] range if it isn't already. n = (n % max) / parseFloat(String(max)); } return n; } /** * Force a number between 0 and 1 * @hidden */ function clamp01(val) { return Math.min(1, Math.max(0, val)); } /** * Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 * <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0> * @hidden */ function isOnePointZero(n) { return typeof n === 'string' && n.indexOf('.') !== -1 && parseFloat(n) === 1; } /** * Check to see if string passed in is a percentage * @hidden */ function isPercentage(n) { return typeof n === 'string' && n.indexOf('%') !== -1; } /** * Return a valid alpha value [0,1] with all invalid values being set to 1 * @hidden */ function boundAlpha(a) { a = parseFloat(a); if (isNaN(a) || a < 0 || a > 1) { a = 1; } return a; } /** * Replace a decimal with it's percentage value * @hidden */ function convertToPercentage(n) { if (Number(n) <= 1) { return `${Number(n) * 100}%`; } return n; } /** * Force a hex value to have 2 characters * @hidden */ function pad2(c) { return c.length === 1 ? '0' + c : String(c); } // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript> /** * Handle bounds / percentage checking to conform to CSS color spec * <http://www.w3.org/TR/css3-color/> * *Assumes:* r, g, b in [0, 255] or [0, 1] * *Returns:* { r, g, b } in [0, 255] */ function rgbToRgb(r, g, b) { return { r: bound01(r, 255) * 255, g: bound01(g, 255) * 255, b: bound01(b, 255) * 255, }; } /** * Converts an RGB color value to HSL. * *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] * *Returns:* { h, s, l } in [0,1] */ function rgbToHsl(r, g, b) { r = bound01(r, 255); g = bound01(g, 255); b = bound01(b, 255); const max = Math.max(r, g, b); const min = Math.min(r, g, b); let h = 0; let s = 0; const l = (max + min) / 2; if (max === min) { s = 0; h = 0; // achromatic } else { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return { h, s, l }; } function hue2rgb(p, q, t) { if (t < 0) { t += 1; } if (t > 1) { t -= 1; } if (t < 1 / 6) { return p + (q - p) * (6 * t); } if (t < 1 / 2) { return q; } if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6; } return p; } /** * Converts an HSL color value to RGB. * * *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] * *Returns:* { r, g, b } in the set [0, 255] */ function hslToRgb(h, s, l) { let r; let g; let b; h = bound01(h, 360); s = bound01(s, 100); l = bound01(l, 100); if (s === 0) { // achromatic g = l; b = l; r = l; } else { const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1 / 3); } return { r: r * 255, g: g * 255, b: b * 255 }; } /** * Converts an RGB color value to HSV * * *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] * *Returns:* { h, s, v } in [0,1] */ function rgbToHsv(r, g, b) { r = bound01(r, 255); g = bound01(g, 255); b = bound01(b, 255); const max = Math.max(r, g, b); const min = Math.min(r, g, b); let h = 0; const v = max; const d = max - min; const s = max === 0 ? 0 : d / max; if (max === min) { h = 0; // achromatic } else { switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return { h, s, v }; } /** * Converts an HSV color value to RGB. * * *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] * *Returns:* { r, g, b } in the set [0, 255] */ function hsvToRgb(h, s, v) { h = bound01(h, 360) * 6; s = bound01(s, 100); v = bound01(v, 100); const i = Math.floor(h); const f = h - i; const p = v * (1 - s); const q = v * (1 - f * s); const t = v * (1 - (1 - f) * s); const mod = i % 6; const r = [v, q, p, p, t, v][mod]; const g = [t, v, v, q, p, p][mod]; const b = [p, p, t, v, v, q][mod]; return { r: r * 255, g: g * 255, b: b * 255 }; } /** * Converts an RGB color to hex * * *Assumes:* r, g, and b are contained in the set [0, 255] * *Returns:* a 3 or 6 character hex */ function rgbToHex(r, g, b, allow3Char) { const hex = [ pad2(Math.round(r).toString(16)), pad2(Math.round(g).toString(16)), pad2(Math.round(b).toString(16)), ]; // Return a 3 character hex if possible if (allow3Char && hex[0].startsWith(hex[0].charAt(1)) && hex[1].startsWith(hex[1].charAt(1)) && hex[2].startsWith(hex[2].charAt(1))) { return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); } return hex.join(''); } /** * Converts an RGBA color plus alpha transparency to hex * * *Assumes:* r, g, b are contained in the set [0, 255] and a in [0, 1] * *Returns:* a 4 or 8 character rgba hex */ // eslint-disable-next-line max-params function rgbaToHex(r, g, b, a, allow4Char) { const hex = [ pad2(Math.round(r).toString(16)), pad2(Math.round(g).toString(16)), pad2(Math.round(b).toString(16)), pad2(convertDecimalToHex(a)), ]; // Return a 4 character hex if possible if (allow4Char && hex[0].startsWith(hex[0].charAt(1)) && hex[1].startsWith(hex[1].charAt(1)) && hex[2].startsWith(hex[2].charAt(1)) && hex[3].startsWith(hex[3].charAt(1))) { return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0); } return hex.join(''); } /** * Converts CMYK to RBG * Assumes c, m, y, k are in the set [0, 100] */ function cmykToRgb(c, m, y, k) { const cConv = c / 100; const mConv = m / 100; const yConv = y / 100; const kConv = k / 100; const r = 255 * (1 - cConv) * (1 - kConv); const g = 255 * (1 - mConv) * (1 - kConv); const b = 255 * (1 - yConv) * (1 - kConv); return { r, g, b }; } function rgbToCmyk(r, g, b) { let c = 1 - r / 255; let m = 1 - g / 255; let y = 1 - b / 255; let k = Math.min(c, m, y); if (k === 1) { c = 0; m = 0; y = 0; } else { c = ((c - k) / (1 - k)) * 100; m = ((m - k) / (1 - k)) * 100; y = ((y - k) / (1 - k)) * 100; } k *= 100; return { c: Math.round(c), m: Math.round(m), y: Math.round(y), k: Math.round(k), }; } /** Converts a decimal to a hex value */ function convertDecimalToHex(d) { return Math.round(parseFloat(d) * 255).toString(16); } /** Converts a hex value to a decimal */ function convertHexToDecimal(h) { return parseIntFromHex(h) / 255; } /** Parse a base-16 hex value into a base-10 integer */ function parseIntFromHex(val) { return parseInt(val, 16); } function numberInputToObject(color) { return { r: color >> 16, g: (color & 0xff00) >> 8, b: color & 0xff, }; } // https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json /** * @hidden */ const names = { aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aqua: '#00ffff', aquamarine: '#7fffd4', azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', black: '#000000', blanchedalmond: '#ffebcd', blue: '#0000ff', blueviolet: '#8a2be2', brown: '#a52a2a', burlywood: '#deb887', cadetblue: '#5f9ea0', chartreuse: '#7fff00', chocolate: '#d2691e', coral: '#ff7f50', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', cyan: '#00ffff', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', darkgreen: '#006400', darkgrey: '#a9a9a9', darkkhaki: '#bdb76b', darkmagenta: '#8b008b', darkolivegreen: '#556b2f', darkorange: '#ff8c00', darkorchid: '#9932cc', darkred: '#8b0000', darksalmon: '#e9967a', darkseagreen: '#8fbc8f', darkslateblue: '#483d8b', darkslategray: '#2f4f4f', darkslategrey: '#2f4f4f', darkturquoise: '#00ced1', darkviolet: '#9400d3', deeppink: '#ff1493', deepskyblue: '#00bfff', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1e90ff', firebrick: '#b22222', floralwhite: '#fffaf0', forestgreen: '#228b22', fuchsia: '#ff00ff', gainsboro: '#dcdcdc', ghostwhite: '#f8f8ff', goldenrod: '#daa520', gold: '#ffd700', gray: '#808080', green: '#008000', greenyellow: '#adff2f', grey: '#808080', honeydew: '#f0fff0', hotpink: '#ff69b4', indianred: '#cd5c5c', indigo: '#4b0082', ivory: '#fffff0', khaki: '#f0e68c', lavenderblush: '#fff0f5', lavender: '#e6e6fa', lawngreen: '#7cfc00', lemonchiffon: '#fffacd', lightblue: '#add8e6', lightcoral: '#f08080', lightcyan: '#e0ffff', lightgoldenrodyellow: '#fafad2', lightgray: '#d3d3d3', lightgreen: '#90ee90', lightgrey: '#d3d3d3', lightpink: '#ffb6c1', lightsalmon: '#ffa07a', lightseagreen: '#20b2aa', lightskyblue: '#87cefa', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#b0c4de', lightyellow: '#ffffe0', lime: '#00ff00', limegreen: '#32cd32', linen: '#faf0e6', magenta: '#ff00ff', maroon: '#800000', mediumaquamarine: '#66cdaa', mediumblue: '#0000cd', mediumorchid: '#ba55d3', mediumpurple: '#9370db', mediumseagreen: '#3cb371', mediumslateblue: '#7b68ee', mediumspringgreen: '#00fa9a', mediumturquoise: '#48d1cc', mediumvioletred: '#c71585', midnightblue: '#191970', mintcream: '#f5fffa', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', navajowhite: '#ffdead', navy: '#000080', oldlace: '#fdf5e6', olive: '#808000', olivedrab: '#6b8e23', orange: '#ffa500', orangered: '#ff4500', orchid: '#da70d6', palegoldenrod: '#eee8aa', palegreen: '#98fb98', paleturquoise: '#afeeee', palevioletred: '#db7093', papayawhip: '#ffefd5', peachpuff: '#ffdab9', peru: '#cd853f', pink: '#ffc0cb', plum: '#dda0dd', powderblue: '#b0e0e6', purple: '#800080', rebeccapurple: '#663399', red: '#ff0000', rosybrown: '#bc8f8f', royalblue: '#4169e1', saddlebrown: '#8b4513', salmon: '#fa8072', sandybrown: '#f4a460', seagreen: '#2e8b57', seashell: '#fff5ee', sienna: '#a0522d', silver: '#c0c0c0', skyblue: '#87ceeb', slateblue: '#6a5acd', slategray: '#708090', slategrey: '#708090', snow: '#fffafa', springgreen: '#00ff7f', steelblue: '#4682b4', tan: '#d2b48c', teal: '#008080', thistle: '#d8bfd8', tomato: '#ff6347', turquoise: '#40e0d0', violet: '#ee82ee', wheat: '#f5deb3', white: '#ffffff', whitesmoke: '#f5f5f5', yellow: '#ffff00', yellowgreen: '#9acd32', }; /** * Given a string or object, convert that input to RGB * * Possible string inputs: * ``` * "red" * "#f00" or "f00" * "#ff0000" or "ff0000" * "#ff000000" or "ff000000" * "rgb 255 0 0" or "rgb (255, 0, 0)" * "rgb 1.0 0 0" or "rgb (1, 0, 0)" * "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" * "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" * "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" * "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" * "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" * "cmyk(0, 20, 0, 0)" or "cmyk 0 20 0 0" * ``` */ function inputToRGB(color) { let rgb = { r: 0, g: 0, b: 0 }; let a = 1; let s = null; let v = null; let l = null; let ok = false; let format = false; if (typeof color === 'string') { color = stringInputToObject(color); } if (typeof color === 'object') { if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) { rgb = rgbToRgb(color.r, color.g, color.b); ok = true; format = String(color.r).substr(-1) === '%' ? 'prgb' : 'rgb'; } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) { s = convertToPercentage(color.s); v = convertToPercentage(color.v); rgb = hsvToRgb(color.h, s, v); ok = true; format = 'hsv'; } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) { s = convertToPercentage(color.s); l = convertToPercentage(color.l); rgb = hslToRgb(color.h, s, l); ok = true; format = 'hsl'; } else if (isValidCSSUnit(color.c) && isValidCSSUnit(color.m) && isValidCSSUnit(color.y) && isValidCSSUnit(color.k)) { rgb = cmykToRgb(color.c, color.m, color.y, color.k); ok = true; format = 'cmyk'; } if (Object.prototype.hasOwnProperty.call(color, 'a')) { a = color.a; } } a = boundAlpha(a); return { ok, format: color.format || format, r: Math.min(255, Math.max(rgb.r, 0)), g: Math.min(255, Math.max(rgb.g, 0)), b: Math.min(255, Math.max(rgb.b, 0)), a, }; } // <http://www.w3.org/TR/css3-values/#integers> const CSS_INTEGER = '[-\\+]?\\d+%?'; // <http://www.w3.org/TR/css3-values/#number-value> const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?'; // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. const CSS_UNIT = '(?:' + CSS_NUMBER + ')|(?:' + CSS_INTEGER + ')'; // Actual matching. // Parentheses and commas are optional, but not required. // Whitespace can take the place of commas or opening paren // eslint-disable-next-line prettier/prettier const PERMISSIVE_MATCH3 = '[\\s|\\(]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')\\s*\\)?'; const PERMISSIVE_MATCH4 = // eslint-disable-next-line prettier/prettier '[\\s|\\(]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')[,|\\s]+(' + CSS_UNIT + ')\\s*\\)?'; const matchers = { CSS_UNIT: new RegExp(CSS_UNIT), rgb: new RegExp('rgb' + PERMISSIVE_MATCH3), rgba: new RegExp('rgba' + PERMISSIVE_MATCH4), hsl: new RegExp('hsl' + PERMISSIVE_MATCH3), hsla: new RegExp('hsla' + PERMISSIVE_MATCH4), hsv: new RegExp('hsv' + PERMISSIVE_MATCH3), hsva: new RegExp('hsva' + PERMISSIVE_MATCH4), cmyk: new RegExp('cmyk' + PERMISSIVE_MATCH4), hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, }; /** * Permissive string parsing. Take in a number of formats, and output an object * based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` or `{c, m, y, k}` or `{c, m, y, k, a}` */ function stringInputToObject(color) { color = color.trim().toLowerCase(); if (color.length === 0) { return false; } let named = false; if (names[color]) { color = names[color]; named = true; } else if (color === 'transparent') { return { r: 0, g: 0, b: 0, a: 0, format: 'name' }; } // Try to match string input using regular expressions. // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] // Just return an object and let the conversion functions handle that. // This way the result will be the same whether the tinycolor is initialized with string or object. let match = matchers.rgb.exec(color); if (match) { return { r: match[1], g: match[2], b: match[3] }; } match = matchers.rgba.exec(color); if (match) { return { r: match[1], g: match[2], b: match[3], a: match[4] }; } match = matchers.hsl.exec(color); if (match) { return { h: match[1], s: match[2], l: match[3] }; } match = matchers.hsla.exec(color); if (match) { return { h: match[1], s: match[2], l: match[3], a: match[4] }; } match = matchers.hsv.exec(color); if (match) { return { h: match[1], s: match[2], v: match[3] }; } match = matchers.hsva.exec(color); if (match) { return { h: match[1], s: match[2], v: match[3], a: match[4] }; } match = matchers.cmyk.exec(color); if (match) { return { c: match[1], m: match[2], y: match[3], k: match[4], }; } match = matchers.hex8.exec(color); if (match) { return { r: parseIntFromHex(match[1]), g: parseIntFromHex(match[2]), b: parseIntFromHex(match[3]), a: convertHexToDecimal(match[4]), format: named ? 'name' : 'hex8', }; } match = matchers.hex6.exec(color); if (match) { return { r: parseIntFromHex(match[1]), g: parseIntFromHex(match[2]), b: parseIntFromHex(match[3]), format: named ? 'name' : 'hex', }; } match = matchers.hex4.exec(color); if (match) { return { r: parseIntFromHex(match[1] + match[1]), g: parseIntFromHex(match[2] + match[2]), b: parseIntFromHex(match[3] + match[3]), a: convertHexToDecimal(match[4] + match[4]), format: named ? 'name' : 'hex8', }; } match = matchers.hex3.exec(color); if (match) { return { r: parseIntFromHex(match[1] + match[1]), g: parseIntFromHex(match[2] + match[2]), b: parseIntFromHex(match[3] + match[3]), format: named ? 'name' : 'hex', }; } return false; } /** * Check to see if it looks like a CSS unit * (see `matchers` above for definition). */ function isValidCSSUnit(color) { if (typeof color === 'number') { return !Number.isNaN(color); } return matchers.CSS_UNIT.test(color); } class TinyColor { constructor(color = '', opts = {}) { // If input is already a tinycolor, return itself if (color instanceof TinyColor) { // eslint-disable-next-line no-constructor-return return color; } if (typeof color === 'number') { color = numberInputToObject(color); } this.originalInput = color; const rgb = inputToRGB(color); this.originalInput = color; this.r = rgb.r; this.g = rgb.g; this.b = rgb.b; this.a = rgb.a; this.roundA = Math.round(100 * this.a) / 100; this.format = opts.format ?? rgb.format; this.gradientType = opts.gradientType; // Don't let the range of [0,255] come back in [0,1]. // Potentially lose a little bit of precision here, but will fix issues where // .5 gets interpreted as half of the total, instead of half of 1 // If it was supposed to be 128, this was already taken care of by `inputToRgb` if (this.r < 1) { this.r = Math.round(this.r); } if (this.g < 1) { this.g = Math.round(this.g); } if (this.b < 1) { this.b = Math.round(this.b); } this.isValid = rgb.ok; } isDark() { return this.getBrightness() < 128; } isLight() { return !this.isDark(); } /** * Returns the perceived brightness of the color, from 0-255. */ getBrightness() { // http://www.w3.org/TR/AERT#color-contrast const rgb = this.toRgb(); return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; } /** * Returns the perceived luminance of a color, from 0-1. */ getLuminance() { // http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef const rgb = this.toRgb(); let R; let G; let B; const RsRGB = rgb.r / 255; const GsRGB = rgb.g / 255; const BsRGB = rgb.b / 255; if (RsRGB <= 0.03928) { R = RsRGB / 12.92; } else { // eslint-disable-next-line prefer-exponentiation-operator R = Math.pow((RsRGB + 0.055) / 1.055, 2.4); } if (GsRGB <= 0.03928) { G = GsRGB / 12.92; } else { // eslint-disable-next-line prefer-exponentiation-operator G = Math.pow((GsRGB + 0.055) / 1.055, 2.4); } if (BsRGB <= 0.03928) { B = BsRGB / 12.92; } else { // eslint-disable-next-line prefer-exponentiation-operator B = Math.pow((BsRGB + 0.055) / 1.055, 2.4); } return 0.2126 * R + 0.7152 * G + 0.0722 * B; } /** * Returns the alpha value of a color, from 0-1. */ getAlpha() { return this.a; } /** * Sets the alpha value on the current color. * * @param alpha - The new alpha value. The accepted range is 0-1. */ setAlpha(alpha) { this.a = boundAlpha(alpha); this.roundA = Math.round(100 * this.a) / 100; return this; } /** * Returns whether the color is monochrome. */ isMonochrome() { const { s } = this.toHsl(); return s === 0; } /** * Returns the object as a HSVA object. */ toHsv() { const hsv = rgbToHsv(this.r, this.g, this.b); return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this.a }; } /** * Returns the hsva values interpolated into a string with the following format: * "hsva(xxx, xxx, xxx, xx)". */ toHsvString() { const hsv = rgbToHsv(this.r, this.g, this.b); const h = Math.round(hsv.h * 360); const s = Math.round(hsv.s * 100); const v = Math.round(hsv.v * 100); return this.a === 1 ? `hsv(${h}, ${s}%, ${v}%)` : `hsva(${h}, ${s}%, ${v}%, ${this.roundA})`; } /** * Returns the object as a HSLA object. */ toHsl() { const hsl = rgbToHsl(this.r, this.g, this.b); return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this.a }; } /** * Returns the hsla values interpolated into a string with the following format: * "hsla(xxx, xxx, xxx, xx)". */ toHslString() { const hsl = rgbToHsl(this.r, this.g, this.b); const h = Math.round(hsl.h * 360); const s = Math.round(hsl.s * 100); const l = Math.round(hsl.l * 100); return this.a === 1 ? `hsl(${h}, ${s}%, ${l}%)` : `hsla(${h}, ${s}%, ${l}%, ${this.roundA})`; } /** * Returns the hex value of the color. * @param allow3Char will shorten hex value to 3 char if possible */ toHex(allow3Char = false) { return rgbToHex(this.r, this.g, this.b, allow3Char); } /** * Returns the hex value of the color -with a # prefixed. * @param allow3Char will shorten hex value to 3 char if possible */ toHexString(allow3Char = false) { return '#' + this.toHex(allow3Char); } /** * Returns the hex 8 value of the color. * @param allow4Char will shorten hex value to 4 char if possible */ toHex8(allow4Char = false) { return rgbaToHex(this.r, this.g, this.b, this.a, allow4Char); } /** * Returns the hex 8 value of the color -with a # prefixed. * @param allow4Char will shorten hex value to 4 char if possible */ toHex8String(allow4Char = false) { return '#' + this.toHex8(allow4Char); } /** * Returns the shorter hex value of the color depends on its alpha -with a # prefixed. * @param allowShortChar will shorten hex value to 3 or 4 char if possible */ toHexShortString(allowShortChar = false) { return this.a === 1 ? this.toHexString(allowShortChar) : this.toHex8String(allowShortChar); } /** * Returns the object as a RGBA object. */ toRgb() { return { r: Math.round(this.r), g: Math.round(this.g), b: Math.round(this.b), a: this.a, }; } /** * Returns the RGBA values interpolated into a string with the following format: * "RGBA(xxx, xxx, xxx, xx)". */ toRgbString() { const r = Math.round(this.r); const g = Math.round(this.g); const b = Math.round(this.b); return this.a === 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${this.roundA})`; } /** * Returns the object as a RGBA object. */ toPercentageRgb() { const fmt = (x) => `${Math.round(bound01(x, 255) * 100)}%`; return { r: fmt(this.r), g: fmt(this.g), b: fmt(this.b), a: this.a, }; } /** * Returns the RGBA relative values interpolated into a string */ toPercentageRgbString() { const rnd = (x) => Math.round(bound01(x, 255) * 100); return this.a === 1 ? `rgb(${rnd(this.r)}%, ${rnd(this.g)}%, ${rnd(this.b)}%)` : `rgba(${rnd(this.r)}%, ${rnd(this.g)}%, ${rnd(this.b)}%, ${this.roundA})`; } toCmyk() { return { ...rgbToCmyk(this.r, this.g, this.b), }; } toCmykString() { const { c, m, y, k } = rgbToCmyk(this.r, this.g, this.b); return `cmyk(${c}, ${m}, ${y}, ${k})`; } /** * The 'real' name of the color -if there is one. */ toName() { if (this.a === 0) { return 'transparent'; } if (this.a < 1) { return false; } const hex = '#' + rgbToHex(this.r, this.g, this.b, false); for (const [key, value] of Object.entries(names)) { if (hex === value) { return key; } } return false; } toString(format) { const formatSet = Boolean(format); format = format ?? this.format; let formattedString = false; const hasAlpha = this.a < 1 && this.a >= 0; const needsAlphaFormat = !formatSet && hasAlpha && (format.startsWith('hex') || format === 'name'); if (needsAlphaFormat) { // Special case for "transparent", all other non-alpha formats // will return rgba when there is transparency. if (format === 'name' && this.a === 0) { return this.toName(); } return this.toRgbString(); } if (format === 'rgb') { formattedString = this.toRgbString(); } if (format === 'prgb') { formattedString = this.toPercentageRgbString(); } if (format === 'hex' || format === 'hex6') { formattedString = this.toHexString(); } if (format === 'hex3') { formattedString = this.toHexString(true); } if (format === 'hex4') { formattedString = this.toHex8String(true); } if (format === 'hex8') { formattedString = this.toHex8String(); } if (format === 'name') { formattedString = this.toName(); } if (format === 'hsl') { formattedString = this.toHslString(); } if (format === 'hsv') { formattedString = this.toHsvString(); } if (format === 'cmyk') { formattedString = this.toCmykString(); } return formattedString || this.toHexString(); } toNumber() { return (Math.round(this.r) << 16) + (Math.round(this.g) << 8) + Math.round(this.b); } clone() { return new TinyColor(this.toString()); } /** * Lighten the color a given amount. Providing 100 will always return white. * @param amount - valid between 1-100 */ lighten(amount = 10) { const hsl = this.toHsl(); hsl.l += amount / 100; hsl.l = clamp01(hsl.l); return new TinyColor(hsl); } /** * Brighten the color a given amount, from 0 to 100. * @param amount - valid between 1-100 */ brighten(amount = 10) { const rgb = this.toRgb(); rgb.r = Math.max(0, Math.min(255, rgb.r - Math.round(255 * -(amount / 100)))); rgb.g = Math.max(0, Math.min(255, rgb.g - Math.round(255 * -(amount / 100)))); rgb.b = Math.max(0, Math.min(255, rgb.b - Math.round(255 * -(amount / 100)))); return new TinyColor(rgb); } /** * Darken the color a given amount, from 0 to 100. * Providing 100 will always return black. * @param amount - valid between 1-100 */ darken(amount = 10) { const hsl = this.toHsl(); hsl.l -= amount / 100; hsl.l = clamp01(hsl.l); return new TinyColor(hsl); } /** * Mix the color with pure white, from 0 to 100. * Providing 0 will do nothing, providing 100 will always return white. * @param amount - valid between 1-100 */ tint(amount = 10) { return this.mix('white', amount); } /** * Mix the color with pure black, from 0 to 100. * Providing 0 will do nothing, providing 100 will always return black. * @param amount - valid between 1-100 */ shade(amount = 10) { return this.mix('black', amount); } /** * Desaturate the color a given amount, from 0 to 100. * Providing 100 will is the same as calling greyscale * @param amount - valid between 1-100 */ desaturate(amount = 10) { const hsl = this.toHsl(); hsl.s -= amount / 100; hsl.s = clamp01(hsl.s); return new TinyColor(hsl); } /** * Saturate the color a given amount, from 0 to 100. * @param amount - valid between 1-100 */ saturate(amount = 10) { const hsl = this.toHsl(); hsl.s += amount / 100; hsl.s = clamp01(hsl.s); return new TinyColor(hsl); } /** * Completely desaturates a color into greyscale. * Same as calling `desaturate(100)` */ greyscale() { return this.desaturate(100); } /** * Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. * Values outside of this range will be wrapped into this range. */ spin(amount) { const hsl = this.toHsl(); const hue = (hsl.h + amount) % 360; hsl.h = hue < 0 ? 360 + hue : hue; return new TinyColor(hsl); } /** * Mix the current color a given amount with another color, from 0 to 100. * 0 means no mixing (return current color). */ mix(color, amount = 50) { const rgb1 = this.toRgb(); const rgb2 = new TinyColor(color).toRgb(); const p = amount / 100; const rgba = { r: (rgb2.r - rgb1.r) * p + rgb1.r, g: (rgb2.g - rgb1.g) * p + rgb1.g, b: (rgb2.b - rgb1.b) * p + rgb1.b, a: (rgb2.a - rgb1.a) * p + rgb1.a, }; return new TinyColor(rgba); } analogous(results = 6, slices = 30) { const hsl = this.toHsl(); const part = 360 / slices; const ret = [this]; for (hsl.h = (hsl.h - ((part * results) >> 1) + 720) % 360; --results;) { hsl.h = (hsl.h + part) % 360; ret.push(new TinyColor(hsl)); } return ret; } /** * taken from https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js */ complement() { const hsl = this.toHsl(); hsl.h = (hsl.h + 180) % 360; return new TinyColor(hsl); } monochromatic(results = 6) { const hsv = this.toHsv(); const { h } = hsv; const { s } = hsv; let { v } = hsv; const res = []; const modification = 1 / results; while (results--) { res.push(new TinyColor({ h, s, v })); v = (v + modification) % 1; } return res; } splitcomplement() { const hsl = this.toHsl(); const { h } = hsl; return [ this, new TinyColor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l }), new TinyColor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l }), ]; } /** * Compute how the color would appear on a background */ onBackground(background) { const fg = this.toRgb(); const bg = new TinyColor(background).toRgb(); const alpha = fg.a + bg.a * (1 - fg.a); return new TinyColor({ r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / alpha, g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / alpha, b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / alpha, a: alpha, }); } /** * Alias for `polyad(3)` */ triad() { return this.polyad(3); } /** * Alias for `polyad(4)` */ tetrad() { return this.polyad(4); } /** * Get polyad colors, like (for 1, 2, 3, 4, 5, 6, 7, 8, etc...) * monad, dyad, triad, tetrad, pentad, hexad, heptad, octad, etc... */ polyad(n) { const hsl = this.toHsl(); const { h } = hsl; const result = [this]; const increment = 360 / n; for (let i = 1; i < n; i++) { result.push(new TinyColor({ h: (h + i * increment) % 360, s: hsl.s, l: hsl.l })); } return result; } /** * compare color vs current color */ equals(color) { const comparedColor = new TinyColor(color); /** * RGB and CMYK do not have the same color gamut, so a CMYK conversion will never be 100%. * This means we need to compare CMYK to CMYK to ensure accuracy of the equals function. */ if (this.format === 'cmyk' || comparedColor.format === 'cmyk') { return this.toCmykString() === comparedColor.toCmykString(); } return this.toRgbString() === comparedColor.toRgbString(); } } function isStableColor(color) { return color >= 0 && color <= 255; } function getAlphaColor$2(frontColor, backgroundColor) { const { r: fR, g: fG, b: fB, a: originAlpha } = new TinyColor(frontColor).toRgb(); if (originAlpha < 1) { return frontColor; } const { r: bR, g: bG, b: bB } = new TinyColor(backgroundColor).toRgb(); for (let fA = 0.01; fA <= 1; fA += 0.01) { const r = Math.round((fR - bR * (1 - fA)) / fA); const g = Math.round((fG - bG * (1 - fA)) / fA); const b = Math.round((fB - bB * (1 - fA)) / fA); if (isStableColor(r) && isStableColor(g) && isStableColor(b)) { return new TinyColor({ r, g, b, a: Math.round(fA * 100) / 100 }).toRgbString(); } } return new TinyColor({ r: fR, g: fG, b: fB, a: 1 }).toRgbString(); } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function toPropertyKey(t) { var i = toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _defineProperty(e, r, t) { return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } const round = Math.round; /** * Support format, alpha unit will check the % mark: * - rgba(102, 204, 255, .5) -> [102, 204, 255, 0.5] * - rgb(102 204 255 / .5) -> [102, 204, 255, 0.5] * - rgb(100%, 50%, 0% / 50%) -> [255, 128, 0, 0.5] * - hsl(270, 60, 40, .5) -> [270, 60, 40, 0.5] * - hsl(270deg 60% 40% / 50%) -> [270, 60, 40, 0.5] * * When `base` is provided, the percentage value will be divided by `base`. */ function splitColorStr(str, parseNum) { const match = str // Remove str before `(` .replace(/^[^(]*\((.*)/, '$1') // Remove str after `)` .replace(/\).*/, '').match(/\d*\.?\d+%?/g) || []; const numList = match.map(item => parseFloat(item)); for (let i = 0; i < 3; i += 1) { numList[i] = parseNum(numList[i] || 0, match[i] || '', i); } // For alpha. 50% should be 0.5 if (match[3]) { numList[3] = match[3].includes('%') ? numList[3] / 100 : numList[3]; } else { // By default, alpha is 1 numList[3] = 1; } return numList; } const parseHSVorHSL = (num, _, index) => index === 0 ? num : num / 100; /** round and limit number to integer between 0-255 */ function limitRange(value, max) { const mergedMax = max || 255; if (value > mergedMax) { return mergedMax; } if (value < 0) { return 0; } return value; } class FastColor { constructor(input) { /** * All FastColor objects are valid. So isValid is always true. This property is kept to be compatible with TinyColor. */ _defineProperty(this, "isValid", true); /** * Red, R in RGB */ _defineProperty(this, "r", 0); /** * Green, G in RGB */ _defineProperty(this, "g", 0); /** * Blue, B in RGB */ _defineProperty(this, "b", 0); /** * Alpha/Opacity, A in RGBA/HSLA */ _defineProperty(this, "a", 1); // HSV privates _defineProperty(this, "_h", void 0); _defineProperty(this, "_s", void 0); _defineProperty(this, "_l", void 0); _defineProperty(this, "_v", void 0); // intermediate variables to calculate HSL/HSV _defineProperty(this, "_max", void 0); _defineProperty(this, "_min", void 0); _defineProperty(this, "_brightness", void 0); /** * Always check 3 char in the object to determine the format. * We not use function in check to save bundle size. * e.g. 'rgb' -> { r: 0, g: 0, b: 0 }. */ function matchFormat(str) { return str[0] in input && str[1] in input && str[2] in input; } if (!input) ; else if (typeof input === 'string') { const trimStr = input.trim(); function matchPrefix(prefix) { return trimStr.startsWith(prefix); } if (/^#?[A-F\d]{3,8}$/i.test(trimStr)) { this.fromHexString(trimStr); } else if (matchPrefix('rgb')) { this.fromRgbString(trimStr); } else if (matchPrefix('hsl')) { this.fromHslString(trimStr); } else if (matchPrefix('hsv') || matchPrefix('hsb')) { this.fromHsvString(trimStr); } } else if (input instanceof FastColor) { this.r = input.r; this.g = input.g; this.b = input.b; this.a = input.a; this._h = input._h; this._s = input._s; this._l = input._l; this._v = input._v; } else if (matchFormat('rgb')) { this.r = limitRange(input.r); this.g = limitRange(input.g); this.b = limitRange(input.b); this.a = typeof input.a === 'number' ? limitRange(input.a, 1) : 1; } else if (matchFormat('hsl')) { this.fromHsl(input); } else if (matchFormat('hsv')) { this.fromHsv(input); } else { throw new Error('@ant-design/fast-color: unsupported input ' + JSON.stringify(input)); } } // ======================= Setter ======================= setR(value) { return this._sc('r', value); } setG(value) { return this._sc('g', value); } setB(value) { return this._sc('b', value); } setA(value) { return this._sc('a', value, 1); } setHue(value) { const hsv = this.toHsv(); hsv.h = value; return this._c(hsv); } // ======================= Getter ======================= /** * Returns the perceived luminance of a color, from 0-1. * @see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef */ getLuminance() { function adjustGamma(raw) { const val = raw / 255; return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4); } const R = adjustGamma(this.r); const G = adjustGamma(this.g); const B = adjustGamma(this.b); return 0.2126 * R + 0.7152 * G + 0.0722 * B; } getHue() { if (typeof this._h === 'undefined') { const delta = this.getMax() - this.getMin(); if (delta === 0) { this._h = 0; } else { this._h = round(60 * (this.r === this.getMax() ? (this.g - this.b) / delta + (this.g < this.b ? 6 : 0) : this.g === this.getMax() ? (this.b - this.r) / delta + 2 : (this.r - this.g) / delta + 4)); } } return this._h; } getSaturation() { if (typeof this._s === 'undefined') { const delta = this.getMax() - this.getMin(); if (delta === 0) { this._s = 0; } else { this._s = delta / this.getMax(); } } return this._s; } getLightness() { if (typeof this._l === 'undefined') { this._l = (this.getMax() + this.getMin()) / 510; } return this._l; } getValue() { if (typeof this._v === 'undefined') { this._v = this.getMax() / 255; } return this._v; } /** * Returns the perceived brightness of the color, from 0-255. * Note: this is not the b of HSB * @see http://www.w3.org/TR/AERT#color-contrast */ getBrightness() { if (typeof this._brightness === 'undefined') { this._brightness = (this.r * 299 + this.g * 587 + this.b * 114) / 1000; } return this._brightness; } // ======================== Func ======================== darken(amount = 10) { const h = this.getHue(); const s = this.getSaturation(); let l = this.getLightness() - amount / 100; if (l < 0) { l = 0; } return this._c({ h, s, l, a: this.a }); } lighten(amount = 10) { const h = this.getHue(); const s = this.getSaturation(); let l = this.getLightness() + amount / 100; if (l > 1) { l = 1; } return this._c({ h, s, l, a: this.a }); } /** * Mix the current color a given amount with another color, from 0 to 100. * 0 means no mixing (return current color). */ mix(input, amount = 50) { const color = this._c(input); const p = amount / 100; const calc = key => (color[key] - this[key]) * p + this[key]; const rgba = { r: round(calc('r')), g: round(calc('g')), b: round(calc('b')), a: round(calc('a') * 100) / 100 }; return this._c(rgba); } /** * Mix the color with pure white, from 0 to 100. * Providing 0 will do nothing, providing 100 will always return white. */ tint(amount = 10) { return this.mix({ r: 255, g: 255, b: 255, a: 1 }, amount); } /** * Mix the color with pure black, from 0 to 100. * Providing 0 will do nothing, providing 100 will always return black. */ shade(amount = 10) { return this.mix({ r: 0, g: 0, b: 0, a: 1 }, amount); } onBackground(background) { const bg = this._c(background); const alpha = this.a + bg.a * (1 - this.a); const calc = key => { return round((this[key] * this.a + bg[key] * bg.a * (1 - this.a)) / alpha); }; return this._c({ r: calc('r'), g: calc('g'), b: calc('b'), a: alpha }); } // ======================= Status ======================= isDark() { return this.getBrightness() < 128; } isLight() { return this.getBrightness() >= 128; } // ======================== MISC ======================== equals(other) { return this.r === other.r && this.g === other.g && this.b === other.b && this.a === other.a; } clone() { return this._c(this); } // ======================= Format ======================= toHexString() { let hex = '#'; const rHex = (this.r || 0).toString(16); hex += rHex.length === 2 ? rHex : '0' + rHex; const gHex = (this.g || 0).toString(16); hex += gHex.length === 2 ? gHex : '0' + gHex; const bHex = (this.b || 0).toString(16); hex += bHex.length === 2 ? bHex : '0' + bHex; if (typeof this.a === 'number' && this.a >= 0 && this.a < 1) { const aHex = round(this.a * 255).toString(16); hex += aHex.length === 2 ? aHex : '0' + aHex; } return hex; } /** CSS support color pattern */ toHsl() { return { h: this.getHue(), s: this.getSaturation(), l: this.getLightness(), a: this.a }; } /** CSS support color pattern */ toHslString() { const h = this.getHue(); const s = round(this.getSaturation() * 100); const l = round(this.getLightness() * 100); return this.a !== 1 ? `hsla(${h},${s}%,${l}%,${this.a})` : `hsl(${h},${s}%,${l}%)`; } /** Same as toHsb */ toHsv() { return { h: this.getHue(), s: this.getSaturation(), v: this.getValue(), a: this.a }; } toRgb() { return { r: this.r, g: this.g, b: this.b, a: this.a }; } toRgbString() { return this.a !== 1 ? `rgba(${this.r},${this.g},${this.b},${this.a})` : `rgb(${this.r},${this.g},${this.b})`; } toString() { return this.toRgbString(); } // ====================== Privates ====================== /** Return a new FastColor object with one channel changed */ _sc(rgb, value, max) { const clone = this.clone(); clone[rgb] = limitRange(value, max); return clone; } _c(input) { return new this.constructor(input); } getMax() { if (typeof this._max === 'undefined') { this._max = Math.max(this.r, this.g, this.b); } return this._max; } getMin() { if (typeof this._min === 'undefined') { this._min = Math.min(this.r, this.g, this.b); } return this._min; } fromHexString(trimStr) { const withoutPrefix = trimStr.replace('#', ''); function connectNum(index1, index2) { return parseInt(withoutPrefix[index1] + withoutPrefix[index2 || index1], 16); } if (withoutPrefix.length < 6) { // #rgb or #rgba this.r = connectNum(0); this.g = connectNum(1); this.b = connectNum(2); this.a = withoutPrefix[3] ? connectNum(3) / 255 : 1; } else { // #rrggbb or #rrggbbaa this.r = connectNum(0, 1); this.g = connectNum(2, 3); this.b = connectNum(4, 5); this.a = withoutPrefix[6] ? connectNum(6, 7) / 255 : 1; } } fromHsl({ h, s, l, a }) { this._h = h % 360; this._s = s; this._l = l; this.a = typeof a === 'number' ? a : 1; if (s <= 0) { const rgb = round(l * 255); this.r = rgb; this.g = rgb; this.b = rgb; } let r = 0, g = 0, b = 0; const huePrime = h / 60; const chroma = (1 - Math.abs(2 * l - 1)) * s; const secondComponent = chroma * (1 - Math.abs(huePrime % 2 - 1)); if (huePrime >= 0 && huePrime < 1) { r = chroma; g = secondComponent; } else if (huePrime >= 1 && huePrime < 2) { r = secondComponent; g = chroma; } else if (huePrime >= 2 && huePrime < 3) { g = chroma; b = secondComponent; } else if (huePrime >= 3 && huePrime < 4) { g = secondComponent; b = chroma; } else if (huePrime >= 4 && huePrime < 5) { r = secondComponent; b = chroma; } else if (huePrime >= 5 && huePrime < 6) { r = chroma; b = secondComponent; } const lightnessModification = l - chroma / 2; this.r = round((r + lightnessModification) * 255); this.g = round((g + lightnessModification) * 255); this.b = round((b + lightnessModification) * 255); } fromHsv({ h, s, v, a }) { this._h = h % 360; this._s = s; this._v