react-native-livechart 4.2.0 → 4.3.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 (35) hide show
  1. package/dist/components/LiveChart.d.ts.map +1 -1
  2. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  3. package/dist/constants.d.ts +6 -0
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/core/liveChartEngineTick.d.ts +11 -0
  6. package/dist/core/liveChartEngineTick.d.ts.map +1 -1
  7. package/dist/core/liveChartSeriesEngineTick.d.ts +10 -0
  8. package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
  9. package/dist/core/resolveConfig.d.ts +11 -1
  10. package/dist/core/resolveConfig.d.ts.map +1 -1
  11. package/dist/core/useLiveChartEngine.d.ts +18 -0
  12. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  13. package/dist/core/useLiveChartSeriesEngine.d.ts +17 -0
  14. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
  15. package/dist/hooks/useReferenceDrag.d.ts +12 -4
  16. package/dist/hooks/useReferenceDrag.d.ts.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/math/referenceDrag.d.ts +9 -0
  20. package/dist/math/referenceDrag.d.ts.map +1 -1
  21. package/dist/types.d.ts +36 -0
  22. package/dist/types.d.ts.map +1 -1
  23. package/package.json +1 -1
  24. package/src/components/LiveChart.tsx +35 -22
  25. package/src/components/LiveChartSeries.tsx +7 -0
  26. package/src/constants.ts +7 -0
  27. package/src/core/liveChartEngineTick.ts +42 -7
  28. package/src/core/liveChartSeriesEngineTick.ts +37 -5
  29. package/src/core/resolveConfig.ts +21 -0
  30. package/src/core/useLiveChartEngine.ts +71 -1
  31. package/src/core/useLiveChartSeriesEngine.ts +65 -1
  32. package/src/hooks/useReferenceDrag.ts +41 -11
  33. package/src/index.ts +1 -0
  34. package/src/math/referenceDrag.ts +19 -0
  35. package/src/types.ts +37 -0
@@ -13,6 +13,7 @@ import {
13
13
  clampToBounds,
14
14
  nearestDraggableIndex,
15
15
  referenceValueOut,
16
+ resolveDragIntent,
16
17
  } from "../math/referenceDrag";
17
18
  import { referenceLineForm } from "../math/referenceLines";
18
19
  import type { ReferenceLine } from "../types";
@@ -24,7 +25,7 @@ import {
24
25
 
25
26
  /** Vertical reach (px) around a line within which a touch grabs it. */
26
27
  const GRAB_SLOP = 14;
27
- /** Travel (px) that resolves drag (vertical) vs. fall-through (horizontal) intent. */
28
+ /** Travel (px) past which a grabbed line starts dragging (in either axis). */
28
29
  const DRAG_ACTIVATE_PX = 4;
29
30
 
30
31
  /** Stable empty array so the handle / out-state worklets stay referentially stable
@@ -40,9 +41,12 @@ const EMPTY: never[] = [];
40
41
  * and overlays read.
41
42
  *
42
43
  * The pan uses `manualActivation`: it grabs only when a touch starts within
43
- * {@link GRAB_SLOP} of a draggable line **and** the motion is vertical a
44
- * horizontal drag (scrub) or a touch off every line fails fast so the chart's other
45
- * gestures run (compose this ahead of them via `Gesture.Exclusive`).
44
+ * {@link GRAB_SLOP} of a draggable line, then **owns** that touch any drag past
45
+ * {@link DRAG_ACTIVATE_PX} (in either axis) drags the line. A touch off every line
46
+ * fails fast so the chart's other gestures (scrub / scroll) run everywhere else
47
+ * (compose this ahead of them via `Gesture.Exclusive`). Crucially it no longer
48
+ * falls through to scrub on a horizontal start, so a drag begun on a line always
49
+ * wins the race rather than dropping a scrub crosshair (#163).
46
50
  *
47
51
  * Also fires the per-line drag callbacks: `onChange` (de-duped during drag),
48
52
  * `onCommit` (on release), and `onDragIn` / `onDragOut` (value crossing the visible
@@ -55,7 +59,12 @@ export function useReferenceDrag(
55
59
  dragValues: SharedValue<number[]>,
56
60
  dragActive: SharedValue<boolean[]>,
57
61
  enabled: boolean,
58
- ): ReturnType<typeof Gesture.Pan> {
62
+ ): {
63
+ gesture: ReturnType<typeof Gesture.Pan>;
64
+ /** True when a touch at (x,y) would grab a draggable line — lets the scrub
65
+ * gesture decline that press so it never drops a crosshair on a line (#163). */
66
+ hitTest: (x: number, y: number) => boolean;
67
+ } {
59
68
  const anyDraggable =
60
69
  enabled &&
61
70
  lines.some((l) => l.draggable && referenceLineForm(l) === "line");
@@ -178,16 +187,22 @@ export function useReferenceDrag(
178
187
  /* istanbul ignore next -- gesture worklet runs on the UI thread, not in Jest */
179
188
  const onTouchesMove = (
180
189
  e: { allTouches: { x: number; y: number }[] },
181
- manager: { activate: () => void; fail: () => void },
190
+ manager: { activate: () => void },
182
191
  ) => {
183
192
  "worklet";
184
193
  if (dragIndex.get() < 0) return;
185
194
  const t = e.allTouches[0];
186
195
  if (!t) return;
187
- const dx = Math.abs(t.x - startX.get());
188
- const dy = Math.abs(t.y - startY.get());
189
- if (dy > DRAG_ACTIVATE_PX && dy >= dx) manager.activate();
190
- else if (dx > DRAG_ACTIVATE_PX) manager.fail();
196
+ // A line is grabbed → it owns the touch: any drag past the threshold drags it.
197
+ // We don't fail on horizontal intent (which used to hand the touch to scrub)
198
+ // that let the scrub crosshair win the race even on a vertical drag started on
199
+ // the line (#163). Scrub / scroll still run off a line's grab band.
200
+ const intent = resolveDragIntent(
201
+ t.x - startX.get(),
202
+ t.y - startY.get(),
203
+ DRAG_ACTIVATE_PX,
204
+ );
205
+ if (intent === "activate") manager.activate();
191
206
  };
192
207
 
193
208
  /* istanbul ignore next -- gesture worklet runs on the UI thread, not in Jest */
@@ -242,7 +257,20 @@ export function useReferenceDrag(
242
257
  activated.set(false);
243
258
  };
244
259
 
245
- return Gesture.Pan()
260
+ // Geometric hit-test shared with the scrub gesture: is (x,y) within reach of a
261
+ // draggable line's handle? The scrub's `onStart` consults this to bail on a
262
+ // press over a line, so it never drops a crosshair there even though it activates
263
+ // independently of this manual-activation pan (the `Exclusive` priority alone
264
+ // doesn't hold scrub back while this gesture is merely pressed). x is unused —
265
+ // a Form-A line spans the full width, so only the y-reach matters.
266
+ /* istanbul ignore next -- worklet, runs on the UI thread */
267
+ const hitTest = (_x: number, y: number): boolean => {
268
+ "worklet";
269
+ if (!anyDraggable) return false;
270
+ return nearestDraggableIndex(handleYs.get(), y, GRAB_SLOP) >= 0;
271
+ };
272
+
273
+ const gesture = Gesture.Pan()
246
274
  .enabled(anyDraggable)
247
275
  .maxPointers(1)
248
276
  .manualActivation(true)
@@ -251,4 +279,6 @@ export function useReferenceDrag(
251
279
  .onStart(onStart)
252
280
  .onUpdate(onUpdate)
253
281
  .onFinalize(onFinalize);
282
+
283
+ return { gesture, hitTest };
254
284
  }
package/src/index.ts CHANGED
@@ -79,6 +79,7 @@ export type {
79
79
  ReferenceLineBadgeConfig,
80
80
  ReferenceLineGroupingConfig,
81
81
  ReferenceLineRenderProps,
82
+ ReturnToLiveConfig,
82
83
  ScrubActionConfig,
83
84
  ScrubActionPoint,
84
85
  ScrubConfig,
@@ -43,6 +43,25 @@ export function nearestDraggableIndex(
43
43
  return best;
44
44
  }
45
45
 
46
+ /**
47
+ * Resolve a grabbed line's drag intent from the touch travel (px) since the grab.
48
+ * Once a line is grabbed it **owns** the touch: any drag past `threshold` px in
49
+ * either axis activates the drag. We deliberately don't fall through to scrub on
50
+ * horizontal / diagonal intent — that let the scrub crosshair win the race even
51
+ * on a vertical drag started on the line (#163). Returns `"activate"` past the
52
+ * threshold, else `"wait"` (keep the gesture armed for more travel).
53
+ */
54
+ export function resolveDragIntent(
55
+ dx: number,
56
+ dy: number,
57
+ threshold: number,
58
+ ): "activate" | "wait" {
59
+ "worklet";
60
+ return Math.abs(dx) > threshold || Math.abs(dy) > threshold
61
+ ? "activate"
62
+ : "wait";
63
+ }
64
+
46
65
  /**
47
66
  * Whether a line's value sits **outside** its watched interval — the trigger for
48
67
  * `onDragOut` / `onDragIn` (edge-detected by the caller). When `bounds` is set the
package/src/types.ts CHANGED
@@ -1492,6 +1492,22 @@ export interface TimeScrollConfig {
1492
1492
  scrubHoldMs?: number;
1493
1493
  }
1494
1494
 
1495
+ /**
1496
+ * Tunes the "return to live" glide — the eased animation that carries the window
1497
+ * back to the live edge when `timeScroll` is switched off while scrolled back. See
1498
+ * {@link TimeScrollConfig.returnToLive}.
1499
+ *
1500
+ * @experimental
1501
+ */
1502
+ export interface ReturnToLiveConfig {
1503
+ /**
1504
+ * Glide duration in milliseconds. Default `450`. The window decelerates onto the
1505
+ * live edge (ease-out cubic). Lower = snappier; `0` is treated as no animation
1506
+ * (an instant snap).
1507
+ */
1508
+ duration?: number;
1509
+ }
1510
+
1495
1511
  /**
1496
1512
  * Pinch-to-zoom the visible time window (see {@link LiveChartCoreProps.zoom}).
1497
1513
  *
@@ -1683,6 +1699,11 @@ export interface LiveChartCoreProps {
1683
1699
  * live edge again. One-finger plot-area scrub is unchanged. Requires retained
1684
1700
  * history in `data` / `candles` to scroll into.
1685
1701
  *
1702
+ * Setting this back to `false` while scrolled back — or switching `mode` so it's
1703
+ * no longer passed — **glides** the window back to the live edge (a brief eased
1704
+ * animation, not an instant jump); it never stays frozen at the previous scroll
1705
+ * position. Tune or disable that glide with {@link returnToLive}.
1706
+ *
1686
1707
  * `true` uses the default drag-to-scroll gesture (`"holdToScrub"`); pass a
1687
1708
  * {@link TimeScrollConfig} to pick the activation (`"holdToScrub"` or
1688
1709
  * `"axisDrag"`). Default `false`.
@@ -1690,6 +1711,22 @@ export interface LiveChartCoreProps {
1690
1711
  * @experimental Prototype — gesture model and API may change.
1691
1712
  */
1692
1713
  timeScroll?: boolean | TimeScrollConfig;
1714
+ /**
1715
+ * Controls the **return-to-live glide** — the animation that carries the window
1716
+ * back to the live edge when {@link timeScroll} is switched off (set to `false`,
1717
+ * or a `mode` switch stops passing it) while scrolled back into history:
1718
+ * - `true` (default) — glide back, easing onto the live edge over `450` ms.
1719
+ * - `false` — snap instantly, no animation.
1720
+ * - {@link ReturnToLiveConfig} — glide with a custom `{ duration }` (ms).
1721
+ *
1722
+ * Kept separate from {@link timeScroll} on purpose: the common way to leave
1723
+ * time-scroll (`timeScroll={false}`) discards any `TimeScrollConfig`, so this
1724
+ * lives alongside it to stay in effect through the toggle. Only governs that
1725
+ * programmatic return; a finger fling back to live keeps its own inertia.
1726
+ *
1727
+ * @experimental
1728
+ */
1729
+ returnToLive?: boolean | ReturnToLiveConfig;
1693
1730
  /**
1694
1731
  * Pinch-to-zoom the visible time window. Two-finger pinch in/out narrows or
1695
1732
  * widens the window, anchored at the focal point between your fingers. Composes