From 703493d052b9f848c655bccfe2df4009bb3bfcb2 Mon Sep 17 00:00:00 2001 From: Bao Nguyen Date: Sun, 6 Sep 2026 23:15:20 +0700 Subject: [PATCH] fix: throw on invalid rgb() and rgba() color strings parseColor('rgb(a, b, c)') returned a Color with NaN channels instead of throwing. The rgb branch matched with /^rgba?\((.*)\)$/ and passed whatever was inside the parens through Number(), so NaN reached the RGBColor constructor. The hsl and hsb branches validate their numeric payload inside the regex, so they throw as documented. Reject components that are empty or do not parse to a finite number, which lets parseColor fall through to its "Invalid color value" error. --- packages/react-stately/src/color/Color.ts | 10 +++++++--- packages/react-stately/test/color/Color.test.tsx | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/react-stately/src/color/Color.ts b/packages/react-stately/src/color/Color.ts index 497227ce174..7ceba66e3d4 100644 --- a/packages/react-stately/src/color/Color.ts +++ b/packages/react-stately/src/color/Color.ts @@ -282,9 +282,13 @@ class RGBColor extends Color { // matching rgb(rrr, ggg, bbb), rgba(rrr, ggg, bbb, 0.a) const match = value.match(/^rgba?\((.*)\)$/); if (match?.[1]) { - colors = match[1].split(',').map(value => Number(value.trim())); - colors = colors.map((num, i) => { - return clamp(num ?? 0, 0, i < 3 ? 255 : 1); + const parts = match[1].split(',').map(value => value.trim()); + // Number('') is 0 rather than NaN, so empty components need their own check. + if (parts.some(part => part === '' || !Number.isFinite(Number(part)))) { + return undefined; + } + colors = parts.map((part, i) => { + return clamp(Number(part), 0, i < 3 ? 255 : 1); }); } if (colors[0] === undefined || colors[1] === undefined || colors[2] === undefined) { diff --git a/packages/react-stately/test/color/Color.test.tsx b/packages/react-stately/test/color/Color.test.tsx index 675b48e1526..680e4c7960a 100644 --- a/packages/react-stately/test/color/Color.test.tsx +++ b/packages/react-stately/test/color/Color.test.tsx @@ -107,6 +107,20 @@ describe('Color', function () { expect(color.getChannelValue('alpha')).toBe(1); expect(color.toString('rgba')).toBe('rgba(255, 0, 0, 1)'); }); + + it('should throw on non-numeric rgb channels', function () { + expect(() => parseColor('rgb(a, b, c)')).toThrow('Invalid color value: rgb(a, b, c)'); + }); + + it('should throw on a non-numeric rgba alpha', function () { + expect(() => parseColor('rgba(0, 0, 0, abc)')).toThrow( + 'Invalid color value: rgba(0, 0, 0, abc)' + ); + }); + + it('should throw on empty rgb channels', function () { + expect(() => parseColor('rgb(, , )')).toThrow('Invalid color value: rgb(, , )'); + }); }); describe('hsl', function () {