@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,209 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.features.loading-ui",
|
|
4
|
+
"title": "Loading UI Manager - Atomic Swap and Overlay Loading States",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import { extractElement } from '../core/utils';
|
|
9
|
+
import { LOADING_UI } from '../core/constants';
|
|
10
|
+
import { loadRouteModule, getRouteModuleKey } from './module-loader';
|
|
11
|
+
/**
|
|
12
|
+
* Loading UI Manager
|
|
13
|
+
* Handles atomic swap, overlay, and progress-only loading policies
|
|
14
|
+
*
|
|
15
|
+
* Supports two loading strategies:
|
|
16
|
+
* 1. Atomic Swap - Replace entire outlet content with loading UI
|
|
17
|
+
* 2. Overlay - Show loading UI on top of existing content
|
|
18
|
+
*
|
|
19
|
+
* @codexApi {"parent":"pithy.router.features.loading-ui","name":"LoadingUIManager","stability":"stable","signature":"class LoadingUIManager"}
|
|
20
|
+
*/
|
|
21
|
+
export class LoadingUIManager {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.outletSnapshot = null;
|
|
24
|
+
this.mountedElement = null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Set the mounted element (outlet)
|
|
28
|
+
*/
|
|
29
|
+
setMountedElement(element) {
|
|
30
|
+
this.mountedElement = element;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Show atomic loading UI (replace outlet content)
|
|
34
|
+
* Creates a container with router-loading-atomic class that replaces all outlet content
|
|
35
|
+
*
|
|
36
|
+
* @codexApi {"parent":"pithy.router.features.loading-ui","name":"showAtomic","stability":"stable","signature":"async showAtomic(pattern: string, navId: NavigationId, outlet: HTMLElement, routes: RouteNode[]): Promise<void>"}
|
|
37
|
+
*/
|
|
38
|
+
async showAtomic(pattern, navId, outlet, routes) {
|
|
39
|
+
// Resolve route-specific loading content or fall back to default
|
|
40
|
+
const contentEl = await this.buildForRoute(pattern, routes);
|
|
41
|
+
// Wrap the content in a removable atomic container (ALWAYS)
|
|
42
|
+
const container = document.createElement('div');
|
|
43
|
+
container.className = 'router-loading-atomic';
|
|
44
|
+
container.setAttribute('data-nav-id', navId.toString());
|
|
45
|
+
container.appendChild(contentEl);
|
|
46
|
+
// Atomic replace: no intermediate empty state
|
|
47
|
+
// Don't use View Transition for loading UI to prevent spinner from getting stuck
|
|
48
|
+
const frag = document.createDocumentFragment();
|
|
49
|
+
frag.appendChild(container);
|
|
50
|
+
outlet.replaceChildren(frag);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Show custom overlay loading UI with route-specific content
|
|
54
|
+
*
|
|
55
|
+
* @codexApi {"parent":"pithy.router.features.loading-ui","name":"showCustomOverlay","stability":"stable","signature":"async showCustomOverlay(pattern: string, navId: NavigationId, outlet: HTMLElement, routes: RouteNode[]): Promise<void>"}
|
|
56
|
+
*/
|
|
57
|
+
async showCustomOverlay(pattern, navId, outlet, routes) {
|
|
58
|
+
// Resolve route-specific loading content or default
|
|
59
|
+
const content = await this.buildForRoute(pattern, routes);
|
|
60
|
+
this.mountOverlay(outlet, navId, content, true);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Internal helper to mount an overlay with content
|
|
64
|
+
* Consolidates overlay creation logic
|
|
65
|
+
*/
|
|
66
|
+
mountOverlay(outlet, navId, content, isCustom) {
|
|
67
|
+
// Make outlet a positioning context
|
|
68
|
+
if (!outlet.style.position || outlet.style.position === 'static') {
|
|
69
|
+
outlet.style.position = 'relative';
|
|
70
|
+
}
|
|
71
|
+
// Remove any previous overlay for safety
|
|
72
|
+
this.removeOverlay(outlet);
|
|
73
|
+
const overlay = document.createElement('div');
|
|
74
|
+
overlay.className = isCustom
|
|
75
|
+
? 'router-loading-overlay router-loading-overlay--custom'
|
|
76
|
+
: 'router-loading-overlay';
|
|
77
|
+
overlay.setAttribute('data-nav-id', String(navId));
|
|
78
|
+
// Base styles for all overlays
|
|
79
|
+
Object.assign(overlay.style, {
|
|
80
|
+
position: 'absolute',
|
|
81
|
+
inset: '0',
|
|
82
|
+
display: 'flex',
|
|
83
|
+
justifyContent: 'center',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
zIndex: String(LOADING_UI.OVERLAY_Z_INDEX),
|
|
86
|
+
pointerEvents: 'none',
|
|
87
|
+
});
|
|
88
|
+
// Add background for default overlay only
|
|
89
|
+
if (!isCustom) {
|
|
90
|
+
Object.assign(overlay.style, {
|
|
91
|
+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
92
|
+
backdropFilter: 'blur(2px)',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
overlay.appendChild(content);
|
|
96
|
+
outlet.appendChild(overlay);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Remove loading overlay from outlet
|
|
100
|
+
*
|
|
101
|
+
* @codexApi {"parent":"pithy.router.features.loading-ui","name":"removeOverlay","stability":"stable","signature":"removeOverlay(outlet: HTMLElement): void"}
|
|
102
|
+
*/
|
|
103
|
+
removeOverlay(outlet) {
|
|
104
|
+
const overlays = outlet.querySelectorAll('.router-loading-overlay');
|
|
105
|
+
overlays.forEach(overlay => {
|
|
106
|
+
overlay.remove();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Clear all loading UI from outlet (handles both overlays and atomic swap loading)
|
|
111
|
+
*
|
|
112
|
+
* @codexApi {"parent":"pithy.router.features.loading-ui","name":"clearAll","stability":"stable","signature":"clearAll(): void"}
|
|
113
|
+
*/
|
|
114
|
+
clearAll() {
|
|
115
|
+
if (!this.mountedElement)
|
|
116
|
+
return;
|
|
117
|
+
// Remove overlay-style loading UI
|
|
118
|
+
this.removeOverlay(this.mountedElement);
|
|
119
|
+
// Remove atomic swap loading UI (elements with router-loading-atomic class)
|
|
120
|
+
const atomicLoadingElements = this.mountedElement.querySelectorAll('.router-loading-atomic');
|
|
121
|
+
atomicLoadingElements.forEach(element => {
|
|
122
|
+
element.remove();
|
|
123
|
+
});
|
|
124
|
+
// If outlet is now empty after clearing loading UI, restore snapshot
|
|
125
|
+
if (this.mountedElement.children.length === 0 && this.outletSnapshot) {
|
|
126
|
+
this.mountedElement.appendChild(this.outletSnapshot.cloneNode(true));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Build default loading content (spinner + "Loading..." text)
|
|
131
|
+
*
|
|
132
|
+
* @codexApi {"parent":"pithy.router.features.loading-ui","name":"buildDefault","stability":"stable","signature":"buildDefault(): HTMLElement"}
|
|
133
|
+
*/
|
|
134
|
+
buildDefault() {
|
|
135
|
+
const wrap = document.createElement('div');
|
|
136
|
+
wrap.className = 'router-loading-content';
|
|
137
|
+
wrap.innerHTML = `
|
|
138
|
+
<div class="spinner"></div>
|
|
139
|
+
<p>Loading…</p>
|
|
140
|
+
<style>
|
|
141
|
+
.router-loading-content {
|
|
142
|
+
text-align: center;
|
|
143
|
+
background: white;
|
|
144
|
+
padding: 2rem;
|
|
145
|
+
border-radius: 8px;
|
|
146
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
147
|
+
}
|
|
148
|
+
.spinner {
|
|
149
|
+
width: ${LOADING_UI.SPINNER_SIZE}px;
|
|
150
|
+
height: ${LOADING_UI.SPINNER_SIZE}px;
|
|
151
|
+
border: ${LOADING_UI.SPINNER_BORDER}px solid #eee;
|
|
152
|
+
border-top-color: #3498db;
|
|
153
|
+
border-radius: 50%;
|
|
154
|
+
animation: spin 1s linear infinite;
|
|
155
|
+
margin: 0 auto 0.5rem;
|
|
156
|
+
}
|
|
157
|
+
@keyframes spin {
|
|
158
|
+
to { transform: rotate(${LOADING_UI.SPINNER_ROTATION}deg) }
|
|
159
|
+
}
|
|
160
|
+
</style>
|
|
161
|
+
`;
|
|
162
|
+
return wrap;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Build loading content for a specific route (supports many export shapes)
|
|
166
|
+
*
|
|
167
|
+
* @codexApi {"parent":"pithy.router.features.loading-ui","name":"buildForRoute","stability":"stable","signature":"async buildForRoute(pattern: string, routes: RouteNode[]): Promise<HTMLElement>"}
|
|
168
|
+
*/
|
|
169
|
+
async buildForRoute(pattern, routes) {
|
|
170
|
+
const route = routes.find(r => r.pattern === pattern);
|
|
171
|
+
if (!route || !route.imports.loading) {
|
|
172
|
+
return this.buildDefault();
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
// Load through shared module cache
|
|
176
|
+
const mod = await loadRouteModule(getRouteModuleKey(pattern, 'loading'), route.imports.loading);
|
|
177
|
+
// candidates to inspect
|
|
178
|
+
const modRecord = mod;
|
|
179
|
+
const candidates = [
|
|
180
|
+
modRecord?.default,
|
|
181
|
+
modRecord?.loading,
|
|
182
|
+
modRecord?.Loading,
|
|
183
|
+
mod,
|
|
184
|
+
];
|
|
185
|
+
for (const c of candidates) {
|
|
186
|
+
if (!c)
|
|
187
|
+
continue;
|
|
188
|
+
// If it's a function, call it (sync or async)
|
|
189
|
+
if (typeof c === 'function') {
|
|
190
|
+
const out = await Promise.resolve(c());
|
|
191
|
+
const el = extractElement(out);
|
|
192
|
+
if (el)
|
|
193
|
+
return el;
|
|
194
|
+
}
|
|
195
|
+
// If it's already an element-like or HTML string/object
|
|
196
|
+
const el = extractElement(c);
|
|
197
|
+
if (el)
|
|
198
|
+
return el;
|
|
199
|
+
}
|
|
200
|
+
// Fallback
|
|
201
|
+
return this.buildDefault();
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
console.warn('[Router] Failed to load loading component:', e);
|
|
205
|
+
return this.buildDefault();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=loading-ui-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loading-ui-manager.js","sourceRoot":"","sources":["../../../src/router/features/loading-ui-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.module-loader",
|
|
4
|
+
"title": "Route Module Loader with Shared Cache Integration",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Module Loader Utility
|
|
10
|
+
*
|
|
11
|
+
* Provides integration with @pithyjs/core's shared module cache for route imports.
|
|
12
|
+
* Falls back to direct imports if core is not available.
|
|
13
|
+
*
|
|
14
|
+
* @module router/features/module-loader
|
|
15
|
+
*/
|
|
16
|
+
import { type RouteModuleType } from '../core/constants';
|
|
17
|
+
/** @codexApi {"parent":"pithy.router.module-loader","name":"loadRouteModule","stability":"stable","signature":"<T>(key: string, loader: () => Promise<T>) => Promise<T>"} */
|
|
18
|
+
/**
|
|
19
|
+
* Load a route module through the shared cache
|
|
20
|
+
*
|
|
21
|
+
* @param key - Cache key for deduplication (e.g., "route:/users:page")
|
|
22
|
+
* @param loader - Dynamic import function
|
|
23
|
+
* @returns The loaded module
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadRouteModule<T>(key: string, loader: () => Promise<T>): Promise<T>;
|
|
26
|
+
/** @codexApi {"parent":"pithy.router.module-loader","name":"getRouteModuleKey","stability":"stable","signature":"(pattern: string, type: RouteModuleType) => string"} */
|
|
27
|
+
/**
|
|
28
|
+
* Generate a cache key for a route module
|
|
29
|
+
*
|
|
30
|
+
* @param pattern - Route pattern (e.g., "/users/:id")
|
|
31
|
+
* @param type - Module type ("page", "layout", "loading", "error", "loader")
|
|
32
|
+
* @returns Cache key (e.g., "route:/users/:id:page")
|
|
33
|
+
*/
|
|
34
|
+
export declare function getRouteModuleKey(pattern: string, type: RouteModuleType): string;
|
|
35
|
+
/** @codexApi {"parent":"pithy.router.module-loader","name":"invalidateAllRouteModules","stability":"stable","signature":"(routes: Array<{ pattern: string }>) => Promise<void>"} */
|
|
36
|
+
/**
|
|
37
|
+
* Invalidate all route modules for a list of routes (for HMR full refresh)
|
|
38
|
+
*
|
|
39
|
+
* @param routes - Array of route nodes to invalidate
|
|
40
|
+
*/
|
|
41
|
+
export declare function invalidateAllRouteModules(routes: Array<{
|
|
42
|
+
pattern: string;
|
|
43
|
+
}>): Promise<void>;
|
|
44
|
+
/** @codexApi {"parent":"pithy.router.module-loader","name":"clearAllModuleCache","stability":"stable","signature":"() => Promise<void>"} */
|
|
45
|
+
/**
|
|
46
|
+
* Clear all route modules from the shared cache (for testing)
|
|
47
|
+
* This clears both the loader cache and the @pithyjs/core module cache
|
|
48
|
+
*/
|
|
49
|
+
export declare function clearAllModuleCache(): Promise<void>;
|
|
50
|
+
//# sourceMappingURL=module-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-loader.d.ts","sourceRoot":"","sources":["../../../src/router/features/module-loader.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF;;;;;;;GAOG;AAEH,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAoC7E,6KAA6K;AAC7K;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,CAAC,EACrC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC,CAGZ;AAED,yKAAyK;AACzK;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,GACpB,MAAM,CAER;AA2BD,oLAAoL;AACpL;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,4IAA4I;AAC5I;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAazD"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.module-loader",
|
|
4
|
+
"title": "Route Module Loader with Shared Cache Integration",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Module Loader Utility
|
|
10
|
+
*
|
|
11
|
+
* Provides integration with @pithyjs/core's shared module cache for route imports.
|
|
12
|
+
* Falls back to direct imports if core is not available.
|
|
13
|
+
*
|
|
14
|
+
* @module router/features/module-loader
|
|
15
|
+
*/
|
|
16
|
+
import { ROUTE_MODULE_TYPES } from '../core/constants';
|
|
17
|
+
/**
|
|
18
|
+
* Cached reference to the module loader function
|
|
19
|
+
*/
|
|
20
|
+
let cachedLoader = null;
|
|
21
|
+
/**
|
|
22
|
+
* Get the module loader function from @pithyjs/core
|
|
23
|
+
* Caches the result for performance
|
|
24
|
+
*/
|
|
25
|
+
async function getModuleLoader() {
|
|
26
|
+
if (cachedLoader) {
|
|
27
|
+
return cachedLoader;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const core = await import('@pithyjs/core');
|
|
31
|
+
if (core.loadModule) {
|
|
32
|
+
const loader = core.loadModule;
|
|
33
|
+
cachedLoader = loader;
|
|
34
|
+
return loader;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Core not available
|
|
39
|
+
}
|
|
40
|
+
// Fallback: direct import without caching
|
|
41
|
+
cachedLoader = (_key, loader) => loader();
|
|
42
|
+
return cachedLoader;
|
|
43
|
+
}
|
|
44
|
+
/** @codexApi {"parent":"pithy.router.module-loader","name":"loadRouteModule","stability":"stable","signature":"<T>(key: string, loader: () => Promise<T>) => Promise<T>"} */
|
|
45
|
+
/**
|
|
46
|
+
* Load a route module through the shared cache
|
|
47
|
+
*
|
|
48
|
+
* @param key - Cache key for deduplication (e.g., "route:/users:page")
|
|
49
|
+
* @param loader - Dynamic import function
|
|
50
|
+
* @returns The loaded module
|
|
51
|
+
*/
|
|
52
|
+
export async function loadRouteModule(key, loader) {
|
|
53
|
+
const moduleLoader = await getModuleLoader();
|
|
54
|
+
return moduleLoader(key, loader);
|
|
55
|
+
}
|
|
56
|
+
/** @codexApi {"parent":"pithy.router.module-loader","name":"getRouteModuleKey","stability":"stable","signature":"(pattern: string, type: RouteModuleType) => string"} */
|
|
57
|
+
/**
|
|
58
|
+
* Generate a cache key for a route module
|
|
59
|
+
*
|
|
60
|
+
* @param pattern - Route pattern (e.g., "/users/:id")
|
|
61
|
+
* @param type - Module type ("page", "layout", "loading", "error", "loader")
|
|
62
|
+
* @returns Cache key (e.g., "route:/users/:id:page")
|
|
63
|
+
*/
|
|
64
|
+
export function getRouteModuleKey(pattern, type) {
|
|
65
|
+
return `route:${pattern}:${type}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Invalidate a route module in the cache (for HMR)
|
|
69
|
+
*/
|
|
70
|
+
async function invalidateRouteModule(key) {
|
|
71
|
+
try {
|
|
72
|
+
const core = await import('@pithyjs/core');
|
|
73
|
+
if (core.invalidateModule) {
|
|
74
|
+
return core.invalidateModule(key);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Core not available
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Invalidate all cached modules for a route pattern (for HMR)
|
|
84
|
+
* Clears page, layout, loading, error, and loader cache entries
|
|
85
|
+
*/
|
|
86
|
+
async function invalidateRouteModules(pattern) {
|
|
87
|
+
for (const type of ROUTE_MODULE_TYPES) {
|
|
88
|
+
await invalidateRouteModule(getRouteModuleKey(pattern, type));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** @codexApi {"parent":"pithy.router.module-loader","name":"invalidateAllRouteModules","stability":"stable","signature":"(routes: Array<{ pattern: string }>) => Promise<void>"} */
|
|
92
|
+
/**
|
|
93
|
+
* Invalidate all route modules for a list of routes (for HMR full refresh)
|
|
94
|
+
*
|
|
95
|
+
* @param routes - Array of route nodes to invalidate
|
|
96
|
+
*/
|
|
97
|
+
export async function invalidateAllRouteModules(routes) {
|
|
98
|
+
for (const route of routes) {
|
|
99
|
+
await invalidateRouteModules(route.pattern);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** @codexApi {"parent":"pithy.router.module-loader","name":"clearAllModuleCache","stability":"stable","signature":"() => Promise<void>"} */
|
|
103
|
+
/**
|
|
104
|
+
* Clear all route modules from the shared cache (for testing)
|
|
105
|
+
* This clears both the loader cache and the @pithyjs/core module cache
|
|
106
|
+
*/
|
|
107
|
+
export async function clearAllModuleCache() {
|
|
108
|
+
// Reset the local loader cache
|
|
109
|
+
cachedLoader = null;
|
|
110
|
+
// Clear the @pithyjs/core module cache if available
|
|
111
|
+
try {
|
|
112
|
+
const core = await import('@pithyjs/core');
|
|
113
|
+
if (core.clearModuleCache) {
|
|
114
|
+
core.clearModuleCache();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Core not available
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=module-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-loader.js","sourceRoot":"","sources":["../../../src/router/features/module-loader.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAwB,MAAM,mBAAmB,CAAC;AAE7E;;GAEG;AACH,IAAI,YAAY,GAEL,IAAI,CAAC;AAEhB;;;GAGG;AACH,KAAK,UAAU,eAAe;IAG5B,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;YAC/B,YAAY,GAAG,MAAM,CAAC;YACtB,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qBAAqB;IACvB,CAAC;IAED,0CAA0C;IAC1C,YAAY,GAAG,CAAC,IAAY,EAAE,MAA8B,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC;IAC1E,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,6KAA6K;AAC7K;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,MAAwB;IAExB,MAAM,YAAY,GAAG,MAAM,eAAe,EAAE,CAAC;IAC7C,OAAO,YAAY,CAAC,GAAG,EAAE,MAAM,CAAe,CAAC;AACjD,CAAC;AAED,yKAAyK;AACzK;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,IAAqB;IAErB,OAAO,SAAS,OAAO,IAAI,IAAI,EAAE,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,qBAAqB,CAAC,GAAW;IAC9C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qBAAqB;IACvB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,sBAAsB,CAAC,OAAe;IACnD,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE,CAAC;QACtC,MAAM,qBAAqB,CAAC,iBAAiB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,oLAAoL;AACpL;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAAkC;IAElC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,sBAAsB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC;AAED,4IAA4I;AAC5I;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,+BAA+B;IAC/B,YAAY,GAAG,IAAI,CAAC;IAEpB,oDAAoD;IACpD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qBAAqB;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.outlet.errors",
|
|
4
|
+
"title": "Outlet Error Boundary System",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import type { RouteSegmentChain, NavigationId } from '../../core/types';
|
|
9
|
+
import { type RouterError } from '../errors';
|
|
10
|
+
/** @codexApi {"parent":"pithy.router.outlet.errors","name":"buildErrorUI","stability":"experimental","signature":"(segment: RouteSegmentChain, navId: NavigationId, error: any, retryHandler: () => void) => Promise<HTMLElement>"} */
|
|
11
|
+
/**
|
|
12
|
+
* Build error UI for a route segment
|
|
13
|
+
* Returns an HTMLElement with error component or global fallback
|
|
14
|
+
*
|
|
15
|
+
* Tries to resolve error component from route hierarchy, falls back to global error UI
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildErrorUI(segment: RouteSegmentChain, navId: NavigationId, error: Error & Partial<RouterError>, retryHandler: () => void): Promise<HTMLElement>;
|
|
18
|
+
/** @codexApi {"parent":"pithy.router.outlet.errors","name":"createRetryHandler","stability":"experimental","signature":"() => () => void"} */
|
|
19
|
+
/**
|
|
20
|
+
* Create a retry handler for route navigation
|
|
21
|
+
* Simple URL-level refresh for v0
|
|
22
|
+
*/
|
|
23
|
+
export declare function createRetryHandler(): () => void;
|
|
24
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../src/router/features/outlet/errors.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EAEb,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAyB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAEpE,uOAAuO;AACvO;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,EACnC,YAAY,EAAE,MAAM,IAAI,GACvB,OAAO,CAAC,WAAW,CAAC,CAiEtB;AAaD,8IAA8I;AAC9I;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,IAAI,CAM/C"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.outlet.errors",
|
|
4
|
+
"title": "Outlet Error Boundary System",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import { resolveErrorImport } from '../errors/error-resolution';
|
|
9
|
+
import { GlobalErrorFallback } from '../errors/global-error-fallback';
|
|
10
|
+
import { ErrorRecoveryStrategy } from '../errors';
|
|
11
|
+
/** @codexApi {"parent":"pithy.router.outlet.errors","name":"buildErrorUI","stability":"experimental","signature":"(segment: RouteSegmentChain, navId: NavigationId, error: any, retryHandler: () => void) => Promise<HTMLElement>"} */
|
|
12
|
+
/**
|
|
13
|
+
* Build error UI for a route segment
|
|
14
|
+
* Returns an HTMLElement with error component or global fallback
|
|
15
|
+
*
|
|
16
|
+
* Tries to resolve error component from route hierarchy, falls back to global error UI
|
|
17
|
+
*/
|
|
18
|
+
export async function buildErrorUI(segment, navId, error, retryHandler) {
|
|
19
|
+
const { node } = segment;
|
|
20
|
+
// Check recovery strategy - silent fail errors don't show UI
|
|
21
|
+
const strategy = error?.getRecoveryStrategy?.();
|
|
22
|
+
if (strategy === ErrorRecoveryStrategy.SILENT_FAIL) {
|
|
23
|
+
console.log('[Router] Silent fail - error will not display UI:', error);
|
|
24
|
+
// Return empty hidden placeholder
|
|
25
|
+
const placeholder = document.createElement('div');
|
|
26
|
+
placeholder.style.display = 'none';
|
|
27
|
+
placeholder.setAttribute('data-silent-error', 'true');
|
|
28
|
+
return placeholder;
|
|
29
|
+
}
|
|
30
|
+
// Build a node-to-segment map for parent lookups
|
|
31
|
+
// This allows us to traverse the parent chain to find error boundaries
|
|
32
|
+
const nodeToSegment = new Map();
|
|
33
|
+
let currentSegment = segment;
|
|
34
|
+
while (currentSegment) {
|
|
35
|
+
nodeToSegment.set(currentSegment.node, currentSegment);
|
|
36
|
+
currentSegment = currentSegment.parent;
|
|
37
|
+
}
|
|
38
|
+
// Try to resolve error component from route hierarchy
|
|
39
|
+
// Walk up the parent chain until we find an error component
|
|
40
|
+
const errorImport = resolveErrorImport(node, n => {
|
|
41
|
+
const seg = nodeToSegment.get(n);
|
|
42
|
+
return seg?.parent?.node;
|
|
43
|
+
});
|
|
44
|
+
// If route has error component, use it
|
|
45
|
+
if (errorImport) {
|
|
46
|
+
try {
|
|
47
|
+
const errorModule = (await errorImport());
|
|
48
|
+
const candidate = errorModule.default ?? errorModule;
|
|
49
|
+
if (typeof candidate === 'function') {
|
|
50
|
+
const errorFactory = candidate;
|
|
51
|
+
const errorInstance = errorFactory({
|
|
52
|
+
error,
|
|
53
|
+
retry: retryHandler,
|
|
54
|
+
});
|
|
55
|
+
if (errorInstance && errorInstance.el) {
|
|
56
|
+
return toElement(errorInstance.el);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.warn('[Router] Route error component default export is not a function; falling back to global error UI.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (errorLoadError) {
|
|
64
|
+
console.warn('[Router] Failed to load error component:', errorLoadError);
|
|
65
|
+
// Fall through to global fallback
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Use global fallback
|
|
69
|
+
const fallbackInstance = GlobalErrorFallback({
|
|
70
|
+
error,
|
|
71
|
+
retry: retryHandler,
|
|
72
|
+
});
|
|
73
|
+
return toElement(fallbackInstance.el);
|
|
74
|
+
}
|
|
75
|
+
// Wrap fragments in a div — appendChild moves the nodes (preserving listeners)
|
|
76
|
+
// so callers that hold the returned element keep a live, non-empty handle.
|
|
77
|
+
function toElement(node) {
|
|
78
|
+
if (node instanceof DocumentFragment) {
|
|
79
|
+
const wrapper = document.createElement('div');
|
|
80
|
+
wrapper.appendChild(node);
|
|
81
|
+
return wrapper;
|
|
82
|
+
}
|
|
83
|
+
return node;
|
|
84
|
+
}
|
|
85
|
+
/** @codexApi {"parent":"pithy.router.outlet.errors","name":"createRetryHandler","stability":"experimental","signature":"() => () => void"} */
|
|
86
|
+
/**
|
|
87
|
+
* Create a retry handler for route navigation
|
|
88
|
+
* Simple URL-level refresh for v0
|
|
89
|
+
*/
|
|
90
|
+
export function createRetryHandler() {
|
|
91
|
+
return () => {
|
|
92
|
+
const currentUrl = window.location.pathname + window.location.search + window.location.hash;
|
|
93
|
+
window.location.href = currentUrl;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../../src/router/features/outlet/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** @codex
|
|
2
|
+
{
|
|
3
|
+
"id": "pithy.router.outlet",
|
|
4
|
+
"title": "Router Outlet Mounting System",
|
|
5
|
+
"category": "runtime"
|
|
6
|
+
}
|
|
7
|
+
*/
|
|
8
|
+
import type { RouteSegmentChain, NavigationId } from '../../core/types';
|
|
9
|
+
import { type RouterInstance } from './mount';
|
|
10
|
+
/** @codexApi {"parent":"pithy.router.outlet","name":"OutletManager","stability":"experimental","signature":"class OutletManager"} */
|
|
11
|
+
/**
|
|
12
|
+
* Enhanced outlet mounting system
|
|
13
|
+
* Implements segment diff mounting with robust outlet resolution
|
|
14
|
+
* Fixed memory leak with proper two-phase mounting and cleanup
|
|
15
|
+
*/
|
|
16
|
+
export declare class OutletManager {
|
|
17
|
+
private mountedComponents;
|
|
18
|
+
private loadingStates;
|
|
19
|
+
private outletElements;
|
|
20
|
+
private router;
|
|
21
|
+
private disposers;
|
|
22
|
+
/** @codexApi {"parent":"pithy.router.outlet","name":"mountSegments","stability":"experimental","signature":"(segmentChain: RouteSegmentChain[], navId: NavigationId, rootElement: HTMLElement) => Promise<boolean>"} */
|
|
23
|
+
/**
|
|
24
|
+
* Mount route segments with two-phase mounting and rollback
|
|
25
|
+
* Phase 1: Build everything off-DOM, Phase 2: Atomic commit
|
|
26
|
+
* Only remounts changed segments, preserves parent layouts
|
|
27
|
+
*/
|
|
28
|
+
mountSegments(segmentChain: RouteSegmentChain[], navId: NavigationId, rootElement: HTMLElement): Promise<boolean>;
|
|
29
|
+
/** @codexApi {"parent":"pithy.router.outlet","name":"setRouter","stability":"experimental","signature":"(router: RouterInstance) => void"} */
|
|
30
|
+
/**
|
|
31
|
+
* Set router instance for navigation checking
|
|
32
|
+
*/
|
|
33
|
+
setRouter(router: RouterInstance): void;
|
|
34
|
+
/** @codexApi {"parent":"pithy.router.outlet","name":"dispose","stability":"experimental","signature":"() => void"} */
|
|
35
|
+
/**
|
|
36
|
+
* Clean up all mounted components and state
|
|
37
|
+
*/
|
|
38
|
+
dispose(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Generate unique key for outlet element
|
|
41
|
+
*/
|
|
42
|
+
private getOutletKey;
|
|
43
|
+
}
|
|
44
|
+
export type { RouterInstance };
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/router/features/outlet/index.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF,OAAO,KAAK,EACV,iBAAiB,EAGjB,YAAY,EACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,SAAS,CAAC;AAEjB,qIAAqI;AACrI;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,iBAAiB,CAA6C;IACtE,OAAO,CAAC,aAAa,CAAwC;IAC7D,OAAO,CAAC,cAAc,CAAkC;IACxD,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,SAAS,CAAwC;IAEzD,wNAAwN;IACxN;;;;OAIG;IACG,aAAa,CACjB,YAAY,EAAE,iBAAiB,EAAE,EACjC,KAAK,EAAE,YAAY,EACnB,WAAW,EAAE,WAAW,GACvB,OAAO,CAAC,OAAO,CAAC;IAoDnB,8IAA8I;IAC9I;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAIvC,sHAAsH;IACtH;;OAEG;IACH,OAAO,IAAI,IAAI;IAmBf;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB;AAED,YAAY,EAAE,cAAc,EAAE,CAAC"}
|