react-native-vroom-chart 0.2.0 → 0.4.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/lib/index.d.ts CHANGED
@@ -92,6 +92,10 @@ type VroomTheme = {
92
92
  crosshair?: VroomColor;
93
93
  /** Crosshair target — the hollow ring/dot at the intersection. */
94
94
  crosshairTarget?: VroomColor;
95
+ /** Line-chart-mode close polyline color. Defaults to a neutral foreground. */
96
+ lineColor?: VroomColor;
97
+ /** Line-chart-mode polyline stroke width in px. Defaults to 1.5. */
98
+ lineWidth?: number;
95
99
  };
96
100
  /** A time window over the candle data, as Unix epoch milliseconds. */
97
101
  type VisibleRange = {
@@ -106,8 +110,15 @@ type VisibleRange = {
106
110
  * 'draw' — left-clicks place drawing points; panning/zooming are suppressed.
107
111
  */
108
112
  type ChartMode = 'pan' | 'draw';
113
+ /**
114
+ * How the price series is drawn.
115
+ * 'candles' — default: candlestick bodies + wicks.
116
+ * 'line' — a single polyline through each candle's close. Volume, indicators,
117
+ * overlays, crosshair, and drawings still render.
118
+ */
119
+ type ChartType = 'candles' | 'line';
109
120
  /** Active drawing tool while in `draw` mode. `null` draws nothing. */
110
- type DrawTool = null | 'line';
121
+ type DrawTool = null | 'line' | 'box' | 'pencil';
111
122
  /** A drawing anchor in data space, so it stays glued to the candles on pan/zoom. */
112
123
  type DrawPoint = {
113
124
  /** Anchor time as Unix epoch milliseconds (not snapped to a candle slot). */
@@ -115,23 +126,81 @@ type DrawPoint = {
115
126
  /** Anchor price. */
116
127
  price: number;
117
128
  };
118
- /**
119
- * A committed drawing. Pass an array of these via the `drawings` prop to render
120
- * persisted annotations; the chart appends a new one (via `onDrawingComplete`)
121
- * each time the user finishes drawing. For now only the `'line'` (two-point
122
- * trendline) type exists.
123
- */
124
- type Drawing = {
129
+ /** Fields shared by every drawing type. */
130
+ type DrawingBase = {
125
131
  /** Stable unique id (the chart generates one for drawings it creates). */
126
132
  id: string;
127
- type: 'line';
128
- /** The two endpoints, in data space. */
129
- points: [DrawPoint, DrawPoint];
130
- /** Line color (hex string or packed ARGB number). Default solid blue. */
133
+ /** Stroke color (hex string or packed ARGB number). Default solid blue. */
131
134
  color?: VroomColor;
132
135
  /** Stroke width in px. Default 2. */
133
136
  width?: number;
134
137
  };
138
+ /** A two-point trendline from `points[0]` to `points[1]`. */
139
+ type LineDrawing = DrawingBase & {
140
+ type: 'line';
141
+ /** The two endpoints, in data space. */
142
+ points: [DrawPoint, DrawPoint];
143
+ };
144
+ /**
145
+ * An axis-aligned rectangle whose two opposite corners are `points[0]` and
146
+ * `points[1]` (the other two corners are derived).
147
+ */
148
+ type BoxDrawing = DrawingBase & {
149
+ type: 'box';
150
+ /** Two opposite corners, in data space. */
151
+ points: [DrawPoint, DrawPoint];
152
+ };
153
+ /**
154
+ * A freehand pencil stroke: an open path through `points`, in order. Unlike the
155
+ * other tools a stroke has a variable number of points, and once committed it
156
+ * can only be translated — never reshaped.
157
+ */
158
+ type PencilDrawing = DrawingBase & {
159
+ type: 'pencil';
160
+ /** The path's points in draw order (at least 2), in data space. */
161
+ points: DrawPoint[];
162
+ };
163
+ /**
164
+ * A committed drawing. Pass an array of these via the `drawings` prop to render
165
+ * persisted annotations; the chart appends a new one (via `onDrawingComplete`)
166
+ * each time the user finishes drawing.
167
+ *
168
+ * This is a discriminated union on `type` — narrow on it before reading
169
+ * `points[1]`, since a `'pencil'` stroke has a variable-length array while
170
+ * `'line'` and `'box'` are always exactly two points.
171
+ */
172
+ type Drawing = LineDrawing | BoxDrawing | PencilDrawing;
173
+ /**
174
+ * Storage adapter for **managed** drawing persistence. Provide it via the
175
+ * `drawingStore` prop and the chart owns the drawings array itself — loading and
176
+ * saving through this adapter instead of you wiring the controlled `drawings`
177
+ * prop + `onDrawing*` callbacks.
178
+ *
179
+ * The adapter is an **opaque string key-value store** — the chart serializes
180
+ * drawings into a **versioned envelope** (`{ v, drawings }`) and hands you the
181
+ * string; you just persist bytes. Because the library owns the schema and
182
+ * migrates old payloads on load, adding drawing tools or persisted fields later
183
+ * never changes this interface — your adapter is written once.
184
+ *
185
+ * `marketId` is the chart's `seriesKey`, so drawings are bucketed per market:
186
+ * they persist across timeframe changes (same key) but not across markets. Both
187
+ * methods may be async (localStorage is sync; AsyncStorage / MMKV / a REST
188
+ * backend are async). The chart debounces `save`. Consumers that only handle a
189
+ * single market can ignore `marketId`.
190
+ */
191
+ type DrawingStore = {
192
+ /**
193
+ * Return the raw string previously handed to `save` for `marketId`, or
194
+ * `null`/`undefined`/`''` if nothing is stored. Sync or async.
195
+ */
196
+ load: (marketId: string) => string | null | undefined | Promise<string | null | undefined>;
197
+ /**
198
+ * Persist the opaque `data` string for `marketId`. Sync or async; the chart
199
+ * debounces calls. The string is a versioned envelope owned by the library —
200
+ * store it verbatim, don't parse or reshape it.
201
+ */
202
+ save: (marketId: string, data: string) => void | Promise<void>;
203
+ };
135
204
  /** RSI indicator config. Rendered in a pane below the candles when enabled. */
136
205
  type RSIConfig = {
137
206
  enabled?: boolean;
@@ -265,6 +334,18 @@ type VroomChartCoreProps = {
265
334
  * devices of different widths.
266
335
  */
267
336
  defaultCandleWidth?: number;
337
+ /**
338
+ * Price-series render style. `'candles'` (default) draws candlesticks;
339
+ * `'line'` draws a polyline through each candle's close (style it with
340
+ * `theme.lineColor` / `theme.lineWidth`). All other layers are unaffected.
341
+ */
342
+ chartType?: ChartType;
343
+ /**
344
+ * Duration (ms) of the animated candle↔line transition when `chartType`
345
+ * changes. Default ~300. `0` snaps instantly. Ignored (snaps) when the OS
346
+ * requests reduced motion, which instead uses a plain cross-fade.
347
+ */
348
+ transitionMs?: number;
268
349
  theme?: VroomTheme;
269
350
  /** RSI indicator (pane below the candles). Omit/disable to hide it. */
270
351
  rsi?: RSIConfig;
@@ -306,10 +387,30 @@ type VroomChartCoreProps = {
306
387
  * Committed drawings to render, anchored to data so they track the candles on
307
388
  * pan/zoom. This is a controlled prop: append the value the chart hands you in
308
389
  * `onDrawingComplete` to persist it.
390
+ *
391
+ * Ignored when `drawingStore` is set (the chart then owns the array itself).
309
392
  */
310
393
  drawings?: Drawing[];
394
+ /**
395
+ * Opt into **managed** drawing persistence: the chart owns the drawings array
396
+ * internally and loads/saves it through this adapter, keyed by `seriesKey`.
397
+ * When set, `drawings` and the `onDrawing*` callbacks are ignored. Web only.
398
+ * Requires `seriesKey` — without one, drawings work in-session but aren't saved.
399
+ */
400
+ drawingStore?: DrawingStore;
311
401
  /** Fired with the finished drawing when the user completes one. */
312
402
  onDrawingComplete?: (drawing: Drawing) => void;
403
+ /**
404
+ * Fired after the user drags a selected line's endpoint handle. The payload is
405
+ * the same drawing (same `id`) with updated `points`; apply it to your
406
+ * controlled `drawings` state (replace by id). Web only.
407
+ */
408
+ onDrawingChange?: (drawing: Drawing) => void;
409
+ /**
410
+ * Fired when the user deletes the selected line (Backspace/Delete). Remove the
411
+ * drawing with this `id` from your controlled `drawings` state. Web only.
412
+ */
413
+ onDrawingDelete?: (id: string) => void;
313
414
  /**
314
415
  * Fired when the chart wants the mode changed — e.g. it requests `'pan'` after
315
416
  * the user clicks away from a just-drawn line. Since `mode` is controlled, the
@@ -349,4 +450,4 @@ declare global {
349
450
  */
350
451
  declare function VroomChart(props: VroomChartProps): React.JSX.Element;
351
452
 
352
- export { type Candle, type CrosshairEvent, type MACDConfig, type MASource, type MovingAverageOverlay, type RSIConfig, type VWAPConfig, type VisibleRange, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme };
453
+ export { type Candle, type ChartType, type CrosshairEvent, type MACDConfig, type MASource, type MovingAverageOverlay, type RSIConfig, type VWAPConfig, type VisibleRange, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme };
package/lib/index.js CHANGED
@@ -92,16 +92,20 @@ var COLOR_KEYS = {
92
92
  // VROOM_COLOR_WICK_BEAR
93
93
  accentBull: 14,
94
94
  // VROOM_COLOR_ACCENT_BULL
95
- accentBear: 15
95
+ accentBear: 15,
96
96
  // VROOM_COLOR_ACCENT_BEAR
97
+ lineColor: 16
98
+ // VROOM_COLOR_LINE
97
99
  };
98
100
  var FLOAT_KEYS = {
99
101
  wickWidth: 1,
100
102
  // VROOM_FLOAT_WICK_WIDTH_PX
101
103
  candleRadius: 8,
102
104
  // VROOM_FLOAT_CANDLE_RADIUS_PX
103
- volumeRadius: 10
105
+ volumeRadius: 10,
104
106
  // VROOM_FLOAT_VOLUME_RADIUS_PX
107
+ lineWidth: 11
108
+ // VROOM_FLOAT_LINE_WIDTH_PX
105
109
  };
106
110
  var BOOL_KEYS = {
107
111
  wickRoundCap: 9
@@ -167,7 +171,7 @@ function ensureInstalled() {
167
171
  }
168
172
  installed = true;
169
173
  }
170
- function useChartCore(candles, size, visibleRange, defaultCandleWidth, theme, rsi, macd, movingAverages, vwap) {
174
+ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap) {
171
175
  const handleRef = (0, import_react.useRef)(null);
172
176
  const defaultWidthAppliedRef = (0, import_react.useRef)(false);
173
177
  const [picture, setPicture] = (0, import_react.useState)(null);
@@ -235,6 +239,8 @@ function VroomChart(props) {
235
239
  style,
236
240
  visibleRange,
237
241
  defaultCandleWidth,
242
+ chartType,
243
+ transitionMs,
238
244
  theme,
239
245
  rsi,
240
246
  macd,
@@ -259,6 +265,7 @@ function VroomChart(props) {
259
265
  { width, height },
260
266
  visibleRange,
261
267
  defaultCandleWidth,
268
+ chartType,
262
269
  theme,
263
270
  rsi,
264
271
  macd,
@@ -307,6 +314,62 @@ function VroomChart(props) {
307
314
  }
308
315
  };
309
316
  }, []);
317
+ const morphRaf = (0, import_react2.useRef)(null);
318
+ const morphFade = (0, import_react2.useRef)(null);
319
+ const morphHandle = (0, import_react2.useRef)(null);
320
+ (0, import_react2.useEffect)(() => {
321
+ if (!handle) return void 0;
322
+ const target = chartType === "line" ? 1 : 0;
323
+ if (morphHandle.current !== handle || morphFade.current == null) {
324
+ morphHandle.current = handle;
325
+ morphFade.current = target;
326
+ handle.setChartType(target);
327
+ const p = handle.render();
328
+ if (p) pictureSV.value = p;
329
+ return void 0;
330
+ }
331
+ if (morphFade.current === target) return void 0;
332
+ if (morphRaf.current != null) {
333
+ cancelAnimationFrame(morphRaf.current);
334
+ morphRaf.current = null;
335
+ }
336
+ const dur = Math.max(0, transitionMs ?? 300);
337
+ if (dur === 0) {
338
+ morphFade.current = target;
339
+ handle.setChartType(target);
340
+ const p = handle.render();
341
+ if (p) pictureSV.value = p;
342
+ return void 0;
343
+ }
344
+ const from = morphFade.current;
345
+ let startTs = null;
346
+ const step = (now) => {
347
+ if (startTs == null) startTs = now;
348
+ const prog = Math.min(1, (now - startTs) / dur);
349
+ const e = prog * prog * (3 - 2 * prog);
350
+ const fade = from + (target - from) * e;
351
+ morphFade.current = fade;
352
+ handle.setMorph(fade, fade);
353
+ const p = handle.render();
354
+ if (p) pictureSV.value = p;
355
+ if (prog < 1) {
356
+ morphRaf.current = requestAnimationFrame(step);
357
+ } else {
358
+ morphRaf.current = null;
359
+ morphFade.current = target;
360
+ handle.setChartType(target);
361
+ const q = handle.render();
362
+ if (q) pictureSV.value = q;
363
+ }
364
+ };
365
+ morphRaf.current = requestAnimationFrame(step);
366
+ return () => {
367
+ if (morphRaf.current != null) {
368
+ cancelAnimationFrame(morphRaf.current);
369
+ morphRaf.current = null;
370
+ }
371
+ };
372
+ }, [handle, chartType, transitionMs, pictureSV]);
310
373
  const hitAxis = (0, import_react2.useCallback)(
311
374
  (x, y) => {
312
375
  if (!handle) return "chart";
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/VroomChart.tsx","../src/useChartCore.ts","../src/NativeVroomChart.ts","../src/packCandles.ts","../src/theme.ts"],"sourcesContent":["export { VroomChart } from './VroomChart';\nexport type {\n VroomChartProps,\n Candle,\n CrosshairEvent,\n VroomTheme,\n VroomColor,\n VisibleRange,\n RSIConfig,\n MACDConfig,\n MASource,\n MovingAverageOverlay,\n VWAPConfig,\n} from './types';\n","// VroomChart — Phase 3.\n//\n// Owns a SharedValue<SkPicture> driven by:\n// - useChartCore's \"initial\" picture (when data/size/range change), AND\n// - Pan gesture callbacks that call handle.pan(dx, dy) → fresh picture.\n//\n// Reanimated 4 + RN-Skia 2 propagate SharedValue<SkPicture> changes to\n// <Picture> without a React re-render, so gesture-driven redraws are cheap.\n//\n// Gestures run on the JS thread for now (`runOnJS(true)`) — installing the\n// JSI bindings on the worklet runtime is a later perf optimization.\n\nimport React, { useEffect, useRef, useCallback, useState, useMemo } from 'react';\nimport { View, type LayoutChangeEvent } from 'react-native';\nimport { Canvas, Picture, Skia, type SkPicture } from '@shopify/react-native-skia';\nimport {\n Gesture,\n GestureDetector,\n GestureHandlerRootView,\n} from 'react-native-gesture-handler';\nimport { useSharedValue } from 'react-native-reanimated';\n\nimport { useChartCore } from './useChartCore';\nimport type { VroomChartProps } from './types';\nimport './jsi.d';\n\n/**\n * Skia-rendered candlestick chart. Pass OHLCV `candles` and size it via `style`\n * (it fills its parent by default). Pan to scroll, pinch to zoom, drag the\n * price/time axes to rescale, and long-press for the crosshair. Optional\n * indicators (`rsi`, `macd`, `movingAverages`, `vwap`), colors (`theme`), and\n * events (`onCrosshair`, `onViewportChange`) are configured through props.\n *\n * @see {@link VroomChartProps} for the full prop reference.\n */\nexport function VroomChart(props: VroomChartProps) {\n const {\n candles,\n width: widthProp,\n height: heightProp,\n style,\n visibleRange,\n defaultCandleWidth,\n theme,\n rsi,\n macd,\n movingAverages,\n vwap,\n crosshairOffset = 40,\n onCrosshair,\n onViewportChange,\n } = props;\n\n // Fill the parent by default: measure via onLayout. Explicit width/height\n // props (if given) win per-axis. Until the first layout, dims are 0 and we\n // render nothing (one frame).\n const [measured, setMeasured] = useState({ width: 0, height: 0 });\n const width = widthProp ?? measured.width;\n const height = heightProp ?? measured.height;\n\n const onLayout = useCallback((e: LayoutChangeEvent) => {\n const w = Math.round(e.nativeEvent.layout.width);\n const h = Math.round(e.nativeEvent.layout.height);\n setMeasured((prev) =>\n prev.width === w && prev.height === h ? prev : { width: w, height: h },\n );\n }, []);\n\n const { handle, picture } = useChartCore(\n candles,\n { width, height },\n visibleRange,\n defaultCandleWidth,\n theme,\n rsi,\n macd,\n movingAverages,\n vwap,\n );\n\n // RN-Skia's recorder reads this SharedValue on the UI/render runtime, a beat\n // behind JS-thread writes. If it ever reads null it throws (\"Invalid prop\n // value for SkTextBlob received\" — RN-Skia's mislabeled SkPicture error), so\n // we seed it with an empty picture and *never* assign null into it.\n const emptyPicture = useMemo(() => {\n const rec = Skia.PictureRecorder();\n rec.beginRecording(Skia.XYWHRect(0, 0, 1, 1));\n return rec.finishRecordingAsPicture();\n }, []);\n const pictureSV = useSharedValue<SkPicture>(emptyPicture);\n\n // When the crosshair is showing, pan moves it (instead of scrolling) and\n // pinch is disabled. A ref (not state) so gesture callbacks read it\n // synchronously without re-subscribing. Tap dismisses it.\n const crosshairActive = useRef(false);\n\n // timeMs of the candle last reported through onCrosshair, so a drag fires a\n // 'move' event only when it crosses into a *different* candle (one per\n // candle, not per frame). Null while the crosshair is hidden.\n const lastCrosshairTime = useRef<number | null>(null);\n\n // Sync the initial picture from useChartCore into the SV whenever it\n // refreshes (data load, size change, externally-controlled range change).\n // Only ever assign a non-null picture (see emptyPicture note above).\n useEffect(() => {\n if (picture) pictureSV.value = picture;\n }, [picture, pictureSV]);\n\n // Momentum scroll. After Pan ends with non-trivial velocity, we run a RAF\n // loop that calls handle.pan(dx, 0) each frame with an exponentially\n // decaying velocity. A new pan (or unmount) cancels the loop.\n const decayRaf = useRef<number | null>(null);\n const cancelDecay = useCallback(() => {\n if (decayRaf.current != null) {\n cancelAnimationFrame(decayRaf.current);\n decayRaf.current = null;\n }\n }, []);\n useEffect(() => cancelDecay, [cancelDecay]);\n\n // Axis-label fade animation loop. When a gesture changes which labels are\n // active, the C++ side starts ramping their opacities. We keep calling\n // render() on every frame until handle.isAnimating() returns false. The\n // loop is started by gesture callbacks (and the momentum tick) after they\n // update the picture, and self-stops when fades settle.\n const animRaf = useRef<number | null>(null);\n const animTick = useCallback(() => {\n animRaf.current = null;\n if (!handle) return;\n const next = handle.render();\n if (next) pictureSV.value = next;\n if (handle.isAnimating()) {\n animRaf.current = requestAnimationFrame(animTick);\n }\n }, [handle, pictureSV]);\n const maybeStartAnim = useCallback(() => {\n if (animRaf.current != null) return;\n if (!handle?.isAnimating()) return;\n animRaf.current = requestAnimationFrame(animTick);\n }, [handle, animTick]);\n useEffect(() => {\n return () => {\n if (animRaf.current != null) {\n cancelAnimationFrame(animRaf.current);\n animRaf.current = null;\n }\n };\n }, []);\n\n // Classifies a touch point into the candle area vs. an axis strip. Axis\n // strips always own their gesture (scale price/time) and take priority over\n // the crosshair: an axis touch never opens, moves, or dismisses it.\n const hitAxis = useCallback(\n (x: number, y: number): 'chart' | 'price-axis' | 'time-axis' | 'indicator' => {\n if (!handle) return 'chart';\n const { yAxisWidth, xAxisHeight, indicatorHeight } =\n handle.getAxisMetrics();\n if (x > width - yAxisWidth) return 'price-axis';\n if (y > height - xAxisHeight) return 'time-axis';\n // The indicator pane sits just above the time-axis strip. A drag here\n // scrolls the candles horizontally (no vertical price change).\n if (indicatorHeight > 0 && y > height - xAxisHeight - indicatorHeight) {\n return 'indicator';\n }\n return 'chart';\n },\n [handle, width, height],\n );\n\n // Pan routes to different C++ mutators depending on where it started: the\n // candle area (chart scroll / crosshair move), the y-axis strip (price\n // scale), the x-axis strip (time scale), or the indicator pane (horizontal\n // scroll only). We classify on onStart.\n const panMode = useRef<'chart' | 'price-axis' | 'time-axis' | 'indicator'>(\n 'chart',\n );\n\n const pan = Gesture.Pan()\n .runOnJS(true)\n .maxPointers(1) // don't fight Pinch's two-finger gesture\n .onStart((e) => {\n cancelDecay();\n // Always classify — an axis drag controls the axis even while the\n // crosshair is up. Only a chart-area drag interacts with the crosshair.\n panMode.current = hitAxis(e.x, e.y);\n })\n .onChange((e) => {\n if (!handle) return;\n let next: ReturnType<typeof handle.pan> = null;\n if (panMode.current === 'price-axis') {\n next = handle.scalePriceAxis(e.changeY);\n } else if (panMode.current === 'time-axis') {\n next = handle.scaleTimeAxis(e.changeX);\n } else if (panMode.current === 'indicator') {\n // Drag in an indicator pane scrolls the candles horizontally only —\n // no vertical price slide (the pane's scale is fixed).\n next = handle.pan(e.changeX, 0);\n } else if (crosshairActive.current) {\n // Chart area + crosshair up → the drag moves the crosshair instead of\n // scrolling. Vertical line tracks the finger x; the dot/horizontal line\n // stay lifted `crosshairOffset` px above the fingertip.\n const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);\n if (ch) pictureSV.value = ch;\n // The line follows the finger every frame (above), but only notify the\n // host when the snapped slot actually changes. The slot has a timeMs\n // even in the empty space ahead of the last candle, where candle=null.\n const info = handle.getCrosshairInfo();\n const t = info?.timeMs ?? null;\n if (t !== lastCrosshairTime.current) {\n lastCrosshairTime.current = t;\n // price is web-only for now (see @vroomchart/react); RN reports null.\n onCrosshair?.({ active: true, candle: info?.candle ?? null, timeMs: t, price: null, reason: 'move' });\n }\n return;\n } else {\n // Chart area: 1-finger drag translates both axes. Horizontal\n // component scrolls time, vertical component slides price bounds\n // (axes follow). Diagonal works naturally.\n next = handle.translate(e.changeX, e.changeY);\n }\n if (next) pictureSV.value = next;\n maybeStartAnim();\n })\n .onEnd((e) => {\n if (!handle) return;\n // A chart-area drag with the crosshair up just moved the crosshair —\n // nothing about the viewport changed, and no momentum.\n if (panMode.current === 'chart' && crosshairActive.current) return;\n onViewportChange?.(0, 0);\n\n // Axis drags don't get momentum — they're a precise size adjustment.\n // Chart and indicator-pane drags both get horizontal fling momentum.\n if (panMode.current !== 'chart' && panMode.current !== 'indicator') return;\n\n let velocity = e.velocityX; // px/s\n const MIN_LAUNCH = 80; // ignore tiny flicks\n const MIN_STOP = 8; // px/s — stop threshold\n const HALF_LIFE_S = 0.35; // velocity halves every 0.35s\n if (Math.abs(velocity) < MIN_LAUNCH) return;\n\n let lastTime = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = (now - lastTime) / 1000;\n lastTime = now;\n\n // Frame-time-independent exponential decay.\n velocity *= Math.pow(0.5, dt / HALF_LIFE_S);\n const dx = velocity * dt;\n const next = handle.pan(dx, 0);\n if (next) pictureSV.value = next;\n maybeStartAnim();\n\n if (Math.abs(velocity) > MIN_STOP) {\n decayRaf.current = requestAnimationFrame(tick);\n } else {\n decayRaf.current = null;\n }\n };\n decayRaf.current = requestAnimationFrame(tick);\n });\n\n // Directional pinch. A single Pinch scale is uniform, so we read the two\n // touch points and track their horizontal/vertical spans independently: a\n // vertical pinch scales price (y), a horizontal pinch scales the time window\n // (x), and a diagonal pinch does both. An axis whose initial span is tiny\n // (fingers ~collinear on that axis) is left alone.\n // Lock the scalable axes at gesture start by orientation: an axis only\n // scales if its initial span is meaningful AND at least AXIS_RATIO of the\n // other axis. This keeps a vertical pinch from ever touching x (and vice\n // versa) — critical because during a vertical pinch the fingers' x-coords\n // drift and cross, sending spanX through ~0 and otherwise exploding frameX.\n const MIN_SPAN = 24; // px — minimum span for an axis to scale at all\n const AXIS_RATIO = 0.5; // axis scales only if its span ≥ this × the other's\n const pinchStart = useRef({\n spanX: 1,\n spanY: 1,\n ratioX: 1,\n ratioY: 1,\n enableX: false,\n enableY: false,\n });\n const pinch = Gesture.Pinch()\n .runOnJS(true)\n .onTouchesDown((e) => {\n if (e.numberOfTouches < 2) return;\n const [a, b] = e.allTouches;\n const spanX = Math.abs(a.x - b.x);\n const spanY = Math.abs(a.y - b.y);\n pinchStart.current = {\n spanX,\n spanY,\n ratioX: 1,\n ratioY: 1,\n enableX: spanX >= MIN_SPAN && spanX >= spanY * AXIS_RATIO,\n enableY: spanY >= MIN_SPAN && spanY >= spanX * AXIS_RATIO,\n };\n })\n .onTouchesMove((e) => {\n if (!handle || crosshairActive.current) return;\n if (e.numberOfTouches < 2) return;\n const [a, b] = e.allTouches;\n const start = pinchStart.current;\n const focalX = (a.x + b.x) * 0.5;\n const focalY = (a.y + b.y) * 0.5;\n\n // Per-frame factor = current cumulative ratio / previous. Floor the\n // current span at MIN_SPAN so a near-zero span (fingers crossing on that\n // axis) can't blow the ratio up.\n let frameX = 1;\n if (start.enableX) {\n const ratioX = Math.max(Math.abs(a.x - b.x), MIN_SPAN) / start.spanX;\n frameX = ratioX / start.ratioX;\n start.ratioX = ratioX;\n }\n let frameY = 1;\n if (start.enableY) {\n const ratioY = Math.max(Math.abs(a.y - b.y), MIN_SPAN) / start.spanY;\n frameY = ratioY / start.ratioY;\n start.ratioY = ratioY;\n }\n if (frameX === 1 && frameY === 1) return;\n\n const next = handle.zoom(frameX, frameY, focalX, focalY);\n if (next) pictureSV.value = next;\n maybeStartAnim();\n });\n\n // Long press shows the crosshair at the press point. A stationary hold never\n // activates `pan` (it needs movement first), so the chart won't scroll under\n // the hold. The dot/horizontal line are lifted above the fingertip.\n const longPress = Gesture.LongPress()\n .runOnJS(true)\n .onStart((e) => {\n if (!handle) return;\n // A long press on an axis strip controls the axis, never the crosshair.\n if (hitAxis(e.x, e.y) !== 'chart') return;\n cancelDecay();\n crosshairActive.current = true;\n const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);\n if (ch) pictureSV.value = ch;\n const info = handle.getCrosshairInfo();\n lastCrosshairTime.current = info?.timeMs ?? null;\n onCrosshair?.({\n active: true,\n candle: info?.candle ?? null,\n timeMs: info?.timeMs ?? null,\n price: null, // web-only for now (see @vroomchart/react)\n reason: 'show',\n });\n });\n\n // A tap dismisses the crosshair while it's up; otherwise it's a no-op (so it\n // never interferes with normal pan/pinch).\n const tap = Gesture.Tap()\n .runOnJS(true)\n .onStart((e) => {\n if (!handle || !crosshairActive.current) return;\n // A tap on an axis strip controls the axis, never dismisses the crosshair.\n if (hitAxis(e.x, e.y) !== 'chart') return;\n crosshairActive.current = false;\n const ch = handle.clearCrosshair();\n if (ch) pictureSV.value = ch;\n lastCrosshairTime.current = null;\n onCrosshair?.({ active: false, candle: null, timeMs: null, price: null, reason: 'hide' });\n });\n\n const gesture = Gesture.Simultaneous(pan, pinch, longPress, tap);\n\n return (\n <GestureHandlerRootView\n onLayout={onLayout}\n style={[\n { width: widthProp, height: heightProp },\n widthProp == null && heightProp == null ? { flex: 1 } : null,\n style,\n ]}\n >\n <GestureDetector gesture={gesture}>\n <View style={{ flex: 1 }}>\n <Canvas style={{ flex: 1 }}>\n {width > 0 && height > 0 ? (\n // pictureSV is always a valid picture (seeded empty, never null),\n // so RN-Skia's UI-thread reader never sees null.\n <Picture picture={pictureSV} />\n ) : null}\n </Canvas>\n </View>\n </GestureDetector>\n </GestureHandlerRootView>\n );\n}\n","import { useEffect, useRef, useState } from 'react';\nimport type { SkPicture } from '@shopify/react-native-skia';\n\nimport NativeVroomChart from './NativeVroomChart';\nimport type { ChartHandle } from './jsi.d';\nimport { packCandles } from './packCandles';\nimport { applyTheme, parseColor } from './theme';\nimport type {\n Candle,\n MACDConfig,\n MovingAverageOverlay,\n RSIConfig,\n VisibleRange,\n VroomTheme,\n VWAPConfig,\n} from './types';\n\n// Mirrors vroom::ma::Source order in packages/core/src/ma.h.\nconst MA_SOURCES = [\n 'close',\n 'open',\n 'high',\n 'low',\n 'hl2',\n 'hlc3',\n 'ohlc4',\n] as const;\n\nfunction overlayToNumeric(o: MovingAverageOverlay) {\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\nlet installed = false;\nfunction ensureInstalled(): void {\n if (installed) return;\n const ok = NativeVroomChart.install();\n if (!ok) throw new Error('VroomChartModule.install() returned false');\n if (typeof globalThis.VroomChartJSI === 'undefined') {\n throw new Error('global.VroomChartJSI undefined after install()');\n }\n installed = true;\n}\n\nexport type ChartCoreState = {\n handle: ChartHandle | null;\n /** Picture freshly rendered after the latest data/size/range push. */\n picture: SkPicture | null;\n};\n\n// Owns a ChartHandle and produces an \"initial\" picture whenever data, size,\n// or the externally-controlled visible range changes. Gesture-driven updates\n// happen outside this hook by calling handle.pan(...) directly and assigning\n// the result into a SharedValue.\nexport function useChartCore(\n candles: Candle[],\n size: { width: number; height: number; pxRatio?: number },\n visibleRange?: VisibleRange,\n defaultCandleWidth?: number,\n theme?: VroomTheme,\n rsi?: RSIConfig,\n macd?: MACDConfig,\n movingAverages?: MovingAverageOverlay[],\n vwap?: VWAPConfig,\n): ChartCoreState {\n const handleRef = useRef<ChartHandle | null>(null);\n // Push setDefaultCandleWidth only once (first load): setCandles re-runs on\n // every data change, and the core setter re-frames when candles are present,\n // so re-pushing would snap the view away from the user's pan/zoom.\n const defaultWidthAppliedRef = useRef(false);\n const [picture, setPicture] = useState<SkPicture | null>(null);\n\n if (!handleRef.current && size.width > 0 && size.height > 0) {\n ensureInstalled();\n handleRef.current = globalThis.VroomChartJSI!.create();\n }\n\n // When no visibleRange is provided, leave the range entirely to the C++\n // side (which defaults to a sensible recent window on first setCandles).\n // Only push setVisibleRange when the caller is actively controlling it,\n // so it doesn't clobber the default or fight gesture-driven pans.\n const explicit = visibleRange != null;\n const startMs = visibleRange?.startMs ?? 0;\n const endMs = visibleRange?.endMs ?? 0;\n\n // Stable deps so inline `theme={{...}}` / `rsi={{...}}` literals don't re-run\n // the effect every render — only when the actual values change.\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\n useEffect(() => {\n const h = handleRef.current;\n if (!h) return;\n h.setSize(size.width, size.height, size.pxRatio ?? 1);\n // Drive the initial zoom from a target candle width. Pushed once, before the\n // first setCandles (while the core window is still 0/0), and only when the\n // caller isn't explicitly controlling the range.\n if (\n !defaultWidthAppliedRef.current &&\n !explicit &&\n defaultCandleWidth != null &&\n defaultCandleWidth > 0\n ) {\n h.setDefaultCandleWidth(defaultCandleWidth);\n defaultWidthAppliedRef.current = true;\n }\n if (candles.length > 0) {\n h.setCandles(packCandles(candles));\n }\n if (explicit) {\n h.setVisibleRange(startMs, endMs);\n }\n if (theme) {\n applyTheme(h, theme);\n }\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(\n macd?.enabled ?? false,\n macd?.fast ?? 12,\n macd?.slow ?? 26,\n macd?.signal ?? 9,\n );\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 // TODO(rn-parity): mirror the web `liquidity` overlay here (setLiquidity +\n // the VroomBand structs in the JSI handle) — web-only for now.\n setPicture(h.render());\n // theme/rsi/macd/movingAverages/vwap are represented by their *Key deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey]);\n\n return { handle: handleRef.current, picture };\n}\n","import type { TurboModule } from 'react-native';\nimport { TurboModuleRegistry } from 'react-native';\n\n// TurboModule spec consumed by codegen. The only method is `install`, which\n// the native side uses to install the `global.VroomChartJSI` host object the\n// first time it's called from JS. All real chart operations go through that\n// host object, not through the TurboModule itself.\nexport interface Spec extends TurboModule {\n install(): boolean;\n}\n\nexport default TurboModuleRegistry.getEnforcing<Spec>('VroomChartModule');\n","import type { Candle } from './types';\n\n// Wire format must match `VroomCandle` in packages/core/include/vroom/vroom_chart.h:\n// int64_t time_ms; double open, high, low, close, volume;\n// = 48 bytes per candle, 8-byte aligned, little-endian on iOS/Android.\nexport const BYTES_PER_CANDLE = 48;\n\n// Serializes candles into the packed little-endian buffer the C++ core expects.\n// Pure (no native/Skia deps) so it can be unit-tested in isolation.\nexport function packCandles(candles: Candle[]): ArrayBuffer {\n const buf = new ArrayBuffer(candles.length * BYTES_PER_CANDLE);\n const view = new DataView(buf);\n for (let i = 0; i < candles.length; i++) {\n const c = candles[i]!;\n const off = i * BYTES_PER_CANDLE;\n view.setBigInt64(off, BigInt(c.timeMs), true);\n view.setFloat64(off + 8, c.open, true);\n view.setFloat64(off + 16, c.high, true);\n view.setFloat64(off + 24, c.low, true);\n view.setFloat64(off + 32, c.close, true);\n view.setFloat64(off + 40, c.volume, true);\n }\n return buf;\n}\n","import type { ChartHandle } from './jsi.d';\nimport type { VroomColor, VroomTheme } from './types';\n\n// Maps each color VroomTheme field to its VroomColorKey index in the C++ enum\n// (packages/core/include/vroom/vroom_chart.h). Keep in sync with that enum;\n// new keys are appended there so existing indices never shift.\nexport const COLOR_KEYS: Partial<Record<keyof VroomTheme, number>> = {\n background: 0, // VROOM_COLOR_BACKGROUND\n bull: 1, // VROOM_COLOR_BULL\n bear: 2, // VROOM_COLOR_BEAR\n grid: 4, // VROOM_COLOR_GRID\n axisText: 5, // VROOM_COLOR_AXIS_TEXT\n crosshair: 6, // VROOM_COLOR_CROSSHAIR\n crosshairTarget: 9, // VROOM_COLOR_CROSSHAIR_TARGET\n borderBull: 10, // VROOM_COLOR_BORDER_BULL\n borderBear: 11, // VROOM_COLOR_BORDER_BEAR\n wickBull: 12, // VROOM_COLOR_WICK_BULL\n wickBear: 13, // VROOM_COLOR_WICK_BEAR\n accentBull: 14, // VROOM_COLOR_ACCENT_BULL\n accentBear: 15, // VROOM_COLOR_ACCENT_BEAR\n};\n\n// Maps each numeric VroomTheme field to its VroomFloatKey index.\nexport const FLOAT_KEYS: Partial<Record<keyof VroomTheme, number>> = {\n wickWidth: 1, // VROOM_FLOAT_WICK_WIDTH_PX\n candleRadius: 8, // VROOM_FLOAT_CANDLE_RADIUS_PX\n volumeRadius: 10, // VROOM_FLOAT_VOLUME_RADIUS_PX\n};\n\n// Maps each boolean VroomTheme field to its VroomFloatKey index (pushed as 0/1).\nexport const BOOL_KEYS: Partial<Record<keyof VroomTheme, number>> = {\n wickRoundCap: 9, // VROOM_FLOAT_WICK_ROUND_CAP\n};\n\n// Parses a color into a packed 0xAARRGGBB integer (Skia's ARGB order).\n// - number → taken as already-packed ARGB\n// - '#rgb'-style 6-digit hex → opaque (alpha forced to ff)\n// - 8-digit hex → interpreted as AARRGGBB\n// Returns null for anything malformed so the caller can skip it.\nexport function parseColor(value: VroomColor): number | null {\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value >>> 0 : null;\n }\n let s = value.trim();\n if (s.startsWith('#')) s = s.slice(1);\n if (s.length === 6) s = `ff${s}`; // assume opaque\n if (s.length !== 8 || !/^[0-9a-fA-F]{8}$/.test(s)) return null;\n return parseInt(s, 16) >>> 0;\n}\n\n// Pushes every provided theme color + numeric float into the chart core via\n// handle.setColor / handle.setFloat. Unspecified or unparseable values are\n// skipped (they keep their default).\nexport function applyTheme(handle: ChartHandle, theme: VroomTheme): void {\n (Object.keys(COLOR_KEYS) as (keyof VroomTheme)[]).forEach((field) => {\n const value = theme[field];\n if (typeof value !== 'string' && typeof value !== 'number') return;\n const argb = parseColor(value);\n if (argb == null) return;\n handle.setColor(COLOR_KEYS[field]!, argb);\n });\n (Object.keys(FLOAT_KEYS) as (keyof VroomTheme)[]).forEach((field) => {\n const value = theme[field];\n if (typeof value !== 'number' || !Number.isFinite(value)) return;\n handle.setFloat(FLOAT_KEYS[field]!, value);\n });\n (Object.keys(BOOL_KEYS) as (keyof VroomTheme)[]).forEach((field) => {\n const value = theme[field];\n if (typeof value !== 'boolean') return;\n handle.setFloat(BOOL_KEYS[field]!, value ? 1 : 0);\n });\n}\n"],"mappings":"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","names":["import_react","import_react_native","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/VroomChart.tsx","../src/useChartCore.ts","../src/NativeVroomChart.ts","../src/packCandles.ts","../src/theme.ts"],"sourcesContent":["export { VroomChart } from './VroomChart';\nexport type {\n VroomChartProps,\n Candle,\n CrosshairEvent,\n VroomTheme,\n VroomColor,\n VisibleRange,\n RSIConfig,\n MACDConfig,\n MASource,\n MovingAverageOverlay,\n VWAPConfig,\n ChartType,\n} from './types';\n","// VroomChart — Phase 3.\n//\n// Owns a SharedValue<SkPicture> driven by:\n// - useChartCore's \"initial\" picture (when data/size/range change), AND\n// - Pan gesture callbacks that call handle.pan(dx, dy) → fresh picture.\n//\n// Reanimated 4 + RN-Skia 2 propagate SharedValue<SkPicture> changes to\n// <Picture> without a React re-render, so gesture-driven redraws are cheap.\n//\n// Gestures run on the JS thread for now (`runOnJS(true)`) — installing the\n// JSI bindings on the worklet runtime is a later perf optimization.\n\nimport React, { useEffect, useRef, useCallback, useState, useMemo } from 'react';\nimport { View, type LayoutChangeEvent } from 'react-native';\nimport { Canvas, Picture, Skia, type SkPicture } from '@shopify/react-native-skia';\nimport {\n Gesture,\n GestureDetector,\n GestureHandlerRootView,\n} from 'react-native-gesture-handler';\nimport { useSharedValue } from 'react-native-reanimated';\n\nimport { useChartCore } from './useChartCore';\nimport type { VroomChartProps } from './types';\nimport './jsi.d';\n\n/**\n * Skia-rendered candlestick chart. Pass OHLCV `candles` and size it via `style`\n * (it fills its parent by default). Pan to scroll, pinch to zoom, drag the\n * price/time axes to rescale, and long-press for the crosshair. Optional\n * indicators (`rsi`, `macd`, `movingAverages`, `vwap`), colors (`theme`), and\n * events (`onCrosshair`, `onViewportChange`) are configured through props.\n *\n * @see {@link VroomChartProps} for the full prop reference.\n */\nexport function VroomChart(props: VroomChartProps) {\n const {\n candles,\n width: widthProp,\n height: heightProp,\n style,\n visibleRange,\n defaultCandleWidth,\n chartType,\n transitionMs,\n theme,\n rsi,\n macd,\n movingAverages,\n vwap,\n crosshairOffset = 40,\n onCrosshair,\n onViewportChange,\n } = props;\n\n // Fill the parent by default: measure via onLayout. Explicit width/height\n // props (if given) win per-axis. Until the first layout, dims are 0 and we\n // render nothing (one frame).\n const [measured, setMeasured] = useState({ width: 0, height: 0 });\n const width = widthProp ?? measured.width;\n const height = heightProp ?? measured.height;\n\n const onLayout = useCallback((e: LayoutChangeEvent) => {\n const w = Math.round(e.nativeEvent.layout.width);\n const h = Math.round(e.nativeEvent.layout.height);\n setMeasured((prev) =>\n prev.width === w && prev.height === h ? prev : { width: w, height: h },\n );\n }, []);\n\n const { handle, picture } = useChartCore(\n candles,\n { width, height },\n visibleRange,\n defaultCandleWidth,\n chartType,\n theme,\n rsi,\n macd,\n movingAverages,\n vwap,\n );\n\n // RN-Skia's recorder reads this SharedValue on the UI/render runtime, a beat\n // behind JS-thread writes. If it ever reads null it throws (\"Invalid prop\n // value for SkTextBlob received\" — RN-Skia's mislabeled SkPicture error), so\n // we seed it with an empty picture and *never* assign null into it.\n const emptyPicture = useMemo(() => {\n const rec = Skia.PictureRecorder();\n rec.beginRecording(Skia.XYWHRect(0, 0, 1, 1));\n return rec.finishRecordingAsPicture();\n }, []);\n const pictureSV = useSharedValue<SkPicture>(emptyPicture);\n\n // When the crosshair is showing, pan moves it (instead of scrolling) and\n // pinch is disabled. A ref (not state) so gesture callbacks read it\n // synchronously without re-subscribing. Tap dismisses it.\n const crosshairActive = useRef(false);\n\n // timeMs of the candle last reported through onCrosshair, so a drag fires a\n // 'move' event only when it crosses into a *different* candle (one per\n // candle, not per frame). Null while the crosshair is hidden.\n const lastCrosshairTime = useRef<number | null>(null);\n\n // Sync the initial picture from useChartCore into the SV whenever it\n // refreshes (data load, size change, externally-controlled range change).\n // Only ever assign a non-null picture (see emptyPicture note above).\n useEffect(() => {\n if (picture) pictureSV.value = picture;\n }, [picture, pictureSV]);\n\n // Momentum scroll. After Pan ends with non-trivial velocity, we run a RAF\n // loop that calls handle.pan(dx, 0) each frame with an exponentially\n // decaying velocity. A new pan (or unmount) cancels the loop.\n const decayRaf = useRef<number | null>(null);\n const cancelDecay = useCallback(() => {\n if (decayRaf.current != null) {\n cancelAnimationFrame(decayRaf.current);\n decayRaf.current = null;\n }\n }, []);\n useEffect(() => cancelDecay, [cancelDecay]);\n\n // Axis-label fade animation loop. When a gesture changes which labels are\n // active, the C++ side starts ramping their opacities. We keep calling\n // render() on every frame until handle.isAnimating() returns false. The\n // loop is started by gesture callbacks (and the momentum tick) after they\n // update the picture, and self-stops when fades settle.\n const animRaf = useRef<number | null>(null);\n const animTick = useCallback(() => {\n animRaf.current = null;\n if (!handle) return;\n const next = handle.render();\n if (next) pictureSV.value = next;\n if (handle.isAnimating()) {\n animRaf.current = requestAnimationFrame(animTick);\n }\n }, [handle, pictureSV]);\n const maybeStartAnim = useCallback(() => {\n if (animRaf.current != null) return;\n if (!handle?.isAnimating()) return;\n animRaf.current = requestAnimationFrame(animTick);\n }, [handle, animTick]);\n useEffect(() => {\n return () => {\n if (animRaf.current != null) {\n cancelAnimationFrame(animRaf.current);\n animRaf.current = null;\n }\n };\n }, []);\n\n // Candle↔line morph. When `chartType` changes we drive the core per-frame with\n // a (collapse, fade) blend and push a fresh picture into the SV each frame — the\n // JS side owns the eased clock (see plan). A fresh handle snaps to the target;\n // transitionMs=0 snaps. Mirrors the web driver in react/src/useChartCore.ts.\n const morphRaf = useRef<number | null>(null);\n const morphFade = useRef<number | null>(null);\n const morphHandle = useRef<typeof handle>(null);\n useEffect(() => {\n if (!handle) return undefined;\n const target = chartType === 'line' ? 1 : 0;\n\n // Fresh handle (first load / recreate): snap, don't animate.\n if (morphHandle.current !== handle || morphFade.current == null) {\n morphHandle.current = handle;\n morphFade.current = target;\n handle.setChartType(target);\n const p = handle.render();\n if (p) pictureSV.value = p;\n return undefined;\n }\n if (morphFade.current === target) return undefined;\n\n if (morphRaf.current != null) {\n cancelAnimationFrame(morphRaf.current);\n morphRaf.current = null;\n }\n const dur = Math.max(0, transitionMs ?? 300);\n if (dur === 0) {\n morphFade.current = target;\n handle.setChartType(target);\n const p = handle.render();\n if (p) pictureSV.value = p;\n return undefined;\n }\n\n const from = morphFade.current;\n let startTs: number | null = null;\n const step = (now: number) => {\n if (startTs == null) startTs = now;\n const prog = Math.min(1, (now - startTs) / dur);\n const e = prog * prog * (3 - 2 * prog); // smoothstep ease-in-out\n const fade = from + (target - from) * e;\n morphFade.current = fade;\n handle.setMorph(fade, fade);\n const p = handle.render();\n if (p) pictureSV.value = p;\n if (prog < 1) {\n morphRaf.current = requestAnimationFrame(step);\n } else {\n morphRaf.current = null;\n morphFade.current = target;\n handle.setChartType(target); // lock the exact endpoint\n const q = handle.render();\n if (q) pictureSV.value = q;\n }\n };\n morphRaf.current = requestAnimationFrame(step);\n\n return () => {\n if (morphRaf.current != null) {\n cancelAnimationFrame(morphRaf.current);\n morphRaf.current = null;\n }\n };\n }, [handle, chartType, transitionMs, pictureSV]);\n\n // Classifies a touch point into the candle area vs. an axis strip. Axis\n // strips always own their gesture (scale price/time) and take priority over\n // the crosshair: an axis touch never opens, moves, or dismisses it.\n const hitAxis = useCallback(\n (x: number, y: number): 'chart' | 'price-axis' | 'time-axis' | 'indicator' => {\n if (!handle) return 'chart';\n const { yAxisWidth, xAxisHeight, indicatorHeight } =\n handle.getAxisMetrics();\n if (x > width - yAxisWidth) return 'price-axis';\n if (y > height - xAxisHeight) return 'time-axis';\n // The indicator pane sits just above the time-axis strip. A drag here\n // scrolls the candles horizontally (no vertical price change).\n if (indicatorHeight > 0 && y > height - xAxisHeight - indicatorHeight) {\n return 'indicator';\n }\n return 'chart';\n },\n [handle, width, height],\n );\n\n // Pan routes to different C++ mutators depending on where it started: the\n // candle area (chart scroll / crosshair move), the y-axis strip (price\n // scale), the x-axis strip (time scale), or the indicator pane (horizontal\n // scroll only). We classify on onStart.\n const panMode = useRef<'chart' | 'price-axis' | 'time-axis' | 'indicator'>(\n 'chart',\n );\n\n const pan = Gesture.Pan()\n .runOnJS(true)\n .maxPointers(1) // don't fight Pinch's two-finger gesture\n .onStart((e) => {\n cancelDecay();\n // Always classify — an axis drag controls the axis even while the\n // crosshair is up. Only a chart-area drag interacts with the crosshair.\n panMode.current = hitAxis(e.x, e.y);\n })\n .onChange((e) => {\n if (!handle) return;\n let next: ReturnType<typeof handle.pan> = null;\n if (panMode.current === 'price-axis') {\n next = handle.scalePriceAxis(e.changeY);\n } else if (panMode.current === 'time-axis') {\n next = handle.scaleTimeAxis(e.changeX);\n } else if (panMode.current === 'indicator') {\n // Drag in an indicator pane scrolls the candles horizontally only —\n // no vertical price slide (the pane's scale is fixed).\n next = handle.pan(e.changeX, 0);\n } else if (crosshairActive.current) {\n // Chart area + crosshair up → the drag moves the crosshair instead of\n // scrolling. Vertical line tracks the finger x; the dot/horizontal line\n // stay lifted `crosshairOffset` px above the fingertip.\n const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);\n if (ch) pictureSV.value = ch;\n // The line follows the finger every frame (above), but only notify the\n // host when the snapped slot actually changes. The slot has a timeMs\n // even in the empty space ahead of the last candle, where candle=null.\n const info = handle.getCrosshairInfo();\n const t = info?.timeMs ?? null;\n if (t !== lastCrosshairTime.current) {\n lastCrosshairTime.current = t;\n // price is web-only for now (see @vroomchart/react); RN reports null.\n onCrosshair?.({ active: true, candle: info?.candle ?? null, timeMs: t, price: null, reason: 'move' });\n }\n return;\n } else {\n // Chart area: 1-finger drag translates both axes. Horizontal\n // component scrolls time, vertical component slides price bounds\n // (axes follow). Diagonal works naturally.\n next = handle.translate(e.changeX, e.changeY);\n }\n if (next) pictureSV.value = next;\n maybeStartAnim();\n })\n .onEnd((e) => {\n if (!handle) return;\n // A chart-area drag with the crosshair up just moved the crosshair —\n // nothing about the viewport changed, and no momentum.\n if (panMode.current === 'chart' && crosshairActive.current) return;\n onViewportChange?.(0, 0);\n\n // Axis drags don't get momentum — they're a precise size adjustment.\n // Chart and indicator-pane drags both get horizontal fling momentum.\n if (panMode.current !== 'chart' && panMode.current !== 'indicator') return;\n\n let velocity = e.velocityX; // px/s\n const MIN_LAUNCH = 80; // ignore tiny flicks\n const MIN_STOP = 8; // px/s — stop threshold\n const HALF_LIFE_S = 0.35; // velocity halves every 0.35s\n if (Math.abs(velocity) < MIN_LAUNCH) return;\n\n let lastTime = performance.now();\n const tick = () => {\n const now = performance.now();\n const dt = (now - lastTime) / 1000;\n lastTime = now;\n\n // Frame-time-independent exponential decay.\n velocity *= Math.pow(0.5, dt / HALF_LIFE_S);\n const dx = velocity * dt;\n const next = handle.pan(dx, 0);\n if (next) pictureSV.value = next;\n maybeStartAnim();\n\n if (Math.abs(velocity) > MIN_STOP) {\n decayRaf.current = requestAnimationFrame(tick);\n } else {\n decayRaf.current = null;\n }\n };\n decayRaf.current = requestAnimationFrame(tick);\n });\n\n // Directional pinch. A single Pinch scale is uniform, so we read the two\n // touch points and track their horizontal/vertical spans independently: a\n // vertical pinch scales price (y), a horizontal pinch scales the time window\n // (x), and a diagonal pinch does both. An axis whose initial span is tiny\n // (fingers ~collinear on that axis) is left alone.\n // Lock the scalable axes at gesture start by orientation: an axis only\n // scales if its initial span is meaningful AND at least AXIS_RATIO of the\n // other axis. This keeps a vertical pinch from ever touching x (and vice\n // versa) — critical because during a vertical pinch the fingers' x-coords\n // drift and cross, sending spanX through ~0 and otherwise exploding frameX.\n const MIN_SPAN = 24; // px — minimum span for an axis to scale at all\n const AXIS_RATIO = 0.5; // axis scales only if its span ≥ this × the other's\n const pinchStart = useRef({\n spanX: 1,\n spanY: 1,\n ratioX: 1,\n ratioY: 1,\n enableX: false,\n enableY: false,\n });\n const pinch = Gesture.Pinch()\n .runOnJS(true)\n .onTouchesDown((e) => {\n if (e.numberOfTouches < 2) return;\n const [a, b] = e.allTouches;\n const spanX = Math.abs(a.x - b.x);\n const spanY = Math.abs(a.y - b.y);\n pinchStart.current = {\n spanX,\n spanY,\n ratioX: 1,\n ratioY: 1,\n enableX: spanX >= MIN_SPAN && spanX >= spanY * AXIS_RATIO,\n enableY: spanY >= MIN_SPAN && spanY >= spanX * AXIS_RATIO,\n };\n })\n .onTouchesMove((e) => {\n if (!handle || crosshairActive.current) return;\n if (e.numberOfTouches < 2) return;\n const [a, b] = e.allTouches;\n const start = pinchStart.current;\n const focalX = (a.x + b.x) * 0.5;\n const focalY = (a.y + b.y) * 0.5;\n\n // Per-frame factor = current cumulative ratio / previous. Floor the\n // current span at MIN_SPAN so a near-zero span (fingers crossing on that\n // axis) can't blow the ratio up.\n let frameX = 1;\n if (start.enableX) {\n const ratioX = Math.max(Math.abs(a.x - b.x), MIN_SPAN) / start.spanX;\n frameX = ratioX / start.ratioX;\n start.ratioX = ratioX;\n }\n let frameY = 1;\n if (start.enableY) {\n const ratioY = Math.max(Math.abs(a.y - b.y), MIN_SPAN) / start.spanY;\n frameY = ratioY / start.ratioY;\n start.ratioY = ratioY;\n }\n if (frameX === 1 && frameY === 1) return;\n\n const next = handle.zoom(frameX, frameY, focalX, focalY);\n if (next) pictureSV.value = next;\n maybeStartAnim();\n });\n\n // Long press shows the crosshair at the press point. A stationary hold never\n // activates `pan` (it needs movement first), so the chart won't scroll under\n // the hold. The dot/horizontal line are lifted above the fingertip.\n const longPress = Gesture.LongPress()\n .runOnJS(true)\n .onStart((e) => {\n if (!handle) return;\n // A long press on an axis strip controls the axis, never the crosshair.\n if (hitAxis(e.x, e.y) !== 'chart') return;\n cancelDecay();\n crosshairActive.current = true;\n const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);\n if (ch) pictureSV.value = ch;\n const info = handle.getCrosshairInfo();\n lastCrosshairTime.current = info?.timeMs ?? null;\n onCrosshair?.({\n active: true,\n candle: info?.candle ?? null,\n timeMs: info?.timeMs ?? null,\n price: null, // web-only for now (see @vroomchart/react)\n reason: 'show',\n });\n });\n\n // A tap dismisses the crosshair while it's up; otherwise it's a no-op (so it\n // never interferes with normal pan/pinch).\n const tap = Gesture.Tap()\n .runOnJS(true)\n .onStart((e) => {\n if (!handle || !crosshairActive.current) return;\n // A tap on an axis strip controls the axis, never dismisses the crosshair.\n if (hitAxis(e.x, e.y) !== 'chart') return;\n crosshairActive.current = false;\n const ch = handle.clearCrosshair();\n if (ch) pictureSV.value = ch;\n lastCrosshairTime.current = null;\n onCrosshair?.({ active: false, candle: null, timeMs: null, price: null, reason: 'hide' });\n });\n\n const gesture = Gesture.Simultaneous(pan, pinch, longPress, tap);\n\n return (\n <GestureHandlerRootView\n onLayout={onLayout}\n style={[\n { width: widthProp, height: heightProp },\n widthProp == null && heightProp == null ? { flex: 1 } : null,\n style,\n ]}\n >\n <GestureDetector gesture={gesture}>\n <View style={{ flex: 1 }}>\n <Canvas style={{ flex: 1 }}>\n {width > 0 && height > 0 ? (\n // pictureSV is always a valid picture (seeded empty, never null),\n // so RN-Skia's UI-thread reader never sees null.\n <Picture picture={pictureSV} />\n ) : null}\n </Canvas>\n </View>\n </GestureDetector>\n </GestureHandlerRootView>\n );\n}\n","import { useEffect, useRef, useState } from 'react';\nimport type { SkPicture } from '@shopify/react-native-skia';\n\nimport NativeVroomChart from './NativeVroomChart';\nimport type { ChartHandle } from './jsi.d';\nimport { packCandles } from './packCandles';\nimport { applyTheme, parseColor } from './theme';\nimport type {\n Candle,\n ChartType,\n MACDConfig,\n MovingAverageOverlay,\n RSIConfig,\n VisibleRange,\n VroomTheme,\n VWAPConfig,\n} from './types';\n\n// Mirrors vroom::ma::Source order in packages/core/src/ma.h.\nconst MA_SOURCES = [\n 'close',\n 'open',\n 'high',\n 'low',\n 'hl2',\n 'hlc3',\n 'ohlc4',\n] as const;\n\nfunction overlayToNumeric(o: MovingAverageOverlay) {\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\nlet installed = false;\nfunction ensureInstalled(): void {\n if (installed) return;\n const ok = NativeVroomChart.install();\n if (!ok) throw new Error('VroomChartModule.install() returned false');\n if (typeof globalThis.VroomChartJSI === 'undefined') {\n throw new Error('global.VroomChartJSI undefined after install()');\n }\n installed = true;\n}\n\nexport type ChartCoreState = {\n handle: ChartHandle | null;\n /** Picture freshly rendered after the latest data/size/range push. */\n picture: SkPicture | null;\n};\n\n// Owns a ChartHandle and produces an \"initial\" picture whenever data, size,\n// or the externally-controlled visible range changes. Gesture-driven updates\n// happen outside this hook by calling handle.pan(...) directly and assigning\n// the result into a SharedValue.\nexport function useChartCore(\n candles: Candle[],\n size: { width: number; height: number; pxRatio?: number },\n visibleRange?: VisibleRange,\n defaultCandleWidth?: number,\n chartType?: ChartType,\n theme?: VroomTheme,\n rsi?: RSIConfig,\n macd?: MACDConfig,\n movingAverages?: MovingAverageOverlay[],\n vwap?: VWAPConfig,\n): ChartCoreState {\n const handleRef = useRef<ChartHandle | null>(null);\n // Push setDefaultCandleWidth only once (first load): setCandles re-runs on\n // every data change, and the core setter re-frames when candles are present,\n // so re-pushing would snap the view away from the user's pan/zoom.\n const defaultWidthAppliedRef = useRef(false);\n const [picture, setPicture] = useState<SkPicture | null>(null);\n\n if (!handleRef.current && size.width > 0 && size.height > 0) {\n ensureInstalled();\n handleRef.current = globalThis.VroomChartJSI!.create();\n }\n\n // When no visibleRange is provided, leave the range entirely to the C++\n // side (which defaults to a sensible recent window on first setCandles).\n // Only push setVisibleRange when the caller is actively controlling it,\n // so it doesn't clobber the default or fight gesture-driven pans.\n const explicit = visibleRange != null;\n const startMs = visibleRange?.startMs ?? 0;\n const endMs = visibleRange?.endMs ?? 0;\n\n // Stable deps so inline `theme={{...}}` / `rsi={{...}}` literals don't re-run\n // the effect every render — only when the actual values change.\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\n useEffect(() => {\n const h = handleRef.current;\n if (!h) return;\n h.setSize(size.width, size.height, size.pxRatio ?? 1);\n // Drive the initial zoom from a target candle width. Pushed once, before the\n // first setCandles (while the core window is still 0/0), and only when the\n // caller isn't explicitly controlling the range.\n if (\n !defaultWidthAppliedRef.current &&\n !explicit &&\n defaultCandleWidth != null &&\n defaultCandleWidth > 0\n ) {\n h.setDefaultCandleWidth(defaultCandleWidth);\n defaultWidthAppliedRef.current = true;\n }\n if (candles.length > 0) {\n h.setCandles(packCandles(candles));\n }\n if (explicit) {\n h.setVisibleRange(startMs, endMs);\n }\n // chartType / the candle↔line morph is driven separately (VroomChart owns the\n // per-frame animation loop so it can update the picture SharedValue directly).\n if (theme) {\n applyTheme(h, theme);\n }\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(\n macd?.enabled ?? false,\n macd?.fast ?? 12,\n macd?.slow ?? 26,\n macd?.signal ?? 9,\n );\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 // TODO(rn-parity): mirror the web `liquidity` overlay here (setLiquidity +\n // the VroomBand structs in the JSI handle) — web-only for now.\n setPicture(h.render());\n // theme/rsi/macd/movingAverages/vwap are represented by their *Key deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey]);\n\n return { handle: handleRef.current, picture };\n}\n","import type { TurboModule } from 'react-native';\nimport { TurboModuleRegistry } from 'react-native';\n\n// TurboModule spec consumed by codegen. The only method is `install`, which\n// the native side uses to install the `global.VroomChartJSI` host object the\n// first time it's called from JS. All real chart operations go through that\n// host object, not through the TurboModule itself.\nexport interface Spec extends TurboModule {\n install(): boolean;\n}\n\nexport default TurboModuleRegistry.getEnforcing<Spec>('VroomChartModule');\n","import type { Candle } from './types';\n\n// Wire format must match `VroomCandle` in packages/core/include/vroom/vroom_chart.h:\n// int64_t time_ms; double open, high, low, close, volume;\n// = 48 bytes per candle, 8-byte aligned, little-endian on iOS/Android.\nexport const BYTES_PER_CANDLE = 48;\n\n// Serializes candles into the packed little-endian buffer the C++ core expects.\n// Pure (no native/Skia deps) so it can be unit-tested in isolation.\nexport function packCandles(candles: Candle[]): ArrayBuffer {\n const buf = new ArrayBuffer(candles.length * BYTES_PER_CANDLE);\n const view = new DataView(buf);\n for (let i = 0; i < candles.length; i++) {\n const c = candles[i]!;\n const off = i * BYTES_PER_CANDLE;\n view.setBigInt64(off, BigInt(c.timeMs), true);\n view.setFloat64(off + 8, c.open, true);\n view.setFloat64(off + 16, c.high, true);\n view.setFloat64(off + 24, c.low, true);\n view.setFloat64(off + 32, c.close, true);\n view.setFloat64(off + 40, c.volume, true);\n }\n return buf;\n}\n","import type { ChartHandle } from './jsi.d';\nimport type { VroomColor, VroomTheme } from './types';\n\n// Maps each color VroomTheme field to its VroomColorKey index in the C++ enum\n// (packages/core/include/vroom/vroom_chart.h). Keep in sync with that enum;\n// new keys are appended there so existing indices never shift.\nexport const COLOR_KEYS: Partial<Record<keyof VroomTheme, number>> = {\n background: 0, // VROOM_COLOR_BACKGROUND\n bull: 1, // VROOM_COLOR_BULL\n bear: 2, // VROOM_COLOR_BEAR\n grid: 4, // VROOM_COLOR_GRID\n axisText: 5, // VROOM_COLOR_AXIS_TEXT\n crosshair: 6, // VROOM_COLOR_CROSSHAIR\n crosshairTarget: 9, // VROOM_COLOR_CROSSHAIR_TARGET\n borderBull: 10, // VROOM_COLOR_BORDER_BULL\n borderBear: 11, // VROOM_COLOR_BORDER_BEAR\n wickBull: 12, // VROOM_COLOR_WICK_BULL\n wickBear: 13, // VROOM_COLOR_WICK_BEAR\n accentBull: 14, // VROOM_COLOR_ACCENT_BULL\n accentBear: 15, // VROOM_COLOR_ACCENT_BEAR\n lineColor: 16, // VROOM_COLOR_LINE\n};\n\n// Maps each numeric VroomTheme field to its VroomFloatKey index.\nexport const FLOAT_KEYS: Partial<Record<keyof VroomTheme, number>> = {\n wickWidth: 1, // VROOM_FLOAT_WICK_WIDTH_PX\n candleRadius: 8, // VROOM_FLOAT_CANDLE_RADIUS_PX\n volumeRadius: 10, // VROOM_FLOAT_VOLUME_RADIUS_PX\n lineWidth: 11, // VROOM_FLOAT_LINE_WIDTH_PX\n};\n\n// Maps each boolean VroomTheme field to its VroomFloatKey index (pushed as 0/1).\nexport const BOOL_KEYS: Partial<Record<keyof VroomTheme, number>> = {\n wickRoundCap: 9, // VROOM_FLOAT_WICK_ROUND_CAP\n};\n\n// Parses a color into a packed 0xAARRGGBB integer (Skia's ARGB order).\n// - number → taken as already-packed ARGB\n// - '#rgb'-style 6-digit hex → opaque (alpha forced to ff)\n// - 8-digit hex → interpreted as AARRGGBB\n// Returns null for anything malformed so the caller can skip it.\nexport function parseColor(value: VroomColor): number | null {\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value >>> 0 : null;\n }\n let s = value.trim();\n if (s.startsWith('#')) s = s.slice(1);\n if (s.length === 6) s = `ff${s}`; // assume opaque\n if (s.length !== 8 || !/^[0-9a-fA-F]{8}$/.test(s)) return null;\n return parseInt(s, 16) >>> 0;\n}\n\n// Pushes every provided theme color + numeric float into the chart core via\n// handle.setColor / handle.setFloat. Unspecified or unparseable values are\n// skipped (they keep their default).\nexport function applyTheme(handle: ChartHandle, theme: VroomTheme): void {\n (Object.keys(COLOR_KEYS) as (keyof VroomTheme)[]).forEach((field) => {\n const value = theme[field];\n if (typeof value !== 'string' && typeof value !== 'number') return;\n const argb = parseColor(value);\n if (argb == null) return;\n handle.setColor(COLOR_KEYS[field]!, argb);\n });\n (Object.keys(FLOAT_KEYS) as (keyof VroomTheme)[]).forEach((field) => {\n const value = theme[field];\n if (typeof value !== 'number' || !Number.isFinite(value)) return;\n handle.setFloat(FLOAT_KEYS[field]!, value);\n });\n (Object.keys(BOOL_KEYS) as (keyof VroomTheme)[]).forEach((field) => {\n const value = theme[field];\n if (typeof value !== 'boolean') return;\n handle.setFloat(BOOL_KEYS[field]!, value ? 1 : 0);\n });\n}\n"],"mappings":"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","names":["import_react","import_react_native","React"]}
package/lib/index.mjs CHANGED
@@ -60,16 +60,20 @@ var COLOR_KEYS = {
60
60
  // VROOM_COLOR_WICK_BEAR
61
61
  accentBull: 14,
62
62
  // VROOM_COLOR_ACCENT_BULL
63
- accentBear: 15
63
+ accentBear: 15,
64
64
  // VROOM_COLOR_ACCENT_BEAR
65
+ lineColor: 16
66
+ // VROOM_COLOR_LINE
65
67
  };
66
68
  var FLOAT_KEYS = {
67
69
  wickWidth: 1,
68
70
  // VROOM_FLOAT_WICK_WIDTH_PX
69
71
  candleRadius: 8,
70
72
  // VROOM_FLOAT_CANDLE_RADIUS_PX
71
- volumeRadius: 10
73
+ volumeRadius: 10,
72
74
  // VROOM_FLOAT_VOLUME_RADIUS_PX
75
+ lineWidth: 11
76
+ // VROOM_FLOAT_LINE_WIDTH_PX
73
77
  };
74
78
  var BOOL_KEYS = {
75
79
  wickRoundCap: 9
@@ -135,7 +139,7 @@ function ensureInstalled() {
135
139
  }
136
140
  installed = true;
137
141
  }
138
- function useChartCore(candles, size, visibleRange, defaultCandleWidth, theme, rsi, macd, movingAverages, vwap) {
142
+ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap) {
139
143
  const handleRef = useRef(null);
140
144
  const defaultWidthAppliedRef = useRef(false);
141
145
  const [picture, setPicture] = useState(null);
@@ -203,6 +207,8 @@ function VroomChart(props) {
203
207
  style,
204
208
  visibleRange,
205
209
  defaultCandleWidth,
210
+ chartType,
211
+ transitionMs,
206
212
  theme,
207
213
  rsi,
208
214
  macd,
@@ -227,6 +233,7 @@ function VroomChart(props) {
227
233
  { width, height },
228
234
  visibleRange,
229
235
  defaultCandleWidth,
236
+ chartType,
230
237
  theme,
231
238
  rsi,
232
239
  macd,
@@ -275,6 +282,62 @@ function VroomChart(props) {
275
282
  }
276
283
  };
277
284
  }, []);
285
+ const morphRaf = useRef2(null);
286
+ const morphFade = useRef2(null);
287
+ const morphHandle = useRef2(null);
288
+ useEffect2(() => {
289
+ if (!handle) return void 0;
290
+ const target = chartType === "line" ? 1 : 0;
291
+ if (morphHandle.current !== handle || morphFade.current == null) {
292
+ morphHandle.current = handle;
293
+ morphFade.current = target;
294
+ handle.setChartType(target);
295
+ const p = handle.render();
296
+ if (p) pictureSV.value = p;
297
+ return void 0;
298
+ }
299
+ if (morphFade.current === target) return void 0;
300
+ if (morphRaf.current != null) {
301
+ cancelAnimationFrame(morphRaf.current);
302
+ morphRaf.current = null;
303
+ }
304
+ const dur = Math.max(0, transitionMs ?? 300);
305
+ if (dur === 0) {
306
+ morphFade.current = target;
307
+ handle.setChartType(target);
308
+ const p = handle.render();
309
+ if (p) pictureSV.value = p;
310
+ return void 0;
311
+ }
312
+ const from = morphFade.current;
313
+ let startTs = null;
314
+ const step = (now) => {
315
+ if (startTs == null) startTs = now;
316
+ const prog = Math.min(1, (now - startTs) / dur);
317
+ const e = prog * prog * (3 - 2 * prog);
318
+ const fade = from + (target - from) * e;
319
+ morphFade.current = fade;
320
+ handle.setMorph(fade, fade);
321
+ const p = handle.render();
322
+ if (p) pictureSV.value = p;
323
+ if (prog < 1) {
324
+ morphRaf.current = requestAnimationFrame(step);
325
+ } else {
326
+ morphRaf.current = null;
327
+ morphFade.current = target;
328
+ handle.setChartType(target);
329
+ const q = handle.render();
330
+ if (q) pictureSV.value = q;
331
+ }
332
+ };
333
+ morphRaf.current = requestAnimationFrame(step);
334
+ return () => {
335
+ if (morphRaf.current != null) {
336
+ cancelAnimationFrame(morphRaf.current);
337
+ morphRaf.current = null;
338
+ }
339
+ };
340
+ }, [handle, chartType, transitionMs, pictureSV]);
278
341
  const hitAxis = useCallback(
279
342
  (x, y) => {
280
343
  if (!handle) return "chart";