Merge pull request #4267 from dpalou/MOBILE-4653
MOBILE-4653 color: Validate colors and handle alphamain
commit
4d743dd7e7
|
@ -42,11 +42,25 @@ export enum CoreIonicColorNames {
|
||||||
*/
|
*/
|
||||||
export class CoreColors {
|
export class CoreColors {
|
||||||
|
|
||||||
|
protected static readonly BLACK_TRANSPARENT_COLORS =
|
||||||
|
['rgba(0, 0, 0, 0)', 'transparent', '#00000000', '#0000', 'hsl(0, 0%, 0%, 0)'];
|
||||||
|
|
||||||
// Avoid creating singleton instances.
|
// Avoid creating singleton instances.
|
||||||
private constructor() {
|
private constructor() {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a color is valid.
|
||||||
|
* Accepted formats are rgb, rgba, hsl, hsla, hex and named colors.
|
||||||
|
*
|
||||||
|
* @param color Color in any format.
|
||||||
|
* @returns Whether color is valid.
|
||||||
|
*/
|
||||||
|
static isValid(color: string): boolean {
|
||||||
|
return CoreColors.getColorRGBA(color).length >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns better contrast color.
|
* Returns better contrast color.
|
||||||
*
|
*
|
||||||
|
@ -58,23 +72,30 @@ export class CoreColors {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the same color % darker.
|
* Returns the same color % darker. Returned color is always hex, unless the color isn't valid.
|
||||||
*
|
*
|
||||||
* @param color Color to get darker.
|
* @param color Color to get darker.
|
||||||
* @returns Darker Hex RGB color.
|
* @returns Darker Hex RGB color.
|
||||||
*/
|
*/
|
||||||
static darker(color: string, percent: number = 48): string {
|
static darker(color: string, percent: number = 48): string {
|
||||||
const inversePercent = 1 - (percent / 100);
|
const inversePercent = 1 - (percent / 100);
|
||||||
const components = CoreColors.hexToRGB(color);
|
|
||||||
components.red = Math.floor(components.red * inversePercent);
|
|
||||||
components.green = Math.floor(components.green * inversePercent);
|
|
||||||
components.blue = Math.floor(components.blue * inversePercent);
|
|
||||||
|
|
||||||
return CoreColors.RGBToHex(components);
|
const rgba = CoreColors.getColorRGBA(color);
|
||||||
|
if (rgba.length < 3) {
|
||||||
|
return color; // Color not valid, return original value.
|
||||||
|
}
|
||||||
|
|
||||||
|
const red = Math.floor(rgba[0] * inversePercent);
|
||||||
|
const green = Math.floor(rgba[1] * inversePercent);
|
||||||
|
const blue = Math.floor(rgba[2] * inversePercent);
|
||||||
|
|
||||||
|
return rgba[3] !== undefined ?
|
||||||
|
CoreColors.getColorHex(`rgba(${red}, ${green}, ${blue}, ${rgba[3]})`) :
|
||||||
|
CoreColors.getColorHex(`rgb(${red}, ${green}, ${blue})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the same color % lighter.
|
* Returns the same color % lighter. Returned color is always hex, unless the color isn't valid.
|
||||||
*
|
*
|
||||||
* @param color Color to get lighter.
|
* @param color Color to get lighter.
|
||||||
* @returns Lighter Hex RGB color.
|
* @returns Lighter Hex RGB color.
|
||||||
|
@ -83,12 +104,18 @@ export class CoreColors {
|
||||||
percent = percent / 100;
|
percent = percent / 100;
|
||||||
const inversePercent = 1 - percent;
|
const inversePercent = 1 - percent;
|
||||||
|
|
||||||
const components = CoreColors.hexToRGB(color);
|
const rgba = CoreColors.getColorRGBA(color);
|
||||||
components.red = Math.floor(255 * percent + components.red * inversePercent);
|
if (rgba.length < 3) {
|
||||||
components.green = Math.floor(255 * percent + components.green * inversePercent);
|
return color; // Color not valid, return original value.
|
||||||
components.blue = Math.floor(255 * percent + components.blue * inversePercent);
|
}
|
||||||
|
|
||||||
return CoreColors.RGBToHex(components);
|
const red = Math.floor(255 * percent + rgba[0] * inversePercent);
|
||||||
|
const green = Math.floor(255 * percent + rgba[1] * inversePercent);
|
||||||
|
const blue = Math.floor(255 * percent + rgba[2] * inversePercent);
|
||||||
|
|
||||||
|
return rgba[3] !== undefined ?
|
||||||
|
CoreColors.getColorHex(`rgba(${red}, ${green}, ${blue}, ${rgba[3]})`) :
|
||||||
|
CoreColors.getColorHex(`rgb(${red}, ${green}, ${blue})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,19 +126,24 @@ export class CoreColors {
|
||||||
*/
|
*/
|
||||||
static getColorHex(color: string): string {
|
static getColorHex(color: string): string {
|
||||||
const rgba = CoreColors.getColorRGBA(color);
|
const rgba = CoreColors.getColorRGBA(color);
|
||||||
if (rgba.length === 0) {
|
if (rgba.length < 3) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const hex = [0,1,2].map(
|
let hex = [0,1,2].map(
|
||||||
(idx) => CoreColors.componentToHex(rgba[idx]),
|
(idx) => CoreColors.componentToHex(rgba[idx]),
|
||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
|
if (rgba.length > 3) {
|
||||||
|
hex += CoreColors.componentToHex(Math.round(rgba[3] * 255));
|
||||||
|
}
|
||||||
|
|
||||||
return '#' + hex;
|
return '#' + hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns RGBA color from any color format.
|
* Returns RGBA color from any color format.
|
||||||
|
* Only works with RGB, RGBA, HSL, HSLA, hex and named colors.
|
||||||
*
|
*
|
||||||
* @param color Color in any format.
|
* @param color Color in any format.
|
||||||
* @returns Red, green, blue and alpha.
|
* @returns Red, green, blue and alpha.
|
||||||
|
@ -119,15 +151,28 @@ export class CoreColors {
|
||||||
static getColorRGBA(color: string): number[] {
|
static getColorRGBA(color: string): number[] {
|
||||||
if (!color.match(/rgba?\(.*\)/)) {
|
if (!color.match(/rgba?\(.*\)/)) {
|
||||||
// Convert the color to RGB format.
|
// Convert the color to RGB format.
|
||||||
|
// Use backgroundColor instead of color because it detects invalid colors like rgb(0, 80) or #0F.
|
||||||
|
const originalColor = color;
|
||||||
const d = document.createElement('span');
|
const d = document.createElement('span');
|
||||||
d.style.color = color;
|
d.style.backgroundColor = color;
|
||||||
document.body.appendChild(d);
|
document.body.appendChild(d);
|
||||||
|
|
||||||
color = getComputedStyle(d).color;
|
color = getComputedStyle(d).backgroundColor;
|
||||||
document.body.removeChild(d);
|
document.body.removeChild(d);
|
||||||
|
|
||||||
|
// Check that the color is valid. Some invalid colors return rgba(0, 0, 0, 0).
|
||||||
|
if (
|
||||||
|
!color.match(/rgba?\(.*\)/) ||
|
||||||
|
(color === 'rgba(0, 0, 0, 0)' && !CoreColors.BLACK_TRANSPARENT_COLORS.includes(originalColor))
|
||||||
|
) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const matches = color.match(/\d+[^.]|\d*\.\d*/g) || [];
|
const matches = color.match(/\d+(\.\d+)?|\.\d+/g) || [];
|
||||||
|
if (matches.length < 3) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return matches.map((a, index) => index < 3 ? parseInt(a, 10) : parseFloat(a));
|
return matches.map((a, index) => index < 3 ? parseInt(a, 10) : parseFloat(a));
|
||||||
}
|
}
|
||||||
|
@ -139,9 +184,12 @@ export class CoreColors {
|
||||||
* @returns Luma number based on SMPTE C, Rec. 709 weightings.
|
* @returns Luma number based on SMPTE C, Rec. 709 weightings.
|
||||||
*/
|
*/
|
||||||
protected static luma(color: string): number {
|
protected static luma(color: string): number {
|
||||||
const rgb = CoreColors.hexToRGB(color);
|
const rgba = CoreColors.getColorRGBA(color);
|
||||||
|
if (rgba.length < 3) {
|
||||||
|
return 0; // Color not valid.
|
||||||
|
}
|
||||||
|
|
||||||
return (rgb.red * 0.2126) + (rgb.green * 0.7152) + (rgb.blue * 0.0722);
|
return (rgba[0] * 0.2126) + (rgba[1] * 0.7152) + (rgba[2] * 0.0722);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -149,39 +197,19 @@ export class CoreColors {
|
||||||
*
|
*
|
||||||
* @param color Hexadec RGB Color.
|
* @param color Hexadec RGB Color.
|
||||||
* @returns RGB color components.
|
* @returns RGB color components.
|
||||||
|
* @deprecated since 5.0. Use getColorRGBA instead.
|
||||||
*/
|
*/
|
||||||
static hexToRGB(color: string): ColorComponents {
|
static hexToRGB(color: string): ColorComponents {
|
||||||
if (color.charAt(0) == '#') {
|
const rgba = CoreColors.getColorRGBA(color);
|
||||||
color = color.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color.length === 3) {
|
|
||||||
color = color.charAt(0) + color.charAt(0) + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2);
|
|
||||||
} else if (color.length !== 6) {
|
|
||||||
throw('Invalid hex color: ' + color);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
red: parseInt(color.substring(0, 2), 16),
|
red: rgba[0] ?? 0,
|
||||||
green: parseInt(color.substring(2, 4), 16),
|
green: rgba[1] ?? 0,
|
||||||
blue: parseInt(color.substring(4, 6), 16),
|
blue: rgba[2] ?? 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts RGB components to Hex string.
|
|
||||||
*
|
|
||||||
* @param color Color components.
|
|
||||||
* @returns RGB color in string.
|
|
||||||
*/
|
|
||||||
protected static RGBToHex(color: ColorComponents): string {
|
|
||||||
return '#' + CoreColors.componentToHex(color.red) +
|
|
||||||
CoreColors.componentToHex(color.green) +
|
|
||||||
CoreColors.componentToHex(color.blue);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a color component from decimal to hexadec.
|
* Converts a color component from decimal to hexadec.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,6 +16,25 @@ import { CoreColors } from '@singletons/colors';
|
||||||
|
|
||||||
describe('CoreColors singleton', () => {
|
describe('CoreColors singleton', () => {
|
||||||
|
|
||||||
|
it('checks if a color is valid', () => {
|
||||||
|
expect(CoreColors.isValid('')).toBe(false);
|
||||||
|
expect(CoreColors.isValid('#000000')).toBe(true);
|
||||||
|
expect(CoreColors.isValid('#00000000')).toBe(true);
|
||||||
|
expect(CoreColors.isValid('#000')).toBe(true);
|
||||||
|
expect(CoreColors.isValid('#0000')).toBe(true);
|
||||||
|
expect(CoreColors.isValid('#00')).toBe(false);
|
||||||
|
expect(CoreColors.isValid('#00000')).toBe(false);
|
||||||
|
expect(CoreColors.isValid('rgb(0,0,0)')).toBe(true);
|
||||||
|
expect(CoreColors.isValid('rgba(0,0,0,0)')).toBe(true);
|
||||||
|
expect(CoreColors.isValid('rgb(0,0)')).toBe(false);
|
||||||
|
expect(CoreColors.isValid('hsl(0, 100%, 0%)')).toBe(true);
|
||||||
|
expect(CoreColors.isValid('hsla(0, 100%, 0%, 0)')).toBe(true);
|
||||||
|
expect(CoreColors.isValid('hsl(0, 100%)')).toBe(false);
|
||||||
|
|
||||||
|
// @todo There are problems when testing color names (e.g. violet).
|
||||||
|
// They work fine in real browsers but not in unit tests.
|
||||||
|
});
|
||||||
|
|
||||||
it('determines if white contrast is better', () => {
|
it('determines if white contrast is better', () => {
|
||||||
expect(CoreColors.isWhiteContrastingBetter('#000000')).toBe(true);
|
expect(CoreColors.isWhiteContrastingBetter('#000000')).toBe(true);
|
||||||
expect(CoreColors.isWhiteContrastingBetter('#999999')).toBe(true);
|
expect(CoreColors.isWhiteContrastingBetter('#999999')).toBe(true);
|
||||||
|
@ -26,6 +45,12 @@ describe('CoreColors singleton', () => {
|
||||||
expect(CoreColors.isWhiteContrastingBetter('#0000ff')).toBe(true);
|
expect(CoreColors.isWhiteContrastingBetter('#0000ff')).toBe(true);
|
||||||
expect(CoreColors.isWhiteContrastingBetter('#ff00ff')).toBe(true);
|
expect(CoreColors.isWhiteContrastingBetter('#ff00ff')).toBe(true);
|
||||||
expect(CoreColors.isWhiteContrastingBetter('#ffff00')).toBe(false);
|
expect(CoreColors.isWhiteContrastingBetter('#ffff00')).toBe(false);
|
||||||
|
expect(CoreColors.isWhiteContrastingBetter('rgb(0, 0, 0)')).toBe(true);
|
||||||
|
expect(CoreColors.isWhiteContrastingBetter('rgb(153, 153, 153)')).toBe(true);
|
||||||
|
expect(CoreColors.isWhiteContrastingBetter('rgb(255, 255, 255)')).toBe(false);
|
||||||
|
expect(CoreColors.isWhiteContrastingBetter('hsl(0, 100%, 0%)')).toBe(true);
|
||||||
|
expect(CoreColors.isWhiteContrastingBetter('hsl(0, 0%, 60%)')).toBe(true);
|
||||||
|
expect(CoreColors.isWhiteContrastingBetter('hsl(0, 0%, 100%)')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('makes color darker', () => {
|
it('makes color darker', () => {
|
||||||
|
@ -33,6 +58,13 @@ describe('CoreColors singleton', () => {
|
||||||
expect(CoreColors.darker('#ffffff', 20)).toEqual('#cccccc');
|
expect(CoreColors.darker('#ffffff', 20)).toEqual('#cccccc');
|
||||||
expect(CoreColors.darker('#ffffff', 80)).toEqual('#323232');
|
expect(CoreColors.darker('#ffffff', 80)).toEqual('#323232');
|
||||||
expect(CoreColors.darker('#aabbcc', 40)).toEqual('#66707a');
|
expect(CoreColors.darker('#aabbcc', 40)).toEqual('#66707a');
|
||||||
|
expect(CoreColors.darker('rgb(255, 255, 255)', 50)).toEqual('#7f7f7f');
|
||||||
|
expect(CoreColors.darker('rgba(255, 255, 255, 0.5)', 50)).toEqual('#7f7f7f80');
|
||||||
|
expect(CoreColors.darker('hsl(0, 0%, 100%)', 50)).toEqual('#7f7f7f');
|
||||||
|
expect(CoreColors.darker('hsla(0, 0%, 100%, 0.8)', 50)).toEqual('#7f7f7fcc');
|
||||||
|
|
||||||
|
// Invalid colors return original value.
|
||||||
|
expect(CoreColors.darker('#fffff', 50)).toEqual('#fffff');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('makes color lighter', () => {
|
it('makes color lighter', () => {
|
||||||
|
@ -40,12 +72,22 @@ describe('CoreColors singleton', () => {
|
||||||
expect(CoreColors.lighter('#000000', 20)).toEqual('#333333');
|
expect(CoreColors.lighter('#000000', 20)).toEqual('#333333');
|
||||||
expect(CoreColors.lighter('#000000', 80)).toEqual('#cccccc');
|
expect(CoreColors.lighter('#000000', 80)).toEqual('#cccccc');
|
||||||
expect(CoreColors.lighter('#223344', 40)).toEqual('#7a848e');
|
expect(CoreColors.lighter('#223344', 40)).toEqual('#7a848e');
|
||||||
|
expect(CoreColors.lighter('rgb(0, 0, 0)', 50)).toEqual('#7f7f7f');
|
||||||
|
expect(CoreColors.lighter('rgba(0, 0, 0, 0.5)', 50)).toEqual('#7f7f7f80');
|
||||||
|
expect(CoreColors.lighter('hsl(0, 100%, 0%)', 50)).toEqual('#7f7f7f');
|
||||||
|
expect(CoreColors.lighter('hsla(0, 100%, 0%, 0.4)', 50)).toEqual('#7f7f7f66');
|
||||||
|
|
||||||
|
// Invalid colors return original value.
|
||||||
|
expect(CoreColors.darker('#00000', 50)).toEqual('#00000');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets color hex value', () => {
|
it('gets color hex value', () => {
|
||||||
expect(CoreColors.getColorHex('#123456')).toEqual('#123456');
|
expect(CoreColors.getColorHex('#123456')).toEqual('#123456');
|
||||||
|
expect(CoreColors.getColorHex('#12345680')).toEqual('#12345680');
|
||||||
expect(CoreColors.getColorHex('rgb(255, 100, 70)')).toEqual('#ff6446');
|
expect(CoreColors.getColorHex('rgb(255, 100, 70)')).toEqual('#ff6446');
|
||||||
expect(CoreColors.getColorHex('rgba(255, 100, 70, 0.5)')).toEqual('#ff6446');
|
expect(CoreColors.getColorHex('rgba(255, 100, 70, 0.5)')).toEqual('#ff644680');
|
||||||
|
expect(CoreColors.getColorHex('hsl(0, 0%, 60%)')).toEqual('#999999');
|
||||||
|
expect(CoreColors.getColorHex('hsla(0, 0%, 60%, 0.8)')).toEqual('#999999cc');
|
||||||
|
|
||||||
// @todo There are problems when testing color names (e.g. violet) or hsf colors.
|
// @todo There are problems when testing color names (e.g. violet) or hsf colors.
|
||||||
// They work fine in real browsers but not in unit tests.
|
// They work fine in real browsers but not in unit tests.
|
||||||
|
@ -53,31 +95,16 @@ describe('CoreColors singleton', () => {
|
||||||
|
|
||||||
it('gets color RGBA value', () => {
|
it('gets color RGBA value', () => {
|
||||||
expect(CoreColors.getColorRGBA('#123456')).toEqual([18, 52, 86]);
|
expect(CoreColors.getColorRGBA('#123456')).toEqual([18, 52, 86]);
|
||||||
|
expect(CoreColors.getColorRGBA('#123456cc')).toEqual([18, 52, 86, 0.8]);
|
||||||
expect(CoreColors.getColorRGBA('rgb(255, 100, 70)')).toEqual([255, 100, 70]);
|
expect(CoreColors.getColorRGBA('rgb(255, 100, 70)')).toEqual([255, 100, 70]);
|
||||||
expect(CoreColors.getColorRGBA('rgba(255, 100, 70, 0.5)')).toEqual([255, 100, 70, 0.5]);
|
expect(CoreColors.getColorRGBA('rgba(255, 100, 70, 0.5)')).toEqual([255, 100, 70, 0.5]);
|
||||||
|
expect(CoreColors.getColorRGBA('hsl(0, 0%, 60%)')).toEqual([153, 153, 153]);
|
||||||
|
expect(CoreColors.getColorRGBA('hsl(0, 0%, 60%, 0.3)')).toEqual([153, 153, 153, 0.3]);
|
||||||
|
|
||||||
// @todo There are problems when testing color names (e.g. violet) or hsf colors.
|
// @todo There are problems when testing color names (e.g. violet) or hsf colors.
|
||||||
// They work fine in real browsers but not in unit tests.
|
// They work fine in real browsers but not in unit tests.
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts hex to rgb', () => {
|
|
||||||
expect(CoreColors.hexToRGB('#000000')).toEqual({
|
|
||||||
red: 0,
|
|
||||||
green: 0,
|
|
||||||
blue: 0,
|
|
||||||
});
|
|
||||||
expect(CoreColors.hexToRGB('#ffffff')).toEqual({
|
|
||||||
red: 255,
|
|
||||||
green: 255,
|
|
||||||
blue: 255,
|
|
||||||
});
|
|
||||||
expect(CoreColors.hexToRGB('#aabbcc')).toEqual({
|
|
||||||
red: 170,
|
|
||||||
green: 187,
|
|
||||||
blue: 204,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('gets toolbar background color', () => {
|
it('gets toolbar background color', () => {
|
||||||
document.body.style.setProperty('--core-header-toolbar-background', '#aabbcc');
|
document.body.style.setProperty('--core-header-toolbar-background', '#aabbcc');
|
||||||
expect(CoreColors.getToolbarBackgroundColor()).toEqual('#aabbcc');
|
expect(CoreColors.getToolbarBackgroundColor()).toEqual('#aabbcc');
|
||||||
|
|
|
@ -6,6 +6,7 @@ For more information about upgrading, read the official documentation: https://m
|
||||||
|
|
||||||
- The logout process has been refactored, now it uses a logout page to trigger Angular guards. CoreSites.logout now uses this process, and CoreSites.logoutForRedirect is deprecated and shouldn't be used anymore.
|
- The logout process has been refactored, now it uses a logout page to trigger Angular guards. CoreSites.logout now uses this process, and CoreSites.logoutForRedirect is deprecated and shouldn't be used anymore.
|
||||||
- The parameters of treatDownloadedFile of plugin file handlers have changed. Now the third parameter is an object with all the optional parameters.
|
- The parameters of treatDownloadedFile of plugin file handlers have changed. Now the third parameter is an object with all the optional parameters.
|
||||||
|
- Some CoreColors functions have been refactored to handle alpha and to validate colors.
|
||||||
|
|
||||||
=== 4.5.0 ===
|
=== 4.5.0 ===
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue