@esmx/router-vue 3.0.0-rc.12

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.
@@ -0,0 +1,4 @@
1
+ export { RouterVuePlugin } from './plugin';
2
+ export { useRouter, useRoute } from './use';
3
+ export { RouterLink } from './link';
4
+ export { RouterView } from './view';
package/dist/index.mjs ADDED
@@ -0,0 +1,4 @@
1
+ export { RouterVuePlugin } from "./plugin.mjs";
2
+ export { useRouter, useRoute } from "./use.mjs";
3
+ export { RouterLink } from "./link.mjs";
4
+ export { RouterView } from "./view.mjs";
package/dist/link.d.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { type RouterRawLocation } from '@esmx/router';
2
+ import { type PropType } from 'vue';
3
+ export interface RouterLinkProps {
4
+ /**
5
+ * 前往的路由路径
6
+ */
7
+ to: RouterRawLocation;
8
+ /**
9
+ * 节点使用的标签名
10
+ * @default 'a'
11
+ */
12
+ tag: string;
13
+ /**
14
+ * 调用 router.replace 以替换 router.push。
15
+ * @default false
16
+ */
17
+ replace: boolean;
18
+ /**
19
+ * 路径激活匹配规则
20
+ * @example include => 路径包含即激活.
21
+ * 如: 当前路由为/en/news/list 此时router-link 的路径为 /en/news 也会激活
22
+ * @example route => 路由匹配才会激活,需要匹配的路由树一致.
23
+ * 如: 当前路由为/en/news/list/123 此时router-link 的路径为 /en/news/list 也会激活
24
+ * @example exact => 路径全匹配才会激活,不仅需要匹配路由树一致,还需要参数匹配才会激活.
25
+ * 如: 当前路由为/en/news/list/123 此时router-link 的路径为 /en/news/list/123 才会激活,如果配置的路径为/en/news/list/123456 也不会激活
26
+ * @default 'include'
27
+ */
28
+ exact: 'include' | 'route' | 'exact';
29
+ /**
30
+ * 是否为相对路径
31
+ * 按照 Hanson 要求目前都是绝对路径,因此废弃此属性
32
+ * @default false
33
+ */
34
+ /**
35
+ * 路由激活时的class
36
+ * @default 'router-link-active'
37
+ */
38
+ activeClass: string;
39
+ /**
40
+ * 哪些事件触发路由跳转
41
+ * @default 'click'
42
+ */
43
+ event: string | string[];
44
+ }
45
+ export declare const RouterLink: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
46
+ to: {
47
+ type: PropType<RouterLinkProps["to"]>;
48
+ required: true;
49
+ };
50
+ tag: {
51
+ type: PropType<RouterLinkProps["tag"]>;
52
+ default: string;
53
+ };
54
+ replace: {
55
+ type: PropType<RouterLinkProps["replace"]>;
56
+ default: boolean;
57
+ };
58
+ exact: {
59
+ type: PropType<RouterLinkProps["exact"]>;
60
+ default: string;
61
+ };
62
+ activeClass: {
63
+ type: PropType<RouterLinkProps["activeClass"]>;
64
+ default: string;
65
+ };
66
+ event: {
67
+ type: PropType<RouterLinkProps["event"]>;
68
+ default: string;
69
+ };
70
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
71
+ to: {
72
+ type: PropType<RouterLinkProps["to"]>;
73
+ required: true;
74
+ };
75
+ tag: {
76
+ type: PropType<RouterLinkProps["tag"]>;
77
+ default: string;
78
+ };
79
+ replace: {
80
+ type: PropType<RouterLinkProps["replace"]>;
81
+ default: boolean;
82
+ };
83
+ exact: {
84
+ type: PropType<RouterLinkProps["exact"]>;
85
+ default: string;
86
+ };
87
+ activeClass: {
88
+ type: PropType<RouterLinkProps["activeClass"]>;
89
+ default: string;
90
+ };
91
+ event: {
92
+ type: PropType<RouterLinkProps["event"]>;
93
+ default: string;
94
+ };
95
+ }>> & Readonly<{}>, {
96
+ exact: "include" | "route" | "exact";
97
+ tag: string;
98
+ replace: boolean;
99
+ activeClass: string;
100
+ event: string | string[];
101
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
package/dist/link.mjs ADDED
@@ -0,0 +1,103 @@
1
+ import {
2
+ isEqualRoute,
3
+ isSameRoute
4
+ } from "@esmx/router";
5
+ import { defineComponent, h } from "vue";
6
+ import { useRoute, useRouter } from "./use.mjs";
7
+ export const RouterLink = defineComponent({
8
+ props: {
9
+ to: {
10
+ type: [String, Object],
11
+ required: true
12
+ },
13
+ tag: {
14
+ type: String,
15
+ default: "a"
16
+ },
17
+ replace: {
18
+ type: Boolean,
19
+ default: false
20
+ },
21
+ exact: {
22
+ type: String,
23
+ default: "include"
24
+ },
25
+ // append: {
26
+ // type: Boolean as PropType<boolean>,
27
+ // default: false
28
+ // },
29
+ activeClass: {
30
+ type: String,
31
+ default: "router-link-active"
32
+ },
33
+ event: {
34
+ type: String,
35
+ default: "click"
36
+ }
37
+ },
38
+ render(props) {
39
+ const { to, tag, replace, exact, activeClass, event } = props;
40
+ const router = useRouter();
41
+ const current = useRoute();
42
+ const resolveRoute = router.resolve(to);
43
+ let compare;
44
+ switch (exact) {
45
+ /* 路由级匹配 */
46
+ case "route":
47
+ compare = isSameRoute;
48
+ break;
49
+ /* 全匹配 */
50
+ case "exact":
51
+ compare = isEqualRoute;
52
+ break;
53
+ /* 是否包含 */
54
+ case "include":
55
+ default:
56
+ compare = (current2, route) => {
57
+ return current2.fullPath.startsWith(route.fullPath);
58
+ };
59
+ break;
60
+ }
61
+ const active = compare(current, resolveRoute);
62
+ const handler = (e) => {
63
+ if (guardEvent(e)) {
64
+ router[replace ? "replace" : "push"](to);
65
+ }
66
+ };
67
+ const on = {};
68
+ const eventTypeList = getEventTypeList(event);
69
+ eventTypeList.forEach((eventName) => {
70
+ on[`on${eventName.toLocaleLowerCase()}`] = handler;
71
+ });
72
+ return h(
73
+ tag,
74
+ {
75
+ class: ["router-link", active ? [activeClass] : ""],
76
+ href: resolveRoute.fullPath,
77
+ ...on
78
+ },
79
+ this.$slots
80
+ );
81
+ }
82
+ });
83
+ function getEventTypeList(eventType) {
84
+ if (eventType instanceof Array) {
85
+ if (eventType.length > 0) {
86
+ return eventType;
87
+ }
88
+ return ["click"];
89
+ }
90
+ return [eventType || "click"];
91
+ }
92
+ function guardEvent(e) {
93
+ var _a;
94
+ if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
95
+ if (e.defaultPrevented) return;
96
+ if (e.button !== void 0 && e.button !== 0) return;
97
+ if ((_a = e.currentTarget) == null ? void 0 : _a.getAttribute) {
98
+ const target = e.currentTarget.getAttribute("target");
99
+ if (/\b_blank\b/i.test(target)) return;
100
+ }
101
+ if (e.preventDefault) e.preventDefault();
102
+ return true;
103
+ }
@@ -0,0 +1,11 @@
1
+ import type { Route, RouterInstance } from '@esmx/router';
2
+ import { type App, type ShallowReactive } from 'vue';
3
+ declare module '@vue/runtime-core' {
4
+ interface ComponentCustomProperties {
5
+ $route: ShallowReactive<Route>;
6
+ $router: RouterInstance;
7
+ }
8
+ interface GlobalComponents {
9
+ }
10
+ }
11
+ export declare function RouterVuePlugin(router: RouterInstance): (app: App) => void;
@@ -0,0 +1,16 @@
1
+ import { shallowReactive, unref } from "vue";
2
+ import { RouterLink } from "./link.mjs";
3
+ import { routerKey, routerViewLocationKey } from "./symbols.mjs";
4
+ import { RouterView } from "./view.mjs";
5
+ export function RouterVuePlugin(router) {
6
+ return function install(app) {
7
+ const route = shallowReactive(router.route);
8
+ router.route = route;
9
+ app.config.globalProperties.$router = router;
10
+ app.config.globalProperties.$route = router.route;
11
+ app.provide(routerKey, unref(router));
12
+ app.provide(routerViewLocationKey, unref(router.route));
13
+ app.component("router-view", RouterView);
14
+ app.component("router-link", RouterLink);
15
+ };
16
+ }
@@ -0,0 +1,3 @@
1
+ export declare const routerKey: unique symbol;
2
+ export declare const routerViewLocationKey: unique symbol;
3
+ export declare const routerViewDepthKey: unique symbol;
@@ -0,0 +1,3 @@
1
+ export const routerKey = Symbol("routerViewLocation");
2
+ export const routerViewLocationKey = Symbol("routerViewLocation");
3
+ export const routerViewDepthKey = Symbol("routerViewDepth");
package/dist/use.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Route, RouterInstance } from '@esmx/router';
2
+ export declare function throwNoCurrentInstance(method: string): void;
3
+ export declare function useRouter(): RouterInstance;
4
+ export declare function useRoute(): Route;
package/dist/use.mjs ADDED
@@ -0,0 +1,15 @@
1
+ import { getCurrentInstance, inject } from "vue";
2
+ import { routerKey, routerViewLocationKey } from "./symbols.mjs";
3
+ export function throwNoCurrentInstance(method) {
4
+ if (!getCurrentInstance()) {
5
+ throw new Error(
6
+ `[router-vue]: Missing current instance. ${method}() must be called inside <script setup> or setup().`
7
+ );
8
+ }
9
+ }
10
+ export function useRouter() {
11
+ return inject(routerKey);
12
+ }
13
+ export function useRoute() {
14
+ return inject(routerViewLocationKey);
15
+ }
package/dist/view.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { Route } from '@esmx/router';
2
+ import { type PropType } from 'vue';
3
+ export declare const RouterView: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
4
+ name: {
5
+ type: PropType<string>;
6
+ default: string;
7
+ };
8
+ route: PropType<Route>;
9
+ }>, (() => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
10
+ [key: string]: any;
11
+ }> | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
12
+ [key: string]: any;
13
+ }>[] | null) | undefined, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
14
+ name: {
15
+ type: PropType<string>;
16
+ default: string;
17
+ };
18
+ route: PropType<Route>;
19
+ }>> & Readonly<{}>, {
20
+ name: string;
21
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
package/dist/view.mjs ADDED
@@ -0,0 +1,75 @@
1
+ import {
2
+ computed,
3
+ defineComponent,
4
+ getCurrentInstance,
5
+ h,
6
+ inject,
7
+ provide,
8
+ shallowReactive
9
+ } from "vue";
10
+ import { routerViewDepthKey, routerViewLocationKey } from "./symbols.mjs";
11
+ export const RouterView = defineComponent({
12
+ name: "RouterView",
13
+ inheritAttrs: true,
14
+ props: {
15
+ name: {
16
+ type: String,
17
+ default: "default"
18
+ },
19
+ route: Object
20
+ },
21
+ // Better compat for @vue/compat users
22
+ // https://github.com/vuejs/router/issues/1315
23
+ compatConfig: { MODE: 3 },
24
+ setup: (props, { attrs, slots }) => {
25
+ const instance = getCurrentInstance();
26
+ if (!instance) {
27
+ console.error("no current instance");
28
+ return;
29
+ }
30
+ const injectedRoute = inject(
31
+ routerViewLocationKey
32
+ );
33
+ const routeToDisplay = computed(
34
+ () => props.route || injectedRoute
35
+ );
36
+ const injectedDepth = inject(
37
+ routerViewDepthKey,
38
+ computed(() => 0)
39
+ );
40
+ const depth = computed(() => {
41
+ let initialDepth = injectedDepth.value;
42
+ const { matched } = routeToDisplay.value;
43
+ let matchedRoute;
44
+ while ((matchedRoute = matched[initialDepth]) && !(matchedRoute == null ? void 0 : matchedRoute.component)) {
45
+ initialDepth++;
46
+ }
47
+ return initialDepth;
48
+ });
49
+ provide(routerViewLocationKey, shallowReactive(routeToDisplay.value));
50
+ provide(
51
+ routerViewDepthKey,
52
+ computed(() => depth.value + 1)
53
+ );
54
+ return () => {
55
+ const matchRoute = routeToDisplay.value.matched[depth.value];
56
+ if (!matchRoute) {
57
+ return null;
58
+ }
59
+ const component = h(
60
+ matchRoute.component,
61
+ Object.assign({}, props, attrs)
62
+ );
63
+ return (
64
+ // pass the vnode to the slot as a prop.
65
+ // h and <component :is="..."> both accept vnodes
66
+ normalizeSlot(slots.default, { Component: component }) || component
67
+ );
68
+ };
69
+ }
70
+ });
71
+ function normalizeSlot(slot, data) {
72
+ if (!slot) return null;
73
+ const slotContent = slot(data);
74
+ return slotContent.length === 1 ? slotContent[0] : slotContent;
75
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@esmx/router-vue",
3
+ "template": "library",
4
+ "scripts": {
5
+ "lint:js": "biome check --write --no-errors-on-unmatched",
6
+ "lint:css": "stylelint '**/*.{css,vue}' --fix --aei",
7
+ "lint:type": "tsc --noEmit",
8
+ "test": "vitest run --pass-with-no-tests",
9
+ "coverage": "vitest run --coverage --pass-with-no-tests",
10
+ "build": "unbuild"
11
+ },
12
+ "contributors": [
13
+ {
14
+ "name": "lzxb",
15
+ "url": "https://github.com/lzxb"
16
+ },
17
+ {
18
+ "name": "RockShi1994",
19
+ "url": "https://github.com/RockShi1994"
20
+ },
21
+ {
22
+ "name": "jerrychan7",
23
+ "url": "https://github.com/jerrychan7"
24
+ },
25
+ {
26
+ "name": "wesloong",
27
+ "url": "https://github.com/wesloong"
28
+ }
29
+ ],
30
+ "peerDependencies": {
31
+ "vue": "^3.0.0"
32
+ },
33
+ "dependencies": {
34
+ "@esmx/router": "3.0.0-rc.12"
35
+ },
36
+ "devDependencies": {
37
+ "@biomejs/biome": "1.9.4",
38
+ "@esmx/lint": "3.0.0-rc.12",
39
+ "@gez/lint": "3.0.0-rc.9",
40
+ "@types/node": "22.13.10",
41
+ "@vitest/coverage-v8": "3.0.8",
42
+ "@vue/runtime-core": "^3.4.27",
43
+ "@vue/runtime-dom": "^3.4.27",
44
+ "stylelint": "16.15.0",
45
+ "typescript": "5.8.2",
46
+ "unbuild": "2.0.0",
47
+ "vitest": "3.0.8",
48
+ "vue": "^3.4.27"
49
+ },
50
+ "version": "3.0.0-rc.12",
51
+ "type": "module",
52
+ "private": false,
53
+ "exports": {
54
+ ".": {
55
+ "import": "./dist/index.mjs",
56
+ "types": "./dist/index.d.ts"
57
+ }
58
+ },
59
+ "module": "dist/index.mjs",
60
+ "types": "./dist/index.d.ts",
61
+ "files": [
62
+ "lib",
63
+ "src",
64
+ "dist",
65
+ "*.mjs",
66
+ "template",
67
+ "public"
68
+ ],
69
+ "gitHead": "7d2c2fc4fe2cc98ebdbc12560f19637ca04398e5"
70
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { RouterVuePlugin } from './plugin';
2
+ export { useRouter, useRoute } from './use';
3
+ export { RouterLink } from './link';
4
+ export { RouterView } from './view';
package/src/link.ts ADDED
@@ -0,0 +1,177 @@
1
+ import {
2
+ type RouteRecord,
3
+ type RouterRawLocation,
4
+ isEqualRoute,
5
+ isSameRoute
6
+ } from '@esmx/router';
7
+ import { type PropType, defineComponent, h } from 'vue';
8
+
9
+ import { useRoute, useRouter } from './use';
10
+
11
+ export interface RouterLinkProps {
12
+ /**
13
+ * 前往的路由路径
14
+ */
15
+ to: RouterRawLocation;
16
+
17
+ /**
18
+ * 节点使用的标签名
19
+ * @default 'a'
20
+ */
21
+ tag: string;
22
+
23
+ /**
24
+ * 调用 router.replace 以替换 router.push。
25
+ * @default false
26
+ */
27
+ replace: boolean;
28
+
29
+ /**
30
+ * 路径激活匹配规则
31
+ * @example include => 路径包含即激活.
32
+ * 如: 当前路由为/en/news/list 此时router-link 的路径为 /en/news 也会激活
33
+ * @example route => 路由匹配才会激活,需要匹配的路由树一致.
34
+ * 如: 当前路由为/en/news/list/123 此时router-link 的路径为 /en/news/list 也会激活
35
+ * @example exact => 路径全匹配才会激活,不仅需要匹配路由树一致,还需要参数匹配才会激活.
36
+ * 如: 当前路由为/en/news/list/123 此时router-link 的路径为 /en/news/list/123 才会激活,如果配置的路径为/en/news/list/123456 也不会激活
37
+ * @default 'include'
38
+ */
39
+ exact: 'include' | 'route' | 'exact';
40
+
41
+ /**
42
+ * 是否为相对路径
43
+ * 按照 Hanson 要求目前都是绝对路径,因此废弃此属性
44
+ * @default false
45
+ */
46
+ // append?: boolean;
47
+
48
+ /**
49
+ * 路由激活时的class
50
+ * @default 'router-link-active'
51
+ */
52
+ activeClass: string;
53
+
54
+ /**
55
+ * 哪些事件触发路由跳转
56
+ * @default 'click'
57
+ */
58
+ event: string | string[];
59
+ }
60
+
61
+ export const RouterLink = defineComponent({
62
+ props: {
63
+ to: {
64
+ type: [String, Object] as PropType<RouterLinkProps['to']>,
65
+ required: true
66
+ },
67
+ tag: {
68
+ type: String as PropType<RouterLinkProps['tag']>,
69
+ default: 'a'
70
+ },
71
+ replace: {
72
+ type: Boolean as PropType<RouterLinkProps['replace']>,
73
+ default: false
74
+ },
75
+ exact: {
76
+ type: String as PropType<RouterLinkProps['exact']>,
77
+ default: 'include'
78
+ },
79
+ // append: {
80
+ // type: Boolean as PropType<boolean>,
81
+ // default: false
82
+ // },
83
+ activeClass: {
84
+ type: String as PropType<RouterLinkProps['activeClass']>,
85
+ default: 'router-link-active'
86
+ },
87
+ event: {
88
+ type: String as PropType<RouterLinkProps['event']>,
89
+ default: 'click'
90
+ }
91
+ },
92
+ render(props: RouterLinkProps) {
93
+ const { to, tag, replace, exact, activeClass, event } = props;
94
+ const router = useRouter();
95
+ const current = useRoute();
96
+ const resolveRoute = router.resolve(to);
97
+
98
+ /* 匹配函数 */
99
+ let compare: (current: RouteRecord, route: RouteRecord) => Boolean;
100
+ switch (exact) {
101
+ /* 路由级匹配 */
102
+ case 'route':
103
+ compare = isSameRoute;
104
+ break;
105
+
106
+ /* 全匹配 */
107
+ case 'exact':
108
+ compare = isEqualRoute;
109
+ break;
110
+
111
+ /* 是否包含 */
112
+ case 'include':
113
+ default:
114
+ compare = (current: RouteRecord, route: RouteRecord) => {
115
+ return current.fullPath.startsWith(route.fullPath);
116
+ };
117
+ break;
118
+ }
119
+
120
+ /* 根据路由是否匹配获取高亮 */
121
+ const active = compare(current, resolveRoute);
122
+
123
+ /* 事件处理函数 */
124
+ const handler = (e: MouseEvent) => {
125
+ if (guardEvent(e)) {
126
+ router[replace ? 'replace' : 'push'](to);
127
+ }
128
+ };
129
+
130
+ /* 可触发事件 map */
131
+ const on: Record<string, Function | Function[]> = {};
132
+ const eventTypeList = getEventTypeList(event);
133
+ eventTypeList.forEach((eventName) => {
134
+ on[`on${eventName.toLocaleLowerCase()}`] = handler;
135
+ });
136
+
137
+ return h(
138
+ tag,
139
+ {
140
+ class: ['router-link', active ? [activeClass] : ''],
141
+ href: resolveRoute.fullPath,
142
+ ...on
143
+ },
144
+ this.$slots
145
+ );
146
+ }
147
+ });
148
+
149
+ function getEventTypeList(eventType: string | string[]): string[] {
150
+ if (eventType instanceof Array) {
151
+ if (eventType.length > 0) {
152
+ return eventType;
153
+ }
154
+ return ['click'];
155
+ }
156
+ return [eventType || 'click'];
157
+ }
158
+
159
+ function guardEvent(e: MouseEvent) {
160
+ // don't redirect with control keys
161
+ if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
162
+ // don't redirect when preventDefault called
163
+ if (e.defaultPrevented) return;
164
+ // don't redirect on right click
165
+ if (e.button !== undefined && e.button !== 0) return;
166
+ // don't redirect if `target="_blank"`
167
+ // @ts-expect-error getAttribute does exist
168
+ if (e.currentTarget?.getAttribute) {
169
+ // @ts-expect-error getAttribute exists
170
+ const target = e.currentTarget.getAttribute('target');
171
+ if (/\b_blank\b/i.test(target)) return;
172
+ }
173
+ // this may be a Weex event which doesn't have this method
174
+ if (e.preventDefault) e.preventDefault();
175
+
176
+ return true;
177
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { Route, RouterInstance } from '@esmx/router';
2
+ import { type App, type ShallowReactive, shallowReactive, unref } from 'vue';
3
+
4
+ import { RouterLink } from './link';
5
+ import { routerKey, routerViewLocationKey } from './symbols';
6
+ import { RouterView } from './view';
7
+
8
+ declare module '@vue/runtime-core' {
9
+ interface ComponentCustomProperties {
10
+ // $route: Route;
11
+ $route: ShallowReactive<Route>;
12
+ $router: RouterInstance;
13
+ }
14
+
15
+ interface GlobalComponents {
16
+ // RouterView:
17
+ // RouterLink:
18
+ }
19
+ }
20
+
21
+ export function RouterVuePlugin(router: RouterInstance) {
22
+ return function install(app: App) {
23
+ const route = shallowReactive(router.route);
24
+ router.route = route;
25
+
26
+ app.config.globalProperties.$router = router;
27
+ app.config.globalProperties.$route = router.route;
28
+
29
+ app.provide(routerKey, unref(router));
30
+ app.provide(routerViewLocationKey, unref(router.route));
31
+
32
+ // 注册组件
33
+ app.component('router-view', RouterView);
34
+ app.component('router-link', RouterLink);
35
+ };
36
+ }
package/src/symbols.ts ADDED
@@ -0,0 +1,8 @@
1
+ /* router key */
2
+ export const routerKey = Symbol('routerViewLocation');
3
+
4
+ /* router location key */
5
+ export const routerViewLocationKey = Symbol('routerViewLocation');
6
+
7
+ /* router depth key */
8
+ export const routerViewDepthKey = Symbol('routerViewDepth');
package/src/use.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { Route, RouterInstance } from '@esmx/router';
2
+ import { getCurrentInstance, inject } from 'vue';
3
+
4
+ import { routerKey, routerViewLocationKey } from './symbols';
5
+
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
+ );
11
+ }
12
+ }
13
+
14
+ export function useRouter(): RouterInstance {
15
+ return inject(routerKey)!;
16
+ }
17
+
18
+ export function useRoute(): Route {
19
+ return inject(routerViewLocationKey)!;
20
+ }
package/src/view.ts ADDED
@@ -0,0 +1,95 @@
1
+ import type { Route, RouteConfig } from '@esmx/router';
2
+ import {
3
+ type ComputedRef,
4
+ type PropType,
5
+ type ShallowReactive,
6
+ type Slot,
7
+ computed,
8
+ defineComponent,
9
+ getCurrentInstance,
10
+ h,
11
+ inject,
12
+ provide,
13
+ shallowReactive
14
+ } from 'vue';
15
+
16
+ import { routerViewDepthKey, routerViewLocationKey } from './symbols';
17
+
18
+ export const RouterView = defineComponent({
19
+ name: 'RouterView',
20
+ inheritAttrs: true,
21
+ props: {
22
+ name: {
23
+ type: String as PropType<string>,
24
+ default: 'default'
25
+ },
26
+ route: Object as PropType<Route>
27
+ },
28
+
29
+ // Better compat for @vue/compat users
30
+ // https://github.com/vuejs/router/issues/1315
31
+ compatConfig: { MODE: 3 },
32
+
33
+ setup: (props, { attrs, slots }) => {
34
+ const instance = getCurrentInstance();
35
+ if (!instance) {
36
+ console.error('no current instance');
37
+ return;
38
+ }
39
+
40
+ const injectedRoute = inject<ShallowReactive<Route>>(
41
+ routerViewLocationKey
42
+ )!;
43
+
44
+ const routeToDisplay = computed<Route>(
45
+ () => props.route || injectedRoute
46
+ );
47
+ const injectedDepth = inject<ComputedRef<number>>(
48
+ routerViewDepthKey,
49
+ computed(() => 0)
50
+ );
51
+
52
+ const depth = computed<number>(() => {
53
+ let initialDepth = injectedDepth.value;
54
+ const { matched } = routeToDisplay.value;
55
+ let matchedRoute: RouteConfig | undefined;
56
+ while (
57
+ (matchedRoute = matched[initialDepth]) &&
58
+ !matchedRoute?.component
59
+ ) {
60
+ initialDepth++;
61
+ }
62
+ return initialDepth;
63
+ });
64
+
65
+ provide(routerViewLocationKey, shallowReactive(routeToDisplay.value));
66
+ provide(
67
+ routerViewDepthKey,
68
+ computed(() => depth.value + 1)
69
+ );
70
+
71
+ return () => {
72
+ const matchRoute = routeToDisplay.value.matched[depth.value];
73
+ if (!matchRoute) {
74
+ return null;
75
+ }
76
+
77
+ const component = h(
78
+ matchRoute.component,
79
+ Object.assign({}, props, attrs)
80
+ );
81
+ return (
82
+ // pass the vnode to the slot as a prop.
83
+ // h and <component :is="..."> both accept vnodes
84
+ normalizeSlot(slots.default, { Component: component }) ||
85
+ component
86
+ );
87
+ };
88
+ }
89
+ });
90
+
91
+ function normalizeSlot(slot: Slot | undefined, data: any) {
92
+ if (!slot) return null;
93
+ const slotContent = slot(data);
94
+ return slotContent.length === 1 ? slotContent[0] : slotContent;
95
+ }