@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.
Files changed (238) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +232 -0
  3. package/changelog.json +228 -0
  4. package/dist/angular/browser.js +7 -8
  5. package/dist/angular/browser.js.map +6 -5
  6. package/dist/angular/components/core/streamingSlotRegistrar.js +6 -2
  7. package/dist/angular/components/core/streamingSlotRegistry.js +7 -3
  8. package/dist/angular/index.js +555 -156
  9. package/dist/angular/index.js.map +29 -23
  10. package/dist/angular/server.js +555 -156
  11. package/dist/angular/server.js.map +29 -23
  12. package/dist/build/buildWorker.js +14 -8
  13. package/dist/build.js +4949 -1941
  14. package/dist/build.js.map +72 -42
  15. package/dist/cli/{add-7azr0c83.js → add-6a7pjkn9.js} +11 -7
  16. package/dist/cli/{analyze-3cyek6v3.js → analyze-606fshwa.js} +1 -1
  17. package/dist/cli/{build-25rz8v25.js → build-g81xqbb9.js} +1 -1
  18. package/dist/cli/{compile-4jnfxts9.js → compile-9y1w9zpj.js} +3 -3
  19. package/dist/cli/config/server.js +10699 -1538
  20. package/dist/cli/{dev-9tk5hn2e.js → dev-hyeb89hp.js} +5 -1
  21. package/dist/cli/{doctor-4nv2c2j3.js → doctor-h55y9hj1.js} +1 -1
  22. package/dist/cli/{generate-v75p2fct.js → generate-20x1pkf7.js} +4 -4
  23. package/dist/cli/{htmx-427swtdz.js → htmx-6mz2h7k1.js} +2 -2
  24. package/dist/cli/{index-bhbs6d5z.js → index-32ge2w1f.js} +12 -4
  25. package/dist/cli/{index-a4ws2jwd.js → index-3wz3mktm.js} +1 -1
  26. package/dist/cli/{index-b7hjgknf.js → index-9sksd8c8.js} +13 -4
  27. package/dist/cli/{index-eyzwc7gz.js → index-qbrzvmh9.js} +4 -2
  28. package/dist/cli/index-qw98g1p6.js +269 -0
  29. package/dist/cli/{index-434b7j4a.js → index-vtp20gwf.js} +69 -1
  30. package/dist/cli/{index-h7qhz02e.js → index-wk0pyxrg.js} +3 -0
  31. package/dist/cli/{index-k3y8cb6c.js → index-z6f43b20.js} +1 -1
  32. package/dist/cli/index.js +16 -16
  33. package/dist/cli/{islands-8sgky9gh.js → islands-dc54e5y1.js} +1 -1
  34. package/dist/cli/{ls-4m4yqwww.js → ls-b6zz24nt.js} +1 -1
  35. package/dist/cli/{mobile-hcxjr2gf.js → mobile-ajnnvqtv.js} +3 -3
  36. package/dist/cli/prerender-fqpp0m1j.js +10 -0
  37. package/dist/cli/{remove-1ycjx85k.js → remove-t8x1mrxg.js} +4 -4
  38. package/dist/cli/{start-mbaejxhm.js → start-d6p831ny.js} +3 -3
  39. package/dist/cli/{typecheck-dr068pmh.js → typecheck-stbt0qt5.js} +6 -1
  40. package/dist/cli/{workspace-1sycp9g1.js → workspace-61hh6jq1.js} +1 -1
  41. package/dist/client/chunks/index-6b66nzyf.js +381 -0
  42. package/dist/client/chunks/index-6b66nzyf.js.map +11 -0
  43. package/dist/client/chunks/{islands-rc28k5sx.js → islands-s2ta5ajb.js} +5 -2
  44. package/dist/client/chunks/{islands-rc28k5sx.js.map → islands-s2ta5ajb.js.map} +7 -6
  45. package/dist/client/index.js +160 -3
  46. package/dist/client/index.js.map +6 -4
  47. package/dist/client/navigation.js +764 -0
  48. package/dist/client/navigation.js.map +10 -0
  49. package/dist/client/prefetch.js +33 -375
  50. package/dist/client/prefetch.js.map +3 -4
  51. package/dist/core/streamingSlotRegistrar.js +5 -2
  52. package/dist/core/streamingSlotRegistrar.js.map +5 -4
  53. package/dist/core/streamingSlotRegistry.js +5 -2
  54. package/dist/core/streamingSlotRegistry.js.map +5 -4
  55. package/dist/dev/client/cspNonce.ts +28 -0
  56. package/dist/dev/client/handlers/html.ts +4 -3
  57. package/dist/dev/client/handlers/htmx.ts +3 -2
  58. package/dist/dev/client/handlers/svelte.ts +15 -90
  59. package/dist/dev/client/syncDevtools.ts +2 -1
  60. package/dist/dev/serverBootstrap.js +11 -4
  61. package/dist/i18n/index.js +543 -0
  62. package/dist/i18n/index.js.map +15 -0
  63. package/dist/index.js +6339 -2233
  64. package/dist/index.js.map +89 -54
  65. package/dist/islands/index.js +282 -40
  66. package/dist/islands/index.js.map +14 -10
  67. package/dist/mobile/browser.js +7 -2
  68. package/dist/mobile/browser.js.map +5 -4
  69. package/dist/mobile/index.js +7 -2
  70. package/dist/mobile/index.js.map +5 -4
  71. package/dist/mobile/remoteMacAgentEntry.js +1 -1
  72. package/dist/react/components/browser/index.js +94 -4
  73. package/dist/react/components/index.js +150 -10
  74. package/dist/react/components/index.js.map +15 -10
  75. package/dist/react/hooks/index.js +479 -13
  76. package/dist/react/hooks/index.js.map +12 -4
  77. package/dist/react/index.js +769 -166
  78. package/dist/react/index.js.map +32 -21
  79. package/dist/react/router/browser.js +21 -4
  80. package/dist/react/router/browser.js.map +5 -4
  81. package/dist/react/router/index.js +21 -4
  82. package/dist/react/router/index.js.map +5 -4
  83. package/dist/react/server.js +597 -203
  84. package/dist/react/server.js.map +25 -15
  85. package/dist/src/angular/zoneRuntime.d.ts +5 -0
  86. package/dist/src/build/buildReactServerComponents.d.ts +22 -0
  87. package/dist/src/build/downlevelAsyncForZone.d.ts +1 -0
  88. package/dist/src/build/fileRoutes.d.ts +65 -0
  89. package/dist/src/build/fonts/buildFonts.d.ts +25 -0
  90. package/dist/src/build/fonts/fallbackMetrics.d.ts +13 -0
  91. package/dist/src/build/fonts/googleFonts.d.ts +18 -0
  92. package/dist/src/build/fonts/injectFontTags.d.ts +7 -0
  93. package/dist/src/build/fonts/parseFontMetrics.d.ts +4 -0
  94. package/dist/src/build/generateReactIndexes.d.ts +7 -1
  95. package/dist/src/build/htmlIncludes.d.ts +16 -0
  96. package/dist/src/build/inlineSourceMapComment.d.ts +1 -0
  97. package/dist/src/build/scanConventions.d.ts +3 -0
  98. package/dist/src/build/vitals.d.ts +21 -0
  99. package/dist/src/client/formActions.d.ts +29 -0
  100. package/dist/src/client/index.d.ts +2 -0
  101. package/dist/src/client/navigation.d.ts +16 -0
  102. package/dist/src/client/prefetch.d.ts +2 -30
  103. package/dist/src/core/build.d.ts +2 -2
  104. package/dist/src/core/cspNonce.d.ts +12 -0
  105. package/dist/src/core/fileRouteAdapter.d.ts +15 -0
  106. package/dist/src/core/fileRoutes.d.ts +5 -0
  107. package/dist/src/core/formActionFlash.d.ts +25 -0
  108. package/dist/src/core/formActions.d.ts +249 -0
  109. package/dist/src/core/index.d.ts +5 -0
  110. package/dist/src/core/partialPrerender.d.ts +28 -0
  111. package/dist/src/core/partialPrerenderServe.d.ts +24 -0
  112. package/dist/src/core/precompressed.d.ts +11 -1
  113. package/dist/src/core/prerenderNonce.d.ts +9 -0
  114. package/dist/src/core/productionRuntime.d.ts +4 -2
  115. package/dist/src/core/serverOnlyStaticFiles.d.ts +8 -0
  116. package/dist/src/core/staticHtmlNonce.d.ts +2 -0
  117. package/dist/src/core/vitals.d.ts +108 -0
  118. package/dist/src/dev/angular/fastHmrCompiler.d.ts +1 -1
  119. package/dist/src/dev/client/cspNonce.d.ts +2 -0
  120. package/dist/src/dev/fileRouteRefresher.d.ts +5 -0
  121. package/dist/src/dev/importCost/instrument.d.ts +1 -1
  122. package/dist/src/dev/reactServerComponentsRebuild.d.ts +3 -0
  123. package/dist/src/i18n/defineMessages.d.ts +5 -0
  124. package/dist/src/i18n/formatMessage.d.ts +26 -0
  125. package/dist/src/i18n/index.d.ts +6 -0
  126. package/dist/src/i18n/intlCache.d.ts +5 -0
  127. package/dist/src/i18n/routing.d.ts +28 -0
  128. package/dist/src/i18n/translator.d.ts +11 -0
  129. package/dist/src/mobile/androidRelease.d.ts +2 -2
  130. package/dist/src/mobile/iosRelease.d.ts +4 -4
  131. package/dist/src/mobile/materializedBundle.d.ts +1 -1
  132. package/dist/src/mobile/mobileInspect.d.ts +1 -1
  133. package/dist/src/mobile/releaseArtifact.d.ts +2 -2
  134. package/dist/src/mobile/remoteMacProtocol.d.ts +2 -2
  135. package/dist/src/plugins/i18n.d.ts +30 -0
  136. package/dist/src/plugins/index.d.ts +2 -1
  137. package/dist/src/plugins/secureHeaders.d.ts +48 -0
  138. package/dist/src/react/components/Head.d.ts +1 -1
  139. package/dist/src/react/components/StreamSlot.browser.d.ts +1 -0
  140. package/dist/src/react/components/StreamSlot.d.ts +5 -1
  141. package/dist/src/react/components/SuspenseSlot.browser.d.ts +2 -0
  142. package/dist/src/react/components/SuspenseSlot.d.ts +12 -0
  143. package/dist/src/react/fileRoute.d.ts +18 -0
  144. package/dist/src/react/hooks/index.d.ts +4 -0
  145. package/dist/src/react/hooks/useFormAction.d.ts +42 -0
  146. package/dist/src/react/hooks/useI18n.d.ts +15 -0
  147. package/dist/src/react/index.d.ts +2 -0
  148. package/dist/src/react/server.d.ts +2 -0
  149. package/dist/src/react/serverComponents/constants.d.ts +11 -0
  150. package/dist/src/react/serverComponents/flightPayload.d.ts +2 -0
  151. package/dist/src/react/serverComponents/pageHandler.d.ts +14 -0
  152. package/dist/src/svelte/browser.d.ts +1 -0
  153. package/dist/src/svelte/fileRoute.d.ts +15 -0
  154. package/dist/src/svelte/i18n.d.ts +8 -0
  155. package/dist/src/svelte/index.d.ts +2 -0
  156. package/dist/src/svelte/lowerAwaitSlotSyntax.d.ts +5 -1
  157. package/dist/src/svelte/renderToReadableStream.d.ts +10 -2
  158. package/dist/src/svelte/router/browser.d.ts +3 -1
  159. package/dist/src/svelte/router/goto.d.ts +13 -10
  160. package/dist/src/svelte/router/guards.d.ts +43 -0
  161. package/dist/src/svelte/router/index.d.ts +3 -1
  162. package/dist/src/svelte/router/lazy.d.ts +17 -0
  163. package/dist/src/svelte/router/lazyModule.d.ts +29 -0
  164. package/dist/src/svelte/router/navigation.d.ts +55 -0
  165. package/dist/src/svelte/router/pushState.d.ts +4 -2
  166. package/dist/src/svelte/router/scroll.d.ts +34 -0
  167. package/dist/src/svelte/routerSsr.d.ts +9 -0
  168. package/dist/src/svelte/server.d.ts +1 -0
  169. package/dist/src/utils/cspNonceRef.d.ts +8 -0
  170. package/dist/src/utils/fontAssets.d.ts +17 -0
  171. package/dist/src/utils/formActionProtocol.d.ts +10 -0
  172. package/dist/src/utils/generateHeadElement.d.ts +1 -1
  173. package/dist/src/utils/index.d.ts +1 -0
  174. package/dist/src/utils/inlinePageCss.d.ts +18 -0
  175. package/dist/src/utils/loadConfig.d.ts +5 -0
  176. package/dist/src/utils/localeRef.d.ts +10 -0
  177. package/dist/src/utils/resolveConvention.d.ts +6 -1
  178. package/dist/src/utils/streamingSlots.d.ts +5 -0
  179. package/dist/src/vue/browser.d.ts +2 -0
  180. package/dist/src/vue/components/Image.d.ts +2 -2
  181. package/dist/src/vue/index.d.ts +2 -0
  182. package/dist/src/vue/serverDataPayload.d.ts +32 -0
  183. package/dist/src/vue/serverDataState.d.ts +9 -0
  184. package/dist/src/vue/useServerData.browser.d.ts +6 -0
  185. package/dist/src/vue/useServerData.d.ts +24 -0
  186. package/dist/svelte/browser.js +330 -3
  187. package/dist/svelte/browser.js.map +8 -4
  188. package/dist/svelte/components/AwaitSlot.svelte +5 -0
  189. package/dist/svelte/components/Head.svelte +50 -0
  190. package/dist/svelte/components/JsonLd.svelte +9 -1
  191. package/dist/svelte/components/StreamSlot.svelte +5 -0
  192. package/dist/svelte/index.js +1017 -117
  193. package/dist/svelte/index.js.map +38 -24
  194. package/dist/svelte/router/Link.svelte +40 -8
  195. package/dist/svelte/router/Route.svelte +157 -12
  196. package/dist/svelte/router/Route.svelte.d.ts +39 -5
  197. package/dist/svelte/router/Router.svelte +140 -25
  198. package/dist/svelte/router/Router.svelte.d.ts +5 -1
  199. package/dist/svelte/router/browser.ts +10 -0
  200. package/dist/svelte/router/goto.ts +15 -86
  201. package/dist/svelte/router/guards.ts +112 -0
  202. package/dist/svelte/router/index.ts +10 -0
  203. package/dist/svelte/router/lazy.ts +82 -0
  204. package/dist/svelte/router/lazyModule.ts +74 -0
  205. package/dist/svelte/router/navigation.ts +412 -0
  206. package/dist/svelte/router/pushState.ts +7 -4
  207. package/dist/svelte/router/scroll.ts +286 -0
  208. package/dist/svelte/server.js +747 -252
  209. package/dist/svelte/server.js.map +30 -20
  210. package/dist/types/angular.d.ts +2 -0
  211. package/dist/types/build.d.ts +37 -0
  212. package/dist/types/conventions.d.ts +3 -0
  213. package/dist/types/fileRoutes.d.ts +72 -0
  214. package/dist/types/fonts.d.ts +86 -0
  215. package/dist/types/formAction.d.ts +54 -0
  216. package/dist/types/globals.d.ts +10 -0
  217. package/dist/types/i18n.d.ts +185 -0
  218. package/dist/types/index.d.ts +4 -0
  219. package/dist/types/metadata.d.ts +11 -0
  220. package/dist/types/navigation.d.ts +37 -0
  221. package/dist/types/prefetch.d.ts +10 -0
  222. package/dist/types/sitemap.d.ts +4 -0
  223. package/dist/types/svelteRouter.d.ts +89 -2
  224. package/dist/types/svelteRouter.ts +112 -2
  225. package/dist/types/vue.d.ts +46 -1
  226. package/dist/utils/iconVersion.ts +34 -0
  227. package/dist/vue/browser.js +107 -11
  228. package/dist/vue/browser.js.map +7 -4
  229. package/dist/vue/components/index.js +5 -2
  230. package/dist/vue/components/index.js.map +5 -4
  231. package/dist/vue/index.js +712 -158
  232. package/dist/vue/index.js.map +32 -23
  233. package/dist/vue/server.js +310 -79
  234. package/dist/vue/server.js.map +20 -15
  235. package/package.json +9 -1
  236. package/dist/cli/prerender-mfnwsvyt.js +0 -7
  237. package/dist/src/plugins/pageRouter.d.ts +0 -1
  238. /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 / overlays that want a
13
- * shareable URL without swapping the active route's content.
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
- window.history.pushState(state, '', url.href);
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
- window.history.replaceState(state, '', url.href);
36
+ writeHistoryEntry(url.href, state, 'replace');
34
37
  setPage({ state, url });
35
38
  };