import { inject, onBeforeUnmount, onMounted, onScopeDispose, watch, type InjectionKey, } from "vue"; import { nativeRouterKey } from "../runtime"; import type { NativeRouterRuntime, NativeViewLifecycle } from "../types"; export const nativeViewLifecycleKey: InjectionKey = Symbol( "native-view-lifecycle", ); export function useNativeRouter() { const runtime = inject(nativeRouterKey); if (!runtime) throw new Error( "Native Vue Router is not installed. Call app.use(nativeRouter).", ); return runtime; } export function useNativeViewLifecycle() { const lifecycle = inject(nativeViewLifecycleKey); if (!lifecycle) throw new Error( "Native view lifecycle APIs must be used inside NativeRouterView.", ); return lifecycle; } type NativeViewHook = () => void; function onNativeViewState( source: Readonly<{ value: boolean }>, entering: boolean, hook: NativeViewHook, ) { onMounted(() => { if (entering && source.value) hook(); }); watch( () => source.value, (value, previous) => { if (value === entering && previous !== entering) hook(); }, { flush: "sync" }, ); } export function onNativeViewActivate(hook: NativeViewHook) { onNativeViewState(useNativeViewLifecycle().isActive, true, hook); } export function onNativeViewDeactivate(hook: NativeViewHook) { onNativeViewState(useNativeViewLifecycle().isActive, false, hook); } export function onNativeViewShow(hook: NativeViewHook) { onNativeViewState(useNativeViewLifecycle().isVisible, true, hook); } export function onNativeViewHide(hook: NativeViewHook) { onNativeViewState(useNativeViewLifecycle().isVisible, false, hook); } export function onNativeViewEvict( hook: (reason: NativeViewLifecycle["evictionReason"]["value"]) => void, ) { const lifecycle = useNativeViewLifecycle(); onBeforeUnmount(() => { if (lifecycle.status.value === "evicted") hook(lifecycle.evictionReason.value); }); } function useNativeViewEffect( source: Readonly<{ value: boolean }>, effect: () => void | (() => void), ) { let cleanup: void | (() => void); const stopEffect = () => { cleanup?.(); cleanup = undefined; }; const stopWatch = watch( () => source.value, (enabled) => { stopEffect(); if (enabled) cleanup = effect(); }, { immediate: true, flush: "sync" }, ); onScopeDispose(() => { stopWatch(); stopEffect(); }); } /** Runs an effect only while this route is the semantically active route. */ export function useNativeViewActiveEffect(effect: () => void | (() => void)) { useNativeViewEffect(useNativeViewLifecycle().isActive, effect); } /** Runs an effect while this route is active or participating in a transition. */ export function useNativeViewVisibleEffect(effect: () => void | (() => void)) { useNativeViewEffect(useNativeViewLifecycle().isVisible, effect); }