@esmx/router-vue 3.0.0-rc.60 → 3.0.0-rc.63
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/README.md +4 -4
- package/README.zh-CN.md +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.test.mjs +2 -0
- package/dist/router-link.d.ts +30 -66
- package/dist/router-link.mjs +48 -69
- package/dist/router-link.test.mjs +61 -1
- package/dist/router-view.d.ts +1 -0
- package/dist/router-view.mjs +3 -3
- package/dist/router-view.test.mjs +9 -9
- package/dist/use.d.ts +1 -1
- package/dist/use.mjs +2 -2
- package/dist/util.d.ts +1 -1
- package/package.json +3 -3
- package/src/index.test.ts +2 -0
- package/src/index.ts +1 -1
- package/src/router-link.test.ts +82 -1
- package/src/router-link.ts +52 -95
- package/src/router-view.test.ts +9 -9
- package/src/router-view.ts +3 -3
- package/src/use.ts +3 -3
- package/src/util.ts +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="https://
|
|
2
|
+
<img src="https://esmx.dev/logo.svg?t=2025" width="120" alt="Esmx Logo" />
|
|
3
3
|
<h1>@esmx/router-vue</h1>
|
|
4
4
|
|
|
5
5
|
<div>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<a href="https://github.com/esmnext/esmx/actions/workflows/build.yml">
|
|
10
10
|
<img src="https://github.com/esmnext/esmx/actions/workflows/build.yml/badge.svg" alt="Build" />
|
|
11
11
|
</a>
|
|
12
|
-
<a href="https://
|
|
12
|
+
<a href="https://esmx.dev/coverage/">
|
|
13
13
|
<img src="https://img.shields.io/badge/coverage-live%20report-brightgreen" alt="Coverage Report" />
|
|
14
14
|
</a>
|
|
15
15
|
<a href="https://nodejs.org/">
|
|
@@ -350,7 +350,7 @@ const link = useLink({
|
|
|
350
350
|
<template>
|
|
351
351
|
<a
|
|
352
352
|
v-bind="link.attributes"
|
|
353
|
-
v-on="link.
|
|
353
|
+
v-on="link.createEventHandlers()"
|
|
354
354
|
:class="{ active: link.isActive }"
|
|
355
355
|
>
|
|
356
356
|
Custom Link
|
|
@@ -460,7 +460,7 @@ const link = useLink(props).value;
|
|
|
460
460
|
<template>
|
|
461
461
|
<button
|
|
462
462
|
v-bind="link.attributes"
|
|
463
|
-
v-on="link.
|
|
463
|
+
v-on="link.createEventHandlers()"
|
|
464
464
|
:class="{
|
|
465
465
|
active: link.isActive,
|
|
466
466
|
disabled: disabled
|
package/README.zh-CN.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="https://
|
|
2
|
+
<img src="https://esmx.dev/logo.svg?t=2025" width="120" alt="Esmx Logo" />
|
|
3
3
|
<h1>@esmx/router-vue</h1>
|
|
4
4
|
|
|
5
5
|
<div>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<a href="https://github.com/esmnext/esmx/actions/workflows/build.yml">
|
|
10
10
|
<img src="https://github.com/esmnext/esmx/actions/workflows/build.yml/badge.svg" alt="Build" />
|
|
11
11
|
</a>
|
|
12
|
-
<a href="https://
|
|
12
|
+
<a href="https://esmx.dev/coverage/">
|
|
13
13
|
<img src="https://img.shields.io/badge/coverage-live%20report-brightgreen" alt="Coverage Report" />
|
|
14
14
|
</a>
|
|
15
15
|
<a href="https://nodejs.org/">
|
|
@@ -350,7 +350,7 @@ const link = useLink({
|
|
|
350
350
|
<template>
|
|
351
351
|
<a
|
|
352
352
|
v-bind="link.attributes"
|
|
353
|
-
v-on="link.
|
|
353
|
+
v-on="link.createEventHandlers()"
|
|
354
354
|
:class="{ active: link.isActive }"
|
|
355
355
|
>
|
|
356
356
|
自定义链接
|
|
@@ -460,7 +460,7 @@ const link = useLink(props).value;
|
|
|
460
460
|
<template>
|
|
461
461
|
<button
|
|
462
462
|
v-bind="link.attributes"
|
|
463
|
-
v-on="link.
|
|
463
|
+
v-on="link.createEventHandlers()"
|
|
464
464
|
:class="{
|
|
465
465
|
active: link.isActive,
|
|
466
466
|
disabled: disabled
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export type * from './vue2';
|
|
|
2
2
|
export type * from './vue3';
|
|
3
3
|
export { useRouter, useRoute, useProvideRouter, useLink, getRoute, getRouter } from './use';
|
|
4
4
|
export { RouterLink } from './router-link';
|
|
5
|
-
export { RouterView } from './router-view';
|
|
5
|
+
export { RouterView, RouterViewDepth } from './router-view';
|
|
6
6
|
export { RouterPlugin } from './plugin';
|
package/dist/index.mjs
CHANGED
package/dist/index.test.mjs
CHANGED
|
@@ -66,6 +66,7 @@ describe("index.ts - Package Entry Point", () => {
|
|
|
66
66
|
// Components
|
|
67
67
|
"RouterLink",
|
|
68
68
|
"RouterView",
|
|
69
|
+
"RouterViewDepth",
|
|
69
70
|
// Plugin
|
|
70
71
|
"RouterPlugin"
|
|
71
72
|
];
|
|
@@ -87,6 +88,7 @@ describe("index.ts - Package Entry Point", () => {
|
|
|
87
88
|
"getRoute",
|
|
88
89
|
"RouterLink",
|
|
89
90
|
"RouterView",
|
|
91
|
+
"RouterViewDepth",
|
|
90
92
|
"RouterPlugin"
|
|
91
93
|
];
|
|
92
94
|
const unexpectedExports = actualExports.filter(
|
package/dist/router-link.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RouterLinkProps } from '@esmx/router';
|
|
2
2
|
import { type PropType } from 'vue';
|
|
3
3
|
/**
|
|
4
4
|
* RouterLink component for navigation.
|
|
@@ -56,7 +56,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
56
56
|
* @example '/home' | { path: '/user', query: { id: '123' } }
|
|
57
57
|
*/
|
|
58
58
|
to: {
|
|
59
|
-
type: PropType<
|
|
59
|
+
type: PropType<RouterLinkProps["to"]>;
|
|
60
60
|
required: true;
|
|
61
61
|
};
|
|
62
62
|
/**
|
|
@@ -65,7 +65,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
65
65
|
* @example 'push' | 'replace' | 'pushWindow' | 'replaceWindow' | 'pushLayer'
|
|
66
66
|
*/
|
|
67
67
|
type: {
|
|
68
|
-
type: PropType<
|
|
68
|
+
type: PropType<RouterLinkProps["type"]>;
|
|
69
69
|
default: string;
|
|
70
70
|
};
|
|
71
71
|
/**
|
|
@@ -73,7 +73,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
73
73
|
* @example :replace={true} → type="replace"
|
|
74
74
|
*/
|
|
75
75
|
replace: {
|
|
76
|
-
type:
|
|
76
|
+
type: PropType<RouterLinkProps["replace"]>;
|
|
77
77
|
default: boolean;
|
|
78
78
|
};
|
|
79
79
|
/**
|
|
@@ -84,7 +84,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
84
84
|
* @default 'include'
|
|
85
85
|
*/
|
|
86
86
|
exact: {
|
|
87
|
-
type: PropType<
|
|
87
|
+
type: PropType<RouterLinkProps["exact"]>;
|
|
88
88
|
default: string;
|
|
89
89
|
};
|
|
90
90
|
/**
|
|
@@ -92,7 +92,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
92
92
|
* @example 'nav-active' | 'selected'
|
|
93
93
|
*/
|
|
94
94
|
activeClass: {
|
|
95
|
-
type:
|
|
95
|
+
type: PropType<RouterLinkProps["activeClass"]>;
|
|
96
96
|
};
|
|
97
97
|
/**
|
|
98
98
|
* Event(s) that trigger navigation. Can be string or array of strings.
|
|
@@ -100,7 +100,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
100
100
|
* @example 'click' | ['click', 'mouseenter']
|
|
101
101
|
*/
|
|
102
102
|
event: {
|
|
103
|
-
type: PropType<
|
|
103
|
+
type: PropType<RouterLinkProps["event"]>;
|
|
104
104
|
default: string;
|
|
105
105
|
};
|
|
106
106
|
/**
|
|
@@ -109,7 +109,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
109
109
|
* @example 'button' | 'div' | 'span'
|
|
110
110
|
*/
|
|
111
111
|
tag: {
|
|
112
|
-
type:
|
|
112
|
+
type: PropType<RouterLinkProps["tag"]>;
|
|
113
113
|
default: string;
|
|
114
114
|
};
|
|
115
115
|
/**
|
|
@@ -118,34 +118,16 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
118
118
|
* @example { zIndex: 1000, autoPush: false, routerOptions: { mode: 'memory' } }
|
|
119
119
|
*/
|
|
120
120
|
layerOptions: {
|
|
121
|
-
type: PropType<
|
|
121
|
+
type: PropType<RouterLinkProps["layerOptions"]>;
|
|
122
122
|
};
|
|
123
123
|
/**
|
|
124
|
-
* Custom
|
|
125
|
-
*
|
|
124
|
+
* Custom navigation handler called before navigation.
|
|
125
|
+
* Receives the event object and the event name that triggered navigation.
|
|
126
126
|
*
|
|
127
127
|
* @Note you need to call `e.preventDefault()` to prevent default browser navigation.
|
|
128
|
-
* @default
|
|
129
|
-
*
|
|
130
|
-
* (event: Event & Partial<MouseEvent>): boolean => {
|
|
131
|
-
* // don't redirect with control keys
|
|
132
|
-
* if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return false;
|
|
133
|
-
* // don't redirect when preventDefault called
|
|
134
|
-
* if (e.defaultPrevented) return false;
|
|
135
|
-
* // don't redirect on right click
|
|
136
|
-
* if (e.button !== undefined && e.button !== 0) return false;
|
|
137
|
-
* // don't redirect if `target="_blank"`
|
|
138
|
-
* const target = e.currentTarget?.getAttribute?.('target') ?? '';
|
|
139
|
-
* if (/\b_blank\b/i.test(target)) return false;
|
|
140
|
-
* // Prevent default browser navigation to enable SPA routing
|
|
141
|
-
* // Note: this may be a Weex event which doesn't have this method
|
|
142
|
-
* if (e.preventDefault) e.preventDefault();
|
|
143
|
-
*
|
|
144
|
-
* return true;
|
|
145
|
-
* }
|
|
146
128
|
*/
|
|
147
|
-
|
|
148
|
-
type: PropType<
|
|
129
|
+
beforeNavigate: {
|
|
130
|
+
type: PropType<RouterLinkProps["beforeNavigate"]>;
|
|
149
131
|
};
|
|
150
132
|
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
151
133
|
[key: string]: any;
|
|
@@ -156,7 +138,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
156
138
|
* @example '/home' | { path: '/user', query: { id: '123' } }
|
|
157
139
|
*/
|
|
158
140
|
to: {
|
|
159
|
-
type: PropType<
|
|
141
|
+
type: PropType<RouterLinkProps["to"]>;
|
|
160
142
|
required: true;
|
|
161
143
|
};
|
|
162
144
|
/**
|
|
@@ -165,7 +147,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
165
147
|
* @example 'push' | 'replace' | 'pushWindow' | 'replaceWindow' | 'pushLayer'
|
|
166
148
|
*/
|
|
167
149
|
type: {
|
|
168
|
-
type: PropType<
|
|
150
|
+
type: PropType<RouterLinkProps["type"]>;
|
|
169
151
|
default: string;
|
|
170
152
|
};
|
|
171
153
|
/**
|
|
@@ -173,7 +155,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
173
155
|
* @example :replace={true} → type="replace"
|
|
174
156
|
*/
|
|
175
157
|
replace: {
|
|
176
|
-
type:
|
|
158
|
+
type: PropType<RouterLinkProps["replace"]>;
|
|
177
159
|
default: boolean;
|
|
178
160
|
};
|
|
179
161
|
/**
|
|
@@ -184,7 +166,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
184
166
|
* @default 'include'
|
|
185
167
|
*/
|
|
186
168
|
exact: {
|
|
187
|
-
type: PropType<
|
|
169
|
+
type: PropType<RouterLinkProps["exact"]>;
|
|
188
170
|
default: string;
|
|
189
171
|
};
|
|
190
172
|
/**
|
|
@@ -192,7 +174,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
192
174
|
* @example 'nav-active' | 'selected'
|
|
193
175
|
*/
|
|
194
176
|
activeClass: {
|
|
195
|
-
type:
|
|
177
|
+
type: PropType<RouterLinkProps["activeClass"]>;
|
|
196
178
|
};
|
|
197
179
|
/**
|
|
198
180
|
* Event(s) that trigger navigation. Can be string or array of strings.
|
|
@@ -200,7 +182,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
200
182
|
* @example 'click' | ['click', 'mouseenter']
|
|
201
183
|
*/
|
|
202
184
|
event: {
|
|
203
|
-
type: PropType<
|
|
185
|
+
type: PropType<RouterLinkProps["event"]>;
|
|
204
186
|
default: string;
|
|
205
187
|
};
|
|
206
188
|
/**
|
|
@@ -209,7 +191,7 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
209
191
|
* @example 'button' | 'div' | 'span'
|
|
210
192
|
*/
|
|
211
193
|
tag: {
|
|
212
|
-
type:
|
|
194
|
+
type: PropType<RouterLinkProps["tag"]>;
|
|
213
195
|
default: string;
|
|
214
196
|
};
|
|
215
197
|
/**
|
|
@@ -218,39 +200,21 @@ export declare const RouterLink: import("vue").DefineComponent<import("vue").Ext
|
|
|
218
200
|
* @example { zIndex: 1000, autoPush: false, routerOptions: { mode: 'memory' } }
|
|
219
201
|
*/
|
|
220
202
|
layerOptions: {
|
|
221
|
-
type: PropType<
|
|
203
|
+
type: PropType<RouterLinkProps["layerOptions"]>;
|
|
222
204
|
};
|
|
223
205
|
/**
|
|
224
|
-
* Custom
|
|
225
|
-
*
|
|
206
|
+
* Custom navigation handler called before navigation.
|
|
207
|
+
* Receives the event object and the event name that triggered navigation.
|
|
226
208
|
*
|
|
227
209
|
* @Note you need to call `e.preventDefault()` to prevent default browser navigation.
|
|
228
|
-
* @default
|
|
229
|
-
*
|
|
230
|
-
* (event: Event & Partial<MouseEvent>): boolean => {
|
|
231
|
-
* // don't redirect with control keys
|
|
232
|
-
* if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return false;
|
|
233
|
-
* // don't redirect when preventDefault called
|
|
234
|
-
* if (e.defaultPrevented) return false;
|
|
235
|
-
* // don't redirect on right click
|
|
236
|
-
* if (e.button !== undefined && e.button !== 0) return false;
|
|
237
|
-
* // don't redirect if `target="_blank"`
|
|
238
|
-
* const target = e.currentTarget?.getAttribute?.('target') ?? '';
|
|
239
|
-
* if (/\b_blank\b/i.test(target)) return false;
|
|
240
|
-
* // Prevent default browser navigation to enable SPA routing
|
|
241
|
-
* // Note: this may be a Weex event which doesn't have this method
|
|
242
|
-
* if (e.preventDefault) e.preventDefault();
|
|
243
|
-
*
|
|
244
|
-
* return true;
|
|
245
|
-
* }
|
|
246
210
|
*/
|
|
247
|
-
|
|
248
|
-
type: PropType<
|
|
211
|
+
beforeNavigate: {
|
|
212
|
+
type: PropType<RouterLinkProps["beforeNavigate"]>;
|
|
249
213
|
};
|
|
250
214
|
}>> & Readonly<{}>, {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
event: string | string[];
|
|
255
|
-
tag: string;
|
|
215
|
+
type: import("@esmx/router").RouterLinkType | undefined;
|
|
216
|
+
replace: boolean | undefined;
|
|
217
|
+
exact: import("@esmx/router").RouteMatchType | undefined;
|
|
218
|
+
event: string | string[] | undefined;
|
|
219
|
+
tag: string | undefined;
|
|
256
220
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/router-link.mjs
CHANGED
|
@@ -18,12 +18,18 @@ export const RouterLink = defineComponent({
|
|
|
18
18
|
* @default 'push'
|
|
19
19
|
* @example 'push' | 'replace' | 'pushWindow' | 'replaceWindow' | 'pushLayer'
|
|
20
20
|
*/
|
|
21
|
-
type: {
|
|
21
|
+
type: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: "push"
|
|
24
|
+
},
|
|
22
25
|
/**
|
|
23
26
|
* @deprecated Use 'type="replace"' instead
|
|
24
27
|
* @example :replace={true} → type="replace"
|
|
25
28
|
*/
|
|
26
|
-
replace: {
|
|
29
|
+
replace: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false
|
|
32
|
+
},
|
|
27
33
|
/**
|
|
28
34
|
* How to match the active state.
|
|
29
35
|
* - 'include': Match if current route includes this path
|
|
@@ -31,12 +37,17 @@ export const RouterLink = defineComponent({
|
|
|
31
37
|
* - 'route': Match based on route configuration
|
|
32
38
|
* @default 'include'
|
|
33
39
|
*/
|
|
34
|
-
exact: {
|
|
40
|
+
exact: {
|
|
41
|
+
type: String,
|
|
42
|
+
default: "include"
|
|
43
|
+
},
|
|
35
44
|
/**
|
|
36
45
|
* CSS class to apply when link is active (route matches).
|
|
37
46
|
* @example 'nav-active' | 'selected'
|
|
38
47
|
*/
|
|
39
|
-
activeClass: {
|
|
48
|
+
activeClass: {
|
|
49
|
+
type: String
|
|
50
|
+
},
|
|
40
51
|
/**
|
|
41
52
|
* Event(s) that trigger navigation. Can be string or array of strings.
|
|
42
53
|
* @default 'click'
|
|
@@ -57,84 +68,52 @@ export const RouterLink = defineComponent({
|
|
|
57
68
|
* Only used when type='pushLayer'.
|
|
58
69
|
* @example { zIndex: 1000, autoPush: false, routerOptions: { mode: 'memory' } }
|
|
59
70
|
*/
|
|
60
|
-
layerOptions: {
|
|
71
|
+
layerOptions: {
|
|
72
|
+
type: Object
|
|
73
|
+
},
|
|
61
74
|
/**
|
|
62
|
-
* Custom
|
|
63
|
-
*
|
|
75
|
+
* Custom navigation handler called before navigation.
|
|
76
|
+
* Receives the event object and the event name that triggered navigation.
|
|
64
77
|
*
|
|
65
78
|
* @Note you need to call `e.preventDefault()` to prevent default browser navigation.
|
|
66
|
-
* @default
|
|
67
|
-
*
|
|
68
|
-
* (event: Event & Partial<MouseEvent>): boolean => {
|
|
69
|
-
* // don't redirect with control keys
|
|
70
|
-
* if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return false;
|
|
71
|
-
* // don't redirect when preventDefault called
|
|
72
|
-
* if (e.defaultPrevented) return false;
|
|
73
|
-
* // don't redirect on right click
|
|
74
|
-
* if (e.button !== undefined && e.button !== 0) return false;
|
|
75
|
-
* // don't redirect if `target="_blank"`
|
|
76
|
-
* const target = e.currentTarget?.getAttribute?.('target') ?? '';
|
|
77
|
-
* if (/\b_blank\b/i.test(target)) return false;
|
|
78
|
-
* // Prevent default browser navigation to enable SPA routing
|
|
79
|
-
* // Note: this may be a Weex event which doesn't have this method
|
|
80
|
-
* if (e.preventDefault) e.preventDefault();
|
|
81
|
-
*
|
|
82
|
-
* return true;
|
|
83
|
-
* }
|
|
84
79
|
*/
|
|
85
|
-
|
|
80
|
+
beforeNavigate: {
|
|
86
81
|
type: Function
|
|
87
82
|
}
|
|
88
83
|
},
|
|
89
84
|
setup(props, context) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
data.tag,
|
|
111
|
-
{
|
|
112
|
-
...data.attributes,
|
|
113
|
-
...eventHandlers
|
|
114
|
-
},
|
|
115
|
-
(_a = slots.default) == null ? void 0 : _a.call(slots)
|
|
116
|
-
);
|
|
117
|
-
};
|
|
118
|
-
const vue2renderer = () => {
|
|
119
|
-
var _a;
|
|
120
|
-
const data = link.value;
|
|
121
|
-
const eventHandlers = data.getEventHandlers();
|
|
122
|
-
const $listeners = context.listeners || {};
|
|
123
|
-
Object.entries($listeners).forEach(([key, listener]) => {
|
|
124
|
-
if (typeof listener !== "function") return;
|
|
125
|
-
eventHandlers[key] = wrapHandler(listener, eventHandlers[key]);
|
|
126
|
-
});
|
|
127
|
-
const { class: className, ...attrs2 } = data.attributes;
|
|
85
|
+
const link = useLink(props).value;
|
|
86
|
+
if (isVue3) {
|
|
87
|
+
return () => {
|
|
88
|
+
var _a, _b;
|
|
89
|
+
return h(
|
|
90
|
+
link.tag,
|
|
91
|
+
{
|
|
92
|
+
...link.attributes,
|
|
93
|
+
...context.attrs,
|
|
94
|
+
...link.createEventHandlers(
|
|
95
|
+
(name) => "on".concat(name.charAt(0).toUpperCase()).concat(name.slice(1))
|
|
96
|
+
)
|
|
97
|
+
},
|
|
98
|
+
(_b = (_a = context.slots).default) == null ? void 0 : _b.call(_a)
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return () => {
|
|
103
|
+
var _a, _b;
|
|
104
|
+
const { class: className, ...attributes } = link.attributes;
|
|
128
105
|
return h(
|
|
129
|
-
|
|
106
|
+
link.tag,
|
|
130
107
|
{
|
|
131
|
-
attrs:
|
|
108
|
+
attrs: {
|
|
109
|
+
...attributes,
|
|
110
|
+
...context.attrs
|
|
111
|
+
},
|
|
132
112
|
class: className,
|
|
133
|
-
on:
|
|
113
|
+
on: link.createEventHandlers()
|
|
134
114
|
},
|
|
135
|
-
(_a = slots.default) == null ? void 0 :
|
|
115
|
+
(_b = (_a = context.slots).default) == null ? void 0 : _b.call(_a)
|
|
136
116
|
);
|
|
137
117
|
};
|
|
138
|
-
return isVue3 ? vue3renderer : vue2renderer;
|
|
139
118
|
}
|
|
140
119
|
});
|
|
@@ -51,7 +51,14 @@ describe("router-link.ts - RouterLink Component", () => {
|
|
|
51
51
|
app.unmount();
|
|
52
52
|
}
|
|
53
53
|
if (router) {
|
|
54
|
-
|
|
54
|
+
try {
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
56
|
+
router.destroy();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (!(error instanceof Error) || !error.message.includes("RouteTaskCancelledError")) {
|
|
59
|
+
console.warn("Router destruction error:", error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
55
62
|
}
|
|
56
63
|
if (container.parentNode) {
|
|
57
64
|
container.parentNode.removeChild(container);
|
|
@@ -97,6 +104,29 @@ describe("router-link.ts - RouterLink Component", () => {
|
|
|
97
104
|
expect(linkElement).toBeTruthy();
|
|
98
105
|
expect(linkElement == null ? void 0 : linkElement.textContent).toBe("About Link");
|
|
99
106
|
});
|
|
107
|
+
it("should render router link with custom attributes", async () => {
|
|
108
|
+
const TestApp = defineComponent({
|
|
109
|
+
setup() {
|
|
110
|
+
useProvideRouter(router);
|
|
111
|
+
return () => h(
|
|
112
|
+
RouterLink,
|
|
113
|
+
{
|
|
114
|
+
to: "/about",
|
|
115
|
+
"data-test": "custom-attr",
|
|
116
|
+
title: "Custom Title"
|
|
117
|
+
},
|
|
118
|
+
() => "Link with Attributes"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
app = createApp(TestApp);
|
|
123
|
+
app.mount(container);
|
|
124
|
+
await nextTick();
|
|
125
|
+
const linkElement = container.querySelector("a");
|
|
126
|
+
expect(linkElement).toBeTruthy();
|
|
127
|
+
expect(linkElement == null ? void 0 : linkElement.getAttribute("data-test")).toBe("custom-attr");
|
|
128
|
+
expect(linkElement == null ? void 0 : linkElement.getAttribute("title")).toBe("Custom Title");
|
|
129
|
+
});
|
|
100
130
|
it("should render with custom tag", async () => {
|
|
101
131
|
const TestApp = defineComponent({
|
|
102
132
|
setup() {
|
|
@@ -255,6 +285,36 @@ describe("router-link.ts - RouterLink Component", () => {
|
|
|
255
285
|
expect(router.route.path).toBe("/about");
|
|
256
286
|
expect(router.route.query.tab).toBe("info");
|
|
257
287
|
});
|
|
288
|
+
it("should handle custom navigation handler", async () => {
|
|
289
|
+
let customHandlerCalled = false;
|
|
290
|
+
let receivedEventName = "";
|
|
291
|
+
const TestApp = defineComponent({
|
|
292
|
+
setup() {
|
|
293
|
+
useProvideRouter(router);
|
|
294
|
+
return () => h(
|
|
295
|
+
RouterLink,
|
|
296
|
+
{
|
|
297
|
+
to: "/about",
|
|
298
|
+
beforeNavigate: (event, eventName) => {
|
|
299
|
+
customHandlerCalled = true;
|
|
300
|
+
receivedEventName = eventName;
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
() => "Custom Handler Link"
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
app = createApp(TestApp);
|
|
309
|
+
app.mount(container);
|
|
310
|
+
await nextTick();
|
|
311
|
+
const linkElement = container.querySelector("a");
|
|
312
|
+
expect(linkElement).toBeTruthy();
|
|
313
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
314
|
+
await nextTick();
|
|
315
|
+
expect(customHandlerCalled).toBe(true);
|
|
316
|
+
expect(receivedEventName).toBe("click");
|
|
317
|
+
});
|
|
258
318
|
});
|
|
259
319
|
describe("Props Validation", () => {
|
|
260
320
|
it("should accept string as to prop", async () => {
|
package/dist/router-view.d.ts
CHANGED
package/dist/router-view.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { defineComponent, h, inject, provide } from "vue";
|
|
2
2
|
import { useRoute } from "./use.mjs";
|
|
3
3
|
import { resolveComponent } from "./util.mjs";
|
|
4
|
-
const
|
|
4
|
+
export const RouterViewDepth = Symbol("RouterViewDepth");
|
|
5
5
|
export const RouterView = defineComponent({
|
|
6
6
|
name: "RouterView",
|
|
7
7
|
setup() {
|
|
8
8
|
const route = useRoute();
|
|
9
|
-
const depth = inject(
|
|
10
|
-
provide(
|
|
9
|
+
const depth = inject(RouterViewDepth, 0);
|
|
10
|
+
provide(RouterViewDepth, depth + 1);
|
|
11
11
|
return () => {
|
|
12
12
|
const matchedRoute = route.matched[depth];
|
|
13
13
|
const component = matchedRoute ? resolveComponent(matchedRoute.component) : null;
|
|
@@ -170,11 +170,11 @@ describe("router-view.ts - RouterView Component", () => {
|
|
|
170
170
|
describe("Depth Tracking", () => {
|
|
171
171
|
it("should inject depth 0 by default", async () => {
|
|
172
172
|
let injectedDepth;
|
|
173
|
-
const
|
|
173
|
+
const RouterViewDepth = Symbol("RouterViewDepth");
|
|
174
174
|
const TestRouterView = defineComponent({
|
|
175
175
|
name: "TestRouterView",
|
|
176
176
|
setup() {
|
|
177
|
-
injectedDepth = inject(
|
|
177
|
+
injectedDepth = inject(RouterViewDepth, -1);
|
|
178
178
|
return () => h(RouterView);
|
|
179
179
|
}
|
|
180
180
|
});
|
|
@@ -193,19 +193,19 @@ describe("router-view.ts - RouterView Component", () => {
|
|
|
193
193
|
it("should provide correct depth in nested RouterViews", async () => {
|
|
194
194
|
let parentDepth;
|
|
195
195
|
let childDepth;
|
|
196
|
-
const
|
|
196
|
+
const RouterViewDepth = Symbol("RouterViewDepth");
|
|
197
197
|
const ParentTestComponent = defineComponent({
|
|
198
198
|
name: "ParentTestComponent",
|
|
199
199
|
setup() {
|
|
200
|
-
parentDepth = inject(
|
|
201
|
-
provide(
|
|
200
|
+
parentDepth = inject(RouterViewDepth, -1);
|
|
201
|
+
provide(RouterViewDepth, 0);
|
|
202
202
|
return () => h("div", [h("span", "Parent"), h(ChildTestComponent)]);
|
|
203
203
|
}
|
|
204
204
|
});
|
|
205
205
|
const ChildTestComponent = defineComponent({
|
|
206
206
|
name: "ChildTestComponent",
|
|
207
207
|
setup() {
|
|
208
|
-
childDepth = inject(
|
|
208
|
+
childDepth = inject(RouterViewDepth, -1);
|
|
209
209
|
return () => h("div", "Child");
|
|
210
210
|
}
|
|
211
211
|
});
|
|
@@ -272,12 +272,12 @@ describe("router-view.ts - RouterView Component", () => {
|
|
|
272
272
|
});
|
|
273
273
|
describe("Edge Cases and Error Handling", () => {
|
|
274
274
|
it("should render null when no route matches at current depth", async () => {
|
|
275
|
-
const
|
|
275
|
+
const RouterViewDepth = Symbol("RouterViewDepth");
|
|
276
276
|
const DeepRouterView = defineComponent({
|
|
277
277
|
name: "DeepRouterView",
|
|
278
278
|
setup() {
|
|
279
|
-
const currentDepth = inject(
|
|
280
|
-
provide(
|
|
279
|
+
const currentDepth = inject(RouterViewDepth, 0);
|
|
280
|
+
provide(RouterViewDepth, currentDepth + 1);
|
|
281
281
|
return () => h(RouterView);
|
|
282
282
|
}
|
|
283
283
|
});
|
package/dist/use.d.ts
CHANGED
|
@@ -178,7 +178,7 @@ export declare function useProvideRouter(router: Router): void;
|
|
|
178
178
|
* <template>
|
|
179
179
|
* <a
|
|
180
180
|
* v-bind="link.attributes"
|
|
181
|
-
* v-on="link.
|
|
181
|
+
* v-on="link.createEventHandlers()"
|
|
182
182
|
* :class="{ active: link.isActive }"
|
|
183
183
|
* >
|
|
184
184
|
* Home
|
package/dist/use.mjs
CHANGED
|
@@ -62,7 +62,7 @@ export function useRoute() {
|
|
|
62
62
|
}
|
|
63
63
|
export function useProvideRouter(router) {
|
|
64
64
|
const proxy = getCurrentProxy("useProvideRouter");
|
|
65
|
-
const dep = ref(
|
|
65
|
+
const dep = ref(0);
|
|
66
66
|
const proxiedRouter = createDependentProxy(router, dep);
|
|
67
67
|
const proxiedRoute = createDependentProxy(router.route, dep);
|
|
68
68
|
const context = {
|
|
@@ -74,7 +74,7 @@ export function useProvideRouter(router) {
|
|
|
74
74
|
const unwatch = router.afterEach((to) => {
|
|
75
75
|
if (router.route === to) {
|
|
76
76
|
to.syncTo(proxiedRoute);
|
|
77
|
-
dep.value
|
|
77
|
+
dep.value++;
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
onBeforeUnmount(unwatch);
|
package/dist/util.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ export declare function createSymbolProperty<T>(symbol: symbol): {
|
|
|
4
4
|
readonly set: (instance: any, value: T) => void;
|
|
5
5
|
readonly get: (instance: any) => T | undefined;
|
|
6
6
|
};
|
|
7
|
-
export declare function createDependentProxy<T extends object>(obj: T, dep: Ref<
|
|
7
|
+
export declare function createDependentProxy<T extends object>(obj: T, dep: Ref<any>): T;
|
|
8
8
|
export declare function isESModule(obj: unknown): obj is Record<string | symbol, any>;
|
|
9
9
|
export declare function resolveComponent(component: unknown): unknown;
|
package/package.json
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"vue": "^3.5.0 || ^2.7.0"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@esmx/router": "3.0.0-rc.
|
|
53
|
+
"@esmx/router": "3.0.0-rc.63"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@biomejs/biome": "1.9.4",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"vue": "3.5.13",
|
|
63
63
|
"vue2": "npm:vue@2.7.16"
|
|
64
64
|
},
|
|
65
|
-
"version": "3.0.0-rc.
|
|
65
|
+
"version": "3.0.0-rc.63",
|
|
66
66
|
"type": "module",
|
|
67
67
|
"private": false,
|
|
68
68
|
"exports": {
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"template",
|
|
82
82
|
"public"
|
|
83
83
|
],
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "18a524ff1b7f9c6ea60d8bb57ad314329febc58c"
|
|
85
85
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -79,6 +79,7 @@ describe('index.ts - Package Entry Point', () => {
|
|
|
79
79
|
// Components
|
|
80
80
|
'RouterLink',
|
|
81
81
|
'RouterView',
|
|
82
|
+
'RouterViewDepth',
|
|
82
83
|
// Plugin
|
|
83
84
|
'RouterPlugin'
|
|
84
85
|
];
|
|
@@ -102,6 +103,7 @@ describe('index.ts - Package Entry Point', () => {
|
|
|
102
103
|
'getRoute',
|
|
103
104
|
'RouterLink',
|
|
104
105
|
'RouterView',
|
|
106
|
+
'RouterViewDepth',
|
|
105
107
|
'RouterPlugin'
|
|
106
108
|
];
|
|
107
109
|
|
package/src/index.ts
CHANGED
package/src/router-link.test.ts
CHANGED
|
@@ -66,7 +66,20 @@ describe('router-link.ts - RouterLink Component', () => {
|
|
|
66
66
|
app.unmount();
|
|
67
67
|
}
|
|
68
68
|
if (router) {
|
|
69
|
-
|
|
69
|
+
try {
|
|
70
|
+
// Wait for any pending navigation to complete before destroying
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
72
|
+
router.destroy();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Ignore router destruction errors, as they might be expected
|
|
75
|
+
// when navigation tasks are cancelled during cleanup
|
|
76
|
+
if (
|
|
77
|
+
!(error instanceof Error) ||
|
|
78
|
+
!error.message.includes('RouteTaskCancelledError')
|
|
79
|
+
) {
|
|
80
|
+
console.warn('Router destruction error:', error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
70
83
|
}
|
|
71
84
|
if (container.parentNode) {
|
|
72
85
|
container.parentNode.removeChild(container);
|
|
@@ -129,6 +142,33 @@ describe('router-link.ts - RouterLink Component', () => {
|
|
|
129
142
|
expect(linkElement?.textContent).toBe('About Link');
|
|
130
143
|
});
|
|
131
144
|
|
|
145
|
+
it('should render router link with custom attributes', async () => {
|
|
146
|
+
const TestApp = defineComponent({
|
|
147
|
+
setup() {
|
|
148
|
+
useProvideRouter(router);
|
|
149
|
+
return () =>
|
|
150
|
+
h(
|
|
151
|
+
RouterLink,
|
|
152
|
+
{
|
|
153
|
+
to: '/about',
|
|
154
|
+
'data-test': 'custom-attr',
|
|
155
|
+
title: 'Custom Title'
|
|
156
|
+
},
|
|
157
|
+
() => 'Link with Attributes'
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
app = createApp(TestApp);
|
|
163
|
+
app.mount(container);
|
|
164
|
+
await nextTick();
|
|
165
|
+
|
|
166
|
+
const linkElement = container.querySelector('a');
|
|
167
|
+
expect(linkElement).toBeTruthy();
|
|
168
|
+
expect(linkElement?.getAttribute('data-test')).toBe('custom-attr');
|
|
169
|
+
expect(linkElement?.getAttribute('title')).toBe('Custom Title');
|
|
170
|
+
});
|
|
171
|
+
|
|
132
172
|
it('should render with custom tag', async () => {
|
|
133
173
|
const TestApp = defineComponent({
|
|
134
174
|
setup() {
|
|
@@ -326,6 +366,47 @@ describe('router-link.ts - RouterLink Component', () => {
|
|
|
326
366
|
expect(router.route.path).toBe('/about');
|
|
327
367
|
expect(router.route.query.tab).toBe('info');
|
|
328
368
|
});
|
|
369
|
+
|
|
370
|
+
it('should handle custom navigation handler', async () => {
|
|
371
|
+
let customHandlerCalled = false;
|
|
372
|
+
let receivedEventName = '';
|
|
373
|
+
const TestApp = defineComponent({
|
|
374
|
+
setup() {
|
|
375
|
+
useProvideRouter(router);
|
|
376
|
+
return () =>
|
|
377
|
+
h(
|
|
378
|
+
RouterLink,
|
|
379
|
+
{
|
|
380
|
+
to: '/about',
|
|
381
|
+
beforeNavigate: (
|
|
382
|
+
event: Event,
|
|
383
|
+
eventName: string
|
|
384
|
+
) => {
|
|
385
|
+
customHandlerCalled = true;
|
|
386
|
+
receivedEventName = eventName;
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
() => 'Custom Handler Link'
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
app = createApp(TestApp);
|
|
396
|
+
app.mount(container);
|
|
397
|
+
await nextTick();
|
|
398
|
+
|
|
399
|
+
const linkElement = container.querySelector('a');
|
|
400
|
+
expect(linkElement).toBeTruthy();
|
|
401
|
+
|
|
402
|
+
// Simulate click
|
|
403
|
+
linkElement?.click();
|
|
404
|
+
await nextTick();
|
|
405
|
+
|
|
406
|
+
// Check if custom handler was called with correct event name
|
|
407
|
+
expect(customHandlerCalled).toBe(true);
|
|
408
|
+
expect(receivedEventName).toBe('click');
|
|
409
|
+
});
|
|
329
410
|
});
|
|
330
411
|
|
|
331
412
|
describe('Props Validation', () => {
|
package/src/router-link.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
RouteLayerOptions,
|
|
3
|
-
RouteLocationInput,
|
|
4
|
-
RouteMatchType,
|
|
5
|
-
RouterLinkType
|
|
6
|
-
} from '@esmx/router';
|
|
1
|
+
import type { RouterLinkProps } from '@esmx/router';
|
|
7
2
|
import { type PropType, defineComponent, h } from 'vue';
|
|
8
3
|
import { useLink } from './use';
|
|
9
4
|
import { isVue3 } from './util';
|
|
@@ -66,7 +61,7 @@ export const RouterLink = defineComponent({
|
|
|
66
61
|
* @example '/home' | { path: '/user', query: { id: '123' } }
|
|
67
62
|
*/
|
|
68
63
|
to: {
|
|
69
|
-
type: [String, Object] as PropType<
|
|
64
|
+
type: [String, Object] as PropType<RouterLinkProps['to']>,
|
|
70
65
|
required: true
|
|
71
66
|
},
|
|
72
67
|
/**
|
|
@@ -74,12 +69,18 @@ export const RouterLink = defineComponent({
|
|
|
74
69
|
* @default 'push'
|
|
75
70
|
* @example 'push' | 'replace' | 'pushWindow' | 'replaceWindow' | 'pushLayer'
|
|
76
71
|
*/
|
|
77
|
-
type: {
|
|
72
|
+
type: {
|
|
73
|
+
type: String as PropType<RouterLinkProps['type']>,
|
|
74
|
+
default: 'push'
|
|
75
|
+
},
|
|
78
76
|
/**
|
|
79
77
|
* @deprecated Use 'type="replace"' instead
|
|
80
78
|
* @example :replace={true} → type="replace"
|
|
81
79
|
*/
|
|
82
|
-
replace: {
|
|
80
|
+
replace: {
|
|
81
|
+
type: Boolean as PropType<RouterLinkProps['replace']>,
|
|
82
|
+
default: false
|
|
83
|
+
},
|
|
83
84
|
/**
|
|
84
85
|
* How to match the active state.
|
|
85
86
|
* - 'include': Match if current route includes this path
|
|
@@ -87,19 +88,24 @@ export const RouterLink = defineComponent({
|
|
|
87
88
|
* - 'route': Match based on route configuration
|
|
88
89
|
* @default 'include'
|
|
89
90
|
*/
|
|
90
|
-
exact: {
|
|
91
|
+
exact: {
|
|
92
|
+
type: String as PropType<RouterLinkProps['exact']>,
|
|
93
|
+
default: 'include'
|
|
94
|
+
},
|
|
91
95
|
/**
|
|
92
96
|
* CSS class to apply when link is active (route matches).
|
|
93
97
|
* @example 'nav-active' | 'selected'
|
|
94
98
|
*/
|
|
95
|
-
activeClass: {
|
|
99
|
+
activeClass: {
|
|
100
|
+
type: String as PropType<RouterLinkProps['activeClass']>
|
|
101
|
+
},
|
|
96
102
|
/**
|
|
97
103
|
* Event(s) that trigger navigation. Can be string or array of strings.
|
|
98
104
|
* @default 'click'
|
|
99
105
|
* @example 'click' | ['click', 'mouseenter']
|
|
100
106
|
*/
|
|
101
107
|
event: {
|
|
102
|
-
type: [String, Array] as PropType<
|
|
108
|
+
type: [String, Array] as PropType<RouterLinkProps['event']>,
|
|
103
109
|
default: 'click'
|
|
104
110
|
},
|
|
105
111
|
/**
|
|
@@ -107,108 +113,59 @@ export const RouterLink = defineComponent({
|
|
|
107
113
|
* @default 'a'
|
|
108
114
|
* @example 'button' | 'div' | 'span'
|
|
109
115
|
*/
|
|
110
|
-
tag: { type: String
|
|
116
|
+
tag: { type: String as PropType<RouterLinkProps['tag']>, default: 'a' },
|
|
111
117
|
/**
|
|
112
118
|
* Layer options for layer-based navigation.
|
|
113
119
|
* Only used when type='pushLayer'.
|
|
114
120
|
* @example { zIndex: 1000, autoPush: false, routerOptions: { mode: 'memory' } }
|
|
115
121
|
*/
|
|
116
|
-
layerOptions: {
|
|
122
|
+
layerOptions: {
|
|
123
|
+
type: Object as PropType<RouterLinkProps['layerOptions']>
|
|
124
|
+
},
|
|
117
125
|
/**
|
|
118
|
-
* Custom
|
|
119
|
-
*
|
|
126
|
+
* Custom navigation handler called before navigation.
|
|
127
|
+
* Receives the event object and the event name that triggered navigation.
|
|
120
128
|
*
|
|
121
129
|
* @Note you need to call `e.preventDefault()` to prevent default browser navigation.
|
|
122
|
-
* @default
|
|
123
|
-
*
|
|
124
|
-
* (event: Event & Partial<MouseEvent>): boolean => {
|
|
125
|
-
* // don't redirect with control keys
|
|
126
|
-
* if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return false;
|
|
127
|
-
* // don't redirect when preventDefault called
|
|
128
|
-
* if (e.defaultPrevented) return false;
|
|
129
|
-
* // don't redirect on right click
|
|
130
|
-
* if (e.button !== undefined && e.button !== 0) return false;
|
|
131
|
-
* // don't redirect if `target="_blank"`
|
|
132
|
-
* const target = e.currentTarget?.getAttribute?.('target') ?? '';
|
|
133
|
-
* if (/\b_blank\b/i.test(target)) return false;
|
|
134
|
-
* // Prevent default browser navigation to enable SPA routing
|
|
135
|
-
* // Note: this may be a Weex event which doesn't have this method
|
|
136
|
-
* if (e.preventDefault) e.preventDefault();
|
|
137
|
-
*
|
|
138
|
-
* return true;
|
|
139
|
-
* }
|
|
140
130
|
*/
|
|
141
|
-
|
|
142
|
-
type: Function as PropType<
|
|
143
|
-
(event: Event) => boolean | undefined | void
|
|
144
|
-
>
|
|
131
|
+
beforeNavigate: {
|
|
132
|
+
type: Function as PropType<RouterLinkProps['beforeNavigate']>
|
|
145
133
|
}
|
|
146
134
|
},
|
|
147
135
|
|
|
148
136
|
setup(props, context) {
|
|
149
|
-
const
|
|
150
|
-
const link = useLink(props);
|
|
151
|
-
|
|
152
|
-
const wrapHandler = (
|
|
153
|
-
externalHandler: Function,
|
|
154
|
-
internalHandler: Function | undefined
|
|
155
|
-
) =>
|
|
156
|
-
!internalHandler
|
|
157
|
-
? (externalHandler as (e: Event) => Promise<void>)
|
|
158
|
-
: async (e: Event) => {
|
|
159
|
-
try {
|
|
160
|
-
await externalHandler(e);
|
|
161
|
-
} finally {
|
|
162
|
-
await internalHandler(e);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const vue3renderer = () => {
|
|
167
|
-
const data = link.value;
|
|
168
|
-
const genEventName = (name: string): string =>
|
|
169
|
-
`on${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
170
|
-
|
|
171
|
-
const eventHandlers = data.getEventHandlers(genEventName);
|
|
172
|
-
Object.entries(attrs).forEach(([key, listener]) => {
|
|
173
|
-
// In Vue 3, external event handlers are in attrs with 'on' prefix
|
|
174
|
-
if (!key.startsWith('on') || typeof listener !== 'function')
|
|
175
|
-
return;
|
|
176
|
-
eventHandlers[key] = wrapHandler(listener, eventHandlers[key]);
|
|
177
|
-
});
|
|
137
|
+
const link = useLink(props).value;
|
|
178
138
|
|
|
139
|
+
if (isVue3) {
|
|
140
|
+
return () => {
|
|
141
|
+
return h(
|
|
142
|
+
link.tag,
|
|
143
|
+
{
|
|
144
|
+
...link.attributes,
|
|
145
|
+
...context.attrs,
|
|
146
|
+
...link.createEventHandlers(
|
|
147
|
+
(name) =>
|
|
148
|
+
`on${name.charAt(0).toUpperCase()}${name.slice(1)}`
|
|
149
|
+
)
|
|
150
|
+
},
|
|
151
|
+
context.slots.default?.()
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return () => {
|
|
156
|
+
const { class: className, ...attributes } = link.attributes;
|
|
179
157
|
return h(
|
|
180
|
-
|
|
181
|
-
{
|
|
182
|
-
...data.attributes,
|
|
183
|
-
...eventHandlers
|
|
184
|
-
},
|
|
185
|
-
slots.default?.()
|
|
186
|
-
);
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const vue2renderer = () => {
|
|
190
|
-
const data = link.value;
|
|
191
|
-
|
|
192
|
-
const eventHandlers = data.getEventHandlers();
|
|
193
|
-
// Vue 2: get external listeners from context
|
|
194
|
-
const $listeners = (context as any).listeners || {};
|
|
195
|
-
Object.entries($listeners).forEach(([key, listener]) => {
|
|
196
|
-
if (typeof listener !== 'function') return;
|
|
197
|
-
eventHandlers[key] = wrapHandler(listener, eventHandlers[key]);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
const { class: className, ...attrs } = data.attributes;
|
|
201
|
-
return h(
|
|
202
|
-
data.tag,
|
|
158
|
+
link.tag,
|
|
203
159
|
{
|
|
204
|
-
attrs
|
|
160
|
+
attrs: {
|
|
161
|
+
...attributes,
|
|
162
|
+
...context.attrs
|
|
163
|
+
},
|
|
205
164
|
class: className,
|
|
206
|
-
on:
|
|
165
|
+
on: link.createEventHandlers()
|
|
207
166
|
},
|
|
208
|
-
slots.default?.()
|
|
167
|
+
context.slots.default?.()
|
|
209
168
|
);
|
|
210
169
|
};
|
|
211
|
-
|
|
212
|
-
return isVue3 ? vue3renderer : vue2renderer;
|
|
213
170
|
}
|
|
214
171
|
});
|
package/src/router-view.test.ts
CHANGED
|
@@ -231,13 +231,13 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
231
231
|
let injectedDepth: number | undefined;
|
|
232
232
|
|
|
233
233
|
// Use the same symbol key that RouterView uses internally
|
|
234
|
-
const
|
|
234
|
+
const RouterViewDepth = Symbol('RouterViewDepth');
|
|
235
235
|
|
|
236
236
|
// Create a custom RouterView component that can capture the injected depth
|
|
237
237
|
const TestRouterView = defineComponent({
|
|
238
238
|
name: 'TestRouterView',
|
|
239
239
|
setup() {
|
|
240
|
-
injectedDepth = inject(
|
|
240
|
+
injectedDepth = inject(RouterViewDepth, -1);
|
|
241
241
|
return () => h(RouterView);
|
|
242
242
|
}
|
|
243
243
|
});
|
|
@@ -263,13 +263,13 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
263
263
|
let parentDepth: number | undefined;
|
|
264
264
|
let childDepth: number | undefined;
|
|
265
265
|
|
|
266
|
-
const
|
|
266
|
+
const RouterViewDepth = Symbol('RouterViewDepth');
|
|
267
267
|
|
|
268
268
|
const ParentTestComponent = defineComponent({
|
|
269
269
|
name: 'ParentTestComponent',
|
|
270
270
|
setup() {
|
|
271
|
-
parentDepth = inject(
|
|
272
|
-
provide(
|
|
271
|
+
parentDepth = inject(RouterViewDepth, -1);
|
|
272
|
+
provide(RouterViewDepth, 0); // Simulate parent RouterView
|
|
273
273
|
return () =>
|
|
274
274
|
h('div', [h('span', 'Parent'), h(ChildTestComponent)]);
|
|
275
275
|
}
|
|
@@ -278,7 +278,7 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
278
278
|
const ChildTestComponent = defineComponent({
|
|
279
279
|
name: 'ChildTestComponent',
|
|
280
280
|
setup() {
|
|
281
|
-
childDepth = inject(
|
|
281
|
+
childDepth = inject(RouterViewDepth, -1);
|
|
282
282
|
return () => h('div', 'Child');
|
|
283
283
|
}
|
|
284
284
|
});
|
|
@@ -362,14 +362,14 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
362
362
|
|
|
363
363
|
describe('Edge Cases and Error Handling', () => {
|
|
364
364
|
it('should render null when no route matches at current depth', async () => {
|
|
365
|
-
const
|
|
365
|
+
const RouterViewDepth = Symbol('RouterViewDepth');
|
|
366
366
|
|
|
367
367
|
const DeepRouterView = defineComponent({
|
|
368
368
|
name: 'DeepRouterView',
|
|
369
369
|
setup() {
|
|
370
370
|
// Inject depth 0 from parent RouterView and provide depth 1
|
|
371
|
-
const currentDepth = inject(
|
|
372
|
-
provide(
|
|
371
|
+
const currentDepth = inject(RouterViewDepth, 0);
|
|
372
|
+
provide(RouterViewDepth, currentDepth + 1);
|
|
373
373
|
return () => h(RouterView);
|
|
374
374
|
}
|
|
375
375
|
});
|
package/src/router-view.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { defineComponent, h, inject, provide } from 'vue';
|
|
|
2
2
|
import { useRoute } from './use';
|
|
3
3
|
import { resolveComponent } from './util';
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
export const RouterViewDepth = Symbol('RouterViewDepth');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* RouterView component for rendering matched route components.
|
|
@@ -39,11 +39,11 @@ export const RouterView = defineComponent({
|
|
|
39
39
|
|
|
40
40
|
// Get current RouterView depth from parent RouterView (if any)
|
|
41
41
|
// This enables proper nested routing by tracking how deep we are in the component tree
|
|
42
|
-
const depth = inject(
|
|
42
|
+
const depth = inject(RouterViewDepth, 0);
|
|
43
43
|
|
|
44
44
|
// Provide depth + 1 to child RouterView components
|
|
45
45
|
// This ensures each nested RouterView renders the correct route component
|
|
46
|
-
provide(
|
|
46
|
+
provide(RouterViewDepth, depth + 1);
|
|
47
47
|
|
|
48
48
|
return () => {
|
|
49
49
|
// Get the matched route configuration at current depth
|
package/src/use.ts
CHANGED
|
@@ -259,7 +259,7 @@ export function useRoute(): Route {
|
|
|
259
259
|
export function useProvideRouter(router: Router): void {
|
|
260
260
|
const proxy = getCurrentProxy('useProvideRouter');
|
|
261
261
|
|
|
262
|
-
const dep = ref(
|
|
262
|
+
const dep = ref(0);
|
|
263
263
|
|
|
264
264
|
const proxiedRouter = createDependentProxy(router, dep);
|
|
265
265
|
const proxiedRoute = createDependentProxy(router.route, dep);
|
|
@@ -275,7 +275,7 @@ export function useProvideRouter(router: Router): void {
|
|
|
275
275
|
const unwatch = router.afterEach((to: Route) => {
|
|
276
276
|
if (router.route === to) {
|
|
277
277
|
to.syncTo(proxiedRoute);
|
|
278
|
-
dep.value
|
|
278
|
+
dep.value++;
|
|
279
279
|
}
|
|
280
280
|
});
|
|
281
281
|
|
|
@@ -294,7 +294,7 @@ export function useProvideRouter(router: Router): void {
|
|
|
294
294
|
* <template>
|
|
295
295
|
* <a
|
|
296
296
|
* v-bind="link.attributes"
|
|
297
|
-
* v-on="link.
|
|
297
|
+
* v-on="link.createEventHandlers()"
|
|
298
298
|
* :class="{ active: link.isActive }"
|
|
299
299
|
* >
|
|
300
300
|
* Home
|
package/src/util.ts
CHANGED