react-native-livechart 4.2.0 → 4.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"LiveChart.d.ts","sourceRoot":"","sources":["../../src/components/LiveChart.tsx"],"names":[],"mappings":"AAqGA,OAAO,KAAK,EAEV,cAAc,EAGf,MAAM,UAAU,CAAC;AAqqDlB,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAmK9C"}
1
+ {"version":3,"file":"LiveChart.d.ts","sourceRoot":"","sources":["../../src/components/LiveChart.tsx"],"names":[],"mappings":"AAqGA,OAAO,KAAK,EAEV,cAAc,EAGf,MAAM,UAAU,CAAC;AAuqDlB,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,+BAmK9C"}
@@ -12,13 +12,21 @@ import type { ReferenceLine } from "../types";
12
12
  * and overlays read.
13
13
  *
14
14
  * The pan uses `manualActivation`: it grabs only when a touch starts within
15
- * {@link GRAB_SLOP} of a draggable line **and** the motion is vertical a
16
- * horizontal drag (scrub) or a touch off every line fails fast so the chart's other
17
- * gestures run (compose this ahead of them via `Gesture.Exclusive`).
15
+ * {@link GRAB_SLOP} of a draggable line, then **owns** that touch any drag past
16
+ * {@link DRAG_ACTIVATE_PX} (in either axis) drags the line. A touch off every line
17
+ * fails fast so the chart's other gestures (scrub / scroll) run everywhere else
18
+ * (compose this ahead of them via `Gesture.Exclusive`). Crucially it no longer
19
+ * falls through to scrub on a horizontal start, so a drag begun on a line always
20
+ * wins the race rather than dropping a scrub crosshair (#163).
18
21
  *
19
22
  * Also fires the per-line drag callbacks: `onChange` (de-duped during drag),
20
23
  * `onCommit` (on release), and `onDragIn` / `onDragOut` (value crossing the visible
21
24
  * range or a `bounds`, from a drag or the axis rescaling — edge-detected each frame).
22
25
  */
23
- export declare function useReferenceDrag(engine: ChartEngineLayout, padding: ChartPadding, lines: ReferenceLine[], dragValues: SharedValue<number[]>, dragActive: SharedValue<boolean[]>, enabled: boolean): ReturnType<typeof Gesture.Pan>;
26
+ export declare function useReferenceDrag(engine: ChartEngineLayout, padding: ChartPadding, lines: ReferenceLine[], dragValues: SharedValue<number[]>, dragActive: SharedValue<boolean[]>, enabled: boolean): {
27
+ gesture: ReturnType<typeof Gesture.Pan>;
28
+ /** True when a touch at (x,y) would grab a draggable line — lets the scrub
29
+ * gesture decline that press so it never drops a crosshair on a line (#163). */
30
+ hitTest: (x: number, y: number) => boolean;
31
+ };
24
32
  //# sourceMappingURL=useReferenceDrag.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useReferenceDrag.d.ts","sourceRoot":"","sources":["../../src/hooks/useReferenceDrag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAOjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAgB9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,aAAa,EAAE,EACtB,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC,EACjC,UAAU,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,EAClC,OAAO,EAAE,OAAO,GACf,UAAU,CAAC,OAAO,OAAO,CAAC,GAAG,CAAC,CAoMhC"}
1
+ {"version":3,"file":"useReferenceDrag.d.ts","sourceRoot":"","sources":["../../src/hooks/useReferenceDrag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAQjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAgB9C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,aAAa,EAAE,EACtB,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC,EACjC,UAAU,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,EAClC,OAAO,EAAE,OAAO,GACf;IACD,OAAO,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACxC;qFACiF;IACjF,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5C,CAyNA"}
@@ -13,6 +13,15 @@ export declare function clampToBounds(value: number, bounds?: [number, number]):
13
13
  * out) are skipped. Ties favor the later (topmost-drawn) line.
14
14
  */
15
15
  export declare function nearestDraggableIndex(handleYs: number[], y: number, slop: number): number;
16
+ /**
17
+ * Resolve a grabbed line's drag intent from the touch travel (px) since the grab.
18
+ * Once a line is grabbed it **owns** the touch: any drag past `threshold` px in
19
+ * either axis activates the drag. We deliberately don't fall through to scrub on
20
+ * horizontal / diagonal intent — that let the scrub crosshair win the race even
21
+ * on a vertical drag started on the line (#163). Returns `"activate"` past the
22
+ * threshold, else `"wait"` (keep the gesture armed for more travel).
23
+ */
24
+ export declare function resolveDragIntent(dx: number, dy: number, threshold: number): "activate" | "wait";
16
25
  /**
17
26
  * Whether a line's value sits **outside** its watched interval — the trigger for
18
27
  * `onDragOut` / `onDragIn` (edge-detected by the caller). When `bounds` is set the
@@ -1 +1 @@
1
- {"version":3,"file":"referenceDrag.d.ts","sourceRoot":"","sources":["../../src/math/referenceDrag.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;2BAC2B;AAC3B,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GACxB,MAAM,CAMR;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAAE,EAClB,CAAC,EAAE,MAAM,EACT,IAAI,EAAE,MAAM,GACX,MAAM,CAcR;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GACxB,OAAO,CAQT"}
1
+ {"version":3,"file":"referenceDrag.d.ts","sourceRoot":"","sources":["../../src/math/referenceDrag.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;2BAC2B;AAC3B,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GACxB,MAAM,CAMR;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAAE,EAClB,CAAC,EAAE,MAAM,EACT,IAAI,EAAE,MAAM,GACX,MAAM,CAcR;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,GAChB,UAAU,GAAG,MAAM,CAKrB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GACxB,OAAO,CAQT"}
package/package.json CHANGED
@@ -67,5 +67,5 @@
67
67
  },
68
68
  "sideEffects": false,
69
69
  "types": "./dist/index.d.ts",
70
- "version": "4.2.0"
70
+ "version": "4.2.1"
71
71
  }
@@ -798,13 +798,30 @@ function useLiveChartController({
798
798
  dragValues,
799
799
  );
800
800
 
801
- // Combined "defer" hit-test for the scrub-action place-tap: yield to a marker or
802
- // a pressable badge under the finger so the tap is routed there instead of
803
- // dropping a reticle. (Both hit-tests return false when their feature is off.)
801
+ // Draggable reference lines: a per-line vertical pan that grabs a line near its
802
+ // value and drags it along the Y-axis (with snap / bounds / callbacks). Built
803
+ // unconditionally for stable hook order (and before `useCrosshair` so the scrub
804
+ // can defer to a line under the finger); the gesture self-disables when no line
805
+ // opts in, and it's only composed into the root when `refDragEnabled`.
806
+ const refDragEnabled =
807
+ !isStatic && allRefLines.some((l) => l.draggable === true);
808
+ const { gesture: refDragGesture, hitTest: refDragHitTest } = useReferenceDrag(
809
+ engine,
810
+ effectivePadding,
811
+ allRefLines,
812
+ dragValues,
813
+ dragActive,
814
+ !isStatic,
815
+ );
816
+
817
+ // Combined "defer" hit-test: the scrub-action place-tap and the live scrub both
818
+ // yield to a marker, a pressable badge, or a draggable line under the finger — so
819
+ // a press there is routed to that overlay / drag instead of dropping a reticle or
820
+ // crosshair. (Each hit-test returns false when its feature is off.)
804
821
  /* istanbul ignore next -- worklet runs on the UI thread, not in Jest */
805
822
  const deferTapHit = (x: number, y: number): boolean => {
806
823
  "worklet";
807
- return markerHitTest(x, y) || refLineHitTest(x, y);
824
+ return markerHitTest(x, y) || refLineHitTest(x, y) || refDragHitTest(x, y);
808
825
  };
809
826
 
810
827
  // Time-scroll activation. `holdToScrub`: a quick one-finger drag scrolls while
@@ -843,7 +860,7 @@ function useLiveChartController({
843
860
  scrubActionCfg,
844
861
  onScrubAction,
845
862
  metricsCfg.badge,
846
- markersActive || refPressActive ? deferTapHit : undefined,
863
+ markersActive || refPressActive || refDragEnabled ? deferTapHit : undefined,
847
864
  scrubCfg?.tooltipPlacement ?? "side",
848
865
  scrubCfg?.tooltipShowValue ?? true,
849
866
  scrubCfg?.tooltipShowTime ?? true,
@@ -876,21 +893,6 @@ function useLiveChartController({
876
893
  },
877
894
  });
878
895
 
879
- // Draggable reference lines: a per-line vertical pan that grabs a line near its
880
- // value and drags it along the Y-axis (with snap / bounds / callbacks). Built
881
- // unconditionally for stable hook order; the gesture self-disables when no line
882
- // opts in, and it's only composed into the root when `refDragEnabled`.
883
- const refDragEnabled =
884
- !isStatic && allRefLines.some((l) => l.draggable === true);
885
- const refDragGesture = useReferenceDrag(
886
- engine,
887
- effectivePadding,
888
- allRefLines,
889
- dragValues,
890
- dragActive,
891
- !isStatic,
892
- );
893
-
894
896
  // Pinch-to-zoom the visible window (two-finger). Anchors at the focal point and
895
897
  // writes viewWindow + viewEnd; composes via Simultaneous (it's two-finger, so
896
898
  // disjoint from the one-finger pan/scrub). See `zoom`.
@@ -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
  }
@@ -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