UNPKG

kinetic-slider

Version:

A WebGL-powered kinetic slider component using PIXI.js

842 lines (839 loc) 39.9 kB
import { useState, useRef, useCallback, useEffect } from 'react'; import { Container, Sprite, Assets } from 'pixi.js'; import { calculateSpriteScale } from '../utils/calculateSpriteScale.js'; import { gsap } from 'gsap'; import { AnimationCoordinator, AnimationGroupType } from '../managers/AnimationCoordinator.js'; // Development environment check const isDevelopment = "production" === 'development'; /** * Hook to create and manage slide sprites with atlas support */ const useSlides = ({ sliderRef, pixi, props, resourceManager, atlasManager, onSlideChange, slidingWindowManager }) => { // Debug logging of props console.log("useSlides received useSlidesAtlas:", props.useSlidesAtlas); console.log("useSlides received props:", props); // Track loading state const [isLoading, setIsLoading] = useState(false); const [loadingProgress, setLoadingProgress] = useState(0); // Ref to store active transitions const activeTransitionRef = useRef(null); // Get the animation coordinator const animationCoordinator = AnimationCoordinator.getInstance(); // Get the slidesBasePath from props, defaulting to '/images/' if not provided props.slidesBasePath || '/images/'; // Normalize path for atlas frame lookup const normalizePath = (imagePath) => { // For paths that start with a slash, remove it for atlas lookup if (imagePath.startsWith('/')) { return imagePath.substring(1); } return imagePath; }; // Helper to check if useSlidesAtlas is enabled (handling different possible values) const isUseSlidesAtlasEnabled = () => { // Handle all possible representations of "true" if (props.useSlidesAtlas === true) return true; if (typeof props.useSlidesAtlas === 'string' && props.useSlidesAtlas === 'true') return true; // Handle numeric representations (needs type checking) if (typeof props.useSlidesAtlas === 'number' && props.useSlidesAtlas === 1) return true; if (typeof props.useSlidesAtlas === 'string' && props.useSlidesAtlas === '1') return true; // Default to false for all other cases return false; }; // Check if assets are available in atlas const areAssetsInAtlas = useCallback(() => { // First check if atlasManager and slidesAtlas are available if (!atlasManager || !props.slidesAtlas) { return false; } // Check if useSlidesAtlas is enabled const useSlidesAtlasEnabled = isUseSlidesAtlasEnabled(); if (!useSlidesAtlasEnabled) { return false; } const result = props.images.every(imagePath => { const normalizedPath = normalizePath(imagePath); const atlasId = atlasManager.hasFrame(normalizedPath); return !!atlasId; }); return result; }, [atlasManager, props.images, props.slidesAtlas, props.useSlidesAtlas]); // Effect to create slides from atlas or individual images useEffect(() => { if (!pixi.app.current || !pixi.app.current.stage) { return; } // Check if we have images to display if (!props.images.length) { return; } // Check if slider ref is available for dimensions if (!sliderRef.current) { return; } // Create a dedicated container for slides if it doesn't exist let slidesContainer; try { const app = pixi.app.current; if (app.stage.children.length > 0 && app.stage.children[0] instanceof Container) { slidesContainer = app.stage.children[0]; } else { slidesContainer = new Container(); slidesContainer.label = 'slidesContainer'; app.stage.addChild(slidesContainer); // Track container with resource manager if available if (resourceManager) { resourceManager.trackDisplayObject(slidesContainer); } } // Clear existing slides with proper cleanup pixi.slides.current.forEach(sprite => { if (sprite && sprite.parent) { try { sprite.parent.removeChild(sprite); } catch (error) { if (isDevelopment) ; } } }); pixi.slides.current = []; // Enhanced handling of the useAtlas decision const useSlidesAtlasEnabled = isUseSlidesAtlasEnabled(); const useAtlas = atlasManager && props.slidesAtlas && areAssetsInAtlas() && useSlidesAtlasEnabled; if (isDevelopment) ; if (useAtlas) { loadSlidesFromAtlas(slidesContainer); } else { loadSlidesFromIndividualImages(slidesContainer); } } catch (error) { setIsLoading(false); } }, [pixi.app.current, props.images, resourceManager, sliderRef, atlasManager, props.slidesAtlas, props.useSlidesAtlas]); /** * Load slides from texture atlas */ const loadSlidesFromAtlas = async (slidesContainer) => { if (!pixi.app.current || !sliderRef.current || !atlasManager) return; try { setIsLoading(true); setLoadingProgress(0); if (isDevelopment) ; const app = pixi.app.current; const sliderWidth = sliderRef.current.clientWidth; const sliderHeight = sliderRef.current.clientHeight; // Prepare for loading from atlas const totalImages = props.images.length; let loadedCount = 0; // Get visibility window indices if sliding window manager is available const visibilityWindowIndices = slidingWindowManager ? slidingWindowManager.getWindowIndices() : []; if (isDevelopment && slidingWindowManager) ; // Create sprites for each image using the atlas for (const [index, imagePath] of props.images.entries()) { try { // Check if this slide is in the visibility window const isInVisibilityWindow = !slidingWindowManager || visibilityWindowIndices.includes(index); if (isDevelopment && slidingWindowManager) ; if (isInVisibilityWindow) { // Fully load the slide if it's within the visibility window // Normalize path for atlas lookup const normalizedPath = normalizePath(imagePath); if (isDevelopment) ; // Get texture from atlas const texture = atlasManager.getFrameTexture(normalizedPath, props.slidesAtlas); if (!texture) { throw new Error(`Frame ${normalizedPath} not found in atlas ${props.slidesAtlas}`); } // Track texture with resource manager if available if (resourceManager) { resourceManager.trackTexture(imagePath, texture); } // Create the sprite with the texture from atlas const sprite = new Sprite(texture); sprite.anchor.set(0.5); sprite.x = app.screen.width / 2; sprite.y = app.screen.height / 2; // Set initial state - only show the first slide sprite.alpha = index === 0 ? 1 : 0; sprite.visible = index === 0; // Calculate and apply scale try { const { scale, baseScale } = calculateSpriteScale(texture.width, texture.height, sliderWidth, sliderHeight); sprite.scale.set(scale); sprite.baseScale = baseScale; } catch (scaleError) { if (isDevelopment) ; // Fallback scaling sprite.scale.set(1); sprite.baseScale = 1; } // Mark as being in the visibility window sprite._inVisibilityWindow = true; // Track the sprite with resource manager if available if (resourceManager) { resourceManager.trackDisplayObject(sprite); } // Add to container and store reference slidesContainer.addChild(sprite); pixi.slides.current.push(sprite); if (isDevelopment) ; } else { // Create a placeholder for slides outside the visibility window const placeholderOptions = { width: sliderWidth, height: sliderHeight, color: 0x333333, showIndex: isDevelopment, // Show index in development mode index, trackWithResourceManager: true, resourceManager, renderer: app.renderer }; // Import the createPlaceholderSprite function dynamically to avoid circular dependencies const { createPlaceholderSprite } = await import('../utils/placeholderUtils.js'); // Create the placeholder sprite const placeholderSprite = createPlaceholderSprite(placeholderOptions); // Position the placeholder at the center placeholderSprite.x = app.screen.width / 2; placeholderSprite.y = app.screen.height / 2; // Set initial state - only show the first slide placeholderSprite.alpha = index === 0 ? 1 : 0; placeholderSprite.visible = index === 0; // Set scale similar to real sprites placeholderSprite.scale.set(1); placeholderSprite.baseScale = 1; // Mark as placeholder and outside visibility window placeholderSprite._isPlaceholder = true; placeholderSprite._placeholderIndex = index; placeholderSprite._inVisibilityWindow = false; // Add to container and store reference slidesContainer.addChild(placeholderSprite); pixi.slides.current.push(placeholderSprite); if (isDevelopment) ; } // Update progress loadedCount++; const progress = (loadedCount / totalImages) * 100; setLoadingProgress(progress); } catch (error) { if (isDevelopment) ; // Fallback to individual image loading if atlas frame not found const texture = await Assets.load(imagePath); createSlideFromTexture(texture, imagePath, index, slidesContainer, app, sliderWidth, sliderHeight); // Update progress loadedCount++; setLoadingProgress((loadedCount / totalImages) * 100); } } setIsLoading(false); setLoadingProgress(100); if (isDevelopment) ; } catch (error) { // Fallback to individual image loading loadSlidesFromIndividualImages(slidesContainer); } }; /** * Load slides from individual images (fallback method) */ const loadSlidesFromIndividualImages = async (slidesContainer) => { if (!pixi.app.current || !sliderRef.current) return; try { setIsLoading(true); setLoadingProgress(0); if (isDevelopment) ; // Prepare the list of images to load const app = pixi.app.current; const sliderWidth = sliderRef.current.clientWidth; const sliderHeight = sliderRef.current.clientHeight; if (isDevelopment) ; // Get visibility window indices if sliding window manager is available const visibilityWindowIndices = slidingWindowManager ? slidingWindowManager.getWindowIndices() : []; if (isDevelopment && slidingWindowManager) ; // Filter out images that are not in the visibility window const imagesToLoad = props.images .filter((_, index) => !slidingWindowManager || visibilityWindowIndices.includes(index)) .filter(image => !Assets.cache.has(image)); if (isDevelopment) ; // Add assets to a bundle for batch loading and progress tracking if (imagesToLoad.length > 0) { // Create an assets bundle Assets.addBundle('slider-images', imagesToLoad.reduce((acc, image, index) => { acc[`slide-${index}`] = image; return acc; }, {})); // Load the bundle with progress tracking await Assets.loadBundle('slider-images', (progress) => { setLoadingProgress(progress * 100); }); } // Create sprites for each image for (const [index, image] of props.images.entries()) { try { // Check if this slide is in the visibility window const isInVisibilityWindow = !slidingWindowManager || visibilityWindowIndices.includes(index); if (isDevelopment && slidingWindowManager) ; if (isInVisibilityWindow) { // Fully load the slide if it's within the visibility window // Get texture from cache const texture = Assets.get(image); // Create slide sprite const sprite = createSlideFromTexture(texture, image, index, slidesContainer, app, sliderWidth, sliderHeight); // Mark as being in the visibility window if (sprite) { sprite._inVisibilityWindow = true; } if (isDevelopment) ; } else { // Create a placeholder for slides outside the visibility window const placeholderOptions = { width: sliderWidth, height: sliderHeight, color: 0x333333, showIndex: isDevelopment, // Show index in development mode index, trackWithResourceManager: true, resourceManager, renderer: app.renderer }; // Import the createPlaceholderSprite function dynamically to avoid circular dependencies const { createPlaceholderSprite } = await import('../utils/placeholderUtils.js'); // Create the placeholder sprite const placeholderSprite = createPlaceholderSprite(placeholderOptions); // Position the placeholder at the center placeholderSprite.x = app.screen.width / 2; placeholderSprite.y = app.screen.height / 2; // Set initial state - only show the first slide placeholderSprite.alpha = index === 0 ? 1 : 0; placeholderSprite.visible = index === 0; // Set scale similar to real sprites placeholderSprite.scale.set(1); placeholderSprite.baseScale = 1; // Mark as placeholder and outside visibility window placeholderSprite._isPlaceholder = true; placeholderSprite._placeholderIndex = index; placeholderSprite._inVisibilityWindow = false; // Add to container and store reference slidesContainer.addChild(placeholderSprite); pixi.slides.current.push(placeholderSprite); if (isDevelopment) ; } } catch (error) { if (isDevelopment) ; } } setIsLoading(false); setLoadingProgress(100); } catch (error) { setIsLoading(false); } }; /** * Helper to create a slide sprite from a texture */ const createSlideFromTexture = (texture, imagePath, index, slidesContainer, app, sliderWidth, sliderHeight) => { try { // Track texture with resource manager if available if (resourceManager) { resourceManager.trackTexture(imagePath, texture); } // Create the sprite const sprite = new Sprite(texture); sprite.anchor.set(0.5); sprite.x = app.screen.width / 2; sprite.y = app.screen.height / 2; // Set initial state - only show the first slide sprite.alpha = index === 0 ? 1 : 0; sprite.visible = index === 0; // Calculate and apply scale try { const { scale, baseScale } = calculateSpriteScale(texture.width, texture.height, sliderWidth, sliderHeight); sprite.scale.set(scale); sprite.baseScale = baseScale; } catch (scaleError) { if (isDevelopment) ; // Fallback scaling sprite.scale.set(1); sprite.baseScale = 1; } // Track the sprite with resource manager if available if (resourceManager) { resourceManager.trackDisplayObject(sprite); } // Add to container and store reference slidesContainer.addChild(sprite); pixi.slides.current.push(sprite); if (isDevelopment) ; return sprite; } catch (error) { return null; } }; /** * Utility function to load a slide at a specific index * @param index Index of the slide to load * @returns Promise that resolves when the slide is loaded */ const loadSlideAtIndex = async (index) => { try { // Validate index if (index < 0 || index >= props.images.length) { if (isDevelopment) ; return false; } // Get the current slide sprite const sprite = pixi.slides.current[index]; // Skip if sprite doesn't exist or is not a placeholder or already loaded if (!sprite || !sprite._isPlaceholder || sprite._loadingState === 'loaded') { return true; } if (isDevelopment) ; // Update loading state sprite._loadingState = 'loading'; // Get image path const imagePath = props.images[index]; // Determine loading method based on atlas availability const useAtlas = atlasManager && props.slidesAtlas && areAssetsInAtlas() && isUseSlidesAtlasEnabled(); let texture; if (useAtlas) { // Load from atlas const normalizedPath = normalizePath(imagePath); const atlasTexture = atlasManager.getFrameTexture(normalizedPath, props.slidesAtlas); if (!atlasTexture) { throw new Error(`Frame ${normalizedPath} not found in atlas ${props.slidesAtlas}`); } texture = atlasTexture; } else { // Load from individual image texture = await Assets.load(imagePath); } // Store original texture for potential reuse sprite._originalTexture = texture; // Apply the texture sprite.texture = texture; // Calculate and apply scale if (sliderRef.current) { const sliderWidth = sliderRef.current.clientWidth; const sliderHeight = sliderRef.current.clientHeight; try { const { scale, baseScale } = calculateSpriteScale(texture.width, texture.height, sliderWidth, sliderHeight); sprite.scale.set(scale); sprite.baseScale = baseScale; } catch (scaleError) { if (isDevelopment) ; // Fallback scaling sprite.scale.set(1); sprite.baseScale = 1; } } // Update flags sprite._isPlaceholder = false; sprite._inVisibilityWindow = true; sprite._loadingState = 'loaded'; // Track the texture and update sprite in resource manager if (resourceManager) { resourceManager.trackTexture(imagePath, texture); resourceManager.trackDisplayObject(sprite); } if (isDevelopment) ; return true; } catch (error) { // Update loading state to error const sprite = pixi.slides.current[index]; if (sprite) { sprite._loadingState = 'error'; } return false; } }; /** * Enhanced transition function with better resource management and animation coordination */ const transitionToSlide = useCallback((nextIndex) => { // Check if slider reference is available if (!sliderRef.current) { return null; } // Validate inputs if (!pixi.slides.current.length) { return null; } if (nextIndex < 0 || nextIndex >= pixi.slides.current.length) { return null; } try { // Cancel any active transition if (activeTransitionRef.current) { activeTransitionRef.current.kill(); activeTransitionRef.current = null; } if (isDevelopment) ; const currentIndex = pixi.currentIndex.current; const currentSlide = pixi.slides.current[currentIndex]; const nextSlide = pixi.slides.current[nextIndex]; // Update sliding window when changing slides if (slidingWindowManager) { // Update the central index in the sliding window slidingWindowManager.updateCurrentIndex(nextIndex); // Get the new visibility window const visibilityIndices = slidingWindowManager.getWindowIndices(); if (isDevelopment) ; // Preload all slides in the visibility window // We do this asynchronously but don't wait for it Promise.all(visibilityIndices.map(async (index) => { // If the slide is a placeholder, load it const slideSprite = pixi.slides.current[index]; if (slideSprite && slideSprite._isPlaceholder) { return loadSlideAtIndex(index); } return Promise.resolve(true); })).then((results) => { if (isDevelopment) ; }); } // Check if the next slide is a placeholder, and if so, load it first const isNextSlideAPlaceholder = nextSlide._isPlaceholder === true; if (isNextSlideAPlaceholder) { if (isDevelopment) ; // Return a promise that resolves with the timeline after loading return new Promise(async (resolve) => { try { // Load the slide const loadSuccess = await loadSlideAtIndex(nextIndex); if (!loadSuccess) { console.error(`Failed to load next slide at index ${nextIndex}`); resolve(null); return; } // Continue with the transition const timeline = performTransition(currentIndex, nextIndex); resolve(timeline); } catch (error) { console.error('Error loading next slide:', error); resolve(null); } }); } // If next slide is already loaded, directly perform the transition return performTransition(currentIndex, nextIndex); } catch (error) { return null; } }, [ sliderRef, pixi.slides, pixi.textContainers, pixi.currentIndex, props.transitionScaleIntensity, resourceManager, onSlideChange, animationCoordinator, slidingWindowManager ]); /** * Helper function to perform the actual transition animation between slides */ const performTransition = (currentIndex, nextIndex) => { try { const currentSlide = pixi.slides.current[currentIndex]; const nextSlide = pixi.slides.current[nextIndex]; // Handle text containers if available const textContainersAvailable = pixi.textContainers.current && pixi.textContainers.current.length > currentIndex && pixi.textContainers.current.length > nextIndex; const currentTextContainer = textContainersAvailable ? pixi.textContainers.current[currentIndex] : null; const nextTextContainer = textContainersAvailable ? pixi.textContainers.current[nextIndex] : null; // IMPORTANT: Make both slides visible during transition currentSlide.visible = true; nextSlide.visible = true; // Ensure next elements start invisible (alpha = 0) nextSlide.alpha = 0; if (nextTextContainer) { nextTextContainer.alpha = 0; nextTextContainer.visible = true; // Make next text visible before transition } // Calculate scale based on transition intensity const transitionScaleIntensity = props.transitionScaleIntensity ?? 30; const scaleMultiplier = 1 + transitionScaleIntensity / 100; // Create animations for slide transitions const slideOutAnimations = []; const slideInAnimations = []; const textOutAnimations = []; const textInAnimations = []; // Create slide out animations slideOutAnimations.push(gsap.to(currentSlide.scale, { x: currentSlide.baseScale * scaleMultiplier, y: currentSlide.baseScale * scaleMultiplier, duration: 1, ease: 'power2.out', onComplete: () => { // Re-track the sprite after animation if (resourceManager) { resourceManager.trackDisplayObject(currentSlide); } } }), gsap.to(currentSlide, { alpha: 0, duration: 1, ease: 'power2.out', onComplete: () => { // IMPORTANT: Hide previous slide after transition completes to save GPU currentSlide.visible = false; // Re-track the sprite after visibility change if (resourceManager) { resourceManager.trackDisplayObject(currentSlide); } } })); // Create slide in animations slideInAnimations.push(gsap.to(nextSlide.scale, { x: nextSlide.baseScale, y: nextSlide.baseScale, duration: 1, ease: 'power2.out', onComplete: () => { // Re-track the sprite after animation if (resourceManager) { resourceManager.trackDisplayObject(nextSlide); } } }), gsap.to(nextSlide, { alpha: 1, duration: 1, ease: 'power2.out', onComplete: () => { // Re-track the sprite after animation if (resourceManager) { resourceManager.trackDisplayObject(nextSlide); } } })); // Set initial scale for next slide nextSlide.scale.set(nextSlide.baseScale * scaleMultiplier, nextSlide.baseScale * scaleMultiplier); // Add text container animations if available if (currentTextContainer && nextTextContainer) { textOutAnimations.push(gsap.to(currentTextContainer, { alpha: 0, duration: 1, ease: 'power2.out', onComplete: () => { // Hide previous text after transition currentTextContainer.visible = false; // Re-track the container after visibility change if (resourceManager) { resourceManager.trackDisplayObject(currentTextContainer); } } })); textInAnimations.push(gsap.to(nextTextContainer, { alpha: 1, duration: 1, ease: 'power2.out', onComplete: () => { // Re-track the container after animation if (resourceManager) { resourceManager.trackDisplayObject(nextTextContainer); } } })); } // Use the AnimationCoordinator to create coordinated animation groups const slideOutGroup = { id: `slide_out_${currentIndex}_${Date.now()}`, type: AnimationGroupType.SLIDE_TRANSITION, animations: slideOutAnimations }; const slideInGroup = { id: `slide_in_${nextIndex}_${Date.now()}`, type: AnimationGroupType.SLIDE_TRANSITION, animations: slideInAnimations }; // Create a master timeline for the entire transition const masterTimeline = gsap.timeline({ onComplete: () => { // Update current index when transition completes pixi.currentIndex.current = nextIndex; activeTransitionRef.current = null; // Call the onSlideChange callback if provided if (onSlideChange) { onSlideChange(nextIndex); } // If using sliding window, check if any slides should be unloaded if (slidingWindowManager) { handleSlidingWindowUnload(nextIndex); } } }); // Add slide animations to the master timeline const slideOutTimeline = animationCoordinator.createAnimationGroup(slideOutGroup); const slideInTimeline = animationCoordinator.createAnimationGroup(slideInGroup); masterTimeline.add(slideOutTimeline, 0); masterTimeline.add(slideInTimeline, 0); // Add text animations if available if (textOutAnimations.length > 0 && textInAnimations.length > 0) { const textOutGroup = { id: `text_out_${currentIndex}_${Date.now()}`, type: AnimationGroupType.TEXT_ANIMATION, animations: textOutAnimations }; const textInGroup = { id: `text_in_${nextIndex}_${Date.now()}`, type: AnimationGroupType.TEXT_ANIMATION, animations: textInAnimations }; const textOutTimeline = animationCoordinator.createAnimationGroup(textOutGroup); const textInTimeline = animationCoordinator.createAnimationGroup(textInGroup); masterTimeline.add(textOutTimeline, 0); masterTimeline.add(textInTimeline, 0); } // Store the master timeline activeTransitionRef.current = masterTimeline; // Track the master timeline with resourceManager if (resourceManager) { resourceManager.trackAnimation(masterTimeline); } return masterTimeline; } catch (error) { return null; } }; /** * Helper function to handle unloading slides that are far outside the visibility window */ const handleSlidingWindowUnload = (currentIndex) => { if (!slidingWindowManager) return; // Get current visibility window const visibilityIndices = slidingWindowManager.getWindowIndices(); // Check all loaded slides and unload those that are far outside the visibility window pixi.slides.current.forEach((sprite, index) => { // Skip if sprite is already a placeholder or uninitialized if (sprite._isPlaceholder || sprite._loadingState === 'uninitialized') { return; } // If this slide is outside the visibility window and not the current slide if (!visibilityIndices.includes(index) && index !== currentIndex) { // How far outside the window is this slide? const distanceFromWindow = Math.min(...visibilityIndices.map(visIndex => Math.abs(index - visIndex))); // Only unload if it's far enough away (e.g., more than 2 slides away from any visible slide) const unloadThreshold = slidingWindowManager.getWindowSize() + 1; if (distanceFromWindow > unloadThreshold) { // Don't unload slides that are very close to the current index const distanceFromCurrent = Math.abs(index - currentIndex); if (distanceFromCurrent <= unloadThreshold) { return; } // Import placeholder utilities import('../utils/placeholderUtils.js').then(({ createPlaceholderSprite }) => { // Create placeholder to replace the loaded sprite if (sliderRef.current && pixi.app.current) { const sliderWidth = sliderRef.current.clientWidth; const sliderHeight = sliderRef.current.clientHeight; const placeholderOptions = { width: sliderWidth, height: sliderHeight, color: 0x333333, showIndex: isDevelopment, index, trackWithResourceManager: true, resourceManager, renderer: pixi.app.current.renderer }; // Create a placeholder const placeholderSprite = createPlaceholderSprite(placeholderOptions); // Copy position and other properties placeholderSprite.x = sprite.x; placeholderSprite.y = sprite.y; placeholderSprite.alpha = sprite.alpha; placeholderSprite.visible = sprite.visible; placeholderSprite.baseScale = sprite.baseScale; placeholderSprite.scale.set(sprite.scale.x, sprite.scale.y); // Mark as outside visibility window placeholderSprite._inVisibilityWindow = false; // Store original texture for potential reuse placeholderSprite._originalTexture = sprite.texture; // Replace in the container if (sprite.parent) { const parent = sprite.parent; const spriteIndex = parent.getChildIndex(sprite); parent.addChildAt(placeholderSprite, spriteIndex); parent.removeChild(sprite); } // Replace in the slides array pixi.slides.current[index] = placeholderSprite; // Properly dispose the sprite (but keep the texture) if (resourceManager) { // First, remove from ResourceManager's tracking resourceManager.trackDisplayObject(sprite); // Then destroy the sprite, but keep the texture sprite.destroy({ children: true, texture: false }); // At this point, the sprite is gone and ResourceManager won't find it during cleanup } else { // If no resource manager, just destroy sprite.destroy({ children: true, texture: false }); } } }); } } }); }; // Add nextSlide and prevSlide methods const nextSlide = useCallback((nextIndex) => { const tl = transitionToSlide(nextIndex); if (tl && onSlideChange) { onSlideChange(nextIndex); } }, [transitionToSlide, onSlideChange]); const prevSlide = useCallback((prevIndex) => { const tl = transitionToSlide(prevIndex); if (tl && onSlideChange) { onSlideChange(prevIndex); } }, [transitionToSlide, onSlideChange]); return { transitionToSlide, nextSlide, prevSlide, isLoading, loadingProgress }; }; export { useSlides as default, useSlides }; //# sourceMappingURL=useSlides.js.map