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
@@ -92,6 +92,30 @@ struct CandleSnapshot {
92
92
  float x;
93
93
  float open, high, low, close;
94
94
  bool bull; // close >= open; selects the fill / wick / border color
95
+ // Set when the capture came from the loading skeleton. Such a slot starts
96
+ // out grey and semi-transparent rather than in its own bull/bear color, so
97
+ // the draw path has to blend from `skeleton_alpha`-scaled grey instead —
98
+ // that color blend is what makes the hand-off read as the placeholder
99
+ // *becoming* the data (see candles::draw).
100
+ bool skeleton = false;
101
+ // The wave's alpha for this bar at capture time. Carried so the morph's
102
+ // first frame matches the skeleton frame it replaced; without it every bar
103
+ // would jump to full opacity the instant data landed.
104
+ float skeleton_alpha = 1.f;
105
+ };
106
+
107
+ // One vertex of the loading line's morph, holding both ends of the animation
108
+ // so a frame is a plain lerp between them.
109
+ //
110
+ // The two y's are in different spaces on purpose. The sine knows nothing about
111
+ // prices, so it's a fraction of the pane; the candle centre is a fraction of
112
+ // the price band, the same space CandleSnapshot uses — which is what keeps the
113
+ // line sitting exactly where the bars emerge from, since both resolve through
114
+ // y_at_fraction.
115
+ struct LinePoint {
116
+ float x; // fraction of the candle-area width
117
+ float from_y; // fraction of the pane height — the sine, frozen at capture
118
+ float to_y; // fraction of the price band — the candle's vertical centre
95
119
  };
96
120
 
97
121
  // How many captured slots still contribute to a frame — 0 once the morph is
@@ -140,6 +164,15 @@ inline void blend_candle_snapshots(CandleSnapshot* dst, std::size_t dst_n,
140
164
  // `bull` stays the fresh one: the draw path colors a paired slot from
141
165
  // the live candle and only reads the capture's flag for a slot the next
142
166
  // update drops, where the newer direction is the better answer.
167
+ //
168
+ // The skeleton state, by contrast, carries over from the interrupted
169
+ // capture: a tick landing mid-hand-off must not abandon the grey blend
170
+ // partway, or the bar would snap to full color while its geometry is
171
+ // still moving. Its alpha follows the same lerp as the geometry.
172
+ if (from.skeleton) {
173
+ to.skeleton = true;
174
+ to.skeleton_alpha = mix(from.skeleton_alpha, 1.f);
175
+ }
143
176
  }
144
177
  }
145
178
 
package/ios/README.md CHANGED
@@ -1,7 +1,19 @@
1
- # iOS shim (placeholder)
1
+ # iOS bridge
2
2
 
3
- When native is wired up, this directory will hold the Objective-C++ view
4
- and module that host the chart and forward gestures into the C++ core.
3
+ A single TurboModule, [`VroomChartModule`](VroomChartModule.mm), conforming to
4
+ `RCTTurboModuleWithJSIBindings`. `RCTTurboModuleManager` calls
5
+ `-installJSIBindingsWithRuntime:callInvoker:` as soon as it instantiates the
6
+ module — before the module object reaches JS — and that installs
7
+ `global.VroomChartJSI` via the platform-agnostic
8
+ [`../cpp/VroomJsiInstaller.cpp`](../cpp/VroomJsiInstaller.cpp) Android also
9
+ uses. There is no native view: the chart renders into an `SkPicture` that
10
+ `<Canvas>` from `@shopify/react-native-skia` paints.
5
11
 
6
- The accompanying `react-native-vroom-chart.podspec` at the package root will
7
- build `cpp/` + `ios/` + the linked `@vroomchart/core` static library.
12
+ This replaced a `[RCTBridge currentBridge]` `RCTCxxBridge.runtime` lookup,
13
+ which stopped working in React Native 0.85. That release enables
14
+ `RCT_REMOVE_LEGACY_ARCH` by default, under which `RCTBridge` compiles down to
15
+ a stub whose `+currentBridge` returns `nil`.
16
+
17
+ [`../react-native-vroom-chart.podspec`](../react-native-vroom-chart.podspec)
18
+ builds `cpp/` + `ios/` plus the core sources mirrored in from
19
+ `packages/core/`.
@@ -1,11 +1,8 @@
1
1
  #import <Foundation/Foundation.h>
2
2
  #import <React/RCTBridgeModule.h>
3
-
4
- #ifdef RCT_NEW_ARCH_ENABLED
3
+ #import <ReactCommon/RCTTurboModuleWithJSIBindings.h>
5
4
  #import <VroomChartSpec/VroomChartSpec.h>
6
- @interface VroomChartModule : NSObject <NativeVroomChartSpec>
7
- #else
8
- @interface VroomChartModule : NSObject <RCTBridgeModule>
9
- #endif
5
+
6
+ @interface VroomChartModule : NSObject <NativeVroomChartSpec, RCTTurboModuleWithJSIBindings>
10
7
 
11
8
  @end
@@ -1,6 +1,5 @@
1
1
  #import "VroomChartModule.h"
2
2
 
3
- #import <React/RCTBridge+Private.h>
4
3
  #import <ReactCommon/CallInvoker.h>
5
4
  #import <jsi/jsi.h>
6
5
 
@@ -8,37 +7,32 @@
8
7
 
9
8
  using namespace facebook;
10
9
 
11
- @implementation VroomChartModule
10
+ @implementation VroomChartModule {
11
+ BOOL _didInstall;
12
+ }
12
13
 
13
14
  RCT_EXPORT_MODULE(VroomChartModule)
14
15
 
15
- // Called from JS via NativeVroomChart.install(). Grabs the JSI runtime from the
16
- // bridge and asks the C++ installer to expose global.VroomChartJSI.
16
+ // RCTTurboModuleManager calls this as soon as it instantiates the module
17
+ // before the module object reaches JS so global.VroomChartJSI always exists
18
+ // by the time anything imports NativeVroomChart.
17
19
  //
18
- // The TurboModule version (new arch) and the legacy version both end up here.
20
+ // callInvoker is unused: every chart call is synchronous on the JS thread.
21
+ - (void)installJSIBindingsWithRuntime:(jsi::Runtime &)runtime
22
+ callInvoker:(const std::shared_ptr<react::CallInvoker> &)callInvoker
23
+ {
24
+ vroom::installJsi(runtime);
25
+ _didInstall = YES;
26
+ }
27
+
19
28
  - (NSNumber *)install
20
29
  {
21
- RCTBridge *bridge = [RCTBridge currentBridge];
22
- RCTCxxBridge *cxxBridge = (RCTCxxBridge *)bridge;
23
- if (cxxBridge == nil) {
24
- return @NO;
25
- }
26
-
27
- jsi::Runtime *runtime = (jsi::Runtime *)cxxBridge.runtime;
28
- if (runtime == nullptr) {
29
- return @NO;
30
- }
31
-
32
- vroom::installJsi(*runtime);
33
- return @YES;
30
+ return @(_didInstall);
34
31
  }
35
32
 
36
- #ifdef RCT_NEW_ARCH_ENABLED
37
- - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
38
- (const facebook::react::ObjCTurboModule::InitParams &)params
33
+ - (std::shared_ptr<react::TurboModule>)getTurboModule:(const react::ObjCTurboModule::InitParams &)params
39
34
  {
40
- return std::make_shared<facebook::react::NativeVroomChartSpecJSI>(params);
35
+ return std::make_shared<react::NativeVroomChartSpecJSI>(params);
41
36
  }
42
- #endif
43
37
 
44
38
  @end
package/lib/index.d.mts CHANGED
@@ -106,6 +106,19 @@ type VroomTheme = {
106
106
  crosshairTarget?: VroomColor;
107
107
  /** Line-chart-mode close polyline color. Defaults to violet, matching the RSI line. */
108
108
  lineColor?: VroomColor;
109
+ /**
110
+ * The line drawn across the plot while loading (see the `loading` prop).
111
+ * Defaults to inheriting `grid`: the gridlines are already the chart's tone
112
+ * for structure rather than data, which is what the line is, so matching
113
+ * them keeps it from being read as a series.
114
+ *
115
+ * The line breathes between roughly 60% and 100% of whatever color it ends
116
+ * up with — a pulse, not a dimmer, so a recessive color stays legible. Any
117
+ * alpha given here multiplies into that, so an opaque color is the usual
118
+ * choice. Pass `lineColor` to make the line read as the chart's own series
119
+ * warming up instead.
120
+ */
121
+ skeleton?: VroomColor;
109
122
  /** Line-chart-mode polyline stroke width in px. Defaults to 1.5. */
110
123
  lineWidth?: number;
111
124
  /**
@@ -1059,6 +1072,23 @@ type ATRConfig = {
1059
1072
  type VroomChartCoreProps = {
1060
1073
  /** OHLCV bars to render. The only required prop. */
1061
1074
  candles: Candle[];
1075
+ /**
1076
+ * Whether the series is still being fetched. While this is true *and*
1077
+ * `candles` is empty, the chart draws a single grey line across the plot,
1078
+ * drifting in a slow sine wave, in place of the scene. Gestures, the
1079
+ * crosshair, the price badge, the axis labels and any indicator panes are all
1080
+ * suppressed for the duration.
1081
+ *
1082
+ * When the data arrives the line doesn't cut away — it becomes the chart, in
1083
+ * two steps that split `transitionMs`: it reshapes to pass through the
1084
+ * vertical centre of every candle about to be drawn, then fades out while
1085
+ * those candles grow outward from it and their color fades up.
1086
+ *
1087
+ * Passing `candles` alongside `loading` leaves the real chart up, so a
1088
+ * background refresh of an already-loaded series won't blank out. Default
1089
+ * false.
1090
+ */
1091
+ loading?: boolean;
1062
1092
  /**
1063
1093
  * Identity of the data series (e.g. "BTC-USD"). When it changes between
1064
1094
  * renders the chart resets to the default view (most recent candles + price
package/lib/index.d.ts CHANGED
@@ -106,6 +106,19 @@ type VroomTheme = {
106
106
  crosshairTarget?: VroomColor;
107
107
  /** Line-chart-mode close polyline color. Defaults to violet, matching the RSI line. */
108
108
  lineColor?: VroomColor;
109
+ /**
110
+ * The line drawn across the plot while loading (see the `loading` prop).
111
+ * Defaults to inheriting `grid`: the gridlines are already the chart's tone
112
+ * for structure rather than data, which is what the line is, so matching
113
+ * them keeps it from being read as a series.
114
+ *
115
+ * The line breathes between roughly 60% and 100% of whatever color it ends
116
+ * up with — a pulse, not a dimmer, so a recessive color stays legible. Any
117
+ * alpha given here multiplies into that, so an opaque color is the usual
118
+ * choice. Pass `lineColor` to make the line read as the chart's own series
119
+ * warming up instead.
120
+ */
121
+ skeleton?: VroomColor;
109
122
  /** Line-chart-mode polyline stroke width in px. Defaults to 1.5. */
110
123
  lineWidth?: number;
111
124
  /**
@@ -1059,6 +1072,23 @@ type ATRConfig = {
1059
1072
  type VroomChartCoreProps = {
1060
1073
  /** OHLCV bars to render. The only required prop. */
1061
1074
  candles: Candle[];
1075
+ /**
1076
+ * Whether the series is still being fetched. While this is true *and*
1077
+ * `candles` is empty, the chart draws a single grey line across the plot,
1078
+ * drifting in a slow sine wave, in place of the scene. Gestures, the
1079
+ * crosshair, the price badge, the axis labels and any indicator panes are all
1080
+ * suppressed for the duration.
1081
+ *
1082
+ * When the data arrives the line doesn't cut away — it becomes the chart, in
1083
+ * two steps that split `transitionMs`: it reshapes to pass through the
1084
+ * vertical centre of every candle about to be drawn, then fades out while
1085
+ * those candles grow outward from it and their color fades up.
1086
+ *
1087
+ * Passing `candles` alongside `loading` leaves the real chart up, so a
1088
+ * background refresh of an already-loaded series won't blank out. Default
1089
+ * false.
1090
+ */
1091
+ loading?: boolean;
1062
1092
  /**
1063
1093
  * Identity of the data series (e.g. "BTC-USD"). When it changes between
1064
1094
  * renders the chart resets to the default view (most recent candles + price
package/lib/index.js CHANGED
@@ -186,8 +186,10 @@ var COLOR_KEYS = {
186
186
  // VROOM_COLOR_ACCENT_BULL
187
187
  accentBear: 15,
188
188
  // VROOM_COLOR_ACCENT_BEAR
189
- lineColor: 16
189
+ lineColor: 16,
190
190
  // VROOM_COLOR_LINE
191
+ skeleton: 17
192
+ // VROOM_COLOR_SKELETON
191
193
  };
192
194
  var FLOAT_KEYS = {
193
195
  wickWidth: 1,
@@ -480,14 +482,15 @@ var EMPTY_FOOTPRINTS = footprintsToSpec({ prints: [] });
480
482
  var installed = false;
481
483
  function ensureInstalled() {
482
484
  if (installed) return;
483
- const ok = NativeVroomChart_default.install();
484
- if (!ok) throw new Error("VroomChartModule.install() returned false");
485
+ NativeVroomChart_default.install();
485
486
  if (typeof globalThis.VroomChartJSI === "undefined") {
486
- throw new Error("global.VroomChartJSI undefined after install()");
487
+ throw new Error(
488
+ "global.VroomChartJSI is undefined \u2014 VroomChartModule did not install its JSI bindings. This requires react-native >= 0.78 with the New Architecture enabled."
489
+ );
487
490
  }
488
491
  installed = true;
489
492
  }
490
- function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, atr, movingAverages, vwap, bollingerBands, ichimoku, fairValueGaps, volume, priceLines, footprints, transition) {
493
+ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, atr, movingAverages, vwap, bollingerBands, ichimoku, fairValueGaps, volume, priceLines, footprints, transition, loading) {
491
494
  const handleRef = (0, import_react.useRef)(null);
492
495
  const defaultWidthAppliedRef = (0, import_react.useRef)(false);
493
496
  const volumeCollapseRef = (0, import_react.useRef)(null);
@@ -522,18 +525,26 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
522
525
  const onFrameRef = (0, import_react.useRef)(transition?.onFrame);
523
526
  onFrameRef.current = transition?.onFrame;
524
527
  const seriesKey = transition?.seriesKey;
528
+ const loadingHandoffRef = (0, import_react.useRef)(false);
525
529
  const endIntervalMorph = (0, import_react.useCallback)(() => {
526
530
  if (intervalMorphRaf.current != null) {
527
531
  cancelAnimationFrame(intervalMorphRaf.current);
528
532
  intervalMorphRaf.current = null;
529
533
  }
530
- handleRef.current?.setIntervalMorph(1);
534
+ const h = handleRef.current;
535
+ if (loadingHandoffRef.current) {
536
+ loadingHandoffRef.current = false;
537
+ h?.setLoadingMorph(1);
538
+ h?.beginLoadingReveal();
539
+ }
540
+ h?.setIntervalMorph(1);
531
541
  }, []);
532
- const startIntervalMorph = (0, import_react.useCallback)((h) => {
542
+ const startIntervalMorph = (0, import_react.useCallback)((h, durationMs) => {
533
543
  const { ms, easing } = animRef.current;
544
+ const dur = durationMs ?? ms;
534
545
  const start = performance.now();
535
546
  const step = (now) => {
536
- const p = Math.min(1, (now - start) / ms);
547
+ const p = Math.min(1, (now - start) / dur);
537
548
  h.setIntervalMorph(p < 1 ? ease(easing, p) : 1);
538
549
  const pic = h.render();
539
550
  if (pic) onFrameRef.current?.(pic);
@@ -541,6 +552,31 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
541
552
  };
542
553
  intervalMorphRaf.current = requestAnimationFrame(step);
543
554
  }, []);
555
+ const startLoadingHandoff = (0, import_react.useCallback)(
556
+ (h) => {
557
+ const { ms, easing } = animRef.current;
558
+ const half = ms / 2;
559
+ loadingHandoffRef.current = true;
560
+ h.beginLoadingMorph();
561
+ const start = performance.now();
562
+ const step = (now) => {
563
+ const p = Math.min(1, (now - start) / half);
564
+ h.setLoadingMorph(p < 1 ? ease(easing, p) : 1);
565
+ const pic = h.render();
566
+ if (pic) onFrameRef.current?.(pic);
567
+ if (p < 1) {
568
+ intervalMorphRaf.current = requestAnimationFrame(step);
569
+ return;
570
+ }
571
+ intervalMorphRaf.current = null;
572
+ loadingHandoffRef.current = false;
573
+ h.beginLoadingReveal();
574
+ startIntervalMorph(h, half);
575
+ };
576
+ intervalMorphRaf.current = requestAnimationFrame(step);
577
+ },
578
+ [startIntervalMorph]
579
+ );
544
580
  const settleStream = (0, import_react.useCallback)((keepMorph = false) => {
545
581
  if (streamRaf.current != null) {
546
582
  cancelAnimationFrame(streamRaf.current);
@@ -610,6 +646,8 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
610
646
  const explicit = visibleRange != null;
611
647
  const startMs = visibleRange?.startMs ?? 0;
612
648
  const endMs = visibleRange?.endMs ?? 0;
649
+ const showLoadingLine = loading === true && candles.length === 0;
650
+ const lineUpRef = (0, import_react.useRef)(false);
613
651
  const themeKey = theme ? JSON.stringify(theme) : "";
614
652
  const rsiKey = rsi ? JSON.stringify(rsi) : "";
615
653
  const macdKey = macd ? JSON.stringify(macd) : "";
@@ -632,6 +670,19 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
632
670
  defaultWidthAppliedRef.current = true;
633
671
  }
634
672
  let morphing = false;
673
+ if (showLoadingLine) {
674
+ if (!lineUpRef.current) {
675
+ endIntervalMorph();
676
+ settleStream();
677
+ h.setCandles(packCandles([]));
678
+ prevDataRef.current = null;
679
+ }
680
+ h.setLoading(true, !animRef.current.reduceMotion);
681
+ lineUpRef.current = true;
682
+ } else if (lineUpRef.current && candles.length === 0) {
683
+ h.setLoading(false, true);
684
+ lineUpRef.current = false;
685
+ }
635
686
  if (candles.length > 0) {
636
687
  const prev = prevDataRef.current;
637
688
  const freshHandle = prev == null || prev.handle !== h;
@@ -639,6 +690,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
639
690
  const transitionKind = freshHandle ? "initial" : explicit ? "stream" : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);
640
691
  let tfArgs = null;
641
692
  let prevEnvelope = null;
693
+ let handOff = false;
642
694
  let stream = null;
643
695
  if (transitionKind === "stream" && prev != null && !explicit) {
644
696
  const { stream: mode, streamMs, reduceMotion } = animRef.current;
@@ -688,6 +740,11 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
688
740
  } else if (transitionKind === "initial" || transitionKind === "reset") {
689
741
  endIntervalMorph();
690
742
  }
743
+ if (lineUpRef.current) {
744
+ lineUpRef.current = false;
745
+ handOff = animRef.current.ms > 0 && !animRef.current.reduceMotion && onFrameRef.current != null;
746
+ if (!handOff) h.setLoading(false, true);
747
+ }
691
748
  h.setCandles(packCandles(candles));
692
749
  if (transitionKind === "timeframe") {
693
750
  const newStepMs = inferStepMs(candles);
@@ -709,6 +766,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
709
766
  } else if (transitionKind === "reset") {
710
767
  h.resetView();
711
768
  }
769
+ if (handOff) startLoadingHandoff(h);
712
770
  prevDataRef.current = { handle: h, candles, seriesKey };
713
771
  }
714
772
  }
@@ -738,7 +796,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
738
796
  footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS
739
797
  );
740
798
  if (!morphing) setPicture(h.render());
741
- }, [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]);
799
+ }, [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]);
742
800
  return { handle: handleRef.current, picture, volumeCollapseRef };
743
801
  }
744
802
 
@@ -750,6 +808,7 @@ function isSkImage(frame) {
750
808
  function VroomChart(props) {
751
809
  const {
752
810
  candles,
811
+ loading,
753
812
  seriesKey,
754
813
  width: widthProp,
755
814
  height: heightProp,
@@ -867,8 +926,10 @@ function VroomChart(props) {
867
926
  streamTransitionMs,
868
927
  reduceMotion,
869
928
  onFrame
870
- }
929
+ },
930
+ loading
871
931
  );
932
+ const showLoadingLine = loading === true && candles.length === 0;
872
933
  const crosshairActive = (0, import_react2.useRef)(false);
873
934
  const footprintActive = (0, import_react2.useRef)(false);
874
935
  const lastCrosshairTime = (0, import_react2.useRef)(null);
@@ -1148,7 +1209,7 @@ function VroomChart(props) {
1148
1209
  pane: null
1149
1210
  });
1150
1211
  };
1151
- const pan = import_react_native_gesture_handler.Gesture.Pan().runOnJS(true).maxPointers(1).onStart((e) => {
1212
+ const pan = import_react_native_gesture_handler.Gesture.Pan().enabled(!showLoadingLine).runOnJS(true).maxPointers(1).onStart((e) => {
1152
1213
  cancelDecay();
1153
1214
  panMode.current = hitAxis(e.x, e.y);
1154
1215
  priceDrag.current = null;
@@ -1243,7 +1304,7 @@ function VroomChart(props) {
1243
1304
  enableX: false,
1244
1305
  enableY: false
1245
1306
  });
1246
- const pinch = import_react_native_gesture_handler.Gesture.Pinch().runOnJS(true).onTouchesDown((e) => {
1307
+ const pinch = import_react_native_gesture_handler.Gesture.Pinch().enabled(!showLoadingLine).runOnJS(true).onTouchesDown((e) => {
1247
1308
  if (e.numberOfTouches < 2) return;
1248
1309
  dismissFootprint();
1249
1310
  const [a, b] = e.allTouches;
@@ -1281,7 +1342,7 @@ function VroomChart(props) {
1281
1342
  if (next) applyFrame(next);
1282
1343
  maybeStartAnim();
1283
1344
  });
1284
- const longPress = import_react_native_gesture_handler.Gesture.LongPress().runOnJS(true).onStart((e) => {
1345
+ const longPress = import_react_native_gesture_handler.Gesture.LongPress().enabled(!showLoadingLine).runOnJS(true).onStart((e) => {
1285
1346
  if (!handle) return;
1286
1347
  if (hitAxis(e.x, e.y) !== "chart") return;
1287
1348
  if (hitPriceLine(e.x, e.y)) return;
@@ -1301,7 +1362,7 @@ function VroomChart(props) {
1301
1362
  reason: "show"
1302
1363
  });
1303
1364
  });
1304
- const tap = import_react_native_gesture_handler.Gesture.Tap().runOnJS(true).onStart((e) => {
1365
+ const tap = import_react_native_gesture_handler.Gesture.Tap().enabled(!showLoadingLine).runOnJS(true).onStart((e) => {
1305
1366
  if (!handle) return;
1306
1367
  const prints = footprints ?? [];
1307
1368
  const fp = prints.length ? handle.hitTestFootprint(e.x, e.y) : null;