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/app/AppView.ts
DELETED
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
import { TemplateBinder } from 'template.ts';
|
|
2
|
-
import type { RouteParams } from '../navigation/types';
|
|
3
|
-
import { App } from './App';
|
|
4
|
-
import type {
|
|
5
|
-
AppViewLifecycle,
|
|
6
|
-
AppViewOptions,
|
|
7
|
-
AppViewState
|
|
8
|
-
} from './types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Abstract base class for creating views with Template.Ts
|
|
12
|
-
* Extends HTMLElement as a Web Component for seamless integration with StackView.Ts
|
|
13
|
-
* Custom elements are automatically registered when views are registered with App
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```typescript
|
|
17
|
-
* const template: `
|
|
18
|
-
* <div>
|
|
19
|
-
* <h1>Count: {{ count }}</h1>
|
|
20
|
-
* <button @on:click="increment">Increment</button>
|
|
21
|
-
* </div>`;
|
|
22
|
-
*
|
|
23
|
-
* class State {
|
|
24
|
-
* count: number = 0;
|
|
25
|
-
* increment: () => { this.count += 1; };
|
|
26
|
-
* }
|
|
27
|
-
*
|
|
28
|
-
* @Register
|
|
29
|
-
* class HomeView extends AppView<{ count: number }> {
|
|
30
|
-
* template(): string {
|
|
31
|
-
* return template;
|
|
32
|
-
* }
|
|
33
|
-
*
|
|
34
|
-
* state() {
|
|
35
|
-
* return new State();
|
|
36
|
-
* }
|
|
37
|
-
* }
|
|
38
|
-
*
|
|
39
|
-
* // Register with app - automatically registers as <home-view>
|
|
40
|
-
* app.registerView('HomeView', HomeView);
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export abstract class AppView<TState extends AppViewState = AppViewState> extends HTMLElement implements AppViewLifecycle {
|
|
44
|
-
protected binder: TemplateBinder | null = null;
|
|
45
|
-
protected _state: TState | null = null;
|
|
46
|
-
protected _options: AppViewOptions;
|
|
47
|
-
protected _root: HTMLElement | ShadowRoot;
|
|
48
|
-
private _isInitialized: boolean = false;
|
|
49
|
-
|
|
50
|
-
constructor(options?: AppViewOptions) {
|
|
51
|
-
super();
|
|
52
|
-
|
|
53
|
-
this._options = {
|
|
54
|
-
transitionClass: 'transition-fade',
|
|
55
|
-
autoUpdate: true,
|
|
56
|
-
useShadowDOM: false,
|
|
57
|
-
...options
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Create shadow root if enabled
|
|
61
|
-
if (this._options.useShadowDOM) {
|
|
62
|
-
const shadow = this.attachShadow({ mode: 'open' });
|
|
63
|
-
shadow.innerHTML = this.template();
|
|
64
|
-
if (!shadow.firstElementChild || !(shadow.firstElementChild instanceof HTMLElement)) {
|
|
65
|
-
throw new Error('AppView template must have a single root element');
|
|
66
|
-
}
|
|
67
|
-
this._root = shadow.firstElementChild as HTMLElement;
|
|
68
|
-
} else {
|
|
69
|
-
this.innerHTML = this.template();
|
|
70
|
-
if (!this.firstElementChild || !(this.firstElementChild instanceof HTMLElement)) {
|
|
71
|
-
throw new Error('AppView template must have a single root element');
|
|
72
|
-
}
|
|
73
|
-
this._root = this.firstElementChild as HTMLElement;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Eager initialization in constructor
|
|
77
|
-
this.initialize();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Render the view with route parameters
|
|
82
|
-
* Initializes the template and binds state
|
|
83
|
-
* @param params - Route parameters from the router
|
|
84
|
-
*/
|
|
85
|
-
initialize(): void {
|
|
86
|
-
if (this._isInitialized) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Add app-view class
|
|
91
|
-
this.classList.add('app-view');
|
|
92
|
-
|
|
93
|
-
// Initialize state
|
|
94
|
-
this._state = this.state();
|
|
95
|
-
|
|
96
|
-
// Bind template using container
|
|
97
|
-
this.binder = new TemplateBinder(
|
|
98
|
-
this._root,
|
|
99
|
-
this._state,
|
|
100
|
-
this._options.transitionClass
|
|
101
|
-
);
|
|
102
|
-
this.binder.autoUpdate = this._options.autoUpdate ?? true;
|
|
103
|
-
|
|
104
|
-
this.binder.bind();
|
|
105
|
-
|
|
106
|
-
this._isInitialized = true;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get the custom element tag name for this class
|
|
111
|
-
* Uses explicit tagName property or falls back to class name conversion
|
|
112
|
-
* To prevent minification issues, define static tagName property in your class
|
|
113
|
-
*/
|
|
114
|
-
static getTagName(): string {
|
|
115
|
-
// Use explicit tagName if provided (prevents minification issues)
|
|
116
|
-
if ((this as any).tagName && typeof (this as any).tagName === 'string') {
|
|
117
|
-
return (this as any).tagName;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Fallback to class name conversion (may break with minification)
|
|
121
|
-
return this.name
|
|
122
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
123
|
-
.toLowerCase();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Register this class as a custom element
|
|
128
|
-
* Should be called before instantiation
|
|
129
|
-
*/
|
|
130
|
-
static register(): void {
|
|
131
|
-
const tagName = this.getTagName();
|
|
132
|
-
if (!customElements.get(tagName)) {
|
|
133
|
-
customElements.define(tagName, this as any);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Define the HTML template for this view
|
|
139
|
-
* Use Template.Ts syntax for data binding
|
|
140
|
-
* @returns HTML template string
|
|
141
|
-
*/
|
|
142
|
-
protected abstract template(): string;
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Define the initial state for this view
|
|
146
|
-
* State should include data and methods
|
|
147
|
-
* @returns Initial state object
|
|
148
|
-
*/
|
|
149
|
-
protected abstract state(): TState;
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Get the current state
|
|
153
|
-
*/
|
|
154
|
-
protected get viewState(): TState {
|
|
155
|
-
if (!this._state) {
|
|
156
|
-
throw new Error('State not initialized. Call render() first.');
|
|
157
|
-
}
|
|
158
|
-
return this._state;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Get the route parameters passed to this view
|
|
163
|
-
*/
|
|
164
|
-
protected get params(): RouteParams {
|
|
165
|
-
return this.app?.router?.currentParams || {};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Get the App instance by traversing up the DOM tree
|
|
170
|
-
*/
|
|
171
|
-
protected get app(): App | null {
|
|
172
|
-
return App.fromElement(this);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Navigate to a specific path using the app's router
|
|
177
|
-
* @param path - The path to navigate to
|
|
178
|
-
*/
|
|
179
|
-
protected navigate(path: string): void {
|
|
180
|
-
const appInstance = this.app;
|
|
181
|
-
if (appInstance) {
|
|
182
|
-
appInstance.navigate(path);
|
|
183
|
-
} else {
|
|
184
|
-
console.warn('Cannot navigate: App instance not found');
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Update a single state value
|
|
190
|
-
* @param key - The state key to update
|
|
191
|
-
* @param value - The new value
|
|
192
|
-
*/
|
|
193
|
-
protected setState<K extends keyof TState>(key: K, value: TState[K]): void {
|
|
194
|
-
if (!this._state) {
|
|
195
|
-
console.warn('Cannot set state before initialization');
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
this._state[key] = value;
|
|
200
|
-
|
|
201
|
-
// Call lifecycle hook
|
|
202
|
-
if (this.onStateChanged) {
|
|
203
|
-
this.onStateChanged(key as string, value);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Auto-update if enabled
|
|
207
|
-
if (this._options.autoUpdate && this.binder) {
|
|
208
|
-
this.update();
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Update multiple state values at once
|
|
214
|
-
* @param updates - Object with state updates
|
|
215
|
-
*/
|
|
216
|
-
protected setStates(updates: Partial<TState>): void {
|
|
217
|
-
if (!this._state) {
|
|
218
|
-
console.warn('Cannot set state before initialization');
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
Object.assign(this._state, updates);
|
|
223
|
-
|
|
224
|
-
// Call lifecycle hooks for each update
|
|
225
|
-
if (this.onStateChanged) {
|
|
226
|
-
for (const [key, value] of Object.entries(updates)) {
|
|
227
|
-
this.onStateChanged(key, value);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Auto-update if enabled
|
|
232
|
-
if (this._options.autoUpdate && this.binder) {
|
|
233
|
-
this.update();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Manually trigger a view update
|
|
239
|
-
* @param withAnimation - Whether to apply transition animation
|
|
240
|
-
*/
|
|
241
|
-
public update(withAnimation: boolean = true): void {
|
|
242
|
-
if (this.binder) {
|
|
243
|
-
this.binder.update(withAnimation);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Update route parameters and re-trigger initialization logic
|
|
249
|
-
* Used when navigating to the same view with different parameters
|
|
250
|
-
* @param params - New route parameters
|
|
251
|
-
*/
|
|
252
|
-
async updateParams(params: RouteParams = {}): Promise<void> {
|
|
253
|
-
// Call the parameter changed hook with old and new params
|
|
254
|
-
if (this.onParamsChanged) {
|
|
255
|
-
await this.onParamsChanged(params, this.params);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Update the view if needed
|
|
259
|
-
if (this.binder && this._options.autoUpdate) {
|
|
260
|
-
this.update();
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* StackView lifecycle: called when view is about to be shown
|
|
266
|
-
*/
|
|
267
|
-
async stackViewShowing(): Promise<void> {
|
|
268
|
-
// Call before mount hook
|
|
269
|
-
if (this.onBeforeMount) {
|
|
270
|
-
await this.onBeforeMount();
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* StackView lifecycle: called when view is about to be hidden
|
|
276
|
-
*/
|
|
277
|
-
async stackViewHiding(): Promise<void> {
|
|
278
|
-
if (this.onBeforeUnmount) {
|
|
279
|
-
await this.onBeforeUnmount();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* StackView lifecycle: called after view is hidden
|
|
285
|
-
*/
|
|
286
|
-
async stackViewHidden(): Promise<void> {
|
|
287
|
-
// Destroy binder
|
|
288
|
-
if (this.binder) {
|
|
289
|
-
this.binder.destroy();
|
|
290
|
-
this.binder = null;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
this._isInitialized = false;
|
|
294
|
-
|
|
295
|
-
if (this.onUnmounted) {
|
|
296
|
-
await this.onUnmounted();
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Web Component lifecycle: called when connected to DOM
|
|
302
|
-
*/
|
|
303
|
-
async connectedCallback(): Promise<void> {
|
|
304
|
-
// Initialize if not already done
|
|
305
|
-
if (!this._isInitialized) {
|
|
306
|
-
await this.initialize();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (this.onMounted) {
|
|
310
|
-
await this.onMounted();
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Web Component lifecycle: called when disconnected from DOM
|
|
316
|
-
*/
|
|
317
|
-
disconnectedCallback(): void {
|
|
318
|
-
// Cleanup is handled by stackViewHidden
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Check if the view is initialized
|
|
323
|
-
*/
|
|
324
|
-
get isInitialized(): boolean {
|
|
325
|
-
return this._isInitialized;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Lifecycle hooks (can be overridden by subclasses)
|
|
329
|
-
onBeforeMount?(): void | Promise<void>;
|
|
330
|
-
onMounted?(): void | Promise<void>;
|
|
331
|
-
onBeforeUnmount?(): void | Promise<void>;
|
|
332
|
-
onUnmounted?(): void | Promise<void>;
|
|
333
|
-
onStateChanged?(key: string, value: any): void;
|
|
334
|
-
onParamsChanged?(newParams: RouteParams, oldParams: RouteParams): void | Promise<void>;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Decorator to register a view as a custom element
|
|
339
|
-
* Optionally accepts a custom tag name to prevent minification issues
|
|
340
|
-
*
|
|
341
|
-
* @example
|
|
342
|
-
* @Register() // Auto-generates tag name from class name
|
|
343
|
-
* @Register('my-component') // Explicit tag name (recommended for production)
|
|
344
|
-
*/
|
|
345
|
-
export function Register(tagName?: string) : any {
|
|
346
|
-
return function(target: any) {
|
|
347
|
-
// Use provided tag name or generate from class name
|
|
348
|
-
if (tagName) {
|
|
349
|
-
target.tagName = tagName;
|
|
350
|
-
} else if (!target.tagName) {
|
|
351
|
-
// Fallback to class name conversion (may fail with minification)
|
|
352
|
-
target.tagName = target.name
|
|
353
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
354
|
-
.toLowerCase();
|
|
355
|
-
}
|
|
356
|
-
target.register();
|
|
357
|
-
return target;
|
|
358
|
-
};
|
|
359
|
-
}
|
package/src/app/types.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import type { RouteParams } from '../navigation/types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* AppView lifecycle hooks
|
|
5
|
-
*/
|
|
6
|
-
export interface AppViewLifecycle {
|
|
7
|
-
/**
|
|
8
|
-
* Called before the view is shown
|
|
9
|
-
* Use this to initialize data, fetch from APIs, etc.
|
|
10
|
-
*/
|
|
11
|
-
onBeforeMount?(): void | Promise<void>;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Called after the view is mounted to the DOM
|
|
15
|
-
* Use this to set up event listeners, third-party libraries, etc.
|
|
16
|
-
*/
|
|
17
|
-
onMounted?(): void | Promise<void>;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Called before the view is unmounted
|
|
21
|
-
* Use this to clean up resources, save state, etc.
|
|
22
|
-
*/
|
|
23
|
-
onBeforeUnmount?(): void | Promise<void>;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Called after the view is unmounted from the DOM
|
|
27
|
-
* Final cleanup
|
|
28
|
-
*/
|
|
29
|
-
onUnmounted?(): void | Promise<void>;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Called when the state is updated
|
|
33
|
-
* Use this to react to state changes
|
|
34
|
-
*/
|
|
35
|
-
onStateChanged?(key: string, value: any): void;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Called when route parameters change while staying on the same view
|
|
39
|
-
* Use this to reload data based on new parameters (e.g., /user/1 -> /user/2)
|
|
40
|
-
* @param newParams - The new route parameters
|
|
41
|
-
* @param oldParams - The previous route parameters
|
|
42
|
-
*/
|
|
43
|
-
onParamsChanged?(newParams: RouteParams, oldParams: RouteParams): void | Promise<void>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* AppView configuration options
|
|
48
|
-
*/
|
|
49
|
-
export interface AppViewOptions {
|
|
50
|
-
/**
|
|
51
|
-
* Custom transition class for Template.Ts animations
|
|
52
|
-
* Default: 'transition-fade'
|
|
53
|
-
*/
|
|
54
|
-
transitionClass?: string;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Whether to automatically update the view on state changes
|
|
58
|
-
* Default: true
|
|
59
|
-
*/
|
|
60
|
-
autoUpdate?: boolean;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Whether to use Shadow DOM for the template
|
|
64
|
-
* When true, template is rendered in shadow root (enables <slot> to work properly)
|
|
65
|
-
* Default: false
|
|
66
|
-
*/
|
|
67
|
-
useShadowDOM?: boolean;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* AppView state object - can be any shape defined by the developer
|
|
72
|
-
*/
|
|
73
|
-
export type AppViewState = Record<string, any>;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Template function that returns HTML string
|
|
77
|
-
*/
|
|
78
|
-
export type TemplateFunction<T extends AppViewState = AppViewState> = (this: T) => string;
|
package/src/index.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Main entry point for the application
|
|
3
|
-
* Export all public APIs
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export { App } from './app/App';
|
|
7
|
-
export { AppView, Register } from './app/AppView';
|
|
8
|
-
export { Router } from './navigation/router';
|
|
9
|
-
export { Route } from './navigation/route';
|
|
10
|
-
|
|
11
|
-
// Export types
|
|
12
|
-
export type {
|
|
13
|
-
AppViewLifecycle,
|
|
14
|
-
AppViewOptions,
|
|
15
|
-
AppViewState,
|
|
16
|
-
TemplateFunction
|
|
17
|
-
} from './app/types';
|
|
18
|
-
|
|
19
|
-
export type {
|
|
20
|
-
RouteParams,
|
|
21
|
-
RouteGuard,
|
|
22
|
-
RouteOptions,
|
|
23
|
-
RouteHandler,
|
|
24
|
-
RouteDefinition,
|
|
25
|
-
NavigationEventDetail
|
|
26
|
-
} from './navigation/types';
|
|
27
|
-
|
|
28
|
-
export { NavigationEvents } from './navigation/types';
|
package/src/navigation/route.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import type { RouteParams, RouteOptions } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Represents a route with path pattern matching and parameter extraction
|
|
5
|
-
*/
|
|
6
|
-
export class Route {
|
|
7
|
-
public readonly path: string;
|
|
8
|
-
public readonly pattern: RegExp;
|
|
9
|
-
public readonly paramNames: string[];
|
|
10
|
-
public readonly options: RouteOptions;
|
|
11
|
-
|
|
12
|
-
constructor(path: string, options: RouteOptions = {}) {
|
|
13
|
-
this.path = path;
|
|
14
|
-
this.options = options;
|
|
15
|
-
this.paramNames = [];
|
|
16
|
-
|
|
17
|
-
// Convert path pattern to RegExp and extract parameter names
|
|
18
|
-
this.pattern = this.pathToRegExp(path);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Convert a path pattern like '/user/:id' to a RegExp
|
|
23
|
-
* Extracts parameter names like 'id'
|
|
24
|
-
*/
|
|
25
|
-
private pathToRegExp(path: string): RegExp {
|
|
26
|
-
// Escape special characters except for :param patterns
|
|
27
|
-
const escapedPath = path.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
28
|
-
|
|
29
|
-
// Replace :param with capture groups and extract param names
|
|
30
|
-
const pattern = escapedPath.replace(/:([^/]+)/g, (match, paramName) => {
|
|
31
|
-
this.paramNames.push(paramName);
|
|
32
|
-
return '([^/]+)';
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Match exact path or with trailing slash
|
|
36
|
-
return new RegExp(`^${pattern}/?$`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Check if the given path matches this route
|
|
41
|
-
* @param path - The path to test
|
|
42
|
-
* @returns The extracted parameters if match, null otherwise
|
|
43
|
-
*/
|
|
44
|
-
match(path: string): RouteParams | null {
|
|
45
|
-
const match = this.pattern.exec(path);
|
|
46
|
-
|
|
47
|
-
if (!match) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Extract parameters from capture groups
|
|
52
|
-
const params: RouteParams = {};
|
|
53
|
-
this.paramNames.forEach((name, index) => {
|
|
54
|
-
params[name] = decodeURIComponent(match[index + 1]);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
return params;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Check if navigation is allowed via the route guard
|
|
62
|
-
* @param params - Route parameters
|
|
63
|
-
* @returns true if allowed, false if denied, or redirect path
|
|
64
|
-
*/
|
|
65
|
-
async canEnter(params: RouteParams): Promise<boolean | string> {
|
|
66
|
-
if (!this.options.canEnter) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return await this.options.canEnter(params);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Generate a path from this route pattern with given parameters
|
|
75
|
-
* @param params - Parameters to inject into the path
|
|
76
|
-
* @returns The generated path
|
|
77
|
-
*/
|
|
78
|
-
generate(params: RouteParams = {}): string {
|
|
79
|
-
let path = this.path;
|
|
80
|
-
|
|
81
|
-
for (const [key, value] of Object.entries(params)) {
|
|
82
|
-
path = path.replace(`:${key}`, encodeURIComponent(value));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return path;
|
|
86
|
-
}
|
|
87
|
-
}
|