Skip to content

Commit f0018ed

Browse files
authored
Add support for #rgba, and #rrggbbaa rgba color parser (#3228)
* Add support for #rgba, #rrggbbaa, and rgba(rrr, ggg, bbb, a) to rgba color parser
1 parent 3aadb10 commit f0018ed

File tree

2 files changed

+40
-25
lines changed

2 files changed

+40
-25
lines changed

packages/@react-stately/color/src/Color.ts

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -81,42 +81,33 @@ abstract class Color implements IColor {
8181
}
8282
abstract getColorChannels(): [ColorChannel, ColorChannel, ColorChannel]
8383
}
84-
85-
const HEX_REGEX = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i;
86-
87-
// X = <negative/positive number with/without decimal places>
88-
// before/after a comma, 0 or more whitespaces are allowed
89-
// - rgb(X, X, X)
90-
// - rgba(X, X, X, X)
91-
const RGB_REGEX = /rgb\(([-+]?\d+(?:.\d+)?\s*,\s*[-+]?\d+(?:.\d+)?\s*,\s*[-+]?\d+(?:.\d+)?)\)|rgba\(([-+]?\d+(?:.\d+)?\s*,\s*[-+]?\d+(?:.\d+)?\s*,\s*[-+]?\d+(?:.\d+)?\s*,\s*[-+]?\d(.\d+)?)\)/;
92-
9384
class RGBColor extends Color {
9485
constructor(private red: number, private green: number, private blue: number, private alpha: number) {
9586
super();
9687
}
9788

98-
static parse(value: string): RGBColor | void {
99-
let m;
100-
if ((m = value.match(HEX_REGEX))) {
101-
if (m[1]) {
102-
let r = parseInt(m[1][0] + m[1][0], 16);
103-
let g = parseInt(m[1][1] + m[1][1], 16);
104-
let b = parseInt(m[1][2] + m[1][2], 16);
105-
return new RGBColor(r, g, b, 1);
106-
} else if (m[2]) {
107-
let r = parseInt(m[2][0] + m[2][1], 16);
108-
let g = parseInt(m[2][2] + m[2][3], 16);
109-
let b = parseInt(m[2][4] + m[2][5], 16);
110-
return new RGBColor(r, g, b, 1);
89+
static parse(value: string) {
90+
let colors = [];
91+
// matching #rgb, #rgba, #rrggbb, #rrggbbaa
92+
if (/^#[\da-f]+$/i.test(value) && [4, 5, 7, 9].includes(value.length)) {
93+
const values = (value.length < 6 ? value.replace(/[^#]/gi, '$&$&') : value).slice(1).split('');
94+
while (values.length > 0) {
95+
colors.push(parseInt(values.splice(0, 2).join(''), 16));
11196
}
97+
colors[3] = colors[3] !== undefined ? colors[3] / 255 : undefined;
11298
}
11399

114-
if ((m = value.match(RGB_REGEX))) {
115-
const [r, g, b, a] = (m[1] ?? m[2]).split(',').map(n => Number(n.trim()));
116-
return new RGBColor(clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255), clamp(a ?? 1, 0, 1));
100+
// matching rgb(rrr, ggg, bbb), rgba(rrr, ggg, bbb, 0.a)
101+
const match = value.match(/^rgba?\((.*)\)$/);
102+
if (match?.[1]) {
103+
colors = match[1].split(',').map(value => Number(value.trim()));
104+
colors = colors.map((num, i) => clamp(num, 0, i < 3 ? 255 : 1));
117105
}
106+
107+
return colors.length < 3 ? undefined : new RGBColor(colors[0], colors[1], colors[2], colors[3] ?? 1);
118108
}
119109

110+
120111
toString(format: ColorFormat | 'css') {
121112
switch (format) {
122113
case 'hex':

packages/@react-stately/color/test/Color.test.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ describe('Color', function () {
3030
expect(color.toString('css')).toBe('rgba(170, 187, 204, 1)');
3131
});
3232

33+
it('should parse a short hexa color', function () {
34+
let color = parseColor('#abc9');
35+
expect(color.getChannelValue('red')).toBe(170);
36+
expect(color.getChannelValue('green')).toBe(187);
37+
expect(color.getChannelValue('blue')).toBe(204);
38+
expect(color.getChannelValue('alpha')).toBe(0.6);
39+
expect(color.toString('hex')).toBe('#AABBCC');
40+
expect(color.toString('rgb')).toBe('rgb(170, 187, 204)');
41+
expect(color.toString('rgba')).toBe('rgba(170, 187, 204, 0.6)');
42+
expect(color.toString('css')).toBe('rgba(170, 187, 204, 0.6)');
43+
});
44+
3345
it('should parse a long hex color', function () {
3446
let color = parseColor('#abcdef');
3547
expect(color.getChannelValue('red')).toBe(171);
@@ -42,6 +54,18 @@ describe('Color', function () {
4254
expect(color.toString('css')).toBe('rgba(171, 205, 239, 1)');
4355
});
4456

57+
it('should parse a long hexa color', function () {
58+
let color = parseColor('#abcdef99');
59+
expect(color.getChannelValue('red')).toBe(171);
60+
expect(color.getChannelValue('green')).toBe(205);
61+
expect(color.getChannelValue('blue')).toBe(239);
62+
expect(color.getChannelValue('alpha')).toBe(0.6);
63+
expect(color.toString('hex')).toBe('#ABCDEF');
64+
expect(color.toString('rgb')).toBe('rgb(171, 205, 239)');
65+
expect(color.toString('rgba')).toBe('rgba(171, 205, 239, 0.6)');
66+
expect(color.toString('css')).toBe('rgba(171, 205, 239, 0.6)');
67+
});
68+
4569
it('should throw on invalid hex value', function () {
4670
expect(() => parseColor('#ggg')).toThrow('Invalid color value: #ggg');
4771
});

0 commit comments

Comments
 (0)