@absolutejs/absolute 0.20.0-beta.169 → 0.20.0-beta.170
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/CHANGELOG.md +80 -0
- package/README.md +232 -0
- package/changelog.json +228 -0
- package/dist/angular/browser.js +7 -8
- package/dist/angular/browser.js.map +6 -5
- package/dist/angular/components/core/streamingSlotRegistrar.js +6 -2
- package/dist/angular/components/core/streamingSlotRegistry.js +7 -3
- package/dist/angular/index.js +555 -156
- package/dist/angular/index.js.map +29 -23
- package/dist/angular/server.js +555 -156
- package/dist/angular/server.js.map +29 -23
- package/dist/build/buildWorker.js +14 -8
- package/dist/build.js +4949 -1941
- package/dist/build.js.map +72 -42
- package/dist/cli/{add-7azr0c83.js → add-6a7pjkn9.js} +11 -7
- package/dist/cli/{analyze-3cyek6v3.js → analyze-606fshwa.js} +1 -1
- package/dist/cli/{build-25rz8v25.js → build-g81xqbb9.js} +1 -1
- package/dist/cli/{compile-4jnfxts9.js → compile-9y1w9zpj.js} +3 -3
- package/dist/cli/config/server.js +10699 -1538
- package/dist/cli/{dev-9tk5hn2e.js → dev-hyeb89hp.js} +5 -1
- package/dist/cli/{doctor-4nv2c2j3.js → doctor-h55y9hj1.js} +1 -1
- package/dist/cli/{generate-v75p2fct.js → generate-20x1pkf7.js} +4 -4
- package/dist/cli/{htmx-427swtdz.js → htmx-6mz2h7k1.js} +2 -2
- package/dist/cli/{index-bhbs6d5z.js → index-32ge2w1f.js} +12 -4
- package/dist/cli/{index-a4ws2jwd.js → index-3wz3mktm.js} +1 -1
- package/dist/cli/{index-b7hjgknf.js → index-9sksd8c8.js} +13 -4
- package/dist/cli/{index-eyzwc7gz.js → index-qbrzvmh9.js} +4 -2
- package/dist/cli/index-qw98g1p6.js +269 -0
- package/dist/cli/{index-434b7j4a.js → index-vtp20gwf.js} +69 -1
- package/dist/cli/{index-h7qhz02e.js → index-wk0pyxrg.js} +3 -0
- package/dist/cli/{index-k3y8cb6c.js → index-z6f43b20.js} +1 -1
- package/dist/cli/index.js +16 -16
- package/dist/cli/{islands-8sgky9gh.js → islands-dc54e5y1.js} +1 -1
- package/dist/cli/{ls-4m4yqwww.js → ls-b6zz24nt.js} +1 -1
- package/dist/cli/{mobile-hcxjr2gf.js → mobile-ajnnvqtv.js} +3 -3
- package/dist/cli/prerender-fqpp0m1j.js +10 -0
- package/dist/cli/{remove-1ycjx85k.js → remove-t8x1mrxg.js} +4 -4
- package/dist/cli/{start-mbaejxhm.js → start-d6p831ny.js} +3 -3
- package/dist/cli/{typecheck-dr068pmh.js → typecheck-stbt0qt5.js} +6 -1
- package/dist/cli/{workspace-1sycp9g1.js → workspace-61hh6jq1.js} +1 -1
- package/dist/client/chunks/index-6b66nzyf.js +381 -0
- package/dist/client/chunks/index-6b66nzyf.js.map +11 -0
- package/dist/client/chunks/{islands-rc28k5sx.js → islands-s2ta5ajb.js} +5 -2
- package/dist/client/chunks/{islands-rc28k5sx.js.map → islands-s2ta5ajb.js.map} +7 -6
- package/dist/client/index.js +160 -3
- package/dist/client/index.js.map +6 -4
- package/dist/client/navigation.js +764 -0
- package/dist/client/navigation.js.map +10 -0
- package/dist/client/prefetch.js +33 -375
- package/dist/client/prefetch.js.map +3 -4
- package/dist/core/streamingSlotRegistrar.js +5 -2
- package/dist/core/streamingSlotRegistrar.js.map +5 -4
- package/dist/core/streamingSlotRegistry.js +5 -2
- package/dist/core/streamingSlotRegistry.js.map +5 -4
- package/dist/dev/client/cspNonce.ts +28 -0
- package/dist/dev/client/handlers/html.ts +4 -3
- package/dist/dev/client/handlers/htmx.ts +3 -2
- package/dist/dev/client/handlers/svelte.ts +15 -90
- package/dist/dev/client/syncDevtools.ts +2 -1
- package/dist/dev/serverBootstrap.js +11 -4
- package/dist/i18n/index.js +543 -0
- package/dist/i18n/index.js.map +15 -0
- package/dist/index.js +6339 -2233
- package/dist/index.js.map +89 -54
- package/dist/islands/index.js +282 -40
- package/dist/islands/index.js.map +14 -10
- package/dist/mobile/browser.js +7 -2
- package/dist/mobile/browser.js.map +5 -4
- package/dist/mobile/index.js +7 -2
- package/dist/mobile/index.js.map +5 -4
- package/dist/mobile/remoteMacAgentEntry.js +1 -1
- package/dist/react/components/browser/index.js +94 -4
- package/dist/react/components/index.js +150 -10
- package/dist/react/components/index.js.map +15 -10
- package/dist/react/hooks/index.js +479 -13
- package/dist/react/hooks/index.js.map +12 -4
- package/dist/react/index.js +769 -166
- package/dist/react/index.js.map +32 -21
- package/dist/react/router/browser.js +21 -4
- package/dist/react/router/browser.js.map +5 -4
- package/dist/react/router/index.js +21 -4
- package/dist/react/router/index.js.map +5 -4
- package/dist/react/server.js +597 -203
- package/dist/react/server.js.map +25 -15
- package/dist/src/angular/zoneRuntime.d.ts +5 -0
- package/dist/src/build/buildReactServerComponents.d.ts +22 -0
- package/dist/src/build/downlevelAsyncForZone.d.ts +1 -0
- package/dist/src/build/fileRoutes.d.ts +65 -0
- package/dist/src/build/fonts/buildFonts.d.ts +25 -0
- package/dist/src/build/fonts/fallbackMetrics.d.ts +13 -0
- package/dist/src/build/fonts/googleFonts.d.ts +18 -0
- package/dist/src/build/fonts/injectFontTags.d.ts +7 -0
- package/dist/src/build/fonts/parseFontMetrics.d.ts +4 -0
- package/dist/src/build/generateReactIndexes.d.ts +7 -1
- package/dist/src/build/htmlIncludes.d.ts +16 -0
- package/dist/src/build/inlineSourceMapComment.d.ts +1 -0
- package/dist/src/build/scanConventions.d.ts +3 -0
- package/dist/src/build/vitals.d.ts +21 -0
- package/dist/src/client/formActions.d.ts +29 -0
- package/dist/src/client/index.d.ts +2 -0
- package/dist/src/client/navigation.d.ts +16 -0
- package/dist/src/client/prefetch.d.ts +2 -30
- package/dist/src/core/build.d.ts +2 -2
- package/dist/src/core/cspNonce.d.ts +12 -0
- package/dist/src/core/fileRouteAdapter.d.ts +15 -0
- package/dist/src/core/fileRoutes.d.ts +5 -0
- package/dist/src/core/formActionFlash.d.ts +25 -0
- package/dist/src/core/formActions.d.ts +249 -0
- package/dist/src/core/index.d.ts +5 -0
- package/dist/src/core/partialPrerender.d.ts +28 -0
- package/dist/src/core/partialPrerenderServe.d.ts +24 -0
- package/dist/src/core/precompressed.d.ts +11 -1
- package/dist/src/core/prerenderNonce.d.ts +9 -0
- package/dist/src/core/productionRuntime.d.ts +4 -2
- package/dist/src/core/serverOnlyStaticFiles.d.ts +8 -0
- package/dist/src/core/staticHtmlNonce.d.ts +2 -0
- package/dist/src/core/vitals.d.ts +108 -0
- package/dist/src/dev/angular/fastHmrCompiler.d.ts +1 -1
- package/dist/src/dev/client/cspNonce.d.ts +2 -0
- package/dist/src/dev/fileRouteRefresher.d.ts +5 -0
- package/dist/src/dev/importCost/instrument.d.ts +1 -1
- package/dist/src/dev/reactServerComponentsRebuild.d.ts +3 -0
- package/dist/src/i18n/defineMessages.d.ts +5 -0
- package/dist/src/i18n/formatMessage.d.ts +26 -0
- package/dist/src/i18n/index.d.ts +6 -0
- package/dist/src/i18n/intlCache.d.ts +5 -0
- package/dist/src/i18n/routing.d.ts +28 -0
- package/dist/src/i18n/translator.d.ts +11 -0
- package/dist/src/mobile/androidRelease.d.ts +2 -2
- package/dist/src/mobile/iosRelease.d.ts +4 -4
- package/dist/src/mobile/materializedBundle.d.ts +1 -1
- package/dist/src/mobile/mobileInspect.d.ts +1 -1
- package/dist/src/mobile/releaseArtifact.d.ts +2 -2
- package/dist/src/mobile/remoteMacProtocol.d.ts +2 -2
- package/dist/src/plugins/i18n.d.ts +30 -0
- package/dist/src/plugins/index.d.ts +2 -1
- package/dist/src/plugins/secureHeaders.d.ts +48 -0
- package/dist/src/react/components/Head.d.ts +1 -1
- package/dist/src/react/components/StreamSlot.browser.d.ts +1 -0
- package/dist/src/react/components/StreamSlot.d.ts +5 -1
- package/dist/src/react/components/SuspenseSlot.browser.d.ts +2 -0
- package/dist/src/react/components/SuspenseSlot.d.ts +12 -0
- package/dist/src/react/fileRoute.d.ts +18 -0
- package/dist/src/react/hooks/index.d.ts +4 -0
- package/dist/src/react/hooks/useFormAction.d.ts +42 -0
- package/dist/src/react/hooks/useI18n.d.ts +15 -0
- package/dist/src/react/index.d.ts +2 -0
- package/dist/src/react/server.d.ts +2 -0
- package/dist/src/react/serverComponents/constants.d.ts +11 -0
- package/dist/src/react/serverComponents/flightPayload.d.ts +2 -0
- package/dist/src/react/serverComponents/pageHandler.d.ts +14 -0
- package/dist/src/svelte/browser.d.ts +1 -0
- package/dist/src/svelte/fileRoute.d.ts +15 -0
- package/dist/src/svelte/i18n.d.ts +8 -0
- package/dist/src/svelte/index.d.ts +2 -0
- package/dist/src/svelte/lowerAwaitSlotSyntax.d.ts +5 -1
- package/dist/src/svelte/renderToReadableStream.d.ts +10 -2
- package/dist/src/svelte/router/browser.d.ts +3 -1
- package/dist/src/svelte/router/goto.d.ts +13 -10
- package/dist/src/svelte/router/guards.d.ts +43 -0
- package/dist/src/svelte/router/index.d.ts +3 -1
- package/dist/src/svelte/router/lazy.d.ts +17 -0
- package/dist/src/svelte/router/lazyModule.d.ts +29 -0
- package/dist/src/svelte/router/navigation.d.ts +55 -0
- package/dist/src/svelte/router/pushState.d.ts +4 -2
- package/dist/src/svelte/router/scroll.d.ts +34 -0
- package/dist/src/svelte/routerSsr.d.ts +9 -0
- package/dist/src/svelte/server.d.ts +1 -0
- package/dist/src/utils/cspNonceRef.d.ts +8 -0
- package/dist/src/utils/fontAssets.d.ts +17 -0
- package/dist/src/utils/formActionProtocol.d.ts +10 -0
- package/dist/src/utils/generateHeadElement.d.ts +1 -1
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/inlinePageCss.d.ts +18 -0
- package/dist/src/utils/loadConfig.d.ts +5 -0
- package/dist/src/utils/localeRef.d.ts +10 -0
- package/dist/src/utils/resolveConvention.d.ts +6 -1
- package/dist/src/utils/streamingSlots.d.ts +5 -0
- package/dist/src/vue/browser.d.ts +2 -0
- package/dist/src/vue/components/Image.d.ts +2 -2
- package/dist/src/vue/index.d.ts +2 -0
- package/dist/src/vue/serverDataPayload.d.ts +32 -0
- package/dist/src/vue/serverDataState.d.ts +9 -0
- package/dist/src/vue/useServerData.browser.d.ts +6 -0
- package/dist/src/vue/useServerData.d.ts +24 -0
- package/dist/svelte/browser.js +330 -3
- package/dist/svelte/browser.js.map +8 -4
- package/dist/svelte/components/AwaitSlot.svelte +5 -0
- package/dist/svelte/components/Head.svelte +50 -0
- package/dist/svelte/components/JsonLd.svelte +9 -1
- package/dist/svelte/components/StreamSlot.svelte +5 -0
- package/dist/svelte/index.js +1017 -117
- package/dist/svelte/index.js.map +38 -24
- package/dist/svelte/router/Link.svelte +40 -8
- package/dist/svelte/router/Route.svelte +157 -12
- package/dist/svelte/router/Route.svelte.d.ts +39 -5
- package/dist/svelte/router/Router.svelte +140 -25
- package/dist/svelte/router/Router.svelte.d.ts +5 -1
- package/dist/svelte/router/browser.ts +10 -0
- package/dist/svelte/router/goto.ts +15 -86
- package/dist/svelte/router/guards.ts +112 -0
- package/dist/svelte/router/index.ts +10 -0
- package/dist/svelte/router/lazy.ts +82 -0
- package/dist/svelte/router/lazyModule.ts +74 -0
- package/dist/svelte/router/navigation.ts +412 -0
- package/dist/svelte/router/pushState.ts +7 -4
- package/dist/svelte/router/scroll.ts +286 -0
- package/dist/svelte/server.js +747 -252
- package/dist/svelte/server.js.map +30 -20
- package/dist/types/angular.d.ts +2 -0
- package/dist/types/build.d.ts +37 -0
- package/dist/types/conventions.d.ts +3 -0
- package/dist/types/fileRoutes.d.ts +72 -0
- package/dist/types/fonts.d.ts +86 -0
- package/dist/types/formAction.d.ts +54 -0
- package/dist/types/globals.d.ts +10 -0
- package/dist/types/i18n.d.ts +185 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/metadata.d.ts +11 -0
- package/dist/types/navigation.d.ts +37 -0
- package/dist/types/prefetch.d.ts +10 -0
- package/dist/types/sitemap.d.ts +4 -0
- package/dist/types/svelteRouter.d.ts +89 -2
- package/dist/types/svelteRouter.ts +112 -2
- package/dist/types/vue.d.ts +46 -1
- package/dist/utils/iconVersion.ts +34 -0
- package/dist/vue/browser.js +107 -11
- package/dist/vue/browser.js.map +7 -4
- package/dist/vue/components/index.js +5 -2
- package/dist/vue/components/index.js.map +5 -4
- package/dist/vue/index.js +712 -158
- package/dist/vue/index.js.map +32 -23
- package/dist/vue/server.js +310 -79
- package/dist/vue/server.js.map +20 -15
- package/package.json +9 -1
- package/dist/cli/prerender-mfnwsvyt.js +0 -7
- package/dist/src/plugins/pageRouter.d.ts +0 -1
- /package/dist/cli/{index-j7m8pq48.js → index-rervww4p.js} +0 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { onDestroy, tick } from 'svelte';
|
|
2
|
+
import type {
|
|
3
|
+
GotoOptions,
|
|
4
|
+
NavigationOutcome,
|
|
5
|
+
NavigationType,
|
|
6
|
+
RouteGuard,
|
|
7
|
+
RouterMode,
|
|
8
|
+
ScrollPosition
|
|
9
|
+
} from '../../types/svelteRouter';
|
|
10
|
+
import { type GuardDecision, type GuardEntry, runGuardChain } from './guards';
|
|
11
|
+
import { buildHashHref } from './hashMode';
|
|
12
|
+
import { hasPendingLazyLoads, whenLazySettled } from './lazy';
|
|
13
|
+
import { page, setPage } from './page.js';
|
|
14
|
+
import {
|
|
15
|
+
adoptHistoryEntry,
|
|
16
|
+
currentCommittedHref,
|
|
17
|
+
currentHistoryEntry,
|
|
18
|
+
readHistoryMeta,
|
|
19
|
+
readUserState,
|
|
20
|
+
rememberScroll,
|
|
21
|
+
savedScrollFor,
|
|
22
|
+
scrollToHash,
|
|
23
|
+
scrollToPosition,
|
|
24
|
+
writeHistoryEntry
|
|
25
|
+
} from './scroll';
|
|
26
|
+
import { withViewTransition } from './viewTransitions';
|
|
27
|
+
|
|
28
|
+
/* The browser navigation path shared by `goto()`, `<Link>` and the
|
|
29
|
+
* outermost `<Router>`'s popstate listener:
|
|
30
|
+
*
|
|
31
|
+
* guards → history write → page swap (in a View Transition) → scroll
|
|
32
|
+
*
|
|
33
|
+
* Each navigation takes a token; a guard that resolves after a newer
|
|
34
|
+
* navigation started is ignored ("superseded"). */
|
|
35
|
+
|
|
36
|
+
export type RouterNavigationHandle = {
|
|
37
|
+
/** Guards that apply when navigating to `url`, outermost first. */
|
|
38
|
+
guardsFor: (url: URL) => GuardEntry[];
|
|
39
|
+
/** Whether one of this router's routes matches `url`. */
|
|
40
|
+
matches: (url: URL) => boolean;
|
|
41
|
+
/** Start loading the lazy route `url` would render, if any. */
|
|
42
|
+
preload: (url: URL) => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type ScrollIntent =
|
|
46
|
+
| { kind: 'none' }
|
|
47
|
+
| { kind: 'top' }
|
|
48
|
+
| { kind: 'hash'; hash: string }
|
|
49
|
+
| { kind: 'restore'; position: ScrollPosition };
|
|
50
|
+
|
|
51
|
+
const ALLOW: GuardDecision = { kind: 'allow' };
|
|
52
|
+
const MAX_REDIRECTS = 10;
|
|
53
|
+
const routers = new Set<RouterNavigationHandle>();
|
|
54
|
+
const globalGuards = new Set<RouteGuard>();
|
|
55
|
+
let activeMode: RouterMode = 'history';
|
|
56
|
+
let navigationToken = 0;
|
|
57
|
+
let ignoredPopStates = 0;
|
|
58
|
+
|
|
59
|
+
/** @internal Router.svelte: make a router's guards and lazy routes
|
|
60
|
+
* visible to navigation. Returns the deregistration. */
|
|
61
|
+
export const registerRouter = (handle: RouterNavigationHandle) => {
|
|
62
|
+
routers.add(handle);
|
|
63
|
+
|
|
64
|
+
return () => {
|
|
65
|
+
routers.delete(handle);
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** @internal Router.svelte: which URL strategy goto() writes. */
|
|
70
|
+
export const setRouterMode = (mode: RouterMode) => {
|
|
71
|
+
activeMode = mode;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const unregisterGuard = (guard: RouteGuard) => () => {
|
|
75
|
+
globalGuards.delete(guard);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run `guard` before every client-side navigation (`<Link>`, `goto()`,
|
|
80
|
+
* back/forward). Return `false` to cancel, a URL (or `redirect(...)`) to
|
|
81
|
+
* redirect, nothing to allow. Called during component initialisation it
|
|
82
|
+
* is removed when the component is destroyed; otherwise call the returned
|
|
83
|
+
* function to remove it.
|
|
84
|
+
*
|
|
85
|
+
* Browser-only: it doesn't run during the server render. Put checks the
|
|
86
|
+
* server must enforce (auth, a 302 on the first request) on
|
|
87
|
+
* `<Router guard>` or `<Route guard>` instead.
|
|
88
|
+
*/
|
|
89
|
+
export const beforeNavigate = (guard: RouteGuard) => {
|
|
90
|
+
const remove = unregisterGuard(guard);
|
|
91
|
+
if (typeof window === 'undefined') return remove;
|
|
92
|
+
globalGuards.add(guard);
|
|
93
|
+
try {
|
|
94
|
+
onDestroy(remove);
|
|
95
|
+
} catch {
|
|
96
|
+
// Called outside component initialisation: the caller owns removal.
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return remove;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const collectGuards = (url: URL) => {
|
|
103
|
+
const entries: GuardEntry[] = Array.from(globalGuards, (guard) => ({
|
|
104
|
+
guard,
|
|
105
|
+
params: {}
|
|
106
|
+
}));
|
|
107
|
+
for (const router of routers) entries.push(...router.guardsFor(url));
|
|
108
|
+
|
|
109
|
+
return entries;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Start loading the lazy route component `href` would render, through
|
|
113
|
+
* every mounted router. Used by `<Link>` prefetching. */
|
|
114
|
+
export const preloadRoute = (href: string) => {
|
|
115
|
+
if (typeof window === 'undefined') return;
|
|
116
|
+
const url = new URL(href, window.location.href);
|
|
117
|
+
if (url.origin !== window.location.origin) return;
|
|
118
|
+
for (const router of routers) router.preload(url);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/* Set by `enableNavigation()` (src/client/navigation.ts) on globalThis, so
|
|
122
|
+
* the check works whichever bundle copy installed it. */
|
|
123
|
+
const DOCUMENT_NAVIGATION_KEY = Symbol.for('absolutejs.navigation');
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @internal Link.svelte: whether a click on `href` belongs to cross-framework
|
|
127
|
+
* document navigation rather than this router. True only when
|
|
128
|
+
* `enableNavigation()` is active, the router uses history mode, and no
|
|
129
|
+
* mounted `<Router>` has a route for the URL: the link points at another
|
|
130
|
+
* page (possibly another framework's), so `<Link>` leaves the click to the
|
|
131
|
+
* browser and the navigation layer swaps that page in.
|
|
132
|
+
*/
|
|
133
|
+
export const belongsToDocumentNavigation = (href: string) => {
|
|
134
|
+
if (typeof window === 'undefined' || activeMode === 'hash') return false;
|
|
135
|
+
if (Reflect.get(globalThis, DOCUMENT_NAVIGATION_KEY) === undefined) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const url = new URL(href, window.location.href);
|
|
139
|
+
if (url.origin !== window.location.origin) return false;
|
|
140
|
+
|
|
141
|
+
return !Array.from(routers).some((router) => router.matches(url));
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const resolveAbsoluteUrl = (target: string) => {
|
|
145
|
+
if (typeof window === 'undefined') {
|
|
146
|
+
// Programmatic goto on the server is rare but we tolerate it as a
|
|
147
|
+
// way for tests to drive the router without a real DOM.
|
|
148
|
+
return new URL(target, 'http://localhost/');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return new URL(target, window.location.href);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const buildHref = (target: URL) =>
|
|
155
|
+
activeMode === 'hash'
|
|
156
|
+
? `${window.location.pathname}${window.location.search}${buildHashHref(target.pathname + target.search)}`
|
|
157
|
+
: `${target.pathname}${target.search}${target.hash}`;
|
|
158
|
+
|
|
159
|
+
/** Same document, same path and query — only the fragment differs. No
|
|
160
|
+
* route changes, so guards don't run (same as shallow routing). */
|
|
161
|
+
const isFragmentOnlyChange = (from: URL, target: URL) =>
|
|
162
|
+
activeMode === 'history' &&
|
|
163
|
+
from.origin === target.origin &&
|
|
164
|
+
from.pathname === target.pathname &&
|
|
165
|
+
from.search === target.search;
|
|
166
|
+
|
|
167
|
+
const applyScrollIntent = (intent: ScrollIntent) => {
|
|
168
|
+
if (intent.kind === 'none') return;
|
|
169
|
+
if (intent.kind === 'restore') {
|
|
170
|
+
scrollToPosition(intent.position);
|
|
171
|
+
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (intent.kind === 'hash' && scrollToHash(intent.hash)) return;
|
|
175
|
+
window.scrollTo({ left: 0, top: 0 });
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const nextFrame = () =>
|
|
179
|
+
new Promise<void>((resolve) => {
|
|
180
|
+
window.requestAnimationFrame(() => resolve());
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/** A lazy route that was still loading when the scroll was applied may
|
|
184
|
+
* have grown the page since; apply the intent once more when it lands,
|
|
185
|
+
* unless the user scrolled in the meantime. */
|
|
186
|
+
const reapplyAfterLazyLoads = async (intent: ScrollIntent) => {
|
|
187
|
+
if (intent.kind === 'none' || intent.kind === 'top') return;
|
|
188
|
+
if (!hasPendingLazyLoads()) return;
|
|
189
|
+
const appliedY = window.scrollY;
|
|
190
|
+
await whenLazySettled();
|
|
191
|
+
await tick();
|
|
192
|
+
await nextFrame();
|
|
193
|
+
if (window.scrollY !== appliedY) return;
|
|
194
|
+
applyScrollIntent(intent);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const blurActiveElement = (options: GotoOptions) => {
|
|
198
|
+
if (options.keepFocus) return;
|
|
199
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
200
|
+
document.activeElement.blur();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const pushIntent = (url: URL, options: GotoOptions): ScrollIntent => {
|
|
205
|
+
if (options.noScroll) return { kind: 'none' };
|
|
206
|
+
if (activeMode === 'history' && url.hash !== '') {
|
|
207
|
+
return { hash: url.hash, kind: 'hash' };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return { kind: 'top' };
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const commitNavigation = async (url: URL, options: GotoOptions) => {
|
|
214
|
+
const intent = pushIntent(url, options);
|
|
215
|
+
const mutate = async () => {
|
|
216
|
+
writeHistoryEntry(
|
|
217
|
+
buildHref(url),
|
|
218
|
+
options.state ?? null,
|
|
219
|
+
options.replaceState ? 'replace' : 'push'
|
|
220
|
+
);
|
|
221
|
+
setPage({ params: {}, state: options.state ?? null, url });
|
|
222
|
+
blurActiveElement(options);
|
|
223
|
+
await tick();
|
|
224
|
+
applyScrollIntent(intent);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
await withViewTransition(mutate);
|
|
228
|
+
await reapplyAfterLazyLoads(intent);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/** Run every applicable guard for a navigation from the current page to
|
|
232
|
+
* `url`, as navigation number `token`. */
|
|
233
|
+
const decide = (url: URL, type: NavigationType, token: number) => {
|
|
234
|
+
const from = new URL(page.url.href);
|
|
235
|
+
if (isFragmentOnlyChange(from, url)) return Promise.resolve(ALLOW);
|
|
236
|
+
|
|
237
|
+
return runGuardChain(
|
|
238
|
+
collectGuards(url),
|
|
239
|
+
{ from, request: undefined, to: url, type },
|
|
240
|
+
() => token !== navigationToken
|
|
241
|
+
);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const startNavigation = () => {
|
|
245
|
+
navigationToken += 1;
|
|
246
|
+
|
|
247
|
+
return navigationToken;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const assertRedirectDepth = (depth: number, location: string) => {
|
|
251
|
+
if (depth < MAX_REDIRECTS) return;
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Svelte router: more than ${MAX_REDIRECTS} guard redirects in a row (last to ${location}).`
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const navigateWithin = async (
|
|
258
|
+
target: string,
|
|
259
|
+
options: GotoOptions,
|
|
260
|
+
type: NavigationType,
|
|
261
|
+
depth: number
|
|
262
|
+
): Promise<NavigationOutcome> => {
|
|
263
|
+
const url = resolveAbsoluteUrl(target);
|
|
264
|
+
|
|
265
|
+
if (typeof window === 'undefined') {
|
|
266
|
+
setPage({ params: {}, state: options.state ?? null, url });
|
|
267
|
+
|
|
268
|
+
return 'committed';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (url.origin !== window.location.origin) {
|
|
272
|
+
// External URLs go through the browser — we don't try to SPA them.
|
|
273
|
+
window.location.href = url.href;
|
|
274
|
+
|
|
275
|
+
return 'external';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const decision = await decide(url, type, startNavigation());
|
|
279
|
+
if (decision.kind === 'stale') return 'superseded';
|
|
280
|
+
if (decision.kind === 'cancel') return 'cancelled';
|
|
281
|
+
if (decision.kind === 'redirect') {
|
|
282
|
+
assertRedirectDepth(depth, decision.location);
|
|
283
|
+
await navigateWithin(
|
|
284
|
+
decision.location,
|
|
285
|
+
{ replaceState: decision.replace || options.replaceState },
|
|
286
|
+
type,
|
|
287
|
+
depth + 1
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
return 'redirected';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// `goto` performs no network fetch of its own: the swap is a client-side
|
|
294
|
+
// route match against the registered <Route>s. What `<Link prefetch>`
|
|
295
|
+
// buys the click is already in place by now — the target's HTML in the
|
|
296
|
+
// HTTP cache, its modules preloaded and, for a lazy route, its
|
|
297
|
+
// component chunk loaded.
|
|
298
|
+
await commitNavigation(url, options);
|
|
299
|
+
|
|
300
|
+
return 'committed';
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/** @internal `goto()` and `<Link>`: guarded client-side navigation. */
|
|
304
|
+
export const navigate = (
|
|
305
|
+
target: string,
|
|
306
|
+
options: GotoOptions,
|
|
307
|
+
type: NavigationType
|
|
308
|
+
) => navigateWithin(target, options, type, 0);
|
|
309
|
+
|
|
310
|
+
/** Put the URL bar back after a cancelled back/forward navigation: undo
|
|
311
|
+
* the traversal with `history.go`, or — when the entry being left is
|
|
312
|
+
* unknown — push the previous URL again. */
|
|
313
|
+
const undoTraversal = (state: unknown, previousHref: string | undefined) => {
|
|
314
|
+
const landed = readHistoryMeta(state);
|
|
315
|
+
const left = currentHistoryEntry();
|
|
316
|
+
const delta = landed && left ? landed.index - left.index : 0;
|
|
317
|
+
if (delta !== 0) {
|
|
318
|
+
ignoredPopStates += 1;
|
|
319
|
+
window.history.go(-delta);
|
|
320
|
+
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (previousHref === undefined) return;
|
|
324
|
+
window.history.pushState(window.history.state, '', previousHref);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const restoreIntent = (state: unknown, url: URL): ScrollIntent => {
|
|
328
|
+
const meta = adoptHistoryEntry(state);
|
|
329
|
+
const saved = savedScrollFor(meta);
|
|
330
|
+
if (saved) return { kind: 'restore', position: saved };
|
|
331
|
+
if (activeMode === 'history' && url.hash !== '') {
|
|
332
|
+
return { hash: url.hash, kind: 'hash' };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return { kind: 'top' };
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const commitTraversal = async (state: unknown, url: URL) => {
|
|
339
|
+
const fragmentOnly = isFragmentOnlyChange(page.url, url);
|
|
340
|
+
const intent = restoreIntent(state, url);
|
|
341
|
+
const mutate = async () => {
|
|
342
|
+
setPage({ params: {}, state: readUserState(state) ?? null, url });
|
|
343
|
+
await tick();
|
|
344
|
+
applyScrollIntent(intent);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
if (fragmentOnly) await mutate();
|
|
348
|
+
else await withViewTransition(mutate);
|
|
349
|
+
await reapplyAfterLazyLoads(intent);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/** @internal Router.svelte (hash mode): guards for the first client
|
|
353
|
+
* render, which the server could not run because it never sees the
|
|
354
|
+
* hash. A redirect replaces the entry; a cancel is ignored (there is no
|
|
355
|
+
* previous page to stay on). */
|
|
356
|
+
export const enterInitialRoute = async (url: URL) => {
|
|
357
|
+
const token = startNavigation();
|
|
358
|
+
const decision = await runGuardChain(
|
|
359
|
+
Array.from(routers, (router) => router.guardsFor(url)).flat(),
|
|
360
|
+
{ from: null, request: undefined, to: url, type: 'enter' },
|
|
361
|
+
() => token !== navigationToken
|
|
362
|
+
);
|
|
363
|
+
if (decision.kind !== 'redirect') return;
|
|
364
|
+
await navigateWithin(decision.location, { replaceState: true }, 'enter', 1);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* @internal Router.svelte: a `popstate` landed. `url` is the routable URL
|
|
369
|
+
* of the entry it landed on (hash mode already resolved). Runs the guards;
|
|
370
|
+
* a cancel puts the previous URL back, a redirect replaces the entry.
|
|
371
|
+
*/
|
|
372
|
+
export const handleTraversal = async (state: unknown, url: URL) => {
|
|
373
|
+
if (ignoredPopStates > 0) {
|
|
374
|
+
ignoredPopStates -= 1;
|
|
375
|
+
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// The browser already moved; under manual scroll restoration the
|
|
380
|
+
// window still shows the entry being left, so this is its position.
|
|
381
|
+
rememberScroll();
|
|
382
|
+
const previousHref = currentCommittedHref();
|
|
383
|
+
const decision = await decide(url, 'popstate', startNavigation());
|
|
384
|
+
if (decision.kind === 'stale') return;
|
|
385
|
+
if (decision.kind === 'cancel') {
|
|
386
|
+
undoTraversal(state, previousHref);
|
|
387
|
+
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (decision.kind === 'redirect') {
|
|
391
|
+
adoptHistoryEntry(state);
|
|
392
|
+
await navigateWithin(
|
|
393
|
+
decision.location,
|
|
394
|
+
{ replaceState: true },
|
|
395
|
+
'popstate',
|
|
396
|
+
1
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
await commitTraversal(state, url);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
/** @internal Router.svelte: restore the position saved in the entry the
|
|
406
|
+
* page loaded on (reload, or back/forward into this document). */
|
|
407
|
+
export const restoreInitialScroll = async (position: ScrollPosition) => {
|
|
408
|
+
await tick();
|
|
409
|
+
await whenLazySettled();
|
|
410
|
+
await nextFrame();
|
|
411
|
+
scrollToPosition(position);
|
|
412
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { setPage } from './page.js';
|
|
2
|
+
import { writeHistoryEntry } from './scroll';
|
|
2
3
|
|
|
3
4
|
const resolveTarget = (target: string) => {
|
|
4
5
|
if (typeof window === 'undefined')
|
|
@@ -9,8 +10,10 @@ const resolveTarget = (target: string) => {
|
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Shallow routing: update the URL bar and `page.state` without re-running
|
|
12
|
-
* `<Route>` matching. Useful for modals / drawers /
|
|
13
|
-
* shareable URL without swapping the active route's
|
|
13
|
+
* `<Route>` matching, guards or scrolling. Useful for modals / drawers /
|
|
14
|
+
* overlays that want a shareable URL without swapping the active route's
|
|
15
|
+
* content. The scroll position of the entry being left is saved, so
|
|
16
|
+
* going back restores it.
|
|
14
17
|
*
|
|
15
18
|
* Mirrors SvelteKit's `pushState` from `$app/navigation`.
|
|
16
19
|
*/
|
|
@@ -18,7 +21,7 @@ export const pushState = (target: string, state: unknown) => {
|
|
|
18
21
|
if (typeof window === 'undefined') return;
|
|
19
22
|
|
|
20
23
|
const url = resolveTarget(target);
|
|
21
|
-
|
|
24
|
+
writeHistoryEntry(url.href, state, 'push');
|
|
22
25
|
setPage({ state, url });
|
|
23
26
|
};
|
|
24
27
|
|
|
@@ -30,6 +33,6 @@ export const replaceState = (target: string, state: unknown) => {
|
|
|
30
33
|
if (typeof window === 'undefined') return;
|
|
31
34
|
|
|
32
35
|
const url = resolveTarget(target);
|
|
33
|
-
|
|
36
|
+
writeHistoryEntry(url.href, state, 'replace');
|
|
34
37
|
setPage({ state, url });
|
|
35
38
|
};
|