@lazycatcloud/vue-router-stack 0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LazycatCloud
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,282 @@
1
+ # @lazycatcloud/vue-router-stack
2
+
3
+ 基于 Vue 3 和 vue-router 的页面栈路由库。它保留 vue-router 的路由定义和导航能力,同时提供类似移动端页面栈的 DOM 缓存、转场、页面生命周期和调试面板。
4
+
5
+ 当前版本适合浏览器端应用使用,暂不处理 SSR、手势返回识别、每个 tab 独立历史栈。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ pnpm add @lazycatcloud/vue-router-stack vue vue-router
11
+ ```
12
+
13
+ 入口处引入样式:
14
+
15
+ ```ts
16
+ import "@lazycatcloud/vue-router-stack/style.css";
17
+ ```
18
+
19
+ ## 基础使用
20
+
21
+ 创建 router 时使用 `createStackRouter`,其余路由写法保持 vue-router 风格。
22
+
23
+ ```ts
24
+ import {
25
+ createStackRouter,
26
+ createWebHistory,
27
+ RouterView,
28
+ } from "@lazycatcloud/vue-router-stack";
29
+
30
+ export const router = createStackRouter({
31
+ history: createWebHistory(),
32
+ transition: "slide",
33
+ transitionDuration: 240,
34
+ routes: [
35
+ {
36
+ path: "/",
37
+ component: HomePage,
38
+ },
39
+ {
40
+ path: "/album",
41
+ component: RouterView,
42
+ children: [
43
+ { path: "detail", component: AlbumDetail },
44
+ { path: "map", component: AlbumMap },
45
+ ],
46
+ },
47
+ ],
48
+ });
49
+ ```
50
+
51
+ 应用入口和普通 vue-router 一样:
52
+
53
+ ```ts
54
+ import { createApp } from "vue";
55
+ import App from "./App.vue";
56
+ import { router } from "./router";
57
+
58
+ createApp(App).use(router).mount("#app");
59
+ ```
60
+
61
+ 页面出口不要直接使用普通 `<router-view>`,使用 `RouterOutlet`:
62
+
63
+ ```vue
64
+ <script setup lang="ts">
65
+ import { RouterOutlet } from "@lazycatcloud/vue-router-stack";
66
+ </script>
67
+
68
+ <template>
69
+ <RouterOutlet />
70
+ </template>
71
+ ```
72
+
73
+ 页面组件根节点使用 `RouterPage`:
74
+
75
+ ```vue
76
+ <script setup lang="ts">
77
+ import { RouterPage } from "@lazycatcloud/vue-router-stack";
78
+ </script>
79
+
80
+ <template>
81
+ <RouterPage>
82
+ <h1>Album Detail</h1>
83
+ </RouterPage>
84
+ </template>
85
+ ```
86
+
87
+ ## 导航 API
88
+
89
+ 在组件里使用 `useStackRouter()`:
90
+
91
+ ```ts
92
+ import { useStackRouter } from "@lazycatcloud/vue-router-stack";
93
+
94
+ const stackRouter = useStackRouter();
95
+
96
+ stackRouter.push("/album/detail");
97
+ stackRouter.replace("/album/detail");
98
+ stackRouter.back();
99
+ stackRouter.forward();
100
+ stackRouter.popTo("/album/detail");
101
+ stackRouter.resetRoot("/");
102
+ ```
103
+
104
+ 单次导航可以控制方向、动画、时长和缓动:
105
+
106
+ ```ts
107
+ stackRouter.push("/search/result", {
108
+ transition: "fade",
109
+ });
110
+
111
+ stackRouter.push("/timeline", {
112
+ direction: "none",
113
+ });
114
+
115
+ stackRouter.push("/detail", {
116
+ transition: "sheet",
117
+ duration: 300,
118
+ easing: "ease-out",
119
+ });
120
+ ```
121
+
122
+ `direction: "none"` 和 `direction: "root"` 会强制不播放转场,适合 tab 切换和清栈回首页。
123
+
124
+ ## 转场动画
125
+
126
+ 内置动画:
127
+
128
+ - `slide`
129
+ - `fade`
130
+ - `scale`
131
+ - `none`
132
+
133
+ 可以通过 `defineStackTransition` 注册自定义动画:
134
+
135
+ ```ts
136
+ import { createStackRouter, defineStackTransition } from "@lazycatcloud/vue-router-stack";
137
+
138
+ export const router = createStackRouter({
139
+ history: createWebHistory(),
140
+ transition: "slide",
141
+ transitions: {
142
+ sheet: defineStackTransition(({ direction }) => ({
143
+ entering:
144
+ direction === "back"
145
+ ? [{ transform: "translateY(-8%)" }, { transform: "translateY(0)" }]
146
+ : [{ transform: "translateY(100%)" }, { transform: "translateY(0)" }],
147
+ leaving:
148
+ direction === "back"
149
+ ? [{ transform: "translateY(0)" }, { transform: "translateY(100%)" }]
150
+ : [{ transform: "translateY(0)" }, { transform: "translateY(-8%)" }],
151
+ })),
152
+ },
153
+ routes,
154
+ });
155
+ ```
156
+
157
+ 返回时会优先使用离开页面进入时记录的动画。例如用 `sheet` 打开详情页,返回时会按 `sheet` 收起。
158
+
159
+ ## 转场决策
160
+
161
+ `shouldTransition` 用于业务侧决定本次导航是否播放动画。库只提供上下文,不内置 iOS 或具体设备判断。
162
+
163
+ ```ts
164
+ const isIOS =
165
+ /iPad|iPhone|iPod/.test(navigator.userAgent) ||
166
+ (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
167
+
168
+ export const router = createStackRouter({
169
+ history: createWebHistory(),
170
+ shouldTransition(ctx) {
171
+ if (isIOS && ctx.source === "external") return false;
172
+ return ctx.defaultResult;
173
+ },
174
+ routes,
175
+ });
176
+ ```
177
+
178
+ `ctx.source` 有两个值:
179
+
180
+ - `stack`:由 `stackRouter.push/back/popTo/resetRoot` 等库 API 触发。
181
+ - `external`:由浏览器 history 或系统手势等外部来源触发。
182
+
183
+ ## 页面生命周期
184
+
185
+ 页面被栈缓存时不会频繁卸载,可以使用页面生命周期处理进入和离开:
186
+
187
+ ```ts
188
+ import {
189
+ onPageDidEnter,
190
+ onPageDidLeave,
191
+ onPageWillEnter,
192
+ onPageWillLeave,
193
+ } from "@lazycatcloud/vue-router-stack";
194
+
195
+ onPageWillEnter(() => {});
196
+ onPageDidEnter(() => {});
197
+ onPageWillLeave(() => {});
198
+ onPageDidLeave(() => {});
199
+ ```
200
+
201
+ 触发顺序保持为:
202
+
203
+ 1. `onPageWillEnter`
204
+ 2. `onPageWillLeave`
205
+ 3. 转场动画
206
+ 4. `onPageDidLeave`
207
+ 5. `onPageDidEnter`
208
+
209
+ ## 缓存策略
210
+
211
+ 默认页面会缓存,返回时复用旧实例并保留滚动位置和组件状态。
212
+
213
+ 可以在路由 meta 中关闭某个页面的缓存:
214
+
215
+ ```ts
216
+ {
217
+ path: "/search/result",
218
+ component: SearchResult,
219
+ meta: {
220
+ stackCache: false,
221
+ },
222
+ }
223
+ ```
224
+
225
+ 也可以使用嵌套写法:
226
+
227
+ ```ts
228
+ {
229
+ path: "/search/result",
230
+ component: SearchResult,
231
+ meta: {
232
+ stack: {
233
+ cache: false,
234
+ },
235
+ },
236
+ }
237
+ ```
238
+
239
+ 通过 `maxStackSize` 限制最大缓存栈长度:
240
+
241
+ ```ts
242
+ createStackRouter({
243
+ history: createWebHistory(),
244
+ maxStackSize: 30,
245
+ routes,
246
+ });
247
+ ```
248
+
249
+ ## 调试面板
250
+
251
+ 开发时可以挂载 `StackRouterDebugPanel` 查看当前栈、缓存、转场和导航来源:
252
+
253
+ ```vue
254
+ <script setup lang="ts">
255
+ import { StackRouterDebugPanel } from "@lazycatcloud/vue-router-stack";
256
+ </script>
257
+
258
+ <template>
259
+ <RouterView />
260
+ <StackRouterDebugPanel />
261
+ </template>
262
+ ```
263
+
264
+ ## Demo
265
+
266
+ 本仓库内置 PC 和 Mobile 两个 demo:
267
+
268
+ - PC:左侧侧边栏 + 右侧内容区
269
+ - Mobile:顶部内容区 + 底部栏切换
270
+
271
+ ```bash
272
+ pnpm install
273
+ pnpm dev
274
+ ```
275
+
276
+ ## 验证
277
+
278
+ ```bash
279
+ pnpm run type-check
280
+ pnpm test
281
+ pnpm run build
282
+ ```
@@ -0,0 +1,20 @@
1
+ import { type Component, type ComponentPublicInstance, type Ref } from "vue";
2
+ import type { RouteLocationMatched } from "vue-router";
3
+ import type { RouteInfo } from "../router/types";
4
+ interface ViewItem {
5
+ id: string;
6
+ key: string;
7
+ routeInfo: RouteInfo;
8
+ matchedRoute: RouteLocationMatched;
9
+ component: Component;
10
+ props: Record<string, unknown>;
11
+ pageElement?: HTMLElement;
12
+ componentRef: Ref<ComponentPublicInstance | undefined>;
13
+ mounted: boolean;
14
+ }
15
+ export declare const RouterOutlet: import("vue").DefineComponent<{}, {
16
+ injectedRoute: import("vue-router").RouteLocationNormalizedLoadedGeneric;
17
+ viewItems: import("vue").ShallowRef<ViewItem[], ViewItem[]>;
18
+ registerStackPage: (viewItem: ViewItem, element: HTMLElement) => void;
19
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
20
+ export {};
@@ -0,0 +1,15 @@
1
+ export declare const RouterPage: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
+ registerStackPage: {
3
+ type: FunctionConstructor;
4
+ default: undefined;
5
+ };
6
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
7
+ [key: string]: any;
8
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
9
+ registerStackPage: {
10
+ type: FunctionConstructor;
11
+ default: undefined;
12
+ };
13
+ }>> & Readonly<{}>, {
14
+ registerStackPage: Function;
15
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,15 @@
1
+ export declare const StackRouterDebugPanel: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
+ initialOpen: {
3
+ type: BooleanConstructor;
4
+ default: boolean;
5
+ };
6
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
7
+ [key: string]: any;
8
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
9
+ initialOpen: {
10
+ type: BooleanConstructor;
11
+ default: boolean;
12
+ };
13
+ }>> & Readonly<{}>, {
14
+ initialOpen: boolean;
15
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,10 @@
1
+ import { type ComponentInternalInstance } from "vue";
2
+ export declare const PAGE_WILL_ENTER = "onPageWillEnter";
3
+ export declare const PAGE_DID_ENTER = "onPageDidEnter";
4
+ export declare const PAGE_WILL_LEAVE = "onPageWillLeave";
5
+ export declare const PAGE_DID_LEAVE = "onPageDidLeave";
6
+ export type PageLifecycleName = typeof PAGE_WILL_ENTER | typeof PAGE_DID_ENTER | typeof PAGE_WILL_LEAVE | typeof PAGE_DID_LEAVE;
7
+ export declare const onPageWillEnter: (hook: () => void, target?: ComponentInternalInstance | null) => void;
8
+ export declare const onPageDidEnter: (hook: () => void, target?: ComponentInternalInstance | null) => void;
9
+ export declare const onPageWillLeave: (hook: () => void, target?: ComponentInternalInstance | null) => void;
10
+ export declare const onPageDidLeave: (hook: () => void, target?: ComponentInternalInstance | null) => void;
@@ -0,0 +1,14 @@
1
+ import type { NavigationFailure, RouteLocationRaw } from "vue-router";
2
+ import type { StackNavigationOptions, StackRouter, StackVueRouter } from "../router/types";
3
+ export interface UseStackRouterResult {
4
+ router: StackVueRouter;
5
+ canGoBack: (deep?: number) => boolean;
6
+ push: (to: RouteLocationRaw, options?: StackNavigationOptions) => Promise<void | NavigationFailure | undefined>;
7
+ replace: (to: RouteLocationRaw, options?: StackNavigationOptions) => Promise<void | NavigationFailure | undefined>;
8
+ popTo: (to: RouteLocationRaw, options?: StackNavigationOptions) => boolean;
9
+ resetRoot: (to: RouteLocationRaw, options?: StackNavigationOptions) => Promise<void | NavigationFailure | undefined>;
10
+ back: () => void;
11
+ forward: () => void;
12
+ }
13
+ export declare function useStackRouter(): UseStackRouterResult;
14
+ export declare function useStackRouterInternal(): StackRouter;
@@ -0,0 +1,10 @@
1
+ import "./styles/core.css";
2
+ export { createStackRouter, stackRouterKey, } from "./router/createStackRouter";
3
+ export { type RouteAction, type RouteDirection, type RouteInfo, type StackNavigationOptions, type StackNavigationSource, type StackRouter, type StackRouterNavigation, type StackRouterOptions, type StackShouldTransition, type StackShouldTransitionContext, type StackTransitionContext, type StackTransitionDefinition, type StackTransitionFactory, type StackTransitionName, type StackTransitionRegistry, type StackVueRouter, } from "./router/types";
4
+ export { defineStackTransition } from "./router/transitions";
5
+ export { RouterOutlet } from "./components/RouterOutlet";
6
+ export { RouterPage } from "./components/RouterPage";
7
+ export { StackRouterDebugPanel } from "./components/StackRouterDebugPanel";
8
+ export { useStackRouter, type UseStackRouterResult } from "./composables/useStackRouter";
9
+ export { onPageDidEnter, onPageDidLeave, onPageWillEnter, onPageWillLeave, } from "./composables/lifecycle";
10
+ export { createWebHashHistory, createWebHistory, createMemoryHistory, RouterLink, RouterView, useRoute, useRouter, } from "vue-router";
@@ -0,0 +1,4 @@
1
+ import { InjectionKey } from "vue";
2
+ import type { StackRouter, StackRouterOptions, StackVueRouter } from "./types";
3
+ export declare const stackRouterKey: InjectionKey<StackRouter>;
4
+ export declare function createStackRouter(options: StackRouterOptions): StackVueRouter;
@@ -0,0 +1 @@
1
+ export declare function createId(type?: string): string;
@@ -0,0 +1,7 @@
1
+ import type { StackTransitionContext, StackTransitionFactory, StackTransitionName, StackTransitionRegistry } from "./types";
2
+ export declare const DEFAULT_STACK_TRANSITION = "slide";
3
+ export declare const DEFAULT_STACK_TRANSITION_EASING = "cubic-bezier(0.2, 0, 0, 1)";
4
+ export declare function defineStackTransition(factory: StackTransitionFactory): StackTransitionFactory;
5
+ export declare const builtinStackTransitions: StackTransitionRegistry;
6
+ export declare function resolveStackTransition(name: StackTransitionName, transitions: StackTransitionRegistry): StackTransitionFactory;
7
+ export declare function runStackTransition(factory: StackTransitionFactory, context: StackTransitionContext): Promise<void>;
@@ -0,0 +1,104 @@
1
+ import type { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, Router, RouterOptions } from "vue-router";
2
+ declare module "vue-router" {
3
+ interface RouteMeta {
4
+ stackCache?: boolean;
5
+ stack?: {
6
+ cache?: boolean;
7
+ };
8
+ }
9
+ }
10
+ export type RouteAction = "push" | "pop" | "replace";
11
+ export type RouteDirection = "forward" | "back" | "root" | "none";
12
+ export type StackNavigationSource = "stack" | "external";
13
+ export type StackTransitionName = "slide" | "fade" | "scale" | "none" | (string & {});
14
+ export interface StackTransitionContext {
15
+ direction: RouteDirection;
16
+ action: RouteAction;
17
+ routeInfo: RouteInfo;
18
+ enteringElement: HTMLElement;
19
+ leavingElement: HTMLElement;
20
+ duration: number;
21
+ easing: string;
22
+ }
23
+ export interface StackShouldTransitionContext {
24
+ action: RouteAction;
25
+ direction: RouteDirection;
26
+ source: StackNavigationSource;
27
+ entering: RouteInfo;
28
+ leaving?: RouteInfo;
29
+ defaultResult: boolean;
30
+ }
31
+ export type StackShouldTransition = (context: StackShouldTransitionContext) => boolean;
32
+ export interface StackTransitionDefinition {
33
+ entering?: Keyframe[] | PropertyIndexedKeyframes;
34
+ leaving?: Keyframe[] | PropertyIndexedKeyframes;
35
+ options?: KeyframeAnimationOptions;
36
+ }
37
+ export type StackTransitionFactory = (context: StackTransitionContext) => StackTransitionDefinition;
38
+ export type StackTransitionRegistry = Record<string, StackTransitionFactory>;
39
+ export interface StackNavigationOptions {
40
+ direction?: RouteDirection;
41
+ transition?: StackTransitionName;
42
+ duration?: number;
43
+ easing?: string;
44
+ }
45
+ export interface RouteInfo {
46
+ id: string;
47
+ key: string;
48
+ fullPath: string;
49
+ pathname: string;
50
+ params: RouteLocationNormalizedLoaded["params"];
51
+ query: RouteLocationNormalizedLoaded["query"];
52
+ action: RouteAction;
53
+ direction: RouteDirection;
54
+ source: StackNavigationSource;
55
+ entryDirection: RouteDirection;
56
+ lastPathname?: string;
57
+ pushedByRoute?: string;
58
+ position: number;
59
+ delta?: number;
60
+ transition?: StackTransitionName;
61
+ transitionDuration?: number;
62
+ transitionEasing?: string;
63
+ cache: boolean;
64
+ isStackFallback?: boolean;
65
+ }
66
+ export interface StackRouterOptions extends RouterOptions {
67
+ transitionDuration?: number;
68
+ transitionEasing?: string;
69
+ transition?: StackTransitionName;
70
+ transitions?: StackTransitionRegistry;
71
+ maxStackSize?: number;
72
+ shouldTransition?: StackShouldTransition;
73
+ }
74
+ export interface PendingNavigation {
75
+ action: RouteAction;
76
+ direction: RouteDirection;
77
+ source: StackNavigationSource;
78
+ delta?: number;
79
+ transition?: StackTransitionName;
80
+ duration?: number;
81
+ easing?: string;
82
+ resetRoot?: boolean;
83
+ }
84
+ export interface StackRouterNavigation {
85
+ push: (to: RouteLocationRaw, options?: StackNavigationOptions) => Promise<void | NavigationFailure | undefined>;
86
+ replace: (to: RouteLocationRaw, options?: StackNavigationOptions) => Promise<void | NavigationFailure | undefined>;
87
+ popTo: (to: RouteLocationRaw, options?: StackNavigationOptions) => boolean;
88
+ resetRoot: (to: RouteLocationRaw, options?: StackNavigationOptions) => Promise<void | NavigationFailure | undefined>;
89
+ back: () => void;
90
+ forward: () => void;
91
+ }
92
+ export interface StackRouter extends StackRouterNavigation {
93
+ router: StackVueRouter;
94
+ transitionDuration: number;
95
+ transitionEasing: string;
96
+ transition: StackTransitionName;
97
+ transitions: StackTransitionRegistry;
98
+ maxStackSize: number;
99
+ shouldTransition?: StackShouldTransition;
100
+ current: Readonly<import("vue").ShallowRef<RouteInfo | undefined>>;
101
+ history: readonly RouteInfo[];
102
+ canGoBack: (deep?: number) => boolean;
103
+ }
104
+ export type StackVueRouter = Router & StackRouterNavigation;
@@ -0,0 +1,3 @@
1
+ import type { ComponentPublicInstance, Ref } from "vue";
2
+ import type { PageLifecycleName } from "../composables/lifecycle";
3
+ export declare function fireLifecycle(componentRef: Ref<ComponentPublicInstance | undefined>, lifecycle: PageLifecycleName): void;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("vue"),b=require("vue-router"),J={};function z(e="route"){const r=(J[e]??0)+1;return J[e]=r,`${e}-${r}`}const v="slide",re="cubic-bezier(0.2, 0, 0, 1)";function oe(e){return e}const ne=({direction:e})=>{const r=e==="back";return{entering:r?[{transform:"translateX(-18%)",opacity:.96},{transform:"translateX(0)",opacity:1}]:[{transform:"translateX(100%)"},{transform:"translateX(0)"}],leaving:r?[{transform:"translateX(0)",opacity:1},{transform:"translateX(100%)",opacity:1}]:[{transform:"translateX(0)",opacity:1},{transform:"translateX(-18%)",opacity:.96}]}},ae=()=>({entering:[{opacity:0},{opacity:1}],leaving:[{opacity:1},{opacity:0}]}),ce=({direction:e})=>{const r=e==="back";return{entering:r?[{transform:"scale(0.98)",opacity:0},{transform:"scale(1)",opacity:1}]:[{transform:"scale(1.04)",opacity:0},{transform:"scale(1)",opacity:1}],leaving:r?[{transform:"scale(1)",opacity:1},{transform:"scale(1.04)",opacity:0}]:[{transform:"scale(1)",opacity:1},{transform:"scale(0.98)",opacity:0}]}},ie=()=>({}),se={slide:ne,fade:ae,scale:ce,none:ie};function ue(e,r){const n=r[e];return n||r[v]}function Q(e,r,n){if(!r||!e.animate||Number(n.duration)<=0)return Promise.resolve();const l=e.animate(r,n);return l.finished.then(()=>l.cancel())}async function le(e,r){const n=e(r),l={duration:r.duration,easing:r.easing,fill:"both",...n.options};await Promise.all([Q(r.enteringElement,n.entering,l),Q(r.leavingElement,n.leaving,l)])}const H=Symbol("stack-router");function fe(){const e=window.history.state;return typeof(e==null?void 0:e.position)=="number"?e.position:window.history.length}function M(e,r){return r||(e==="pop"?"back":e==="replace"?"none":"forward")}function de(e){const r=b.createRouter(e),n=[],l=i.shallowRef(),f=e.transitionDuration??240,T=e.transitionEasing??re,S=e.transition??v,p={...se,...e.transitions},E=e.maxStackSize??30;let d=-1,s,g=!1;const P=(c,a,u,h,k=!1,w="stack")=>{s={action:c,direction:M(c,a),source:w,delta:u,transition:h==null?void 0:h.transition,duration:h==null?void 0:h.duration,easing:h==null?void 0:h.easing,resetRoot:k}},F=(c,a)=>{const u=(s==null?void 0:s.action)??"push",h=M(u,s==null?void 0:s.direction),k=l.value,w=u==="push"&&h==="forward",te=L(c);return{id:z("route"),key:c.fullPath,fullPath:c.fullPath,pathname:c.path,params:c.params,query:c.query,action:u,direction:h,source:(s==null?void 0:s.source)??"stack",entryDirection:h,lastPathname:(k==null?void 0:k.pathname)||(a.name?a.path:void 0),pushedByRoute:w?k==null?void 0:k.pathname:k==null?void 0:k.pushedByRoute,position:fe(),delta:s==null?void 0:s.delta,transition:(s==null?void 0:s.transition)??S,transitionDuration:(s==null?void 0:s.duration)??f,transitionEasing:(s==null?void 0:s.easing)??T,cache:te}},L=c=>{const a=c.meta.stack;return c.meta.stackCache!==!1&&(a==null?void 0:a.cache)!==!1},x=()=>{if(E<=0||n.length<=E)return;const c=n.length-E;n.splice(0,c),d=Math.max(0,d-c)},W=c=>{if(c.action==="pop"){let a=-1;if(c.direction==="forward"||(c.delta??0)>0){for(let u=d+1;u<n.length;u++)if(n[u].key===c.key){a=u;break}}else for(let u=d-1;u>=0;u--)if(n[u].key===c.key){a=u;break}if(a>=0){const u=n[a];n[a]={...u,...c,id:u.id,source:c.source,entryDirection:u.entryDirection,transition:u.transition,transitionDuration:u.transitionDuration,transitionEasing:u.transitionEasing},d=a}else n.push({...c,direction:"none",entryDirection:"none",transition:"none",isStackFallback:!0}),d=n.length-1}else if(c.action==="replace"){if(s!=null&&s.resetRoot){n.splice(0,n.length,c),d=0,l.value=n[d];return}d>=0?n[d]=c:(n.push(c),d=0)}else d<n.length-1&&n.splice(d+1),n.push(c),d=n.length-1;x(),l.value=n[d]};e.history.listen((c,a,u)=>{const h="delta"in u?u.delta:void 0,k=u.direction==="back"?"back":u.direction==="forward"?"forward":void 0;s?(s.delta=h,s.direction=M(s.action,s.direction??k)):P(u.type==="pop"?"pop":u.type,k,h,void 0,!1,"external")}),r.beforeEach(c=>!((s==null?void 0:s.action)==="pop"&&g&&!n.some(a=>a.fullPath===c.fullPath))),r.afterEach((c,a,u)=>{if(u){s=void 0;return}W(F(c,a)),s=void 0});const B=r.push.bind(r),A=r.replace.bind(r),G=r.back.bind(r),V=r.forward.bind(r),$=r.go.bind(r),j=(c,a)=>(P("push",(a==null?void 0:a.direction)??"forward",void 0,a),B(c)),K=(c,a)=>(P("replace",(a==null?void 0:a.direction)??"root",void 0,a),A(c)),t=(c,a)=>{const u=r.resolve(c).fullPath;let h=-1;for(let w=d;w>=0;w--)if(n[w].fullPath===u){h=w;break}if(h<0)return!1;const k=h-d;return k===0||(P("pop",(a==null?void 0:a.direction)??"back",k,a),$(k)),!0},o=(c,a)=>{const u=r.resolve(c).fullPath;if(d>=0&&r.currentRoute.value.fullPath===u){const h=n[d],k={...h,id:z("route"),action:"replace",direction:(a==null?void 0:a.direction)??"root",source:"stack",entryDirection:(a==null?void 0:a.direction)??"root",delta:void 0,transition:(a==null?void 0:a.transition)??h.transition,transitionDuration:(a==null?void 0:a.duration)??h.transitionDuration,transitionEasing:(a==null?void 0:a.easing)??h.transitionEasing,cache:!0,isStackFallback:!1};return n.splice(0,n.length,k),d=0,g=!0,l.value=k,Promise.resolve(void 0)}return P("replace",(a==null?void 0:a.direction)??"root",void 0,a,!0),g=!0,A(c)},m=()=>{P("pop","back",-1),G()},R=()=>{P("pop","forward",1),V()},y=r;y.push=j,y.replace=K,y.popTo=t,y.resetRoot=o,y.back=m,y.forward=R;const _={router:y,transitionDuration:f,transitionEasing:T,transition:S,transitions:p,maxStackSize:E,shouldTransition:e.shouldTransition,current:l,get history(){return n},canGoBack:(c=1)=>d-c>=0,push:j,replace:K,popTo:t,resetRoot:o,back:m,forward:R},X=r.install.bind(r);return r.install=c=>{c.provide(H,_),X(c)},y}const Y="onPageWillEnter",U="onPageDidEnter",Z="onPageWillLeave",N="onPageDidLeave";function he(e,r,n){if(!n)throw new Error(`${e} must be called inside setup().`);const l=n.proxy,f=l[e]??[];f.push(()=>{n.isUnmounted||r()}),l[e]=f,n.exposed&&(n.exposed[e]=f)}function C(e){return(r,n=i.getCurrentInstance())=>he(e,r,n)}const me=C(Y),ye=C(U),ke=C(Z),pe=C(N);function Re(){const e=i.inject(H);if(!e)throw new Error("useStackRouter must be used after createStackRouter().");return{router:e.router,canGoBack:e.canGoBack,push:e.push,replace:e.replace,popTo:e.popTo,resetRoot:e.resetRoot,back:e.back,forward:e.forward}}function O(){const e=i.inject(H);if(!e)throw new Error("RouterOutlet must be used with createStackRouter().");return e}function D(e,r){const n=e.value;if(!n)return;const l=n[r];Array.isArray(l)?l.forEach(f=>f()):typeof l=="function"&&l.call(n)}function I(e){e.classList.remove("rs-page-hidden","rs-page-invisible"),e.removeAttribute("aria-hidden")}function q(e){e.classList.add("rs-page-hidden"),e.setAttribute("aria-hidden","true")}function ee(e){var r;return(r=e.components)==null?void 0:r.default}function be(e){const r=ee(e);return r!==void 0&&typeof r=="object"&&"name"in r&&r.name==="RouterView"}const Pe=i.defineComponent({name:"RouterOutlet",setup(){const e=O(),r=b.useRoute(),n=i.inject(b.routeLocationKey),l=i.inject(b.matchedRouteKey,void 0),f=i.computed(()=>{const t=l==null?void 0:l.value;if(!t)return 0;const o=r.matched.findIndex(m=>m===t);return o>=0?o+1:0}),T=i.computed(()=>r.matched[f.value]),S=i.computed(()=>{for(let t=f.value;t<r.matched.length;t++){const o=r.matched[t];if(!be(o))return o}return T.value}),p=i.shallowRef([]),E=i.ref("");let d=0;i.provide(b.matchedRouteKey,S);const s=t=>p.value.find(o=>o.key===t),g=t=>{p.value.forEach(o=>{o.pageElement&&(o===t?I(o.pageElement):q(o.pageElement))})},P=t=>{const o=p.value.findIndex(m=>m===t);o>=0&&(p.value=p.value.slice(0,o+1))},F=t=>{p.value=p.value.filter(o=>o!==t)},L=t=>{const o=new Set(e.history.map(m=>m.key));p.value=p.value.filter(m=>m===t||m.routeInfo.cache&&o.has(m.key))},x=t=>{var m;const o=t.props.default;return o?o===!0?((m=e.current.value)==null?void 0:m.params)??{}:typeof o=="function"?o(n):o:{}},W=(t,o)=>t.routeInfo.action==="pop"?o.routeInfo.transition??t.routeInfo.transition??e.transition:t.routeInfo.transition??e.transition,B=(t,o)=>!(t.routeInfo.isStackFallback||t.routeInfo.direction==="none"||t.routeInfo.direction==="root"||t.routeInfo.action==="pop"&&(o.routeInfo.entryDirection==="none"||o.routeInfo.entryDirection==="root"||o.routeInfo.transition==="none")),A=(t,o)=>{var R;const m=B(t,o);return((R=e.shouldTransition)==null?void 0:R.call(e,{action:t.routeInfo.action,direction:t.routeInfo.direction,source:t.routeInfo.source,entering:t.routeInfo,leaving:o.routeInfo,defaultResult:m}))??m},G=(t,o)=>t.routeInfo.action==="pop"?o.routeInfo.transitionDuration??t.routeInfo.transitionDuration??e.transitionDuration:t.routeInfo.transitionDuration??e.transitionDuration,V=(t,o)=>t.routeInfo.action==="pop"?o.routeInfo.transitionEasing??t.routeInfo.transitionEasing??e.transitionEasing:t.routeInfo.transitionEasing??e.transitionEasing,$=async(t,o)=>{const m=++d;await i.nextTick();const R=t.pageElement;if(!R)return;if(D(t.componentRef,Y),!o||o===t||!o.pageElement){I(R),D(t.componentRef,U),L(t),g(t);return}const y=o.pageElement,_=A(t,o),X=_?W(t,o):"none",c=_?G(t,o):0,a=V(t,o),u=ue(X||v,e.transitions);D(o.componentRef,Z),I(R),I(y),await le(u,{direction:t.routeInfo.direction,action:t.routeInfo.action,routeInfo:t.routeInfo,enteringElement:R,leavingElement:y,duration:c,easing:a}),m===d&&(q(y),D(o.componentRef,N),D(t.componentRef,U),t.routeInfo.action==="pop"?P(t):t.routeInfo.action==="replace"&&F(o),L(t),g(t))},j=async()=>{const t=S.value,o=e.current.value;if(!t||!o)return;const m=ee(t);if(!m)return;const R=s(E.value);let y=s(o.key);y?(y.routeInfo=o,y.matchedRoute=t,y.component=m,y.props=x(t),y.mounted=!0):(y={id:z("view"),key:o.key,routeInfo:o,matchedRoute:t,component:m,props:x(t),componentRef:i.ref(),mounted:!0},p.value=[...p.value,y]),E.value=y.key,await $(y,R)};return i.watch(()=>{var t;return[(t=e.current.value)==null?void 0:t.id,S.value]},()=>{j()},{immediate:!0}),i.onUnmounted(()=>{d++,p.value=[]}),{injectedRoute:n,viewItems:p,registerStackPage:(t,o)=>{t.pageElement=o,t.key===E.value?I(o):q(o)}}},render(){return i.h("div",{class:"rs-outlet"},this.viewItems.filter(e=>e.mounted).map(e=>i.h(e.component,{key:e.id,ref:e.componentRef,...e.props,registerStackPage:r=>this.registerStackPage(e,r)})))}}),Ee=i.defineComponent({name:"RouterPage",props:{registerStackPage:{type:Function,default:void 0}},setup(e,{attrs:r,slots:n}){const l=i.ref();return i.onMounted(()=>{var f;l.value&&((f=e.registerStackPage)==null||f.call(e,l.value))}),()=>{var f;return i.h("section",{...r,ref:l,class:["rs-page",r.class]},(f=n.default)==null?void 0:f.call(n))}}}),we=i.defineComponent({name:"StackRouterDebugPanel",props:{initialOpen:{type:Boolean,default:!1}},setup(e){const r=O(),n=i.ref(e.initialOpen);return()=>{const l=r.current.value;return i.h("aside",{class:"rs-debug-panel"},[i.h("button",{class:"rs-debug-toggle",type:"button",onClick:()=>{n.value=!n.value}},n.value?"Stack x":"Stack"),n.value?i.h("section",{class:"rs-debug-body"},[i.h("header",[i.h("strong","Router Stack"),i.h("span",`${r.history.length} pages`)]),i.h("dl",[i.h("dt","current"),i.h("dd",(l==null?void 0:l.fullPath)??"-"),i.h("dt","canGoBack"),i.h("dd",String(r.canGoBack()))]),i.h("ol",r.history.map(f=>i.h("li",{key:f.id,class:{"rs-debug-active":f.id===(l==null?void 0:l.id),"rs-debug-no-cache":!f.cache}},[i.h("code",f.fullPath),i.h("span",[`${f.action}/${f.direction}`,` source:${f.source}`,` entry:${f.entryDirection}`,` cache:${f.cache?"yes":"no"}`,` transition:${f.transition??"-"}`])])))]):void 0])}}});Object.defineProperty(exports,"RouterLink",{enumerable:!0,get:()=>b.RouterLink});Object.defineProperty(exports,"RouterView",{enumerable:!0,get:()=>b.RouterView});Object.defineProperty(exports,"createMemoryHistory",{enumerable:!0,get:()=>b.createMemoryHistory});Object.defineProperty(exports,"createWebHashHistory",{enumerable:!0,get:()=>b.createWebHashHistory});Object.defineProperty(exports,"createWebHistory",{enumerable:!0,get:()=>b.createWebHistory});Object.defineProperty(exports,"useRoute",{enumerable:!0,get:()=>b.useRoute});Object.defineProperty(exports,"useRouter",{enumerable:!0,get:()=>b.useRouter});exports.RouterOutlet=Pe;exports.RouterPage=Ee;exports.StackRouterDebugPanel=we;exports.createStackRouter=de;exports.defineStackTransition=oe;exports.onPageDidEnter=ye;exports.onPageDidLeave=pe;exports.onPageWillEnter=me;exports.onPageWillLeave=ke;exports.stackRouterKey=H;exports.useStackRouter=Re;
@@ -0,0 +1 @@
1
+ .rs-outlet{position:relative;display:block;width:100%;height:100%;overflow:hidden;contain:layout size style}.rs-page{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;min-width:0;min-height:0;flex-direction:column;overflow:hidden;background:var(--rs-page-background, #fff);z-index:0}.rs-page-hidden,[hidden].rs-page{display:none!important}.rs-page-invisible{opacity:0}.rs-debug-panel{position:fixed;right:12px;bottom:12px;z-index:9999;color:#d9e5ef;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace}.rs-debug-toggle{min-width:72px;border:0;border-radius:6px;padding:8px 10px;color:#f8fafc;background:#111827;cursor:pointer}.rs-debug-body{width:min(420px,calc(100vw - 24px));max-height:min(520px,calc(100vh - 96px));overflow:auto;margin-top:8px;border:1px solid rgb(148 163 184 / 28%);border-radius:8px;padding:12px;background:#0f172af0;box-shadow:0 18px 48px #0f172a4d}.rs-debug-body header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.rs-debug-body dl{display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 10px;margin:0 0 10px}.rs-debug-body dt{color:#94a3b8}.rs-debug-body dd{min-width:0;margin:0;overflow-wrap:anywhere}.rs-debug-body ol{display:grid;gap:6px;margin:0;padding:0;list-style:none}.rs-debug-body li{display:grid;gap:3px;border-radius:6px;padding:7px;background:#ffffff0f}.rs-debug-body code{overflow-wrap:anywhere;color:#f8fafc}.rs-debug-body li span{color:#a7b7c8}.rs-debug-body .rs-debug-active{background:#14b8a638}.rs-debug-body .rs-debug-no-cache{outline:1px solid rgb(248 113 113 / 55%)}
@@ -0,0 +1,566 @@
1
+ import { shallowRef as tt, getCurrentInstance as st, inject as F, defineComponent as Q, h as k, computed as z, ref as B, provide as it, watch as ut, onUnmounted as lt, nextTick as ft, onMounted as dt } from "vue";
2
+ import { createRouter as ht, useRoute as mt, routeLocationKey as yt, matchedRouteKey as v } from "vue-router";
3
+ import { RouterLink as Kt, RouterView as jt, createMemoryHistory as zt, createWebHashHistory as Ut, createWebHistory as Mt, useRoute as qt, useRouter as Jt } from "vue-router";
4
+ const N = {};
5
+ function q(t = "route") {
6
+ const r = (N[t] ?? 0) + 1;
7
+ return N[t] = r, `${t}-${r}`;
8
+ }
9
+ const Y = "slide", kt = "cubic-bezier(0.2, 0, 0, 1)";
10
+ function Lt(t) {
11
+ return t;
12
+ }
13
+ const pt = ({ direction: t }) => {
14
+ const r = t === "back";
15
+ return {
16
+ entering: r ? [
17
+ { transform: "translateX(-18%)", opacity: 0.96 },
18
+ { transform: "translateX(0)", opacity: 1 }
19
+ ] : [{ transform: "translateX(100%)" }, { transform: "translateX(0)" }],
20
+ leaving: r ? [
21
+ { transform: "translateX(0)", opacity: 1 },
22
+ { transform: "translateX(100%)", opacity: 1 }
23
+ ] : [
24
+ { transform: "translateX(0)", opacity: 1 },
25
+ { transform: "translateX(-18%)", opacity: 0.96 }
26
+ ]
27
+ };
28
+ }, Rt = () => ({
29
+ entering: [{ opacity: 0 }, { opacity: 1 }],
30
+ leaving: [{ opacity: 1 }, { opacity: 0 }]
31
+ }), bt = ({ direction: t }) => {
32
+ const r = t === "back";
33
+ return {
34
+ entering: r ? [
35
+ { transform: "scale(0.98)", opacity: 0 },
36
+ { transform: "scale(1)", opacity: 1 }
37
+ ] : [
38
+ { transform: "scale(1.04)", opacity: 0 },
39
+ { transform: "scale(1)", opacity: 1 }
40
+ ],
41
+ leaving: r ? [
42
+ { transform: "scale(1)", opacity: 1 },
43
+ { transform: "scale(1.04)", opacity: 0 }
44
+ ] : [
45
+ { transform: "scale(1)", opacity: 1 },
46
+ { transform: "scale(0.98)", opacity: 0 }
47
+ ]
48
+ };
49
+ }, Et = () => ({}), Pt = {
50
+ slide: pt,
51
+ fade: Rt,
52
+ scale: bt,
53
+ none: Et
54
+ };
55
+ function wt(t, r) {
56
+ const n = r[t];
57
+ return n || r[Y];
58
+ }
59
+ function O(t, r, n) {
60
+ if (!r || !t.animate || Number(n.duration) <= 0)
61
+ return Promise.resolve();
62
+ const u = t.animate(r, n);
63
+ return u.finished.then(() => u.cancel());
64
+ }
65
+ async function It(t, r) {
66
+ const n = t(r), u = {
67
+ duration: r.duration,
68
+ easing: r.easing,
69
+ fill: "both",
70
+ ...n.options
71
+ };
72
+ await Promise.all([
73
+ O(r.enteringElement, n.entering, u),
74
+ O(r.leavingElement, n.leaving, u)
75
+ ]);
76
+ }
77
+ const Z = Symbol("stack-router");
78
+ function St() {
79
+ const t = window.history.state;
80
+ return typeof (t == null ? void 0 : t.position) == "number" ? t.position : window.history.length;
81
+ }
82
+ function U(t, r) {
83
+ return r || (t === "pop" ? "back" : t === "replace" ? "none" : "forward");
84
+ }
85
+ function At(t) {
86
+ const r = ht(t), n = [], u = tt(), l = t.transitionDuration ?? 240, T = t.transitionEasing ?? kt, w = t.transition ?? Y, p = {
87
+ ...Pt,
88
+ ...t.transitions
89
+ }, E = t.maxStackSize ?? 30;
90
+ let f = -1, s, I = !1;
91
+ const b = (c, a, i, d, y = !1, P = "stack") => {
92
+ s = {
93
+ action: c,
94
+ direction: U(c, a),
95
+ source: P,
96
+ delta: i,
97
+ transition: d == null ? void 0 : d.transition,
98
+ duration: d == null ? void 0 : d.duration,
99
+ easing: d == null ? void 0 : d.easing,
100
+ resetRoot: y
101
+ };
102
+ }, C = (c, a) => {
103
+ const i = (s == null ? void 0 : s.action) ?? "push", d = U(i, s == null ? void 0 : s.direction), y = u.value, P = i === "push" && d === "forward", ct = g(c);
104
+ return {
105
+ id: q("route"),
106
+ key: c.fullPath,
107
+ fullPath: c.fullPath,
108
+ pathname: c.path,
109
+ params: c.params,
110
+ query: c.query,
111
+ action: i,
112
+ direction: d,
113
+ source: (s == null ? void 0 : s.source) ?? "stack",
114
+ entryDirection: d,
115
+ lastPathname: (y == null ? void 0 : y.pathname) || (a.name ? a.path : void 0),
116
+ pushedByRoute: P ? y == null ? void 0 : y.pathname : y == null ? void 0 : y.pushedByRoute,
117
+ position: St(),
118
+ delta: s == null ? void 0 : s.delta,
119
+ transition: (s == null ? void 0 : s.transition) ?? w,
120
+ transitionDuration: (s == null ? void 0 : s.duration) ?? l,
121
+ transitionEasing: (s == null ? void 0 : s.easing) ?? T,
122
+ cache: ct
123
+ };
124
+ }, g = (c) => {
125
+ const a = c.meta.stack;
126
+ return c.meta.stackCache !== !1 && (a == null ? void 0 : a.cache) !== !1;
127
+ }, x = () => {
128
+ if (E <= 0 || n.length <= E) return;
129
+ const c = n.length - E;
130
+ n.splice(0, c), f = Math.max(0, f - c);
131
+ }, H = (c) => {
132
+ if (c.action === "pop") {
133
+ let a = -1;
134
+ if (c.direction === "forward" || (c.delta ?? 0) > 0) {
135
+ for (let i = f + 1; i < n.length; i++)
136
+ if (n[i].key === c.key) {
137
+ a = i;
138
+ break;
139
+ }
140
+ } else
141
+ for (let i = f - 1; i >= 0; i--)
142
+ if (n[i].key === c.key) {
143
+ a = i;
144
+ break;
145
+ }
146
+ if (a >= 0) {
147
+ const i = n[a];
148
+ n[a] = {
149
+ ...i,
150
+ ...c,
151
+ id: i.id,
152
+ source: c.source,
153
+ entryDirection: i.entryDirection,
154
+ transition: i.transition,
155
+ transitionDuration: i.transitionDuration,
156
+ transitionEasing: i.transitionEasing
157
+ }, f = a;
158
+ } else
159
+ n.push({
160
+ ...c,
161
+ direction: "none",
162
+ entryDirection: "none",
163
+ transition: "none",
164
+ isStackFallback: !0
165
+ }), f = n.length - 1;
166
+ } else if (c.action === "replace") {
167
+ if (s != null && s.resetRoot) {
168
+ n.splice(0, n.length, c), f = 0, u.value = n[f];
169
+ return;
170
+ }
171
+ f >= 0 ? n[f] = c : (n.push(c), f = 0);
172
+ } else
173
+ f < n.length - 1 && n.splice(f + 1), n.push(c), f = n.length - 1;
174
+ x(), u.value = n[f];
175
+ };
176
+ t.history.listen((c, a, i) => {
177
+ const d = "delta" in i ? i.delta : void 0, y = i.direction === "back" ? "back" : i.direction === "forward" ? "forward" : void 0;
178
+ s ? (s.delta = d, s.direction = U(
179
+ s.action,
180
+ s.direction ?? y
181
+ )) : b(
182
+ i.type === "pop" ? "pop" : i.type,
183
+ y,
184
+ d,
185
+ void 0,
186
+ !1,
187
+ "external"
188
+ );
189
+ }), r.beforeEach((c) => !((s == null ? void 0 : s.action) === "pop" && I && !n.some((a) => a.fullPath === c.fullPath))), r.afterEach((c, a, i) => {
190
+ if (i) {
191
+ s = void 0;
192
+ return;
193
+ }
194
+ H(C(c, a)), s = void 0;
195
+ });
196
+ const V = r.push.bind(r), L = r.replace.bind(r), $ = r.back.bind(r), W = r.forward.bind(r), X = r.go.bind(r), A = (c, a) => (b(
197
+ "push",
198
+ (a == null ? void 0 : a.direction) ?? "forward",
199
+ void 0,
200
+ a
201
+ ), V(c)), K = (c, a) => (b(
202
+ "replace",
203
+ (a == null ? void 0 : a.direction) ?? "root",
204
+ void 0,
205
+ a
206
+ ), L(c)), e = (c, a) => {
207
+ const i = r.resolve(c).fullPath;
208
+ let d = -1;
209
+ for (let P = f; P >= 0; P--)
210
+ if (n[P].fullPath === i) {
211
+ d = P;
212
+ break;
213
+ }
214
+ if (d < 0) return !1;
215
+ const y = d - f;
216
+ return y === 0 || (b(
217
+ "pop",
218
+ (a == null ? void 0 : a.direction) ?? "back",
219
+ y,
220
+ a
221
+ ), X(y)), !0;
222
+ }, o = (c, a) => {
223
+ const i = r.resolve(c).fullPath;
224
+ if (f >= 0 && r.currentRoute.value.fullPath === i) {
225
+ const d = n[f], y = {
226
+ ...d,
227
+ id: q("route"),
228
+ action: "replace",
229
+ direction: (a == null ? void 0 : a.direction) ?? "root",
230
+ source: "stack",
231
+ entryDirection: (a == null ? void 0 : a.direction) ?? "root",
232
+ delta: void 0,
233
+ transition: (a == null ? void 0 : a.transition) ?? d.transition,
234
+ transitionDuration: (a == null ? void 0 : a.duration) ?? d.transitionDuration,
235
+ transitionEasing: (a == null ? void 0 : a.easing) ?? d.transitionEasing,
236
+ cache: !0,
237
+ isStackFallback: !1
238
+ };
239
+ return n.splice(0, n.length, y), f = 0, I = !0, u.value = y, Promise.resolve(void 0);
240
+ }
241
+ return b(
242
+ "replace",
243
+ (a == null ? void 0 : a.direction) ?? "root",
244
+ void 0,
245
+ a,
246
+ !0
247
+ ), I = !0, L(c);
248
+ }, h = () => {
249
+ b("pop", "back", -1), $();
250
+ }, R = () => {
251
+ b("pop", "forward", 1), W();
252
+ }, m = r;
253
+ m.push = A, m.replace = K, m.popTo = e, m.resetRoot = o, m.back = h, m.forward = R;
254
+ const _ = {
255
+ router: m,
256
+ transitionDuration: l,
257
+ transitionEasing: T,
258
+ transition: w,
259
+ transitions: p,
260
+ maxStackSize: E,
261
+ shouldTransition: t.shouldTransition,
262
+ current: u,
263
+ get history() {
264
+ return n;
265
+ },
266
+ canGoBack: (c = 1) => f - c >= 0,
267
+ push: A,
268
+ replace: K,
269
+ popTo: e,
270
+ resetRoot: o,
271
+ back: h,
272
+ forward: R
273
+ }, j = r.install.bind(r);
274
+ return r.install = (c) => {
275
+ c.provide(Z, _), j(c);
276
+ }, m;
277
+ }
278
+ const et = "onPageWillEnter", J = "onPageDidEnter", rt = "onPageWillLeave", ot = "onPageDidLeave";
279
+ function Dt(t, r, n) {
280
+ if (!n)
281
+ throw new Error(`${t} must be called inside setup().`);
282
+ const u = n.proxy, l = u[t] ?? [];
283
+ l.push(() => {
284
+ n.isUnmounted || r();
285
+ }), u[t] = l, n.exposed && (n.exposed[t] = l);
286
+ }
287
+ function G(t) {
288
+ return (r, n = st()) => Dt(t, r, n);
289
+ }
290
+ const _t = G(et), Ft = G(J), Bt = G(rt), Gt = G(ot);
291
+ function Ct() {
292
+ const t = F(Z);
293
+ if (!t)
294
+ throw new Error("useStackRouter must be used after createStackRouter().");
295
+ return {
296
+ router: t.router,
297
+ canGoBack: t.canGoBack,
298
+ push: t.push,
299
+ replace: t.replace,
300
+ popTo: t.popTo,
301
+ resetRoot: t.resetRoot,
302
+ back: t.back,
303
+ forward: t.forward
304
+ };
305
+ }
306
+ function nt() {
307
+ const t = F(Z);
308
+ if (!t)
309
+ throw new Error("RouterOutlet must be used with createStackRouter().");
310
+ return t;
311
+ }
312
+ function S(t, r) {
313
+ const n = t.value;
314
+ if (!n) return;
315
+ const u = n[r];
316
+ Array.isArray(u) ? u.forEach((l) => l()) : typeof u == "function" && u.call(n);
317
+ }
318
+ function D(t) {
319
+ t.classList.remove("rs-page-hidden", "rs-page-invisible"), t.removeAttribute("aria-hidden");
320
+ }
321
+ function M(t) {
322
+ t.classList.add("rs-page-hidden"), t.setAttribute("aria-hidden", "true");
323
+ }
324
+ function at(t) {
325
+ var r;
326
+ return (r = t.components) == null ? void 0 : r.default;
327
+ }
328
+ function Tt(t) {
329
+ const r = at(t);
330
+ return r !== void 0 && typeof r == "object" && "name" in r && r.name === "RouterView";
331
+ }
332
+ const Ht = Q({
333
+ name: "RouterOutlet",
334
+ setup() {
335
+ const t = nt(), r = mt(), n = F(yt), u = F(v, void 0), l = z(() => {
336
+ const e = u == null ? void 0 : u.value;
337
+ if (!e) return 0;
338
+ const o = r.matched.findIndex(
339
+ (h) => h === e
340
+ );
341
+ return o >= 0 ? o + 1 : 0;
342
+ }), T = z(() => r.matched[l.value]), w = z(() => {
343
+ for (let e = l.value; e < r.matched.length; e++) {
344
+ const o = r.matched[e];
345
+ if (!Tt(o))
346
+ return o;
347
+ }
348
+ return T.value;
349
+ }), p = tt([]), E = B("");
350
+ let f = 0;
351
+ it(v, w);
352
+ const s = (e) => p.value.find((o) => o.key === e), I = (e) => {
353
+ p.value.forEach((o) => {
354
+ o.pageElement && (o === e ? D(o.pageElement) : M(o.pageElement));
355
+ });
356
+ }, b = (e) => {
357
+ const o = p.value.findIndex((h) => h === e);
358
+ o >= 0 && (p.value = p.value.slice(0, o + 1));
359
+ }, C = (e) => {
360
+ p.value = p.value.filter((o) => o !== e);
361
+ }, g = (e) => {
362
+ const o = new Set(
363
+ t.history.map((h) => h.key)
364
+ );
365
+ p.value = p.value.filter(
366
+ (h) => h === e || h.routeInfo.cache && o.has(h.key)
367
+ );
368
+ }, x = (e) => {
369
+ var h;
370
+ const o = e.props.default;
371
+ return o ? o === !0 ? ((h = t.current.value) == null ? void 0 : h.params) ?? {} : typeof o == "function" ? o(n) : o : {};
372
+ }, H = (e, o) => e.routeInfo.action === "pop" ? o.routeInfo.transition ?? e.routeInfo.transition ?? t.transition : e.routeInfo.transition ?? t.transition, V = (e, o) => !(e.routeInfo.isStackFallback || e.routeInfo.direction === "none" || e.routeInfo.direction === "root" || e.routeInfo.action === "pop" && (o.routeInfo.entryDirection === "none" || o.routeInfo.entryDirection === "root" || o.routeInfo.transition === "none")), L = (e, o) => {
373
+ var R;
374
+ const h = V(e, o);
375
+ return ((R = t.shouldTransition) == null ? void 0 : R.call(t, {
376
+ action: e.routeInfo.action,
377
+ direction: e.routeInfo.direction,
378
+ source: e.routeInfo.source,
379
+ entering: e.routeInfo,
380
+ leaving: o.routeInfo,
381
+ defaultResult: h
382
+ })) ?? h;
383
+ }, $ = (e, o) => e.routeInfo.action === "pop" ? o.routeInfo.transitionDuration ?? e.routeInfo.transitionDuration ?? t.transitionDuration : e.routeInfo.transitionDuration ?? t.transitionDuration, W = (e, o) => e.routeInfo.action === "pop" ? o.routeInfo.transitionEasing ?? e.routeInfo.transitionEasing ?? t.transitionEasing : e.routeInfo.transitionEasing ?? t.transitionEasing, X = async (e, o) => {
384
+ const h = ++f;
385
+ await ft();
386
+ const R = e.pageElement;
387
+ if (!R) return;
388
+ if (S(e.componentRef, et), !o || o === e || !o.pageElement) {
389
+ D(R), S(e.componentRef, J), g(e), I(e);
390
+ return;
391
+ }
392
+ const m = o.pageElement, _ = L(e, o), j = _ ? H(e, o) : "none", c = _ ? $(e, o) : 0, a = W(e, o), i = wt(
393
+ j || Y,
394
+ t.transitions
395
+ );
396
+ S(o.componentRef, rt), D(R), D(m), await It(i, {
397
+ direction: e.routeInfo.direction,
398
+ action: e.routeInfo.action,
399
+ routeInfo: e.routeInfo,
400
+ enteringElement: R,
401
+ leavingElement: m,
402
+ duration: c,
403
+ easing: a
404
+ }), h === f && (M(m), S(o.componentRef, ot), S(e.componentRef, J), e.routeInfo.action === "pop" ? b(e) : e.routeInfo.action === "replace" && C(o), g(e), I(e));
405
+ }, A = async () => {
406
+ const e = w.value, o = t.current.value;
407
+ if (!e || !o) return;
408
+ const h = at(e);
409
+ if (!h) return;
410
+ const R = s(E.value);
411
+ let m = s(o.key);
412
+ m ? (m.routeInfo = o, m.matchedRoute = e, m.component = h, m.props = x(e), m.mounted = !0) : (m = {
413
+ id: q("view"),
414
+ key: o.key,
415
+ routeInfo: o,
416
+ matchedRoute: e,
417
+ component: h,
418
+ props: x(e),
419
+ componentRef: B(),
420
+ mounted: !0
421
+ }, p.value = [...p.value, m]), E.value = m.key, await X(m, R);
422
+ };
423
+ return ut(
424
+ () => {
425
+ var e;
426
+ return [(e = t.current.value) == null ? void 0 : e.id, w.value];
427
+ },
428
+ () => {
429
+ A();
430
+ },
431
+ { immediate: !0 }
432
+ ), lt(() => {
433
+ f++, p.value = [];
434
+ }), {
435
+ injectedRoute: n,
436
+ viewItems: p,
437
+ registerStackPage: (e, o) => {
438
+ e.pageElement = o, e.key === E.value ? D(o) : M(o);
439
+ }
440
+ };
441
+ },
442
+ render() {
443
+ return k(
444
+ "div",
445
+ { class: "rs-outlet" },
446
+ this.viewItems.filter((t) => t.mounted).map(
447
+ (t) => k(t.component, {
448
+ key: t.id,
449
+ ref: t.componentRef,
450
+ ...t.props,
451
+ registerStackPage: (r) => this.registerStackPage(t, r)
452
+ })
453
+ )
454
+ );
455
+ }
456
+ }), Vt = Q({
457
+ name: "RouterPage",
458
+ props: {
459
+ registerStackPage: {
460
+ type: Function,
461
+ default: void 0
462
+ }
463
+ },
464
+ setup(t, { attrs: r, slots: n }) {
465
+ const u = B();
466
+ return dt(() => {
467
+ var l;
468
+ u.value && ((l = t.registerStackPage) == null || l.call(t, u.value));
469
+ }), () => {
470
+ var l;
471
+ return k(
472
+ "section",
473
+ {
474
+ ...r,
475
+ ref: u,
476
+ class: ["rs-page", r.class]
477
+ },
478
+ (l = n.default) == null ? void 0 : l.call(n)
479
+ );
480
+ };
481
+ }
482
+ }), $t = Q({
483
+ name: "StackRouterDebugPanel",
484
+ props: {
485
+ initialOpen: {
486
+ type: Boolean,
487
+ default: !1
488
+ }
489
+ },
490
+ setup(t) {
491
+ const r = nt(), n = B(t.initialOpen);
492
+ return () => {
493
+ const u = r.current.value;
494
+ return k("aside", { class: "rs-debug-panel" }, [
495
+ k(
496
+ "button",
497
+ {
498
+ class: "rs-debug-toggle",
499
+ type: "button",
500
+ onClick: () => {
501
+ n.value = !n.value;
502
+ }
503
+ },
504
+ n.value ? "Stack x" : "Stack"
505
+ ),
506
+ n.value ? k("section", { class: "rs-debug-body" }, [
507
+ k("header", [
508
+ k("strong", "Router Stack"),
509
+ k("span", `${r.history.length} pages`)
510
+ ]),
511
+ k("dl", [
512
+ k("dt", "current"),
513
+ k("dd", (u == null ? void 0 : u.fullPath) ?? "-"),
514
+ k("dt", "canGoBack"),
515
+ k("dd", String(r.canGoBack()))
516
+ ]),
517
+ k(
518
+ "ol",
519
+ r.history.map(
520
+ (l) => k(
521
+ "li",
522
+ {
523
+ key: l.id,
524
+ class: {
525
+ "rs-debug-active": l.id === (u == null ? void 0 : u.id),
526
+ "rs-debug-no-cache": !l.cache
527
+ }
528
+ },
529
+ [
530
+ k("code", l.fullPath),
531
+ k("span", [
532
+ `${l.action}/${l.direction}`,
533
+ ` source:${l.source}`,
534
+ ` entry:${l.entryDirection}`,
535
+ ` cache:${l.cache ? "yes" : "no"}`,
536
+ ` transition:${l.transition ?? "-"}`
537
+ ])
538
+ ]
539
+ )
540
+ )
541
+ )
542
+ ]) : void 0
543
+ ]);
544
+ };
545
+ }
546
+ });
547
+ export {
548
+ Kt as RouterLink,
549
+ Ht as RouterOutlet,
550
+ Vt as RouterPage,
551
+ jt as RouterView,
552
+ $t as StackRouterDebugPanel,
553
+ zt as createMemoryHistory,
554
+ At as createStackRouter,
555
+ Ut as createWebHashHistory,
556
+ Mt as createWebHistory,
557
+ Lt as defineStackTransition,
558
+ Ft as onPageDidEnter,
559
+ Gt as onPageDidLeave,
560
+ _t as onPageWillEnter,
561
+ Bt as onPageWillLeave,
562
+ Z as stackRouterKey,
563
+ qt as useRoute,
564
+ Jt as useRouter,
565
+ Ct as useStackRouter
566
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@lazycatcloud/vue-router-stack",
3
+ "version": "0.0.1",
4
+ "description": "Page stack, caching, transitions, and lifecycle utilities built on vue-router.",
5
+ "private": false,
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "main": "./dist/vue-router-stack.cjs",
9
+ "module": "./dist/vue-router-stack.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/vue-router-stack.js",
15
+ "require": "./dist/vue-router-stack.cjs"
16
+ },
17
+ "./style.css": "./dist/vue-router-stack.css"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "sideEffects": [
23
+ "*.css",
24
+ "**/*.css"
25
+ ],
26
+ "keywords": [
27
+ "vue",
28
+ "vue-router",
29
+ "router",
30
+ "stack",
31
+ "transition",
32
+ "keep-alive"
33
+ ],
34
+ "scripts": {
35
+ "dev": "vite --config demo/vite.config.ts --port 2244 --host",
36
+ "build": "pnpm run type-check && pnpm run build:lib && pnpm run build:demo",
37
+ "build:lib": "vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.build.json",
38
+ "build:demo": "vite build --config demo/vite.config.ts",
39
+ "type-check": "vue-tsc --noEmit -p tsconfig.app.json",
40
+ "test": "vitest run"
41
+ },
42
+ "peerDependencies": {
43
+ "vue": "^3.3.0",
44
+ "vue-router": "^4.2.0"
45
+ },
46
+ "devDependencies": {
47
+ "@vitejs/plugin-vue": "^5.2.4",
48
+ "@vue/test-utils": "^2.4.6",
49
+ "jsdom": "^25.0.1",
50
+ "typescript": "^5.8.3",
51
+ "vite": "^6.3.5",
52
+ "vitest": "^3.2.4",
53
+ "vue": "^3.5.16",
54
+ "vue-router": "^4.5.1",
55
+ "vue-tsc": "^2.2.10"
56
+ }
57
+ }