vue-micro-router 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +617 -0
- package/dist/audio.d.ts +104 -0
- package/dist/audio.mjs +100 -0
- package/dist/index.d.ts +887 -0
- package/dist/index.mjs +1230 -0
- package/dist/styles.css +1 -0
- package/dist/styles.d.ts +1 -0
- package/dist/styles.mjs +1 -0
- package/dist/timer-manager.mjs +20 -0
- package/package.json +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Danh Nguyen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
# vue-micro-router
|
|
2
|
+
|
|
3
|
+
**Mobile-app-style navigation for Vue 3 — no URL paths, just screens.**
|
|
4
|
+
|
|
5
|
+
Build apps that feel like native mobile — pages slide in/out with smooth transitions, dialogs stack as modals, and persistent HUD controls float above everything. No `/users/:id` URL routing. Just `push('profile')` and watch it animate.
|
|
6
|
+
|
|
7
|
+
### Why not vue-router?
|
|
8
|
+
|
|
9
|
+
| | vue-router | vue-micro-router |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Navigation model | URL-based (`/path/:param`) | Segment stack (`home → home/menu → home/menu/settings`) |
|
|
12
|
+
| Page transitions | Manual (TransitionGroup) | Built-in slide/fade animations + per-route customization |
|
|
13
|
+
| Multiple visible pages | No (one route = one view) | Yes — stacked pages all render simultaneously |
|
|
14
|
+
| Modal dialogs | DIY | First-class with stacking, backdrop, focus trap |
|
|
15
|
+
| GUI overlays / HUD | DIY | First-class controls with auto-show/hide |
|
|
16
|
+
| Route guards | `beforeRouteEnter` etc. | `beforeEach`, `beforeEnter`, `beforeLeave` + async |
|
|
17
|
+
| State passing | Query params / route params | Reactive `useMicroState()` bridge |
|
|
18
|
+
| Navigation history | Browser history API | Built-in `canGoBack` / `historyBack()` |
|
|
19
|
+
| Gesture navigation | None | Swipe-back from left edge |
|
|
20
|
+
| Type safety | Route params typing | Auto-typed via `Register` + `useMicroRouter()` |
|
|
21
|
+
| State persistence | None | `serialize()` / `restore()` |
|
|
22
|
+
| Nested routers | Nested `<router-view>` | Independent `<MicroRouterView nested>` |
|
|
23
|
+
| Lifecycle hooks | `beforeRouteEnter` etc. | `onRouteEnter`, `onDialogEnter`, `onControlEnter` |
|
|
24
|
+
| Use case | Websites, SPAs | Games, mobile-style apps, kiosks, wizards |
|
|
25
|
+
|
|
26
|
+
### How it works
|
|
27
|
+
|
|
28
|
+
Pages stack as path segments — `push('menu')` slides a new page on top. `push(-1)` slides it back. No browser URL changes. Just screens animating like a native app.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
push('menu') → [home] ← [menu slides in]
|
|
32
|
+
push('settings') → [home] [menu] ← [settings slides in]
|
|
33
|
+
push(-1) → [home] [menu slides out →]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Dialogs and controls layer on top — independently managed with their own lifecycle, stacking, and transitions.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bun add vue-micro-router
|
|
42
|
+
# or
|
|
43
|
+
npm install vue-micro-router
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<!-- app-plugin.ts — declare once, typed everywhere -->
|
|
50
|
+
<script lang="ts">
|
|
51
|
+
import { defineFeaturePlugin } from 'vue-micro-router';
|
|
52
|
+
import HomePage from './pages/HomePage.vue';
|
|
53
|
+
import MenuPage from './pages/MenuPage.vue';
|
|
54
|
+
import SettingsPage from './pages/SettingsPage.vue';
|
|
55
|
+
import ConfirmDialog from './dialogs/ConfirmDialog.vue';
|
|
56
|
+
import MainGUI from './controls/MainGUI.vue';
|
|
57
|
+
|
|
58
|
+
export const appPlugin = defineFeaturePlugin({
|
|
59
|
+
name: 'app',
|
|
60
|
+
routes: [
|
|
61
|
+
{ path: 'home', component: HomePage },
|
|
62
|
+
{ path: 'menu', component: MenuPage, transition: 'fade', transitionDuration: 300 },
|
|
63
|
+
{ path: 'settings', component: SettingsPage, preload: 'adjacent',
|
|
64
|
+
beforeLeave: () => confirm('Leave settings?') },
|
|
65
|
+
],
|
|
66
|
+
dialogs: [
|
|
67
|
+
{ path: 'confirm', component: ConfirmDialog, activated: false },
|
|
68
|
+
],
|
|
69
|
+
controls: [
|
|
70
|
+
{ name: 'main_gui', component: MainGUI, activated: false },
|
|
71
|
+
],
|
|
72
|
+
} as const);
|
|
73
|
+
|
|
74
|
+
// Register plugin type globally — no generics needed in useMicroRouter()
|
|
75
|
+
declare module 'vue-micro-router' {
|
|
76
|
+
interface Register {
|
|
77
|
+
plugin: typeof appPlugin;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
</script>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```vue
|
|
84
|
+
<!-- App.vue — fully typed without generics -->
|
|
85
|
+
<script setup>
|
|
86
|
+
import { MicroRouterView } from 'vue-micro-router';
|
|
87
|
+
import 'vue-micro-router/styles';
|
|
88
|
+
import { appPlugin } from './app-plugin';
|
|
89
|
+
|
|
90
|
+
const config = {
|
|
91
|
+
defaultPath: 'home',
|
|
92
|
+
defaultControlName: 'main_gui',
|
|
93
|
+
history: { enabled: true, maxEntries: 50 },
|
|
94
|
+
gesture: { enabled: true },
|
|
95
|
+
guards: {
|
|
96
|
+
beforeEach: [(to, from) => {
|
|
97
|
+
console.log(`Navigating: ${from} → ${to}`);
|
|
98
|
+
return true;
|
|
99
|
+
}],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<MicroRouterView :config :plugins="[appPlugin]" />
|
|
106
|
+
</template>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Core Concepts
|
|
110
|
+
|
|
111
|
+
### Segment-Based Paths
|
|
112
|
+
|
|
113
|
+
Pages stack as path segments. `"home/menu/settings"` renders 3 pages simultaneously — each page slides in on top of the previous one.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const { push } = useMicroRouter();
|
|
117
|
+
|
|
118
|
+
// Push a new page on top
|
|
119
|
+
await push('menu'); // home → home/menu
|
|
120
|
+
|
|
121
|
+
// Push with absolute path
|
|
122
|
+
await push('/home/settings'); // → home/settings
|
|
123
|
+
|
|
124
|
+
// Go back one step
|
|
125
|
+
await push(-1); // home/settings → home
|
|
126
|
+
|
|
127
|
+
// Go back with props (forces remount)
|
|
128
|
+
await push(-1, { reset: true });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Navigation with Props
|
|
132
|
+
|
|
133
|
+
Pass data to pages via `push()` and read it with `useMicroState()`:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// Parent — push with props
|
|
137
|
+
await push('profile', { userId: 42 });
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```vue
|
|
141
|
+
<!-- ProfilePage.vue -->
|
|
142
|
+
<script setup>
|
|
143
|
+
import { useMicroState } from 'vue-micro-router';
|
|
144
|
+
|
|
145
|
+
// Read props passed via push() — auto-syncs mutations back to store
|
|
146
|
+
const { userId } = useMicroState<{ userId: number }>();
|
|
147
|
+
|
|
148
|
+
// With defaults for optional props
|
|
149
|
+
const { tab } = useMicroState({ tab: 'overview' });
|
|
150
|
+
</script>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Route Guards
|
|
154
|
+
|
|
155
|
+
Prevent or control navigation with sync/async guards:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// Global guards — run on every navigation
|
|
159
|
+
const config = {
|
|
160
|
+
guards: {
|
|
161
|
+
beforeEach: [
|
|
162
|
+
async (to, from) => {
|
|
163
|
+
if (to.includes('admin') && !isAuthenticated()) return false;
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
afterEach: [(to, from) => analytics.track('navigate', { to, from })],
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Per-route guards — on specific routes
|
|
172
|
+
defineFeaturePlugin({
|
|
173
|
+
routes: [
|
|
174
|
+
{
|
|
175
|
+
path: 'admin',
|
|
176
|
+
component: AdminPage,
|
|
177
|
+
beforeEnter: async (to, from) => {
|
|
178
|
+
const user = await fetchUser();
|
|
179
|
+
return user.isAdmin;
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
path: 'editor',
|
|
184
|
+
component: EditorPage,
|
|
185
|
+
beforeLeave: (to, from) => {
|
|
186
|
+
if (hasUnsavedChanges()) return confirm('Discard changes?');
|
|
187
|
+
return true;
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Guard execution order: `global beforeEach → target beforeEnter → source beforeLeave → global afterEach`
|
|
195
|
+
|
|
196
|
+
Guards have a 5s timeout — if a guard doesn't resolve, navigation is cancelled.
|
|
197
|
+
|
|
198
|
+
### Per-Route Transitions
|
|
199
|
+
|
|
200
|
+
Each route can have its own transition style and duration:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
defineFeaturePlugin({
|
|
204
|
+
routes: [
|
|
205
|
+
{ path: 'home', component: HomePage }, // default: slide 500ms
|
|
206
|
+
{ path: 'settings', component: SettingsPage, transition: 'fade', transitionDuration: 300 },
|
|
207
|
+
{ path: 'modal-page', component: ModalPage, transition: 'none' }, // instant, no animation
|
|
208
|
+
],
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Supported: `'slide'` (default), `'fade'`, `'none'`
|
|
213
|
+
|
|
214
|
+
### Navigation History
|
|
215
|
+
|
|
216
|
+
Opt-in browser-like history with back/forward:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
const config = {
|
|
220
|
+
history: { enabled: true, maxEntries: 50 },
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// In any component:
|
|
224
|
+
const router = useMicroRouter();
|
|
225
|
+
router.canGoBack?.value; // reactive boolean
|
|
226
|
+
router.canGoForward?.value; // reactive boolean
|
|
227
|
+
await router.historyBack?.();
|
|
228
|
+
await router.historyForward?.();
|
|
229
|
+
await router.historyGo?.(-2); // go back 2 entries
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
History truncates forward entries on new push (browser behavior).
|
|
233
|
+
|
|
234
|
+
### Type-Safe Routes
|
|
235
|
+
|
|
236
|
+
#### Register Pattern (Recommended)
|
|
237
|
+
|
|
238
|
+
Use `Register` module augmentation to declare your plugin type once. Then `useMicroRouter()` auto-infers everywhere — no generics needed.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// app-plugin.ts — declare once with `as const`
|
|
242
|
+
export const appPlugin = defineFeaturePlugin({
|
|
243
|
+
name: 'my-app',
|
|
244
|
+
routes: [
|
|
245
|
+
{ path: 'home', component: HomePage },
|
|
246
|
+
{ path: 'profile', component: ProfilePage },
|
|
247
|
+
],
|
|
248
|
+
dialogs: [{ path: 'confirm', component: ConfirmDialog, activated: false }],
|
|
249
|
+
controls: [{ name: 'main_hud', component: MainHUD, activated: false }],
|
|
250
|
+
} as const);
|
|
251
|
+
|
|
252
|
+
// Declare module once — this is the ONLY place you write the type
|
|
253
|
+
declare module 'vue-micro-router' {
|
|
254
|
+
interface Register {
|
|
255
|
+
plugin: typeof appPlugin;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Then everywhere:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// Any component — fully typed, zero generics
|
|
264
|
+
const { push, openDialog, toggleControl } = useMicroRouter();
|
|
265
|
+
push('profile'); // ✅ OK
|
|
266
|
+
push('typo'); // ❌ TS Error
|
|
267
|
+
openDialog('confirm'); // ✅ OK
|
|
268
|
+
toggleControl('main_hud', true); // ✅ OK
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Benefits:** Type-safe push/openDialog/closeDialog/toggleControl. No duplication. One declaration fixes all usages.
|
|
272
|
+
|
|
273
|
+
#### Manual Route Map (Alternative)
|
|
274
|
+
|
|
275
|
+
For simple cases or when you only need typed props (not route names):
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
interface AppRoutes {
|
|
279
|
+
home: undefined;
|
|
280
|
+
profile: { userId: number };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const router = useMicroRouter<AppRoutes>();
|
|
284
|
+
router.push('profile', { userId: 42 }); // ✅ OK
|
|
285
|
+
router.push('profile'); // ❌ TS Error: missing props
|
|
286
|
+
router.push('unknown'); // ❌ TS Error: unknown route
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Untyped:** `useMicroRouter()` without Register returns `MicroRouterStore` with untyped methods.
|
|
290
|
+
|
|
291
|
+
### State Serialization
|
|
292
|
+
|
|
293
|
+
Save and restore full router state for session persistence:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
const router = useMicroRouter();
|
|
297
|
+
|
|
298
|
+
// Save state (e.g., on page hide)
|
|
299
|
+
const snapshot = router.serialize!();
|
|
300
|
+
localStorage.setItem('router-state', JSON.stringify(snapshot));
|
|
301
|
+
|
|
302
|
+
// Restore state (e.g., on page load)
|
|
303
|
+
const saved = localStorage.getItem('router-state');
|
|
304
|
+
if (saved) await router.restore!(JSON.parse(saved));
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Serializes: navigation path + attrs, dialog stack + attrs, control state + attrs.
|
|
308
|
+
|
|
309
|
+
### Route Preloading
|
|
310
|
+
|
|
311
|
+
Preload async route components before they're needed:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
defineFeaturePlugin({
|
|
315
|
+
routes: [
|
|
316
|
+
{ path: 'home', component: () => import('./Home.vue') },
|
|
317
|
+
{ path: 'shop', component: () => import('./Shop.vue'), preload: 'eager' }, // loads on mount
|
|
318
|
+
{ path: 'cart', component: () => import('./Cart.vue'), preload: 'adjacent' }, // loads after each nav
|
|
319
|
+
],
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Manual preload
|
|
323
|
+
await router.preloadRoute('cart');
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Gesture Navigation (Swipe Back)
|
|
327
|
+
|
|
328
|
+
iOS-style swipe-back from the left edge:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const config = {
|
|
332
|
+
gesture: {
|
|
333
|
+
enabled: true,
|
|
334
|
+
edgeWidth: 20, // px from left edge (default: 20)
|
|
335
|
+
threshold: 0.3, // 30% screen width to trigger (default: 0.3)
|
|
336
|
+
velocityThreshold: 0.5, // px/ms fast swipe (default: 0.5)
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Nested Routers
|
|
342
|
+
|
|
343
|
+
Independent router instances within the same component tree:
|
|
344
|
+
|
|
345
|
+
```vue
|
|
346
|
+
<template>
|
|
347
|
+
<!-- Root router -->
|
|
348
|
+
<MicroRouterView :config="rootConfig" :plugins="[mainPlugin]">
|
|
349
|
+
<!-- Inside a page component: -->
|
|
350
|
+
<MicroRouterView nested :config="tabConfig" :plugins="[tabPlugin]" />
|
|
351
|
+
</MicroRouterView>
|
|
352
|
+
</template>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
// Access root router from within nested router
|
|
357
|
+
const rootRouter = useMicroRouter({ root: true });
|
|
358
|
+
const localRouter = useMicroRouter(); // nearest parent
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Step-Wise Navigation
|
|
362
|
+
|
|
363
|
+
Animate through intermediate pages one-by-one:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
const { stepWisePush, stepWiseBack } = useMicroRouter();
|
|
367
|
+
|
|
368
|
+
// Walk through: home → home/onboarding → home/onboarding/step1
|
|
369
|
+
await stepWisePush('/home/onboarding/step1');
|
|
370
|
+
|
|
371
|
+
// Step back through each page with animation
|
|
372
|
+
await stepWiseBack(3);
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Dialogs
|
|
376
|
+
|
|
377
|
+
Open modal dialogs with props and handle close:
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
const { openDialog, closeDialog, closeAllDialogs } = useMicroRouter();
|
|
381
|
+
|
|
382
|
+
// Open with props
|
|
383
|
+
openDialog('confirm', {
|
|
384
|
+
title: 'Delete item?',
|
|
385
|
+
onConfirm: () => handleDelete(),
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Close specific dialog
|
|
389
|
+
closeDialog('confirm');
|
|
390
|
+
|
|
391
|
+
// Close all open dialogs
|
|
392
|
+
closeAllDialogs();
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Dialog options:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
registerDialog({
|
|
399
|
+
path: 'settings-modal',
|
|
400
|
+
component: SettingsModal,
|
|
401
|
+
activated: false,
|
|
402
|
+
position: 'right', // 'standard' | 'top' | 'right' | 'bottom' | 'left'
|
|
403
|
+
transition: 'slide', // 'fade' | 'slide' | 'scale'
|
|
404
|
+
transitionDuration: 400,
|
|
405
|
+
fullscreen: false,
|
|
406
|
+
persistent: true, // prevent close on backdrop click / Escape
|
|
407
|
+
seamless: true, // transparent background, no shadow
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### GUI Controls
|
|
412
|
+
|
|
413
|
+
Persistent overlay controls (HUDs, toolbars) that auto-manage visibility:
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
const { toggleControl } = useMicroRouter();
|
|
417
|
+
|
|
418
|
+
// Show inventory HUD (auto-hides default main_gui)
|
|
419
|
+
toggleControl('inventory', true, { filter: 'weapons' });
|
|
420
|
+
|
|
421
|
+
// Hide inventory (auto-restores main_gui)
|
|
422
|
+
toggleControl('inventory', false);
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Lifecycle Hooks
|
|
426
|
+
|
|
427
|
+
iOS-style `viewWillAppear` / `viewWillDisappear` — available for routes, dialogs, and controls:
|
|
428
|
+
|
|
429
|
+
```vue
|
|
430
|
+
<script setup>
|
|
431
|
+
import { useRouteLifecycle } from 'vue-micro-router';
|
|
432
|
+
|
|
433
|
+
useRouteLifecycle({
|
|
434
|
+
onRouteEnter: () => console.log('Page is now the active (top) page'),
|
|
435
|
+
onRouteLeave: () => console.log('Page is no longer the active page'),
|
|
436
|
+
});
|
|
437
|
+
</script>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Also: `useDialogLifecycle({ onDialogEnter, onDialogLeave })` and `useControlLifecycle({ onControlEnter, onControlLeave })`.
|
|
441
|
+
|
|
442
|
+
### Feature Plugins
|
|
443
|
+
|
|
444
|
+
Bundle routes, dialogs, and controls into feature modules:
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
import { defineFeaturePlugin } from 'vue-micro-router';
|
|
448
|
+
|
|
449
|
+
export const shopPlugin = defineFeaturePlugin({
|
|
450
|
+
name: 'shop',
|
|
451
|
+
routes: [
|
|
452
|
+
{ path: 'shop', component: () => import('./ShopPage.vue'), preload: 'eager' },
|
|
453
|
+
{ path: 'cart', component: () => import('./CartPage.vue'), preload: 'adjacent' },
|
|
454
|
+
],
|
|
455
|
+
dialogs: [
|
|
456
|
+
{ path: 'buy-confirm', component: () => import('./BuyConfirm.vue'), activated: false },
|
|
457
|
+
],
|
|
458
|
+
controls: [
|
|
459
|
+
{ name: 'shop_hud', component: () => import('./ShopHUD.vue'), activated: false },
|
|
460
|
+
],
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Analytics / Page Tracking
|
|
465
|
+
|
|
466
|
+
Hook into navigation events:
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
const config: MicroRouterConfig = {
|
|
470
|
+
tracker: {
|
|
471
|
+
trackPageEnter: (page, from, to) => analytics.track('page_view', { page }),
|
|
472
|
+
trackPageLeave: (page, from, to) => analytics.track('page_leave', { page }),
|
|
473
|
+
trackDialogEnter: (dialog) => analytics.track('dialog_open', { dialog }),
|
|
474
|
+
trackDialogLeave: (dialog) => analytics.track('dialog_close', { dialog }),
|
|
475
|
+
trackGuiEnter: (name) => analytics.track('gui_show', { name }),
|
|
476
|
+
trackGuiLeave: (name) => analytics.track('gui_hide', { name }),
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Vue Devtools
|
|
482
|
+
|
|
483
|
+
Automatic in development — shows a "Micro Router" inspector tab with:
|
|
484
|
+
- Current path and page stack
|
|
485
|
+
- Open dialogs with attrs
|
|
486
|
+
- Active controls
|
|
487
|
+
- Navigation timeline events
|
|
488
|
+
|
|
489
|
+
Requires `@vue/devtools-api` (optional peer dependency). Zero cost in production builds.
|
|
490
|
+
|
|
491
|
+
## Optional: Audio Manager
|
|
492
|
+
|
|
493
|
+
Background music tied to route BGM fields. Supports custom audio backends via `AudioAdapter`:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
bun add howler
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
import { useAudioManager, HowlerAdapter } from 'vue-micro-router/audio';
|
|
501
|
+
|
|
502
|
+
const audio = useAudioManager({
|
|
503
|
+
volumeRef: ref(80),
|
|
504
|
+
urlResolver: (name) => `/assets/audio/${name}.mp3`,
|
|
505
|
+
// adapter: new HowlerAdapter(), // default — or provide your own AudioAdapter
|
|
506
|
+
});
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Custom adapter:
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
import type { AudioAdapter } from 'vue-micro-router/audio';
|
|
513
|
+
|
|
514
|
+
class WebAudioAdapter implements AudioAdapter {
|
|
515
|
+
async play(src, options) { /* Web Audio API */ }
|
|
516
|
+
stop() { /* ... */ }
|
|
517
|
+
pause() { /* ... */ }
|
|
518
|
+
resume() { /* ... */ }
|
|
519
|
+
fade(from, to, duration) { /* ... */ }
|
|
520
|
+
isPlaying() { return false; }
|
|
521
|
+
state() { return 'unloaded'; }
|
|
522
|
+
cleanup() { /* ... */ }
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const audio = useAudioManager({ adapter: new WebAudioAdapter() });
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## Styles
|
|
529
|
+
|
|
530
|
+
Import styles separately (not bundled with JS):
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
import 'vue-micro-router/styles';
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Includes page slide/fade transitions, dialog animations, control fade transitions, and GUI layer positioning.
|
|
537
|
+
|
|
538
|
+
## Development
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
bun run lint # ESLint check
|
|
542
|
+
bun run lint:fix # Auto-fix lint issues
|
|
543
|
+
bun test # Run all tests
|
|
544
|
+
bun run typecheck # TypeScript strict check
|
|
545
|
+
bun run build # Build package
|
|
546
|
+
bun run dev:example # Run example app
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## API Reference
|
|
550
|
+
|
|
551
|
+
### Composables
|
|
552
|
+
|
|
553
|
+
| Composable | Description |
|
|
554
|
+
|-----------|-------------|
|
|
555
|
+
| `useGlobalMicroRouter(config?)` | Create & provide store. Call once in root. |
|
|
556
|
+
| `useMicroRouter(options?)` | Inject store. Pass `{ root: true }` for root in nested setups. |
|
|
557
|
+
| `useMicroRouter()` | Auto-typed via `Register` augmentation. Explicit `<T>` generic also supported. |
|
|
558
|
+
| `useMicroState<T>(defaults?)` | Reactive attrs bridge — read/write props in routes, dialogs, controls. |
|
|
559
|
+
| `useRouteLifecycle(hooks)` | `onRouteEnter` / `onRouteLeave` — fires when page becomes/stops being top. |
|
|
560
|
+
| `useDialogLifecycle(hooks)` | `onDialogEnter` / `onDialogLeave` — fires when dialog becomes/stops being topmost. |
|
|
561
|
+
| `useControlLifecycle(hooks)` | `onControlEnter` / `onControlLeave` — fires when control activates/deactivates. |
|
|
562
|
+
| `usePageTracker(hooks?)` | Normalize tracker hooks with no-op fallbacks. |
|
|
563
|
+
| `useNavigation(config?, tracker?)` | Low-level page navigation (used internally). |
|
|
564
|
+
| `useDialogManager(tracker?)` | Low-level dialog management (used internally). |
|
|
565
|
+
| `useControlManager(config?, tracker?)` | Low-level control management (used internally). |
|
|
566
|
+
| `useGestureNavigation(config, ctx)` | Swipe-back gesture handler (used internally by MicroRouterView). |
|
|
567
|
+
|
|
568
|
+
### Components
|
|
569
|
+
|
|
570
|
+
| Component | Description |
|
|
571
|
+
|-----------|-------------|
|
|
572
|
+
| `<MicroRouterView>` | Root wrapper — renders pages, dialogs, controls. Accepts `nested` prop. |
|
|
573
|
+
| `<RoutePage>` | Page slot wrapper — provides attrs injection. |
|
|
574
|
+
| `<MicroDialogComponent>` | Headless native `<dialog>` — focus trap, backdrop, escape key. |
|
|
575
|
+
| `<MicroControlWrapper>` | Control slot wrapper — provides attrs injection. |
|
|
576
|
+
|
|
577
|
+
### Plugin Helpers
|
|
578
|
+
|
|
579
|
+
| Function | Description |
|
|
580
|
+
|----------|-------------|
|
|
581
|
+
| `defineFeaturePlugin(config)` | Create a feature plugin bundle. |
|
|
582
|
+
| `registerFeaturePlugins(plugins, store)` | Register all plugins with the store. |
|
|
583
|
+
|
|
584
|
+
### Types
|
|
585
|
+
|
|
586
|
+
| Type | Description |
|
|
587
|
+
|------|-------------|
|
|
588
|
+
| `NavigationGuard` | `(to, from) => boolean \| Promise<boolean>` |
|
|
589
|
+
| `NavigationAfterHook` | `(to, from) => void` |
|
|
590
|
+
| `RouteMap` | `Record<string, Record<string, unknown> \| undefined>` |
|
|
591
|
+
| `TypedPush<T>` | Type-safe push overloads for a RouteMap |
|
|
592
|
+
| `SerializedState` | JSON-serializable router state snapshot |
|
|
593
|
+
| `AudioAdapter` | Abstract audio playback interface |
|
|
594
|
+
| `GestureConfig` | Gesture navigation configuration |
|
|
595
|
+
|
|
596
|
+
## Performance
|
|
597
|
+
|
|
598
|
+
| Metric | Value |
|
|
599
|
+
|--------|-------|
|
|
600
|
+
| Core bundle (gzip) | 10.35 kB |
|
|
601
|
+
| Audio (gzip) | 1.07 kB |
|
|
602
|
+
| Styles (gzip) | 0.84 kB |
|
|
603
|
+
| Navigation latency (p50) | < 0.01 ms |
|
|
604
|
+
| Tests | 150 passing |
|
|
605
|
+
| Coverage | ~96% |
|
|
606
|
+
|
|
607
|
+
Run benchmarks: `bun run bench` (navigation timing) / `bun run bench:size` (bundle check)
|
|
608
|
+
|
|
609
|
+
## Peer Dependencies
|
|
610
|
+
|
|
611
|
+
- `vue` >= 3.4.0
|
|
612
|
+
- `howler` >= 2.2.0 *(optional — only for `vue-micro-router/audio`)*
|
|
613
|
+
- `@vue/devtools-api` >= 6.0.0 *(optional — only for devtools inspector)*
|
|
614
|
+
|
|
615
|
+
## License
|
|
616
|
+
|
|
617
|
+
MIT
|