mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 11:01:21 +08:00
feat(css): Add HSL/HSLA support (#7730)
This commit is contained in:

committed by
Manol Donev

parent
f438ad7d69
commit
3cabdde05f
@ -1,6 +1,7 @@
|
|||||||
import * as definition from ".";
|
import * as definition from ".";
|
||||||
import * as types from "../utils/types";
|
import * as types from "../utils/types";
|
||||||
import * as knownColors from "./known-colors";
|
import * as knownColors from "./known-colors";
|
||||||
|
import { convertHSLToRGBColor } from "tns-core-modules/css/parser";
|
||||||
|
|
||||||
const SHARP = "#";
|
const SHARP = "#";
|
||||||
const HEX_REGEX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)|(^#[0-9A-F]{8}$)/i;
|
const HEX_REGEX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)|(^#[0-9A-F]{8}$)/i;
|
||||||
@ -18,6 +19,8 @@ export class Color implements definition.Color {
|
|||||||
if (types.isString(arg)) {
|
if (types.isString(arg)) {
|
||||||
if (isRgbOrRgba(arg)) {
|
if (isRgbOrRgba(arg)) {
|
||||||
this._argb = argbFromRgbOrRgba(arg);
|
this._argb = argbFromRgbOrRgba(arg);
|
||||||
|
} else if (isHslOrHsla(arg)) {
|
||||||
|
this._argb = argbFromHslOrHsla(arg);
|
||||||
} else if (knownColors.isKnownName(arg)) {
|
} else if (knownColors.isKnownName(arg)) {
|
||||||
// The parameter is a known color name
|
// The parameter is a known color name
|
||||||
const hex = knownColors.getKnownColor(arg);
|
const hex = knownColors.getKnownColor(arg);
|
||||||
@ -127,7 +130,7 @@ export class Color implements definition.Color {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HEX_REGEX.test(value) || isRgbOrRgba(value);
|
return HEX_REGEX.test(value) || isRgbOrRgba(value) || isHslOrHsla(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _componentToHex(component: number): string {
|
private _componentToHex(component: number): string {
|
||||||
@ -162,33 +165,58 @@ function isRgbOrRgba(value: string): boolean {
|
|||||||
return (toLower.indexOf("rgb(") === 0 || toLower.indexOf("rgba(") === 0) && toLower.indexOf(")") === (toLower.length - 1);
|
return (toLower.indexOf("rgb(") === 0 || toLower.indexOf("rgba(") === 0) && toLower.indexOf(")") === (toLower.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function argbFromRgbOrRgba(value: string): number {
|
function isHslOrHsla(value: string): boolean {
|
||||||
const toLower = value.toLowerCase();
|
const toLower = value.toLowerCase();
|
||||||
const parts = toLower.replace("rgba(", "").replace("rgb(", "").replace(")", "").trim().split(",");
|
|
||||||
|
|
||||||
let r = 255;
|
return (toLower.indexOf("hsl(") === 0 || toLower.indexOf("hsla(") === 0) && toLower.indexOf(")") === (toLower.length - 1);
|
||||||
let g = 255;
|
}
|
||||||
let b = 255;
|
|
||||||
|
function parseColorWithAlpha(value: string): any {
|
||||||
|
const toLower = value.toLowerCase();
|
||||||
|
const parts = toLower.replace(/(rgb|hsl)a?\(/, "")
|
||||||
|
.replace(")", "")
|
||||||
|
.trim().split(",");
|
||||||
|
|
||||||
|
let f = 255;
|
||||||
|
let s = 255;
|
||||||
|
let t = 255;
|
||||||
let a = 255;
|
let a = 255;
|
||||||
|
|
||||||
if (parts[0]) {
|
if (parts[0]) {
|
||||||
r = parseInt(parts[0].trim());
|
f = parseInt(parts[0].trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parts[1]) {
|
if (parts[1]) {
|
||||||
g = parseInt(parts[1].trim());
|
s = parseInt(parts[1].trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parts[2]) {
|
if (parts[2]) {
|
||||||
b = parseInt(parts[2].trim());
|
t = parseInt(parts[2].trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parts[3]) {
|
if (parts[3]) {
|
||||||
a = Math.round(parseFloat(parts[3].trim()) * 255);
|
a = Math.round(parseFloat(parts[3].trim()) * 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { f, s, t, a };
|
||||||
|
}
|
||||||
|
|
||||||
|
function argbFromRgbOrRgba(value: string): number {
|
||||||
|
const { f: r, s: g, t: b, a } = parseColorWithAlpha(value);
|
||||||
|
|
||||||
return (a & 0xFF) * 0x01000000
|
return (a & 0xFF) * 0x01000000
|
||||||
+ (r & 0xFF) * 0x00010000
|
+ (r & 0xFF) * 0x00010000
|
||||||
+ (g & 0xFF) * 0x00000100
|
+ (g & 0xFF) * 0x00000100
|
||||||
+ (b & 0xFF) * 0x00000001;
|
+ (b & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
function argbFromHslOrHsla(value: string): number {
|
||||||
|
const { f: h, s: s, t: l, a } = parseColorWithAlpha(value);
|
||||||
|
|
||||||
|
const { r, g, b } = convertHSLToRGBColor(h, s, l);
|
||||||
|
|
||||||
|
return (a & 0xFF) * 0x01000000
|
||||||
|
+ (r & 0xFF) * 0x00010000
|
||||||
|
+ (g & 0xFF) * 0x00000100
|
||||||
|
+ (b & 0xFF);
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ export function parseHexColor(text: string, start: number = 0): Parsed<ARGB> {
|
|||||||
|
|
||||||
function rgbaToArgbNumber(r: number, g: number, b: number, a: number = 1): number | undefined {
|
function rgbaToArgbNumber(r: number, g: number, b: number, a: number = 1): number | undefined {
|
||||||
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 1) {
|
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 1) {
|
||||||
return (Math.round(a * 0xFF) * 0x01000000) + (r * 0x010000) + (g * 0x000100) + (b * 0x000001);
|
return (Math.round(a * 0xFF) * 0x01000000) + (r * 0x010000) + (g * 0x000100) + b;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -116,6 +116,67 @@ export function parseRGBAColor(text: string, start: number = 0): Parsed<ARGB> {
|
|||||||
return { start, end, value };
|
return { start, end, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertHSLToRGBColor(hue: number, saturation: number, lightness: number): { r: number; g: number; b: number; } {
|
||||||
|
// Per formula it will be easier if hue is divided to 60° and saturation to 100 beforehand
|
||||||
|
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
|
||||||
|
hue /= 60;
|
||||||
|
lightness /= 100;
|
||||||
|
|
||||||
|
let chroma = (1 - Math.abs(2 * lightness - 1)) * saturation / 100,
|
||||||
|
X = chroma * (1 - Math.abs(hue % 2 - 1)),
|
||||||
|
// Add lightness match to all RGB components beforehand
|
||||||
|
{ m: r, m: g, m: b } = { m: lightness - chroma / 2 };
|
||||||
|
|
||||||
|
if (0 <= hue && hue < 1) { r += chroma; g += X; }
|
||||||
|
else if (hue < 2) { r += X; g += chroma; }
|
||||||
|
else if (hue < 3) { g += chroma; b += X; }
|
||||||
|
else if (hue < 4) { g += X; b += chroma; }
|
||||||
|
else if (hue < 5) { r += X; b += chroma; }
|
||||||
|
else if (hue < 6) { r += chroma; b += X; }
|
||||||
|
|
||||||
|
return {
|
||||||
|
r: Math.round(r * 0xFF),
|
||||||
|
g: Math.round(g * 0xFF),
|
||||||
|
b: Math.round(b * 0xFF)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function hslaToArgbNumber(h: number, s: number, l: number, a: number = 1): number | undefined {
|
||||||
|
let { r, g, b } = convertHSLToRGBColor(h, s, l);
|
||||||
|
|
||||||
|
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 1) {
|
||||||
|
return (Math.round(a * 0xFF) * 0x01000000) + (r * 0x010000) + (g * 0x000100) + b;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hslColorRegEx = /\s*(hsl\(\s*([\d.]*)\s*,\s*([\d.]*)%\s*,\s*([\d.]*)%\s*\))/gy;
|
||||||
|
export function parseHSLColor(text: string, start: number = 0): Parsed<ARGB> {
|
||||||
|
hslColorRegEx.lastIndex = start;
|
||||||
|
const result = hslColorRegEx.exec(text);
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const end = hslColorRegEx.lastIndex;
|
||||||
|
const value = result[1] && hslaToArgbNumber(parseFloat(result[2]), parseFloat(result[3]), parseFloat(result[4]));
|
||||||
|
|
||||||
|
return { start, end, value };
|
||||||
|
}
|
||||||
|
|
||||||
|
const hslaColorRegEx = /\s*(hsla\(\s*([\d.]*)\s*,\s*([\d.]*)%\s*,\s*([\d.]*)%\s*,\s*([01]?\.?\d*)\s*\))/gy;
|
||||||
|
export function parseHSLAColor(text: string, start: number = 0): Parsed<ARGB> {
|
||||||
|
hslaColorRegEx.lastIndex = start;
|
||||||
|
const result = hslaColorRegEx.exec(text);
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const end = hslaColorRegEx.lastIndex;
|
||||||
|
const value = hslaToArgbNumber(parseFloat(result[2]), parseFloat(result[3]), parseFloat(result[4]), parseFloat(result[5]));
|
||||||
|
|
||||||
|
return { start, end, value };
|
||||||
|
}
|
||||||
|
|
||||||
export enum colors {
|
export enum colors {
|
||||||
transparent = 0x00000000,
|
transparent = 0x00000000,
|
||||||
aliceblue = 0xFFF0F8FF,
|
aliceblue = 0xFFF0F8FF,
|
||||||
@ -280,7 +341,12 @@ export function parseColorKeyword(value, start: number, keyword = parseKeyword(v
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseColor(value: string, start: number = 0, keyword = parseKeyword(value, start)): Parsed<ARGB> {
|
export function parseColor(value: string, start: number = 0, keyword = parseKeyword(value, start)): Parsed<ARGB> {
|
||||||
return parseHexColor(value, start) || parseColorKeyword(value, start, keyword) || parseRGBColor(value, start) || parseRGBAColor(value, start);
|
return parseHexColor(value, start) ||
|
||||||
|
parseColorKeyword(value, start, keyword) ||
|
||||||
|
parseRGBColor(value, start) ||
|
||||||
|
parseRGBAColor(value, start) ||
|
||||||
|
parseHSLColor(value, start) ||
|
||||||
|
parseHSLAColor(value, start);
|
||||||
}
|
}
|
||||||
|
|
||||||
const keywordRegEx = /\s*([a-z][\w\-]*)\s*/giy;
|
const keywordRegEx = /\s*([a-z][\w\-]*)\s*/giy;
|
||||||
|
@ -56,6 +56,8 @@ describe("css", () => {
|
|||||||
test(parseColor, " #85456789 ", { start: 0, end: 12, value: 0x85456789 });
|
test(parseColor, " #85456789 ", { start: 0, end: 12, value: 0x85456789 });
|
||||||
test(parseColor, " rgb(255, 8, 128) ", { start: 0, end: 18, value: 0xFFFF0880 });
|
test(parseColor, " rgb(255, 8, 128) ", { start: 0, end: 18, value: 0xFFFF0880 });
|
||||||
test(parseColor, " rgba(255, 8, 128, 0.5) ", { start: 0, end: 24, value: 0x80FF0880 });
|
test(parseColor, " rgba(255, 8, 128, 0.5) ", { start: 0, end: 24, value: 0x80FF0880 });
|
||||||
|
test(parseColor, " hsl(330.9, 100%, 51.6%) ", { start: 0, end: 25, value: 0xFFFF0880 });
|
||||||
|
test(parseColor, " hsla(330.9, 100%, 51.6%, 0.5) ", { start: 0, end: 31, value: 0x80FF0880 });
|
||||||
test(parseColor, "#FF0000 url(lucky.gif)", 8, null);
|
test(parseColor, "#FF0000 url(lucky.gif)", 8, null);
|
||||||
test(parseColor, "url(lucky.gif) #FF0000 repeat", 15, { start: 15, end: 23, value: 0xFFFF0000 });
|
test(parseColor, "url(lucky.gif) #FF0000 repeat", 15, { start: 15, end: 23, value: 0xFFFF0000 });
|
||||||
});
|
});
|
||||||
@ -175,11 +177,11 @@ describe("css", () => {
|
|||||||
test(parseSelector, `[src ${attributeTest} "val"]`, { start: 0, end: 12 + attributeTest.length, value: [[[{ type: "[]", property: "src", test: attributeTest, value: "val"}], undefined]]});
|
test(parseSelector, `[src ${attributeTest} "val"]`, { start: 0, end: 12 + attributeTest.length, value: [[[{ type: "[]", property: "src", test: attributeTest, value: "val"}], undefined]]});
|
||||||
});
|
});
|
||||||
test(parseSelector, "listview > .image", { start: 0, end: 17, value: [
|
test(parseSelector, "listview > .image", { start: 0, end: 17, value: [
|
||||||
[[{ type: "", identifier: "listview"}], ">"],
|
[[{ type: "", identifier: "listview"}], ">"],
|
||||||
[[{ type: ".", identifier: "image"}], undefined]
|
[[{ type: ".", identifier: "image"}], undefined]
|
||||||
]});
|
]});
|
||||||
test(parseSelector, "listview .image", { start: 0, end: 16, value: [
|
test(parseSelector, "listview .image", { start: 0, end: 16, value: [
|
||||||
[[{ type: "", identifier: "listview"}], " "],
|
[[{ type: "", identifier: "listview"}], " "],
|
||||||
[[{ type: ".", identifier: "image"}], undefined]
|
[[{ type: ".", identifier: "image"}], undefined]
|
||||||
]});
|
]});
|
||||||
test(parseSelector, "button:hover", { start: 0, end: 12, value: [[[{ type: "", identifier: "button" }, { type: ":", identifier: "hover"}], undefined]]});
|
test(parseSelector, "button:hover", { start: 0, end: 12, value: [[[{ type: "", identifier: "button" }, { type: ":", identifier: "hover"}], undefined]]});
|
||||||
|
Reference in New Issue
Block a user