@vroomchart/react 0.3.0 → 0.5.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
@@ -94,6 +94,10 @@ type VroomTheme = {
94
94
  crosshair?: VroomColor;
95
95
  /** Crosshair target — the hollow ring/dot at the intersection. */
96
96
  crosshairTarget?: VroomColor;
97
+ /** Line-chart-mode close polyline color. Defaults to a neutral foreground. */
98
+ lineColor?: VroomColor;
99
+ /** Line-chart-mode polyline stroke width in px. Defaults to 1.5. */
100
+ lineWidth?: number;
97
101
  };
98
102
  /** A time window over the candle data, as Unix epoch milliseconds. */
99
103
  type VisibleRange = {
@@ -108,8 +112,15 @@ type VisibleRange = {
108
112
  * 'draw' — left-clicks place drawing points; panning/zooming are suppressed.
109
113
  */
110
114
  type ChartMode = 'pan' | 'draw';
115
+ /**
116
+ * How the price series is drawn.
117
+ * 'candles' — default: candlestick bodies + wicks.
118
+ * 'line' — a single polyline through each candle's close. Volume, indicators,
119
+ * overlays, crosshair, and drawings still render.
120
+ */
121
+ type ChartType = 'candles' | 'line';
111
122
  /** Active drawing tool while in `draw` mode. `null` draws nothing. */
112
- type DrawTool = null | 'line';
123
+ type DrawTool = null | 'line' | 'box' | 'pencil';
113
124
  /** A drawing anchor in data space, so it stays glued to the candles on pan/zoom. */
114
125
  type DrawPoint = {
115
126
  /** Anchor time as Unix epoch milliseconds (not snapped to a candle slot). */
@@ -117,23 +128,50 @@ type DrawPoint = {
117
128
  /** Anchor price. */
118
129
  price: number;
119
130
  };
120
- /**
121
- * A committed drawing. Pass an array of these via the `drawings` prop to render
122
- * persisted annotations; the chart appends a new one (via `onDrawingComplete`)
123
- * each time the user finishes drawing. For now only the `'line'` (two-point
124
- * trendline) type exists.
125
- */
126
- type Drawing = {
131
+ /** Fields shared by every drawing type. */
132
+ type DrawingBase = {
127
133
  /** Stable unique id (the chart generates one for drawings it creates). */
128
134
  id: string;
129
- type: 'line';
130
- /** The two endpoints, in data space. */
131
- points: [DrawPoint, DrawPoint];
132
- /** Line color (hex string or packed ARGB number). Default solid blue. */
135
+ /** Stroke color (hex string or packed ARGB number). Default solid blue. */
133
136
  color?: VroomColor;
134
137
  /** Stroke width in px. Default 2. */
135
138
  width?: number;
136
139
  };
140
+ /** A two-point trendline from `points[0]` to `points[1]`. */
141
+ type LineDrawing = DrawingBase & {
142
+ type: 'line';
143
+ /** The two endpoints, in data space. */
144
+ points: [DrawPoint, DrawPoint];
145
+ };
146
+ /**
147
+ * An axis-aligned rectangle whose two opposite corners are `points[0]` and
148
+ * `points[1]` (the other two corners are derived).
149
+ */
150
+ type BoxDrawing = DrawingBase & {
151
+ type: 'box';
152
+ /** Two opposite corners, in data space. */
153
+ points: [DrawPoint, DrawPoint];
154
+ };
155
+ /**
156
+ * A freehand pencil stroke: an open path through `points`, in order. Unlike the
157
+ * other tools a stroke has a variable number of points, and once committed it
158
+ * can only be translated — never reshaped.
159
+ */
160
+ type PencilDrawing = DrawingBase & {
161
+ type: 'pencil';
162
+ /** The path's points in draw order (at least 2), in data space. */
163
+ points: DrawPoint[];
164
+ };
165
+ /**
166
+ * A committed drawing. Pass an array of these via the `drawings` prop to render
167
+ * persisted annotations; the chart appends a new one (via `onDrawingComplete`)
168
+ * each time the user finishes drawing.
169
+ *
170
+ * This is a discriminated union on `type` — narrow on it before reading
171
+ * `points[1]`, since a `'pencil'` stroke has a variable-length array while
172
+ * `'line'` and `'box'` are always exactly two points.
173
+ */
174
+ type Drawing = LineDrawing | BoxDrawing | PencilDrawing;
137
175
  /**
138
176
  * Storage adapter for **managed** drawing persistence. Provide it via the
139
177
  * `drawingStore` prop and the chart owns the drawings array itself — loading and
@@ -298,6 +336,18 @@ type VroomChartCoreProps = {
298
336
  * devices of different widths.
299
337
  */
300
338
  defaultCandleWidth?: number;
339
+ /**
340
+ * Price-series render style. `'candles'` (default) draws candlesticks;
341
+ * `'line'` draws a polyline through each candle's close (style it with
342
+ * `theme.lineColor` / `theme.lineWidth`). All other layers are unaffected.
343
+ */
344
+ chartType?: ChartType;
345
+ /**
346
+ * Duration (ms) of the animated candle↔line transition when `chartType`
347
+ * changes. Default ~300. `0` snaps instantly. Ignored (snaps) when the OS
348
+ * requests reduced motion, which instead uses a plain cross-fade.
349
+ */
350
+ transitionMs?: number;
301
351
  theme?: VroomTheme;
302
352
  /** RSI indicator (pane below the candles). Omit/disable to hide it. */
303
353
  rsi?: RSIConfig;
@@ -434,4 +484,4 @@ declare function serializeDrawings(drawings: Drawing[]): string;
434
484
  */
435
485
  declare function deserializeDrawings(raw: string | null | undefined): Drawing[];
436
486
 
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 };
487
+ export { type Candle, type ChartMode, type ChartType, 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
@@ -76,13 +76,17 @@ function overlayToNumeric(o) {
76
76
  };
77
77
  }
78
78
  function drawingToSpec(d) {
79
+ const first = d.points[0];
80
+ const last = d.points[d.points.length - 1];
79
81
  return {
80
- aTime: d.points[0].timeMs,
81
- aPrice: d.points[0].price,
82
- bTime: d.points[1].timeMs,
83
- bPrice: d.points[1].price,
82
+ aTime: first.timeMs,
83
+ aPrice: first.price,
84
+ bTime: last.timeMs,
85
+ bPrice: last.price,
84
86
  color: (d.color != null ? parseColor(d.color) : null) ?? 4280902399,
85
- width: d.width ?? 2
87
+ width: d.width ?? 2,
88
+ kind: d.type === "box" ? 1 : d.type === "pencil" ? 2 : 0,
89
+ ...d.type === "pencil" ? { points: d.points.map((p) => ({ timeMs: p.timeMs, price: p.price })) } : {}
86
90
  };
87
91
  }
88
92
  var DEFAULT_BUY_COLOR = 4280723098;
@@ -122,6 +126,8 @@ function useChartCore(props, loadOpts) {
122
126
  height: heightProp,
123
127
  visibleRange,
124
128
  defaultCandleWidth,
129
+ chartType,
130
+ transitionMs,
125
131
  theme,
126
132
  rsi,
127
133
  macd,
@@ -257,11 +263,103 @@ function useChartCore(props, loadOpts) {
257
263
  );
258
264
  scheduleRender();
259
265
  }, [ready, width, height, candles, seriesKey, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, drawingsKey, liquidityKey, scheduleRender]);
266
+ const morphRafRef = useRef(null);
267
+ const morphFadeRef = useRef(null);
268
+ const morphHandleRef = useRef(null);
269
+ useEffect(() => {
270
+ if (!ready) return;
271
+ const h = handleRef.current;
272
+ if (!h) return;
273
+ const target = chartType === "line" ? 1 : 0;
274
+ if (morphHandleRef.current !== h || morphFadeRef.current == null) {
275
+ morphHandleRef.current = h;
276
+ morphFadeRef.current = target;
277
+ h.setChartType(target);
278
+ scheduleRender();
279
+ return;
280
+ }
281
+ if (morphFadeRef.current === target) return;
282
+ const reduce = !!(typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
283
+ const dur = Math.max(0, transitionMs ?? 300);
284
+ if (morphRafRef.current != null) {
285
+ cancelAnimationFrame(morphRafRef.current);
286
+ morphRafRef.current = null;
287
+ }
288
+ if (dur === 0) {
289
+ morphFadeRef.current = target;
290
+ h.setChartType(target);
291
+ scheduleRender();
292
+ return;
293
+ }
294
+ const from = morphFadeRef.current;
295
+ const start = performance.now();
296
+ const step = (now) => {
297
+ const p = Math.min(1, (now - start) / dur);
298
+ const e = p * p * (3 - 2 * p);
299
+ const fade = from + (target - from) * e;
300
+ morphFadeRef.current = fade;
301
+ h.setMorph(reduce ? 0 : fade, fade);
302
+ h.present();
303
+ if (p < 1) {
304
+ morphRafRef.current = requestAnimationFrame(step);
305
+ } else {
306
+ morphRafRef.current = null;
307
+ morphFadeRef.current = target;
308
+ h.setChartType(target);
309
+ h.present();
310
+ }
311
+ };
312
+ morphRafRef.current = requestAnimationFrame(step);
313
+ return () => {
314
+ if (morphRafRef.current != null) {
315
+ cancelAnimationFrame(morphRafRef.current);
316
+ morphRafRef.current = null;
317
+ }
318
+ };
319
+ }, [ready, chartType, transitionMs, scheduleRender]);
260
320
  return { containerRef, canvasRef, handleRef, scheduleRender, size: { width, height } };
261
321
  }
262
322
 
263
323
  // src/useGestures.ts
264
324
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
325
+
326
+ // src/simplify.ts
327
+ function perpendicularDistance(p, a, b) {
328
+ const dx = b.x - a.x;
329
+ const dy = b.y - a.y;
330
+ if (dx === 0 && dy === 0) return Math.hypot(p.x - a.x, p.y - a.y);
331
+ return Math.abs(dy * p.x - dx * p.y + b.x * a.y - b.y * a.x) / Math.hypot(dx, dy);
332
+ }
333
+ function simplifyIndices(points, epsilon) {
334
+ const n = points.length;
335
+ if (n <= 2) return points.map((_, i) => i);
336
+ if (epsilon <= 0) return points.map((_, i) => i);
337
+ const keep = new Uint8Array(n);
338
+ keep[0] = 1;
339
+ keep[n - 1] = 1;
340
+ const stack = [[0, n - 1]];
341
+ while (stack.length > 0) {
342
+ const [start, end] = stack.pop();
343
+ if (end <= start + 1) continue;
344
+ let farthest = -1;
345
+ let maxDist = -1;
346
+ for (let i = start + 1; i < end; i++) {
347
+ const d = perpendicularDistance(points[i], points[start], points[end]);
348
+ if (d > maxDist) {
349
+ maxDist = d;
350
+ farthest = i;
351
+ }
352
+ }
353
+ if (maxDist <= epsilon || farthest < 0) continue;
354
+ keep[farthest] = 1;
355
+ stack.push([start, farthest], [farthest, end]);
356
+ }
357
+ const out = [];
358
+ for (let i = 0; i < n; i++) if (keep[i]) out.push(i);
359
+ return out;
360
+ }
361
+
362
+ // src/useGestures.ts
265
363
  var MIN_SPAN = 24;
266
364
  var AXIS_RATIO = 0.5;
267
365
  var LONG_PRESS_MS = 350;
@@ -270,12 +368,35 @@ var WHEEL_K = 15e-4;
270
368
  var SEP_HIT = 4;
271
369
  var DRAW_COLOR = 4280902399;
272
370
  var DRAW_WIDTH = 2;
371
+ var PENCIL_MIN_DIST = 2;
372
+ var PENCIL_EPSILON = 1;
273
373
  function drawingId() {
274
374
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
275
375
  return crypto.randomUUID();
276
376
  }
277
377
  return `draw-${Math.random().toString(36).slice(2)}`;
278
378
  }
379
+ function bodyPart(type) {
380
+ return type === "box" ? 4 : type === "pencil" ? 5 : 2;
381
+ }
382
+ function makeDrawing(base, type, pts) {
383
+ if (pts.length < 2) return null;
384
+ const style = {
385
+ ...base.color != null ? { color: base.color } : {},
386
+ ...base.width != null ? { width: base.width } : {}
387
+ };
388
+ if (type === "pencil") return { id: base.id, type, points: pts, ...style };
389
+ return { id: base.id, type, points: [pts[0], pts[pts.length - 1]], ...style };
390
+ }
391
+ function boxCorners(points) {
392
+ const [a, b] = points;
393
+ return [
394
+ { timeMs: a.timeMs, price: a.price },
395
+ { timeMs: b.timeMs, price: a.price },
396
+ { timeMs: b.timeMs, price: b.price },
397
+ { timeMs: a.timeMs, price: b.price }
398
+ ];
399
+ }
279
400
  function useGestures(containerRef, handleRef, scheduleRender, opts) {
280
401
  const optsRef = useRef2(opts);
281
402
  optsRef.current = opts;
@@ -283,6 +404,9 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
283
404
  const selectedIdRef = useRef2(null);
284
405
  const grabRef = useRef2(null);
285
406
  const dragLineRef = useRef2(null);
407
+ const pencilRef = useRef2(
408
+ null
409
+ );
286
410
  const downHitRef = useRef2(null);
287
411
  const lastCoordRef = useRef2(null);
288
412
  const selectedTRef = useRef2(0.5);
@@ -317,7 +441,10 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
317
441
  const placePaste = (clip) => {
318
442
  const h = handleRef.current;
319
443
  if (!h) return null;
320
- const [a, b] = clip.points;
444
+ const pts = clip.points;
445
+ if (pts.length < 2) return null;
446
+ const a = pts[0];
447
+ const b = pts[pts.length - 1];
321
448
  const t = clip.t;
322
449
  const pSel = {
323
450
  timeMs: a.timeMs + t * (b.timeMs - a.timeMs),
@@ -328,17 +455,12 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
328
455
  if (moved && cur) {
329
456
  const dt = cur.timeMs - pSel.timeMs;
330
457
  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
- ];
458
+ return pts.map((q) => ({ timeMs: q.timeMs + dt, price: q.price + dp }));
335
459
  }
336
460
  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);
461
+ const shift = (dp) => pts.map((q) => ({ timeMs: q.timeMs, price: q.price + dp }));
462
+ const prices = pts.map((q) => q.price);
463
+ const span = Math.max(...prices) - Math.min(...prices);
342
464
  if (!el) return shift(-(span || 1));
343
465
  const rect = el.getBoundingClientRect();
344
466
  const m = h.getAxisMetrics();
@@ -351,8 +473,8 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
351
473
  const pricePerPx = (vTop - vBot) / priceBottom;
352
474
  const GAP_PX = 30;
353
475
  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);
476
+ const pMin = Math.min(...prices);
477
+ const pMax = Math.max(...prices);
356
478
  const upFits = pMax + offset <= vTop;
357
479
  const downFits = pMin - offset >= vBot;
358
480
  let dir;
@@ -403,7 +525,8 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
403
525
  e.preventDefault();
404
526
  const info = handleRef.current?.getCrosshairInfo() ?? null;
405
527
  clipboardRef.current = {
406
- points: [d.points[0], d.points[1]],
528
+ type: d.type,
529
+ points: d.points.map((q) => ({ timeMs: q.timeMs, price: q.price })),
407
530
  color: d.color,
408
531
  width: d.width,
409
532
  t: selectedTRef.current,
@@ -414,15 +537,15 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
414
537
  if (!clip) return;
415
538
  const pts = placePaste(clip);
416
539
  if (!pts) return;
417
- e.preventDefault();
418
540
  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
- });
541
+ const copy = makeDrawing(
542
+ { id, color: clip.color, width: clip.width },
543
+ clip.type,
544
+ pts
545
+ );
546
+ if (!copy) return;
547
+ e.preventDefault();
548
+ optsRef.current.onDrawingComplete?.(copy);
426
549
  selectedIdRef.current = id;
427
550
  selectedTRef.current = clip.t;
428
551
  }
@@ -432,9 +555,10 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
432
555
  }, [containerRef, handleRef, scheduleRender]);
433
556
  const localCrosshairActiveRef = useRef2(false);
434
557
  useEffect2(() => {
435
- const active = opts.mode === "draw" && opts.tool === "line";
558
+ const active = opts.mode === "draw" && (opts.tool === "line" || opts.tool === "box" || opts.tool === "pencil");
436
559
  if (active) return;
437
560
  drawAnchorRef.current = null;
561
+ pencilRef.current = null;
438
562
  const h = handleRef.current;
439
563
  if (h) {
440
564
  h.clearDraft();
@@ -487,6 +611,21 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
487
611
  const len = dx * ux + dy * uy;
488
612
  return h.coordAt(f.x + len * ux, f.y + len * uy) ?? free;
489
613
  };
614
+ const snapToSquare = (fixed, x, y) => {
615
+ const h = handleRef.current;
616
+ if (!h) return null;
617
+ const free = h.coordAt(x, y);
618
+ if (!shiftHeld || !free) return free;
619
+ const f = h.project(fixed.timeMs, fixed.price);
620
+ if (!f) return free;
621
+ const dx = x - f.x;
622
+ const dy = y - f.y;
623
+ const side = Math.max(Math.abs(dx), Math.abs(dy));
624
+ if (side === 0) return free;
625
+ const nx = f.x + (dx < 0 ? -side : side);
626
+ const ny = f.y + (dy < 0 ? -side : side);
627
+ return h.coordAt(nx, ny) ?? free;
628
+ };
490
629
  const regionAt = (x, y) => {
491
630
  const h = handleRef.current;
492
631
  const r = el.getBoundingClientRect();
@@ -553,33 +692,38 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
553
692
  scheduleRender();
554
693
  reportCrosshair("hide");
555
694
  };
556
- const drawActive = () => optsRef.current.mode === "draw" && optsRef.current.tool === "line";
695
+ const drawActive = () => optsRef.current.mode === "draw" && (optsRef.current.tool === "line" || optsRef.current.tool === "box" || optsRef.current.tool === "pencil");
696
+ const drawIsBox = () => optsRef.current.tool === "box";
697
+ const drawIsPencil = () => optsRef.current.tool === "pencil";
698
+ const snapMoving = (isBox, fixed, x, y) => isBox ? snapToSquare(fixed, x, y) : snapToLine(fixed, x, y);
557
699
  const updateGuideline = (x, y) => {
558
700
  const h = handleRef.current;
559
701
  if (!h) return;
560
702
  const a = drawAnchorRef.current;
561
703
  if (!a) return;
562
- const c = snapToLine(a, x, y);
704
+ const isBox = drawIsBox();
705
+ const c = snapMoving(isBox, a, x, y);
563
706
  if (!c) return;
564
- h.setDraft(a.timeMs, a.price, true, c.timeMs, c.price, true, DRAW_COLOR, DRAW_WIDTH);
707
+ h.setDraft(a.timeMs, a.price, true, c.timeMs, c.price, true, DRAW_COLOR, DRAW_WIDTH, isBox ? 1 : 0);
565
708
  scheduleRender();
566
709
  };
567
710
  const handleDrawClick = (x, y) => {
568
711
  const h = handleRef.current;
569
712
  if (!h) return;
713
+ const isBox = drawIsBox();
570
714
  if (!drawAnchorRef.current) {
571
715
  const coord = h.coordAt(x, y);
572
716
  if (!coord) return;
573
717
  drawAnchorRef.current = coord;
574
- h.setDraft(coord.timeMs, coord.price, false, 0, 0, true, DRAW_COLOR, DRAW_WIDTH);
718
+ h.setDraft(coord.timeMs, coord.price, false, 0, 0, true, DRAW_COLOR, DRAW_WIDTH, isBox ? 1 : 0);
575
719
  scheduleRender();
576
720
  } else {
577
721
  const a = drawAnchorRef.current;
578
- const coord = snapToLine(a, x, y);
722
+ const coord = snapMoving(isBox, a, x, y);
579
723
  if (!coord) return;
580
724
  optsRef.current.onDrawingComplete?.({
581
725
  id: drawingId(),
582
- type: "line",
726
+ type: isBox ? "box" : "line",
583
727
  points: [
584
728
  { timeMs: a.timeMs, price: a.price },
585
729
  { timeMs: coord.timeMs, price: coord.price }
@@ -590,11 +734,73 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
590
734
  scheduleRender();
591
735
  }
592
736
  };
737
+ const pencilStart = (x, y) => {
738
+ const h = handleRef.current;
739
+ if (!h) return;
740
+ const c = h.coordAt(x, y);
741
+ if (!c) return;
742
+ pencilRef.current = { pts: [c], px: [{ x, y }] };
743
+ h.startDraftStroke(DRAW_COLOR, DRAW_WIDTH);
744
+ h.appendDraftPoint(c.timeMs, c.price);
745
+ scheduleRender();
746
+ };
747
+ const pencilMove = (x, y) => {
748
+ const h = handleRef.current;
749
+ const s = pencilRef.current;
750
+ if (!h || !s) return;
751
+ const last = s.px[s.px.length - 1];
752
+ if (Math.hypot(x - last.x, y - last.y) < PENCIL_MIN_DIST) return;
753
+ const c = h.coordAt(x, y);
754
+ if (!c) return;
755
+ s.pts.push(c);
756
+ s.px.push({ x, y });
757
+ h.appendDraftPoint(c.timeMs, c.price);
758
+ scheduleRender();
759
+ };
760
+ const priceDecimals = () => {
761
+ const h = handleRef.current;
762
+ const el2 = containerRef.current;
763
+ if (!h || !el2) return 6;
764
+ const rect = el2.getBoundingClientRect();
765
+ const m = h.getAxisMetrics();
766
+ const priceBottom = rect.height - m.xAxisHeight - m.indicatorHeight;
767
+ if (priceBottom <= 0) return 6;
768
+ const top = h.coordAt(rect.width / 2, 0);
769
+ const bot = h.coordAt(rect.width / 2, priceBottom);
770
+ if (!top || !bot || top.price <= bot.price) return 6;
771
+ const pricePerPx = (top.price - bot.price) / priceBottom;
772
+ const d = Math.ceil(-Math.log10(pricePerPx / 100));
773
+ return Math.max(0, Math.min(12, Number.isFinite(d) ? d : 6));
774
+ };
775
+ const roundPoints = (pts) => {
776
+ const dp = priceDecimals();
777
+ return pts.map((p) => ({
778
+ timeMs: Math.round(p.timeMs),
779
+ price: Number(p.price.toFixed(dp))
780
+ }));
781
+ };
782
+ const pencilEnd = () => {
783
+ const h = handleRef.current;
784
+ const s = pencilRef.current;
785
+ pencilRef.current = null;
786
+ if (!h) return;
787
+ h.clearDraft();
788
+ scheduleRender();
789
+ if (!s || s.pts.length < 2) return;
790
+ const keep = simplifyIndices(s.px, PENCIL_EPSILON);
791
+ if (keep.length < 2) return;
792
+ const stroke = makeDrawing(
793
+ { id: drawingId() },
794
+ "pencil",
795
+ roundPoints(keep.map((i) => s.pts[i]))
796
+ );
797
+ if (stroke) optsRef.current.onDrawingComplete?.(stroke);
798
+ };
593
799
  const moveGrab = (x, y) => {
594
800
  const h = handleRef.current;
595
801
  const g = grabRef.current;
596
802
  if (!h || !g) return;
597
- const c = snapToLine(g.fixed, x, y);
803
+ const c = snapMoving(g.isBox, g.fixed, x, y);
598
804
  if (!c) return;
599
805
  lastCoordRef.current = c;
600
806
  h.moveDrawingEndpoint(g.index, g.endpoint, c.timeMs, c.price);
@@ -629,38 +835,58 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
629
835
  downY = y;
630
836
  moved = false;
631
837
  panMode = regionAt(x, y);
838
+ if (drawIsPencil() && drawActive() && panMode === "chart") {
839
+ pencilStart(x, y);
840
+ return;
841
+ }
632
842
  downHitRef.current = null;
633
843
  if (!drawActive() && panMode === "chart") {
634
844
  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
- }
845
+ const gd = hit ? (optsRef.current.drawings ?? [])[hit.index] : void 0;
846
+ if (hit && gd && gd.type === "box" && hit.part <= 3) {
847
+ const corners = boxCorners(gd.points);
848
+ const grabbed = corners[hit.part];
849
+ const diagonal = corners[(hit.part + 2) % 4];
850
+ h.moveDrawingEndpoint(hit.index, 0, grabbed.timeMs, grabbed.price);
851
+ h.moveDrawingEndpoint(hit.index, 1, diagonal.timeMs, diagonal.price);
852
+ grabRef.current = { index: hit.index, endpoint: 0, fixed: diagonal, isBox: true };
853
+ lastCoordRef.current = grabbed;
854
+ applySelection();
855
+ return;
856
+ }
857
+ if (hit && gd && gd.type === "line" && hit.part <= 1) {
858
+ grabRef.current = {
859
+ index: hit.index,
860
+ endpoint: hit.part,
861
+ fixed: gd.points[hit.part === 0 ? 1 : 0],
862
+ // the other end stays put
863
+ isBox: false
864
+ };
865
+ lastCoordRef.current = null;
866
+ applySelection();
867
+ return;
648
868
  }
649
- if (hit && hit.part === 2) {
869
+ const isBody = hit != null && gd != null && hit.part === bodyPart(gd.type);
870
+ if (hit && isBody) {
650
871
  const list = optsRef.current.drawings ?? [];
651
872
  const selIdx = selectedIdRef.current ? list.findIndex((d) => d.id === selectedIdRef.current) : -1;
652
873
  if (hit.index === selIdx && selIdx >= 0) {
653
874
  const start = h.coordAt(x, y);
654
875
  const d = list[selIdx];
655
876
  if (start && d) {
877
+ const first = d.points[0];
878
+ const last = d.points[d.points.length - 1];
656
879
  dragLineRef.current = {
657
880
  index: selIdx,
658
881
  startTime: start.timeMs,
659
882
  startPrice: start.price,
660
- a0: d.points[0],
661
- b0: d.points[1],
662
- lastA: d.points[0],
663
- lastB: d.points[1]
883
+ a0: first,
884
+ b0: last,
885
+ lastA: first,
886
+ lastB: last,
887
+ // A stroke can't be restated cheaply each frame, so it keeps its
888
+ // original points and is nudged with relative translate calls.
889
+ ...d.type === "pencil" ? { points0: d.points, appliedTime: 0, appliedPrice: 0 } : {}
664
890
  };
665
891
  el.style.cursor = "move";
666
892
  return;
@@ -704,6 +930,10 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
704
930
  const dx = x - prev.x;
705
931
  const dy = y - prev.y;
706
932
  pointers.set(e.pointerId, { x, y });
933
+ if (pencilRef.current) {
934
+ pencilMove(x, y);
935
+ return;
936
+ }
707
937
  if (grabRef.current) {
708
938
  moveGrab(x, y);
709
939
  return;
@@ -714,10 +944,16 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
714
944
  if (c) {
715
945
  const dT = c.timeMs - g.startTime;
716
946
  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);
947
+ if (g.points0) {
948
+ h.translateDrawing(g.index, dT - (g.appliedTime ?? 0), dP - (g.appliedPrice ?? 0));
949
+ g.appliedTime = dT;
950
+ g.appliedPrice = dP;
951
+ } else {
952
+ g.lastA = { timeMs: g.a0.timeMs + dT, price: g.a0.price + dP };
953
+ g.lastB = { timeMs: g.b0.timeMs + dT, price: g.b0.price + dP };
954
+ h.moveDrawingEndpoint(g.index, 0, g.lastA.timeMs, g.lastA.price);
955
+ h.moveDrawingEndpoint(g.index, 1, g.lastB.timeMs, g.lastB.price);
956
+ }
721
957
  if (!moved && Math.hypot(x - downX, y - downY) > MOVE_THRESH) moved = true;
722
958
  scheduleRender();
723
959
  }
@@ -776,6 +1012,11 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
776
1012
  clearLongPress();
777
1013
  if (pointers.size < 2) pinch.active = false;
778
1014
  if (!had) return;
1015
+ if (pencilRef.current && pointers.size === 0) {
1016
+ pencilEnd();
1017
+ el.style.cursor = "crosshair";
1018
+ return;
1019
+ }
779
1020
  if (grabRef.current && pointers.size === 0) {
780
1021
  const g = grabRef.current;
781
1022
  grabRef.current = null;
@@ -783,9 +1024,11 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
783
1024
  const d = list[g.index];
784
1025
  const c = lastCoordRef.current;
785
1026
  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 });
1027
+ const movedPt = { timeMs: c.timeMs, price: c.price };
1028
+ const pts = g.isBox ? [movedPt, g.fixed] : [d.points[0], d.points[1]];
1029
+ if (!g.isBox) pts[g.endpoint] = movedPt;
1030
+ const next = makeDrawing({ id: d.id, color: d.color, width: d.width }, d.type, pts);
1031
+ if (next) optsRef.current.onDrawingChange?.(next);
789
1032
  }
790
1033
  applySelection();
791
1034
  el.style.cursor = "";
@@ -797,7 +1040,16 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
797
1040
  if (moved) {
798
1041
  const list = optsRef.current.drawings ?? [];
799
1042
  const d = list[g.index];
800
- if (d) optsRef.current.onDrawingChange?.({ ...d, points: [g.lastA, g.lastB] });
1043
+ if (d) {
1044
+ const pts = g.points0 ? roundPoints(
1045
+ g.points0.map((p) => ({
1046
+ timeMs: p.timeMs + (g.appliedTime ?? 0),
1047
+ price: p.price + (g.appliedPrice ?? 0)
1048
+ }))
1049
+ ) : [g.lastA, g.lastB];
1050
+ const next = makeDrawing({ id: d.id, color: d.color, width: d.width }, d.type, pts);
1051
+ if (next) optsRef.current.onDrawingChange?.(next);
1052
+ }
801
1053
  }
802
1054
  applySelection();
803
1055
  el.style.cursor = "";
@@ -811,10 +1063,12 @@ function useGestures(containerRef, handleRef, scheduleRender, opts) {
811
1063
  if (pointers.size === 0 && !moved && panMode === "chart" && crosshairSource !== "press") {
812
1064
  const hit = downHitRef.current;
813
1065
  const list = optsRef.current.drawings ?? [];
814
- if (hit && hit.part === 2 && list[hit.index]) {
1066
+ const hitDrawing = hit ? list[hit.index] : void 0;
1067
+ const isBodyHit = hit != null && hitDrawing != null && hit.part === bodyPart(hitDrawing.type);
1068
+ if (hit && isBodyHit && hitDrawing) {
815
1069
  selectedTRef.current = hit.t;
816
- if (selectedIdRef.current !== list[hit.index].id) {
817
- selectedIdRef.current = list[hit.index].id;
1070
+ if (selectedIdRef.current !== hitDrawing.id) {
1071
+ selectedIdRef.current = hitDrawing.id;
818
1072
  applySelection();
819
1073
  }
820
1074
  } else if (!hit && selectedIdRef.current != null) {
@@ -887,7 +1141,25 @@ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3
887
1141
 
888
1142
  // src/drawingStorage.ts
889
1143
  var DRAWINGS_VERSION = 1;
890
- var KNOWN_TYPES = /* @__PURE__ */ new Set(["line"]);
1144
+ var KNOWN_TYPES = /* @__PURE__ */ new Set(["line", "box", "pencil"]);
1145
+ var FIXED_POINT_TYPES = /* @__PURE__ */ new Set(["line", "box"]);
1146
+ function isPoint(p) {
1147
+ if (p == null || typeof p !== "object") return false;
1148
+ const { timeMs, price } = p;
1149
+ return typeof timeMs === "number" && Number.isFinite(timeMs) && typeof price === "number" && Number.isFinite(price);
1150
+ }
1151
+ function isValidDrawing(d) {
1152
+ if (d == null || typeof d !== "object") return false;
1153
+ const { type, points } = d;
1154
+ if (typeof type !== "string" || !KNOWN_TYPES.has(type)) {
1155
+ return false;
1156
+ }
1157
+ if (!Array.isArray(points)) return false;
1158
+ if (FIXED_POINT_TYPES.has(type) ? points.length !== 2 : points.length < 2) {
1159
+ return false;
1160
+ }
1161
+ return points.every(isPoint);
1162
+ }
891
1163
  var MIGRATIONS = {};
892
1164
  function serializeDrawings(drawings) {
893
1165
  const env = { v: DRAWINGS_VERSION, drawings };
@@ -919,9 +1191,7 @@ function deserializeDrawings(raw) {
919
1191
  if (!up) break;
920
1192
  env = up(env);
921
1193
  }
922
- return (Array.isArray(env.drawings) ? env.drawings : []).filter(
923
- (d) => d != null && typeof d === "object" && KNOWN_TYPES.has(d.type)
924
- );
1194
+ return (Array.isArray(env.drawings) ? env.drawings : []).filter(isValidDrawing);
925
1195
  }
926
1196
 
927
1197
  // src/useManagedDrawings.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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"]}
1
+ {"version":3,"sources":["../src/useChartCore.ts","../src/dataTransitions.ts","../src/useGestures.ts","../src/simplify.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 // a/b are the first and last anchor — the two endpoints of a line, the two\n // opposite corners of a box, or the ends of a pencil path (whose full point\n // list rides along in `points`).\n const first = d.points[0];\n const last = d.points[d.points.length - 1];\n return {\n aTime: first.timeMs,\n aPrice: first.price,\n bTime: last.timeMs,\n bPrice: last.price,\n color: (d.color != null ? parseColor(d.color) : null) ?? 0xff2962ff,\n width: d.width ?? 2,\n kind: d.type === 'box' ? 1 : d.type === 'pencil' ? 2 : 0,\n ...(d.type === 'pencil'\n ? { points: d.points.map((p) => ({ timeMs: p.timeMs, price: p.price })) }\n : {}),\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 chartType,\n transitionMs,\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 // Animate the candle↔line transition when `chartType` changes. The core is\n // driven per-frame with a (collapse, fade) blend; we own the eased clock here\n // in JS (see plan) so it works identically on the web direct-draw path. A new\n // handle applies the target instantly; reduced motion / transitionMs=0 snaps\n // (reduced motion still cross-fades opacity but skips the vertical collapse).\n const morphRafRef = useRef<number | null>(null);\n const morphFadeRef = useRef<number | null>(null);\n const morphHandleRef = useRef<VroomChartHandle | null>(null);\n useEffect(() => {\n if (!ready) return;\n const h = handleRef.current;\n if (!h) return;\n const target = chartType === 'line' ? 1 : 0;\n\n // Fresh handle (first load or remount): snap, don't animate.\n if (morphHandleRef.current !== h || morphFadeRef.current == null) {\n morphHandleRef.current = h;\n morphFadeRef.current = target;\n h.setChartType(target);\n scheduleRender();\n return;\n }\n if (morphFadeRef.current === target) return;\n\n const reduce = !!(\n typeof window !== 'undefined' &&\n window.matchMedia &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n const dur = Math.max(0, transitionMs ?? 300);\n\n if (morphRafRef.current != null) {\n cancelAnimationFrame(morphRafRef.current);\n morphRafRef.current = null;\n }\n if (dur === 0) {\n morphFadeRef.current = target;\n h.setChartType(target);\n scheduleRender();\n return;\n }\n\n const from = morphFadeRef.current;\n const start = performance.now();\n const step = (now: number) => {\n const p = Math.min(1, (now - start) / dur);\n const e = p * p * (3 - 2 * p); // smoothstep ease-in-out\n const fade = from + (target - from) * e;\n morphFadeRef.current = fade;\n h.setMorph(reduce ? 0 : fade, fade);\n h.present();\n if (p < 1) {\n morphRafRef.current = requestAnimationFrame(step);\n } else {\n morphRafRef.current = null;\n morphFadeRef.current = target;\n h.setChartType(target); // lock the exact endpoint\n h.present();\n }\n };\n morphRafRef.current = requestAnimationFrame(step);\n\n return () => {\n if (morphRafRef.current != null) {\n cancelAnimationFrame(morphRafRef.current);\n morphRafRef.current = null;\n }\n };\n }, [ready, chartType, transitionMs, 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\nimport { simplifyIndices } from './simplify';\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// Freehand capture tuning. PENCIL_MIN_DIST drops samples the pointer barely\n// moved between (browsers fire moves far faster than the stroke changes);\n// PENCIL_EPSILON is the RDP tolerance applied on commit, in px — roughly \"no\n// point may pull the rendered stroke more than a pixel off\".\nconst PENCIL_MIN_DIST = 2;\nconst PENCIL_EPSILON = 1;\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// The core's hit-test `part` that means \"the body of this shape\" (as opposed to\n// a grab handle). See packages/core/src/drawings.h for the full part encoding.\nfunction bodyPart(type: Drawing['type']): number {\n return type === 'box' ? 4 : type === 'pencil' ? 5 : 2;\n}\n\n// Rebuilds a drawing of `type` from a point array. `Drawing` is a discriminated\n// union where line/box carry an exact two-point tuple and only pencil is\n// variable-length, so this is where an array is narrowed back to the right\n// variant. Returns null for a degenerate (< 2 point) shape.\nfunction makeDrawing(\n base: { id: string; color?: Drawing['color']; width?: number },\n type: Drawing['type'],\n pts: DrawPoint[],\n): Drawing | null {\n if (pts.length < 2) return null;\n const style = {\n ...(base.color != null ? { color: base.color } : {}),\n ...(base.width != null ? { width: base.width } : {}),\n };\n if (type === 'pencil') return { id: base.id, type, points: pts, ...style };\n return { id: base.id, type, points: [pts[0]!, pts[pts.length - 1]!], ...style };\n}\n\n// The four corners of a box (given its two opposite-corner `points`), in the\n// same order the core's hit-test reports: a, (b.time,a.price), b,\n// (a.time,b.price). Corner k's diagonal is corner (k+2)%4.\nfunction boxCorners(\n points: readonly [DrawPoint, DrawPoint],\n): [DrawPoint, DrawPoint, DrawPoint, DrawPoint] {\n const [a, b] = points;\n return [\n { timeMs: a.timeMs, price: a.price },\n { timeMs: b.timeMs, price: a.price },\n { timeMs: b.timeMs, price: b.price },\n { timeMs: a.timeMs, price: b.price },\n ];\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 // The endpoint/corner being dragged. `fixed` is the anchor that stays put (the\n // other line endpoint, or — for a box — the diagonally opposite corner). For a\n // box we normalize the core so `endpoint` is always 0 (the grabbed corner) and\n // `fixed` is stored as endpoint 1, so the line's endpoint-drag path drives it.\n const grabRef = useRef<{\n index: number;\n endpoint: number;\n fixed: DrawPoint;\n isBox: boolean;\n } | null>(null);\n // A whole shape being translated by its body. Line/box restate both anchors\n // absolutely from a0/b0; a pencil can't (hundreds of points), so it carries\n // `points0` and is driven by relative translateDrawing calls instead.\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 points0?: DrawPoint[];\n /** Total delta applied to the core so far, for the incremental translate. */\n appliedTime?: number;\n appliedPrice?: number;\n } | null>(null);\n // An in-progress freehand stroke: the captured data-space points plus their\n // pixel positions, which drive the min-distance sampling and the RDP pass.\n const pencilRef = useRef<{ pts: DrawPoint[]; px: { x: number; y: number }[] } | null>(\n null,\n );\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 type: Drawing['type'];\n points: 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 // Points 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 // Works for any point count — a line/box's two anchors or a whole pencil path.\n const placePaste = (clip: NonNullable<typeof clipboardRef.current>): DrawPoint[] | null => {\n const h = handleRef.current;\n if (!h) return null;\n const pts = clip.points;\n if (pts.length < 2) return null;\n const a = pts[0]!;\n const b = pts[pts.length - 1]!;\n const t = clip.t;\n // Reference point that lands under the cursor: where the shape was grabbed\n // along first→last (t is 0 for a pencil, i.e. its starting point).\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 pts.map((q) => ({ timeMs: q.timeMs + dt, price: q.price + dp }));\n }\n // Above/below the original. Derive the visible price range + px scale.\n const el = containerRef.current;\n const shift = (dp: number): DrawPoint[] =>\n pts.map((q) => ({ timeMs: q.timeMs, price: q.price + dp }));\n // Full price extent of the shape, so the copy clears the original.\n const prices = pts.map((q) => q.price);\n const span = Math.max(...prices) - Math.min(...prices);\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(...prices);\n const pMax = Math.max(...prices);\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 type: d.type,\n points: d.points.map((q) => ({ timeMs: q.timeMs, price: q.price })),\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 const id = drawingId();\n const copy = makeDrawing(\n { id, color: clip.color, width: clip.width },\n clip.type,\n pts,\n );\n if (!copy) return;\n e.preventDefault();\n optsRef.current.onDrawingComplete?.(copy);\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 no draw tool is active (e.g. the host\n // toggled the tool off, or switched tools, mid-draw), so re-entering draw mode\n // starts clean.\n useEffect(() => {\n const active =\n opts.mode === 'draw' &&\n (opts.tool === 'line' || opts.tool === 'box' || opts.tool === 'pencil');\n if (active) return;\n drawAnchorRef.current = null;\n pencilRef.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 // Data coord for a moving box corner at pixel (x,y), opposite the `fixed`\n // corner. With Shift held, constrains the box to a perfect square (equal side\n // lengths in px), enclosing the cursor — the box analogue of snapToLine's 45°.\n const snapToSquare = (\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 const side = Math.max(Math.abs(dx), Math.abs(dy)); // equal side = larger delta\n if (side === 0) return free;\n const nx = f.x + (dx < 0 ? -side : side);\n const ny = f.y + (dy < 0 ? -side : side);\n return h.coordAt(nx, ny) ?? 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 a drawing tool should own input (suppress pan/zoom/crosshair).\n const drawActive = () =>\n optsRef.current.mode === 'draw' &&\n (optsRef.current.tool === 'line' ||\n optsRef.current.tool === 'box' ||\n optsRef.current.tool === 'pencil');\n\n // Whether the active draw tool is the box (vs the line). `1`/`0` maps to the\n // core's draft/drawing `kind`.\n const drawIsBox = () => optsRef.current.tool === 'box';\n\n // The pencil is drag-driven rather than click-click, so it takes its own\n // path through pointerdown/move/up instead of the two-click draft flow.\n const drawIsPencil = () => optsRef.current.tool === 'pencil';\n\n // Snap the moving anchor relative to `fixed`, using the constraint that fits\n // the shape: square for a box, 45° for a line.\n const snapMoving = (isBox: boolean, fixed: DrawPoint, x: number, y: number) =>\n isBox ? snapToSquare(fixed, x, y) : snapToLine(fixed, x, y);\n\n // While placing the second point, keep the live preview 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 isBox = drawIsBox();\n const c = snapMoving(isBox, a, x, y);\n if (!c) return;\n h.setDraft(a.timeMs, a.price, true, c.timeMs, c.price, true, DRAW_COLOR, DRAW_WIDTH, isBox ? 1 : 0);\n scheduleRender();\n };\n\n // Draw-tool tap: place the first point, then commit on the second. Works for\n // both the line (two endpoints) and the box (two opposite corners).\n const handleDrawClick = (x: number, y: number) => {\n const h = handleRef.current;\n if (!h) return;\n\n const isBox = drawIsBox();\n if (!drawAnchorRef.current) {\n // First point: show node A; the preview 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, isBox ? 1 : 0);\n scheduleRender();\n } else {\n // Second point: commit the shape (Shift-snapped to match the preview)\n // and clear the draft. Editing happens in pan mode via the committed shape.\n const a = drawAnchorRef.current;\n const coord = snapMoving(isBox, a, x, y);\n if (!coord) return;\n optsRef.current.onDrawingComplete?.({\n id: drawingId(),\n type: isBox ? 'box' : '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 // --- Pencil (freehand) capture ---------------------------------------\n // The stroke is captured in two parallel arrays: data-space points (what\n // gets committed, so the stroke tracks the candles) and their pixel\n // positions (what drives min-distance sampling and the RDP pass, so both\n // tolerances mean \"on screen\" regardless of zoom or price scale).\n\n const pencilStart = (x: number, y: number) => {\n const h = handleRef.current;\n if (!h) return;\n const c = h.coordAt(x, y);\n if (!c) return;\n pencilRef.current = { pts: [c], px: [{ x, y }] };\n h.startDraftStroke(DRAW_COLOR, DRAW_WIDTH);\n h.appendDraftPoint(c.timeMs, c.price);\n scheduleRender();\n };\n\n const pencilMove = (x: number, y: number) => {\n const h = handleRef.current;\n const s = pencilRef.current;\n if (!h || !s) return;\n // Skip samples the pointer barely moved between — pointermove fires far\n // faster than the stroke actually changes shape.\n const last = s.px[s.px.length - 1]!;\n if (Math.hypot(x - last.x, y - last.y) < PENCIL_MIN_DIST) return;\n const c = h.coordAt(x, y);\n if (!c) return;\n s.pts.push(c);\n s.px.push({ x, y });\n h.appendDraftPoint(c.timeMs, c.price);\n scheduleRender();\n };\n\n // Price precision at which the rounding error stays well under a pixel, so\n // the persisted stroke renders identically to the drawn one while shedding\n // float noise (54812.34567890123 → 54812.345679) from the stored payload.\n const priceDecimals = (): number => {\n const h = handleRef.current;\n const el = containerRef.current;\n if (!h || !el) return 6;\n const rect = el.getBoundingClientRect();\n const m = h.getAxisMetrics();\n const priceBottom = rect.height - m.xAxisHeight - m.indicatorHeight;\n if (priceBottom <= 0) return 6;\n const top = h.coordAt(rect.width / 2, 0);\n const bot = h.coordAt(rect.width / 2, priceBottom);\n if (!top || !bot || top.price <= bot.price) return 6;\n const pricePerPx = (top.price - bot.price) / priceBottom;\n const d = Math.ceil(-Math.log10(pricePerPx / 100));\n return Math.max(0, Math.min(12, Number.isFinite(d) ? d : 6));\n };\n\n // Quantize a stroke's points so the persisted payload stays compact. Applied\n // wherever a stroke is emitted (commit and translate) so stored coordinates\n // never re-accumulate float noise.\n const roundPoints = (pts: DrawPoint[]): DrawPoint[] => {\n const dp = priceDecimals();\n return pts.map((p) => ({\n timeMs: Math.round(p.timeMs),\n price: Number(p.price.toFixed(dp)),\n }));\n };\n\n // Commit the stroke: thin it with RDP, round the coordinates, and hand it to\n // the host. Sub-threshold strokes (a stray click) are dropped, not committed\n // as a speck.\n const pencilEnd = () => {\n const h = handleRef.current;\n const s = pencilRef.current;\n pencilRef.current = null;\n if (!h) return;\n h.clearDraft();\n scheduleRender();\n if (!s || s.pts.length < 2) return;\n\n const keep = simplifyIndices(s.px, PENCIL_EPSILON);\n if (keep.length < 2) return;\n const stroke = makeDrawing(\n { id: drawingId() },\n 'pencil',\n roundPoints(keep.map((i) => s.pts[i]!)),\n );\n if (stroke) optsRef.current.onDrawingComplete?.(stroke);\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 = snapMoving(g.isBox, 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 // Pencil: the press itself starts the stroke and owns the pointer for its\n // whole duration — no long-press crosshair, no pan.\n if (drawIsPencil() && drawActive() && panMode === 'chart') {\n pencilStart(x, y);\n return;\n }\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 const gd = hit ? (optsRef.current.drawings ?? [])[hit.index] : undefined;\n // Handle/corner grab: line endpoints (part 0/1) or box corners (part\n // 0..3). A pencil has no handles at all — its end anchors translate the\n // stroke like any other part of it — so it never takes this path.\n if (hit && gd && gd.type === 'box' && hit.part <= 3) {\n // A box corner: the diagonally opposite corner stays fixed. Normalize\n // the core so endpoint 0 = grabbed corner, endpoint 1 = diagonal, then\n // the line's endpoint-drag path resizes it (all corners stay 90°).\n const corners = boxCorners(gd.points);\n const grabbed = corners[hit.part];\n const diagonal = corners[(hit.part + 2) % 4];\n h.moveDrawingEndpoint(hit.index, 0, grabbed.timeMs, grabbed.price);\n h.moveDrawingEndpoint(hit.index, 1, diagonal.timeMs, diagonal.price);\n grabRef.current = { index: hit.index, endpoint: 0, fixed: diagonal, isBox: true };\n lastCoordRef.current = grabbed;\n applySelection(); // enlarge the grabbed handle\n return; // the handle drag owns this pointer — no pan / long-press\n }\n if (hit && gd && gd.type === 'line' && hit.part <= 1) {\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 isBox: false,\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 // Body of the already-selected shape → translate the whole shape on drag.\n // Line body is part 2, box body (interior/edge) part 4, pencil stroke 5.\n const isBody = hit != null && gd != null && hit.part === bodyPart(gd.type);\n if (hit && isBody) {\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 const first = d.points[0]!;\n const last = d.points[d.points.length - 1]!;\n dragLineRef.current = {\n index: selIdx,\n startTime: start.timeMs,\n startPrice: start.price,\n a0: first,\n b0: last,\n lastA: first,\n lastB: last,\n // A stroke can't be restated cheaply each frame, so it keeps its\n // original points and is nudged with relative translate calls.\n ...(d.type === 'pencil'\n ? { points0: d.points, appliedTime: 0, appliedPrice: 0 }\n : {}),\n };\n el.style.cursor = 'move';\n return; // the shape 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 // Freehand stroke in progress: extend it. Deliberately ahead of the\n // MOVE_THRESH gate below — a pencil records from the very first pixel, so\n // waiting for a 6px \"is this a drag?\" threshold would clip every stroke's\n // start.\n if (pencilRef.current) {\n pencilMove(x, y);\n return;\n }\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 // Shape translate (pan-mode editing): shift the whole drawing by the\n // pointer's 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 if (g.points0) {\n // Pencil: nudge the core by the change since the last move, since\n // restating a whole path every frame would be wasteful. Only the\n // live preview accumulates; the committed payload below is computed\n // from the originals, so no float drift is persisted.\n h.translateDrawing(g.index, dT - (g.appliedTime ?? 0), dP - (g.appliedPrice ?? 0));\n g.appliedTime = dT;\n g.appliedPrice = dP;\n } else {\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 }\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 // Pencil release: commit the stroke (simplified + rounded). The tool stays\n // active so the next press starts another stroke.\n if (pencilRef.current && pointers.size === 0) {\n pencilEnd();\n el.style.cursor = 'crosshair';\n return;\n }\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 // Box: the two opposite corners are the dragged corner + its fixed\n // diagonal (order-independent). Line: replace the grabbed endpoint,\n // keeping the other in place.\n const movedPt = { timeMs: c.timeMs, price: c.price };\n const pts: DrawPoint[] = g.isBox\n ? [movedPt, g.fixed]\n : [d.points[0]!, d.points[1]!];\n if (!g.isBox) pts[g.endpoint] = movedPt;\n const next = makeDrawing({ id: d.id, color: d.color, width: d.width }, d.type, pts);\n if (next) optsRef.current.onDrawingChange?.(next);\n }\n applySelection();\n el.style.cursor = '';\n return;\n }\n\n // Shape-translate release: persist the moved drawing, 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) {\n // Pencil: recompute every point from the originals + the total\n // delta, so what's persisted carries none of the live preview's\n // accumulated float error.\n const pts = g.points0\n ? roundPoints(\n g.points0.map((p) => ({\n timeMs: p.timeMs + (g.appliedTime ?? 0),\n price: p.price + (g.appliedPrice ?? 0),\n })),\n )\n : [g.lastA, g.lastB];\n const next = makeDrawing({ id: d.id, color: d.color, width: d.width }, d.type, pts);\n if (next) optsRef.current.onDrawingChange?.(next);\n }\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 const hitDrawing = hit ? list[hit.index] : undefined;\n // Body hit: line 2, box 4, pencil 5.\n const isBodyHit =\n hit != null && hitDrawing != null && hit.part === bodyPart(hitDrawing.type);\n if (hit && isBodyHit && hitDrawing) {\n selectedTRef.current = hit.t; // remember where along the line it was grabbed\n if (selectedIdRef.current !== hitDrawing.id) {\n selectedIdRef.current = hitDrawing.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","// Ramer–Douglas–Peucker polyline simplification, used to thin a freehand pencil\n// stroke before it is committed and persisted.\n//\n// A drag captures a sample every few pixels, so a single stroke can arrive with\n// hundreds of points — most of them redundant along near-straight runs. RDP\n// keeps only the points that actually carry the shape: it finds the sample\n// farthest from the chord between the current endpoints and, if that distance\n// exceeds `epsilon`, keeps it and recurses on both halves; otherwise the whole\n// span collapses to its endpoints.\n//\n// We run this in *pixel* space (not data space) so `epsilon` means \"visible\n// deviation on screen\" regardless of the chart's zoom or price scale, then map\n// the surviving indices back to the captured data-space points.\n\nexport type Pt = { x: number; y: number };\n\n/** Perpendicular distance from `p` to the (infinite) line through `a` and `b`. */\nfunction perpendicularDistance(p: Pt, a: Pt, b: Pt): number {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n // Degenerate chord (a === b): fall back to plain point distance.\n if (dx === 0 && dy === 0) return Math.hypot(p.x - a.x, p.y - a.y);\n // |cross product| / |chord| — the triangle-area formulation, no division by\n // zero and no need to normalize the direction vector first.\n return Math.abs(dy * p.x - dx * p.y + b.x * a.y - b.y * a.x) / Math.hypot(dx, dy);\n}\n\n/**\n * Indices of the points to keep, in ascending order, always including the first\n * and last. `epsilon` is the maximum allowed deviation in the same units as the\n * input (px); `0` keeps everything.\n *\n * Iterative rather than recursive so a pathological stroke can't blow the stack.\n */\nexport function simplifyIndices(points: readonly Pt[], epsilon: number): number[] {\n const n = points.length;\n if (n <= 2) return points.map((_, i) => i);\n if (epsilon <= 0) return points.map((_, i) => i);\n\n const keep = new Uint8Array(n);\n keep[0] = 1;\n keep[n - 1] = 1;\n\n // Explicit stack of [start, end] spans still to examine.\n const stack: [number, number][] = [[0, n - 1]];\n while (stack.length > 0) {\n const [start, end] = stack.pop()!;\n if (end <= start + 1) continue; // nothing between the endpoints\n\n let farthest = -1;\n let maxDist = -1;\n for (let i = start + 1; i < end; i++) {\n const d = perpendicularDistance(points[i]!, points[start]!, points[end]!);\n if (d > maxDist) {\n maxDist = d;\n farthest = i;\n }\n }\n\n // Within tolerance: every point between start and end is redundant.\n if (maxDist <= epsilon || farthest < 0) continue;\n\n keep[farthest] = 1;\n stack.push([start, farthest], [farthest, end]);\n }\n\n const out: number[] = [];\n for (let i = 0; i < n; i++) if (keep[i]) out.push(i);\n return out;\n}\n\n/** Convenience wrapper returning the kept points themselves. */\nexport function simplify(points: readonly Pt[], epsilon: number): Pt[] {\n return simplifyIndices(points, epsilon).map((i) => points[i]!);\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 (and\n// to FIXED_POINT_TYPES if it has exactly two anchors). Old payloads still\n// parse; payloads carrying an unknown tool (e.g. written by a newer version,\n// 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', 'box', 'pencil']);\n\n/** How many points each type must carry; pencil is variable (2 or more). */\nconst FIXED_POINT_TYPES = new Set(['line', 'box']);\n\nfunction isPoint(p: unknown): boolean {\n if (p == null || typeof p !== 'object') return false;\n const { timeMs, price } = p as { timeMs?: unknown; price?: unknown };\n return (\n typeof timeMs === 'number' &&\n Number.isFinite(timeMs) &&\n typeof price === 'number' &&\n Number.isFinite(price)\n );\n}\n\n/**\n * Whether a parsed entry is a drawing this build can actually render. Checks the\n * `type` *and* the shape of `points` — the store is an opaque, consumer-supplied\n * blob, and since a pencil stroke is a variable-length array we can no longer\n * assume two well-formed anchors. Anything malformed is dropped exactly like an\n * unknown type, rather than reaching the renderer as garbage.\n */\nfunction isValidDrawing(d: unknown): d is Drawing {\n if (d == null || typeof d !== 'object') return false;\n const { type, points } = d as { type?: unknown; points?: unknown };\n if (typeof type !== 'string' || !KNOWN_TYPES.has(type as Drawing['type'])) {\n return false;\n }\n if (!Array.isArray(points)) return false;\n if (FIXED_POINT_TYPES.has(type) ? points.length !== 2 : points.length < 2) {\n return false;\n }\n return points.every(isPoint);\n}\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 this build can render — a known `type`\n // carrying a well-formed `points` array. A newer file with an unknown tool (or\n // a corrupt entry) loads cleanly minus that entry, rather than throwing or\n // drawing garbage.\n return (Array.isArray(env.drawings) ? env.drawings : []).filter(isValidDrawing);\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","useRef","useCallback","useEffect","el","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.3.0",
3
+ "version": "0.5.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.3.0"
43
+ "@vroomchart/core-wasm": "0.5.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "react": ">=18",