/** * Navigation Helpers * * Shared utilities for page navigation in RuntimePresentation and constructor.tsx. * Handles target page resolution, back navigation detection, and transition blocking. */ import type { RuntimePage } from '../types/runtime'; import type { NavigableElement, NavigationTarget, TransitionPhase, } from '../types/presentation'; /** * Resolve target page from element navigation properties. * Supports both targetPageSlug (new) and targetPageId (legacy). * * @param element - Element with navigation properties * @param pages - Available pages to search * @returns The target page or undefined if not found */ export const resolveNavigationTarget = ( element: NavigableElement, pages: RuntimePage[], ): NavigationTarget | null => { const targetPageSlug = element.targetPageSlug; const legacyTargetPageId = element.targetPageId; let targetPage: RuntimePage | undefined; if (targetPageSlug) { targetPage = pages.find((p) => p.slug === targetPageSlug); } else if (legacyTargetPageId) { targetPage = pages.find((p) => p.id === legacyTargetPageId); } if (!targetPage) { return null; } const isBack = isBackNavigation(element); return { page: targetPage, pageId: targetPage.id, transitionVideoUrl: element.transitionVideoUrl, isBack, }; }; /** * Determine if navigation direction is "back". * Elements with navType='back' or type='navigation_prev' navigate backwards. * * @param element - Element to check * @returns true if this is a back navigation */ export const isBackNavigation = (element: NavigableElement): boolean => { return element.navType === 'back' || element.type === 'navigation_prev'; }; /** * Get navigation direction based on element properties. * * @param element - Element with navigation properties * @returns 'back' or 'forward' */ export const getNavigationDirection = ( element: NavigableElement, ): 'back' | 'forward' => { return isBackNavigation(element) ? 'back' : 'forward'; }; /** * Check if transition is actively blocking navigation. * Navigation should be blocked during preparing, playing, or reversing phases. * * @param transitionPhase - Current transition phase * @param isBuffering - Whether video is buffering * @returns true if navigation should be blocked */ export const isTransitionBlocking = ( transitionPhase: TransitionPhase, isBuffering: boolean, ): boolean => { const activePhases: TransitionPhase[] = ['preparing', 'playing', 'reversing']; return activePhases.includes(transitionPhase) || isBuffering; }; /** * Check if element has a playable transition. * A transition is playable if it has a video URL, and for back navigation, * either supports reverse or has a separate reverse video. * * @param element - Element with transition properties * @param direction - Navigation direction * @returns true if element has a playable transition */ export const hasPlayableTransition = ( element: { transitionVideoUrl?: string; transitionReverseMode?: string; reverseVideoUrl?: string; }, direction: 'back' | 'forward' = 'forward', ): boolean => { if (!element.transitionVideoUrl) { return false; } // For back navigation with separate_video mode, need reverse video if ( direction === 'back' && element.transitionReverseMode === 'separate_video' && !element.reverseVideoUrl ) { return false; } return true; }; /** * Check if element is a navigation element type. * * @param elementType - Element type to check * @returns true if element is a navigation type */ export const isNavigationType = (elementType: string): boolean => { return ( elementType === 'navigation_next' || elementType === 'navigation_prev' || elementType === 'navigation' ); };