UNPKG

react-native-animated-glow

Version:

A performant, highly-customizable animated glow effect for React Native, powered by Skia and Reanimated.

175 lines (174 loc) 8.97 kB
"use strict"; // src/AnimatedGlow.tsx var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const react_1 = __importStar(require("react")); const react_native_1 = require("react-native"); const react_native_reanimated_1 = require("react-native-reanimated"); const react_native_gesture_handler_1 = require("react-native-gesture-handler"); const LazyUnifiedSkiaGlow_1 = require("./animated-glow/LazyUnifiedSkiaGlow"); const SkiaWebLoader_1 = require("./animated-glow/SkiaWebLoader"); const isObject = (item) => (item && typeof item === 'object' && !Array.isArray(item)); const mergeDeep = (target, source) => { const output = { ...target }; if (isObject(target) && isObject(source)) { Object.keys(source).forEach(key => { if (key === 'glowLayers' && Array.isArray(source[key]) && Array.isArray(target[key])) { const mergedLayers = target[key].map((layer, index) => source[key][index] ? { ...layer, ...source[key][index] } : layer); if (source[key].length > target[key].length) { mergedLayers.push(...source[key].slice(target[key].length)); } output[key] = mergedLayers; } else if (isObject(source[key]) && key in target && isObject(target[key])) { output[key] = mergeDeep(target[key], source[key]); } else { output[key] = source[key]; } }); } return output; }; const AnimatedGlow = (props) => { const { preset = {}, states: statesProp, initialState = 'default', children, style, isVisible = true, ...overrideProps } = props; const [isSkiaReady, setIsSkiaReady] = (0, react_1.useState)(SkiaWebLoader_1.skiaWebState.status === 'ready'); const [layout, setLayout] = (0, react_1.useState)({ width: 0, height: 0 }); const [hasLaidOut, setHasLaidOut] = (0, react_1.useState)(false); const [activeState, setActiveState] = (0, react_1.useState)(initialState); const prevActiveState = (0, react_1.useRef)(initialState); const skiaOpacity = (0, react_native_reanimated_1.useSharedValue)(0); const animationProgress = (0, react_native_reanimated_1.useSharedValue)(0); const fromConfigSV = (0, react_native_reanimated_1.useSharedValue)({}); const toConfigSV = (0, react_native_reanimated_1.useSharedValue)({}); (0, react_1.useEffect)(() => { if (SkiaWebLoader_1.skiaWebState.status === 'ready') { if (!isSkiaReady) setIsSkiaReady(true); return; } const onReady = () => setIsSkiaReady(true); SkiaWebLoader_1.skiaWebState.subscribers.add(onReady); (0, SkiaWebLoader_1.ensureSkiaWebLoaded)(); return () => { SkiaWebLoader_1.skiaWebState.subscribers.delete(onReady); }; }, [isSkiaReady]); const states = (0, react_1.useMemo)(() => statesProp || preset.states || [], [statesProp, preset.states]); const targetConfig = (0, react_1.useMemo)(() => { const allStates = statesProp || preset.states || []; const defaultState = allStates.find(s => s.name === 'default')?.preset || {}; const legacyBase = { ...preset, ...overrideProps }; delete legacyBase.metadata; delete legacyBase.states; const baseConfig = mergeDeep(defaultState, legacyBase); const stateOverride = allStates.find(s => s.name === activeState)?.preset || {}; return mergeDeep(baseConfig, stateOverride); }, [preset, overrideProps, statesProp, activeState]); (0, react_1.useEffect)(() => { if (animationProgress.value === 0 && !fromConfigSV.value.cornerRadius) { fromConfigSV.value = targetConfig; toConfigSV.value = targetConfig; animationProgress.value = 1; prevActiveState.current = activeState; return; } let transition = states.find(s => s.name === activeState)?.transition ?? 0; if (activeState === 'default' && prevActiveState.current !== 'default') { transition = states.find(s => s.name === prevActiveState.current)?.transition ?? transition; } fromConfigSV.value = toConfigSV.value; toConfigSV.value = targetConfig; if (transition > 0) { animationProgress.value = 0; animationProgress.value = (0, react_native_reanimated_1.withTiming)(1, { duration: transition, easing: react_native_reanimated_1.Easing.out(react_native_reanimated_1.Easing.quad) }); } else { animationProgress.value = 1; } prevActiveState.current = activeState; }, [targetConfig, activeState, states]); const pressGesture = react_native_gesture_handler_1.Gesture.LongPress() .minDuration(1) .onStart(() => { if (states.some(s => s.name === 'press')) { setActiveState('press'); } }) .onEnd(() => { setActiveState(current => (current === 'press' ? initialState : current)); }); const hoverGesture = react_native_gesture_handler_1.Gesture.Hover() .onStart(() => { if (states.some(s => s.name === 'hover')) { setActiveState('hover'); } }) .onEnd(() => { setActiveState(current => (current === 'hover' ? initialState : current)); }); const gesture = react_native_gesture_handler_1.Gesture.Race(react_native_1.Platform.OS === 'web' ? hoverGesture : react_native_gesture_handler_1.Gesture.Manual(), pressGesture); const { cornerRadius = 10, outlineWidth = 2, borderColor = 'white', backgroundColor } = targetConfig; const hasAnimatedBorder = Array.isArray(borderColor) && borderColor.length > 1; const hasGlowLayers = (targetConfig.glowLayers?.length ?? 0) > 0; const useSkiaRenderer = (0, react_1.useMemo)(() => hasGlowLayers || hasAnimatedBorder, [hasGlowLayers, hasAnimatedBorder]); const wrapperStyle = (0, react_1.useMemo)(() => ({ backgroundColor: useSkiaRenderer ? 'transparent' : backgroundColor, borderWidth: useSkiaRenderer ? 0 : outlineWidth, borderColor: useSkiaRenderer ? 'transparent' : (Array.isArray(borderColor) ? borderColor[0] : borderColor), borderRadius: cornerRadius, overflow: 'hidden', }), [useSkiaRenderer, outlineWidth, borderColor, cornerRadius, backgroundColor]); const shouldRenderSkia = useSkiaRenderer && hasLaidOut && isVisible && isSkiaReady; (0, react_1.useEffect)(() => { skiaOpacity.value = (0, react_native_reanimated_1.withTiming)(shouldRenderSkia ? 1 : 0, { duration: 300 }); }, [shouldRenderSkia]); return (<react_native_gesture_handler_1.GestureDetector gesture={gesture}> <react_native_1.View style={[style]} onLayout={(e) => { const l = e.nativeEvent.layout; if (l.width !== layout.width || l.height !== layout.height) { setLayout({ width: l.width, height: l.height }); } if (!hasLaidOut) setHasLaidOut(true); }}> {shouldRenderSkia && (<react_1.Suspense fallback={null}> <LazyUnifiedSkiaGlow_1.LazyUnifiedSkiaGlow layout={layout} masterOpacity={skiaOpacity} progress={animationProgress} fromConfig={fromConfigSV} toConfig={toConfigSV}/> </react_1.Suspense>)} <react_native_1.View style={wrapperStyle}> {children} </react_native_1.View> </react_native_1.View> </react_native_gesture_handler_1.GestureDetector>); }; exports.default = AnimatedGlow;