@pithyjs/router 0.1.0-beta.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.
- package/LICENSE +21 -0
- package/README.md +621 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/router/core/config.d.ts +22 -0
- package/dist/router/core/config.d.ts.map +1 -0
- package/dist/router/core/config.js +54 -0
- package/dist/router/core/config.js.map +1 -0
- package/dist/router/core/constants.d.ts +89 -0
- package/dist/router/core/constants.d.ts.map +1 -0
- package/dist/router/core/constants.js +95 -0
- package/dist/router/core/constants.js.map +1 -0
- package/dist/router/core/history.d.ts +54 -0
- package/dist/router/core/history.d.ts.map +1 -0
- package/dist/router/core/history.js +89 -0
- package/dist/router/core/history.js.map +1 -0
- package/dist/router/core/matcher.d.ts +43 -0
- package/dist/router/core/matcher.d.ts.map +1 -0
- package/dist/router/core/matcher.js +242 -0
- package/dist/router/core/matcher.js.map +1 -0
- package/dist/router/core/params.d.ts +88 -0
- package/dist/router/core/params.d.ts.map +1 -0
- package/dist/router/core/params.js +150 -0
- package/dist/router/core/params.js.map +1 -0
- package/dist/router/core/pipeline.d.ts +90 -0
- package/dist/router/core/pipeline.d.ts.map +1 -0
- package/dist/router/core/pipeline.js +323 -0
- package/dist/router/core/pipeline.js.map +1 -0
- package/dist/router/core/route-tree.d.ts +76 -0
- package/dist/router/core/route-tree.d.ts.map +1 -0
- package/dist/router/core/route-tree.js +143 -0
- package/dist/router/core/route-tree.js.map +1 -0
- package/dist/router/core/router.d.ts +229 -0
- package/dist/router/core/router.d.ts.map +1 -0
- package/dist/router/core/router.js +762 -0
- package/dist/router/core/router.js.map +1 -0
- package/dist/router/core/types.d.ts +387 -0
- package/dist/router/core/types.d.ts.map +1 -0
- package/dist/router/core/types.js +22 -0
- package/dist/router/core/types.js.map +1 -0
- package/dist/router/core/utils.d.ts +66 -0
- package/dist/router/core/utils.d.ts.map +1 -0
- package/dist/router/core/utils.js +152 -0
- package/dist/router/core/utils.js.map +1 -0
- package/dist/router/features/coordinator.d.ts +75 -0
- package/dist/router/features/coordinator.d.ts.map +1 -0
- package/dist/router/features/coordinator.js +203 -0
- package/dist/router/features/coordinator.js.map +1 -0
- package/dist/router/features/errors/error-codes.d.ts +107 -0
- package/dist/router/features/errors/error-codes.d.ts.map +1 -0
- package/dist/router/features/errors/error-codes.js +247 -0
- package/dist/router/features/errors/error-codes.js.map +1 -0
- package/dist/router/features/errors/error-resolution.d.ts +31 -0
- package/dist/router/features/errors/error-resolution.d.ts.map +1 -0
- package/dist/router/features/errors/error-resolution.js +47 -0
- package/dist/router/features/errors/error-resolution.js.map +1 -0
- package/dist/router/features/errors/error-types.d.ts +60 -0
- package/dist/router/features/errors/error-types.d.ts.map +1 -0
- package/dist/router/features/errors/error-types.js +57 -0
- package/dist/router/features/errors/error-types.js.map +1 -0
- package/dist/router/features/errors/global-error-fallback.d.ts +33 -0
- package/dist/router/features/errors/global-error-fallback.d.ts.map +1 -0
- package/dist/router/features/errors/global-error-fallback.js +182 -0
- package/dist/router/features/errors/global-error-fallback.js.map +1 -0
- package/dist/router/features/errors/index.d.ts +15 -0
- package/dist/router/features/errors/index.d.ts.map +1 -0
- package/dist/router/features/errors/index.js +18 -0
- package/dist/router/features/errors/index.js.map +1 -0
- package/dist/router/features/errors/router-errors.d.ts +44 -0
- package/dist/router/features/errors/router-errors.d.ts.map +1 -0
- package/dist/router/features/errors/router-errors.js +53 -0
- package/dist/router/features/errors/router-errors.js.map +1 -0
- package/dist/router/features/guards/engine.d.ts +40 -0
- package/dist/router/features/guards/engine.d.ts.map +1 -0
- package/dist/router/features/guards/engine.js +67 -0
- package/dist/router/features/guards/engine.js.map +1 -0
- package/dist/router/features/guards/index.d.ts +52 -0
- package/dist/router/features/guards/index.d.ts.map +1 -0
- package/dist/router/features/guards/index.js +95 -0
- package/dist/router/features/guards/index.js.map +1 -0
- package/dist/router/features/loaders/cache.d.ts +60 -0
- package/dist/router/features/loaders/cache.d.ts.map +1 -0
- package/dist/router/features/loaders/cache.js +80 -0
- package/dist/router/features/loaders/cache.js.map +1 -0
- package/dist/router/features/loaders/engine.d.ts +17 -0
- package/dist/router/features/loaders/engine.d.ts.map +1 -0
- package/dist/router/features/loaders/engine.js +27 -0
- package/dist/router/features/loaders/engine.js.map +1 -0
- package/dist/router/features/loaders/index.d.ts +49 -0
- package/dist/router/features/loaders/index.d.ts.map +1 -0
- package/dist/router/features/loaders/index.js +112 -0
- package/dist/router/features/loaders/index.js.map +1 -0
- package/dist/router/features/loading-ui-manager.d.ts +69 -0
- package/dist/router/features/loading-ui-manager.d.ts.map +1 -0
- package/dist/router/features/loading-ui-manager.js +209 -0
- package/dist/router/features/loading-ui-manager.js.map +1 -0
- package/dist/router/features/module-loader.d.ts +50 -0
- package/dist/router/features/module-loader.d.ts.map +1 -0
- package/dist/router/features/module-loader.js +121 -0
- package/dist/router/features/module-loader.js.map +1 -0
- package/dist/router/features/outlet/errors.d.ts +24 -0
- package/dist/router/features/outlet/errors.d.ts.map +1 -0
- package/dist/router/features/outlet/errors.js +96 -0
- package/dist/router/features/outlet/errors.js.map +1 -0
- package/dist/router/features/outlet/index.d.ts +45 -0
- package/dist/router/features/outlet/index.d.ts.map +1 -0
- package/dist/router/features/outlet/index.js +95 -0
- package/dist/router/features/outlet/index.js.map +1 -0
- package/dist/router/features/outlet/mount.d.ts +38 -0
- package/dist/router/features/outlet/mount.d.ts.map +1 -0
- package/dist/router/features/outlet/mount.js +301 -0
- package/dist/router/features/outlet/mount.js.map +1 -0
- package/dist/router/features/outlet/segment-chain.d.ts +16 -0
- package/dist/router/features/outlet/segment-chain.d.ts.map +1 -0
- package/dist/router/features/outlet/segment-chain.js +62 -0
- package/dist/router/features/outlet/segment-chain.js.map +1 -0
- package/dist/router/features/prefetch/index.d.ts +97 -0
- package/dist/router/features/prefetch/index.d.ts.map +1 -0
- package/dist/router/features/prefetch/index.js +345 -0
- package/dist/router/features/prefetch/index.js.map +1 -0
- package/dist/router/features/ux/focus.d.ts +62 -0
- package/dist/router/features/ux/focus.d.ts.map +1 -0
- package/dist/router/features/ux/focus.js +136 -0
- package/dist/router/features/ux/focus.js.map +1 -0
- package/dist/router/features/ux/live-region.d.ts +81 -0
- package/dist/router/features/ux/live-region.d.ts.map +1 -0
- package/dist/router/features/ux/live-region.js +166 -0
- package/dist/router/features/ux/live-region.js.map +1 -0
- package/dist/router/features/ux/scroll-focus.d.ts +105 -0
- package/dist/router/features/ux/scroll-focus.d.ts.map +1 -0
- package/dist/router/features/ux/scroll-focus.js +111 -0
- package/dist/router/features/ux/scroll-focus.js.map +1 -0
- package/dist/router/features/ux/scroll.d.ts +71 -0
- package/dist/router/features/ux/scroll.d.ts.map +1 -0
- package/dist/router/features/ux/scroll.js +137 -0
- package/dist/router/features/ux/scroll.js.map +1 -0
- package/dist/router/features/ux/transitions.d.ts +120 -0
- package/dist/router/features/ux/transitions.d.ts.map +1 -0
- package/dist/router/features/ux/transitions.js +235 -0
- package/dist/router/features/ux/transitions.js.map +1 -0
- package/dist/router/integration/directive-binder.d.ts +20 -0
- package/dist/router/integration/directive-binder.d.ts.map +1 -0
- package/dist/router/integration/directive-binder.js +139 -0
- package/dist/router/integration/directive-binder.js.map +1 -0
- package/dist/router/integration/directives.d.ts +42 -0
- package/dist/router/integration/directives.d.ts.map +1 -0
- package/dist/router/integration/directives.js +109 -0
- package/dist/router/integration/directives.js.map +1 -0
- package/dist/router/integration/history-adapter.d.ts +76 -0
- package/dist/router/integration/history-adapter.d.ts.map +1 -0
- package/dist/router/integration/history-adapter.js +157 -0
- package/dist/router/integration/history-adapter.js.map +1 -0
- package/dist/router/integration/hmr.d.ts +83 -0
- package/dist/router/integration/hmr.d.ts.map +1 -0
- package/dist/router/integration/hmr.js +160 -0
- package/dist/router/integration/hmr.js.map +1 -0
- package/dist/router/integration/link-interceptor.d.ts +49 -0
- package/dist/router/integration/link-interceptor.d.ts.map +1 -0
- package/dist/router/integration/link-interceptor.js +94 -0
- package/dist/router/integration/link-interceptor.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.core.pipeline",
|
|
4
|
+
"title": "Navigation Pipeline - Multi-Phase Navigation Orchestration",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import { clearAllRouteErrors } from '../features/errors';
|
|
9
|
+
import { scrollFocusManager } from '../features/ux/scroll-focus';
|
|
10
|
+
import { viewTransitionManager } from '../features/ux/transitions';
|
|
11
|
+
/**
|
|
12
|
+
* Navigation Pipeline
|
|
13
|
+
* Orchestrates multi-phase navigation flow: GUARD → HISTORY → LOADER → MOUNT
|
|
14
|
+
*
|
|
15
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"NavigationPipeline","stability":"stable","signature":"class NavigationPipeline"}
|
|
16
|
+
*/
|
|
17
|
+
export class NavigationPipeline {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.nextNavId = 1;
|
|
20
|
+
this.activeNavId = 0;
|
|
21
|
+
this.activeTransaction = null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Execute a navigation transaction
|
|
25
|
+
* Returns true if navigation succeeded, false if aborted/failed
|
|
26
|
+
*
|
|
27
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"execute","stability":"stable","signature":"async execute(path: string, options: NavigationOptions, context: NavigationContext): Promise<boolean>"}
|
|
28
|
+
*/
|
|
29
|
+
async execute(path, options, context) {
|
|
30
|
+
const { replace = false, state = null, source = 'programmatic' } = options;
|
|
31
|
+
const navId = this.nextNavId++;
|
|
32
|
+
const tx = this.createTransaction(navId, path);
|
|
33
|
+
this.cancelActiveNavigation();
|
|
34
|
+
context.onClearLoadingUI(); // Defensive: clear any stale loading/error UI before new nav
|
|
35
|
+
this.activeNavId = navId;
|
|
36
|
+
this.activeTransaction = tx;
|
|
37
|
+
// Pre-commit state: navigating=true, but DO NOT set match yet
|
|
38
|
+
context.updateState({ isNavigating: true, activeNavigation: tx });
|
|
39
|
+
try {
|
|
40
|
+
const url = new URL(path, window.location.origin);
|
|
41
|
+
const pathname = context.normalizePath(url.pathname);
|
|
42
|
+
const search = url.search;
|
|
43
|
+
const match = context.findMatch(pathname);
|
|
44
|
+
if (!match) {
|
|
45
|
+
// 404 decision - crucially, don't clear DOM here
|
|
46
|
+
console.warn('[Router] No route found:', path);
|
|
47
|
+
context.updateState({ isNavigating: false, activeNavigation: null });
|
|
48
|
+
this.activeTransaction = null;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (!this.isActive(navId))
|
|
52
|
+
return false;
|
|
53
|
+
const query = context.parseQueryParams(search);
|
|
54
|
+
// GUARDS (no DOM mutation)
|
|
55
|
+
const guards = await context.onLoadGuards(match.pattern);
|
|
56
|
+
const guardRes = await context.onExecuteGuards(guards, {
|
|
57
|
+
path: pathname,
|
|
58
|
+
params: match.params,
|
|
59
|
+
query,
|
|
60
|
+
signal: tx.abortController.signal,
|
|
61
|
+
navId,
|
|
62
|
+
from: context.getCurrentPath(),
|
|
63
|
+
});
|
|
64
|
+
if (!this.isActive(navId))
|
|
65
|
+
return false;
|
|
66
|
+
if (!guardRes.allowed) {
|
|
67
|
+
// Redirect if provided
|
|
68
|
+
if (guardRes.redirect) {
|
|
69
|
+
const redirectOpts = {
|
|
70
|
+
source,
|
|
71
|
+
};
|
|
72
|
+
if (guardRes.redirect.replace !== undefined) {
|
|
73
|
+
redirectOpts.replace = guardRes.redirect.replace;
|
|
74
|
+
}
|
|
75
|
+
return this.execute(guardRes.redirect.redirect, redirectOpts, context);
|
|
76
|
+
}
|
|
77
|
+
// Announce guard denial for screen readers
|
|
78
|
+
scrollFocusManager.announceGuardDenied();
|
|
79
|
+
// Snap back only if we mutated history earlier (we won't for pop)
|
|
80
|
+
if (source !== 'pop') {
|
|
81
|
+
context.onHistorySnapBack();
|
|
82
|
+
}
|
|
83
|
+
context.onClearLoadingUI();
|
|
84
|
+
context.updateState({ isNavigating: false, activeNavigation: null });
|
|
85
|
+
this.activeTransaction = null;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
// HISTORY write happens only for programmatic/intercept navigations
|
|
89
|
+
const finalHref = pathname + search;
|
|
90
|
+
if (source !== 'pop') {
|
|
91
|
+
// Save current scroll position to history state BEFORE mutating history
|
|
92
|
+
context.onSaveScrollToHistory();
|
|
93
|
+
if (replace)
|
|
94
|
+
context.onHistoryReplace(state, finalHref);
|
|
95
|
+
else
|
|
96
|
+
context.onHistoryPush(state, finalHref);
|
|
97
|
+
}
|
|
98
|
+
// Pre-commit reactive state (no mount yet)
|
|
99
|
+
context.updateState({
|
|
100
|
+
path: pathname,
|
|
101
|
+
params: match.params,
|
|
102
|
+
query,
|
|
103
|
+
isNavigating: true,
|
|
104
|
+
activeNavigation: tx,
|
|
105
|
+
// match intentionally omitted until commit
|
|
106
|
+
});
|
|
107
|
+
// Branch by navigation source for loading UI policy
|
|
108
|
+
const route = context.routes.find(r => r.pattern === match.pattern);
|
|
109
|
+
const hasLoader = !!route?.imports.loader;
|
|
110
|
+
if (source === 'pop') {
|
|
111
|
+
// POPSTATE: defensive cleanup + instant commit
|
|
112
|
+
context.onClearLoadingUI(); // ensure no loader from superseded navigation
|
|
113
|
+
if (context.config.popstateLoading === 'none') {
|
|
114
|
+
// Run loader silently in background; when done, apply data (no structural swap)
|
|
115
|
+
if (hasLoader) {
|
|
116
|
+
context
|
|
117
|
+
.onExecuteLoaders(match.pattern, {
|
|
118
|
+
params: match.params,
|
|
119
|
+
query,
|
|
120
|
+
signal: tx.abortController.signal,
|
|
121
|
+
navId,
|
|
122
|
+
})
|
|
123
|
+
.then(() => {
|
|
124
|
+
if (this.isActive(navId)) {
|
|
125
|
+
// Restore scroll after loader completes
|
|
126
|
+
// Note: scrollFocusManager is accessed via context in the future
|
|
127
|
+
// For now, we'll let the router handle this
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
.catch(err => {
|
|
131
|
+
if (!tx.abortController.signal.aborted) {
|
|
132
|
+
console.warn('[Router] Background loader failed:', err);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Immediately commit the route change (no loading UI)
|
|
137
|
+
context.updateState({
|
|
138
|
+
path: pathname,
|
|
139
|
+
params: match.params,
|
|
140
|
+
query,
|
|
141
|
+
match,
|
|
142
|
+
isNavigating: false,
|
|
143
|
+
activeNavigation: null,
|
|
144
|
+
});
|
|
145
|
+
// Restore scroll immediately for pop without loader, or before loader starts
|
|
146
|
+
// This will be handled by the router's scroll manager
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Handle overlay or replace policies for popstate if configured
|
|
150
|
+
await this.commitPhase(route, match, pathname, query, navId, tx, source, url.hash, context);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// PROGRAMMATIC/INTERCEPT: Replace outlet with loading view using atomic swap
|
|
155
|
+
await this.commitPhase(route, match, pathname, query, navId, tx, source, url.hash, context);
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (tx.abortController.signal.aborted) {
|
|
161
|
+
// canceled by newer nav/pop: just clean overlay and keep previous screen
|
|
162
|
+
context.onClearLoadingUI();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.error('[Router] Navigation failed:', err);
|
|
166
|
+
// Announce error for screen readers
|
|
167
|
+
scrollFocusManager.announceError('Navigation failed');
|
|
168
|
+
// keep previous DOM; optional: show a toast/error boundary
|
|
169
|
+
context.onClearLoadingUI();
|
|
170
|
+
}
|
|
171
|
+
if (this.isActive(navId)) {
|
|
172
|
+
context.updateState({ isNavigating: false, activeNavigation: null });
|
|
173
|
+
this.activeTransaction = null;
|
|
174
|
+
}
|
|
175
|
+
// Safety: if outlet somehow got emptied by user code, restore snapshot
|
|
176
|
+
context.onRestoreSnapshotIfEmpty();
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Create a new navigation transaction
|
|
182
|
+
*
|
|
183
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"createTransaction","stability":"internal","signature":"private createTransaction(id: NavigationId, path: string): NavigationTransaction"}
|
|
184
|
+
*/
|
|
185
|
+
createTransaction(id, path) {
|
|
186
|
+
return {
|
|
187
|
+
id,
|
|
188
|
+
path,
|
|
189
|
+
abortController: new AbortController(),
|
|
190
|
+
isActive: true,
|
|
191
|
+
startTime: Date.now(),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Cancel active navigation
|
|
196
|
+
* Useful for cleanup when disposing the router
|
|
197
|
+
*
|
|
198
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"cancelActiveNavigation","stability":"stable","signature":"public cancelActiveNavigation(): void"}
|
|
199
|
+
*/
|
|
200
|
+
cancelActiveNavigation() {
|
|
201
|
+
if (this.activeTransaction) {
|
|
202
|
+
this.activeTransaction.isActive = false;
|
|
203
|
+
this.activeTransaction.abortController.abort();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check if navigation is active
|
|
208
|
+
*
|
|
209
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"isActive","stability":"stable","signature":"isActive(navId: NavigationId): boolean"}
|
|
210
|
+
*/
|
|
211
|
+
isActive(navId) {
|
|
212
|
+
if (navId !== this.activeNavId) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (!this.activeTransaction) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
if (this.activeTransaction.abortController.signal.aborted) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
return this.activeTransaction.isActive;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Get current active navigation ID
|
|
225
|
+
*
|
|
226
|
+
* @codexApi {"parent":"pithy.router.core.pipeline","name":"getActiveNavId","stability":"stable","signature":"getActiveNavId(): NavigationId"}
|
|
227
|
+
*/
|
|
228
|
+
getActiveNavId() {
|
|
229
|
+
return this.activeNavId;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Initialize next nav ID (for HMR or testing)
|
|
233
|
+
*/
|
|
234
|
+
initializeNavId(startId) {
|
|
235
|
+
this.nextNavId = startId;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* COMMIT phase: Execute loaders, show loading UI, commit route change
|
|
239
|
+
* This is the core navigation commit logic extracted from router.ts
|
|
240
|
+
*/
|
|
241
|
+
async commitPhase(route, match, pathname, query, navId, tx, source, hash, context) {
|
|
242
|
+
// Show loading UI (router context handles the logic based on policy)
|
|
243
|
+
await context.onShowLoadingUI(route, match, navId, source);
|
|
244
|
+
// Announce loading for screen readers if there's a loader
|
|
245
|
+
if (route?.imports.loader) {
|
|
246
|
+
scrollFocusManager.announceLoading(`Loading ${pathname}`);
|
|
247
|
+
}
|
|
248
|
+
// Execute loaders and capture success/failure
|
|
249
|
+
const loadersOk = await context.onExecuteLoaders(match.pattern, {
|
|
250
|
+
params: match.params,
|
|
251
|
+
query,
|
|
252
|
+
signal: tx.abortController.signal,
|
|
253
|
+
navId,
|
|
254
|
+
});
|
|
255
|
+
if (!this.isActive(navId)) {
|
|
256
|
+
// Navigation superseded; clean up and bail
|
|
257
|
+
context.onClearLoadingUI();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// COMMIT: set match to trigger mount with view transitions
|
|
261
|
+
const transitionOptions = viewTransitionManager.getTransitionOptionsFromElement() || undefined;
|
|
262
|
+
const commit = async () => {
|
|
263
|
+
// Only clear all errors if loaders succeeded
|
|
264
|
+
if (loadersOk) {
|
|
265
|
+
clearAllRouteErrors();
|
|
266
|
+
}
|
|
267
|
+
// Update route state and wait for mount to complete
|
|
268
|
+
await context.updateState({
|
|
269
|
+
path: pathname,
|
|
270
|
+
params: match.params,
|
|
271
|
+
query,
|
|
272
|
+
match,
|
|
273
|
+
isNavigating: false, // Navigation completes after mount
|
|
274
|
+
activeNavigation: null,
|
|
275
|
+
});
|
|
276
|
+
// Scroll and focus management runs AFTER mount completes
|
|
277
|
+
// This ensures anchor elements rendered by the new route exist
|
|
278
|
+
// Skip for HMR (handled separately)
|
|
279
|
+
if (source !== 'hmr') {
|
|
280
|
+
// Handle scroll restoration based on navigation type
|
|
281
|
+
if (source === 'pop') {
|
|
282
|
+
scrollFocusManager.restoreFromHistory({ navId });
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// For push/replace navigation, handle normally
|
|
286
|
+
if (hash) {
|
|
287
|
+
// Await scroll to prevent unhandled rejections after test teardown
|
|
288
|
+
await scrollFocusManager.scrollToAnchor(hash);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
scrollFocusManager.scrollToTop();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Defer focus management to allow DOM to settle
|
|
295
|
+
queueMicrotask(() => {
|
|
296
|
+
requestAnimationFrame(() => {
|
|
297
|
+
scrollFocusManager.manageFocus({ preventScroll: true });
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
// Skip view transitions for HMR to prevent race conditions
|
|
303
|
+
if (source === 'hmr') {
|
|
304
|
+
await commit();
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
try {
|
|
308
|
+
await viewTransitionManager.executeTransition(commit, transitionOptions);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
console.warn('[Router] View transition failed:', err);
|
|
312
|
+
// Avoid double-commit if the callback already ran
|
|
313
|
+
const currentMatch = context.getCurrentMatch();
|
|
314
|
+
if (currentMatch?.pattern !== match.pattern) {
|
|
315
|
+
await commit();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Clear loading UI - will be replaced by actual component
|
|
320
|
+
context.onClearLoadingUI();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../src/router/core/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.route-tree",
|
|
4
|
+
"title": "Route Tree Utilities for Nested Routes",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import type { RouteNode, RouteParams } from './types';
|
|
9
|
+
/**
|
|
10
|
+
* Flattened route entry with parent reference for building ancestor chains
|
|
11
|
+
* @internal Used internally by the router for nested route resolution
|
|
12
|
+
*/
|
|
13
|
+
export interface FlatRouteEntry {
|
|
14
|
+
/** The route node */
|
|
15
|
+
node: RouteNode;
|
|
16
|
+
/** Parent route ID (null for root routes) */
|
|
17
|
+
parentId: string | null;
|
|
18
|
+
/** Full ancestor chain from root to this route */
|
|
19
|
+
ancestors: RouteNode[];
|
|
20
|
+
}
|
|
21
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"flattenRouteTree","stability":"experimental","signature":"(routes: RouteNode[], parent?: RouteNode, ancestors?: RouteNode[]) => FlatRouteEntry[]","description":"Flatten hierarchical route tree into flat list with parent references"} */
|
|
22
|
+
/**
|
|
23
|
+
* Flatten a hierarchical route tree into a flat list with parent references
|
|
24
|
+
* This enables efficient route matching while preserving parent-child relationships
|
|
25
|
+
* for nested layouts and outlet chains
|
|
26
|
+
* @internal Used internally by the router for building route lookup structures
|
|
27
|
+
*/
|
|
28
|
+
export declare function flattenRouteTree(routes: RouteNode[], parent?: RouteNode | null, ancestors?: RouteNode[]): FlatRouteEntry[];
|
|
29
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"buildRouteMap","stability":"experimental","signature":"(flatRoutes: FlatRouteEntry[]) => Map<string, FlatRouteEntry>","description":"Build route map from flattened routes for quick lookup by ID"} */
|
|
30
|
+
/**
|
|
31
|
+
* Build route map from flattened routes for quick lookup by ID
|
|
32
|
+
* @internal Used internally by the router for efficient route lookup
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildRouteMap(flatRoutes: FlatRouteEntry[]): Map<string, FlatRouteEntry>;
|
|
35
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"getAncestorChain","stability":"experimental","signature":"(routeId: string, routeMap: Map<string, FlatRouteEntry>) => RouteNode[]","description":"Get full ancestor chain for a route, ordered root to leaf"} */
|
|
36
|
+
/**
|
|
37
|
+
* Get full ancestor chain for a route (including the route itself)
|
|
38
|
+
* Returns routes in order from root to leaf
|
|
39
|
+
* @internal Used internally for nested route resolution
|
|
40
|
+
*/
|
|
41
|
+
export declare function getAncestorChain(routeId: string, routeMap: Map<string, FlatRouteEntry>): RouteNode[];
|
|
42
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"findMatchingEntry","stability":"experimental","signature":"(path: string, flatRoutes: FlatRouteEntry[]) => FlatRouteEntry | null","description":"Find matching route entry from flattened routes for a given path"} */
|
|
43
|
+
/**
|
|
44
|
+
* Find matching route entry from flattened routes for a given path
|
|
45
|
+
* @internal Used internally by the router for route matching
|
|
46
|
+
*/
|
|
47
|
+
export declare function findMatchingEntry(path: string, flatRoutes: FlatRouteEntry[]): FlatRouteEntry | null;
|
|
48
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"extractAncestorParams","stability":"experimental","signature":"(path: string, ancestors: RouteNode[]) => RouteParams[]","description":"Extract parameters for each ancestor by matching path progressively"} */
|
|
49
|
+
/**
|
|
50
|
+
* Extract parameters for each ancestor by matching path progressively
|
|
51
|
+
* @internal Used internally for nested route parameter extraction
|
|
52
|
+
*/
|
|
53
|
+
export declare function extractAncestorParams(path: string, ancestors: RouteNode[]): RouteParams[];
|
|
54
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"findRouteByPattern","stability":"stable","signature":"(routes: RouteNode[], pattern: string) => RouteNode | null","description":"Find a route node by exact pattern match, searching recursively"} */
|
|
55
|
+
/**
|
|
56
|
+
* Find a route node by exact pattern match in a nested route tree.
|
|
57
|
+
* Searches recursively through children.
|
|
58
|
+
*
|
|
59
|
+
* @returns The matching RouteNode, or null if not found
|
|
60
|
+
*/
|
|
61
|
+
export declare function findRouteByPattern(routes: RouteNode[], pattern: string): RouteNode | null;
|
|
62
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"collectPatterns","stability":"stable","signature":"(routes: RouteNode[]) => string[]","description":"Collect all route patterns from a nested tree into a flat array"} */
|
|
63
|
+
/**
|
|
64
|
+
* Collect all route patterns from a nested route tree.
|
|
65
|
+
* Returns a flat array of pattern strings.
|
|
66
|
+
*/
|
|
67
|
+
export declare function collectPatterns(routes: RouteNode[]): string[];
|
|
68
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"mergeAncestorParams","stability":"experimental","signature":"(paramsByLevel: RouteParams[]) => RouteParams[]","description":"Merge ancestor params cumulatively; child params override parent"} */
|
|
69
|
+
/**
|
|
70
|
+
* Merge parameters from all ancestors in the chain
|
|
71
|
+
* Child params override parent params with same name
|
|
72
|
+
* Returns merged params for each level (cumulative)
|
|
73
|
+
* @internal Used internally for nested route parameter merging
|
|
74
|
+
*/
|
|
75
|
+
export declare function mergeAncestorParams(paramsByLevel: RouteParams[]): RouteParams[];
|
|
76
|
+
//# sourceMappingURL=route-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-tree.d.ts","sourceRoot":"","sources":["../../../src/router/core/route-tree.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,qBAAqB;IACrB,IAAI,EAAE,SAAS,CAAC;IAChB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kDAAkD;IAClD,SAAS,EAAE,SAAS,EAAE,CAAC;CACxB;AAED,qSAAqS;AACrS;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,MAAM,GAAE,SAAS,GAAG,IAAW,EAC/B,SAAS,GAAE,SAAS,EAAO,GAC1B,cAAc,EAAE,CAyBlB;AAED,gQAAgQ;AAChQ;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,cAAc,EAAE,GAC3B,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAQ7B;AAED,0QAA0Q;AAC1Q;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GACpC,SAAS,EAAE,CASb;AAED,gRAAgR;AAChR;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,cAAc,EAAE,GAC3B,cAAc,GAAG,IAAI,CAavB;AAED,yQAAyQ;AACzQ;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,EAAE,GACrB,WAAW,EAAE,CAUf;AAED,+PAA+P;AAC/P;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EAAE,EACnB,OAAO,EAAE,MAAM,GACd,SAAS,GAAG,IAAI,CASlB;AAED,mOAAmO;AACnO;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAS7D;AAED,4PAA4P;AAC5P;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,WAAW,EAAE,GAC3B,WAAW,EAAE,CAWf"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.route-tree",
|
|
4
|
+
"title": "Route Tree Utilities for Nested Routes",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import { matchRoute, findBestMatch } from './matcher';
|
|
9
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"flattenRouteTree","stability":"experimental","signature":"(routes: RouteNode[], parent?: RouteNode, ancestors?: RouteNode[]) => FlatRouteEntry[]","description":"Flatten hierarchical route tree into flat list with parent references"} */
|
|
10
|
+
/**
|
|
11
|
+
* Flatten a hierarchical route tree into a flat list with parent references
|
|
12
|
+
* This enables efficient route matching while preserving parent-child relationships
|
|
13
|
+
* for nested layouts and outlet chains
|
|
14
|
+
* @internal Used internally by the router for building route lookup structures
|
|
15
|
+
*/
|
|
16
|
+
export function flattenRouteTree(routes, parent = null, ancestors = []) {
|
|
17
|
+
const flat = [];
|
|
18
|
+
for (const route of routes) {
|
|
19
|
+
// Add current route to flat list with parent reference
|
|
20
|
+
const entry = {
|
|
21
|
+
node: route,
|
|
22
|
+
parentId: parent?.id || null,
|
|
23
|
+
ancestors: [...ancestors],
|
|
24
|
+
};
|
|
25
|
+
flat.push(entry);
|
|
26
|
+
// Recursively flatten children
|
|
27
|
+
if (route.children && route.children.length > 0) {
|
|
28
|
+
const childAncestors = [...ancestors, route];
|
|
29
|
+
const childEntries = flattenRouteTree(route.children, route, childAncestors);
|
|
30
|
+
flat.push(...childEntries);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return flat;
|
|
34
|
+
}
|
|
35
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"buildRouteMap","stability":"experimental","signature":"(flatRoutes: FlatRouteEntry[]) => Map<string, FlatRouteEntry>","description":"Build route map from flattened routes for quick lookup by ID"} */
|
|
36
|
+
/**
|
|
37
|
+
* Build route map from flattened routes for quick lookup by ID
|
|
38
|
+
* @internal Used internally by the router for efficient route lookup
|
|
39
|
+
*/
|
|
40
|
+
export function buildRouteMap(flatRoutes) {
|
|
41
|
+
const map = new Map();
|
|
42
|
+
for (const entry of flatRoutes) {
|
|
43
|
+
map.set(entry.node.id, entry);
|
|
44
|
+
}
|
|
45
|
+
return map;
|
|
46
|
+
}
|
|
47
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"getAncestorChain","stability":"experimental","signature":"(routeId: string, routeMap: Map<string, FlatRouteEntry>) => RouteNode[]","description":"Get full ancestor chain for a route, ordered root to leaf"} */
|
|
48
|
+
/**
|
|
49
|
+
* Get full ancestor chain for a route (including the route itself)
|
|
50
|
+
* Returns routes in order from root to leaf
|
|
51
|
+
* @internal Used internally for nested route resolution
|
|
52
|
+
*/
|
|
53
|
+
export function getAncestorChain(routeId, routeMap) {
|
|
54
|
+
const entry = routeMap.get(routeId);
|
|
55
|
+
if (!entry) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
// Return ancestors plus the route itself
|
|
59
|
+
return [...entry.ancestors, entry.node];
|
|
60
|
+
}
|
|
61
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"findMatchingEntry","stability":"experimental","signature":"(path: string, flatRoutes: FlatRouteEntry[]) => FlatRouteEntry | null","description":"Find matching route entry from flattened routes for a given path"} */
|
|
62
|
+
/**
|
|
63
|
+
* Find matching route entry from flattened routes for a given path
|
|
64
|
+
* @internal Used internally by the router for route matching
|
|
65
|
+
*/
|
|
66
|
+
export function findMatchingEntry(path, flatRoutes) {
|
|
67
|
+
// Extract patterns for matching
|
|
68
|
+
const patterns = flatRoutes.map(entry => entry.node.pattern);
|
|
69
|
+
// Use matcher to find best match
|
|
70
|
+
const match = findBestMatch(path, patterns);
|
|
71
|
+
if (!match) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
// Find the entry with matching pattern
|
|
75
|
+
return flatRoutes.find(entry => entry.node.pattern === match.pattern) || null;
|
|
76
|
+
}
|
|
77
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"extractAncestorParams","stability":"experimental","signature":"(path: string, ancestors: RouteNode[]) => RouteParams[]","description":"Extract parameters for each ancestor by matching path progressively"} */
|
|
78
|
+
/**
|
|
79
|
+
* Extract parameters for each ancestor by matching path progressively
|
|
80
|
+
* @internal Used internally for nested route parameter extraction
|
|
81
|
+
*/
|
|
82
|
+
export function extractAncestorParams(path, ancestors) {
|
|
83
|
+
const paramsByLevel = [];
|
|
84
|
+
// Match path against each ancestor's pattern to extract params
|
|
85
|
+
for (const ancestor of ancestors) {
|
|
86
|
+
const match = matchRoute(path, ancestor.pattern);
|
|
87
|
+
paramsByLevel.push(match?.params || {});
|
|
88
|
+
}
|
|
89
|
+
return paramsByLevel;
|
|
90
|
+
}
|
|
91
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"findRouteByPattern","stability":"stable","signature":"(routes: RouteNode[], pattern: string) => RouteNode | null","description":"Find a route node by exact pattern match, searching recursively"} */
|
|
92
|
+
/**
|
|
93
|
+
* Find a route node by exact pattern match in a nested route tree.
|
|
94
|
+
* Searches recursively through children.
|
|
95
|
+
*
|
|
96
|
+
* @returns The matching RouteNode, or null if not found
|
|
97
|
+
*/
|
|
98
|
+
export function findRouteByPattern(routes, pattern) {
|
|
99
|
+
for (const node of routes) {
|
|
100
|
+
if (node.pattern === pattern)
|
|
101
|
+
return node;
|
|
102
|
+
if (node.children && node.children.length > 0) {
|
|
103
|
+
const found = findRouteByPattern(node.children, pattern);
|
|
104
|
+
if (found)
|
|
105
|
+
return found;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"collectPatterns","stability":"stable","signature":"(routes: RouteNode[]) => string[]","description":"Collect all route patterns from a nested tree into a flat array"} */
|
|
111
|
+
/**
|
|
112
|
+
* Collect all route patterns from a nested route tree.
|
|
113
|
+
* Returns a flat array of pattern strings.
|
|
114
|
+
*/
|
|
115
|
+
export function collectPatterns(routes) {
|
|
116
|
+
const patterns = [];
|
|
117
|
+
for (const node of routes) {
|
|
118
|
+
if (node.pattern)
|
|
119
|
+
patterns.push(node.pattern);
|
|
120
|
+
if (node.children && node.children.length > 0) {
|
|
121
|
+
patterns.push(...collectPatterns(node.children));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return patterns;
|
|
125
|
+
}
|
|
126
|
+
/** @codexApi {"parent":"pithy.router.route-tree","name":"mergeAncestorParams","stability":"experimental","signature":"(paramsByLevel: RouteParams[]) => RouteParams[]","description":"Merge ancestor params cumulatively; child params override parent"} */
|
|
127
|
+
/**
|
|
128
|
+
* Merge parameters from all ancestors in the chain
|
|
129
|
+
* Child params override parent params with same name
|
|
130
|
+
* Returns merged params for each level (cumulative)
|
|
131
|
+
* @internal Used internally for nested route parameter merging
|
|
132
|
+
*/
|
|
133
|
+
export function mergeAncestorParams(paramsByLevel) {
|
|
134
|
+
const merged = [];
|
|
135
|
+
let cumulative = {};
|
|
136
|
+
// Build cumulative params at each level
|
|
137
|
+
for (const levelParams of paramsByLevel) {
|
|
138
|
+
cumulative = { ...cumulative, ...levelParams };
|
|
139
|
+
merged.push({ ...cumulative });
|
|
140
|
+
}
|
|
141
|
+
return merged;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=route-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-tree.js","sourceRoot":"","sources":["../../../src/router/core/route-tree.ts"],"names":[],"mappings":"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"}
|