@esmx/router-vue 3.0.0-rc.17 → 3.0.0-rc.20

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 (55) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +563 -0
  3. package/README.zh-CN.md +563 -0
  4. package/dist/index.d.ts +6 -4
  5. package/dist/index.mjs +11 -4
  6. package/dist/index.test.d.ts +1 -0
  7. package/dist/index.test.mjs +206 -0
  8. package/dist/plugin.d.ts +61 -11
  9. package/dist/plugin.mjs +32 -16
  10. package/dist/plugin.test.d.ts +1 -0
  11. package/dist/plugin.test.mjs +436 -0
  12. package/dist/router-link.d.ts +202 -0
  13. package/dist/router-link.mjs +84 -0
  14. package/dist/router-link.test.d.ts +1 -0
  15. package/dist/router-link.test.mjs +456 -0
  16. package/dist/router-view.d.ts +31 -0
  17. package/dist/router-view.mjs +17 -0
  18. package/dist/router-view.test.d.ts +1 -0
  19. package/dist/router-view.test.mjs +459 -0
  20. package/dist/use.d.ts +198 -3
  21. package/dist/use.mjs +75 -9
  22. package/dist/use.test.d.ts +1 -0
  23. package/dist/use.test.mjs +461 -0
  24. package/dist/util.d.ts +7 -0
  25. package/dist/util.mjs +24 -0
  26. package/dist/util.test.d.ts +1 -0
  27. package/dist/util.test.mjs +319 -0
  28. package/dist/vue2.d.ts +13 -0
  29. package/dist/vue2.mjs +0 -0
  30. package/dist/vue3.d.ts +13 -0
  31. package/dist/vue3.mjs +0 -0
  32. package/package.json +31 -14
  33. package/src/index.test.ts +263 -0
  34. package/src/index.ts +16 -4
  35. package/src/plugin.test.ts +574 -0
  36. package/src/plugin.ts +92 -31
  37. package/src/router-link.test.ts +569 -0
  38. package/src/router-link.ts +148 -0
  39. package/src/router-view.test.ts +599 -0
  40. package/src/router-view.ts +62 -0
  41. package/src/use.test.ts +616 -0
  42. package/src/use.ts +307 -11
  43. package/src/util.test.ts +418 -0
  44. package/src/util.ts +32 -0
  45. package/src/vue2.ts +16 -0
  46. package/src/vue3.ts +15 -0
  47. package/dist/link.d.ts +0 -101
  48. package/dist/link.mjs +0 -103
  49. package/dist/symbols.d.ts +0 -3
  50. package/dist/symbols.mjs +0 -3
  51. package/dist/view.d.ts +0 -21
  52. package/dist/view.mjs +0 -75
  53. package/src/link.ts +0 -177
  54. package/src/symbols.ts +0 -8
  55. package/src/view.ts +0 -95
package/src/use.ts CHANGED
@@ -1,20 +1,316 @@
1
- import type { Route, RouterInstance } from '@esmx/router';
2
- import { getCurrentInstance, inject } from 'vue';
1
+ import type { Route, Router, RouterLinkProps } from '@esmx/router';
2
+ import {
3
+ type Ref,
4
+ computed,
5
+ getCurrentInstance,
6
+ inject,
7
+ onBeforeUnmount,
8
+ provide,
9
+ ref
10
+ } from 'vue';
11
+ import { createSymbolProperty } from './util';
3
12
 
4
- import { routerKey, routerViewLocationKey } from './symbols';
13
+ export interface VueInstance {
14
+ $parent?: VueInstance | null;
15
+ $root?: VueInstance | null;
16
+ $children?: VueInstance[] | null;
17
+ }
18
+
19
+ interface RouterContext {
20
+ router: Router;
21
+ route: Ref<Route>;
22
+ }
23
+
24
+ const ROUTER_CONTEXT_KEY = Symbol('router-context');
25
+ const ROUTER_INJECT_KEY = Symbol('router-inject');
26
+
27
+ const ERROR_MESSAGES = {
28
+ SETUP_ONLY: (fnName: string) =>
29
+ `[@esmx/router-vue] ${fnName}() can only be called during setup()`,
30
+ CONTEXT_NOT_FOUND:
31
+ '[@esmx/router-vue] Router context not found. ' +
32
+ 'Please ensure useProvideRouter() is called in a parent component.'
33
+ } as const;
34
+
35
+ const routerContextProperty =
36
+ createSymbolProperty<RouterContext>(ROUTER_CONTEXT_KEY);
37
+
38
+ function getCurrentProxy(functionName: string): VueInstance {
39
+ const instance = getCurrentInstance();
40
+ if (!instance || !instance.proxy) {
41
+ throw new Error(ERROR_MESSAGES.SETUP_ONLY(functionName));
42
+ }
43
+ return instance.proxy;
44
+ }
45
+
46
+ function findRouterContext(vm?: VueInstance): RouterContext {
47
+ // If no vm provided, try to get current instance
48
+ if (!vm) {
49
+ vm = getCurrentProxy('findRouterContext');
50
+ }
51
+
52
+ let context = routerContextProperty.get(vm);
53
+ if (context) {
54
+ return context;
55
+ }
56
+
57
+ let current = vm.$parent;
58
+ while (current) {
59
+ context = routerContextProperty.get(current);
60
+ if (context) {
61
+ routerContextProperty.set(vm, context);
62
+ return context;
63
+ }
64
+ current = current.$parent;
65
+ }
66
+
67
+ throw new Error(ERROR_MESSAGES.CONTEXT_NOT_FOUND);
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 (optional, will use getCurrentInstance if not provided)
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
+ * // Can also be called without instance (uses getCurrentInstance internally)
99
+ * const router = getRouter(); // Works in globalProperties getters
100
+ * ```
101
+ */
102
+ export function getRouter(instance?: VueInstance): Router {
103
+ return findRouterContext(instance).router;
104
+ }
105
+
106
+ /**
107
+ * Get current route from a Vue component instance.
108
+ * This is a lower-level function used internally by useRoute().
109
+ * Use this in Options API, use useRoute() in Composition API.
110
+ *
111
+ * @param instance - Vue component instance (optional, will use getCurrentInstance if not provided)
112
+ * @returns Current route object
113
+ * @throws {Error} If router context is not found
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * // Options API usage
118
+ * import { defineComponent } from 'vue';
119
+ * import { getRoute } from '@esmx/router-vue';
120
+ *
121
+ * export default defineComponent({
122
+ * computed: {
123
+ * routeInfo() {
124
+ * const route = getRoute(this);
125
+ * return {
126
+ * path: route.path,
127
+ * params: route.params,
128
+ * query: route.query
129
+ * };
130
+ * }
131
+ * }
132
+ * });
133
+ *
134
+ * // Can also be called without instance (uses getCurrentInstance internally)
135
+ * const route = getRoute(); // Works in globalProperties getters
136
+ * ```
137
+ */
138
+ export function getRoute(instance?: VueInstance): Route {
139
+ return findRouterContext(instance).route.value;
140
+ }
5
141
 
6
- export function throwNoCurrentInstance(method: string) {
7
- if (!getCurrentInstance()) {
8
- throw new Error(
9
- `[router-vue]: Missing current instance. ${method}() must be called inside <script setup> or setup().`
10
- );
142
+ /**
143
+ * Get router context using the optimal method available.
144
+ * First tries provide/inject (works in setup), then falls back to hierarchy traversal.
145
+ */
146
+ function useRouterContext(functionName: string): RouterContext {
147
+ // First try to get context from provide/inject (works in setup)
148
+ const injectedContext = inject<RouterContext>(ROUTER_INJECT_KEY);
149
+ if (injectedContext) {
150
+ return injectedContext;
11
151
  }
152
+
153
+ // Fallback to component hierarchy traversal (works after mount)
154
+ const proxy = getCurrentProxy(functionName);
155
+ return findRouterContext(proxy);
12
156
  }
13
157
 
14
- export function useRouter(): RouterInstance {
15
- return inject(routerKey)!;
158
+ /**
159
+ * Get the router instance in a Vue component.
160
+ * Must be called within setup() or other composition functions.
161
+ * Use this in Composition API, use getRouter() in Options API.
162
+ *
163
+ * @returns Router instance for navigation and route management
164
+ * @throws {Error} If called outside setup() or router context not found
165
+ *
166
+ * @example
167
+ * ```vue
168
+ * <script setup lang="ts">
169
+ * import { useRouter } from '@esmx/router-vue';
170
+ *
171
+ * const router = useRouter();
172
+ *
173
+ * const navigateToHome = () => {
174
+ * router.push('/home');
175
+ * };
176
+ *
177
+ * const goBack = () => {
178
+ * router.back();
179
+ * };
180
+ *
181
+ * const navigateWithQuery = () => {
182
+ * router.push({
183
+ * path: '/search',
184
+ * query: { q: 'vue router', page: '1' }
185
+ * });
186
+ * };
187
+ * </script>
188
+ * ```
189
+ */
190
+ export function useRouter(): Router {
191
+ return useRouterContext('useRouter').router;
16
192
  }
17
193
 
194
+ /**
195
+ * Get the current route information in a Vue component.
196
+ * Returns a reactive reference that automatically updates when the route changes.
197
+ * Must be called within setup() or other composition functions.
198
+ * Use this in Composition API, use getRoute() in Options API.
199
+ *
200
+ * @returns Current route object with path, params, query, etc.
201
+ * @throws {Error} If called outside setup() or router context not found
202
+ *
203
+ * @example
204
+ * ```vue
205
+ * <template>
206
+ * <div>
207
+ * <h1>{{ route.meta?.title || 'Page' }}</h1>
208
+ * <p>Path: {{ route.path }}</p>
209
+ * <p>Params: {{ JSON.stringify(route.params) }}</p>
210
+ * <p>Query: {{ JSON.stringify(route.query) }}</p>
211
+ * </div>
212
+ * </template>
213
+ *
214
+ * <script setup lang="ts">
215
+ * import { useRoute } from '@esmx/router-vue';
216
+ * import { watch } from 'vue';
217
+ *
218
+ * const route = useRoute();
219
+ *
220
+ * watch(() => route.path, (newPath) => {
221
+ * console.log('Route changed to:', newPath);
222
+ * });
223
+ * </script>
224
+ * ```
225
+ */
18
226
  export function useRoute(): Route {
19
- return inject(routerViewLocationKey)!;
227
+ return useRouterContext('useRoute').route.value;
228
+ }
229
+
230
+ /**
231
+ * Provide router context to child components.
232
+ * This must be called in a parent component to make the router available
233
+ * to child components via useRouter() and useRoute().
234
+ *
235
+ * @param router - Router instance to provide to child components
236
+ * @throws {Error} If called outside setup()
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * // Vue 3 usage
241
+ * import { createApp } from 'vue';
242
+ * import { Router } from '@esmx/router';
243
+ * import { useProvideRouter } from '@esmx/router-vue';
244
+ *
245
+ * const routes = [
246
+ * { path: '/', component: () => import('./Home.vue') },
247
+ * { path: '/about', component: () => import('./About.vue') }
248
+ * ];
249
+ *
250
+ * const router = new Router({ routes });
251
+ * const app = createApp({
252
+ * setup() {
253
+ * useProvideRouter(router);
254
+ * }
255
+ * });
256
+ * app.mount('#app');
257
+ * ```
258
+ */
259
+ export function useProvideRouter(router: Router): void {
260
+ const proxy = getCurrentProxy('useProvideRouter');
261
+
262
+ const context: RouterContext = {
263
+ router,
264
+ route: ref(router.route) as Ref<Route>
265
+ };
266
+
267
+ // Provide context via Vue 3's provide/inject (works in setup)
268
+ provide(ROUTER_INJECT_KEY, context);
269
+
270
+ // Also set on component instance for fallback (works after mount)
271
+ routerContextProperty.set(proxy, context);
272
+
273
+ const unwatch = router.afterEach((to: Route) => {
274
+ if (router.route === to) {
275
+ to.syncTo(context.route.value);
276
+ }
277
+ });
278
+
279
+ onBeforeUnmount(unwatch);
280
+ }
281
+
282
+ /**
283
+ * Create reactive link helpers for navigation elements.
284
+ * Returns computed properties for link attributes, classes, and event handlers.
285
+ *
286
+ * @param props - RouterLink properties configuration
287
+ * @returns Computed link resolver with attributes and event handlers
288
+ *
289
+ * @example
290
+ * ```vue
291
+ * <template>
292
+ * <a
293
+ * v-bind="link.attributes"
294
+ * v-on="link.getEventHandlers()"
295
+ * :class="{ active: link.isActive }"
296
+ * >
297
+ * Home
298
+ * </a>
299
+ * </template>
300
+ *
301
+ * <script setup lang="ts">
302
+ * import { useLink } from '@esmx/router-vue';
303
+ *
304
+ * const link = useLink({
305
+ * to: '/home',
306
+ * type: 'push',
307
+ * exact: 'include'
308
+ * }).value;
309
+ * </script>
310
+ * ```
311
+ */
312
+ export function useLink(props: RouterLinkProps) {
313
+ const router = useRouter();
314
+
315
+ return computed(() => router.resolveLink(props));
20
316
  }