4. SusRouteView — visual container
API
SusRouteView is a subclass of the core primitive SusScreenOutlet<SusScreen> — the navigation-agnostic screens-layer container. It only adds route-aware logic; the mounting + KeepAlive (LRU) plumbing lives in core.
public class SusRouteView : SusScreenOutlet<SusScreen>
{
public SusRouter Router { get; set; }
// Inherited from SusScreenOutlet<SusScreen>:
public int MaxKeepAlive { get; set; } = 10;
public SusScreen CurrentScreen { get; protected set; }
public void OnRouteChanged(SusRoute fromRoute, SusRoute toRoute);
public void RenderNestedChain(List<SusScreen> chainScreens);
}Flow
SusRouter.Navigate(from, to)
|
SusRouteView.OnRouteChanged(from, to):
1. if !KeepAlive → from.Screen.parent.Remove(from.Screen) ← detach + drop
if KeepAlive → Remove(from.Screen) + CacheKeepAliveScreen(key, screen) ← detach + cache
2. Add(to.Screen) ← add new/cached screen
3. to.Screen.style.flexGrow = 1; CurrentScreen = to.ScreenTransitions (PlayIn/PlayOut) are driven by SusRouter around this swap.
KeepAlive — off-DOM LRU cache
router.Register("/battle/:id", typeof(BattleScreen), new SusRouteConfig
{
KeepAlive = true,
Transition = SusRouteTransition.Fade(0.2f)
});How it works
KeepAlive is an off-DOM cache, not a wrapper element — the screen is detached from the outlet (parent.Remove(screen)), not hidden with display:none, and not wrapped in a core SusKeepAlive component.
- On leave from a KeepAlive screen:
OnRouteChangedremoves the screen from the outlet and callsCacheKeepAliveScreen(key, screen). The detached VisualElement subtree and itsProp<T>state are preserved in memory.fromRoute.Screenis cleared andIsActive = false. - On return: the router asks
TryGetKeepAliveScreen(key, out screen); on a hit the cached instance is re-added to the outlet — no new instance, noLeft(). The key isSusRouter.KeepAliveKey(route)(fallbackroute.FullPath, incl. query parameters). - In the router: if the target KeepAlive route already has a live cached screen, it reuses it instead of constructing a new one.
LRU eviction
Cache and order live in SusScreenOutlet<TScreen>:
private readonly Dictionary<string, TScreen> _keepAliveCache;
private readonly List<string> _keepAliveOrder; // LRU: front = oldest
public int MaxKeepAlive { get; set; } = 10;When adding a new key would exceed MaxKeepAlive, the oldest entries are evicted first:
_keepAliveOrder[0](oldest) is removed from the order list;OnScreenEvicted(oldScreen)runs —SusRouteViewoverrides it to callscreen.Left()(teardown);- the entry is dropped from
_keepAliveCache.
ClearKeepAliveCache() evicts everything (running the teardown hook for each).
Usage in App.sharq
<template>
<ui:VisualElement $MainElement class="app">
<sus:SusRouteView ref="RouteView" />
</ui:VisualElement>
</template>
<script>
public SusRouter Router = new();
public SusRouteView RouteView;
public override void Mounted()
{
Router.SetRouteView(RouteView);
RouteView.Router = Router;
Router.Register("/main-menu", typeof(MainMenuScreen));
Router.Register("/battle", typeof(BattleHudScreen),
new SusRouteConfig { KeepAlive = true });
Router.Register("/loading", typeof(LoadingScreen));
Router.Replace("/loading");
}
</script>Nested routes — nested render
Each parent screen can register a nested SusRouteView:
public class SettingsScreen : SusScreen
{
public SusRouteView ChildRouterView;
protected override void Build()
{
ChildRouterView = new SusRouteView();
RegisterChildView(ChildRouterView);
Add(ChildRouterView);
}
}When navigating to /settings/profile:
ResolveChain()builds the chain: SettingsRecord → ProfileRecord- Root
SusRouteViewrendersSettingsScreen SettingsScreen.ChildRouterViewrendersProfileTabScreen
Limitation
When the child route changes (/settings/profile → /settings/account) the parent is recreated. Chain-level diffing is planned for the future.