@vroomchart/react 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Darion Welch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,185 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { CSSProperties } from 'react';
3
+ import { WasmConfig } from '@vroomchart/core-wasm';
4
+ export { WasmConfig } from '@vroomchart/core-wasm';
5
+
6
+ /** A single OHLCV bar. `candles` is an array of these. */
7
+ type Candle = {
8
+ /** Bar open time as Unix epoch milliseconds. */
9
+ timeMs: number;
10
+ /** Opening price. */
11
+ open: number;
12
+ /** Highest price during the bar. */
13
+ high: number;
14
+ /** Lowest price during the bar. */
15
+ low: number;
16
+ /** Closing price. */
17
+ close: number;
18
+ /** Traded volume during the bar. */
19
+ volume: number;
20
+ };
21
+ /** Payload passed to `onCrosshair` as the crosshair shows, moves, or hides. */
22
+ type CrosshairEvent = {
23
+ /** True while the crosshair is showing; false when it's dismissed. */
24
+ active: boolean;
25
+ /** OHLCV of the candle under the crosshair, or null when inactive. */
26
+ candle: Candle | null;
27
+ /**
28
+ * Why this event fired — lets the host react differently (e.g. haptics):
29
+ * 'show' — long-press activated the crosshair
30
+ * 'move' — the crosshair snapped to a *different* candle (one per candle
31
+ * crossed; not per drag frame)
32
+ * 'hide' — the crosshair was dismissed
33
+ * The library never plays haptics itself; the host decides.
34
+ */
35
+ reason: 'show' | 'move' | 'hide';
36
+ };
37
+ /**
38
+ * A color value: a hex string (`'#0d1117'`, or 8-digit `'#aarrggbb'`) or a
39
+ * packed ARGB number. In `VroomTheme` every field is optional — omitted colors
40
+ * keep the library default.
41
+ */
42
+ type VroomColor = string | number;
43
+ /** Color overrides for the chart, passed via the `theme` prop. */
44
+ type VroomTheme = {
45
+ /** Chart + axis-strip background. */
46
+ background?: VroomColor;
47
+ /** Up candles (also bull wicks, bull volume bars, rising price indicator). */
48
+ bull?: VroomColor;
49
+ /** Down candles (also bear wicks, bear volume bars, falling price indicator). */
50
+ bear?: VroomColor;
51
+ /** Gridlines. */
52
+ grid?: VroomColor;
53
+ /** Axis label text (price + time). */
54
+ axisText?: VroomColor;
55
+ /** Crosshair dashed lines. */
56
+ crosshair?: VroomColor;
57
+ /** Crosshair target — the hollow ring/dot at the intersection. */
58
+ crosshairTarget?: VroomColor;
59
+ };
60
+ /** A time window over the candle data, as Unix epoch milliseconds. */
61
+ type VisibleRange = {
62
+ /** Window start (inclusive), Unix epoch milliseconds. */
63
+ startMs: number;
64
+ /** Window end (inclusive), Unix epoch milliseconds. */
65
+ endMs: number;
66
+ };
67
+ /** RSI indicator config. Rendered in a pane below the candles when enabled. */
68
+ type RSIConfig = {
69
+ enabled?: boolean;
70
+ /** Lookback period in candle counts. Default 14, clamped to >= 2. */
71
+ period?: number;
72
+ /** Overbought band level (0..100). Default 70. */
73
+ upperBand?: number;
74
+ /** Oversold band level (0..100). Default 30. */
75
+ lowerBand?: number;
76
+ /** Show the RSI-based moving-average trendline. Default true. */
77
+ maEnabled?: boolean;
78
+ /** Trendline (MA of RSI) length. Default 14, clamped to >= 1. */
79
+ maPeriod?: number;
80
+ };
81
+ /** Price source for a moving average. */
82
+ type MASource = 'close' | 'open' | 'high' | 'low' | 'hl2' | 'hlc3' | 'ohlc4';
83
+ /**
84
+ * A moving-average overlay line drawn on the price pane. Provide an array of
85
+ * these via `movingAverages` to render a ribbon of SMA/EMA lines.
86
+ */
87
+ type MovingAverageOverlay = {
88
+ /** 'sma' (simple) or 'ema' (exponential). */
89
+ kind: 'sma' | 'ema';
90
+ /** Lookback in candles. */
91
+ length: number;
92
+ /** Price source. Default 'close'. */
93
+ source?: MASource;
94
+ /** Line color (hex string or packed ARGB number). */
95
+ color?: string | number;
96
+ /** Stroke width in px. Default 1.5. */
97
+ width?: number;
98
+ };
99
+ /**
100
+ * VWAP overlay config (session anchor). Drawn as a single line on the price
101
+ * pane, resetting each session.
102
+ */
103
+ type VWAPConfig = {
104
+ enabled?: boolean;
105
+ /** Session reset offset from UTC midnight, in minutes (default 0). */
106
+ resetMinutes?: number;
107
+ /** Line color (hex string or packed ARGB number). */
108
+ color?: string | number;
109
+ /** Stroke width in px. Default 1.5. */
110
+ width?: number;
111
+ };
112
+ /** MACD indicator config. Rendered in its own pane below the candles. */
113
+ type MACDConfig = {
114
+ enabled?: boolean;
115
+ /** Fast EMA length. Default 12. */
116
+ fast?: number;
117
+ /** Slow EMA length (forced > fast). Default 26. */
118
+ slow?: number;
119
+ /** Signal-line EMA length. Default 9. */
120
+ signal?: number;
121
+ };
122
+ /**
123
+ * Platform-agnostic props shared by every vroom chart component. Each platform
124
+ * extends this with its own `style` typing (and any platform-only props) to
125
+ * form its public `VroomChartProps`.
126
+ */
127
+ type VroomChartCoreProps = {
128
+ /** OHLCV bars to render. The only required prop. */
129
+ candles: Candle[];
130
+ /**
131
+ * Explicit size overrides in logical px. When omitted, the chart fills its
132
+ * parent (measured at runtime). Prefer layout-driven sizing via the
133
+ * platform's `style` (flex / aspect-ratio / absolute fill) over hard-coding.
134
+ */
135
+ width?: number;
136
+ height?: number;
137
+ /** Time window to render. Omit (or both 0) to show every candle. */
138
+ visibleRange?: VisibleRange;
139
+ theme?: VroomTheme;
140
+ /** RSI indicator (pane below the candles). Omit/disable to hide it. */
141
+ rsi?: RSIConfig;
142
+ /** MACD indicator (its own pane below the candles). Omit/disable to hide it. */
143
+ macd?: MACDConfig;
144
+ /** Moving-average overlay lines (SMA/EMA) drawn on the price pane. */
145
+ movingAverages?: MovingAverageOverlay[];
146
+ /** VWAP overlay (session anchor, configurable reset). */
147
+ vwap?: VWAPConfig;
148
+ /**
149
+ * Pixels the crosshair dot / horizontal line sit *above* the touch point so
150
+ * they aren't hidden under the thumb. The vertical line stays centered on the
151
+ * touch x. Default 40.
152
+ */
153
+ crosshairOffset?: number;
154
+ onCrosshair?: (e: CrosshairEvent) => void;
155
+ onViewportChange?: (startMs: number, endMs: number) => void;
156
+ };
157
+
158
+ type VroomChartProps = VroomChartCoreProps & {
159
+ /** Class applied to the chart's root element. */
160
+ className?: string;
161
+ /** Inline style for the chart's root element. Defaults to filling its parent. */
162
+ style?: CSSProperties;
163
+ /**
164
+ * Use the real Skia-WASM core instead of the Canvas2D stub by pointing at the
165
+ * built module/font assets. Omit to use the stub. Read once when the chart
166
+ * first mounts; the core is shared across all charts on the page.
167
+ */
168
+ wasm?: WasmConfig;
169
+ /** Force the Canvas2D stub even when `wasm` is provided (tests / SSR). */
170
+ forceStub?: boolean;
171
+ };
172
+
173
+ /**
174
+ * Skia-quality candlestick chart for the web. Pass OHLCV `candles` and size it
175
+ * via `style`/`width`/`height` (it fills its parent by default). Drag to pan,
176
+ * pinch or wheel to zoom, drag the price/time axes to rescale, hover (mouse) or
177
+ * long-press (touch) for the crosshair. Optional indicators (`rsi`, `macd`,
178
+ * `movingAverages`, `vwap`), colors (`theme`), and events (`onCrosshair`,
179
+ * `onViewportChange`) are configured through props.
180
+ *
181
+ * @see {@link VroomChartProps} for the full prop reference.
182
+ */
183
+ declare function VroomChart(props: VroomChartProps): react_jsx_runtime.JSX.Element;
184
+
185
+ export { type Candle, type CrosshairEvent, type MACDConfig, type MASource, type MovingAverageOverlay, type RSIConfig, type VWAPConfig, type VisibleRange, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme };
package/dist/index.js ADDED
@@ -0,0 +1,365 @@
1
+ // src/useChartCore.ts
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import {
4
+ loadVroom,
5
+ packCandles,
6
+ applyTheme,
7
+ parseColor
8
+ } from "@vroomchart/core-wasm";
9
+ var MA_SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4"];
10
+ function overlayToNumeric(o) {
11
+ const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
12
+ return {
13
+ kind: o.kind === "ema" ? 1 : 0,
14
+ period: o.length,
15
+ source: srcIdx < 0 ? 0 : srcIdx,
16
+ color: (o.color != null ? parseColor(o.color) : null) ?? 4280902399,
17
+ width: o.width ?? 1.5
18
+ };
19
+ }
20
+ function useChartCore(props, loadOpts) {
21
+ const {
22
+ candles,
23
+ width: widthProp,
24
+ height: heightProp,
25
+ visibleRange,
26
+ theme,
27
+ rsi,
28
+ macd,
29
+ movingAverages,
30
+ vwap
31
+ } = props;
32
+ const containerRef = useRef(null);
33
+ const canvasRef = useRef(null);
34
+ const handleRef = useRef(null);
35
+ const rafRef = useRef(null);
36
+ const [ready, setReady] = useState(false);
37
+ const [measured, setMeasured] = useState({ width: 0, height: 0 });
38
+ const loadOptsRef = useRef(loadOpts);
39
+ loadOptsRef.current = loadOpts;
40
+ const width = widthProp ?? measured.width;
41
+ const height = heightProp ?? measured.height;
42
+ const scheduleRender = useCallback(() => {
43
+ if (rafRef.current != null) return;
44
+ rafRef.current = requestAnimationFrame(() => {
45
+ rafRef.current = null;
46
+ const h = handleRef.current;
47
+ if (!h) return;
48
+ h.present();
49
+ if (h.isAnimating()) scheduleRender();
50
+ });
51
+ }, []);
52
+ useEffect(() => {
53
+ const canvas = canvasRef.current;
54
+ if (!canvas) return;
55
+ let disposed = false;
56
+ loadVroom(loadOptsRef.current).then((mod) => {
57
+ if (disposed) return;
58
+ handleRef.current = mod.create(canvas);
59
+ setReady(true);
60
+ });
61
+ return () => {
62
+ disposed = true;
63
+ if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
64
+ rafRef.current = null;
65
+ handleRef.current?.destroy();
66
+ handleRef.current = null;
67
+ setReady(false);
68
+ };
69
+ }, []);
70
+ useEffect(() => {
71
+ const el = containerRef.current;
72
+ if (!el || widthProp != null && heightProp != null) return;
73
+ const ro = new ResizeObserver((entries) => {
74
+ const r = entries[0]?.contentRect;
75
+ if (!r) return;
76
+ const w = Math.round(r.width);
77
+ const h = Math.round(r.height);
78
+ setMeasured((prev) => prev.width === w && prev.height === h ? prev : { width: w, height: h });
79
+ });
80
+ ro.observe(el);
81
+ return () => ro.disconnect();
82
+ }, [widthProp, heightProp]);
83
+ const themeKey = theme ? JSON.stringify(theme) : "";
84
+ const rsiKey = rsi ? JSON.stringify(rsi) : "";
85
+ const macdKey = macd ? JSON.stringify(macd) : "";
86
+ const maKey = movingAverages ? JSON.stringify(movingAverages) : "";
87
+ const vwapKey = vwap ? JSON.stringify(vwap) : "";
88
+ const explicit = visibleRange != null;
89
+ const startMs = visibleRange?.startMs ?? 0;
90
+ const endMs = visibleRange?.endMs ?? 0;
91
+ useEffect(() => {
92
+ const h = handleRef.current;
93
+ if (!h || width <= 0 || height <= 0) return;
94
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
95
+ h.setSize(width, height, dpr);
96
+ if (candles.length > 0) h.setCandles(packCandles(candles));
97
+ if (explicit) h.setVisibleRange(startMs, endMs);
98
+ if (theme) applyTheme(h, theme);
99
+ h.setRSI(
100
+ rsi?.enabled ?? false,
101
+ rsi?.period ?? 14,
102
+ rsi?.upperBand ?? 70,
103
+ rsi?.lowerBand ?? 30,
104
+ rsi?.maEnabled ?? true,
105
+ rsi?.maPeriod ?? 14
106
+ );
107
+ h.setMACD(macd?.enabled ?? false, macd?.fast ?? 12, macd?.slow ?? 26, macd?.signal ?? 9);
108
+ h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
109
+ h.setVWAP(
110
+ vwap?.enabled ?? false,
111
+ vwap?.resetMinutes ?? 0,
112
+ (vwap?.color != null ? parseColor(vwap.color) : null) ?? 4278238420,
113
+ vwap?.width ?? 1.5
114
+ );
115
+ scheduleRender();
116
+ }, [ready, width, height, candles, explicit, startMs, endMs, themeKey, rsiKey, macdKey, maKey, vwapKey, scheduleRender]);
117
+ return { containerRef, canvasRef, handleRef, scheduleRender, size: { width, height } };
118
+ }
119
+
120
+ // src/useGestures.ts
121
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
122
+ var MIN_SPAN = 24;
123
+ var AXIS_RATIO = 0.5;
124
+ var LONG_PRESS_MS = 350;
125
+ var MOVE_THRESH = 6;
126
+ var WHEEL_K = 15e-4;
127
+ function useGestures(containerRef, handleRef, scheduleRender, opts) {
128
+ const optsRef = useRef2(opts);
129
+ optsRef.current = opts;
130
+ useEffect2(() => {
131
+ const el = containerRef.current;
132
+ if (!el) return;
133
+ const pointers = /* @__PURE__ */ new Map();
134
+ let panMode = "chart";
135
+ let crosshairActive = false;
136
+ let crosshairSource = null;
137
+ let lastCrosshairTime = null;
138
+ let longPressTimer = null;
139
+ let downX = 0;
140
+ let downY = 0;
141
+ let moved = false;
142
+ const pinch = { spanX: 1, spanY: 1, ratioX: 1, ratioY: 1, enableX: false, enableY: false, active: false };
143
+ const rel = (e) => {
144
+ const r = el.getBoundingClientRect();
145
+ return { x: e.clientX - r.left, y: e.clientY - r.top };
146
+ };
147
+ const regionAt = (x, y) => {
148
+ const h = handleRef.current;
149
+ const r = el.getBoundingClientRect();
150
+ if (!h) return "chart";
151
+ const { yAxisWidth, xAxisHeight, indicatorHeight } = h.getAxisMetrics();
152
+ if (x > r.width - yAxisWidth) return "price-axis";
153
+ if (y > r.height - xAxisHeight) return "time-axis";
154
+ if (indicatorHeight > 0 && y > r.height - xAxisHeight - indicatorHeight) return "indicator";
155
+ return "chart";
156
+ };
157
+ const reportCrosshair = (reason) => {
158
+ const h = handleRef.current;
159
+ if (!h) return;
160
+ const c = h.getCrosshairCandle();
161
+ const t = c?.timeMs ?? null;
162
+ if (reason === "move" && t === lastCrosshairTime) return;
163
+ lastCrosshairTime = t;
164
+ optsRef.current.onCrosshair?.({ active: reason !== "hide", candle: c, reason });
165
+ };
166
+ const clearLongPress = () => {
167
+ if (longPressTimer != null) {
168
+ clearTimeout(longPressTimer);
169
+ longPressTimer = null;
170
+ }
171
+ };
172
+ const showCrosshair = (x, y, source, reason) => {
173
+ const h = handleRef.current;
174
+ if (!h) return;
175
+ crosshairActive = true;
176
+ crosshairSource = source;
177
+ const lift = source === "press" ? optsRef.current.crosshairOffset : 0;
178
+ h.setCrosshair(x, y - lift);
179
+ scheduleRender();
180
+ reportCrosshair(reason);
181
+ };
182
+ const hideCrosshair = () => {
183
+ const h = handleRef.current;
184
+ if (!h || !crosshairActive) return;
185
+ crosshairActive = false;
186
+ crosshairSource = null;
187
+ h.clearCrosshair();
188
+ scheduleRender();
189
+ reportCrosshair("hide");
190
+ };
191
+ const onPointerDown = (e) => {
192
+ const h = handleRef.current;
193
+ if (!h) return;
194
+ if (e.pointerType === "mouse" && e.button !== 0) return;
195
+ const { x, y } = rel(e);
196
+ el.setPointerCapture(e.pointerId);
197
+ pointers.set(e.pointerId, { x, y });
198
+ if (pointers.size === 2) {
199
+ clearLongPress();
200
+ hideCrosshair();
201
+ const pts = [...pointers.values()];
202
+ const sx = Math.abs(pts[0].x - pts[1].x);
203
+ const sy = Math.abs(pts[0].y - pts[1].y);
204
+ pinch.spanX = sx;
205
+ pinch.spanY = sy;
206
+ pinch.ratioX = 1;
207
+ pinch.ratioY = 1;
208
+ pinch.enableX = sx >= MIN_SPAN && sx >= sy * AXIS_RATIO;
209
+ pinch.enableY = sy >= MIN_SPAN && sy >= sx * AXIS_RATIO;
210
+ pinch.active = true;
211
+ return;
212
+ }
213
+ downX = x;
214
+ downY = y;
215
+ moved = false;
216
+ panMode = regionAt(x, y);
217
+ if (e.pointerType !== "mouse" && panMode === "chart") {
218
+ longPressTimer = setTimeout(() => {
219
+ longPressTimer = null;
220
+ if (!moved) showCrosshair(downX, downY, "press", "show");
221
+ }, LONG_PRESS_MS);
222
+ }
223
+ };
224
+ const onPointerMove = (e) => {
225
+ const h = handleRef.current;
226
+ if (!h) return;
227
+ const { x, y } = rel(e);
228
+ if (!pointers.has(e.pointerId)) {
229
+ if (e.pointerType === "mouse" && pointers.size === 0) {
230
+ if (regionAt(x, y) === "chart") {
231
+ showCrosshair(x, y, "hover", crosshairActive ? "move" : "show");
232
+ } else {
233
+ hideCrosshair();
234
+ }
235
+ }
236
+ return;
237
+ }
238
+ const prev = pointers.get(e.pointerId);
239
+ const dx = x - prev.x;
240
+ const dy = y - prev.y;
241
+ pointers.set(e.pointerId, { x, y });
242
+ if (pointers.size >= 2 && pinch.active) {
243
+ const pts = [...pointers.values()];
244
+ const focalX = (pts[0].x + pts[1].x) / 2;
245
+ const focalY = (pts[0].y + pts[1].y) / 2;
246
+ let frameX = 1;
247
+ if (pinch.enableX) {
248
+ const r = Math.max(Math.abs(pts[0].x - pts[1].x), MIN_SPAN) / pinch.spanX;
249
+ frameX = r / pinch.ratioX;
250
+ pinch.ratioX = r;
251
+ }
252
+ let frameY = 1;
253
+ if (pinch.enableY) {
254
+ const r = Math.max(Math.abs(pts[0].y - pts[1].y), MIN_SPAN) / pinch.spanY;
255
+ frameY = r / pinch.ratioY;
256
+ pinch.ratioY = r;
257
+ }
258
+ if (frameX !== 1 || frameY !== 1) {
259
+ h.zoom(frameX, frameY, focalX, focalY);
260
+ scheduleRender();
261
+ }
262
+ return;
263
+ }
264
+ if (!moved && Math.hypot(x - downX, y - downY) > MOVE_THRESH) {
265
+ moved = true;
266
+ clearLongPress();
267
+ }
268
+ if (!moved) return;
269
+ if (crosshairActive && crosshairSource === "press" && panMode === "chart") {
270
+ showCrosshair(x, y, "press", "move");
271
+ return;
272
+ }
273
+ if (panMode === "price-axis") h.scalePriceAxis(dy);
274
+ else if (panMode === "time-axis") h.scaleTimeAxis(dx);
275
+ else if (panMode === "indicator") h.pan(dx, 0);
276
+ else h.translate(dx, dy);
277
+ if (crosshairActive && crosshairSource === "hover") {
278
+ h.setCrosshair(x, y);
279
+ reportCrosshair("move");
280
+ }
281
+ scheduleRender();
282
+ };
283
+ const endPointer = (e) => {
284
+ const had = pointers.delete(e.pointerId);
285
+ clearLongPress();
286
+ if (pointers.size < 2) pinch.active = false;
287
+ if (!had) return;
288
+ if (crosshairSource === "press" && pointers.size === 0) {
289
+ hideCrosshair();
290
+ } else if (moved && (panMode === "chart" || panMode === "indicator")) {
291
+ optsRef.current.onViewportChange?.(0, 0);
292
+ }
293
+ };
294
+ const onPointerLeave = () => {
295
+ if (crosshairSource === "hover") hideCrosshair();
296
+ };
297
+ const onWheel = (e) => {
298
+ const h = handleRef.current;
299
+ if (!h) return;
300
+ e.preventDefault();
301
+ const { x, y } = rel(e);
302
+ if (e.ctrlKey || e.metaKey) {
303
+ const f = Math.exp(-e.deltaY * WHEEL_K);
304
+ h.zoom(f, f, x, y);
305
+ } else if (e.shiftKey) {
306
+ h.pan(-(e.deltaX || e.deltaY), 0);
307
+ } else if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
308
+ h.pan(-e.deltaX, 0);
309
+ } else {
310
+ const f = Math.exp(-e.deltaY * WHEEL_K);
311
+ h.zoom(f, 1, x, y);
312
+ }
313
+ scheduleRender();
314
+ };
315
+ el.addEventListener("pointerdown", onPointerDown);
316
+ el.addEventListener("pointermove", onPointerMove);
317
+ el.addEventListener("pointerup", endPointer);
318
+ el.addEventListener("pointercancel", endPointer);
319
+ el.addEventListener("pointerleave", onPointerLeave);
320
+ el.addEventListener("wheel", onWheel, { passive: false });
321
+ return () => {
322
+ clearLongPress();
323
+ el.removeEventListener("pointerdown", onPointerDown);
324
+ el.removeEventListener("pointermove", onPointerMove);
325
+ el.removeEventListener("pointerup", endPointer);
326
+ el.removeEventListener("pointercancel", endPointer);
327
+ el.removeEventListener("pointerleave", onPointerLeave);
328
+ el.removeEventListener("wheel", onWheel);
329
+ };
330
+ }, [containerRef, handleRef, scheduleRender]);
331
+ }
332
+
333
+ // src/VroomChart.tsx
334
+ import { jsx } from "react/jsx-runtime";
335
+ var FILL = { position: "relative", width: "100%", height: "100%" };
336
+ var CANVAS_STYLE = {
337
+ display: "block",
338
+ width: "100%",
339
+ height: "100%",
340
+ touchAction: "none"
341
+ // we own pan/zoom; don't let the browser scroll/zoom
342
+ };
343
+ function VroomChart(props) {
344
+ const { className, style, wasm, forceStub, crosshairOffset = 40, onCrosshair, onViewportChange } = props;
345
+ const { containerRef, canvasRef, handleRef, scheduleRender } = useChartCore(
346
+ props,
347
+ wasm || forceStub ? { wasm, forceStub } : void 0
348
+ );
349
+ useGestures(containerRef, handleRef, scheduleRender, {
350
+ crosshairOffset,
351
+ onCrosshair,
352
+ onViewportChange
353
+ });
354
+ const rootStyle = {
355
+ ...FILL,
356
+ ...props.width != null ? { width: props.width } : null,
357
+ ...props.height != null ? { height: props.height } : null,
358
+ ...style
359
+ };
360
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style: rootStyle, children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, style: CANVAS_STYLE }) });
361
+ }
362
+ export {
363
+ VroomChart
364
+ };
365
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useChartCore.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 VroomChartHandle,\n} from '@vroomchart/core-wasm';\nimport type { VroomChartCoreProps } from '@vroomchart/types';\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\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 width: widthProp,\n height: heightProp,\n visibleRange,\n theme,\n rsi,\n macd,\n movingAverages,\n vwap,\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 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 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) h.setCandles(packCandles(candles));\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 scheduleRender();\n // theme/rsi/macd/movingAverages/vwap tracked via their *Key deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ready, width, height, candles, explicit, startMs, endMs, themeKey, rsiKey, macdKey, maKey, vwapKey, scheduleRender]);\n\n return { containerRef, canvasRef, handleRef, scheduleRender, size: { width, height } };\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 { CrosshairEvent } from '@vroomchart/types';\nimport type { VroomChartHandle } from '@vroomchart/core-wasm';\n\ntype Region = 'chart' | 'price-axis' | 'time-axis' | 'indicator';\n\nexport type GestureOptions = {\n crosshairOffset: number;\n onCrosshair?: (e: CrosshairEvent) => void;\n onViewportChange?: (startMs: number, endMs: number) => 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\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 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 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) return 'price-axis';\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 const c = h.getCrosshairCandle();\n const t = c?.timeMs ?? null;\n if (reason === 'move' && t === lastCrosshairTime) return;\n lastCrosshairTime = t;\n optsRef.current.onCrosshair?.({ active: reason !== 'hide', candle: c, reason });\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 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 crosshairSource = null;\n h.clearCrosshair();\n scheduleRender();\n reportCrosshair('hide');\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') {\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 if (regionAt(x, y) === '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.\n if (pointers.size >= 2 && pinch.active) {\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 // 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 === '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 // 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 };\n\n const onPointerLeave = () => {\n if (crosshairSource === 'hover') hideCrosshair();\n };\n\n const onWheel = (e: WheelEvent) => {\n const h = handleRef.current;\n if (!h) return;\n e.preventDefault();\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 { className, style, wasm, forceStub, crosshairOffset = 40, onCrosshair, onViewportChange } =\n props;\n const { containerRef, canvasRef, handleRef, scheduleRender } = useChartCore(\n props,\n wasm || forceStub ? { wasm, forceStub } : undefined,\n );\n\n useGestures(containerRef, handleRef, scheduleRender, {\n crosshairOffset,\n onCrosshair,\n onViewportChange,\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"]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@vroomchart/react",
3
+ "version": "0.1.0",
4
+ "description": "Skia-quality candlestick chart for the web (React DOM).",
5
+ "license": "MIT",
6
+ "author": "Darion Welch",
7
+ "homepage": "https://github.com/darionwelch/vroom#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/darionwelch/vroom.git",
11
+ "directory": "packages/react"
12
+ },
13
+ "keywords": [
14
+ "chart",
15
+ "candlestick",
16
+ "react",
17
+ "skia",
18
+ "wasm",
19
+ "finance",
20
+ "trading",
21
+ "charting"
22
+ ],
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "sideEffects": false,
35
+ "files": [
36
+ "dist",
37
+ "README.md"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "@vroomchart/core-wasm": "0.1.0"
44
+ },
45
+ "peerDependencies": {
46
+ "react": ">=18",
47
+ "react-dom": ">=18"
48
+ },
49
+ "devDependencies": {
50
+ "@types/react": "~19.1.0",
51
+ "@types/react-dom": "~19.1.0",
52
+ "react": "19.1.0",
53
+ "react-dom": "19.1.0",
54
+ "tsup": "^8.5.1",
55
+ "typescript": "~5.9.2",
56
+ "@vroomchart/types": "0.0.1"
57
+ },
58
+ "scripts": {
59
+ "typecheck": "tsc --noEmit",
60
+ "build": "tsup"
61
+ }
62
+ }