@real-router/vue 0.15.2 → 0.15.4

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 (39) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/esm/index.mjs +1 -1
  4. package/dist/esm/index.mjs.map +1 -1
  5. package/package.json +7 -8
  6. package/src/RouterProvider.ts +0 -162
  7. package/src/components/Await.ts +0 -47
  8. package/src/components/ClientOnly.ts +0 -16
  9. package/src/components/HttpStatusCode.ts +0 -74
  10. package/src/components/HttpStatusProvider.ts +0 -22
  11. package/src/components/Link.ts +0 -226
  12. package/src/components/RouteView/RouteView.ts +0 -233
  13. package/src/components/RouteView/components.ts +0 -53
  14. package/src/components/RouteView/helpers.ts +0 -207
  15. package/src/components/RouteView/index.ts +0 -8
  16. package/src/components/RouteView/types.ts +0 -20
  17. package/src/components/RouterErrorBoundary.ts +0 -61
  18. package/src/components/ServerOnly.ts +0 -16
  19. package/src/components/Streamed.ts +0 -31
  20. package/src/composables/useDeferred.ts +0 -37
  21. package/src/composables/useIsActiveRoute.ts +0 -61
  22. package/src/composables/useNavigator.ts +0 -15
  23. package/src/composables/useRoute.ts +0 -34
  24. package/src/composables/useRouteEnter.ts +0 -120
  25. package/src/composables/useRouteExit.ts +0 -116
  26. package/src/composables/useRouteNode.ts +0 -31
  27. package/src/composables/useRouteUtils.ts +0 -12
  28. package/src/composables/useRouter.ts +0 -15
  29. package/src/composables/useRouterTransition.ts +0 -14
  30. package/src/constants.ts +0 -9
  31. package/src/context.ts +0 -13
  32. package/src/createRouterPlugin.ts +0 -31
  33. package/src/directives/vLink.ts +0 -208
  34. package/src/index.ts +0 -64
  35. package/src/setupRouteProvision.ts +0 -42
  36. package/src/ssr.ts +0 -39
  37. package/src/types.ts +0 -40
  38. package/src/useRefFromSource.ts +0 -16
  39. package/src/utils/createHttpStatusSink.ts +0 -31
@@ -1,233 +0,0 @@
1
- import {
2
- Fragment,
3
- defineComponent,
4
- h,
5
- KeepAlive,
6
- markRaw,
7
- Suspense,
8
- } from "vue";
9
-
10
- import { Match, NotFound, Self } from "./components";
11
- import {
12
- buildRenderList,
13
- collectElements,
14
- isKeepAliveEnabled,
15
- } from "./helpers";
16
- import { useRouteNode } from "../../composables/useRouteNode";
17
-
18
- import type { Component, VNode } from "vue";
19
-
20
- type SlotChildren = Record<string, (() => VNode[]) | undefined> | null;
21
-
22
- function getSlotContent(vnode: VNode): VNode[] | null {
23
- const slots = vnode.children as SlotChildren;
24
-
25
- return slots?.default?.() ?? null;
26
- }
27
-
28
- function getOrCreateWrapper(
29
- cache: Map<string, Component>,
30
- segment: string,
31
- ): Component {
32
- const existing = cache.get(segment);
33
-
34
- if (existing) {
35
- return existing;
36
- }
37
-
38
- const wrapper = markRaw(
39
- defineComponent({
40
- name: `KeepAlive-${segment}`,
41
- setup(_wrapperProps, wrapperCtx) {
42
- return () => wrapperCtx.slots.default?.();
43
- },
44
- }),
45
- );
46
-
47
- cache.set(segment, wrapper);
48
-
49
- return wrapper;
50
- }
51
-
52
- function wrapWithSuspense(content: VNode, fallback: unknown): VNode {
53
- if (fallback === undefined) {
54
- return content;
55
- }
56
-
57
- const fallbackContent =
58
- typeof fallback === "function" ? (fallback as () => VNode)() : fallback;
59
-
60
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
61
- const suspenseComponent = Suspense as any;
62
-
63
- return h(
64
- suspenseComponent,
65
- {},
66
- {
67
- default: () => content,
68
- fallback: () => fallbackContent,
69
- },
70
- );
71
- }
72
-
73
- // Lazy-initialised — only allocated when a per-Match keepAlive path needs to
74
- // keep the cache slot "occupied" with a no-render placeholder. Apps without
75
- // keepAlive never pay the markRaw + defineComponent allocation at import.
76
- let emptyKeepAlivePlaceholderInstance: Component | null = null;
77
-
78
- function getEmptyKeepAlivePlaceholder(): Component {
79
- emptyKeepAlivePlaceholderInstance ??= markRaw(
80
- defineComponent({
81
- name: "KeepAlive-placeholder",
82
- render() {
83
- return null;
84
- },
85
- }),
86
- );
87
-
88
- return emptyKeepAlivePlaceholderInstance;
89
- }
90
-
91
- function renderWithRootKA(
92
- activeChild: VNode,
93
- wrapperCache: Map<string, Component>,
94
- fallback: unknown,
95
- ): VNode {
96
- const activeProps = activeChild.props as { segment?: string } | null;
97
- const segment = activeProps?.segment ?? "__not-found__";
98
- const WrapperComponent = getOrCreateWrapper(wrapperCache, segment);
99
- const slotContent = getSlotContent(activeChild) ?? [];
100
- const keepAliveContent = h(KeepAlive, null, {
101
- default: () =>
102
- h(WrapperComponent, { key: segment }, { default: () => slotContent }),
103
- });
104
-
105
- return wrapWithSuspense(keepAliveContent, fallback);
106
- }
107
-
108
- function renderWithPerMatchKA(
109
- activeChild: VNode,
110
- wrapperCache: Map<string, Component>,
111
- fallback: unknown,
112
- ): VNode | null {
113
- const matchProps = activeChild.props as {
114
- segment?: string;
115
- keepAlive?: unknown;
116
- } | null;
117
-
118
- if (isKeepAliveEnabled(matchProps?.keepAlive) && activeChild.type === Match) {
119
- /* v8 ignore start */
120
- const segment = matchProps?.segment ?? "__not-found__";
121
- /* v8 ignore stop */
122
- const WrapperComponent = getOrCreateWrapper(wrapperCache, segment);
123
- const slotContent = getSlotContent(activeChild) ?? [];
124
-
125
- return h(Fragment, [
126
- h(KeepAlive, null, {
127
- default: () =>
128
- h(WrapperComponent, { key: segment }, { default: () => slotContent }),
129
- }),
130
- ]);
131
- }
132
-
133
- const content = getSlotContent(activeChild);
134
-
135
- /* v8 ignore start */
136
- if (!content) {
137
- return null;
138
- }
139
- /* v8 ignore stop */
140
-
141
- return h(Fragment, [
142
- h(KeepAlive, null, {
143
- default: () => h(getEmptyKeepAlivePlaceholder()),
144
- }),
145
- wrapWithSuspense(h(Fragment, content), fallback),
146
- ]);
147
- }
148
-
149
- const RouteViewComponent = defineComponent({
150
- name: "RouteView",
151
- props: {
152
- nodeName: {
153
- type: String,
154
- required: true,
155
- },
156
- keepAlive: {
157
- type: Boolean,
158
- default: false,
159
- },
160
- },
161
- setup(props, { slots }) {
162
- const routeContext = useRouteNode(props.nodeName);
163
- const wrapperCache = new Map<string, Component>();
164
-
165
- return (): VNode | null => {
166
- const route = routeContext.route.value;
167
-
168
- if (!route) {
169
- return null;
170
- }
171
-
172
- const slotOutput = slots.default?.();
173
- const elements: VNode[] = [];
174
-
175
- collectElements(slotOutput, elements);
176
-
177
- // `hasPerMatchKA` is a side-channel produced by the same pipeline pass
178
- // that builds `rendered` — closes the audit §8.1 "double iteration"
179
- // finding. The previous identity-cache on `slotOutput` is no longer
180
- // needed: per-render cost is one O(n) walk instead of two.
181
- const { rendered, fallback, hasPerMatchKA } = buildRenderList(
182
- elements,
183
- route.name,
184
- props.nodeName,
185
- );
186
-
187
- if (rendered.length === 0) {
188
- return null;
189
- }
190
-
191
- const activeChild = rendered[0];
192
-
193
- if (props.keepAlive) {
194
- return renderWithRootKA(activeChild, wrapperCache, fallback);
195
- }
196
-
197
- /* v8 ignore start */
198
- if (
199
- activeChild.type !== Match &&
200
- activeChild.type !== Self &&
201
- activeChild.type !== NotFound
202
- ) {
203
- return null;
204
- }
205
- /* v8 ignore stop */
206
-
207
- if (hasPerMatchKA) {
208
- return renderWithPerMatchKA(activeChild, wrapperCache, fallback);
209
- }
210
-
211
- const content = getSlotContent(activeChild);
212
-
213
- if (!content) {
214
- return null;
215
- }
216
-
217
- return wrapWithSuspense(h(Fragment, content), fallback);
218
- };
219
- },
220
- });
221
-
222
- export const RouteView = Object.assign(RouteViewComponent, {
223
- Match,
224
- Self,
225
- NotFound,
226
- });
227
-
228
- export type {
229
- RouteViewProps,
230
- MatchProps as RouteViewMatchProps,
231
- SelfProps as RouteViewSelfProps,
232
- NotFoundProps as RouteViewNotFoundProps,
233
- } from "./types";
@@ -1,53 +0,0 @@
1
- import { defineComponent } from "vue";
2
-
3
- import type { SelfProps } from "./types";
4
- import type { FunctionalComponent, PropType, VNode } from "vue";
5
-
6
- function renderNull() {
7
- return null;
8
- }
9
-
10
- export const Match = defineComponent({
11
- name: "RouteView.Match",
12
- props: {
13
- segment: {
14
- type: String as PropType<string>,
15
- required: true,
16
- },
17
- exact: {
18
- type: Boolean,
19
- default: false,
20
- },
21
- fallback: {
22
- type: [Object, Function] as PropType<VNode | (() => VNode)>,
23
- default: undefined,
24
- },
25
- keepAlive: {
26
- type: Boolean,
27
- default: false,
28
- },
29
- },
30
- render: renderNull,
31
- });
32
-
33
- // Type Self via FunctionalComponent<SelfProps> so the SelfProps interface
34
- // is anchored to the component contract — knip otherwise flags SelfProps
35
- // as unused even though it's re-exported as RouteViewSelfProps for
36
- // consumers wrapping Self in custom HOCs.
37
- const SelfImpl = defineComponent({
38
- name: "RouteView.Self",
39
- props: {
40
- fallback: {
41
- type: [Object, Function] as PropType<VNode | (() => VNode)>,
42
- default: undefined,
43
- },
44
- },
45
- render: renderNull,
46
- });
47
-
48
- export const Self = SelfImpl as unknown as FunctionalComponent<SelfProps>;
49
-
50
- export const NotFound = defineComponent({
51
- name: "RouteView.NotFound",
52
- render: renderNull,
53
- });
@@ -1,207 +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, Self } from "./components";
6
-
7
- import type { VNode } from "vue";
8
-
9
- const MARKER_TYPES: ReadonlySet<unknown> = new Set([Match, Self, NotFound]);
10
- const KEEP_ALIVE_VALUES: ReadonlySet<unknown> = new Set([
11
- true,
12
- "",
13
- "keep-alive",
14
- ]);
15
-
16
- type FallbackType = VNode | (() => VNode) | undefined;
17
-
18
- interface FallbackSlots {
19
- selfVNode: VNode | null;
20
- selfFallback: FallbackType;
21
- notFoundChildren: unknown;
22
- }
23
-
24
- export function isSegmentMatch(
25
- routeName: string,
26
- fullSegmentName: string,
27
- exact: boolean,
28
- ): boolean {
29
- if (exact) {
30
- return routeName === fullSegmentName;
31
- }
32
-
33
- return startsWithSegment(routeName, fullSegmentName);
34
- }
35
-
36
- // Vue compiles boolean-shorthand template attributes (`<Match keepAlive>`) to
37
- // an empty string instead of `true`, and converts them to `true` only when the
38
- // receiving component's prop is declared with `type: Boolean`. `Match` is a
39
- // marker component (`render: null`) — its props are inspected on the VNode
40
- // without ever going through Vue's prop-casting pipeline, so the raw `""` (or
41
- // the hyphenated attribute name) reaches us here. Accept the same trio Vue's
42
- // runtime does.
43
- export function isKeepAliveEnabled(value: unknown): boolean {
44
- return KEEP_ALIVE_VALUES.has(value);
45
- }
46
-
47
- function normalizeChildren(children: unknown): VNode[] {
48
- if (Array.isArray(children)) {
49
- const result: VNode[] = [];
50
-
51
- for (const child of children) {
52
- if (Array.isArray(child)) {
53
- result.push(...normalizeChildren(child));
54
- } else if (isVNode(child)) {
55
- result.push(child);
56
- }
57
- }
58
-
59
- return result;
60
- }
61
-
62
- if (isVNode(children)) {
63
- return [children];
64
- }
65
-
66
- return [];
67
- }
68
-
69
- export function collectElements(children: unknown, result: VNode[]): void {
70
- const vnodes = normalizeChildren(children);
71
-
72
- for (const child of vnodes) {
73
- if (MARKER_TYPES.has(child.type)) {
74
- result.push(child);
75
- } else if (child.type === Fragment) {
76
- collectElements(child.children, result);
77
- }
78
- }
79
- }
80
-
81
- function recordFallback(child: VNode, slots: FallbackSlots): boolean {
82
- if (child.type === NotFound) {
83
- slots.notFoundChildren = child.children;
84
-
85
- return true;
86
- }
87
-
88
- if (child.type === Self) {
89
- if (slots.selfVNode === null) {
90
- slots.selfVNode = child;
91
- const props = child.props as { fallback?: FallbackType } | null;
92
-
93
- slots.selfFallback = props?.fallback;
94
- }
95
-
96
- return true;
97
- }
98
-
99
- return false;
100
- }
101
-
102
- function evaluateMatch(
103
- child: VNode,
104
- routeName: string,
105
- nodeName: string,
106
- ): { isActive: boolean; fallback: FallbackType } {
107
- const props = child.props as {
108
- segment: string;
109
- exact?: boolean;
110
- fallback?: FallbackType;
111
- } | null;
112
- const segment = props?.segment ?? "";
113
- const exact = props?.exact ?? false;
114
- const fullSegmentName = nodeName ? `${nodeName}.${segment}` : segment;
115
- const isActive = isSegmentMatch(routeName, fullSegmentName, exact);
116
-
117
- return { isActive, fallback: props?.fallback };
118
- }
119
-
120
- function appendFallback(
121
- rendered: VNode[],
122
- routeName: string,
123
- nodeName: string,
124
- slots: FallbackSlots,
125
- elements: VNode[],
126
- ): FallbackType {
127
- if (slots.selfVNode !== null && routeName === nodeName) {
128
- rendered.push(slots.selfVNode);
129
-
130
- return slots.selfFallback;
131
- }
132
-
133
- if (routeName === UNKNOWN_ROUTE && slots.notFoundChildren !== null) {
134
- const nfElements = elements.filter((element) => element.type === NotFound);
135
- /* v8 ignore next 3 */
136
- const lastNf = nfElements.at(-1);
137
-
138
- if (lastNf) {
139
- rendered.push(lastNf);
140
- }
141
- }
142
-
143
- return undefined;
144
- }
145
-
146
- export function buildRenderList(
147
- elements: VNode[],
148
- routeName: string,
149
- nodeName: string,
150
- ): {
151
- rendered: VNode[];
152
- activeMatchFound: boolean;
153
- fallback?: FallbackType;
154
- /**
155
- * True iff any `<Match>` child in the input has its `keepAlive` prop set
156
- * to one of Vue's accepted boolean-shorthand forms. Surfaced as a
157
- * side-channel from the single pipeline pass so the caller doesn't have
158
- * to re-iterate `elements` after `buildRenderList` returns — closes a MED
159
- * code-quality finding (audit §8.1).
160
- */
161
- hasPerMatchKA: boolean;
162
- } {
163
- const slots: FallbackSlots = {
164
- selfVNode: null,
165
- selfFallback: undefined,
166
- notFoundChildren: null,
167
- };
168
- let activeMatchFound = false;
169
- let fallback: FallbackType = undefined;
170
- let hasPerMatchKA = false;
171
- const rendered: VNode[] = [];
172
-
173
- for (const child of elements) {
174
- // Match-only side-channel: scan for the keepAlive shorthand in the same
175
- // pass that already inspects every child. Short-circuits once a positive
176
- // is found to avoid redundant prop reads in big slot trees.
177
- if (!hasPerMatchKA && child.type === Match) {
178
- const matchProps = child.props as { keepAlive?: unknown } | null;
179
-
180
- if (isKeepAliveEnabled(matchProps?.keepAlive)) {
181
- hasPerMatchKA = true;
182
- }
183
- }
184
-
185
- if (recordFallback(child, slots)) {
186
- continue;
187
- }
188
-
189
- if (activeMatchFound) {
190
- continue;
191
- }
192
-
193
- const result = evaluateMatch(child, routeName, nodeName);
194
-
195
- if (result.isActive) {
196
- activeMatchFound = true;
197
- fallback = result.fallback;
198
- rendered.push(child);
199
- }
200
- }
201
-
202
- if (!activeMatchFound) {
203
- fallback = appendFallback(rendered, routeName, nodeName, slots, elements);
204
- }
205
-
206
- return { rendered, activeMatchFound, fallback, hasPerMatchKA };
207
- }
@@ -1,8 +0,0 @@
1
- export { RouteView } from "./RouteView";
2
-
3
- export type {
4
- RouteViewProps,
5
- RouteViewMatchProps,
6
- RouteViewSelfProps,
7
- RouteViewNotFoundProps,
8
- } from "./RouteView";
@@ -1,20 +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 interface SelfProps {
16
- /** Fallback content while children are suspended. */
17
- readonly fallback?: VNode | (() => VNode);
18
- }
19
-
20
- export type NotFoundProps = Record<string, never>;
@@ -1,61 +0,0 @@
1
- import { createDismissableError } from "@real-router/sources";
2
- import { defineComponent, h, watch, Fragment } from "vue";
3
-
4
- import { useRouter } from "../composables/useRouter";
5
- import { useRefFromSource } from "../useRefFromSource";
6
-
7
- import type { RouterError, State } from "@real-router/core";
8
- import type { VNode, PropType } from "vue";
9
-
10
- export const RouterErrorBoundary = defineComponent({
11
- name: "RouterErrorBoundary",
12
- props: {
13
- fallback: {
14
- type: Function as PropType<
15
- (error: RouterError, resetError: () => void) => VNode
16
- >,
17
- required: true,
18
- },
19
- onError: {
20
- type: Function as PropType<
21
- (
22
- error: RouterError,
23
- toRoute: State | null,
24
- fromRoute: State | null,
25
- ) => void
26
- >,
27
- default: undefined,
28
- },
29
- },
30
- setup(props, { slots }) {
31
- const router = useRouter();
32
- const snapshot = useRefFromSource(createDismissableError(router));
33
-
34
- watch(
35
- () => snapshot.value.version,
36
- () => {
37
- if (snapshot.value.error) {
38
- props.onError?.(
39
- snapshot.value.error,
40
- snapshot.value.toRoute,
41
- snapshot.value.fromRoute,
42
- );
43
- }
44
- },
45
- { immediate: true },
46
- );
47
-
48
- return () => {
49
- const children = slots.default?.() ?? [];
50
- const errorVNode = snapshot.value.error
51
- ? props.fallback(snapshot.value.error, snapshot.value.resetError)
52
- : null;
53
-
54
- return h(Fragment, null, [...children, errorVNode]);
55
- };
56
- },
57
- });
58
-
59
- export type RouterErrorBoundaryProps = InstanceType<
60
- typeof RouterErrorBoundary
61
- >["$props"];
@@ -1,16 +0,0 @@
1
- import { defineComponent, onMounted, ref } from "vue";
2
-
3
- export const ServerOnly = defineComponent({
4
- name: "ServerOnly",
5
- setup(_, { slots }) {
6
- const mounted = ref(false);
7
-
8
- onMounted(() => {
9
- mounted.value = true;
10
- });
11
-
12
- return () => (mounted.value ? slots.fallback?.() : slots.default?.());
13
- },
14
- });
15
-
16
- export type ServerOnlyProps = InstanceType<typeof ServerOnly>["$props"];
@@ -1,31 +0,0 @@
1
- import { defineComponent, h, Suspense } from "vue";
2
-
3
- /**
4
- * Cross-adapter alias for Vue's native `<Suspense>`. Symmetric naming with
5
- * the React/Preact/Solid/Svelte/Angular `<Streamed>` components.
6
- *
7
- * Slots:
8
- * - `default` — content (may contain `<Await>` or `async setup()` children).
9
- * - `fallback` — shown while any descendant suspends.
10
- *
11
- * Vue's `<Suspense>` is **blocking** under SSR (no out-of-order placeholder
12
- * resolution) — render of HTML after `<Streamed>` waits for every
13
- * `async setup()` inside. This matches Vue 3's stable streaming behaviour
14
- * (vs React 19 / Solid which support OOO resolution).
15
- */
16
- export const Streamed = defineComponent({
17
- name: "Streamed",
18
- setup(_, { slots }) {
19
- return () =>
20
- h(
21
- Suspense,
22
- {},
23
- {
24
- default: () => slots.default?.(),
25
- fallback: () => slots.fallback?.(),
26
- },
27
- );
28
- },
29
- });
30
-
31
- export type StreamedProps = InstanceType<typeof Streamed>["$props"];
@@ -1,37 +0,0 @@
1
- import { useRoute } from "./useRoute";
2
-
3
- interface DeferredContext {
4
- ssrDataDeferred?: Record<string, Promise<unknown>>;
5
- }
6
-
7
- const NEVER_PROMISE = new Promise<never>(() => {
8
- // Intentionally never resolves — surfaces a forever-pending Suspense boundary
9
- // when a key is requested that the loader never declared.
10
- });
11
-
12
- /**
13
- * Read a deferred promise published by `defer({ deferred: { <key>: Promise } })`
14
- * inside an SSR data loader. Returns the Promise for use inside `async setup()`
15
- * (Vue's native Suspense pattern) or paired with `<Await name="key">`.
16
- *
17
- * ```ts
18
- * // Vue async setup pattern
19
- * export default defineComponent({
20
- * async setup() {
21
- * const reviews = await useDeferred<Review[]>("reviews");
22
- * return () => h("div", reviews.map(...));
23
- * },
24
- * });
25
- * ```
26
- *
27
- * Returns a forever-pending promise when the key is missing — surfaces
28
- * loader/consumer key drift as a visible Suspense fallback rather than a
29
- * silent runtime error.
30
- */
31
- export function useDeferred<T = unknown>(key: string): Promise<T> {
32
- const { route } = useRoute();
33
- const context = route.value.context as DeferredContext;
34
- const deferred = context.ssrDataDeferred;
35
-
36
- return (deferred?.[key] ?? NEVER_PROMISE) as Promise<T>;
37
- }