UNPKG

kinetic-slider

Version:

A WebGL-powered kinetic slider component using PIXI.js

246 lines (243 loc) 8.96 kB
import { useRef, useCallback, useEffect } from 'react'; // Development environment check const isDevelopment = "production" === 'development'; /** * Hook to set up navigation controls for the slider * Fully optimized with: * - Batch event listener registration * - Comprehensive error handling * - Memory leak prevention * - Stable handler references * - Strong cancellation mechanisms * - Server-side rendering safety */ const useNavigation = ({ onNext, onPrev, enableKeyboardNav = true, resourceManager }) => { // Track component mount state const isMountedRef = useRef(true); // Track batch operations const batchOperationsRef = useRef({ pendingListeners: new Map(), processedCount: 0 }); // Define stable handler interface with ref const handlersRef = useRef({ keyDownHandler: (event) => { try { if (!isMountedRef.current || !enableKeyboardNav) return; const keyEvent = event; const handlers = handlersRef.current; // Handle keyboard navigation switch (keyEvent.key) { case 'ArrowLeft': if (handlers.latestPrevFn) { handlers.latestPrevFn(); } break; case 'ArrowRight': if (handlers.latestNextFn) { handlers.latestNextFn(); } break; } } catch (error) { } }, slideChangeHandler: (event) => { try { if (!isMountedRef.current) return; const customEvent = event; const handlers = handlersRef.current; // Handle custom slide change events if (customEvent.detail && typeof customEvent.detail.nextIndex === 'number') { if (handlers.latestNextFn) { handlers.latestNextFn(); } } } catch (error) { } }, latestNextFn: null, latestPrevFn: null }); /** * Process any pending event listeners in batch */ const processPendingListeners = useCallback(() => { try { const { pendingListeners } = batchOperationsRef.current; // Skip if no ResourceManager or no pending listeners if (!resourceManager || pendingListeners.size === 0) return; let totalProcessed = 0; // Process each event target pendingListeners.forEach((listenerMap, target) => { try { resourceManager.addEventListenerBatch(target, listenerMap); totalProcessed += Array.from(listenerMap.values()) .reduce((sum, callbacks) => sum + callbacks.length, 0); } catch (error) { if (isDevelopment) ; } }); // Clear the pending map pendingListeners.clear(); // Update processed count batchOperationsRef.current.processedCount += totalProcessed; if (isDevelopment) ; } catch (error) { // Clear pending listeners even on error batchOperationsRef.current.pendingListeners.clear(); } }, [resourceManager]); /** * Add a listener to the pending batch */ const addListenerToBatch = useCallback((target, eventType, callback) => { try { const { pendingListeners } = batchOperationsRef.current; // Get or create map for this target if (!pendingListeners.has(target)) { pendingListeners.set(target, new Map()); } const targetMap = pendingListeners.get(target); // Get or create array for this event type if (!targetMap.has(eventType)) { targetMap.set(eventType, []); } // Add callback to the list targetMap.get(eventType).push(callback); return true; } catch (error) { return false; } }, []); // Keep the latest function references updated useEffect(() => { handlersRef.current.latestNextFn = onNext; handlersRef.current.latestPrevFn = onPrev; }, [onNext, onPrev]); // Set up keyboard navigation useEffect(() => { // Skip during server-side rendering if (typeof window === 'undefined') return; // Skip if keyboard navigation is disabled if (!enableKeyboardNav) return; // Reset mounted state isMountedRef.current = true; try { const { keyDownHandler } = handlersRef.current; // Start performance timer if in development const startTime = isDevelopment ? performance.now() : 0; // Register event listener with batch processing if ResourceManager available if (resourceManager) { // Add to batch operations addListenerToBatch(window, 'keydown', keyDownHandler); // Process in batch processPendingListeners(); } else { // Direct registration window.addEventListener('keydown', keyDownHandler); } // Log performance if in development if (isDevelopment && startTime > 0) ; // Cleanup on unmount return () => { // Update mounted state immediately isMountedRef.current = false; try { // ResourceManager handles its own cleanup if (!resourceManager) { window.removeEventListener('keydown', keyDownHandler); } } catch (cleanupError) { if (isDevelopment) ; } }; } catch (error) { // Return empty cleanup function return () => { }; } }, [enableKeyboardNav, resourceManager, addListenerToBatch, processPendingListeners]); // Listen for custom slide change events useEffect(() => { // Skip during server-side rendering if (typeof window === 'undefined') return; try { const { slideChangeHandler } = handlersRef.current; // Start performance timer if in development const startTime = isDevelopment ? performance.now() : 0; // Register with batch processing if ResourceManager is available if (resourceManager) { // Add to batch operations addListenerToBatch(window, 'slideChange', slideChangeHandler); // Process in batch processPendingListeners(); } else { // Direct registration window.addEventListener('slideChange', slideChangeHandler); } // Log performance if in development if (isDevelopment && startTime > 0) ; // Cleanup on unmount return () => { try { // ResourceManager handles its own cleanup if (!resourceManager) { window.removeEventListener('slideChange', slideChangeHandler); } } catch (cleanupError) { if (isDevelopment) ; } }; } catch (error) { // Return empty cleanup function return () => { }; } }, [resourceManager, addListenerToBatch, processPendingListeners]); // Expose memoized navigation methods const goNext = useCallback(() => { try { if (isMountedRef.current && handlersRef.current.latestNextFn) { const startTime = isDevelopment ? performance.now() : 0; handlersRef.current.latestNextFn(); if (isDevelopment && startTime > 0) ; } } catch (error) { } }, []); const goPrev = useCallback(() => { try { if (isMountedRef.current && handlersRef.current.latestPrevFn) { const startTime = isDevelopment ? performance.now() : 0; handlersRef.current.latestPrevFn(); if (isDevelopment && startTime > 0) ; } } catch (error) { } }, []); return { goNext, goPrev, isKeyboardEnabled: enableKeyboardNav }; }; export { useNavigation as default }; //# sourceMappingURL=useNavigation.js.map