From 67166b8101478bab2a2616445d1835f53783b9ca Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Thu, 15 Oct 2026 12:21:23 +0300 Subject: [PATCH] Match functional colors against the whole string in normalize-colors normalize-colors anchors its hex matchers but not rgb, rgba, hsl, hsla and hwb, so a string that only contains one of those calls normalizes to that color. AnimatedInterpolation reads an output range that normalizes as a color, so a JS-driven interpolation of a box shadow such as `inset 0 0 0 1px rgba(124, 58, 237, 1)` returns the bare color, which processBoxShadow parses to no shadow at all. Anchor the five functional matchers, as the hex ones already are. --- .../__tests__/normalizeColor-test.js | 9 +++++++++ packages/normalize-color/index.js | 14 +++++++------- .../Animated/__tests__/Interpolation-itest.js | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/normalize-color/__tests__/normalizeColor-test.js b/packages/normalize-color/__tests__/normalizeColor-test.js index 95a26a415ec9..338d35684858 100644 --- a/packages/normalize-color/__tests__/normalizeColor-test.js +++ b/packages/normalize-color/__tests__/normalizeColor-test.js @@ -53,6 +53,15 @@ it('refuses non-spec compliant colors', () => { expect(normalizeColor('rgb(1%, 2%, 3%)')).toBe(null); }); +it('refuses a string that only contains a functional color', () => { + expect(normalizeColor('xxrgb(1, 2, 3)yy')).toBe(null); + expect(normalizeColor('rgba(1, 2, 3, 0.5) extra')).toBe(null); + expect(normalizeColor('0 0 0 1px rgba(124, 58, 237, 1)')).toBe(null); + expect(normalizeColor('drop-shadow(0 0 4px hsl(0, 100%, 50%))')).toBe(null); + expect(normalizeColor('hsla(0, 100%, 50%, 1);')).toBe(null); + expect(normalizeColor('x hwb(0 0% 0%)')).toBe(null); +}); + it('handles hex6 properly', () => { expect(normalizeColor('#000000')).toBe(0x000000ff); expect(normalizeColor('#ffffff')).toBe(0xffffffff); diff --git a/packages/normalize-color/index.js b/packages/normalize-color/index.js index 05bdc3adc93e..0500a1cc82c3 100644 --- a/packages/normalize-color/index.js +++ b/packages/normalize-color/index.js @@ -277,22 +277,22 @@ function getMatchers() { callWithSlashSeparator(NUMBER, NUMBER, NUMBER, NUMBER); cachedMatchers = { - rgb: new RegExp('rgb(' + rgbRegexPattern + ')'), - rgba: new RegExp('rgba(' + rgbRegexPattern + ')'), - hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)), + rgb: new RegExp('^rgb(' + rgbRegexPattern + ')$'), + rgba: new RegExp('^rgba(' + rgbRegexPattern + ')$'), + hsl: new RegExp('^hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE) + '$'), hsla: new RegExp( - 'hsla(' + + '^hsla(' + commaSeparatedCall(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) + '|' + callWithSlashSeparator(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) + - ')', + ')$', ), hwb: new RegExp( - 'hwb(' + + '^hwb(' + callModern(NUMBER, PERCENTAGE, PERCENTAGE) + '|' + callWithSlashSeparator(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) + - ')', + ')$', ), hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, diff --git a/packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js b/packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js index da1ed8489130..75bbb57613a2 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/Interpolation-itest.js @@ -333,6 +333,20 @@ describe('Interpolation', () => { expect(interpolation(1)).toBe('translate(20px, 10px)'); }); + it('should keep the rest of a string range that contains a color', () => { + const interpolation = createInterpolation({ + inputRange: [0, 1], + outputRange: [ + 'inset 0 0 0 1px rgba(124, 58, 237, 1)', + 'inset 0 0 0 3px rgba(124, 58, 237, 0.5)', + ], + }); + + expect(interpolation(0)).toBe('inset 0 0 0 1px rgba(124, 58, 237, 1)'); + expect(interpolation(0.5)).toBe('inset 0 0 0 2px rgba(124, 58, 237, 0.75)'); + expect(interpolation(1)).toBe('inset 0 0 0 3px rgba(124, 58, 237, 0.5)'); + }); + it('should crash when chaining an interpolation that returns a string', () => { const interpolation = createInterpolation({ inputRange: [0, 1],