@vroomchart/react 0.1.7 → 0.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.
package/dist/index.d.ts CHANGED
@@ -70,14 +70,22 @@ type VroomTheme = {
70
70
  accentBull?: VroomColor;
71
71
  /** Generic down color for the price indicator, volume bars, and MACD histogram. Defaults to red; independent of `bear`. */
72
72
  accentBear?: VroomColor;
73
- /** Up candle body 1px border. Defaults to the bull fill color. */
73
+ /** Up candle body border (1px, drawn *inside* the body so it never changes candle width). Omit or set to the bull fill color to hide it. */
74
74
  borderBull?: VroomColor;
75
- /** Down candle body 1px border. Defaults to the bear fill color. */
75
+ /** Down candle body border (1px, drawn *inside* the body so it never changes candle width). Omit or set to the bear fill color to hide it. */
76
76
  borderBear?: VroomColor;
77
77
  /** Up candle wick color. Defaults to the bull fill color. */
78
78
  wickBull?: VroomColor;
79
79
  /** Down candle wick color. Defaults to the bear fill color. */
80
80
  wickBear?: VroomColor;
81
+ /** Wick stroke width in px (applies to both up and down wicks). Defaults to 1. */
82
+ wickWidth?: number;
83
+ /** Corner radius (px) of candle bodies. Defaults to 0 (square). */
84
+ candleRadius?: number;
85
+ /** Round the wick end caps. Defaults to false. */
86
+ wickRoundCap?: boolean;
87
+ /** Corner radius (px) of the *top* of volume bars. Defaults to 0 (square). */
88
+ volumeRadius?: number;
81
89
  /** Gridlines. */
82
90
  grid?: VroomColor;
83
91
  /** Axis label text (price + time). */
@@ -126,6 +134,37 @@ type Drawing = {
126
134
  /** Stroke width in px. Default 2. */
127
135
  width?: number;
128
136
  };
137
+ /**
138
+ * Storage adapter for **managed** drawing persistence. Provide it via the
139
+ * `drawingStore` prop and the chart owns the drawings array itself — loading and
140
+ * saving through this adapter instead of you wiring the controlled `drawings`
141
+ * prop + `onDrawing*` callbacks.
142
+ *
143
+ * The adapter is an **opaque string key-value store** — the chart serializes
144
+ * drawings into a **versioned envelope** (`{ v, drawings }`) and hands you the
145
+ * string; you just persist bytes. Because the library owns the schema and
146
+ * migrates old payloads on load, adding drawing tools or persisted fields later
147
+ * never changes this interface — your adapter is written once.
148
+ *
149
+ * `marketId` is the chart's `seriesKey`, so drawings are bucketed per market:
150
+ * they persist across timeframe changes (same key) but not across markets. Both
151
+ * methods may be async (localStorage is sync; AsyncStorage / MMKV / a REST
152
+ * backend are async). The chart debounces `save`. Consumers that only handle a
153
+ * single market can ignore `marketId`.
154
+ */
155
+ type DrawingStore = {
156
+ /**
157
+ * Return the raw string previously handed to `save` for `marketId`, or
158
+ * `null`/`undefined`/`''` if nothing is stored. Sync or async.
159
+ */
160
+ load: (marketId: string) => string | null | undefined | Promise<string | null | undefined>;
161
+ /**
162
+ * Persist the opaque `data` string for `marketId`. Sync or async; the chart
163
+ * debounces calls. The string is a versioned envelope owned by the library —
164
+ * store it verbatim, don't parse or reshape it.
165
+ */
166
+ save: (marketId: string, data: string) => void | Promise<void>;
167
+ };
129
168
  /** RSI indicator config. Rendered in a pane below the candles when enabled. */
130
169
  type RSIConfig = {
131
170
  enabled?: boolean;
@@ -251,6 +290,14 @@ type VroomChartCoreProps = {
251
290
  height?: number;
252
291
  /** Time window to render. Omit (or both 0) to show every candle. */
253
292
  visibleRange?: VisibleRange;
293
+ /**
294
+ * Target candle *body* width in logical px for the INITIAL framing. Larger →
295
+ * more zoomed in (fewer candles); smaller → more zoomed out. Only affects
296
+ * first load / reset; an explicit `visibleRange` takes precedence. Omit for
297
+ * the default (~80 candles). Useful to keep candles a consistent size across
298
+ * devices of different widths.
299
+ */
300
+ defaultCandleWidth?: number;
254
301
  theme?: VroomTheme;
255
302
  /** RSI indicator (pane below the candles). Omit/disable to hide it. */
256
303
  rsi?: RSIConfig;
@@ -292,10 +339,30 @@ type VroomChartCoreProps = {
292
339
  * Committed drawings to render, anchored to data so they track the candles on
293
340
  * pan/zoom. This is a controlled prop: append the value the chart hands you in
294
341
  * `onDrawingComplete` to persist it.
342
+ *
343
+ * Ignored when `drawingStore` is set (the chart then owns the array itself).
295
344
  */
296
345
  drawings?: Drawing[];
346
+ /**
347
+ * Opt into **managed** drawing persistence: the chart owns the drawings array
348
+ * internally and loads/saves it through this adapter, keyed by `seriesKey`.
349
+ * When set, `drawings` and the `onDrawing*` callbacks are ignored. Web only.
350
+ * Requires `seriesKey` — without one, drawings work in-session but aren't saved.
351
+ */
352
+ drawingStore?: DrawingStore;
297
353
  /** Fired with the finished drawing when the user completes one. */
298
354
  onDrawingComplete?: (drawing: Drawing) => void;
355
+ /**
356
+ * Fired after the user drags a selected line's endpoint handle. The payload is
357
+ * the same drawing (same `id`) with updated `points`; apply it to your
358
+ * controlled `drawings` state (replace by id). Web only.
359
+ */
360
+ onDrawingChange?: (drawing: Drawing) => void;
361
+ /**
362
+ * Fired when the user deletes the selected line (Backspace/Delete). Remove the
363
+ * drawing with this `id` from your controlled `drawings` state. Web only.
364
+ */
365
+ onDrawingDelete?: (id: string) => void;
299
366
  /**
300
367
  * Fired when the chart wants the mode changed — e.g. it requests `'pan'` after
301
368
  * the user clicks away from a just-drawn line. Since `mode` is controlled, the
@@ -356,4 +423,15 @@ declare function classifyTransition(prev: Candle[] | null, next: Candle[], serie
356
423
  */
357
424
  declare function timeframeWindow(oldWindow: VisibleRange, oldStepMs: number, oldLastMs: number, newStepMs: number, newLastMs: number): VisibleRange;
358
425
 
359
- export { type Candle, type ChartMode, type CrosshairEvent, type DataTransition, type DrawPoint, type DrawTool, type Drawing, type LiquidityBand, type LiquidityConfig, type MACDConfig, type MASource, type MovingAverageOverlay, type RSIConfig, type VWAPConfig, type VisibleRange, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme, classifyTransition, inferStepMs, timeframeWindow };
426
+ /** Current envelope schema version. Bump when the persisted shape changes. */
427
+ declare const DRAWINGS_VERSION = 1;
428
+ /** Wrap the current drawings in a versioned envelope and stringify. */
429
+ declare function serializeDrawings(drawings: Drawing[]): string;
430
+ /**
431
+ * Parse a stored string back into drawings, migrating older envelopes and
432
+ * dropping anything malformed or of an unknown drawing `type`. Never throws —
433
+ * returns `[]` for null/empty/garbage input.
434
+ */
435
+ declare function deserializeDrawings(raw: string | null | undefined): Drawing[];
436
+
437
+ export { type Candle, type ChartMode, type CrosshairEvent, DRAWINGS_VERSION, type DataTransition, type DrawPoint, type DrawTool, type Drawing, type DrawingStore, type LiquidityBand, type LiquidityConfig, type MACDConfig, type MASource, type MovingAverageOverlay, type RSIConfig, type VWAPConfig, type VisibleRange, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme, classifyTransition, deserializeDrawings, inferStepMs, serializeDrawings, timeframeWindow };
package/dist/index.js CHANGED
@@ -121,6 +121,7 @@ function useChartCore(props, loadOpts) {
121
121
  width: widthProp,
122
122
  height: heightProp,
123
123
  visibleRange,
124
+ defaultCandleWidth,
124
125
  theme,
125
126
  rsi,
126
127
  macd,
@@ -209,6 +210,9 @@ function useChartCore(props, loadOpts) {
209
210
  tfArgs = { oldWindow, oldStepMs, oldLastMs: prev.candles[prev.candles.length - 1].timeMs };
210
211
  }
211
212
  }
213
+ if (freshHandle && !explicit && defaultCandleWidth != null && defaultCandleWidth > 0) {
214
+ h.setDefaultCandleWidth(defaultCandleWidth);
215
+ }
212
216
  h.setCandles(packCandles(candles));
213
217
  if (transition === "timeframe") {
214
218
  const newStepMs = inferStepMs(candles);
@@ -252,12 +256,12 @@ function useChartCore(props, loadOpts) {
252
256
  liquidity?.bands?.length ? liquidityToSpec(liquidity) : EMPTY_LIQUIDITY
253
257
  );
254
258
  scheduleRender();
255
- }, [ready, width, height, candles, seriesKey, explicit, startMs, endMs, themeKey, rsiKey, macdKey, maKey, vwapKey, drawingsKey, liquidityKey, scheduleRender]);
259
+ }, [ready, width, height, candles, seriesKey, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, drawingsKey, liquidityKey, scheduleRender]);
256
260
  return { containerRef, canvasRef, handleRef, scheduleRender, size: { width, height } };
257
261
  }
258
262
 
259
263
  // src/useGestures.ts
260
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
264
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
261
265
  var MIN_SPAN = 24;
262
266
  var AXIS_RATIO = 0.5;
263
267
  var LONG_PRESS_MS = 350;
@@ -266,34 +270,171 @@ var WHEEL_K = 15e-4;
266
270
  var SEP_HIT = 4;
267
271
  var DRAW_COLOR = 4280902399;
268
272
  var DRAW_WIDTH = 2;
269
- var DRAW_HIT = 6;
270
273
  function drawingId() {
271
274
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
272
275
  return crypto.randomUUID();
273
276
  }
274
277
  return `draw-${Math.random().toString(36).slice(2)}`;
275
278
  }
276
- function distToSegment(px, py, ax, ay, bx, by) {
277
- const dx = bx - ax;
278
- const dy = by - ay;
279
- const len2 = dx * dx + dy * dy;
280
- let t = len2 > 0 ? ((px - ax) * dx + (py - ay) * dy) / len2 : 0;
281
- t = Math.max(0, Math.min(1, t));
282
- return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));
283
- }
284
279
  function useGestures(containerRef, handleRef, scheduleRender, opts) {
285
280
  const optsRef = useRef2(opts);
286
281
  optsRef.current = opts;
287
282
  const drawAnchorRef = useRef2(null);
288
- const drawAnchorPxRef = useRef2(null);
289
- const drawSelectedPxRef = useRef2(null);
283
+ const selectedIdRef = useRef2(null);
284
+ const grabRef = useRef2(null);
285
+ const dragLineRef = useRef2(null);
286
+ const downHitRef = useRef2(null);
287
+ const lastCoordRef = useRef2(null);
288
+ const selectedTRef = useRef2(0.5);
289
+ const clipboardRef = useRef2(null);
290
+ const applySelection = useCallback2(() => {
291
+ const h = handleRef.current;
292
+ if (!h) return;
293
+ const id = selectedIdRef.current;
294
+ const list = optsRef.current.drawings ?? [];
295
+ let index = -1;
296
+ if (id != null) {
297
+ index = list.findIndex((d) => d.id === id);
298
+ if (index < 0) selectedIdRef.current = null;
299
+ }
300
+ h.setSelectedDrawing(index, index >= 0 ? grabRef.current?.endpoint ?? -1 : -1);
301
+ scheduleRender();
302
+ }, [handleRef, scheduleRender]);
303
+ useEffect2(() => {
304
+ applySelection();
305
+ }, [opts.drawings, applySelection]);
306
+ useEffect2(() => {
307
+ const isEditableTarget = () => {
308
+ const ae = document.activeElement;
309
+ const tag = ae?.tagName;
310
+ return tag === "INPUT" || tag === "TEXTAREA" || !!ae?.isContentEditable;
311
+ };
312
+ const findSelected = () => {
313
+ const id = selectedIdRef.current;
314
+ if (id == null) return null;
315
+ return (optsRef.current.drawings ?? []).find((x) => x.id === id) ?? null;
316
+ };
317
+ const placePaste = (clip) => {
318
+ const h = handleRef.current;
319
+ if (!h) return null;
320
+ const [a, b] = clip.points;
321
+ const t = clip.t;
322
+ const pSel = {
323
+ timeMs: a.timeMs + t * (b.timeMs - a.timeMs),
324
+ price: a.price + t * (b.price - a.price)
325
+ };
326
+ const cur = h.getCrosshairInfo();
327
+ const moved = cur != null && (clip.crosshair == null || cur.timeMs !== clip.crosshair.timeMs || cur.price !== clip.crosshair.price);
328
+ if (moved && cur) {
329
+ const dt = cur.timeMs - pSel.timeMs;
330
+ const dp = cur.price - pSel.price;
331
+ return [
332
+ { timeMs: a.timeMs + dt, price: a.price + dp },
333
+ { timeMs: b.timeMs + dt, price: b.price + dp }
334
+ ];
335
+ }
336
+ const el = containerRef.current;
337
+ const shift = (dp) => [
338
+ { timeMs: a.timeMs, price: a.price + dp },
339
+ { timeMs: b.timeMs, price: b.price + dp }
340
+ ];
341
+ const span = Math.abs(a.price - b.price);
342
+ if (!el) return shift(-(span || 1));
343
+ const rect = el.getBoundingClientRect();
344
+ const m = h.getAxisMetrics();
345
+ const priceBottom = rect.height - m.xAxisHeight - m.indicatorHeight;
346
+ const top = h.coordAt(rect.width / 2, 0);
347
+ const bot = h.coordAt(rect.width / 2, priceBottom);
348
+ if (!top || !bot || priceBottom <= 0 || top.price <= bot.price) return shift(-(span || 1));
349
+ const vTop = top.price;
350
+ const vBot = bot.price;
351
+ const pricePerPx = (vTop - vBot) / priceBottom;
352
+ const GAP_PX = 30;
353
+ const offset = (span / pricePerPx + GAP_PX) * pricePerPx;
354
+ const pMin = Math.min(a.price, b.price);
355
+ const pMax = Math.max(a.price, b.price);
356
+ const upFits = pMax + offset <= vTop;
357
+ const downFits = pMin - offset >= vBot;
358
+ let dir;
359
+ if (upFits && !downFits) dir = 1;
360
+ else if (downFits && !upFits) dir = -1;
361
+ else if (upFits && downFits) {
362
+ dir = vTop - (pMax + offset) >= pMin - offset - vBot ? 1 : -1;
363
+ } else {
364
+ const upVis = Math.min(pMax + offset, vTop) - Math.max(pMin + offset, vBot);
365
+ const dnVis = Math.min(pMax - offset, vTop) - Math.max(pMin - offset, vBot);
366
+ dir = upVis >= dnVis ? 1 : -1;
367
+ }
368
+ return shift(dir * offset);
369
+ };
370
+ const onKeyDown = (e) => {
371
+ if (isEditableTarget()) return;
372
+ if (drawAnchorRef.current && (e.key === "Escape" || e.key === "Backspace" || e.key === "Delete")) {
373
+ e.preventDefault();
374
+ drawAnchorRef.current = null;
375
+ const h = handleRef.current;
376
+ if (h) {
377
+ h.clearDraft();
378
+ scheduleRender();
379
+ }
380
+ return;
381
+ }
382
+ if (e.key === "Backspace" || e.key === "Delete") {
383
+ const id = selectedIdRef.current;
384
+ if (id == null) return;
385
+ e.preventDefault();
386
+ optsRef.current.onDrawingDelete?.(id);
387
+ selectedIdRef.current = null;
388
+ grabRef.current = null;
389
+ dragLineRef.current = null;
390
+ const h = handleRef.current;
391
+ if (h) {
392
+ h.setSelectedDrawing(-1, -1);
393
+ scheduleRender();
394
+ }
395
+ return;
396
+ }
397
+ if (!(e.metaKey || e.ctrlKey)) return;
398
+ const key = e.key.toLowerCase();
399
+ if (key === "c") {
400
+ if (window.getSelection()?.toString()) return;
401
+ const d = findSelected();
402
+ if (!d) return;
403
+ e.preventDefault();
404
+ const info = handleRef.current?.getCrosshairInfo() ?? null;
405
+ clipboardRef.current = {
406
+ points: [d.points[0], d.points[1]],
407
+ color: d.color,
408
+ width: d.width,
409
+ t: selectedTRef.current,
410
+ crosshair: info ? { timeMs: info.timeMs, price: info.price } : null
411
+ };
412
+ } else if (key === "v") {
413
+ const clip = clipboardRef.current;
414
+ if (!clip) return;
415
+ const pts = placePaste(clip);
416
+ if (!pts) return;
417
+ e.preventDefault();
418
+ const id = drawingId();
419
+ optsRef.current.onDrawingComplete?.({
420
+ id,
421
+ type: "line",
422
+ points: pts,
423
+ ...clip.color != null ? { color: clip.color } : {},
424
+ ...clip.width != null ? { width: clip.width } : {}
425
+ });
426
+ selectedIdRef.current = id;
427
+ selectedTRef.current = clip.t;
428
+ }
429
+ };
430
+ window.addEventListener("keydown", onKeyDown);
431
+ return () => window.removeEventListener("keydown", onKeyDown);
432
+ }, [containerRef, handleRef, scheduleRender]);
290
433
  const localCrosshairActiveRef = useRef2(false);
291
434
  useEffect2(() => {
292
435
  const active = opts.mode === "draw" && opts.tool === "line";
293
436
  if (active) return;
294
437
  drawAnchorRef.current = null;
295
- drawAnchorPxRef.current = null;
296
- drawSelectedPxRef.current = null;
297
438
  const h = handleRef.current;
298
439
  if (h) {
299
440
  h.clearDraft();
@@ -321,11 +462,31 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
321
462
  let downX = 0;
322
463
  let downY = 0;
323
464
  let moved = false;
465
+ let shiftHeld = false;
466
+ let lastX = 0;
467
+ let lastY = 0;
324
468
  const pinch = { spanX: 1, spanY: 1, ratioX: 1, ratioY: 1, enableX: false, enableY: false, active: false };
325
469
  const rel = (e) => {
326
470
  const r = el.getBoundingClientRect();
327
471
  return { x: e.clientX - r.left, y: e.clientY - r.top };
328
472
  };
473
+ const snapToLine = (fixed, x, y) => {
474
+ const h = handleRef.current;
475
+ if (!h) return null;
476
+ const free = h.coordAt(x, y);
477
+ if (!shiftHeld || !free) return free;
478
+ const f = h.project(fixed.timeMs, fixed.price);
479
+ if (!f) return free;
480
+ const dx = x - f.x;
481
+ const dy = y - f.y;
482
+ if (dx === 0 && dy === 0) return free;
483
+ const step = Math.PI / 4;
484
+ const ang = Math.round(Math.atan2(dy, dx) / step) * step;
485
+ const ux = Math.cos(ang);
486
+ const uy = Math.sin(ang);
487
+ const len = dx * ux + dy * uy;
488
+ return h.coordAt(f.x + len * ux, f.y + len * uy) ?? free;
489
+ };
329
490
  const regionAt = (x, y) => {
330
491
  const h = handleRef.current;
331
492
  const r = el.getBoundingClientRect();
@@ -396,40 +557,27 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
396
557
  const updateGuideline = (x, y) => {
397
558
  const h = handleRef.current;
398
559
  if (!h) return;
399
- if (!drawAnchorRef.current || drawSelectedPxRef.current) return;
400
- const c = h.coordAt(x, y);
401
- if (!c) return;
402
560
  const a = drawAnchorRef.current;
561
+ if (!a) return;
562
+ const c = snapToLine(a, x, y);
563
+ if (!c) return;
403
564
  h.setDraft(a.timeMs, a.price, true, c.timeMs, c.price, true, DRAW_COLOR, DRAW_WIDTH);
404
565
  scheduleRender();
405
566
  };
406
567
  const handleDrawClick = (x, y) => {
407
568
  const h = handleRef.current;
408
569
  if (!h) return;
409
- const o = optsRef.current;
410
- const sel = drawSelectedPxRef.current;
411
- if (sel) {
412
- if (distToSegment(x, y, sel.ax, sel.ay, sel.bx, sel.by) <= DRAW_HIT) return;
413
- drawAnchorRef.current = null;
414
- drawAnchorPxRef.current = null;
415
- drawSelectedPxRef.current = null;
416
- h.clearDraft();
417
- scheduleRender();
418
- el.style.cursor = "";
419
- o.onRequestMode?.("pan");
420
- return;
421
- }
422
- const coord = h.coordAt(x, y);
423
- if (!coord) return;
424
570
  if (!drawAnchorRef.current) {
571
+ const coord = h.coordAt(x, y);
572
+ if (!coord) return;
425
573
  drawAnchorRef.current = coord;
426
- drawAnchorPxRef.current = { x, y };
427
574
  h.setDraft(coord.timeMs, coord.price, false, 0, 0, true, DRAW_COLOR, DRAW_WIDTH);
428
575
  scheduleRender();
429
576
  } else {
430
577
  const a = drawAnchorRef.current;
431
- const apx = drawAnchorPxRef.current;
432
- o.onDrawingComplete?.({
578
+ const coord = snapToLine(a, x, y);
579
+ if (!coord) return;
580
+ optsRef.current.onDrawingComplete?.({
433
581
  id: drawingId(),
434
582
  type: "line",
435
583
  points: [
@@ -437,16 +585,29 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
437
585
  { timeMs: coord.timeMs, price: coord.price }
438
586
  ]
439
587
  });
440
- drawSelectedPxRef.current = { ax: apx.x, ay: apx.y, bx: x, by: y };
441
- h.setDraft(a.timeMs, a.price, true, coord.timeMs, coord.price, false, DRAW_COLOR, DRAW_WIDTH);
588
+ drawAnchorRef.current = null;
589
+ h.clearDraft();
442
590
  scheduleRender();
443
591
  }
444
592
  };
593
+ const moveGrab = (x, y) => {
594
+ const h = handleRef.current;
595
+ const g = grabRef.current;
596
+ if (!h || !g) return;
597
+ const c = snapToLine(g.fixed, x, y);
598
+ if (!c) return;
599
+ lastCoordRef.current = c;
600
+ h.moveDrawingEndpoint(g.index, g.endpoint, c.timeMs, c.price);
601
+ scheduleRender();
602
+ };
445
603
  const onPointerDown = (e) => {
446
604
  const h = handleRef.current;
447
605
  if (!h) return;
448
606
  if (e.pointerType === "mouse" && e.button !== 0) return;
449
607
  const { x, y } = rel(e);
608
+ shiftHeld = e.shiftKey;
609
+ lastX = x;
610
+ lastY = y;
450
611
  el.setPointerCapture(e.pointerId);
451
612
  pointers.set(e.pointerId, { x, y });
452
613
  if (pointers.size === 2) {
@@ -468,6 +629,46 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
468
629
  downY = y;
469
630
  moved = false;
470
631
  panMode = regionAt(x, y);
632
+ downHitRef.current = null;
633
+ if (!drawActive() && panMode === "chart") {
634
+ const hit = h.hitTestDrawing(x, y);
635
+ if (hit && (hit.part === 0 || hit.part === 1)) {
636
+ const gd = (optsRef.current.drawings ?? [])[hit.index];
637
+ if (gd) {
638
+ grabRef.current = {
639
+ index: hit.index,
640
+ endpoint: hit.part,
641
+ fixed: gd.points[hit.part === 0 ? 1 : 0]
642
+ // the other end stays put
643
+ };
644
+ lastCoordRef.current = null;
645
+ applySelection();
646
+ return;
647
+ }
648
+ }
649
+ if (hit && hit.part === 2) {
650
+ const list = optsRef.current.drawings ?? [];
651
+ const selIdx = selectedIdRef.current ? list.findIndex((d) => d.id === selectedIdRef.current) : -1;
652
+ if (hit.index === selIdx && selIdx >= 0) {
653
+ const start = h.coordAt(x, y);
654
+ const d = list[selIdx];
655
+ if (start && d) {
656
+ dragLineRef.current = {
657
+ index: selIdx,
658
+ startTime: start.timeMs,
659
+ startPrice: start.price,
660
+ a0: d.points[0],
661
+ b0: d.points[1],
662
+ lastA: d.points[0],
663
+ lastB: d.points[1]
664
+ };
665
+ el.style.cursor = "move";
666
+ return;
667
+ }
668
+ }
669
+ }
670
+ downHitRef.current = hit;
671
+ }
471
672
  if (e.pointerType !== "mouse" && panMode === "chart" && !drawActive()) {
472
673
  longPressTimer = setTimeout(() => {
473
674
  longPressTimer = null;
@@ -479,6 +680,9 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
479
680
  const h = handleRef.current;
480
681
  if (!h) return;
481
682
  const { x, y } = rel(e);
683
+ shiftHeld = e.shiftKey;
684
+ lastX = x;
685
+ lastY = y;
482
686
  if (!pointers.has(e.pointerId)) {
483
687
  if (e.pointerType === "mouse" && pointers.size === 0) {
484
688
  if (drawActive()) {
@@ -500,6 +704,25 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
500
704
  const dx = x - prev.x;
501
705
  const dy = y - prev.y;
502
706
  pointers.set(e.pointerId, { x, y });
707
+ if (grabRef.current) {
708
+ moveGrab(x, y);
709
+ return;
710
+ }
711
+ if (dragLineRef.current) {
712
+ const g = dragLineRef.current;
713
+ const c = h.coordAt(x, y);
714
+ if (c) {
715
+ const dT = c.timeMs - g.startTime;
716
+ const dP = c.price - g.startPrice;
717
+ g.lastA = { timeMs: g.a0.timeMs + dT, price: g.a0.price + dP };
718
+ g.lastB = { timeMs: g.b0.timeMs + dT, price: g.b0.price + dP };
719
+ h.moveDrawingEndpoint(g.index, 0, g.lastA.timeMs, g.lastA.price);
720
+ h.moveDrawingEndpoint(g.index, 1, g.lastB.timeMs, g.lastB.price);
721
+ if (!moved && Math.hypot(x - downX, y - downY) > MOVE_THRESH) moved = true;
722
+ scheduleRender();
723
+ }
724
+ return;
725
+ }
503
726
  if (pointers.size >= 2 && pinch.active && !drawActive()) {
504
727
  const pts = [...pointers.values()];
505
728
  const focalX = (pts[0].x + pts[1].x) / 2;
@@ -548,15 +771,57 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
548
771
  scheduleRender();
549
772
  };
550
773
  const endPointer = (e) => {
774
+ shiftHeld = e.shiftKey;
551
775
  const had = pointers.delete(e.pointerId);
552
776
  clearLongPress();
553
777
  if (pointers.size < 2) pinch.active = false;
554
778
  if (!had) return;
779
+ if (grabRef.current && pointers.size === 0) {
780
+ const g = grabRef.current;
781
+ grabRef.current = null;
782
+ const list = optsRef.current.drawings ?? [];
783
+ const d = list[g.index];
784
+ const c = lastCoordRef.current;
785
+ if (d && c) {
786
+ const pts = [d.points[0], d.points[1]];
787
+ pts[g.endpoint] = { timeMs: c.timeMs, price: c.price };
788
+ optsRef.current.onDrawingChange?.({ ...d, points: pts });
789
+ }
790
+ applySelection();
791
+ el.style.cursor = "";
792
+ return;
793
+ }
794
+ if (dragLineRef.current && pointers.size === 0) {
795
+ const g = dragLineRef.current;
796
+ dragLineRef.current = null;
797
+ if (moved) {
798
+ const list = optsRef.current.drawings ?? [];
799
+ const d = list[g.index];
800
+ if (d) optsRef.current.onDrawingChange?.({ ...d, points: [g.lastA, g.lastB] });
801
+ }
802
+ applySelection();
803
+ el.style.cursor = "";
804
+ return;
805
+ }
555
806
  if (drawActive() && pointers.size === 0) {
556
807
  if (!moved && panMode === "chart") handleDrawClick(downX, downY);
557
808
  else el.style.cursor = "crosshair";
558
809
  return;
559
810
  }
811
+ if (pointers.size === 0 && !moved && panMode === "chart" && crosshairSource !== "press") {
812
+ const hit = downHitRef.current;
813
+ const list = optsRef.current.drawings ?? [];
814
+ if (hit && hit.part === 2 && list[hit.index]) {
815
+ selectedTRef.current = hit.t;
816
+ if (selectedIdRef.current !== list[hit.index].id) {
817
+ selectedIdRef.current = list[hit.index].id;
818
+ applySelection();
819
+ }
820
+ } else if (!hit && selectedIdRef.current != null) {
821
+ selectedIdRef.current = null;
822
+ applySelection();
823
+ }
824
+ }
560
825
  if (crosshairSource === "press" && pointers.size === 0) {
561
826
  hideCrosshair();
562
827
  } else if (moved && (panMode === "chart" || panMode === "indicator")) {
@@ -587,12 +852,22 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
587
852
  }
588
853
  scheduleRender();
589
854
  };
855
+ const onShiftKey = (e) => {
856
+ if (e.key !== "Shift") return;
857
+ const held = e.type === "keydown";
858
+ if (held === shiftHeld) return;
859
+ shiftHeld = held;
860
+ if (drawAnchorRef.current) updateGuideline(lastX, lastY);
861
+ else if (grabRef.current) moveGrab(lastX, lastY);
862
+ };
590
863
  el.addEventListener("pointerdown", onPointerDown);
591
864
  el.addEventListener("pointermove", onPointerMove);
592
865
  el.addEventListener("pointerup", endPointer);
593
866
  el.addEventListener("pointercancel", endPointer);
594
867
  el.addEventListener("pointerleave", onPointerLeave);
595
868
  el.addEventListener("wheel", onWheel, { passive: false });
869
+ window.addEventListener("keydown", onShiftKey);
870
+ window.addEventListener("keyup", onShiftKey);
596
871
  return () => {
597
872
  clearLongPress();
598
873
  el.removeEventListener("pointerdown", onPointerDown);
@@ -601,10 +876,171 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
601
876
  el.removeEventListener("pointercancel", endPointer);
602
877
  el.removeEventListener("pointerleave", onPointerLeave);
603
878
  el.removeEventListener("wheel", onWheel);
879
+ window.removeEventListener("keydown", onShiftKey);
880
+ window.removeEventListener("keyup", onShiftKey);
604
881
  };
605
882
  }, [containerRef, handleRef, scheduleRender]);
606
883
  }
607
884
 
885
+ // src/useManagedDrawings.ts
886
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
887
+
888
+ // src/drawingStorage.ts
889
+ var DRAWINGS_VERSION = 1;
890
+ var KNOWN_TYPES = /* @__PURE__ */ new Set(["line"]);
891
+ var MIGRATIONS = {};
892
+ function serializeDrawings(drawings) {
893
+ const env = { v: DRAWINGS_VERSION, drawings };
894
+ return JSON.stringify(env);
895
+ }
896
+ function deserializeDrawings(raw) {
897
+ if (raw == null || raw === "") return [];
898
+ let parsed;
899
+ try {
900
+ parsed = JSON.parse(raw);
901
+ } catch {
902
+ return [];
903
+ }
904
+ let env;
905
+ if (Array.isArray(parsed)) {
906
+ env = { v: 0, drawings: parsed };
907
+ } else if (parsed != null && typeof parsed === "object") {
908
+ const obj = parsed;
909
+ env = {
910
+ v: typeof obj.v === "number" ? obj.v : 0,
911
+ drawings: Array.isArray(obj.drawings) ? obj.drawings : []
912
+ };
913
+ } else {
914
+ return [];
915
+ }
916
+ let guard = 0;
917
+ while (env.v < DRAWINGS_VERSION && guard++ < 100) {
918
+ const up = MIGRATIONS[env.v];
919
+ if (!up) break;
920
+ env = up(env);
921
+ }
922
+ return (Array.isArray(env.drawings) ? env.drawings : []).filter(
923
+ (d) => d != null && typeof d === "object" && KNOWN_TYPES.has(d.type)
924
+ );
925
+ }
926
+
927
+ // src/useManagedDrawings.ts
928
+ var SAVE_DEBOUNCE_MS = 400;
929
+ function useManagedDrawings(seriesKey, store) {
930
+ const [drawings, setDrawings] = useState2([]);
931
+ const drawingsRef = useRef3([]);
932
+ const storeRef = useRef3(store);
933
+ storeRef.current = store;
934
+ const loadedKeyRef = useRef3(void 0);
935
+ const loadTokenRef = useRef3(0);
936
+ const saveTimerRef = useRef3(null);
937
+ const warnedRef = useRef3(false);
938
+ const setBoth = (list) => {
939
+ drawingsRef.current = list;
940
+ setDrawings(list);
941
+ };
942
+ const flushSave = useCallback3((key, list) => {
943
+ if (saveTimerRef.current != null) {
944
+ clearTimeout(saveTimerRef.current);
945
+ saveTimerRef.current = null;
946
+ }
947
+ const s = storeRef.current;
948
+ if (!s || key == null) return;
949
+ try {
950
+ const r = s.save(key, serializeDrawings(list));
951
+ if (r && typeof r.catch === "function") {
952
+ r.catch(() => {
953
+ });
954
+ }
955
+ } catch {
956
+ }
957
+ }, []);
958
+ const scheduleSave = useCallback3(() => {
959
+ const key = seriesKey;
960
+ if (!storeRef.current || key == null) return;
961
+ if (saveTimerRef.current != null) clearTimeout(saveTimerRef.current);
962
+ saveTimerRef.current = setTimeout(() => {
963
+ saveTimerRef.current = null;
964
+ flushSave(key, drawingsRef.current);
965
+ }, SAVE_DEBOUNCE_MS);
966
+ }, [seriesKey, flushSave]);
967
+ useEffect3(() => {
968
+ const s = storeRef.current;
969
+ if (!s) return;
970
+ if (seriesKey == null) {
971
+ if (!warnedRef.current && typeof console !== "undefined") {
972
+ console.warn(
973
+ "[vroom] drawingStore is set but seriesKey is missing \u2014 drawings will render but not persist."
974
+ );
975
+ warnedRef.current = true;
976
+ }
977
+ setBoth([]);
978
+ loadedKeyRef.current = void 0;
979
+ return;
980
+ }
981
+ const token = ++loadTokenRef.current;
982
+ const result = s.load(seriesKey);
983
+ if (result && typeof result.then === "function") {
984
+ setBoth([]);
985
+ loadedKeyRef.current = void 0;
986
+ result.then((raw) => {
987
+ if (token !== loadTokenRef.current) return;
988
+ loadedKeyRef.current = seriesKey;
989
+ setBoth(deserializeDrawings(raw));
990
+ }).catch(() => {
991
+ if (token !== loadTokenRef.current) return;
992
+ loadedKeyRef.current = seriesKey;
993
+ setBoth([]);
994
+ });
995
+ } else {
996
+ loadedKeyRef.current = seriesKey;
997
+ setBoth(deserializeDrawings(result));
998
+ }
999
+ return () => {
1000
+ if (loadedKeyRef.current === seriesKey) {
1001
+ flushSave(seriesKey, drawingsRef.current);
1002
+ } else if (saveTimerRef.current != null) {
1003
+ clearTimeout(saveTimerRef.current);
1004
+ saveTimerRef.current = null;
1005
+ }
1006
+ };
1007
+ }, [seriesKey, flushSave]);
1008
+ const onDrawingComplete = useCallback3(
1009
+ (d) => {
1010
+ setDrawings((p) => {
1011
+ const next = [...p, d];
1012
+ drawingsRef.current = next;
1013
+ return next;
1014
+ });
1015
+ scheduleSave();
1016
+ },
1017
+ [scheduleSave]
1018
+ );
1019
+ const onDrawingChange = useCallback3(
1020
+ (d) => {
1021
+ setDrawings((p) => {
1022
+ const next = p.map((x) => x.id === d.id ? d : x);
1023
+ drawingsRef.current = next;
1024
+ return next;
1025
+ });
1026
+ scheduleSave();
1027
+ },
1028
+ [scheduleSave]
1029
+ );
1030
+ const onDrawingDelete = useCallback3(
1031
+ (id) => {
1032
+ setDrawings((p) => {
1033
+ const next = p.filter((x) => x.id !== id);
1034
+ drawingsRef.current = next;
1035
+ return next;
1036
+ });
1037
+ scheduleSave();
1038
+ },
1039
+ [scheduleSave]
1040
+ );
1041
+ return { drawings, onDrawingComplete, onDrawingChange, onDrawingDelete };
1042
+ }
1043
+
608
1044
  // src/VroomChart.tsx
609
1045
  import { jsx } from "react/jsx-runtime";
610
1046
  var FILL = { position: "relative", width: "100%", height: "100%" };
@@ -624,13 +1060,21 @@ function VroomChart(props) {
624
1060
  crosshairOverride,
625
1061
  mode,
626
1062
  tool,
1063
+ drawings,
627
1064
  onCrosshair,
628
1065
  onViewportChange,
629
1066
  onDrawingComplete,
630
- onModeChange
1067
+ onDrawingChange,
1068
+ onDrawingDelete,
1069
+ onModeChange,
1070
+ drawingStore,
1071
+ seriesKey
631
1072
  } = props;
1073
+ const managed = useManagedDrawings(seriesKey, drawingStore);
1074
+ const stored = drawingStore != null;
1075
+ const effectiveDrawings = stored ? managed.drawings : drawings;
632
1076
  const { containerRef, canvasRef, handleRef, scheduleRender } = useChartCore(
633
- props,
1077
+ stored ? { ...props, drawings: effectiveDrawings } : props,
634
1078
  wasm ? { wasm } : void 0
635
1079
  );
636
1080
  useGestures(containerRef, handleRef, scheduleRender, {
@@ -638,9 +1082,12 @@ function VroomChart(props) {
638
1082
  crosshairOverride,
639
1083
  mode,
640
1084
  tool,
1085
+ drawings: effectiveDrawings,
641
1086
  onCrosshair,
642
1087
  onViewportChange,
643
- onDrawingComplete,
1088
+ onDrawingComplete: stored ? managed.onDrawingComplete : onDrawingComplete,
1089
+ onDrawingChange: stored ? managed.onDrawingChange : onDrawingChange,
1090
+ onDrawingDelete: stored ? managed.onDrawingDelete : onDrawingDelete,
644
1091
  onRequestMode: onModeChange
645
1092
  });
646
1093
  const rootStyle = {
@@ -652,9 +1099,12 @@ function VroomChart(props) {
652
1099
  return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style: rootStyle, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, style: CANVAS_STYLE }) });
653
1100
  }
654
1101
  export {
1102
+ DRAWINGS_VERSION,
655
1103
  VroomChart,
656
1104
  classifyTransition,
1105
+ deserializeDrawings,
657
1106
  inferStepMs,
1107
+ serializeDrawings,
658
1108
  timeframeWindow
659
1109
  };
660
1110
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useChartCore.ts","../src/dataTransitions.ts","../src/useGestures.ts","../src/VroomChart.tsx"],"sourcesContent":["// Owns one VroomChartHandle bound to a <canvas>: loads the core, measures the\n// container (ResizeObserver), pushes data/size/theme/indicators on change, and\n// drives a rAF-batched present() loop. The web analogue of\n// packages/react-native/src/useChartCore.ts — but it paints the canvas via\n// handle.present() instead of producing an SkPicture.\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n loadVroom,\n packCandles,\n applyTheme,\n parseColor,\n type LoadVroomOptions,\n type OverlaySpec,\n type DrawingSpec,\n type LiquiditySpec,\n type VroomChartHandle,\n} from '@vroomchart/core-wasm';\nimport type { VroomChartCoreProps } from '@vroomchart/types';\nimport {\n classifyTransition,\n inferStepMs,\n timeframeWindow,\n type DataTransition,\n} from './dataTransitions';\n\n// Mirrors vroom::ma::Source order (packages/core/src/ma.h).\nconst MA_SOURCES = ['close', 'open', 'high', 'low', 'hl2', 'hlc3', 'ohlc4'] as const;\n\nfunction overlayToNumeric(\n o: NonNullable<VroomChartCoreProps['movingAverages']>[number],\n): OverlaySpec {\n const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;\n return {\n kind: o.kind === 'ema' ? 1 : 0,\n period: o.length,\n source: srcIdx < 0 ? 0 : srcIdx,\n color: (o.color != null ? parseColor(o.color) : null) ?? 0xff2962ff,\n width: o.width ?? 1.5,\n };\n}\n\nfunction drawingToSpec(\n d: NonNullable<VroomChartCoreProps['drawings']>[number],\n): DrawingSpec {\n return {\n aTime: d.points[0].timeMs,\n aPrice: d.points[0].price,\n bTime: d.points[1].timeMs,\n bPrice: d.points[1].price,\n color: (d.color != null ? parseColor(d.color) : null) ?? 0xff2962ff,\n width: d.width ?? 2,\n };\n}\n\n// Default buy/sell colors (teal-green / red), matching the accent palette.\nconst DEFAULT_BUY_COLOR = 0xff26a69a;\nconst DEFAULT_SELL_COLOR = 0xffef5350;\n\nfunction liquidityToSpec(\n cfg: NonNullable<VroomChartCoreProps['liquidity']>,\n): LiquiditySpec {\n return {\n bands: cfg.bands.map((b) => ({\n minPrice: b.minPrice,\n maxPrice: b.maxPrice,\n side: b.side === 'sell' ? 1 : 0,\n volume: b.volume,\n })),\n buyColor: (cfg.buyColor != null ? parseColor(cfg.buyColor) : null) ?? DEFAULT_BUY_COLOR,\n sellColor:\n (cfg.sellColor != null ? parseColor(cfg.sellColor) : null) ?? DEFAULT_SELL_COLOR,\n maxVolume: cfg.maxVolume ?? 0,\n minOpacity: cfg.minOpacity ?? 0.05,\n maxOpacity: cfg.maxOpacity ?? 0.8,\n widthPx: cfg.widthPx ?? 300,\n widthFrac: cfg.widthFrac ?? 0.25,\n };\n}\n\n// Cleared overlay: an empty band set (style values are irrelevant when there are\n// no bands, but the spec shape requires them).\nconst EMPTY_LIQUIDITY: LiquiditySpec = {\n bands: [],\n buyColor: DEFAULT_BUY_COLOR,\n sellColor: DEFAULT_SELL_COLOR,\n maxVolume: 0,\n minOpacity: 0.05,\n maxOpacity: 0.8,\n widthPx: 300,\n widthFrac: 0.25,\n};\n\nexport type UseChartCore = {\n containerRef: React.RefObject<HTMLDivElement | null>;\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n handleRef: React.RefObject<VroomChartHandle | null>;\n /** Schedules a rAF-batched repaint (and keeps ticking while animating). */\n scheduleRender: () => void;\n /** Measured CSS size of the container (0 until first layout). */\n size: { width: number; height: number };\n};\n\nexport function useChartCore(\n props: VroomChartCoreProps,\n loadOpts?: LoadVroomOptions,\n): UseChartCore {\n const {\n candles,\n seriesKey,\n width: widthProp,\n height: heightProp,\n visibleRange,\n theme,\n rsi,\n macd,\n movingAverages,\n vwap,\n drawings,\n liquidity,\n } = props;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const handleRef = useRef<VroomChartHandle | null>(null);\n // What the core currently holds, for classifying the next data change.\n // Keyed by handle so a recreated core is treated as a fresh initial load.\n const prevDataRef = useRef<{\n handle: VroomChartHandle;\n candles: VroomChartCoreProps['candles'];\n seriesKey?: string;\n } | null>(null);\n const rafRef = useRef<number | null>(null);\n const [ready, setReady] = useState(false);\n const [measured, setMeasured] = useState({ width: 0, height: 0 });\n\n // Captured at mount: which core to load (stub vs Skia-WASM). Changing it after\n // mount has no effect — the core is created once and shared process-wide.\n const loadOptsRef = useRef(loadOpts);\n loadOptsRef.current = loadOpts;\n\n const width = widthProp ?? measured.width;\n const height = heightProp ?? measured.height;\n\n const scheduleRender = useCallback(() => {\n if (rafRef.current != null) return;\n rafRef.current = requestAnimationFrame(() => {\n rafRef.current = null;\n const h = handleRef.current;\n if (!h) return;\n h.present();\n if (h.isAnimating()) scheduleRender();\n });\n }, []);\n\n // Create the handle once the canvas exists; destroy on unmount.\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n let disposed = false;\n loadVroom(loadOptsRef.current).then((mod) => {\n if (disposed) return;\n handleRef.current = mod.create(canvas);\n setReady(true);\n });\n return () => {\n disposed = true;\n if (rafRef.current != null) cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n handleRef.current?.destroy();\n handleRef.current = null;\n setReady(false);\n };\n }, []);\n\n // Measure the container (unless both dims are pinned via props).\n useEffect(() => {\n const el = containerRef.current;\n if (!el || (widthProp != null && heightProp != null)) return;\n const ro = new ResizeObserver((entries) => {\n const r = entries[0]?.contentRect;\n if (!r) return;\n const w = Math.round(r.width);\n const h = Math.round(r.height);\n setMeasured((prev) => (prev.width === w && prev.height === h ? prev : { width: w, height: h }));\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, [widthProp, heightProp]);\n\n // Stable deps so inline object/array literals don't re-run every render.\n const themeKey = theme ? JSON.stringify(theme) : '';\n const rsiKey = rsi ? JSON.stringify(rsi) : '';\n const macdKey = macd ? JSON.stringify(macd) : '';\n const maKey = movingAverages ? JSON.stringify(movingAverages) : '';\n const vwapKey = vwap ? JSON.stringify(vwap) : '';\n const drawingsKey = drawings ? JSON.stringify(drawings) : '';\n const liquidityKey = liquidity ? JSON.stringify(liquidity) : '';\n const explicit = visibleRange != null;\n const startMs = visibleRange?.startMs ?? 0;\n const endMs = visibleRange?.endMs ?? 0;\n\n // Push everything into the core whenever data/size/config changes, then paint.\n useEffect(() => {\n const h = handleRef.current;\n if (!h || width <= 0 || height <= 0) return;\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n h.setSize(width, height, dpr);\n if (candles.length > 0) {\n const prev = prevDataRef.current;\n const freshHandle = prev == null || prev.handle !== h;\n if (freshHandle || prev.candles !== candles || prev.seriesKey !== seriesKey) {\n // A fresh core frames itself (its window starts at 0/0); an explicit\n // visibleRange prop overrides any auto behavior, so treat the change\n // like a stream and let the range application below win.\n const transition: DataTransition = freshHandle\n ? 'initial'\n : explicit\n ? 'stream'\n : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);\n\n // Capture the outgoing view before setCandles re-infers the candle\n // period from the new data.\n let tfArgs: { oldWindow: { startMs: number; endMs: number }; oldStepMs: number; oldLastMs: number } | null =\n null;\n if (transition === 'timeframe' && prev != null) {\n const oldWindow = h.getVisibleRange();\n const oldStepMs = inferStepMs(prev.candles);\n if (oldWindow.endMs > oldWindow.startMs && oldStepMs != null) {\n tfArgs = { oldWindow, oldStepMs, oldLastMs: prev.candles[prev.candles.length - 1].timeMs };\n }\n }\n\n h.setCandles(packCandles(candles));\n\n if (transition === 'timeframe') {\n const newStepMs = inferStepMs(candles);\n if (tfArgs && newStepMs != null) {\n const w = timeframeWindow(\n tfArgs.oldWindow,\n tfArgs.oldStepMs,\n tfArgs.oldLastMs,\n newStepMs,\n candles[candles.length - 1].timeMs,\n );\n h.setVisibleRange(w.startMs, w.endMs);\n }\n h.resetPriceScale();\n } else if (transition === 'reset') {\n h.resetView();\n }\n prevDataRef.current = { handle: h, candles, seriesKey };\n }\n }\n if (explicit) h.setVisibleRange(startMs, endMs);\n if (theme) applyTheme(h, theme);\n h.setRSI(\n rsi?.enabled ?? false,\n rsi?.period ?? 14,\n rsi?.upperBand ?? 70,\n rsi?.lowerBand ?? 30,\n rsi?.maEnabled ?? true,\n rsi?.maPeriod ?? 14,\n );\n h.setMACD(macd?.enabled ?? false, macd?.fast ?? 12, macd?.slow ?? 26, macd?.signal ?? 9);\n h.setOverlays((movingAverages ?? []).map(overlayToNumeric));\n h.setVWAP(\n vwap?.enabled ?? false,\n vwap?.resetMinutes ?? 0,\n (vwap?.color != null ? parseColor(vwap.color) : null) ?? 0xff00bcd4,\n vwap?.width ?? 1.5,\n );\n h.setDrawings((drawings ?? []).map(drawingToSpec));\n h.setLiquidity(\n liquidity?.bands?.length ? liquidityToSpec(liquidity) : EMPTY_LIQUIDITY,\n );\n scheduleRender();\n // theme/rsi/macd/movingAverages/vwap/drawings/liquidity tracked via *Key deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ready, width, height, candles, seriesKey, explicit, startMs, endMs, themeKey, rsiKey, macdKey, maKey, vwapKey, drawingsKey, liquidityKey, scheduleRender]);\n\n return { containerRef, canvasRef, handleRef, scheduleRender, size: { width, height } };\n}\n","// Classifies how a new `candles` prop relates to the previous one so the chart\n// can react appropriately: leave the viewport alone for streaming updates,\n// re-anchor the time window for a timeframe switch, or fully reset the view\n// for a different asset. Pure functions, no React — see useChartCore for the\n// orchestration.\n\nimport type { Candle, VisibleRange } from '@vroomchart/types';\n\nexport type DataTransition = 'initial' | 'stream' | 'timeframe' | 'reset';\n\n// A step change below this ratio is treated as the same timeframe. Real steps\n// are exact integer ms; the tolerance only absorbs rounding/DST quirks (the\n// smallest real timeframe jump, 1m -> 2m, is 100% apart).\nconst STEP_TOLERANCE = 0.01;\n\n// Same-asset check for a timeframe switch: both series end \"now\", so their\n// last closes must be close. No asset moves 25% between two consecutive prop\n// pushes; distinct assets within 25% of each other are what `seriesKey` is for.\nconst MAX_SAME_ASSET_CLOSE_RATIO = 1.25;\n\n// A coarser bucketing can shift the final bar's open by up to one coarse bar;\n// allow that plus an in-flight bar when checking the two series end together.\nconst MAX_END_DRIFT_STEPS = 3;\n\n// Streaming pushes may batch a few bars (e.g. a throttled background tab), but\n// a jump of more than this many steps means the data was re-fetched elsewhere.\nconst MAX_STREAM_ADVANCE_STEPS = 5;\n\n/**\n * The candle period in ms, inferred as the median of the first few intervals\n * (robust to a single gap). Null when there are fewer than two candles.\n */\nexport function inferStepMs(candles: Candle[]): number | null {\n if (candles.length < 2) return null;\n const k = Math.min(candles.length - 1, 8);\n const diffs: number[] = [];\n for (let i = 0; i < k; i++) diffs.push(candles[i + 1].timeMs - candles[i].timeMs);\n diffs.sort((a, b) => a - b);\n const median = diffs[Math.floor(diffs.length / 2)];\n return median > 0 ? median : null;\n}\n\n// Index of the candle whose timeMs exactly equals `t`, or -1. Binary search over\n// the ascending-by-time series, so it tolerates interior gaps (missing bars from\n// downtime / illiquid periods) — unlike a uniform-grid index computed from the\n// step, which assumes a hole-free grid.\nfunction indexByTime(candles: Candle[], t: number): number {\n let lo = 0;\n let hi = candles.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n const v = candles[mid].timeMs;\n if (v === t) return mid;\n if (v < t) lo = mid + 1;\n else hi = mid - 1;\n }\n return -1;\n}\n\n/**\n * Classify a candles-prop change. `prev` is the previously rendered array\n * (null on first render); `seriesKeyChanged` forces `reset` regardless of the\n * data (the explicit escape hatch).\n *\n * Constraint: detection compares two immutable snapshots. An array mutated in\n * place (same reference) never reaches this code — React props must change\n * identity to re-render.\n */\nexport function classifyTransition(\n prev: Candle[] | null,\n next: Candle[],\n seriesKeyChanged: boolean,\n): DataTransition {\n if (!prev || prev.length === 0) return 'initial';\n if (next.length === 0) return 'stream'; // nothing to reframe against\n if (seriesKeyChanged) return 'reset';\n\n const prevStep = inferStepMs(prev);\n const nextStep = inferStepMs(next);\n if (prevStep == null || nextStep == null) return 'reset'; // too little data to reason\n\n const prevLast = prev[prev.length - 1];\n const nextLast = next[next.length - 1];\n\n if (Math.abs(nextStep - prevStep) <= prevStep * STEP_TOLERANCE) {\n // Same step: streaming iff prev's last bar still appears in next (covers\n // append, update-last, and rolling buffers that drop old bars from the\n // front) and the series only advanced by a few bars. Locate that bar by\n // timestamp, not by a step-derived index — real series have interior gaps\n // (downtime / illiquid periods), so a uniform-grid index would miss it and\n // misread a harmless in-place tick as a reset.\n // Time alignment alone isn't enough: two assets on the same exchange share\n // the bar grid, so the bar at the shared timestamp must also be (nearly) the\n // same bar — update-last moves the close, but never by the same-asset ratio.\n const idx = indexByTime(next, prevLast.timeMs);\n const aligned = idx >= 0;\n const sharedBarRatio =\n aligned && next[idx].close > 0 && prevLast.close > 0\n ? Math.max(next[idx].close / prevLast.close, prevLast.close / next[idx].close)\n : Infinity;\n const advanced =\n nextLast.timeMs >= prevLast.timeMs &&\n nextLast.timeMs - prevLast.timeMs <= MAX_STREAM_ADVANCE_STEPS * nextStep;\n return sharedBarRatio <= MAX_SAME_ASSET_CLOSE_RATIO && advanced ? 'stream' : 'reset';\n }\n\n // Step changed: a timeframe switch iff it still looks like the same asset —\n // last closes near each other and both series ending around the same time.\n const closeRatio =\n prevLast.close > 0 && nextLast.close > 0\n ? Math.max(nextLast.close / prevLast.close, prevLast.close / nextLast.close)\n : Infinity;\n const prevEnd = prevLast.timeMs + prevStep;\n const nextEnd = nextLast.timeMs + nextStep;\n const endsTogether =\n Math.abs(nextEnd - prevEnd) <= MAX_END_DRIFT_STEPS * Math.max(prevStep, nextStep);\n return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? 'timeframe' : 'reset';\n}\n\n/**\n * The visible window to apply after a timeframe switch so each candle keeps\n * the exact pixel width it had before: the visible slot count is preserved and\n * the right edge re-anchors on the newest candle (any future-gap overshoot is\n * carried over in slots, clamped to the core's 3/4-window cap). The new start\n * may precede the first candle — that gap is intentional, width wins.\n */\nexport function timeframeWindow(\n oldWindow: VisibleRange,\n oldStepMs: number,\n oldLastMs: number,\n newStepMs: number,\n newLastMs: number,\n): VisibleRange {\n const slots = (oldWindow.endMs - oldWindow.startMs) / oldStepMs;\n const offsetRaw = (oldWindow.endMs - oldLastMs) / oldStepMs;\n const offsetSlots = Math.min(Math.max(offsetRaw, 0), slots * 0.75);\n const endMs = Math.round(newLastMs + offsetSlots * newStepMs);\n return { startMs: Math.round(endMs - slots * newStepMs), endMs };\n}\n","// Pointer/wheel gesture controller for the web chart. Maps input to the same\n// VroomChartHandle mutators the React Native gestures use (see\n// packages/react-native/src/VroomChart.tsx), so behavior matches across\n// platforms. Listeners are attached natively (not via React props) so wheel can\n// be non-passive and call preventDefault.\n\nimport { useEffect, useRef } from 'react';\nimport type { ChartMode, CrosshairEvent, Drawing, DrawTool } from '@vroomchart/types';\nimport type { VroomChartHandle } from '@vroomchart/core-wasm';\n\ntype Region = 'chart' | 'price-axis' | 'time-axis' | 'indicator' | 'separator' | 'indicator-axis';\n\nexport type GestureOptions = {\n crosshairOffset: number;\n /** Interaction mode. In 'draw' mode panning/zooming/crosshair are suppressed. */\n mode?: ChartMode;\n /** Active drawing tool while in 'draw' mode. */\n tool?: DrawTool;\n onCrosshair?: (e: CrosshairEvent) => void;\n /** External crosshair to mirror (data space), or null. See VroomChartCoreProps. */\n crosshairOverride?: { timeMs: number; price: number } | null;\n onViewportChange?: (startMs: number, endMs: number) => void;\n /** Fired with the finished line when the user places its second point. */\n onDrawingComplete?: (drawing: Drawing) => void;\n /** Fired when the chart wants the host to change mode (e.g. exit on click-away). */\n onRequestMode?: (mode: ChartMode) => void;\n};\n\nconst MIN_SPAN = 24; // px — minimum two-finger span for an axis to scale\nconst AXIS_RATIO = 0.5; // an axis scales only if its span ≥ this × the other's\nconst LONG_PRESS_MS = 350;\nconst MOVE_THRESH = 6; // px before a press becomes a drag\nconst WHEEL_K = 0.0015; // wheel delta → zoom factor exponent\nconst SEP_HIT = 4; // px band around the indicator separator for hit-testing\n\n// Drawing-tool styling: the guideline (and committed line) default to solid blue\n// at 2px, matching the core's default and useChartCore's drawing color.\nconst DRAW_COLOR = 0xff2962ff;\nconst DRAW_WIDTH = 2;\nconst DRAW_HIT = 6; // px tolerance for \"clicked on the selected line\" vs. away\n\n// Stable unique id for a freshly drawn line.\nfunction drawingId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `draw-${Math.random().toString(36).slice(2)}`;\n}\n\n// Distance in px from point (px,py) to the segment (ax,ay)-(bx,by).\nfunction distToSegment(\n px: number,\n py: number,\n ax: number,\n ay: number,\n bx: number,\n by: number,\n): number {\n const dx = bx - ax;\n const dy = by - ay;\n const len2 = dx * dx + dy * dy;\n let t = len2 > 0 ? ((px - ax) * dx + (py - ay) * dy) / len2 : 0;\n t = Math.max(0, Math.min(1, t));\n return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));\n}\n\nexport function useGestures(\n containerRef: React.RefObject<HTMLElement | null>,\n handleRef: React.RefObject<VroomChartHandle | null>,\n scheduleRender: () => void,\n opts: GestureOptions,\n): void {\n // Keep latest opts in a ref so the effect's listeners stay stable.\n const optsRef = useRef(opts);\n optsRef.current = opts;\n\n // Drawing-tool state. Lives in refs (not effect-local) so the mode-change\n // effect below can reset it when the host leaves draw mode, and so it survives\n // the gesture effect's stable-listener lifecycle.\n // drawAnchor* — first point placed, awaiting the second (data + px coords).\n // drawSelectedPx — set once a line is committed: its px endpoints, used to\n // hit-test \"clicked on the line\" vs. \"clicked away to exit\".\n const drawAnchorRef = useRef<{ timeMs: number; price: number } | null>(null);\n const drawAnchorPxRef = useRef<{ x: number; y: number } | null>(null);\n const drawSelectedPxRef = useRef<{\n ax: number;\n ay: number;\n bx: number;\n by: number;\n } | null>(null);\n\n // True while a local pointer (hover/press) owns the crosshair. Lets the\n // crosshair-override effect below know to stand down (local input wins).\n const localCrosshairActiveRef = useRef(false);\n\n // Reset the in-progress draft whenever draw+line mode is not active (e.g. the\n // host toggled the tool off mid-draw), so re-entering draw mode starts clean.\n useEffect(() => {\n const active = opts.mode === 'draw' && opts.tool === 'line';\n if (active) return;\n drawAnchorRef.current = null;\n drawAnchorPxRef.current = null;\n drawSelectedPxRef.current = null;\n const h = handleRef.current;\n if (h) {\n h.clearDraft();\n scheduleRender();\n }\n }, [opts.mode, opts.tool, handleRef, scheduleRender]);\n\n // Apply the controlled crosshair override (cross-chart sync). A local\n // hover/press always wins, so we stand down while one is active; the override\n // is (re)applied on local release inside hideCrosshair. This path is silent —\n // it never calls onCrosshair — so two charts driving each other can't loop.\n // (If the override is set before the WASM handle finishes loading it applies\n // on its next change; in the sync use case the initial value is null.)\n useEffect(() => {\n const h = handleRef.current;\n if (!h || localCrosshairActiveRef.current) return;\n const ov = opts.crosshairOverride;\n if (ov) h.setCrosshairData(ov.timeMs, ov.price);\n else h.clearCrosshair();\n scheduleRender();\n }, [opts.crosshairOverride, handleRef, scheduleRender]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const pointers = new Map<number, { x: number; y: number }>();\n let panMode: Region = 'chart';\n let crosshairActive = false;\n let crosshairSource: 'press' | 'hover' | null = null;\n let lastCrosshairTime: number | null = null;\n let lastCrosshairPrice: number | null = null;\n let longPressTimer: ReturnType<typeof setTimeout> | null = null;\n let downX = 0;\n let downY = 0;\n let moved = false;\n const pinch = { spanX: 1, spanY: 1, ratioX: 1, ratioY: 1, enableX: false, enableY: false, active: false };\n\n const rel = (e: PointerEvent | WheelEvent) => {\n const r = el.getBoundingClientRect();\n return { x: e.clientX - r.left, y: e.clientY - r.top };\n };\n\n const regionAt = (x: number, y: number): Region => {\n const h = handleRef.current;\n const r = el.getBoundingClientRect();\n if (!h) return 'chart';\n const { yAxisWidth, xAxisHeight, indicatorHeight } = h.getAxisMetrics();\n if (x > r.width - yAxisWidth) {\n // The y-axis strip beside an indicator pane scales that pane's y-axis;\n // the rest of the strip scales the main price axis.\n const priceBottom = r.height - xAxisHeight - indicatorHeight;\n if (indicatorHeight > 0 && y > priceBottom && y < r.height - xAxisHeight) {\n return 'indicator-axis';\n }\n return 'price-axis';\n }\n // Separator sits at the top edge of the indicator band, within the candle\n // area width — a narrow grab band for resizing the panes.\n const sepY = r.height - xAxisHeight - indicatorHeight;\n if (indicatorHeight > 0 && x <= r.width - yAxisWidth && Math.abs(y - sepY) <= SEP_HIT) {\n return 'separator';\n }\n if (y > r.height - xAxisHeight) return 'time-axis';\n if (indicatorHeight > 0 && y > r.height - xAxisHeight - indicatorHeight) return 'indicator';\n return 'chart';\n };\n\n const reportCrosshair = (reason: CrosshairEvent['reason']) => {\n const h = handleRef.current;\n if (!h) return;\n // Snapped slot — has a timeMs even in the empty space ahead of the last\n // candle, where `candle` is null.\n const info = h.getCrosshairInfo();\n const t = info?.timeMs ?? null;\n const p = info?.price ?? null;\n // Fire on any positional change — a different candle (time) OR a vertical\n // move within the same candle (price). Deduping on time alone would freeze\n // the reported price mid-candle, which breaks cross-chart price sync.\n if (reason === 'move' && t === lastCrosshairTime && p === lastCrosshairPrice) return;\n lastCrosshairTime = t;\n lastCrosshairPrice = p;\n optsRef.current.onCrosshair?.({\n active: reason !== 'hide',\n candle: info?.candle ?? null,\n timeMs: t,\n price: reason === 'hide' ? null : p,\n reason,\n });\n };\n\n const clearLongPress = () => {\n if (longPressTimer != null) {\n clearTimeout(longPressTimer);\n longPressTimer = null;\n }\n };\n\n const showCrosshair = (x: number, y: number, source: 'press' | 'hover', reason: 'show' | 'move') => {\n const h = handleRef.current;\n if (!h) return;\n crosshairActive = true;\n localCrosshairActiveRef.current = true;\n crosshairSource = source;\n const lift = source === 'press' ? optsRef.current.crosshairOffset : 0;\n h.setCrosshair(x, y - lift);\n scheduleRender();\n reportCrosshair(reason);\n };\n\n const hideCrosshair = () => {\n const h = handleRef.current;\n if (!h || !crosshairActive) return;\n crosshairActive = false;\n localCrosshairActiveRef.current = false;\n crosshairSource = null;\n // Fall back to the synced crosshair (if any) rather than clearing, so\n // releasing a local hover on a follower chart restores the driver's line.\n const ov = optsRef.current.crosshairOverride;\n if (ov) h.setCrosshairData(ov.timeMs, ov.price);\n else h.clearCrosshair();\n scheduleRender();\n reportCrosshair('hide');\n };\n\n // True while the line tool should own input (suppress pan/zoom/crosshair).\n const drawActive = () =>\n optsRef.current.mode === 'draw' && optsRef.current.tool === 'line';\n\n // While placing the second point, keep the guideline glued to the cursor.\n const updateGuideline = (x: number, y: number) => {\n const h = handleRef.current;\n if (!h) return;\n if (!drawAnchorRef.current || drawSelectedPxRef.current) return;\n const c = h.coordAt(x, y);\n if (!c) return;\n const a = drawAnchorRef.current;\n h.setDraft(a.timeMs, a.price, true, c.timeMs, c.price, true, DRAW_COLOR, DRAW_WIDTH);\n scheduleRender();\n };\n\n // A tap in the chart area: place the next point, or exit on click-away.\n const handleDrawClick = (x: number, y: number) => {\n const h = handleRef.current;\n if (!h) return;\n const o = optsRef.current;\n\n // A committed line is selected: clicking on it keeps it; clicking away\n // hides the nodes and asks the host to return to pan mode.\n const sel = drawSelectedPxRef.current;\n if (sel) {\n if (distToSegment(x, y, sel.ax, sel.ay, sel.bx, sel.by) <= DRAW_HIT) return;\n drawAnchorRef.current = null;\n drawAnchorPxRef.current = null;\n drawSelectedPxRef.current = null;\n h.clearDraft();\n scheduleRender();\n el.style.cursor = '';\n o.onRequestMode?.('pan');\n return;\n }\n\n const coord = h.coordAt(x, y);\n if (!coord) return;\n\n if (!drawAnchorRef.current) {\n // First point: show node A; the guideline follows on the next move.\n drawAnchorRef.current = coord;\n drawAnchorPxRef.current = { x, y };\n h.setDraft(coord.timeMs, coord.price, false, 0, 0, true, DRAW_COLOR, DRAW_WIDTH);\n scheduleRender();\n } else {\n // Second point: commit the line and keep both nodes shown (selected).\n const a = drawAnchorRef.current;\n const apx = drawAnchorPxRef.current!;\n o.onDrawingComplete?.({\n id: drawingId(),\n type: 'line',\n points: [\n { timeMs: a.timeMs, price: a.price },\n { timeMs: coord.timeMs, price: coord.price },\n ],\n });\n drawSelectedPxRef.current = { ax: apx.x, ay: apx.y, bx: x, by: y };\n h.setDraft(a.timeMs, a.price, true, coord.timeMs, coord.price, false, DRAW_COLOR, DRAW_WIDTH);\n scheduleRender();\n }\n };\n\n const onPointerDown = (e: PointerEvent) => {\n const h = handleRef.current;\n if (!h) return;\n if (e.pointerType === 'mouse' && e.button !== 0) return; // left button only\n const { x, y } = rel(e);\n el.setPointerCapture(e.pointerId);\n pointers.set(e.pointerId, { x, y });\n\n if (pointers.size === 2) {\n clearLongPress();\n hideCrosshair();\n const pts = [...pointers.values()];\n const sx = Math.abs(pts[0]!.x - pts[1]!.x);\n const sy = Math.abs(pts[0]!.y - pts[1]!.y);\n pinch.spanX = sx;\n pinch.spanY = sy;\n pinch.ratioX = 1;\n pinch.ratioY = 1;\n pinch.enableX = sx >= MIN_SPAN && sx >= sy * AXIS_RATIO;\n pinch.enableY = sy >= MIN_SPAN && sy >= sx * AXIS_RATIO;\n pinch.active = true;\n return;\n }\n\n // Single pointer: classify region; arm long-press → crosshair (touch/pen).\n downX = x;\n downY = y;\n moved = false;\n panMode = regionAt(x, y);\n if (e.pointerType !== 'mouse' && panMode === 'chart' && !drawActive()) {\n longPressTimer = setTimeout(() => {\n longPressTimer = null;\n if (!moved) showCrosshair(downX, downY, 'press', 'show');\n }, LONG_PRESS_MS);\n }\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const h = handleRef.current;\n if (!h) return;\n const { x, y } = rel(e);\n\n // Hover (mouse, no button) → crosshair follows the cursor on the chart.\n if (!pointers.has(e.pointerId)) {\n if (e.pointerType === 'mouse' && pointers.size === 0) {\n // Draw mode: no crosshair; the guideline tracks the cursor instead.\n if (drawActive()) {\n el.style.cursor = 'crosshair';\n updateGuideline(x, y);\n return;\n }\n const region = regionAt(x, y);\n el.style.cursor =\n region === 'separator'\n ? 'row-resize'\n : region === 'indicator-axis'\n ? 'ns-resize'\n : '';\n if (region === 'chart') {\n showCrosshair(x, y, 'hover', crosshairActive ? 'move' : 'show');\n } else {\n hideCrosshair();\n }\n }\n return;\n }\n\n const prev = pointers.get(e.pointerId)!;\n const dx = x - prev.x;\n const dy = y - prev.y;\n pointers.set(e.pointerId, { x, y });\n\n // Two-finger directional pinch (disabled while drawing).\n if (pointers.size >= 2 && pinch.active && !drawActive()) {\n const pts = [...pointers.values()];\n const focalX = (pts[0]!.x + pts[1]!.x) / 2;\n const focalY = (pts[0]!.y + pts[1]!.y) / 2;\n let frameX = 1;\n if (pinch.enableX) {\n const r = Math.max(Math.abs(pts[0]!.x - pts[1]!.x), MIN_SPAN) / pinch.spanX;\n frameX = r / pinch.ratioX;\n pinch.ratioX = r;\n }\n let frameY = 1;\n if (pinch.enableY) {\n const r = Math.max(Math.abs(pts[0]!.y - pts[1]!.y), MIN_SPAN) / pinch.spanY;\n frameY = r / pinch.ratioY;\n pinch.ratioY = r;\n }\n if (frameX !== 1 || frameY !== 1) {\n h.zoom(frameX, frameY, focalX, focalY);\n scheduleRender();\n }\n return;\n }\n\n if (!moved && Math.hypot(x - downX, y - downY) > MOVE_THRESH) {\n moved = true;\n clearLongPress();\n }\n if (!moved) return;\n\n // Draw mode: a press-drag neither pans nor moves a crosshair; if a point\n // is down, keep the guideline tracking the cursor so a drag still previews.\n if (drawActive()) {\n if (panMode === 'chart') updateGuideline(x, y);\n return;\n }\n\n // Chart-area drag with the (press) crosshair up moves the crosshair.\n if (crosshairActive && crosshairSource === 'press' && panMode === 'chart') {\n showCrosshair(x, y, 'press', 'move');\n return;\n }\n\n if (panMode === 'price-axis') h.scalePriceAxis(dy);\n else if (panMode === 'time-axis') h.scaleTimeAxis(dx);\n else if (panMode === 'separator') h.resizeIndicatorPane(dy);\n else if (panMode === 'indicator-axis') h.scaleIndicatorAxis(downY, dy);\n else if (panMode === 'indicator') h.pan(dx, 0);\n else h.translate(dx, dy);\n\n // Keep a hover crosshair glued to the cursor while dragging — the chart\n // pans under it, so re-snap to whatever candle is now beneath the cursor.\n if (crosshairActive && crosshairSource === 'hover') {\n h.setCrosshair(x, y);\n reportCrosshair('move');\n }\n scheduleRender();\n };\n\n const endPointer = (e: PointerEvent) => {\n const had = pointers.delete(e.pointerId);\n clearLongPress();\n if (pointers.size < 2) pinch.active = false;\n if (!had) return;\n\n // Draw mode: a stationary tap in the chart places a point (or exits on\n // click-away). Drags are ignored (no pan). Crosshair logic is skipped.\n if (drawActive() && pointers.size === 0) {\n if (!moved && panMode === 'chart') handleDrawClick(downX, downY);\n else el.style.cursor = 'crosshair';\n return;\n }\n\n // Press crosshair is active only while held — release dismisses it.\n if (crosshairSource === 'press' && pointers.size === 0) {\n hideCrosshair();\n } else if (moved && (panMode === 'chart' || panMode === 'indicator')) {\n optsRef.current.onViewportChange?.(0, 0);\n }\n if (pointers.size === 0) el.style.cursor = '';\n };\n\n const onPointerLeave = () => {\n if (crosshairSource === 'hover') hideCrosshair();\n el.style.cursor = '';\n };\n\n const onWheel = (e: WheelEvent) => {\n const h = handleRef.current;\n if (!h) return;\n e.preventDefault();\n // Draw mode: no pan/zoom from the wheel (keeps placed points stable).\n if (drawActive()) return;\n const { x, y } = rel(e);\n if (e.ctrlKey || e.metaKey) {\n // Trackpad pinch (sent as ctrl+wheel) / ctrl+wheel → zoom both axes.\n const f = Math.exp(-e.deltaY * WHEEL_K);\n h.zoom(f, f, x, y);\n } else if (e.shiftKey) {\n // Shift+wheel → horizontal pan (mouse-wheel-only users).\n h.pan(-(e.deltaX || e.deltaY), 0);\n } else if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {\n // Horizontal scroll → pan the time axis (scroll right = forward in time).\n h.pan(-e.deltaX, 0);\n } else {\n // Vertical scroll → zoom the time window around the cursor.\n const f = Math.exp(-e.deltaY * WHEEL_K);\n h.zoom(f, 1, x, y);\n }\n scheduleRender();\n };\n\n el.addEventListener('pointerdown', onPointerDown);\n el.addEventListener('pointermove', onPointerMove);\n el.addEventListener('pointerup', endPointer);\n el.addEventListener('pointercancel', endPointer);\n el.addEventListener('pointerleave', onPointerLeave);\n el.addEventListener('wheel', onWheel, { passive: false });\n\n return () => {\n clearLongPress();\n el.removeEventListener('pointerdown', onPointerDown);\n el.removeEventListener('pointermove', onPointerMove);\n el.removeEventListener('pointerup', endPointer);\n el.removeEventListener('pointercancel', endPointer);\n el.removeEventListener('pointerleave', onPointerLeave);\n el.removeEventListener('wheel', onWheel);\n };\n }, [containerRef, handleRef, scheduleRender]);\n}\n","// VroomChart (web) — DOM React component that renders the candlestick chart to\n// a <canvas> via @vroomchart/core-wasm and wires pointer/wheel gestures. API-matched\n// to the React Native component (same props from @vroomchart/types).\n\nimport React from 'react';\nimport type { CSSProperties } from 'react';\n\nimport { useChartCore } from './useChartCore';\nimport { useGestures } from './useGestures';\nimport type { VroomChartProps } from './types';\n\nconst FILL: CSSProperties = { position: 'relative', width: '100%', height: '100%' };\nconst CANVAS_STYLE: CSSProperties = {\n display: 'block',\n width: '100%',\n height: '100%',\n touchAction: 'none', // we own pan/zoom; don't let the browser scroll/zoom\n};\n\n/**\n * Skia-quality candlestick chart for the web. Pass OHLCV `candles` and size it\n * via `style`/`width`/`height` (it fills its parent by default). Drag to pan,\n * pinch or wheel to zoom, drag the price/time axes to rescale, hover (mouse) or\n * long-press (touch) for the crosshair. Optional indicators (`rsi`, `macd`,\n * `movingAverages`, `vwap`), colors (`theme`), and events (`onCrosshair`,\n * `onViewportChange`) are configured through props.\n *\n * @see {@link VroomChartProps} for the full prop reference.\n */\nexport function VroomChart(props: VroomChartProps) {\n const {\n className,\n style,\n wasm,\n crosshairOffset = 40,\n crosshairOverride,\n mode,\n tool,\n onCrosshair,\n onViewportChange,\n onDrawingComplete,\n onModeChange,\n } = props;\n const { containerRef, canvasRef, handleRef, scheduleRender } = useChartCore(\n props,\n wasm ? { wasm } : undefined,\n );\n\n useGestures(containerRef, handleRef, scheduleRender, {\n crosshairOffset,\n crosshairOverride,\n mode,\n tool,\n onCrosshair,\n onViewportChange,\n onDrawingComplete,\n onRequestMode: onModeChange,\n });\n\n const rootStyle: CSSProperties = {\n ...FILL,\n ...(props.width != null ? { width: props.width } : null),\n ...(props.height != null ? { height: props.height } : null),\n ...style,\n };\n\n return (\n <div ref={containerRef} className={className} style={rootStyle}>\n <canvas ref={canvasRef} style={CANVAS_STYLE} />\n </div>\n );\n}\n"],"mappings":"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","names":["useEffect","useRef"]}
1
+ {"version":3,"sources":["../src/useChartCore.ts","../src/dataTransitions.ts","../src/useGestures.ts","../src/useManagedDrawings.ts","../src/drawingStorage.ts","../src/VroomChart.tsx"],"sourcesContent":["// Owns one VroomChartHandle bound to a <canvas>: loads the core, measures the\n// container (ResizeObserver), pushes data/size/theme/indicators on change, and\n// drives a rAF-batched present() loop. The web analogue of\n// packages/react-native/src/useChartCore.ts — but it paints the canvas via\n// handle.present() instead of producing an SkPicture.\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n loadVroom,\n packCandles,\n applyTheme,\n parseColor,\n type LoadVroomOptions,\n type OverlaySpec,\n type DrawingSpec,\n type LiquiditySpec,\n type VroomChartHandle,\n} from '@vroomchart/core-wasm';\nimport type { VroomChartCoreProps } from '@vroomchart/types';\nimport {\n classifyTransition,\n inferStepMs,\n timeframeWindow,\n type DataTransition,\n} from './dataTransitions';\n\n// Mirrors vroom::ma::Source order (packages/core/src/ma.h).\nconst MA_SOURCES = ['close', 'open', 'high', 'low', 'hl2', 'hlc3', 'ohlc4'] as const;\n\nfunction overlayToNumeric(\n o: NonNullable<VroomChartCoreProps['movingAverages']>[number],\n): OverlaySpec {\n const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;\n return {\n kind: o.kind === 'ema' ? 1 : 0,\n period: o.length,\n source: srcIdx < 0 ? 0 : srcIdx,\n color: (o.color != null ? parseColor(o.color) : null) ?? 0xff2962ff,\n width: o.width ?? 1.5,\n };\n}\n\nfunction drawingToSpec(\n d: NonNullable<VroomChartCoreProps['drawings']>[number],\n): DrawingSpec {\n return {\n aTime: d.points[0].timeMs,\n aPrice: d.points[0].price,\n bTime: d.points[1].timeMs,\n bPrice: d.points[1].price,\n color: (d.color != null ? parseColor(d.color) : null) ?? 0xff2962ff,\n width: d.width ?? 2,\n };\n}\n\n// Default buy/sell colors (teal-green / red), matching the accent palette.\nconst DEFAULT_BUY_COLOR = 0xff26a69a;\nconst DEFAULT_SELL_COLOR = 0xffef5350;\n\nfunction liquidityToSpec(\n cfg: NonNullable<VroomChartCoreProps['liquidity']>,\n): LiquiditySpec {\n return {\n bands: cfg.bands.map((b) => ({\n minPrice: b.minPrice,\n maxPrice: b.maxPrice,\n side: b.side === 'sell' ? 1 : 0,\n volume: b.volume,\n })),\n buyColor: (cfg.buyColor != null ? parseColor(cfg.buyColor) : null) ?? DEFAULT_BUY_COLOR,\n sellColor:\n (cfg.sellColor != null ? parseColor(cfg.sellColor) : null) ?? DEFAULT_SELL_COLOR,\n maxVolume: cfg.maxVolume ?? 0,\n minOpacity: cfg.minOpacity ?? 0.05,\n maxOpacity: cfg.maxOpacity ?? 0.8,\n widthPx: cfg.widthPx ?? 300,\n widthFrac: cfg.widthFrac ?? 0.25,\n };\n}\n\n// Cleared overlay: an empty band set (style values are irrelevant when there are\n// no bands, but the spec shape requires them).\nconst EMPTY_LIQUIDITY: LiquiditySpec = {\n bands: [],\n buyColor: DEFAULT_BUY_COLOR,\n sellColor: DEFAULT_SELL_COLOR,\n maxVolume: 0,\n minOpacity: 0.05,\n maxOpacity: 0.8,\n widthPx: 300,\n widthFrac: 0.25,\n};\n\nexport type UseChartCore = {\n containerRef: React.RefObject<HTMLDivElement | null>;\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n handleRef: React.RefObject<VroomChartHandle | null>;\n /** Schedules a rAF-batched repaint (and keeps ticking while animating). */\n scheduleRender: () => void;\n /** Measured CSS size of the container (0 until first layout). */\n size: { width: number; height: number };\n};\n\nexport function useChartCore(\n props: VroomChartCoreProps,\n loadOpts?: LoadVroomOptions,\n): UseChartCore {\n const {\n candles,\n seriesKey,\n width: widthProp,\n height: heightProp,\n visibleRange,\n defaultCandleWidth,\n theme,\n rsi,\n macd,\n movingAverages,\n vwap,\n drawings,\n liquidity,\n } = props;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const handleRef = useRef<VroomChartHandle | null>(null);\n // What the core currently holds, for classifying the next data change.\n // Keyed by handle so a recreated core is treated as a fresh initial load.\n const prevDataRef = useRef<{\n handle: VroomChartHandle;\n candles: VroomChartCoreProps['candles'];\n seriesKey?: string;\n } | null>(null);\n const rafRef = useRef<number | null>(null);\n const [ready, setReady] = useState(false);\n const [measured, setMeasured] = useState({ width: 0, height: 0 });\n\n // Captured at mount: which core to load (stub vs Skia-WASM). Changing it after\n // mount has no effect — the core is created once and shared process-wide.\n const loadOptsRef = useRef(loadOpts);\n loadOptsRef.current = loadOpts;\n\n const width = widthProp ?? measured.width;\n const height = heightProp ?? measured.height;\n\n const scheduleRender = useCallback(() => {\n if (rafRef.current != null) return;\n rafRef.current = requestAnimationFrame(() => {\n rafRef.current = null;\n const h = handleRef.current;\n if (!h) return;\n h.present();\n if (h.isAnimating()) scheduleRender();\n });\n }, []);\n\n // Create the handle once the canvas exists; destroy on unmount.\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n let disposed = false;\n loadVroom(loadOptsRef.current).then((mod) => {\n if (disposed) return;\n handleRef.current = mod.create(canvas);\n setReady(true);\n });\n return () => {\n disposed = true;\n if (rafRef.current != null) cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n handleRef.current?.destroy();\n handleRef.current = null;\n setReady(false);\n };\n }, []);\n\n // Measure the container (unless both dims are pinned via props).\n useEffect(() => {\n const el = containerRef.current;\n if (!el || (widthProp != null && heightProp != null)) return;\n const ro = new ResizeObserver((entries) => {\n const r = entries[0]?.contentRect;\n if (!r) return;\n const w = Math.round(r.width);\n const h = Math.round(r.height);\n setMeasured((prev) => (prev.width === w && prev.height === h ? prev : { width: w, height: h }));\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, [widthProp, heightProp]);\n\n // Stable deps so inline object/array literals don't re-run every render.\n const themeKey = theme ? JSON.stringify(theme) : '';\n const rsiKey = rsi ? JSON.stringify(rsi) : '';\n const macdKey = macd ? JSON.stringify(macd) : '';\n const maKey = movingAverages ? JSON.stringify(movingAverages) : '';\n const vwapKey = vwap ? JSON.stringify(vwap) : '';\n const drawingsKey = drawings ? JSON.stringify(drawings) : '';\n const liquidityKey = liquidity ? JSON.stringify(liquidity) : '';\n const explicit = visibleRange != null;\n const startMs = visibleRange?.startMs ?? 0;\n const endMs = visibleRange?.endMs ?? 0;\n\n // Push everything into the core whenever data/size/config changes, then paint.\n useEffect(() => {\n const h = handleRef.current;\n if (!h || width <= 0 || height <= 0) return;\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n h.setSize(width, height, dpr);\n if (candles.length > 0) {\n const prev = prevDataRef.current;\n const freshHandle = prev == null || prev.handle !== h;\n if (freshHandle || prev.candles !== candles || prev.seriesKey !== seriesKey) {\n // A fresh core frames itself (its window starts at 0/0); an explicit\n // visibleRange prop overrides any auto behavior, so treat the change\n // like a stream and let the range application below win.\n const transition: DataTransition = freshHandle\n ? 'initial'\n : explicit\n ? 'stream'\n : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);\n\n // Capture the outgoing view before setCandles re-infers the candle\n // period from the new data.\n let tfArgs: { oldWindow: { startMs: number; endMs: number }; oldStepMs: number; oldLastMs: number } | null =\n null;\n if (transition === 'timeframe' && prev != null) {\n const oldWindow = h.getVisibleRange();\n const oldStepMs = inferStepMs(prev.candles);\n if (oldWindow.endMs > oldWindow.startMs && oldStepMs != null) {\n tfArgs = { oldWindow, oldStepMs, oldLastMs: prev.candles[prev.candles.length - 1].timeMs };\n }\n }\n\n // Drive the initial zoom from a target candle width, but only on a\n // fresh handle and only when the caller isn't explicitly controlling the\n // range. Pushed before setCandles so the core's default framing (run\n // inside setCandles while the window is still 0/0) picks it up.\n if (\n freshHandle &&\n !explicit &&\n defaultCandleWidth != null &&\n defaultCandleWidth > 0\n ) {\n h.setDefaultCandleWidth(defaultCandleWidth);\n }\n\n h.setCandles(packCandles(candles));\n\n if (transition === 'timeframe') {\n const newStepMs = inferStepMs(candles);\n if (tfArgs && newStepMs != null) {\n const w = timeframeWindow(\n tfArgs.oldWindow,\n tfArgs.oldStepMs,\n tfArgs.oldLastMs,\n newStepMs,\n candles[candles.length - 1].timeMs,\n );\n h.setVisibleRange(w.startMs, w.endMs);\n }\n h.resetPriceScale();\n } else if (transition === 'reset') {\n h.resetView();\n }\n prevDataRef.current = { handle: h, candles, seriesKey };\n }\n }\n if (explicit) h.setVisibleRange(startMs, endMs);\n if (theme) applyTheme(h, theme);\n h.setRSI(\n rsi?.enabled ?? false,\n rsi?.period ?? 14,\n rsi?.upperBand ?? 70,\n rsi?.lowerBand ?? 30,\n rsi?.maEnabled ?? true,\n rsi?.maPeriod ?? 14,\n );\n h.setMACD(macd?.enabled ?? false, macd?.fast ?? 12, macd?.slow ?? 26, macd?.signal ?? 9);\n h.setOverlays((movingAverages ?? []).map(overlayToNumeric));\n h.setVWAP(\n vwap?.enabled ?? false,\n vwap?.resetMinutes ?? 0,\n (vwap?.color != null ? parseColor(vwap.color) : null) ?? 0xff00bcd4,\n vwap?.width ?? 1.5,\n );\n h.setDrawings((drawings ?? []).map(drawingToSpec));\n h.setLiquidity(\n liquidity?.bands?.length ? liquidityToSpec(liquidity) : EMPTY_LIQUIDITY,\n );\n scheduleRender();\n // theme/rsi/macd/movingAverages/vwap/drawings/liquidity tracked via *Key deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ready, width, height, candles, seriesKey, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, drawingsKey, liquidityKey, scheduleRender]);\n\n return { containerRef, canvasRef, handleRef, scheduleRender, size: { width, height } };\n}\n","// Classifies how a new `candles` prop relates to the previous one so the chart\n// can react appropriately: leave the viewport alone for streaming updates,\n// re-anchor the time window for a timeframe switch, or fully reset the view\n// for a different asset. Pure functions, no React — see useChartCore for the\n// orchestration.\n\nimport type { Candle, VisibleRange } from '@vroomchart/types';\n\nexport type DataTransition = 'initial' | 'stream' | 'timeframe' | 'reset';\n\n// A step change below this ratio is treated as the same timeframe. Real steps\n// are exact integer ms; the tolerance only absorbs rounding/DST quirks (the\n// smallest real timeframe jump, 1m -> 2m, is 100% apart).\nconst STEP_TOLERANCE = 0.01;\n\n// Same-asset check for a timeframe switch: both series end \"now\", so their\n// last closes must be close. No asset moves 25% between two consecutive prop\n// pushes; distinct assets within 25% of each other are what `seriesKey` is for.\nconst MAX_SAME_ASSET_CLOSE_RATIO = 1.25;\n\n// A coarser bucketing can shift the final bar's open by up to one coarse bar;\n// allow that plus an in-flight bar when checking the two series end together.\nconst MAX_END_DRIFT_STEPS = 3;\n\n// Streaming pushes may batch a few bars (e.g. a throttled background tab), but\n// a jump of more than this many steps means the data was re-fetched elsewhere.\nconst MAX_STREAM_ADVANCE_STEPS = 5;\n\n/**\n * The candle period in ms, inferred as the median of the first few intervals\n * (robust to a single gap). Null when there are fewer than two candles.\n */\nexport function inferStepMs(candles: Candle[]): number | null {\n if (candles.length < 2) return null;\n const k = Math.min(candles.length - 1, 8);\n const diffs: number[] = [];\n for (let i = 0; i < k; i++) diffs.push(candles[i + 1].timeMs - candles[i].timeMs);\n diffs.sort((a, b) => a - b);\n const median = diffs[Math.floor(diffs.length / 2)];\n return median > 0 ? median : null;\n}\n\n// Index of the candle whose timeMs exactly equals `t`, or -1. Binary search over\n// the ascending-by-time series, so it tolerates interior gaps (missing bars from\n// downtime / illiquid periods) — unlike a uniform-grid index computed from the\n// step, which assumes a hole-free grid.\nfunction indexByTime(candles: Candle[], t: number): number {\n let lo = 0;\n let hi = candles.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n const v = candles[mid].timeMs;\n if (v === t) return mid;\n if (v < t) lo = mid + 1;\n else hi = mid - 1;\n }\n return -1;\n}\n\n/**\n * Classify a candles-prop change. `prev` is the previously rendered array\n * (null on first render); `seriesKeyChanged` forces `reset` regardless of the\n * data (the explicit escape hatch).\n *\n * Constraint: detection compares two immutable snapshots. An array mutated in\n * place (same reference) never reaches this code — React props must change\n * identity to re-render.\n */\nexport function classifyTransition(\n prev: Candle[] | null,\n next: Candle[],\n seriesKeyChanged: boolean,\n): DataTransition {\n if (!prev || prev.length === 0) return 'initial';\n if (next.length === 0) return 'stream'; // nothing to reframe against\n if (seriesKeyChanged) return 'reset';\n\n const prevStep = inferStepMs(prev);\n const nextStep = inferStepMs(next);\n if (prevStep == null || nextStep == null) return 'reset'; // too little data to reason\n\n const prevLast = prev[prev.length - 1];\n const nextLast = next[next.length - 1];\n\n if (Math.abs(nextStep - prevStep) <= prevStep * STEP_TOLERANCE) {\n // Same step: streaming iff prev's last bar still appears in next (covers\n // append, update-last, and rolling buffers that drop old bars from the\n // front) and the series only advanced by a few bars. Locate that bar by\n // timestamp, not by a step-derived index — real series have interior gaps\n // (downtime / illiquid periods), so a uniform-grid index would miss it and\n // misread a harmless in-place tick as a reset.\n // Time alignment alone isn't enough: two assets on the same exchange share\n // the bar grid, so the bar at the shared timestamp must also be (nearly) the\n // same bar — update-last moves the close, but never by the same-asset ratio.\n const idx = indexByTime(next, prevLast.timeMs);\n const aligned = idx >= 0;\n const sharedBarRatio =\n aligned && next[idx].close > 0 && prevLast.close > 0\n ? Math.max(next[idx].close / prevLast.close, prevLast.close / next[idx].close)\n : Infinity;\n const advanced =\n nextLast.timeMs >= prevLast.timeMs &&\n nextLast.timeMs - prevLast.timeMs <= MAX_STREAM_ADVANCE_STEPS * nextStep;\n return sharedBarRatio <= MAX_SAME_ASSET_CLOSE_RATIO && advanced ? 'stream' : 'reset';\n }\n\n // Step changed: a timeframe switch iff it still looks like the same asset —\n // last closes near each other and both series ending around the same time.\n const closeRatio =\n prevLast.close > 0 && nextLast.close > 0\n ? Math.max(nextLast.close / prevLast.close, prevLast.close / nextLast.close)\n : Infinity;\n const prevEnd = prevLast.timeMs + prevStep;\n const nextEnd = nextLast.timeMs + nextStep;\n const endsTogether =\n Math.abs(nextEnd - prevEnd) <= MAX_END_DRIFT_STEPS * Math.max(prevStep, nextStep);\n return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? 'timeframe' : 'reset';\n}\n\n/**\n * The visible window to apply after a timeframe switch so each candle keeps\n * the exact pixel width it had before: the visible slot count is preserved and\n * the right edge re-anchors on the newest candle (any future-gap overshoot is\n * carried over in slots, clamped to the core's 3/4-window cap). The new start\n * may precede the first candle — that gap is intentional, width wins.\n */\nexport function timeframeWindow(\n oldWindow: VisibleRange,\n oldStepMs: number,\n oldLastMs: number,\n newStepMs: number,\n newLastMs: number,\n): VisibleRange {\n const slots = (oldWindow.endMs - oldWindow.startMs) / oldStepMs;\n const offsetRaw = (oldWindow.endMs - oldLastMs) / oldStepMs;\n const offsetSlots = Math.min(Math.max(offsetRaw, 0), slots * 0.75);\n const endMs = Math.round(newLastMs + offsetSlots * newStepMs);\n return { startMs: Math.round(endMs - slots * newStepMs), endMs };\n}\n","// Pointer/wheel gesture controller for the web chart. Maps input to the same\n// VroomChartHandle mutators the React Native gestures use (see\n// packages/react-native/src/VroomChart.tsx), so behavior matches across\n// platforms. Listeners are attached natively (not via React props) so wheel can\n// be non-passive and call preventDefault.\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport type { ChartMode, CrosshairEvent, Drawing, DrawPoint, DrawTool } from '@vroomchart/types';\nimport type { VroomChartHandle } from '@vroomchart/core-wasm';\n\ntype Region = 'chart' | 'price-axis' | 'time-axis' | 'indicator' | 'separator' | 'indicator-axis';\n\nexport type GestureOptions = {\n crosshairOffset: number;\n /** Interaction mode. In 'draw' mode panning/zooming/crosshair are suppressed. */\n mode?: ChartMode;\n /** Active drawing tool while in 'draw' mode. */\n tool?: DrawTool;\n onCrosshair?: (e: CrosshairEvent) => void;\n /** External crosshair to mirror (data space), or null. See VroomChartCoreProps. */\n crosshairOverride?: { timeMs: number; price: number } | null;\n onViewportChange?: (startMs: number, endMs: number) => void;\n /** Committed drawings (controlled). Used to hit-test/select/drag/delete. */\n drawings?: Drawing[];\n /** Fired with the finished line when the user places its second point. */\n onDrawingComplete?: (drawing: Drawing) => void;\n /** Fired after a selected line's endpoint is dragged (same id, new points). */\n onDrawingChange?: (drawing: Drawing) => void;\n /** Fired when the selected line is deleted (Backspace/Delete). */\n onDrawingDelete?: (id: string) => void;\n /** Fired when the chart wants the host to change mode (e.g. exit on click-away). */\n onRequestMode?: (mode: ChartMode) => void;\n};\n\nconst MIN_SPAN = 24; // px — minimum two-finger span for an axis to scale\nconst AXIS_RATIO = 0.5; // an axis scales only if its span ≥ this × the other's\nconst LONG_PRESS_MS = 350;\nconst MOVE_THRESH = 6; // px before a press becomes a drag\nconst WHEEL_K = 0.0015; // wheel delta → zoom factor exponent\nconst SEP_HIT = 4; // px band around the indicator separator for hit-testing\n\n// Drawing-tool styling: the guideline (and committed line) default to solid blue\n// at 2px, matching the core's default and useChartCore's drawing color.\nconst DRAW_COLOR = 0xff2962ff;\nconst DRAW_WIDTH = 2;\n\n// Stable unique id for a freshly drawn line.\nfunction drawingId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `draw-${Math.random().toString(36).slice(2)}`;\n}\n\nexport function useGestures(\n containerRef: React.RefObject<HTMLElement | null>,\n handleRef: React.RefObject<VroomChartHandle | null>,\n scheduleRender: () => void,\n opts: GestureOptions,\n): void {\n // Keep latest opts in a ref so the effect's listeners stay stable.\n const optsRef = useRef(opts);\n optsRef.current = opts;\n\n // Drawing-tool state (live in refs so the mode-change effect can reset it and\n // it survives the gesture effect's stable-listener lifecycle).\n // drawAnchor — first point placed, awaiting the second (data coords).\n const drawAnchorRef = useRef<{ timeMs: number; price: number } | null>(null);\n\n // Editing state for committed drawings (pan-mode select / drag / delete).\n // selectedId — the selected Drawing.id (data-space identity; survives\n // pan/zoom + list changes). The core is told the matching index.\n // grab — the endpoint being dragged { index, endpoint: 0|1 }, or null.\n // dragLine — the whole selected line being translated by its body, or null.\n // downHit — the hit under the last pointerdown, resolved to select/deselect\n // on a stationary release.\n // lastCoord — the last coordAt during a handle drag, for the release payload.\n const selectedIdRef = useRef<string | null>(null);\n const grabRef = useRef<{ index: number; endpoint: number; fixed: DrawPoint } | null>(null);\n const dragLineRef = useRef<{\n index: number;\n startTime: number;\n startPrice: number;\n a0: DrawPoint;\n b0: DrawPoint;\n lastA: DrawPoint;\n lastB: DrawPoint;\n } | null>(null);\n const downHitRef = useRef<{ index: number; part: number; t: number } | null>(null);\n const lastCoordRef = useRef<{ timeMs: number; price: number } | null>(null);\n // Where along the selected line it was grabbed (0..1, A→B), and the in-memory\n // clipboard for copy/paste (color/width + the grab t + the crosshair at copy).\n const selectedTRef = useRef(0.5);\n const clipboardRef = useRef<{\n points: [DrawPoint, DrawPoint];\n color?: Drawing['color'];\n width?: number;\n t: number;\n crosshair: { timeMs: number; price: number } | null;\n } | null>(null);\n\n // Push the current selection (id → core index) and grabbed-handle state into\n // the core. Reads live refs/opts so a captured instance is never stale. Runs\n // after useChartCore's setDrawings (declared earlier in VroomChart) so the\n // index is valid whenever the drawings list changes.\n const applySelection = useCallback(() => {\n const h = handleRef.current;\n if (!h) return;\n const id = selectedIdRef.current;\n const list = optsRef.current.drawings ?? [];\n let index = -1;\n if (id != null) {\n index = list.findIndex((d) => d.id === id);\n if (index < 0) selectedIdRef.current = null;\n }\n h.setSelectedDrawing(index, index >= 0 ? (grabRef.current?.endpoint ?? -1) : -1);\n scheduleRender();\n }, [handleRef, scheduleRender]);\n\n // Re-assert selection whenever the drawings list changes (add / delete / the\n // just-dragged update), so the core index tracks the selected id.\n useEffect(() => {\n applySelection();\n }, [opts.drawings, applySelection]);\n\n // Keyboard: delete the selected line (Backspace/Delete) and copy/paste it\n // (Cmd/Ctrl+C / +V). Skipped while focus is in a field / a real text selection.\n useEffect(() => {\n const isEditableTarget = () => {\n const ae = document.activeElement as HTMLElement | null;\n const tag = ae?.tagName;\n return tag === 'INPUT' || tag === 'TEXTAREA' || !!ae?.isContentEditable;\n };\n const findSelected = (): Drawing | null => {\n const id = selectedIdRef.current;\n if (id == null) return null;\n return (optsRef.current.drawings ?? []).find((x) => x.id === id) ?? null;\n };\n\n // Endpoints for a pasted copy: under the crosshair (intersecting at the same\n // grab t), else offset above/below the original, kept within the viewport.\n const placePaste = (clip: NonNullable<typeof clipboardRef.current>): [DrawPoint, DrawPoint] | null => {\n const h = handleRef.current;\n if (!h) return null;\n const [a, b] = clip.points;\n const t = clip.t;\n const pSel = {\n timeMs: a.timeMs + t * (b.timeMs - a.timeMs),\n price: a.price + t * (b.price - a.price),\n };\n const cur = h.getCrosshairInfo();\n const moved =\n cur != null &&\n (clip.crosshair == null ||\n cur.timeMs !== clip.crosshair.timeMs ||\n cur.price !== clip.crosshair.price);\n if (moved && cur) {\n const dt = cur.timeMs - pSel.timeMs;\n const dp = cur.price - pSel.price;\n return [\n { timeMs: a.timeMs + dt, price: a.price + dp },\n { timeMs: b.timeMs + dt, price: b.price + dp },\n ];\n }\n // Above/below the original. Derive the visible price range + px scale.\n const el = containerRef.current;\n const shift = (dp: number): [DrawPoint, DrawPoint] => [\n { timeMs: a.timeMs, price: a.price + dp },\n { timeMs: b.timeMs, price: b.price + dp },\n ];\n const span = Math.abs(a.price - b.price);\n if (!el) return shift(-(span || 1));\n const rect = el.getBoundingClientRect();\n const m = h.getAxisMetrics();\n const priceBottom = rect.height - m.xAxisHeight - m.indicatorHeight;\n const top = h.coordAt(rect.width / 2, 0);\n const bot = h.coordAt(rect.width / 2, priceBottom);\n if (!top || !bot || priceBottom <= 0 || top.price <= bot.price) return shift(-(span || 1));\n const vTop = top.price; // higher price = screen top\n const vBot = bot.price; // lower price = screen bottom\n const pricePerPx = (vTop - vBot) / priceBottom;\n const GAP_PX = 30;\n const offset = (span / pricePerPx + GAP_PX) * pricePerPx; // clear the original\n const pMin = Math.min(a.price, b.price);\n const pMax = Math.max(a.price, b.price);\n const upFits = pMax + offset <= vTop;\n const downFits = pMin - offset >= vBot;\n let dir: 1 | -1;\n if (upFits && !downFits) dir = 1;\n else if (downFits && !upFits) dir = -1;\n else if (upFits && downFits) {\n dir = vTop - (pMax + offset) >= pMin - offset - vBot ? 1 : -1; // more edge margin\n } else {\n const upVis = Math.min(pMax + offset, vTop) - Math.max(pMin + offset, vBot);\n const dnVis = Math.min(pMax - offset, vTop) - Math.max(pMin - offset, vBot);\n dir = upVis >= dnVis ? 1 : -1; // larger visible portion\n }\n return shift(dir * offset);\n };\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (isEditableTarget()) return;\n\n // Mid-draw (first anchor placed, awaiting the second): Escape/Delete/\n // Backspace cancels the in-progress anchor but keeps draw mode active.\n if (\n drawAnchorRef.current &&\n (e.key === 'Escape' || e.key === 'Backspace' || e.key === 'Delete')\n ) {\n e.preventDefault();\n drawAnchorRef.current = null;\n const h = handleRef.current;\n if (h) {\n h.clearDraft();\n scheduleRender();\n }\n return;\n }\n\n if (e.key === 'Backspace' || e.key === 'Delete') {\n const id = selectedIdRef.current;\n if (id == null) return;\n e.preventDefault();\n optsRef.current.onDrawingDelete?.(id);\n selectedIdRef.current = null;\n grabRef.current = null;\n dragLineRef.current = null;\n const h = handleRef.current;\n if (h) {\n h.setSelectedDrawing(-1, -1);\n scheduleRender();\n }\n return;\n }\n\n if (!(e.metaKey || e.ctrlKey)) return;\n const key = e.key.toLowerCase();\n if (key === 'c') {\n if (window.getSelection()?.toString()) return; // don't hijack a real text copy\n const d = findSelected();\n if (!d) return;\n e.preventDefault();\n const info = handleRef.current?.getCrosshairInfo() ?? null;\n clipboardRef.current = {\n points: [d.points[0], d.points[1]],\n color: d.color,\n width: d.width,\n t: selectedTRef.current,\n crosshair: info ? { timeMs: info.timeMs, price: info.price } : null,\n };\n } else if (key === 'v') {\n const clip = clipboardRef.current;\n if (!clip) return;\n const pts = placePaste(clip);\n if (!pts) return;\n e.preventDefault();\n const id = drawingId();\n optsRef.current.onDrawingComplete?.({\n id,\n type: 'line',\n points: pts,\n ...(clip.color != null ? { color: clip.color } : {}),\n ...(clip.width != null ? { width: clip.width } : {}),\n });\n // The drawings-change effect selects the new id once it's appended.\n selectedIdRef.current = id;\n selectedTRef.current = clip.t;\n }\n };\n window.addEventListener('keydown', onKeyDown);\n return () => window.removeEventListener('keydown', onKeyDown);\n }, [containerRef, handleRef, scheduleRender]);\n\n // True while a local pointer (hover/press) owns the crosshair. Lets the\n // crosshair-override effect below know to stand down (local input wins).\n const localCrosshairActiveRef = useRef(false);\n\n // Reset the in-progress draft whenever draw+line mode is not active (e.g. the\n // host toggled the tool off mid-draw), so re-entering draw mode starts clean.\n useEffect(() => {\n const active = opts.mode === 'draw' && opts.tool === 'line';\n if (active) return;\n drawAnchorRef.current = null;\n const h = handleRef.current;\n if (h) {\n h.clearDraft();\n scheduleRender();\n }\n }, [opts.mode, opts.tool, handleRef, scheduleRender]);\n\n // Apply the controlled crosshair override (cross-chart sync). A local\n // hover/press always wins, so we stand down while one is active; the override\n // is (re)applied on local release inside hideCrosshair. This path is silent —\n // it never calls onCrosshair — so two charts driving each other can't loop.\n // (If the override is set before the WASM handle finishes loading it applies\n // on its next change; in the sync use case the initial value is null.)\n useEffect(() => {\n const h = handleRef.current;\n if (!h || localCrosshairActiveRef.current) return;\n const ov = opts.crosshairOverride;\n if (ov) h.setCrosshairData(ov.timeMs, ov.price);\n else h.clearCrosshair();\n scheduleRender();\n }, [opts.crosshairOverride, handleRef, scheduleRender]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const pointers = new Map<number, { x: number; y: number }>();\n let panMode: Region = 'chart';\n let crosshairActive = false;\n let crosshairSource: 'press' | 'hover' | null = null;\n let lastCrosshairTime: number | null = null;\n let lastCrosshairPrice: number | null = null;\n let longPressTimer: ReturnType<typeof setTimeout> | null = null;\n let downX = 0;\n let downY = 0;\n let moved = false;\n // Shift-constrain state: whether Shift is held, and the last cursor position\n // (so a Shift press/release with the mouse still can re-snap live).\n let shiftHeld = false;\n let lastX = 0;\n let lastY = 0;\n const pinch = { spanX: 1, spanY: 1, ratioX: 1, ratioY: 1, enableX: false, enableY: false, active: false };\n\n const rel = (e: PointerEvent | WheelEvent) => {\n const r = el.getBoundingClientRect();\n return { x: e.clientX - r.left, y: e.clientY - r.top };\n };\n\n // Data coord to use for a moving point at pixel (x,y) relative to `fixed`.\n // With Shift held, snaps to the nearest 45° *screen* angle from `fixed`.\n const snapToLine = (\n fixed: DrawPoint,\n x: number,\n y: number,\n ): { timeMs: number; price: number } | null => {\n const h = handleRef.current;\n if (!h) return null;\n const free = h.coordAt(x, y);\n if (!shiftHeld || !free) return free;\n const f = h.project(fixed.timeMs, fixed.price);\n if (!f) return free;\n const dx = x - f.x;\n const dy = y - f.y;\n if (dx === 0 && dy === 0) return free;\n const step = Math.PI / 4;\n const ang = Math.round(Math.atan2(dy, dx) / step) * step; // nearest 45°\n const ux = Math.cos(ang);\n const uy = Math.sin(ang);\n const len = dx * ux + dy * uy; // project the cursor onto the snapped ray\n return h.coordAt(f.x + len * ux, f.y + len * uy) ?? free;\n };\n\n const regionAt = (x: number, y: number): Region => {\n const h = handleRef.current;\n const r = el.getBoundingClientRect();\n if (!h) return 'chart';\n const { yAxisWidth, xAxisHeight, indicatorHeight } = h.getAxisMetrics();\n if (x > r.width - yAxisWidth) {\n // The y-axis strip beside an indicator pane scales that pane's y-axis;\n // the rest of the strip scales the main price axis.\n const priceBottom = r.height - xAxisHeight - indicatorHeight;\n if (indicatorHeight > 0 && y > priceBottom && y < r.height - xAxisHeight) {\n return 'indicator-axis';\n }\n return 'price-axis';\n }\n // Separator sits at the top edge of the indicator band, within the candle\n // area width — a narrow grab band for resizing the panes.\n const sepY = r.height - xAxisHeight - indicatorHeight;\n if (indicatorHeight > 0 && x <= r.width - yAxisWidth && Math.abs(y - sepY) <= SEP_HIT) {\n return 'separator';\n }\n if (y > r.height - xAxisHeight) return 'time-axis';\n if (indicatorHeight > 0 && y > r.height - xAxisHeight - indicatorHeight) return 'indicator';\n return 'chart';\n };\n\n const reportCrosshair = (reason: CrosshairEvent['reason']) => {\n const h = handleRef.current;\n if (!h) return;\n // Snapped slot — has a timeMs even in the empty space ahead of the last\n // candle, where `candle` is null.\n const info = h.getCrosshairInfo();\n const t = info?.timeMs ?? null;\n const p = info?.price ?? null;\n // Fire on any positional change — a different candle (time) OR a vertical\n // move within the same candle (price). Deduping on time alone would freeze\n // the reported price mid-candle, which breaks cross-chart price sync.\n if (reason === 'move' && t === lastCrosshairTime && p === lastCrosshairPrice) return;\n lastCrosshairTime = t;\n lastCrosshairPrice = p;\n optsRef.current.onCrosshair?.({\n active: reason !== 'hide',\n candle: info?.candle ?? null,\n timeMs: t,\n price: reason === 'hide' ? null : p,\n reason,\n });\n };\n\n const clearLongPress = () => {\n if (longPressTimer != null) {\n clearTimeout(longPressTimer);\n longPressTimer = null;\n }\n };\n\n const showCrosshair = (x: number, y: number, source: 'press' | 'hover', reason: 'show' | 'move') => {\n const h = handleRef.current;\n if (!h) return;\n crosshairActive = true;\n localCrosshairActiveRef.current = true;\n crosshairSource = source;\n const lift = source === 'press' ? optsRef.current.crosshairOffset : 0;\n h.setCrosshair(x, y - lift);\n scheduleRender();\n reportCrosshair(reason);\n };\n\n const hideCrosshair = () => {\n const h = handleRef.current;\n if (!h || !crosshairActive) return;\n crosshairActive = false;\n localCrosshairActiveRef.current = false;\n crosshairSource = null;\n // Fall back to the synced crosshair (if any) rather than clearing, so\n // releasing a local hover on a follower chart restores the driver's line.\n const ov = optsRef.current.crosshairOverride;\n if (ov) h.setCrosshairData(ov.timeMs, ov.price);\n else h.clearCrosshair();\n scheduleRender();\n reportCrosshair('hide');\n };\n\n // True while the line tool should own input (suppress pan/zoom/crosshair).\n const drawActive = () =>\n optsRef.current.mode === 'draw' && optsRef.current.tool === 'line';\n\n // While placing the second point, keep the guideline glued to the cursor.\n const updateGuideline = (x: number, y: number) => {\n const h = handleRef.current;\n if (!h) return;\n const a = drawAnchorRef.current;\n if (!a) return;\n const c = snapToLine(a, x, y);\n if (!c) return;\n h.setDraft(a.timeMs, a.price, true, c.timeMs, c.price, true, DRAW_COLOR, DRAW_WIDTH);\n scheduleRender();\n };\n\n // Line tool tap: place the first point, then commit on the second.\n const handleDrawClick = (x: number, y: number) => {\n const h = handleRef.current;\n if (!h) return;\n\n if (!drawAnchorRef.current) {\n // First point: show node A; the guideline follows on the next move.\n const coord = h.coordAt(x, y);\n if (!coord) return;\n drawAnchorRef.current = coord;\n h.setDraft(coord.timeMs, coord.price, false, 0, 0, true, DRAW_COLOR, DRAW_WIDTH);\n scheduleRender();\n } else {\n // Second point: commit the line (Shift-snapped to match the guideline)\n // and clear the draft. Editing happens in pan mode via the committed line.\n const a = drawAnchorRef.current;\n const coord = snapToLine(a, x, y);\n if (!coord) return;\n optsRef.current.onDrawingComplete?.({\n id: drawingId(),\n type: 'line',\n points: [\n { timeMs: a.timeMs, price: a.price },\n { timeMs: coord.timeMs, price: coord.price },\n ],\n });\n drawAnchorRef.current = null;\n h.clearDraft();\n scheduleRender();\n }\n };\n\n // Reposition the grabbed endpoint at pixel (x,y), Shift-snapping to 45°\n // relative to the fixed (other) endpoint.\n const moveGrab = (x: number, y: number) => {\n const h = handleRef.current;\n const g = grabRef.current;\n if (!h || !g) return;\n const c = snapToLine(g.fixed, x, y);\n if (!c) return;\n lastCoordRef.current = c;\n h.moveDrawingEndpoint(g.index, g.endpoint, c.timeMs, c.price);\n scheduleRender();\n };\n\n const onPointerDown = (e: PointerEvent) => {\n const h = handleRef.current;\n if (!h) return;\n if (e.pointerType === 'mouse' && e.button !== 0) return; // left button only\n const { x, y } = rel(e);\n shiftHeld = e.shiftKey;\n lastX = x;\n lastY = y;\n el.setPointerCapture(e.pointerId);\n pointers.set(e.pointerId, { x, y });\n\n if (pointers.size === 2) {\n clearLongPress();\n hideCrosshair();\n const pts = [...pointers.values()];\n const sx = Math.abs(pts[0]!.x - pts[1]!.x);\n const sy = Math.abs(pts[0]!.y - pts[1]!.y);\n pinch.spanX = sx;\n pinch.spanY = sy;\n pinch.ratioX = 1;\n pinch.ratioY = 1;\n pinch.enableX = sx >= MIN_SPAN && sx >= sy * AXIS_RATIO;\n pinch.enableY = sy >= MIN_SPAN && sy >= sx * AXIS_RATIO;\n pinch.active = true;\n return;\n }\n\n // Single pointer: classify region; arm long-press → crosshair (touch/pen).\n downX = x;\n downY = y;\n moved = false;\n panMode = regionAt(x, y);\n\n // Editing (pan mode, chart area): grab a handle to drag it now; otherwise\n // record the hit so a stationary release can select a body / deselect.\n downHitRef.current = null;\n if (!drawActive() && panMode === 'chart') {\n const hit = h.hitTestDrawing(x, y);\n if (hit && (hit.part === 0 || hit.part === 1)) {\n const gd = (optsRef.current.drawings ?? [])[hit.index];\n if (gd) {\n grabRef.current = {\n index: hit.index,\n endpoint: hit.part,\n fixed: gd.points[hit.part === 0 ? 1 : 0], // the other end stays put\n };\n lastCoordRef.current = null;\n applySelection(); // enlarge the grabbed handle\n return; // the handle drag owns this pointer — no pan / long-press\n }\n }\n // Body of the already-selected line → translate the whole line on drag.\n if (hit && hit.part === 2) {\n const list = optsRef.current.drawings ?? [];\n const selIdx = selectedIdRef.current\n ? list.findIndex((d) => d.id === selectedIdRef.current)\n : -1;\n if (hit.index === selIdx && selIdx >= 0) {\n const start = h.coordAt(x, y);\n const d = list[selIdx];\n if (start && d) {\n dragLineRef.current = {\n index: selIdx,\n startTime: start.timeMs,\n startPrice: start.price,\n a0: d.points[0],\n b0: d.points[1],\n lastA: d.points[0],\n lastB: d.points[1],\n };\n el.style.cursor = 'move';\n return; // the line drag owns this pointer — no pan / long-press\n }\n }\n }\n downHitRef.current = hit; // non-selected body or miss → resolved on release\n }\n\n if (e.pointerType !== 'mouse' && panMode === 'chart' && !drawActive()) {\n longPressTimer = setTimeout(() => {\n longPressTimer = null;\n if (!moved) showCrosshair(downX, downY, 'press', 'show');\n }, LONG_PRESS_MS);\n }\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const h = handleRef.current;\n if (!h) return;\n const { x, y } = rel(e);\n shiftHeld = e.shiftKey;\n lastX = x;\n lastY = y;\n\n // Hover (mouse, no button) → crosshair follows the cursor on the chart.\n if (!pointers.has(e.pointerId)) {\n if (e.pointerType === 'mouse' && pointers.size === 0) {\n // Draw mode: no crosshair; the guideline tracks the cursor instead.\n if (drawActive()) {\n el.style.cursor = 'crosshair';\n updateGuideline(x, y);\n return;\n }\n const region = regionAt(x, y);\n el.style.cursor =\n region === 'separator'\n ? 'row-resize'\n : region === 'indicator-axis'\n ? 'ns-resize'\n : '';\n if (region === 'chart') {\n showCrosshair(x, y, 'hover', crosshairActive ? 'move' : 'show');\n } else {\n hideCrosshair();\n }\n }\n return;\n }\n\n const prev = pointers.get(e.pointerId)!;\n const dx = x - prev.x;\n const dy = y - prev.y;\n pointers.set(e.pointerId, { x, y });\n\n // Handle drag (pan-mode editing): reposition the grabbed endpoint live,\n // with no move threshold, and never pan.\n if (grabRef.current) {\n moveGrab(x, y);\n return;\n }\n\n // Line translate (pan-mode editing): shift both endpoints by the pointer's\n // data-space delta, live, no threshold, never pan.\n if (dragLineRef.current) {\n const g = dragLineRef.current;\n const c = h.coordAt(x, y);\n if (c) {\n const dT = c.timeMs - g.startTime;\n const dP = c.price - g.startPrice;\n g.lastA = { timeMs: g.a0.timeMs + dT, price: g.a0.price + dP };\n g.lastB = { timeMs: g.b0.timeMs + dT, price: g.b0.price + dP };\n h.moveDrawingEndpoint(g.index, 0, g.lastA.timeMs, g.lastA.price);\n h.moveDrawingEndpoint(g.index, 1, g.lastB.timeMs, g.lastB.price);\n if (!moved && Math.hypot(x - downX, y - downY) > MOVE_THRESH) moved = true;\n scheduleRender();\n }\n return;\n }\n\n // Two-finger directional pinch (disabled while drawing).\n if (pointers.size >= 2 && pinch.active && !drawActive()) {\n const pts = [...pointers.values()];\n const focalX = (pts[0]!.x + pts[1]!.x) / 2;\n const focalY = (pts[0]!.y + pts[1]!.y) / 2;\n let frameX = 1;\n if (pinch.enableX) {\n const r = Math.max(Math.abs(pts[0]!.x - pts[1]!.x), MIN_SPAN) / pinch.spanX;\n frameX = r / pinch.ratioX;\n pinch.ratioX = r;\n }\n let frameY = 1;\n if (pinch.enableY) {\n const r = Math.max(Math.abs(pts[0]!.y - pts[1]!.y), MIN_SPAN) / pinch.spanY;\n frameY = r / pinch.ratioY;\n pinch.ratioY = r;\n }\n if (frameX !== 1 || frameY !== 1) {\n h.zoom(frameX, frameY, focalX, focalY);\n scheduleRender();\n }\n return;\n }\n\n if (!moved && Math.hypot(x - downX, y - downY) > MOVE_THRESH) {\n moved = true;\n clearLongPress();\n }\n if (!moved) return;\n\n // Draw mode: a press-drag neither pans nor moves a crosshair; if a point\n // is down, keep the guideline tracking the cursor so a drag still previews.\n if (drawActive()) {\n if (panMode === 'chart') updateGuideline(x, y);\n return;\n }\n\n // Chart-area drag with the (press) crosshair up moves the crosshair.\n if (crosshairActive && crosshairSource === 'press' && panMode === 'chart') {\n showCrosshair(x, y, 'press', 'move');\n return;\n }\n\n if (panMode === 'price-axis') h.scalePriceAxis(dy);\n else if (panMode === 'time-axis') h.scaleTimeAxis(dx);\n else if (panMode === 'separator') h.resizeIndicatorPane(dy);\n else if (panMode === 'indicator-axis') h.scaleIndicatorAxis(downY, dy);\n else if (panMode === 'indicator') h.pan(dx, 0);\n else h.translate(dx, dy);\n\n // Keep a hover crosshair glued to the cursor while dragging — the chart\n // pans under it, so re-snap to whatever candle is now beneath the cursor.\n if (crosshairActive && crosshairSource === 'hover') {\n h.setCrosshair(x, y);\n reportCrosshair('move');\n }\n scheduleRender();\n };\n\n const endPointer = (e: PointerEvent) => {\n shiftHeld = e.shiftKey; // so a Shift-held draw commit matches the guideline\n const had = pointers.delete(e.pointerId);\n clearLongPress();\n if (pointers.size < 2) pinch.active = false;\n if (!had) return;\n\n // Handle-drag release (editing): persist the moved endpoint, keep it\n // selected, and un-grab (drops the 50% enlarge).\n if (grabRef.current && pointers.size === 0) {\n const g = grabRef.current;\n grabRef.current = null;\n const list = optsRef.current.drawings ?? [];\n const d = list[g.index];\n const c = lastCoordRef.current;\n if (d && c) {\n const pts: [DrawPoint, DrawPoint] = [d.points[0], d.points[1]];\n pts[g.endpoint] = { timeMs: c.timeMs, price: c.price };\n optsRef.current.onDrawingChange?.({ ...d, points: pts });\n }\n applySelection();\n el.style.cursor = '';\n return;\n }\n\n // Line-translate release: persist the moved line, keep it selected.\n if (dragLineRef.current && pointers.size === 0) {\n const g = dragLineRef.current;\n dragLineRef.current = null;\n if (moved) {\n const list = optsRef.current.drawings ?? [];\n const d = list[g.index];\n if (d) optsRef.current.onDrawingChange?.({ ...d, points: [g.lastA, g.lastB] });\n }\n applySelection();\n el.style.cursor = '';\n return;\n }\n\n // Draw mode: a stationary tap in the chart places a point (or exits on\n // click-away). Drags are ignored (no pan). Crosshair logic is skipped.\n if (drawActive() && pointers.size === 0) {\n if (!moved && panMode === 'chart') handleDrawClick(downX, downY);\n else el.style.cursor = 'crosshair';\n return;\n }\n\n // Editing tap (pan mode): a stationary click on a line body selects it;\n // on empty space it deselects. (Skip a long-press, which owns the crosshair.)\n if (pointers.size === 0 && !moved && panMode === 'chart' && crosshairSource !== 'press') {\n const hit = downHitRef.current;\n const list = optsRef.current.drawings ?? [];\n if (hit && hit.part === 2 && list[hit.index]) {\n selectedTRef.current = hit.t; // remember where along the line it was grabbed\n if (selectedIdRef.current !== list[hit.index].id) {\n selectedIdRef.current = list[hit.index].id;\n applySelection();\n }\n } else if (!hit && selectedIdRef.current != null) {\n selectedIdRef.current = null;\n applySelection();\n }\n }\n\n // Press crosshair is active only while held — release dismisses it.\n if (crosshairSource === 'press' && pointers.size === 0) {\n hideCrosshair();\n } else if (moved && (panMode === 'chart' || panMode === 'indicator')) {\n optsRef.current.onViewportChange?.(0, 0);\n }\n if (pointers.size === 0) el.style.cursor = '';\n };\n\n const onPointerLeave = () => {\n if (crosshairSource === 'hover') hideCrosshair();\n el.style.cursor = '';\n };\n\n const onWheel = (e: WheelEvent) => {\n const h = handleRef.current;\n if (!h) return;\n e.preventDefault();\n // Draw mode: no pan/zoom from the wheel (keeps placed points stable).\n if (drawActive()) return;\n const { x, y } = rel(e);\n if (e.ctrlKey || e.metaKey) {\n // Trackpad pinch (sent as ctrl+wheel) / ctrl+wheel → zoom both axes.\n const f = Math.exp(-e.deltaY * WHEEL_K);\n h.zoom(f, f, x, y);\n } else if (e.shiftKey) {\n // Shift+wheel → horizontal pan (mouse-wheel-only users).\n h.pan(-(e.deltaX || e.deltaY), 0);\n } else if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {\n // Horizontal scroll → pan the time axis (scroll right = forward in time).\n h.pan(-e.deltaX, 0);\n } else {\n // Vertical scroll → zoom the time window around the cursor.\n const f = Math.exp(-e.deltaY * WHEEL_K);\n h.zoom(f, 1, x, y);\n }\n scheduleRender();\n };\n\n // Shift press/release re-applies the 45° constraint live at the last cursor\n // position — even with the mouse still — while drawing or dragging a handle.\n const onShiftKey = (e: KeyboardEvent) => {\n if (e.key !== 'Shift') return;\n const held = e.type === 'keydown';\n if (held === shiftHeld) return;\n shiftHeld = held;\n if (drawAnchorRef.current) updateGuideline(lastX, lastY);\n else if (grabRef.current) moveGrab(lastX, lastY);\n };\n\n el.addEventListener('pointerdown', onPointerDown);\n el.addEventListener('pointermove', onPointerMove);\n el.addEventListener('pointerup', endPointer);\n el.addEventListener('pointercancel', endPointer);\n el.addEventListener('pointerleave', onPointerLeave);\n el.addEventListener('wheel', onWheel, { passive: false });\n window.addEventListener('keydown', onShiftKey);\n window.addEventListener('keyup', onShiftKey);\n\n return () => {\n clearLongPress();\n el.removeEventListener('pointerdown', onPointerDown);\n el.removeEventListener('pointermove', onPointerMove);\n el.removeEventListener('pointerup', endPointer);\n el.removeEventListener('pointercancel', endPointer);\n el.removeEventListener('pointerleave', onPointerLeave);\n el.removeEventListener('wheel', onWheel);\n window.removeEventListener('keydown', onShiftKey);\n window.removeEventListener('keyup', onShiftKey);\n };\n }, [containerRef, handleRef, scheduleRender]);\n}\n","// Managed drawing persistence. When a `drawingStore` is provided, the chart owns\n// the drawings array itself and loads/saves it through the store, keyed by\n// `seriesKey` (the market). Drawings are data-space anchored, so keying by market\n// means they persist across timeframe changes but not across markets.\n//\n// Returns the internal drawings plus the three edit handlers to feed into the\n// gesture layer (in place of the controlled props). A no-op when `store` is\n// undefined, so it can be called unconditionally (rules of hooks).\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport type { Drawing, DrawingStore } from '@vroomchart/types';\n\nimport { serializeDrawings, deserializeDrawings } from './drawingStorage';\n\nconst SAVE_DEBOUNCE_MS = 400;\n\nexport type ManagedDrawings = {\n drawings: Drawing[];\n onDrawingComplete: (d: Drawing) => void;\n onDrawingChange: (d: Drawing) => void;\n onDrawingDelete: (id: string) => void;\n};\n\nexport function useManagedDrawings(\n seriesKey: string | undefined,\n store: DrawingStore | undefined,\n): ManagedDrawings {\n const [drawings, setDrawings] = useState<Drawing[]>([]);\n\n // Latest-committed refs so the load effect / debounce read current values\n // without re-subscribing.\n const drawingsRef = useRef<Drawing[]>([]);\n const storeRef = useRef(store);\n storeRef.current = store;\n // The market whose drawings are currently in state — so we never save one\n // market's drawings under another's key during an async load race.\n const loadedKeyRef = useRef<string | undefined>(undefined);\n const loadTokenRef = useRef(0);\n const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const warnedRef = useRef(false);\n\n const setBoth = (list: Drawing[]) => {\n drawingsRef.current = list;\n setDrawings(list);\n };\n\n // Persist immediately (cancelling any pending debounce). Best-effort.\n const flushSave = useCallback((key: string | undefined, list: Drawing[]) => {\n if (saveTimerRef.current != null) {\n clearTimeout(saveTimerRef.current);\n saveTimerRef.current = null;\n }\n const s = storeRef.current;\n if (!s || key == null) return;\n try {\n // The store is an opaque string KV — the library owns the versioned\n // envelope, so serialize here and hand over bytes.\n const r = s.save(key, serializeDrawings(list));\n if (r && typeof (r as Promise<void>).catch === 'function') {\n (r as Promise<void>).catch(() => {});\n }\n } catch {\n /* best-effort */\n }\n }, []);\n\n const scheduleSave = useCallback(() => {\n const key = seriesKey;\n if (!storeRef.current || key == null) return;\n if (saveTimerRef.current != null) clearTimeout(saveTimerRef.current);\n saveTimerRef.current = setTimeout(() => {\n saveTimerRef.current = null;\n flushSave(key, drawingsRef.current);\n }, SAVE_DEBOUNCE_MS);\n }, [seriesKey, flushSave]);\n\n // Load on mount / market change; flush-save the outgoing market on the way out.\n useEffect(() => {\n const s = storeRef.current;\n if (!s) return;\n if (seriesKey == null) {\n if (!warnedRef.current && typeof console !== 'undefined') {\n console.warn(\n '[vroom] drawingStore is set but seriesKey is missing — drawings will render but not persist.',\n );\n warnedRef.current = true;\n }\n setBoth([]);\n loadedKeyRef.current = undefined;\n return;\n }\n\n const token = ++loadTokenRef.current;\n const result = s.load(seriesKey);\n if (result && typeof (result as Promise<string | null>).then === 'function') {\n // Async: clear now so stale drawings don't show, apply when resolved.\n setBoth([]);\n loadedKeyRef.current = undefined;\n (result as Promise<string | null | undefined>)\n .then((raw) => {\n if (token !== loadTokenRef.current) return; // superseded by a newer switch\n loadedKeyRef.current = seriesKey;\n setBoth(deserializeDrawings(raw));\n })\n .catch(() => {\n if (token !== loadTokenRef.current) return;\n loadedKeyRef.current = seriesKey;\n setBoth([]);\n });\n } else {\n loadedKeyRef.current = seriesKey;\n setBoth(deserializeDrawings(result as string | null | undefined));\n }\n\n // On the next market switch (or unmount), save this market's drawings — but\n // only if the in-memory set actually belongs to this key (async-load guard).\n return () => {\n if (loadedKeyRef.current === seriesKey) {\n flushSave(seriesKey, drawingsRef.current);\n } else if (saveTimerRef.current != null) {\n clearTimeout(saveTimerRef.current);\n saveTimerRef.current = null;\n }\n };\n }, [seriesKey, flushSave]);\n\n const onDrawingComplete = useCallback(\n (d: Drawing) => {\n setDrawings((p) => {\n const next = [...p, d];\n drawingsRef.current = next;\n return next;\n });\n scheduleSave();\n },\n [scheduleSave],\n );\n const onDrawingChange = useCallback(\n (d: Drawing) => {\n setDrawings((p) => {\n const next = p.map((x) => (x.id === d.id ? d : x));\n drawingsRef.current = next;\n return next;\n });\n scheduleSave();\n },\n [scheduleSave],\n );\n const onDrawingDelete = useCallback(\n (id: string) => {\n setDrawings((p) => {\n const next = p.filter((x) => x.id !== id);\n drawingsRef.current = next;\n return next;\n });\n scheduleSave();\n },\n [scheduleSave],\n );\n\n return { drawings, onDrawingComplete, onDrawingChange, onDrawingDelete };\n}\n","// Versioned envelope for persisted drawings. The `DrawingStore` adapter is an\n// opaque string key-value store; this module owns everything inside that string\n// so the adapter never has to change as the schema evolves:\n//\n// drawings ──serialize──▶ '{\"v\":1,\"drawings\":[…]}' ──store.save──▶ bytes\n// bytes ──store.load──▶ string ──deserialize (+migrate)──▶ drawings\n//\n// Evolving the schema without breaking clients:\n// • New drawing tool → add its `type` to KNOWN_TYPES + the Drawing union.\n// Old payloads still parse; payloads carrying an unknown tool (e.g. written\n// by a newer version, then read by an older one) are dropped, not crashed.\n// • New persisted field → add it to the envelope, bump DRAWINGS_VERSION, and\n// register a MIGRATIONS[oldVersion] that upgrades an old envelope to the new\n// shape. Old stored strings migrate transparently on the next load.\n// The adapter interface (string in / string out) stays fixed through all of it.\n\nimport type { Drawing } from '@vroomchart/types';\n\n/** Current envelope schema version. Bump when the persisted shape changes. */\nexport const DRAWINGS_VERSION = 1;\n\n/** Drawing `type`s this build understands. Extend as tools are added. */\nconst KNOWN_TYPES = new Set<Drawing['type']>(['line']);\n\ntype Envelope = { v: number; drawings: Drawing[] };\n\n/**\n * Migration map: `MIGRATIONS[n]` upgrades a version-`n` envelope to version\n * `n+1`. Applied in sequence until the payload reaches DRAWINGS_VERSION, so a\n * very old blob walks up one step at a time. Add an entry each time the schema\n * changes; never mutate the input.\n *\n * Example (when v2 arrives):\n * [1]: (e) => ({ v: 2, drawings: e.drawings.map((d) => ({ ...d, opacity: 1 })) }),\n */\nconst MIGRATIONS: Record<number, (e: Envelope) => Envelope> = {};\n\n/** Wrap the current drawings in a versioned envelope and stringify. */\nexport function serializeDrawings(drawings: Drawing[]): string {\n const env: Envelope = { v: DRAWINGS_VERSION, drawings };\n return JSON.stringify(env);\n}\n\n/**\n * Parse a stored string back into drawings, migrating older envelopes and\n * dropping anything malformed or of an unknown drawing `type`. Never throws —\n * returns `[]` for null/empty/garbage input.\n */\nexport function deserializeDrawings(raw: string | null | undefined): Drawing[] {\n if (raw == null || raw === '') return [];\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return [];\n }\n\n // Accept a bare array as the pre-envelope v0 shape, so payloads written before\n // versioning (or by a hand-rolled adapter) still load and get upgraded.\n let env: Envelope;\n if (Array.isArray(parsed)) {\n env = { v: 0, drawings: parsed as Drawing[] };\n } else if (parsed != null && typeof parsed === 'object') {\n const obj = parsed as Partial<Envelope>;\n env = {\n v: typeof obj.v === 'number' ? obj.v : 0,\n drawings: Array.isArray(obj.drawings) ? obj.drawings : [],\n };\n } else {\n return [];\n }\n\n // Walk the migration chain up to the current version. Stop (keep what we have)\n // if a step is missing or the payload is somehow newer than we understand.\n let guard = 0;\n while (env.v < DRAWINGS_VERSION && guard++ < 100) {\n const up = MIGRATIONS[env.v];\n if (!up) break;\n env = up(env);\n }\n\n // Forward-compat: keep only drawings whose `type` this build renders. A newer\n // file with an unknown tool loads cleanly minus that tool, rather than\n // throwing or drawing garbage.\n return (Array.isArray(env.drawings) ? env.drawings : []).filter(\n (d): d is Drawing =>\n d != null && typeof d === 'object' && KNOWN_TYPES.has((d as Drawing).type),\n );\n}\n","// VroomChart (web) — DOM React component that renders the candlestick chart to\n// a <canvas> via @vroomchart/core-wasm and wires pointer/wheel gestures. API-matched\n// to the React Native component (same props from @vroomchart/types).\n\nimport React from 'react';\nimport type { CSSProperties } from 'react';\n\nimport { useChartCore } from './useChartCore';\nimport { useGestures } from './useGestures';\nimport { useManagedDrawings } from './useManagedDrawings';\nimport type { VroomChartProps } from './types';\n\nconst FILL: CSSProperties = { position: 'relative', width: '100%', height: '100%' };\nconst CANVAS_STYLE: CSSProperties = {\n display: 'block',\n width: '100%',\n height: '100%',\n touchAction: 'none', // we own pan/zoom; don't let the browser scroll/zoom\n};\n\n/**\n * Skia-quality candlestick chart for the web. Pass OHLCV `candles` and size it\n * via `style`/`width`/`height` (it fills its parent by default). Drag to pan,\n * pinch or wheel to zoom, drag the price/time axes to rescale, hover (mouse) or\n * long-press (touch) for the crosshair. Optional indicators (`rsi`, `macd`,\n * `movingAverages`, `vwap`), colors (`theme`), and events (`onCrosshair`,\n * `onViewportChange`) are configured through props.\n *\n * @see {@link VroomChartProps} for the full prop reference.\n */\nexport function VroomChart(props: VroomChartProps) {\n const {\n className,\n style,\n wasm,\n crosshairOffset = 40,\n crosshairOverride,\n mode,\n tool,\n drawings,\n onCrosshair,\n onViewportChange,\n onDrawingComplete,\n onDrawingChange,\n onDrawingDelete,\n onModeChange,\n drawingStore,\n seriesKey,\n } = props;\n\n // Managed persistence: when a `drawingStore` is provided the chart owns the\n // drawings array itself (keyed by seriesKey) instead of the controlled props.\n // The hook is always called (rules of hooks) but no-ops without a store.\n const managed = useManagedDrawings(seriesKey, drawingStore);\n const stored = drawingStore != null;\n const effectiveDrawings = stored ? managed.drawings : drawings;\n\n const { containerRef, canvasRef, handleRef, scheduleRender } = useChartCore(\n stored ? { ...props, drawings: effectiveDrawings } : props,\n wasm ? { wasm } : undefined,\n );\n\n useGestures(containerRef, handleRef, scheduleRender, {\n crosshairOffset,\n crosshairOverride,\n mode,\n tool,\n drawings: effectiveDrawings,\n onCrosshair,\n onViewportChange,\n onDrawingComplete: stored ? managed.onDrawingComplete : onDrawingComplete,\n onDrawingChange: stored ? managed.onDrawingChange : onDrawingChange,\n onDrawingDelete: stored ? managed.onDrawingDelete : onDrawingDelete,\n onRequestMode: onModeChange,\n });\n\n const rootStyle: CSSProperties = {\n ...FILL,\n ...(props.width != null ? { width: props.width } : null),\n ...(props.height != null ? { height: props.height } : null),\n ...style,\n };\n\n return (\n <div ref={containerRef} className={className} style={rootStyle}>\n <canvas ref={canvasRef} style={CANVAS_STYLE} />\n </div>\n );\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useCallback","useEffect","useRef","useState","useState","useRef","useCallback","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vroomchart/react",
3
- "version": "0.1.7",
3
+ "version": "0.3.0",
4
4
  "description": "Skia-quality candlestick chart for the web (React DOM).",
5
5
  "license": "MIT",
6
6
  "author": "Darion Welch",
@@ -40,7 +40,7 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
- "@vroomchart/core-wasm": "0.1.5"
43
+ "@vroomchart/core-wasm": "0.3.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "react": ">=18",