Skip to content

Refactoring animation code#7457

Merged
PavelVanecek merged 26 commits into
mainfrom
rechartsanimation
Jun 20, 2026
Merged

Refactoring animation code#7457
PavelVanecek merged 26 commits into
mainfrom
rechartsanimation

Conversation

@PavelVanecek

@PavelVanecek PavelVanecek commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

So I wasn't happy with how the animation code is structured so I rewrote it from scratch. This didn't matter before but in 3.9 this will be a public API so let's get it nice and separated.

This is a refactor so the aim is to have 100% identical behavior to what it was before, just with new code.

Before:

  • everything is kind of responsible for everything, multiple files are doing the same job
  • test and production code are mixed together

After:

  • clear separation of concerns: time management, animation state machine, connector, and React facade
  • test code isolated for tests only, production code only ships what is required to run in browser

Not quite yet 100% settled, please share feedback:

  • naming

Notable changes:

  • ProgressAnimationManager and useAnimationManager are gone. I think these were the wrong abstraction, and the new AnimationController interface should replace them both. I will iterate on this in followup PR, I want to be able to override the AnimationController from the outside eventually
  • This is not a breaking change because neither of those has been released to npm yet. These were new additions in 3.9 and the current version is 3.8.1 so they are both getting removed before they became a stable API
  • easing code is greatly simplified
  • in tests: I noticed that the old mock animation code was calling onAnimationStart asynchronously which is different from the production code which is calling it synchronously. I fixed the inconsistency which in turn made a few tests to fail so that's why you see changes in test code.

Summary by CodeRabbit

  • Breaking Changes
    • Removed AnimationProgressProvider and useAnimationProgress from the public API.
  • New Features
    • Added a new animation state machine with JavaScript and CSS-transition implementations, including explicit complete() finalization and improved progress interpolation behavior.
  • Refactor
    • Reworked animation driving around an AnimationController, updating animation lifecycle and easing wiring across relevant components.
    • Simplified animation manager usage to accept a controller instance.
  • Chores / Tests
    • Added an lgtm script to run lint, build, tests, and type checks together.
    • Updated and expanded animation-related test coverage and documentation categories.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcef7944-38eb-42a1-b94f-c59725680480

📥 Commits

Reviewing files that changed from the base of the PR and between 50393c0 and ec11c0c.

📒 Files selected for processing (4)
  • src/animation/AnimationController.ts
  • src/animation/AnimationControllerImpl.ts
  • test/animation/CompositeAnimationManager.ts
  • test/animation/RechartsAnimation.spec.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/animation/AnimationController.ts
  • src/animation/AnimationControllerImpl.ts
  • test/animation/CompositeAnimationManager.ts
  • test/animation/RechartsAnimation.spec.ts

Walkthrough

The PR replaces the queue-based AnimationManager/ProgressAnimationManager/configUpdate animation infrastructure with a new RechartsAnimation<T,E> state machine interface plus JavascriptAnimation and CSSTransition concrete implementations, driven by a new AnimationController function type and animationControllerImpl. CSSTransitionAnimate and JavascriptAnimate are rewired to the new abstractions, AnimationProgressProvider/useAnimationProgress are removed from the public API, and all tests, mock utilities, build snapshots, and docs are updated accordingly.

Changes

Animation System Refactor

Layer / File(s) Summary
RechartsAnimation state machine and AnimationController contract
src/animation/RechartsAnimation.ts, src/animation/easing.ts, src/animation/timeoutController.ts, src/animation/AnimationController.ts
Defines RechartsAnimation<T,E> interface with init→pending→active→completed transitions, implements RechartsAnimationImpl (calls onAnimationStart in constructor, onAnimationEnd only from active), adds JavascriptAnimation (easing-applied interpolation, immediate rescheduling) and CSSTransition (returns to value, deferred rescheduling). Exports NamedBezier and AnimationController function type.
animationControllerImpl timeout-driven driver
src/animation/AnimationControllerImpl.ts
Implements animationControllerImpl: schedules immediate first update, calls listener(getInterpolated()) while active, calls complete() when progress reaches 1, reschedules with tick(now)-derived remaining time, returns a cancel function.
Deleted old animation infrastructure
Removed: src/animation/AnimationManager.ts, src/animation/ProgressAnimationManager.ts, src/animation/ManualTimeoutController.ts, src/animation/configUpdate.ts, src/animation/createDefaultAnimationManager.tsx, src/animation/AnimationProgressProvider.tsx
Removes createAnimateManager, ProgressAnimationManager, ManualTimeoutController, configUpdate default export, createDefaultAnimationManager, and AnimationProgressProvider/useAnimationProgress.
React component wiring to new AnimationController
src/animation/useAnimationManager.tsx, src/animation/JavascriptAnimate.tsx, src/animation/CSSTransitionAnimate.tsx, src/cartesian/ErrorBar.tsx, src/chart/Treemap.tsx, src/animation/util.ts
Simplifies useAnimationManager to drop animationId param, context defaults to animationControllerImpl. Refactors JavascriptAnimate to numeric style state delegating to animationManager(timeoutController, JavascriptAnimation, setStyle). Refactors CSSTransitionAnimate to string style state with new extractCssEasing helper; ErrorBar and Treemap adopt extractCssEasing. Removes getIntersectionKeys, mapObject, and interpolateCSSValue utilities.
Test infrastructure: mocks and utilities
test/animation/MockProgressAnimationManager.ts, test/animation/CompositeAnimationManager.ts, test/animation/mockAnimationController.ts, test/animation/mockTimeoutController.ts
Rewrites MockProgressAnimationManager from queue-based to RechartsAnimation-instance-driven. Refactors CompositeAnimationManager.factory to AnimationController shape, removes subscribe/useAllAnimationManagers. Adds mockAnimationController (drives listener without auto-completing). Updates MockTimeoutController with optional timeoutLimit and copied-callback cancellation.
Unit tests: RechartsAnimation and AnimationControllerImpl
test/animation/RechartsAnimation.spec.ts, test/animation/AnimationControllerImpl.spec.ts
Adds state-transition, callback, progress-clamping, timing, and edge-case tests for JavascriptAnimation/CSSTransition. Adds controller tests covering state progression, timeout scheduling durations, multi-animation cancellation, interpolated value delivery, and stop-function semantics.
Integration tests: component animation specs
test/animation/CSSTransitionAnimate.timing.spec.tsx, test/animation/JavascriptAnimate.timing.spec.tsx, test/animation/JavascriptAnimate.progress.spec.tsx
Updates component animation tests to drive via CompositeAnimationManager.factory/setAnimationProgress/completeAnimation, replacing queue/poll assertions with isAnimating checks and progress-driven expectations across restart, canBegin, and isActive scenarios.
Downstream chart and utility expectations
test/cartesian/Line.animation.spec.tsx, test/cartesian/Bar.animation.spec.tsx, test/cartesian/Area.animation.spec.tsx, test/polar/Pie/Pie.animation.spec.tsx, test/component/Label.spec.tsx, test/animation/util.spec.ts
Updates Line initial stroke-dasharray to 0px 100px, label-hidden expectations during animation, and onAnimationStart call-count timing. Updates Pie and Bar callback timing. Removes deleted utility test cases. Makes Label test async to await animation completion.
Build snapshots, public API, and config
scripts/snapshots/*.txt, src/index.ts, stryker.config.mjs, package.json, omnidoc/verifyExamples.spec.ts, www/src/docs/apiCates.ts
Updates distribution snapshots for new/removed modules. Removes AnimationProgressProvider/useAnimationProgress/AnimationHandle/AnimationStatus from public exports and docs. Updates Stryker mutation targets to new animation files. Adds lgtm npm script. Updates omnidoc exclusions and API docs.

Sequence Diagram(s)

sequenceDiagram
  participant Component as JavascriptAnimate / CSSTransitionAnimate
  participant useAnimationManager
  participant animationControllerImpl
  participant RechartsAnimation as JavascriptAnimation / CSSTransition
  participant TimeoutController as RequestAnimationFrameTimeoutController
  participant setState

  Component->>useAnimationManager: animationManagerFromProps?
  useAnimationManager-->>Component: AnimationController (from props or context)
  Component->>RechartsAnimation: new JavascriptAnimation(from, to, easing, duration, begin, callbacks)
  Note right of RechartsAnimation: onAnimationStart called immediately
  Component->>animationControllerImpl: (timeoutController, animation, setStyle)
  animationControllerImpl->>TimeoutController: setTimeout(onUpdate, 0)
  loop active ticks
    TimeoutController-->>animationControllerImpl: onUpdate(now)
    animationControllerImpl->>RechartsAnimation: tick(now) → timeRemaining
    animationControllerImpl->>RechartsAnimation: getState() / getProgress()
    animationControllerImpl->>RechartsAnimation: getInterpolated()
    animationControllerImpl->>setState: listener(interpolatedValue)
    animationControllerImpl->>TimeoutController: setTimeout(onUpdate, timeRemaining)
  end
  animationControllerImpl->>RechartsAnimation: complete()
  Note right of RechartsAnimation: onAnimationEnd called
  Component-->>animationControllerImpl: cleanup → cancel()
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • recharts/recharts#7215: The main PR's animation refactor (notably src/animation/CSSTransitionAnimate.tsx and removal of AnimationProgressProvider/ProgressAnimationManager exports) directly overlaps with PR #7215's animation-props work in the same subsystem, including changing CSSTransitionAnimate's control flow and the presence of the progress provider/manager APIs.
  • recharts/recharts#7289: Both PRs modify test/cartesian/Line.animation.spec.tsx assertions around stroke-dasharray/line entrance animation behavior (including composing/responsive scenarios), so the changes are directly related at the test-code level.
  • recharts/recharts#7295: Both PRs involve Line animation behavior driven by animation progress/completion (the main PR refactors the underlying animation state-machine/controller, and the retrieved PR adds assertions about sparse Line SVG path commands at progress=0.5 and after completion).

Suggested labels

typescript

Suggested reviewers

  • ckifer
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Refactoring animation code' accurately describes the primary change in this pull request - a comprehensive rewrite of the animation system architecture.
Description check ✅ Passed The description is comprehensive and well-structured, covering motivation, context, notable changes, and architectural improvements. It clearly explains why the refactor was necessary and what problems it solves.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rechartsanimation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Bundle Report

Changes will decrease total bundle size by 47.0kB (-0.84%) ⬇️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.42MB -17.56kB (-1.22%) ⬇️
recharts/bundle-es6 1.25MB -15.69kB (-1.24%) ⬇️
recharts/bundle-umd 585.98kB -8.16kB (-1.37%) ⬇️
recharts/bundle-treeshaking-cartesian 713.78kB -1.95kB (-0.27%) ⬇️
recharts/bundle-treeshaking-polar 470.58kB -847 bytes (-0.18%) ⬇️
recharts/bundle-treeshaking-treemap 384.63kB -1.95kB (-0.5%) ⬇️
recharts/bundle-treeshaking-sankey 371.57kB -847 bytes (-0.23%) ⬇️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
chart/Treemap.js -14 bytes 33.85kB -0.04%
cartesian/ErrorBar.js -14 bytes 12.23kB -0.11%
animation/RechartsAnimation.js (New) 7.13kB 7.13kB 100.0% 🚀
animation/CSSTransitionAnimate.js -962 bytes 4.49kB -17.63%
index.js -105 bytes 4.18kB -2.45%
animation/JavascriptAnimate.js -34 bytes 3.78kB -0.89%
animation/AnimationControllerImpl.js (New) 1.22kB 1.22kB 100.0% 🚀
animation/useAnimationManager.js -57 bytes 562 bytes -9.21%
animation/util.js -2.57kB 330 bytes -88.62%
animation/AnimationController.js (New) 10 bytes 10 bytes 100.0% 🚀
animation/ProgressAnimationManager.js (Deleted) -6.63kB 0 bytes -100.0% 🗑️
animation/AnimationProgressProvider.js (Deleted) -5.77kB 0 bytes -100.0% 🗑️
animation/configUpdate.js (Deleted) -3.33kB 0 bytes -100.0% 🗑️
animation/AnimationManager.js (Deleted) -2.9kB 0 bytes -100.0% 🗑️
animation/ManualTimeoutController.js (Deleted) -1.4kB 0 bytes -100.0% 🗑️
animation/createDefaultAnimationManager.js (Deleted) -265 bytes 0 bytes -100.0% 🗑️
view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js -8.16kB 585.98kB -1.37%
view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
chart/Treemap.js -5 bytes 35.57kB -0.01%
index.js -431 bytes 16.36kB -2.57%
cartesian/ErrorBar.js -5 bytes 13.24kB -0.04%
animation/RechartsAnimation.js (New) 7.36kB 7.36kB 100.0% 🚀
animation/CSSTransitionAnimate.js -905 bytes 4.8kB -15.87%
animation/JavascriptAnimate.js -151 bytes 4.04kB -3.6%
animation/AnimationControllerImpl.js (New) 1.4kB 1.4kB 100.0% 🚀
animation/useAnimationManager.js -63 bytes 787 bytes -7.41%
animation/util.js -2.76kB 533 bytes -83.79%
animation/AnimationController.js (New) 13 bytes 13 bytes 100.0% 🚀
animation/ProgressAnimationManager.js (Deleted) -6.83kB 0 bytes -100.0% 🗑️
animation/AnimationProgressProvider.js (Deleted) -6.66kB 0 bytes -100.0% 🗑️
animation/configUpdate.js (Deleted) -3.5kB 0 bytes -100.0% 🗑️
animation/AnimationManager.js (Deleted) -3.02kB 0 bytes -100.0% 🗑️
animation/ManualTimeoutController.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
animation/createDefaultAnimationManager.js (Deleted) -426 bytes 0 bytes -100.0% 🗑️
view changes for bundle: recharts/bundle-treeshaking-polar

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js -847 bytes 470.58kB -0.18%
view changes for bundle: recharts/bundle-treeshaking-cartesian

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js -1.95kB 713.78kB -0.27%
view changes for bundle: recharts/bundle-treeshaking-sankey

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js -847 bytes 371.57kB -0.23%
view changes for bundle: recharts/bundle-treeshaking-treemap

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js -1.95kB 384.63kB -0.5%

@chromatic-com

chromatic-com Bot commented Jun 13, 2026

Copy link
Copy Markdown

Tip

All tests passed and all changes approved!

🟢 UI Tests: 198 tests unchanged
🟢 UI Review: 198 stories published -- no changes
Storybook icon Storybook Publish: 198 stories published

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.45238% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.41%. Comparing base (4f27699) to head (ec11c0c).

Files with missing lines Patch % Lines
test/animation/MockProgressAnimationManager.ts 84.84% 5 Missing ⚠️
src/animation/RechartsAnimation.ts 94.20% 4 Missing ⚠️
src/animation/CSSTransitionAnimate.tsx 88.88% 1 Missing ⚠️
test/animation/mockTimeoutController.ts 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7457      +/-   ##
==========================================
+ Coverage   88.25%   88.41%   +0.15%     
==========================================
  Files         607      602       -5     
  Lines       14102    13906     -196     
  Branches     3545     3514      -31     
==========================================
- Hits        12446    12295     -151     
+ Misses       1469     1428      -41     
+ Partials      187      183       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@PavelVanecek PavelVanecek changed the title RechartsAnimation state machine Refactoring animation code Jun 19, 2026
@PavelVanecek

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (3)
src/animation/AnimationController.ts (1)

4-15: ⚡ Quick win

Update the controller JSDoc to match the function contract.

This exported type is a function, not a class, and it returns a cancel callback rather than a listener handle. Please remove the stale AnimationManager wording too. As per coding guidelines, “Update TypeScript interfaces and JSDoc comments in source code to automatically update API documentation”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/animation/AnimationController.ts` around lines 4 - 15, Update the JSDoc
comments for AnimationController to accurately reflect the function contract.
Remove the description referring to AnimationController as a class and update
the return value documentation to correctly state that the function returns a
cancel callback rather than an animation handle for listening to updates. Remove
any stale references to AnimationManager from the comments as it is no longer
relevant to this function's responsibility.

Source: Coding guidelines

test/animation/AnimationControllerImpl.spec.ts (1)

173-173: ⚡ Quick win

Use expectLastCalledWith helper for last-call assertions.

These assertions use toHaveBeenLastCalledWith; switch to the repository helper for typed call checks.

As per coding guidelines, "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion."

Also applies to: 180-180, 204-204, 211-211, 250-250, 254-254, 258-258, 262-262

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/animation/AnimationControllerImpl.spec.ts` at line 173, Replace all
instances of `expect(spy).toHaveBeenLastCalledWith(...)` with the
`expectLastCalledWith` helper function throughout the test file. This includes
the assertions at lines 173, 180, 204, 211, 250, 254, 258, and 262. Import the
`expectLastCalledWith` helper function at the top of the file if it is not
already imported, then refactor each assertion to use the helper function
pattern instead of the direct expect syntax to improve typing and autocompletion
support.

Source: Coding guidelines

test/animation/RechartsAnimation.spec.ts (1)

470-470: ⚡ Quick win

Use expectLastCalledWith helper instead of toHaveBeenLastCalledWith.

Replace this matcher with the typed helper to align with test conventions and keep assertion typing consistent.

As per coding guidelines, "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/animation/RechartsAnimation.spec.ts` at line 470, Replace the matcher
call expect(spy).toHaveBeenLastCalledWith(0.2) with the expectLastCalledWith
helper function instead. This helper provides better typing and aligns with the
project's test conventions for consistency in assertion patterns and improved
autocompletion support.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 143: The `@types/node` version in package.json is set to 25.x.x but the
.nvmrc file specifies Node 24 as the runtime, creating a mismatch where
TypeScript can check for Node 25 APIs that don't exist in the Node 24 runtime.
Either update the `@types/node` dependency in package.json to 24.x.x to match the
Node 24 runtime specified in .nvmrc, or alternatively update .nvmrc to specify
Node 25 to match the types version. Choose whichever aligns with your project's
runtime requirements.

In `@src/animation/AnimationControllerImpl.ts`:
- Around line 18-31: The nextUpdate function reschedules indefinitely after an
animation completes because the non-active branch (the fallback setTimeout on
line 33) always reschedules regardless of whether the handle is already
completed. When animationHandle.getState() is no longer 'active' after
completion, tick() returns 0 and the fallback reschedule creates an infinite
loop. Add a guard condition before the final timeoutController.setTimeout call
(line 33) to check if the animation handle is completed, and only reschedule if
it is not completed. This prevents already-completed or custom-completing
handles from spinning with perpetual zero-timeout reschedules.

In `@src/animation/JavascriptAnimate.tsx`:
- Around line 66-86: The useEffect hook does not reset the animation progress
when starting a new animation cycle, causing stale style values from previous
animations to persist. Before creating the new JavascriptAnimation instance,
reset the style state by calling setStyle with the initial progress value (0) to
ensure a clean slate for each new animation that begins, preventing the jump
from stale progress to the correct value.

In `@src/animation/RechartsAnimation.ts`:
- Around line 15-23: The JSDoc comment for the RechartsAnimation interface's
`init` state inaccurately describes when the `onAnimationStart` callback is
called. The documentation currently states that in the `init` state the
animation "did not announce its start yet", but the constructor actually calls
`onAnimationStart` synchronously. Update the JSDoc documentation for the `init`
state (in the RechartsAnimation interface) to accurately reflect that
`onAnimationStart` is invoked synchronously during construction, ensuring the
state description aligns with the actual implementation behavior.
- Around line 280-282: The getInterpolated method in RechartsAnimation currently
applies the easing function to the interpolated result, but it should apply
easing to the normalized progress value before interpolation. Change the order
so that the easing function is applied to this.getProgress() first to get the
eased progress value, then pass that eased progress to the interpolate function
along with this.getFrom() and this.getTo(). This ensures the easing function
operates on the correct normalized progress range regardless of the actual from
and to numeric values.
- Around line 214-216: In the code block containing the setProgress call, check
if animationDuration is less than or equal to 0 before performing the division
operation. If animationDuration is non-positive, immediately set the progress to
1 (complete state) by calling setProgress(1). Otherwise, proceed with the normal
calculation of timeElapsed divided by animationDuration. This prevents NaN or
negative progress values from being passed to setProgress, which would cause the
animation controller to keep scheduling zero-delay updates instead of
completing.

In `@test/animation/AnimationControllerImpl.spec.ts`:
- Around line 135-136: The second assertion on line 136 duplicates the spy2
check instead of verifying the pre-cancel state. Replace the duplicate
expect(spy2).toHaveBeenCalledTimes(2) on line 136 with
expect(spy1).toHaveBeenCalledTimes(1) to properly assert that spy1 (the callback
for animation1) was called once before the cancellation occurred, ensuring the
test validates both animations advanced before cancellation.

In `@test/animation/JavascriptAnimate.timing.spec.tsx`:
- Around line 66-68: In the test block around the duplicate assertions, replace
the second expect statement that checks
handleAnimationStart.not.toHaveBeenCalled() with an assertion that verifies
handleAnimationEnd is also not called when canBegin is false. This ensures the
test properly validates that both animation callbacks remain unused when the
animation cannot begin.

In `@test/animation/MockProgressAnimationManager.ts`:
- Around line 108-117: The start() method calls mockAnimationController() but
does not capture or store the cancel handle it returns, so the stop() method has
no way to actually cancel the active controller loop. Store the return value of
mockAnimationController() as an instance variable (likely something like
this.cancelAnimation or similar), and then in the stop() method, call that
cancel function before nulling out the animation and isPrimed fields to ensure
scheduled callbacks are properly cleaned up.

In `@test/animation/mockTimeoutController.ts`:
- Around line 39-45: The timeoutLimit check in mockTimeoutController.ts occurs
after the callback is already pushed to the this.timeouts queue, allowing the
queue to exceed the limit before the error is thrown. Reorder the logic to
validate the timeoutLimit condition before pushing the callback to the queue,
ensuring that if the limit would be exceeded, the callback is never added and
the queue remains in a valid state when the error is thrown.

---

Nitpick comments:
In `@src/animation/AnimationController.ts`:
- Around line 4-15: Update the JSDoc comments for AnimationController to
accurately reflect the function contract. Remove the description referring to
AnimationController as a class and update the return value documentation to
correctly state that the function returns a cancel callback rather than an
animation handle for listening to updates. Remove any stale references to
AnimationManager from the comments as it is no longer relevant to this
function's responsibility.

In `@test/animation/AnimationControllerImpl.spec.ts`:
- Line 173: Replace all instances of `expect(spy).toHaveBeenLastCalledWith(...)`
with the `expectLastCalledWith` helper function throughout the test file. This
includes the assertions at lines 173, 180, 204, 211, 250, 254, 258, and 262.
Import the `expectLastCalledWith` helper function at the top of the file if it
is not already imported, then refactor each assertion to use the helper function
pattern instead of the direct expect syntax to improve typing and autocompletion
support.

In `@test/animation/RechartsAnimation.spec.ts`:
- Line 470: Replace the matcher call expect(spy).toHaveBeenLastCalledWith(0.2)
with the expectLastCalledWith helper function instead. This helper provides
better typing and aligns with the project's test conventions for consistency in
assertion patterns and improved autocompletion support.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d820dda1-f913-4ed1-a05c-a93b5cab2803

📥 Commits

Reviewing files that changed from the base of the PR and between 62758d1 and 99e594f.

📒 Files selected for processing (46)
  • omnidoc/verifyExamples.spec.ts
  • package.json
  • scripts/snapshots/es6Files.txt
  • scripts/snapshots/libFiles.txt
  • scripts/snapshots/typesFiles.txt
  • src/animation/AnimationController.ts
  • src/animation/AnimationControllerImpl.ts
  • src/animation/AnimationManager.ts
  • src/animation/AnimationProgressProvider.tsx
  • src/animation/CSSTransitionAnimate.tsx
  • src/animation/JavascriptAnimate.tsx
  • src/animation/ManualTimeoutController.ts
  • src/animation/ProgressAnimationManager.ts
  • src/animation/RechartsAnimation.ts
  • src/animation/configUpdate.ts
  • src/animation/createDefaultAnimationManager.tsx
  • src/animation/easing.ts
  • src/animation/timeoutController.ts
  • src/animation/useAnimationManager.tsx
  • src/animation/util.ts
  • src/cartesian/ErrorBar.tsx
  • src/chart/Treemap.tsx
  • src/index.ts
  • stryker.config.mjs
  • test/animation/AnimationControllerImpl.spec.ts
  • test/animation/AnimationManager.spec.ts
  • test/animation/CSSTransitionAnimate.timing.spec.tsx
  • test/animation/CompositeAnimationManager.ts
  • test/animation/JavascriptAnimate.progress.spec.tsx
  • test/animation/JavascriptAnimate.timing.spec.tsx
  • test/animation/ManualTimeoutController.spec.ts
  • test/animation/MockAbstractAnimationManager.ts
  • test/animation/MockProgressAnimationManager.ts
  • test/animation/MockTickingAnimationManager.ts
  • test/animation/ProgressAnimationManager.spec.ts
  • test/animation/RechartsAnimation.spec.ts
  • test/animation/configUpdate.spec.ts
  • test/animation/mockAnimationController.ts
  • test/animation/mockTimeoutController.ts
  • test/animation/util.spec.ts
  • test/cartesian/Area.animation.spec.tsx
  • test/cartesian/Bar.animation.spec.tsx
  • test/cartesian/Line.animation.spec.tsx
  • test/component/Label.spec.tsx
  • test/polar/Pie/Pie.animation.spec.tsx
  • www/src/docs/apiCates.ts
💤 Files with no reviewable changes (15)
  • omnidoc/verifyExamples.spec.ts
  • test/animation/MockTickingAnimationManager.ts
  • test/animation/configUpdate.spec.ts
  • test/animation/ManualTimeoutController.spec.ts
  • test/animation/MockAbstractAnimationManager.ts
  • src/animation/AnimationProgressProvider.tsx
  • src/animation/AnimationManager.ts
  • src/animation/createDefaultAnimationManager.tsx
  • test/animation/ProgressAnimationManager.spec.ts
  • test/animation/AnimationManager.spec.ts
  • src/animation/ManualTimeoutController.ts
  • src/animation/configUpdate.ts
  • src/index.ts
  • src/animation/ProgressAnimationManager.ts
  • src/animation/util.ts
✅ Files skipped from review due to trivial changes (2)
  • src/animation/timeoutController.ts
  • scripts/snapshots/typesFiles.txt

@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 10

🧹 Nitpick comments (3)
src/animation/AnimationController.ts (1)

4-15: ⚡ Quick win

Update the controller JSDoc to match the function contract.

This exported type is a function, not a class, and it returns a cancel callback rather than a listener handle. Please remove the stale AnimationManager wording too. As per coding guidelines, “Update TypeScript interfaces and JSDoc comments in source code to automatically update API documentation”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/animation/AnimationController.ts` around lines 4 - 15, Update the JSDoc
comments for AnimationController to accurately reflect the function contract.
Remove the description referring to AnimationController as a class and update
the return value documentation to correctly state that the function returns a
cancel callback rather than an animation handle for listening to updates. Remove
any stale references to AnimationManager from the comments as it is no longer
relevant to this function's responsibility.

Source: Coding guidelines

test/animation/AnimationControllerImpl.spec.ts (1)

173-173: ⚡ Quick win

Use expectLastCalledWith helper for last-call assertions.

These assertions use toHaveBeenLastCalledWith; switch to the repository helper for typed call checks.

As per coding guidelines, "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion."

Also applies to: 180-180, 204-204, 211-211, 250-250, 254-254, 258-258, 262-262

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/animation/AnimationControllerImpl.spec.ts` at line 173, Replace all
instances of `expect(spy).toHaveBeenLastCalledWith(...)` with the
`expectLastCalledWith` helper function throughout the test file. This includes
the assertions at lines 173, 180, 204, 211, 250, 254, 258, and 262. Import the
`expectLastCalledWith` helper function at the top of the file if it is not
already imported, then refactor each assertion to use the helper function
pattern instead of the direct expect syntax to improve typing and autocompletion
support.

Source: Coding guidelines

test/animation/RechartsAnimation.spec.ts (1)

470-470: ⚡ Quick win

Use expectLastCalledWith helper instead of toHaveBeenLastCalledWith.

Replace this matcher with the typed helper to align with test conventions and keep assertion typing consistent.

As per coding guidelines, "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/animation/RechartsAnimation.spec.ts` at line 470, Replace the matcher
call expect(spy).toHaveBeenLastCalledWith(0.2) with the expectLastCalledWith
helper function instead. This helper provides better typing and aligns with the
project's test conventions for consistency in assertion patterns and improved
autocompletion support.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 143: The `@types/node` version in package.json is set to 25.x.x but the
.nvmrc file specifies Node 24 as the runtime, creating a mismatch where
TypeScript can check for Node 25 APIs that don't exist in the Node 24 runtime.
Either update the `@types/node` dependency in package.json to 24.x.x to match the
Node 24 runtime specified in .nvmrc, or alternatively update .nvmrc to specify
Node 25 to match the types version. Choose whichever aligns with your project's
runtime requirements.

In `@src/animation/AnimationControllerImpl.ts`:
- Around line 18-31: The nextUpdate function reschedules indefinitely after an
animation completes because the non-active branch (the fallback setTimeout on
line 33) always reschedules regardless of whether the handle is already
completed. When animationHandle.getState() is no longer 'active' after
completion, tick() returns 0 and the fallback reschedule creates an infinite
loop. Add a guard condition before the final timeoutController.setTimeout call
(line 33) to check if the animation handle is completed, and only reschedule if
it is not completed. This prevents already-completed or custom-completing
handles from spinning with perpetual zero-timeout reschedules.

In `@src/animation/JavascriptAnimate.tsx`:
- Around line 66-86: The useEffect hook does not reset the animation progress
when starting a new animation cycle, causing stale style values from previous
animations to persist. Before creating the new JavascriptAnimation instance,
reset the style state by calling setStyle with the initial progress value (0) to
ensure a clean slate for each new animation that begins, preventing the jump
from stale progress to the correct value.

In `@src/animation/RechartsAnimation.ts`:
- Around line 15-23: The JSDoc comment for the RechartsAnimation interface's
`init` state inaccurately describes when the `onAnimationStart` callback is
called. The documentation currently states that in the `init` state the
animation "did not announce its start yet", but the constructor actually calls
`onAnimationStart` synchronously. Update the JSDoc documentation for the `init`
state (in the RechartsAnimation interface) to accurately reflect that
`onAnimationStart` is invoked synchronously during construction, ensuring the
state description aligns with the actual implementation behavior.
- Around line 280-282: The getInterpolated method in RechartsAnimation currently
applies the easing function to the interpolated result, but it should apply
easing to the normalized progress value before interpolation. Change the order
so that the easing function is applied to this.getProgress() first to get the
eased progress value, then pass that eased progress to the interpolate function
along with this.getFrom() and this.getTo(). This ensures the easing function
operates on the correct normalized progress range regardless of the actual from
and to numeric values.
- Around line 214-216: In the code block containing the setProgress call, check
if animationDuration is less than or equal to 0 before performing the division
operation. If animationDuration is non-positive, immediately set the progress to
1 (complete state) by calling setProgress(1). Otherwise, proceed with the normal
calculation of timeElapsed divided by animationDuration. This prevents NaN or
negative progress values from being passed to setProgress, which would cause the
animation controller to keep scheduling zero-delay updates instead of
completing.

In `@test/animation/AnimationControllerImpl.spec.ts`:
- Around line 135-136: The second assertion on line 136 duplicates the spy2
check instead of verifying the pre-cancel state. Replace the duplicate
expect(spy2).toHaveBeenCalledTimes(2) on line 136 with
expect(spy1).toHaveBeenCalledTimes(1) to properly assert that spy1 (the callback
for animation1) was called once before the cancellation occurred, ensuring the
test validates both animations advanced before cancellation.

In `@test/animation/JavascriptAnimate.timing.spec.tsx`:
- Around line 66-68: In the test block around the duplicate assertions, replace
the second expect statement that checks
handleAnimationStart.not.toHaveBeenCalled() with an assertion that verifies
handleAnimationEnd is also not called when canBegin is false. This ensures the
test properly validates that both animation callbacks remain unused when the
animation cannot begin.

In `@test/animation/MockProgressAnimationManager.ts`:
- Around line 108-117: The start() method calls mockAnimationController() but
does not capture or store the cancel handle it returns, so the stop() method has
no way to actually cancel the active controller loop. Store the return value of
mockAnimationController() as an instance variable (likely something like
this.cancelAnimation or similar), and then in the stop() method, call that
cancel function before nulling out the animation and isPrimed fields to ensure
scheduled callbacks are properly cleaned up.

In `@test/animation/mockTimeoutController.ts`:
- Around line 39-45: The timeoutLimit check in mockTimeoutController.ts occurs
after the callback is already pushed to the this.timeouts queue, allowing the
queue to exceed the limit before the error is thrown. Reorder the logic to
validate the timeoutLimit condition before pushing the callback to the queue,
ensuring that if the limit would be exceeded, the callback is never added and
the queue remains in a valid state when the error is thrown.

---

Nitpick comments:
In `@src/animation/AnimationController.ts`:
- Around line 4-15: Update the JSDoc comments for AnimationController to
accurately reflect the function contract. Remove the description referring to
AnimationController as a class and update the return value documentation to
correctly state that the function returns a cancel callback rather than an
animation handle for listening to updates. Remove any stale references to
AnimationManager from the comments as it is no longer relevant to this
function's responsibility.

In `@test/animation/AnimationControllerImpl.spec.ts`:
- Line 173: Replace all instances of `expect(spy).toHaveBeenLastCalledWith(...)`
with the `expectLastCalledWith` helper function throughout the test file. This
includes the assertions at lines 173, 180, 204, 211, 250, 254, 258, and 262.
Import the `expectLastCalledWith` helper function at the top of the file if it
is not already imported, then refactor each assertion to use the helper function
pattern instead of the direct expect syntax to improve typing and autocompletion
support.

In `@test/animation/RechartsAnimation.spec.ts`:
- Line 470: Replace the matcher call expect(spy).toHaveBeenLastCalledWith(0.2)
with the expectLastCalledWith helper function instead. This helper provides
better typing and aligns with the project's test conventions for consistency in
assertion patterns and improved autocompletion support.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d820dda1-f913-4ed1-a05c-a93b5cab2803

📥 Commits

Reviewing files that changed from the base of the PR and between 62758d1 and 99e594f.

📒 Files selected for processing (46)
  • omnidoc/verifyExamples.spec.ts
  • package.json
  • scripts/snapshots/es6Files.txt
  • scripts/snapshots/libFiles.txt
  • scripts/snapshots/typesFiles.txt
  • src/animation/AnimationController.ts
  • src/animation/AnimationControllerImpl.ts
  • src/animation/AnimationManager.ts
  • src/animation/AnimationProgressProvider.tsx
  • src/animation/CSSTransitionAnimate.tsx
  • src/animation/JavascriptAnimate.tsx
  • src/animation/ManualTimeoutController.ts
  • src/animation/ProgressAnimationManager.ts
  • src/animation/RechartsAnimation.ts
  • src/animation/configUpdate.ts
  • src/animation/createDefaultAnimationManager.tsx
  • src/animation/easing.ts
  • src/animation/timeoutController.ts
  • src/animation/useAnimationManager.tsx
  • src/animation/util.ts
  • src/cartesian/ErrorBar.tsx
  • src/chart/Treemap.tsx
  • src/index.ts
  • stryker.config.mjs
  • test/animation/AnimationControllerImpl.spec.ts
  • test/animation/AnimationManager.spec.ts
  • test/animation/CSSTransitionAnimate.timing.spec.tsx
  • test/animation/CompositeAnimationManager.ts
  • test/animation/JavascriptAnimate.progress.spec.tsx
  • test/animation/JavascriptAnimate.timing.spec.tsx
  • test/animation/ManualTimeoutController.spec.ts
  • test/animation/MockAbstractAnimationManager.ts
  • test/animation/MockProgressAnimationManager.ts
  • test/animation/MockTickingAnimationManager.ts
  • test/animation/ProgressAnimationManager.spec.ts
  • test/animation/RechartsAnimation.spec.ts
  • test/animation/configUpdate.spec.ts
  • test/animation/mockAnimationController.ts
  • test/animation/mockTimeoutController.ts
  • test/animation/util.spec.ts
  • test/cartesian/Area.animation.spec.tsx
  • test/cartesian/Bar.animation.spec.tsx
  • test/cartesian/Line.animation.spec.tsx
  • test/component/Label.spec.tsx
  • test/polar/Pie/Pie.animation.spec.tsx
  • www/src/docs/apiCates.ts
💤 Files with no reviewable changes (15)
  • omnidoc/verifyExamples.spec.ts
  • test/animation/MockTickingAnimationManager.ts
  • test/animation/configUpdate.spec.ts
  • test/animation/ManualTimeoutController.spec.ts
  • test/animation/MockAbstractAnimationManager.ts
  • src/animation/AnimationProgressProvider.tsx
  • src/animation/AnimationManager.ts
  • src/animation/createDefaultAnimationManager.tsx
  • test/animation/ProgressAnimationManager.spec.ts
  • test/animation/AnimationManager.spec.ts
  • src/animation/ManualTimeoutController.ts
  • src/animation/configUpdate.ts
  • src/index.ts
  • src/animation/ProgressAnimationManager.ts
  • src/animation/util.ts
✅ Files skipped from review due to trivial changes (2)
  • src/animation/timeoutController.ts
  • scripts/snapshots/typesFiles.txt
🛑 Comments failed to post (10)
package.json (1)

143-143: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '.nvmrc: '
cat .nvmrc

printf '\n@types/node: '
jq -r '.devDependencies["`@types/node`"] // .dependencies["`@types/node`"] // "not found"' package.json

Repository: recharts/recharts

Length of output: 93


Align @types/node version with Node runtime in .nvmrc.

Currently .nvmrc specifies Node 24, but @types/[email protected] targets Node 25 APIs. This mismatch allows type-checking for Node 25 features that are unavailable in the Node 24 runtime used by dev scripts and tests. Either update .nvmrc to Node 25 or downgrade @types/node to 24.x.x.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 143, The `@types/node` version in package.json is set to
25.x.x but the .nvmrc file specifies Node 24 as the runtime, creating a mismatch
where TypeScript can check for Node 25 APIs that don't exist in the Node 24
runtime. Either update the `@types/node` dependency in package.json to 24.x.x to
match the Node 24 runtime specified in .nvmrc, or alternatively update .nvmrc to
specify Node 25 to match the types version. Choose whichever aligns with your
project's runtime requirements.

Source: Learnings

src/animation/AnimationControllerImpl.ts (1)

18-31: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop scheduling once the handle is completed.

A completed handle’s tick() returns 0; the non-active branch then reschedules forever. Guard completed before the Line 30 fallback so already-completed or custom-completing handles don’t spin.

Suggested fix
   let cancellable: CancelableTimeout | undefined;
   const nextUpdate = (now: number) => {
     const timeRemaining = animationHandle.tick(now);
+    if (animationHandle.getState() === 'completed') {
+      cancellable = undefined;
+      return;
+    }
     if (animationHandle.getState() === 'active') {
       listener(animationHandle.getInterpolated());
       if (animationHandle.getProgress() === 1) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/animation/AnimationControllerImpl.ts` around lines 18 - 31, The
nextUpdate function reschedules indefinitely after an animation completes
because the non-active branch (the fallback setTimeout on line 33) always
reschedules regardless of whether the handle is already completed. When
animationHandle.getState() is no longer 'active' after completion, tick()
returns 0 and the fallback reschedule creates an infinite loop. Add a guard
condition before the final timeoutController.setTimeout call (line 33) to check
if the animation handle is completed, and only reschedule if it is not
completed. This prevents already-completed or custom-completing handles from
spinning with perpetual zero-timeout reschedules.
src/animation/JavascriptAnimate.tsx (1)

66-86: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset progress when starting a new animation.

When this effect restarts after a completed animation, style can still be 1. Because the controller does not emit during the begin delay, children render stale progress until activation, then jump back to 0.

Suggested fix
     if (!isActive || !canBegin || easingFunction == null) {
       return noop;
     }
 
+    setStyle(from);
+
     const timeoutController = new RequestAnimationFrameTimeoutController();
 
     const animation = new JavascriptAnimation({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  useEffect(() => {
    const easingFunction = createEasingFunction(easing);

    if (!isActive || !canBegin || easingFunction == null) {
      return noop;
    }

    setStyle(from);

    const timeoutController = new RequestAnimationFrameTimeoutController();

    const animation = new JavascriptAnimation({
      animationId,
      easing: easingFunction,
      animationDuration: duration,
      animationBegin: begin,
      onAnimationStart,
      onAnimationEnd,
      from,
      to,
    });

    return animationManager(timeoutController, animation, setStyle);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/animation/JavascriptAnimate.tsx` around lines 66 - 86, The useEffect hook
does not reset the animation progress when starting a new animation cycle,
causing stale style values from previous animations to persist. Before creating
the new JavascriptAnimation instance, reset the style state by calling setStyle
with the initial progress value (0) to ensure a clean slate for each new
animation that begins, preventing the jump from stale progress to the correct
value.
src/animation/RechartsAnimation.ts (3)

15-23: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align the init state docs with the synchronous start callback.

Line 18 says init has not announced onAnimationStart, but the constructor calls it synchronously on Lines 171-172. Please update the contract before this becomes public API documentation. As per coding guidelines, “Update TypeScript interfaces and JSDoc comments in source code to automatically update API documentation”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/animation/RechartsAnimation.ts` around lines 15 - 23, The JSDoc comment
for the RechartsAnimation interface's `init` state inaccurately describes when
the `onAnimationStart` callback is called. The documentation currently states
that in the `init` state the animation "did not announce its start yet", but the
constructor actually calls `onAnimationStart` synchronously. Update the JSDoc
documentation for the `init` state (in the RechartsAnimation interface) to
accurately reflect that `onAnimationStart` is invoked synchronously during
construction, ensuring the state description aligns with the actual
implementation behavior.

Source: Coding guidelines


214-216: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle non-positive durations before dividing.

With animationDuration <= 0, Line 215 can produce NaN or negative progress; the controller then keeps scheduling zero-delay updates instead of completing. Treat zero/negative durations as immediately complete progress.

Suggested fix
       }
       const timeElapsed = now - this.animationStartedTime;
+      if (this.animationDuration <= 0) {
+        this.setProgress(1);
+        return 0;
+      }
       this.setProgress(timeElapsed / this.animationDuration);
       return this.nextAnimationUpdate(timeElapsed);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      const timeElapsed = now - this.animationStartedTime;
      if (this.animationDuration <= 0) {
        this.setProgress(1);
        return 0;
      }
      this.setProgress(timeElapsed / this.animationDuration);
      return this.nextAnimationUpdate(timeElapsed);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/animation/RechartsAnimation.ts` around lines 214 - 216, In the code block
containing the setProgress call, check if animationDuration is less than or
equal to 0 before performing the division operation. If animationDuration is
non-positive, immediately set the progress to 1 (complete state) by calling
setProgress(1). Otherwise, proceed with the normal calculation of timeElapsed
divided by animationDuration. This prevents NaN or negative progress values from
being passed to setProgress, which would cause the animation controller to keep
scheduling zero-delay updates instead of completing.

280-282: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply easing to progress before interpolation.

EasingFunction operates on normalized progress. The current order only works for from=0/to=1; for other numeric ranges it eases the interpolated value itself and returns incorrect values.

Suggested fix
   public getInterpolated(): number {
-    return this.easing(interpolate(this.getFrom(), this.getTo(), this.getProgress()));
+    return interpolate(this.getFrom(), this.getTo(), this.easing(this.getProgress()));
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/animation/RechartsAnimation.ts` around lines 280 - 282, The
getInterpolated method in RechartsAnimation currently applies the easing
function to the interpolated result, but it should apply easing to the
normalized progress value before interpolation. Change the order so that the
easing function is applied to this.getProgress() first to get the eased progress
value, then pass that eased progress to the interpolate function along with
this.getFrom() and this.getTo(). This ensures the easing function operates on
the correct normalized progress range regardless of the actual from and to
numeric values.
test/animation/AnimationControllerImpl.spec.ts (1)

135-136: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Second assertion repeats spy2 and misses spy1 pre-cancel check.

Line 136 duplicates the previous assertion, so this step never verifies that animation1 also advanced before cancellation.

Suggested fix
     expect(spy2).toHaveBeenCalledTimes(2);
-    expect(spy2).toHaveBeenCalledTimes(2);
+    expect(spy1).toHaveBeenCalledTimes(2);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/animation/AnimationControllerImpl.spec.ts` around lines 135 - 136, The
second assertion on line 136 duplicates the spy2 check instead of verifying the
pre-cancel state. Replace the duplicate expect(spy2).toHaveBeenCalledTimes(2) on
line 136 with expect(spy1).toHaveBeenCalledTimes(1) to properly assert that spy1
(the callback for animation1) was called once before the cancellation occurred,
ensuring the test validates both animations advanced before cancellation.
test/animation/JavascriptAnimate.timing.spec.tsx (1)

66-68: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Duplicate assertion hides missing end-callback check.

handleAnimationStart is asserted twice, so this case never verifies that onAnimationEnd stays uncalled when canBegin=false.

Suggested fix
       expect(handleAnimationStart).not.toHaveBeenCalled();
-      expect(handleAnimationStart).not.toHaveBeenCalled();
+      expect(handleAnimationEnd).not.toHaveBeenCalled();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/animation/JavascriptAnimate.timing.spec.tsx` around lines 66 - 68, In
the test block around the duplicate assertions, replace the second expect
statement that checks handleAnimationStart.not.toHaveBeenCalled() with an
assertion that verifies handleAnimationEnd is also not called when canBegin is
false. This ensures the test properly validates that both animation callbacks
remain unused when the animation cannot begin.
test/animation/MockProgressAnimationManager.ts (1)

108-117: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

stop() does not cancel the active controller loop.

Line 111 starts a controller but drops the cancel handle, so Line 114 only nulls fields while scheduled callbacks can still run.

Suggested fix
 export class MockProgressAnimationManager<T, E> implements MockAnimationManager {
   private readonly onStop?: () => void;
+  private cancelController?: () => void;
@@
   start(animation: RechartsAnimation<T, E>, listener: (newState: T) => void) {
+    this.cancelController?.();
     this.animation = animation;
     this.isPrimed = false; // Reset the primed state when starting a new animation
-    mockAnimationController(this.timeoutController, this.animation, listener);
+    this.cancelController = mockAnimationController(this.timeoutController, this.animation, listener);
   }
 
   stop() {
+    this.cancelController?.();
+    this.cancelController = undefined;
     this.animation = null;
     this.isPrimed = false; // Reset the primed state when stopping the queue
     this.onStop?.();
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/animation/MockProgressAnimationManager.ts` around lines 108 - 117, The
start() method calls mockAnimationController() but does not capture or store the
cancel handle it returns, so the stop() method has no way to actually cancel the
active controller loop. Store the return value of mockAnimationController() as
an instance variable (likely something like this.cancelAnimation or similar),
and then in the stop() method, call that cancel function before nulling out the
animation and isPrimed fields to ensure scheduled callbacks are properly cleaned
up.
test/animation/mockTimeoutController.ts (1)

39-45: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Check timeoutLimit before mutating the queue.

Line 39 pushes the timeout before Line 41 enforces the limit, so a rejected setTimeout still leaves a queued callback behind.

Suggested fix
-    this.timeouts.push({ callback: copy, delay });
-
-    if (this.config.timeoutLimit != null && this.timeouts.length > this.config.timeoutLimit) {
+    if (this.config.timeoutLimit != null && this.timeouts.length >= this.config.timeoutLimit) {
       throw new Error(
-        `Timeout queue has size ${this.timeouts.length} which is more than the allowed ${this.config.timeoutLimit}`,
+        `Timeout queue has size ${this.timeouts.length + 1} which is more than the allowed ${this.config.timeoutLimit}`,
       );
     }
+    this.timeouts.push({ callback: copy, delay });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/animation/mockTimeoutController.ts` around lines 39 - 45, The
timeoutLimit check in mockTimeoutController.ts occurs after the callback is
already pushed to the this.timeouts queue, allowing the queue to exceed the
limit before the error is thrown. Reorder the logic to validate the timeoutLimit
condition before pushing the callback to the queue, ensuring that if the limit
would be exceeded, the callback is never added and the queue remains in a valid
state when the error is thrown.

@PavelVanecek

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

Copy link
Copy Markdown
Contributor

@PavelVanecek
PavelVanecek marked this pull request as ready for review June 20, 2026 00:31
@github-actions

Copy link
Copy Markdown
Contributor

@PavelVanecek
PavelVanecek merged commit df650b4 into main Jun 20, 2026
65 checks passed
@PavelVanecek
PavelVanecek deleted the rechartsanimation branch June 20, 2026 07:20
PavelVanecek added a commit that referenced this pull request Jun 23, 2026
## Description

So I tried to reproduce the Pie + Legend animation problem and turns out
that I accidentally fixed it in
#7457. So here is at least a
test to prevent regressions. I confirmed I can reproduce in browser and
in the unit test before that commit, and cannot reproduce after the
commit.

## Related Issue

Closes #7254


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Enhanced test infrastructure for pie chart animations with new helper
functions for validating sector paths and angles during animation.
* Added comprehensive test coverage for pie chart animations when a
legend is present, including animation lifecycle callbacks.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

1 participant