import { StyleSheet, View, type LayoutChangeEvent } from "react-native"; import Animated, { useAnimatedStyle, useSharedValue, type SharedValue, } from "react-native-reanimated"; import type { ChartEngineLayout } from "../core/useLiveChartEngine"; import type { ChartPadding } from "../draw/line"; /** Inset from the anchored edge, matching the built-in threshold badge. */ const EDGE_INSET = 2; /** * React Native overlay for a custom threshold badge. The chart owns the * position, measuring the returned element and pinning its vertical center to * the live threshold Y on the UI thread. The consumer owns only its contents and * chrome. */ export function CustomThresholdBadgeOverlay({ element, engine, padding, y, visible, position, leftAnchorX, }: { element: React.ReactElement; engine: ChartEngineLayout; padding: ChartPadding; y: SharedValue; visible: SharedValue; position: "left" | "right"; /** Optional first rendered X for a non-extended series badge. */ leftAnchorX?: SharedValue; }) { const size = useSharedValue({ width: 0, height: 0 }); const onLayout = (event: LayoutChangeEvent) => { const { width, height } = event.nativeEvent.layout; size.set({ width, height }); }; const animatedStyle = useAnimatedStyle(() => { const canvasWidth = engine.canvasWidth.get(); const yy = y.get(); const measured = size.get(); const show = visible.get() && canvasWidth > 0 && Number.isFinite(yy); let translateX = EDGE_INSET; if (position === "right") { translateX = canvasWidth - padding.right - EDGE_INSET - measured.width; } else if (leftAnchorX) { const minX = padding.left + EDGE_INSET; const maxX = canvasWidth - padding.right - EDGE_INSET - measured.width; translateX = Math.max(minX, Math.min(leftAnchorX.get(), maxX)); } return { opacity: show ? 1 : 0, transform: [ { translateX }, { translateY: yy - measured.height / 2 }, ], }; }); return ( {element} ); } const styles = StyleSheet.create({ anchor: { position: "absolute", top: 0, left: 0 }, });