react-native-vroom-chart 0.16.0 → 0.18.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/README.md +1 -1
  2. package/android/CMakeLists.txt +12 -2
  3. package/android/README.md +16 -6
  4. package/android/build.gradle +10 -0
  5. package/android/src/main/cpp/OnLoad.cpp +10 -0
  6. package/android/src/main/cpp/VroomChartJsiBindings.cpp +35 -0
  7. package/android/src/main/cpp/VroomChartJsiBindings.h +27 -0
  8. package/android/src/main/java/com/vroom/chart/VroomChartModule.kt +15 -11
  9. package/cpp/VroomChartHostObject.cpp +81 -0
  10. package/cpp/VroomJsiInstaller.cpp +6 -0
  11. package/cpp/_core_include/vroom/vroom_chart.h +41 -0
  12. package/cpp/_core_src/candles.cpp +15 -14
  13. package/cpp/_core_src/chart.cpp +297 -28
  14. package/cpp/_core_src/chart.h +71 -2
  15. package/cpp/_core_src/chart_facade.cpp +27 -0
  16. package/cpp/_core_src/color_lerp.h +28 -0
  17. package/cpp/_core_src/loading_line.cpp +183 -0
  18. package/cpp/_core_src/loading_line.h +38 -0
  19. package/cpp/_core_src/loading_wave.h +140 -0
  20. package/cpp/_core_src/ma_overlay.cpp +15 -10
  21. package/cpp/_core_src/ma_overlay.h +7 -0
  22. package/cpp/_core_src/price_indicator.cpp +21 -7
  23. package/cpp/_core_src/price_indicator.h +11 -1
  24. package/cpp/_core_src/price_indicator_anim.h +81 -0
  25. package/cpp/_core_src/theme.cpp +5 -0
  26. package/cpp/_core_src/tip_geometry.h +55 -0
  27. package/cpp/_core_src/viewport.h +33 -0
  28. package/ios/README.md +17 -5
  29. package/ios/VroomChartModule.h +3 -6
  30. package/ios/VroomChartModule.mm +17 -23
  31. package/lib/index.d.mts +30 -0
  32. package/lib/index.d.ts +30 -0
  33. package/lib/index.js +75 -14
  34. package/lib/index.js.map +1 -1
  35. package/lib/index.mjs +75 -14
  36. package/lib/index.mjs.map +1 -1
  37. package/package.json +9 -9
  38. package/react-native-vroom-chart.podspec +1 -1
  39. package/src/NativeVroomChart.ts +7 -4
  40. package/src/VroomChart.tsx +12 -0
  41. package/src/jsi.d.ts +42 -0
  42. package/src/theme.ts +1 -0
  43. package/src/useChartCore.ts +129 -7
  44. package/android/src/main/cpp/VroomChartJni.cpp +0 -24
package/lib/index.mjs CHANGED
@@ -158,8 +158,10 @@ var COLOR_KEYS = {
158
158
  // VROOM_COLOR_ACCENT_BULL
159
159
  accentBear: 15,
160
160
  // VROOM_COLOR_ACCENT_BEAR
161
- lineColor: 16
161
+ lineColor: 16,
162
162
  // VROOM_COLOR_LINE
163
+ skeleton: 17
164
+ // VROOM_COLOR_SKELETON
163
165
  };
164
166
  var FLOAT_KEYS = {
165
167
  wickWidth: 1,
@@ -452,14 +454,15 @@ var EMPTY_FOOTPRINTS = footprintsToSpec({ prints: [] });
452
454
  var installed = false;
453
455
  function ensureInstalled() {
454
456
  if (installed) return;
455
- const ok = NativeVroomChart_default.install();
456
- if (!ok) throw new Error("VroomChartModule.install() returned false");
457
+ NativeVroomChart_default.install();
457
458
  if (typeof globalThis.VroomChartJSI === "undefined") {
458
- throw new Error("global.VroomChartJSI undefined after install()");
459
+ throw new Error(
460
+ "global.VroomChartJSI is undefined \u2014 VroomChartModule did not install its JSI bindings. This requires react-native >= 0.78 with the New Architecture enabled."
461
+ );
459
462
  }
460
463
  installed = true;
461
464
  }
462
- function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, atr, movingAverages, vwap, bollingerBands, ichimoku, fairValueGaps, volume, priceLines, footprints, transition) {
465
+ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, atr, movingAverages, vwap, bollingerBands, ichimoku, fairValueGaps, volume, priceLines, footprints, transition, loading) {
463
466
  const handleRef = useRef(null);
464
467
  const defaultWidthAppliedRef = useRef(false);
465
468
  const volumeCollapseRef = useRef(null);
@@ -494,18 +497,26 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
494
497
  const onFrameRef = useRef(transition?.onFrame);
495
498
  onFrameRef.current = transition?.onFrame;
496
499
  const seriesKey = transition?.seriesKey;
500
+ const loadingHandoffRef = useRef(false);
497
501
  const endIntervalMorph = useCallback(() => {
498
502
  if (intervalMorphRaf.current != null) {
499
503
  cancelAnimationFrame(intervalMorphRaf.current);
500
504
  intervalMorphRaf.current = null;
501
505
  }
502
- handleRef.current?.setIntervalMorph(1);
506
+ const h = handleRef.current;
507
+ if (loadingHandoffRef.current) {
508
+ loadingHandoffRef.current = false;
509
+ h?.setLoadingMorph(1);
510
+ h?.beginLoadingReveal();
511
+ }
512
+ h?.setIntervalMorph(1);
503
513
  }, []);
504
- const startIntervalMorph = useCallback((h) => {
514
+ const startIntervalMorph = useCallback((h, durationMs) => {
505
515
  const { ms, easing } = animRef.current;
516
+ const dur = durationMs ?? ms;
506
517
  const start = performance.now();
507
518
  const step = (now) => {
508
- const p = Math.min(1, (now - start) / ms);
519
+ const p = Math.min(1, (now - start) / dur);
509
520
  h.setIntervalMorph(p < 1 ? ease(easing, p) : 1);
510
521
  const pic = h.render();
511
522
  if (pic) onFrameRef.current?.(pic);
@@ -513,6 +524,31 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
513
524
  };
514
525
  intervalMorphRaf.current = requestAnimationFrame(step);
515
526
  }, []);
527
+ const startLoadingHandoff = useCallback(
528
+ (h) => {
529
+ const { ms, easing } = animRef.current;
530
+ const half = ms / 2;
531
+ loadingHandoffRef.current = true;
532
+ h.beginLoadingMorph();
533
+ const start = performance.now();
534
+ const step = (now) => {
535
+ const p = Math.min(1, (now - start) / half);
536
+ h.setLoadingMorph(p < 1 ? ease(easing, p) : 1);
537
+ const pic = h.render();
538
+ if (pic) onFrameRef.current?.(pic);
539
+ if (p < 1) {
540
+ intervalMorphRaf.current = requestAnimationFrame(step);
541
+ return;
542
+ }
543
+ intervalMorphRaf.current = null;
544
+ loadingHandoffRef.current = false;
545
+ h.beginLoadingReveal();
546
+ startIntervalMorph(h, half);
547
+ };
548
+ intervalMorphRaf.current = requestAnimationFrame(step);
549
+ },
550
+ [startIntervalMorph]
551
+ );
516
552
  const settleStream = useCallback((keepMorph = false) => {
517
553
  if (streamRaf.current != null) {
518
554
  cancelAnimationFrame(streamRaf.current);
@@ -582,6 +618,8 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
582
618
  const explicit = visibleRange != null;
583
619
  const startMs = visibleRange?.startMs ?? 0;
584
620
  const endMs = visibleRange?.endMs ?? 0;
621
+ const showLoadingLine = loading === true && candles.length === 0;
622
+ const lineUpRef = useRef(false);
585
623
  const themeKey = theme ? JSON.stringify(theme) : "";
586
624
  const rsiKey = rsi ? JSON.stringify(rsi) : "";
587
625
  const macdKey = macd ? JSON.stringify(macd) : "";
@@ -604,6 +642,19 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
604
642
  defaultWidthAppliedRef.current = true;
605
643
  }
606
644
  let morphing = false;
645
+ if (showLoadingLine) {
646
+ if (!lineUpRef.current) {
647
+ endIntervalMorph();
648
+ settleStream();
649
+ h.setCandles(packCandles([]));
650
+ prevDataRef.current = null;
651
+ }
652
+ h.setLoading(true, !animRef.current.reduceMotion);
653
+ lineUpRef.current = true;
654
+ } else if (lineUpRef.current && candles.length === 0) {
655
+ h.setLoading(false, true);
656
+ lineUpRef.current = false;
657
+ }
607
658
  if (candles.length > 0) {
608
659
  const prev = prevDataRef.current;
609
660
  const freshHandle = prev == null || prev.handle !== h;
@@ -611,6 +662,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
611
662
  const transitionKind = freshHandle ? "initial" : explicit ? "stream" : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);
612
663
  let tfArgs = null;
613
664
  let prevEnvelope = null;
665
+ let handOff = false;
614
666
  let stream = null;
615
667
  if (transitionKind === "stream" && prev != null && !explicit) {
616
668
  const { stream: mode, streamMs, reduceMotion } = animRef.current;
@@ -660,6 +712,11 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
660
712
  } else if (transitionKind === "initial" || transitionKind === "reset") {
661
713
  endIntervalMorph();
662
714
  }
715
+ if (lineUpRef.current) {
716
+ lineUpRef.current = false;
717
+ handOff = animRef.current.ms > 0 && !animRef.current.reduceMotion && onFrameRef.current != null;
718
+ if (!handOff) h.setLoading(false, true);
719
+ }
663
720
  h.setCandles(packCandles(candles));
664
721
  if (transitionKind === "timeframe") {
665
722
  const newStepMs = inferStepMs(candles);
@@ -681,6 +738,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
681
738
  } else if (transitionKind === "reset") {
682
739
  h.resetView();
683
740
  }
741
+ if (handOff) startLoadingHandoff(h);
684
742
  prevDataRef.current = { handle: h, candles, seriesKey };
685
743
  }
686
744
  }
@@ -710,7 +768,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
710
768
  footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS
711
769
  );
712
770
  if (!morphing) setPicture(h.render());
713
- }, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, atrKey, maKey, vwapKey, bollingerKey, ichimokuKey, fvgKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, endIntervalMorph, startStreamAnim, settleStream]);
771
+ }, [candles, showLoadingLine, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, atrKey, maKey, vwapKey, bollingerKey, ichimokuKey, fvgKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, startLoadingHandoff, endIntervalMorph, startStreamAnim, settleStream]);
714
772
  return { handle: handleRef.current, picture, volumeCollapseRef };
715
773
  }
716
774
 
@@ -722,6 +780,7 @@ function isSkImage(frame) {
722
780
  function VroomChart(props) {
723
781
  const {
724
782
  candles,
783
+ loading,
725
784
  seriesKey,
726
785
  width: widthProp,
727
786
  height: heightProp,
@@ -839,8 +898,10 @@ function VroomChart(props) {
839
898
  streamTransitionMs,
840
899
  reduceMotion,
841
900
  onFrame
842
- }
901
+ },
902
+ loading
843
903
  );
904
+ const showLoadingLine = loading === true && candles.length === 0;
844
905
  const crosshairActive = useRef2(false);
845
906
  const footprintActive = useRef2(false);
846
907
  const lastCrosshairTime = useRef2(null);
@@ -1120,7 +1181,7 @@ function VroomChart(props) {
1120
1181
  pane: null
1121
1182
  });
1122
1183
  };
1123
- const pan = Gesture.Pan().runOnJS(true).maxPointers(1).onStart((e) => {
1184
+ const pan = Gesture.Pan().enabled(!showLoadingLine).runOnJS(true).maxPointers(1).onStart((e) => {
1124
1185
  cancelDecay();
1125
1186
  panMode.current = hitAxis(e.x, e.y);
1126
1187
  priceDrag.current = null;
@@ -1215,7 +1276,7 @@ function VroomChart(props) {
1215
1276
  enableX: false,
1216
1277
  enableY: false
1217
1278
  });
1218
- const pinch = Gesture.Pinch().runOnJS(true).onTouchesDown((e) => {
1279
+ const pinch = Gesture.Pinch().enabled(!showLoadingLine).runOnJS(true).onTouchesDown((e) => {
1219
1280
  if (e.numberOfTouches < 2) return;
1220
1281
  dismissFootprint();
1221
1282
  const [a, b] = e.allTouches;
@@ -1253,7 +1314,7 @@ function VroomChart(props) {
1253
1314
  if (next) applyFrame(next);
1254
1315
  maybeStartAnim();
1255
1316
  });
1256
- const longPress = Gesture.LongPress().runOnJS(true).onStart((e) => {
1317
+ const longPress = Gesture.LongPress().enabled(!showLoadingLine).runOnJS(true).onStart((e) => {
1257
1318
  if (!handle) return;
1258
1319
  if (hitAxis(e.x, e.y) !== "chart") return;
1259
1320
  if (hitPriceLine(e.x, e.y)) return;
@@ -1273,7 +1334,7 @@ function VroomChart(props) {
1273
1334
  reason: "show"
1274
1335
  });
1275
1336
  });
1276
- const tap = Gesture.Tap().runOnJS(true).onStart((e) => {
1337
+ const tap = Gesture.Tap().enabled(!showLoadingLine).runOnJS(true).onStart((e) => {
1277
1338
  if (!handle) return;
1278
1339
  const prints = footprints ?? [];
1279
1340
  const fp = prints.length ? handle.hitTestFootprint(e.x, e.y) : null;