react-native-vroom-chart 0.12.0 → 0.13.1
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/android/CMakeLists.txt +8 -0
- package/android/README.md +11 -10
- package/cpp/VroomChartHostObject.cpp +140 -47
- package/cpp/VroomChartHostObject.h +1 -1
- package/cpp/VroomJsiInstaller.cpp +1 -1
- package/cpp/_core_src/price_format.cpp +55 -4
- package/cpp/_core_src/price_format.h +32 -5
- package/lib/index.js +62 -32
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +71 -34
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +92 -43
- package/src/jsi.d.ts +12 -9
- package/src/useChartCore.ts +4 -5
package/src/VroomChart.tsx
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
// VroomChart — Phase 3.
|
|
2
2
|
//
|
|
3
|
-
// Owns
|
|
4
|
-
// - useChartCore's "initial"
|
|
5
|
-
// - Pan gesture callbacks that call handle.pan(dx, dy) → fresh
|
|
3
|
+
// Owns SharedValues driven by:
|
|
4
|
+
// - useChartCore's "initial" frame (when data/size/range change), AND
|
|
5
|
+
// - Pan gesture callbacks that call handle.pan(dx, dy) → a fresh frame.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
7
|
+
// iOS wraps an SkPicture in-process. Android rasterizes to an SkImage (the
|
|
8
|
+
// two Skia copies can't share a picture pointer) so pan/zoom don't serialize
|
|
9
|
+
// the scene — and the system typeface — on every frame.
|
|
10
|
+
//
|
|
11
|
+
// Reanimated 4 + RN-Skia 2 propagate SharedValue changes to <Picture>/<Image>
|
|
12
|
+
// without a React re-render, so gesture-driven redraws are cheap.
|
|
9
13
|
//
|
|
10
14
|
// Gestures run on the JS thread for now (`runOnJS(true)`) — installing the
|
|
11
15
|
// JSI bindings on the worklet runtime is a later perf optimization.
|
|
12
16
|
|
|
13
17
|
import React, { useEffect, useRef, useCallback, useState, useMemo } from 'react';
|
|
14
|
-
import { View, type LayoutChangeEvent } from 'react-native';
|
|
15
|
-
import {
|
|
18
|
+
import { PixelRatio, View, type LayoutChangeEvent } from 'react-native';
|
|
19
|
+
import {
|
|
20
|
+
AlphaType,
|
|
21
|
+
Canvas,
|
|
22
|
+
ColorType,
|
|
23
|
+
Image,
|
|
24
|
+
Picture,
|
|
25
|
+
Skia,
|
|
26
|
+
type SkImage,
|
|
27
|
+
type SkPicture,
|
|
28
|
+
} from '@shopify/react-native-skia';
|
|
16
29
|
import {
|
|
17
30
|
Gesture,
|
|
18
31
|
GestureDetector,
|
|
@@ -22,9 +35,14 @@ import { useReducedMotion, useSharedValue } from 'react-native-reanimated';
|
|
|
22
35
|
|
|
23
36
|
import { useChartCore } from './useChartCore';
|
|
24
37
|
import { ease, easingIndex } from './easing';
|
|
38
|
+
import type { ChartFrame } from './jsi.d';
|
|
25
39
|
import type { VroomChartProps } from './types';
|
|
26
40
|
import './jsi.d';
|
|
27
41
|
|
|
42
|
+
function isSkImage(frame: ChartFrame): frame is SkImage {
|
|
43
|
+
return typeof (frame as SkImage).getImageInfo === 'function';
|
|
44
|
+
}
|
|
45
|
+
|
|
28
46
|
/**
|
|
29
47
|
* Skia-rendered candlestick chart. Pass OHLCV `candles` and size it via `style`
|
|
30
48
|
* (it fills its parent by default). Pan to scroll, pinch to zoom, drag the
|
|
@@ -92,16 +110,39 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
92
110
|
[priceLines, priceLinesStyle, onPriceLineClose],
|
|
93
111
|
);
|
|
94
112
|
|
|
95
|
-
// RN-Skia's recorder reads
|
|
96
|
-
// behind JS-thread writes. If it ever reads null it throws ("Invalid
|
|
97
|
-
// value for SkTextBlob received" — RN-Skia's mislabeled SkPicture
|
|
98
|
-
// we seed
|
|
113
|
+
// RN-Skia's recorder reads these SharedValues on the UI/render runtime, a
|
|
114
|
+
// beat behind JS-thread writes. If it ever reads null it throws ("Invalid
|
|
115
|
+
// prop value for SkTextBlob received" — RN-Skia's mislabeled SkPicture
|
|
116
|
+
// error), so we seed them and *never* assign null. Android writes the image
|
|
117
|
+
// SV (raster path); iOS writes the picture SV. The unused layer stays a
|
|
118
|
+
// transparent 1×1 so it doesn't cover the other.
|
|
99
119
|
const emptyPicture = useMemo(() => {
|
|
100
120
|
const rec = Skia.PictureRecorder();
|
|
101
121
|
rec.beginRecording(Skia.XYWHRect(0, 0, 1, 1));
|
|
102
122
|
return rec.finishRecordingAsPicture();
|
|
103
123
|
}, []);
|
|
124
|
+
const emptyImage = useMemo(() => {
|
|
125
|
+
const data = Skia.Data.fromBytes(new Uint8Array(4));
|
|
126
|
+
return Skia.Image.MakeImage(
|
|
127
|
+
{
|
|
128
|
+
width: 1,
|
|
129
|
+
height: 1,
|
|
130
|
+
colorType: ColorType.RGBA_8888,
|
|
131
|
+
alphaType: AlphaType.Premul,
|
|
132
|
+
},
|
|
133
|
+
data,
|
|
134
|
+
4,
|
|
135
|
+
)!;
|
|
136
|
+
}, []);
|
|
104
137
|
const pictureSV = useSharedValue<SkPicture>(emptyPicture);
|
|
138
|
+
const imageSV = useSharedValue<SkImage>(emptyImage);
|
|
139
|
+
const applyFrame = useCallback(
|
|
140
|
+
(frame: ChartFrame) => {
|
|
141
|
+
if (isSkImage(frame)) imageSV.value = frame;
|
|
142
|
+
else pictureSV.value = frame;
|
|
143
|
+
},
|
|
144
|
+
[imageSV, pictureSV],
|
|
145
|
+
);
|
|
105
146
|
|
|
106
147
|
// An OS reduced-motion preference snaps every transition, the way
|
|
107
148
|
// prefers-reduced-motion does on web.
|
|
@@ -111,15 +152,15 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
111
152
|
// capture) but repaints every frame, so it writes straight into the SV rather
|
|
112
153
|
// than through React state — the same bypass the gesture handlers use.
|
|
113
154
|
const onFrame = useCallback(
|
|
114
|
-
(p:
|
|
115
|
-
|
|
155
|
+
(p: ChartFrame) => {
|
|
156
|
+
applyFrame(p);
|
|
116
157
|
},
|
|
117
|
-
[
|
|
158
|
+
[applyFrame],
|
|
118
159
|
);
|
|
119
160
|
|
|
120
161
|
const { handle, picture, volumeCollapseRef } = useChartCore(
|
|
121
162
|
candles,
|
|
122
|
-
{ width, height },
|
|
163
|
+
{ width, height, pxRatio: PixelRatio.get() },
|
|
123
164
|
visibleRange,
|
|
124
165
|
defaultCandleWidth,
|
|
125
166
|
chartType,
|
|
@@ -166,11 +207,11 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
166
207
|
animRaf.current = null;
|
|
167
208
|
if (!handle) return;
|
|
168
209
|
const next = handle.render();
|
|
169
|
-
if (next)
|
|
210
|
+
if (next) applyFrame(next);
|
|
170
211
|
if (handle.isAnimating()) {
|
|
171
212
|
animRaf.current = requestAnimationFrame(animTick);
|
|
172
213
|
}
|
|
173
|
-
}, [handle,
|
|
214
|
+
}, [handle, applyFrame]);
|
|
174
215
|
const maybeStartAnim = useCallback(() => {
|
|
175
216
|
if (animRaf.current != null) return;
|
|
176
217
|
if (!handle?.isAnimating()) return;
|
|
@@ -194,9 +235,9 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
194
235
|
// pulse on needs — otherwise the ring wouldn't move until you touched the
|
|
195
236
|
// chart.
|
|
196
237
|
useEffect(() => {
|
|
197
|
-
if (picture)
|
|
238
|
+
if (picture) applyFrame(picture);
|
|
198
239
|
maybeStartAnim();
|
|
199
|
-
}, [picture,
|
|
240
|
+
}, [picture, applyFrame, maybeStartAnim]);
|
|
200
241
|
|
|
201
242
|
// Candle↔line morph. When `chartType` changes we drive the core per-frame with
|
|
202
243
|
// a (collapse, fade) blend and push a fresh picture into the SV each frame — the
|
|
@@ -225,7 +266,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
225
266
|
morphFade.current = target;
|
|
226
267
|
handle.setChartType(target);
|
|
227
268
|
const p = handle.render();
|
|
228
|
-
if (p)
|
|
269
|
+
if (p) applyFrame(p);
|
|
229
270
|
maybeStartAnim();
|
|
230
271
|
return undefined;
|
|
231
272
|
}
|
|
@@ -243,7 +284,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
243
284
|
morphFade.current = target;
|
|
244
285
|
handle.setChartType(target);
|
|
245
286
|
const p = handle.render();
|
|
246
|
-
if (p)
|
|
287
|
+
if (p) applyFrame(p);
|
|
247
288
|
maybeStartAnim();
|
|
248
289
|
return undefined;
|
|
249
290
|
}
|
|
@@ -258,7 +299,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
258
299
|
// Reduced motion still crossfades, but skips the vertical collapse.
|
|
259
300
|
handle.setMorph(reduceMotion ? 0 : fade, fade);
|
|
260
301
|
const p = handle.render();
|
|
261
|
-
if (p)
|
|
302
|
+
if (p) applyFrame(p);
|
|
262
303
|
if (prog < 1) {
|
|
263
304
|
morphRaf.current = requestAnimationFrame(step);
|
|
264
305
|
} else {
|
|
@@ -266,7 +307,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
266
307
|
morphFade.current = target;
|
|
267
308
|
handle.setChartType(target); // lock the exact endpoint
|
|
268
309
|
const q = handle.render();
|
|
269
|
-
if (q)
|
|
310
|
+
if (q) applyFrame(q);
|
|
270
311
|
maybeStartAnim();
|
|
271
312
|
}
|
|
272
313
|
};
|
|
@@ -279,9 +320,9 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
279
320
|
}
|
|
280
321
|
};
|
|
281
322
|
// maybeStartAnim is memoized on [handle, animTick] and animTick on
|
|
282
|
-
// [handle,
|
|
323
|
+
// [handle, applyFrame], both already deps here — so it adds no new restarts
|
|
283
324
|
// of this clock.
|
|
284
|
-
}, [handle, chartType, transitionMs, reduceMotion,
|
|
325
|
+
}, [handle, chartType, transitionMs, reduceMotion, applyFrame, maybeStartAnim]);
|
|
285
326
|
|
|
286
327
|
// Volume-bar collapse. The core staggers the bars itself — tallest falling
|
|
287
328
|
// first, all landing together — so unlike the loop above this one hands it
|
|
@@ -321,7 +362,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
321
362
|
volumeCollapseRef.current = { t: target, easing };
|
|
322
363
|
handle.setVolumeCollapse(target, easing);
|
|
323
364
|
const p = handle.render();
|
|
324
|
-
if (p)
|
|
365
|
+
if (p) applyFrame(p);
|
|
325
366
|
maybeStartAnim();
|
|
326
367
|
return undefined;
|
|
327
368
|
}
|
|
@@ -338,7 +379,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
338
379
|
volumeCollapseRef.current = { t, easing: kind };
|
|
339
380
|
handle.setVolumeCollapse(t, kind);
|
|
340
381
|
const p = handle.render();
|
|
341
|
-
if (p)
|
|
382
|
+
if (p) applyFrame(p);
|
|
342
383
|
if (prog < 1) {
|
|
343
384
|
volumeRaf.current = requestAnimationFrame(step);
|
|
344
385
|
} else {
|
|
@@ -359,7 +400,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
359
400
|
volume?.enabled,
|
|
360
401
|
transitionMs,
|
|
361
402
|
reduceMotion,
|
|
362
|
-
|
|
403
|
+
applyFrame,
|
|
363
404
|
volumeCollapseRef,
|
|
364
405
|
maybeStartAnim,
|
|
365
406
|
]);
|
|
@@ -386,7 +427,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
386
427
|
axisCollapse.current = { y: targetY, x: targetX };
|
|
387
428
|
handle.setAxisCollapse(targetY, targetX);
|
|
388
429
|
const p = handle.render();
|
|
389
|
-
if (p)
|
|
430
|
+
if (p) applyFrame(p);
|
|
390
431
|
maybeStartAnim();
|
|
391
432
|
return undefined;
|
|
392
433
|
}
|
|
@@ -408,7 +449,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
408
449
|
axisCollapse.current = { y: targetY, x: targetX };
|
|
409
450
|
handle.setAxisCollapse(targetY, targetX);
|
|
410
451
|
const p = handle.render();
|
|
411
|
-
if (p)
|
|
452
|
+
if (p) applyFrame(p);
|
|
412
453
|
maybeStartAnim();
|
|
413
454
|
return undefined;
|
|
414
455
|
}
|
|
@@ -425,7 +466,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
425
466
|
axisCollapse.current = { y, x };
|
|
426
467
|
handle.setAxisCollapse(y, x);
|
|
427
468
|
const p = handle.render();
|
|
428
|
-
if (p)
|
|
469
|
+
if (p) applyFrame(p);
|
|
429
470
|
if (prog < 1) {
|
|
430
471
|
axisRaf.current = requestAnimationFrame(step);
|
|
431
472
|
} else {
|
|
@@ -447,7 +488,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
447
488
|
showXAxis,
|
|
448
489
|
transitionMs,
|
|
449
490
|
reduceMotion,
|
|
450
|
-
|
|
491
|
+
applyFrame,
|
|
451
492
|
maybeStartAnim,
|
|
452
493
|
]);
|
|
453
494
|
|
|
@@ -516,7 +557,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
516
557
|
priceDrag.current = { index: pl.index, id: pl.line.id, price: pl.line.price };
|
|
517
558
|
handle.setPriceLineDrag(pl.index, pl.line.price);
|
|
518
559
|
const p = handle.render();
|
|
519
|
-
if (p)
|
|
560
|
+
if (p) applyFrame(p);
|
|
520
561
|
}
|
|
521
562
|
}
|
|
522
563
|
})
|
|
@@ -546,7 +587,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
546
587
|
// scrolling. Vertical line tracks the finger x; the dot/horizontal line
|
|
547
588
|
// stay lifted `crosshairOffset` px above the fingertip.
|
|
548
589
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
549
|
-
if (ch)
|
|
590
|
+
if (ch) applyFrame(ch);
|
|
550
591
|
// The line follows the finger every frame (above), but only notify the
|
|
551
592
|
// host when the snapped slot actually changes. The slot has a timeMs
|
|
552
593
|
// even in the empty space ahead of the last candle, where candle=null.
|
|
@@ -564,7 +605,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
564
605
|
// (axes follow). Diagonal works naturally.
|
|
565
606
|
next = handle.translate(e.changeX, e.changeY);
|
|
566
607
|
}
|
|
567
|
-
if (next)
|
|
608
|
+
if (next) applyFrame(next);
|
|
568
609
|
maybeStartAnim();
|
|
569
610
|
})
|
|
570
611
|
.onEnd((e) => {
|
|
@@ -577,7 +618,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
577
618
|
priceDrag.current = null;
|
|
578
619
|
handle.setPriceLineDrag(-1, 0);
|
|
579
620
|
const p = handle.render();
|
|
580
|
-
if (p)
|
|
621
|
+
if (p) applyFrame(p);
|
|
581
622
|
if (g) onPriceLineDragEnd?.(g.id, g.price);
|
|
582
623
|
return;
|
|
583
624
|
}
|
|
@@ -606,7 +647,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
606
647
|
velocity *= Math.pow(0.5, dt / HALF_LIFE_S);
|
|
607
648
|
const dx = velocity * dt;
|
|
608
649
|
const next = handle.pan(dx, 0);
|
|
609
|
-
if (next)
|
|
650
|
+
if (next) applyFrame(next);
|
|
610
651
|
maybeStartAnim();
|
|
611
652
|
|
|
612
653
|
if (Math.abs(velocity) > MIN_STOP) {
|
|
@@ -680,7 +721,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
680
721
|
if (frameX === 1 && frameY === 1) return;
|
|
681
722
|
|
|
682
723
|
const next = handle.zoom(frameX, frameY, focalX, focalY);
|
|
683
|
-
if (next)
|
|
724
|
+
if (next) applyFrame(next);
|
|
684
725
|
maybeStartAnim();
|
|
685
726
|
});
|
|
686
727
|
|
|
@@ -699,7 +740,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
699
740
|
cancelDecay();
|
|
700
741
|
crosshairActive.current = true;
|
|
701
742
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
702
|
-
if (ch)
|
|
743
|
+
if (ch) applyFrame(ch);
|
|
703
744
|
const info = handle.getCrosshairInfo();
|
|
704
745
|
lastCrosshairTime.current = info?.timeMs ?? null;
|
|
705
746
|
onCrosshair?.({
|
|
@@ -729,7 +770,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
729
770
|
if (hitAxis(e.x, e.y) !== 'chart') return;
|
|
730
771
|
crosshairActive.current = false;
|
|
731
772
|
const ch = handle.clearCrosshair();
|
|
732
|
-
if (ch)
|
|
773
|
+
if (ch) applyFrame(ch);
|
|
733
774
|
lastCrosshairTime.current = null;
|
|
734
775
|
onCrosshair?.({ active: false, candle: null, timeMs: null, price: null, reason: 'hide' });
|
|
735
776
|
});
|
|
@@ -749,9 +790,17 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
749
790
|
<View style={{ flex: 1 }}>
|
|
750
791
|
<Canvas style={{ flex: 1 }}>
|
|
751
792
|
{width > 0 && height > 0 ? (
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
793
|
+
<>
|
|
794
|
+
<Picture picture={pictureSV} />
|
|
795
|
+
<Image
|
|
796
|
+
image={imageSV}
|
|
797
|
+
x={0}
|
|
798
|
+
y={0}
|
|
799
|
+
width={width}
|
|
800
|
+
height={height}
|
|
801
|
+
fit="fill"
|
|
802
|
+
/>
|
|
803
|
+
</>
|
|
755
804
|
) : null}
|
|
756
805
|
</Canvas>
|
|
757
806
|
</View>
|
package/src/jsi.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Ambient declaration for the JSI HostObject installed by VroomChartModule.
|
|
2
2
|
|
|
3
|
-
import type { SkPicture } from '@shopify/react-native-skia';
|
|
3
|
+
import type { SkImage, SkPicture } from '@shopify/react-native-skia';
|
|
4
|
+
|
|
5
|
+
/** Frame the JSI handle returns: an SkPicture on iOS, an SkImage on Android. */
|
|
6
|
+
export type ChartFrame = SkPicture | SkImage;
|
|
4
7
|
|
|
5
8
|
export interface ChartHandle {
|
|
6
9
|
setCandles(buffer: ArrayBuffer): void;
|
|
@@ -79,30 +82,30 @@ export interface ChartHandle {
|
|
|
79
82
|
*/
|
|
80
83
|
setIntervalMorph(t: number): void;
|
|
81
84
|
/** Shifts the visible range by `dx`/`dy` pixels and returns a fresh picture. */
|
|
82
|
-
pan(dx: number, dy: number):
|
|
85
|
+
pan(dx: number, dy: number): ChartFrame | null;
|
|
83
86
|
/**
|
|
84
87
|
* Two-finger translation: shifts the time window AND the price bounds
|
|
85
88
|
* without rescaling. dy > 0 (drag down) moves content down.
|
|
86
89
|
*/
|
|
87
|
-
translate(dx: number, dy: number):
|
|
90
|
+
translate(dx: number, dy: number): ChartFrame | null;
|
|
88
91
|
/**
|
|
89
92
|
* Directional zoom by per-axis multiplicative factors around focus point
|
|
90
93
|
* (`fx`, `fy`) in pixels. `scaleX` resizes the time window (>1 = wider
|
|
91
94
|
* candles); `scaleY` resizes the price range (>1 = taller candles). Pass 1
|
|
92
95
|
* for an axis to leave it untouched.
|
|
93
96
|
*/
|
|
94
|
-
zoom(scaleX: number, scaleY: number, fx: number, fy: number):
|
|
97
|
+
zoom(scaleX: number, scaleY: number, fx: number, fy: number): ChartFrame | null;
|
|
95
98
|
/**
|
|
96
99
|
* Drag-on-y-axis price scaling. `dy > 0` widens the price range
|
|
97
100
|
* (candles shrink). Pivots around the price-range center.
|
|
98
101
|
*/
|
|
99
|
-
scalePriceAxis(dy: number):
|
|
102
|
+
scalePriceAxis(dy: number): ChartFrame | null;
|
|
100
103
|
/**
|
|
101
104
|
* Drag-on-x-axis time scaling. `dx > 0` widens the time window
|
|
102
105
|
* (candles thin). Pivots around the right edge so the most recent
|
|
103
106
|
* visible candle stays in place.
|
|
104
107
|
*/
|
|
105
|
-
scaleTimeAxis(dx: number):
|
|
108
|
+
scaleTimeAxis(dx: number): ChartFrame | null;
|
|
106
109
|
/**
|
|
107
110
|
* Current axis dimensions in pixels for hit testing in JS gestures.
|
|
108
111
|
* `indicatorHeight` is the below-chart indicator pane height (0 when none).
|
|
@@ -117,9 +120,9 @@ export interface ChartHandle {
|
|
|
117
120
|
* `y` should already be lifted above the touch point so the dot/horizontal
|
|
118
121
|
* line aren't hidden under the thumb.
|
|
119
122
|
*/
|
|
120
|
-
setCrosshair(x: number, y: number):
|
|
123
|
+
setCrosshair(x: number, y: number): ChartFrame | null;
|
|
121
124
|
/** Hides the crosshair and returns a fresh picture. */
|
|
122
|
-
clearCrosshair():
|
|
125
|
+
clearCrosshair(): ChartFrame | null;
|
|
123
126
|
/**
|
|
124
127
|
* OHLCV of the candle the crosshair currently snaps to, or null when the
|
|
125
128
|
* crosshair is inactive / there are no visible candles. Cheap to poll at
|
|
@@ -326,7 +329,7 @@ export interface ChartHandle {
|
|
|
326
329
|
setPriceLineDrag(index: number, price: number): void;
|
|
327
330
|
/** True while any axis-label fade is still in progress. Drives a RAF loop. */
|
|
328
331
|
isAnimating(): boolean;
|
|
329
|
-
render():
|
|
332
|
+
render(): ChartFrame | null;
|
|
330
333
|
}
|
|
331
334
|
|
|
332
335
|
export interface VroomChartJSI {
|
package/src/useChartCore.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import type { MutableRefObject } from 'react';
|
|
3
|
-
import type { SkPicture } from '@shopify/react-native-skia';
|
|
4
3
|
|
|
5
4
|
import NativeVroomChart from './NativeVroomChart';
|
|
6
5
|
import type { DataTransition } from './dataTransitions';
|
|
7
6
|
import { classifyTransition, inferStepMs, timeframeWindow } from './dataTransitions';
|
|
8
7
|
import { ease } from './easing';
|
|
9
|
-
import type { ChartHandle } from './jsi.d';
|
|
8
|
+
import type { ChartFrame, ChartHandle } from './jsi.d';
|
|
10
9
|
import { packCandles } from './packCandles';
|
|
11
10
|
import { applyTheme, parseColor, FLOAT_LINE_TIP_PULSE } from './theme';
|
|
12
11
|
import type {
|
|
@@ -233,13 +232,13 @@ export type TransitionOptions = {
|
|
|
233
232
|
/** OS reduced-motion preference: skips the capture and snaps. */
|
|
234
233
|
reduceMotion?: boolean;
|
|
235
234
|
/** Receives every morph frame. Without one, data swaps snap. */
|
|
236
|
-
onFrame?: (picture:
|
|
235
|
+
onFrame?: (picture: ChartFrame) => void;
|
|
237
236
|
};
|
|
238
237
|
|
|
239
238
|
export type ChartCoreState = {
|
|
240
239
|
handle: ChartHandle | null;
|
|
241
240
|
/** Picture freshly rendered after the latest data/size/range push. */
|
|
242
|
-
picture:
|
|
241
|
+
picture: ChartFrame | null;
|
|
243
242
|
/**
|
|
244
243
|
* The last volume collapse handed to the core, or null before the first push.
|
|
245
244
|
* VroomChart's animation loop owns this — it lives here only so the data effect
|
|
@@ -282,7 +281,7 @@ export function useChartCore(
|
|
|
282
281
|
seriesKey?: string;
|
|
283
282
|
} | null>(null);
|
|
284
283
|
const intervalMorphRaf = useRef<number | null>(null);
|
|
285
|
-
const [picture, setPicture] = useState<
|
|
284
|
+
const [picture, setPicture] = useState<ChartFrame | null>(null);
|
|
286
285
|
|
|
287
286
|
if (!handleRef.current && size.width > 0 && size.height > 0) {
|
|
288
287
|
ensureInstalled();
|