react-native-livechart 4.4.0 → 4.6.0

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 (44) hide show
  1. package/dist/components/LiveChart.d.ts.map +1 -1
  2. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  3. package/dist/components/LoadingOverlay.d.ts +9 -1
  4. package/dist/components/LoadingOverlay.d.ts.map +1 -1
  5. package/dist/constants.d.ts +14 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/liveChartEngineTick.d.ts +11 -0
  8. package/dist/core/liveChartEngineTick.d.ts.map +1 -1
  9. package/dist/core/liveChartSeriesEngineTick.d.ts +11 -0
  10. package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
  11. package/dist/core/resolveConfig.d.ts +35 -1
  12. package/dist/core/resolveConfig.d.ts.map +1 -1
  13. package/dist/core/useLiveChartEngine.d.ts +13 -0
  14. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  15. package/dist/core/useLiveChartSeriesEngine.d.ts +13 -0
  16. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
  17. package/dist/hooks/useChartPaths.d.ts +5 -1
  18. package/dist/hooks/useChartPaths.d.ts.map +1 -1
  19. package/dist/hooks/useChartReveal.d.ts +3 -1
  20. package/dist/hooks/useChartReveal.d.ts.map +1 -1
  21. package/dist/hooks/useModeBlend.d.ts +4 -1
  22. package/dist/hooks/useModeBlend.d.ts.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/math/squiggly.d.ts +8 -5
  26. package/dist/math/squiggly.d.ts.map +1 -1
  27. package/dist/types.d.ts +110 -1
  28. package/dist/types.d.ts.map +1 -1
  29. package/package.json +1 -1
  30. package/src/components/LiveChart.tsx +128 -42
  31. package/src/components/LiveChartSeries.tsx +97 -26
  32. package/src/components/LoadingOverlay.tsx +25 -6
  33. package/src/constants.ts +17 -0
  34. package/src/core/liveChartEngineTick.ts +26 -10
  35. package/src/core/liveChartSeriesEngineTick.ts +22 -4
  36. package/src/core/resolveConfig.ts +63 -0
  37. package/src/core/useLiveChartEngine.ts +40 -1
  38. package/src/core/useLiveChartSeriesEngine.ts +35 -1
  39. package/src/hooks/useChartPaths.ts +11 -1
  40. package/src/hooks/useChartReveal.ts +7 -2
  41. package/src/hooks/useModeBlend.ts +5 -3
  42. package/src/index.ts +2 -0
  43. package/src/math/squiggly.ts +21 -7
  44. package/src/types.ts +112 -1
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @see https://github.com/benjitaylor/liveline
7
7
  */
8
- import { useEffect, useState } from "react";
8
+ import { useEffect, useRef, useState } from "react";
9
9
  import {
10
10
  cancelAnimation,
11
11
  Easing,
@@ -62,6 +62,13 @@ export interface EngineConfig {
62
62
  * {@link LiveChartProps.static}.
63
63
  */
64
64
  static?: boolean;
65
+ /**
66
+ * Opaque key that snaps the framing to its target in one frame whenever it
67
+ * changes (the next tick bypasses `smoothing` for the window, Y-range, and
68
+ * value, then normal easing resumes). Lets a timeframe / dataset switch land
69
+ * instantly while live ticks stay smooth. See {@link LiveChartProps.snapKey}.
70
+ */
71
+ snapKey?: string | number;
65
72
  mode?: "line" | "candle";
66
73
  candles?: SharedValue<CandlePoint[]>;
67
74
  liveCandle?: SharedValue<CandlePoint | null>;
@@ -193,6 +200,12 @@ export interface EngineFrameRefs {
193
200
  liveEdgeSV?: SharedValue<number>;
194
201
  /** Receives the smoothed right-edge value each frame. Optional for callers/tests. */
195
202
  edgeValueSV?: SharedValue<number>;
203
+ /**
204
+ * One-shot "snap the framing this frame" flag (set by the `snapKey` effect).
205
+ * Consumed and cleared by {@link applyLiveChartEngineFrame} after the tick.
206
+ * Optional for callers/tests.
207
+ */
208
+ snapSV?: SharedValue<boolean>;
196
209
  modeSV: SharedValue<"line" | "candle">;
197
210
  candles?: SharedValue<CandlePoint[]>;
198
211
  liveCandle?: SharedValue<CandlePoint | null>;
@@ -212,6 +225,9 @@ export function applyLiveChartEngineFrame(
212
225
  ): void {
213
226
  "worklet";
214
227
  const dt = frameInfo.timeSincePreviousFrame ?? MS_PER_FRAME_60FPS;
228
+ // One-shot settle: a `snapKey` change set this flag; consume it for this tick
229
+ // and clear it below so only this frame snaps (live ticks stay smoothed).
230
+ const snap = sv.snapSV?.value ?? false;
215
231
  const state = {
216
232
  displayValue: sv.displayValue.value,
217
233
  displayMin: sv.displayMin.value,
@@ -243,6 +259,7 @@ export function applyLiveChartEngineFrame(
243
259
  points: sv.data.value,
244
260
  nowSeconds: Date.now() / 1000,
245
261
  paused: sv.pausedSV.value,
262
+ snap,
246
263
  viewEnd: sv.viewEndSV?.value,
247
264
  returnT: sv.returnTSV?.value,
248
265
  returnFrom: sv.returnFromSV?.value,
@@ -262,6 +279,13 @@ export function applyLiveChartEngineFrame(
262
279
  sv.extremaMaxValue.value = state.extremaMaxValue;
263
280
  sv.extremaMinTime.value = state.extremaMinTime;
264
281
  sv.extremaMaxTime.value = state.extremaMaxTime;
282
+ // Clear the one-shot snap so the next frame eases normally again — but only
283
+ // once a real (measured) frame consumed it. The tick early-returns on a
284
+ // zero-size canvas (before applying the snap), so keep the flag pending until
285
+ // the canvas has laid out, or a snap arriving pre-layout would be dropped.
286
+ if (snap && sv.snapSV && sv.canvasWidth.value !== 0 && sv.canvasHeight.value !== 0) {
287
+ sv.snapSV.value = false;
288
+ }
265
289
  }
266
290
 
267
291
  export function useLiveChartEngine(
@@ -352,6 +376,20 @@ export function useLiveChartEngine(
352
376
  // no useDerivedValue bridging, no closure serialization per tick.
353
377
  const { data, value, candles, liveCandle } = config;
354
378
 
379
+ // One-shot "snap the framing" flag. The effect below flips it to `true` when
380
+ // `snapKey` changes; the next frame consumes it (bypassing `smoothing` for the
381
+ // window / range / value) and clears it, so a timeframe / dataset switch lands
382
+ // in one frame while live ticks stay smoothed. See `snapKey`.
383
+ const snapSV = useSharedValue(false);
384
+ // Compare against the previous key (not a "first render" flag) so React 18
385
+ // StrictMode's double-invoked mount effect can't fire a spurious snap.
386
+ const lastSnapKey = useRef(config.snapKey);
387
+ useEffect(() => {
388
+ if (config.snapKey === lastSnapKey.current) return;
389
+ lastSnapKey.current = config.snapKey;
390
+ snapSV.set(true);
391
+ }, [config.snapKey, snapSV]);
392
+
355
393
  // Static charts run zero per-frame loops. `isStaticSV` gates both the
356
394
  // frame-callback autostart and the one-shot settle reaction below.
357
395
  const isStaticSV = useDerivedValue(() => config.static ?? false);
@@ -385,6 +423,7 @@ export function useLiveChartEngine(
385
423
  viewWindowSV: viewWindow,
386
424
  liveEdgeSV: liveEdge,
387
425
  edgeValueSV: edgeValue,
426
+ snapSV,
388
427
  modeSV,
389
428
  candles,
390
429
  liveCandle,
@@ -1,4 +1,4 @@
1
- import { useRef, useState } from "react";
1
+ import { useEffect, useRef, useState } from "react";
2
2
  import {
3
3
  cancelAnimation,
4
4
  Easing,
@@ -41,6 +41,13 @@ export interface MultiSeriesEngineConfig {
41
41
  * {@link RETURN_TO_LIVE_MS}. Resolved from `timeScroll.returnToLive`. See #164.
42
42
  */
43
43
  returnToLiveMs?: number;
44
+ /**
45
+ * Opaque key that snaps the framing to its target in one frame whenever it
46
+ * changes (the next tick bypasses `smoothing` for the window, Y-range, and
47
+ * series tips, then normal easing resumes). Lets a timeframe / dataset switch
48
+ * land instantly while live ticks stay smooth. See {@link LiveChartSeriesProps.snapKey}.
49
+ */
50
+ snapKey?: string | number;
44
51
  }
45
52
 
46
53
  export interface MultiEngineFrameRefs {
@@ -74,6 +81,12 @@ export interface MultiEngineFrameRefs {
74
81
  viewWindowSV?: SharedValue<number | null>;
75
82
  /** Receives the computed live edge each frame. Optional for callers/tests. */
76
83
  liveEdgeSV?: SharedValue<number>;
84
+ /**
85
+ * One-shot "snap the framing this frame" flag (set by the `snapKey` effect).
86
+ * Consumed and cleared by {@link applyLiveChartSeriesEngineFrame} after the
87
+ * tick. Optional for callers/tests.
88
+ */
89
+ snapSV?: SharedValue<boolean>;
77
90
  extremaMinValue: SharedValue<number>;
78
91
  extremaMaxValue: SharedValue<number>;
79
92
  extremaMinTime: SharedValue<number>;
@@ -110,6 +123,9 @@ export function applyLiveChartSeriesEngineFrame(
110
123
  ): void {
111
124
  "worklet";
112
125
  const dt = frameInfo.timeSincePreviousFrame ?? MS_PER_FRAME_60FPS;
126
+ // One-shot settle: a `snapKey` change set this flag; consume it for this tick
127
+ // and clear it below so only this frame snaps (live ticks stay smoothed).
128
+ const snap = sv.snapSV?.value ?? false;
113
129
  const seriesSnap = sv.series.value;
114
130
  const curDv = sv.displaySeriesValues.value;
115
131
  const curOp = sv.seriesOpacities.value;
@@ -157,6 +173,7 @@ export function applyLiveChartSeriesEngineFrame(
157
173
  series: seriesSnap,
158
174
  nowSeconds: Date.now() / 1000,
159
175
  paused: sv.pausedSV.value,
176
+ snap,
160
177
  viewEnd: sv.viewEndSV?.value,
161
178
  returnT: sv.returnTSV?.value,
162
179
  returnFrom: sv.returnFromSV?.value,
@@ -173,6 +190,12 @@ export function applyLiveChartSeriesEngineFrame(
173
190
  sv.extremaMaxValue.value = state.extremaMaxValue;
174
191
  sv.extremaMinTime.value = state.extremaMinTime;
175
192
  sv.extremaMaxTime.value = state.extremaMaxTime;
193
+ // Clear the one-shot snap so the next frame eases normally again — but only
194
+ // once a real (measured) frame consumed it (the tick early-returns on a
195
+ // zero-size canvas before applying the snap). See applyLiveChartEngineFrame.
196
+ if (snap && sv.snapSV && sv.canvasWidth.value !== 0 && sv.canvasHeight.value !== 0) {
197
+ sv.snapSV.value = false;
198
+ }
176
199
  }
177
200
 
178
201
  /**
@@ -234,6 +257,16 @@ export function useLiveChartSeriesEngine(
234
257
  const value = useSharedValue(0);
235
258
  const displayValue = useSharedValue(0);
236
259
 
260
+ // One-shot "snap the framing" flag — flipped by the effect when `snapKey`
261
+ // changes, consumed + cleared by the next frame (mirrors useLiveChartEngine).
262
+ const snapSV = useSharedValue(false);
263
+ const lastSnapKey = useRef(config.snapKey);
264
+ useEffect(() => {
265
+ if (config.snapKey === lastSnapKey.current) return;
266
+ lastSnapKey.current = config.snapKey;
267
+ snapSV.set(true);
268
+ }, [config.snapKey, snapSV]);
269
+
237
270
  const { series } = config;
238
271
 
239
272
  // Reused per-frame output buffers (ping-ponged) — see applyLiveChartSeriesEngineFrame.
@@ -273,6 +306,7 @@ export function useLiveChartSeriesEngine(
273
306
  returnFromSV: returnFrom,
274
307
  viewWindowSV: viewWindow,
275
308
  liveEdgeSV: liveEdge,
309
+ snapSV,
276
310
  extremaMinValue,
277
311
  extremaMaxValue,
278
312
  extremaMinTime,
@@ -35,6 +35,10 @@ export function useChartPaths(
35
35
  */
36
36
  edgeValue?: SharedValue<number>,
37
37
  followViewEdge = false,
38
+ /** Loading squiggle wave amplitude (px) for the reveal morph. Default 14. */
39
+ squiggleAmplitude = 14,
40
+ /** Loading squiggle wave speed multiplier for the reveal morph. Default 1. */
41
+ squiggleSpeed = 1,
38
42
  ) {
39
43
  const lineBuilder = usePathBuilder();
40
44
  const fillBuilder = usePathBuilder();
@@ -86,7 +90,13 @@ export function useChartPaths(
86
90
  // Compute squiggly Y values at the same X positions as the real line
87
91
  const centerY =
88
92
  (engine.canvasHeight.get() - padding.bottom + padding.top) / 2;
89
- const squigglyPts = squigglifyPts(realPts, engine.timestamp.get(), centerY);
93
+ const squigglyPts = squigglifyPts(
94
+ realPts,
95
+ engine.timestamp.get(),
96
+ centerY,
97
+ squiggleAmplitude,
98
+ squiggleSpeed,
99
+ );
90
100
 
91
101
  // Blend center-out: centre of chart reveals first, edges last
92
102
  return blendPtsY(
@@ -60,6 +60,8 @@ export function useChartReveal(
60
60
  hasData: SharedValue<boolean>,
61
61
  /** Static charts skip the entry ramp: morphT snaps to its target, no `withTiming`. */
62
62
  isStatic = false,
63
+ /** Reveal/collapse duration (ms). Default {@link CHART_REVEAL_DURATION_MS}; `0` snaps. */
64
+ revealDuration: number = CHART_REVEAL_DURATION_MS,
63
65
  ): ChartRevealState {
64
66
  // Best-guess initial so the common case — a live chart that already has data
65
67
  // and isn't loading — paints fully revealed with no flash. The reaction below
@@ -85,15 +87,18 @@ export function useChartReveal(
85
87
  return;
86
88
  }
87
89
  if (prev !== chartVisible) {
90
+ // 0ms → withTiming resolves on the next frame (effectively a snap), so an
91
+ // explicit `transitions={{ reveal: 0 }}` / `transitions={false}` removes
92
+ // the grow-in without a special-case branch.
88
93
  morphT.set(
89
94
  withTiming(chartVisible ? 1 : 0, {
90
- duration: CHART_REVEAL_DURATION_MS,
95
+ duration: revealDuration,
91
96
  easing: Easing.out(Easing.cubic),
92
97
  }),
93
98
  );
94
99
  }
95
100
  },
96
- [hasData, isStatic],
101
+ [hasData, isStatic, revealDuration],
97
102
  );
98
103
 
99
104
  const isEmpty = useDerivedValue(() => !isLoading.get() && !hasData.get());
@@ -7,7 +7,7 @@ import {
7
7
  type SharedValue,
8
8
  } from "react-native-reanimated";
9
9
 
10
- const MODE_BLEND_DURATION_MS = 300;
10
+ export const MODE_BLEND_DURATION_MS = 300;
11
11
 
12
12
  export interface ModeBlendState {
13
13
  /** 0 = fully line, 1 = fully candle */
@@ -28,17 +28,19 @@ export interface ModeBlendState {
28
28
  export function useModeBlend(
29
29
  isCandle: boolean,
30
30
  lineOpacity: SharedValue<number>,
31
+ /** Crossfade duration (ms). Default {@link MODE_BLEND_DURATION_MS}; `0` snaps. */
32
+ duration: number = MODE_BLEND_DURATION_MS,
31
33
  ): ModeBlendState {
32
34
  const modeBlend = useSharedValue(isCandle ? 1 : 0);
33
35
 
34
36
  useEffect(() => {
35
37
  modeBlend.set(
36
38
  withTiming(isCandle ? 1 : 0, {
37
- duration: MODE_BLEND_DURATION_MS,
39
+ duration,
38
40
  easing: Easing.inOut(Easing.ease),
39
41
  }),
40
42
  );
41
- }, [isCandle, modeBlend]);
43
+ }, [isCandle, modeBlend, duration]);
42
44
 
43
45
  const lineGroupOpacity = useDerivedValue(
44
46
  () => lineOpacity.get() * (1 - modeBlend.get()),
package/src/index.ts CHANGED
@@ -64,6 +64,7 @@ export type {
64
64
  LiveChartPoint,
65
65
  LiveChartProps,
66
66
  LiveChartSeriesProps,
67
+ LoadingConfig,
67
68
  Marker,
68
69
  MarkerClusterConfig,
69
70
  MarkerGroupBadge,
@@ -97,6 +98,7 @@ export type {
97
98
  ThresholdLineConfig,
98
99
  TooltipRenderProps,
99
100
  TradeEvent,
101
+ TransitionConfig,
100
102
  ValueLineConfig,
101
103
  VisibleRange,
102
104
  VolumeConfig,
@@ -4,16 +4,26 @@ import type { ChartPadding } from "../draw/line";
4
4
  * Composite sine squiggly — two overlapping frequencies with a breathing
5
5
  * amplitude envelope. Matches the original web LiveChart loading animation.
6
6
  *
7
- * amplitude = 14 * (0.4 + 0.6 * sin(0.8 * t)) // 5.6 → 22.4px breathing range
8
- * y = centerY + amplitude * (sin(0.035*x + 1.2*t) + 0.45*sin(0.08*x + 2.1*t))
7
+ * amplitude = base * (0.4 + 0.6 * sin(0.8 * t·speed)) // 0.4×→1. base
8
+ * y = centerY + amplitude * (sin(0.035*x + 1.2*t·speed) + 0.45*sin(0.08*x + 2.1*t·speed))
9
+ *
10
+ * `base` (default `14`) scales the wave height; `speed` (default `1`) scales the
11
+ * time phase so the ripple/breathing runs faster or slower (`0` freezes it).
9
12
  */
10
- export function squigglyYAt(x: number, centerY: number, t: number): number {
13
+ export function squigglyYAt(
14
+ x: number,
15
+ centerY: number,
16
+ t: number,
17
+ base = 14,
18
+ speed = 1,
19
+ ): number {
11
20
  "worklet";
12
- const amplitude = 14 * (0.4 + 0.6 * Math.sin(0.8 * t));
21
+ const ts = t * speed;
22
+ const amplitude = base * (0.4 + 0.6 * Math.sin(0.8 * ts));
13
23
  return (
14
24
  centerY +
15
25
  amplitude *
16
- (Math.sin(0.035 * x + 1.2 * t) + 0.45 * Math.sin(0.08 * x + 2.1 * t))
26
+ (Math.sin(0.035 * x + 1.2 * ts) + 0.45 * Math.sin(0.08 * x + 2.1 * ts))
17
27
  );
18
28
  }
19
29
 
@@ -27,6 +37,8 @@ export function buildSquigglyPts(
27
37
  canvasHeight: number,
28
38
  padding: ChartPadding,
29
39
  t: number,
40
+ base = 14,
41
+ speed = 1,
30
42
  ): number[] {
31
43
  "worklet";
32
44
  const leftEdge = padding.left;
@@ -41,7 +53,7 @@ export function buildSquigglyPts(
41
53
  for (let i = 0; i < count; i++) {
42
54
  const x = leftEdge + Math.min(i * step, chartW);
43
55
  pts[i * 2] = x;
44
- pts[i * 2 + 1] = squigglyYAt(x, centerY, t);
56
+ pts[i * 2 + 1] = squigglyYAt(x, centerY, t, base, speed);
45
57
  }
46
58
  return pts;
47
59
  }
@@ -54,13 +66,15 @@ export function squigglifyPts(
54
66
  flatPts: number[],
55
67
  t: number,
56
68
  centerY: number,
69
+ base = 14,
70
+ speed = 1,
57
71
  ): number[] {
58
72
  "worklet";
59
73
  const n = flatPts.length;
60
74
  const out: number[] = new Array(n);
61
75
  for (let i = 0; i < n; i += 2) {
62
76
  out[i] = flatPts[i];
63
- out[i + 1] = squigglyYAt(flatPts[i], centerY, t);
77
+ out[i + 1] = squigglyYAt(flatPts[i], centerY, t, base, speed);
64
78
  }
65
79
  return out;
66
80
  }
package/src/types.ts CHANGED
@@ -720,6 +720,23 @@ export interface ScrubConfig {
720
720
  * Default `0`.
721
721
  */
722
722
  panGestureDelay?: number;
723
+ /**
724
+ * Fade the annotation overlays — buy/sell **markers** and **reference lines**
725
+ * (both the built-in Skia tags/lines and any custom `renderMarker` /
726
+ * `renderReferenceLine` RN views) — out while scrubbing, so they don't clutter
727
+ * the crosshair read-out. Reverses on release.
728
+ *
729
+ * The fade is driven by the **scrub-active** state (not the crosshair's
730
+ * edge-proximity fade, which would resurface the overlays as the crosshair
731
+ * nears the live dot) and eased on the UI thread over `SCRUB_OVERLAY_FADE_MS`.
732
+ * It animates only a **group opacity** — the marker atlas and reference-line
733
+ * geometry are left intact (still one batched draw each), so it's far cheaper
734
+ * than emptying / rebuilding overlay data per scrub. The leading dot is
735
+ * governed separately by `selectionDot`.
736
+ *
737
+ * `false` / omitted keeps the overlays visible while scrubbing (default).
738
+ */
739
+ hideOverlaysOnScrub?: boolean;
723
740
  }
724
741
 
725
742
  /**
@@ -1586,6 +1603,60 @@ export interface VisibleRange {
1586
1603
  following: boolean;
1587
1604
  }
1588
1605
 
1606
+ /**
1607
+ * Per-transition animation durations (ms). Passed as the object form of
1608
+ * {@link LiveChartCoreProps.transitions} to tune or disable the built-in
1609
+ * animations. Omit a field to keep its default; `0` makes that transition
1610
+ * instant (snap). Setting `transitions={false}` is shorthand for all `0`.
1611
+ */
1612
+ export interface TransitionConfig {
1613
+ /**
1614
+ * Reveal / collapse transition — the grow-in **opacity** fade when data first
1615
+ * appears (and the fade-out when it goes away or `loading` toggles). This is
1616
+ * the fade that plays on a timeframe change and when a `line` chart's data
1617
+ * appears (e.g. switching from candle to line). Default `600`. `0` = instant.
1618
+ *
1619
+ * Note this animates opacity only, not geometry: it does not control the time
1620
+ * window / Y-range *easing* on a timeframe or dataset change (that's
1621
+ * `smoothing`). To make that framing settle in one frame instead of sliding,
1622
+ * use {@link LiveChartCoreProps.snapKey}.
1623
+ */
1624
+ reveal?: number;
1625
+ /**
1626
+ * Candle ↔ line crossfade duration when `mode` changes. Single-series
1627
+ * `LiveChart` only (multi-series is always lines). Default `300`. `0` = instant.
1628
+ */
1629
+ mode?: number;
1630
+ }
1631
+
1632
+ /**
1633
+ * Styling for the breathing-line loading shell (the {@link LiveChartCoreProps.loading}
1634
+ * state). Every field is optional — pass it as the object form of `loading` to
1635
+ * restyle the shell; omit a field to keep its default.
1636
+ */
1637
+ export interface LoadingConfig {
1638
+ /**
1639
+ * Color of the loading squiggle **and** the skeleton Y-axis placeholders.
1640
+ * Default: the theme's `gridLine` color.
1641
+ */
1642
+ color?: string;
1643
+ /**
1644
+ * Stroke width (px) of the loading squiggle. Default: the chart's line
1645
+ * `strokeWidth`.
1646
+ */
1647
+ strokeWidth?: number;
1648
+ /**
1649
+ * Base wave amplitude (px) of the breathing squiggle — it breathes between
1650
+ * `0.4×` and `1.0×` this. Default `14`.
1651
+ */
1652
+ amplitude?: number;
1653
+ /**
1654
+ * Multiplier on the breathing-wave animation cadence: `>1` ripples faster,
1655
+ * `<1` slower, `0` freezes it. Default `1`.
1656
+ */
1657
+ speed?: number;
1658
+ }
1659
+
1589
1660
  /** Props shared between `LiveChart` and `LiveChartSeries`. */
1590
1661
  export interface LiveChartCoreProps {
1591
1662
  /** Color scheme. Default `"dark"`. */
@@ -1602,17 +1673,57 @@ export interface LiveChartCoreProps {
1602
1673
  timeWindow?: number;
1603
1674
  /** Freeze chart scrolling. Resume catches up to real time. Default `false`. */
1604
1675
  paused?: boolean;
1676
+ /**
1677
+ * Tune or disable the built-in transition animations. `true` / omitted keeps
1678
+ * the defaults; `false` makes them all instant (no grow-in on data
1679
+ * appear/timeframe change, no candle↔line crossfade); a {@link TransitionConfig}
1680
+ * sets per-transition durations in ms (`reveal`, `mode`), where `0` snaps that
1681
+ * one. Live value tracking is governed separately by `smoothing`; static-entry
1682
+ * suppression by `static`.
1683
+ */
1684
+ transitions?: boolean | TransitionConfig;
1605
1685
  /**
1606
1686
  * Breathing-line loading shell. When this becomes `false`, the chart reveals
1607
1687
  * only if there is data (≥2 line points or ≥2 committed candles).
1688
+ *
1689
+ * `true` shows the shell with the defaults; pass a {@link LoadingConfig} to
1690
+ * restyle it — `color` / `strokeWidth` for the squiggle + skeleton, `amplitude`
1691
+ * / `speed` for the breathing wave. `false` / omitted is "not loading". Toggle
1692
+ * between the config and `false` as data loads (e.g. `loading={isLoading && cfg}`).
1693
+ * The loading→live reveal duration is governed separately by
1694
+ * {@link TransitionConfig.reveal} (the `transitions` prop).
1608
1695
  */
1609
- loading?: boolean;
1696
+ loading?: boolean | LoadingConfig;
1610
1697
  /**
1611
1698
  * Value-lerp speed — how quickly the drawn value, time window, and Y-range chase
1612
1699
  * their targets each frame (0 = frozen, 1 = instant). Equivalent to liveline's
1613
1700
  * `lerpSpeed`. Default `0.08`.
1614
1701
  */
1615
1702
  smoothing?: number;
1703
+ /**
1704
+ * Snap the framing to its target in a single frame whenever this key changes —
1705
+ * without giving up smooth live ticks. On a timeframe / dataset switch, the
1706
+ * window, Y-range, and value otherwise *ease* toward their new targets over
1707
+ * `smoothing`, which reads as a slide. Bump `snapKey` at that moment and the
1708
+ * next frame jumps straight to the new framing (no slide), then normal
1709
+ * `smoothing` resumes for subsequent live ticks.
1710
+ *
1711
+ * Pass any value that changes once per discrete view change — the current
1712
+ * timeframe id, or a counter you increment when you swap the `data` / `candles`
1713
+ * array for a different range:
1714
+ *
1715
+ * ```tsx
1716
+ * const [tf, setTf] = useState("1H");
1717
+ * <LiveChart data={data} value={value} timeWindow={windowFor(tf)} snapKey={tf} />
1718
+ * ```
1719
+ *
1720
+ * Unlike `transitions` (which only governs the reveal fade and the candle↔line
1721
+ * crossfade — opacity, not geometry) and `static` (which turns off the live
1722
+ * loop entirely), `snapKey` settles only the geometry and only for one frame.
1723
+ * Leaves the timestamp / time-scroll position untouched. Omit to keep easing on
1724
+ * every change (the default).
1725
+ */
1726
+ snapKey?: string | number;
1616
1727
  /** Tight Y-axis — small value moves fill the full chart height. Default `false`. */
1617
1728
  exaggerate?: boolean;
1618
1729
  /**