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