slicejs-web-framework 2.3.1 → 2.3.2
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/Slice/Components/Structural/ContextManager/ContextManager.js +369 -361
- package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +258 -0
- package/Slice/Components/Structural/Controller/Controller.js +944 -925
- package/Slice/Components/Structural/Debugger/Debugger.js +1547 -1347
- package/Slice/Components/Structural/EventManager/EventManager.js +338 -329
- package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +238 -0
- package/Slice/Components/Structural/Logger/Logger.js +146 -104
- package/Slice/Components/Structural/Router/Router.js +720 -598
- package/Slice/Components/Structural/StylesManager/StylesManager.js +64 -49
- package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +84 -56
- package/Slice/Slice.js +383 -297
- package/package.json +1 -1
|
@@ -1,598 +1,720 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
*
|
|
141
|
-
* @param {Object}
|
|
142
|
-
* @
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
*
|
|
282
|
-
* @
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
//
|
|
384
|
-
this.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
this.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} RouteConfig
|
|
3
|
+
* @property {string} path
|
|
4
|
+
* @property {string} component
|
|
5
|
+
* @property {RouteConfig[]} [children]
|
|
6
|
+
* @property {Object} [metadata]
|
|
7
|
+
* @property {string} [fullPath]
|
|
8
|
+
* @property {string|null} [parentPath]
|
|
9
|
+
* @property {RouteConfig|null} [parentRoute]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} RouteInfo
|
|
14
|
+
* @property {string} path
|
|
15
|
+
* @property {string} component
|
|
16
|
+
* @property {Object} params
|
|
17
|
+
* @property {Object} query
|
|
18
|
+
* @property {Object} metadata
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Object} GuardRedirect
|
|
23
|
+
* @property {string} path
|
|
24
|
+
* @property {boolean} [replace]
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} RouteMatch
|
|
29
|
+
* @property {RouteConfig|null} route
|
|
30
|
+
* @property {Object} params
|
|
31
|
+
* @property {RouteConfig} [childRoute]
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @callback RouterNext
|
|
36
|
+
* @param {void|false|string|{ path: string, replace?: boolean }} [arg]
|
|
37
|
+
* @returns {void}
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export default class Router {
|
|
41
|
+
/**
|
|
42
|
+
* @param {RouteConfig[]} routes
|
|
43
|
+
*/
|
|
44
|
+
constructor(routes) {
|
|
45
|
+
this.routes = routes;
|
|
46
|
+
this.activeRoute = null;
|
|
47
|
+
this.pathToRouteMap = this.createPathToRouteMap(routes);
|
|
48
|
+
|
|
49
|
+
// Navigation Guards
|
|
50
|
+
this._beforeEachGuard = null;
|
|
51
|
+
this._afterEachGuard = null;
|
|
52
|
+
|
|
53
|
+
// Router state
|
|
54
|
+
this._started = false;
|
|
55
|
+
this._autoStartTimeout = null;
|
|
56
|
+
|
|
57
|
+
// Sistema de caché optimizado
|
|
58
|
+
this.routeContainersCache = new Map();
|
|
59
|
+
this.lastCacheUpdate = 0;
|
|
60
|
+
this.CACHE_DURATION = 100; // ms - caché muy corto pero efectivo
|
|
61
|
+
|
|
62
|
+
// Observer para invalidar caché automáticamente
|
|
63
|
+
this.setupMutationObserver();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Inicializa el router
|
|
68
|
+
* Si el usuario no llama start() manualmente, se auto-inicia despues de un delay
|
|
69
|
+
* @returns {void}
|
|
70
|
+
*/
|
|
71
|
+
init() {
|
|
72
|
+
window.addEventListener('popstate', this.onRouteChange.bind(this));
|
|
73
|
+
|
|
74
|
+
// Auto-start después de 50ms si el usuario no llama start() manualmente
|
|
75
|
+
// Esto da tiempo para que el usuario configure guards si lo necesita
|
|
76
|
+
this._autoStartTimeout = setTimeout(async () => {
|
|
77
|
+
if (!this._started) {
|
|
78
|
+
slice.logger.logInfo('Router', 'Auto-starting router (no manual start() called)');
|
|
79
|
+
await this.start();
|
|
80
|
+
}
|
|
81
|
+
}, 50);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Inicia el router y carga la ruta inicial
|
|
86
|
+
* OPCIONAL: Solo necesario si usas guards (beforeEach/afterEach)
|
|
87
|
+
* Si no lo llamas, el router se auto-inicia despues de 50ms
|
|
88
|
+
* @returns {Promise<void>}
|
|
89
|
+
*/
|
|
90
|
+
async start() {
|
|
91
|
+
// Prevenir múltiples llamadas
|
|
92
|
+
if (this._started) {
|
|
93
|
+
slice.logger.logWarning('Router', 'start() already called');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Cancelar auto-start si existe
|
|
98
|
+
if (this._autoStartTimeout) {
|
|
99
|
+
clearTimeout(this._autoStartTimeout);
|
|
100
|
+
this._autoStartTimeout = null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this._started = true;
|
|
104
|
+
await this.loadInitialRoute();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============================================
|
|
108
|
+
// NAVIGATION GUARDS API
|
|
109
|
+
// ============================================
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Registra un guard que se ejecuta ANTES de cada navegacion.
|
|
113
|
+
* Puede bloquear o redirigir la navegacion mediante next().
|
|
114
|
+
* @param {(to: RouteInfo, from: RouteInfo, next: RouterNext) => void|Promise<void>} guard
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
117
|
+
beforeEach(guard) {
|
|
118
|
+
if (typeof guard !== 'function') {
|
|
119
|
+
slice.logger.logError('Router', 'beforeEach expects a function');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this._beforeEachGuard = guard;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Registra un guard que se ejecuta DESPUES de cada navegacion.
|
|
127
|
+
* No puede bloquear la navegacion.
|
|
128
|
+
* @param {(to: RouteInfo, from: RouteInfo) => void} guard
|
|
129
|
+
* @returns {void}
|
|
130
|
+
*/
|
|
131
|
+
afterEach(guard) {
|
|
132
|
+
if (typeof guard !== 'function') {
|
|
133
|
+
slice.logger.logError('Router', 'afterEach expects a function');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this._afterEachGuard = guard;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Crea un objeto con información de ruta para los guards
|
|
141
|
+
* @param {Object} route - Objeto de ruta
|
|
142
|
+
* @param {Object} params - Parámetros de la ruta
|
|
143
|
+
* @param {String} requestedPath - Path original solicitado
|
|
144
|
+
* @returns {Object} Objeto con path, component, params, query, metadata
|
|
145
|
+
*/
|
|
146
|
+
/**
|
|
147
|
+
* Build route info used by guards and events.
|
|
148
|
+
* @param {RouteConfig|null} route
|
|
149
|
+
* @param {Object} [params]
|
|
150
|
+
* @param {string|null} [requestedPath]
|
|
151
|
+
* @returns {RouteInfo}
|
|
152
|
+
*/
|
|
153
|
+
_createRouteInfo(route, params = {}, requestedPath = null) {
|
|
154
|
+
if (!route) {
|
|
155
|
+
return {
|
|
156
|
+
path: requestedPath || '/404',
|
|
157
|
+
component: 'NotFound',
|
|
158
|
+
params: {},
|
|
159
|
+
query: this._parseQueryParams(),
|
|
160
|
+
metadata: {},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
path: requestedPath || route.fullPath || route.path,
|
|
166
|
+
component: route.parentRoute ? route.parentRoute.component : route.component,
|
|
167
|
+
params: params,
|
|
168
|
+
query: this._parseQueryParams(),
|
|
169
|
+
metadata: route.metadata || {},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Parsea los query parameters de la URL actual
|
|
175
|
+
* @returns {Object} Objeto con los query params
|
|
176
|
+
*/
|
|
177
|
+
/**
|
|
178
|
+
* Parse query params from current URL.
|
|
179
|
+
* @returns {Object}
|
|
180
|
+
*/
|
|
181
|
+
_parseQueryParams() {
|
|
182
|
+
const queryString = window.location.search;
|
|
183
|
+
if (!queryString) return {};
|
|
184
|
+
|
|
185
|
+
const params = {};
|
|
186
|
+
const urlParams = new URLSearchParams(queryString);
|
|
187
|
+
|
|
188
|
+
for (const [key, value] of urlParams) {
|
|
189
|
+
params[key] = value;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return params;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Ejecuta el beforeEach guard si existe
|
|
197
|
+
* @param {Object} to - Información de ruta destino
|
|
198
|
+
* @param {Object} from - Información de ruta origen
|
|
199
|
+
* @returns {Object|null} Objeto con redirectPath y options, o null si continúa
|
|
200
|
+
*/
|
|
201
|
+
/**
|
|
202
|
+
* Execute beforeEach guard if defined.
|
|
203
|
+
* @param {RouteInfo} to
|
|
204
|
+
* @param {RouteInfo} from
|
|
205
|
+
* @returns {Promise<{ path: string|false, options: { replace?: boolean } }|null>}
|
|
206
|
+
*/
|
|
207
|
+
async _executeBeforeEachGuard(to, from) {
|
|
208
|
+
if (!this._beforeEachGuard) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let redirectPath = null;
|
|
213
|
+
let redirectOptions = {};
|
|
214
|
+
let nextCalled = false;
|
|
215
|
+
|
|
216
|
+
const next = (arg) => {
|
|
217
|
+
if (nextCalled) {
|
|
218
|
+
slice.logger.logWarning('Router', 'next() called multiple times in guard');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
nextCalled = true;
|
|
222
|
+
|
|
223
|
+
// Caso 1: Sin argumentos - continuar navegación
|
|
224
|
+
if (arg === undefined) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Caso 2: false - cancelar navegación
|
|
229
|
+
if (arg === false) {
|
|
230
|
+
redirectPath = false;
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Caso 3: String - redirección simple (backward compatibility)
|
|
235
|
+
if (typeof arg === 'string') {
|
|
236
|
+
redirectPath = arg;
|
|
237
|
+
redirectOptions = { replace: false };
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Caso 4: Objeto - redirección con opciones
|
|
242
|
+
if (typeof arg === 'object' && arg.path) {
|
|
243
|
+
redirectPath = arg.path;
|
|
244
|
+
redirectOptions = {
|
|
245
|
+
replace: arg.replace || false,
|
|
246
|
+
};
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Argumento inválido
|
|
251
|
+
slice.logger.logError(
|
|
252
|
+
'Router',
|
|
253
|
+
'Invalid argument passed to next(). Expected string, object with path, false, or undefined.'
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
await this._beforeEachGuard(to, from, next);
|
|
259
|
+
|
|
260
|
+
// Si no se llamó next(), loguear advertencia pero continuar
|
|
261
|
+
if (!nextCalled) {
|
|
262
|
+
slice.logger.logWarning('Router', 'beforeEach guard did not call next(). Navigation will continue.');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Retornar tanto el path como las opciones
|
|
266
|
+
return redirectPath ? { path: redirectPath, options: redirectOptions } : null;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
slice.logger.logError('Router', 'Error in beforeEach guard', error);
|
|
269
|
+
return null; // En caso de error, continuar con la navegación
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Ejecuta el afterEach guard si existe
|
|
275
|
+
* @param {Object} to - Información de ruta destino
|
|
276
|
+
* @param {Object} from - Información de ruta origen
|
|
277
|
+
*/
|
|
278
|
+
/**
|
|
279
|
+
* Execute afterEach guard if defined.
|
|
280
|
+
* @param {RouteInfo} to
|
|
281
|
+
* @param {RouteInfo} from
|
|
282
|
+
* @returns {void}
|
|
283
|
+
*/
|
|
284
|
+
_executeAfterEachGuard(to, from) {
|
|
285
|
+
if (!this._afterEachGuard) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
this._afterEachGuard(to, from);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
slice.logger.logError('Router', 'Error in afterEach guard', error);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ============================================
|
|
297
|
+
// ROUTING CORE (MODIFICADO CON GUARDS)
|
|
298
|
+
// ============================================
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Navigate to a route path with guards support. Add replace to do router.replace() instead of push.
|
|
302
|
+
* @param {string} path
|
|
303
|
+
* @param {string[]} [_redirectChain]
|
|
304
|
+
* @param {{ replace?: boolean }} [_options]
|
|
305
|
+
* @returns {Promise<void>}
|
|
306
|
+
*/
|
|
307
|
+
async navigate(path, _redirectChain = [], _options = {}) {
|
|
308
|
+
const currentPath = window.location.pathname;
|
|
309
|
+
|
|
310
|
+
// Detectar loops infinitos: si ya visitamos esta ruta en la cadena de redirecciones
|
|
311
|
+
if (_redirectChain.includes(path)) {
|
|
312
|
+
slice.logger.logError('Router', `Guard redirection loop detected: ${_redirectChain.join(' → ')} → ${path}`);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Límite de seguridad: máximo 10 redirecciones
|
|
317
|
+
if (_redirectChain.length >= 10) {
|
|
318
|
+
slice.logger.logError('Router', `Too many redirections: ${_redirectChain.join(' → ')} → ${path}`);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Obtener información de ruta actual
|
|
323
|
+
const { route: fromRoute, params: fromParams } = this.matchRoute(currentPath);
|
|
324
|
+
const from = this._createRouteInfo(fromRoute, fromParams, currentPath);
|
|
325
|
+
|
|
326
|
+
// Obtener información de ruta destino
|
|
327
|
+
const { route: toRoute, params: toParams } = this.matchRoute(path);
|
|
328
|
+
const to = this._createRouteInfo(toRoute, toParams, path);
|
|
329
|
+
|
|
330
|
+
// EJECUTAR BEFORE EACH GUARD
|
|
331
|
+
const guardResult = await this._executeBeforeEachGuard(to, from);
|
|
332
|
+
|
|
333
|
+
// Si el guard redirige
|
|
334
|
+
if (guardResult && guardResult.path) {
|
|
335
|
+
const newChain = [..._redirectChain, path];
|
|
336
|
+
return this.navigate(guardResult.path, newChain, guardResult.options);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Si el guard cancela la navegación (next(false))
|
|
340
|
+
if (guardResult && guardResult.path === false) {
|
|
341
|
+
slice.logger.logInfo('Router', 'Navigation cancelled by guard');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// No hay redirección - continuar con la navegación normal
|
|
346
|
+
// Usar replace o push según las opciones
|
|
347
|
+
if (_options.replace) {
|
|
348
|
+
window.history.replaceState({}, path, window.location.origin + path);
|
|
349
|
+
} else {
|
|
350
|
+
window.history.pushState({}, path, window.location.origin + path);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
await this._performNavigation(to, from);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Método interno para ejecutar la navegación después de pasar los guards
|
|
358
|
+
* @param {Object} to - Información de ruta destino
|
|
359
|
+
* @param {Object} from - Información de ruta origen
|
|
360
|
+
*/
|
|
361
|
+
/**
|
|
362
|
+
* Perform navigation after guards.
|
|
363
|
+
* @param {RouteInfo} to
|
|
364
|
+
* @param {RouteInfo} from
|
|
365
|
+
* @returns {Promise<void>}
|
|
366
|
+
*/
|
|
367
|
+
async _performNavigation(to, from) {
|
|
368
|
+
// Renderizar la nueva ruta
|
|
369
|
+
await this.onRouteChange();
|
|
370
|
+
|
|
371
|
+
// EJECUTAR AFTER EACH GUARD
|
|
372
|
+
this._executeAfterEachGuard(to, from);
|
|
373
|
+
|
|
374
|
+
// Emitir evento de cambio de ruta
|
|
375
|
+
this._emitRouteChange(to, from);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* React to URL changes and render routes.
|
|
380
|
+
* @returns {Promise<void>}
|
|
381
|
+
*/
|
|
382
|
+
async onRouteChange() {
|
|
383
|
+
// Cancelar el timeout anterior si existe
|
|
384
|
+
if (this.routeChangeTimeout) {
|
|
385
|
+
clearTimeout(this.routeChangeTimeout);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Debounce de 10ms para evitar múltiples llamadas seguidas
|
|
389
|
+
this.routeChangeTimeout = setTimeout(async () => {
|
|
390
|
+
const path = window.location.pathname;
|
|
391
|
+
const routeContainersFlag = await this.renderRoutesComponentsInPage();
|
|
392
|
+
|
|
393
|
+
if (routeContainersFlag) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const { route, params } = this.matchRoute(path);
|
|
398
|
+
if (route) {
|
|
399
|
+
await this.handleRoute(route, params);
|
|
400
|
+
}
|
|
401
|
+
}, 10);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Build or update the active route component.
|
|
406
|
+
* @param {RouteConfig} route
|
|
407
|
+
* @param {Object} params
|
|
408
|
+
* @returns {Promise<void>}
|
|
409
|
+
*/
|
|
410
|
+
async handleRoute(route, params) {
|
|
411
|
+
const targetElement = document.querySelector('#app');
|
|
412
|
+
|
|
413
|
+
const componentName = route.parentRoute ? route.parentRoute.component : route.component;
|
|
414
|
+
const sliceId = `route-${componentName}`;
|
|
415
|
+
|
|
416
|
+
const existingComponent = slice.controller.getComponent(sliceId);
|
|
417
|
+
|
|
418
|
+
if (slice.loading) {
|
|
419
|
+
slice.loading.start();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (existingComponent) {
|
|
423
|
+
targetElement.innerHTML = '';
|
|
424
|
+
if (existingComponent.update) {
|
|
425
|
+
existingComponent.props = { ...existingComponent.props, ...params };
|
|
426
|
+
await existingComponent.update();
|
|
427
|
+
}
|
|
428
|
+
targetElement.appendChild(existingComponent);
|
|
429
|
+
await this.renderRoutesInComponent(existingComponent);
|
|
430
|
+
} else {
|
|
431
|
+
const component = await slice.build(componentName, {
|
|
432
|
+
params,
|
|
433
|
+
sliceId: sliceId,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
targetElement.innerHTML = '';
|
|
437
|
+
targetElement.appendChild(component);
|
|
438
|
+
|
|
439
|
+
await this.renderRoutesInComponent(component);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Invalidar caché después de cambios importantes en el DOM
|
|
443
|
+
this.invalidateCache();
|
|
444
|
+
|
|
445
|
+
if (slice.loading) {
|
|
446
|
+
slice.loading.stop();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
slice.router.activeRoute = route;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Load initial route and run guards.
|
|
454
|
+
* @returns {Promise<void>}
|
|
455
|
+
*/
|
|
456
|
+
async loadInitialRoute() {
|
|
457
|
+
const path = window.location.pathname;
|
|
458
|
+
const { route, params } = this.matchRoute(path);
|
|
459
|
+
|
|
460
|
+
// Para la carga inicial, también ejecutar guards
|
|
461
|
+
const from = this._createRouteInfo(null, {}, null);
|
|
462
|
+
const to = this._createRouteInfo(route, params, path);
|
|
463
|
+
|
|
464
|
+
// EJECUTAR BEFORE EACH GUARD en carga inicial
|
|
465
|
+
const guardResult = await this._executeBeforeEachGuard(to, from);
|
|
466
|
+
|
|
467
|
+
if (guardResult && guardResult.path) {
|
|
468
|
+
return this.navigate(guardResult.path, [], guardResult.options);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Si el guard cancela la navegación inicial (caso raro pero posible)
|
|
472
|
+
if (guardResult && guardResult.path === false) {
|
|
473
|
+
slice.logger.logWarning('Router', 'Initial route navigation cancelled by guard');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
await this.handleRoute(route, params);
|
|
478
|
+
|
|
479
|
+
// EJECUTAR AFTER EACH GUARD en carga inicial
|
|
480
|
+
this._executeAfterEachGuard(to, from);
|
|
481
|
+
|
|
482
|
+
// Emitir evento de cambio de ruta
|
|
483
|
+
this._emitRouteChange(to, from);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Emitir evento de cambio de ruta
|
|
488
|
+
* @param {Object} to
|
|
489
|
+
* @param {Object} from
|
|
490
|
+
*/
|
|
491
|
+
/**
|
|
492
|
+
* Emit route change event.
|
|
493
|
+
* @param {RouteInfo} to
|
|
494
|
+
* @param {RouteInfo} from
|
|
495
|
+
* @returns {void}
|
|
496
|
+
*/
|
|
497
|
+
_emitRouteChange(to, from) {
|
|
498
|
+
const payload = { to, from };
|
|
499
|
+
|
|
500
|
+
if (slice.eventsConfig?.enabled && slice.events && typeof slice.events.emit === 'function') {
|
|
501
|
+
slice.events.emit('router:change', payload);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
window.dispatchEvent(new CustomEvent('router:change', { detail: payload }));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// ============================================
|
|
509
|
+
// MÉTODOS EXISTENTES (SIN CAMBIOS)
|
|
510
|
+
// ============================================
|
|
511
|
+
|
|
512
|
+
setupMutationObserver() {
|
|
513
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
514
|
+
this.observer = new MutationObserver((mutations) => {
|
|
515
|
+
let shouldInvalidateCache = false;
|
|
516
|
+
|
|
517
|
+
mutations.forEach((mutation) => {
|
|
518
|
+
if (mutation.type === 'childList') {
|
|
519
|
+
const addedNodes = Array.from(mutation.addedNodes);
|
|
520
|
+
const removedNodes = Array.from(mutation.removedNodes);
|
|
521
|
+
|
|
522
|
+
const hasRouteNodes = [...addedNodes, ...removedNodes].some(
|
|
523
|
+
(node) =>
|
|
524
|
+
node.nodeType === Node.ELEMENT_NODE &&
|
|
525
|
+
(node.tagName === 'SLICE-ROUTE' ||
|
|
526
|
+
node.tagName === 'SLICE-MULTI-ROUTE' ||
|
|
527
|
+
node.querySelector?.('slice-route, slice-multi-route'))
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
if (hasRouteNodes) {
|
|
531
|
+
shouldInvalidateCache = true;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
if (shouldInvalidateCache) {
|
|
537
|
+
this.invalidateCache();
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
this.observer.observe(document.body, {
|
|
542
|
+
childList: true,
|
|
543
|
+
subtree: true,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
invalidateCache() {
|
|
549
|
+
this.routeContainersCache.clear();
|
|
550
|
+
this.lastCacheUpdate = 0;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
createPathToRouteMap(routes, basePath = '', parentRoute = null) {
|
|
554
|
+
const pathToRouteMap = new Map();
|
|
555
|
+
|
|
556
|
+
for (const route of routes) {
|
|
557
|
+
const fullPath = `${basePath}${route.path}`.replace(/\/+/g, '/');
|
|
558
|
+
|
|
559
|
+
const routeWithParent = {
|
|
560
|
+
...route,
|
|
561
|
+
fullPath,
|
|
562
|
+
parentPath: parentRoute ? parentRoute.fullPath : null,
|
|
563
|
+
parentRoute: parentRoute,
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
pathToRouteMap.set(fullPath, routeWithParent);
|
|
567
|
+
|
|
568
|
+
if (route.children) {
|
|
569
|
+
const childPathToRouteMap = this.createPathToRouteMap(route.children, fullPath, routeWithParent);
|
|
570
|
+
|
|
571
|
+
for (const [childPath, childRoute] of childPathToRouteMap.entries()) {
|
|
572
|
+
pathToRouteMap.set(childPath, childRoute);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return pathToRouteMap;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Render any Route/MultiRoute components in a container.
|
|
582
|
+
* @param {Document|HTMLElement} [searchContainer]
|
|
583
|
+
* @returns {Promise<boolean>}
|
|
584
|
+
*/
|
|
585
|
+
async renderRoutesComponentsInPage(searchContainer = document) {
|
|
586
|
+
let routerContainersFlag = false;
|
|
587
|
+
const routeContainers = this.getCachedRouteContainers(searchContainer);
|
|
588
|
+
|
|
589
|
+
for (const routeContainer of routeContainers) {
|
|
590
|
+
try {
|
|
591
|
+
if (!routeContainer.isConnected) {
|
|
592
|
+
this.invalidateCache();
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
let response = await routeContainer.renderIfCurrentRoute();
|
|
597
|
+
if (response) {
|
|
598
|
+
this.activeRoute = routeContainer.props;
|
|
599
|
+
routerContainersFlag = true;
|
|
600
|
+
}
|
|
601
|
+
} catch (error) {
|
|
602
|
+
slice.logger.logError('Router', `Error rendering route container`, error);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return routerContainersFlag;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
getCachedRouteContainers(container) {
|
|
610
|
+
const containerKey = container === document ? 'document' : container.sliceId || 'anonymous';
|
|
611
|
+
const now = Date.now();
|
|
612
|
+
|
|
613
|
+
if (this.routeContainersCache.has(containerKey) && now - this.lastCacheUpdate < this.CACHE_DURATION) {
|
|
614
|
+
return this.routeContainersCache.get(containerKey);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const routeContainers = this.findAllRouteContainersOptimized(container);
|
|
618
|
+
this.routeContainersCache.set(containerKey, routeContainers);
|
|
619
|
+
this.lastCacheUpdate = now;
|
|
620
|
+
|
|
621
|
+
return routeContainers;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
findAllRouteContainersOptimized(container) {
|
|
625
|
+
const routeContainers = [];
|
|
626
|
+
|
|
627
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
|
|
628
|
+
acceptNode: (node) => {
|
|
629
|
+
if (node.tagName === 'SLICE-ROUTE' || node.tagName === 'SLICE-MULTI-ROUTE') {
|
|
630
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
631
|
+
}
|
|
632
|
+
return NodeFilter.FILTER_SKIP;
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
let node;
|
|
637
|
+
while ((node = walker.nextNode())) {
|
|
638
|
+
routeContainers.push(node);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return routeContainers;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Render route containers inside a component.
|
|
646
|
+
* @param {HTMLElement} component
|
|
647
|
+
* @returns {Promise<boolean>}
|
|
648
|
+
*/
|
|
649
|
+
async renderRoutesInComponent(component) {
|
|
650
|
+
if (!component) {
|
|
651
|
+
slice.logger.logWarning('Router', 'No component provided for route rendering');
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return await this.renderRoutesComponentsInPage(component);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Match a path to a configured route.
|
|
660
|
+
* @param {string} path
|
|
661
|
+
* @returns {RouteMatch}
|
|
662
|
+
*/
|
|
663
|
+
matchRoute(path) {
|
|
664
|
+
const exactMatch = this.pathToRouteMap.get(path);
|
|
665
|
+
if (exactMatch) {
|
|
666
|
+
if (exactMatch.parentRoute) {
|
|
667
|
+
return {
|
|
668
|
+
route: exactMatch.parentRoute,
|
|
669
|
+
params: {},
|
|
670
|
+
childRoute: exactMatch,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
return { route: exactMatch, params: {} };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
for (const [routePattern, route] of this.pathToRouteMap.entries()) {
|
|
677
|
+
if (routePattern.includes('${')) {
|
|
678
|
+
const { regex, paramNames } = this.compilePathPattern(routePattern);
|
|
679
|
+
const match = path.match(regex);
|
|
680
|
+
if (match) {
|
|
681
|
+
const params = {};
|
|
682
|
+
paramNames.forEach((name, i) => {
|
|
683
|
+
params[name] = match[i + 1];
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
if (route.parentRoute) {
|
|
687
|
+
return {
|
|
688
|
+
route: route.parentRoute,
|
|
689
|
+
params: params,
|
|
690
|
+
childRoute: route,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return { route, params };
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const notFoundRoute = this.pathToRouteMap.get('/404');
|
|
700
|
+
return { route: notFoundRoute, params: {} };
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Compile a path pattern with ${param} segments.
|
|
705
|
+
* @param {string} pattern
|
|
706
|
+
* @returns {{ regex: RegExp, paramNames: string[] }}
|
|
707
|
+
*/
|
|
708
|
+
compilePathPattern(pattern) {
|
|
709
|
+
const paramNames = [];
|
|
710
|
+
const regexPattern =
|
|
711
|
+
'^' +
|
|
712
|
+
pattern.replace(/\$\{([^}]+)\}/g, (_, paramName) => {
|
|
713
|
+
paramNames.push(paramName);
|
|
714
|
+
return '([^/]+)';
|
|
715
|
+
}) +
|
|
716
|
+
'$';
|
|
717
|
+
|
|
718
|
+
return { regex: new RegExp(regexPattern), paramNames };
|
|
719
|
+
}
|
|
720
|
+
}
|