@real-router/vue 0.3.1 → 0.3.3

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.
@@ -1,97 +0,0 @@
1
- import { shouldNavigate, buildHref, buildActiveClassName } from "dom-utils";
2
- import { defineComponent, h, computed } from "vue";
3
-
4
- import { useIsActiveRoute } from "../composables/useIsActiveRoute";
5
- import { useRouter } from "../composables/useRouter";
6
- import { EMPTY_PARAMS, EMPTY_OPTIONS } from "../constants";
7
-
8
- import type { Params, NavigationOptions } from "@real-router/core";
9
- import type { PropType } from "vue";
10
-
11
- export const Link = defineComponent({
12
- name: "Link",
13
- props: {
14
- routeName: {
15
- type: String,
16
- required: true,
17
- },
18
- routeParams: {
19
- type: Object as PropType<Params>,
20
- default: () => EMPTY_PARAMS,
21
- },
22
- routeOptions: {
23
- type: Object as PropType<NavigationOptions>,
24
- default: () => EMPTY_OPTIONS,
25
- },
26
- class: {
27
- type: String,
28
- default: undefined,
29
- },
30
- activeClassName: {
31
- type: String,
32
- default: "active",
33
- },
34
- activeStrict: {
35
- type: Boolean,
36
- default: false,
37
- },
38
- ignoreQueryParams: {
39
- type: Boolean,
40
- default: true,
41
- },
42
- target: {
43
- type: String,
44
- default: undefined,
45
- },
46
- },
47
- setup(props, { slots, attrs }) {
48
- const router = useRouter();
49
-
50
- const isActive = useIsActiveRoute(
51
- props.routeName,
52
- props.routeParams,
53
- props.activeStrict,
54
- props.ignoreQueryParams,
55
- );
56
-
57
- const href = computed(() =>
58
- buildHref(router, props.routeName, props.routeParams),
59
- );
60
-
61
- const finalClassName = computed(() =>
62
- buildActiveClassName(isActive.value, props.activeClassName, props.class),
63
- );
64
-
65
- const handleClick = (evt: MouseEvent) => {
66
- if (attrs.onClick && typeof attrs.onClick === "function") {
67
- (attrs.onClick as (evt: MouseEvent) => void)(evt);
68
-
69
- if (evt.defaultPrevented) {
70
- return;
71
- }
72
- }
73
-
74
- if (!shouldNavigate(evt) || props.target === "_blank") {
75
- return;
76
- }
77
-
78
- evt.preventDefault();
79
- router
80
- .navigate(props.routeName, props.routeParams, props.routeOptions)
81
- .catch(() => {});
82
- };
83
-
84
- return () =>
85
- h(
86
- "a",
87
- {
88
- ...attrs,
89
- href: href.value,
90
- class: finalClassName.value,
91
- target: props.target,
92
- onClick: handleClick,
93
- },
94
- slots.default?.(),
95
- );
96
- },
97
- });
@@ -1,210 +0,0 @@
1
- import {
2
- Fragment,
3
- defineComponent,
4
- h,
5
- KeepAlive,
6
- markRaw,
7
- Suspense,
8
- } from "vue";
9
-
10
- import { Match, NotFound } from "./components";
11
- import { buildRenderList, collectElements } from "./helpers";
12
- import { useRouteNode } from "../../composables/useRouteNode";
13
-
14
- import type { Component, VNode } from "vue";
15
-
16
- type SlotChildren = Record<string, (() => VNode[]) | undefined> | null;
17
-
18
- function getSlotContent(vnode: VNode): VNode[] | null {
19
- const slots = vnode.children as SlotChildren;
20
-
21
- return slots?.default?.() ?? null;
22
- }
23
-
24
- function getOrCreateWrapper(
25
- cache: Map<string, Component>,
26
- segment: string,
27
- ): Component {
28
- const existing = cache.get(segment);
29
-
30
- if (existing) {
31
- return existing;
32
- }
33
-
34
- const wrapper = markRaw(
35
- defineComponent({
36
- name: `KeepAlive-${segment}`,
37
- setup(_wrapperProps, wrapperCtx) {
38
- return () => wrapperCtx.slots.default?.();
39
- },
40
- }),
41
- );
42
-
43
- cache.set(segment, wrapper);
44
-
45
- return wrapper;
46
- }
47
-
48
- function wrapWithSuspense(content: VNode, fallback: unknown): VNode {
49
- if (fallback === undefined) {
50
- return content;
51
- }
52
-
53
- const fallbackContent =
54
- typeof fallback === "function" ? (fallback as () => VNode)() : fallback;
55
-
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
57
- const suspenseComponent = Suspense as any;
58
-
59
- return h(
60
- suspenseComponent,
61
- {},
62
- {
63
- default: () => content,
64
- fallback: () => fallbackContent,
65
- },
66
- );
67
- }
68
-
69
- const emptyKeepAlivePlaceholder = markRaw(
70
- defineComponent({
71
- name: "KeepAlive-placeholder",
72
- render() {
73
- return null;
74
- },
75
- }),
76
- );
77
-
78
- function renderWithRootKA(
79
- activeChild: VNode,
80
- wrapperCache: Map<string, Component>,
81
- fallback: unknown,
82
- ): VNode {
83
- const activeProps = activeChild.props as { segment?: string } | null;
84
- const segment = activeProps?.segment ?? "__not-found__";
85
- const WrapperComponent = getOrCreateWrapper(wrapperCache, segment);
86
- const slotContent = getSlotContent(activeChild) ?? [];
87
- const keepAliveContent = h(KeepAlive, null, {
88
- default: () =>
89
- h(WrapperComponent, { key: segment }, { default: () => slotContent }),
90
- });
91
-
92
- return wrapWithSuspense(keepAliveContent, fallback);
93
- }
94
-
95
- function renderWithPerMatchKA(
96
- activeChild: VNode,
97
- wrapperCache: Map<string, Component>,
98
- fallback: unknown,
99
- ): VNode | null {
100
- const matchProps = activeChild.props as {
101
- segment?: string;
102
- keepAlive?: boolean;
103
- } | null;
104
-
105
- if (matchProps?.keepAlive === true && activeChild.type === Match) {
106
- /* v8 ignore start */
107
- const segment = matchProps.segment ?? "__not-found__";
108
- /* v8 ignore stop */
109
- const WrapperComponent = getOrCreateWrapper(wrapperCache, segment);
110
- const slotContent = getSlotContent(activeChild) ?? [];
111
-
112
- return h(Fragment, [
113
- h(KeepAlive, null, {
114
- default: () =>
115
- h(WrapperComponent, { key: segment }, { default: () => slotContent }),
116
- }),
117
- ]);
118
- }
119
-
120
- const content = getSlotContent(activeChild);
121
-
122
- /* v8 ignore start */
123
- if (!content) {
124
- return null;
125
- }
126
- /* v8 ignore stop */
127
-
128
- return h(Fragment, [
129
- h(KeepAlive, null, { default: () => h(emptyKeepAlivePlaceholder) }),
130
- wrapWithSuspense(h(Fragment, content), fallback),
131
- ]);
132
- }
133
-
134
- const RouteViewComponent = defineComponent({
135
- name: "RouteView",
136
- props: {
137
- nodeName: {
138
- type: String,
139
- required: true,
140
- },
141
- keepAlive: {
142
- type: Boolean,
143
- default: false,
144
- },
145
- },
146
- setup(props, { slots }) {
147
- const routeContext = useRouteNode(props.nodeName);
148
- const wrapperCache = new Map<string, Component>();
149
-
150
- return (): VNode | null => {
151
- const route = routeContext.route.value;
152
-
153
- if (!route) {
154
- return null;
155
- }
156
-
157
- const elements: VNode[] = [];
158
-
159
- collectElements(slots.default?.(), elements);
160
-
161
- const { rendered, fallback } = buildRenderList(
162
- elements,
163
- route.name,
164
- props.nodeName,
165
- );
166
-
167
- if (rendered.length === 0) {
168
- return null;
169
- }
170
-
171
- const activeChild = rendered[0];
172
-
173
- if (props.keepAlive) {
174
- return renderWithRootKA(activeChild, wrapperCache, fallback);
175
- }
176
-
177
- /* v8 ignore start */
178
- if (activeChild.type !== Match && activeChild.type !== NotFound) {
179
- return null;
180
- }
181
- /* v8 ignore stop */
182
-
183
- const hasPerMatchKA = elements.some(
184
- (element) =>
185
- element.type === Match &&
186
- (element.props as { keepAlive?: boolean } | null)?.keepAlive === true,
187
- );
188
-
189
- if (hasPerMatchKA) {
190
- return renderWithPerMatchKA(activeChild, wrapperCache, fallback);
191
- }
192
-
193
- const content = getSlotContent(activeChild);
194
-
195
- if (!content) {
196
- return null;
197
- }
198
-
199
- return wrapWithSuspense(h(Fragment, content), fallback);
200
- };
201
- },
202
- });
203
-
204
- export const RouteView = Object.assign(RouteViewComponent, { Match, NotFound });
205
-
206
- export type {
207
- RouteViewProps,
208
- MatchProps as RouteViewMatchProps,
209
- NotFoundProps as RouteViewNotFoundProps,
210
- } from "./types";
@@ -1,35 +0,0 @@
1
- import { defineComponent } from "vue";
2
-
3
- import type { PropType, VNode } from "vue";
4
-
5
- function renderNull() {
6
- return null;
7
- }
8
-
9
- export const Match = defineComponent({
10
- name: "RouteView.Match",
11
- props: {
12
- segment: {
13
- type: String as PropType<string>,
14
- required: true,
15
- },
16
- exact: {
17
- type: Boolean,
18
- default: false,
19
- },
20
- fallback: {
21
- type: [Object, Function] as PropType<VNode | (() => VNode)>,
22
- default: undefined,
23
- },
24
- keepAlive: {
25
- type: Boolean,
26
- default: false,
27
- },
28
- },
29
- render: renderNull,
30
- });
31
-
32
- export const NotFound = defineComponent({
33
- name: "RouteView.NotFound",
34
- render: renderNull,
35
- });
@@ -1,110 +0,0 @@
1
- import { UNKNOWN_ROUTE } from "@real-router/core";
2
- import { startsWithSegment } from "@real-router/route-utils";
3
- import { Fragment, isVNode } from "vue";
4
-
5
- import { Match, NotFound } from "./components";
6
-
7
- import type { VNode } from "vue";
8
-
9
- type FallbackType = VNode | (() => VNode) | undefined;
10
-
11
- function isSegmentMatch(
12
- routeName: string,
13
- fullSegmentName: string,
14
- exact: boolean,
15
- ): boolean {
16
- if (exact) {
17
- return routeName === fullSegmentName;
18
- }
19
-
20
- return startsWithSegment(routeName, fullSegmentName);
21
- }
22
-
23
- function normalizeChildren(children: unknown): VNode[] {
24
- if (Array.isArray(children)) {
25
- const result: VNode[] = [];
26
-
27
- for (const child of children) {
28
- if (Array.isArray(child)) {
29
- result.push(...normalizeChildren(child));
30
- } else if (isVNode(child)) {
31
- result.push(child);
32
- }
33
- }
34
-
35
- return result;
36
- }
37
-
38
- if (isVNode(children)) {
39
- return [children];
40
- }
41
-
42
- return [];
43
- }
44
-
45
- export function collectElements(children: unknown, result: VNode[]): void {
46
- const vnodes = normalizeChildren(children);
47
-
48
- for (const child of vnodes) {
49
- if (child.type === Match || child.type === NotFound) {
50
- result.push(child);
51
- } else if (child.type === Fragment) {
52
- collectElements(child.children, result);
53
- }
54
- }
55
- }
56
-
57
- export function buildRenderList(
58
- elements: VNode[],
59
- routeName: string,
60
- nodeName: string,
61
- ): {
62
- rendered: VNode[];
63
- activeMatchFound: boolean;
64
- fallback?: FallbackType;
65
- } {
66
- let notFoundChildren: unknown = null;
67
- let activeMatchFound = false;
68
- let fallback: FallbackType = undefined;
69
- const rendered: VNode[] = [];
70
-
71
- for (const child of elements) {
72
- if (child.type === NotFound) {
73
- notFoundChildren = child.children;
74
- continue;
75
- }
76
-
77
- const props = child.props as {
78
- segment: string;
79
- exact?: boolean;
80
- fallback?: FallbackType;
81
- } | null;
82
- const segment = props?.segment ?? "";
83
- const exact = props?.exact ?? false;
84
- const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
85
- const isActive =
86
- !activeMatchFound && isSegmentMatch(routeName, fullSegmentName, exact);
87
-
88
- if (isActive) {
89
- activeMatchFound = true;
90
- fallback = props?.fallback;
91
- rendered.push(child);
92
- }
93
- }
94
-
95
- if (
96
- !activeMatchFound &&
97
- routeName === UNKNOWN_ROUTE &&
98
- notFoundChildren !== null
99
- ) {
100
- const nfElements = elements.filter((element) => element.type === NotFound);
101
- /* v8 ignore next 3 */
102
- const lastNf = nfElements.at(-1);
103
-
104
- if (lastNf) {
105
- rendered.push(lastNf);
106
- }
107
- }
108
-
109
- return { rendered, activeMatchFound, fallback };
110
- }
@@ -1,7 +0,0 @@
1
- export { RouteView } from "./RouteView";
2
-
3
- export type {
4
- RouteViewProps,
5
- RouteViewMatchProps,
6
- RouteViewNotFoundProps,
7
- } from "./RouteView";
@@ -1,15 +0,0 @@
1
- import type { VNode } from "vue";
2
-
3
- export interface RouteViewProps {
4
- readonly nodeName: string;
5
- readonly keepAlive?: boolean;
6
- }
7
-
8
- export interface MatchProps {
9
- readonly segment: string;
10
- readonly exact?: boolean;
11
- readonly fallback?: VNode | (() => VNode);
12
- readonly keepAlive?: boolean;
13
- }
14
-
15
- export type NotFoundProps = Record<string, never>;
@@ -1,69 +0,0 @@
1
- import { defineComponent, h, ref, watch, computed, Fragment } from "vue";
2
-
3
- import { useRouterError } from "../composables/useRouterError";
4
-
5
- import type { RouterError, State } from "@real-router/core";
6
- import type { VNode, PropType } from "vue";
7
-
8
- export const RouterErrorBoundary = defineComponent({
9
- name: "RouterErrorBoundary",
10
- props: {
11
- fallback: {
12
- type: Function as PropType<
13
- (error: RouterError, resetError: () => void) => VNode
14
- >,
15
- required: true,
16
- },
17
- onError: {
18
- type: Function as PropType<
19
- (
20
- error: RouterError,
21
- toRoute: State | null,
22
- fromRoute: State | null,
23
- ) => void
24
- >,
25
- default: undefined,
26
- },
27
- },
28
- setup(props, { slots }) {
29
- const snapshot = useRouterError();
30
- const dismissedVersion = ref(-1);
31
-
32
- watch(
33
- () => snapshot.value.version,
34
- () => {
35
- if (snapshot.value.error) {
36
- props.onError?.(
37
- snapshot.value.error,
38
- snapshot.value.toRoute,
39
- snapshot.value.fromRoute,
40
- );
41
- }
42
- },
43
- { immediate: true },
44
- );
45
-
46
- const visibleError = computed(() =>
47
- snapshot.value.version > dismissedVersion.value
48
- ? snapshot.value.error
49
- : null,
50
- );
51
-
52
- const resetError = () => {
53
- dismissedVersion.value = snapshot.value.version;
54
- };
55
-
56
- return () => {
57
- const children = slots.default?.() ?? [];
58
- const errorVNode = visibleError.value
59
- ? props.fallback(visibleError.value, resetError)
60
- : null;
61
-
62
- return h(Fragment, null, [...children, errorVNode]);
63
- };
64
- },
65
- });
66
-
67
- export type RouterErrorBoundaryProps = InstanceType<
68
- typeof RouterErrorBoundary
69
- >["$props"];
@@ -1,23 +0,0 @@
1
- import { createActiveRouteSource } from "@real-router/sources";
2
-
3
- import { useRefFromSource } from "../useRefFromSource";
4
- import { useRouter } from "./useRouter";
5
-
6
- import type { Params } from "@real-router/core";
7
- import type { ShallowRef } from "vue";
8
-
9
- export function useIsActiveRoute(
10
- routeName: string,
11
- params?: Params,
12
- strict = false,
13
- ignoreQueryParams = true,
14
- ): ShallowRef<boolean> {
15
- const router = useRouter();
16
-
17
- const source = createActiveRouteSource(router, routeName, params, {
18
- strict,
19
- ignoreQueryParams,
20
- });
21
-
22
- return useRefFromSource(source);
23
- }
@@ -1,15 +0,0 @@
1
- import { inject } from "vue";
2
-
3
- import { NavigatorKey } from "../context";
4
-
5
- import type { Navigator } from "@real-router/core";
6
-
7
- export const useNavigator = (): Navigator => {
8
- const navigator = inject(NavigatorKey);
9
-
10
- if (!navigator) {
11
- throw new Error("useNavigator must be used within a RouterProvider");
12
- }
13
-
14
- return navigator;
15
- };
@@ -1,15 +0,0 @@
1
- import { inject } from "vue";
2
-
3
- import { RouteKey } from "../context";
4
-
5
- import type { RouteContext } from "../types";
6
-
7
- export const useRoute = (): RouteContext => {
8
- const routeContext = inject(RouteKey);
9
-
10
- if (!routeContext) {
11
- throw new Error("useRoute must be used within a RouterProvider");
12
- }
13
-
14
- return routeContext;
15
- };
@@ -1,38 +0,0 @@
1
- import { getNavigator } from "@real-router/core";
2
- import { createRouteNodeSource } from "@real-router/sources";
3
- import { shallowRef, watch } from "vue";
4
-
5
- import { useRefFromSource } from "../useRefFromSource";
6
- import { useRouter } from "./useRouter";
7
-
8
- import type { RouteContext } from "../types";
9
- import type { State } from "@real-router/core";
10
-
11
- export function useRouteNode(nodeName: string): RouteContext {
12
- const router = useRouter();
13
-
14
- const source = createRouteNodeSource(router, nodeName);
15
- const snapshot = useRefFromSource(source);
16
-
17
- const navigator = getNavigator(router);
18
-
19
- const route = shallowRef<State | undefined>(snapshot.value.route);
20
- const previousRoute = shallowRef<State | undefined>(
21
- snapshot.value.previousRoute,
22
- );
23
-
24
- watch(
25
- snapshot,
26
- (newSnapshot) => {
27
- route.value = newSnapshot.route;
28
- previousRoute.value = newSnapshot.previousRoute;
29
- },
30
- { flush: "sync" },
31
- );
32
-
33
- return {
34
- navigator,
35
- route,
36
- previousRoute,
37
- };
38
- }
@@ -1,12 +0,0 @@
1
- import { getPluginApi } from "@real-router/core/api";
2
- import { getRouteUtils } from "@real-router/route-utils";
3
-
4
- import { useRouter } from "./useRouter";
5
-
6
- import type { RouteUtils } from "@real-router/route-utils";
7
-
8
- export const useRouteUtils = (): RouteUtils => {
9
- const router = useRouter();
10
-
11
- return getRouteUtils(getPluginApi(router).getTree());
12
- };
@@ -1,15 +0,0 @@
1
- import { inject } from "vue";
2
-
3
- import { RouterKey } from "../context";
4
-
5
- import type { Router } from "@real-router/core";
6
-
7
- export const useRouter = (): Router => {
8
- const router = inject(RouterKey);
9
-
10
- if (!router) {
11
- throw new Error("useRouter must be used within a RouterProvider");
12
- }
13
-
14
- return router;
15
- };
@@ -1,23 +0,0 @@
1
- import { createErrorSource } from "@real-router/sources";
2
-
3
- import { useRefFromSource } from "../useRefFromSource";
4
- import { useRouter } from "./useRouter";
5
-
6
- import type { Router } from "@real-router/core";
7
- import type { RouterErrorSnapshot, RouterSource } from "@real-router/sources";
8
- import type { ShallowRef } from "vue";
9
-
10
- const cache = new WeakMap<Router, RouterSource<RouterErrorSnapshot>>();
11
-
12
- export function useRouterError(): ShallowRef<RouterErrorSnapshot> {
13
- const router = useRouter();
14
-
15
- let source = cache.get(router);
16
-
17
- if (!source) {
18
- source = createErrorSource(router);
19
- cache.set(router, source);
20
- }
21
-
22
- return useRefFromSource(source);
23
- }