react-native-vroom-chart 0.16.0 → 0.17.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/cpp/VroomChartHostObject.cpp +81 -0
- package/cpp/_core_include/vroom/vroom_chart.h +41 -0
- package/cpp/_core_src/candles.cpp +15 -14
- package/cpp/_core_src/chart.cpp +297 -28
- package/cpp/_core_src/chart.h +71 -2
- package/cpp/_core_src/chart_facade.cpp +27 -0
- package/cpp/_core_src/color_lerp.h +28 -0
- package/cpp/_core_src/loading_line.cpp +183 -0
- package/cpp/_core_src/loading_line.h +38 -0
- package/cpp/_core_src/loading_wave.h +140 -0
- package/cpp/_core_src/ma_overlay.cpp +15 -10
- package/cpp/_core_src/ma_overlay.h +7 -0
- package/cpp/_core_src/price_indicator.cpp +21 -7
- package/cpp/_core_src/price_indicator.h +11 -1
- package/cpp/_core_src/price_indicator_anim.h +81 -0
- package/cpp/_core_src/theme.cpp +5 -0
- package/cpp/_core_src/tip_geometry.h +55 -0
- package/cpp/_core_src/viewport.h +33 -0
- package/lib/index.d.mts +30 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +71 -11
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +71 -11
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +12 -0
- package/src/jsi.d.ts +42 -0
- package/src/theme.ts +1 -0
- package/src/useChartCore.ts +119 -4
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/dataTransitions.ts","../src/easing.ts","../src/packCandles.ts","../src/theme.ts"],"sourcesContent":["export { VroomChart } from './VroomChart';\nexport {\n classifyTransition,\n inferStepMs,\n timeframeWindow,\n type DataTransition,\n} from './dataTransitions';\nexport type {\n VroomChartProps,\n Candle,\n CrosshairEvent,\n VroomTheme,\n VroomColor,\n VisibleRange,\n RSIConfig,\n MACDConfig,\n ATRConfig,\n ATRSmoothing,\n MASource,\n MAKind,\n MovingAverageOverlay,\n VWAPConfig,\n BollingerBandsConfig,\n IchimokuConfig,\n FairValueGapsConfig,\n VolumeConfig,\n ChartType,\n TransitionEasing,\n IntervalTransition,\n StreamTransition,\n PriceLine,\n PriceLinesStyle,\n Footprint,\n FootprintSide,\n FootprintsStyle,\n FootprintEvent,\n PlotRect,\n DefaultDrawingStyle,\n} from './types';\n","// VroomChart — Phase 3.\n//\n// Owns SharedValues driven by:\n// - useChartCore's \"initial\" frame (when data/size/range change), AND\n// - Pan gesture callbacks that call handle.pan(dx, dy) → a fresh frame.\n//\n// iOS wraps an SkPicture in-process. Android rasterizes to an SkImage (the\n// two Skia copies can't share a picture pointer) so pan/zoom don't serialize\n// the scene — and the system typeface — on every frame.\n//\n// Reanimated 4 + RN-Skia 2 propagate SharedValue changes to <Picture>/<Image>\n// 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 { PixelRatio, View, type LayoutChangeEvent } from 'react-native';\nimport {\n AlphaType,\n Canvas,\n ColorType,\n Image,\n Picture,\n Skia,\n type SkImage,\n type SkPicture,\n} from '@shopify/react-native-skia';\nimport {\n Gesture,\n GestureDetector,\n GestureHandlerRootView,\n} from 'react-native-gesture-handler';\nimport { useReducedMotion, useSharedValue } from 'react-native-reanimated';\n\nimport { useChartCore } from './useChartCore';\nimport { ease, easingIndex } from './easing';\nimport type { ChartFrame } from './jsi.d';\nimport type { Footprint, VroomChartProps } from './types';\nimport './jsi.d';\n\n// Mirrors VroomFootprintSide in packages/core/include/vroom/vroom_chart.h.\nconst FOOTPRINT_SELL = 1;\n\nfunction isSkImage(frame: ChartFrame): frame is SkImage {\n return typeof (frame as SkImage).getImageInfo === 'function';\n}\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`, `bollingerBands`,\n * `ichimoku`, and more), colors (`theme`), and events (`onCrosshair`,\n * `onViewportChange`) are configured through props.\n *\n * @see {@link VroomChartProps} for the full prop reference.\n */\nexport function VroomChart(props: VroomChartProps) {\n const {\n candles,\n seriesKey,\n width: widthProp,\n height: heightProp,\n style,\n visibleRange,\n defaultCandleWidth,\n chartType,\n transitionMs,\n transitionEasing,\n intervalTransition,\n streamTransition,\n streamTransitionMs,\n theme,\n rsi,\n macd,\n atr,\n movingAverages,\n vwap,\n bollingerBands,\n ichimoku,\n fairValueGaps,\n volume,\n crosshairOffset = 40,\n onCrosshair,\n onViewportChange,\n priceLines,\n priceLinesStyle,\n onPriceLineDrag,\n onPriceLineDragEnd,\n onPriceLineClose,\n footprints,\n footprintsStyle,\n onFootprint,\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 // The close button is callback-gated, so whether a handler exists is part of\n // what gets rendered.\n const priceLinesProp = useMemo(\n () =>\n priceLines\n ? {\n lines: priceLines,\n style: priceLinesStyle,\n hasCloseHandler: onPriceLineClose != null,\n }\n : undefined,\n [priceLines, priceLinesStyle, onPriceLineClose],\n );\n\n const footprintsProp = useMemo(\n () => (footprints ? { prints: footprints, style: footprintsStyle } : undefined),\n [footprints, footprintsStyle],\n );\n\n // RN-Skia's recorder reads these SharedValues on the UI/render runtime, a\n // beat behind JS-thread writes. If it ever reads null it throws (\"Invalid\n // prop value for SkTextBlob received\" — RN-Skia's mislabeled SkPicture\n // error), so we seed them and *never* assign null. Android writes the image\n // SV (raster path); iOS writes the picture SV. The unused layer stays a\n // transparent 1×1 so it doesn't cover the other.\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 emptyImage = useMemo(() => {\n const data = Skia.Data.fromBytes(new Uint8Array(4));\n return Skia.Image.MakeImage(\n {\n width: 1,\n height: 1,\n colorType: ColorType.RGBA_8888,\n alphaType: AlphaType.Premul,\n },\n data,\n 4,\n )!;\n }, []);\n const pictureSV = useSharedValue<SkPicture>(emptyPicture);\n const imageSV = useSharedValue<SkImage>(emptyImage);\n const applyFrame = useCallback(\n (frame: ChartFrame) => {\n if (isSkImage(frame)) imageSV.value = frame;\n else pictureSV.value = frame;\n },\n [imageSV, pictureSV],\n );\n\n // An OS reduced-motion preference snaps every transition, the way\n // prefers-reduced-motion does on web.\n const reduceMotion = useReducedMotion();\n\n // The interval morph starts inside the data effect (it needs the pre-swap\n // capture) but repaints every frame, so it writes straight into the SV rather\n // than through React state — the same bypass the gesture handlers use.\n const onFrame = useCallback(\n (p: ChartFrame) => {\n applyFrame(p);\n },\n [applyFrame],\n );\n\n const { handle, picture, volumeCollapseRef } = useChartCore(\n candles,\n { width, height, pxRatio: PixelRatio.get() },\n visibleRange,\n defaultCandleWidth,\n chartType,\n theme,\n rsi,\n macd,\n atr,\n movingAverages,\n vwap,\n bollingerBands,\n ichimoku,\n fairValueGaps,\n volume,\n priceLinesProp,\n footprintsProp,\n {\n seriesKey,\n transitionMs,\n transitionEasing,\n intervalTransition,\n streamTransition,\n streamTransitionMs,\n reduceMotion,\n onFrame,\n },\n );\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 // Whether a footprint badge is currently open, so a tap that misses every badge\n // knows whether it has a tooltip to dismiss. A ref for the same reason as\n // crosshairActive: gesture callbacks read it synchronously.\n const footprintActive = 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 // 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) applyFrame(next);\n if (handle.isAnimating()) {\n animRaf.current = requestAnimationFrame(animTick);\n }\n }, [handle, applyFrame]);\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 // 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 //\n // Sits below maybeStartAnim so it can kick the loop: it's the only path a\n // non-gesture change has into it, which is what a theme that turns the line-tip\n // pulse on needs — otherwise the ring wouldn't move until you touched the\n // chart.\n useEffect(() => {\n if (picture) applyFrame(picture);\n maybeStartAnim();\n }, [picture, applyFrame, maybeStartAnim]);\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 // In a ref so changing the curve mid-animation doesn't restart the clock.\n const easingRef = useRef(transitionEasing);\n easingRef.current = transitionEasing;\n useEffect(() => {\n if (!handle) return undefined;\n const target = chartType === 'line' ? 1 : 0;\n\n // Every exit below hands off to maybeStartAnim. Landing in line mode turns\n // tip_pulse_active() on, and the pulse only moves while that loop is\n // requeueing frames — this loop's own clock stops here. Without the handoff\n // the ring sits frozen until some gesture happens to restart the other loop.\n // It no-ops when a frame is already queued or nothing is animating, so\n // landing in candle mode costs nothing.\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) applyFrame(p);\n maybeStartAnim();\n return undefined;\n }\n if (morphFade.current === target) {\n maybeStartAnim();\n return undefined;\n }\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) applyFrame(p);\n maybeStartAnim();\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 fade = from + (target - from) * ease(easingRef.current, prog);\n morphFade.current = fade;\n // Reduced motion still crossfades, but skips the vertical collapse.\n handle.setMorph(reduceMotion ? 0 : fade, fade);\n const p = handle.render();\n if (p) applyFrame(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) applyFrame(q);\n maybeStartAnim();\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 // maybeStartAnim is memoized on [handle, animTick] and animTick on\n // [handle, applyFrame], both already deps here — so it adds no new restarts\n // of this clock.\n }, [handle, chartType, transitionMs, reduceMotion, applyFrame, maybeStartAnim]);\n\n // Volume-bar collapse. The core staggers the bars itself — tallest falling\n // first, all landing together — so unlike the loop above this one hands it\n // *linear* progress plus the curve; pre-easing here would compound the two.\n // Hiding drives 0→1, revealing 1→0, which is the same cascade backwards.\n // Mirrors the web driver in react/src/useChartCore.ts.\n const volumeRaf = useRef<number | null>(null);\n const volumeHandle = useRef<typeof handle>(null);\n useEffect(() => {\n if (!handle) return undefined;\n const target = (volume?.enabled ?? true) ? 0 : 1;\n const easing = easingIndex(easingRef.current);\n\n // Fresh handle (first load / recreate): the data effect's setVolume already\n // snapped it, so a chart that mounts with bars doesn't animate them in.\n // Every exit below hands off to maybeStartAnim, for the same reason the\n // morph loop does: this clock stops here, and anything the core is still\n // animating (the line-tip pulse) needs the other loop requeueing frames.\n if (volumeHandle.current !== handle || volumeCollapseRef.current == null) {\n volumeHandle.current = handle;\n volumeCollapseRef.current = { t: target, easing };\n maybeStartAnim();\n return undefined;\n }\n if (volumeCollapseRef.current.t === target) {\n maybeStartAnim();\n return undefined;\n }\n\n if (volumeRaf.current != null) {\n cancelAnimationFrame(volumeRaf.current);\n volumeRaf.current = null;\n }\n\n const dur = Math.max(0, transitionMs ?? 300);\n if (dur === 0 || reduceMotion) {\n volumeCollapseRef.current = { t: target, easing };\n handle.setVolumeCollapse(target, easing);\n const p = handle.render();\n if (p) applyFrame(p);\n maybeStartAnim();\n return undefined;\n }\n\n // From wherever the last frame left off, so toggling mid-flight reverses\n // instead of jumping. A partial trip covers less ground in the same time.\n const from = volumeCollapseRef.current.t;\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 t = prog < 1 ? from + (target - from) * prog : target;\n const kind = easingIndex(easingRef.current);\n volumeCollapseRef.current = { t, easing: kind };\n handle.setVolumeCollapse(t, kind);\n const p = handle.render();\n if (p) applyFrame(p);\n if (prog < 1) {\n volumeRaf.current = requestAnimationFrame(step);\n } else {\n volumeRaf.current = null;\n maybeStartAnim();\n }\n };\n volumeRaf.current = requestAnimationFrame(step);\n\n return () => {\n if (volumeRaf.current != null) {\n cancelAnimationFrame(volumeRaf.current);\n volumeRaf.current = null;\n }\n };\n }, [\n handle,\n volume?.enabled,\n transitionMs,\n reduceMotion,\n applyFrame,\n volumeCollapseRef,\n maybeStartAnim,\n ]);\n\n // Axis-strip collapse. Unlike the volume bars there is no per-element stagger\n // for the core to distribute, so this pre-eases in JS and hands over the eased\n // scalar. Both axes ride one clock so toggling them together stays in step.\n // This one moves the *layout* — the plot reflows into the reclaimed space\n // every frame. Mirrors the web driver in react/src/useChartCore.ts.\n const axisRaf = useRef<number | null>(null);\n const axisHandle = useRef<typeof handle>(null);\n const axisCollapse = useRef<{ y: number; x: number } | null>(null);\n const showYAxis = theme?.showYAxis ?? true;\n const showXAxis = theme?.showXAxis ?? true;\n useEffect(() => {\n if (!handle) return undefined;\n const targetY = showYAxis ? 0 : 1;\n const targetX = showXAxis ? 0 : 1;\n\n // Fresh handle (first load / recreate): snap, so a chart that mounts with an\n // axis already hidden doesn't play it out.\n if (axisHandle.current !== handle || axisCollapse.current == null) {\n axisHandle.current = handle;\n axisCollapse.current = { y: targetY, x: targetX };\n handle.setAxisCollapse(targetY, targetX);\n const p = handle.render();\n if (p) applyFrame(p);\n maybeStartAnim();\n return undefined;\n }\n\n const fromY = axisCollapse.current.y;\n const fromX = axisCollapse.current.x;\n if (fromY === targetY && fromX === targetX) {\n maybeStartAnim();\n return undefined;\n }\n\n if (axisRaf.current != null) {\n cancelAnimationFrame(axisRaf.current);\n axisRaf.current = null;\n }\n\n const dur = Math.max(0, transitionMs ?? 300);\n if (dur === 0 || reduceMotion) {\n axisCollapse.current = { y: targetY, x: targetX };\n handle.setAxisCollapse(targetY, targetX);\n const p = handle.render();\n if (p) applyFrame(p);\n maybeStartAnim();\n return undefined;\n }\n\n // From wherever the last frame left off, so toggling mid-flight reverses\n // instead of jumping.\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 = ease(easingRef.current, prog);\n const y = prog < 1 ? fromY + (targetY - fromY) * e : targetY;\n const x = prog < 1 ? fromX + (targetX - fromX) * e : targetX;\n axisCollapse.current = { y, x };\n handle.setAxisCollapse(y, x);\n const p = handle.render();\n if (p) applyFrame(p);\n if (prog < 1) {\n axisRaf.current = requestAnimationFrame(step);\n } else {\n axisRaf.current = null;\n maybeStartAnim();\n }\n };\n axisRaf.current = requestAnimationFrame(step);\n\n return () => {\n if (axisRaf.current != null) {\n cancelAnimationFrame(axisRaf.current);\n axisRaf.current = null;\n }\n };\n }, [\n handle,\n showYAxis,\n showXAxis,\n transitionMs,\n reduceMotion,\n applyFrame,\n maybeStartAnim,\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 // Hit-tests the price lines at a touch point, resolving the core's index back\n // to the line it belongs to. Null when nothing was hit.\n const hitPriceLine = useCallback(\n (x: number, y: number) => {\n if (!handle || !priceLines?.length) return null;\n const hit = handle.hitTestPriceLine(x, y);\n const line = hit ? priceLines[hit.index] : undefined;\n return hit && line ? { index: hit.index, part: hit.part, line } : null;\n },\n [handle, priceLines],\n );\n\n // A price line being dragged vertically: its core index, its id, and the last\n // previewed price (the payload for the drop).\n const priceDrag = useRef<{ index: number; id: string; price: number } | null>(\n null,\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), the indicator pane (horizontal\n // scroll only), or a draggable price line. We classify on onStart.\n const panMode = useRef<\n 'chart' | 'price-axis' | 'time-axis' | 'indicator' | 'price-line'\n >('chart');\n\n // Closes an open footprint tooltip. Any viewport change slides the candles out\n // from under it and the crosshair replaces it outright, so the host is told to\n // take it down rather than left holding a position the badge has moved away\n // from. `redraw` is false for callers that render a frame of their own right\n // after — on Android that render rasterizes pixels, so the duplicate is worth\n // skipping.\n const dismissFootprint = (redraw = true) => {\n if (!handle || !footprintActive.current) return;\n footprintActive.current = false;\n handle.setFootprintHover(0, -1);\n if (redraw) {\n const frame = handle.render();\n if (frame) applyFrame(frame);\n }\n onFootprint?.({\n active: false,\n reason: 'hide',\n side: null,\n timeMs: null,\n footprints: [],\n badge: null,\n pane: null,\n });\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 // A draggable price line takes the drag over from the chart. Seeding the\n // preview at the committed price puts the label in drag styling before the\n // first move, so the grab registers immediately.\n priceDrag.current = null;\n if (handle && panMode.current === 'chart' && !crosshairActive.current) {\n const pl = hitPriceLine(e.x, e.y);\n if (pl && pl.part === 0) {\n panMode.current = 'price-line';\n priceDrag.current = { index: pl.index, id: pl.line.id, price: pl.line.price };\n handle.setPriceLineDrag(pl.index, pl.line.price);\n const p = handle.render();\n if (p) applyFrame(p);\n }\n }\n // Every mode but a price-line drag moves the viewport, and this one call\n // covers the momentum fling too — decay only ever starts from a pan that\n // already began here.\n if (panMode.current !== 'price-line') dismissFootprint();\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 (panMode.current === 'price-line') {\n // Preview the price under the finger; nothing is committed until the drop.\n const g = priceDrag.current;\n if (!g) return;\n const c = handle.coordAt(e.x, e.y);\n if (!c) return;\n g.price = c.price;\n handle.setPriceLineDrag(g.index, c.price);\n onPriceLineDrag?.(g.id, c.price);\n next = handle.render();\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) applyFrame(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) applyFrame(next);\n maybeStartAnim();\n })\n .onEnd((e) => {\n if (!handle) return;\n // Price-line drop. The preview always clears here: the line is a controlled\n // prop, so it only really moves once the host restates it — which means a\n // rejected (or ignored) move reverts on its own.\n if (panMode.current === 'price-line') {\n const g = priceDrag.current;\n priceDrag.current = null;\n handle.setPriceLineDrag(-1, 0);\n const p = handle.render();\n if (p) applyFrame(p);\n if (g) onPriceLineDragEnd?.(g.id, g.price);\n return;\n }\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) applyFrame(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 dismissFootprint();\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) applyFrame(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 // A press on a price line belongs to that line — dragging it or tapping its\n // close button — so it must not raise the crosshair over the top.\n if (hitPriceLine(e.x, e.y)) return;\n cancelDecay();\n // The crosshair takes the pane over, so it can't share it with a tooltip.\n // No redraw: setCrosshair below returns a frame that already has the badge\n // un-highlighted.\n dismissFootprint(false);\n crosshairActive.current = true;\n const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);\n if (ch) applyFrame(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 activates a price line's close button, selects or dismisses a footprint\n // badge, and otherwise dismisses the crosshair while it's up. Any other tap is a\n // no-op, so it never interferes with normal pan/pinch.\n const tap = Gesture.Tap()\n .runOnJS(true)\n .onStart((e) => {\n if (!handle) return;\n\n // Badges get first refusal: one is a ~9px circle, while a price line's grab\n // band spans the pane and would otherwise swallow any badge it crosses.\n // Touch has no hover, so a tap is what opens a footprint here, and the next\n // tap anywhere closes it.\n const prints = footprints ?? [];\n const fp = prints.length ? handle.hitTestFootprint(e.x, e.y) : null;\n if (fp) {\n handle.setFootprintHover(fp.candleTimeMs, fp.side);\n const frame = handle.render();\n if (frame) applyFrame(frame);\n const wasActive = footprintActive.current;\n footprintActive.current = true;\n onFootprint?.({\n active: true,\n reason: wasActive ? 'move' : 'show',\n side: fp.side === FOOTPRINT_SELL ? 'sell' : 'buy',\n timeMs: fp.candleTimeMs,\n // The core reports indices into the array we last pushed, which is this\n // same prop — so this rejoins each badge to the consumer's own objects.\n footprints: fp.indices\n .map((i) => prints[i])\n .filter((f): f is Footprint => f != null),\n badge: { x: fp.x, y: fp.y, radius: fp.radius },\n pane: fp.pane,\n });\n return;\n }\n // A tap that missed every badge dismisses the open one, so the host tooltip\n // goes away the same way the crosshair does.\n dismissFootprint();\n\n // The close button is a tap target whether or not the crosshair is up.\n const pl = hitPriceLine(e.x, e.y);\n if (pl && pl.part === 1) {\n onPriceLineClose?.(pl.line.id);\n return;\n }\n if (!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) applyFrame(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 <>\n <Picture picture={pictureSV} />\n <Image\n image={imageSV}\n x={0}\n y={0}\n width={width}\n height={height}\n fit=\"fill\"\n />\n </>\n ) : null}\n </Canvas>\n </View>\n </GestureDetector>\n </GestureHandlerRootView>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport type { MutableRefObject } from 'react';\n\nimport NativeVroomChart from './NativeVroomChart';\nimport type { DataTransition } from './dataTransitions';\nimport {\n classifyStream,\n classifyTransition,\n inferStepMs,\n isPinnedToLatest,\n timeframeWindow,\n} from './dataTransitions';\nimport { ease } from './easing';\nimport type { ChartFrame, ChartHandle } from './jsi.d';\nimport { packCandles } from './packCandles';\nimport { applyTheme, parseColor, FLOAT_LINE_TIP_PULSE } from './theme';\nimport type {\n BollingerBandsConfig,\n ATRConfig,\n Candle,\n ChartType,\n FairValueGapsConfig,\n IchimokuConfig,\n MACDConfig,\n MovingAverageOverlay,\n PriceLine,\n PriceLinesStyle,\n Footprint,\n FootprintsStyle,\n RSIConfig,\n TransitionEasing,\n IntervalTransition,\n StreamTransition,\n VisibleRange,\n VolumeConfig,\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\n// Mirrors vroom::atr::Smoothing order in packages/core/src/atr.h.\nconst ATR_SMOOTHINGS = ['rma', 'sma', 'ema'] as const;\n\n// An unset style color marshals as the core's transparent inherit sentinel.\nconst inheritColor = (v: string | number | undefined): number =>\n (v != null ? parseColor(v) : null) ?? 0;\n\nfunction overlayToNumeric(o: MovingAverageOverlay) {\n const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;\n return {\n kind: o.maType === 'ema' ? 1 : 0,\n period: o.period,\n source: srcIdx < 0 ? 0 : srcIdx,\n color: (o.color != null ? parseColor(o.color) : null) ?? 0xff2962ff,\n width: o.width ?? 1.5,\n };\n}\n\nfunction rsiToSpec(cfg: RSIConfig | undefined) {\n return {\n enabled: cfg?.enabled ?? false,\n period: cfg?.period ?? 14,\n upperBand: cfg?.upperBand ?? 70,\n lowerBand: cfg?.lowerBand ?? 30,\n maPeriod: cfg?.maPeriod ?? 14,\n maKind: cfg?.maType === 'ema' ? 1 : 0,\n maVisible: cfg?.maVisible ?? true,\n lineColor: inheritColor(cfg?.lineColor),\n lineWidth: cfg?.lineWidth ?? -1,\n lineVisible: cfg?.lineVisible ?? true,\n maColor: inheritColor(cfg?.maColor),\n maWidth: cfg?.maWidth ?? -1,\n bandColor: inheritColor(cfg?.bandColor),\n bandsVisible: cfg?.bandsVisible ?? true,\n extremeFill: cfg?.extremeFill ?? true,\n };\n}\n\nfunction vwapToSpec(cfg: VWAPConfig | undefined) {\n return {\n enabled: cfg?.enabled ?? false,\n resetOffsetMin: cfg?.resetMinutes ?? 0,\n color: inheritColor(cfg?.color),\n width: cfg?.width ?? -1,\n };\n}\n\n// Bollinger defaults: blue bands / orange basis, matching the repo palette.\nconst DEFAULT_BB_BAND_COLOR = 0xff2962ff;\nconst DEFAULT_BB_BASIS_COLOR = 0xffff6d00;\n\nfunction bollingerToSpec(cfg: BollingerBandsConfig | undefined) {\n const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;\n return {\n enabled: cfg?.enabled ?? false,\n period: cfg?.period ?? 20,\n mult: cfg?.stdDev ?? 2,\n source: srcIdx < 0 ? 0 : srcIdx,\n basisKind: cfg?.maType === 'ema' ? 1 : 0,\n upperColor:\n (cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ??\n DEFAULT_BB_BAND_COLOR,\n upperWidth: cfg?.upperWidth ?? 1,\n middleColor:\n (cfg?.middleColor != null ? parseColor(cfg.middleColor) : null) ??\n DEFAULT_BB_BASIS_COLOR,\n middleWidth: cfg?.middleWidth ?? 1,\n lowerColor:\n (cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ??\n DEFAULT_BB_BAND_COLOR,\n lowerWidth: cfg?.lowerWidth ?? 1,\n fillEnabled: cfg?.fillVisible ?? true,\n fillOpacity: cfg?.fillOpacity ?? 0.1,\n };\n}\n\n// Ichimoku defaults. Green and red do double duty: they color span A and kijun,\n// and tint the cloud for whichever span is on top.\nconst DEFAULT_ICH_GREEN = 0xff26a69a;\nconst DEFAULT_ICH_RED = 0xffef5350;\nconst DEFAULT_ICH_BLUE = 0xff2962ff;\nconst DEFAULT_ICH_ORANGE = 0xffff6d00;\nconst DEFAULT_ICH_TEAL = 0xff00bcd4;\n\nfunction ichimokuToSpec(cfg: IchimokuConfig | undefined) {\n const color = (v: string | number | undefined, fallback: number) =>\n (v != null ? parseColor(v) : null) ?? fallback;\n return {\n enabled: cfg?.enabled ?? false,\n tenkanPeriod: cfg?.tenkanPeriod ?? 9,\n kijunPeriod: cfg?.kijunPeriod ?? 26,\n senkouBPeriod: cfg?.senkouBPeriod ?? 52,\n displacement: cfg?.displacement ?? 26,\n tenkanColor: color(cfg?.tenkanColor, DEFAULT_ICH_BLUE),\n tenkanWidth: cfg?.tenkanWidth ?? 1,\n tenkanEnabled: cfg?.tenkanVisible ?? true,\n kijunColor: color(cfg?.kijunColor, DEFAULT_ICH_RED),\n kijunWidth: cfg?.kijunWidth ?? 1,\n kijunEnabled: cfg?.kijunVisible ?? true,\n senkouAColor: color(cfg?.senkouAColor, DEFAULT_ICH_GREEN),\n senkouAWidth: cfg?.senkouAWidth ?? 1,\n senkouAEnabled: cfg?.senkouAVisible ?? true,\n senkouBColor: color(cfg?.senkouBColor, DEFAULT_ICH_ORANGE),\n senkouBWidth: cfg?.senkouBWidth ?? 1,\n senkouBEnabled: cfg?.senkouBVisible ?? true,\n chikouColor: color(cfg?.chikouColor, DEFAULT_ICH_TEAL),\n chikouWidth: cfg?.chikouWidth ?? 1,\n chikouEnabled: cfg?.chikouVisible ?? true,\n cloudEnabled: cfg?.cloudVisible ?? true,\n bullishCloudColor: color(cfg?.bullishCloudColor, DEFAULT_ICH_GREEN),\n bearishCloudColor: color(cfg?.bearishCloudColor, DEFAULT_ICH_RED),\n cloudOpacity: cfg?.cloudOpacity ?? 0.15,\n };\n}\n\n// Fair Value Gap defaults. The border colors fall back to the fill color, so a\n// config that only restyles the fill keeps its outline in the same hue.\nconst DEFAULT_FVG_GREEN = 0xff26a69a;\nconst DEFAULT_FVG_RED = 0xffef5350;\nconst FVG_FILL_TYPES = ['close', 'wick'] as const;\nconst FVG_BORDER_STYLES = ['solid', 'dotted', 'dashed'] as const;\n\nfunction fvgToSpec(cfg: FairValueGapsConfig | undefined) {\n const color = (v: string | number | undefined, fallback: number) =>\n (v != null ? parseColor(v) : null) ?? fallback;\n const bullish = color(cfg?.bullishColor, DEFAULT_FVG_GREEN);\n const bearish = color(cfg?.bearishColor, DEFAULT_FVG_RED);\n return {\n enabled: cfg?.enabled ?? false,\n maxBarsBack: cfg?.maxBarsBack ?? 300,\n waitForClose: cfg?.waitForClose ?? false,\n fillType: Math.max(0, FVG_FILL_TYPES.indexOf(cfg?.fillType ?? 'close')),\n deleteAfterFill: cfg?.deleteAfterFill ?? true,\n extendBoxes: cfg?.extendBoxes ?? false,\n boxLength: cfg?.boxLength ?? 20,\n bullishColor: bullish,\n bearishColor: bearish,\n opacity: cfg?.opacity ?? 0.15,\n borderEnabled: cfg?.borderVisible ?? true,\n borderStyle: Math.max(\n 0,\n FVG_BORDER_STYLES.indexOf(cfg?.borderStyle ?? 'solid'),\n ),\n borderWidth: cfg?.borderWidth ?? 1,\n bullishBorderColor: color(cfg?.bullishBorderColor, bullish),\n bearishBorderColor: color(cfg?.bearishBorderColor, bearish),\n labelsEnabled: cfg?.showLabels ?? true,\n label: cfg?.label ?? 'FVG',\n labelDistance: cfg?.labelDistance ?? 10,\n // Alpha 0 is the core's \"inherit the border color\" sentinel.\n labelColor: color(cfg?.labelColor, 0),\n labelFontSize: cfg?.labelFontSize ?? 0,\n showInverse: cfg?.showInverse ?? false,\n inverseBullishColor: color(cfg?.inverseBullishColor, bullish),\n inverseBearishColor: color(cfg?.inverseBearishColor, bearish),\n inverseLabel: cfg?.inverseLabel ?? 'iFVG',\n };\n}\n\nfunction macdToSpec(cfg: MACDConfig | undefined) {\n const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;\n return {\n enabled: cfg?.enabled ?? false,\n fast: cfg?.fast ?? 12,\n slow: cfg?.slow ?? 26,\n signal: cfg?.signal ?? 9,\n source: srcIdx < 0 ? 0 : srcIdx,\n maKind: cfg?.maType === 'sma' ? 0 : 1,\n signalMaKind: cfg?.signalMaType === 'sma' ? 0 : 1,\n lineColor: inheritColor(cfg?.lineColor),\n lineWidth: cfg?.lineWidth ?? -1,\n lineVisible: cfg?.lineVisible ?? true,\n signalColor: inheritColor(cfg?.signalColor),\n signalWidth: cfg?.signalWidth ?? -1,\n signalVisible: cfg?.signalVisible ?? true,\n histVisible: cfg?.histogramVisible ?? true,\n histUpColor: inheritColor(cfg?.histogramUpColor),\n histUpFadingColor: inheritColor(cfg?.histogramUpFadingColor),\n histDownColor: inheritColor(cfg?.histogramDownColor),\n histDownFadingColor: inheritColor(cfg?.histogramDownFadingColor),\n zeroColor: inheritColor(cfg?.zeroLineColor),\n zeroVisible: cfg?.zeroLineVisible ?? true,\n };\n}\n\nfunction atrToSpec(cfg: ATRConfig | undefined) {\n return {\n enabled: cfg?.enabled ?? false,\n period: cfg?.period ?? 14,\n smoothing: Math.max(0, ATR_SMOOTHINGS.indexOf(cfg?.smoothing ?? 'rma')),\n lineColor: inheritColor(cfg?.lineColor),\n lineWidth: cfg?.lineWidth ?? -1,\n };\n}\n\n// Unset style fields go down as the core's inherit sentinels (negative float,\n// transparent color) rather than as literal defaults, so the theme keys stay in\n// charge of anything the consumer didn't set.\nfunction volumeToSpec(cfg: VolumeConfig | undefined) {\n return {\n enabled: cfg?.enabled ?? true,\n heightFrac: cfg?.height ?? -1,\n opacity: cfg?.opacity ?? -1,\n radiusPx: cfg?.radius ?? -1,\n upColor: (cfg?.upColor != null ? parseColor(cfg.upColor) : null) ?? 0,\n downColor: (cfg?.downColor != null ? parseColor(cfg.downColor) : null) ?? 0,\n };\n}\n\n// Price-line defaults: a soft red dotted rule with a dark translucent label,\n// close in weight to the current-price indicator it sits beside.\nconst DEFAULT_PRICE_LINE_COLOR = 0xffef5350;\nconst DEFAULT_PRICE_LINE_BODY_BG = 0xd91c2128;\nconst DEFAULT_PRICE_LINE_HOVER_BOOST = 1.25;\n\nconst LINE_STYLES = { solid: 0, dotted: 1, dashed: 2 } as const;\n\n// Mirrors VroomPriceLineFlags in packages/core/include/vroom/vroom_chart.h.\nconst PRICE_LINE_DRAGGABLE = 1 << 0;\nconst PRICE_LINE_CLOSABLE = 1 << 1;\nconst PRICE_LINE_AXIS_LABEL = 1 << 2;\nconst PRICE_LINE_EXTEND_LEFT = 1 << 3;\n\n/** The price lines + their shared style, as the chart's props express them. */\nexport type PriceLinesProp = {\n lines: PriceLine[];\n style?: PriceLinesStyle;\n /**\n * Whether the host supplied a close handler. The close button is\n * callback-gated, so with nothing for it to do it isn't drawn at all.\n */\n hasCloseHandler: boolean;\n};\n\nfunction priceLinesToSpec(cfg: PriceLinesProp) {\n return {\n lines: cfg.lines.map((l) => ({\n price: l.price,\n color:\n (l.color != null ? parseColor(l.color) : null) ?? DEFAULT_PRICE_LINE_COLOR,\n width: l.width ?? 1,\n lineStyle: LINE_STYLES[l.lineStyle ?? 'dotted'],\n text: l.text ?? '',\n quantity: l.quantity ?? '',\n flags:\n (l.draggable ? PRICE_LINE_DRAGGABLE : 0) |\n (cfg.hasCloseHandler && l.closable !== false ? PRICE_LINE_CLOSABLE : 0) |\n (l.axisLabel !== false ? PRICE_LINE_AXIS_LABEL : 0) |\n (l.extendLeft !== false ? PRICE_LINE_EXTEND_LEFT : 0),\n })),\n bodyBg:\n (cfg.style?.bodyBackground != null ? parseColor(cfg.style.bodyBackground) : null) ??\n DEFAULT_PRICE_LINE_BODY_BG,\n fontSizePx: cfg.style?.fontSize ?? 0,\n lineLengthFrac: cfg.style?.inset ?? 0,\n align: cfg.style?.align === 'left' ? 0 : cfg.style?.align === 'center' ? 1 : 2,\n hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_PRICE_LINE_HOVER_BOOST,\n };\n}\n\n// Cleared overlay: no lines (the style values are irrelevant, but the spec shape\n// requires them).\nconst EMPTY_PRICE_LINES = priceLinesToSpec({ lines: [], hasCloseHandler: false });\n\n// Footprints share the price lines' hover weight so the two widgets light up\n// alike. Zeroed geometry defers to the core's own defaults.\nconst DEFAULT_FOOTPRINT_HOVER_BOOST = 1.25;\n\n// Mirrors VroomFootprintSide in packages/core/include/vroom/vroom_chart.h.\nconst FOOTPRINT_BUY = 0;\nconst FOOTPRINT_SELL = 1;\n\n// A time no real series can contain (~273,000 BCE), still comfortably inside\n// int64. Parks a malformed footprint where the core will never bucket it.\nconst UNBUCKETABLE_MS = -8.64e15;\n\n/** The footprints + their shared style, as the chart's props express them. */\nexport type FootprintsProp = {\n prints: Footprint[];\n style?: FootprintsStyle;\n};\n\nfunction footprintsToSpec(cfg: FootprintsProp) {\n return {\n // Index alignment is load-bearing: the core reports hits as indices into this\n // array and the gesture layer maps them straight back to the consumer's\n // `footprints`. So a non-finite time — which can't be bucketed and would\n // reach the native side as a garbage int64 — is neutralized *in place* rather\n // than filtered out, which would shift every index after it onto the wrong\n // trade.\n prints: cfg.prints.map((f) => ({\n timeMs: Number.isFinite(f.timeMs) ? f.timeMs : UNBUCKETABLE_MS,\n side: f.side === 'sell' ? FOOTPRINT_SELL : FOOTPRINT_BUY,\n })),\n radiusPx: cfg.style?.radius ?? 0,\n gapPx: cfg.style?.gap ?? 0,\n marginPx: cfg.style?.margin ?? 0,\n hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_FOOTPRINT_HOVER_BOOST,\n };\n}\n\nconst EMPTY_FOOTPRINTS = footprintsToSpec({ prints: [] });\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\n/** Progress + curve of the staggered volume-bar collapse. See setVolumeCollapse. */\nexport type VolumeCollapse = { t: number; easing: number };\n\n/**\n * How a data swap should animate, plus where its frames go. The interval morph\n * has to be started from inside the data effect (it needs the pre-swap capture),\n * but it repaints at 60fps — far too often for React state — so the host passes\n * a sink that writes straight into the picture SharedValue.\n */\nexport type TransitionOptions = {\n /** Identity of the series; a change forces a full view reset. */\n seriesKey?: string;\n /** Duration of the interval morph in ms. 0 snaps. Default 300. */\n transitionMs?: number;\n /** Curve applied to the morph's progress. Default 'ease-in-out'. */\n transitionEasing?: TransitionEasing;\n /** `'transform'` (default) slot-lerps; `'fade'` fades out then in. */\n intervalTransition?: IntervalTransition;\n /** `'transform'` eases live updates; `'none'` (default) snaps them. */\n streamTransition?: StreamTransition;\n /** Duration of the stream animation in ms. 0 snaps. Default 150. */\n streamTransitionMs?: number;\n /** OS reduced-motion preference: skips the capture and snaps. */\n reduceMotion?: boolean;\n /** Receives every morph frame. Without one, data swaps snap. */\n onFrame?: (picture: ChartFrame) => void;\n};\n\nexport type ChartCoreState = {\n handle: ChartHandle | null;\n /** Picture freshly rendered after the latest data/size/range push. */\n picture: ChartFrame | null;\n /**\n * The last volume collapse handed to the core, or null before the first push.\n * VroomChart's animation loop owns this — it lives here only so the data effect\n * can restore it, since setVolume snaps the scalar (see below).\n */\n volumeCollapseRef: MutableRefObject<VolumeCollapse | 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 atr?: ATRConfig,\n movingAverages?: MovingAverageOverlay[],\n vwap?: VWAPConfig,\n bollingerBands?: BollingerBandsConfig,\n ichimoku?: IchimokuConfig,\n fairValueGaps?: FairValueGapsConfig,\n volume?: VolumeConfig,\n priceLines?: PriceLinesProp,\n footprints?: FootprintsProp,\n transition?: TransitionOptions,\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 volumeCollapseRef = useRef<VolumeCollapse | null>(null);\n // What the core currently holds, for classifying the next data change. Keyed\n // by handle so a recreated core is treated as a fresh initial load.\n const prevDataRef = useRef<{\n handle: ChartHandle;\n candles: Candle[];\n seriesKey?: string;\n } | null>(null);\n const intervalMorphRaf = useRef<number | null>(null);\n const streamRaf = useRef<number | null>(null);\n // Where an in-flight stream shift is headed, so cancelling it can land there\n // rather than stranding the view mid-slide.\n const streamWindowRef = useRef<VisibleRange | null>(null);\n // Whether that loop is the one driving the morph scalar, so settling it never\n // cuts short a timeframe switch that happens to overlap.\n const streamMorphRef = useRef(false);\n const [picture, setPicture] = useState<ChartFrame | null>(null);\n\n if (!handleRef.current && size.width > 0 && size.height > 0) {\n ensureInstalled();\n handleRef.current = globalThis.VroomChartJSI!.create();\n }\n\n // Animation config and frame sink in refs, refreshed each render, so changing\n // the duration, curve or callback identity doesn't re-run the data effect\n // below (which would re-push every candle).\n const animRef = useRef<{\n ms: number;\n easing: TransitionEasing | undefined;\n reduceMotion: boolean;\n interval: IntervalTransition;\n stream: StreamTransition;\n streamMs: number;\n }>({\n ms: 300,\n easing: undefined,\n reduceMotion: false,\n interval: 'transform',\n stream: 'none',\n streamMs: 150,\n });\n animRef.current = {\n ms: Math.max(0, transition?.transitionMs ?? 300),\n easing: transition?.transitionEasing,\n reduceMotion: transition?.reduceMotion ?? false,\n interval: transition?.intervalTransition === 'fade' ? 'fade' : 'transform',\n stream: transition?.streamTransition === 'transform' ? 'transform' : 'none',\n // Shorter than transitionMs by default: ticks can land faster than a 300ms\n // curve, and every one that does interrupts the last.\n streamMs: Math.max(0, transition?.streamTransitionMs ?? 150),\n };\n const onFrameRef = useRef(transition?.onFrame);\n onFrameRef.current = transition?.onFrame;\n const seriesKey = transition?.seriesKey;\n\n // Stop an in-flight interval morph and land the core on the new candles.\n const endIntervalMorph = useCallback(() => {\n if (intervalMorphRaf.current != null) {\n cancelAnimationFrame(intervalMorphRaf.current);\n intervalMorphRaf.current = null;\n }\n handleRef.current?.setIntervalMorph(1);\n }, []);\n\n // Runs the interval morph clock. The core holds the pre-swap geometry (see\n // beginIntervalMorph) and reshapes each candle slot toward its new counterpart.\n const startIntervalMorph = useCallback((h: ChartHandle) => {\n const { ms, easing } = animRef.current;\n const start = performance.now();\n const step = (now: number) => {\n const p = Math.min(1, (now - start) / ms);\n h.setIntervalMorph(p < 1 ? ease(easing, p) : 1);\n const pic = h.render();\n if (pic) onFrameRef.current?.(pic);\n intervalMorphRaf.current = p < 1 ? requestAnimationFrame(step) : null;\n };\n intervalMorphRaf.current = requestAnimationFrame(step);\n }, []);\n\n // Stops an in-flight stream animation and puts the chart somewhere coherent.\n //\n // A pending window shift always lands on its target: abandoned mid-slide it\n // would strand the view between two bars, half a candle off the grid.\n //\n // `keepMorph` is for a tick restarting on top of one already running —\n // beginStreamMorph blends out of the geometry currently on screen, so landing\n // that geometry first would throw away the very thing it resumes from.\n const settleStream = useCallback((keepMorph = false) => {\n if (streamRaf.current != null) {\n cancelAnimationFrame(streamRaf.current);\n streamRaf.current = null;\n }\n const h = handleRef.current;\n const target = streamWindowRef.current;\n streamWindowRef.current = null;\n if (target) h?.setVisibleRange(target.startMs, target.endMs);\n if (streamMorphRef.current && !keepMorph) {\n streamMorphRef.current = false;\n h?.setIntervalMorph(1);\n }\n }, []);\n\n // Runs the clock for a live update. One loop drives both halves so they land\n // on the same frame.\n //\n // `window` is null for a plain tick; for an append it is where the view has to\n // end up. The slide is measured from wherever the window is *now*, so a shift\n // interrupting another continues from the current position instead of\n // snapping back to the start of the last one.\n const startStreamAnim = useCallback(\n (h: ChartHandle, morphing: boolean, window: VisibleRange | null) => {\n const { streamMs, easing } = animRef.current;\n let from = window ? h.getVisibleRange() : null;\n // What the previous frame left the window at. Anything else — a pan, a\n // pinch — lands somewhere different, which is how the slide notices it is\n // no longer the only thing moving the view and gets out of the way.\n // Cheaper than teaching every gesture to cancel it, and it can't miss one.\n let applied: VisibleRange | null = null;\n streamWindowRef.current = window;\n streamMorphRef.current = morphing;\n const start = performance.now();\n const step = (now: number) => {\n if (from && applied) {\n const now_w = h.getVisibleRange();\n if (now_w.startMs !== applied.startMs || now_w.endMs !== applied.endMs) {\n from = null;\n streamWindowRef.current = null;\n }\n }\n const p = Math.min(1, (now - start) / streamMs);\n const e = p < 1 ? ease(easing, p) : 1;\n if (morphing) h.setIntervalMorph(e);\n if (from && window) {\n applied = {\n startMs: Math.round(from.startMs + (window.startMs - from.startMs) * e),\n endMs: Math.round(from.endMs + (window.endMs - from.endMs) * e),\n };\n h.setVisibleRange(applied.startMs, applied.endMs);\n }\n const pic = h.render();\n if (pic) onFrameRef.current?.(pic);\n if (p < 1) {\n streamRaf.current = requestAnimationFrame(step);\n } else {\n streamRaf.current = null;\n streamWindowRef.current = null;\n streamMorphRef.current = false;\n }\n };\n streamRaf.current = requestAnimationFrame(step);\n },\n [],\n );\n\n useEffect(() => {\n return () => {\n if (intervalMorphRaf.current != null) {\n cancelAnimationFrame(intervalMorphRaf.current);\n intervalMorphRaf.current = null;\n }\n if (streamRaf.current != null) {\n cancelAnimationFrame(streamRaf.current);\n streamRaf.current = null;\n }\n };\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 atrKey = atr ? JSON.stringify(atr) : '';\n const maKey = movingAverages ? JSON.stringify(movingAverages) : '';\n const vwapKey = vwap ? JSON.stringify(vwap) : '';\n const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : '';\n const ichimokuKey = ichimoku ? JSON.stringify(ichimoku) : '';\n const fvgKey = fairValueGaps ? JSON.stringify(fairValueGaps) : '';\n const volumeKey = volume ? JSON.stringify(volume) : '';\n const priceLinesKey = priceLines ? JSON.stringify(priceLines) : '';\n const footprintsKey = footprints ? JSON.stringify(footprints) : '';\n\n useEffect(() => {\n const h = handleRef.current;\n if (!h) return;\n h.setSize(size.width, size.height, size.pxRatio ?? 1);\n // Ahead of setCandles, like setDefaultCandleWidth below: the default framing\n // runs inside setCandles and reserves room past the newest candle for\n // Ichimoku's leading spans, so it has to already know they're coming.\n h.setIchimoku(ichimokuToSpec(ichimoku));\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 // How the new candles relate to what the core holds decides what happens to\n // the viewport: a stream leaves it alone, a timeframe switch re-anchors and\n // morphs into it, a different asset resets it.\n let morphing = false;\n if (candles.length > 0) {\n const prev = prevDataRef.current;\n const freshHandle = prev == null || prev.handle !== h;\n if (freshHandle || prev.candles !== candles || prev.seriesKey !== seriesKey) {\n // A fresh core frames itself (its window starts at 0/0); an explicit\n // visibleRange prop overrides any auto behavior, so treat the change\n // like a stream and let the range application below win.\n const transitionKind: DataTransition = freshHandle\n ? 'initial'\n : explicit\n ? 'stream'\n : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);\n\n // Capture the outgoing view before setCandles re-infers the candle\n // period from the new data.\n let tfArgs: {\n oldWindow: VisibleRange;\n oldStepMs: number;\n oldLastMs: number;\n } | null = null;\n // The pre-swap candle envelope, used to scale-lock the y-axis below.\n let prevEnvelope: { low: number; high: number } | null = null;\n // Set for an animated live update: whether the last bar reshapes, and\n // the window an appended bar should pull the view to.\n let stream: { morph: boolean; window: VisibleRange | null } | null = null;\n if (transitionKind === 'stream' && prev != null && !explicit) {\n const { stream: mode, streamMs, reduceMotion } = animRef.current;\n const stepMs = inferStepMs(candles);\n if (\n mode === 'transform' &&\n streamMs > 0 &&\n stepMs != null &&\n !reduceMotion &&\n onFrameRef.current != null\n ) {\n const lastMs = candles[candles.length - 1].timeMs;\n const prevLastMs = prev.candles[prev.candles.length - 1].timeMs;\n if (classifyStream(prev.candles, candles) === 'append') {\n // Pull the window along by exactly what the data advanced, so the\n // series translates a whole slot and the newest bar holds its\n // place on screen. Only for a view still following the newest bar\n // — someone reading history keeps their window.\n //\n // No capture here: slots pair from the right edge, so the new bar\n // would take the previous one's geometry and drag every candle\n // onto its neighbour. Translating the window moves them by their\n // own timestamps instead.\n const w = h.getVisibleRange();\n const prevStepMs = inferStepMs(prev.candles) ?? stepMs;\n if (isPinnedToLatest(w, prevLastMs, prevStepMs)) {\n const by = lastMs - prevLastMs;\n stream = {\n morph: false,\n window: { startMs: w.startMs + by, endMs: w.endMs + by },\n };\n }\n } else {\n stream = { morph: true, window: null };\n }\n }\n if (stream?.morph) {\n // Keep the geometry on screen for beginStreamMorph to resume from:\n // at any real tick rate most ticks interrupt the previous one, and\n // that continuity is what keeps the bar from stuttering.\n settleStream(true);\n h.beginStreamMorph();\n } else {\n // An append has no use for a capture — it would pair the new bar\n // with the old one's geometry and drag the whole series along.\n settleStream();\n }\n } else if (transitionKind === 'stream') {\n settleStream();\n }\n if (transitionKind === 'timeframe' && prev != null) {\n const oldWindow = h.getVisibleRange();\n const oldStepMs = inferStepMs(prev.candles);\n if (oldWindow.endMs > oldWindow.startMs && oldStepMs != null) {\n tfArgs = {\n oldWindow,\n oldStepMs,\n oldLastMs: prev.candles[prev.candles.length - 1].timeMs,\n };\n }\n prevEnvelope = h.getVisiblePriceEnvelope();\n // Capture the outgoing candle geometry, but only when it will actually\n // be animated so a disabled animation costs no snapshot. A switch\n // during a morph restarts from the data the core currently holds.\n morphing =\n animRef.current.ms > 0 &&\n !animRef.current.reduceMotion &&\n onFrameRef.current != null;\n if (morphing) {\n endIntervalMorph();\n h.beginIntervalMorph(animRef.current.interval);\n }\n } else if (transitionKind === 'initial' || transitionKind === 'reset') {\n // Wholesale reframing — the slot pairing no longer holds, so land any\n // in-flight morph rather than reshaping into unrelated data.\n endIntervalMorph();\n }\n\n h.setCandles(packCandles(candles));\n\n if (transitionKind === 'timeframe') {\n const newStepMs = inferStepMs(candles);\n if (tfArgs && newStepMs != null) {\n const w = timeframeWindow(\n tfArgs.oldWindow,\n tfArgs.oldStepMs,\n tfArgs.oldLastMs,\n newStepMs,\n candles[candles.length - 1].timeMs,\n );\n h.setVisibleRange(w.startMs, w.endMs);\n }\n // Scale-lock the y-axis: the same price action re-buckets into a\n // smaller/larger high-low span, so a manual price range is rescaled to\n // keep the candle envelope at the pixel height it just had instead of\n // snapping back to auto-fit. A no-op in auto-y mode, which is already\n // span-invariant.\n if (prevEnvelope) h.preservePriceEnvelope(prevEnvelope.low, prevEnvelope.high);\n else h.resetPriceScale();\n // Started after the new bounds are in place: the snapshot is in band\n // fractions, so frame 0 still matches the pre-switch pixels exactly.\n if (morphing) startIntervalMorph(h);\n } else if (stream) {\n // After setCandles, so the capture (and the window it slides from) is\n // measured against the data the animation is heading toward.\n startStreamAnim(h, stream.morph, stream.window);\n } else if (transitionKind === 'reset') {\n h.resetView();\n }\n prevDataRef.current = { handle: h, candles, seriesKey };\n }\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 // The tip dot stays, only its animation drops — the same bargain the\n // candle↔line morph strikes when it keeps the crossfade but skips the\n // collapse. Also stops the pulse from pinning a RAF loop for a user who\n // asked for less motion.\n if (animRef.current.reduceMotion) {\n h.setFloat(FLOAT_LINE_TIP_PULSE, 0);\n }\n h.setRSI(rsiToSpec(rsi));\n h.setMACD(macdToSpec(macd));\n h.setATR(atrToSpec(atr));\n h.setOverlays((movingAverages ?? []).map(overlayToNumeric));\n h.setVWAP(vwapToSpec(vwap));\n h.setBollinger(bollingerToSpec(bollingerBands));\n h.setFairValueGaps(fvgToSpec(fairValueGaps));\n h.setVolume(volumeToSpec(volume));\n // setVolume snaps the collapse scalar to its `enabled`, which would cut a\n // toggle animation short whenever this effect re-runs (a streaming candle, a\n // resize). Hand the in-flight value back; VroomChart's loop drives it from\n // there.\n const collapse = volumeCollapseRef.current;\n if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);\n h.setPriceLines(\n priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES,\n );\n h.setFootprints(\n footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS,\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 // A just-started morph is already pushing frames straight to the host sink;\n // this snapshot would land on top of them a frame or two later. The morph's\n // frame 0 is pixel-identical to what's on screen, so there's nothing to show\n // in the meantime anyway.\n if (!morphing) setPicture(h.render());\n // theme/rsi/macd/atr/movingAverages/vwap/bollingerBands/ichimoku/\n // fairValueGaps/volume/priceLines/footprints are represented by their *Key\n // deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, atrKey, maKey, vwapKey, bollingerKey, ichimokuKey, fvgKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, endIntervalMorph, startStreamAnim, settleStream]);\n\n return { handle: handleRef.current, picture, volumeCollapseRef };\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","// Mirror of packages/react/src/dataTransitions.ts — the platform packages don't\n// depend on each other, and @vroomchart/types carries types only.\n//\n// Classifies how a new `candles` prop relates to the previous one so the chart\n// can react appropriately: leave the viewport alone for streaming updates,\n// re-anchor the time window for a timeframe switch, or fully reset the view\n// for a different asset. Pure functions, no React — see useChartCore for the\n// orchestration.\n\nimport type { Candle, VisibleRange } from '@vroomchart/types';\n\n/**\n * How a new `candles` array relates to the one the chart already holds:\n * `'initial'` is the first data, `'stream'` a live update to the same series,\n * `'timeframe'` the same asset re-bucketed into a different interval, and\n * `'reset'` a different series entirely.\n */\nexport type DataTransition = 'initial' | 'stream' | 'timeframe' | 'reset';\n\n// A step change below this ratio is treated as the same timeframe. Real steps\n// are exact integer ms; the tolerance only absorbs rounding/DST quirks (the\n// smallest real timeframe jump, 1m -> 2m, is 100% apart).\nconst STEP_TOLERANCE = 0.01;\n\n// Same-asset check for a timeframe switch: both series end \"now\", so their\n// last closes must be close. No asset moves 25% between two consecutive prop\n// pushes; distinct assets within 25% of each other are what `seriesKey` is for.\nconst MAX_SAME_ASSET_CLOSE_RATIO = 1.25;\n\n// A coarser bucketing can shift the final bar's open by up to one coarse bar;\n// allow that plus an in-flight bar when checking the two series end together.\nconst MAX_END_DRIFT_STEPS = 3;\n\n// Streaming pushes may batch a few bars (e.g. a throttled background tab), but\n// a jump of more than this many steps means the data was re-fetched elsewhere.\nconst MAX_STREAM_ADVANCE_STEPS = 5;\n\n/**\n * The candle period in ms, inferred as the median of the first few intervals\n * (robust to a single gap). Null when there are fewer than two candles.\n */\nexport function inferStepMs(candles: Candle[]): number | null {\n if (candles.length < 2) return null;\n const k = Math.min(candles.length - 1, 8);\n const diffs: number[] = [];\n for (let i = 0; i < k; i++) diffs.push(candles[i + 1].timeMs - candles[i].timeMs);\n diffs.sort((a, b) => a - b);\n const median = diffs[Math.floor(diffs.length / 2)];\n return median > 0 ? median : null;\n}\n\n// Index of the candle whose timeMs exactly equals `t`, or -1. Binary search over\n// the ascending-by-time series, so it tolerates interior gaps (missing bars from\n// downtime / illiquid periods) — unlike a uniform-grid index computed from the\n// step, which assumes a hole-free grid.\nfunction indexByTime(candles: Candle[], t: number): number {\n let lo = 0;\n let hi = candles.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n const v = candles[mid].timeMs;\n if (v === t) return mid;\n if (v < t) lo = mid + 1;\n else hi = mid - 1;\n }\n return -1;\n}\n\n/**\n * Classify a candles-prop change. `prev` is the previously rendered array\n * (null on first render); `seriesKeyChanged` forces `reset` regardless of the\n * data (the explicit escape hatch).\n *\n * Constraint: detection compares two immutable snapshots. An array mutated in\n * place (same reference) never reaches this code — React props must change\n * identity to re-render.\n */\nexport function classifyTransition(\n prev: Candle[] | null,\n next: Candle[],\n seriesKeyChanged: boolean,\n): DataTransition {\n if (!prev || prev.length === 0) return 'initial';\n if (next.length === 0) return 'stream'; // nothing to reframe against\n if (seriesKeyChanged) return 'reset';\n\n const prevStep = inferStepMs(prev);\n const nextStep = inferStepMs(next);\n if (prevStep == null || nextStep == null) return 'reset'; // too little data to reason\n\n const prevLast = prev[prev.length - 1];\n const nextLast = next[next.length - 1];\n\n if (Math.abs(nextStep - prevStep) <= prevStep * STEP_TOLERANCE) {\n // Same step: streaming iff prev's last bar still appears in next (covers\n // append, update-last, and rolling buffers that drop old bars from the\n // front) and the series only advanced by a few bars. Locate that bar by\n // timestamp, not by a step-derived index — real series have interior gaps\n // (downtime / illiquid periods), so a uniform-grid index would miss it and\n // misread a harmless in-place tick as a reset.\n // Time alignment alone isn't enough: two assets on the same exchange share\n // the bar grid, so the bar at the shared timestamp must also be (nearly) the\n // same bar — update-last moves the close, but never by the same-asset ratio.\n const idx = indexByTime(next, prevLast.timeMs);\n const aligned = idx >= 0;\n const sharedBarRatio =\n aligned && next[idx].close > 0 && prevLast.close > 0\n ? Math.max(next[idx].close / prevLast.close, prevLast.close / next[idx].close)\n : Infinity;\n const advanced =\n nextLast.timeMs >= prevLast.timeMs &&\n nextLast.timeMs - prevLast.timeMs <= MAX_STREAM_ADVANCE_STEPS * nextStep;\n return sharedBarRatio <= MAX_SAME_ASSET_CLOSE_RATIO && advanced ? 'stream' : 'reset';\n }\n\n // Step changed: a timeframe switch iff it still looks like the same asset —\n // last closes near each other and both series ending around the same time.\n const closeRatio =\n prevLast.close > 0 && nextLast.close > 0\n ? Math.max(nextLast.close / prevLast.close, prevLast.close / nextLast.close)\n : Infinity;\n const prevEnd = prevLast.timeMs + prevStep;\n const nextEnd = nextLast.timeMs + nextStep;\n const endsTogether =\n Math.abs(nextEnd - prevEnd) <= MAX_END_DRIFT_STEPS * Math.max(prevStep, nextStep);\n return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? 'timeframe' : 'reset';\n}\n\n/**\n * What a `'stream'` update did to the series: `'tick'` revised the bar already\n * on screen, `'append'` brought at least one new one.\n *\n * The two animate by different means. A tick keeps the bar count, so the morph\n * capture's slots still pair one-to-one and the last bar can reshape in place.\n * An append can't use that capture at all — slots pair from the right edge, so\n * a new bar shifts every candle onto its neighbour's geometry — and instead\n * advances the visible window, which translates the series left and lets the\n * new bar in at the right edge.\n */\nexport type StreamKind = 'tick' | 'append';\n\n/**\n * Which of the two a `'stream'` transition is. Read from the newest timestamp\n * rather than a length comparison, so a rolling buffer that drops a bar from\n * the front as it adds one to the back still reads as an append.\n *\n * An update that both appends and revises the bar that just closed counts as an\n * append: the translation is the dominant motion, and the revision is a final\n * print that has nowhere to slot-pair to.\n */\nexport function classifyStream(prev: Candle[], next: Candle[]): StreamKind {\n if (prev.length === 0 || next.length === 0) return 'tick';\n return next[next.length - 1].timeMs > prev[prev.length - 1].timeMs\n ? 'append'\n : 'tick';\n}\n\n/**\n * Whether the view is still following the newest bar, which is what decides if\n * an appended bar should pull the window along with it.\n *\n * True when the right edge sits at or past the newest bar's slot *end* — where\n * the default framing leaves it, plus whatever gap it reserved. Someone who has\n * panned back into history falls below that and is left where they are: nothing\n * is more disorienting than the chart walking out from under you while you read\n * it.\n */\nexport function isPinnedToLatest(\n window: VisibleRange,\n lastMs: number,\n stepMs: number,\n): boolean {\n return window.endMs >= lastMs + stepMs;\n}\n\n/**\n * The visible window to apply after a timeframe switch so each candle keeps\n * the exact pixel width it had before: the visible slot count is preserved and\n * the right edge re-anchors on the newest candle (any future-gap overshoot is\n * carried over in slots, clamped to the core's 3/4-window cap). The new start\n * may precede the first candle — that gap is intentional, width wins.\n */\nexport function timeframeWindow(\n oldWindow: VisibleRange,\n oldStepMs: number,\n oldLastMs: number,\n newStepMs: number,\n newLastMs: number,\n): VisibleRange {\n const slots = (oldWindow.endMs - oldWindow.startMs) / oldStepMs;\n const offsetRaw = (oldWindow.endMs - oldLastMs) / oldStepMs;\n const offsetSlots = Math.min(Math.max(offsetRaw, 0), slots * 0.75);\n const endMs = Math.round(newLastMs + offsetSlots * newStepMs);\n return { startMs: Math.round(endMs - slots * newStepMs), endMs };\n}\n","// Mirror of packages/react/src/easing.ts — the platform packages don't depend on\n// each other, and @vroomchart/types carries types only.\n\nimport type { TransitionEasing } from '@vroomchart/types';\n\n/**\n * Maps linear animation progress (0..1) to eased progress (0..1) for the chart's\n * transitions. Unknown values fall back to `'ease-in-out'`, which is a\n * smoothstep — the curve the candle↔line transition has always used.\n */\nexport function ease(kind: TransitionEasing | undefined, p: number): number {\n switch (kind) {\n case 'linear':\n return p;\n case 'ease-in':\n return p * p;\n case 'ease-out':\n return p * (2 - p);\n default:\n return p * p * (3 - 2 * p);\n }\n}\n\n// Index order matches VroomEasing in vroom_chart.h.\nconst EASINGS: readonly TransitionEasing[] = [\n 'linear',\n 'ease-in',\n 'ease-out',\n 'ease-in-out',\n];\n\n/**\n * The curve as a `VroomEasing` index, for the animations the core paces itself\n * (see `setVolumeCollapse`) rather than taking pre-eased progress. Falls back to\n * `'ease-in-out'`, matching {@link ease}.\n */\nexport function easingIndex(kind: TransitionEasing | undefined): number {\n const i = kind ? EASINGS.indexOf(kind) : -1;\n return i < 0 ? EASINGS.indexOf('ease-in-out') : i;\n}\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 badgeText: 8, // VROOM_COLOR_BADGE_TEXT (7 is a retired slot)\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 lineGradientOpacity: 12, // VROOM_FLOAT_LINE_GRADIENT_OPACITY\n lineTension: 13, // VROOM_FLOAT_LINE_TENSION\n};\n\n// Named because useChartCore clears it directly under reduced motion, outside\n// the theme sweep below.\nexport const FLOAT_LINE_TIP_PULSE = 15; // VROOM_FLOAT_LINE_TIP_PULSE\n\n// Maps each boolean VroomTheme field to its VroomFloatKey index (pushed as 0/1).\n//\n// `showXAxis` / `showYAxis` are absent on purpose: they animate, so VroomChart\n// drives them as a collapse scalar through setAxisCollapse. A float slot here\n// would let this sweep snap them behind the animation's back.\nexport const BOOL_KEYS: Partial<Record<keyof VroomTheme, number>> = {\n wickRoundCap: 9, // VROOM_FLOAT_WICK_ROUND_CAP\n lineTipDot: 14, // VROOM_FLOAT_LINE_TIP_DOT\n lineTipPulse: FLOAT_LINE_TIP_PULSE,\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","FOOTPRINT_SELL","React"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/VroomChart.tsx","../src/useChartCore.ts","../src/NativeVroomChart.ts","../src/dataTransitions.ts","../src/easing.ts","../src/packCandles.ts","../src/theme.ts"],"sourcesContent":["export { VroomChart } from './VroomChart';\nexport {\n classifyTransition,\n inferStepMs,\n timeframeWindow,\n type DataTransition,\n} from './dataTransitions';\nexport type {\n VroomChartProps,\n Candle,\n CrosshairEvent,\n VroomTheme,\n VroomColor,\n VisibleRange,\n RSIConfig,\n MACDConfig,\n ATRConfig,\n ATRSmoothing,\n MASource,\n MAKind,\n MovingAverageOverlay,\n VWAPConfig,\n BollingerBandsConfig,\n IchimokuConfig,\n FairValueGapsConfig,\n VolumeConfig,\n ChartType,\n TransitionEasing,\n IntervalTransition,\n StreamTransition,\n PriceLine,\n PriceLinesStyle,\n Footprint,\n FootprintSide,\n FootprintsStyle,\n FootprintEvent,\n PlotRect,\n DefaultDrawingStyle,\n} from './types';\n","// VroomChart — Phase 3.\n//\n// Owns SharedValues driven by:\n// - useChartCore's \"initial\" frame (when data/size/range change), AND\n// - Pan gesture callbacks that call handle.pan(dx, dy) → a fresh frame.\n//\n// iOS wraps an SkPicture in-process. Android rasterizes to an SkImage (the\n// two Skia copies can't share a picture pointer) so pan/zoom don't serialize\n// the scene — and the system typeface — on every frame.\n//\n// Reanimated 4 + RN-Skia 2 propagate SharedValue changes to <Picture>/<Image>\n// 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 { PixelRatio, View, type LayoutChangeEvent } from 'react-native';\nimport {\n AlphaType,\n Canvas,\n ColorType,\n Image,\n Picture,\n Skia,\n type SkImage,\n type SkPicture,\n} from '@shopify/react-native-skia';\nimport {\n Gesture,\n GestureDetector,\n GestureHandlerRootView,\n} from 'react-native-gesture-handler';\nimport { useReducedMotion, useSharedValue } from 'react-native-reanimated';\n\nimport { useChartCore } from './useChartCore';\nimport { ease, easingIndex } from './easing';\nimport type { ChartFrame } from './jsi.d';\nimport type { Footprint, VroomChartProps } from './types';\nimport './jsi.d';\n\n// Mirrors VroomFootprintSide in packages/core/include/vroom/vroom_chart.h.\nconst FOOTPRINT_SELL = 1;\n\nfunction isSkImage(frame: ChartFrame): frame is SkImage {\n return typeof (frame as SkImage).getImageInfo === 'function';\n}\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`, `bollingerBands`,\n * `ichimoku`, and more), colors (`theme`), and events (`onCrosshair`,\n * `onViewportChange`) are configured through props.\n *\n * @see {@link VroomChartProps} for the full prop reference.\n */\nexport function VroomChart(props: VroomChartProps) {\n const {\n candles,\n loading,\n seriesKey,\n width: widthProp,\n height: heightProp,\n style,\n visibleRange,\n defaultCandleWidth,\n chartType,\n transitionMs,\n transitionEasing,\n intervalTransition,\n streamTransition,\n streamTransitionMs,\n theme,\n rsi,\n macd,\n atr,\n movingAverages,\n vwap,\n bollingerBands,\n ichimoku,\n fairValueGaps,\n volume,\n crosshairOffset = 40,\n onCrosshair,\n onViewportChange,\n priceLines,\n priceLinesStyle,\n onPriceLineDrag,\n onPriceLineDragEnd,\n onPriceLineClose,\n footprints,\n footprintsStyle,\n onFootprint,\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 // The close button is callback-gated, so whether a handler exists is part of\n // what gets rendered.\n const priceLinesProp = useMemo(\n () =>\n priceLines\n ? {\n lines: priceLines,\n style: priceLinesStyle,\n hasCloseHandler: onPriceLineClose != null,\n }\n : undefined,\n [priceLines, priceLinesStyle, onPriceLineClose],\n );\n\n const footprintsProp = useMemo(\n () => (footprints ? { prints: footprints, style: footprintsStyle } : undefined),\n [footprints, footprintsStyle],\n );\n\n // RN-Skia's recorder reads these SharedValues on the UI/render runtime, a\n // beat behind JS-thread writes. If it ever reads null it throws (\"Invalid\n // prop value for SkTextBlob received\" — RN-Skia's mislabeled SkPicture\n // error), so we seed them and *never* assign null. Android writes the image\n // SV (raster path); iOS writes the picture SV. The unused layer stays a\n // transparent 1×1 so it doesn't cover the other.\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 emptyImage = useMemo(() => {\n const data = Skia.Data.fromBytes(new Uint8Array(4));\n return Skia.Image.MakeImage(\n {\n width: 1,\n height: 1,\n colorType: ColorType.RGBA_8888,\n alphaType: AlphaType.Premul,\n },\n data,\n 4,\n )!;\n }, []);\n const pictureSV = useSharedValue<SkPicture>(emptyPicture);\n const imageSV = useSharedValue<SkImage>(emptyImage);\n const applyFrame = useCallback(\n (frame: ChartFrame) => {\n if (isSkImage(frame)) imageSV.value = frame;\n else pictureSV.value = frame;\n },\n [imageSV, pictureSV],\n );\n\n // An OS reduced-motion preference snaps every transition, the way\n // prefers-reduced-motion does on web.\n const reduceMotion = useReducedMotion();\n\n // The interval morph starts inside the data effect (it needs the pre-swap\n // capture) but repaints every frame, so it writes straight into the SV rather\n // than through React state — the same bypass the gesture handlers use.\n const onFrame = useCallback(\n (p: ChartFrame) => {\n applyFrame(p);\n },\n [applyFrame],\n );\n\n const { handle, picture, volumeCollapseRef } = useChartCore(\n candles,\n { width, height, pxRatio: PixelRatio.get() },\n visibleRange,\n defaultCandleWidth,\n chartType,\n theme,\n rsi,\n macd,\n atr,\n movingAverages,\n vwap,\n bollingerBands,\n ichimoku,\n fairValueGaps,\n volume,\n priceLinesProp,\n footprintsProp,\n {\n seriesKey,\n transitionMs,\n transitionEasing,\n intervalTransition,\n streamTransition,\n streamTransitionMs,\n reduceMotion,\n onFrame,\n },\n loading,\n );\n\n // Same condition useChartCore draws the line on: a refresh that still has\n // data keeps the chart interactive. Every gesture below is gated on this —\n // there's nothing to pan, zoom or inspect while the line is up, and a\n // crosshair reading prices off a placeholder walk would be actively wrong.\n const showLoadingLine = loading === true && candles.length === 0;\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 // Whether a footprint badge is currently open, so a tap that misses every badge\n // knows whether it has a tooltip to dismiss. A ref for the same reason as\n // crosshairActive: gesture callbacks read it synchronously.\n const footprintActive = 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 // 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) applyFrame(next);\n if (handle.isAnimating()) {\n animRaf.current = requestAnimationFrame(animTick);\n }\n }, [handle, applyFrame]);\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 // 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 //\n // Sits below maybeStartAnim so it can kick the loop: it's the only path a\n // non-gesture change has into it, which is what a theme that turns the line-tip\n // pulse on needs — otherwise the ring wouldn't move until you touched the\n // chart.\n useEffect(() => {\n if (picture) applyFrame(picture);\n maybeStartAnim();\n }, [picture, applyFrame, maybeStartAnim]);\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 // In a ref so changing the curve mid-animation doesn't restart the clock.\n const easingRef = useRef(transitionEasing);\n easingRef.current = transitionEasing;\n useEffect(() => {\n if (!handle) return undefined;\n const target = chartType === 'line' ? 1 : 0;\n\n // Every exit below hands off to maybeStartAnim. Landing in line mode turns\n // tip_pulse_active() on, and the pulse only moves while that loop is\n // requeueing frames — this loop's own clock stops here. Without the handoff\n // the ring sits frozen until some gesture happens to restart the other loop.\n // It no-ops when a frame is already queued or nothing is animating, so\n // landing in candle mode costs nothing.\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) applyFrame(p);\n maybeStartAnim();\n return undefined;\n }\n if (morphFade.current === target) {\n maybeStartAnim();\n return undefined;\n }\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) applyFrame(p);\n maybeStartAnim();\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 fade = from + (target - from) * ease(easingRef.current, prog);\n morphFade.current = fade;\n // Reduced motion still crossfades, but skips the vertical collapse.\n handle.setMorph(reduceMotion ? 0 : fade, fade);\n const p = handle.render();\n if (p) applyFrame(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) applyFrame(q);\n maybeStartAnim();\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 // maybeStartAnim is memoized on [handle, animTick] and animTick on\n // [handle, applyFrame], both already deps here — so it adds no new restarts\n // of this clock.\n }, [handle, chartType, transitionMs, reduceMotion, applyFrame, maybeStartAnim]);\n\n // Volume-bar collapse. The core staggers the bars itself — tallest falling\n // first, all landing together — so unlike the loop above this one hands it\n // *linear* progress plus the curve; pre-easing here would compound the two.\n // Hiding drives 0→1, revealing 1→0, which is the same cascade backwards.\n // Mirrors the web driver in react/src/useChartCore.ts.\n const volumeRaf = useRef<number | null>(null);\n const volumeHandle = useRef<typeof handle>(null);\n useEffect(() => {\n if (!handle) return undefined;\n const target = (volume?.enabled ?? true) ? 0 : 1;\n const easing = easingIndex(easingRef.current);\n\n // Fresh handle (first load / recreate): the data effect's setVolume already\n // snapped it, so a chart that mounts with bars doesn't animate them in.\n // Every exit below hands off to maybeStartAnim, for the same reason the\n // morph loop does: this clock stops here, and anything the core is still\n // animating (the line-tip pulse) needs the other loop requeueing frames.\n if (volumeHandle.current !== handle || volumeCollapseRef.current == null) {\n volumeHandle.current = handle;\n volumeCollapseRef.current = { t: target, easing };\n maybeStartAnim();\n return undefined;\n }\n if (volumeCollapseRef.current.t === target) {\n maybeStartAnim();\n return undefined;\n }\n\n if (volumeRaf.current != null) {\n cancelAnimationFrame(volumeRaf.current);\n volumeRaf.current = null;\n }\n\n const dur = Math.max(0, transitionMs ?? 300);\n if (dur === 0 || reduceMotion) {\n volumeCollapseRef.current = { t: target, easing };\n handle.setVolumeCollapse(target, easing);\n const p = handle.render();\n if (p) applyFrame(p);\n maybeStartAnim();\n return undefined;\n }\n\n // From wherever the last frame left off, so toggling mid-flight reverses\n // instead of jumping. A partial trip covers less ground in the same time.\n const from = volumeCollapseRef.current.t;\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 t = prog < 1 ? from + (target - from) * prog : target;\n const kind = easingIndex(easingRef.current);\n volumeCollapseRef.current = { t, easing: kind };\n handle.setVolumeCollapse(t, kind);\n const p = handle.render();\n if (p) applyFrame(p);\n if (prog < 1) {\n volumeRaf.current = requestAnimationFrame(step);\n } else {\n volumeRaf.current = null;\n maybeStartAnim();\n }\n };\n volumeRaf.current = requestAnimationFrame(step);\n\n return () => {\n if (volumeRaf.current != null) {\n cancelAnimationFrame(volumeRaf.current);\n volumeRaf.current = null;\n }\n };\n }, [\n handle,\n volume?.enabled,\n transitionMs,\n reduceMotion,\n applyFrame,\n volumeCollapseRef,\n maybeStartAnim,\n ]);\n\n // Axis-strip collapse. Unlike the volume bars there is no per-element stagger\n // for the core to distribute, so this pre-eases in JS and hands over the eased\n // scalar. Both axes ride one clock so toggling them together stays in step.\n // This one moves the *layout* — the plot reflows into the reclaimed space\n // every frame. Mirrors the web driver in react/src/useChartCore.ts.\n const axisRaf = useRef<number | null>(null);\n const axisHandle = useRef<typeof handle>(null);\n const axisCollapse = useRef<{ y: number; x: number } | null>(null);\n const showYAxis = theme?.showYAxis ?? true;\n const showXAxis = theme?.showXAxis ?? true;\n useEffect(() => {\n if (!handle) return undefined;\n const targetY = showYAxis ? 0 : 1;\n const targetX = showXAxis ? 0 : 1;\n\n // Fresh handle (first load / recreate): snap, so a chart that mounts with an\n // axis already hidden doesn't play it out.\n if (axisHandle.current !== handle || axisCollapse.current == null) {\n axisHandle.current = handle;\n axisCollapse.current = { y: targetY, x: targetX };\n handle.setAxisCollapse(targetY, targetX);\n const p = handle.render();\n if (p) applyFrame(p);\n maybeStartAnim();\n return undefined;\n }\n\n const fromY = axisCollapse.current.y;\n const fromX = axisCollapse.current.x;\n if (fromY === targetY && fromX === targetX) {\n maybeStartAnim();\n return undefined;\n }\n\n if (axisRaf.current != null) {\n cancelAnimationFrame(axisRaf.current);\n axisRaf.current = null;\n }\n\n const dur = Math.max(0, transitionMs ?? 300);\n if (dur === 0 || reduceMotion) {\n axisCollapse.current = { y: targetY, x: targetX };\n handle.setAxisCollapse(targetY, targetX);\n const p = handle.render();\n if (p) applyFrame(p);\n maybeStartAnim();\n return undefined;\n }\n\n // From wherever the last frame left off, so toggling mid-flight reverses\n // instead of jumping.\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 = ease(easingRef.current, prog);\n const y = prog < 1 ? fromY + (targetY - fromY) * e : targetY;\n const x = prog < 1 ? fromX + (targetX - fromX) * e : targetX;\n axisCollapse.current = { y, x };\n handle.setAxisCollapse(y, x);\n const p = handle.render();\n if (p) applyFrame(p);\n if (prog < 1) {\n axisRaf.current = requestAnimationFrame(step);\n } else {\n axisRaf.current = null;\n maybeStartAnim();\n }\n };\n axisRaf.current = requestAnimationFrame(step);\n\n return () => {\n if (axisRaf.current != null) {\n cancelAnimationFrame(axisRaf.current);\n axisRaf.current = null;\n }\n };\n }, [\n handle,\n showYAxis,\n showXAxis,\n transitionMs,\n reduceMotion,\n applyFrame,\n maybeStartAnim,\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 // Hit-tests the price lines at a touch point, resolving the core's index back\n // to the line it belongs to. Null when nothing was hit.\n const hitPriceLine = useCallback(\n (x: number, y: number) => {\n if (!handle || !priceLines?.length) return null;\n const hit = handle.hitTestPriceLine(x, y);\n const line = hit ? priceLines[hit.index] : undefined;\n return hit && line ? { index: hit.index, part: hit.part, line } : null;\n },\n [handle, priceLines],\n );\n\n // A price line being dragged vertically: its core index, its id, and the last\n // previewed price (the payload for the drop).\n const priceDrag = useRef<{ index: number; id: string; price: number } | null>(\n null,\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), the indicator pane (horizontal\n // scroll only), or a draggable price line. We classify on onStart.\n const panMode = useRef<\n 'chart' | 'price-axis' | 'time-axis' | 'indicator' | 'price-line'\n >('chart');\n\n // Closes an open footprint tooltip. Any viewport change slides the candles out\n // from under it and the crosshair replaces it outright, so the host is told to\n // take it down rather than left holding a position the badge has moved away\n // from. `redraw` is false for callers that render a frame of their own right\n // after — on Android that render rasterizes pixels, so the duplicate is worth\n // skipping.\n const dismissFootprint = (redraw = true) => {\n if (!handle || !footprintActive.current) return;\n footprintActive.current = false;\n handle.setFootprintHover(0, -1);\n if (redraw) {\n const frame = handle.render();\n if (frame) applyFrame(frame);\n }\n onFootprint?.({\n active: false,\n reason: 'hide',\n side: null,\n timeMs: null,\n footprints: [],\n badge: null,\n pane: null,\n });\n };\n\n const pan = Gesture.Pan()\n .enabled(!showLoadingLine)\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 // A draggable price line takes the drag over from the chart. Seeding the\n // preview at the committed price puts the label in drag styling before the\n // first move, so the grab registers immediately.\n priceDrag.current = null;\n if (handle && panMode.current === 'chart' && !crosshairActive.current) {\n const pl = hitPriceLine(e.x, e.y);\n if (pl && pl.part === 0) {\n panMode.current = 'price-line';\n priceDrag.current = { index: pl.index, id: pl.line.id, price: pl.line.price };\n handle.setPriceLineDrag(pl.index, pl.line.price);\n const p = handle.render();\n if (p) applyFrame(p);\n }\n }\n // Every mode but a price-line drag moves the viewport, and this one call\n // covers the momentum fling too — decay only ever starts from a pan that\n // already began here.\n if (panMode.current !== 'price-line') dismissFootprint();\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 (panMode.current === 'price-line') {\n // Preview the price under the finger; nothing is committed until the drop.\n const g = priceDrag.current;\n if (!g) return;\n const c = handle.coordAt(e.x, e.y);\n if (!c) return;\n g.price = c.price;\n handle.setPriceLineDrag(g.index, c.price);\n onPriceLineDrag?.(g.id, c.price);\n next = handle.render();\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) applyFrame(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) applyFrame(next);\n maybeStartAnim();\n })\n .onEnd((e) => {\n if (!handle) return;\n // Price-line drop. The preview always clears here: the line is a controlled\n // prop, so it only really moves once the host restates it — which means a\n // rejected (or ignored) move reverts on its own.\n if (panMode.current === 'price-line') {\n const g = priceDrag.current;\n priceDrag.current = null;\n handle.setPriceLineDrag(-1, 0);\n const p = handle.render();\n if (p) applyFrame(p);\n if (g) onPriceLineDragEnd?.(g.id, g.price);\n return;\n }\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) applyFrame(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 .enabled(!showLoadingLine)\n .runOnJS(true)\n .onTouchesDown((e) => {\n if (e.numberOfTouches < 2) return;\n dismissFootprint();\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) applyFrame(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 .enabled(!showLoadingLine)\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 // A press on a price line belongs to that line — dragging it or tapping its\n // close button — so it must not raise the crosshair over the top.\n if (hitPriceLine(e.x, e.y)) return;\n cancelDecay();\n // The crosshair takes the pane over, so it can't share it with a tooltip.\n // No redraw: setCrosshair below returns a frame that already has the badge\n // un-highlighted.\n dismissFootprint(false);\n crosshairActive.current = true;\n const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);\n if (ch) applyFrame(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 activates a price line's close button, selects or dismisses a footprint\n // badge, and otherwise dismisses the crosshair while it's up. Any other tap is a\n // no-op, so it never interferes with normal pan/pinch.\n const tap = Gesture.Tap()\n .enabled(!showLoadingLine)\n .runOnJS(true)\n .onStart((e) => {\n if (!handle) return;\n\n // Badges get first refusal: one is a ~9px circle, while a price line's grab\n // band spans the pane and would otherwise swallow any badge it crosses.\n // Touch has no hover, so a tap is what opens a footprint here, and the next\n // tap anywhere closes it.\n const prints = footprints ?? [];\n const fp = prints.length ? handle.hitTestFootprint(e.x, e.y) : null;\n if (fp) {\n handle.setFootprintHover(fp.candleTimeMs, fp.side);\n const frame = handle.render();\n if (frame) applyFrame(frame);\n const wasActive = footprintActive.current;\n footprintActive.current = true;\n onFootprint?.({\n active: true,\n reason: wasActive ? 'move' : 'show',\n side: fp.side === FOOTPRINT_SELL ? 'sell' : 'buy',\n timeMs: fp.candleTimeMs,\n // The core reports indices into the array we last pushed, which is this\n // same prop — so this rejoins each badge to the consumer's own objects.\n footprints: fp.indices\n .map((i) => prints[i])\n .filter((f): f is Footprint => f != null),\n badge: { x: fp.x, y: fp.y, radius: fp.radius },\n pane: fp.pane,\n });\n return;\n }\n // A tap that missed every badge dismisses the open one, so the host tooltip\n // goes away the same way the crosshair does.\n dismissFootprint();\n\n // The close button is a tap target whether or not the crosshair is up.\n const pl = hitPriceLine(e.x, e.y);\n if (pl && pl.part === 1) {\n onPriceLineClose?.(pl.line.id);\n return;\n }\n if (!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) applyFrame(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 <>\n <Picture picture={pictureSV} />\n <Image\n image={imageSV}\n x={0}\n y={0}\n width={width}\n height={height}\n fit=\"fill\"\n />\n </>\n ) : null}\n </Canvas>\n </View>\n </GestureDetector>\n </GestureHandlerRootView>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport type { MutableRefObject } from 'react';\n\nimport NativeVroomChart from './NativeVroomChart';\nimport type { DataTransition } from './dataTransitions';\nimport {\n classifyStream,\n classifyTransition,\n inferStepMs,\n isPinnedToLatest,\n timeframeWindow,\n} from './dataTransitions';\nimport { ease } from './easing';\nimport type { ChartFrame, ChartHandle } from './jsi.d';\nimport { packCandles } from './packCandles';\nimport { applyTheme, parseColor, FLOAT_LINE_TIP_PULSE } from './theme';\nimport type {\n BollingerBandsConfig,\n ATRConfig,\n Candle,\n ChartType,\n FairValueGapsConfig,\n IchimokuConfig,\n MACDConfig,\n MovingAverageOverlay,\n PriceLine,\n PriceLinesStyle,\n Footprint,\n FootprintsStyle,\n RSIConfig,\n TransitionEasing,\n IntervalTransition,\n StreamTransition,\n VisibleRange,\n VolumeConfig,\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\n// Mirrors vroom::atr::Smoothing order in packages/core/src/atr.h.\nconst ATR_SMOOTHINGS = ['rma', 'sma', 'ema'] as const;\n\n// An unset style color marshals as the core's transparent inherit sentinel.\nconst inheritColor = (v: string | number | undefined): number =>\n (v != null ? parseColor(v) : null) ?? 0;\n\nfunction overlayToNumeric(o: MovingAverageOverlay) {\n const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;\n return {\n kind: o.maType === 'ema' ? 1 : 0,\n period: o.period,\n source: srcIdx < 0 ? 0 : srcIdx,\n color: (o.color != null ? parseColor(o.color) : null) ?? 0xff2962ff,\n width: o.width ?? 1.5,\n };\n}\n\nfunction rsiToSpec(cfg: RSIConfig | undefined) {\n return {\n enabled: cfg?.enabled ?? false,\n period: cfg?.period ?? 14,\n upperBand: cfg?.upperBand ?? 70,\n lowerBand: cfg?.lowerBand ?? 30,\n maPeriod: cfg?.maPeriod ?? 14,\n maKind: cfg?.maType === 'ema' ? 1 : 0,\n maVisible: cfg?.maVisible ?? true,\n lineColor: inheritColor(cfg?.lineColor),\n lineWidth: cfg?.lineWidth ?? -1,\n lineVisible: cfg?.lineVisible ?? true,\n maColor: inheritColor(cfg?.maColor),\n maWidth: cfg?.maWidth ?? -1,\n bandColor: inheritColor(cfg?.bandColor),\n bandsVisible: cfg?.bandsVisible ?? true,\n extremeFill: cfg?.extremeFill ?? true,\n };\n}\n\nfunction vwapToSpec(cfg: VWAPConfig | undefined) {\n return {\n enabled: cfg?.enabled ?? false,\n resetOffsetMin: cfg?.resetMinutes ?? 0,\n color: inheritColor(cfg?.color),\n width: cfg?.width ?? -1,\n };\n}\n\n// Bollinger defaults: blue bands / orange basis, matching the repo palette.\nconst DEFAULT_BB_BAND_COLOR = 0xff2962ff;\nconst DEFAULT_BB_BASIS_COLOR = 0xffff6d00;\n\nfunction bollingerToSpec(cfg: BollingerBandsConfig | undefined) {\n const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;\n return {\n enabled: cfg?.enabled ?? false,\n period: cfg?.period ?? 20,\n mult: cfg?.stdDev ?? 2,\n source: srcIdx < 0 ? 0 : srcIdx,\n basisKind: cfg?.maType === 'ema' ? 1 : 0,\n upperColor:\n (cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ??\n DEFAULT_BB_BAND_COLOR,\n upperWidth: cfg?.upperWidth ?? 1,\n middleColor:\n (cfg?.middleColor != null ? parseColor(cfg.middleColor) : null) ??\n DEFAULT_BB_BASIS_COLOR,\n middleWidth: cfg?.middleWidth ?? 1,\n lowerColor:\n (cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ??\n DEFAULT_BB_BAND_COLOR,\n lowerWidth: cfg?.lowerWidth ?? 1,\n fillEnabled: cfg?.fillVisible ?? true,\n fillOpacity: cfg?.fillOpacity ?? 0.1,\n };\n}\n\n// Ichimoku defaults. Green and red do double duty: they color span A and kijun,\n// and tint the cloud for whichever span is on top.\nconst DEFAULT_ICH_GREEN = 0xff26a69a;\nconst DEFAULT_ICH_RED = 0xffef5350;\nconst DEFAULT_ICH_BLUE = 0xff2962ff;\nconst DEFAULT_ICH_ORANGE = 0xffff6d00;\nconst DEFAULT_ICH_TEAL = 0xff00bcd4;\n\nfunction ichimokuToSpec(cfg: IchimokuConfig | undefined) {\n const color = (v: string | number | undefined, fallback: number) =>\n (v != null ? parseColor(v) : null) ?? fallback;\n return {\n enabled: cfg?.enabled ?? false,\n tenkanPeriod: cfg?.tenkanPeriod ?? 9,\n kijunPeriod: cfg?.kijunPeriod ?? 26,\n senkouBPeriod: cfg?.senkouBPeriod ?? 52,\n displacement: cfg?.displacement ?? 26,\n tenkanColor: color(cfg?.tenkanColor, DEFAULT_ICH_BLUE),\n tenkanWidth: cfg?.tenkanWidth ?? 1,\n tenkanEnabled: cfg?.tenkanVisible ?? true,\n kijunColor: color(cfg?.kijunColor, DEFAULT_ICH_RED),\n kijunWidth: cfg?.kijunWidth ?? 1,\n kijunEnabled: cfg?.kijunVisible ?? true,\n senkouAColor: color(cfg?.senkouAColor, DEFAULT_ICH_GREEN),\n senkouAWidth: cfg?.senkouAWidth ?? 1,\n senkouAEnabled: cfg?.senkouAVisible ?? true,\n senkouBColor: color(cfg?.senkouBColor, DEFAULT_ICH_ORANGE),\n senkouBWidth: cfg?.senkouBWidth ?? 1,\n senkouBEnabled: cfg?.senkouBVisible ?? true,\n chikouColor: color(cfg?.chikouColor, DEFAULT_ICH_TEAL),\n chikouWidth: cfg?.chikouWidth ?? 1,\n chikouEnabled: cfg?.chikouVisible ?? true,\n cloudEnabled: cfg?.cloudVisible ?? true,\n bullishCloudColor: color(cfg?.bullishCloudColor, DEFAULT_ICH_GREEN),\n bearishCloudColor: color(cfg?.bearishCloudColor, DEFAULT_ICH_RED),\n cloudOpacity: cfg?.cloudOpacity ?? 0.15,\n };\n}\n\n// Fair Value Gap defaults. The border colors fall back to the fill color, so a\n// config that only restyles the fill keeps its outline in the same hue.\nconst DEFAULT_FVG_GREEN = 0xff26a69a;\nconst DEFAULT_FVG_RED = 0xffef5350;\nconst FVG_FILL_TYPES = ['close', 'wick'] as const;\nconst FVG_BORDER_STYLES = ['solid', 'dotted', 'dashed'] as const;\n\nfunction fvgToSpec(cfg: FairValueGapsConfig | undefined) {\n const color = (v: string | number | undefined, fallback: number) =>\n (v != null ? parseColor(v) : null) ?? fallback;\n const bullish = color(cfg?.bullishColor, DEFAULT_FVG_GREEN);\n const bearish = color(cfg?.bearishColor, DEFAULT_FVG_RED);\n return {\n enabled: cfg?.enabled ?? false,\n maxBarsBack: cfg?.maxBarsBack ?? 300,\n waitForClose: cfg?.waitForClose ?? false,\n fillType: Math.max(0, FVG_FILL_TYPES.indexOf(cfg?.fillType ?? 'close')),\n deleteAfterFill: cfg?.deleteAfterFill ?? true,\n extendBoxes: cfg?.extendBoxes ?? false,\n boxLength: cfg?.boxLength ?? 20,\n bullishColor: bullish,\n bearishColor: bearish,\n opacity: cfg?.opacity ?? 0.15,\n borderEnabled: cfg?.borderVisible ?? true,\n borderStyle: Math.max(\n 0,\n FVG_BORDER_STYLES.indexOf(cfg?.borderStyle ?? 'solid'),\n ),\n borderWidth: cfg?.borderWidth ?? 1,\n bullishBorderColor: color(cfg?.bullishBorderColor, bullish),\n bearishBorderColor: color(cfg?.bearishBorderColor, bearish),\n labelsEnabled: cfg?.showLabels ?? true,\n label: cfg?.label ?? 'FVG',\n labelDistance: cfg?.labelDistance ?? 10,\n // Alpha 0 is the core's \"inherit the border color\" sentinel.\n labelColor: color(cfg?.labelColor, 0),\n labelFontSize: cfg?.labelFontSize ?? 0,\n showInverse: cfg?.showInverse ?? false,\n inverseBullishColor: color(cfg?.inverseBullishColor, bullish),\n inverseBearishColor: color(cfg?.inverseBearishColor, bearish),\n inverseLabel: cfg?.inverseLabel ?? 'iFVG',\n };\n}\n\nfunction macdToSpec(cfg: MACDConfig | undefined) {\n const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;\n return {\n enabled: cfg?.enabled ?? false,\n fast: cfg?.fast ?? 12,\n slow: cfg?.slow ?? 26,\n signal: cfg?.signal ?? 9,\n source: srcIdx < 0 ? 0 : srcIdx,\n maKind: cfg?.maType === 'sma' ? 0 : 1,\n signalMaKind: cfg?.signalMaType === 'sma' ? 0 : 1,\n lineColor: inheritColor(cfg?.lineColor),\n lineWidth: cfg?.lineWidth ?? -1,\n lineVisible: cfg?.lineVisible ?? true,\n signalColor: inheritColor(cfg?.signalColor),\n signalWidth: cfg?.signalWidth ?? -1,\n signalVisible: cfg?.signalVisible ?? true,\n histVisible: cfg?.histogramVisible ?? true,\n histUpColor: inheritColor(cfg?.histogramUpColor),\n histUpFadingColor: inheritColor(cfg?.histogramUpFadingColor),\n histDownColor: inheritColor(cfg?.histogramDownColor),\n histDownFadingColor: inheritColor(cfg?.histogramDownFadingColor),\n zeroColor: inheritColor(cfg?.zeroLineColor),\n zeroVisible: cfg?.zeroLineVisible ?? true,\n };\n}\n\nfunction atrToSpec(cfg: ATRConfig | undefined) {\n return {\n enabled: cfg?.enabled ?? false,\n period: cfg?.period ?? 14,\n smoothing: Math.max(0, ATR_SMOOTHINGS.indexOf(cfg?.smoothing ?? 'rma')),\n lineColor: inheritColor(cfg?.lineColor),\n lineWidth: cfg?.lineWidth ?? -1,\n };\n}\n\n// Unset style fields go down as the core's inherit sentinels (negative float,\n// transparent color) rather than as literal defaults, so the theme keys stay in\n// charge of anything the consumer didn't set.\nfunction volumeToSpec(cfg: VolumeConfig | undefined) {\n return {\n enabled: cfg?.enabled ?? true,\n heightFrac: cfg?.height ?? -1,\n opacity: cfg?.opacity ?? -1,\n radiusPx: cfg?.radius ?? -1,\n upColor: (cfg?.upColor != null ? parseColor(cfg.upColor) : null) ?? 0,\n downColor: (cfg?.downColor != null ? parseColor(cfg.downColor) : null) ?? 0,\n };\n}\n\n// Price-line defaults: a soft red dotted rule with a dark translucent label,\n// close in weight to the current-price indicator it sits beside.\nconst DEFAULT_PRICE_LINE_COLOR = 0xffef5350;\nconst DEFAULT_PRICE_LINE_BODY_BG = 0xd91c2128;\nconst DEFAULT_PRICE_LINE_HOVER_BOOST = 1.25;\n\nconst LINE_STYLES = { solid: 0, dotted: 1, dashed: 2 } as const;\n\n// Mirrors VroomPriceLineFlags in packages/core/include/vroom/vroom_chart.h.\nconst PRICE_LINE_DRAGGABLE = 1 << 0;\nconst PRICE_LINE_CLOSABLE = 1 << 1;\nconst PRICE_LINE_AXIS_LABEL = 1 << 2;\nconst PRICE_LINE_EXTEND_LEFT = 1 << 3;\n\n/** The price lines + their shared style, as the chart's props express them. */\nexport type PriceLinesProp = {\n lines: PriceLine[];\n style?: PriceLinesStyle;\n /**\n * Whether the host supplied a close handler. The close button is\n * callback-gated, so with nothing for it to do it isn't drawn at all.\n */\n hasCloseHandler: boolean;\n};\n\nfunction priceLinesToSpec(cfg: PriceLinesProp) {\n return {\n lines: cfg.lines.map((l) => ({\n price: l.price,\n color:\n (l.color != null ? parseColor(l.color) : null) ?? DEFAULT_PRICE_LINE_COLOR,\n width: l.width ?? 1,\n lineStyle: LINE_STYLES[l.lineStyle ?? 'dotted'],\n text: l.text ?? '',\n quantity: l.quantity ?? '',\n flags:\n (l.draggable ? PRICE_LINE_DRAGGABLE : 0) |\n (cfg.hasCloseHandler && l.closable !== false ? PRICE_LINE_CLOSABLE : 0) |\n (l.axisLabel !== false ? PRICE_LINE_AXIS_LABEL : 0) |\n (l.extendLeft !== false ? PRICE_LINE_EXTEND_LEFT : 0),\n })),\n bodyBg:\n (cfg.style?.bodyBackground != null ? parseColor(cfg.style.bodyBackground) : null) ??\n DEFAULT_PRICE_LINE_BODY_BG,\n fontSizePx: cfg.style?.fontSize ?? 0,\n lineLengthFrac: cfg.style?.inset ?? 0,\n align: cfg.style?.align === 'left' ? 0 : cfg.style?.align === 'center' ? 1 : 2,\n hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_PRICE_LINE_HOVER_BOOST,\n };\n}\n\n// Cleared overlay: no lines (the style values are irrelevant, but the spec shape\n// requires them).\nconst EMPTY_PRICE_LINES = priceLinesToSpec({ lines: [], hasCloseHandler: false });\n\n// Footprints share the price lines' hover weight so the two widgets light up\n// alike. Zeroed geometry defers to the core's own defaults.\nconst DEFAULT_FOOTPRINT_HOVER_BOOST = 1.25;\n\n// Mirrors VroomFootprintSide in packages/core/include/vroom/vroom_chart.h.\nconst FOOTPRINT_BUY = 0;\nconst FOOTPRINT_SELL = 1;\n\n// A time no real series can contain (~273,000 BCE), still comfortably inside\n// int64. Parks a malformed footprint where the core will never bucket it.\nconst UNBUCKETABLE_MS = -8.64e15;\n\n/** The footprints + their shared style, as the chart's props express them. */\nexport type FootprintsProp = {\n prints: Footprint[];\n style?: FootprintsStyle;\n};\n\nfunction footprintsToSpec(cfg: FootprintsProp) {\n return {\n // Index alignment is load-bearing: the core reports hits as indices into this\n // array and the gesture layer maps them straight back to the consumer's\n // `footprints`. So a non-finite time — which can't be bucketed and would\n // reach the native side as a garbage int64 — is neutralized *in place* rather\n // than filtered out, which would shift every index after it onto the wrong\n // trade.\n prints: cfg.prints.map((f) => ({\n timeMs: Number.isFinite(f.timeMs) ? f.timeMs : UNBUCKETABLE_MS,\n side: f.side === 'sell' ? FOOTPRINT_SELL : FOOTPRINT_BUY,\n })),\n radiusPx: cfg.style?.radius ?? 0,\n gapPx: cfg.style?.gap ?? 0,\n marginPx: cfg.style?.margin ?? 0,\n hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_FOOTPRINT_HOVER_BOOST,\n };\n}\n\nconst EMPTY_FOOTPRINTS = footprintsToSpec({ prints: [] });\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\n/** Progress + curve of the staggered volume-bar collapse. See setVolumeCollapse. */\nexport type VolumeCollapse = { t: number; easing: number };\n\n/**\n * How a data swap should animate, plus where its frames go. The interval morph\n * has to be started from inside the data effect (it needs the pre-swap capture),\n * but it repaints at 60fps — far too often for React state — so the host passes\n * a sink that writes straight into the picture SharedValue.\n */\nexport type TransitionOptions = {\n /** Identity of the series; a change forces a full view reset. */\n seriesKey?: string;\n /** Duration of the interval morph in ms. 0 snaps. Default 300. */\n transitionMs?: number;\n /** Curve applied to the morph's progress. Default 'ease-in-out'. */\n transitionEasing?: TransitionEasing;\n /** `'transform'` (default) slot-lerps; `'fade'` fades out then in. */\n intervalTransition?: IntervalTransition;\n /** `'transform'` eases live updates; `'none'` (default) snaps them. */\n streamTransition?: StreamTransition;\n /** Duration of the stream animation in ms. 0 snaps. Default 150. */\n streamTransitionMs?: number;\n /** OS reduced-motion preference: skips the capture and snaps. */\n reduceMotion?: boolean;\n /** Receives every morph frame. Without one, data swaps snap. */\n onFrame?: (picture: ChartFrame) => void;\n};\n\nexport type ChartCoreState = {\n handle: ChartHandle | null;\n /** Picture freshly rendered after the latest data/size/range push. */\n picture: ChartFrame | null;\n /**\n * The last volume collapse handed to the core, or null before the first push.\n * VroomChart's animation loop owns this — it lives here only so the data effect\n * can restore it, since setVolume snaps the scalar (see below).\n */\n volumeCollapseRef: MutableRefObject<VolumeCollapse | 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 atr?: ATRConfig,\n movingAverages?: MovingAverageOverlay[],\n vwap?: VWAPConfig,\n bollingerBands?: BollingerBandsConfig,\n ichimoku?: IchimokuConfig,\n fairValueGaps?: FairValueGapsConfig,\n volume?: VolumeConfig,\n priceLines?: PriceLinesProp,\n footprints?: FootprintsProp,\n transition?: TransitionOptions,\n // Trails the config params because it's data state, not configuration: it\n // pairs with `candles` above (see showLoadingLine below).\n loading?: boolean,\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 volumeCollapseRef = useRef<VolumeCollapse | null>(null);\n // What the core currently holds, for classifying the next data change. Keyed\n // by handle so a recreated core is treated as a fresh initial load.\n const prevDataRef = useRef<{\n handle: ChartHandle;\n candles: Candle[];\n seriesKey?: string;\n } | null>(null);\n const intervalMorphRaf = useRef<number | null>(null);\n const streamRaf = useRef<number | null>(null);\n // Where an in-flight stream shift is headed, so cancelling it can land there\n // rather than stranding the view mid-slide.\n const streamWindowRef = useRef<VisibleRange | null>(null);\n // Whether that loop is the one driving the morph scalar, so settling it never\n // cuts short a timeframe switch that happens to overlap.\n const streamMorphRef = useRef(false);\n const [picture, setPicture] = useState<ChartFrame | null>(null);\n\n if (!handleRef.current && size.width > 0 && size.height > 0) {\n ensureInstalled();\n handleRef.current = globalThis.VroomChartJSI!.create();\n }\n\n // Animation config and frame sink in refs, refreshed each render, so changing\n // the duration, curve or callback identity doesn't re-run the data effect\n // below (which would re-push every candle).\n const animRef = useRef<{\n ms: number;\n easing: TransitionEasing | undefined;\n reduceMotion: boolean;\n interval: IntervalTransition;\n stream: StreamTransition;\n streamMs: number;\n }>({\n ms: 300,\n easing: undefined,\n reduceMotion: false,\n interval: 'transform',\n stream: 'none',\n streamMs: 150,\n });\n animRef.current = {\n ms: Math.max(0, transition?.transitionMs ?? 300),\n easing: transition?.transitionEasing,\n reduceMotion: transition?.reduceMotion ?? false,\n interval: transition?.intervalTransition === 'fade' ? 'fade' : 'transform',\n stream: transition?.streamTransition === 'transform' ? 'transform' : 'none',\n // Shorter than transitionMs by default: ticks can land faster than a 300ms\n // curve, and every one that does interrupts the last.\n streamMs: Math.max(0, transition?.streamTransitionMs ?? 150),\n };\n const onFrameRef = useRef(transition?.onFrame);\n onFrameRef.current = transition?.onFrame;\n const seriesKey = transition?.seriesKey;\n\n // Set only while the loading hand-off is in its *first* stage, which is the\n // one an interruption can't simply land: stage one leaves `loading` set in\n // the core, and only stage two releases it.\n const loadingHandoffRef = useRef(false);\n\n // Stop an in-flight interval morph and land the core on the new candles.\n const endIntervalMorph = useCallback(() => {\n if (intervalMorphRaf.current != null) {\n cancelAnimationFrame(intervalMorphRaf.current);\n intervalMorphRaf.current = null;\n }\n const h = handleRef.current;\n // Walk a half-finished hand-off through the rest of its stages rather than\n // just stopping the clock, or the core would be left drawing the loading\n // line over the data it was supposed to hand off to.\n if (loadingHandoffRef.current) {\n loadingHandoffRef.current = false;\n h?.setLoadingMorph(1);\n h?.beginLoadingReveal();\n }\n h?.setIntervalMorph(1);\n }, []);\n\n // Runs the interval morph clock. The core holds the pre-swap geometry (see\n // beginIntervalMorph) and reshapes each candle slot toward its new counterpart.\n // `durationMs` overrides transitionMs for the loading hand-off, which splits\n // it across two stages.\n const startIntervalMorph = useCallback((h: ChartHandle, durationMs?: number) => {\n const { ms, easing } = animRef.current;\n const dur = durationMs ?? ms;\n const start = performance.now();\n const step = (now: number) => {\n const p = Math.min(1, (now - start) / dur);\n h.setIntervalMorph(p < 1 ? ease(easing, p) : 1);\n const pic = h.render();\n if (pic) onFrameRef.current?.(pic);\n intervalMorphRaf.current = p < 1 ? requestAnimationFrame(step) : null;\n };\n intervalMorphRaf.current = requestAnimationFrame(step);\n }, []);\n\n // Hands the loading line over to the data that just landed, in two stages:\n // the line reshapes into the series' silhouette, then the candles grow out of\n // it while it fades.\n //\n // Sequential rather than overlapped — the shape has to read as the data\n // before the bars start emerging from it — so the two split transitionMs and\n // the whole hand-off costs what any other transition costs.\n const startLoadingHandoff = useCallback(\n (h: ChartHandle) => {\n const { ms, easing } = animRef.current;\n const half = ms / 2;\n loadingHandoffRef.current = true;\n h.beginLoadingMorph();\n const start = performance.now();\n const step = (now: number) => {\n const p = Math.min(1, (now - start) / half);\n h.setLoadingMorph(p < 1 ? ease(easing, p) : 1);\n const pic = h.render();\n if (pic) onFrameRef.current?.(pic);\n if (p < 1) {\n intervalMorphRaf.current = requestAnimationFrame(step);\n return;\n }\n intervalMorphRaf.current = null;\n // Past here an interruption is an ordinary interval morph again: the\n // core has left the loading state, so landing the clock is enough.\n loadingHandoffRef.current = false;\n // Stage two rides the interval-morph clock, which also carries the\n // line's fade-out — so the bars' growth and the line's exit finish\n // together instead of one outlasting the other.\n h.beginLoadingReveal();\n startIntervalMorph(h, half);\n };\n intervalMorphRaf.current = requestAnimationFrame(step);\n },\n [startIntervalMorph],\n );\n\n // Stops an in-flight stream animation and puts the chart somewhere coherent.\n //\n // A pending window shift always lands on its target: abandoned mid-slide it\n // would strand the view between two bars, half a candle off the grid.\n //\n // `keepMorph` is for a tick restarting on top of one already running —\n // beginStreamMorph blends out of the geometry currently on screen, so landing\n // that geometry first would throw away the very thing it resumes from.\n const settleStream = useCallback((keepMorph = false) => {\n if (streamRaf.current != null) {\n cancelAnimationFrame(streamRaf.current);\n streamRaf.current = null;\n }\n const h = handleRef.current;\n const target = streamWindowRef.current;\n streamWindowRef.current = null;\n if (target) h?.setVisibleRange(target.startMs, target.endMs);\n if (streamMorphRef.current && !keepMorph) {\n streamMorphRef.current = false;\n h?.setIntervalMorph(1);\n }\n }, []);\n\n // Runs the clock for a live update. One loop drives both halves so they land\n // on the same frame.\n //\n // `window` is null for a plain tick; for an append it is where the view has to\n // end up. The slide is measured from wherever the window is *now*, so a shift\n // interrupting another continues from the current position instead of\n // snapping back to the start of the last one.\n const startStreamAnim = useCallback(\n (h: ChartHandle, morphing: boolean, window: VisibleRange | null) => {\n const { streamMs, easing } = animRef.current;\n let from = window ? h.getVisibleRange() : null;\n // What the previous frame left the window at. Anything else — a pan, a\n // pinch — lands somewhere different, which is how the slide notices it is\n // no longer the only thing moving the view and gets out of the way.\n // Cheaper than teaching every gesture to cancel it, and it can't miss one.\n let applied: VisibleRange | null = null;\n streamWindowRef.current = window;\n streamMorphRef.current = morphing;\n const start = performance.now();\n const step = (now: number) => {\n if (from && applied) {\n const now_w = h.getVisibleRange();\n if (now_w.startMs !== applied.startMs || now_w.endMs !== applied.endMs) {\n from = null;\n streamWindowRef.current = null;\n }\n }\n const p = Math.min(1, (now - start) / streamMs);\n const e = p < 1 ? ease(easing, p) : 1;\n if (morphing) h.setIntervalMorph(e);\n if (from && window) {\n applied = {\n startMs: Math.round(from.startMs + (window.startMs - from.startMs) * e),\n endMs: Math.round(from.endMs + (window.endMs - from.endMs) * e),\n };\n h.setVisibleRange(applied.startMs, applied.endMs);\n }\n const pic = h.render();\n if (pic) onFrameRef.current?.(pic);\n if (p < 1) {\n streamRaf.current = requestAnimationFrame(step);\n } else {\n streamRaf.current = null;\n streamWindowRef.current = null;\n streamMorphRef.current = false;\n }\n };\n streamRaf.current = requestAnimationFrame(step);\n },\n [],\n );\n\n useEffect(() => {\n return () => {\n if (intervalMorphRaf.current != null) {\n cancelAnimationFrame(intervalMorphRaf.current);\n intervalMorphRaf.current = null;\n }\n if (streamRaf.current != null) {\n cancelAnimationFrame(streamRaf.current);\n streamRaf.current = null;\n }\n };\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 // The core trusts `setLoading` outright, so the \"and no data yet\" half of the\n // condition is decided here. Both halves matter: without `loading` a chart\n // that legitimately has no bars would wave a placeholder forever, and without\n // the emptiness check a background refresh of a loaded series would blank the\n // chart the user is already reading.\n const showLoadingLine = loading === true && candles.length === 0;\n // Tracks whether the *core* is currently showing the line, which is what\n // decides if the next data push is a hand-off. Distinct from `showLoadingLine`:\n // that is this render's intent, this is what's on screen.\n const lineUpRef = useRef(false);\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 atrKey = atr ? JSON.stringify(atr) : '';\n const maKey = movingAverages ? JSON.stringify(movingAverages) : '';\n const vwapKey = vwap ? JSON.stringify(vwap) : '';\n const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : '';\n const ichimokuKey = ichimoku ? JSON.stringify(ichimoku) : '';\n const fvgKey = fairValueGaps ? JSON.stringify(fairValueGaps) : '';\n const volumeKey = volume ? JSON.stringify(volume) : '';\n const priceLinesKey = priceLines ? JSON.stringify(priceLines) : '';\n const footprintsKey = footprints ? JSON.stringify(footprints) : '';\n\n useEffect(() => {\n const h = handleRef.current;\n if (!h) return;\n h.setSize(size.width, size.height, size.pxRatio ?? 1);\n // Ahead of setCandles, like setDefaultCandleWidth below: the default framing\n // runs inside setCandles and reserves room past the newest candle for\n // Ichimoku's leading spans, so it has to already know they're coming.\n h.setIchimoku(ichimokuToSpec(ichimoku));\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 // How the new candles relate to what the core holds decides what happens to\n // the viewport: a stream leaves it alone, a timeframe switch re-anchors and\n // morphs into it, a different asset resets it.\n let morphing = false;\n\n if (showLoadingLine) {\n // Clear the core's buffer, which the `candles.length > 0` gate below\n // otherwise never does: pushing an empty array is treated as \"hold the\n // last frame\" everywhere else, so a chart switching assets would still be\n // holding the previous one's bars underneath the line — and would\n // classify the incoming series as a timeframe switch rather than a fresh\n // load. Scoped to the loading case so that hold-the-last-frame behavior\n // is untouched for every other empty push.\n if (!lineUpRef.current) {\n endIntervalMorph();\n settleStream();\n h.setCandles(packCandles([]));\n prevDataRef.current = null;\n }\n h.setLoading(true, !animRef.current.reduceMotion);\n lineUpRef.current = true;\n } else if (lineUpRef.current && candles.length === 0) {\n // Loading resolved to nothing — an empty result, or an error the consumer\n // handled. There's no geometry to morph into, so drop the line rather\n // than leaving it waving at data that isn't coming.\n h.setLoading(false, true);\n lineUpRef.current = false;\n }\n\n if (candles.length > 0) {\n const prev = prevDataRef.current;\n const freshHandle = prev == null || prev.handle !== h;\n if (freshHandle || prev.candles !== candles || prev.seriesKey !== seriesKey) {\n // A fresh core frames itself (its window starts at 0/0); an explicit\n // visibleRange prop overrides any auto behavior, so treat the change\n // like a stream and let the range application below win.\n const transitionKind: DataTransition = freshHandle\n ? 'initial'\n : explicit\n ? 'stream'\n : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);\n\n // Capture the outgoing view before setCandles re-infers the candle\n // period from the new data.\n let tfArgs: {\n oldWindow: VisibleRange;\n oldStepMs: number;\n oldLastMs: number;\n } | null = null;\n // The pre-swap candle envelope, used to scale-lock the y-axis below.\n let prevEnvelope: { low: number; high: number } | null = null;\n // Set when this push is the loading line's hand-off to real data.\n let handOff = false;\n // Set for an animated live update: whether the last bar reshapes, and\n // the window an appended bar should pull the view to.\n let stream: { morph: boolean; window: VisibleRange | null } | null = null;\n if (transitionKind === 'stream' && prev != null && !explicit) {\n const { stream: mode, streamMs, reduceMotion } = animRef.current;\n const stepMs = inferStepMs(candles);\n if (\n mode === 'transform' &&\n streamMs > 0 &&\n stepMs != null &&\n !reduceMotion &&\n onFrameRef.current != null\n ) {\n const lastMs = candles[candles.length - 1].timeMs;\n const prevLastMs = prev.candles[prev.candles.length - 1].timeMs;\n if (classifyStream(prev.candles, candles) === 'append') {\n // Pull the window along by exactly what the data advanced, so the\n // series translates a whole slot and the newest bar holds its\n // place on screen. Only for a view still following the newest bar\n // — someone reading history keeps their window.\n //\n // No capture here: slots pair from the right edge, so the new bar\n // would take the previous one's geometry and drag every candle\n // onto its neighbour. Translating the window moves them by their\n // own timestamps instead.\n const w = h.getVisibleRange();\n const prevStepMs = inferStepMs(prev.candles) ?? stepMs;\n if (isPinnedToLatest(w, prevLastMs, prevStepMs)) {\n const by = lastMs - prevLastMs;\n stream = {\n morph: false,\n window: { startMs: w.startMs + by, endMs: w.endMs + by },\n };\n }\n } else {\n stream = { morph: true, window: null };\n }\n }\n if (stream?.morph) {\n // Keep the geometry on screen for beginStreamMorph to resume from:\n // at any real tick rate most ticks interrupt the previous one, and\n // that continuity is what keeps the bar from stuttering.\n settleStream(true);\n h.beginStreamMorph();\n } else {\n // An append has no use for a capture — it would pair the new bar\n // with the old one's geometry and drag the whole series along.\n settleStream();\n }\n } else if (transitionKind === 'stream') {\n settleStream();\n }\n if (transitionKind === 'timeframe' && prev != null) {\n const oldWindow = h.getVisibleRange();\n const oldStepMs = inferStepMs(prev.candles);\n if (oldWindow.endMs > oldWindow.startMs && oldStepMs != null) {\n tfArgs = {\n oldWindow,\n oldStepMs,\n oldLastMs: prev.candles[prev.candles.length - 1].timeMs,\n };\n }\n prevEnvelope = h.getVisiblePriceEnvelope();\n // Capture the outgoing candle geometry, but only when it will actually\n // be animated so a disabled animation costs no snapshot. A switch\n // during a morph restarts from the data the core currently holds.\n morphing =\n animRef.current.ms > 0 &&\n !animRef.current.reduceMotion &&\n onFrameRef.current != null;\n if (morphing) {\n endIntervalMorph();\n h.beginIntervalMorph(animRef.current.interval);\n }\n } else if (transitionKind === 'initial' || transitionKind === 'reset') {\n // Wholesale reframing — the slot pairing no longer holds, so land any\n // in-flight morph rather than reshaping into unrelated data.\n endIntervalMorph();\n }\n\n // The loading line's data has landed, so it hands over to the series\n // instead of the chart cutting to it. Always classified 'initial' (the\n // loading branch above cleared prevDataRef), so this runs after that\n // branch's endIntervalMorph.\n if (lineUpRef.current) {\n lineUpRef.current = false;\n handOff =\n animRef.current.ms > 0 &&\n !animRef.current.reduceMotion &&\n onFrameRef.current != null;\n // Snap path only. The hand-off itself starts after setCandles and the\n // framing below: both its stages aim at where the candles will\n // actually sit, so neither can be set up until they're there.\n if (!handOff) h.setLoading(false, true);\n }\n\n h.setCandles(packCandles(candles));\n\n if (transitionKind === 'timeframe') {\n const newStepMs = inferStepMs(candles);\n if (tfArgs && newStepMs != null) {\n const w = timeframeWindow(\n tfArgs.oldWindow,\n tfArgs.oldStepMs,\n tfArgs.oldLastMs,\n newStepMs,\n candles[candles.length - 1].timeMs,\n );\n h.setVisibleRange(w.startMs, w.endMs);\n }\n // Scale-lock the y-axis: the same price action re-buckets into a\n // smaller/larger high-low span, so a manual price range is rescaled to\n // keep the candle envelope at the pixel height it just had instead of\n // snapping back to auto-fit. A no-op in auto-y mode, which is already\n // span-invariant.\n if (prevEnvelope) h.preservePriceEnvelope(prevEnvelope.low, prevEnvelope.high);\n else h.resetPriceScale();\n // Started after the new bounds are in place: the snapshot is in band\n // fractions, so frame 0 still matches the pre-switch pixels exactly.\n if (morphing) startIntervalMorph(h);\n } else if (stream) {\n // After setCandles, so the capture (and the window it slides from) is\n // measured against the data the animation is heading toward.\n startStreamAnim(h, stream.morph, stream.window);\n } else if (transitionKind === 'reset') {\n h.resetView();\n }\n // After setCandles and the framing above, so the line aims at — and the\n // candles grow from — the geometry each bar will actually occupy.\n if (handOff) startLoadingHandoff(h);\n prevDataRef.current = { handle: h, candles, seriesKey };\n }\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 // The tip dot stays, only its animation drops — the same bargain the\n // candle↔line morph strikes when it keeps the crossfade but skips the\n // collapse. Also stops the pulse from pinning a RAF loop for a user who\n // asked for less motion.\n if (animRef.current.reduceMotion) {\n h.setFloat(FLOAT_LINE_TIP_PULSE, 0);\n }\n h.setRSI(rsiToSpec(rsi));\n h.setMACD(macdToSpec(macd));\n h.setATR(atrToSpec(atr));\n h.setOverlays((movingAverages ?? []).map(overlayToNumeric));\n h.setVWAP(vwapToSpec(vwap));\n h.setBollinger(bollingerToSpec(bollingerBands));\n h.setFairValueGaps(fvgToSpec(fairValueGaps));\n h.setVolume(volumeToSpec(volume));\n // setVolume snaps the collapse scalar to its `enabled`, which would cut a\n // toggle animation short whenever this effect re-runs (a streaming candle, a\n // resize). Hand the in-flight value back; VroomChart's loop drives it from\n // there.\n const collapse = volumeCollapseRef.current;\n if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);\n h.setPriceLines(\n priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES,\n );\n h.setFootprints(\n footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS,\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 // A just-started morph is already pushing frames straight to the host sink;\n // this snapshot would land on top of them a frame or two later. The morph's\n // frame 0 is pixel-identical to what's on screen, so there's nothing to show\n // in the meantime anyway.\n if (!morphing) setPicture(h.render());\n // theme/rsi/macd/atr/movingAverages/vwap/bollingerBands/ichimoku/\n // fairValueGaps/volume/priceLines/footprints are represented by their *Key\n // deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [candles, showLoadingLine, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, atrKey, maKey, vwapKey, bollingerKey, ichimokuKey, fvgKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, startLoadingHandoff, endIntervalMorph, startStreamAnim, settleStream]);\n\n return { handle: handleRef.current, picture, volumeCollapseRef };\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","// Mirror of packages/react/src/dataTransitions.ts — the platform packages don't\n// depend on each other, and @vroomchart/types carries types only.\n//\n// Classifies how a new `candles` prop relates to the previous one so the chart\n// can react appropriately: leave the viewport alone for streaming updates,\n// re-anchor the time window for a timeframe switch, or fully reset the view\n// for a different asset. Pure functions, no React — see useChartCore for the\n// orchestration.\n\nimport type { Candle, VisibleRange } from '@vroomchart/types';\n\n/**\n * How a new `candles` array relates to the one the chart already holds:\n * `'initial'` is the first data, `'stream'` a live update to the same series,\n * `'timeframe'` the same asset re-bucketed into a different interval, and\n * `'reset'` a different series entirely.\n */\nexport type DataTransition = 'initial' | 'stream' | 'timeframe' | 'reset';\n\n// A step change below this ratio is treated as the same timeframe. Real steps\n// are exact integer ms; the tolerance only absorbs rounding/DST quirks (the\n// smallest real timeframe jump, 1m -> 2m, is 100% apart).\nconst STEP_TOLERANCE = 0.01;\n\n// Same-asset check for a timeframe switch: both series end \"now\", so their\n// last closes must be close. No asset moves 25% between two consecutive prop\n// pushes; distinct assets within 25% of each other are what `seriesKey` is for.\nconst MAX_SAME_ASSET_CLOSE_RATIO = 1.25;\n\n// A coarser bucketing can shift the final bar's open by up to one coarse bar;\n// allow that plus an in-flight bar when checking the two series end together.\nconst MAX_END_DRIFT_STEPS = 3;\n\n// Streaming pushes may batch a few bars (e.g. a throttled background tab), but\n// a jump of more than this many steps means the data was re-fetched elsewhere.\nconst MAX_STREAM_ADVANCE_STEPS = 5;\n\n/**\n * The candle period in ms, inferred as the median of the first few intervals\n * (robust to a single gap). Null when there are fewer than two candles.\n */\nexport function inferStepMs(candles: Candle[]): number | null {\n if (candles.length < 2) return null;\n const k = Math.min(candles.length - 1, 8);\n const diffs: number[] = [];\n for (let i = 0; i < k; i++) diffs.push(candles[i + 1].timeMs - candles[i].timeMs);\n diffs.sort((a, b) => a - b);\n const median = diffs[Math.floor(diffs.length / 2)];\n return median > 0 ? median : null;\n}\n\n// Index of the candle whose timeMs exactly equals `t`, or -1. Binary search over\n// the ascending-by-time series, so it tolerates interior gaps (missing bars from\n// downtime / illiquid periods) — unlike a uniform-grid index computed from the\n// step, which assumes a hole-free grid.\nfunction indexByTime(candles: Candle[], t: number): number {\n let lo = 0;\n let hi = candles.length - 1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n const v = candles[mid].timeMs;\n if (v === t) return mid;\n if (v < t) lo = mid + 1;\n else hi = mid - 1;\n }\n return -1;\n}\n\n/**\n * Classify a candles-prop change. `prev` is the previously rendered array\n * (null on first render); `seriesKeyChanged` forces `reset` regardless of the\n * data (the explicit escape hatch).\n *\n * Constraint: detection compares two immutable snapshots. An array mutated in\n * place (same reference) never reaches this code — React props must change\n * identity to re-render.\n */\nexport function classifyTransition(\n prev: Candle[] | null,\n next: Candle[],\n seriesKeyChanged: boolean,\n): DataTransition {\n if (!prev || prev.length === 0) return 'initial';\n if (next.length === 0) return 'stream'; // nothing to reframe against\n if (seriesKeyChanged) return 'reset';\n\n const prevStep = inferStepMs(prev);\n const nextStep = inferStepMs(next);\n if (prevStep == null || nextStep == null) return 'reset'; // too little data to reason\n\n const prevLast = prev[prev.length - 1];\n const nextLast = next[next.length - 1];\n\n if (Math.abs(nextStep - prevStep) <= prevStep * STEP_TOLERANCE) {\n // Same step: streaming iff prev's last bar still appears in next (covers\n // append, update-last, and rolling buffers that drop old bars from the\n // front) and the series only advanced by a few bars. Locate that bar by\n // timestamp, not by a step-derived index — real series have interior gaps\n // (downtime / illiquid periods), so a uniform-grid index would miss it and\n // misread a harmless in-place tick as a reset.\n // Time alignment alone isn't enough: two assets on the same exchange share\n // the bar grid, so the bar at the shared timestamp must also be (nearly) the\n // same bar — update-last moves the close, but never by the same-asset ratio.\n const idx = indexByTime(next, prevLast.timeMs);\n const aligned = idx >= 0;\n const sharedBarRatio =\n aligned && next[idx].close > 0 && prevLast.close > 0\n ? Math.max(next[idx].close / prevLast.close, prevLast.close / next[idx].close)\n : Infinity;\n const advanced =\n nextLast.timeMs >= prevLast.timeMs &&\n nextLast.timeMs - prevLast.timeMs <= MAX_STREAM_ADVANCE_STEPS * nextStep;\n return sharedBarRatio <= MAX_SAME_ASSET_CLOSE_RATIO && advanced ? 'stream' : 'reset';\n }\n\n // Step changed: a timeframe switch iff it still looks like the same asset —\n // last closes near each other and both series ending around the same time.\n const closeRatio =\n prevLast.close > 0 && nextLast.close > 0\n ? Math.max(nextLast.close / prevLast.close, prevLast.close / nextLast.close)\n : Infinity;\n const prevEnd = prevLast.timeMs + prevStep;\n const nextEnd = nextLast.timeMs + nextStep;\n const endsTogether =\n Math.abs(nextEnd - prevEnd) <= MAX_END_DRIFT_STEPS * Math.max(prevStep, nextStep);\n return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? 'timeframe' : 'reset';\n}\n\n/**\n * What a `'stream'` update did to the series: `'tick'` revised the bar already\n * on screen, `'append'` brought at least one new one.\n *\n * The two animate by different means. A tick keeps the bar count, so the morph\n * capture's slots still pair one-to-one and the last bar can reshape in place.\n * An append can't use that capture at all — slots pair from the right edge, so\n * a new bar shifts every candle onto its neighbour's geometry — and instead\n * advances the visible window, which translates the series left and lets the\n * new bar in at the right edge.\n */\nexport type StreamKind = 'tick' | 'append';\n\n/**\n * Which of the two a `'stream'` transition is. Read from the newest timestamp\n * rather than a length comparison, so a rolling buffer that drops a bar from\n * the front as it adds one to the back still reads as an append.\n *\n * An update that both appends and revises the bar that just closed counts as an\n * append: the translation is the dominant motion, and the revision is a final\n * print that has nowhere to slot-pair to.\n */\nexport function classifyStream(prev: Candle[], next: Candle[]): StreamKind {\n if (prev.length === 0 || next.length === 0) return 'tick';\n return next[next.length - 1].timeMs > prev[prev.length - 1].timeMs\n ? 'append'\n : 'tick';\n}\n\n/**\n * Whether the view is still following the newest bar, which is what decides if\n * an appended bar should pull the window along with it.\n *\n * True when the right edge sits at or past the newest bar's slot *end* — where\n * the default framing leaves it, plus whatever gap it reserved. Someone who has\n * panned back into history falls below that and is left where they are: nothing\n * is more disorienting than the chart walking out from under you while you read\n * it.\n */\nexport function isPinnedToLatest(\n window: VisibleRange,\n lastMs: number,\n stepMs: number,\n): boolean {\n return window.endMs >= lastMs + stepMs;\n}\n\n/**\n * The visible window to apply after a timeframe switch so each candle keeps\n * the exact pixel width it had before: the visible slot count is preserved and\n * the right edge re-anchors on the newest candle (any future-gap overshoot is\n * carried over in slots, clamped to the core's 3/4-window cap). The new start\n * may precede the first candle — that gap is intentional, width wins.\n */\nexport function timeframeWindow(\n oldWindow: VisibleRange,\n oldStepMs: number,\n oldLastMs: number,\n newStepMs: number,\n newLastMs: number,\n): VisibleRange {\n const slots = (oldWindow.endMs - oldWindow.startMs) / oldStepMs;\n const offsetRaw = (oldWindow.endMs - oldLastMs) / oldStepMs;\n const offsetSlots = Math.min(Math.max(offsetRaw, 0), slots * 0.75);\n const endMs = Math.round(newLastMs + offsetSlots * newStepMs);\n return { startMs: Math.round(endMs - slots * newStepMs), endMs };\n}\n","// Mirror of packages/react/src/easing.ts — the platform packages don't depend on\n// each other, and @vroomchart/types carries types only.\n\nimport type { TransitionEasing } from '@vroomchart/types';\n\n/**\n * Maps linear animation progress (0..1) to eased progress (0..1) for the chart's\n * transitions. Unknown values fall back to `'ease-in-out'`, which is a\n * smoothstep — the curve the candle↔line transition has always used.\n */\nexport function ease(kind: TransitionEasing | undefined, p: number): number {\n switch (kind) {\n case 'linear':\n return p;\n case 'ease-in':\n return p * p;\n case 'ease-out':\n return p * (2 - p);\n default:\n return p * p * (3 - 2 * p);\n }\n}\n\n// Index order matches VroomEasing in vroom_chart.h.\nconst EASINGS: readonly TransitionEasing[] = [\n 'linear',\n 'ease-in',\n 'ease-out',\n 'ease-in-out',\n];\n\n/**\n * The curve as a `VroomEasing` index, for the animations the core paces itself\n * (see `setVolumeCollapse`) rather than taking pre-eased progress. Falls back to\n * `'ease-in-out'`, matching {@link ease}.\n */\nexport function easingIndex(kind: TransitionEasing | undefined): number {\n const i = kind ? EASINGS.indexOf(kind) : -1;\n return i < 0 ? EASINGS.indexOf('ease-in-out') : i;\n}\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 badgeText: 8, // VROOM_COLOR_BADGE_TEXT (7 is a retired slot)\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 skeleton: 17, // VROOM_COLOR_SKELETON\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 lineGradientOpacity: 12, // VROOM_FLOAT_LINE_GRADIENT_OPACITY\n lineTension: 13, // VROOM_FLOAT_LINE_TENSION\n};\n\n// Named because useChartCore clears it directly under reduced motion, outside\n// the theme sweep below.\nexport const FLOAT_LINE_TIP_PULSE = 15; // VROOM_FLOAT_LINE_TIP_PULSE\n\n// Maps each boolean VroomTheme field to its VroomFloatKey index (pushed as 0/1).\n//\n// `showXAxis` / `showYAxis` are absent on purpose: they animate, so VroomChart\n// drives them as a collapse scalar through setAxisCollapse. A float slot here\n// would let this sweep snap them behind the animation's back.\nexport const BOOL_KEYS: Partial<Record<keyof VroomTheme, number>> = {\n wickRoundCap: 9, // VROOM_FLOAT_WICK_ROUND_CAP\n lineTipDot: 14, // VROOM_FLOAT_LINE_TIP_DOT\n lineTipPulse: FLOAT_LINE_TIP_PULSE,\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","FOOTPRINT_SELL","React"]}
|