application.ts 1.0.1 → 1.0.3
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/README.md +4 -0
- package/dist/app/App.js +8 -4
- package/dist/app/App.js.map +1 -1
- package/dist/app/AppView.d.ts +4 -3
- package/dist/app/AppView.js +43 -7
- package/dist/app/AppView.js.map +1 -1
- package/package.json +11 -3
- package/src/app/App.ts +0 -391
- package/src/app/AppView.ts +0 -359
- package/src/app/types.ts +0 -78
- package/src/index.ts +0 -28
- package/src/navigation/route.ts +0 -87
- package/src/navigation/router.ts +0 -284
- package/src/navigation/types.ts +0 -73
package/src/navigation/router.ts
DELETED
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import { Route } from './route';
|
|
2
|
-
import type { RouteParams, RouteOptions, RouteHandler, NavigationEventDetail } from './types';
|
|
3
|
-
import { NavigationEvents } from './types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Router class for managing navigation and route mapping
|
|
7
|
-
* Emits events for Application.ts to handle view rendering via StackView
|
|
8
|
-
*/
|
|
9
|
-
export class Router extends EventTarget {
|
|
10
|
-
private routes: Map<string, { route: Route; handler: RouteHandler }> = new Map();
|
|
11
|
-
private notFoundHandler?: RouteHandler;
|
|
12
|
-
private isInitialized: boolean = false;
|
|
13
|
-
private basePath: string = '';
|
|
14
|
-
private _currentPath: string = '/';
|
|
15
|
-
private _currentParams: RouteParams = {};
|
|
16
|
-
private _currentMeta?: Record<string, any>;
|
|
17
|
-
|
|
18
|
-
constructor() {
|
|
19
|
-
super();
|
|
20
|
-
|
|
21
|
-
// Listen for popstate events (browser back/forward)
|
|
22
|
-
window.addEventListener('popstate', () => {
|
|
23
|
-
this.handleRouteChange();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// Intercept link clicks for client-side navigation
|
|
27
|
-
document.addEventListener('click', (e) => {
|
|
28
|
-
const target = e.target as HTMLElement;
|
|
29
|
-
const link = target.closest('a[href]') as HTMLAnchorElement;
|
|
30
|
-
|
|
31
|
-
if (link && link.href.startsWith(window.location.origin) && !link.hasAttribute('target')) {
|
|
32
|
-
e.preventDefault();
|
|
33
|
-
this.navigate(link.pathname);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Map a route path to a handler (view identifier)
|
|
40
|
-
* @param path - The route path (e.g., '/', '/user/:id')
|
|
41
|
-
* @param handler - The view identifier/handler
|
|
42
|
-
* @param options - Optional route configuration
|
|
43
|
-
* @returns The router instance for chaining
|
|
44
|
-
*/
|
|
45
|
-
map(path: string, handler: RouteHandler, options?: RouteOptions): this {
|
|
46
|
-
const route = new Route(path, options);
|
|
47
|
-
this.routes.set(path, { route, handler });
|
|
48
|
-
return this;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Set the 404 not found handler
|
|
53
|
-
* @param handler - The view identifier to use for 404
|
|
54
|
-
* @returns The router instance for chaining
|
|
55
|
-
*/
|
|
56
|
-
notFound(handler: RouteHandler): this {
|
|
57
|
-
this.notFoundHandler = handler;
|
|
58
|
-
return this;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Set the base path for all routes
|
|
63
|
-
* @param base - Base path (e.g., '/basic', '/app')
|
|
64
|
-
* @returns The router instance for chaining
|
|
65
|
-
*/
|
|
66
|
-
setBasePath(base: string): this {
|
|
67
|
-
// Normalize: ensure starts with / and no trailing /
|
|
68
|
-
this.basePath = base.replace(/\/$/, '').replace(/^(?!\/)/, '/');
|
|
69
|
-
return this;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get the current base path
|
|
74
|
-
*/
|
|
75
|
-
getBasePath(): string {
|
|
76
|
-
return this.basePath;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Initialize the router and handle the current path
|
|
81
|
-
*/
|
|
82
|
-
start(): void {
|
|
83
|
-
if (this.isInitialized) {
|
|
84
|
-
console.warn('Router is already initialized');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Auto-detect base path from <base> tag if not set
|
|
89
|
-
if (!this.basePath) {
|
|
90
|
-
const baseTag = document.querySelector('base[href]') as HTMLBaseElement;
|
|
91
|
-
if (baseTag) {
|
|
92
|
-
const baseHref = baseTag.getAttribute('href') || '';
|
|
93
|
-
// Extract path from href (could be full URL or relative path)
|
|
94
|
-
try {
|
|
95
|
-
const url = new URL(baseHref, window.location.origin);
|
|
96
|
-
this.basePath = url.pathname.replace(/\/$/, '');
|
|
97
|
-
} catch {
|
|
98
|
-
this.basePath = baseHref.replace(/\/$/, '');
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
this.isInitialized = true;
|
|
104
|
-
this.handleRouteChange();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Navigate to a specific path
|
|
109
|
-
* @param path - The path to navigate to (relative to basePath)
|
|
110
|
-
* @param replaceState - If true, replaces current history entry instead of pushing
|
|
111
|
-
*/
|
|
112
|
-
async navigate(path: string, replaceState: boolean = false): Promise<void> {
|
|
113
|
-
// Strip basePath if present to get route path
|
|
114
|
-
const routePath = this.stripBasePath(path);
|
|
115
|
-
|
|
116
|
-
// Find matching route
|
|
117
|
-
const match = this.findRoute(routePath);
|
|
118
|
-
|
|
119
|
-
if (!match) {
|
|
120
|
-
this.emitNotFound(routePath);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const { route, handler, params } = match;
|
|
125
|
-
|
|
126
|
-
// Check route guard
|
|
127
|
-
const guardResult = await route.canEnter(params);
|
|
128
|
-
|
|
129
|
-
if (guardResult === false) {
|
|
130
|
-
// Navigation denied
|
|
131
|
-
console.warn(`Navigation to ${path} was denied by route guard`);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (typeof guardResult === 'string') {
|
|
136
|
-
// Redirect to another path
|
|
137
|
-
await this.navigate(guardResult, replaceState);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Add basePath to create full URL
|
|
142
|
-
const fullPath = this.addBasePath(routePath);
|
|
143
|
-
|
|
144
|
-
// Update browser history
|
|
145
|
-
if (replaceState) {
|
|
146
|
-
window.history.replaceState({ path: fullPath }, '', fullPath);
|
|
147
|
-
} else {
|
|
148
|
-
window.history.pushState({ path: fullPath }, '', fullPath);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Emit navigation event for Application.ts to handle
|
|
152
|
-
this.emitNavigation(routePath, params, handler, route.options.meta);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get the current path (route pattern)
|
|
157
|
-
*/
|
|
158
|
-
get currentPath(): string {
|
|
159
|
-
return this._currentPath;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get the current route parameters
|
|
164
|
-
*/
|
|
165
|
-
get currentParams(): RouteParams {
|
|
166
|
-
return this._currentParams;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Get the current route metadata
|
|
171
|
-
*/
|
|
172
|
-
get currentMeta(): Record<string, any> | undefined {
|
|
173
|
-
return this._currentMeta;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Find a route that matches the given path
|
|
178
|
-
*/
|
|
179
|
-
private findRoute(path: string): { route: Route; handler: RouteHandler; params: RouteParams } | null {
|
|
180
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
181
|
-
for (const [_, { route, handler }] of this.routes) {
|
|
182
|
-
const params = route.match(path);
|
|
183
|
-
if (params !== null) {
|
|
184
|
-
return { route, handler, params };
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Handle route change (from popstate or initial load)
|
|
192
|
-
*/
|
|
193
|
-
private async handleRouteChange(): Promise<void> {
|
|
194
|
-
const routePath = this.stripBasePath(window.location.pathname);
|
|
195
|
-
await this.navigate(routePath, true);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Strip base path from a full path
|
|
200
|
-
*/
|
|
201
|
-
private stripBasePath(fullPath: string): string {
|
|
202
|
-
if (!this.basePath) {
|
|
203
|
-
return fullPath;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (fullPath.startsWith(this.basePath)) {
|
|
207
|
-
const stripped = fullPath.slice(this.basePath.length);
|
|
208
|
-
return stripped || '/';
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return fullPath;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Add base path to a route path
|
|
216
|
-
*/
|
|
217
|
-
private addBasePath(routePath: string): string {
|
|
218
|
-
if (!this.basePath) {
|
|
219
|
-
return routePath;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Ensure route path starts with /
|
|
223
|
-
const normalizedPath = routePath.startsWith('/') ? routePath : '/' + routePath;
|
|
224
|
-
return this.basePath + normalizedPath;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Emit navigation event
|
|
229
|
-
*/
|
|
230
|
-
private emitNavigation(path: string, params: RouteParams, handler: RouteHandler, meta?: Record<string, any>): void {
|
|
231
|
-
// Store current navigation state
|
|
232
|
-
this._currentPath = path;
|
|
233
|
-
this._currentParams = params;
|
|
234
|
-
this._currentMeta = meta;
|
|
235
|
-
|
|
236
|
-
const detail: NavigationEventDetail = {
|
|
237
|
-
path,
|
|
238
|
-
params,
|
|
239
|
-
handler,
|
|
240
|
-
meta
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
this.dispatchEvent(new CustomEvent(NavigationEvents.NAVIGATE, { detail }));
|
|
244
|
-
window.dispatchEvent(new CustomEvent(NavigationEvents.NAVIGATE, { detail }));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Emit not found event
|
|
249
|
-
*/
|
|
250
|
-
private emitNotFound(path: string): void {
|
|
251
|
-
if (this.notFoundHandler) {
|
|
252
|
-
// Store current navigation state
|
|
253
|
-
this._currentPath = path;
|
|
254
|
-
this._currentParams = {};
|
|
255
|
-
this._currentMeta = undefined;
|
|
256
|
-
|
|
257
|
-
const detail: NavigationEventDetail = {
|
|
258
|
-
path,
|
|
259
|
-
params: {},
|
|
260
|
-
handler: this.notFoundHandler
|
|
261
|
-
};
|
|
262
|
-
this.dispatchEvent(new CustomEvent(NavigationEvents.NOT_FOUND, { detail }));
|
|
263
|
-
window.dispatchEvent(new CustomEvent(NavigationEvents.NOT_FOUND, { detail }));
|
|
264
|
-
} else {
|
|
265
|
-
console.error(`404: No route found for ${path} and no notFound handler configured`);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Generate a URL for a route with parameters
|
|
271
|
-
* @param path - The route path pattern
|
|
272
|
-
* @param params - Parameters to inject
|
|
273
|
-
* @returns The generated URL (with basePath) or null if route not found
|
|
274
|
-
*/
|
|
275
|
-
generateUrl(path: string, params: RouteParams = {}): string | null {
|
|
276
|
-
const routeEntry = this.routes.get(path);
|
|
277
|
-
if (!routeEntry) {
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const routePath = routeEntry.route.generate(params);
|
|
282
|
-
return this.addBasePath(routePath);
|
|
283
|
-
}
|
|
284
|
-
}
|
package/src/navigation/types.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Route parameters extracted from URL path
|
|
3
|
-
*/
|
|
4
|
-
export type RouteParams = Record<string, string>;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Navigation guard function that can prevent navigation
|
|
8
|
-
* @returns true to allow navigation, false to prevent, or string path to redirect
|
|
9
|
-
*/
|
|
10
|
-
export type RouteGuard = (params: RouteParams) => boolean | string | Promise<boolean | string>;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Route configuration options
|
|
14
|
-
*/
|
|
15
|
-
export interface RouteOptions {
|
|
16
|
-
/**
|
|
17
|
-
* Guard function to check if navigation is allowed
|
|
18
|
-
*/
|
|
19
|
-
canEnter?: RouteGuard;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Additional metadata for the route
|
|
23
|
-
*/
|
|
24
|
-
meta?: Record<string, any>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Route handler - reference to view class constructor or string identifier
|
|
29
|
-
*/
|
|
30
|
-
export type RouteHandler<T = any> = (new () => T) | string;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Route definition
|
|
34
|
-
*/
|
|
35
|
-
export interface RouteDefinition {
|
|
36
|
-
path: string;
|
|
37
|
-
handler: RouteHandler;
|
|
38
|
-
options?: RouteOptions;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Navigation event detail
|
|
43
|
-
*/
|
|
44
|
-
export interface NavigationEventDetail {
|
|
45
|
-
/**
|
|
46
|
-
* The path being navigated to
|
|
47
|
-
*/
|
|
48
|
-
path: string;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Route parameters extracted from the path
|
|
52
|
-
*/
|
|
53
|
-
params: RouteParams;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* The route handler (view identifier)
|
|
57
|
-
*/
|
|
58
|
-
handler: RouteHandler;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Route metadata
|
|
62
|
-
*/
|
|
63
|
-
meta?: Record<string, any>;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Navigation events
|
|
68
|
-
*/
|
|
69
|
-
export const NavigationEvents = {
|
|
70
|
-
BEFORE_NAVIGATE: 'navigation:before',
|
|
71
|
-
NAVIGATE: 'navigation:navigate',
|
|
72
|
-
NOT_FOUND: 'navigation:notfound'
|
|
73
|
-
} as const;
|