react-native-vroom-chart 0.11.1 → 0.13.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.
@@ -212,6 +212,13 @@ export function VroomChart(props: VroomChartProps) {
212
212
  if (!handle) return undefined;
213
213
  const target = chartType === 'line' ? 1 : 0;
214
214
 
215
+ // Every exit below hands off to maybeStartAnim. Landing in line mode turns
216
+ // tip_pulse_active() on, and the pulse only moves while that loop is
217
+ // requeueing frames — this loop's own clock stops here. Without the handoff
218
+ // the ring sits frozen until some gesture happens to restart the other loop.
219
+ // It no-ops when a frame is already queued or nothing is animating, so
220
+ // landing in candle mode costs nothing.
221
+
215
222
  // Fresh handle (first load / recreate): snap, don't animate.
216
223
  if (morphHandle.current !== handle || morphFade.current == null) {
217
224
  morphHandle.current = handle;
@@ -219,9 +226,13 @@ export function VroomChart(props: VroomChartProps) {
219
226
  handle.setChartType(target);
220
227
  const p = handle.render();
221
228
  if (p) pictureSV.value = p;
229
+ maybeStartAnim();
230
+ return undefined;
231
+ }
232
+ if (morphFade.current === target) {
233
+ maybeStartAnim();
222
234
  return undefined;
223
235
  }
224
- if (morphFade.current === target) return undefined;
225
236
 
226
237
  if (morphRaf.current != null) {
227
238
  cancelAnimationFrame(morphRaf.current);
@@ -233,6 +244,7 @@ export function VroomChart(props: VroomChartProps) {
233
244
  handle.setChartType(target);
234
245
  const p = handle.render();
235
246
  if (p) pictureSV.value = p;
247
+ maybeStartAnim();
236
248
  return undefined;
237
249
  }
238
250
 
@@ -255,6 +267,7 @@ export function VroomChart(props: VroomChartProps) {
255
267
  handle.setChartType(target); // lock the exact endpoint
256
268
  const q = handle.render();
257
269
  if (q) pictureSV.value = q;
270
+ maybeStartAnim();
258
271
  }
259
272
  };
260
273
  morphRaf.current = requestAnimationFrame(step);
@@ -265,7 +278,10 @@ export function VroomChart(props: VroomChartProps) {
265
278
  morphRaf.current = null;
266
279
  }
267
280
  };
268
- }, [handle, chartType, transitionMs, reduceMotion, pictureSV]);
281
+ // maybeStartAnim is memoized on [handle, animTick] and animTick on
282
+ // [handle, pictureSV], both already deps here — so it adds no new restarts
283
+ // of this clock.
284
+ }, [handle, chartType, transitionMs, reduceMotion, pictureSV, maybeStartAnim]);
269
285
 
270
286
  // Volume-bar collapse. The core staggers the bars itself — tallest falling
271
287
  // first, all landing together — so unlike the loop above this one hands it
@@ -281,12 +297,19 @@ export function VroomChart(props: VroomChartProps) {
281
297
 
282
298
  // Fresh handle (first load / recreate): the data effect's setVolume already
283
299
  // snapped it, so a chart that mounts with bars doesn't animate them in.
300
+ // Every exit below hands off to maybeStartAnim, for the same reason the
301
+ // morph loop does: this clock stops here, and anything the core is still
302
+ // animating (the line-tip pulse) needs the other loop requeueing frames.
284
303
  if (volumeHandle.current !== handle || volumeCollapseRef.current == null) {
285
304
  volumeHandle.current = handle;
286
305
  volumeCollapseRef.current = { t: target, easing };
306
+ maybeStartAnim();
307
+ return undefined;
308
+ }
309
+ if (volumeCollapseRef.current.t === target) {
310
+ maybeStartAnim();
287
311
  return undefined;
288
312
  }
289
- if (volumeCollapseRef.current.t === target) return undefined;
290
313
 
291
314
  if (volumeRaf.current != null) {
292
315
  cancelAnimationFrame(volumeRaf.current);
@@ -299,6 +322,7 @@ export function VroomChart(props: VroomChartProps) {
299
322
  handle.setVolumeCollapse(target, easing);
300
323
  const p = handle.render();
301
324
  if (p) pictureSV.value = p;
325
+ maybeStartAnim();
302
326
  return undefined;
303
327
  }
304
328
 
@@ -315,7 +339,12 @@ export function VroomChart(props: VroomChartProps) {
315
339
  handle.setVolumeCollapse(t, kind);
316
340
  const p = handle.render();
317
341
  if (p) pictureSV.value = p;
318
- volumeRaf.current = prog < 1 ? requestAnimationFrame(step) : null;
342
+ if (prog < 1) {
343
+ volumeRaf.current = requestAnimationFrame(step);
344
+ } else {
345
+ volumeRaf.current = null;
346
+ maybeStartAnim();
347
+ }
319
348
  };
320
349
  volumeRaf.current = requestAnimationFrame(step);
321
350
 
@@ -325,7 +354,102 @@ export function VroomChart(props: VroomChartProps) {
325
354
  volumeRaf.current = null;
326
355
  }
327
356
  };
328
- }, [handle, volume?.enabled, transitionMs, reduceMotion, pictureSV, volumeCollapseRef]);
357
+ }, [
358
+ handle,
359
+ volume?.enabled,
360
+ transitionMs,
361
+ reduceMotion,
362
+ pictureSV,
363
+ volumeCollapseRef,
364
+ maybeStartAnim,
365
+ ]);
366
+
367
+ // Axis-strip collapse. Unlike the volume bars there is no per-element stagger
368
+ // for the core to distribute, so this pre-eases in JS and hands over the eased
369
+ // scalar. Both axes ride one clock so toggling them together stays in step.
370
+ // This one moves the *layout* — the plot reflows into the reclaimed space
371
+ // every frame. Mirrors the web driver in react/src/useChartCore.ts.
372
+ const axisRaf = useRef<number | null>(null);
373
+ const axisHandle = useRef<typeof handle>(null);
374
+ const axisCollapse = useRef<{ y: number; x: number } | null>(null);
375
+ const showYAxis = theme?.showYAxis ?? true;
376
+ const showXAxis = theme?.showXAxis ?? true;
377
+ useEffect(() => {
378
+ if (!handle) return undefined;
379
+ const targetY = showYAxis ? 0 : 1;
380
+ const targetX = showXAxis ? 0 : 1;
381
+
382
+ // Fresh handle (first load / recreate): snap, so a chart that mounts with an
383
+ // axis already hidden doesn't play it out.
384
+ if (axisHandle.current !== handle || axisCollapse.current == null) {
385
+ axisHandle.current = handle;
386
+ axisCollapse.current = { y: targetY, x: targetX };
387
+ handle.setAxisCollapse(targetY, targetX);
388
+ const p = handle.render();
389
+ if (p) pictureSV.value = p;
390
+ maybeStartAnim();
391
+ return undefined;
392
+ }
393
+
394
+ const fromY = axisCollapse.current.y;
395
+ const fromX = axisCollapse.current.x;
396
+ if (fromY === targetY && fromX === targetX) {
397
+ maybeStartAnim();
398
+ return undefined;
399
+ }
400
+
401
+ if (axisRaf.current != null) {
402
+ cancelAnimationFrame(axisRaf.current);
403
+ axisRaf.current = null;
404
+ }
405
+
406
+ const dur = Math.max(0, transitionMs ?? 300);
407
+ if (dur === 0 || reduceMotion) {
408
+ axisCollapse.current = { y: targetY, x: targetX };
409
+ handle.setAxisCollapse(targetY, targetX);
410
+ const p = handle.render();
411
+ if (p) pictureSV.value = p;
412
+ maybeStartAnim();
413
+ return undefined;
414
+ }
415
+
416
+ // From wherever the last frame left off, so toggling mid-flight reverses
417
+ // instead of jumping.
418
+ let startTs: number | null = null;
419
+ const step = (now: number) => {
420
+ if (startTs == null) startTs = now;
421
+ const prog = Math.min(1, (now - startTs) / dur);
422
+ const e = ease(easingRef.current, prog);
423
+ const y = prog < 1 ? fromY + (targetY - fromY) * e : targetY;
424
+ const x = prog < 1 ? fromX + (targetX - fromX) * e : targetX;
425
+ axisCollapse.current = { y, x };
426
+ handle.setAxisCollapse(y, x);
427
+ const p = handle.render();
428
+ if (p) pictureSV.value = p;
429
+ if (prog < 1) {
430
+ axisRaf.current = requestAnimationFrame(step);
431
+ } else {
432
+ axisRaf.current = null;
433
+ maybeStartAnim();
434
+ }
435
+ };
436
+ axisRaf.current = requestAnimationFrame(step);
437
+
438
+ return () => {
439
+ if (axisRaf.current != null) {
440
+ cancelAnimationFrame(axisRaf.current);
441
+ axisRaf.current = null;
442
+ }
443
+ };
444
+ }, [
445
+ handle,
446
+ showYAxis,
447
+ showXAxis,
448
+ transitionMs,
449
+ reduceMotion,
450
+ pictureSV,
451
+ maybeStartAnim,
452
+ ]);
329
453
 
330
454
  // Classifies a touch point into the candle area vs. an axis strip. Axis
331
455
  // strips always own their gesture (scale price/time) and take priority over
package/src/jsi.d.ts CHANGED
@@ -269,6 +269,13 @@ export interface ChartHandle {
269
269
  * match its `enabled`, so it's only needed while animating.
270
270
  */
271
271
  setVolumeCollapse(t: number, easing: number): void;
272
+ /**
273
+ * Collapses the axis strips: 0 leaves a strip at full size, 1 hides it. The
274
+ * plot grows into whatever the strips give up, and their contents fade out
275
+ * over the first half of the collapse so text is never squeezed into a strip
276
+ * too narrow for it. Unlike setVolumeCollapse, both take **eased** progress.
277
+ */
278
+ setAxisCollapse(yT: number, xT: number): void;
272
279
  /**
273
280
  * The continuous data coordinate at pixel (x, y) — not snapped to a candle
274
281
  * slot. Null when there are no candles or the viewport is degenerate. Cheap to
package/src/theme.ts CHANGED
@@ -11,6 +11,7 @@ export const COLOR_KEYS: Partial<Record<keyof VroomTheme, number>> = {
11
11
  grid: 4, // VROOM_COLOR_GRID
12
12
  axisText: 5, // VROOM_COLOR_AXIS_TEXT
13
13
  crosshair: 6, // VROOM_COLOR_CROSSHAIR
14
+ badgeText: 8, // VROOM_COLOR_BADGE_TEXT (7 is a retired slot)
14
15
  crosshairTarget: 9, // VROOM_COLOR_CROSSHAIR_TARGET
15
16
  borderBull: 10, // VROOM_COLOR_BORDER_BULL
16
17
  borderBear: 11, // VROOM_COLOR_BORDER_BEAR
@@ -36,6 +37,10 @@ export const FLOAT_KEYS: Partial<Record<keyof VroomTheme, number>> = {
36
37
  export const FLOAT_LINE_TIP_PULSE = 15; // VROOM_FLOAT_LINE_TIP_PULSE
37
38
 
38
39
  // Maps each boolean VroomTheme field to its VroomFloatKey index (pushed as 0/1).
40
+ //
41
+ // `showXAxis` / `showYAxis` are absent on purpose: they animate, so VroomChart
42
+ // drives them as a collapse scalar through setAxisCollapse. A float slot here
43
+ // would let this sweep snap them behind the animation's back.
39
44
  export const BOOL_KEYS: Partial<Record<keyof VroomTheme, number>> = {
40
45
  wickRoundCap: 9, // VROOM_FLOAT_WICK_ROUND_CAP
41
46
  lineTipDot: 14, // VROOM_FLOAT_LINE_TIP_DOT