@askrjs/askr 0.0.39 → 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bench/components/benchmark-row.d.ts.map +1 -1
- package/dist/bench/components/benchmark-row.js.map +1 -1
- package/dist/bench/components/benchmark-table.js.map +1 -1
- package/dist/benchmark.d.ts.map +1 -1
- package/dist/benchmark.js +2 -2
- package/dist/benchmark.js.map +1 -1
- package/dist/bin/askr-ssg.d.ts.map +1 -1
- package/dist/bin/askr-ssg.js.map +1 -1
- package/dist/boot/index.d.ts +9 -6
- package/dist/boot/index.d.ts.map +1 -1
- package/dist/boot/index.js +210 -75
- package/dist/boot/index.js.map +1 -1
- package/dist/common/attr-names.js +17 -0
- package/dist/common/attr-names.js.map +1 -0
- package/dist/common/component.d.ts.map +1 -1
- package/dist/common/control.d.ts.map +1 -1
- package/dist/common/control.js.map +1 -1
- package/dist/common/env.js.map +1 -1
- package/dist/common/jsx.d.ts.map +1 -1
- package/dist/common/jsx.js.map +1 -1
- package/dist/common/promise.js +8 -0
- package/dist/common/promise.js.map +1 -0
- package/dist/common/props.d.ts +286 -1
- package/dist/common/props.d.ts.map +1 -1
- package/dist/common/router-internal.js +6 -0
- package/dist/common/router-internal.js.map +1 -0
- package/dist/common/router.d.ts +45 -36
- package/dist/common/router.d.ts.map +1 -1
- package/dist/common/ssr-errors.d.ts.map +1 -1
- package/dist/common/ssr-errors.js.map +1 -1
- package/dist/common/ssr.d.ts +1 -0
- package/dist/common/ssr.d.ts.map +1 -1
- package/dist/common/ssr.js +6 -0
- package/dist/common/ssr.js.map +1 -0
- package/dist/common/vnode.d.ts +2 -1
- package/dist/common/vnode.d.ts.map +1 -1
- package/dist/common/vnode.js.map +1 -1
- package/dist/components/error-boundary.d.ts +7 -4
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/link.d.ts +2 -1
- package/dist/components/link.d.ts.map +1 -1
- package/dist/components/link.js +10 -1
- package/dist/components/link.js.map +1 -1
- package/dist/control/case.d.ts +4 -2
- package/dist/control/case.d.ts.map +1 -1
- package/dist/control/case.js.map +1 -1
- package/dist/control/for.d.ts +2 -1
- package/dist/control/for.d.ts.map +1 -1
- package/dist/control/for.js.map +1 -1
- package/dist/control/shared.d.ts +9 -0
- package/dist/control/shared.d.ts.map +1 -0
- package/dist/control/shared.js.map +1 -1
- package/dist/control/show.d.ts +4 -3
- package/dist/control/show.d.ts.map +1 -1
- package/dist/control/show.js.map +1 -1
- package/dist/data/index.d.ts +94 -14
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +65 -14
- package/dist/data/index.js.map +1 -1
- package/dist/dev/invariant.js.map +1 -1
- package/dist/dev/logger.js.map +1 -1
- package/dist/foundations/icon/icon.d.ts.map +1 -1
- package/dist/foundations/icon/icon.js.map +1 -1
- package/dist/foundations/icon/icon.types.d.ts.map +1 -1
- package/dist/foundations/interactions/dismissable.d.ts +9 -3
- package/dist/foundations/interactions/dismissable.d.ts.map +1 -1
- package/dist/foundations/interactions/dismissable.js.map +1 -1
- package/dist/foundations/interactions/focusable.d.ts.map +1 -1
- package/dist/foundations/interactions/focusable.js.map +1 -1
- package/dist/foundations/interactions/hoverable.d.ts.map +1 -1
- package/dist/foundations/interactions/hoverable.js.map +1 -1
- package/dist/foundations/interactions/interaction-policy.d.ts +1 -1
- package/dist/foundations/interactions/interaction-policy.d.ts.map +1 -1
- package/dist/foundations/interactions/interaction-policy.js +3 -3
- package/dist/foundations/interactions/interaction-policy.js.map +1 -1
- package/dist/foundations/interactions/pressable.d.ts.map +1 -1
- package/dist/foundations/interactions/pressable.js.map +1 -1
- package/dist/foundations/interactions/roving-focus.d.ts.map +1 -1
- package/dist/foundations/interactions/roving-focus.js +8 -8
- package/dist/foundations/interactions/roving-focus.js.map +1 -1
- package/dist/foundations/state/controllable.d.ts.map +1 -1
- package/dist/foundations/state/controllable.js.map +1 -1
- package/dist/foundations/structures/collection.d.ts.map +1 -1
- package/dist/foundations/structures/collection.js.map +1 -1
- package/dist/foundations/structures/layer.d.ts.map +1 -1
- package/dist/foundations/structures/layer.js.map +1 -1
- package/dist/foundations/structures/layout.d.ts +4 -2
- package/dist/foundations/structures/layout.d.ts.map +1 -1
- package/dist/foundations/structures/layout.js.map +1 -1
- package/dist/foundations/structures/portal.d.ts +9 -25
- package/dist/foundations/structures/portal.d.ts.map +1 -1
- package/dist/foundations/structures/portal.js +98 -17
- package/dist/foundations/structures/portal.js.map +1 -1
- package/dist/foundations/structures/presence.d.ts +4 -3
- package/dist/foundations/structures/presence.d.ts.map +1 -1
- package/dist/foundations/structures/presence.js +2 -2
- package/dist/foundations/structures/presence.js.map +1 -1
- package/dist/foundations/structures/slot.d.ts +2 -1
- package/dist/foundations/structures/slot.d.ts.map +1 -1
- package/dist/foundations/structures/slot.js.map +1 -1
- package/dist/foundations/utilities/aria.d.ts.map +1 -1
- package/dist/foundations/utilities/aria.js.map +1 -1
- package/dist/foundations/utilities/compose-handlers.d.ts.map +1 -1
- package/dist/foundations/utilities/compose-handlers.js.map +1 -1
- package/dist/foundations/utilities/compose-ref.d.ts.map +1 -1
- package/dist/foundations/utilities/compose-ref.js.map +1 -1
- package/dist/foundations/utilities/event-types.d.ts.map +1 -1
- package/dist/foundations/utilities/merge-props.d.ts.map +1 -1
- package/dist/foundations/utilities/merge-props.js.map +1 -1
- package/dist/foundations/utilities/use-id.d.ts.map +1 -1
- package/dist/foundations/utilities/use-id.js.map +1 -1
- package/dist/fx/fx.d.ts.map +1 -1
- package/dist/fx/fx.js.map +1 -1
- package/dist/fx/noop.js.map +1 -1
- package/dist/fx/timing.d.ts +5 -4
- package/dist/fx/timing.d.ts.map +1 -1
- package/dist/fx/timing.js +7 -4
- package/dist/fx/timing.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/jsx/types.d.ts +3 -3
- package/dist/jsx/types.d.ts.map +1 -1
- package/dist/jsx/utils.d.ts.map +1 -1
- package/dist/jsx/utils.js.map +1 -1
- package/dist/jsx-dev-runtime.d.ts +6 -2
- package/dist/jsx-dev-runtime.d.ts.map +1 -1
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.d.ts +16 -6
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/renderer/children.js.map +1 -1
- package/dist/renderer/cleanup.js +40 -13
- package/dist/renderer/cleanup.js.map +1 -1
- package/dist/renderer/dom.js +163 -63
- package/dist/renderer/dom.js.map +1 -1
- package/dist/renderer/evaluate.js +9 -6
- package/dist/renderer/evaluate.js.map +1 -1
- package/dist/renderer/fastpath.js.map +1 -1
- package/dist/renderer/for-commit.js +1 -0
- package/dist/renderer/for-commit.js.map +1 -1
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/keyed.js.map +1 -1
- package/dist/renderer/reconcile.js +34 -0
- package/dist/renderer/reconcile.js.map +1 -1
- package/dist/renderer/utils.js +34 -3
- package/dist/renderer/utils.js.map +1 -1
- package/dist/router/index.d.ts +2 -2
- package/dist/router/match.js +16 -2
- package/dist/router/match.js.map +1 -1
- package/dist/router/navigate.d.ts +9 -1
- package/dist/router/navigate.d.ts.map +1 -1
- package/dist/router/navigate.js +235 -81
- package/dist/router/navigate.js.map +1 -1
- package/dist/router/policy.d.ts +7 -7
- package/dist/router/policy.d.ts.map +1 -1
- package/dist/router/policy.js +5 -7
- package/dist/router/policy.js.map +1 -1
- package/dist/router/route-context.js.map +1 -1
- package/dist/router/route.d.ts +20 -9
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/route.js +95 -29
- package/dist/router/route.js.map +1 -1
- package/dist/runtime/child-scope.d.ts.map +1 -1
- package/dist/runtime/child-scope.js +5 -1
- package/dist/runtime/child-scope.js.map +1 -1
- package/dist/runtime/component.d.ts +5 -2
- package/dist/runtime/component.d.ts.map +1 -1
- package/dist/runtime/component.js +85 -25
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/context.d.ts +5 -2
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/context.js +5 -2
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.d.ts.map +1 -1
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/derive.d.ts.map +1 -1
- package/dist/runtime/derive.js +3 -1
- package/dist/runtime/derive.js.map +1 -1
- package/dist/runtime/dev-namespace.js.map +1 -1
- package/dist/runtime/effect.d.ts.map +1 -1
- package/dist/runtime/effect.js +17 -11
- package/dist/runtime/effect.js.map +1 -1
- package/dist/runtime/events.js +43 -1
- package/dist/runtime/events.js.map +1 -1
- package/dist/runtime/execution-model.js.map +1 -1
- package/dist/runtime/fastlane.js +2 -23
- package/dist/runtime/fastlane.js.map +1 -1
- package/dist/runtime/for-bench.d.ts.map +1 -1
- package/dist/runtime/for-bench.js.map +1 -1
- package/dist/runtime/for.d.ts +1 -1
- package/dist/runtime/for.d.ts.map +1 -1
- package/dist/runtime/for.js +36 -12
- package/dist/runtime/for.js.map +1 -1
- package/dist/runtime/operations.d.ts +3 -13
- package/dist/runtime/operations.d.ts.map +1 -1
- package/dist/runtime/operations.js +47 -39
- package/dist/runtime/operations.js.map +1 -1
- package/dist/runtime/perf-metrics.js.map +1 -1
- package/dist/runtime/readable.d.ts +1 -0
- package/dist/runtime/readable.d.ts.map +1 -1
- package/dist/runtime/readable.js +6 -1
- package/dist/runtime/readable.js.map +1 -1
- package/dist/runtime/resource-cell.js +13 -2
- package/dist/runtime/resource-cell.js.map +1 -1
- package/dist/runtime/scheduler.d.ts.map +1 -1
- package/dist/runtime/scheduler.js +8 -4
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/selector.d.ts.map +1 -1
- package/dist/runtime/selector.js +8 -2
- package/dist/runtime/selector.js.map +1 -1
- package/dist/runtime/snapshot-source.d.ts.map +1 -1
- package/dist/runtime/snapshot-source.js.map +1 -1
- package/dist/runtime/ssr-bridge.js.map +1 -1
- package/dist/runtime/state.d.ts +4 -3
- package/dist/runtime/state.d.ts.map +1 -1
- package/dist/runtime/state.js.map +1 -1
- package/dist/ssg/batch-render.js +4 -8
- package/dist/ssg/batch-render.js.map +1 -1
- package/dist/ssg/create-static-gen.d.ts +10 -2
- package/dist/ssg/create-static-gen.d.ts.map +1 -1
- package/dist/ssg/create-static-gen.js +5 -4
- package/dist/ssg/create-static-gen.js.map +1 -1
- package/dist/ssg/generate-metadata.js.map +1 -1
- package/dist/ssg/incremental-manifest.js.map +1 -1
- package/dist/ssg/resolve-ssg-data.js +54 -12
- package/dist/ssg/resolve-ssg-data.js.map +1 -1
- package/dist/ssg/route-utils.js.map +1 -1
- package/dist/ssg/types.d.ts +8 -7
- package/dist/ssg/types.d.ts.map +1 -1
- package/dist/ssg/write-static-files.js.map +1 -1
- package/dist/ssr/attrs.js +3 -2
- package/dist/ssr/attrs.js.map +1 -1
- package/dist/ssr/context.d.ts +1 -0
- package/dist/ssr/context.d.ts.map +1 -1
- package/dist/ssr/context.js +1 -0
- package/dist/ssr/context.js.map +1 -1
- package/dist/ssr/errors.d.ts.map +1 -1
- package/dist/ssr/escape.js.map +1 -1
- package/dist/ssr/index.d.ts +8 -11
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/ssr/index.js +55 -29
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/render-keys.js +15 -4
- package/dist/ssr/render-keys.js.map +1 -1
- package/dist/ssr/render-resolved.d.ts +20 -0
- package/dist/ssr/render-resolved.d.ts.map +1 -0
- package/dist/ssr/render-resolved.js +33 -0
- package/dist/ssr/render-resolved.js.map +1 -0
- package/dist/ssr/sink.js.map +1 -1
- package/dist/ssr/types.d.ts.map +1 -1
- package/dist/ssr/verify-hydration.js +5 -6
- package/dist/ssr/verify-hydration.js.map +1 -1
- package/package.json +13 -5
package/dist/router/navigate.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { isDevelopmentEnvironment, isProductionEnvironment } from "../common/env.js";
|
|
2
2
|
import { logger } from "../dev/logger.js";
|
|
3
3
|
import { ELEMENT_TYPE, Fragment } from "../common/jsx.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { isPromiseLike } from "../common/promise.js";
|
|
5
|
+
import { cleanupComponent, mountComponent } from "../runtime/component.js";
|
|
6
|
+
import { cleanupInstancesUnder } from "../renderer/cleanup.js";
|
|
7
|
+
import { lockRouteRegistration, resolveRouteFromRoutes, resolveRouteRequest, syncCurrentRouteSnapshot } from "./route.js";
|
|
6
8
|
import "../jsx/index.js";
|
|
7
|
-
import { DefaultPortal } from "../foundations/structures/portal.js";
|
|
9
|
+
import { DefaultPortal, clearDefaultPortalForInstance } from "../foundations/structures/portal.js";
|
|
8
10
|
//#region src/router/navigate.ts
|
|
9
11
|
/**
|
|
10
12
|
* Client-side navigation with History API
|
|
@@ -13,6 +15,14 @@ let currentInstance = null;
|
|
|
13
15
|
let currentPathname = "/";
|
|
14
16
|
let currentHref = "/";
|
|
15
17
|
let navigationInitialized = false;
|
|
18
|
+
let activeRouteRequestId = 0;
|
|
19
|
+
let activeRouteRequestController = null;
|
|
20
|
+
const MAX_NAVIGATION_REDIRECTS = 20;
|
|
21
|
+
const registeredApps = [];
|
|
22
|
+
function syncAppRegistrationLocation(app, pathname, href) {
|
|
23
|
+
app.pathname = pathname;
|
|
24
|
+
app.href = href;
|
|
25
|
+
}
|
|
16
26
|
const DEFAULT_SCROLL_RESTORATION = {
|
|
17
27
|
enabled: true,
|
|
18
28
|
navigation: "top",
|
|
@@ -111,8 +121,24 @@ function parseTargetUrl(path) {
|
|
|
111
121
|
function isRenderResult(result) {
|
|
112
122
|
return result !== null && result.kind === "render";
|
|
113
123
|
}
|
|
114
|
-
function
|
|
115
|
-
return
|
|
124
|
+
function getRedirectHistoryMode(replace) {
|
|
125
|
+
return replace === false ? "push" : "replace";
|
|
126
|
+
}
|
|
127
|
+
function getNavigationHistoryMode(options) {
|
|
128
|
+
if (options.history) return options.history;
|
|
129
|
+
return options.replace ? "replace" : "push";
|
|
130
|
+
}
|
|
131
|
+
function beginRouteRequest() {
|
|
132
|
+
activeRouteRequestId += 1;
|
|
133
|
+
activeRouteRequestController?.abort();
|
|
134
|
+
activeRouteRequestController = new AbortController();
|
|
135
|
+
return {
|
|
136
|
+
id: activeRouteRequestId,
|
|
137
|
+
signal: activeRouteRequestController.signal
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function isStaleRouteRequest(requestId) {
|
|
141
|
+
return requestId !== activeRouteRequestId;
|
|
116
142
|
}
|
|
117
143
|
function createDeniedResolvedRoute(status) {
|
|
118
144
|
return {
|
|
@@ -130,6 +156,7 @@ function bindResolvedRouteHandler(resolved) {
|
|
|
130
156
|
function wrapRootRouteHandler(componentFn) {
|
|
131
157
|
const wrappedFn = (props, ctx) => {
|
|
132
158
|
const out = componentFn(props, ctx);
|
|
159
|
+
if (isPromiseLike(out)) throw new Error("Async components are not supported. Components must return synchronously.");
|
|
133
160
|
const portalVNode = {
|
|
134
161
|
$$typeof: ELEMENT_TYPE,
|
|
135
162
|
type: DefaultPortal,
|
|
@@ -145,139 +172,266 @@ function wrapRootRouteHandler(componentFn) {
|
|
|
145
172
|
Object.defineProperty(wrappedFn, "name", { value: componentFn.name || "Component" });
|
|
146
173
|
return wrappedFn;
|
|
147
174
|
}
|
|
148
|
-
function
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
175
|
+
function cleanupRouteOwnership(instance) {
|
|
176
|
+
const cleanupErrors = [];
|
|
177
|
+
const children = instance.target ? Array.from(instance.target.childNodes) : [];
|
|
178
|
+
for (const child of children) try {
|
|
179
|
+
cleanupInstancesUnder(child, { strict: instance.cleanupStrict });
|
|
180
|
+
} catch (error) {
|
|
181
|
+
cleanupErrors.push(error);
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
cleanupComponent(instance);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
cleanupErrors.push(error);
|
|
187
|
+
}
|
|
188
|
+
if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, "Route cleanup failed");
|
|
189
|
+
}
|
|
190
|
+
function remountResolvedRoute(instance, resolved, pathname, href) {
|
|
191
|
+
clearDefaultPortalForInstance(instance);
|
|
192
|
+
cleanupRouteOwnership(instance);
|
|
193
|
+
instance.fn = wrapRootRouteHandler(bindResolvedRouteHandler(resolved));
|
|
194
|
+
instance.props = {};
|
|
195
|
+
instance.stateValues = [];
|
|
196
|
+
instance.expectedStateIndices = [];
|
|
197
|
+
instance.firstRenderComplete = false;
|
|
198
|
+
instance.stateIndexCheck = -1;
|
|
199
|
+
instance.evaluationGeneration++;
|
|
200
|
+
instance.notifyUpdate = null;
|
|
201
|
+
instance.mountOperations = [];
|
|
202
|
+
instance.cleanupFns = [];
|
|
203
|
+
instance._placeholder = void 0;
|
|
204
|
+
instance.hasPendingUpdate = false;
|
|
205
|
+
instance._currentRenderToken = void 0;
|
|
206
|
+
instance.lastRenderToken = 0;
|
|
207
|
+
instance._pendingReadSources = void 0;
|
|
208
|
+
instance._lastReadSources = void 0;
|
|
209
|
+
instance.abortController = null;
|
|
210
|
+
mountComponent(instance);
|
|
163
211
|
currentPathname = pathname;
|
|
164
212
|
currentHref = href;
|
|
165
213
|
return true;
|
|
166
214
|
}
|
|
167
|
-
function rerenderResolvedRoute(pathname, href) {
|
|
168
|
-
if (!currentInstance) return false;
|
|
169
|
-
if (!resolveRoute(pathname)) return false;
|
|
215
|
+
function rerenderResolvedRoute(instance, pathname, href) {
|
|
170
216
|
currentPathname = pathname;
|
|
171
217
|
currentHref = href;
|
|
172
|
-
|
|
218
|
+
instance._enqueueRun?.();
|
|
173
219
|
return true;
|
|
174
220
|
}
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
};
|
|
221
|
+
function resolveAppRouteRequest(app, pathname, href, signal) {
|
|
222
|
+
if (app.routes) {
|
|
223
|
+
const resolved = resolveRouteFromRoutes(pathname, app.routes);
|
|
224
|
+
if (!resolved) return null;
|
|
225
|
+
return {
|
|
226
|
+
kind: "render",
|
|
227
|
+
handler: resolved.handler,
|
|
228
|
+
params: resolved.params
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return resolveRouteRequest(href, {
|
|
232
|
+
manifest: app.manifest,
|
|
233
|
+
auth: app.auth,
|
|
234
|
+
signal
|
|
235
|
+
});
|
|
190
236
|
}
|
|
191
|
-
function
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
|
|
237
|
+
function resolveNavigationTargetsForApps(pathname, href, signal) {
|
|
238
|
+
const apps = [...registeredApps];
|
|
239
|
+
if (apps.length === 1) {
|
|
240
|
+
const app = apps[0];
|
|
241
|
+
const resolved = resolveAppRouteRequest(app, pathname, href, signal);
|
|
242
|
+
if (isPromiseLike(resolved)) return Promise.resolve(resolved).then((next) => [{
|
|
243
|
+
app,
|
|
244
|
+
resolved: next
|
|
245
|
+
}]);
|
|
246
|
+
return [{
|
|
247
|
+
app,
|
|
248
|
+
resolved
|
|
249
|
+
}];
|
|
250
|
+
}
|
|
251
|
+
const syncTargets = [];
|
|
252
|
+
const pendingTargets = [];
|
|
253
|
+
for (const app of apps) {
|
|
254
|
+
const resolved = resolveAppRouteRequest(app, pathname, href, signal);
|
|
255
|
+
if (isPromiseLike(resolved)) {
|
|
256
|
+
pendingTargets.push(Promise.resolve(resolved).then((next) => ({
|
|
257
|
+
app,
|
|
258
|
+
resolved: next
|
|
259
|
+
})));
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
syncTargets.push({
|
|
263
|
+
app,
|
|
264
|
+
resolved
|
|
265
|
+
});
|
|
198
266
|
}
|
|
199
|
-
if (
|
|
267
|
+
if (pendingTargets.length === 0) return syncTargets;
|
|
268
|
+
return Promise.all([...syncTargets.map((target) => Promise.resolve(target)), ...pendingTargets]);
|
|
269
|
+
}
|
|
270
|
+
function applyNavigationTargets(path, options, redirectState, pathname, href, targets) {
|
|
271
|
+
const previousPathname = currentPathname;
|
|
272
|
+
for (const target of targets) {
|
|
273
|
+
const resolved = target.resolved;
|
|
274
|
+
if (!resolved || resolved.kind !== "redirect") continue;
|
|
200
275
|
const redirectTarget = parseTargetUrl(resolved.to);
|
|
201
276
|
const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;
|
|
202
277
|
if (redirectHref === href) {
|
|
203
278
|
if (isDevelopmentEnvironment()) logger.warn(`Navigation guard redirected to the same path: ${redirectHref}`);
|
|
204
279
|
return;
|
|
205
280
|
}
|
|
206
|
-
|
|
281
|
+
if (redirectState.visited.has(redirectHref)) throw new Error(`[Askr] Navigation redirect cycle detected at ${redirectHref}.`);
|
|
282
|
+
if (redirectState.redirects >= MAX_NAVIGATION_REDIRECTS) throw new Error(`[Askr] Navigation redirect limit exceeded (${MAX_NAVIGATION_REDIRECTS}).`);
|
|
283
|
+
redirectState.visited.add(redirectHref);
|
|
284
|
+
redirectState.redirects++;
|
|
285
|
+
navigateWithRedirectState(redirectHref, { history: getRedirectHistoryMode(resolved.replace) }, redirectState);
|
|
207
286
|
return;
|
|
208
287
|
}
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
288
|
+
const matchedTargets = targets.filter((target) => target.resolved !== null);
|
|
289
|
+
if (matchedTargets.length === 0) {
|
|
290
|
+
if (isDevelopmentEnvironment()) logger.warn(`No route found for path: ${path}`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
saveScrollPosition(currentHref);
|
|
294
|
+
const historyMethod = getNavigationHistoryMode(options) === "replace" ? "replaceState" : "pushState";
|
|
214
295
|
window.history[historyMethod]({ path: href }, "", href);
|
|
215
296
|
syncCurrentRouteSnapshot(window.location.pathname, window.location.search, window.location.hash);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
297
|
+
for (const target of matchedTargets) {
|
|
298
|
+
const resolved = target.resolved;
|
|
299
|
+
if (resolved.kind === "redirect") continue;
|
|
300
|
+
if (pathname === target.app.pathname && isRenderResult(resolved)) {
|
|
301
|
+
rerenderResolvedRoute(target.app.instance, pathname, href);
|
|
302
|
+
syncAppRegistrationLocation(target.app, pathname, href);
|
|
303
|
+
continue;
|
|
220
304
|
}
|
|
221
|
-
remountResolvedRoute(
|
|
222
|
-
|
|
305
|
+
remountResolvedRoute(target.app.instance, resolved.kind === "deny" ? createDeniedResolvedRoute(resolved.status) : {
|
|
306
|
+
handler: resolved.handler,
|
|
307
|
+
params: resolved.params
|
|
308
|
+
}, pathname, href);
|
|
309
|
+
syncAppRegistrationLocation(target.app, pathname, href);
|
|
223
310
|
}
|
|
311
|
+
if (pathname !== previousPathname) applyNavigationScroll(options.scroll ?? scrollRestorationOptions.navigation);
|
|
224
312
|
}
|
|
225
313
|
/** Register the current app instance (called by createSPA/hydrateSPA). */
|
|
226
|
-
function registerAppInstance(instance, path) {
|
|
314
|
+
function registerAppInstance(instance, path, source = {}) {
|
|
315
|
+
const existingIndex = registeredApps.findIndex((app) => app.instance === instance);
|
|
316
|
+
const registration = {
|
|
317
|
+
instance,
|
|
318
|
+
pathname: path,
|
|
319
|
+
href: getWindowHref(),
|
|
320
|
+
...source
|
|
321
|
+
};
|
|
322
|
+
if (existingIndex >= 0) registeredApps[existingIndex] = registration;
|
|
323
|
+
else registeredApps.push(registration);
|
|
227
324
|
currentInstance = instance;
|
|
228
325
|
currentPathname = path;
|
|
229
326
|
currentHref = getWindowHref();
|
|
230
327
|
syncCurrentRouteSnapshot(window.location.pathname, window.location.search, window.location.hash);
|
|
231
328
|
if (isProductionEnvironment()) lockRouteRegistration();
|
|
232
329
|
}
|
|
330
|
+
function unregisterAppInstance(instance) {
|
|
331
|
+
const existingIndex = registeredApps.findIndex((app) => app.instance === instance);
|
|
332
|
+
if (existingIndex >= 0) registeredApps.splice(existingIndex, 1);
|
|
333
|
+
if (currentInstance !== instance) return;
|
|
334
|
+
const nextApp = registeredApps.length > 0 ? registeredApps[registeredApps.length - 1] : null;
|
|
335
|
+
currentInstance = nextApp?.instance ?? null;
|
|
336
|
+
if (nextApp) {
|
|
337
|
+
currentPathname = nextApp.pathname;
|
|
338
|
+
currentHref = nextApp.href;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
activeRouteRequestId += 1;
|
|
342
|
+
activeRouteRequestController?.abort();
|
|
343
|
+
activeRouteRequestController = null;
|
|
344
|
+
currentPathname = "/";
|
|
345
|
+
currentHref = "/";
|
|
346
|
+
}
|
|
233
347
|
/**
|
|
234
348
|
* Navigate to a new path
|
|
235
349
|
* Updates URL, resolves route, and re-mounts app with new handler
|
|
236
350
|
*/
|
|
237
351
|
function navigate(path, options = {}) {
|
|
238
352
|
if (typeof window === "undefined") return;
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
353
|
+
const initialTarget = parseTargetUrl(path);
|
|
354
|
+
navigateWithRedirectState(path, options, {
|
|
355
|
+
redirects: 0,
|
|
356
|
+
visited: new Set([`${initialTarget.pathname}${initialTarget.search}${initialTarget.hash}`])
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function navigateWithRedirectState(path, options, redirectState) {
|
|
360
|
+
if (typeof window === "undefined") return;
|
|
361
|
+
const request = beginRouteRequest();
|
|
362
|
+
const target = parseTargetUrl(path);
|
|
363
|
+
const pathname = target.pathname;
|
|
364
|
+
const href = `${target.pathname}${target.search}${target.hash}`;
|
|
365
|
+
const resolvedTargets = resolveNavigationTargetsForApps(pathname, href, request.signal);
|
|
366
|
+
if (isPromiseLike(resolvedTargets)) {
|
|
367
|
+
Promise.resolve(resolvedTargets).then((targets) => {
|
|
368
|
+
if (isStaleRouteRequest(request.id)) return;
|
|
369
|
+
try {
|
|
370
|
+
applyNavigationTargets(path, options, redirectState, pathname, href, targets);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
logger.error("[Askr] navigation failed:", error);
|
|
373
|
+
}
|
|
374
|
+
}, (error) => {
|
|
375
|
+
logger.error("[Askr] navigation failed:", error);
|
|
376
|
+
});
|
|
242
377
|
return;
|
|
243
378
|
}
|
|
244
|
-
|
|
379
|
+
applyNavigationTargets(path, options, redirectState, pathname, href, resolvedTargets);
|
|
245
380
|
}
|
|
246
381
|
/**
|
|
247
382
|
* Handle browser back/forward buttons
|
|
248
383
|
*/
|
|
249
384
|
function handlePopState(_event) {
|
|
385
|
+
const request = beginRouteRequest();
|
|
250
386
|
const previousHref = currentHref;
|
|
251
387
|
const pathname = window.location.pathname;
|
|
252
388
|
const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
253
389
|
saveScrollPosition(previousHref);
|
|
254
|
-
if (
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
390
|
+
if (registeredApps.length === 0) return;
|
|
391
|
+
const applyResolved = (targets) => {
|
|
392
|
+
if (isStaleRouteRequest(request.id)) return;
|
|
393
|
+
const matchedTargets = targets.filter((target) => target.resolved !== null);
|
|
394
|
+
if (matchedTargets.length === 0) {
|
|
258
395
|
if (isDevelopmentEnvironment()) logger.warn(`No route found for path: ${pathname}`);
|
|
259
396
|
return;
|
|
260
397
|
}
|
|
261
|
-
|
|
262
|
-
|
|
398
|
+
for (const target of matchedTargets) {
|
|
399
|
+
const resolved = target.resolved;
|
|
400
|
+
if (resolved.kind !== "redirect") continue;
|
|
401
|
+
navigate(resolved.to, { history: getRedirectHistoryMode(resolved.replace) });
|
|
263
402
|
return;
|
|
264
403
|
}
|
|
265
404
|
syncCurrentRouteSnapshot(window.location.pathname, window.location.search, window.location.hash);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
405
|
+
for (const target of matchedTargets) {
|
|
406
|
+
const resolved = target.resolved;
|
|
407
|
+
if (pathname === target.app.pathname && isRenderResult(resolved)) {
|
|
408
|
+
rerenderResolvedRoute(target.app.instance, pathname, href);
|
|
409
|
+
syncAppRegistrationLocation(target.app, pathname, href);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (resolved.kind === "redirect") continue;
|
|
413
|
+
remountResolvedRoute(target.app.instance, resolved.kind === "deny" ? createDeniedResolvedRoute(resolved.status) : {
|
|
414
|
+
handler: resolved.handler,
|
|
415
|
+
params: resolved.params
|
|
416
|
+
}, pathname, href);
|
|
417
|
+
syncAppRegistrationLocation(target.app, pathname, href);
|
|
269
418
|
}
|
|
270
|
-
remountResolvedRoute(resolved.kind === "deny" ? createDeniedResolvedRoute(resolved.status) : {
|
|
271
|
-
handler: resolved.handler,
|
|
272
|
-
params: resolved.params
|
|
273
|
-
}, pathname, href);
|
|
274
419
|
applyHistoryScroll(href, _event.state);
|
|
275
420
|
};
|
|
276
|
-
|
|
277
|
-
|
|
421
|
+
const resolvedTargets = resolveNavigationTargetsForApps(pathname, href, request.signal);
|
|
422
|
+
if (isPromiseLike(resolvedTargets)) {
|
|
423
|
+
Promise.resolve(resolvedTargets).then((next) => {
|
|
424
|
+
try {
|
|
425
|
+
applyResolved(next);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
logger.error("[Askr] popstate navigation failed:", error);
|
|
428
|
+
}
|
|
429
|
+
}, (error) => {
|
|
430
|
+
logger.error("[Askr] popstate navigation failed:", error);
|
|
431
|
+
});
|
|
278
432
|
return;
|
|
279
433
|
}
|
|
280
|
-
applyResolved(
|
|
434
|
+
applyResolved(resolvedTargets);
|
|
281
435
|
}
|
|
282
436
|
/**
|
|
283
437
|
* Setup popstate listener for browser navigation
|
|
@@ -288,6 +442,6 @@ function initializeNavigation() {
|
|
|
288
442
|
window.addEventListener("popstate", handlePopState);
|
|
289
443
|
}
|
|
290
444
|
//#endregion
|
|
291
|
-
export { configureScrollRestoration, initializeNavigation, navigate, registerAppInstance };
|
|
445
|
+
export { configureScrollRestoration, initializeNavigation, navigate, registerAppInstance, unregisterAppInstance };
|
|
292
446
|
|
|
293
447
|
//# sourceMappingURL=navigate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigate.js","names":[],"sources":["../../src/router/navigate.ts"],"sourcesContent":["/**\n * Client-side navigation with History API\n */\n\nimport {\n resolveRoute,\n resolveRouteRequest,\n lockRouteRegistration,\n syncCurrentRouteSnapshot,\n type ResolvedRoute,\n} from './route';\nimport { mountComponent, type ComponentInstance } from '../runtime/component';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport type { RouteRenderResult, RouteRequestResult } from '../common/router';\nimport { Fragment, ELEMENT_TYPE } from '../jsx';\nimport { DefaultPortal } from '../foundations/structures/portal';\n\n// Global app state for navigation\nlet currentInstance: ComponentInstance | null = null;\nlet currentPathname = '/';\nlet currentHref = '/';\nlet navigationInitialized = false;\n\nexport type NavigationScrollBehavior = 'top' | 'preserve';\nexport type HistoryScrollBehavior = 'restore' | 'top' | 'preserve';\n\nexport type ScrollRestorationOptions = {\n navigation?: NavigationScrollBehavior;\n history?: HistoryScrollBehavior;\n};\n\ntype NormalizedScrollRestorationOptions = {\n enabled: boolean;\n navigation: NavigationScrollBehavior;\n history: HistoryScrollBehavior;\n};\n\nconst DEFAULT_SCROLL_RESTORATION: NormalizedScrollRestorationOptions = {\n enabled: true,\n navigation: 'top',\n history: 'restore',\n};\n\nlet scrollRestorationOptions: NormalizedScrollRestorationOptions = {\n ...DEFAULT_SCROLL_RESTORATION,\n};\n\nconst scrollPositions = new Map<string, { x: number; y: number }>();\n\nexport type NavigateOptions = {\n history?: 'push' | 'replace';\n scroll?: NavigationScrollBehavior;\n};\n\nfunction getWindowHref(): string {\n if (typeof window === 'undefined') {\n return currentHref;\n }\n\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\nfunction normalizeScrollRestorationOptions(\n options?: boolean | ScrollRestorationOptions\n): NormalizedScrollRestorationOptions {\n if (options === false) {\n return {\n enabled: false,\n navigation: DEFAULT_SCROLL_RESTORATION.navigation,\n history: DEFAULT_SCROLL_RESTORATION.history,\n };\n }\n\n if (options === true || options === undefined) {\n return { ...DEFAULT_SCROLL_RESTORATION };\n }\n\n return {\n enabled: true,\n navigation: options.navigation ?? DEFAULT_SCROLL_RESTORATION.navigation,\n history: options.history ?? DEFAULT_SCROLL_RESTORATION.history,\n };\n}\n\nfunction readScrollPosition(): { x: number; y: number } {\n if (typeof window === 'undefined') {\n return { x: 0, y: 0 };\n }\n\n const x =\n typeof window.scrollX === 'number'\n ? window.scrollX\n : typeof window.pageXOffset === 'number'\n ? window.pageXOffset\n : 0;\n const y =\n typeof window.scrollY === 'number'\n ? window.scrollY\n : typeof window.pageYOffset === 'number'\n ? window.pageYOffset\n : 0;\n\n return { x, y };\n}\n\nfunction writeHistoryScrollPosition(\n href: string,\n position: { x: number; y: number }\n): void {\n if (typeof window === 'undefined' || getWindowHref() !== href) {\n return;\n }\n if (typeof window.history?.replaceState !== 'function') {\n return;\n }\n const state =\n window.history.state && typeof window.history.state === 'object'\n ? window.history.state\n : {};\n\n window.history.replaceState(\n {\n ...state,\n path: href,\n scroll: position,\n },\n '',\n href\n );\n}\n\nfunction saveScrollPosition(href: string): void {\n if (!scrollRestorationOptions.enabled || typeof window === 'undefined') {\n return;\n }\n\n const position = readScrollPosition();\n scrollPositions.set(href, position);\n writeHistoryScrollPosition(href, position);\n}\n\nfunction scrollToPosition(position: { x: number; y: number }): void {\n if (typeof window === 'undefined' || typeof window.scrollTo !== 'function') {\n return;\n }\n\n window.scrollTo(position.x, position.y);\n}\n\nfunction applyNavigationScroll(behavior: NavigationScrollBehavior): void {\n if (!scrollRestorationOptions.enabled || behavior === 'preserve') {\n return;\n }\n\n scrollToPosition({ x: 0, y: 0 });\n}\n\nfunction applyHistoryScroll(href: string, state: PopStateEvent['state']): void {\n if (!scrollRestorationOptions.enabled) {\n return;\n }\n\n if (scrollRestorationOptions.history === 'preserve') {\n return;\n }\n\n if (scrollRestorationOptions.history === 'top') {\n scrollToPosition({ x: 0, y: 0 });\n return;\n }\n\n const fromState =\n state && typeof state === 'object' && 'scroll' in state\n ? (state.scroll as { x?: unknown; y?: unknown })\n : undefined;\n const saved =\n fromState &&\n typeof fromState.x === 'number' &&\n typeof fromState.y === 'number'\n ? { x: fromState.x, y: fromState.y }\n : scrollPositions.get(href);\n\n scrollToPosition(saved ?? { x: 0, y: 0 });\n}\n\nexport function configureScrollRestoration(\n options?: boolean | ScrollRestorationOptions\n): void {\n scrollRestorationOptions = normalizeScrollRestorationOptions(options);\n\n if (typeof window === 'undefined') {\n return;\n }\n\n if ('scrollRestoration' in window.history) {\n try {\n window.history.scrollRestoration = scrollRestorationOptions.enabled\n ? 'manual'\n : 'auto';\n } catch {\n // Ignore environments that expose but do not allow setting scrollRestoration.\n }\n }\n}\n\nfunction parseTargetUrl(path: string): URL {\n const pathname = window.location.pathname || '/';\n const search = window.location.search || '';\n const hash = window.location.hash || '';\n const href =\n typeof window.location.href === 'string' ? window.location.href : '';\n const base =\n href &&\n href !== 'about:blank' &&\n !href.startsWith('about:') &&\n !href.startsWith('data:')\n ? href\n : `http://localhost${pathname}${search}${hash}`;\n\n return new URL(path, base);\n}\n\nfunction isRenderResult(\n result: RouteRequestResult\n): result is RouteRenderResult {\n return result !== null && result.kind === 'render';\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return value instanceof Promise;\n}\n\nfunction createDeniedResolvedRoute(status: number): ResolvedRoute {\n return {\n handler: () => ({\n type: 'div',\n props: {\n 'data-route-denied': String(status),\n },\n children: [String(status)],\n }),\n params: {},\n };\n}\n\nfunction bindResolvedRouteHandler(\n resolved: ResolvedRoute\n): ComponentInstance['fn'] {\n return () =>\n resolved.handler(resolved.params) as ReturnType<ComponentInstance['fn']>;\n}\n\nfunction wrapRootRouteHandler(\n componentFn: ComponentInstance['fn']\n): ComponentInstance['fn'] {\n const wrappedFn: ComponentInstance['fn'] = (props, ctx) => {\n const out = componentFn(props, ctx);\n const portalVNode = {\n $$typeof: ELEMENT_TYPE,\n type: DefaultPortal,\n props: {},\n key: '__default_portal',\n } as unknown;\n\n return {\n $$typeof: ELEMENT_TYPE,\n type: Fragment,\n props: {\n children:\n out === undefined || out === null\n ? [portalVNode]\n : [out, portalVNode],\n },\n } as ReturnType<ComponentInstance['fn']>;\n };\n\n Object.defineProperty(wrappedFn, 'name', {\n value: componentFn.name || 'Component',\n });\n\n return wrappedFn;\n}\n\nfunction remountResolvedRoute(\n resolved: ResolvedRoute,\n pathname: string,\n href: string\n): boolean {\n if (!currentInstance) {\n return false;\n }\n\n // The route handler IS the component function\n // It takes params as props and renders the route\n currentInstance.fn = wrapRootRouteHandler(bindResolvedRouteHandler(resolved));\n currentInstance.props = {};\n\n // Reset state to prevent leakage from previous route\n // Each route navigation starts completely fresh\n currentInstance.stateValues = [];\n currentInstance.expectedStateIndices = [];\n currentInstance.firstRenderComplete = false;\n currentInstance.stateIndexCheck = -1;\n // Increment generation to invalidate pending async evaluations from previous route\n currentInstance.evaluationGeneration++;\n currentInstance.notifyUpdate = null;\n currentInstance.mountOperations = [];\n currentInstance._placeholder = undefined;\n currentInstance.hasPendingUpdate = false;\n\n // Route-local async work should create a fresh abort controller lazily.\n currentInstance.abortController = null;\n\n // Re-execute component against the existing host so reconciliation can\n // preserve any shared layout DOM between sibling routes.\n mountComponent(currentInstance);\n currentPathname = pathname;\n currentHref = href;\n return true;\n}\n\nfunction rerenderResolvedRoute(pathname: string, href: string): boolean {\n if (!currentInstance) {\n return false;\n }\n\n const resolved = resolveRoute(pathname);\n if (!resolved) {\n return false;\n }\n\n currentPathname = pathname;\n currentHref = href;\n currentInstance._enqueueRun?.();\n return true;\n}\n\nfunction resolveNavigationTarget(path: string) {\n const target = parseTargetUrl(path);\n const pathname = target.pathname;\n const href = `${target.pathname}${target.search}${target.hash}`;\n const resolved = resolveRouteRequest(href);\n\n if (isPromise(resolved)) {\n return resolved.then((next) => ({\n href,\n pathname,\n resolved: next,\n }));\n }\n\n return {\n href,\n pathname,\n resolved,\n };\n}\n\nfunction applyNavigationTarget(\n path: string,\n options: NavigateOptions,\n target: {\n href: string;\n pathname: string;\n resolved: RouteRequestResult;\n }\n): void {\n const { href, pathname, resolved } = target;\n const previousPathname = currentPathname;\n\n saveScrollPosition(currentHref);\n\n if (!resolved) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${path}`);\n }\n return;\n }\n\n if (resolved.kind === 'redirect') {\n const redirectTarget = parseTargetUrl(resolved.to);\n const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;\n if (redirectHref === href) {\n if (isDevelopmentEnvironment()) {\n logger.warn(\n `Navigation guard redirected to the same path: ${redirectHref}`\n );\n }\n return;\n }\n\n navigate(redirectHref, { history: 'replace' });\n return;\n }\n\n const nextResolved =\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n };\n\n const historyMethod =\n options.history === 'replace' ? 'replaceState' : 'pushState';\n window.history[historyMethod]({ path: href }, '', href);\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n if (currentInstance) {\n if (pathname === currentPathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(pathname, href);\n return;\n }\n\n remountResolvedRoute(nextResolved, pathname, href);\n if (pathname !== previousPathname) {\n applyNavigationScroll(\n options.scroll ?? scrollRestorationOptions.navigation\n );\n }\n }\n}\n\n/** Register the current app instance (called by createSPA/hydrateSPA). */\nexport function registerAppInstance(\n instance: ComponentInstance,\n path: string\n): void {\n currentInstance = instance;\n currentPathname = path;\n currentHref = getWindowHref();\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n // Lock further route registrations after the app has started — but allow tests to register routes.\n // Enforce only in production to avoid breaking test infra which registers routes dynamically.\n if (isProductionEnvironment()) {\n lockRouteRegistration();\n }\n}\n\n/**\n * Navigate to a new path\n * Updates URL, resolves route, and re-mounts app with new handler\n */\nexport function navigate(path: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n // SSR context\n return;\n }\n\n const target = resolveNavigationTarget(path);\n if (isPromise(target)) {\n void target.then((resolvedTarget) =>\n applyNavigationTarget(path, options, resolvedTarget)\n );\n return;\n }\n\n applyNavigationTarget(path, options, target);\n}\n\n/**\n * Handle browser back/forward buttons\n */\nfunction handlePopState(_event: PopStateEvent): void {\n const previousHref = currentHref;\n const pathname = window.location.pathname;\n const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;\n\n saveScrollPosition(previousHref);\n\n if (!currentInstance) {\n return;\n }\n\n const resolved = resolveRouteRequest(href);\n\n const applyResolved = (resolved: RouteRequestResult) => {\n if (!resolved) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${pathname}`);\n }\n return;\n }\n\n if (resolved.kind === 'redirect') {\n navigate(resolved.to, { history: 'replace' });\n return;\n }\n\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n if (pathname === currentPathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(pathname, href);\n return;\n }\n\n remountResolvedRoute(\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n },\n pathname,\n href\n );\n\n applyHistoryScroll(href, _event.state);\n };\n\n if (isPromise(resolved)) {\n void resolved.then((next) => applyResolved(next));\n return;\n }\n\n applyResolved(resolved);\n}\n\n/**\n * Setup popstate listener for browser navigation\n */\nexport function initializeNavigation(): void {\n if (typeof window === 'undefined' || navigationInitialized) {\n return;\n }\n\n navigationInitialized = true;\n window.addEventListener('popstate', handlePopState);\n}\n\n/**\n * Cleanup navigation listeners\n */\nexport function cleanupNavigation(): void {\n if (typeof window === 'undefined' || !navigationInitialized) {\n return;\n }\n\n navigationInitialized = false;\n window.removeEventListener('popstate', handlePopState);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"navigate.js","names":[],"sources":["../../src/router/navigate.ts"],"sourcesContent":["/**\n * Client-side navigation with History API\n */\n\nimport {\n resolveRouteRequest,\n resolveRouteFromRoutes,\n lockRouteRegistration,\n syncCurrentRouteSnapshot,\n type ResolvedRoute,\n} from './route';\nimport {\n cleanupComponent,\n mountComponent,\n type ComponentInstance,\n} from '../runtime/component';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport type {\n Route,\n RouteAuthOptions,\n RouteManifest,\n RouteRenderResult,\n RouteRequestResult,\n} from '../common/router';\nimport { Fragment, ELEMENT_TYPE } from '../jsx';\nimport {\n DefaultPortal,\n clearDefaultPortalForInstance,\n} from '../foundations/structures/portal';\nimport { isPromiseLike } from '../common/promise';\nimport { cleanupInstancesUnder } from '../renderer/cleanup';\n\n// Global app state for navigation\nlet currentInstance: ComponentInstance | null = null;\nlet currentPathname = '/';\nlet currentHref = '/';\nlet navigationInitialized = false;\nlet activeRouteRequestId = 0;\nlet activeRouteRequestController: AbortController | null = null;\nconst MAX_NAVIGATION_REDIRECTS = 20;\n\ntype AppNavigationSource = {\n manifest?: RouteManifest;\n routes?: readonly Route[];\n auth?: RouteAuthOptions;\n};\n\ntype AppRegistration = AppNavigationSource & {\n instance: ComponentInstance;\n pathname: string;\n href: string;\n};\n\nconst registeredApps: AppRegistration[] = [];\n\nfunction syncAppRegistrationLocation(\n app: AppRegistration,\n pathname: string,\n href: string\n): void {\n app.pathname = pathname;\n app.href = href;\n}\n\nexport type NavigationScrollBehavior = 'top' | 'preserve';\nexport type HistoryScrollBehavior = 'restore' | 'top' | 'preserve';\n\nexport type ScrollRestorationOptions = {\n navigation?: NavigationScrollBehavior;\n history?: HistoryScrollBehavior;\n};\n\ntype NormalizedScrollRestorationOptions = {\n enabled: boolean;\n navigation: NavigationScrollBehavior;\n history: HistoryScrollBehavior;\n};\n\nconst DEFAULT_SCROLL_RESTORATION: NormalizedScrollRestorationOptions = {\n enabled: true,\n navigation: 'top',\n history: 'restore',\n};\n\nlet scrollRestorationOptions: NormalizedScrollRestorationOptions = {\n ...DEFAULT_SCROLL_RESTORATION,\n};\n\nconst scrollPositions = new Map<string, { x: number; y: number }>();\n\nexport type NavigateOptions = {\n history?: 'push' | 'replace';\n replace?: boolean;\n scroll?: NavigationScrollBehavior;\n};\n\nfunction getWindowHref(): string {\n if (typeof window === 'undefined') {\n return currentHref;\n }\n\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\nfunction normalizeScrollRestorationOptions(\n options?: boolean | ScrollRestorationOptions\n): NormalizedScrollRestorationOptions {\n if (options === false) {\n return {\n enabled: false,\n navigation: DEFAULT_SCROLL_RESTORATION.navigation,\n history: DEFAULT_SCROLL_RESTORATION.history,\n };\n }\n\n if (options === true || options === undefined) {\n return { ...DEFAULT_SCROLL_RESTORATION };\n }\n\n return {\n enabled: true,\n navigation: options.navigation ?? DEFAULT_SCROLL_RESTORATION.navigation,\n history: options.history ?? DEFAULT_SCROLL_RESTORATION.history,\n };\n}\n\nfunction readScrollPosition(): { x: number; y: number } {\n if (typeof window === 'undefined') {\n return { x: 0, y: 0 };\n }\n\n const x =\n typeof window.scrollX === 'number'\n ? window.scrollX\n : typeof window.pageXOffset === 'number'\n ? window.pageXOffset\n : 0;\n const y =\n typeof window.scrollY === 'number'\n ? window.scrollY\n : typeof window.pageYOffset === 'number'\n ? window.pageYOffset\n : 0;\n\n return { x, y };\n}\n\nfunction writeHistoryScrollPosition(\n href: string,\n position: { x: number; y: number }\n): void {\n if (typeof window === 'undefined' || getWindowHref() !== href) {\n return;\n }\n if (typeof window.history?.replaceState !== 'function') {\n return;\n }\n const state =\n window.history.state && typeof window.history.state === 'object'\n ? window.history.state\n : {};\n\n window.history.replaceState(\n {\n ...state,\n path: href,\n scroll: position,\n },\n '',\n href\n );\n}\n\nfunction saveScrollPosition(href: string): void {\n if (!scrollRestorationOptions.enabled || typeof window === 'undefined') {\n return;\n }\n\n const position = readScrollPosition();\n scrollPositions.set(href, position);\n writeHistoryScrollPosition(href, position);\n}\n\nfunction scrollToPosition(position: { x: number; y: number }): void {\n if (typeof window === 'undefined' || typeof window.scrollTo !== 'function') {\n return;\n }\n\n window.scrollTo(position.x, position.y);\n}\n\nfunction applyNavigationScroll(behavior: NavigationScrollBehavior): void {\n if (!scrollRestorationOptions.enabled || behavior === 'preserve') {\n return;\n }\n\n scrollToPosition({ x: 0, y: 0 });\n}\n\nfunction applyHistoryScroll(href: string, state: PopStateEvent['state']): void {\n if (!scrollRestorationOptions.enabled) {\n return;\n }\n\n if (scrollRestorationOptions.history === 'preserve') {\n return;\n }\n\n if (scrollRestorationOptions.history === 'top') {\n scrollToPosition({ x: 0, y: 0 });\n return;\n }\n\n const fromState =\n state && typeof state === 'object' && 'scroll' in state\n ? (state.scroll as { x?: unknown; y?: unknown })\n : undefined;\n const saved =\n fromState &&\n typeof fromState.x === 'number' &&\n typeof fromState.y === 'number'\n ? { x: fromState.x, y: fromState.y }\n : scrollPositions.get(href);\n\n scrollToPosition(saved ?? { x: 0, y: 0 });\n}\n\nexport function configureScrollRestoration(\n options?: boolean | ScrollRestorationOptions\n): void {\n scrollRestorationOptions = normalizeScrollRestorationOptions(options);\n\n if (typeof window === 'undefined') {\n return;\n }\n\n if ('scrollRestoration' in window.history) {\n try {\n window.history.scrollRestoration = scrollRestorationOptions.enabled\n ? 'manual'\n : 'auto';\n } catch {\n // Ignore environments that expose but do not allow setting scrollRestoration.\n }\n }\n}\n\nfunction parseTargetUrl(path: string): URL {\n const pathname = window.location.pathname || '/';\n const search = window.location.search || '';\n const hash = window.location.hash || '';\n const href =\n typeof window.location.href === 'string' ? window.location.href : '';\n const base =\n href &&\n href !== 'about:blank' &&\n !href.startsWith('about:') &&\n !href.startsWith('data:')\n ? href\n : `http://localhost${pathname}${search}${hash}`;\n\n return new URL(path, base);\n}\n\nfunction isRenderResult(\n result: RouteRequestResult\n): result is RouteRenderResult {\n return result !== null && result.kind === 'render';\n}\n\nfunction getRedirectHistoryMode(\n replace: boolean | undefined\n): 'push' | 'replace' {\n return replace === false ? 'push' : 'replace';\n}\n\nfunction getNavigationHistoryMode(\n options: NavigateOptions\n): 'push' | 'replace' {\n if (options.history) {\n return options.history;\n }\n\n return options.replace ? 'replace' : 'push';\n}\n\nfunction beginRouteRequest(): { id: number; signal: AbortSignal } {\n activeRouteRequestId += 1;\n activeRouteRequestController?.abort();\n activeRouteRequestController = new AbortController();\n\n return {\n id: activeRouteRequestId,\n signal: activeRouteRequestController.signal,\n };\n}\n\nfunction isStaleRouteRequest(requestId: number): boolean {\n return requestId !== activeRouteRequestId;\n}\n\nfunction createDeniedResolvedRoute(status: number): ResolvedRoute {\n return {\n handler: () => ({\n type: 'div',\n props: {\n 'data-route-denied': String(status),\n },\n children: [String(status)],\n }),\n params: {},\n };\n}\n\nfunction bindResolvedRouteHandler(\n resolved: ResolvedRoute\n): ComponentInstance['fn'] {\n return () =>\n resolved.handler(resolved.params) as ReturnType<ComponentInstance['fn']>;\n}\n\nfunction wrapRootRouteHandler(\n componentFn: ComponentInstance['fn']\n): ComponentInstance['fn'] {\n const wrappedFn: ComponentInstance['fn'] = (props, ctx) => {\n const out = componentFn(props, ctx);\n if (isPromiseLike(out)) {\n throw new Error(\n 'Async components are not supported. Components must return synchronously.'\n );\n }\n const portalVNode = {\n $$typeof: ELEMENT_TYPE,\n type: DefaultPortal,\n props: {},\n key: '__default_portal',\n } as unknown;\n\n return {\n $$typeof: ELEMENT_TYPE,\n type: Fragment,\n props: {\n children:\n out === undefined || out === null\n ? [portalVNode]\n : [out, portalVNode],\n },\n } as ReturnType<ComponentInstance['fn']>;\n };\n\n Object.defineProperty(wrappedFn, 'name', {\n value: componentFn.name || 'Component',\n });\n\n return wrappedFn;\n}\n\nfunction cleanupRouteOwnership(instance: ComponentInstance): void {\n const cleanupErrors: unknown[] = [];\n const children = instance.target\n ? Array.from(instance.target.childNodes)\n : [];\n\n for (const child of children) {\n try {\n cleanupInstancesUnder(child, { strict: instance.cleanupStrict });\n } catch (error) {\n cleanupErrors.push(error);\n }\n }\n\n try {\n cleanupComponent(instance);\n } catch (error) {\n cleanupErrors.push(error);\n }\n\n if (cleanupErrors.length > 0) {\n throw new AggregateError(cleanupErrors, 'Route cleanup failed');\n }\n}\n\nfunction remountResolvedRoute(\n instance: ComponentInstance,\n resolved: ResolvedRoute,\n pathname: string,\n href: string\n): boolean {\n // The route handler IS the component function\n // It takes params as props and renders the route\n clearDefaultPortalForInstance(instance);\n cleanupRouteOwnership(instance);\n\n instance.fn = wrapRootRouteHandler(bindResolvedRouteHandler(resolved));\n instance.props = {};\n\n // Reset state to prevent leakage from previous route\n // Each route navigation starts completely fresh\n instance.stateValues = [];\n instance.expectedStateIndices = [];\n instance.firstRenderComplete = false;\n instance.stateIndexCheck = -1;\n // Increment generation to invalidate pending async evaluations from previous route\n instance.evaluationGeneration++;\n instance.notifyUpdate = null;\n instance.mountOperations = [];\n instance.cleanupFns = [];\n instance._placeholder = undefined;\n instance.hasPendingUpdate = false;\n instance._currentRenderToken = undefined;\n instance.lastRenderToken = 0;\n instance._pendingReadSources = undefined;\n instance._lastReadSources = undefined;\n\n // Route-local async work should create a fresh abort controller lazily.\n instance.abortController = null;\n\n // Re-execute component against the existing host so reconciliation can\n // preserve any shared layout DOM between sibling routes.\n mountComponent(instance);\n currentPathname = pathname;\n currentHref = href;\n return true;\n}\n\nfunction rerenderResolvedRoute(\n instance: ComponentInstance,\n pathname: string,\n href: string\n): boolean {\n currentPathname = pathname;\n currentHref = href;\n instance._enqueueRun?.();\n return true;\n}\n\nfunction resolveAppRouteRequest(\n app: AppRegistration,\n pathname: string,\n href: string,\n signal: AbortSignal\n): RouteRequestResult | Promise<RouteRequestResult> {\n if (app.routes) {\n const resolved = resolveRouteFromRoutes(pathname, app.routes);\n if (!resolved) {\n return null;\n }\n\n return {\n kind: 'render',\n handler: resolved.handler,\n params: resolved.params,\n };\n }\n\n return resolveRouteRequest(href, {\n manifest: app.manifest,\n auth: app.auth,\n signal,\n });\n}\n\ntype AppNavigationTarget = {\n app: AppRegistration;\n resolved: RouteRequestResult;\n};\n\nfunction resolveNavigationTargetsForApps(\n pathname: string,\n href: string,\n signal: AbortSignal\n): AppNavigationTarget[] | Promise<AppNavigationTarget[]> {\n const apps = [...registeredApps];\n\n if (apps.length === 1) {\n const app = apps[0]!;\n const resolved = resolveAppRouteRequest(app, pathname, href, signal);\n if (isPromiseLike<RouteRequestResult>(resolved)) {\n return Promise.resolve(resolved).then((next) => [\n {\n app,\n resolved: next,\n },\n ]);\n }\n\n return [\n {\n app,\n resolved,\n },\n ];\n }\n\n const syncTargets: AppNavigationTarget[] = [];\n const pendingTargets: Array<Promise<AppNavigationTarget>> = [];\n\n for (const app of apps) {\n const resolved = resolveAppRouteRequest(app, pathname, href, signal);\n if (isPromiseLike<RouteRequestResult>(resolved)) {\n pendingTargets.push(\n Promise.resolve(resolved).then((next) => ({ app, resolved: next }))\n );\n continue;\n }\n\n syncTargets.push({ app, resolved });\n }\n\n if (pendingTargets.length === 0) {\n return syncTargets;\n }\n\n return Promise.all([\n ...syncTargets.map((target) => Promise.resolve(target)),\n ...pendingTargets,\n ]);\n}\n\nfunction applyNavigationTargets(\n path: string,\n options: NavigateOptions,\n redirectState: NavigationRedirectState,\n pathname: string,\n href: string,\n targets: AppNavigationTarget[]\n): void {\n const previousPathname = currentPathname;\n\n for (const target of targets) {\n const resolved = target.resolved;\n if (!resolved || resolved.kind !== 'redirect') {\n continue;\n }\n\n const redirectTarget = parseTargetUrl(resolved.to);\n const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;\n if (redirectHref === href) {\n if (isDevelopmentEnvironment()) {\n logger.warn(\n `Navigation guard redirected to the same path: ${redirectHref}`\n );\n }\n return;\n }\n\n if (redirectState.visited.has(redirectHref)) {\n throw new Error(\n `[Askr] Navigation redirect cycle detected at ${redirectHref}.`\n );\n }\n if (redirectState.redirects >= MAX_NAVIGATION_REDIRECTS) {\n throw new Error(\n `[Askr] Navigation redirect limit exceeded (${MAX_NAVIGATION_REDIRECTS}).`\n );\n }\n\n redirectState.visited.add(redirectHref);\n redirectState.redirects++;\n navigateWithRedirectState(\n redirectHref,\n {\n history: getRedirectHistoryMode(resolved.replace),\n },\n redirectState\n );\n return;\n }\n\n const matchedTargets = targets.filter((target) => target.resolved !== null);\n if (matchedTargets.length === 0) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${path}`);\n }\n return;\n }\n\n saveScrollPosition(currentHref);\n\n const historyMethod =\n getNavigationHistoryMode(options) === 'replace'\n ? 'replaceState'\n : 'pushState';\n window.history[historyMethod]({ path: href }, '', href);\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n for (const target of matchedTargets) {\n const resolved = target.resolved!;\n if (resolved.kind === 'redirect') {\n continue;\n }\n\n if (pathname === target.app.pathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(target.app.instance, pathname, href);\n syncAppRegistrationLocation(target.app, pathname, href);\n continue;\n }\n\n remountResolvedRoute(\n target.app.instance,\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n },\n pathname,\n href\n );\n syncAppRegistrationLocation(target.app, pathname, href);\n }\n\n if (pathname !== previousPathname) {\n applyNavigationScroll(\n options.scroll ?? scrollRestorationOptions.navigation\n );\n }\n}\n\n/** Register the current app instance (called by createSPA/hydrateSPA). */\nexport function registerAppInstance(\n instance: ComponentInstance,\n path: string,\n source: AppNavigationSource = {}\n): void {\n const existingIndex = registeredApps.findIndex(\n (app) => app.instance === instance\n );\n const registration: AppRegistration = {\n instance,\n pathname: path,\n href: getWindowHref(),\n ...source,\n };\n if (existingIndex >= 0) {\n registeredApps[existingIndex] = registration;\n } else {\n registeredApps.push(registration);\n }\n\n currentInstance = instance;\n currentPathname = path;\n currentHref = getWindowHref();\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n // Lock further route registrations after the app has started — but allow tests to register routes.\n // Enforce only in production to avoid breaking test infra which registers routes dynamically.\n if (isProductionEnvironment()) {\n lockRouteRegistration();\n }\n}\n\nexport function unregisterAppInstance(instance: ComponentInstance): void {\n const existingIndex = registeredApps.findIndex(\n (app) => app.instance === instance\n );\n if (existingIndex >= 0) {\n registeredApps.splice(existingIndex, 1);\n }\n\n if (currentInstance !== instance) {\n return;\n }\n\n const nextApp =\n registeredApps.length > 0\n ? registeredApps[registeredApps.length - 1]!\n : null;\n currentInstance = nextApp?.instance ?? null;\n if (nextApp) {\n currentPathname = nextApp.pathname;\n currentHref = nextApp.href;\n return;\n }\n\n activeRouteRequestId += 1;\n activeRouteRequestController?.abort();\n activeRouteRequestController = null;\n currentPathname = '/';\n currentHref = '/';\n}\n\n/**\n * Navigate to a new path\n * Updates URL, resolves route, and re-mounts app with new handler\n */\nexport function navigate(path: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const initialTarget = parseTargetUrl(path);\n navigateWithRedirectState(path, options, {\n redirects: 0,\n visited: new Set([\n `${initialTarget.pathname}${initialTarget.search}${initialTarget.hash}`,\n ]),\n });\n}\n\ntype NavigationRedirectState = {\n redirects: number;\n visited: Set<string>;\n};\n\nfunction navigateWithRedirectState(\n path: string,\n options: NavigateOptions,\n redirectState: NavigationRedirectState\n): void {\n if (typeof window === 'undefined') {\n // SSR context\n return;\n }\n\n const request = beginRouteRequest();\n\n const target = parseTargetUrl(path);\n const pathname = target.pathname;\n const href = `${target.pathname}${target.search}${target.hash}`;\n const resolvedTargets = resolveNavigationTargetsForApps(\n pathname,\n href,\n request.signal\n );\n\n if (isPromiseLike(resolvedTargets)) {\n void Promise.resolve(resolvedTargets).then(\n (targets) => {\n if (isStaleRouteRequest(request.id)) {\n return;\n }\n\n try {\n applyNavigationTargets(\n path,\n options,\n redirectState,\n pathname,\n href,\n targets\n );\n } catch (error) {\n logger.error('[Askr] navigation failed:', error);\n }\n },\n (error) => {\n logger.error('[Askr] navigation failed:', error);\n }\n );\n return;\n }\n\n applyNavigationTargets(\n path,\n options,\n redirectState,\n pathname,\n href,\n resolvedTargets\n );\n}\n\n/**\n * Handle browser back/forward buttons\n */\nfunction handlePopState(_event: PopStateEvent): void {\n const request = beginRouteRequest();\n const previousHref = currentHref;\n const pathname = window.location.pathname;\n const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;\n\n saveScrollPosition(previousHref);\n\n if (registeredApps.length === 0) {\n return;\n }\n\n const applyResolved = (targets: AppNavigationTarget[]) => {\n if (isStaleRouteRequest(request.id)) {\n return;\n }\n\n const matchedTargets = targets.filter((target) => target.resolved !== null);\n if (matchedTargets.length === 0) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${pathname}`);\n }\n return;\n }\n\n for (const target of matchedTargets) {\n const resolved = target.resolved!;\n if (resolved.kind !== 'redirect') {\n continue;\n }\n\n navigate(resolved.to, {\n history: getRedirectHistoryMode(resolved.replace),\n });\n return;\n }\n\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n for (const target of matchedTargets) {\n const resolved = target.resolved!;\n if (pathname === target.app.pathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(target.app.instance, pathname, href);\n syncAppRegistrationLocation(target.app, pathname, href);\n continue;\n }\n\n if (resolved.kind === 'redirect') {\n continue;\n }\n\n remountResolvedRoute(\n target.app.instance,\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n },\n pathname,\n href\n );\n syncAppRegistrationLocation(target.app, pathname, href);\n }\n\n applyHistoryScroll(href, _event.state);\n };\n\n const resolvedTargets = resolveNavigationTargetsForApps(\n pathname,\n href,\n request.signal\n );\n\n if (isPromiseLike<AppNavigationTarget[]>(resolvedTargets)) {\n void Promise.resolve(resolvedTargets).then(\n (next) => {\n try {\n applyResolved(next);\n } catch (error) {\n logger.error('[Askr] popstate navigation failed:', error);\n }\n },\n (error) => {\n logger.error('[Askr] popstate navigation failed:', error);\n }\n );\n return;\n }\n\n applyResolved(resolvedTargets);\n}\n\n/**\n * Setup popstate listener for browser navigation\n */\nexport function initializeNavigation(): void {\n if (typeof window === 'undefined' || navigationInitialized) {\n return;\n }\n\n navigationInitialized = true;\n window.addEventListener('popstate', handlePopState);\n}\n\n/**\n * Cleanup navigation listeners\n */\nexport function cleanupNavigation(): void {\n activeRouteRequestId += 1;\n activeRouteRequestController?.abort();\n activeRouteRequestController = null;\n\n if (typeof window === 'undefined' || !navigationInitialized) {\n return;\n }\n\n navigationInitialized = false;\n window.removeEventListener('popstate', handlePopState);\n}\n"],"mappings":"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"}
|
package/dist/router/policy.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { AccessDecision, AccessDenyStatus, AccessRedirectStatus, RouteAuthOptions, RouteContext, RoutePolicy } from "../common/router.js";
|
|
1
|
+
import { AccessAllowDecision, AccessDecision, AccessDenyDecision, AccessDenyStatus, AccessRedirectDecision, AccessRedirectStatus, RouteAuthOptions, RouteContext, RoutePolicy } from "../common/router.js";
|
|
2
2
|
|
|
3
3
|
//#region src/router/policy.d.ts
|
|
4
|
-
declare function allow():
|
|
4
|
+
declare function allow(): AccessAllowDecision;
|
|
5
5
|
declare function redirect(to: string, init?: {
|
|
6
6
|
status?: AccessRedirectStatus;
|
|
7
7
|
replace?: boolean;
|
|
8
|
-
}):
|
|
9
|
-
declare function deny(status: AccessDenyStatus):
|
|
10
|
-
declare function unauthorized():
|
|
11
|
-
declare function forbidden():
|
|
12
|
-
declare function notFound():
|
|
8
|
+
}): AccessRedirectDecision;
|
|
9
|
+
declare function deny(status: AccessDenyStatus): AccessDenyDecision;
|
|
10
|
+
declare function unauthorized(): AccessDenyDecision;
|
|
11
|
+
declare function forbidden(): AccessDenyDecision;
|
|
12
|
+
declare function notFound(): AccessDenyDecision;
|
|
13
13
|
declare function normalizeAccessDecision(result: AccessDecision): AccessDecision;
|
|
14
14
|
declare function withRouteAuthOptions(context: RouteContext, auth: RouteAuthOptions | undefined): RouteContext;
|
|
15
15
|
declare function requireAuth(): RoutePolicy;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.d.ts","names":[],"sources":["../../src/router/policy.ts"],"mappings":";;;iBAuBgB,KAAA,
|
|
1
|
+
{"version":3,"file":"policy.d.ts","names":[],"sources":["../../src/router/policy.ts"],"mappings":";;;iBAuBgB,KAAA,IAAS,mBAAmB;AAAA,iBAI5B,QAAA,CACd,EAAA,UACA,IAAA;EACE,MAAA,GAAS,oBAAA;EACT,OAAA;AAAA,IAED,sBAAsB;AAAA,iBAST,IAAA,CAAK,MAAA,EAAQ,gBAAA,GAAmB,kBAAkB;AAAA,iBAIlD,YAAA,IAAgB,kBAAkB;AAAA,iBAIlC,SAAA,IAAa,kBAAkB;AAAA,iBAI/B,QAAA,IAAY,kBAAkB;AAAA,iBAI9B,uBAAA,CACd,MAAA,EAAQ,cAAA,GACP,cAAc;AAAA,iBAID,oBAAA,CACd,OAAA,EAAS,YAAA,EACT,IAAA,EAAM,gBAAA,eACL,YAAA;AAAA,iBA8Ha,WAAA,IAAe,WAAW;AAAA,iBAU1B,YAAA,IAAgB,WAAW;AAAA,iBAU3B,WAAA,CAAY,IAAA,WAAe,WAAW;AAAA,iBAqBtC,iBAAA,CAAkB,UAAA,WAAqB,WAAW;AAAA,iBAqB5C,mBAAA,CACpB,MAAA,EAAQ,WAAA,EACR,OAAA,EAAS,YAAA,GACR,OAAA,CAAQ,cAAA"}
|
package/dist/router/policy.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
import { isPromiseLike } from "../common/promise.js";
|
|
1
2
|
//#region src/router/policy.ts
|
|
2
3
|
const ROUTE_AUTH_OPTIONS = Symbol.for("__ASKR_ROUTE_AUTH_OPTIONS__");
|
|
3
|
-
function isPromise(value) {
|
|
4
|
-
return value instanceof Promise;
|
|
5
|
-
}
|
|
6
4
|
function allow() {
|
|
7
5
|
return { kind: "allow" };
|
|
8
6
|
}
|
|
@@ -72,12 +70,12 @@ function defaultHasPermission(user, permission) {
|
|
|
72
70
|
}
|
|
73
71
|
function resolveUnauthenticatedRedirect(context) {
|
|
74
72
|
const loginPath = resolvePathSetting(assertRouteAuthOptions(context).loginPath, "/login", context);
|
|
75
|
-
if (
|
|
73
|
+
if (isPromiseLike(loginPath)) return Promise.resolve(loginPath).then((nextPath) => redirect(appendNextTarget(nextPath, context.href), { replace: context.mode === "spa" }));
|
|
76
74
|
return redirect(appendNextTarget(loginPath, context.href), { replace: context.mode === "spa" });
|
|
77
75
|
}
|
|
78
76
|
function resolveAuthenticatedRedirect(context) {
|
|
79
77
|
const target = resolvePathSetting(assertRouteAuthOptions(context).guestRedirectTo, "/", context);
|
|
80
|
-
if (
|
|
78
|
+
if (isPromiseLike(target)) return Promise.resolve(target).then((nextTarget) => redirect(nextTarget, { replace: true }));
|
|
81
79
|
return redirect(target, { replace: true });
|
|
82
80
|
}
|
|
83
81
|
function requireAuth() {
|
|
@@ -97,7 +95,7 @@ function requireRole(role) {
|
|
|
97
95
|
if (!context.user) return resolveUnauthenticatedRedirect(context);
|
|
98
96
|
const auth = assertRouteAuthOptions(context);
|
|
99
97
|
const hasRole = auth.hasRole ? auth.hasRole(context.user, role, context) : defaultHasRole(context.user, role);
|
|
100
|
-
if (
|
|
98
|
+
if (isPromiseLike(hasRole)) return Promise.resolve(hasRole).then((next) => next ? allow() : forbidden());
|
|
101
99
|
return hasRole ? allow() : forbidden();
|
|
102
100
|
};
|
|
103
101
|
}
|
|
@@ -106,7 +104,7 @@ function requirePermission(permission) {
|
|
|
106
104
|
if (!context.user) return resolveUnauthenticatedRedirect(context);
|
|
107
105
|
const auth = assertRouteAuthOptions(context);
|
|
108
106
|
const hasPermission = auth.hasPermission ? auth.hasPermission(context.user, permission, context) : defaultHasPermission(context.user, permission);
|
|
109
|
-
if (
|
|
107
|
+
if (isPromiseLike(hasPermission)) return Promise.resolve(hasPermission).then((next) => next ? allow() : forbidden());
|
|
110
108
|
return hasPermission ? allow() : forbidden();
|
|
111
109
|
};
|
|
112
110
|
}
|