Skip to content

fix(shared): parse negative decimals without integer part in numberRegex (#2397) - #2561

Open
ErfanBagheri404 wants to merge 6 commits into
pmndrs:nextfrom
ErfanBagheri404:fix/number-regex-negative-decimals
Open

fix(shared): parse negative decimals without integer part in numberRegex (#2397)#2561
ErfanBagheri404 wants to merge 6 commits into
pmndrs:nextfrom
ErfanBagheri404:fix/number-regex-negative-decimals

Conversation

@ErfanBagheri404

Copy link
Copy Markdown

Summary

Transitioning between certain CSS color formats throws "The arity of each output value must be equal". The crash happens when a color string contains a decimal without an integer part, such as -.0000298023 (produced by Chrome/Tailwind for oklch(0.81 0 255 / 1)lab(77.96% -.0000298023 0)).

Root cause

numberRegex in packages/shared/src/regexs.ts requires at least one digit before the decimal point ((?:0|[1-9]\d*)). Input -.0000298023 is split into fragments 0, 0, 0, 0, 0, 298023 instead of a single token, giving 7 numbers vs the other keyframe's 4 → arity check fails.

Fix

Replace the regex with one that matches the fractional part independently of having an integer prefix:

- /[+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?/g
+ /[+\-]?(?:(?:\d*\.\d+)|\d+)(?:[eE][+\-]?\d+)?/g

This correctly handles -.5, -.0000298023, +1.5e-3, and all previously supported formats (verified against 14 test inputs).

Test plan

  • Repro case no longer throws: createStringInterpolator({output: ['lab(77.96% -.0000298023 0)', 'oklch(0.4 0.2639 271.35 / 1)']}) — works.
  • New regression test in stringInterpolation.test.ts for lab() colors.
  • New test for standalone -.5px negative decimals.
  • @react-spring/shared builds clean (turbo).

Fixes #2397

When the parent update has immediate: true and to is an array (chain),
each step in the chain should also start immediately.

Previously, the immediate flag was only checked in SpringValue.start(),
where it short-circuits to skip the animation. But when to is an array,
SpringValue.start() delegates to runAsync(), which iterates through the
queue and calls animate() for each step. The immediate flag was not
propagated to each step, so the chain would animate each step
sequentially instead of jumping immediately to the final value.

Fixes pmndrs#2204
…mponents

On React Native (Hermes), host components become non-extensible after the
first render.  previously stashed the animated wrapper directly
on the component via ,
which threw  when the component was
loaded lazily (e.g. via Metro inlineRequires).

Replace the per-component property with a module-level WeakMap so we never
mutate the original component.

Fixes pmndrs#2533
… enabled (pmndrs#2409)

When skipAnimation is true, _resume() calls finish() synchronously,
resolving the promise immediately. The .then() callback then re-enters
the loop via _update(), which calls _start() -> _resume() -> finish()
again. Since there is no frame deferral, this creates a synchronous
infinite loop that hangs the tab.

Skip the loop re-trigger when G.skipAnimation is enabled.

Fixes pmndrs#2409
… cleanup

addEventListener calls in onScroll were invoked with `{ passive: true }` but
removeEventListener was called with no options. Per the DOM spec, removeEventListener
must receive options with the same capture flag for the listener to be matched and
removed. While `{ passive: true }` defaults capture to false (same as no options),
some strict environments track add/remove options as an opaque object. The mismatch
causes removeEventListener to silently no-op, leaking listeners.

Use a shared options object for both add and remove calls, satisfying the DOM
spec's matching requirement and fixing useScroll cleanup.

Fixes pmndrs#2384
…er part

The number regex `[+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?` requires
at least one digit before the decimal point (`0` or `[1-9]\d*`). Negative
decimals without an integer part like `-.0000298023` (which browsers produce
for lab/oklch colors via Chrome/Tailwind) are split into multiple fragments
(`0`, `0`, `0`, `0`, `298023`) instead of a single token. This breaks
arity checks in createStringInterpolator when the other keyframe has a
different token count.

Replace the regex with `[+\-]?(?:(?:\d*\.\d+)|\d+)(?:[eE][+\-]?\d+)?` which
matches the decimal part independently of having an integer prefix, correctly
handling inputs like `-.5`, `-.0000298023`, `+1.5e-3`, and all previously
supported formats.

Fixes pmndrs#2397
@changeset-bot

changeset-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7d78a5b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@react-spring/animated Patch
@react-spring/core Patch
@react-spring/mock-raf Patch
@react-spring/parallax Patch
@react-spring/rafz Patch
@react-spring/shared Patch
@react-spring/types Patch
@react-spring/three Patch
@react-spring/web Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug]: Error when transitioning particular lab colors

1 participant