import { useCallback, useEffect, useRef, useState } from 'react'; import { Animated, Dimensions, Easing, View, type LayoutChangeEvent, } from 'react-native'; import Svg, { Path } from 'react-native-svg'; import type { MaskProps, SvgMaskPathFunction, ValueXY } from '../types'; const AnimatedSvgPath = Animated.createAnimatedComponent(Path); const windowDimensions = Dimensions.get('window'); const defaultSvgPath: SvgMaskPathFunction = ({ size, position, canvasSize, }): string => { const positionX = (position.x as any)._value as number; const positionY = (position.y as any)._value as number; const sizeX = (size.x as any)._value as number; const sizeY = (size.y as any)._value as number; return `M0,0H${canvasSize.x}V${canvasSize.y}H0V0ZM${positionX},${positionY}H${ positionX + sizeX }V${positionY + sizeY}H${positionX}V${positionY}Z`; }; export const SvgMask = ({ size, position, style, easing = Easing.linear, animationDuration = 300, animated, backdropColor, svgMaskPath = defaultSvgPath, onClick, currentStep, }: MaskProps) => { const [canvasSize, setCanvasSize] = useState({ x: windowDimensions.width, y: windowDimensions.height, }); const sizeValue = useRef( new Animated.ValueXY(size) ).current; const positionValue = useRef( new Animated.ValueXY(position) ).current; const maskRef = useRef(null); const animationListener = useCallback(() => { const d: string = svgMaskPath({ size: sizeValue, position: positionValue, canvasSize, step: currentStep, }); if (maskRef.current) { maskRef.current.setNativeProps({ d }); } }, [canvasSize, currentStep, svgMaskPath, positionValue, sizeValue]); const animate = useCallback( (toSize: ValueXY = size, toPosition: ValueXY = position) => { if (animated) { Animated.parallel([ Animated.timing(sizeValue, { toValue: toSize, duration: animationDuration, easing, useNativeDriver: false, }), Animated.timing(positionValue, { toValue: toPosition, duration: animationDuration, easing, useNativeDriver: false, }), ]).start(); } else { sizeValue.setValue(toSize); positionValue.setValue(toPosition); } }, [ animated, animationDuration, easing, positionValue, position, size, sizeValue, ] ); useEffect(() => { const id = positionValue.addListener(animationListener); return () => { positionValue.removeListener(id); }; }, [animationListener, positionValue]); useEffect(() => { if (size && position) { animate(size, position); } }, [animate, position, size]); const handleLayout = ({ nativeEvent: { layout: { width, height }, }, }: LayoutChangeEvent) => { setCanvasSize({ x: width, y: height, }); }; return ( {canvasSize ? ( ) : null} ); };