react-native-livechart 4.22.2 → 4.23.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 (50) hide show
  1. package/dist/components/DotOverlay.d.ts +8 -1
  2. package/dist/components/DotOverlay.d.ts.map +1 -1
  3. package/dist/components/LiveChart.d.ts.map +1 -1
  4. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  5. package/dist/components/YAxisOverlay.d.ts +3 -1
  6. package/dist/components/YAxisOverlay.d.ts.map +1 -1
  7. package/dist/core/liveChartEngineTick.d.ts +13 -0
  8. package/dist/core/liveChartEngineTick.d.ts.map +1 -1
  9. package/dist/core/resolveConfig.d.ts +1 -0
  10. package/dist/core/resolveConfig.d.ts.map +1 -1
  11. package/dist/core/useLiveChartEngine.d.ts +5 -1
  12. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  13. package/dist/hooks/useBadge.d.ts.map +1 -1
  14. package/dist/hooks/useCandlePaths.d.ts +3 -1
  15. package/dist/hooks/useCandlePaths.d.ts.map +1 -1
  16. package/dist/hooks/useDegen.d.ts +1 -1
  17. package/dist/hooks/useDegen.d.ts.map +1 -1
  18. package/dist/hooks/useMarkers.d.ts +3 -1
  19. package/dist/hooks/useMarkers.d.ts.map +1 -1
  20. package/dist/hooks/useReferenceDrag.d.ts.map +1 -1
  21. package/dist/hooks/useReferenceLine.d.ts.map +1 -1
  22. package/dist/hooks/useTradeStream.d.ts +3 -1
  23. package/dist/hooks/useTradeStream.d.ts.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/lib/measureFontTextWidth.d.ts +10 -1
  27. package/dist/lib/measureFontTextWidth.d.ts.map +1 -1
  28. package/dist/math/referenceDrag.d.ts +9 -0
  29. package/dist/math/referenceDrag.d.ts.map +1 -1
  30. package/dist/types.d.ts +29 -0
  31. package/dist/types.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/components/DotOverlay.tsx +36 -1
  34. package/src/components/LiveChart.tsx +26 -3
  35. package/src/components/LiveChartSeries.tsx +51 -23
  36. package/src/components/YAxisOverlay.tsx +16 -10
  37. package/src/core/liveChartEngineTick.ts +99 -6
  38. package/src/core/resolveConfig.ts +2 -0
  39. package/src/core/useLiveChartEngine.ts +108 -41
  40. package/src/hooks/useBadge.ts +7 -2
  41. package/src/hooks/useCandlePaths.ts +10 -8
  42. package/src/hooks/useDegen.ts +2 -0
  43. package/src/hooks/useMarkers.ts +3 -0
  44. package/src/hooks/useReferenceDrag.ts +12 -7
  45. package/src/hooks/useReferenceLine.ts +30 -4
  46. package/src/hooks/useTradeStream.ts +3 -0
  47. package/src/index.ts +1 -0
  48. package/src/lib/measureFontTextWidth.ts +26 -2
  49. package/src/math/referenceDrag.ts +19 -0
  50. package/src/types.ts +30 -0
@@ -1,4 +1,5 @@
1
1
  import { useDerivedValue, type SharedValue } from "react-native-reanimated";
2
+ import { useRef } from "react";
2
3
 
3
4
  import type { SkFont } from "@shopify/react-native-skia";
4
5
  import type { ChartEngineLayout } from "../core/useLiveChartEngine";
@@ -7,7 +8,10 @@ import {
7
8
  type YAxisEntry,
8
9
  } from "../draw/grid";
9
10
  import type { ChartPadding } from "../draw/line";
10
- import { measureFontTextWidth } from "../lib/measureFontTextWidth";
11
+ import {
12
+ measureFontTextWidth,
13
+ type TextWidthCache,
14
+ } from "../lib/measureFontTextWidth";
11
15
  import {
12
16
  classifyReferenceEdge,
13
17
  referenceLineForm,
@@ -120,6 +124,7 @@ function badgeGeometry(
120
124
  x1: number,
121
125
  x2: number,
122
126
  font: SkFont,
127
+ textWidthCache?: TextWidthCache,
123
128
  ): BadgeGeometry {
124
129
  "worklet";
125
130
  // Measure the icon's visual bounds (not just width): `SkiaText` draws from the
@@ -129,7 +134,9 @@ function badgeGeometry(
129
134
  // price-pill centering in computeActionBadgeLayout.
130
135
  const iconBounds = icon ? font.measureText(icon) : null;
131
136
  const iconW = iconBounds ? iconBounds.width : 0;
132
- const textW = text ? measureFontTextWidth(font, text) : 0;
137
+ const textW = text
138
+ ? measureFontTextWidth(font, text, textWidthCache)
139
+ : 0;
133
140
 
134
141
  let contentW = 0;
135
142
  let count = 0;
@@ -310,6 +317,7 @@ export function useReferenceLine(
310
317
  const form = line ? referenceLineForm(line) : "none";
311
318
  // Badge presentation depends only on the (stable) line props — resolve once.
312
319
  const badge = line ? resolveReferenceBadge(line) : null;
320
+ const textWidthCacheRef = useRef<TextWidthCache>({});
313
321
 
314
322
  return useDerivedValue<ReferenceLineLayout>(() => {
315
323
  if (!line || form === "none") return INVISIBLE;
@@ -447,7 +455,16 @@ export function useReferenceLine(
447
455
  ? chartTop + OFF_AXIS_EDGE_INSET
448
456
  : chartBottom - OFF_AXIS_EDGE_INSET;
449
457
  const text = referenceBadgeText(line, badge, v, formatValue, true);
450
- const g = badgeGeometry(badge.position, badge.icon, text, true, x1, x2, font);
458
+ const g = badgeGeometry(
459
+ badge.position,
460
+ badge.icon,
461
+ text,
462
+ true,
463
+ x1,
464
+ x2,
465
+ font,
466
+ textWidthCacheRef.current,
467
+ );
451
468
  return {
452
469
  ...INVISIBLE,
453
470
  visible: true,
@@ -476,7 +493,16 @@ export function useReferenceLine(
476
493
  // In-range pill badge (the `badge` config, not the legacy off-axis-only flag).
477
494
  if (badge && badge.inRange) {
478
495
  const text = referenceBadgeText(line, badge, v, formatValue, false);
479
- const g = badgeGeometry(badge.position, badge.icon, text, false, x1, x2, font);
496
+ const g = badgeGeometry(
497
+ badge.position,
498
+ badge.icon,
499
+ text,
500
+ false,
501
+ x1,
502
+ x2,
503
+ font,
504
+ textWidthCacheRef.current,
505
+ );
480
506
  return {
481
507
  ...INVISIBLE,
482
508
  visible: true,
@@ -28,6 +28,8 @@ export function useTradeStream(
28
28
  active: boolean,
29
29
  /** Static charts run no loops: register without starting. Default `true`. */
30
30
  autostart = true,
31
+ /** Runtime gate shared with the chart engine. */
32
+ isFrameLoopActive?: SharedValue<boolean>,
31
33
  ): SharedValue<TradeMarker[]> {
32
34
  const markers = useSharedValue<TradeMarker[]>([]);
33
35
  const state = useSharedValue<TradeStreamState>(createTradeStreamState());
@@ -37,6 +39,7 @@ export function useTradeStream(
37
39
  frameInfo,
38
40
  ) => {
39
41
  "worklet";
42
+ if (isFrameLoopActive?.get() === false) return;
40
43
  if (!active) {
41
44
  if (markers.get().length > 0) markers.set([]);
42
45
  return;
package/src/index.ts CHANGED
@@ -75,6 +75,7 @@ export type {
75
75
  LegendStyle,
76
76
  LineConfig,
77
77
  LiveChartCoreProps,
78
+ LiveChartFrameStats,
78
79
  LiveChartHandle,
79
80
  LiveChartMetrics,
80
81
  LiveChartMetricsOverride,
@@ -1,10 +1,34 @@
1
1
  import type { SkFont } from "@shopify/react-native-skia";
2
2
 
3
+ /** Caller-owned last-value cache for repeated UI-thread text measurement. */
4
+ export interface TextWidthCache {
5
+ /** Font used for the cached measurement. */
6
+ font?: SkFont;
7
+ /** Text used for the cached measurement. */
8
+ text?: string;
9
+ /** Cached width. */
10
+ width?: number;
11
+ }
12
+
3
13
  /**
4
14
  * Horizontal layout width for `text` via Skia `measureText` (replaces deprecated `getTextWidth`).
5
15
  * Marked worklet for use inside Reanimated `useDerivedValue`.
6
16
  */
7
- export function measureFontTextWidth(font: SkFont, text: string): number {
17
+ export function measureFontTextWidth(
18
+ font: SkFont,
19
+ text: string,
20
+ cache?: TextWidthCache,
21
+ ): number {
8
22
  "worklet";
9
- return font.measureText(text).width;
23
+ if (cache?.font === font && cache.text === text && cache.width !== undefined) {
24
+ return cache.width;
25
+ }
26
+
27
+ const width = font.measureText(text).width;
28
+ if (cache) {
29
+ cache.font = font;
30
+ cache.text = text;
31
+ cache.width = width;
32
+ }
33
+ return width;
10
34
  }
@@ -43,6 +43,25 @@ export function nearestDraggableIndex(
43
43
  return best;
44
44
  }
45
45
 
46
+ /**
47
+ * Whether the reference-drag gesture owns the current touch — the answer the
48
+ * scrub's activation guard needs. A **grabbed** line (`dragIndex >= 0`, pressed
49
+ * or already dragging) owns the touch outright, wherever the asking gesture says
50
+ * the finger is: the scrub pan can activate mid-drag and asks with its
51
+ * touch-DOWN point, which a longer drag has carried the line away from. With no
52
+ * line grabbed, it is the geometric reach test around the handles.
53
+ */
54
+ export function referenceDragOwnsTouch(
55
+ dragIndex: number,
56
+ handleYs: number[],
57
+ y: number,
58
+ slop: number,
59
+ ): boolean {
60
+ "worklet";
61
+ if (dragIndex >= 0) return true;
62
+ return nearestDraggableIndex(handleYs, y, slop) >= 0;
63
+ }
64
+
46
65
  /**
47
66
  * Resolve a grabbed line's drag intent from the touch travel (px) since the grab.
48
67
  * Once a line is grabbed it **owns** the touch: any drag past `threshold` px in
package/src/types.ts CHANGED
@@ -16,6 +16,16 @@ export interface LiveChartPoint {
16
16
  value: number;
17
17
  }
18
18
 
19
+ /** Development counters for continuous engine-frame state updates. */
20
+ export interface LiveChartFrameStats {
21
+ /** Frame callbacks that ran while the chart was live. */
22
+ frames: number;
23
+ /** Frames that changed at least one tracked engine value. */
24
+ published: number;
25
+ /** Frames whose tracked engine values were unchanged. */
26
+ skipped: number;
27
+ }
28
+
19
29
  /** Direction of recent price movement, used for dot/badge coloring and degen effects. */
20
30
  export type Momentum = "up" | "down" | "flat";
21
31
 
@@ -707,6 +717,9 @@ export interface VolumeConfig {
707
717
 
708
718
  /** Y-axis grid configuration. */
709
719
  export interface YAxisConfig {
720
+ /** Price-label side. For left placement, reserve the label gutter with insets.left.
721
+ * Right-side float/labelRightMargin options do not affect left labels. Default "right". */
722
+ side?: "left" | "right";
710
723
  /** Minimum pixel gap between grid lines. Default `36`. */
711
724
  minGap?: number;
712
725
  /**
@@ -2664,6 +2677,23 @@ export interface LiveChartProps extends LiveChartCoreProps {
2664
2677
  * switching back to live restarts the suspended loops and catches up. Frame the data
2665
2678
  * with `timeWindow` + `nowOverride` (see the historical-data-fill pattern). */
2666
2679
  static?: boolean;
2680
+ /**
2681
+ * Runtime gate for continuous frame work. Set this SharedValue to `false`
2682
+ * while a parent scroll gesture needs the UI thread; data keeps updating and
2683
+ * the chart catches up when it becomes `true`. Unlike {@link static}, this
2684
+ * keeps pan, zoom, scrub, and draggable reference-line gestures mounted.
2685
+ *
2686
+ * @experimental
2687
+ */
2688
+ isFrameLoopActive?: SharedValue<boolean>;
2689
+ /**
2690
+ * Optional development counter for engine frames that did or did not change
2691
+ * tracked engine values. It is not a count of Skia redraws. Supplying it adds
2692
+ * one SharedValue write per active engine frame.
2693
+ *
2694
+ * @experimental
2695
+ */
2696
+ debugFrameStats?: SharedValue<LiveChartFrameStats>;
2667
2697
  /**
2668
2698
  * Render a custom overlay floated over the chart canvas, handed a price↔pixel /
2669
2699
  * time↔pixel {@link ChartOverlayContext} so it can track the auto-rescaling axis