10000 feat(css): Add HSL/HSLA support (#7730) · NativeScript/NativeScript@3cabdde · GitHub
[go: up one dir, main page]

Skip to content

Commit 3cabdde

Browse files
bundyomanoldonev
authored andcommitted
feat(css): Add HSL/HSLA support (#7730)
1 parent f438ad7 commit 3cabdde

File tree

3 files changed

+111
-15
lines changed

3 files changed

+111
-15
lines changed

tns-core-modules/color/color-common.ts

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import * as definition from ".";
1+
import * as definition from ".";
22
import * as types from "../utils/types";
33
import * as knownColors from "./known-colors";
4+
import { convertHSLToRGBColor } from "tns-core-modules/css/parser";
45

56
const SHARP = "#";
67
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 {
1819
if (types.isString(arg)) {
1920
if (isRgbOrRgba(arg)) {
2021
this._argb = argbFromRgbOrRgba(arg);
22+
} else if (isHslOrHsla(arg)) {
23+
this._argb = argbFromHslOrHsla(arg);
2124
} else if (knownColors.isKnownName(arg)) {
2225
// The parameter is a known color name
2326
const hex = knownColors.getKnownColor(arg);
@@ -127,7 +130,7 @@ export class Color implements definition.Color {
127130
return true;
128131
}
129132

130-
return HEX_REGEX.test(value) || isRgbOrRgba(value);
133+
return HEX_REGEX.test(value) || isRgbOrRgba(value) || isHslOrHsla(value);
131134
}
132135

133136
private _componentToHex(component: number): string {
@@ -162,33 +165,58 @@ function isRgbOrRgba(value: string): boolean {
162165
return (toLower.indexOf("rgb(") === 0 || toLower.indexOf("rgba(") === 0) && toLower.indexOf(")") === (toLower.length - 1);
163166
}
164167

165-
function argbFromRgbOrRgba(value: string): number {
168+
function isHslOrHsla(value: string): boolean {
169+
const toLower = value.toLowerCase();
170+
171+
return (toLower.indexOf("hsl(") === 0 || toLower.indexOf("hsla(") === 0) && toLower.indexOf(")") === (toLower.length - 1);
172+
}
173+
174+
function parseColorWithAlpha(value: string): any {
166175
const toLower = value.toLowerCase();
167-
const parts = toLower.replace("rgba(", "").replace("rgb(", "").replace(")", "").trim().split(",");
176+
const parts = toLower.replace(/(rgb|hsl)a?\(/, "")
177 B41A +
.replace(")", "")
178+
.trim().split(",");
168179

169-
let r = 255;
170-
let g = 255;
171-
let b = 255;
180+
let f = 255;
181+
let s = 255;
182+
let t = 255;
172183
let a = 255;
173184

174185
if (parts[0]) {
175-
r = parseInt(parts[0].trim());
186+
f = parseInt(parts[0].trim());
176187
}
177188

178189
if (parts[1]) {
179-
g = parseInt(parts[1].trim());
190+
s = parseInt(parts[1].trim());
180191
}
181192

182193
if (parts[2]) {
183-
b = parseInt(parts[2].trim());
194+
t = parseInt(parts[2].trim());
184195
}
185196

186197
if (parts[3]) {
187198
a = Math.round(parseFloat(parts[3].trim()) * 255);
188199
}
189200

201+
return { f, s, t, a };
202+
}
203+
204+
function argbFromRgbOrRgba(value: string): number {
205+
const { f: r, s: g, t: b, a } = parseColorWithAlpha(value);
206+
207+
return (a & 0xFF) * 0x01000000
208+
+ (r & 0xFF) * 0x00010000
209+
+ (g & 0xFF) * 0x00000100
210+
+ (b & 0xFF);
211+
}
212+
213+
function argbFromHslOrHsla(value: string): number {
214+
const { f: h, s: s, t: l, a } = parseColorWithAlpha(value);
215+
216+
const { r, g, b } = convertHSLToRGBColor(h, s, l);
217+
190218
return (a & 0xFF) * 0x01000000
191219
+ (r & 0xFF) * 0x00010000
192220
+ (g & 0xFF) * 0x00000100
193-
+ (b & 0xFF) * 0x00000001;
221+
+ (b & 0xFF);
194222
}

tns-core-modules/css/parser.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function parseHexColor(text: string, start: number = 0): Parsed<ARGB> {
8484

8585
function rgbaToArgbNumber(r: number, g: number, b: number, a: number = 1): number | undefined {
8686
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 1) {
87-
return (Math.round(a * 0xFF) * 0x01000000) + (r * 0x010000) + (g * 0x000100) + (b * 0x000001);
87+
return (Math.round(a * 0xFF) * 0x01000000) + (r * 0x010000) + (g * 0x000100) + b;
8888
} else {
8989
return null;
9090
}
@@ -116,6 +116,67 @@ export function parseRGBAColor(text: string, start: number = 0): Parsed<ARGB> {
116116
return { start, end, value };
117117
}
118118

119+
export function convertHSLToRGBColor(hue: number, saturation: number, lightness: number): { r: number; g: number; b: number; } {
120+
// Per formula it will be easier if hue is divided to 60° and saturation to 100 beforehand
121+
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
122+
hue /= 60;
123+
lightness /= 100;
124+
125+
let chroma = (1 - Math.abs(2 * lightness - 1)) * saturation / 100,
126+
X = chroma * (1 - Math.abs(hue % 2 - 1)),
127+
// Add lightness match to all RGB components beforehand
128+
{ m: r, m: g, m: b } = { m: lightness - chroma / 2 };
129+
130+
if (0 <= hue && hue < 1) { r += chroma; g += X; }
131+
else if (hue < 2) { r += X; g += chroma; }
132+
else if (hue < 3) { g += chroma; b += X; }
133+
else if (hue < 4) { g += X; b += chroma; }
134+
else if (hue < 5) { r += X; b += chroma; }
135+
else if (hue < 6) { r += chroma; b += X; }
136+
137+
return {
138+
r: Math.round(r * 0xFF),
139+
g: Math.round(g * 0xFF),
140+
b: Math.round(b * 0xFF)
141+
};
142+
}
143+
144+
function hslaToArgbNumber(h: number, s: number, l: number, a: number = 1): number | undefined {
145+
let { r, g, b } = convertHSLToRGBColor(h, s, l);
146+
147+
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 1) {
148+
return (Math.round(a * 0xFF) * 0x01000000) + (r * 0x010000) + (g * 0x000100) + b;
149+
} else {
150+
return null;
151+
}
152+
}
153+
154+
const hslColorRegEx = /\s*(hsl\(\s*([\d.]*)\s*,\s*([\d.]*)%\s*,\s*([\d.]*)%\s*\))/gy;
155+
export function parseHSLColor(text: string, start: number = 0): Parsed<ARGB> {
156+
hslColorRegEx.lastIndex = start;
157+
const result = hslColorRegEx.exec(text);
158+
if (!result) {
159+
return null;
160+
}
161+
const end = hslColorRegEx.lastIndex;
162+
const value = result[1] && hslaToArgbNumber(parseFloat(result[2]), parseFloat(result[3]), parseFloat(result[4]));
163+
164+
return { start, end, value };
165+
}
166+
167+
const hslaColorRegEx = /\s*(hsla\(\s*([\d.]*)\s*,\s*([\d.]*)%\s*,\s*([\d.]*)%\s*,\s*([01]?\.?\d*)\s*\))/gy;
168+
export function parseHSLAColor(text: string, start: number = 0): Parsed<ARGB> {
169+
hslaColorRegEx.lastIndex = start;
170+
const result = hslaColorRegEx.exec(text);
171+
if (!result) {
172+
return null;
173+
}
174+
const end = hslaColorRegEx.lastIndex;
175+
const value = hslaToArgbNumber(parseFloat(result[2]), parseFloat(result[3]), parseFloat(result[4]), parseFloat(result[5]));
176+
177+
return { start, end, value };
178+
}
179+
119180
export enum colors {
120181
transparent = 0x00000000,
121182
aliceblue = 0xFFF0F8FF,
@@ -280,7 +341,12 @@ export function parseColorKeyword(value, start: number, keyword = parseKeyword(v
280341
}
281342

282343
export function parseColor(value: string, start: number = 0, keyword = parseKeyword(value, start)): Parsed<ARGB> {
283-
return parseHexColor(value, start) || parseColorKeyword(value, start, keyword) || parseRGBColor(value, start) || parseRGBAColor(value, start);
344+
return parseHexColor(value, start) ||
345+
parseColorKeyword(value, start, keyword) ||
346+
parseRGBColor(value, start) ||
347+
parseRGBAColor(value, start) ||< 2851 /div>
348+
parseHSLColor(value, start) ||
349+
parseHSLAColor(value, start);
284350
}
285351

286352
const keywordRegEx = /\s*([a-z][\w\-]*)\s*/giy;

unit-tests/css/parser.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ describe("css", () => {
5656
test(parseColor, " #85456789 ", { start: 0, end: 12, value: 0x85456789 });
5757
test(parseColor, " rgb(255, 8, 128) ", { start: 0, end: 18, value: 0xFFFF0880 });
5858
test(parseColor, " rgba(255, 8, 128, 0.5) ", { start: 0, end: 24, value: 0x80FF0880 });
59+
test(parseColor, " hsl(330.9, 100%, 51.6%) ", { start: 0, end: 25, value: 0xFFFF0880 });
60+
test(parseColor, " hsla(330.9, 100%, 51.6%, 0.5) ", { start: 0, end: 31, value: 0x80FF0880 });
5961
test(parseColor, "#FF0000 url(lucky.gif)", 8, null);
6062
test(parseColor, "url(lucky.gif) #FF0000 repeat", 15, { start: 15, end: 23, value: 0xFFFF0000 });
6163
});
@@ -175,11 +177,11 @@ describe("css", () => {
175177
test(parseSelector, `[src ${attributeTest} "val"]`, { start: 0, end: 12 + attributeTest.length, value: [[[{ type: "[]", property: "src", test: attributeTest, value: "val"}], undefined]]});
176178
});
177179
test(parseSelector, "listview > .image", { start: 0, end: 17, value: [
178-
[[{ type: "", identifier: "listview"}], ">"],
180+
[[{ type: "", identifier: "listview"}], ">"],
179181
[[{ type: ".", identifier: "image"}], undefined]
180182
]});
181183
test(parseSelector, "listview .image", { start: 0, end: 16, value: [
182-
[[{ type: "", identifier: "listview"}], " "],
184+
[[{ type: "", identifier: "listview"}], " "],
183185
[[{ type: ".", identifier: "image"}], undefined]
184186
]});
185187
test(parseSelector, "button:hover", { start: 0, end: 12, value: [[[{ type: "", identifier: "button" }, { type: ":", identifier: "hover"}], undefined]]});

0 commit comments

Comments
 (0)
0