layerchart@2.0.0-next.62
Minor Changes
- feat(Blur): Add Canvas support (#449)
Patch Changes
-
perf(Chart): Eliminate per-instance props spread in
ChartState(#857) -
fix(SeriesState): Avoid
derived_inertcrash when chart unmounts under a<svelte:boundary>(#855)The
selectedKeyssync effect was wrapped in$effect.root, creating an isolated scope that survived chart unmount. When the parent chart was destroyed (e.g. an example reloading inside the docs<svelte:boundary>after an async$derivedre-evaluated), the#seriesderived became inert while the orphaned effect kept reading it — producingReading a derived belonging to a now-destroyed effect may result in stale valueswarnings followed byTypeError: e.some is not a function. The effect now lives in the constructor, scoped to the component that instantiatedSeriesState, so it is torn down with the chart. -
fix(Arc, RectClipPath, ChartClipPath): Restore on-mount tween animations (#855)
Two related regressions introduced in the layer-component split (#848) prevented
motion+initial*props from animating on mount.Arc—motion,value,initialValueand the rest of Arc's geometry props (domain,range,startAngle,endAngle,innerRadius,outerRadius,cornerRadius,padAngle,track*,offset) were not destructured inArc.base.svelte, so they leaked through{...restProps}onto the inner<Path>. The forwardedmotionmadePathalso tween the path-string on top of the end-angle tween thatArcStatealready drives, producing visibly wrong arcs (NaN coordinates, runaway radii). They are now extracted and passed explicitly toArcState.RectClipPath/ChartClipPath—motion,initialX,initialY,initialWidth,initialHeightwere declared on the type but never consumed: the path was a plain$derivedof the staticx/y/width/heightprops, so passing<ChartClipPath initialWidth={0} motion={{ width: { type: 'tween', … } }}>rendered the final width on mount with no animation. Each dimension now flows through its owncreateMotion(using the correspondinginitial*value as the animation start), and the path is built from the animated values. -
perf: Reduce per-tick reactive overhead in
Path/Link(force-simulation graphs) (#855)In mark-heavy scenes (force simulations with hundreds of links flowing through
Link → Path) several reactive structures unconditionally subscribed every<path>template updater to props that don't change on a tick, causing per-frame work to scale with the number of props × the number of marks. Each fix below is independent; together they take the lattice (n=20, 760 links) example from ~5–6 fps to ~9 fps during simulation.PathState.tweenedPathDatanow reads onlypathData, not all Path props. Pre-fix, the getter resolvedpathDataviagetProps(), a function that constructs an object literal of every reactive Path prop. Each read oftweenedPathData(i.e. each per-tick<path d=...>update) therefore subscribed the updater to every Path prop and re-read all of them.PathStatenow takes a dedicatedgetPathDatagetter alongsidegetProps, and the hot-path tween / DOM read only touchespathData.Path.svg.svelteandPath.canvas.sveltepass them as separate getters.Link.base.sveltepasses a stablegetPathDatafunction rather thanmotionPath.currentdirectly. ReadingmotionPath.currentfromLink.base.svelte's template subscribed the entire<Path>block to every tick, forcing the parent's prop spread ({...restProps}) andcls(...)evaluation to re-run on every change. Passing a stable function reference moves the per-tick read inside<Path>'s own template, keepingLink.base.sveltestable. Requires the newpathData?: string | (() => string)form onPath.Path.svg.svelteallocatesdraw-related state lazily.endPoint = createControlledMotion(..., { type: 'none' })was created for everyPath, even when nodrawtransition was configured. Now only created whendrawis set.- The
$effectthat trackedtweenedPathDataforstartContent/endContentpositioning ran on everyPath, even when neither prop was provided. Now only registered when at least one is set. drawKeyis only ever set whendrawis configured, so the{#key c.drawKey}block is a no-op for paths without a draw transition. The block stays unconditional — splitting it behind{#if draw}showed no measurable benefit over leaving the inert subscription in place.
Path.svg.svelteextracts styling props out of...rest.pathData,class,fill/fillOpacity/stroke/strokeOpacity/strokeWidth/opacityandmotionare now destructured out of$props()rather than left in...rest, so the<path>element's{...rest}spread doesn't re-evaluate every frame when those props change (pathDatachanges on every force-sim tick;classis typically a freshcls(...)string per parent render).Link.base.sveltedrops a redundant prop spread. Removed{...extractLayerProps(restProps, 'lc-link')}before{...restProps}— the call's only contribution (class) was being immediately overridden by the explicitclass={cls('lc-link', …)}that follows, making the spread pure overhead. -
perf: Skip mark-info
$effectfor pixel-mode primitives (#855)registerComponentnow probesmarkInfo()once at construction; if the result is initially empty (pixel-mode primitives wherecx/cy/r/etc. are numbers rather than string/function accessors), it skips creating the tracking$effectentirely. Saves one effect frame per primitive — adds up in mark-heavy scenes (force simulations, scatter plots with hundreds of nodes).Trade-off: a primitive that starts in pixel mode and later flips to data mode at runtime (e.g.
cxmutates from a number to a string) will not register a mark. Mark mode is typically static; if a chart needs runtime data-mode marks, define an explicitserieson the chart instead.