react-native-vroom-chart 0.13.0 → 0.14.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.
package/lib/index.mjs CHANGED
@@ -1,7 +1,14 @@
1
1
  // src/VroomChart.tsx
2
2
  import React, { useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2, useState as useState2, useMemo } from "react";
3
- import { View } from "react-native";
4
- import { Canvas, Picture, Skia } from "@shopify/react-native-skia";
3
+ import { PixelRatio, View } from "react-native";
4
+ import {
5
+ AlphaType,
6
+ Canvas,
7
+ ColorType,
8
+ Image,
9
+ Picture,
10
+ Skia
11
+ } from "@shopify/react-native-skia";
5
12
  import {
6
13
  Gesture,
7
14
  GestureDetector,
@@ -349,11 +356,12 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
349
356
  ensureInstalled();
350
357
  handleRef.current = globalThis.VroomChartJSI.create();
351
358
  }
352
- const animRef = useRef({ ms: 300, easing: void 0, reduceMotion: false });
359
+ const animRef = useRef({ ms: 300, easing: void 0, reduceMotion: false, interval: "transform" });
353
360
  animRef.current = {
354
361
  ms: Math.max(0, transition?.transitionMs ?? 300),
355
362
  easing: transition?.transitionEasing,
356
- reduceMotion: transition?.reduceMotion ?? false
363
+ reduceMotion: transition?.reduceMotion ?? false,
364
+ interval: transition?.intervalTransition === "fade" ? "fade" : "transform"
357
365
  };
358
366
  const onFrameRef = useRef(transition?.onFrame);
359
367
  onFrameRef.current = transition?.onFrame;
@@ -426,7 +434,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
426
434
  morphing = animRef.current.ms > 0 && !animRef.current.reduceMotion && onFrameRef.current != null;
427
435
  if (morphing) {
428
436
  endIntervalMorph();
429
- h.beginIntervalMorph();
437
+ h.beginIntervalMorph(animRef.current.interval);
430
438
  }
431
439
  } else if (transitionKind === "initial" || transitionKind === "reset") {
432
440
  endIntervalMorph();
@@ -479,6 +487,9 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
479
487
  }
480
488
 
481
489
  // src/VroomChart.tsx
490
+ function isSkImage(frame) {
491
+ return typeof frame.getImageInfo === "function";
492
+ }
482
493
  function VroomChart(props) {
483
494
  const {
484
495
  candles,
@@ -491,6 +502,7 @@ function VroomChart(props) {
491
502
  chartType,
492
503
  transitionMs,
493
504
  transitionEasing,
505
+ intervalTransition,
494
506
  theme,
495
507
  rsi,
496
508
  macd,
@@ -530,17 +542,38 @@ function VroomChart(props) {
530
542
  rec.beginRecording(Skia.XYWHRect(0, 0, 1, 1));
531
543
  return rec.finishRecordingAsPicture();
532
544
  }, []);
545
+ const emptyImage = useMemo(() => {
546
+ const data = Skia.Data.fromBytes(new Uint8Array(4));
547
+ return Skia.Image.MakeImage(
548
+ {
549
+ width: 1,
550
+ height: 1,
551
+ colorType: ColorType.RGBA_8888,
552
+ alphaType: AlphaType.Premul
553
+ },
554
+ data,
555
+ 4
556
+ );
557
+ }, []);
533
558
  const pictureSV = useSharedValue(emptyPicture);
559
+ const imageSV = useSharedValue(emptyImage);
560
+ const applyFrame = useCallback2(
561
+ (frame) => {
562
+ if (isSkImage(frame)) imageSV.value = frame;
563
+ else pictureSV.value = frame;
564
+ },
565
+ [imageSV, pictureSV]
566
+ );
534
567
  const reduceMotion = useReducedMotion();
535
568
  const onFrame = useCallback2(
536
569
  (p) => {
537
- pictureSV.value = p;
570
+ applyFrame(p);
538
571
  },
539
- [pictureSV]
572
+ [applyFrame]
540
573
  );
541
574
  const { handle, picture, volumeCollapseRef } = useChartCore(
542
575
  candles,
543
- { width, height },
576
+ { width, height, pxRatio: PixelRatio.get() },
544
577
  visibleRange,
545
578
  defaultCandleWidth,
546
579
  chartType,
@@ -552,7 +585,7 @@ function VroomChart(props) {
552
585
  bollingerBands,
553
586
  volume,
554
587
  priceLinesProp,
555
- { seriesKey, transitionMs, transitionEasing, reduceMotion, onFrame }
588
+ { seriesKey, transitionMs, transitionEasing, intervalTransition, reduceMotion, onFrame }
556
589
  );
557
590
  const crosshairActive = useRef2(false);
558
591
  const lastCrosshairTime = useRef2(null);
@@ -569,11 +602,11 @@ function VroomChart(props) {
569
602
  animRaf.current = null;
570
603
  if (!handle) return;
571
604
  const next = handle.render();
572
- if (next) pictureSV.value = next;
605
+ if (next) applyFrame(next);
573
606
  if (handle.isAnimating()) {
574
607
  animRaf.current = requestAnimationFrame(animTick);
575
608
  }
576
- }, [handle, pictureSV]);
609
+ }, [handle, applyFrame]);
577
610
  const maybeStartAnim = useCallback2(() => {
578
611
  if (animRaf.current != null) return;
579
612
  if (!handle?.isAnimating()) return;
@@ -588,9 +621,9 @@ function VroomChart(props) {
588
621
  };
589
622
  }, []);
590
623
  useEffect2(() => {
591
- if (picture) pictureSV.value = picture;
624
+ if (picture) applyFrame(picture);
592
625
  maybeStartAnim();
593
- }, [picture, pictureSV, maybeStartAnim]);
626
+ }, [picture, applyFrame, maybeStartAnim]);
594
627
  const morphRaf = useRef2(null);
595
628
  const morphFade = useRef2(null);
596
629
  const morphHandle = useRef2(null);
@@ -604,7 +637,7 @@ function VroomChart(props) {
604
637
  morphFade.current = target;
605
638
  handle.setChartType(target);
606
639
  const p = handle.render();
607
- if (p) pictureSV.value = p;
640
+ if (p) applyFrame(p);
608
641
  maybeStartAnim();
609
642
  return void 0;
610
643
  }
@@ -621,7 +654,7 @@ function VroomChart(props) {
621
654
  morphFade.current = target;
622
655
  handle.setChartType(target);
623
656
  const p = handle.render();
624
- if (p) pictureSV.value = p;
657
+ if (p) applyFrame(p);
625
658
  maybeStartAnim();
626
659
  return void 0;
627
660
  }
@@ -634,7 +667,7 @@ function VroomChart(props) {
634
667
  morphFade.current = fade;
635
668
  handle.setMorph(reduceMotion ? 0 : fade, fade);
636
669
  const p = handle.render();
637
- if (p) pictureSV.value = p;
670
+ if (p) applyFrame(p);
638
671
  if (prog < 1) {
639
672
  morphRaf.current = requestAnimationFrame(step);
640
673
  } else {
@@ -642,7 +675,7 @@ function VroomChart(props) {
642
675
  morphFade.current = target;
643
676
  handle.setChartType(target);
644
677
  const q = handle.render();
645
- if (q) pictureSV.value = q;
678
+ if (q) applyFrame(q);
646
679
  maybeStartAnim();
647
680
  }
648
681
  };
@@ -653,7 +686,7 @@ function VroomChart(props) {
653
686
  morphRaf.current = null;
654
687
  }
655
688
  };
656
- }, [handle, chartType, transitionMs, reduceMotion, pictureSV, maybeStartAnim]);
689
+ }, [handle, chartType, transitionMs, reduceMotion, applyFrame, maybeStartAnim]);
657
690
  const volumeRaf = useRef2(null);
658
691
  const volumeHandle = useRef2(null);
659
692
  useEffect2(() => {
@@ -679,7 +712,7 @@ function VroomChart(props) {
679
712
  volumeCollapseRef.current = { t: target, easing };
680
713
  handle.setVolumeCollapse(target, easing);
681
714
  const p = handle.render();
682
- if (p) pictureSV.value = p;
715
+ if (p) applyFrame(p);
683
716
  maybeStartAnim();
684
717
  return void 0;
685
718
  }
@@ -693,7 +726,7 @@ function VroomChart(props) {
693
726
  volumeCollapseRef.current = { t, easing: kind };
694
727
  handle.setVolumeCollapse(t, kind);
695
728
  const p = handle.render();
696
- if (p) pictureSV.value = p;
729
+ if (p) applyFrame(p);
697
730
  if (prog < 1) {
698
731
  volumeRaf.current = requestAnimationFrame(step);
699
732
  } else {
@@ -713,7 +746,7 @@ function VroomChart(props) {
713
746
  volume?.enabled,
714
747
  transitionMs,
715
748
  reduceMotion,
716
- pictureSV,
749
+ applyFrame,
717
750
  volumeCollapseRef,
718
751
  maybeStartAnim
719
752
  ]);
@@ -731,7 +764,7 @@ function VroomChart(props) {
731
764
  axisCollapse.current = { y: targetY, x: targetX };
732
765
  handle.setAxisCollapse(targetY, targetX);
733
766
  const p = handle.render();
734
- if (p) pictureSV.value = p;
767
+ if (p) applyFrame(p);
735
768
  maybeStartAnim();
736
769
  return void 0;
737
770
  }
@@ -750,7 +783,7 @@ function VroomChart(props) {
750
783
  axisCollapse.current = { y: targetY, x: targetX };
751
784
  handle.setAxisCollapse(targetY, targetX);
752
785
  const p = handle.render();
753
- if (p) pictureSV.value = p;
786
+ if (p) applyFrame(p);
754
787
  maybeStartAnim();
755
788
  return void 0;
756
789
  }
@@ -764,7 +797,7 @@ function VroomChart(props) {
764
797
  axisCollapse.current = { y, x };
765
798
  handle.setAxisCollapse(y, x);
766
799
  const p = handle.render();
767
- if (p) pictureSV.value = p;
800
+ if (p) applyFrame(p);
768
801
  if (prog < 1) {
769
802
  axisRaf.current = requestAnimationFrame(step);
770
803
  } else {
@@ -785,7 +818,7 @@ function VroomChart(props) {
785
818
  showXAxis,
786
819
  transitionMs,
787
820
  reduceMotion,
788
- pictureSV,
821
+ applyFrame,
789
822
  maybeStartAnim
790
823
  ]);
791
824
  const hitAxis = useCallback2(
@@ -825,7 +858,7 @@ function VroomChart(props) {
825
858
  priceDrag.current = { index: pl.index, id: pl.line.id, price: pl.line.price };
826
859
  handle.setPriceLineDrag(pl.index, pl.line.price);
827
860
  const p = handle.render();
828
- if (p) pictureSV.value = p;
861
+ if (p) applyFrame(p);
829
862
  }
830
863
  }
831
864
  }).onChange((e) => {
@@ -848,7 +881,7 @@ function VroomChart(props) {
848
881
  next = handle.render();
849
882
  } else if (crosshairActive.current) {
850
883
  const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
851
- if (ch) pictureSV.value = ch;
884
+ if (ch) applyFrame(ch);
852
885
  const info = handle.getCrosshairInfo();
853
886
  const t = info?.timeMs ?? null;
854
887
  if (t !== lastCrosshairTime.current) {
@@ -859,7 +892,7 @@ function VroomChart(props) {
859
892
  } else {
860
893
  next = handle.translate(e.changeX, e.changeY);
861
894
  }
862
- if (next) pictureSV.value = next;
895
+ if (next) applyFrame(next);
863
896
  maybeStartAnim();
864
897
  }).onEnd((e) => {
865
898
  if (!handle) return;
@@ -868,7 +901,7 @@ function VroomChart(props) {
868
901
  priceDrag.current = null;
869
902
  handle.setPriceLineDrag(-1, 0);
870
903
  const p = handle.render();
871
- if (p) pictureSV.value = p;
904
+ if (p) applyFrame(p);
872
905
  if (g) onPriceLineDragEnd?.(g.id, g.price);
873
906
  return;
874
907
  }
@@ -888,7 +921,7 @@ function VroomChart(props) {
888
921
  velocity *= Math.pow(0.5, dt / HALF_LIFE_S);
889
922
  const dx = velocity * dt;
890
923
  const next = handle.pan(dx, 0);
891
- if (next) pictureSV.value = next;
924
+ if (next) applyFrame(next);
892
925
  maybeStartAnim();
893
926
  if (Math.abs(velocity) > MIN_STOP) {
894
927
  decayRaf.current = requestAnimationFrame(tick);
@@ -942,7 +975,7 @@ function VroomChart(props) {
942
975
  }
943
976
  if (frameX === 1 && frameY === 1) return;
944
977
  const next = handle.zoom(frameX, frameY, focalX, focalY);
945
- if (next) pictureSV.value = next;
978
+ if (next) applyFrame(next);
946
979
  maybeStartAnim();
947
980
  });
948
981
  const longPress = Gesture.LongPress().runOnJS(true).onStart((e) => {
@@ -952,7 +985,7 @@ function VroomChart(props) {
952
985
  cancelDecay();
953
986
  crosshairActive.current = true;
954
987
  const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
955
- if (ch) pictureSV.value = ch;
988
+ if (ch) applyFrame(ch);
956
989
  const info = handle.getCrosshairInfo();
957
990
  lastCrosshairTime.current = info?.timeMs ?? null;
958
991
  onCrosshair?.({
@@ -975,7 +1008,7 @@ function VroomChart(props) {
975
1008
  if (hitAxis(e.x, e.y) !== "chart") return;
976
1009
  crosshairActive.current = false;
977
1010
  const ch = handle.clearCrosshair();
978
- if (ch) pictureSV.value = ch;
1011
+ if (ch) applyFrame(ch);
979
1012
  lastCrosshairTime.current = null;
980
1013
  onCrosshair?.({ active: false, candle: null, timeMs: null, price: null, reason: "hide" });
981
1014
  });
@@ -990,11 +1023,17 @@ function VroomChart(props) {
990
1023
  style
991
1024
  ]
992
1025
  },
993
- /* @__PURE__ */ React.createElement(GestureDetector, { gesture }, /* @__PURE__ */ React.createElement(View, { style: { flex: 1 } }, /* @__PURE__ */ React.createElement(Canvas, { style: { flex: 1 } }, width > 0 && height > 0 ? (
994
- // pictureSV is always a valid picture (seeded empty, never null),
995
- // so RN-Skia's UI-thread reader never sees null.
996
- /* @__PURE__ */ React.createElement(Picture, { picture: pictureSV })
997
- ) : null)))
1026
+ /* @__PURE__ */ React.createElement(GestureDetector, { gesture }, /* @__PURE__ */ React.createElement(View, { style: { flex: 1 } }, /* @__PURE__ */ React.createElement(Canvas, { style: { flex: 1 } }, width > 0 && height > 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Picture, { picture: pictureSV }), /* @__PURE__ */ React.createElement(
1027
+ Image,
1028
+ {
1029
+ image: imageSV,
1030
+ x: 0,
1031
+ y: 0,
1032
+ width,
1033
+ height,
1034
+ fit: "fill"
1035
+ }
1036
+ )) : null)))
998
1037
  );
999
1038
  }
1000
1039
  export {