@esmx/router-vue 3.0.0-rc.103

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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +570 -0
  3. package/README.zh-CN.md +570 -0
  4. package/dist/index.d.ts +6 -0
  5. package/dist/index.mjs +13 -0
  6. package/dist/index.test.d.ts +1 -0
  7. package/dist/index.test.mjs +216 -0
  8. package/dist/plugin.d.ts +61 -0
  9. package/dist/plugin.mjs +41 -0
  10. package/dist/plugin.test.d.ts +1 -0
  11. package/dist/plugin.test.mjs +631 -0
  12. package/dist/router-link.d.ts +220 -0
  13. package/dist/router-link.mjs +119 -0
  14. package/dist/router-link.test.d.ts +1 -0
  15. package/dist/router-link.test.mjs +663 -0
  16. package/dist/router-view.d.ts +31 -0
  17. package/dist/router-view.mjs +15 -0
  18. package/dist/router-view.test.d.ts +1 -0
  19. package/dist/router-view.test.mjs +676 -0
  20. package/dist/run-with-context.test.d.ts +1 -0
  21. package/dist/run-with-context.test.mjs +57 -0
  22. package/dist/use.d.ts +260 -0
  23. package/dist/use.mjs +125 -0
  24. package/dist/use.test.d.ts +1 -0
  25. package/dist/use.test.mjs +381 -0
  26. package/dist/util.d.ts +20 -0
  27. package/dist/util.mjs +49 -0
  28. package/dist/util.test.d.ts +4 -0
  29. package/dist/util.test.mjs +604 -0
  30. package/dist/vue2.d.ts +15 -0
  31. package/dist/vue2.mjs +0 -0
  32. package/dist/vue3.d.ts +13 -0
  33. package/dist/vue3.mjs +0 -0
  34. package/package.json +85 -0
  35. package/src/index.test.ts +273 -0
  36. package/src/index.ts +15 -0
  37. package/src/plugin.test.ts +812 -0
  38. package/src/plugin.ts +107 -0
  39. package/src/router-link.test.ts +830 -0
  40. package/src/router-link.ts +172 -0
  41. package/src/router-view.test.ts +840 -0
  42. package/src/router-view.ts +59 -0
  43. package/src/run-with-context.test.ts +64 -0
  44. package/src/use.test.ts +484 -0
  45. package/src/use.ts +416 -0
  46. package/src/util.test.ts +760 -0
  47. package/src/util.ts +85 -0
  48. package/src/vue2.ts +18 -0
  49. package/src/vue3.ts +15 -0
package/src/use.ts ADDED
@@ -0,0 +1,416 @@
1
+ import type { Route, Router, RouterLinkProps } from '@esmx/router';
2
+ import {
3
+ computed,
4
+ getCurrentInstance,
5
+ inject,
6
+ onBeforeUnmount,
7
+ provide,
8
+ ref
9
+ } from 'vue';
10
+ import {
11
+ createDependentProxy,
12
+ createSymbolProperty,
13
+ defineRouterProperties,
14
+ isVue2
15
+ } from './util';
16
+
17
+ export interface VueInstance {
18
+ $parent?: VueInstance | null;
19
+ $root?: VueInstance | null;
20
+ $children?: VueInstance[] | null;
21
+ }
22
+
23
+ interface RouterContext {
24
+ router: Router;
25
+ route: Route;
26
+ }
27
+
28
+ const ROUTER_CONTEXT_KEY = Symbol('router-context');
29
+ const ROUTER_INJECT_KEY = Symbol('router-inject');
30
+ const ROUTER_VIEW_DEPTH_KEY = Symbol('router-view-depth');
31
+
32
+ const routerContextProperty =
33
+ createSymbolProperty<RouterContext>(ROUTER_CONTEXT_KEY);
34
+
35
+ const routerViewDepthProperty = createSymbolProperty<number>(
36
+ ROUTER_VIEW_DEPTH_KEY
37
+ );
38
+
39
+ function getCurrentProxy(): VueInstance {
40
+ const instance = getCurrentInstance();
41
+ if (!instance || !instance.proxy) {
42
+ throw new Error(
43
+ '[@esmx/router-vue] Must be used within setup() or other composition functions'
44
+ );
45
+ }
46
+ return instance.proxy;
47
+ }
48
+
49
+ function findRouterContext(vm: VueInstance): RouterContext {
50
+ let context = routerContextProperty.get(vm);
51
+ if (context) {
52
+ return context;
53
+ }
54
+
55
+ let current = vm.$parent;
56
+ while (current) {
57
+ context = routerContextProperty.get(current);
58
+ if (context) {
59
+ routerContextProperty.set(vm, context);
60
+ return context;
61
+ }
62
+ current = current.$parent;
63
+ }
64
+
65
+ throw new Error(
66
+ '[@esmx/router-vue] Router context not found. Please ensure useProvideRouter() is called in a parent component.'
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Get router instance from a Vue component instance.
72
+ * This is a lower-level function used internally by useRouter().
73
+ * Use this in Options API, use useRouter() in Composition API.
74
+ *
75
+ * @param instance - Vue component instance
76
+ * @returns Router instance
77
+ * @throws {Error} If router context is not found
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * // Options API usage
82
+ * import { defineComponent } from 'vue';
83
+ * import { getRouter } from '@esmx/router-vue';
84
+ *
85
+ * export default defineComponent({
86
+ * mounted() {
87
+ * const router = getRouter(this);
88
+ * router.push('/dashboard');
89
+ * },
90
+ * methods: {
91
+ * handleNavigation() {
92
+ * const router = getRouter(this);
93
+ * router.replace('/profile');
94
+ * }
95
+ * }
96
+ * });
97
+ * ```
98
+ */
99
+ export function getRouter(instance: VueInstance): Router {
100
+ return findRouterContext(instance).router;
101
+ }
102
+
103
+ /**
104
+ * Get current route from a Vue component instance.
105
+ * This is a lower-level function used internally by useRoute().
106
+ * Use this in Options API, use useRoute() in Composition API.
107
+ *
108
+ * @param instance - Vue component instance
109
+ * @returns Current route object
110
+ * @throws {Error} If router context is not found
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * // Options API usage
115
+ * import { defineComponent } from 'vue';
116
+ * import { getRoute } from '@esmx/router-vue';
117
+ *
118
+ * export default defineComponent({
119
+ * computed: {
120
+ * routeInfo() {
121
+ * const route = getRoute(this);
122
+ * return {
123
+ * path: route.path,
124
+ * params: route.params,
125
+ * query: route.query
126
+ * };
127
+ * }
128
+ * }
129
+ * });
130
+ * ```
131
+ */
132
+ export function getRoute(instance: VueInstance): Route {
133
+ return findRouterContext(instance).route;
134
+ }
135
+
136
+ /**
137
+ * Get router context using the optimal method available.
138
+ * First tries provide/inject (works in setup), then falls back to hierarchy traversal.
139
+ */
140
+ function useRouterContext(): RouterContext {
141
+ // First try to get context from provide/inject (works in setup)
142
+ const injectedContext = inject<RouterContext>(ROUTER_INJECT_KEY);
143
+ if (injectedContext) {
144
+ return injectedContext;
145
+ }
146
+
147
+ // Fallback to component hierarchy traversal (works after mount)
148
+ const proxy = getCurrentProxy();
149
+ return findRouterContext(proxy);
150
+ }
151
+
152
+ /**
153
+ * Get the router instance in a Vue component.
154
+ * Must be called within setup() or other composition functions.
155
+ * Use this in Composition API, use getRouter() in Options API.
156
+ *
157
+ * @returns Router instance for navigation and route management
158
+ * @throws {Error} If called outside setup() or router context not found
159
+ *
160
+ * @example
161
+ * ```vue
162
+ * <script setup lang="ts">
163
+ * import { useRouter } from '@esmx/router-vue';
164
+ *
165
+ * const router = useRouter();
166
+ *
167
+ * const navigateToHome = () => {
168
+ * router.push('/home');
169
+ * };
170
+ *
171
+ * const goBack = () => {
172
+ * router.back();
173
+ * };
174
+ *
175
+ * const navigateWithQuery = () => {
176
+ * router.push({
177
+ * path: '/search',
178
+ * query: { q: 'vue router', page: '1' }
179
+ * });
180
+ * };
181
+ * </script>
182
+ * ```
183
+ */
184
+ export function useRouter(): Router {
185
+ return useRouterContext().router;
186
+ }
187
+
188
+ /**
189
+ * Get the current route information in a Vue component.
190
+ * Returns a reactive reference that automatically updates when the route changes.
191
+ * Must be called within setup() or other composition functions.
192
+ * Use this in Composition API, use getRoute() in Options API.
193
+ *
194
+ * @returns Current route object with path, params, query, etc.
195
+ * @throws {Error} If called outside setup() or router context not found
196
+ *
197
+ * @example
198
+ * ```vue
199
+ * <template>
200
+ * <div>
201
+ * <h1>{{ route.meta?.title || 'Page' }}</h1>
202
+ * <p>Path: {{ route.path }}</p>
203
+ * <p>Params: {{ JSON.stringify(route.params) }}</p>
204
+ * <p>Query: {{ JSON.stringify(route.query) }}</p>
205
+ * </div>
206
+ * </template>
207
+ *
208
+ * <script setup lang="ts">
209
+ * import { useRoute } from '@esmx/router-vue';
210
+ * import { watch } from 'vue';
211
+ *
212
+ * const route = useRoute();
213
+ *
214
+ * watch(() => route.path, (newPath) => {
215
+ * console.log('Route changed to:', newPath);
216
+ * });
217
+ * </script>
218
+ * ```
219
+ */
220
+ export function useRoute(): Route {
221
+ return useRouterContext().route;
222
+ }
223
+
224
+ /**
225
+ * Provide router context to child components.
226
+ * This must be called in a parent component to make the router available
227
+ * to child components via useRouter() and useRoute().
228
+ *
229
+ * @param router - Router instance to provide to child components
230
+ * @throws {Error} If called outside setup()
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * // Vue 3 usage
235
+ * import { createApp } from 'vue';
236
+ * import { Router } from '@esmx/router';
237
+ * import { useProvideRouter } from '@esmx/router-vue';
238
+ *
239
+ * const routes = [
240
+ * { path: '/', component: () => import('./Home.vue') },
241
+ * { path: '/about', component: () => import('./About.vue') }
242
+ * ];
243
+ *
244
+ * const router = new Router({ routes });
245
+ * const app = createApp({
246
+ * setup() {
247
+ * useProvideRouter(router);
248
+ * }
249
+ * });
250
+ * app.mount('#app');
251
+ * ```
252
+ */
253
+ export function useProvideRouter(router: Router): void {
254
+ const proxy = getCurrentProxy();
255
+
256
+ const dep = ref(0);
257
+
258
+ const proxiedRouter = createDependentProxy(router, dep);
259
+ const proxiedRoute = createDependentProxy(router.route, dep);
260
+
261
+ const context: RouterContext = {
262
+ router: proxiedRouter,
263
+ route: proxiedRoute
264
+ };
265
+
266
+ provide(ROUTER_INJECT_KEY, context);
267
+ routerContextProperty.set(proxy, context);
268
+
269
+ if (!isVue2) {
270
+ const app = getCurrentInstance()!.appContext.app;
271
+ defineRouterProperties(
272
+ app.config.globalProperties,
273
+ () => proxiedRouter,
274
+ () => proxiedRoute,
275
+ true
276
+ );
277
+ }
278
+
279
+ const unwatch = router.afterEach((to: Route) => {
280
+ if (router.route === to) {
281
+ to.syncTo(proxiedRoute);
282
+ dep.value++;
283
+ }
284
+ });
285
+
286
+ onBeforeUnmount(unwatch);
287
+ }
288
+
289
+ /**
290
+ * Get the current RouterView depth in nested routing scenarios.
291
+ * Returns the depth of the current RouterView component in the component tree.
292
+ * Useful for advanced routing scenarios where you need to know the nesting level.
293
+ *
294
+ * @param isRender - Whether this is used in a RouterView component that needs to provide depth for children (default: false)
295
+ * @returns Current RouterView depth (0 for root level, 1 for first nested level, etc.)
296
+ * @throws {Error} If called outside setup()
297
+ *
298
+ * @example
299
+ * ```vue
300
+ * <template>
301
+ * <div>
302
+ * <p>Current RouterView depth: {{ depth }}</p>
303
+ * <RouterView />
304
+ * </div>
305
+ * </template>
306
+ *
307
+ * <script setup lang="ts">
308
+ * import { useRouterViewDepth } from '@esmx/router-vue';
309
+ *
310
+ * // Get current depth without providing for children
311
+ * const depth = useRouterViewDepth();
312
+ * console.log('Current RouterView depth:', depth); // 0, 1, 2, etc.
313
+ *
314
+ * // Get current depth and provide depth + 1 for children (used in RouterView component)
315
+ * const depth = useRouterViewDepth(true);
316
+ * </script>
317
+ * ```
318
+ */
319
+ export function _useRouterViewDepth(isRender?: boolean): number {
320
+ const depth = inject(ROUTER_VIEW_DEPTH_KEY, 0);
321
+
322
+ if (isRender) {
323
+ provide(ROUTER_VIEW_DEPTH_KEY, depth + 1);
324
+ const proxy = getCurrentProxy();
325
+ routerViewDepthProperty.set(proxy, depth + 1);
326
+ }
327
+
328
+ return depth;
329
+ }
330
+ /**
331
+ * Get the current RouterView depth in nested routing scenarios.
332
+ * Returns the depth of the current RouterView component in the component tree.
333
+ * Useful for advanced routing scenarios where you need to know the nesting level.
334
+ *
335
+ * @returns Current RouterView depth (0 for root level, 1 for first nested level, etc.)
336
+ * @throws {Error} If called outside setup()
337
+ *
338
+ * @example
339
+ * ```vue
340
+ * <template>
341
+ * <div>
342
+ * <p>Current RouterView depth: {{ depth }}</p>
343
+ * <RouterView />
344
+ * </div>
345
+ * </template>
346
+ *
347
+ * <script setup lang="ts">
348
+ * import { useRouterViewDepth } from '@esmx/router-vue';
349
+ *
350
+ * // Get current depth without providing for children
351
+ * const depth = useRouterViewDepth();
352
+ * console.log('Current RouterView depth:', depth); // 0, 1, 2, etc.
353
+ * </script>
354
+ * ```
355
+ */
356
+ export function useRouterViewDepth(): number {
357
+ return _useRouterViewDepth();
358
+ }
359
+
360
+ /**
361
+ * Get injected RouterView depth from a Vue instance's ancestors.
362
+ * Traverses parent chain to find the value provided under ROUTER_VIEW_DEPTH_KEY.
363
+ *
364
+ * @param instance - Vue component instance to start from
365
+ * @returns Injected RouterView depth value from nearest ancestor
366
+ * @throws {Error} If no ancestor provided ROUTER_VIEW_DEPTH_KEY
367
+ */
368
+ export function getRouterViewDepth(instance: VueInstance): number {
369
+ let current = instance.$parent;
370
+ while (current) {
371
+ const value = routerViewDepthProperty.get(current);
372
+ if (typeof value === 'number') return value;
373
+ current = current.$parent;
374
+ }
375
+ throw new Error(
376
+ '[@esmx/router-vue] RouterView depth not found. Please ensure a RouterView exists in ancestor components.'
377
+ );
378
+ }
379
+
380
+ /**
381
+ * Create reactive link helpers for navigation elements.
382
+ * Returns computed properties for link attributes, classes, and event handlers.
383
+ *
384
+ * @param props - RouterLink properties configuration
385
+ * @returns Computed link resolver with attributes and event handlers
386
+ *
387
+ * @example
388
+ * ```vue
389
+ * <template>
390
+ * <a
391
+ * v-bind="link.attributes"
392
+ * v-on="link.createEventHandlers()"
393
+ * :class="{ active: link.isActive }"
394
+ * >
395
+ * Home
396
+ * </a>
397
+ * </template>
398
+ *
399
+ * <script setup lang="ts">
400
+ * import { useLink } from '@esmx/router-vue';
401
+ *
402
+ * const link = useLink({
403
+ * to: '/home',
404
+ * type: 'push',
405
+ * exact: 'include'
406
+ * }).value;
407
+ * </script>
408
+ * ```
409
+ */
410
+ export function useLink(props: RouterLinkProps) {
411
+ const router = useRouter();
412
+
413
+ return computed(() => {
414
+ return router.resolveLink(props);
415
+ });
416
+ }