// Transition horizontale unique pour les relations parent → enfant. // Les changements d'onglets frères passent `direction="none"`; les feuilles // verticales restent volontairement hors de ce composant. function useVascoReducedMotion(forcedValue) { const [reduced, setReduced] = React.useState(() => { if (typeof forcedValue === 'boolean') return forcedValue; return Boolean(window.matchMedia?.('(prefers-reduced-motion: reduce)').matches); }); React.useEffect(() => { if (typeof forcedValue === 'boolean') { setReduced(forcedValue); return undefined; } const media = window.matchMedia?.('(prefers-reduced-motion: reduce)'); if (!media) return undefined; const update = () => setReduced(media.matches); update(); media.addEventListener?.('change', update); return () => media.removeEventListener?.('change', update); }, [forcedValue]); return reduced; } function VascoNavigationTransition({ screenKey, direction = 'forward', children, reducedMotion: forcedReducedMotion, onEntered = null, style = null }) { const reducedMotion = useVascoReducedMotion(forcedReducedMotion); const previousKeyRef = React.useRef(screenKey); const previousNodeRef = React.useRef(children); const transitionIdRef = React.useRef(0); const timerRef = React.useRef(null); const [transition, setTransition] = React.useState(null); const finish = React.useCallback((id) => { setTransition((current) => { if (!current || current.id !== id) return current; window.clearTimeout(timerRef.current); timerRef.current = null; window.setTimeout(() => onEntered?.(), 0); return null; }); }, [onEntered]); const keyChanged = previousKeyRef.current !== screenKey; const leavingNode = keyChanged ? previousNodeRef.current : null; React.useLayoutEffect(() => { const fromKey = previousKeyRef.current; const outgoing = previousNodeRef.current; const next = window.VascoNavigationContract.plan(fromKey, screenKey, direction, reducedMotion); previousKeyRef.current = screenKey; previousNodeRef.current = children; window.clearTimeout(timerRef.current); timerRef.current = null; if (!next.animate) { setTransition(null); window.setTimeout(() => onEntered?.(), 0); return undefined; } const id = ++transitionIdRef.current; setTransition({ id, direction: next.direction, leavingNode: outgoing }); timerRef.current = window.setTimeout(() => finish(id), next.durationMs + 80); return () => window.clearTimeout(timerRef.current); }, [screenKey, direction, reducedMotion]); if (!keyChanged) previousNodeRef.current = children; const active = transition; const currentDirection = active?.direction || 'none'; const incomingAnimation = currentDirection === 'forward' ? 'vascoNavForwardIn' : 'vascoNavBackIn'; const outgoingAnimation = currentDirection === 'forward' ? 'vascoNavForwardOut' : 'vascoNavBackOut'; const duration = `${window.VascoNavigationContract.DURATION_MS}ms`; return