From d3577edc6cd3c5dd45b82f4ea12c8097d36ecaaf Mon Sep 17 00:00:00 2001 From: ErfanBagheri404 Date: Mon, 17 Aug 2026 12:00:45 +0330 Subject: [PATCH 1/3] fix(shared): fix numberRegex to parse negative decimals without integer part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Isolate PR #2561 onto next — single commit. --- packages/shared/src/regexs.ts | 2 +- .../shared/src/stringInterpolation.test.ts | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/regexs.ts b/packages/shared/src/regexs.ts index b250a90491..8a90bc8cd7 100644 --- a/packages/shared/src/regexs.ts +++ b/packages/shared/src/regexs.ts @@ -1,6 +1,6 @@ // Problem: https://github.com/animatedjs/animated/pull/102 // Solution: https://stackoverflow.com/questions/638565/parsing-scientific-notation-sensibly/658662 -export const numberRegex = /[+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?/g +export const numberRegex = /[+\-]?(?:(?:\d*\.\d+)|\d+)(?:[eE][+\-]?\d+)?/g // Covers rgb, rgba, hsl, hsla // Taken from https://gist.github.com/olmokramer/82ccce673f86db7cda5e diff --git a/packages/shared/src/stringInterpolation.test.ts b/packages/shared/src/stringInterpolation.test.ts index b77441628f..7b16281a06 100644 --- a/packages/shared/src/stringInterpolation.test.ts +++ b/packages/shared/src/stringInterpolation.test.ts @@ -16,3 +16,28 @@ it('interpolates a number-less output value instead of throwing on a null match' expect(interpolate(0)).toBe('none') expect(interpolate(0.5)).toBe('none') }) + +// https://github.com/pmndrs/react-spring/issues/2397 +// lab() values like `lab(77.96% -.0000298023 0)` contain decimals with no +// integer part and a leading minus. The old number regex split these into +// multiple tokens (`0`, `0`, `0`, `0`, `298023`) which broke arity +// checks against other keyframes. +it('interpolates colors containing negative decimals without an integer part', () => { + const interpolate = createStringInterpolator({ + range: [0, 1], + output: ['lab(77.96% -.0000298023 0)', 'oklch(0.4 0.2639 271.35 / 1)'], + }) + + expect(() => interpolate(0.5)).not.toThrow() + expect(interpolate(1)).toBe('oklch(0.4 0.2639 271.35 / 1)') +}) + +it('parses a standalone negative decimal without an integer part', () => { + const interpolate = createStringInterpolator({ + range: [0, 1], + output: ['translateY(-.5px)', 'translateY(10px)'], + }) + + expect(() => interpolate(0.5)).not.toThrow() + expect(interpolate(1)).toBe('translateY(10px)') +}) From e7438068e7a3f7a9c67a6fac08b97f55820f4a5e Mon Sep 17 00:00:00 2001 From: ErfanBagheri404 Date: Tue, 18 Aug 2026 10:59:17 +0330 Subject: [PATCH 2/3] fix: assert interpolated value in negative decimal test (#2561) Test only checked not.toThrow() which passes without the regex fix. Assert the actual interpolated value so the test catches regressions. --- packages/shared/src/stringInterpolation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/stringInterpolation.test.ts b/packages/shared/src/stringInterpolation.test.ts index 7b16281a06..0916a37a57 100644 --- a/packages/shared/src/stringInterpolation.test.ts +++ b/packages/shared/src/stringInterpolation.test.ts @@ -38,6 +38,6 @@ it('parses a standalone negative decimal without an integer part', () => { output: ['translateY(-.5px)', 'translateY(10px)'], }) - expect(() => interpolate(0.5)).not.toThrow() + expect(interpolate(0.5)).toBe('translateY(4.75px)') expect(interpolate(1)).toBe('translateY(10px)') }) From 89c74291d7706ee31ec18feef7276927ebe05d2d Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 18 Aug 2026 08:41:43 +0100 Subject: [PATCH 3/3] chore: add changeset for numberRegex negative decimal fix --- .changeset/fix-number-regex-negative-decimals.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/fix-number-regex-negative-decimals.md diff --git a/.changeset/fix-number-regex-negative-decimals.md b/.changeset/fix-number-regex-negative-decimals.md new file mode 100644 index 0000000000..121876615a --- /dev/null +++ b/.changeset/fix-number-regex-negative-decimals.md @@ -0,0 +1,7 @@ +--- +'@react-spring/shared': patch +--- + +fix(shared): parse negative decimals without an integer part in numberRegex + +`numberRegex` required at least one digit before the decimal point, so a value like `-.0000298023` (emitted by Chrome for some `lab()`/`oklch()` colors) was split into several tokens instead of one. That inflated a keyframe's number count and threw `The arity of each "output" value must be equal` when interpolating between color formats. The regex now matches a leading-dot fraction as a single token while keeping all previously supported number formats.