Add experiments folder. Add usage.md and SKILL.md. Fix Sheet interactions and add dynamic sheet mode.
This commit is contained in:
204
skills/integrate-native-vue-router/SKILL.md
Normal file
204
skills/integrate-native-vue-router/SKILL.md
Normal file
@@ -0,0 +1,204 @@
|
||||
---
|
||||
name: integrate-native-vue-router
|
||||
description: Integrate, migrate, configure, or debug Native Vue Router in new or existing Vue 3 applications. Use when adding @native-vue-router/core, converting a Vue Router app to native route surfaces, defining parent or sibling topology, adding gesture links and modal/sheet navigation, handling cached-view lifecycle, composing Vue built-ins with NativeRouterView, creating custom presentations, or wiring Capacitor and Electron adapters.
|
||||
---
|
||||
|
||||
# Integrate Native Vue Router
|
||||
|
||||
Implement Native Vue Router as a visual transaction layer around Vue Router.
|
||||
Keep Vue Router authoritative for committed URLs, history, guards, redirects, and
|
||||
route matching.
|
||||
|
||||
Read [references/integration-reference.md](references/integration-reference.md)
|
||||
before changing a project. Treat it as the API baseline for version `0.1.x`. If
|
||||
the installed package version or local source differs, inspect that version's
|
||||
`package.json`, exported declarations, and source before editing.
|
||||
|
||||
## Choose the adoption path
|
||||
|
||||
Determine whether the target is:
|
||||
|
||||
- A new Vue application: establish the native route shell and topology while
|
||||
creating the router.
|
||||
- An existing Vue Router application: preserve its history mode, routes,
|
||||
guards, redirects, nested outlets, state, and deep links while migrating the
|
||||
visual root and selected navigation calls.
|
||||
- A host integration: add the core first, then add Capacitor or Electron as an
|
||||
adapter at the application boundary.
|
||||
- A targeted enhancement: adopt imperative animated navigation first and defer
|
||||
predictive gestures or sibling paging.
|
||||
|
||||
Do not widen the task into a framework upgrade without user authorization. If
|
||||
the project is below Vue 3.5 or Vue Router 5, report the compatibility gap and
|
||||
the exact upgrade it requires before changing dependencies.
|
||||
|
||||
## Inspect the target
|
||||
|
||||
Before editing, identify:
|
||||
|
||||
1. Package manager, workspace layout, Vue version, and Vue Router version.
|
||||
2. The router creation file, history implementation, route records, guards,
|
||||
redirects, scroll behavior, and lazy components.
|
||||
3. The app entry and plugin installation order.
|
||||
4. Every root and nested `RouterView`, plus wrappers such as `KeepAlive`,
|
||||
`Transition`, and `Suspense`.
|
||||
5. Calls to `router.push`, `router.replace`, `router.back`, `RouterLink`, tab
|
||||
controls, modal routing, and bespoke swipe handlers.
|
||||
6. Route-local side effects in setup/mount hooks, including analytics,
|
||||
subscriptions, polling, media, and mutations.
|
||||
7. Deep-linkable child routes, ordered peer routes, and routes whose local state
|
||||
can or cannot be evicted.
|
||||
8. Horizontal gesture owners such as carousels, maps, editors, and canvases.
|
||||
9. Browser/PWA, Capacitor, Electron, SSR, and accessibility requirements.
|
||||
|
||||
Summarize the route topology before implementation when it is non-trivial.
|
||||
|
||||
## Install and bootstrap
|
||||
|
||||
Add `@native-vue-router/core` and its stylesheet. Add preset or platform packages
|
||||
only when needed. Match the project's package manager and formatting style.
|
||||
|
||||
Create exactly one native runtime for one authoritative root route surface:
|
||||
|
||||
1. Create the Vue Router normally.
|
||||
2. Pass that router to `createNativeRouter`.
|
||||
3. Call `app.use(router)` before `app.use(nativeRouter)`.
|
||||
4. Wait for `router.isReady()` when the application already does so or relies on
|
||||
deterministic initial rendering.
|
||||
5. Replace the root visual `RouterView` with `NativeRouterView`.
|
||||
6. Wrap it in `NativeNavigator` only when predictive Back or sibling paging is
|
||||
required.
|
||||
|
||||
Retain nested `RouterView` components inside route components. Do not add nested
|
||||
independent native runtimes without an explicit ownership design.
|
||||
|
||||
## Model route topology
|
||||
|
||||
Add the smallest route metadata needed for the requested behavior:
|
||||
|
||||
- Use `presentation` for a route's default visual treatment.
|
||||
- Add `parent` to directly addressable child or presented routes so a cold start
|
||||
has a predictive Back destination.
|
||||
- Add numeric `siblingOrder` to peer routes whose direction must be stable.
|
||||
- Choose `siblingHistory: "replace"` for tabs and other peers that should not
|
||||
grow Back history; use `"push"` only when Back should revisit peer selection.
|
||||
- Use `cache: false` for routes that must be destroyed when inactive and
|
||||
`cache: "pin"` only for deliberately retained views.
|
||||
- Use `gesture: false` to disable navigator gesture handling for a route.
|
||||
|
||||
Preserve params, query, and hash in dynamic `parent` functions whenever they are
|
||||
part of the logical parent location. Do not infer parentage from the mounted
|
||||
cache or route registration order.
|
||||
|
||||
`navigator` and `siblingGroup` are reserved labels in `0.1.x`; do not claim that
|
||||
they automatically create navigator ownership. Pass sibling locations to
|
||||
`NativeNavigator` explicitly.
|
||||
|
||||
## Migrate navigation intentionally
|
||||
|
||||
Use native runtime methods for navigation that needs preview-driven motion:
|
||||
|
||||
- `push` or `NativeLink` for forward stack navigation.
|
||||
- `replace` for no-growth replacement.
|
||||
- `sibling` for ordered peers.
|
||||
- `pop` for Back.
|
||||
- `present` and `dismiss` for modal or sheet routes.
|
||||
- `NativeSheet` for safe-area-contained, content-height, or snapping sheet
|
||||
surfaces.
|
||||
- `NativeGestureLink` for component-originated horizontal dragging.
|
||||
- `NativeDismissGesture` for downward dismissal.
|
||||
|
||||
Leave raw Vue Router navigation in place when it is an intentional redirect,
|
||||
non-animated control flow, or external integration. The runtime reconciles such
|
||||
navigation, but it cannot preview it before commit.
|
||||
|
||||
Preserve standard link semantics. Prefer `NativeLink` when a true anchor is
|
||||
needed. If using `NativeGestureLink`, select a semantic `as` element and retain
|
||||
keyboard activation and accessible naming.
|
||||
|
||||
## Make preview mounts safe
|
||||
|
||||
Assume a destination can run setup and mount before its route guard approves the
|
||||
navigation, and can then unmount without becoming current.
|
||||
|
||||
Move committed-screen side effects from unconditional setup/mount code to
|
||||
`useNativeViewActiveEffect`, `onNativeViewActivate`, or explicit user actions.
|
||||
Use `useNativeViewVisibleEffect` only for work needed while the route is active
|
||||
or participating in a transition. Keep durable data in a store or persistence
|
||||
layer because the bounded native cache may evict component instances.
|
||||
|
||||
Do not replace the native multi-route cache with a single Vue `KeepAlive`.
|
||||
`KeepAlive`, `Transition`, `Teleport`, and `Suspense` may still be composed
|
||||
inside the `NativeRouterView` slot. Gate teleported overlays on native view
|
||||
visibility because teleported DOM is outside the inactive layer's `inert` and
|
||||
`aria-hidden` boundary.
|
||||
|
||||
For partial sheets, use `NativeSheet` rather than styling a route component with
|
||||
an arbitrary viewport height. Use no breakpoints for content height, or
|
||||
fractional breakpoints for snap points. Keep `presentation: "sheet"` in route
|
||||
metadata so direct entries retain safe-area and underlay behavior.
|
||||
Preserve the built-in scroll body unless replacing its gesture arbitration:
|
||||
normal content scrolling owns interior positions, while top/down and bottom/up
|
||||
overscroll started at a boundary hands off to sheet resizing. Keep one owner for
|
||||
the complete physical gesture; never reinterpret accumulated content-scroll
|
||||
distance as sheet movement after an edge is reached or direction reverses.
|
||||
|
||||
Use scoped `useRoute()` or Options API `$route` inside route trees. During a
|
||||
preview, do not substitute `router.currentRoute`: it intentionally remains the
|
||||
committed source location until navigation succeeds.
|
||||
|
||||
## Add gesture ownership safely
|
||||
|
||||
Wrap only the intended route surface in `NativeNavigator`. Give the container a
|
||||
definite height and import core CSS.
|
||||
|
||||
Mark nested horizontal interaction regions with
|
||||
`data-native-gesture="ignore"`. Confirm vertical scrolling, controls, text
|
||||
selection, RTL behavior, reduced motion, keyboard navigation, focus, and screen
|
||||
reader isolation after adding gestures.
|
||||
|
||||
Do not promise deterministic browser-edge ownership. Recommend Capacitor when a
|
||||
product requires native-level suppression of host Back gestures.
|
||||
|
||||
## Add host adapters at the boundary
|
||||
|
||||
For Capacitor, install the adapter in `createNativeRouter` and configure hardware
|
||||
Back, haptics, deep-link mapping, background cache trimming, and root exit. Do
|
||||
not assume Universal Links or App Links are configured by JavaScript alone.
|
||||
|
||||
For Electron, call `disableElectronHistoryGestures(app.commandLine)` before
|
||||
`app.whenReady()` in the main process. Expose only the renderer callbacks needed
|
||||
by `createElectronRendererAdapter` through a secure preload. Preserve hash or
|
||||
custom-protocol history behavior used by packaged applications.
|
||||
|
||||
Keep host detection and host APIs out of route components unless the product
|
||||
experience genuinely differs.
|
||||
|
||||
## Verify the integration
|
||||
|
||||
Run the target project's formatter, type checker, unit tests, build, and relevant
|
||||
end-to-end tests. Add tests or manual verification for the changed behavior:
|
||||
|
||||
- Direct entry and reload on deep child URLs.
|
||||
- Params, query, hash, redirects, and nested route injection.
|
||||
- Both routes remain live during a held gesture while the URL stays unchanged.
|
||||
- Gesture cancellation preserves the source URL and state.
|
||||
- Accepted and rejected guards settle correctly.
|
||||
- Browser Back/Forward and cold-start parent Back.
|
||||
- Sibling direction and replace/push history behavior.
|
||||
- Cached state, eviction, unload, and active/visible effects.
|
||||
- Vue built-ins, provide/inject, Options API `$route`, and lifecycle hooks used
|
||||
by the application.
|
||||
- Teleported overlay cleanup and inactive-route focus isolation.
|
||||
- Gesture conflicts, RTL, reduced motion, keyboard behavior, and target hosts.
|
||||
|
||||
Use `runtime.entries`, `activeKey`, `transaction`, `canGoBack`, `cacheStats`, and
|
||||
`onDiagnostic` for focused diagnostics. Use the profiler only when measuring
|
||||
frame behavior; it is opt-in and should be stopped/disposed after capture.
|
||||
|
||||
## Report the result
|
||||
|
||||
State which routes and navigation paths became native-aware, which raw Vue Router
|
||||
paths remain intentionally unchanged, what lifecycle work moved, and what was
|
||||
verified. Call out unresolved host limitations, missing parent topology, or
|
||||
dependency incompatibility explicitly.
|
||||
4
skills/integrate-native-vue-router/agents/openai.yaml
Normal file
4
skills/integrate-native-vue-router/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Integrate Native Vue Router"
|
||||
short_description: "Add native navigation to Vue Router apps"
|
||||
default_prompt: "Use $integrate-native-vue-router to integrate Native Vue Router into this Vue project."
|
||||
@@ -0,0 +1,521 @@
|
||||
# Native Vue Router integration reference
|
||||
|
||||
Use this reference for `@native-vue-router/*` version `0.1.x`. Inspect installed
|
||||
declarations or local source when integrating another version.
|
||||
|
||||
## Support and ownership
|
||||
|
||||
- Requires Vue `^3.5.0` and Vue Router `^5.0.0`.
|
||||
- Targets client-side DOM navigation. SSR/live-stack hydration is incomplete.
|
||||
- Vue Router owns committed routes, URLs, history, guards, redirects, and lazy
|
||||
route matching.
|
||||
- Native Vue Router owns preview surfaces, mounted-view caching, gesture
|
||||
progress, presentations, and transaction state.
|
||||
- Preview trees receive scoped Vue Router route injection. `useRoute()` and
|
||||
Options API `$route` identify that surface; `router.currentRoute` identifies
|
||||
the globally committed route.
|
||||
- The URL does not change during a forward preview. Vue Router navigation and
|
||||
guards run when the gesture or imperative transaction commits.
|
||||
|
||||
## Packages and styles
|
||||
|
||||
```bash
|
||||
npm install vue@^3.5 vue-router@^5 @native-vue-router/core
|
||||
```
|
||||
|
||||
Optional packages:
|
||||
|
||||
```bash
|
||||
npm install @native-vue-router/preset-native
|
||||
npm install @native-vue-router/capacitor @capacitor/app @capacitor/core @capacitor/haptics
|
||||
npm install @native-vue-router/electron
|
||||
```
|
||||
|
||||
Import styles explicitly:
|
||||
|
||||
```ts
|
||||
import "@native-vue-router/core/style.css";
|
||||
import "@native-vue-router/preset-native/style.css"; // when used
|
||||
```
|
||||
|
||||
Give the route shell a definite height. The core `.nvr-navigator` and
|
||||
`.nvr-router-view` elements use `height: 100%`.
|
||||
|
||||
## Bootstrap template
|
||||
|
||||
```ts
|
||||
import { createApp } from "vue";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import { createNativeRouter } from "@native-vue-router/core";
|
||||
import App from "./App.vue";
|
||||
import "@native-vue-router/core/style.css";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
const nativeRouter = createNativeRouter({
|
||||
router,
|
||||
cache: { maxInactive: 4 },
|
||||
// edgeWidth: 28,
|
||||
// platform,
|
||||
// presentations: [customPresentation],
|
||||
});
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(nativeRouter);
|
||||
await router.isReady();
|
||||
app.mount("#app");
|
||||
```
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { NativeNavigator, NativeRouterView } from "@native-vue-router/core";
|
||||
|
||||
const siblings = ["/inbox", "/stories", "/profile"];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NativeNavigator :siblings="siblings" :edge-width="28">
|
||||
<NativeRouterView />
|
||||
</NativeNavigator>
|
||||
</template>
|
||||
```
|
||||
|
||||
Install Vue Router before the native plugin. Retain ordinary nested
|
||||
`RouterView`s within route components.
|
||||
|
||||
## Route metadata
|
||||
|
||||
```ts
|
||||
interface NativeRouteOptions {
|
||||
navigator?: string;
|
||||
presentation?:
|
||||
"push" | "reveal" | "slide" | "fade" | "modal" | "sheet" | "none" | string;
|
||||
transition?: string;
|
||||
parent?: RouteLocationRaw | ((route) => RouteLocationRaw);
|
||||
siblingGroup?: string;
|
||||
siblingOrder?: number;
|
||||
siblingHistory?: "push" | "replace";
|
||||
cache?: boolean | "pin";
|
||||
gesture?: boolean | "edge" | "full";
|
||||
}
|
||||
```
|
||||
|
||||
- Prefer `presentation`; `transition` is a compatibility alias.
|
||||
- `parent` supplies a synthetic predictive Back destination if no warm native
|
||||
history predecessor exists.
|
||||
- `siblingOrder` determines direction for programmatic sibling navigation.
|
||||
- `siblingHistory: "replace"` is the usual tab behavior.
|
||||
- `cache: false` prevents inactive retention. `cache: "pin"` avoids ordinary LRU
|
||||
trimming and is excluded from default `trimCache()`.
|
||||
- `gesture: false` is enforced by `NativeNavigator`. In `0.1.x`, finer
|
||||
`edge`/`full` policy is mainly structural.
|
||||
- `navigator` and `siblingGroup` are reserved; pass siblings to the navigator
|
||||
explicitly.
|
||||
|
||||
Example with dynamic parent state:
|
||||
|
||||
```ts
|
||||
{
|
||||
path: "/chat/:id/details",
|
||||
name: "chat-details",
|
||||
component: () => import("./ChatDetailsView.vue"),
|
||||
meta: {
|
||||
native: {
|
||||
presentation: "push",
|
||||
parent: (route) => ({
|
||||
name: "chat",
|
||||
params: { id: route.params.id },
|
||||
query: route.query,
|
||||
hash: route.hash,
|
||||
}),
|
||||
gesture: "edge",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Core components
|
||||
|
||||
### `NativeRouterView`
|
||||
|
||||
Renders every mounted native entry as a sibling layer. Its default slot exposes:
|
||||
|
||||
```ts
|
||||
{
|
||||
Component: Component | undefined;
|
||||
route: RouteLocationNormalizedLoaded;
|
||||
entry: NativeViewEntry;
|
||||
}
|
||||
```
|
||||
|
||||
Without a slot, it renders the matched route component. Inactive layers are
|
||||
hidden, `inert`, and `aria-hidden`.
|
||||
|
||||
### `NativeNavigator`
|
||||
|
||||
Props:
|
||||
|
||||
- `siblings: RouteLocationRaw[]` (default `[]`)
|
||||
- `edgeWidth: number` (default `28`)
|
||||
|
||||
Owns leading-edge predictive Back and optional full-surface paging between the
|
||||
listed siblings. The list is visual order. Route `siblingOrder` also makes
|
||||
programmatic direction deterministic.
|
||||
|
||||
### `NativeLink`
|
||||
|
||||
Props:
|
||||
|
||||
- `to: RouteLocationRaw` (required)
|
||||
- `replace: boolean`
|
||||
- `presentation: NativePresentationName`
|
||||
|
||||
Renders an anchor with a resolved `href`. Primary unmodified clicks use native
|
||||
`push` or `replace`; other attributes pass through.
|
||||
|
||||
### `NativeGestureLink`
|
||||
|
||||
Props:
|
||||
|
||||
- `to: RouteLocationRaw` (required)
|
||||
- `presentation` (default `"reveal"`)
|
||||
- `replace: boolean`
|
||||
- `direction: "left" | "right" | "any"` (default `"any"`)
|
||||
- `as: string` (default `"div"`)
|
||||
|
||||
Owns horizontal component-originated navigation. A click uses native `push`.
|
||||
Choose semantic markup and add accessible keyboard behavior as needed.
|
||||
|
||||
### `NativeDismissGesture`
|
||||
|
||||
Prop `as` defaults to `div`. Owns downward vertical dismissal for a modal or
|
||||
sheet surface.
|
||||
|
||||
### `NativeSheet`
|
||||
|
||||
Use inside a route with `meta.native.presentation: "sheet"`. The sheet route is
|
||||
contained below `--nvr-safe-top` plus `--nvr-sheet-top-gap`, and its previous
|
||||
route remains visible but inert as an underlay.
|
||||
|
||||
Props:
|
||||
|
||||
- `breakpoints: number[]` (default `[]`): normalized fractions in `(0, 1]`.
|
||||
Empty means content height capped at available height.
|
||||
- `initialBreakpoint?: number`: starts at the nearest declared fraction.
|
||||
- `modelValue?: number`: current fraction for `v-model`.
|
||||
- `dismissible: boolean` (default `true`).
|
||||
- `backdropDismiss: boolean` (default `true`).
|
||||
- `showHandle: boolean` (default `true`).
|
||||
- `ariaLabel: string` (default `"Sheet"`).
|
||||
|
||||
Events are `update:modelValue`, `breakpoint-change`, and `dismiss`. The named
|
||||
`handle` slot replaces only the handle visual. The built-in handle supports
|
||||
pointer dragging, Escape, Arrow Up/Down, Home, and End.
|
||||
|
||||
The body keeps native scrolling while it has content in the requested
|
||||
direction. At the top, downward overscroll shrinks or dismisses the sheet. At
|
||||
the bottom, upward overscroll expands it. The handoff supports touch,
|
||||
mouse/pen dragging, and thresholded wheel/trackpad input. A snapping
|
||||
`NativeSheet` animates its own measured surface, while the route-sized wrapper
|
||||
stays fixed and the previous route retains its underlay scale across commit.
|
||||
|
||||
Ownership locks after the gesture's initial directional intent. If content owns
|
||||
the gesture, reaching an edge or reversing does not transfer that same gesture
|
||||
to the sheet; release and start at the edge to resize. Never implement a
|
||||
mid-gesture reinterpretation using distance accumulated while content was
|
||||
scrolling.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { NativeSheet } from "@native-vue-router/core";
|
||||
|
||||
const point = ref(0.55);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NativeSheet
|
||||
v-model="point"
|
||||
:breakpoints="[0.3, 0.55, 1]"
|
||||
:initial-breakpoint="0.55"
|
||||
aria-label="Filters"
|
||||
>
|
||||
<FilterForm />
|
||||
</NativeSheet>
|
||||
</template>
|
||||
```
|
||||
|
||||
Theme with `--nvr-sheet-background`, `--nvr-sheet-backdrop`,
|
||||
`--nvr-sheet-radius`, and `--nvr-sheet-top-gap`. Prefer `NativeSheet` to
|
||||
`NativeDismissGesture` for partial or snapping route sheets.
|
||||
|
||||
### Gesture exclusions
|
||||
|
||||
The recognizers ignore ordinary form/editable controls and elements within:
|
||||
|
||||
```html
|
||||
<div data-native-gesture="ignore">...</div>
|
||||
```
|
||||
|
||||
Use this for maps, carousels, canvases, code editors, and custom horizontal
|
||||
controls.
|
||||
|
||||
## Runtime API
|
||||
|
||||
Create/inject:
|
||||
|
||||
- `createNativeRouter(options): NativeRouterRuntime`
|
||||
- `useNativeRouter(): NativeRouterRuntime`
|
||||
- Options API: `this.$nativeRouter`
|
||||
|
||||
Reactive readonly state:
|
||||
|
||||
- `router`
|
||||
- `entries`
|
||||
- `activeKey`
|
||||
- `transaction`
|
||||
- `canGoBack`
|
||||
- `cacheStats`
|
||||
|
||||
Navigation:
|
||||
|
||||
```ts
|
||||
push(to, options?): Promise<boolean>
|
||||
replace(to, options?): Promise<boolean>
|
||||
sibling(to, options?): Promise<boolean>
|
||||
pop(): Promise<boolean>
|
||||
present(to, presentation = "modal"): Promise<boolean>
|
||||
dismiss(): Promise<boolean>
|
||||
preload(to): Promise<RouteLocationNormalizedLoaded>
|
||||
```
|
||||
|
||||
`NativeNavigationOptions` supports:
|
||||
|
||||
```ts
|
||||
{
|
||||
presentation?: NativePresentationName;
|
||||
replace?: boolean;
|
||||
direction?: "forward" | "back" | "up" | "down";
|
||||
sourceRect?: {
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
viewportWidth: number;
|
||||
viewportHeight: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Interactive driver:
|
||||
|
||||
```ts
|
||||
beginInteractive(kind, to?, options?): Promise<number | null>
|
||||
updateInteractive(progress, velocity?): void
|
||||
finishInteractive(forceCommit?): Promise<boolean>
|
||||
cancelInteractive(): Promise<void>
|
||||
```
|
||||
|
||||
Kinds are `push`, `pop`, `sibling`, `present`, and `dismiss`. Progress is clamped
|
||||
to `0...1`. Velocity is normalized route progress per second. The default commit
|
||||
rule is progress at least `0.36`, or progress at least `0.08` with velocity at
|
||||
least `1.1`.
|
||||
|
||||
Cache/diagnostics/extension:
|
||||
|
||||
```ts
|
||||
unload(to): number
|
||||
trimCache({ includePinned?, reason? }?): void
|
||||
onDiagnostic(listener): () => void
|
||||
registerPresentation(definition): void
|
||||
presentationFor(name): NativePresentationDefinition | undefined
|
||||
dispose(): void
|
||||
```
|
||||
|
||||
Call `dispose()` if the owning application lifecycle tears the runtime down
|
||||
without a page unload.
|
||||
|
||||
## Native view lifecycle
|
||||
|
||||
Available APIs:
|
||||
|
||||
```ts
|
||||
useNativeViewLifecycle();
|
||||
onNativeViewActivate(hook);
|
||||
onNativeViewDeactivate(hook);
|
||||
onNativeViewShow(hook);
|
||||
onNativeViewHide(hook);
|
||||
onNativeViewEvict(hook);
|
||||
useNativeViewActiveEffect(effect);
|
||||
useNativeViewVisibleEffect(effect);
|
||||
```
|
||||
|
||||
Lifecycle state:
|
||||
|
||||
- `route`: scoped route for the view.
|
||||
- `status`: `active`, `inactive`, `preview`, or `evicted`.
|
||||
- `role`: `active`, `inactive`, `underlay`, `from`, or `to`.
|
||||
- `isActive`: authoritative committed route.
|
||||
- `isVisible`: active, one side of a live transition, or a visible sheet
|
||||
underlay. Underlays remain inert.
|
||||
- `isPreview`: destination not yet committed.
|
||||
- `isCached`: mounted inactive route.
|
||||
- `evictionReason`: `cache-disabled`, `cache-limit`,
|
||||
`navigation-rejected`, `popped`, `manual`, `trimmed`, or `memory-pressure`.
|
||||
|
||||
Effects may return cleanup functions. Active effects suit polling,
|
||||
subscriptions, media, and committed-screen analytics. Visible effects suit work
|
||||
needed while the surface participates in a transition.
|
||||
|
||||
Do not treat Vue component mount as route commitment. A preview can mount before
|
||||
guards run and unmount after rejection or gesture cancellation.
|
||||
|
||||
## Vue compatibility
|
||||
|
||||
Within a native route surface:
|
||||
|
||||
- Vue Composition and Options API lifecycle hooks retain normal component
|
||||
semantics.
|
||||
- App/plugin/ancestor `provide` and `inject` work normally.
|
||||
- `useRoute`, `useRouter`, and Options API `$route` are supported.
|
||||
- Params, query, hash, metadata, and matched records are scoped to each preview
|
||||
or committed surface.
|
||||
- Nested `RouterView` is supported.
|
||||
- `KeepAlive`, `Transition`, `Teleport`, and `Suspense` may be used inside the
|
||||
`NativeRouterView` slot.
|
||||
|
||||
Important distinctions:
|
||||
|
||||
- Native route caching is separate from `KeepAlive`. A cached native route stays
|
||||
mounted, so native inactivity alone does not trigger Vue `onDeactivated`.
|
||||
- Native presentations animate route layers; a Vue `Transition` handles changes
|
||||
within one layer.
|
||||
- Teleported DOM escapes the route layer's `inert`/`aria-hidden` isolation. Gate
|
||||
it with `isVisible` and close it when the owning view hides where appropriate.
|
||||
- Async route components are loaded before preview. A `Suspense` inside the route
|
||||
can still show fallback UI for async descendants.
|
||||
|
||||
Slot composition example:
|
||||
|
||||
```vue
|
||||
<NativeRouterView v-slot="{ Component, route }">
|
||||
<Suspense>
|
||||
<Transition name="content" mode="out-in">
|
||||
<KeepAlive :max="3">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</KeepAlive>
|
||||
</Transition>
|
||||
<template #fallback><RouteSkeleton /></template>
|
||||
</Suspense>
|
||||
</NativeRouterView>
|
||||
```
|
||||
|
||||
## Custom presentation
|
||||
|
||||
```ts
|
||||
import { definePresentation } from "@native-vue-router/core";
|
||||
|
||||
const scaleFade = definePresentation({
|
||||
name: "scale-fade",
|
||||
axis: "x",
|
||||
layerStyle({ role, progress, direction, sourceRect }) {
|
||||
return role === "to"
|
||||
? { opacity: progress, transform: `scale(${0.94 + progress * 0.06})` }
|
||||
: { opacity: 1 - progress * 0.25 };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Register through `createNativeRouter({ presentations: [...] })` or
|
||||
`runtime.registerPresentation()`. Keep layer styles compositor-friendly and do
|
||||
not perform application or history mutations in a style function.
|
||||
|
||||
## Preset-native API
|
||||
|
||||
- `NativeBackButton` prop: `label` (default `"Back"`). Calls `native.pop()`.
|
||||
- `NativeTabBar` prop: required `items: NativeTabItem[]`. It uses replace-style
|
||||
native sibling navigation.
|
||||
- `NativeTabItem`: `{ label, to, icon?, activeWhen? }`.
|
||||
- `detectNativePlatform()`: `"ios" | "android" | "desktop"`.
|
||||
- `nativeMotionTokens`: edge widths and commit thresholds for those three
|
||||
platform labels. Core physics are not route-configurable from these tokens in
|
||||
`0.1.x`.
|
||||
|
||||
## Capacitor adapter
|
||||
|
||||
```ts
|
||||
createCapacitorAdapter({
|
||||
exitAtRoot?: boolean; // default true behavior
|
||||
haptics?: boolean; // default enabled on native
|
||||
trimCacheOnPause?: boolean; // default true
|
||||
deepLinkPath?: (url: URL) => string;
|
||||
})
|
||||
```
|
||||
|
||||
It handles Android hardware Back, App URL open/launch URLs, interactive
|
||||
cancellation on pause, optional inactive-cache trimming, root exit, and haptics.
|
||||
Native link association/configuration remains an application responsibility.
|
||||
|
||||
## Electron adapter
|
||||
|
||||
Main process, before `app.whenReady()`:
|
||||
|
||||
```ts
|
||||
disableElectronHistoryGestures(app.commandLine);
|
||||
```
|
||||
|
||||
Renderer:
|
||||
|
||||
```ts
|
||||
createNativeRouter({
|
||||
router,
|
||||
platform: createElectronRendererAdapter(),
|
||||
});
|
||||
```
|
||||
|
||||
The preload may expose:
|
||||
|
||||
```ts
|
||||
window.nativeVueHost = {
|
||||
onBack(callback): () => void,
|
||||
onForward?(callback): () => void,
|
||||
onMemoryPressure?(callback): () => void,
|
||||
};
|
||||
```
|
||||
|
||||
Use a secure context bridge; do not enable Node integration merely for the
|
||||
adapter. Packaged `file:` apps commonly require hash history.
|
||||
|
||||
## Verification matrix
|
||||
|
||||
At minimum verify:
|
||||
|
||||
| Area | Checks |
|
||||
| ----------- | --------------------------------------------------------------------------------- |
|
||||
| Boot | Initial route renders after `router.isReady`; CSS and height are correct |
|
||||
| Route state | Params, query, hash, `useRoute`, `$route`, nested routes, provide/inject |
|
||||
| Preview | Source and destination coexist; URL stays on source; cancellation removes preview |
|
||||
| Guards | Acceptance, rejection, and redirect reconcile to Vue Router's result |
|
||||
| Back | Warm Back, browser Back/Forward, direct-entry parent, no unrelated cached target |
|
||||
| Siblings | Direction follows order; chosen push/replace history behavior is correct |
|
||||
| Cache | Local warm state, `cache: false`, pin, LRU, unload, trim, eviction cleanup |
|
||||
| Lifecycle | Active/visible effects stop and resume at the correct boundaries |
|
||||
| Vue | KeepAlive, Transition, Teleport, Suspense, component hook ordering |
|
||||
| Input | Edge Back, component drag, vertical scroll, ignored regions, rapid interruption |
|
||||
| A11y | Inactive focus isolation, semantic controls, keyboard access, reduced motion |
|
||||
| Hosts | PWA limits, Electron commands/history, Capacitor Back/deep links/pause |
|
||||
|
||||
## Known boundaries
|
||||
|
||||
- One runtime coordinates one authoritative visual transaction at a time.
|
||||
- Preview setup can run before navigation approval.
|
||||
- The mounted cache is bounded; route-local state can be lost after eviction.
|
||||
- Cold-start predictive Back requires explicit parent topology.
|
||||
- Browser content cannot guarantee ownership of operating-system/browser edge
|
||||
gestures.
|
||||
- Independent nested navigation controllers and SSR stack hydration need an
|
||||
explicit design beyond the current `0.1.x` implementation.
|
||||
Reference in New Issue
Block a user