solid-recharts 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/LICENSE +24 -0
  2. package/package.json +10 -7
  3. package/src/cartesian/Area.tsx +40 -55
  4. package/src/cartesian/Bar.tsx +66 -58
  5. package/src/cartesian/BarStack.tsx +5 -2
  6. package/src/cartesian/Brush.tsx +1 -1
  7. package/src/cartesian/Funnel.tsx +15 -19
  8. package/src/cartesian/Line.tsx +34 -51
  9. package/src/cartesian/LineDrawShape.tsx +8 -6
  10. package/src/cartesian/ReferenceArea.tsx +25 -35
  11. package/src/cartesian/ReferenceDot.tsx +24 -34
  12. package/src/cartesian/ReferenceLine.tsx +25 -34
  13. package/src/cartesian/Scatter.tsx +25 -37
  14. package/src/cartesian/XAxis.tsx +54 -67
  15. package/src/cartesian/YAxis.tsx +53 -66
  16. package/src/cartesian/ZAxis.tsx +27 -40
  17. package/src/chart/SunburstChart.tsx +1 -1
  18. package/src/component/ActivePoints.tsx +0 -9
  19. package/src/component/Customized.tsx +1 -1
  20. package/src/component/Dots.tsx +7 -3
  21. package/src/context/ErrorBarContext.tsx +15 -24
  22. package/src/polar/Pie.tsx +36 -48
  23. package/src/polar/PolarAngleAxis.tsx +41 -50
  24. package/src/polar/PolarRadiusAxis.tsx +40 -48
  25. package/src/polar/Radar.tsx +22 -34
  26. package/src/polar/RadialBar.tsx +21 -39
  27. package/src/shape/Curve.tsx +1 -1
  28. package/src/shape/Symbols.tsx +1 -1
  29. package/src/state/RechartsReduxContext.tsx +1 -1
  30. package/src/state/SetLegendPayload.ts +19 -27
  31. package/src/state/SetTooltipEntrySettings.tsx +9 -26
  32. package/src/state/cartesianAxisSlice.ts +40 -60
  33. package/src/state/createCollectionActions.ts +51 -0
  34. package/src/state/createSelector.ts +97 -0
  35. package/src/state/externalEventsMiddleware.ts +2 -11
  36. package/src/state/graphicalItemsSlice.ts +27 -40
  37. package/src/state/isEventThrottled.ts +12 -0
  38. package/src/state/keyboardEventsMiddleware.ts +2 -8
  39. package/src/state/layoutSlice.ts +8 -4
  40. package/src/state/legendSlice.ts +24 -26
  41. package/src/state/mouseEventsMiddleware.ts +2 -13
  42. package/src/state/patched.ts +19 -0
  43. package/src/state/referenceElementsSlice.ts +40 -31
  44. package/src/state/registerInStore.ts +68 -0
  45. package/src/state/selectors/areaSelectors.ts +13 -36
  46. package/src/state/selectors/arrayEqualityCheck.ts +32 -7
  47. package/src/state/selectors/axisSelectors.ts +215 -169
  48. package/src/state/selectors/barSelectors.ts +8 -36
  49. package/src/state/selectors/combiners/combineConfiguredScale.ts +1 -1
  50. package/src/state/selectors/combiners/combineRealScaleType.ts +1 -1
  51. package/src/state/selectors/dataSelectors.ts +35 -16
  52. package/src/state/selectors/numberDomainEqualityCheck.ts +16 -8
  53. package/src/state/selectors/selectAxisForGraphicalItem.ts +52 -0
  54. package/src/state/store.ts +0 -1
  55. package/src/state/tooltipSlice.ts +93 -64
  56. package/src/util/ActiveShapeUtils.tsx +0 -7
  57. package/src/util/BarUtils.tsx +27 -22
  58. package/src/util/ChartUtils.ts +69 -25
  59. package/src/util/propsAreEqual.ts +3 -33
  60. package/src/util/stacks/stackTypes.ts +1 -1
  61. package/src/util/types.ts +2 -2
  62. package/test/animation/CSSTransitionAnimate.timing.spec.tsx +1 -1
  63. package/test/animation/JavascriptAnimate.spec.tsx +1 -1
  64. package/test/animation/legacyAnimationLengthChange.spec.tsx +1 -1
  65. package/test/cartesian/Area.animation.spec.tsx +1 -1
  66. package/test/cartesian/Area.range.spec.tsx +1 -1
  67. package/test/cartesian/Area.spec.tsx +1 -1
  68. package/test/cartesian/Area.stacked.render.spec.tsx +1 -1
  69. package/test/cartesian/Area.typed.spec.tsx +1 -1
  70. package/test/cartesian/Axis.integration.spec.tsx +1 -1
  71. package/test/cartesian/Bar/Bar.csstransition.spec.tsx +1 -1
  72. package/test/cartesian/Bar/Bar.spec.tsx +1 -1
  73. package/test/cartesian/Bar/Bar.typed.spec.tsx +1 -1
  74. package/test/cartesian/Bar.animation.spec.tsx +1 -1
  75. package/test/cartesian/Bar.stackDomain.spec.tsx +1 -1
  76. package/test/cartesian/Bar.stacked.render.spec.tsx +1 -1
  77. package/test/cartesian/Bar.zeroDimensionFiltering.spec.tsx +1 -1
  78. package/test/cartesian/Brush.keyboard.spec.tsx +1 -1
  79. package/test/cartesian/Brush.panorama.spec.tsx +1 -1
  80. package/test/cartesian/Brush.props.spec.tsx +1 -1
  81. package/test/cartesian/Brush.render.spec.tsx +1 -1
  82. package/test/cartesian/Brush.spec.tsx +1 -1
  83. package/test/cartesian/Brush.stacked.spec.tsx +1 -1
  84. package/test/cartesian/CartesianAxis.render.spec.tsx +1 -1
  85. package/test/cartesian/CartesianAxis.spec.tsx +2 -2
  86. package/test/cartesian/CartesianGrid.render.spec.tsx +1 -1
  87. package/test/cartesian/CartesianGrid.spec.tsx +1 -1
  88. package/test/cartesian/ErrorBar.render.spec.tsx +1 -1
  89. package/test/cartesian/ErrorBar.spec.tsx +1 -1
  90. package/test/cartesian/Funnel.animation.spec.tsx +1 -1
  91. package/test/cartesian/Funnel.spec.tsx +1 -1
  92. package/test/cartesian/Funnel.typed.spec.tsx +1 -1
  93. package/test/cartesian/GraphicalItemClipPath.scale-behavior.spec.tsx +2 -2
  94. package/test/cartesian/Line.animation.spec.tsx +1 -1
  95. package/test/cartesian/Line.sparseAnimationPath.spec.tsx +1 -1
  96. package/test/cartesian/Line.spec.tsx +1 -1
  97. package/test/cartesian/Line.typed.spec.tsx +1 -1
  98. package/test/cartesian/ReferenceArea.render.spec.tsx +1 -1
  99. package/test/cartesian/ReferenceArea.spec.tsx +1 -1
  100. package/test/cartesian/ReferenceArea.typed.spec.tsx +1 -1
  101. package/test/cartesian/ReferenceDot.render.spec.tsx +1 -1
  102. package/test/cartesian/ReferenceDot.spec.tsx +1 -1
  103. package/test/cartesian/ReferenceDot.typed.spec.tsx +1 -1
  104. package/test/cartesian/ReferenceLine/ReferenceLine.panorama.spec.tsx +1 -1
  105. package/test/cartesian/ReferenceLine/ReferenceLine.spec.tsx +1 -1
  106. package/test/cartesian/ReferenceLine/ReferenceLine.typed.spec.tsx +1 -1
  107. package/test/cartesian/ReferenceLine/getEndPoints.spec.ts +1 -1
  108. package/test/cartesian/ReferenceLine.render.spec.tsx +1 -1
  109. package/test/cartesian/Scatter.animation.spec.tsx +1 -1
  110. package/test/cartesian/Scatter.render.spec.tsx +1 -1
  111. package/test/cartesian/Scatter.spec.tsx +1 -1
  112. package/test/cartesian/Scatter.typed.spec.tsx +1 -1
  113. package/test/cartesian/XAxis/XAxis.barSize.spec.tsx +1 -1
  114. package/test/cartesian/XAxis/XAxis.brush.spec.tsx +1 -1
  115. package/test/cartesian/XAxis/XAxis.categorydomain.spec.tsx +1 -1
  116. package/test/cartesian/XAxis/XAxis.datatypes.spec.tsx +1 -1
  117. package/test/cartesian/XAxis/XAxis.hide.spec.tsx +1 -1
  118. package/test/cartesian/XAxis/XAxis.multiaxis.spec.tsx +1 -1
  119. package/test/cartesian/XAxis/XAxis.numberdomain.spec.tsx +1 -1
  120. package/test/cartesian/XAxis/XAxis.padding.spec.tsx +1 -1
  121. package/test/cartesian/XAxis/XAxis.padding_clip.spec.tsx +1 -1
  122. package/test/cartesian/XAxis/XAxis.state.spec.tsx +1 -1
  123. package/test/cartesian/XAxis/XAxis.tick.spec.tsx +1 -1
  124. package/test/cartesian/XAxis/XAxis.timescale.spec.tsx +2 -2
  125. package/test/cartesian/XAxis/XAxis.typed.spec.tsx +1 -1
  126. package/test/cartesian/XAxis/XAxis.vertical.spec.tsx +1 -1
  127. package/test/cartesian/XAxis.tickFormatter.spec.tsx +1 -1
  128. package/test/cartesian/YAxis/YAxis.7362.derivedDomain.spec.tsx +1 -1
  129. package/test/cartesian/YAxis/YAxis.interval-zero.spec.tsx +1 -1
  130. package/test/cartesian/YAxis/YAxis.label.spec.tsx +1 -1
  131. package/test/cartesian/YAxis/YAxis.spec.tsx +1 -1
  132. package/test/cartesian/YAxis/YAxis.tick.spec.tsx +1 -1
  133. package/test/cartesian/YAxis/YAxis.tickFormatter.spec.tsx +1 -1
  134. package/test/cartesian/YAxis/YAxis.typed.spec.tsx +1 -1
  135. package/test/cartesian/ZAxis.spec.tsx +1 -1
  136. package/test/cartesian/ZAxis.typed.spec.tsx +1 -1
  137. package/test/cartesian/axisLineAndStroke.spec.tsx +1 -1
  138. package/test/cartesian/miscFeatureGaps.spec.tsx +1 -1
  139. package/test/chart/AccessibilityLayer.spec.tsx +1 -1
  140. package/test/chart/AccessibilityScans.spec.tsx +2 -2
  141. package/test/chart/AreaChart.spec.tsx +1 -1
  142. package/test/chart/AreaChart.typed.spec.tsx +1 -1
  143. package/test/chart/BarChart.spec.tsx +1 -1
  144. package/test/chart/BarChart.typed.spec.tsx +1 -1
  145. package/test/chart/CategoricalChart.spec.tsx +1 -1
  146. package/test/chart/ComposedChart.spec.tsx +1 -1
  147. package/test/chart/ComposedChart.typed.spec.tsx +1 -1
  148. package/test/chart/FunnelChart.spec.tsx +1 -1
  149. package/test/chart/FunnelChart.typed.spec.tsx +1 -1
  150. package/test/chart/LineChart.smoke.spec.tsx +1 -1
  151. package/test/chart/LineChart.spec.tsx +1 -1
  152. package/test/chart/LineChart.typed.spec.tsx +1 -1
  153. package/test/chart/NicheChartTooltip.spec.tsx +1 -1
  154. package/test/chart/PieChart.spec.tsx +1 -1
  155. package/test/chart/PieChart.typed.spec.tsx +1 -1
  156. package/test/chart/RadarChart.spec.tsx +1 -1
  157. package/test/chart/RadarChart.typed.spec.tsx +1 -1
  158. package/test/chart/RadialBarChart.spec.tsx +1 -1
  159. package/test/chart/RadialBarChart.typed.spec.tsx +1 -1
  160. package/test/chart/RechartsWrapper.spec.tsx +1 -1
  161. package/test/chart/Sankey.content.spec.tsx +1 -1
  162. package/test/chart/Sankey.spec.tsx +1 -1
  163. package/test/chart/ScatterChart.spec.tsx +1 -1
  164. package/test/chart/ScatterChart.typed.spec.tsx +1 -1
  165. package/test/chart/SunburstChart.spec.tsx +1 -1
  166. package/test/chart/Treemap.animation.spec.tsx +1 -1
  167. package/test/chart/Treemap.spec.tsx +1 -1
  168. package/test/chart/chartEvents.spec.tsx +1 -1
  169. package/test/chart/responsive.spec.tsx +1 -1
  170. package/test/component/Cell.render.spec.tsx +1 -1
  171. package/test/component/Cell.spec.tsx +1 -1
  172. package/test/component/Cursor.spec.tsx +1 -1
  173. package/test/component/DefaultTooltipContent.spec.tsx +1 -1
  174. package/test/component/Label.render.spec.tsx +1 -1
  175. package/test/component/Label.spec.tsx +1 -1
  176. package/test/component/LabelList.spec.tsx +1 -1
  177. package/test/component/Legend.itemSorter.spec.tsx +1 -1
  178. package/test/component/Legend.render.spec.tsx +1 -1
  179. package/test/component/Legend.spec.tsx +1 -1
  180. package/test/component/ResponsiveContainer.spec.tsx +1 -1
  181. package/test/component/ResponsiveContainerDataTest.spec.tsx +1 -1
  182. package/test/component/Text.spec.tsx +1 -1
  183. package/test/component/Tooltip/ActiveDot.spec.tsx +1 -1
  184. package/test/component/Tooltip/tooltipEventType.spec.tsx +1 -1
  185. package/test/component/Tooltip/tooltipTestHelpers.tsx +1 -1
  186. package/test/component/Tooltip.defaultIndex.spec.tsx +1 -1
  187. package/test/component/Tooltip.hover.spec.tsx +1 -1
  188. package/test/component/Tooltip.item.spec.tsx +1 -1
  189. package/test/component/Tooltip.keyboard.spec.tsx +1 -1
  190. package/test/component/Tooltip.payload.spec.tsx +1 -1
  191. package/test/component/Tooltip.touch.spec.tsx +1 -1
  192. package/test/component/Tooltip.visibility.spec.tsx +1 -1
  193. package/test/component/TooltipBoundingBox.spec.tsx +2 -2
  194. package/test/container/ClipPath.spec.tsx +1 -1
  195. package/test/context/chartLayoutContext.spec.tsx +1 -1
  196. package/test/helper/createSelectorTestCase.tsx +1 -1
  197. package/test/helper/mockAxes.ts +1 -1
  198. package/test/helper/selectorTestHelpers.tsx +1 -1
  199. package/test/hooks/useActiveTooltipDataPoints.spec.tsx +1 -1
  200. package/test/hooks/useOffset.spec.tsx +1 -1
  201. package/test/hooks/useTooltipStateHooks.spec.tsx +1 -1
  202. package/test/polar/Pie/Pie-TwoLevelPieChart.spec.tsx +1 -1
  203. package/test/polar/Pie/Pie.spec.tsx +1 -1
  204. package/test/polar/Pie/Pie.typed.spec.tsx +1 -1
  205. package/test/polar/Pie.animation.spec.tsx +1 -1
  206. package/test/polar/PieWithLegend.animation.spec.tsx +1 -1
  207. package/test/polar/PolarAngleAxis/PolarAngleAxis.events.spec.tsx +1 -1
  208. package/test/polar/PolarAngleAxis/PolarAngleAxis.spec.tsx +1 -1
  209. package/test/polar/PolarAngleAxis/PolarAngleAxis.typed.spec.tsx +1 -1
  210. package/test/polar/PolarAngleAxis.render.spec.tsx +1 -1
  211. package/test/polar/PolarGrid.render.spec.tsx +1 -1
  212. package/test/polar/PolarGrid.spec.tsx +1 -1
  213. package/test/polar/PolarLegendPerRow.render.spec.tsx +1 -1
  214. package/test/polar/PolarRadiusAxis.events.spec.tsx +1 -1
  215. package/test/polar/PolarRadiusAxis.render.spec.tsx +1 -1
  216. package/test/polar/PolarRadiusAxis.spec.tsx +1 -1
  217. package/test/polar/PolarRadiusAxis.typed.spec.tsx +1 -1
  218. package/test/polar/PolarTooltipLegend.render.spec.tsx +1 -1
  219. package/test/polar/Radar.animation.spec.tsx +1 -1
  220. package/test/polar/Radar.render.spec.tsx +1 -1
  221. package/test/polar/Radar.spec.tsx +1 -1
  222. package/test/polar/Radar.typed.spec.tsx +1 -1
  223. package/test/polar/RadialBar/RadialBar.spec.tsx +1 -1
  224. package/test/polar/RadialBar/RadialBar.typed.spec.tsx +1 -1
  225. package/test/polar/RadialBar.animation.spec.tsx +1 -1
  226. package/test/polar/RadialBar.render.spec.tsx +1 -1
  227. package/test/polar/RadialBar.stacked.spec.tsx +1 -1
  228. package/test/setup.ts +1 -1
  229. package/test/shape/ActiveShape.spec.tsx +1 -1
  230. package/test/shape/Cross.spec.tsx +1 -1
  231. package/test/shape/Curve.spec.tsx +2 -2
  232. package/test/shape/Curve.typed.spec.tsx +1 -1
  233. package/test/shape/Dot.spec.tsx +1 -1
  234. package/test/shape/Polygon.spec.tsx +1 -1
  235. package/test/shape/Rectangle.animation.spec.tsx +1 -1
  236. package/test/shape/Rectangle.spec.tsx +1 -1
  237. package/test/shape/Sector.spec.tsx +1 -1
  238. package/test/shape/Symbols.spec.tsx +1 -1
  239. package/test/shape/Trapezoid.spec.tsx +1 -1
  240. package/test/state/createCollectionActions.spec.ts +87 -0
  241. package/test/state/createSelector.spec.ts +158 -0
  242. package/test/state/hooks.spec.tsx +1 -1
  243. package/test/state/registerInStore.spec.tsx +101 -0
  244. package/test/state/selectors/axisSelectors.spec.tsx +1 -1
  245. package/test/state/selectors/combiners/combineConfiguredScale.spec.ts +1 -1
  246. package/test/state/selectors/combiners/combineRealScaleType.spec.ts +1 -1
  247. package/test/state/selectors/dataSelectors.spec.tsx +1 -1
  248. package/test/state/selectors/legendSelectors.spec.tsx +1 -1
  249. package/test/state/selectors/radarSelectors.spec.tsx +1 -1
  250. package/test/state/selectors/selectActiveTooltipIndex.spec.tsx +1 -1
  251. package/test/state/selectors/selectDisplayedData.spec.tsx +1 -1
  252. package/test/state/selectors/selectStackGroups.spec.tsx +1 -1
  253. package/test/state/selectors/selectorMemoization.spec.tsx +136 -0
  254. package/test/state/selectors/selectors.spec.tsx +1 -1
  255. package/test/util/CartesianUtils/CartesianUtils.spec.ts +1 -1
  256. package/test/util/ChartUtils/ChartUtils.spec.ts +1 -1
  257. package/test/util/ChartUtils/getCateCoordinateOfLine.spec.ts +14 -0
  258. package/test/util/ChartUtils/getStackedData.spec.ts +1 -1
  259. package/test/util/ChartUtils/truncateByDomain.spec.ts +1 -1
  260. package/test/util/DomUtils.spec.tsx +1 -1
  261. package/test/util/FunnelUtils.spec.tsx +1 -1
  262. package/test/util/scale/createCategoricalInverse.spec.ts +1 -1
  263. package/test/util/useElementOffset.spec.tsx +1 -1
  264. package/test/util/usePrefersReducedMotion.spec.ts +1 -1
  265. package/test/util/useUniqueId.spec.ts +1 -1
  266. package/test/zIndex/AllZIndexPortals.spec.tsx +1 -1
  267. package/test/zIndex/DynamicZIndex.spec.tsx +1 -1
  268. package/test/zIndex/ZIndexLayer.portalOwner.spec.tsx +1 -1
  269. package/test/zIndex/componentZIndex.spec.tsx +1 -1
  270. package/test/zIndex/multiChartZIndexIsolation.spec.tsx +1 -1
  271. package/test-results/.last-run.json +4 -0
  272. package/NEXT_SESSION_PROMPT.md +0 -114
  273. package/src/state/reduxDevtoolsJsonStringifyReplacer.ts +0 -15
  274. package/src/util/axisPropsAreEqual.ts +0 -42
  275. package/src/util/resolveDefaultProps.tsx +0 -8
  276. package/src/util/typedDataKey.ts +0 -8
  277. package/test/helper/render.ts +0 -7
  278. package/test/state/reduxDevtoolsJsonStringifyReplacer.spec.ts +0 -25
  279. package/test/util/axisPropsAreEqual.spec.ts +0 -93
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Guehenneux
4
+
5
+ Portions derived from recharts (https://github.com/recharts/recharts),
6
+ Copyright (c) 2015 recharts, released under the MIT License.
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
package/package.json CHANGED
@@ -1,23 +1,26 @@
1
1
  {
2
2
  "name": "solid-recharts",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
+ "license": "MIT",
4
5
  "type": "module",
5
6
  "sideEffects": false,
6
7
  "exports": {
7
8
  ".": "./src/index.ts"
8
9
  },
9
10
  "dependencies": {
10
- "tiny-invariant": "^1.3.3",
11
- "victory-vendor": "^37.0.2"
11
+ "d3-scale": "^4.0.2",
12
+ "d3-shape": "^3.1.0"
12
13
  },
13
14
  "devDependencies": {
15
+ "@solidjs/testing-library": "^1.0.0-beta.2",
16
+ "@types/d3-scale": "^4.0.2",
17
+ "@types/d3-shape": "^3.1.0",
14
18
  "axe-core": "^4.12.0",
15
- "vitest-axe": "^0.1.0",
16
- "@solid-recharts/testing-library": "0.0.0"
19
+ "vitest-axe": "^0.1.0"
17
20
  },
18
21
  "peerDependencies": {
19
- "@solidjs/web": "^2.0.0-beta.14",
20
- "solid-js": "^2.0.0-beta.14"
22
+ "@solidjs/web": "^2.0.0-beta.17",
23
+ "solid-js": "^2.0.0-beta.17"
21
24
  },
22
25
  "scripts": {
23
26
  "pretest": "node ../../scripts/verify-reference.mjs",
@@ -11,10 +11,14 @@
11
11
  // registration components → `createEffect` add/replace + `onCleanup` (Line idiom);
12
12
  // React `useRef` prev-frame state → plain `{ current }` MutableRef objects driven
13
13
  // by AnimatedItems/useAnimationStartSnapshot.
14
- import { createEffect, createMemo, createSignal, merge, Show } from "solid-js";
14
+ import { createMemo, merge, Show } from "solid-js";
15
15
  import type { JSX } from "@solidjs/web";
16
16
  import { useAppSelector, useAppStore } from "../state/hooks";
17
- import { AnimatedItems, type AnimationInterpolateFn } from "../animation/AnimatedItems";
17
+ import {
18
+ AnimatedItems,
19
+ useAnimationCallbacks,
20
+ type AnimationInterpolateFn,
21
+ } from "../animation/AnimatedItems";
18
22
  import {
19
23
  matchAnimationItems,
20
24
  matchByIndex,
@@ -78,7 +82,7 @@ import { interpolate, isNan, isNullish, isNumber } from "../util/DataUtils";
78
82
  import { BaseAxisWithScale } from "../state/selectors/axisSelectors";
79
83
  import { StackId } from "../util/ChartUtils";
80
84
  import { StackDataPoint } from "../util/stacks/stackTypes";
81
- import { unregisterOnCleanup } from "../state/unregisterOnCleanup";
85
+ import { registerInStore } from "../state/registerInStore";
82
86
  import { AreaRevealShape, type AreaRevealShapeProps } from "./AreaRevealShape";
83
87
 
84
88
  function getLegendItemColor(
@@ -417,45 +421,33 @@ export const Area = (props: AreaProps): JSX.Element => {
417
421
  const layout = useCartesianChartLayout();
418
422
  const id = useUniqueId("recharts-area", props.id);
419
423
 
420
- let prev: AreaSettings | null = null;
421
-
422
- createEffect(
423
- (): AreaSettings => ({
424
- id,
425
- type: "area",
426
- isPanorama,
427
- data: merged.data,
428
- dataKey: merged.dataKey ?? "",
429
- xAxisId: merged.xAxisId,
430
- yAxisId: merged.yAxisId,
431
- zAxisId: 0,
432
- hide: merged.hide,
433
- // Area has no bar size; the field exists only because AreaSettings shares
434
- // the MaybeStackedGraphicalItem shape with Bar.
435
- barSize: undefined,
436
- stackId: merged.stackId == null ? undefined : String(merged.stackId),
437
- connectNulls: merged.connectNulls,
438
- baseValue: merged.baseValue,
439
- }),
440
- (settings) => {
441
- if (store == null) {
442
- return;
443
- }
444
- if (prev === null) {
445
- store.actions.graphicalItems.addCartesianGraphicalItem(settings);
446
- } else if (prev !== settings) {
447
- store.actions.graphicalItems.replaceCartesianGraphicalItem({ prev, next: settings });
448
- }
449
- prev = settings;
450
- },
451
- );
452
-
453
- unregisterOnCleanup(() => {
454
- if (prev && store != null) {
455
- store.actions.graphicalItems.removeCartesianGraphicalItem(prev);
456
- prev = null;
457
- }
458
- });
424
+ if (store != null) {
425
+ registerInStore<AreaSettings>(
426
+ () => ({
427
+ id,
428
+ type: "area",
429
+ isPanorama,
430
+ data: merged.data,
431
+ dataKey: merged.dataKey ?? "",
432
+ xAxisId: merged.xAxisId,
433
+ yAxisId: merged.yAxisId,
434
+ zAxisId: 0,
435
+ hide: merged.hide,
436
+ // Area has no bar size; the field exists only because AreaSettings shares
437
+ // the MaybeStackedGraphicalItem shape with Bar.
438
+ barSize: undefined,
439
+ stackId: merged.stackId == null ? undefined : String(merged.stackId),
440
+ connectNulls: merged.connectNulls,
441
+ baseValue: merged.baseValue,
442
+ }),
443
+ {
444
+ add: (next) => store.actions.graphicalItems.addCartesianGraphicalItem(next),
445
+ replace: (prev, next) =>
446
+ store.actions.graphicalItems.replaceCartesianGraphicalItem({ prev, next }),
447
+ remove: (prev) => store.actions.graphicalItems.removeCartesianGraphicalItem(prev),
448
+ },
449
+ );
450
+ }
459
451
 
460
452
  const area = useAppSelector((state) => selectArea(state, id, isPanorama));
461
453
 
@@ -569,18 +561,11 @@ export const Area = (props: AreaProps): JSX.Element => {
569
561
  });
570
562
  });
571
563
  // Labels are hidden while the entrance/update animation runs, shown once it ends.
572
- // ownedWrite: handleAnimationEnd also fires from the driver's effect cleanup when
573
- // a mid-animation chart is disposed (route/soft navigation) a legal teardown.
574
- const [showLabels, setShowLabels] = createSignal(true, { ownedWrite: true });
575
- const labelContextEntries = createMemo(() => (showLabels() ? labelListEntries() : undefined));
576
- const handleAnimationStart = (): void => {
577
- setShowLabels(false);
578
- props.onAnimationStart?.();
579
- };
580
- const handleAnimationEnd = (): void => {
581
- setShowLabels(true);
582
- props.onAnimationEnd?.();
583
- };
564
+ const { isAnimating, handleAnimationStart, handleAnimationEnd } = useAnimationCallbacks(
565
+ () => props.onAnimationStart?.(),
566
+ () => props.onAnimationEnd?.(),
567
+ );
568
+ const labelContextEntries = createMemo(() => (isAnimating() ? undefined : labelListEntries()));
584
569
 
585
570
  // Per-frame prev geometry (recharts' previousPointsRef / previousBaselineRef):
586
571
  // plain `{ current }` refs (NOT signals) driven by AnimatedItems +
@@ -761,7 +746,7 @@ export const Area = (props: AreaProps): JSX.Element => {
761
746
  stepBaseLine,
762
747
  target.isRange,
763
748
  animationElapsedTime,
764
- !showLabels() || animationElapsedTime < 1,
749
+ isAnimating() || animationElapsedTime < 1,
765
750
  effectiveIsEntrance,
766
751
  );
767
752
  }}
@@ -93,7 +93,7 @@ import {
93
93
  import { BaseAxisWithScale } from "../state/selectors/axisSelectors";
94
94
  import { StackId } from "../util/ChartUtils";
95
95
  import { BarStackClipLayer, useStackId } from "./BarStack";
96
- import { unregisterOnCleanup } from "../state/unregisterOnCleanup";
96
+ import { registerInStore } from "../state/registerInStore";
97
97
 
98
98
  const defaultMinPointSize = 0;
99
99
 
@@ -419,7 +419,10 @@ function BarBackground(props: {
419
419
  if (!backgroundFromProps) {
420
420
  return undefined;
421
421
  }
422
- const active = activeIndex();
422
+ // NOTE: intentionally does NOT read activeIndex() here — that would rebuild
423
+ // the whole rects array (and recreate every background-rect DOM node) on
424
+ // every hover move. `isActive` is derived per-rect in the JSX below, so the
425
+ // array stays stable across hover and only the active flag updates.
423
426
  const dataKey = props.dataKey;
424
427
  const backgroundProps = svgPropertiesNoEventsFromUnknown(backgroundFromProps);
425
428
  const backgroundEventProps = svgPropertiesAndEventsFromUnknown({
@@ -441,7 +444,7 @@ function BarBackground(props: {
441
444
  return [
442
445
  {
443
446
  option: backgroundFromProps,
444
- isActive: String(entry.originalDataIndex) === active,
447
+ isActive: false, // placeholder; derived reactively per-rect in the JSX below
445
448
  ...rest,
446
449
  fill: "#eee",
447
450
  ...bg,
@@ -465,7 +468,12 @@ function BarBackground(props: {
465
468
  <ZIndexLayer zIndex={getZIndexFromUnknown(props.background, DefaultZIndexes.barBackground)}>
466
469
  <Layer>
467
470
  <For each={rects()}>
468
- {(barRectangleProps) => <BarRectangle {...barRectangleProps} />}
471
+ {(barRectangleProps) => (
472
+ <BarRectangle
473
+ {...barRectangleProps}
474
+ isActive={String(barRectangleProps.originalDataIndex) === activeIndex()}
475
+ />
476
+ )}
469
477
  </For>
470
478
  </Layer>
471
479
  </ZIndexLayer>
@@ -850,44 +858,32 @@ export const Bar = (props: BarProps): JSX.Element => {
850
858
  const activeBar = merged.activeBar;
851
859
  const hasCustomShape = shape !== defaultBarShape;
852
860
 
853
- let prev: BarSettings | null = null;
854
-
855
- createEffect(
856
- (): BarSettings => ({
857
- id,
858
- type: "bar",
859
- isPanorama,
860
- data: merged.data,
861
- dataKey: merged.dataKey,
862
- xAxisId: merged.xAxisId,
863
- yAxisId: merged.yAxisId,
864
- zAxisId: 0,
865
- hide: merged.hide,
866
- stackId,
867
- barSize: merged.barSize,
868
- maxBarSize: merged.maxBarSize,
869
- minPointSize: merged.minPointSize,
870
- hasCustomShape,
871
- }),
872
- (settings) => {
873
- if (store == null) {
874
- return;
875
- }
876
- if (prev === null) {
877
- store.actions.graphicalItems.addCartesianGraphicalItem(settings);
878
- } else if (prev !== settings) {
879
- store.actions.graphicalItems.replaceCartesianGraphicalItem({ prev, next: settings });
880
- }
881
- prev = settings;
882
- },
883
- );
884
-
885
- unregisterOnCleanup(() => {
886
- if (prev && store != null) {
887
- store.actions.graphicalItems.removeCartesianGraphicalItem(prev);
888
- prev = null;
889
- }
890
- });
861
+ if (store != null) {
862
+ registerInStore<BarSettings>(
863
+ () => ({
864
+ id,
865
+ type: "bar",
866
+ isPanorama,
867
+ data: merged.data,
868
+ dataKey: merged.dataKey,
869
+ xAxisId: merged.xAxisId,
870
+ yAxisId: merged.yAxisId,
871
+ zAxisId: 0,
872
+ hide: merged.hide,
873
+ stackId,
874
+ barSize: merged.barSize,
875
+ maxBarSize: merged.maxBarSize,
876
+ minPointSize: merged.minPointSize,
877
+ hasCustomShape,
878
+ }),
879
+ {
880
+ add: (next) => store.actions.graphicalItems.addCartesianGraphicalItem(next),
881
+ replace: (prev, next) =>
882
+ store.actions.graphicalItems.replaceCartesianGraphicalItem({ prev, next }),
883
+ remove: (prev) => store.actions.graphicalItems.removeCartesianGraphicalItem(prev),
884
+ },
885
+ );
886
+ }
891
887
 
892
888
  // Per-bar <Cell> overrides (collected from children, in data order).
893
889
  const cells = collectCells(() => props.children);
@@ -1112,7 +1108,15 @@ export const Bar = (props: BarProps): JSX.Element => {
1112
1108
  >
1113
1109
  {(frame, animationElapsedTime, isEntrance) => (
1114
1110
  <Layer>
1115
- <For each={frame}>
1111
+ {/* keyed={false} (index-keyed): `frame` is a fresh array of
1112
+ fresh rect objects every animation tick; reference-keyed
1113
+ <For> would dispose+recreate every bar's DOM node and
1114
+ rebuild its ~10 event closures per frame. Index-keying
1115
+ reuses the rows (geometry updates reactively via the
1116
+ getter-wrapped props) and builds the handlers once per
1117
+ slot. `r` is now an accessor — event handlers read `r()`
1118
+ at dispatch time so they see the current frame's rect. */}
1119
+ <For each={frame} keyed={false}>
1116
1120
  {(r, i) => (
1117
1121
  // Render every selector-output rectangle, matching recharts
1118
1122
  // (zero-dimension genuinely-empty bars are already dropped at
@@ -1124,12 +1128,12 @@ export const Bar = (props: BarProps): JSX.Element => {
1124
1128
  // here suppressed all animated bars until the animation was
1125
1129
  // driven to a non-zero frame.
1126
1130
  <BarRectangleLayer
1127
- entry={r}
1128
- displayIndex={i()}
1131
+ entry={r()}
1132
+ displayIndex={i}
1129
1133
  dataKey={merged.dataKey}
1130
1134
  activeBar={activeBar}
1131
- fill={r.fill ?? r.cell?.fill ?? merged.fill}
1132
- stroke={r.stroke ?? r.cell?.stroke ?? merged.stroke}
1135
+ fill={r().fill ?? r().cell?.fill ?? merged.fill}
1136
+ stroke={r().stroke ?? r().cell?.stroke ?? merged.stroke}
1133
1137
  radius={merged.radius}
1134
1138
  shape={shape}
1135
1139
  animationElapsedTime={animationElapsedTime}
@@ -1137,43 +1141,47 @@ export const Bar = (props: BarProps): JSX.Element => {
1137
1141
  isAnimating={isAnimating() || animationElapsedTime < 1}
1138
1142
  isEntrance={isEntrance}
1139
1143
  isEntryActive={isEntryActive}
1140
- cellClass={typeof r.class === "string" ? r.class : undefined}
1141
- onMouseEnter={onItemEnter(r, r.originalDataIndex)}
1142
- onMouseLeave={onItemLeave(r, r.originalDataIndex)}
1143
- onClick={onItemClick(r, r.originalDataIndex)}
1144
+ cellClass={typeof r().class === "string" ? r().class : undefined}
1145
+ onMouseEnter={(event) =>
1146
+ onItemEnter(r(), r().originalDataIndex)(event)
1147
+ }
1148
+ onMouseLeave={(event) =>
1149
+ onItemLeave(r(), r().originalDataIndex)(event)
1150
+ }
1151
+ onClick={(event) => onItemClick(r(), r().originalDataIndex)(event)}
1144
1152
  onMouseOver={
1145
1153
  onMouseOverHandler != null
1146
- ? (event) => onMouseOverHandler(r, r.originalDataIndex, event)
1154
+ ? (event) => onMouseOverHandler(r(), r().originalDataIndex, event)
1147
1155
  : undefined
1148
1156
  }
1149
1157
  onMouseOut={
1150
1158
  onMouseOutHandler != null
1151
- ? (event) => onMouseOutHandler(r, r.originalDataIndex, event)
1159
+ ? (event) => onMouseOutHandler(r(), r().originalDataIndex, event)
1152
1160
  : undefined
1153
1161
  }
1154
1162
  onMouseMove={
1155
1163
  onMouseMoveHandler != null
1156
- ? (event) => onMouseMoveHandler(r, r.originalDataIndex, event)
1164
+ ? (event) => onMouseMoveHandler(r(), r().originalDataIndex, event)
1157
1165
  : undefined
1158
1166
  }
1159
1167
  onMouseDown={
1160
1168
  onMouseDownHandler != null
1161
- ? (event) => onMouseDownHandler(r, r.originalDataIndex, event)
1169
+ ? (event) => onMouseDownHandler(r(), r().originalDataIndex, event)
1162
1170
  : undefined
1163
1171
  }
1164
1172
  onMouseUp={
1165
1173
  onMouseUpHandler != null
1166
- ? (event) => onMouseUpHandler(r, r.originalDataIndex, event)
1174
+ ? (event) => onMouseUpHandler(r(), r().originalDataIndex, event)
1167
1175
  : undefined
1168
1176
  }
1169
1177
  onTouchMove={
1170
1178
  onTouchMoveHandler != null
1171
- ? (event) => onTouchMoveHandler(r, i(), event)
1179
+ ? (event) => onTouchMoveHandler(r(), i, event)
1172
1180
  : undefined
1173
1181
  }
1174
1182
  onTouchEnd={
1175
1183
  onTouchEndHandler != null
1176
- ? (event) => onTouchEndHandler(r, i(), event)
1184
+ ? (event) => onTouchEndHandler(r(), i, event)
1177
1185
  : undefined
1178
1186
  }
1179
1187
  />
@@ -73,11 +73,14 @@ export const useBarStackClipPathUrl = (index: number): string | undefined => {
73
73
  };
74
74
 
75
75
  export const BarStackClipLayer = (props: LayerProps & { index: number }): JSX.Element => {
76
- const clipPathUrl = useBarStackClipPathUrl(props.index);
76
+ // Memoized so `props.index` is read in a tracking scope: when an index-keyed
77
+ // <For> feeds a reactive `entry.originalDataIndex` here, a bare hook call would
78
+ // read it untracked (STRICT_READ_UNTRACKED) and never update.
79
+ const clipPathUrl = createMemo(() => useBarStackClipPathUrl(props.index));
77
80
  return (
78
81
  <Layer
79
82
  class={["recharts-bar-stack-layer", props.class].filter(Boolean).join(" ")}
80
- clipPath={clipPathUrl}
83
+ clipPath={clipPathUrl()}
81
84
  onMouseEnter={props.onMouseEnter}
82
85
  onMouseLeave={props.onMouseLeave}
83
86
  onClick={props.onClick}
@@ -25,7 +25,7 @@
25
25
  // "with controlled startIndex/endIndex" Brush.spec block.
26
26
  import { createEffect, createMemo, createSignal, merge, onCleanup, Show } from "solid-js";
27
27
  import type { JSX } from "@solidjs/web";
28
- import { scalePoint } from "victory-vendor/d3-scale";
28
+ import { scalePoint } from "d3-scale";
29
29
  import { useAppSelector, useAppStore } from "../state/hooks";
30
30
  import { Layer } from "../container/Layer";
31
31
  import { Text } from "../component/Text";
@@ -17,11 +17,15 @@
17
17
  // Enter/Leave/onTouchStart/Move/End, props-first `(data, index, event)`) ARE wired.
18
18
  // No legend registration and no ZIndexLayer — matching recharts (upstream Funnel
19
19
  // registers no legend payload and renders inline without a zIndex portal).
20
- import { createMemo, createSignal, For, merge, Show } from "solid-js";
20
+ import { createMemo, For, merge, Show } from "solid-js";
21
21
  import type { JSX } from "@solidjs/web";
22
22
  import { useAppSelector } from "../state/hooks";
23
23
  import { useUniqueId } from "../util/useUniqueId";
24
- import { AnimatedItems, type AnimationInterpolateFn } from "../animation/AnimatedItems";
24
+ import {
25
+ AnimatedItems,
26
+ useAnimationCallbacks,
27
+ type AnimationInterpolateFn,
28
+ } from "../animation/AnimatedItems";
25
29
  import { matchAppend, type AnimationMatchByProp } from "../animation/matchBy";
26
30
  import type { MutableRef } from "../animation/useAnimationStartSnapshot";
27
31
  import { useCartesianChartLayout } from "../context/chartLayoutContext";
@@ -452,18 +456,11 @@ export function Funnel<DataPointType = Record<string, unknown>, DataValueType =
452
456
  current: null,
453
457
  };
454
458
 
455
- // ownedWrite: handleAnimationEnd also fires from the animation effect cleanup when
456
- // a mid-animation chart is disposed (route/soft navigation) a legal teardown.
457
- const [showLabels, setShowLabels] = createSignal(true, { ownedWrite: true });
458
- const labelContextEntries = createMemo(() => (showLabels() ? labelListEntries() : undefined));
459
- const handleAnimationStart = (): void => {
460
- props.onAnimationStart?.();
461
- setShowLabels(false);
462
- };
463
- const handleAnimationEnd = (): void => {
464
- props.onAnimationEnd?.();
465
- setShowLabels(true);
466
- };
459
+ const { isAnimating, handleAnimationStart, handleAnimationEnd } = useAnimationCallbacks(
460
+ () => props.onAnimationStart?.(),
461
+ () => props.onAnimationEnd?.(),
462
+ );
463
+ const labelContextEntries = createMemo(() => (isAnimating() ? undefined : labelListEntries()));
467
464
 
468
465
  const layerClass = (): string =>
469
466
  ["recharts-trapezoids", merged.class].filter((c): c is string => Boolean(c)).join(" ");
@@ -514,13 +511,12 @@ export function Funnel<DataPointType = Record<string, unknown>, DataValueType =
514
511
  const isActive = (): boolean =>
515
512
  Boolean(activeShape) && activeItemIndex() === String(i());
516
513
  // v3.9 ShapeAnimationProps forwarded into the custom-shape
517
- // invocation, mirroring upstream FunnelTrapezoids. `showLabels`
518
- // is the inverse of upstream's `isAnimating` (false during the
519
- // animation); combine `< 1` like upstream so the shape sees
520
- // `isAnimating` through the whole entrance frame.
514
+ // invocation, mirroring upstream FunnelTrapezoids. Combine
515
+ // `< 1` like upstream so the shape sees `isAnimating` through
516
+ // the whole entrance frame.
521
517
  const shapeAnimationProps = (): ShapeAnimationProps => ({
522
518
  animationElapsedTime,
523
- isAnimating: !showLabels() || animationElapsedTime < 1,
519
+ isAnimating: isAnimating() || animationElapsedTime < 1,
524
520
  isEntrance,
525
521
  });
526
522
  return (
@@ -25,7 +25,11 @@ import { useActiveTooltipDataPoints } from "../hooks";
25
25
  import { selectActiveTooltipIndex } from "../state/selectors/tooltipSelectors";
26
26
  import type { JSX } from "@solidjs/web";
27
27
  import { useAppSelector, useAppStore } from "../state/hooks";
28
- import { AnimatedItems, type AnimationInterpolateFn } from "../animation/AnimatedItems";
28
+ import {
29
+ AnimatedItems,
30
+ useAnimationCallbacks,
31
+ type AnimationInterpolateFn,
32
+ } from "../animation/AnimatedItems";
29
33
  import { matchByIndex, type AnimationItem, type AnimationMatchByProp } from "../animation/matchBy";
30
34
  import type { MutableRef } from "../animation/useAnimationStartSnapshot";
31
35
  import { LineDrawShape, type LineDrawShapeProps } from "./LineDrawShape";
@@ -80,7 +84,7 @@ import { BaseAxisWithScale } from "../state/selectors/axisSelectors";
80
84
  import { GraphicalItemClipPath, useNeedsClip } from "./GraphicalItemClipPath";
81
85
  import { DefaultZIndexes } from "../zIndex/DefaultZIndexes";
82
86
  import { ZIndexLayer } from "../zIndex/ZIndexLayer";
83
- import { unregisterOnCleanup } from "../state/unregisterOnCleanup";
87
+ import { registerInStore } from "../state/registerInStore";
84
88
 
85
89
  export interface LinePointItem {
86
90
  readonly value: unknown;
@@ -342,39 +346,27 @@ export const Line = (props: LineProps): JSX.Element => {
342
346
  const layout = useCartesianChartLayout();
343
347
  const id = useUniqueId("recharts-line", props.id);
344
348
 
345
- let prev: LineSettings | null = null;
346
-
347
- createEffect(
348
- (): LineSettings => ({
349
- id,
350
- type: "line",
351
- isPanorama,
352
- data: merged.data,
353
- dataKey: merged.dataKey,
354
- xAxisId: merged.xAxisId,
355
- yAxisId: merged.yAxisId,
356
- zAxisId: 0,
357
- hide: merged.hide,
358
- }),
359
- (settings) => {
360
- if (store == null) {
361
- return;
362
- }
363
- if (prev === null) {
364
- store.actions.graphicalItems.addCartesianGraphicalItem(settings);
365
- } else if (prev !== settings) {
366
- store.actions.graphicalItems.replaceCartesianGraphicalItem({ prev, next: settings });
367
- }
368
- prev = settings;
369
- },
370
- );
371
-
372
- unregisterOnCleanup(() => {
373
- if (prev && store != null) {
374
- store.actions.graphicalItems.removeCartesianGraphicalItem(prev);
375
- prev = null;
376
- }
377
- });
349
+ if (store != null) {
350
+ registerInStore<LineSettings>(
351
+ () => ({
352
+ id,
353
+ type: "line",
354
+ isPanorama,
355
+ data: merged.data,
356
+ dataKey: merged.dataKey,
357
+ xAxisId: merged.xAxisId,
358
+ yAxisId: merged.yAxisId,
359
+ zAxisId: 0,
360
+ hide: merged.hide,
361
+ }),
362
+ {
363
+ add: (next) => store.actions.graphicalItems.addCartesianGraphicalItem(next),
364
+ replace: (prev, next) =>
365
+ store.actions.graphicalItems.replaceCartesianGraphicalItem({ prev, next }),
366
+ remove: (prev) => store.actions.graphicalItems.removeCartesianGraphicalItem(prev),
367
+ },
368
+ );
369
+ }
378
370
 
379
371
  const points = useAppSelector((state) =>
380
372
  selectLinePoints(state, merged.xAxisId, merged.yAxisId, isPanorama, id),
@@ -494,19 +486,11 @@ export const Line = (props: LineProps): JSX.Element => {
494
486
  });
495
487
  // Labels are hidden while the entrance animation runs and shown once it ends
496
488
  // (matching recharts: showLabels = !isAnimating).
497
- // ownedWrite: handleAnimationEnd also fires from the animation effect cleanup
498
- // when a mid-animation chart is disposed (route/soft navigation) — a legal
499
- // teardown write, not a write-in-computation bug.
500
- const [showLabels, setShowLabels] = createSignal(true, { ownedWrite: true });
501
- const labelContextEntries = createMemo(() => (showLabels() ? labelListEntries() : undefined));
502
- const handleAnimationStart = (): void => {
503
- props.onAnimationStart?.();
504
- setShowLabels(false);
505
- };
506
- const handleAnimationEnd = (): void => {
507
- props.onAnimationEnd?.();
508
- setShowLabels(true);
509
- };
489
+ const { isAnimating, handleAnimationStart, handleAnimationEnd } = useAnimationCallbacks(
490
+ () => props.onAnimationStart?.(),
491
+ () => props.onAnimationEnd?.(),
492
+ );
493
+ const labelContextEntries = createMemo(() => (isAnimating() ? undefined : labelListEntries()));
510
494
 
511
495
  // Measure on data change AND on animation start/end (showLabels flips): a
512
496
  // data-change morph alters the path geometry per frame, so the length taken
@@ -515,7 +499,7 @@ export const Line = (props: LineProps): JSX.Element => {
515
499
  // settled dasharray the new curve's true length — mirroring recharts, which
516
500
  // recomputes totalLength on the post-animation re-render.
517
501
  createEffect(
518
- () => ({ points: points(), showLabels: showLabels() }),
502
+ () => ({ points: points(), showLabels: !isAnimating() }),
519
503
  () => {
520
504
  if (curveEl?.getTotalLength == null) {
521
505
  return;
@@ -627,8 +611,7 @@ export const Line = (props: LineProps): JSX.Element => {
627
611
  // recharts: visibleLength is computed only while the animation is
628
612
  // active; once settled it is null so no entrance dasharray remains.
629
613
  const len = untrack(totalLength);
630
- const animationActive =
631
- untrack(showLabels) === false || animationElapsedTime < 1;
614
+ const animationActive = untrack(isAnimating) || animationElapsedTime < 1;
632
615
  const visibleLength = animationActive
633
616
  ? animatedLineLength.getVisibleLength(animationElapsedTime, len)
634
617
  : null;
@@ -138,14 +138,16 @@ export function LineDrawShape(props: LineDrawShapeProps): JSX.Element {
138
138
  props.forwardPathRef?.(el);
139
139
  };
140
140
 
141
- // Re-measure whenever the geometry or the animation frame changes (a data-change
142
- // morph alters the path per frame; remeasuring once the frame committed gives the
143
- // settled dash the new curve's true length mirrors recharts recomputing
144
- // totalLength on the post-animation re-render).
141
+ // Re-measure the path length only while the entrance dash is actually being
142
+ // drawn (`visibleLength != null`). `totalLength` feeds `strokeDasharray` ONLY on
143
+ // that branch (see below), so once the reveal is doneincluding every
144
+ // subsequent data-change morph, where `frame` changes per frame — measuring
145
+ // would force a synchronous SVG layout (reflow) each frame for a value nothing
146
+ // reads. Guarding on visibleLength drops those per-frame reflows.
145
147
  createEffect(
146
148
  () => ({ points: props.points, visibleLength: props.visibleLength }),
147
- () => {
148
- if (pathEl?.getTotalLength == null) {
149
+ (deps) => {
150
+ if (deps.visibleLength == null || pathEl?.getTotalLength == null) {
149
151
  return;
150
152
  }
151
153
  const length = getTotalLength(pathEl);