Files
Native-Router-Vue/docs/architecture.md
2026-07-21 14:54:36 +10:00

3.4 KiB

Architecture

Ownership boundary

Vue Router owns matching, lazy components, committed routes, redirects, guards, URL serialization, and browser history. Native Vue Router owns a separate visual ledger containing mounted route entries, scroll/focus state, cache status, and the current interactive transaction.

A forward drag calls router.resolve() and Vue Router's public loadRouteLocation(), then renders the location through <RouterView :route> without calling push(). The URL and application history remain unchanged until the gesture commits.

Each preview subtree receives a scoped routeLocationKey, so useRoute() returns preview params even though the global route is not committed. A normal router.push() or replace() runs only after the gesture chooses to commit. A guard failure cancels the transaction and removes the preview.

Transaction lifecycle

Transactions move through interactive, committing, settling, and cancellation states. They expose normalized progress and velocity plus fromKey, toKey, direction, presentation, and optional source geometry.

The runtime deliberately keeps two ledgers. The navigation stack mirrors committed push/replace/pop semantics and is the only source of predictive-back targets. The view cache owns mounted component instances independently, so a replaced tab can be reused without becoming an accidental back destination.

Pointer movement changes only a CSS progress variable. Built-in presentations restrict active-frame work to compositor-friendly properties. Release uses distance/velocity intent and a damped spring. Reduced-motion mode settles immediately.

Component gesture owners stop propagation before a containing navigator can claim the same pointer. Navigator gestures wait for horizontal intent, use pointer capture, respect form controls and data-native-gesture="ignore", and preserve normal vertical scrolling through touch-action: pan-y.

Route metadata

interface NativeRouteOptions {
  navigator?: string
  presentation?: 'push' | 'reveal' | 'slide' | 'fade' | 'modal' | 'sheet' | string
  parent?: RouteLocationRaw | ((route) => RouteLocationRaw)
  siblingGroup?: string
  siblingOrder?: number
  siblingHistory?: 'push' | 'replace'
  cache?: boolean
  gesture?: boolean | 'edge' | 'full'
}

parent supplies a predictive back target when a deep link starts without an in-memory predecessor. Sibling routes replace history by default and use the built-in slide presentation, which moves both pages one-to-one as adjacent surfaces. Direction comes from siblingOrder. Set siblingHistory: 'push' when browser back should visit prior sibling selections.

Custom presentations

nativeRouter.registerPresentation(definePresentation({
  name: 'scale-fade',
  axis: 'x',
  layerStyle({ role, progress }) {
    return role === 'to'
      ? { opacity: progress, transform: `scale(${0.92 + progress * 0.08})` }
      : { opacity: 1 - progress * 0.4 }
  },
}))

Applications can call beginInteractive(), updateInteractive(), and finishInteractive() to drive the same transaction engine from a bespoke recognizer.

Cache semantics

The active route and recent inactive routes remain mounted. The default limit is eight inactive views per runtime. Older entries keep their route descriptor but are unmounted and lazily restored when revisited. Application data that must survive eviction belongs in an application store.