@rxdi/router 0.7.220 → 0.7.222

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.
@@ -0,0 +1,833 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Router = void 0;
4
+ const path_to_regexp_1 = require("./path-to-regexp");
5
+ const resolver_1 = require("./resolver");
6
+ const triggers_1 = require("./triggers");
7
+ const types_1 = require("./types");
8
+ const utils_1 = require("./utils");
9
+ const willAnimate = (elem) => {
10
+ const name = getComputedStyle(elem).getPropertyValue('animation-name');
11
+ return name && name !== 'none';
12
+ };
13
+ const waitForAnimation = (elem, cb) => {
14
+ const listener = () => {
15
+ elem.removeEventListener('animationend', listener);
16
+ cb();
17
+ };
18
+ elem.addEventListener('animationend', listener);
19
+ };
20
+ function animate(elem, className) {
21
+ elem.classList.add(className);
22
+ return new Promise((resolve) => {
23
+ if (willAnimate(elem)) {
24
+ const rect = elem.getBoundingClientRect();
25
+ const size = `height: ${rect.bottom - rect.top}px; width: ${rect.right - rect.left}px`;
26
+ elem.setAttribute('style', `position: absolute; ${size}`);
27
+ waitForAnimation(elem, () => {
28
+ elem.classList.remove(className);
29
+ elem.removeAttribute('style');
30
+ resolve();
31
+ });
32
+ }
33
+ else {
34
+ elem.classList.remove(className);
35
+ resolve();
36
+ }
37
+ });
38
+ }
39
+ const MAX_REDIRECT_COUNT = 256;
40
+ function isResultNotEmpty(result) {
41
+ return result !== null && result !== undefined;
42
+ }
43
+ function copyContextWithoutNext(context) {
44
+ const copy = Object.assign({}, context);
45
+ delete copy.next;
46
+ return copy;
47
+ }
48
+ function getPathnameForRouter(pathname, router) {
49
+ const base = router.__getEffectiveBaseUrl();
50
+ return base
51
+ ? router.constructor.__createUrl(pathname.replace(/^\//, ''), base).pathname
52
+ : pathname;
53
+ }
54
+ function getMatchedPath(chain) {
55
+ return chain
56
+ .map((item) => {
57
+ const path = item.path || item.path || '';
58
+ return Array.isArray(path) ? path[0] : path;
59
+ })
60
+ .reduce((a, b) => {
61
+ if (b.length) {
62
+ return a.replace(/\/$/, '') + '/' + b.replace(/^\//, '');
63
+ }
64
+ return a;
65
+ }, '');
66
+ }
67
+ function createLocation({ pathname = '', search = '', hash = '', chain = [], params = {}, redirectFrom, resolver, }, route) {
68
+ const routes = chain.map((item) => item.route);
69
+ return {
70
+ baseUrl: (resolver && resolver.baseUrl) || '',
71
+ pathname,
72
+ search,
73
+ hash,
74
+ routes,
75
+ route: route || (routes.length && routes[routes.length - 1]) || null,
76
+ params,
77
+ redirectFrom,
78
+ getUrl: (userParams = {}) => getPathnameForRouter((0, path_to_regexp_1.compile)(getMatchedPath(routes))(Object.assign({}, params, userParams)), resolver),
79
+ };
80
+ }
81
+ function createRedirect(context, pathname) {
82
+ const params = Object.assign({}, context.params);
83
+ return {
84
+ redirect: {
85
+ pathname,
86
+ from: context.pathname,
87
+ params,
88
+ },
89
+ };
90
+ }
91
+ function renderElement(context, element) {
92
+ element.location = createLocation(context);
93
+ const index = context.chain.map((item) => item.route).indexOf(context.route);
94
+ context.chain[index].element = element;
95
+ return element;
96
+ }
97
+ function amend(amendmentFunction, args, element) {
98
+ return (amendmentResult) => {
99
+ if (amendmentResult &&
100
+ (amendmentResult.cancel || amendmentResult.redirect)) {
101
+ return amendmentResult;
102
+ }
103
+ if (element) {
104
+ return (0, utils_1.runCallbackIfPossible)(element[amendmentFunction], args, element);
105
+ }
106
+ return undefined;
107
+ };
108
+ }
109
+ function processNewChildren(newChildren, route) {
110
+ if (!Array.isArray(newChildren) && !(0, utils_1.isObject)(newChildren)) {
111
+ throw new Error((0, utils_1.log)(`Incorrect "children" value for the route ${route.path}: expected array or object, but got ${newChildren}`));
112
+ }
113
+ route.__children = [];
114
+ const childRoutes = (0, utils_1.toArray)(newChildren);
115
+ for (let i = 0; i < childRoutes.length; i++) {
116
+ (0, utils_1.ensureRoute)(childRoutes[i]);
117
+ route.__children.push(childRoutes[i]);
118
+ }
119
+ }
120
+ function removeDomNodes(nodes) {
121
+ if (nodes && nodes.length) {
122
+ const parent = nodes[0].parentNode;
123
+ for (let i = 0; i < nodes.length; i++) {
124
+ parent.removeChild(nodes[i]);
125
+ }
126
+ }
127
+ }
128
+ /**
129
+ * A simple client-side router for single-page applications. It uses
130
+ * express-style middleware and has a first-class support for Web Components and
131
+ * lazy-loading. Works great in Polymer and non-Polymer apps.
132
+ *
133
+ * Use `new Router(outlet, options)` to create a new Router instance.
134
+ *
135
+ * * The `outlet` parameter is a reference to the DOM node to render
136
+ * the content into.
137
+ *
138
+ * * The `options` parameter is an optional object with options. The following
139
+ * keys are supported:
140
+ * * `baseUrl` — the initial value for [
141
+ * the `baseUrl` property
142
+ * ](#/classes/Router#property-baseUrl)
143
+ *
144
+ * The Router instance is automatically subscribed to navigation events
145
+ * on `window`.
146
+ *
147
+ * See [Live Examples](#/classes/Router/demos/demo/index.html) for the detailed usage demo and code snippets.
148
+ *
149
+ * See also detailed API docs for the following methods, for the advanced usage:
150
+ *
151
+ * * [setOutlet](#/classes/Router#method-setOutlet) – should be used to configure the outlet.
152
+ * * [setTriggers](#/classes/Router#method-setTriggers) – should be used to configure the navigation events.
153
+ * * [setRoutes](#/classes/Router#method-setRoutes) – should be used to configure the routes.
154
+ *
155
+ * Only `setRoutes` has to be called manually, others are automatically invoked when creating a new instance.
156
+ *
157
+ * @extends Resolver
158
+ * @demo demo/index.html
159
+ * @summary JavaScript class that renders different DOM content depending on
160
+ * a given path. It can re-render when triggered or automatically on
161
+ * 'popstate' and / or 'click' events.
162
+ */
163
+ class Router extends resolver_1.Resolver {
164
+ /**
165
+ * Creates a new Router instance with a given outlet, and
166
+ * automatically subscribes it to navigation events on the `window`.
167
+ * Using a constructor argument or a setter for outlet is equivalent:
168
+ *
169
+ * ```
170
+ * const router = new Router();
171
+ * router.setOutlet(outlet);
172
+ * ```
173
+ * @param outlet
174
+ * @param options
175
+ */
176
+ constructor(outlet, options) {
177
+ const baseElement = document.head.querySelector('base');
178
+ const baseHref = baseElement && baseElement.getAttribute('href');
179
+ super([], Object.assign({
180
+ baseUrl: baseHref && resolver_1.Resolver.__createUrl(baseHref, document.URL).pathname.replace(/[^\/]*$/, ''),
181
+ }, options));
182
+ this.resolveRoute = (context) => this.__resolveRoute(context);
183
+ (0, triggers_1.setNavigationTriggers)([triggers_1.POPSTATE, triggers_1.CLICK]);
184
+ this.ready = Promise.resolve(outlet);
185
+ this.location = createLocation({ resolver: this });
186
+ this.__lastStartedRenderId = 0;
187
+ this.__navigationEventHandler = this.__onNavigationEvent.bind(this);
188
+ this.setOutlet(outlet);
189
+ this.subscribe();
190
+ this.__createdByRouter = new WeakMap();
191
+ this.__addedByRouter = new WeakMap();
192
+ }
193
+ __resolveRoute(context) {
194
+ const route = context.route;
195
+ let callbacks = Promise.resolve();
196
+ if ((0, utils_1.isFunction)(route.children)) {
197
+ callbacks = callbacks
198
+ .then(() => route.children(copyContextWithoutNext(context)))
199
+ .then((children) => {
200
+ if (!isResultNotEmpty(children) && !(0, utils_1.isFunction)(route.children)) {
201
+ children = route.children;
202
+ }
203
+ processNewChildren(children, route);
204
+ });
205
+ }
206
+ const commands = {
207
+ redirect: (path) => createRedirect(context, path),
208
+ component: (component) => {
209
+ const element = document.createElement(component);
210
+ this.__createdByRouter.set(element, true);
211
+ return element;
212
+ },
213
+ };
214
+ return callbacks
215
+ .then(() => {
216
+ if (this.__isLatestRender(context)) {
217
+ return (0, utils_1.runCallbackIfPossible)(route.action, [context, commands], route);
218
+ }
219
+ return undefined;
220
+ })
221
+ .then((result) => {
222
+ if (isResultNotEmpty(result)) {
223
+ if (result instanceof HTMLElement || result.redirect || result === types_1.notFoundResult) {
224
+ return result;
225
+ }
226
+ }
227
+ if ((0, utils_1.isString)(route.redirect)) {
228
+ return commands.redirect(route.redirect);
229
+ }
230
+ if (route.bundle) {
231
+ return (0, utils_1.loadBundle)(route.bundle).then(() => undefined, () => {
232
+ throw new Error((0, utils_1.log)(`Bundle not found: ${route.bundle}. Check if the file name is correct`));
233
+ });
234
+ }
235
+ return undefined;
236
+ })
237
+ .then((result) => {
238
+ if (isResultNotEmpty(result)) {
239
+ return result;
240
+ }
241
+ if ((0, utils_1.isString)(route.component)) {
242
+ return commands.component(route.component);
243
+ }
244
+ return undefined;
245
+ });
246
+ }
247
+ /**
248
+ * Takes current routes and set it
249
+ * @param routes: Route<C>[]
250
+ * @returns void
251
+ */
252
+ /**
253
+ * Sets the router outlet (the DOM node where the content for the current
254
+ * route is inserted). Any content pre-existing in the router outlet is
255
+ * removed at the end of each render pass.
256
+ *
257
+ * NOTE: this method is automatically invoked first time when creating a new Router instance.
258
+ *
259
+ * @param outlet the DOM node where the content for the current route
260
+ * is inserted.
261
+ */
262
+ setOutlet(outlet) {
263
+ if (outlet) {
264
+ this.__ensureOutlet(outlet);
265
+ }
266
+ this.__outlet = outlet;
267
+ }
268
+ /**
269
+ * Returns the current router outlet. The initial value is undefined.
270
+ */
271
+ /**
272
+ * Returns the current router outlet. The initial value is undefined.
273
+ *
274
+ * @return the current router outlet (or `undefined`)
275
+ */
276
+ getOutlet() {
277
+ return this.__outlet;
278
+ }
279
+ /**
280
+ * Takes current routes and set it
281
+ * @param routes: Route | Route[]
282
+ * @returns Route | Route[]
283
+ */
284
+ /**
285
+ * Sets the routing config (replacing the existing one) and triggers a
286
+ * navigation event so that the router outlet is refreshed according to the
287
+ * current `window.location` and the new routing config.
288
+ *
289
+ * Each route object may have the following properties, listed here in the processing order:
290
+ * * `path` – the route path (relative to the parent route if any) in the
291
+ * [express.js syntax](https://expressjs.com/en/guide/routing.html#route-paths").
292
+ *
293
+ * * `children` – an array of nested routes or a function that provides this
294
+ * array at the render time. The function can be synchronous or asynchronous:
295
+ * in the latter case the render is delayed until the returned promise is
296
+ * resolved. The `children` function is executed every time when this route is
297
+ * being rendered. This allows for dynamic route structures (e.g. backend-defined),
298
+ * but it might have a performance impact as well. In order to avoid calling
299
+ * the function on subsequent renders, you can override the `children` property
300
+ * of the route object and save the calculated array there
301
+ * (via `context.route.children = [ route1, route2, ...];`).
302
+ * Parent routes are fully resolved before resolving the children. Children
303
+ * 'path' values are relative to the parent ones.
304
+ *
305
+ * * `action` – the action that is executed before the route is resolved.
306
+ * The value for this property should be a function, accepting `context`
307
+ * and `commands` parameters described below. If present, this function is
308
+ * always invoked first, disregarding of the other properties' presence.
309
+ * The action can return a result directly or within a `Promise`, which
310
+ * resolves to the result. If the action result is an `HTMLElement` instance,
311
+ * a `commands.component(name)` result, a `commands.redirect(path)` result,
312
+ * or a `context.next()` result, the current route resolution is finished,
313
+ * and other route config properties are ignored.
314
+ * See also **Route Actions** section in [Live Examples](#/classes/Router/demos/demo/index.html).
315
+ *
316
+ * * `redirect` – other route's path to redirect to. Passes all route parameters to the redirect target.
317
+ * The target route should also be defined.
318
+ * See also **Redirects** section in [Live Examples](#/classes/Router/demos/demo/index.html).
319
+ *
320
+ * * `bundle` – string containing the path to `.js` or `.mjs` bundle to load before resolving the route,
321
+ * or the object with "module" and "nomodule" keys referring to different bundles.
322
+ * Each bundle is only loaded once. If "module" and "nomodule" are set, only one bundle is loaded,
323
+ * depending on whether the browser supports ES modules or not.
324
+ * The property is ignored when either an `action` returns the result or `redirect` property is present.
325
+ * Any error, e.g. 404 while loading bundle will cause route resolution to throw.
326
+ * See also **Code Splitting** section in [Live Examples](#/classes/Router/demos/demo/index.html).
327
+ *
328
+ * * `component` – the tag name of the Web Component to resolve the route to.
329
+ * The property is ignored when either an `action` returns the result or `redirect` property is present.
330
+ * If route contains the `component` property (or an action that return a component)
331
+ * and its child route also contains the `component` property, child route's component
332
+ * will be rendered as a light dom child of a parent component.
333
+ *
334
+ * * `name` – the string name of the route to use in the
335
+ * [`router.urlForName(name, params)`](#/classes/Router#method-urlForName)
336
+ * navigation helper method.
337
+ *
338
+ * For any route function (`action`, `children`) defined, the corresponding `route` object is available inside the callback
339
+ * through the `this` reference. If you need to access it, make sure you define the callback as a non-arrow function
340
+ * because arrow functions do not have their own `this` reference.
341
+ *
342
+ * `context` object that is passed to `action` function holds the following properties:
343
+ * * `context.pathname` – string with the pathname being resolved
344
+ *
345
+ * * `context.search` – search query string
346
+ *
347
+ * * `context.hash` – hash string
348
+ *
349
+ * * `context.params` – object with route parameters
350
+ *
351
+ * * `context.route` – object that holds the route that is currently being rendered.
352
+ *
353
+ * * `context.next()` – function for asynchronously getting the next route
354
+ * contents from the resolution chain (if any)
355
+ *
356
+ * `commands` object that is passed to `action` function has
357
+ * the following methods:
358
+ *
359
+ * * `commands.redirect(path)` – function that creates a redirect data
360
+ * for the path specified.
361
+ *
362
+ * * `commands.component(component)` – function that creates a new HTMLElement
363
+ * with current context. Note: the component created by this function is reused if visiting the same path twice in row.
364
+ *
365
+ *
366
+ * @param routes a single route or an array of those
367
+ * @param skipRender configure the router but skip rendering the
368
+ * route corresponding to the current `window.location` values
369
+ *
370
+ * @return
371
+ */
372
+ setRoutes(routes, skipRender = false) {
373
+ this.__previousContext = undefined;
374
+ this.__urlForName = undefined;
375
+ super.setRoutes(routes);
376
+ if (!skipRender) {
377
+ this.__onNavigationEvent();
378
+ }
379
+ return this.ready;
380
+ }
381
+ /**
382
+ * Asynchronously resolves the given pathname and renders the resolved route component into the router outlet. If no router outlet is set at the time of calling this method, or at the time when the route resolution is completed, a TypeError is thrown.
383
+ * Returns a promise that is fulfilled with the router outlet DOM Node after the route component is created and inserted into the router outlet, or rejected if no route matches the given path.
384
+ * If another render pass is started before the previous one is completed, the result of the previous render pass is ignored.
385
+ * @param pathnameOrContext — the pathname to render or a context object with a pathname property and other properties to pass to the resolver.
386
+ * @param shouldUpdateHistory
387
+ */
388
+ /**
389
+ * Asynchronously resolves the given pathname and renders the resolved route
390
+ * component into the router outlet. If no router outlet is set at the time of
391
+ * calling this method, or at the time when the route resolution is completed,
392
+ * a `TypeError` is thrown.
393
+ *
394
+ * Returns a promise that is fulfilled with the router outlet DOM Node after
395
+ * the route component is created and inserted into the router outlet, or
396
+ * rejected if no route matches the given path.
397
+ *
398
+ * If another render pass is started before the previous one is completed, the
399
+ * result of the previous render pass is ignored.
400
+ *
401
+ * @param pathnameOrContext
402
+ * the pathname to render or a context object with a `pathname` property,
403
+ * optional `search` and `hash` properties, and other properties
404
+ * to pass to the resolver.
405
+ * @param shouldUpdateHistory
406
+ * update browser history with the rendered location
407
+ * @return
408
+ */
409
+ render(pathnameOrContext, shouldUpdateHistory) {
410
+ const renderId = ++this.__lastStartedRenderId;
411
+ const context = Object.assign({
412
+ search: '',
413
+ hash: '',
414
+ }, (0, utils_1.isString)(pathnameOrContext) ? { pathname: pathnameOrContext } : pathnameOrContext, {
415
+ __renderId: renderId,
416
+ });
417
+ this.ready = this.resolve(context)
418
+ .then((context) => this.__fullyResolveChain(context))
419
+ .then((context) => {
420
+ if (this.__isLatestRender(context)) {
421
+ const previousContext = this.__previousContext;
422
+ if (context === previousContext) {
423
+ this.__updateBrowserHistory(previousContext, true);
424
+ return this.location;
425
+ }
426
+ this.location = createLocation(context);
427
+ if (shouldUpdateHistory) {
428
+ this.__updateBrowserHistory(context, renderId === 1);
429
+ }
430
+ (0, utils_1.fireRouterEvent)('location-changed', { location: this.location });
431
+ if (context.__skipAttach) {
432
+ this.__copyUnchangedElements(context, previousContext);
433
+ this.__previousContext = context;
434
+ return this.location;
435
+ }
436
+ this.__addAppearingContent(context, previousContext);
437
+ const animationDone = this.__animateIfNeeded(context);
438
+ this.__runOnAfterEnterCallbacks(context);
439
+ this.__runOnAfterLeaveCallbacks(context, previousContext);
440
+ return animationDone.then(() => {
441
+ if (this.__isLatestRender(context)) {
442
+ this.__removeDisappearingContent();
443
+ this.__previousContext = context;
444
+ return this.location;
445
+ }
446
+ return undefined;
447
+ });
448
+ }
449
+ return undefined;
450
+ })
451
+ .catch((error) => {
452
+ if (renderId === this.__lastStartedRenderId) {
453
+ if (shouldUpdateHistory) {
454
+ this.__updateBrowserHistory(context);
455
+ }
456
+ removeDomNodes(this.__outlet && this.__outlet.children);
457
+ this.location = createLocation(Object.assign(context, { resolver: this }));
458
+ (0, utils_1.fireRouterEvent)('error', Object.assign({ router: this, error }, context));
459
+ throw error;
460
+ }
461
+ return undefined;
462
+ });
463
+ return this.ready;
464
+ }
465
+ __fullyResolveChain(topOfTheChainContextBeforeRedirects, contextBeforeRedirects = topOfTheChainContextBeforeRedirects) {
466
+ return this.__findComponentContextAfterAllRedirects(contextBeforeRedirects).then((contextAfterRedirects) => {
467
+ const redirectsHappened = contextAfterRedirects !== contextBeforeRedirects;
468
+ const topOfTheChainContextAfterRedirects = redirectsHappened
469
+ ? contextAfterRedirects
470
+ : topOfTheChainContextBeforeRedirects;
471
+ const matchedPath = getPathnameForRouter(getMatchedPath(contextAfterRedirects.chain), contextAfterRedirects.resolver);
472
+ const isFound = matchedPath === contextAfterRedirects.pathname;
473
+ const findNextContextIfAny = (context, parent = context.route, prevResult) => {
474
+ return context
475
+ .next(undefined, parent, prevResult)
476
+ .then((nextContext) => {
477
+ if (nextContext === null || nextContext === types_1.notFoundResult) {
478
+ if (isFound) {
479
+ return context;
480
+ }
481
+ else if (parent.parent !== null) {
482
+ return findNextContextIfAny(context, parent.parent, nextContext);
483
+ }
484
+ else {
485
+ return nextContext;
486
+ }
487
+ }
488
+ return nextContext;
489
+ });
490
+ };
491
+ return findNextContextIfAny(contextAfterRedirects).then((nextContext) => {
492
+ if (nextContext === null || nextContext === types_1.notFoundResult) {
493
+ throw (0, utils_1.getNotFoundError)(topOfTheChainContextAfterRedirects);
494
+ }
495
+ return nextContext && nextContext !== types_1.notFoundResult && nextContext !== contextAfterRedirects
496
+ ? this.__fullyResolveChain(topOfTheChainContextAfterRedirects, nextContext)
497
+ : this.__amendWithOnBeforeCallbacks(contextAfterRedirects);
498
+ });
499
+ });
500
+ }
501
+ __findComponentContextAfterAllRedirects(context) {
502
+ const result = context.result;
503
+ if (result instanceof HTMLElement) {
504
+ renderElement(context, result);
505
+ return Promise.resolve(context);
506
+ }
507
+ else if (result && result.redirect) {
508
+ return this.__redirect(result.redirect, context.__redirectCount, context.__renderId).then((context) => this.__findComponentContextAfterAllRedirects(context));
509
+ }
510
+ else if (result instanceof Error) {
511
+ return Promise.reject(result);
512
+ }
513
+ else {
514
+ return Promise.reject(new Error((0, utils_1.log)(`Invalid route resolution result for path "${context.pathname}". ` +
515
+ `Expected redirect object or HTML element, but got: "${(0, utils_1.logValue)(result)}". ` +
516
+ `Double check the action return value for the route.`)));
517
+ }
518
+ }
519
+ __amendWithOnBeforeCallbacks(contextWithFullChain) {
520
+ return this.__runOnBeforeCallbacks(contextWithFullChain).then((amendedContext) => {
521
+ if (amendedContext === this.__previousContext || amendedContext === contextWithFullChain) {
522
+ return amendedContext;
523
+ }
524
+ return this.__fullyResolveChain(amendedContext);
525
+ });
526
+ }
527
+ __runOnBeforeCallbacks(newContext) {
528
+ const previousContext = this.__previousContext || {};
529
+ const previousChain = previousContext.chain || [];
530
+ const newChain = newContext.chain;
531
+ let callbacks = Promise.resolve();
532
+ const prevent = () => ({ cancel: true });
533
+ const redirect = (pathname) => createRedirect(newContext, pathname);
534
+ newContext.__divergedChainIndex = 0;
535
+ newContext.__skipAttach = false;
536
+ if (previousChain.length) {
537
+ for (let i = 0; i < Math.min(previousChain.length, newChain.length); i = ++newContext.__divergedChainIndex) {
538
+ if (previousChain[i].route !== newChain[i].route ||
539
+ (previousChain[i].path !== newChain[i].path && previousChain[i].element !== newChain[i].element) ||
540
+ !this.__isReusableElement(previousChain[i].element, newChain[i].element)) {
541
+ break;
542
+ }
543
+ }
544
+ newContext.__skipAttach =
545
+ newChain.length === previousChain.length &&
546
+ newContext.__divergedChainIndex == newChain.length &&
547
+ this.__isReusableElement(newContext.result, previousContext.result);
548
+ if (newContext.__skipAttach) {
549
+ for (let i = newChain.length - 1; i >= 0; i--) {
550
+ callbacks = this.__runOnBeforeLeaveCallbacks(callbacks, newContext, { prevent }, previousChain[i]);
551
+ }
552
+ for (let i = 0; i < newChain.length; i++) {
553
+ callbacks = this.__runOnBeforeEnterCallbacks(callbacks, newContext, { prevent, redirect }, newChain[i]);
554
+ previousChain[i].element.location = createLocation(newContext, previousChain[i].route);
555
+ }
556
+ }
557
+ else {
558
+ for (let i = previousChain.length - 1; i >= newContext.__divergedChainIndex; i--) {
559
+ callbacks = this.__runOnBeforeLeaveCallbacks(callbacks, newContext, { prevent }, previousChain[i]);
560
+ }
561
+ }
562
+ }
563
+ if (!newContext.__skipAttach) {
564
+ for (let i = 0; i < newChain.length; i++) {
565
+ if (i < newContext.__divergedChainIndex) {
566
+ if (i < previousChain.length && previousChain[i].element) {
567
+ previousChain[i].element.location = createLocation(newContext, previousChain[i].route);
568
+ }
569
+ }
570
+ else {
571
+ callbacks = this.__runOnBeforeEnterCallbacks(callbacks, newContext, { prevent, redirect }, newChain[i]);
572
+ if (newChain[i].element) {
573
+ newChain[i].element.location = createLocation(newContext, newChain[i].route);
574
+ }
575
+ }
576
+ }
577
+ }
578
+ return callbacks.then((amendmentResult) => {
579
+ if (amendmentResult) {
580
+ if (amendmentResult.cancel) {
581
+ this.__previousContext.__renderId = newContext.__renderId;
582
+ return this.__previousContext;
583
+ }
584
+ if (amendmentResult.redirect) {
585
+ return this.__redirect(amendmentResult.redirect, newContext.__redirectCount, newContext.__renderId);
586
+ }
587
+ }
588
+ return newContext;
589
+ });
590
+ }
591
+ __runOnBeforeLeaveCallbacks(callbacks, newContext, commands, chainElement) {
592
+ const location = createLocation(newContext);
593
+ return callbacks
594
+ .then((result) => {
595
+ if (this.__isLatestRender(newContext)) {
596
+ const afterLeaveFunction = amend('onBeforeLeave', [location, commands, this], chainElement.element);
597
+ return afterLeaveFunction(result);
598
+ }
599
+ return undefined;
600
+ })
601
+ .then((result) => {
602
+ if (!(result || {}).redirect) {
603
+ return result;
604
+ }
605
+ return undefined;
606
+ });
607
+ }
608
+ __runOnBeforeEnterCallbacks(callbacks, newContext, commands, chainElement) {
609
+ const location = createLocation(newContext, chainElement.route);
610
+ return callbacks.then((result) => {
611
+ if (this.__isLatestRender(newContext)) {
612
+ const beforeEnterFunction = amend('onBeforeEnter', [location, commands, this], chainElement.element);
613
+ return beforeEnterFunction(result);
614
+ }
615
+ return undefined;
616
+ });
617
+ }
618
+ __isReusableElement(element, otherElement) {
619
+ if (element && otherElement) {
620
+ return this.__createdByRouter.get(element) && this.__createdByRouter.get(otherElement)
621
+ ? element.localName === otherElement.localName
622
+ : element === otherElement;
623
+ }
624
+ return false;
625
+ }
626
+ __isLatestRender(context) {
627
+ return context.__renderId === this.__lastStartedRenderId;
628
+ }
629
+ __redirect(redirectData, counter, renderId) {
630
+ if ((counter || 0) > MAX_REDIRECT_COUNT) {
631
+ throw new Error((0, utils_1.log)(`Too many redirects when rendering ${redirectData.from}`));
632
+ }
633
+ return this.resolve({
634
+ pathname: this.urlForPath(redirectData.pathname, redirectData.params),
635
+ redirectFrom: redirectData.from,
636
+ __redirectCount: (counter || 0) + 1,
637
+ __renderId: renderId,
638
+ });
639
+ }
640
+ __ensureOutlet(outlet = this.__outlet) {
641
+ if (!(outlet instanceof Node)) {
642
+ throw new TypeError((0, utils_1.log)(`Expected router outlet to be a valid DOM Node (but got ${outlet})`));
643
+ }
644
+ }
645
+ __updateBrowserHistory({ pathname, search = '', hash = '' }, replace) {
646
+ if (window.location.pathname !== pathname || window.location.search !== search || window.location.hash !== hash) {
647
+ const changeState = replace ? 'replaceState' : 'pushState';
648
+ window.history[changeState](null, document.title, pathname + search + hash);
649
+ window.dispatchEvent(new PopStateEvent('popstate', { state: 'router-ignore' }));
650
+ }
651
+ }
652
+ __copyUnchangedElements(context, previousContext) {
653
+ let deepestCommonParent = this.__outlet;
654
+ for (let i = 0; i < context.__divergedChainIndex; i++) {
655
+ const unchangedElement = previousContext && previousContext.chain[i].element;
656
+ if (unchangedElement) {
657
+ if (unchangedElement.parentNode === deepestCommonParent) {
658
+ context.chain[i].element = unchangedElement;
659
+ deepestCommonParent = unchangedElement;
660
+ }
661
+ else {
662
+ break;
663
+ }
664
+ }
665
+ }
666
+ return deepestCommonParent;
667
+ }
668
+ __addAppearingContent(context, previousContext) {
669
+ this.__ensureOutlet();
670
+ this.__removeAppearingContent();
671
+ const deepestCommonParent = this.__copyUnchangedElements(context, previousContext);
672
+ this.__appearingContent = [];
673
+ this.__disappearingContent = Array.from(deepestCommonParent.children).filter((e) => this.__addedByRouter.get(e) && e !== context.result);
674
+ let parentElement = deepestCommonParent;
675
+ for (let i = context.__divergedChainIndex; i < context.chain.length; i++) {
676
+ const elementToAdd = context.chain[i].element;
677
+ if (elementToAdd) {
678
+ parentElement.appendChild(elementToAdd);
679
+ this.__addedByRouter.set(elementToAdd, true);
680
+ if (parentElement === deepestCommonParent) {
681
+ this.__appearingContent.push(elementToAdd);
682
+ }
683
+ parentElement = elementToAdd;
684
+ }
685
+ }
686
+ }
687
+ __removeDisappearingContent() {
688
+ if (this.__disappearingContent) {
689
+ removeDomNodes(this.__disappearingContent);
690
+ }
691
+ this.__disappearingContent = null;
692
+ this.__appearingContent = null;
693
+ }
694
+ __removeAppearingContent() {
695
+ if (this.__disappearingContent && this.__appearingContent) {
696
+ removeDomNodes(this.__appearingContent);
697
+ this.__disappearingContent = null;
698
+ this.__appearingContent = null;
699
+ }
700
+ }
701
+ __runOnAfterLeaveCallbacks(currentContext, targetContext) {
702
+ if (!targetContext) {
703
+ return;
704
+ }
705
+ for (let i = targetContext.chain.length - 1; i >= currentContext.__divergedChainIndex; i--) {
706
+ if (!this.__isLatestRender(currentContext)) {
707
+ break;
708
+ }
709
+ const currentComponent = targetContext.chain[i].element;
710
+ if (!currentComponent) {
711
+ continue;
712
+ }
713
+ try {
714
+ const location = createLocation(currentContext);
715
+ (0, utils_1.runCallbackIfPossible)(currentComponent.onAfterLeave, [location, {}, targetContext.resolver], currentComponent);
716
+ }
717
+ finally {
718
+ if (this.__disappearingContent.indexOf(currentComponent) > -1) {
719
+ removeDomNodes(currentComponent.children);
720
+ }
721
+ }
722
+ }
723
+ }
724
+ __runOnAfterEnterCallbacks(currentContext) {
725
+ for (let i = currentContext.__divergedChainIndex; i < currentContext.chain.length; i++) {
726
+ if (!this.__isLatestRender(currentContext)) {
727
+ break;
728
+ }
729
+ const currentComponent = currentContext.chain[i].element || {};
730
+ const location = createLocation(currentContext, currentContext.chain[i].route);
731
+ (0, utils_1.runCallbackIfPossible)(currentComponent.onAfterEnter, [location, {}, currentContext.resolver], currentComponent);
732
+ }
733
+ }
734
+ __animateIfNeeded(context) {
735
+ const from = (this.__disappearingContent || [])[0];
736
+ const to = (this.__appearingContent || [])[0];
737
+ const promises = [];
738
+ const chain = context.chain;
739
+ let config;
740
+ for (let i = chain.length; i > 0; i--) {
741
+ if (chain[i - 1].route.animate) {
742
+ config = chain[i - 1].route.animate;
743
+ break;
744
+ }
745
+ }
746
+ if (from && to && config) {
747
+ const leave = ((0, utils_1.isObject)(config) && config.leave) || 'leaving';
748
+ const enter = ((0, utils_1.isObject)(config) && config.enter) || 'entering';
749
+ promises.push(animate(from, leave));
750
+ promises.push(animate(to, enter));
751
+ }
752
+ return Promise.all(promises).then(() => context);
753
+ }
754
+ /**
755
+ * Subscribes this instance to navigation events on the `window`.
756
+ *
757
+ * NOTE: beware of resource leaks. For as long as a router instance is
758
+ * subscribed to navigation events, it won't be garbage collected.
759
+ */
760
+ subscribe() {
761
+ window.addEventListener('router-go', this.__navigationEventHandler);
762
+ }
763
+ /**
764
+ * Removes the subscription to navigation events created in the `subscribe()`
765
+ * method.
766
+ */
767
+ unsubscribe() {
768
+ window.removeEventListener('router-go', this.__navigationEventHandler);
769
+ }
770
+ __onNavigationEvent(event) {
771
+ const { pathname, search, hash } = event ? event.detail : window.location;
772
+ if ((0, utils_1.isString)(this.__normalizePathname(pathname))) {
773
+ if (event && event.preventDefault) {
774
+ event.preventDefault();
775
+ }
776
+ this.render({ pathname, search, hash }, true);
777
+ }
778
+ }
779
+ /**
780
+ * Generates a URL for the route with the given name, optionally performing
781
+ * substitution of parameters.
782
+ *
783
+ * The route is searched in all the Router instances subscribed to
784
+ * navigation events.
785
+ *
786
+ * **Note:** For child route names, only array children are considered.
787
+ * It is not possible to generate URLs using a name for routes set with
788
+ * a children function.
789
+ *
790
+ * @param name the route name or the route’s `component` name.
791
+ * @param params Optional object with route path parameters.
792
+ * Named parameters are passed by name (`params[name] = value`), unnamed
793
+ * parameters are passed by index (`params[index] = value`).
794
+ *
795
+ * @return
796
+ */
797
+ urlForName(name, params) {
798
+ if (!this.__urlForName) {
799
+ this.__urlForName = (0, resolver_1.generateUrls)(this);
800
+ }
801
+ return getPathnameForRouter(this.__urlForName(name, params), this);
802
+ }
803
+ /**
804
+ * Generates a URL for the given route path, optionally performing
805
+ * substitution of parameters.
806
+ *
807
+ * @param path string route path declared in [express.js syntax](https://expressjs.com/en/guide/routing.html#route-paths").
808
+ * @param params Optional object with route path parameters.
809
+ * Named parameters are passed by name (`params[name] = value`), unnamed
810
+ * parameters are passed by index (`params[index] = value`).
811
+ *
812
+ * @return
813
+ */
814
+ urlForPath(path, params) {
815
+ return getPathnameForRouter((0, path_to_regexp_1.compile)(path)(params), this);
816
+ }
817
+ /**
818
+ * Triggers navigation to a new path. Returns a boolean without waiting until
819
+ * the navigation is complete. Returns `true` if at least one `Router`
820
+ * has handled the navigation (was subscribed and had `baseUrl` matching
821
+ * the `path` argument), otherwise returns `false`.
822
+ *
823
+ * @param path
824
+ * a new in-app path string, or an URL-like object with `pathname`
825
+ * string property, and optional `search` and `hash` string properties.
826
+ * @return
827
+ */
828
+ static go(path) {
829
+ const { pathname, search, hash } = (0, utils_1.isString)(path) ? this.__createUrl(path, 'http://a') : path;
830
+ return (0, utils_1.fireRouterEvent)('go', { pathname, search: search || '', hash: hash || '' });
831
+ }
832
+ }
833
+ exports.Router = Router;