@real-router/vue 0.15.1 → 0.15.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.
- package/dist/cjs/createHttpStatusSink-BENH-5-J.d.ts.map +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/ssr.d.ts.map +1 -1
- package/dist/cjs/ssr.js.map +1 -1
- package/dist/cjs/useRoute-Dba_kop6.js.map +1 -1
- package/dist/esm/createHttpStatusSink-BENH-5-J.d.mts.map +1 -1
- package/dist/esm/index.d.mts.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/ssr.d.mts.map +1 -1
- package/package.json +8 -9
- package/src/RouterProvider.ts +0 -162
- package/src/components/Await.ts +0 -47
- package/src/components/ClientOnly.ts +0 -16
- package/src/components/HttpStatusCode.ts +0 -74
- package/src/components/HttpStatusProvider.ts +0 -22
- package/src/components/Link.ts +0 -226
- package/src/components/RouteView/RouteView.ts +0 -233
- package/src/components/RouteView/components.ts +0 -53
- package/src/components/RouteView/helpers.ts +0 -204
- package/src/components/RouteView/index.ts +0 -8
- package/src/components/RouteView/types.ts +0 -20
- package/src/components/RouterErrorBoundary.ts +0 -61
- package/src/components/ServerOnly.ts +0 -16
- package/src/components/Streamed.ts +0 -31
- package/src/composables/useDeferred.ts +0 -37
- package/src/composables/useIsActiveRoute.ts +0 -61
- package/src/composables/useNavigator.ts +0 -15
- package/src/composables/useRoute.ts +0 -34
- package/src/composables/useRouteEnter.ts +0 -120
- package/src/composables/useRouteExit.ts +0 -116
- package/src/composables/useRouteNode.ts +0 -31
- package/src/composables/useRouteUtils.ts +0 -12
- package/src/composables/useRouter.ts +0 -15
- package/src/composables/useRouterTransition.ts +0 -14
- package/src/constants.ts +0 -9
- package/src/context.ts +0 -13
- package/src/createRouterPlugin.ts +0 -31
- package/src/directives/vLink.ts +0 -208
- package/src/index.ts +0 -64
- package/src/setupRouteProvision.ts +0 -42
- package/src/ssr.ts +0 -39
- package/src/types.ts +0 -40
- package/src/useRefFromSource.ts +0 -16
- package/src/utils/createHttpStatusSink.ts +0 -31
package/dist/esm/ssr.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.d.mts","names":[],"sources":["../../src/components/ClientOnly.ts","../../src/components/ServerOnly.ts","../../src/components/Await.ts","../../src/components/Streamed.ts","../../src/components/HttpStatusCode.ts","../../src/components/HttpStatusProvider.ts","../../src/composables/useDeferred.ts"],"mappings":";;;;cAEa,UAAA,gBAAU,eAAA,yBAAA,KAAA,eAAA,YAAA,gBAAA,eAAA;EAAA;+IAWrB,QAAA,OAAA,QAAA;KAEU,eAAA,GAAkB,YAAY,QAAQ,UAAA;;;cCbrC,UAAA,gBAAU,eAAA,yBAAA,KAAA,eAAA,YAAA,gBAAA,eAAA;EAAA;+IAWrB,QAAA,OAAA,QAAA;KAEU,eAAA,GAAkB,YAAY,QAAQ,UAAA;;;;;;;ADblD;;;;;;;;;;;;;;;;;AAaA;;;;AAA4D;;;;cEkB/C,KAAA,gBAAK,eAAA,eAAA,gBAAA;EDpBhB;;;;;;;;;;;;KCiCU,UAAA,GAAa,YAAY,QAAQ,KAAA;;;;;;;AF5C7C;;;;;;;;;cGaa,QAAA,gBAAQ,eAAA,yBAAA,KAAA,eAAA,YAAA,gBAAA,eAAA;EAAA;iIAanB,QAAA,OAAA,QAAA;KAEU,aAAA,GAAgB,YAAY,QAAQ,QAAA;;;;;;;AH5BhD;;;;;;;;;;;;;;;;;AAaA;;;;AAA4D;;;;ACb5D;;;;;;;;;;;;;;;;;AAaA;cGyCa,cAAA,gBAAc,eAAA,eAAA,gBAAA;;;;;0CAjDA,qBAAA,gBAAA,qBAAA,4BAAA,WAAA,EAAA,QAAA,eAAA,gBAAA;EF0Bd;;;;;KEwCD,mBAAA,GAAsB,YAAY,QAAQ,cAAA;;;cClEzC,kBAAA,gBAAkB,eAAA,eAAA,gBAAA;;UAGH,QAAA,CAAS,cAAA;;;;;;;UAAT,QAAA,CAAS,cAAA;;;;KASzB,uBAAA,GAA0B,YAAY,QACzC,kBAAA;;;;;;;ALlBT;;;;;;;;;;;;;;;iBM4BgB,WAAA,
|
|
1
|
+
{"version":3,"file":"ssr.d.mts","names":[],"sources":["../../src/components/ClientOnly.ts","../../src/components/ServerOnly.ts","../../src/components/Await.ts","../../src/components/Streamed.ts","../../src/components/HttpStatusCode.ts","../../src/components/HttpStatusProvider.ts","../../src/composables/useDeferred.ts"],"mappings":";;;;cAEa,UAAA,gBAAU,eAAA,yBAAA,KAAA,eAAA,YAAA,gBAAA,eAAA;EAAA;+IAWrB,QAAA,OAAA,QAAA;KAEU,eAAA,GAAkB,YAAY,QAAQ,UAAA;;;cCbrC,UAAA,gBAAU,eAAA,yBAAA,KAAA,eAAA,YAAA,gBAAA,eAAA;EAAA;+IAWrB,QAAA,OAAA,QAAA;KAEU,eAAA,GAAkB,YAAY,QAAQ,UAAA;;;;;;;ADblD;;;;;;;;;;;;;;;;;AAaA;;;;AAA4D;;;;cEkB/C,KAAA,gBAAK,eAAA,eAAA,gBAAA;EDpBhB;;;;;;;;;;;;KCiCU,UAAA,GAAa,YAAY,QAAQ,KAAA;;;;;;;AF5C7C;;;;;;;;;cGaa,QAAA,gBAAQ,eAAA,yBAAA,KAAA,eAAA,YAAA,gBAAA,eAAA;EAAA;iIAanB,QAAA,OAAA,QAAA;KAEU,aAAA,GAAgB,YAAY,QAAQ,QAAA;;;;;;;AH5BhD;;;;;;;;;;;;;;;;;AAaA;;;;AAA4D;;;;ACb5D;;;;;;;;;;;;;;;;;AAaA;cGyCa,cAAA,gBAAc,eAAA,eAAA,gBAAA;;;;;0CAjDA,qBAAA,gBAAA,qBAAA,4BAAA,WAAA,EAAA,QAAA,eAAA,gBAAA;EF0Bd;;;;;KEwCD,mBAAA,GAAsB,YAAY,QAAQ,cAAA;;;cClEzC,kBAAA,gBAAkB,eAAA,eAAA,gBAAA;;UAGH,QAAA,CAAS,cAAA;;;;;;;UAAT,QAAA,CAAS,cAAA;;;;KASzB,uBAAA,GAA0B,YAAY,QACzC,kBAAA;;;;;;;ALlBT;;;;;;;;;;;;;;;iBM4BgB,WAAA,cAAyB,GAAA,WAAc,OAAO,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@real-router/vue",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.3",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Vue 3 integration for Real-Router",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -34,8 +34,7 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
|
-
"dist"
|
|
38
|
-
"src"
|
|
37
|
+
"dist"
|
|
39
38
|
],
|
|
40
39
|
"homepage": "https://github.com/greydragon888/real-router",
|
|
41
40
|
"repository": {
|
|
@@ -66,15 +65,15 @@
|
|
|
66
65
|
"license": "MIT",
|
|
67
66
|
"sideEffects": false,
|
|
68
67
|
"dependencies": {
|
|
69
|
-
"@real-router/core": "^0.
|
|
70
|
-
"@real-router/route-utils": "^0.2.
|
|
71
|
-
"@real-router/sources": "^0.8.
|
|
68
|
+
"@real-router/core": "^0.57.0",
|
|
69
|
+
"@real-router/route-utils": "^0.2.3",
|
|
70
|
+
"@real-router/sources": "^0.8.6"
|
|
72
71
|
},
|
|
73
72
|
"devDependencies": {
|
|
74
73
|
"@testing-library/jest-dom": "6.9.1",
|
|
75
|
-
"@vue/test-utils": "2.4.
|
|
76
|
-
"vue": "3.5.
|
|
77
|
-
"@real-router/browser-plugin": "^0.17.
|
|
74
|
+
"@vue/test-utils": "2.4.11",
|
|
75
|
+
"vue": "3.5.38",
|
|
76
|
+
"@real-router/browser-plugin": "^0.17.7"
|
|
78
77
|
},
|
|
79
78
|
"peerDependencies": {
|
|
80
79
|
"vue": ">=3.3.0"
|
package/src/RouterProvider.ts
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { defineComponent, onScopeDispose, provide, watch } from "vue";
|
|
2
|
-
|
|
3
|
-
import { NavigatorKey, RouteKey, RouterKey } from "./context";
|
|
4
|
-
import { pushDirectiveRouter } from "./directives/vLink";
|
|
5
|
-
import {
|
|
6
|
-
createRouteAnnouncer,
|
|
7
|
-
createScrollRestoration,
|
|
8
|
-
createScrollSpy,
|
|
9
|
-
createViewTransitions,
|
|
10
|
-
} from "./dom-utils";
|
|
11
|
-
import { setupRouteProvision } from "./setupRouteProvision";
|
|
12
|
-
|
|
13
|
-
import type { ScrollRestorationOptions, ScrollSpyOptions } from "./dom-utils";
|
|
14
|
-
import type { Router } from "@real-router/core";
|
|
15
|
-
import type { PropType } from "vue";
|
|
16
|
-
|
|
17
|
-
interface Disposable {
|
|
18
|
-
destroy: () => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Watch a dependency tuple and (re)create a toggleable utility (announcer /
|
|
23
|
-
* scroll-restorer / view-transitions). The factory returns `undefined` to
|
|
24
|
-
* mean "feature disabled" — no utility is created and no cleanup is wired.
|
|
25
|
-
* When a utility IS returned, its `destroy()` is registered via `onCleanup`,
|
|
26
|
-
* so flipping any dep (incl. the feature flag) tears down the previous
|
|
27
|
-
* instance before constructing the next.
|
|
28
|
-
*
|
|
29
|
-
* Extracted from three near-identical `watch(... { immediate: true })` blocks
|
|
30
|
-
* (announceNavigation / scrollRestoration / viewTransitions) — DRY without
|
|
31
|
-
* losing the per-utility dep tuple shape.
|
|
32
|
-
*/
|
|
33
|
-
function watchToggleableUtility<D extends readonly unknown[]>(
|
|
34
|
-
deps: () => D,
|
|
35
|
-
factory: (current: D) => Disposable | undefined,
|
|
36
|
-
): void {
|
|
37
|
-
watch(
|
|
38
|
-
deps,
|
|
39
|
-
(current, _prev, onCleanup) => {
|
|
40
|
-
const utility = factory(current);
|
|
41
|
-
|
|
42
|
-
if (utility) {
|
|
43
|
-
onCleanup(() => {
|
|
44
|
-
utility.destroy();
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{ immediate: true },
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const RouterProvider = defineComponent({
|
|
53
|
-
name: "RouterProvider",
|
|
54
|
-
props: {
|
|
55
|
-
router: {
|
|
56
|
-
type: Object as PropType<Router>,
|
|
57
|
-
required: true,
|
|
58
|
-
},
|
|
59
|
-
announceNavigation: {
|
|
60
|
-
type: Boolean,
|
|
61
|
-
default: false,
|
|
62
|
-
},
|
|
63
|
-
scrollRestoration: {
|
|
64
|
-
type: Object as PropType<ScrollRestorationOptions>,
|
|
65
|
-
},
|
|
66
|
-
scrollSpy: {
|
|
67
|
-
type: Object as PropType<ScrollSpyOptions>,
|
|
68
|
-
},
|
|
69
|
-
viewTransitions: {
|
|
70
|
-
type: Boolean,
|
|
71
|
-
default: false,
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
setup(props, { slots }) {
|
|
75
|
-
// Reactive announceNavigation: setting prop true/false at runtime
|
|
76
|
-
// creates/destroys the announcer accordingly. Prior implementation read
|
|
77
|
-
// the prop only inside onMounted, so toggling it post-mount silently no-op'd.
|
|
78
|
-
watchToggleableUtility(
|
|
79
|
-
() => [props.router, props.announceNavigation] as const,
|
|
80
|
-
([router, enabled]) =>
|
|
81
|
-
enabled ? createRouteAnnouncer(router) : undefined,
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
// Watch by primitives so inline `{ mode: "restore" }` doesn't thrash.
|
|
85
|
-
// scrollContainer is a getter invoked lazily on every event inside the
|
|
86
|
-
// utility — swapping its reference doesn't change the resolved element,
|
|
87
|
-
// so we intentionally omit it from watched sources.
|
|
88
|
-
watchToggleableUtility(
|
|
89
|
-
() =>
|
|
90
|
-
[
|
|
91
|
-
props.router,
|
|
92
|
-
props.scrollRestoration !== undefined,
|
|
93
|
-
props.scrollRestoration?.mode,
|
|
94
|
-
props.scrollRestoration?.anchorScrolling,
|
|
95
|
-
props.scrollRestoration?.behavior,
|
|
96
|
-
props.scrollRestoration?.storageKey,
|
|
97
|
-
] as const,
|
|
98
|
-
([router, enabled, mode, anchorScrolling, behavior, storageKey]) => {
|
|
99
|
-
if (!enabled) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return createScrollRestoration(router, {
|
|
104
|
-
mode,
|
|
105
|
-
anchorScrolling,
|
|
106
|
-
behavior,
|
|
107
|
-
storageKey,
|
|
108
|
-
scrollContainer: props.scrollRestoration?.scrollContainer,
|
|
109
|
-
});
|
|
110
|
-
},
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
// Reactive scrollSpy: watch by primitives, omit scrollContainer getter
|
|
114
|
-
// identity for the same reason scrollRestoration does.
|
|
115
|
-
watchToggleableUtility(
|
|
116
|
-
() =>
|
|
117
|
-
[
|
|
118
|
-
props.router,
|
|
119
|
-
props.scrollSpy !== undefined && props.scrollSpy.selector !== "",
|
|
120
|
-
props.scrollSpy?.selector,
|
|
121
|
-
props.scrollSpy?.rootMargin,
|
|
122
|
-
] as const,
|
|
123
|
-
([router, enabled, selector, rootMargin]) => {
|
|
124
|
-
if (!enabled || !selector) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return createScrollSpy(router, {
|
|
129
|
-
selector,
|
|
130
|
-
rootMargin,
|
|
131
|
-
scrollContainer: props.scrollSpy?.scrollContainer,
|
|
132
|
-
});
|
|
133
|
-
},
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
// Reactive viewTransitions: toggling prop creates/destroys the utility.
|
|
137
|
-
watchToggleableUtility(
|
|
138
|
-
() => [props.router, props.viewTransitions] as const,
|
|
139
|
-
([router, enabled]) =>
|
|
140
|
-
enabled ? createViewTransitions(router) : undefined,
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
// Push this provider's router on the v-link directive stack so nested
|
|
144
|
-
// RouterProviders behave like nested DI scopes (LIFO). Release on unmount
|
|
145
|
-
// restores the outer router for any v-link still mounted in the parent.
|
|
146
|
-
const releaseDirective = pushDirectiveRouter(props.router);
|
|
147
|
-
|
|
148
|
-
const { navigator, route, previousRoute, unsubscribe } =
|
|
149
|
-
setupRouteProvision(props.router);
|
|
150
|
-
|
|
151
|
-
onScopeDispose(() => {
|
|
152
|
-
releaseDirective();
|
|
153
|
-
unsubscribe();
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
provide(RouterKey, props.router);
|
|
157
|
-
provide(NavigatorKey, navigator);
|
|
158
|
-
provide(RouteKey, { navigator, route, previousRoute });
|
|
159
|
-
|
|
160
|
-
return () => slots.default?.();
|
|
161
|
-
},
|
|
162
|
-
});
|
package/src/components/Await.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { defineComponent } from "vue";
|
|
2
|
-
|
|
3
|
-
import { useDeferred } from "../composables/useDeferred";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Reads `useDeferred(name)` and hands the resolved value to the `default`
|
|
7
|
-
* scoped slot via Vue's native `async setup()` Suspense pattern. Wrap in
|
|
8
|
-
* `<Streamed>` (or Vue's `<Suspense>`).
|
|
9
|
-
*
|
|
10
|
-
* ```vue-html
|
|
11
|
-
* <Streamed>
|
|
12
|
-
* <Await name="reviews" v-slot="{ value }">
|
|
13
|
-
* <ReviewList :items="value" />
|
|
14
|
-
* </Await>
|
|
15
|
-
* <template #fallback>
|
|
16
|
-
* <Spinner />
|
|
17
|
-
* </template>
|
|
18
|
-
* </Streamed>
|
|
19
|
-
* ```
|
|
20
|
-
*
|
|
21
|
-
* Or with the render function:
|
|
22
|
-
*
|
|
23
|
-
* ```ts
|
|
24
|
-
* h(Await, { name: "reviews" }, {
|
|
25
|
-
* default: ({ value }: { value: Review[] }) => h(ReviewList, { items: value }),
|
|
26
|
-
* });
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* Implementation: `async setup()` awaits the deferred promise. Vue's
|
|
30
|
-
* `<Suspense>` boundary catches the pending promise and shows the fallback
|
|
31
|
-
* until resolution. Rejection bubbles to the nearest `onErrorCaptured`
|
|
32
|
-
* handler.
|
|
33
|
-
*/
|
|
34
|
-
export const Await = defineComponent({
|
|
35
|
-
name: "Await",
|
|
36
|
-
props: {
|
|
37
|
-
/** Deferred key declared in the loader's `defer({ deferred: { <name>: ... } })`. */
|
|
38
|
-
name: { type: String, required: true },
|
|
39
|
-
},
|
|
40
|
-
async setup(props, { slots }) {
|
|
41
|
-
const value = await useDeferred(props.name);
|
|
42
|
-
|
|
43
|
-
return () => slots.default?.({ value });
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
export type AwaitProps = InstanceType<typeof Await>["$props"];
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { defineComponent, onMounted, ref } from "vue";
|
|
2
|
-
|
|
3
|
-
export const ClientOnly = defineComponent({
|
|
4
|
-
name: "ClientOnly",
|
|
5
|
-
setup(_, { slots }) {
|
|
6
|
-
const mounted = ref(false);
|
|
7
|
-
|
|
8
|
-
onMounted(() => {
|
|
9
|
-
mounted.value = true;
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
return () => (mounted.value ? slots.default?.() : slots.fallback?.());
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
export type ClientOnlyProps = InstanceType<typeof ClientOnly>["$props"];
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { defineComponent, inject } from "vue";
|
|
2
|
-
|
|
3
|
-
import { HTTP_STATUS_KEY } from "../context";
|
|
4
|
-
|
|
5
|
-
// Module-scope render function — returns null since the component emits no DOM.
|
|
6
|
-
// Hoisted to satisfy `unicorn/consistent-function-scoping` and to avoid
|
|
7
|
-
// re-creating the closure on every component instantiation.
|
|
8
|
-
const renderNull = (): null => null;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Render-time HTTP status declaration. Mount inside a route component (typical
|
|
12
|
-
* use case: a glob `*` route's NotFound page) when the status is decided by
|
|
13
|
-
* the rendered tree rather than a loader.
|
|
14
|
-
*
|
|
15
|
-
* Writes `code` to the nearest `<HttpStatusProvider>`'s sink during `setup()`
|
|
16
|
-
* and renders nothing. With no provider mounted (the standard client-side
|
|
17
|
-
* case) the component is a silent no-op — same component tree hydrates
|
|
18
|
-
* without touching the DOM or warning about mismatches.
|
|
19
|
-
*
|
|
20
|
-
* Loader-driven errors (`LoaderNotFound` → 404, `LoaderRedirect` → 30x) keep
|
|
21
|
-
* working as before; this component covers render-time decisions only.
|
|
22
|
-
*
|
|
23
|
-
* Last write wins when several `<HttpStatusCode />` instances mount in the
|
|
24
|
-
* same render pass — sink reflects the last component that ran.
|
|
25
|
-
*
|
|
26
|
-
* ```vue-html
|
|
27
|
-
* <HttpStatusProvider :sink="sink">
|
|
28
|
-
* <RouterProvider :router="router">
|
|
29
|
-
* <App />
|
|
30
|
-
* </RouterProvider>
|
|
31
|
-
* </HttpStatusProvider>
|
|
32
|
-
*
|
|
33
|
-
* <!-- inside NotFound.vue -->
|
|
34
|
-
* <HttpStatusCode :code="404" />
|
|
35
|
-
* ```
|
|
36
|
-
*
|
|
37
|
-
* **`renderToWebStream` (streaming SSR):** Vue 3 `<Suspense>` is
|
|
38
|
-
* chunked-blocking — Vue waits for every `async setup()` inside a boundary
|
|
39
|
-
* before emitting the chunks past it. So in practice `<HttpStatusCode />`
|
|
40
|
-
* inside a `<Suspense>` boundary still writes to the sink before the
|
|
41
|
-
* response headers flush. Still, prefer mounting in the shell to avoid
|
|
42
|
-
* coupling the contract to that particular Vue 3 streaming behaviour. With
|
|
43
|
-
* `renderToString` there is no ordering concern at all.
|
|
44
|
-
*
|
|
45
|
-
* **Hydration symmetry:** `<HttpStatusProvider>` wraps a render slot, so
|
|
46
|
-
* Vue emits a fragment marker pair (`<!--[-->` / `<!--]-->`) around its
|
|
47
|
-
* children server-side. Mount the same `<HttpStatusProvider>` on the
|
|
48
|
-
* client (with a throwaway sink) to keep the marker count balanced — see
|
|
49
|
-
* the `ssr/` example's `entry-client.ts`. Otherwise hydration logs
|
|
50
|
-
* "Hydration completed but contains mismatches".
|
|
51
|
-
*
|
|
52
|
-
* **Valid `code` range:** Node's `res.end()` throws `Invalid status code`
|
|
53
|
-
* on `NaN`, `0`, negative values, or values `> 999` — this surfaces as a
|
|
54
|
-
* 5xx / dropped connection, not silent corruption. Pass a real HTTP status
|
|
55
|
-
* integer (commonly 4xx/5xx; 100-999 is what Node accepts).
|
|
56
|
-
*/
|
|
57
|
-
export const HttpStatusCode = defineComponent({
|
|
58
|
-
name: "HttpStatusCode",
|
|
59
|
-
props: {
|
|
60
|
-
/** HTTP status to apply to the response. Common values: 404, 410, 451, 503. */
|
|
61
|
-
code: { type: Number, required: true },
|
|
62
|
-
},
|
|
63
|
-
setup(props) {
|
|
64
|
-
const sink = inject(HTTP_STATUS_KEY, null);
|
|
65
|
-
|
|
66
|
-
if (sink) {
|
|
67
|
-
sink.code = props.code;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return renderNull;
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
export type HttpStatusCodeProps = InstanceType<typeof HttpStatusCode>["$props"];
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { defineComponent, provide } from "vue";
|
|
2
|
-
|
|
3
|
-
import { HTTP_STATUS_KEY } from "../context";
|
|
4
|
-
|
|
5
|
-
import type { HttpStatusSink } from "../utils/createHttpStatusSink";
|
|
6
|
-
import type { PropType } from "vue";
|
|
7
|
-
|
|
8
|
-
export const HttpStatusProvider = defineComponent({
|
|
9
|
-
name: "HttpStatusProvider",
|
|
10
|
-
props: {
|
|
11
|
-
sink: { type: Object as PropType<HttpStatusSink>, required: true },
|
|
12
|
-
},
|
|
13
|
-
setup(props, { slots }) {
|
|
14
|
-
provide(HTTP_STATUS_KEY, props.sink);
|
|
15
|
-
|
|
16
|
-
return () => slots.default?.();
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
export type HttpStatusProviderProps = InstanceType<
|
|
21
|
-
typeof HttpStatusProvider
|
|
22
|
-
>["$props"];
|
package/src/components/Link.ts
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { canonicalJson, createActiveRouteSource } from "@real-router/sources";
|
|
2
|
-
import { defineComponent, h, computed, shallowRef, watch } from "vue";
|
|
3
|
-
|
|
4
|
-
import { useRouter } from "../composables/useRouter";
|
|
5
|
-
import { EMPTY_PARAMS, EMPTY_OPTIONS } from "../constants";
|
|
6
|
-
import {
|
|
7
|
-
shouldNavigate,
|
|
8
|
-
buildHref,
|
|
9
|
-
buildActiveClassName,
|
|
10
|
-
navigateWithHash,
|
|
11
|
-
} from "../dom-utils";
|
|
12
|
-
|
|
13
|
-
import type { Params, NavigationOptions } from "@real-router/core";
|
|
14
|
-
import type { PropType } from "vue";
|
|
15
|
-
|
|
16
|
-
type OnClickHandler = (evt: MouseEvent) => void;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Vue's compiled template binds multiple `@click` handlers as an array.
|
|
20
|
-
* Single render-function `onClick` is a function. Both must be invoked.
|
|
21
|
-
*
|
|
22
|
-
* The function-branch deliberately omits a `defaultPrevented` check: the
|
|
23
|
-
* single call short-circuits naturally and control returns to the caller
|
|
24
|
-
* (`handleClick`), which then re-reads `evt.defaultPrevented` on the same
|
|
25
|
-
* MouseEvent. The array-branch needs the per-iteration check because the
|
|
26
|
-
* caller cannot observe intermediate handlers — without it, later handlers
|
|
27
|
-
* would still run after an earlier one called `preventDefault()`.
|
|
28
|
-
*/
|
|
29
|
-
function invokeAttributesOnClick(value: unknown, evt: MouseEvent): void {
|
|
30
|
-
if (typeof value === "function") {
|
|
31
|
-
(value as OnClickHandler)(evt);
|
|
32
|
-
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (Array.isArray(value)) {
|
|
36
|
-
const handlers = value as OnClickHandler[];
|
|
37
|
-
|
|
38
|
-
for (const fn of handlers) {
|
|
39
|
-
if (typeof fn === "function") {
|
|
40
|
-
fn(evt);
|
|
41
|
-
|
|
42
|
-
if (evt.defaultPrevented) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const Link = defineComponent({
|
|
51
|
-
name: "Link",
|
|
52
|
-
// Disable Vue's automatic attribute fallthrough. Without this, attrs.onClick
|
|
53
|
-
// (function OR array) is auto-attached as a native click listener AND our
|
|
54
|
-
// explicit onClick fires too — user handlers are double-invoked. We invoke
|
|
55
|
-
// attrs.onClick manually inside handleClick to preserve preventDefault.
|
|
56
|
-
inheritAttrs: false,
|
|
57
|
-
props: {
|
|
58
|
-
routeName: {
|
|
59
|
-
type: String,
|
|
60
|
-
required: true,
|
|
61
|
-
},
|
|
62
|
-
routeParams: {
|
|
63
|
-
type: Object as PropType<Params>,
|
|
64
|
-
default: () => EMPTY_PARAMS,
|
|
65
|
-
},
|
|
66
|
-
routeOptions: {
|
|
67
|
-
type: Object as PropType<NavigationOptions>,
|
|
68
|
-
default: () => EMPTY_OPTIONS,
|
|
69
|
-
},
|
|
70
|
-
class: {
|
|
71
|
-
type: String,
|
|
72
|
-
default: undefined,
|
|
73
|
-
},
|
|
74
|
-
activeClassName: {
|
|
75
|
-
type: String,
|
|
76
|
-
default: "active",
|
|
77
|
-
},
|
|
78
|
-
activeStrict: {
|
|
79
|
-
type: Boolean,
|
|
80
|
-
default: false,
|
|
81
|
-
},
|
|
82
|
-
ignoreQueryParams: {
|
|
83
|
-
type: Boolean,
|
|
84
|
-
default: true,
|
|
85
|
-
},
|
|
86
|
-
target: {
|
|
87
|
-
type: String,
|
|
88
|
-
default: undefined,
|
|
89
|
-
},
|
|
90
|
-
/**
|
|
91
|
-
* URL fragment (decoded form, no leading "#") (#532).
|
|
92
|
-
* - omitted/`undefined` → preserve current fragment on same-route navigation
|
|
93
|
-
* - `""` → clear fragment
|
|
94
|
-
* - non-empty → set fragment
|
|
95
|
-
*/
|
|
96
|
-
hash: {
|
|
97
|
-
type: String,
|
|
98
|
-
default: undefined,
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
setup(props, { slots, attrs }) {
|
|
102
|
-
const router = useRouter();
|
|
103
|
-
|
|
104
|
-
const isActive = shallowRef(false);
|
|
105
|
-
|
|
106
|
-
// watch with an explicit dep getter recreates the source ONLY when the
|
|
107
|
-
// structural identity of routeName/routeParams/strict/ignoreQueryParams/
|
|
108
|
-
// hash changes — not on every parent rerender that hands a fresh
|
|
109
|
-
// `routeParams` literal with the same shape.
|
|
110
|
-
//
|
|
111
|
-
// Hot-path note: inline `:routeParams="{ id: 1 }"` in a parent template
|
|
112
|
-
// allocates a new object each render. Comparing by reference would
|
|
113
|
-
// tear down + recreate the ActiveRouteSource subscription on every
|
|
114
|
-
// unrelated parent state change. `canonicalJson(routeParams)` collapses
|
|
115
|
-
// structurally-equal objects to the same key-order-stable string, so the
|
|
116
|
-
// subscription persists across re-renders that don't change shape.
|
|
117
|
-
// (The source's own per-router cache uses the same canonical key under
|
|
118
|
-
// the hood — this watch dep just mirrors it at the consumer layer.)
|
|
119
|
-
watch(
|
|
120
|
-
() =>
|
|
121
|
-
[
|
|
122
|
-
props.routeName,
|
|
123
|
-
canonicalJson(props.routeParams),
|
|
124
|
-
props.activeStrict,
|
|
125
|
-
props.ignoreQueryParams,
|
|
126
|
-
props.hash,
|
|
127
|
-
] as const,
|
|
128
|
-
(
|
|
129
|
-
[routeName, _paramsKey, activeStrict, ignoreQueryParams, hash],
|
|
130
|
-
_prev,
|
|
131
|
-
onCleanup,
|
|
132
|
-
) => {
|
|
133
|
-
// Re-read the raw `routeParams` ref when constructing the source —
|
|
134
|
-
// canonicalJson was only used for change-detection above, the source
|
|
135
|
-
// factory still wants the live object.
|
|
136
|
-
const routeParams = props.routeParams;
|
|
137
|
-
// Hash-aware active (#532): pass hash through so tab links with the
|
|
138
|
-
// same routeName but different `hash` props don't all light up.
|
|
139
|
-
const source = createActiveRouteSource(
|
|
140
|
-
router,
|
|
141
|
-
routeName,
|
|
142
|
-
routeParams,
|
|
143
|
-
hash === undefined
|
|
144
|
-
? { strict: activeStrict, ignoreQueryParams }
|
|
145
|
-
: { strict: activeStrict, ignoreQueryParams, hash },
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
isActive.value = source.getSnapshot();
|
|
149
|
-
|
|
150
|
-
const unsub = source.subscribe(() => {
|
|
151
|
-
isActive.value = source.getSnapshot();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
onCleanup(unsub);
|
|
155
|
-
},
|
|
156
|
-
{ immediate: true, flush: "sync" },
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
const href = computed(() =>
|
|
160
|
-
buildHref(
|
|
161
|
-
router,
|
|
162
|
-
props.routeName,
|
|
163
|
-
props.routeParams,
|
|
164
|
-
props.hash === undefined ? undefined : { hash: props.hash },
|
|
165
|
-
),
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
const finalClassName = computed(() =>
|
|
169
|
-
buildActiveClassName(isActive.value, props.activeClassName, props.class),
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
const handleClick = (evt: MouseEvent) => {
|
|
173
|
-
// Vue allows attrs.onClick to be a function or an array of functions
|
|
174
|
-
// (compiled templates with multiple @click bindings produce arrays).
|
|
175
|
-
// Both must be invoked; treating arrays as "no handler" silently drops
|
|
176
|
-
// user code.
|
|
177
|
-
if (attrs.onClick !== undefined && attrs.onClick !== null) {
|
|
178
|
-
invokeAttributesOnClick(attrs.onClick, evt);
|
|
179
|
-
|
|
180
|
-
if (evt.defaultPrevented) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (!shouldNavigate(evt) || props.target === "_blank") {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
evt.preventDefault();
|
|
190
|
-
navigateWithHash(
|
|
191
|
-
router,
|
|
192
|
-
props.routeName,
|
|
193
|
-
props.routeParams,
|
|
194
|
-
props.hash,
|
|
195
|
-
props.routeOptions,
|
|
196
|
-
).catch(() => {});
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
return () => {
|
|
200
|
-
// Build forwarded attrs without `onClick`. Vue's runtime auto-attaches
|
|
201
|
-
// attrs.onClick (function OR array) as a native DOM listener, which would
|
|
202
|
-
// double-invoke user handlers when combined with our explicit `onClick`.
|
|
203
|
-
// We invoke the original attrs.onClick manually inside handleClick so the
|
|
204
|
-
// preventDefault contract is preserved.
|
|
205
|
-
//
|
|
206
|
-
// Spread + delete avoids the per-key copy loop on every render — one
|
|
207
|
-
// allocation + one property deletion instead of N iterations across
|
|
208
|
-
// data-*, aria-*, role, etc. Hot-path optimisation for Link-heavy pages.
|
|
209
|
-
const restAttributes = { ...attrs };
|
|
210
|
-
|
|
211
|
-
delete restAttributes.onClick;
|
|
212
|
-
|
|
213
|
-
return h(
|
|
214
|
-
"a",
|
|
215
|
-
{
|
|
216
|
-
...restAttributes,
|
|
217
|
-
href: href.value,
|
|
218
|
-
class: finalClassName.value,
|
|
219
|
-
target: props.target,
|
|
220
|
-
onClick: handleClick,
|
|
221
|
-
},
|
|
222
|
-
slots.default?.(),
|
|
223
|
-
);
|
|
224
|
-
};
|
|
225
|
-
},
|
|
226
|
-
});
|