react-native-vroom-chart 0.6.0 → 0.8.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 (49) hide show
  1. package/cpp/VroomChartHostObject.cpp +281 -33
  2. package/cpp/_core_include/vroom/vroom_chart.h +231 -36
  3. package/cpp/_core_src/bollinger.h +1 -1
  4. package/cpp/_core_src/candles.cpp +138 -41
  5. package/cpp/_core_src/candles.h +11 -1
  6. package/cpp/_core_src/chart.cpp +91 -40
  7. package/cpp/_core_src/chart.h +69 -29
  8. package/cpp/_core_src/chart_facade.cpp +247 -69
  9. package/cpp/_core_src/drawings.cpp +147 -4
  10. package/cpp/_core_src/drawings.h +10 -5
  11. package/cpp/_core_src/gradient.cpp +46 -0
  12. package/cpp/_core_src/gradient.h +31 -0
  13. package/cpp/_core_src/labels.cpp +49 -16
  14. package/cpp/_core_src/labels.h +33 -4
  15. package/cpp/_core_src/liquidity.cpp +3 -37
  16. package/cpp/_core_src/ma_overlay.cpp +174 -12
  17. package/cpp/_core_src/ma_overlay.h +55 -3
  18. package/cpp/_core_src/macd.cpp +13 -42
  19. package/cpp/_core_src/macd.h +11 -8
  20. package/cpp/_core_src/macd_pane.cpp +57 -27
  21. package/cpp/_core_src/price_line_layout.h +1 -1
  22. package/cpp/_core_src/rsi.cpp +4 -18
  23. package/cpp/_core_src/rsi.h +6 -6
  24. package/cpp/_core_src/rsi_pane.cpp +34 -19
  25. package/cpp/_core_src/series_ma.cpp +64 -0
  26. package/cpp/_core_src/series_ma.h +30 -0
  27. package/cpp/_core_src/style_inherit.h +35 -0
  28. package/cpp/_core_src/theme.cpp +2 -1
  29. package/cpp/_core_src/viewport.cpp +36 -12
  30. package/cpp/_core_src/viewport.h +50 -0
  31. package/cpp/_core_src/volume.cpp +33 -8
  32. package/cpp/_core_src/volume.h +12 -1
  33. package/cpp/_core_src/volume_anim.cpp +32 -0
  34. package/cpp/_core_src/volume_anim.h +41 -0
  35. package/lib/index.d.mts +206 -31
  36. package/lib/index.d.ts +206 -31
  37. package/lib/index.js +324 -44
  38. package/lib/index.js.map +1 -1
  39. package/lib/index.mjs +329 -52
  40. package/lib/index.mjs.map +1 -1
  41. package/package.json +1 -1
  42. package/src/VroomChart.tsx +100 -17
  43. package/src/dataTransitions.ts +148 -0
  44. package/src/easing.ts +40 -0
  45. package/src/index.ts +9 -0
  46. package/src/jsi.d.ts +126 -19
  47. package/src/theme.ts +1 -0
  48. package/src/types.ts +3 -0
  49. package/src/useChartCore.ts +273 -30
package/lib/index.js CHANGED
@@ -30,7 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- VroomChart: () => VroomChart
33
+ VroomChart: () => VroomChart,
34
+ classifyTransition: () => classifyTransition,
35
+ inferStepMs: () => inferStepMs,
36
+ timeframeWindow: () => timeframeWindow
34
37
  });
35
38
  module.exports = __toCommonJS(index_exports);
36
39
 
@@ -48,6 +51,86 @@ var import_react = require("react");
48
51
  var import_react_native = require("react-native");
49
52
  var NativeVroomChart_default = import_react_native.TurboModuleRegistry.getEnforcing("VroomChartModule");
50
53
 
54
+ // src/dataTransitions.ts
55
+ var STEP_TOLERANCE = 0.01;
56
+ var MAX_SAME_ASSET_CLOSE_RATIO = 1.25;
57
+ var MAX_END_DRIFT_STEPS = 3;
58
+ var MAX_STREAM_ADVANCE_STEPS = 5;
59
+ function inferStepMs(candles) {
60
+ if (candles.length < 2) return null;
61
+ const k = Math.min(candles.length - 1, 8);
62
+ const diffs = [];
63
+ for (let i = 0; i < k; i++) diffs.push(candles[i + 1].timeMs - candles[i].timeMs);
64
+ diffs.sort((a, b) => a - b);
65
+ const median = diffs[Math.floor(diffs.length / 2)];
66
+ return median > 0 ? median : null;
67
+ }
68
+ function indexByTime(candles, t) {
69
+ let lo = 0;
70
+ let hi = candles.length - 1;
71
+ while (lo <= hi) {
72
+ const mid = lo + hi >>> 1;
73
+ const v = candles[mid].timeMs;
74
+ if (v === t) return mid;
75
+ if (v < t) lo = mid + 1;
76
+ else hi = mid - 1;
77
+ }
78
+ return -1;
79
+ }
80
+ function classifyTransition(prev, next, seriesKeyChanged) {
81
+ if (!prev || prev.length === 0) return "initial";
82
+ if (next.length === 0) return "stream";
83
+ if (seriesKeyChanged) return "reset";
84
+ const prevStep = inferStepMs(prev);
85
+ const nextStep = inferStepMs(next);
86
+ if (prevStep == null || nextStep == null) return "reset";
87
+ const prevLast = prev[prev.length - 1];
88
+ const nextLast = next[next.length - 1];
89
+ if (Math.abs(nextStep - prevStep) <= prevStep * STEP_TOLERANCE) {
90
+ const idx = indexByTime(next, prevLast.timeMs);
91
+ const aligned = idx >= 0;
92
+ const sharedBarRatio = aligned && next[idx].close > 0 && prevLast.close > 0 ? Math.max(next[idx].close / prevLast.close, prevLast.close / next[idx].close) : Infinity;
93
+ const advanced = nextLast.timeMs >= prevLast.timeMs && nextLast.timeMs - prevLast.timeMs <= MAX_STREAM_ADVANCE_STEPS * nextStep;
94
+ return sharedBarRatio <= MAX_SAME_ASSET_CLOSE_RATIO && advanced ? "stream" : "reset";
95
+ }
96
+ const closeRatio = prevLast.close > 0 && nextLast.close > 0 ? Math.max(nextLast.close / prevLast.close, prevLast.close / nextLast.close) : Infinity;
97
+ const prevEnd = prevLast.timeMs + prevStep;
98
+ const nextEnd = nextLast.timeMs + nextStep;
99
+ const endsTogether = Math.abs(nextEnd - prevEnd) <= MAX_END_DRIFT_STEPS * Math.max(prevStep, nextStep);
100
+ return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? "timeframe" : "reset";
101
+ }
102
+ function timeframeWindow(oldWindow, oldStepMs, oldLastMs, newStepMs, newLastMs) {
103
+ const slots = (oldWindow.endMs - oldWindow.startMs) / oldStepMs;
104
+ const offsetRaw = (oldWindow.endMs - oldLastMs) / oldStepMs;
105
+ const offsetSlots = Math.min(Math.max(offsetRaw, 0), slots * 0.75);
106
+ const endMs = Math.round(newLastMs + offsetSlots * newStepMs);
107
+ return { startMs: Math.round(endMs - slots * newStepMs), endMs };
108
+ }
109
+
110
+ // src/easing.ts
111
+ function ease(kind, p) {
112
+ switch (kind) {
113
+ case "linear":
114
+ return p;
115
+ case "ease-in":
116
+ return p * p;
117
+ case "ease-out":
118
+ return p * (2 - p);
119
+ default:
120
+ return p * p * (3 - 2 * p);
121
+ }
122
+ }
123
+ var EASINGS = [
124
+ "linear",
125
+ "ease-in",
126
+ "ease-out",
127
+ "ease-in-out"
128
+ ];
129
+ function easingIndex(kind) {
130
+ const i = kind ? EASINGS.indexOf(kind) : -1;
131
+ return i < 0 ? EASINGS.indexOf("ease-in-out") : i;
132
+ }
133
+
51
134
  // src/packCandles.ts
52
135
  var BYTES_PER_CANDLE = 48;
53
136
  function packCandles(candles) {
@@ -104,8 +187,10 @@ var FLOAT_KEYS = {
104
187
  // VROOM_FLOAT_CANDLE_RADIUS_PX
105
188
  volumeRadius: 10,
106
189
  // VROOM_FLOAT_VOLUME_RADIUS_PX
107
- lineWidth: 11
190
+ lineWidth: 11,
108
191
  // VROOM_FLOAT_LINE_WIDTH_PX
192
+ lineGradientOpacity: 12
193
+ // VROOM_FLOAT_LINE_GRADIENT_OPACITY
109
194
  };
110
195
  var BOOL_KEYS = {
111
196
  wickRoundCap: 9
@@ -151,16 +236,43 @@ var MA_SOURCES = [
151
236
  "hlc3",
152
237
  "ohlc4"
153
238
  ];
239
+ var inheritColor = (v) => (v != null ? parseColor(v) : null) ?? 0;
154
240
  function overlayToNumeric(o) {
155
241
  const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
156
242
  return {
157
- kind: o.kind === "ema" ? 1 : 0,
158
- period: o.length,
243
+ kind: o.maType === "ema" ? 1 : 0,
244
+ period: o.period,
159
245
  source: srcIdx < 0 ? 0 : srcIdx,
160
246
  color: (o.color != null ? parseColor(o.color) : null) ?? 4280902399,
161
247
  width: o.width ?? 1.5
162
248
  };
163
249
  }
250
+ function rsiToSpec(cfg) {
251
+ return {
252
+ enabled: cfg?.enabled ?? false,
253
+ period: cfg?.period ?? 14,
254
+ upperBand: cfg?.upperBand ?? 70,
255
+ lowerBand: cfg?.lowerBand ?? 30,
256
+ maPeriod: cfg?.maPeriod ?? 14,
257
+ maKind: cfg?.maType === "ema" ? 1 : 0,
258
+ maVisible: cfg?.maVisible ?? true,
259
+ lineColor: inheritColor(cfg?.lineColor),
260
+ lineWidth: cfg?.lineWidth ?? -1,
261
+ lineVisible: cfg?.lineVisible ?? true,
262
+ maColor: inheritColor(cfg?.maColor),
263
+ maWidth: cfg?.maWidth ?? -1,
264
+ bandColor: inheritColor(cfg?.bandColor),
265
+ bandsVisible: cfg?.bandsVisible ?? true
266
+ };
267
+ }
268
+ function vwapToSpec(cfg) {
269
+ return {
270
+ enabled: cfg?.enabled ?? false,
271
+ resetOffsetMin: cfg?.resetMinutes ?? 0,
272
+ color: inheritColor(cfg?.color),
273
+ width: cfg?.width ?? -1
274
+ };
275
+ }
164
276
  var DEFAULT_BB_BAND_COLOR = 4280902399;
165
277
  var DEFAULT_BB_BASIS_COLOR = 4294929664;
166
278
  function bollingerToSpec(cfg) {
@@ -170,17 +282,52 @@ function bollingerToSpec(cfg) {
170
282
  period: cfg?.period ?? 20,
171
283
  mult: cfg?.stdDev ?? 2,
172
284
  source: srcIdx < 0 ? 0 : srcIdx,
173
- basisKind: cfg?.basis === "ema" ? 1 : 0,
285
+ basisKind: cfg?.maType === "ema" ? 1 : 0,
174
286
  upperColor: (cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ?? DEFAULT_BB_BAND_COLOR,
175
287
  upperWidth: cfg?.upperWidth ?? 1,
176
288
  middleColor: (cfg?.middleColor != null ? parseColor(cfg.middleColor) : null) ?? DEFAULT_BB_BASIS_COLOR,
177
289
  middleWidth: cfg?.middleWidth ?? 1,
178
290
  lowerColor: (cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ?? DEFAULT_BB_BAND_COLOR,
179
291
  lowerWidth: cfg?.lowerWidth ?? 1,
180
- fillEnabled: cfg?.fill ?? true,
292
+ fillEnabled: cfg?.fillVisible ?? true,
181
293
  fillOpacity: cfg?.fillOpacity ?? 0.1
182
294
  };
183
295
  }
296
+ function macdToSpec(cfg) {
297
+ const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
298
+ return {
299
+ enabled: cfg?.enabled ?? false,
300
+ fast: cfg?.fast ?? 12,
301
+ slow: cfg?.slow ?? 26,
302
+ signal: cfg?.signal ?? 9,
303
+ source: srcIdx < 0 ? 0 : srcIdx,
304
+ maKind: cfg?.maType === "sma" ? 0 : 1,
305
+ signalMaKind: cfg?.signalMaType === "sma" ? 0 : 1,
306
+ lineColor: inheritColor(cfg?.lineColor),
307
+ lineWidth: cfg?.lineWidth ?? -1,
308
+ lineVisible: cfg?.lineVisible ?? true,
309
+ signalColor: inheritColor(cfg?.signalColor),
310
+ signalWidth: cfg?.signalWidth ?? -1,
311
+ signalVisible: cfg?.signalVisible ?? true,
312
+ histVisible: cfg?.histogramVisible ?? true,
313
+ histUpColor: inheritColor(cfg?.histogramUpColor),
314
+ histUpFadingColor: inheritColor(cfg?.histogramUpFadingColor),
315
+ histDownColor: inheritColor(cfg?.histogramDownColor),
316
+ histDownFadingColor: inheritColor(cfg?.histogramDownFadingColor),
317
+ zeroColor: inheritColor(cfg?.zeroLineColor),
318
+ zeroVisible: cfg?.zeroLineVisible ?? true
319
+ };
320
+ }
321
+ function volumeToSpec(cfg) {
322
+ return {
323
+ enabled: cfg?.enabled ?? true,
324
+ heightFrac: cfg?.height ?? -1,
325
+ opacity: cfg?.opacity ?? -1,
326
+ radiusPx: cfg?.radius ?? -1,
327
+ upColor: (cfg?.upColor != null ? parseColor(cfg.upColor) : null) ?? 0,
328
+ downColor: (cfg?.downColor != null ? parseColor(cfg.downColor) : null) ?? 0
329
+ };
330
+ }
184
331
  var DEFAULT_PRICE_LINE_COLOR = 4293874512;
185
332
  var DEFAULT_PRICE_LINE_BODY_BG = 3642499368;
186
333
  var DEFAULT_PRICE_LINE_HOVER_BOOST = 1.25;
@@ -218,14 +365,53 @@ function ensureInstalled() {
218
365
  }
219
366
  installed = true;
220
367
  }
221
- function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, priceLines) {
368
+ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines, transition) {
222
369
  const handleRef = (0, import_react.useRef)(null);
223
370
  const defaultWidthAppliedRef = (0, import_react.useRef)(false);
371
+ const volumeCollapseRef = (0, import_react.useRef)(null);
372
+ const prevDataRef = (0, import_react.useRef)(null);
373
+ const intervalMorphRaf = (0, import_react.useRef)(null);
224
374
  const [picture, setPicture] = (0, import_react.useState)(null);
225
375
  if (!handleRef.current && size.width > 0 && size.height > 0) {
226
376
  ensureInstalled();
227
377
  handleRef.current = globalThis.VroomChartJSI.create();
228
378
  }
379
+ const animRef = (0, import_react.useRef)({ ms: 300, easing: void 0, reduceMotion: false });
380
+ animRef.current = {
381
+ ms: Math.max(0, transition?.transitionMs ?? 300),
382
+ easing: transition?.transitionEasing,
383
+ reduceMotion: transition?.reduceMotion ?? false
384
+ };
385
+ const onFrameRef = (0, import_react.useRef)(transition?.onFrame);
386
+ onFrameRef.current = transition?.onFrame;
387
+ const seriesKey = transition?.seriesKey;
388
+ const endIntervalMorph = (0, import_react.useCallback)(() => {
389
+ if (intervalMorphRaf.current != null) {
390
+ cancelAnimationFrame(intervalMorphRaf.current);
391
+ intervalMorphRaf.current = null;
392
+ }
393
+ handleRef.current?.setIntervalMorph(1);
394
+ }, []);
395
+ const startIntervalMorph = (0, import_react.useCallback)((h) => {
396
+ const { ms, easing } = animRef.current;
397
+ const start = performance.now();
398
+ const step = (now) => {
399
+ const p = Math.min(1, (now - start) / ms);
400
+ h.setIntervalMorph(p < 1 ? ease(easing, p) : 1);
401
+ const pic = h.render();
402
+ if (pic) onFrameRef.current?.(pic);
403
+ intervalMorphRaf.current = p < 1 ? requestAnimationFrame(step) : null;
404
+ };
405
+ intervalMorphRaf.current = requestAnimationFrame(step);
406
+ }, []);
407
+ (0, import_react.useEffect)(() => {
408
+ return () => {
409
+ if (intervalMorphRaf.current != null) {
410
+ cancelAnimationFrame(intervalMorphRaf.current);
411
+ intervalMorphRaf.current = null;
412
+ }
413
+ };
414
+ }, []);
229
415
  const explicit = visibleRange != null;
230
416
  const startMs = visibleRange?.startMs ?? 0;
231
417
  const endMs = visibleRange?.endMs ?? 0;
@@ -235,6 +421,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
235
421
  const maKey = movingAverages ? JSON.stringify(movingAverages) : "";
236
422
  const vwapKey = vwap ? JSON.stringify(vwap) : "";
237
423
  const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
424
+ const volumeKey = volume ? JSON.stringify(volume) : "";
238
425
  const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
239
426
  (0, import_react.useEffect)(() => {
240
427
  const h = handleRef.current;
@@ -244,8 +431,54 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
244
431
  h.setDefaultCandleWidth(defaultCandleWidth);
245
432
  defaultWidthAppliedRef.current = true;
246
433
  }
434
+ let morphing = false;
247
435
  if (candles.length > 0) {
248
- h.setCandles(packCandles(candles));
436
+ const prev = prevDataRef.current;
437
+ const freshHandle = prev == null || prev.handle !== h;
438
+ if (freshHandle || prev.candles !== candles || prev.seriesKey !== seriesKey) {
439
+ const transitionKind = freshHandle ? "initial" : explicit ? "stream" : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);
440
+ let tfArgs = null;
441
+ let prevEnvelope = null;
442
+ if (transitionKind === "timeframe" && prev != null) {
443
+ const oldWindow = h.getVisibleRange();
444
+ const oldStepMs = inferStepMs(prev.candles);
445
+ if (oldWindow.endMs > oldWindow.startMs && oldStepMs != null) {
446
+ tfArgs = {
447
+ oldWindow,
448
+ oldStepMs,
449
+ oldLastMs: prev.candles[prev.candles.length - 1].timeMs
450
+ };
451
+ }
452
+ prevEnvelope = h.getVisiblePriceEnvelope();
453
+ morphing = animRef.current.ms > 0 && !animRef.current.reduceMotion && onFrameRef.current != null;
454
+ if (morphing) {
455
+ endIntervalMorph();
456
+ h.beginIntervalMorph();
457
+ }
458
+ } else if (transitionKind === "initial" || transitionKind === "reset") {
459
+ endIntervalMorph();
460
+ }
461
+ h.setCandles(packCandles(candles));
462
+ if (transitionKind === "timeframe") {
463
+ const newStepMs = inferStepMs(candles);
464
+ if (tfArgs && newStepMs != null) {
465
+ const w = timeframeWindow(
466
+ tfArgs.oldWindow,
467
+ tfArgs.oldStepMs,
468
+ tfArgs.oldLastMs,
469
+ newStepMs,
470
+ candles[candles.length - 1].timeMs
471
+ );
472
+ h.setVisibleRange(w.startMs, w.endMs);
473
+ }
474
+ if (prevEnvelope) h.preservePriceEnvelope(prevEnvelope.low, prevEnvelope.high);
475
+ else h.resetPriceScale();
476
+ if (morphing) startIntervalMorph(h);
477
+ } else if (transitionKind === "reset") {
478
+ h.resetView();
479
+ }
480
+ prevDataRef.current = { handle: h, candles, seriesKey };
481
+ }
249
482
  }
250
483
  if (explicit) {
251
484
  h.setVisibleRange(startMs, endMs);
@@ -253,40 +486,27 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
253
486
  if (theme) {
254
487
  applyTheme(h, theme);
255
488
  }
256
- h.setRSI(
257
- rsi?.enabled ?? false,
258
- rsi?.period ?? 14,
259
- rsi?.upperBand ?? 70,
260
- rsi?.lowerBand ?? 30,
261
- rsi?.maEnabled ?? true,
262
- rsi?.maPeriod ?? 14
263
- );
264
- h.setMACD(
265
- macd?.enabled ?? false,
266
- macd?.fast ?? 12,
267
- macd?.slow ?? 26,
268
- macd?.signal ?? 9
269
- );
489
+ h.setRSI(rsiToSpec(rsi));
490
+ h.setMACD(macdToSpec(macd));
270
491
  h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
271
- h.setVWAP(
272
- vwap?.enabled ?? false,
273
- vwap?.resetMinutes ?? 0,
274
- (vwap?.color != null ? parseColor(vwap.color) : null) ?? 4278238420,
275
- vwap?.width ?? 1.5
276
- );
492
+ h.setVWAP(vwapToSpec(vwap));
277
493
  h.setBollinger(bollingerToSpec(bollingerBands));
494
+ h.setVolume(volumeToSpec(volume));
495
+ const collapse = volumeCollapseRef.current;
496
+ if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);
278
497
  h.setPriceLines(
279
498
  priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES
280
499
  );
281
- setPicture(h.render());
282
- }, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, priceLinesKey]);
283
- return { handle: handleRef.current, picture };
500
+ if (!morphing) setPicture(h.render());
501
+ }, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey, startIntervalMorph, endIntervalMorph]);
502
+ return { handle: handleRef.current, picture, volumeCollapseRef };
284
503
  }
285
504
 
286
505
  // src/VroomChart.tsx
287
506
  function VroomChart(props) {
288
507
  const {
289
508
  candles,
509
+ seriesKey,
290
510
  width: widthProp,
291
511
  height: heightProp,
292
512
  style,
@@ -294,12 +514,14 @@ function VroomChart(props) {
294
514
  defaultCandleWidth,
295
515
  chartType,
296
516
  transitionMs,
517
+ transitionEasing,
297
518
  theme,
298
519
  rsi,
299
520
  macd,
300
521
  movingAverages,
301
522
  vwap,
302
523
  bollingerBands,
524
+ volume,
303
525
  crosshairOffset = 40,
304
526
  onCrosshair,
305
527
  onViewportChange,
@@ -327,7 +549,20 @@ function VroomChart(props) {
327
549
  } : void 0,
328
550
  [priceLines, priceLinesStyle, onPriceLineClose]
329
551
  );
330
- const { handle, picture } = useChartCore(
552
+ const emptyPicture = (0, import_react2.useMemo)(() => {
553
+ const rec = import_react_native_skia.Skia.PictureRecorder();
554
+ rec.beginRecording(import_react_native_skia.Skia.XYWHRect(0, 0, 1, 1));
555
+ return rec.finishRecordingAsPicture();
556
+ }, []);
557
+ const pictureSV = (0, import_react_native_reanimated.useSharedValue)(emptyPicture);
558
+ const reduceMotion = (0, import_react_native_reanimated.useReducedMotion)();
559
+ const onFrame = (0, import_react2.useCallback)(
560
+ (p) => {
561
+ pictureSV.value = p;
562
+ },
563
+ [pictureSV]
564
+ );
565
+ const { handle, picture, volumeCollapseRef } = useChartCore(
331
566
  candles,
332
567
  { width, height },
333
568
  visibleRange,
@@ -339,14 +574,10 @@ function VroomChart(props) {
339
574
  movingAverages,
340
575
  vwap,
341
576
  bollingerBands,
342
- priceLinesProp
577
+ volume,
578
+ priceLinesProp,
579
+ { seriesKey, transitionMs, transitionEasing, reduceMotion, onFrame }
343
580
  );
344
- const emptyPicture = (0, import_react2.useMemo)(() => {
345
- const rec = import_react_native_skia.Skia.PictureRecorder();
346
- rec.beginRecording(import_react_native_skia.Skia.XYWHRect(0, 0, 1, 1));
347
- return rec.finishRecordingAsPicture();
348
- }, []);
349
- const pictureSV = (0, import_react_native_reanimated.useSharedValue)(emptyPicture);
350
581
  const crosshairActive = (0, import_react2.useRef)(false);
351
582
  const lastCrosshairTime = (0, import_react2.useRef)(null);
352
583
  (0, import_react2.useEffect)(() => {
@@ -386,6 +617,8 @@ function VroomChart(props) {
386
617
  const morphRaf = (0, import_react2.useRef)(null);
387
618
  const morphFade = (0, import_react2.useRef)(null);
388
619
  const morphHandle = (0, import_react2.useRef)(null);
620
+ const easingRef = (0, import_react2.useRef)(transitionEasing);
621
+ easingRef.current = transitionEasing;
389
622
  (0, import_react2.useEffect)(() => {
390
623
  if (!handle) return void 0;
391
624
  const target = chartType === "line" ? 1 : 0;
@@ -415,10 +648,9 @@ function VroomChart(props) {
415
648
  const step = (now) => {
416
649
  if (startTs == null) startTs = now;
417
650
  const prog = Math.min(1, (now - startTs) / dur);
418
- const e = prog * prog * (3 - 2 * prog);
419
- const fade = from + (target - from) * e;
651
+ const fade = from + (target - from) * ease(easingRef.current, prog);
420
652
  morphFade.current = fade;
421
- handle.setMorph(fade, fade);
653
+ handle.setMorph(reduceMotion ? 0 : fade, fade);
422
654
  const p = handle.render();
423
655
  if (p) pictureSV.value = p;
424
656
  if (prog < 1) {
@@ -438,7 +670,52 @@ function VroomChart(props) {
438
670
  morphRaf.current = null;
439
671
  }
440
672
  };
441
- }, [handle, chartType, transitionMs, pictureSV]);
673
+ }, [handle, chartType, transitionMs, reduceMotion, pictureSV]);
674
+ const volumeRaf = (0, import_react2.useRef)(null);
675
+ const volumeHandle = (0, import_react2.useRef)(null);
676
+ (0, import_react2.useEffect)(() => {
677
+ if (!handle) return void 0;
678
+ const target = volume?.enabled ?? true ? 0 : 1;
679
+ const easing = easingIndex(easingRef.current);
680
+ if (volumeHandle.current !== handle || volumeCollapseRef.current == null) {
681
+ volumeHandle.current = handle;
682
+ volumeCollapseRef.current = { t: target, easing };
683
+ return void 0;
684
+ }
685
+ if (volumeCollapseRef.current.t === target) return void 0;
686
+ if (volumeRaf.current != null) {
687
+ cancelAnimationFrame(volumeRaf.current);
688
+ volumeRaf.current = null;
689
+ }
690
+ const dur = Math.max(0, transitionMs ?? 300);
691
+ if (dur === 0 || reduceMotion) {
692
+ volumeCollapseRef.current = { t: target, easing };
693
+ handle.setVolumeCollapse(target, easing);
694
+ const p = handle.render();
695
+ if (p) pictureSV.value = p;
696
+ return void 0;
697
+ }
698
+ const from = volumeCollapseRef.current.t;
699
+ let startTs = null;
700
+ const step = (now) => {
701
+ if (startTs == null) startTs = now;
702
+ const prog = Math.min(1, (now - startTs) / dur);
703
+ const t = prog < 1 ? from + (target - from) * prog : target;
704
+ const kind = easingIndex(easingRef.current);
705
+ volumeCollapseRef.current = { t, easing: kind };
706
+ handle.setVolumeCollapse(t, kind);
707
+ const p = handle.render();
708
+ if (p) pictureSV.value = p;
709
+ volumeRaf.current = prog < 1 ? requestAnimationFrame(step) : null;
710
+ };
711
+ volumeRaf.current = requestAnimationFrame(step);
712
+ return () => {
713
+ if (volumeRaf.current != null) {
714
+ cancelAnimationFrame(volumeRaf.current);
715
+ volumeRaf.current = null;
716
+ }
717
+ };
718
+ }, [handle, volume?.enabled, transitionMs, reduceMotion, pictureSV, volumeCollapseRef]);
442
719
  const hitAxis = (0, import_react2.useCallback)(
443
720
  (x, y) => {
444
721
  if (!handle) return "chart";
@@ -650,6 +927,9 @@ function VroomChart(props) {
650
927
  }
651
928
  // Annotate the CommonJS export names for ESM import in node:
652
929
  0 && (module.exports = {
653
- VroomChart
930
+ VroomChart,
931
+ classifyTransition,
932
+ inferStepMs,
933
+ timeframeWindow
654
934
  });
655
935
  //# sourceMappingURL=index.js.map