@askrjs/askr 0.3.0 → 0.4.0

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 (123) hide show
  1. package/CHANGELOG.md +874 -0
  2. package/README.md +61 -29
  3. package/capabilities.json +19 -9
  4. package/dist/actions/index.js +56 -12
  5. package/dist/benchmark.js +10 -15
  6. package/dist/boot/index.d.ts +3 -2
  7. package/dist/boot/index.js +1 -1
  8. package/dist/boot-DaoTYoCw.js +3989 -0
  9. package/dist/browser-activity-CB8e-V4f.js +220 -0
  10. package/dist/components/index.d.ts +8 -1
  11. package/dist/components/index.js +1 -43
  12. package/dist/{aria-hKjThHcp.js → compose-ref-DWzdbxsD.js} +41 -4
  13. package/dist/control/index.d.ts +2 -0
  14. package/dist/control/index.js +1 -1
  15. package/dist/control-nfpT2fbK.js +209 -0
  16. package/dist/core.d.ts +5 -9
  17. package/dist/csp-nonce-DwGw1A6Q.js +35 -0
  18. package/dist/data/index.js +3 -3
  19. package/dist/{data-2edW0jhF.js → data-BCwClJiF.js} +283 -94
  20. package/dist/data-runtime-C1bFAlba.js +242 -0
  21. package/dist/{deferred-CbgNhf67.js → deferred-bEEHHvic.js} +42 -20
  22. package/dist/domains/actions.d.ts +8 -3
  23. package/dist/domains/component.d.ts +1 -107
  24. package/dist/domains/context.d.ts +0 -15
  25. package/dist/domains/control.d.ts +36 -249
  26. package/dist/domains/data.d.ts +31 -13
  27. package/dist/domains/diagnostics.d.ts +1 -2
  28. package/dist/domains/lifecycle.d.ts +2 -5
  29. package/dist/domains/refs.d.ts +0 -2
  30. package/dist/domains/routing.d.ts +43 -8
  31. package/dist/domains/scheduler.d.ts +1 -76
  32. package/dist/domains/server.d.ts +5 -2
  33. package/dist/domains/state.d.ts +2 -5
  34. package/dist/domains/telemetry.d.ts +0 -2
  35. package/dist/elements.d.ts +214 -51
  36. package/dist/error-boundary-Bpwofwps.js +293 -0
  37. package/dist/foundations/icon/index.d.ts +0 -58
  38. package/dist/foundations/icon/index.js +2 -2
  39. package/dist/foundations/index.js +2 -1
  40. package/dist/foundations/interactions/index.js +1 -1
  41. package/dist/foundations/state/index.d.ts +3 -3
  42. package/dist/foundations/state/index.js +5 -3
  43. package/dist/foundations/structures/index.js +2 -1
  44. package/dist/foundations/utilities/index.d.ts +2 -0
  45. package/dist/foundations/utilities/index.js +1 -2
  46. package/dist/fx/index.d.ts +31 -58
  47. package/dist/fx/index.js +313 -195
  48. package/dist/hooks-BimKStr4.js +202 -0
  49. package/dist/index.d.ts +11 -74
  50. package/dist/index.js +8 -447
  51. package/dist/instance-BAnfmXOR.js +988 -0
  52. package/dist/{interactions-CTeACdT_.js → interactions-D2BoREb-.js} +3 -2
  53. package/dist/jsx-dev-runtime.d.ts +41 -18
  54. package/dist/jsx-dev-runtime.js +1 -6
  55. package/dist/{jsx-runtime-CPJXAOBF.js → jsx-runtime-C4idqHY0.js} +18 -7
  56. package/dist/jsx-runtime.js +1 -1
  57. package/dist/jsx.d.ts +61 -37
  58. package/dist/{manifest-D1P3UNWV.js → manifest-CW9gyNBY.js} +3 -3
  59. package/dist/{navigate-prC5V8XV.js → navigate-CP_0Zy5U.js} +454 -87
  60. package/dist/navigation.d.ts +12 -3
  61. package/dist/peer-types.d.ts +8 -0
  62. package/dist/portal-DuOKfnLa.js +349 -0
  63. package/dist/promise-Dz8iIHAm.js +16 -0
  64. package/dist/query-registry-WMCN0BJL.js +320 -0
  65. package/dist/readable-DFWOsTac.js +21 -0
  66. package/dist/{render-context-B7ohlh6t.js → render-context-DGHnEveC.js} +27 -11
  67. package/dist/render-sync-IC5lMFpq.js +1240 -0
  68. package/dist/resolution-Cx82-xjW.js +1244 -0
  69. package/dist/resource-CLjB7RBQ.js +377 -0
  70. package/dist/resources/index.d.ts +21 -11
  71. package/dist/resources/index.js +45 -244
  72. package/dist/router/index.d.ts +14 -5
  73. package/dist/router/index.js +22 -53
  74. package/dist/scope-CwpVSHVf.js +55 -0
  75. package/dist/{shared-kU8yvryp.js → shared-O2ZnmYAf.js} +1 -7
  76. package/dist/{snapshot-source-An4iKajG.js → snapshot-Cjx4N988.js} +6 -1
  77. package/dist/ssg/index.d.ts +1 -2
  78. package/dist/ssg/index.js +5 -13
  79. package/dist/ssr/index.d.ts +26 -28
  80. package/dist/ssr/index.js +4 -4
  81. package/dist/{ssr-CiNm6Vzl.js → ssr-BJ8ug7zH.js} +133 -122
  82. package/dist/ssr-DoV-vJ-5.js +229 -0
  83. package/dist/state-DWO1KzkX.js +232 -0
  84. package/dist/structures-D4dDMqHX.js +231 -0
  85. package/dist/testing/index.d.ts +8 -4
  86. package/dist/testing/index.js +29 -34
  87. package/dist/url-CnbfpX0K.js +185 -0
  88. package/dist/utilities.d.ts +48 -2
  89. package/dist/verify-hydration-DpVaDnqz.js +152 -0
  90. package/package.json +19 -9
  91. package/dist/access-CEA2pOry.js +0 -1046
  92. package/dist/activity-oy2YFTFF.js +0 -201
  93. package/dist/boot-BMBMWUGI.js +0 -1084
  94. package/dist/capabilities-Bbf0mlp9.js +0 -42
  95. package/dist/compose-ref-v0qGlHoN.js +0 -26
  96. package/dist/control-BR_mrX__.js +0 -173
  97. package/dist/control-C-ApnFs1.js +0 -20
  98. package/dist/csp-nonce-BfFWcd0w.js +0 -173
  99. package/dist/data-runtime-CxoY98lX.js +0 -149
  100. package/dist/default-portal-runtime-DvMvWJVp.js +0 -20
  101. package/dist/dom-renderer.d.ts +0 -77
  102. package/dist/domains/renderer.d.ts +0 -127
  103. package/dist/eager-control.d.ts +0 -8
  104. package/dist/effect-C3Zsc1k6.js +0 -292
  105. package/dist/for-state-BDykeOJL.js +0 -1753
  106. package/dist/instance-VAFYVJOa.js +0 -1027
  107. package/dist/notify-DxxuDPeY.js +0 -266
  108. package/dist/portal-w3oQ6oQU.js +0 -29
  109. package/dist/query-registry-LrYpQsDJ.js +0 -83
  110. package/dist/render-resolved-BC0W7_5b.js +0 -1123
  111. package/dist/renderer-CT0HeY58.js +0 -8623
  112. package/dist/resolution-3DskUkjG.js +0 -577
  113. package/dist/resource-18-sFRl9.js +0 -344
  114. package/dist/route-matching-CUzZ31gT.js +0 -551
  115. package/dist/router-internal-7nuNEHfG.js +0 -36
  116. package/dist/scope-Cq_iYbTR.js +0 -403
  117. package/dist/selector-BSPVkjKP.js +0 -311
  118. package/dist/ssr-EPjwJH5R.js +0 -29
  119. package/dist/state-BXJfudq1.js +0 -91
  120. package/dist/structures-CjEzfj4h.js +0 -654
  121. package/dist/timer-C7uqnJVt.js +0 -85
  122. package/dist/verify-hydration-ikaxotf7.js +0 -27
  123. package/dist/vnode-D24p6IW2.js +0 -7
@@ -1,9 +1,34 @@
1
- import { T as isProductionEnvironment, r as flushRuntimeScheduler, u as isRuntimeSchedulerExecuting, v as logger, w as isDevelopmentEnvironment } from "./access-CEA2pOry.js";
2
- import { P as isPromiseLike } from "./instance-VAFYVJOa.js";
3
- import { i as commitTransaction, l as registerCommitParticipant, o as discardTransaction, r as beginCommitTransaction } from "./notify-DxxuDPeY.js";
4
- import { i as syncCurrentRouteSnapshot } from "./activity-oy2YFTFF.js";
5
- import { a as resolveRouteRequest, i as hasRouteRenderData, n as getRouteRenderContext, r as getRouteRenderData } from "./resolution-3DskUkjG.js";
6
- import { L as lockRouteRegistration, b as getActiveRouteBasePath, o as addRouteBasePath, r as computeRouteActivityMatches } from "./route-matching-CUzZ31gT.js";
1
+ import { F as logger, I as isDevelopmentEnvironment, L as isProductionEnvironment, x as isFlushing, y as flushSync } from "./instance-BAnfmXOR.js";
2
+ import { b as reportUncaughtErrorLater, c as onCommit, i as currentComponent, n as createSource, o as hookSlot, r as currentAppRuntime, s as notify, u as readSource } from "./hooks-BimKStr4.js";
3
+ import { n as getActiveRenderContext } from "./render-context-DGHnEveC.js";
4
+ import { n as isPromiseLike } from "./promise-Dz8iIHAm.js";
5
+ import { C as removeRouteBasePath, E as parseLocation, M as getActiveRouteBasePath, N as getActiveRoutes, T as makeQuery, Y as lockRouteRegistration, a as resolveRouteRequest, b as addLogicalRouteBasePath, g as computeRouteActivityMatches, h as computeMatchesFromRoutes, i as hasRouteRenderData, n as getRouteRenderContext, r as getRouteRenderData, w as deepFreeze } from "./resolution-Cx82-xjW.js";
6
+ import { n as syncRouteActivitySnapshot } from "./route-activity-CNJfpwx-.js";
7
+ import { c as resolveNavigationUrl } from "./url-CnbfpX0K.js";
8
+ //#region src/common/app-render-runtime.ts
9
+ const stagedRouteLocations = /* @__PURE__ */ new WeakMap();
10
+ function createAppRenderRuntime(input = {}) {
11
+ return {
12
+ hydrationResources: input.hydrationResources,
13
+ framework: Object.freeze({ ...input.framework }),
14
+ route: input.route,
15
+ hasRoute: input.hasRoute ?? false,
16
+ dataRuntime: input.dataRuntime,
17
+ routeRegistry: input.routeRegistry,
18
+ routeAuth: input.routeAuth,
19
+ lifetime: input.lifetime
20
+ };
21
+ }
22
+ function stageAppRenderRouteLocation(runtime, href) {
23
+ stagedRouteLocations.set(runtime, href);
24
+ }
25
+ function getStagedAppRenderRouteLocation(runtime) {
26
+ return runtime ? stagedRouteLocations.get(runtime) : void 0;
27
+ }
28
+ function clearStagedAppRenderRouteLocation(runtime) {
29
+ if (runtime) stagedRouteLocations.delete(runtime);
30
+ }
31
+ //#endregion
7
32
  //#region src/common/root-update.ts
8
33
  let rootUpdateHost;
9
34
  function configureRootUpdateHost(host) {
@@ -14,6 +39,174 @@ function prepareRootUpdate(root, input) {
14
39
  return rootUpdateHost.prepare(root, input);
15
40
  }
16
41
  //#endregion
42
+ //#region src/router/activity.ts
43
+ function routeSignature(route) {
44
+ return `${route.path}\u0000${JSON.stringify(route.query.toJSON())}\u0000${route.hash ?? ""}`;
45
+ }
46
+ let currentRouteSnapshot = buildRouteSnapshot("/", "", "");
47
+ const currentRouteSource = createSource();
48
+ /** Register a callback to run whenever the active route changes, with optional cleanup. */
49
+ function onRouteChange(fn, options = {}) {
50
+ const instance = currentComponent();
51
+ if (!instance) return;
52
+ const route = currentRoute();
53
+ const slot = hookSlot(instance, "onRouteChange", () => ({
54
+ kind: "route-change",
55
+ previous: null,
56
+ pending: null,
57
+ cleanup: null,
58
+ cleanupRegistered: false,
59
+ callback: fn,
60
+ immediate: options.immediate === true
61
+ }));
62
+ slot.pending = route;
63
+ slot.callback = fn;
64
+ slot.immediate = options.immediate === true;
65
+ if (!slot.cleanupRegistered) {
66
+ instance.onCleanup(() => {
67
+ slot.cleanup?.();
68
+ slot.cleanup = null;
69
+ });
70
+ slot.cleanupRegistered = true;
71
+ }
72
+ if (!slot.previous) {
73
+ onCommit(instance, () => {
74
+ const committed = slot.pending;
75
+ if (!committed) return;
76
+ if (slot.immediate) slot.cleanup = slot.callback(committed, null) ?? null;
77
+ slot.previous = committed;
78
+ });
79
+ return;
80
+ }
81
+ if (routeSignature(slot.previous) === routeSignature(route)) return;
82
+ onCommit(instance, () => {
83
+ const previous = slot.previous;
84
+ const committed = slot.pending;
85
+ if (!previous || !committed || routeSignature(previous) === routeSignature(committed)) return;
86
+ slot.cleanup?.();
87
+ slot.cleanup = slot.callback(committed, previous) ?? null;
88
+ slot.previous = committed;
89
+ });
90
+ }
91
+ let serverLocation = null;
92
+ function readLocationState() {
93
+ const historyState = typeof window !== "undefined" && window.history?.state ? window.history.state : null;
94
+ const hasState = historyState?.askrHasState === true;
95
+ return {
96
+ hasState,
97
+ state: hasState ? historyState.askrState : void 0
98
+ };
99
+ }
100
+ function logicalRoutePathname(pathname) {
101
+ const logical = removeRouteBasePath(pathname, getActiveRouteBasePath());
102
+ return logical === void 0 ? void 0 : parseLocation(logical).pathname;
103
+ }
104
+ function routePathname(pathname) {
105
+ const logical = logicalRoutePathname(pathname);
106
+ return logical === void 0 ? {
107
+ pathname: parseLocation(pathname).pathname,
108
+ withinBasePath: false
109
+ } : {
110
+ pathname: logical,
111
+ withinBasePath: true
112
+ };
113
+ }
114
+ function setServerLocation(url) {
115
+ serverLocation = url;
116
+ if (url) {
117
+ const location = routePathname(parseLocation(url).pathname);
118
+ syncRouteActivitySnapshot(location.pathname, location.withinBasePath ? computeMatchesFromRoutes(location.pathname, getActiveRoutes()) : []);
119
+ return;
120
+ }
121
+ syncRouteActivitySnapshot(currentRouteSnapshot.path, currentRouteSnapshot.matches);
122
+ }
123
+ function buildRouteSnapshot(pathname, search, hash) {
124
+ const location = routePathname(pathname);
125
+ pathname = location.pathname;
126
+ const query = makeQuery(search);
127
+ const matches = location.withinBasePath ? computeMatchesFromRoutes(pathname, getActiveRoutes()) : [];
128
+ const locationState = readLocationState();
129
+ return Object.freeze({
130
+ path: pathname,
131
+ params: deepFreeze({ ...matches[0]?.params }),
132
+ query,
133
+ hash: hash || null,
134
+ ...locationState,
135
+ matches: Object.freeze(matches)
136
+ });
137
+ }
138
+ function setCurrentRouteSnapshot(pathname, search, hash, activityMatches) {
139
+ currentRouteSnapshot = buildRouteSnapshot(pathname, search, hash);
140
+ syncRouteActivitySnapshot(currentRouteSnapshot.path, activityMatches ?? currentRouteSnapshot.matches);
141
+ notify(currentRouteSource);
142
+ }
143
+ function readCurrentRouteLocation() {
144
+ const renderContext = getActiveRenderContext();
145
+ if (renderContext?.url) {
146
+ const parsed = parseLocation(renderContext.url);
147
+ const location = routePathname(parsed.pathname);
148
+ return {
149
+ ...parsed,
150
+ ...location
151
+ };
152
+ }
153
+ const stagedRouteLocation = getStagedAppRenderRouteLocation(currentAppRuntime());
154
+ if (stagedRouteLocation) {
155
+ const parsed = parseLocation(stagedRouteLocation);
156
+ const location = routePathname(parsed.pathname);
157
+ return {
158
+ ...parsed,
159
+ ...location
160
+ };
161
+ }
162
+ if (typeof window !== "undefined" && window.location) return {
163
+ ...routePathname(window.location.pathname || "/"),
164
+ search: window.location.search || "",
165
+ hash: window.location.hash || ""
166
+ };
167
+ if (serverLocation) {
168
+ const parsed = parseLocation(serverLocation);
169
+ const location = routePathname(parsed.pathname);
170
+ return {
171
+ ...parsed,
172
+ ...location
173
+ };
174
+ }
175
+ return {
176
+ ...routePathname(currentRouteSnapshot.path),
177
+ search: "",
178
+ hash: currentRouteSnapshot.hash ?? ""
179
+ };
180
+ }
181
+ function readCurrentRouteSnapshot() {
182
+ if (!currentComponent()) throw new Error("currentRoute() can only be called during component render execution. Call currentRoute() from inside your component function.");
183
+ const { pathname, search, hash, withinBasePath } = readCurrentRouteLocation();
184
+ const query = makeQuery(search);
185
+ const matches = withinBasePath ? computeMatchesFromRoutes(pathname, getActiveRoutes()) : [];
186
+ const routeParams = (withinBasePath ? getActiveRenderContext()?.params : void 0) ?? matches[0]?.params ?? {};
187
+ const params = deepFreeze({ ...routeParams });
188
+ const locationState = readLocationState();
189
+ return Object.freeze({
190
+ path: pathname,
191
+ params,
192
+ query,
193
+ hash: hash || null,
194
+ ...locationState,
195
+ matches: Object.freeze(matches)
196
+ });
197
+ }
198
+ /** Read the currently active route's {@link RouteSnapshot}; reactive during component render. */
199
+ function currentRoute() {
200
+ const instance = currentComponent();
201
+ if (!instance) throw new Error("currentRoute() can only be called during component render execution. Call currentRoute() from inside your component function.");
202
+ if (typeof window === "undefined" || instance.server) return readCurrentRouteSnapshot();
203
+ readSource(currentRouteSource);
204
+ return readCurrentRouteSnapshot();
205
+ }
206
+ function syncCurrentRouteSnapshot(pathname, search, hash, activityMatches) {
207
+ setCurrentRouteSnapshot(pathname, search, hash, activityMatches);
208
+ }
209
+ //#endregion
17
210
  //#region src/router/navigation-registry.ts
18
211
  let currentInstance = null;
19
212
  let currentPathname = "/";
@@ -45,6 +238,29 @@ function parseTargetUrl(path) {
45
238
  const base = href && href !== "about:blank" && !href.startsWith("about:") && !href.startsWith("data:") ? href : `http://localhost${pathname}${search}${hash}`;
46
239
  return new URL(path, base);
47
240
  }
241
+ /** Whether a parsed target shares the origin of the current document. */
242
+ function isCurrentOrigin(target) {
243
+ return target.origin === parseTargetUrl("/").origin;
244
+ }
245
+ /**
246
+ * The absolute URL of a root-relative href on the current origin. Browsers
247
+ * read a root-relative `//host/x` as another host, so history writes and
248
+ * document loads never receive the root-relative form.
249
+ */
250
+ function toDocumentUrl(href) {
251
+ const origin = typeof window === "undefined" ? void 0 : window.location.origin;
252
+ return origin && origin !== "null" ? `${origin}${href}` : href;
253
+ }
254
+ /**
255
+ * Parse a navigation or redirect target. Only a target written with an
256
+ * explicit `http:`/`https:` scheme may leave the current origin: a path-like
257
+ * string that the URL parser resolves elsewhere (`/\\evil.example`,
258
+ * `//evil.example`, `/.//evil.example`) would otherwise turn an app path into
259
+ * an open redirect.
260
+ */
261
+ function parseNavigationTarget(path) {
262
+ return resolveNavigationUrl(path, parseTargetUrl("").href);
263
+ }
48
264
  function getRegisteredAppsSnapshot() {
49
265
  return [...registeredApps];
50
266
  }
@@ -217,13 +433,29 @@ function scrollToPosition(position) {
217
433
  if (typeof window === "undefined" || typeof window.scrollTo !== "function") return;
218
434
  window.scrollTo(position.x, position.y);
219
435
  }
436
+ /**
437
+ * Find the element a URL fragment indicates. Like the browser, try the raw
438
+ * fragment first, then its percent-decoded form. A fragment that is not valid
439
+ * percent-encoding (`#%E0`) has no decoded form, so only the raw id applies.
440
+ */
441
+ function findFragmentTarget(fragment) {
442
+ const raw = document.getElementById(fragment);
443
+ if (raw) return raw;
444
+ let decoded;
445
+ try {
446
+ decoded = decodeURIComponent(fragment);
447
+ } catch (error) {
448
+ if (error instanceof URIError) return null;
449
+ throw error;
450
+ }
451
+ return decoded === fragment ? null : document.getElementById(decoded);
452
+ }
220
453
  function applyNavigationScroll(behavior) {
221
454
  const nextBehavior = behavior ?? scrollRestorationOptions.navigation;
222
455
  if (!scrollRestorationOptions.enabled || nextBehavior === "preserve") return;
223
456
  const hash = typeof window !== "undefined" ? window.location.hash : "";
224
457
  if (hash && typeof document !== "undefined") {
225
- const id = decodeURIComponent(hash.slice(1));
226
- const target = document.getElementById(id);
458
+ const target = findFragmentTarget(hash.slice(1));
227
459
  if (target && typeof target.scrollIntoView === "function") {
228
460
  target.scrollIntoView({ behavior: "auto" });
229
461
  return;
@@ -410,6 +642,118 @@ function serializeRouteMeta(meta) {
410
642
  return output.join("");
411
643
  }
412
644
  //#endregion
645
+ //#region src/router/document-navigation.ts
646
+ /**
647
+ * Hand a location no registered app can render to the browser as a document
648
+ * load, keeping the requested history mode.
649
+ */
650
+ function loadDocument(href, history) {
651
+ if (history === "replace") window.location.replace(href);
652
+ else window.location.assign(href);
653
+ }
654
+ /** Reload the document at the current history entry. */
655
+ function reloadDocument() {
656
+ window.location.reload();
657
+ }
658
+ //#endregion
659
+ //#region src/router/history-index.ts
660
+ /**
661
+ * Positions in this document's session history.
662
+ *
663
+ * Askr stamps an index into every entry it writes. A failed back/forward
664
+ * traversal then returns with `history.go()` to the entry whose page is still
665
+ * rendered, instead of rewriting the entry the browser landed on. A position
666
+ * is `undefined` once it cannot be known, for example after other code pushed
667
+ * an entry; rollback then reloads rather than guess.
668
+ */
669
+ let landedIndex;
670
+ let renderedIndex;
671
+ let pendingReturnIndex;
672
+ let historyIndexInitialized = false;
673
+ function readHistoryIndex(state) {
674
+ if (!state || typeof state !== "object") return void 0;
675
+ const index = state.askrIndex;
676
+ return typeof index === "number" ? index : void 0;
677
+ }
678
+ function stampActiveEntry(state, index) {
679
+ window.history.replaceState({
680
+ ...state && typeof state === "object" ? state : {},
681
+ askrIndex: index
682
+ }, "", window.location.href);
683
+ }
684
+ /**
685
+ * The index an entry written with `mode` occupies. The active entry must still
686
+ * carry the index Askr last tracked; otherwise other code wrote an entry and
687
+ * positions are unknown from here on.
688
+ */
689
+ function nextHistoryIndex(mode) {
690
+ if (readHistoryIndex(window.history.state) !== landedIndex) landedIndex = renderedIndex = void 0;
691
+ if (landedIndex === void 0) return void 0;
692
+ return mode === "push" ? landedIndex + 1 : landedIndex;
693
+ }
694
+ /** Record that the entry at `index` is active and its page rendered. */
695
+ function commitHistoryIndex(index) {
696
+ landedIndex = renderedIndex = index;
697
+ }
698
+ /**
699
+ * Record the entry a popstate landed on, before its page renders. An entry
700
+ * without an index was added by a fragment navigation from the entry the user
701
+ * was on, so it is stamped as the next position.
702
+ */
703
+ function landOnHistoryEntry(state) {
704
+ let index = readHistoryIndex(state);
705
+ if (index === void 0 && landedIndex !== void 0) {
706
+ index = landedIndex + 1;
707
+ stampActiveEntry(state, index);
708
+ }
709
+ landedIndex = index;
710
+ return index;
711
+ }
712
+ /**
713
+ * Adopt the active entry's index. Only the document's first entry is stamped
714
+ * as index 0: stamping later would guess a position the user may not be at.
715
+ */
716
+ function initializeHistoryIndex() {
717
+ const state = window.history?.state;
718
+ const index = readHistoryIndex(state);
719
+ if (index !== void 0) {
720
+ historyIndexInitialized = true;
721
+ commitHistoryIndex(index);
722
+ return;
723
+ }
724
+ if (historyIndexInitialized || typeof window.history?.replaceState !== "function") return;
725
+ historyIndexInitialized = true;
726
+ commitHistoryIndex(0);
727
+ stampActiveEntry(state, 0);
728
+ }
729
+ /**
730
+ * Traverse back to the entry whose page is rendered. Returns false when the
731
+ * positions are unknown, leaving history untouched.
732
+ */
733
+ function returnToRenderedHistoryEntry() {
734
+ if (landedIndex === void 0 || renderedIndex === void 0) return false;
735
+ const delta = renderedIndex - landedIndex;
736
+ if (delta === 0) return false;
737
+ pendingReturnIndex = renderedIndex;
738
+ window.history.go(delta);
739
+ return true;
740
+ }
741
+ /** A newer navigation owns history, so a pending return must not skip it. */
742
+ function cancelHistoryReturn() {
743
+ pendingReturnIndex = void 0;
744
+ }
745
+ /**
746
+ * Whether this popstate completes a {@link returnToRenderedHistoryEntry}
747
+ * traversal, whose destination is already rendered.
748
+ */
749
+ function consumeHistoryReturn(state) {
750
+ const expected = pendingReturnIndex;
751
+ pendingReturnIndex = void 0;
752
+ if (expected === void 0 || readHistoryIndex(state) !== expected) return false;
753
+ commitHistoryIndex(expected);
754
+ return true;
755
+ }
756
+ //#endregion
413
757
  //#region src/router/navigation-targets.ts
414
758
  const MAX_NAVIGATION_REDIRECTS = 20;
415
759
  let activeRouteRequestId = 0;
@@ -506,7 +850,8 @@ function resolveAppRouteRequest(app, pathname, href, signal) {
506
850
  return resolveRouteRequest(href, {
507
851
  registry: app.registry,
508
852
  auth: app.auth,
509
- signal
853
+ signal,
854
+ dataRuntime: app.instance.appRuntime?.dataRuntime
510
855
  });
511
856
  }
512
857
  function getResolvedRouteHandler(resolved) {
@@ -562,8 +907,8 @@ function applyNavigationTargets(requestId, path, options, redirectState, pathnam
562
907
  for (const target of targets) {
563
908
  const resolved = target.resolved;
564
909
  if (!resolved || resolved.kind !== "redirect") continue;
565
- const redirectTarget = parseTargetUrl(resolved.to);
566
- const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;
910
+ const redirectTarget = parseNavigationTarget(resolved.to);
911
+ const redirectHref = isCurrentOrigin(redirectTarget) ? `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}` : redirectTarget.href;
567
912
  if (redirectHref === href) {
568
913
  if (isDevelopmentEnvironment()) logger.warn(`Navigation guard redirected to the same path: ${redirectHref}`);
569
914
  return;
@@ -578,26 +923,35 @@ function applyNavigationTargets(requestId, path, options, redirectState, pathnam
578
923
  const matchedTargets = targets.filter((target) => target.resolved !== null);
579
924
  if (matchedTargets.length === 0) {
580
925
  if (isDevelopmentEnvironment()) logger.warn(`No route found for path: ${path}`);
926
+ if (href !== getWindowHref$1()) loadDocument(toDocumentUrl(href), getNavigationHistoryMode(options));
581
927
  return;
582
928
  }
583
929
  if (isStaleRouteRequest(requestId)) return;
584
930
  commitNavigationRoots(requestId, pathname, href, matchedTargets, () => {
585
931
  saveScrollPosition(previousHref);
586
- const historyMethod = getNavigationHistoryMode(options) === "replace" ? "replaceState" : "pushState";
587
- window.history[historyMethod]({
932
+ const historyMode = getNavigationHistoryMode(options);
933
+ const historyIndex = nextHistoryIndex(historyMode);
934
+ window.history[historyMode === "replace" ? "replaceState" : "pushState"]({
588
935
  path: href,
589
936
  askrHasState: Object.prototype.hasOwnProperty.call(options, "state"),
590
- askrState: options.state
591
- }, "", href);
937
+ askrState: options.state,
938
+ askrIndex: historyIndex
939
+ }, "", toDocumentUrl(href));
940
+ commitHistoryIndex(historyIndex);
592
941
  }, () => {
593
942
  if (pathname !== previousPathname || parseTargetUrl(href).hash) applyNavigationScroll(options.scroll);
594
943
  });
595
944
  }
596
- function applyPopStateNavigationTargets(requestId, previousHref, previousState, pathname, href, state, targets, navigate) {
945
+ function applyPopStateNavigationTargets(requestId, previousHref, historyIndex, pathname, href, state, targets, navigate) {
597
946
  if (isStaleRouteRequest(requestId)) return;
598
947
  const matchedTargets = targets.filter((target) => target.resolved !== null);
599
948
  if (matchedTargets.length === 0) {
949
+ if (href.split("#")[0] === previousHref.split("#")[0]) {
950
+ commitHistoryIndex(historyIndex);
951
+ return;
952
+ }
600
953
  if (isDevelopmentEnvironment()) logger.warn(`No route found for path: ${pathname}`);
954
+ reloadDocument();
601
955
  return;
602
956
  }
603
957
  for (const target of matchedTargets) {
@@ -607,13 +961,16 @@ function applyPopStateNavigationTargets(requestId, previousHref, previousState,
607
961
  return;
608
962
  }
609
963
  saveScrollPosition(previousHref);
610
- commitNavigationRoots(requestId, pathname, href, matchedTargets, () => {}, () => applyHistoryScroll(href, state), () => {
611
- window.history.replaceState(previousState, "", previousHref);
612
- syncRegisteredRouteSnapshot();
964
+ commitNavigationRoots(requestId, pathname, href, matchedTargets, () => commitHistoryIndex(historyIndex), () => applyHistoryScroll(href, state), () => {
965
+ if (isStaleRouteRequest(requestId)) return;
966
+ if (!returnToRenderedHistoryEntry()) reloadDocument();
613
967
  });
614
968
  }
615
- /** Every root joins one transaction. History remains after lifecycle work,
616
- * so a navigation started by that work can supersede this destination. */
969
+ /**
970
+ * Render every destination root, then commit all of them or none. Lifecycle
971
+ * work of the committed destination runs before history is updated, so a
972
+ * navigation it starts supersedes this one.
973
+ */
617
974
  function commitNavigationRoots(requestId, pathname, href, targets, updateHistory, updateScroll, restoreHistory) {
618
975
  const previousPathname = getCurrentPathname();
619
976
  const previousHref = getCurrentHref();
@@ -622,70 +979,58 @@ function commitNavigationRoots(requestId, pathname, href, targets, updateHistory
622
979
  return {
623
980
  target,
624
981
  replaceLifetime,
625
- previousPathname: target.app.pathname,
626
- previousHref: target.app.href,
627
982
  prepared: prepareNavigationRoot(target, href, replaceLifetime)
628
983
  };
629
984
  });
630
- const transaction = beginCommitTransaction();
631
- let completionFailure;
632
- registerCommitParticipant({
633
- rollback() {
634
- const errors = [];
635
- for (let index = roots.length - 1; index >= 0; index--) errors.push(...roots[index].prepared.rollback());
636
- for (const root of roots) {
637
- root.target.app.pathname = root.previousPathname;
638
- root.target.app.href = root.previousHref;
639
- }
640
- setCurrentRouteLocation(previousPathname, previousHref);
641
- try {
642
- restoreHistory?.();
643
- } catch (error) {
644
- errors.push(error);
645
- }
646
- reportRouteCleanupErrors(errors);
647
- },
648
- settle() {
649
- const errors = [];
650
- for (const root of roots) errors.push(...root.prepared.retire());
651
- reportRouteCleanupErrors(errors);
652
- },
653
- complete() {
654
- if (isStaleRouteRequest(requestId)) return;
655
- try {
656
- updateHistory();
657
- setCurrentRouteLocation(pathname, href);
658
- syncRegisteredRouteSnapshot();
659
- reconcileNavigationMetadata(targets);
660
- updateScroll();
661
- } catch (error) {
662
- completionFailure = { error };
663
- throw error;
664
- } finally {
665
- for (const root of roots) root.prepared.publish();
666
- }
985
+ const rollback = () => {
986
+ const errors = [];
987
+ for (let index = roots.length - 1; index >= 0; index--) errors.push(...roots[index].prepared.rollback());
988
+ setCurrentRouteLocation(previousPathname, previousHref);
989
+ try {
990
+ restoreHistory?.();
991
+ } catch (error) {
992
+ errors.push(error);
667
993
  }
668
- });
994
+ reportRouteCleanupErrors(errors);
995
+ };
669
996
  try {
670
- for (const replaceLifetime of [true, false]) {
671
- for (const root of roots) {
672
- if (isStaleRouteRequest(requestId)) return;
673
- if (root.replaceLifetime === replaceLifetime) root.prepared.apply();
674
- }
675
- if (isStaleRouteRequest(requestId)) return;
676
- flushRuntimeScheduler();
677
- if (isStaleRouteRequest(requestId)) return;
997
+ for (const replaceLifetime of [true, false]) for (const root of roots) if (root.replaceLifetime === replaceLifetime) root.prepared.apply();
998
+ } catch (error) {
999
+ rollback();
1000
+ logger.error("[Askr] navigation failed:", error);
1001
+ throw error;
1002
+ }
1003
+ if (isStaleRouteRequest(requestId)) {
1004
+ rollback();
1005
+ return;
1006
+ }
1007
+ const committedFailures = [];
1008
+ for (const root of roots) {
1009
+ const result = root.prepared.publish();
1010
+ if (result.aborted) {
1011
+ rollback();
1012
+ const failure = result.errors[0];
1013
+ logger.error("[Askr] navigation failed:", failure);
1014
+ throw failure;
678
1015
  }
679
- registerCommitParticipant({ publish() {
680
- for (const root of roots) syncAppRegistrationLocation(root.target.app, pathname, href);
681
- } });
682
- commitTransaction(transaction);
683
- if (completionFailure) throw completionFailure.error;
1016
+ committedFailures.push(...result.errors);
1017
+ syncAppRegistrationLocation(root.target.app, pathname, href);
1018
+ }
1019
+ for (const failure of committedFailures) reportUncaughtErrorLater(failure);
1020
+ const retired = [];
1021
+ for (const root of roots) retired.push(...root.prepared.retire());
1022
+ if (retired.length) reportUncaughtErrorLater(retired.length === 1 ? retired[0] : new AggregateError(retired, "Route cleanup failed"));
1023
+ flushSync();
1024
+ if (isStaleRouteRequest(requestId)) return;
1025
+ try {
1026
+ updateHistory();
1027
+ setCurrentRouteLocation(pathname, href);
1028
+ syncRegisteredRouteSnapshot();
1029
+ reconcileNavigationMetadata(targets);
1030
+ updateScroll();
684
1031
  } catch (error) {
685
1032
  logger.error("[Askr] navigation failed:", error);
686
1033
  throw error;
687
- } finally {
688
- discardTransaction(transaction);
689
1034
  }
690
1035
  }
691
1036
  //#endregion
@@ -708,29 +1053,43 @@ function ensureNavigationRegistryHost() {
708
1053
  navigationRegistryHosted = true;
709
1054
  configureNavigationRegistryHost({ cancelRouteRequests });
710
1055
  }
711
- /** Navigate the client-side router to `path` using the History API. */
712
- function navigate(path, options = {}) {
1056
+ /**
1057
+ * Navigate the client-side router using the History API.
1058
+ *
1059
+ * A string is a logical path: a root-relative path gains the registry
1060
+ * `basePath`. A typed destination from `to()` already carries its public href.
1061
+ * A target on another origin is handed to the browser.
1062
+ */
1063
+ function navigate(target, options = {}) {
1064
+ navigateToPublicHref(typeof target === "string" ? addLogicalRouteBasePath(target, getActiveRouteBasePath()) : target.href, options);
1065
+ }
1066
+ /** Navigate to a public (already mounted) href without adding the `basePath`. */
1067
+ function navigateToPublicHref(href, options = {}) {
713
1068
  ensureNavigationRegistryHost();
714
1069
  if (typeof window === "undefined") return;
715
1070
  prepareNavigationFocus();
716
- const targetPath = addRouteBasePath(path, getActiveRouteBasePath());
717
- const initialTarget = parseTargetUrl(targetPath);
1071
+ cancelHistoryReturn();
1072
+ const initialTarget = parseTargetUrl(href);
718
1073
  const redirectState = {
719
1074
  redirects: 0,
720
1075
  visited: /* @__PURE__ */ new Set([`${initialTarget.pathname}${initialTarget.search}${initialTarget.hash}`])
721
1076
  };
722
- if (isRuntimeSchedulerExecuting()) {
1077
+ if (isFlushing()) {
723
1078
  queueMicrotask(() => {
724
- navigateWithRedirectState(targetPath, options, redirectState);
1079
+ navigateWithRedirectState(href, options, redirectState);
725
1080
  });
726
1081
  return;
727
1082
  }
728
- navigateWithRedirectState(targetPath, options, redirectState);
1083
+ navigateWithRedirectState(href, options, redirectState);
729
1084
  }
730
1085
  function navigateWithRedirectState(path, options, redirectState) {
731
1086
  if (typeof window === "undefined") return;
732
1087
  const request = beginRouteRequest();
733
- const target = parseTargetUrl(path);
1088
+ const target = parseNavigationTarget(path);
1089
+ if (!isCurrentOrigin(target)) {
1090
+ loadDocument(target.href, getNavigationHistoryMode(options));
1091
+ return;
1092
+ }
734
1093
  const pathname = target.pathname;
735
1094
  const href = `${target.pathname}${target.search}${target.hash}`;
736
1095
  const resolvedTargets = resolveNavigationTargetsForApps(pathname, href, request.signal);
@@ -751,15 +1110,20 @@ function navigateWithRedirectState(path, options, redirectState) {
751
1110
  applyNavigationTargets(request.id, path, options, redirectState, pathname, href, resolvedTargets, navigateWithRedirectState);
752
1111
  }
753
1112
  function handlePopState(event) {
1113
+ if (consumeHistoryReturn(event.state)) {
1114
+ syncRegisteredRouteSnapshot();
1115
+ return;
1116
+ }
754
1117
  beginHistoryFocusRestoration();
755
1118
  const request = beginRouteRequest();
756
1119
  const previousHref = getCurrentHref();
1120
+ const historyIndex = landOnHistoryEntry(event.state);
757
1121
  const pathname = window.location.pathname;
758
1122
  const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;
759
1123
  if (!hasRegisteredApps()) return;
760
1124
  const applyResolved = (targets) => {
761
1125
  try {
762
- applyPopStateNavigationTargets(request.id, previousHref, { path: previousHref }, pathname, href, event.state, targets, navigate);
1126
+ applyPopStateNavigationTargets(request.id, previousHref, historyIndex, pathname, href, event.state, targets, navigateToPublicHref);
763
1127
  } catch (error) {
764
1128
  logger.error("[Askr] popstate navigation failed:", error);
765
1129
  }
@@ -771,6 +1135,7 @@ function handlePopState(event) {
771
1135
  }, (error) => {
772
1136
  if (isStaleRouteRequest(request.id)) return;
773
1137
  logger.error("[Askr] popstate navigation failed:", error);
1138
+ if (!returnToRenderedHistoryEntry()) reloadDocument();
774
1139
  });
775
1140
  return;
776
1141
  }
@@ -778,11 +1143,13 @@ function handlePopState(event) {
778
1143
  }
779
1144
  function initializeNavigation() {
780
1145
  ensureNavigationRegistryHost();
781
- if (typeof window === "undefined" || navigationInitialized) return;
1146
+ if (typeof window === "undefined") return;
1147
+ if (getRegisteredAppsSnapshot().length <= 1) initializeHistoryIndex();
1148
+ if (navigationInitialized) return;
782
1149
  navigationInitialized = true;
783
1150
  window.addEventListener("popstate", handlePopState);
784
1151
  document.addEventListener("pointerdown", captureNavigationFocus, true);
785
1152
  document.addEventListener("click", releaseNavigationFocusCapture);
786
1153
  }
787
1154
  //#endregion
788
- export { configureRootUpdateHost as _, serializeRouteMeta as a, getCurrentHref as c, parseTargetUrl as d, registerAppInstance as f, unregisterAppInstance as g, syncRegisteredRouteSnapshot as h, resolveRouteMeta as i, getRegisteredAppsSnapshot as l, syncAppRegistrationLocation as m, navigate as n, configureScrollRestoration as o, setCurrentRouteLocation as p, reconcileRouteMeta as r, saveScrollPosition as s, initializeNavigation as t, getWindowHref$1 as u };
1155
+ export { currentRoute as C, clearStagedAppRenderRouteLocation as D, configureRootUpdateHost as E, createAppRenderRuntime as O, unregisterAppInstance as S, setServerLocation as T, registerAppInstance as _, nextHistoryIndex as a, syncRegisteredRouteSnapshot as b, reconcileRouteMeta as c, configureScrollRestoration as d, saveScrollPosition as f, parseTargetUrl as g, getWindowHref$1 as h, commitHistoryIndex as i, stageAppRenderRouteLocation as k, resolveRouteMeta as l, getRegisteredAppsSnapshot as m, navigate as n, readHistoryIndex as o, getCurrentHref as p, navigateToPublicHref as r, loadDocument as s, initializeNavigation as t, serializeRouteMeta as u, setCurrentRouteLocation as v, onRouteChange as w, toDocumentUrl as x, syncAppRegistrationLocation as y };