UNPKG

25.9 kBJavaScriptView Raw
1/*!
2 * (C) Ionic http://ionicframework.com - MIT License
3 */
4import { proxyCustomElement, HTMLElement, createEvent } from '@stencil/core/internal/client';
5import { c as componentOnReady, o as debounce } from './helpers.js';
6
7const ROUTER_INTENT_NONE = 'root';
8const ROUTER_INTENT_FORWARD = 'forward';
9const ROUTER_INTENT_BACK = 'back';
10
11/** Join the non empty segments with "/". */
12const generatePath = (segments) => {
13 const path = segments
14 .filter(s => s.length > 0)
15 .join('/');
16 return '/' + path;
17};
18const generateUrl = (segments, useHash, queryString) => {
19 let url = generatePath(segments);
20 if (useHash) {
21 url = '#' + url;
22 }
23 if (queryString !== undefined) {
24 url += '?' + queryString;
25 }
26 return url;
27};
28const writeSegments = (history, root, useHash, segments, direction, state, queryString) => {
29 const url = generateUrl([...parsePath(root).segments, ...segments], useHash, queryString);
30 if (direction === ROUTER_INTENT_FORWARD) {
31 history.pushState(state, '', url);
32 }
33 else {
34 history.replaceState(state, '', url);
35 }
36};
37/**
38 * Transforms a chain to a list of segments.
39 *
40 * Notes:
41 * - parameter segments of the form :param are replaced with their value,
42 * - null is returned when a value is missing for any parameter segment.
43 */
44const chainToSegments = (chain) => {
45 const segments = [];
46 for (const route of chain) {
47 for (const segment of route.segments) {
48 if (segment[0] === ':') {
49 const param = route.params && route.params[segment.slice(1)];
50 if (!param) {
51 return null;
52 }
53 segments.push(param);
54 }
55 else if (segment !== '') {
56 segments.push(segment);
57 }
58 }
59 }
60 return segments;
61};
62/**
63 * Removes the prefix segments from the path segments.
64 *
65 * Return:
66 * - null when the path segments do not start with the passed prefix,
67 * - the path segments after the prefix otherwise.
68 */
69const removePrefix = (prefix, segments) => {
70 if (prefix.length > segments.length) {
71 return null;
72 }
73 if (prefix.length <= 1 && prefix[0] === '') {
74 return segments;
75 }
76 for (let i = 0; i < prefix.length; i++) {
77 if (prefix[i] !== segments[i]) {
78 return null;
79 }
80 }
81 if (segments.length === prefix.length) {
82 return [''];
83 }
84 return segments.slice(prefix.length);
85};
86const readSegments = (loc, root, useHash) => {
87 const prefix = parsePath(root).segments;
88 const pathname = useHash ? loc.hash.slice(1) : loc.pathname;
89 const segments = parsePath(pathname).segments;
90 return removePrefix(prefix, segments);
91};
92/**
93 * Parses the path to:
94 * - segments an array of '/' separated parts,
95 * - queryString (undefined when no query string).
96 */
97const parsePath = (path) => {
98 let segments = [''];
99 let queryString;
100 if (path != null) {
101 const qsStart = path.indexOf('?');
102 if (qsStart > -1) {
103 queryString = path.substring(qsStart + 1);
104 path = path.substring(0, qsStart);
105 }
106 segments = path.split('/')
107 .map(s => s.trim())
108 .filter(s => s.length > 0);
109 if (segments.length === 0) {
110 segments = [''];
111 }
112 }
113 return { segments, queryString };
114};
115
116const printRoutes = (routes) => {
117 console.group(`[ion-core] ROUTES[${routes.length}]`);
118 for (const chain of routes) {
119 const segments = [];
120 chain.forEach(r => segments.push(...r.segments));
121 const ids = chain.map(r => r.id);
122 console.debug(`%c ${generatePath(segments)}`, 'font-weight: bold; padding-left: 20px', '=>\t', `(${ids.join(', ')})`);
123 }
124 console.groupEnd();
125};
126const printRedirects = (redirects) => {
127 console.group(`[ion-core] REDIRECTS[${redirects.length}]`);
128 for (const redirect of redirects) {
129 if (redirect.to) {
130 console.debug('FROM: ', `$c ${generatePath(redirect.from)}`, 'font-weight: bold', ' TO: ', `$c ${generatePath(redirect.to.segments)}`, 'font-weight: bold');
131 }
132 }
133 console.groupEnd();
134};
135
136/**
137 * Activates the passed route chain.
138 *
139 * There must be exactly one outlet per route entry in the chain.
140 *
141 * The methods calls setRouteId on each of the outlet with the corresponding route entry in the chain.
142 * setRouteId will create or select the view in the outlet.
143 */
144const writeNavState = async (root, chain, direction, index, changed = false, animation) => {
145 try {
146 // find next navigation outlet in the DOM
147 const outlet = searchNavNode(root);
148 // make sure we can continue interacting the DOM, otherwise abort
149 if (index >= chain.length || !outlet) {
150 return changed;
151 }
152 await new Promise(resolve => componentOnReady(outlet, resolve));
153 const route = chain[index];
154 const result = await outlet.setRouteId(route.id, route.params, direction, animation);
155 // if the outlet changed the page, reset navigation to neutral (no direction)
156 // this means nested outlets will not animate
157 if (result.changed) {
158 direction = ROUTER_INTENT_NONE;
159 changed = true;
160 }
161 // recursively set nested outlets
162 changed = await writeNavState(result.element, chain, direction, index + 1, changed, animation);
163 // once all nested outlets are visible let's make the parent visible too,
164 // using markVisible prevents flickering
165 if (result.markVisible) {
166 await result.markVisible();
167 }
168 return changed;
169 }
170 catch (e) {
171 console.error(e);
172 return false;
173 }
174};
175/**
176 * Recursively walks the outlet in the DOM.
177 *
178 * The function returns a list of RouteID corresponding to each of the outlet and the last outlet without a RouteID.
179 */
180const readNavState = async (root) => {
181 const ids = [];
182 let outlet;
183 let node = root;
184 while (outlet = searchNavNode(node)) {
185 const id = await outlet.getRouteId();
186 if (id) {
187 node = id.element;
188 id.element = undefined;
189 ids.push(id);
190 }
191 else {
192 break;
193 }
194 }
195 return { ids, outlet };
196};
197const waitUntilNavNode = () => {
198 if (searchNavNode(document.body)) {
199 return Promise.resolve();
200 }
201 return new Promise(resolve => {
202 window.addEventListener('ionNavWillLoad', () => resolve(), { once: true });
203 });
204};
205/** Selector for all the outlets supported by the router. */
206const OUTLET_SELECTOR = ':not([no-router]) ion-nav, :not([no-router]) ion-tabs, :not([no-router]) ion-router-outlet';
207const searchNavNode = (root) => {
208 if (!root) {
209 return undefined;
210 }
211 if (root.matches(OUTLET_SELECTOR)) {
212 return root;
213 }
214 const outlet = root.querySelector(OUTLET_SELECTOR);
215 return outlet !== null && outlet !== void 0 ? outlet : undefined;
216};
217
218/**
219 * Returns whether the given redirect matches the given path segments.
220 *
221 * A redirect matches when the segments of the path and redirect.from are equal.
222 * Note that segments are only checked until redirect.from contains a '*' which matches any path segment.
223 * The path ['some', 'path', 'to', 'page'] matches both ['some', 'path', 'to', 'page'] and ['some', 'path', '*'].
224 */
225const matchesRedirect = (segments, redirect) => {
226 const { from, to } = redirect;
227 if (to === undefined) {
228 return false;
229 }
230 if (from.length > segments.length) {
231 return false;
232 }
233 for (let i = 0; i < from.length; i++) {
234 const expected = from[i];
235 if (expected === '*') {
236 return true;
237 }
238 if (expected !== segments[i]) {
239 return false;
240 }
241 }
242 return from.length === segments.length;
243};
244/** Returns the first redirect matching the path segments or undefined when no match found. */
245const findRouteRedirect = (segments, redirects) => {
246 return redirects.find(redirect => matchesRedirect(segments, redirect));
247};
248const matchesIDs = (ids, chain) => {
249 const len = Math.min(ids.length, chain.length);
250 let score = 0;
251 for (let i = 0; i < len; i++) {
252 const routeId = ids[i];
253 const routeChain = chain[i];
254 // Skip results where the route id does not match the chain at the same index
255 if (routeId.id.toLowerCase() !== routeChain.id) {
256 break;
257 }
258 if (routeId.params) {
259 const routeIdParams = Object.keys(routeId.params);
260 // Only compare routes with the chain that have the same number of parameters.
261 if (routeIdParams.length === routeChain.segments.length) {
262 // Maps the route's params into a path based on the path variable names,
263 // to compare against the route chain format.
264 //
265 // Before:
266 // ```ts
267 // {
268 // params: {
269 // s1: 'a',
270 // s2: 'b'
271 // }
272 // }
273 // ```
274 //
275 // After:
276 // ```ts
277 // [':s1',':s2']
278 // ```
279 //
280 const pathWithParams = routeIdParams.map(key => `:${key}`);
281 for (let j = 0; j < pathWithParams.length; j++) {
282 // Skip results where the path variable is not a match
283 if (pathWithParams[j].toLowerCase() !== routeChain.segments[j]) {
284 break;
285 }
286 // Weight path matches for the same index higher.
287 score++;
288 }
289 }
290 }
291 // Weight id matches
292 score++;
293 }
294 return score;
295};
296/**
297 * Matches the segments against the chain.
298 *
299 * Returns:
300 * - null when there is no match,
301 * - a chain with the params properties updated with the parameter segments on match.
302 */
303const matchesSegments = (segments, chain) => {
304 const inputSegments = new RouterSegments(segments);
305 let matchesDefault = false;
306 let allparams;
307 for (let i = 0; i < chain.length; i++) {
308 const chainSegments = chain[i].segments;
309 if (chainSegments[0] === '') {
310 matchesDefault = true;
311 }
312 else {
313 for (const segment of chainSegments) {
314 const data = inputSegments.next();
315 // data param
316 if (segment[0] === ':') {
317 if (data === '') {
318 return null;
319 }
320 allparams = allparams || [];
321 const params = allparams[i] || (allparams[i] = {});
322 params[segment.slice(1)] = data;
323 }
324 else if (data !== segment) {
325 return null;
326 }
327 }
328 matchesDefault = false;
329 }
330 }
331 const matches = (matchesDefault)
332 ? matchesDefault === (inputSegments.next() === '')
333 : true;
334 if (!matches) {
335 return null;
336 }
337 if (allparams) {
338 return chain.map((route, i) => ({
339 id: route.id,
340 segments: route.segments,
341 params: mergeParams(route.params, allparams[i]),
342 beforeEnter: route.beforeEnter,
343 beforeLeave: route.beforeLeave
344 }));
345 }
346 return chain;
347};
348/**
349 * Merges the route parameter objects.
350 * Returns undefined when both parameters are undefined.
351 */
352const mergeParams = (a, b) => {
353 return a || b ? Object.assign(Object.assign({}, a), b) : undefined;
354};
355/**
356 * Finds the best match for the ids in the chains.
357 *
358 * Returns the best match or null when no match is found.
359 * When a chain is returned the parameters are updated from the RouteIDs.
360 * That is they contain both the componentProps of the <ion-route> and the parameter segment.
361 */
362const findChainForIDs = (ids, chains) => {
363 let match = null;
364 let maxMatches = 0;
365 for (const chain of chains) {
366 const score = matchesIDs(ids, chain);
367 if (score > maxMatches) {
368 match = chain;
369 maxMatches = score;
370 }
371 }
372 if (match) {
373 return match.map((route, i) => {
374 var _a;
375 return ({
376 id: route.id,
377 segments: route.segments,
378 params: mergeParams(route.params, (_a = ids[i]) === null || _a === void 0 ? void 0 : _a.params)
379 });
380 });
381 }
382 return null;
383};
384/**
385 * Finds the best match for the segments in the chains.
386 *
387 * Returns the best match or null when no match is found.
388 * When a chain is returned the parameters are updated from the segments.
389 * That is they contain both the componentProps of the <ion-route> and the parameter segments.
390 */
391const findChainForSegments = (segments, chains) => {
392 let match = null;
393 let bestScore = 0;
394 for (const chain of chains) {
395 const matchedChain = matchesSegments(segments, chain);
396 if (matchedChain !== null) {
397 const score = computePriority(matchedChain);
398 if (score > bestScore) {
399 bestScore = score;
400 match = matchedChain;
401 }
402 }
403 }
404 return match;
405};
406/**
407 * Computes the priority of a chain.
408 *
409 * Parameter segments are given a lower priority over fixed segments.
410 *
411 * Considering the following 2 chains matching the path /path/to/page:
412 * - /path/to/:where
413 * - /path/to/page
414 *
415 * The second one will be given a higher priority because "page" is a fixed segment (vs ":where", a parameter segment).
416 */
417const computePriority = (chain) => {
418 let score = 1;
419 let level = 1;
420 for (const route of chain) {
421 for (const segment of route.segments) {
422 if (segment[0] === ':') {
423 score += Math.pow(1, level);
424 }
425 else if (segment !== '') {
426 score += Math.pow(2, level);
427 }
428 level++;
429 }
430 }
431 return score;
432};
433class RouterSegments {
434 constructor(segments) {
435 this.segments = segments.slice();
436 }
437 next() {
438 if (this.segments.length > 0) {
439 return this.segments.shift();
440 }
441 return '';
442 }
443}
444
445const readProp = (el, prop) => {
446 if (prop in el) {
447 return el[prop];
448 }
449 if (el.hasAttribute(prop)) {
450 return el.getAttribute(prop);
451 }
452 return null;
453};
454/**
455 * Extracts the redirects (that is <ion-route-redirect> elements inside the root).
456 *
457 * The redirects are returned as a list of RouteRedirect.
458 */
459const readRedirects = (root) => {
460 return Array.from(root.children)
461 .filter(el => el.tagName === 'ION-ROUTE-REDIRECT')
462 .map(el => {
463 const to = readProp(el, 'to');
464 return {
465 from: parsePath(readProp(el, 'from')).segments,
466 to: to == null ? undefined : parsePath(to),
467 };
468 });
469};
470/**
471 * Extracts all the routes (that is <ion-route> elements inside the root).
472 *
473 * The routes are returned as a list of chains - the flattened tree.
474 */
475const readRoutes = (root) => {
476 return flattenRouterTree(readRouteNodes(root));
477};
478/**
479 * Reads the route nodes as a tree modeled after the DOM tree of <ion-route> elements.
480 *
481 * Note: routes without a component are ignored together with their children.
482 */
483const readRouteNodes = (node) => {
484 return Array.from(node.children)
485 .filter(el => el.tagName === 'ION-ROUTE' && el.component)
486 .map(el => {
487 const component = readProp(el, 'component');
488 return {
489 segments: parsePath(readProp(el, 'url')).segments,
490 id: component.toLowerCase(),
491 params: el.componentProps,
492 beforeLeave: el.beforeLeave,
493 beforeEnter: el.beforeEnter,
494 children: readRouteNodes(el)
495 };
496 });
497};
498/**
499 * Flattens a RouterTree in a list of chains.
500 *
501 * Each chain represents a path from the root node to a terminal node.
502 */
503const flattenRouterTree = (nodes) => {
504 const chains = [];
505 for (const node of nodes) {
506 flattenNode([], chains, node);
507 }
508 return chains;
509};
510/** Flattens a route node recursively and push each branch to the chains list. */
511const flattenNode = (chain, chains, node) => {
512 chain = [...chain, {
513 id: node.id,
514 segments: node.segments,
515 params: node.params,
516 beforeLeave: node.beforeLeave,
517 beforeEnter: node.beforeEnter
518 }];
519 if (node.children.length === 0) {
520 chains.push(chain);
521 return;
522 }
523 for (const child of node.children) {
524 flattenNode(chain, chains, child);
525 }
526};
527
528const Router = /*@__PURE__*/ proxyCustomElement(class extends HTMLElement {
529 constructor() {
530 super();
531 this.__registerHost();
532 this.ionRouteWillChange = createEvent(this, "ionRouteWillChange", 7);
533 this.ionRouteDidChange = createEvent(this, "ionRouteDidChange", 7);
534 this.previousPath = null;
535 this.busy = false;
536 this.state = 0;
537 this.lastState = 0;
538 /**
539 * The root path to use when matching URLs. By default, this is set to "/", but you can specify
540 * an alternate prefix for all URL paths.
541 */
542 this.root = '/';
543 /**
544 * The router can work in two "modes":
545 * - With hash: `/index.html#/path/to/page`
546 * - Without hash: `/path/to/page`
547 *
548 * Using one or another might depend in the requirements of your app and/or where it's deployed.
549 *
550 * Usually "hash-less" navigation works better for SEO and it's more user friendly too, but it might
551 * requires additional server-side configuration in order to properly work.
552 *
553 * On the other side hash-navigation is much easier to deploy, it even works over the file protocol.
554 *
555 * By default, this property is `true`, change to `false` to allow hash-less URLs.
556 */
557 this.useHash = true;
558 }
559 async componentWillLoad() {
560 await waitUntilNavNode();
561 const canProceed = await this.runGuards(this.getSegments());
562 if (canProceed !== true) {
563 if (typeof canProceed === 'object') {
564 const { redirect } = canProceed;
565 const path = parsePath(redirect);
566 this.setSegments(path.segments, ROUTER_INTENT_NONE, path.queryString);
567 await this.writeNavStateRoot(path.segments, ROUTER_INTENT_NONE);
568 }
569 }
570 else {
571 await this.onRoutesChanged();
572 }
573 }
574 componentDidLoad() {
575 window.addEventListener('ionRouteRedirectChanged', debounce(this.onRedirectChanged.bind(this), 10));
576 window.addEventListener('ionRouteDataChanged', debounce(this.onRoutesChanged.bind(this), 100));
577 }
578 async onPopState() {
579 const direction = this.historyDirection();
580 let segments = this.getSegments();
581 const canProceed = await this.runGuards(segments);
582 if (canProceed !== true) {
583 if (typeof canProceed === 'object') {
584 segments = parsePath(canProceed.redirect).segments;
585 }
586 else {
587 return false;
588 }
589 }
590 return this.writeNavStateRoot(segments, direction);
591 }
592 onBackButton(ev) {
593 ev.detail.register(0, processNextHandler => {
594 this.back();
595 processNextHandler();
596 });
597 }
598 /** @internal */
599 async canTransition() {
600 const canProceed = await this.runGuards();
601 if (canProceed !== true) {
602 if (typeof canProceed === 'object') {
603 return canProceed.redirect;
604 }
605 else {
606 return false;
607 }
608 }
609 return true;
610 }
611 /**
612 * Navigate to the specified path.
613 *
614 * @param path The path to navigate to.
615 * @param direction The direction of the animation. Defaults to `"forward"`.
616 */
617 async push(path, direction = 'forward', animation) {
618 var _a;
619 if (path.startsWith('.')) {
620 const currentPath = (_a = this.previousPath) !== null && _a !== void 0 ? _a : '/';
621 // Convert currentPath to an URL by pre-pending a protocol and a host to resolve the relative path.
622 const url = new URL(path, `https://host/${currentPath}`);
623 path = url.pathname + url.search;
624 }
625 let parsedPath = parsePath(path);
626 const canProceed = await this.runGuards(parsedPath.segments);
627 if (canProceed !== true) {
628 if (typeof canProceed === 'object') {
629 parsedPath = parsePath(canProceed.redirect);
630 }
631 else {
632 return false;
633 }
634 }
635 this.setSegments(parsedPath.segments, direction, parsedPath.queryString);
636 return this.writeNavStateRoot(parsedPath.segments, direction, animation);
637 }
638 /** Go back to previous page in the window.history. */
639 back() {
640 window.history.back();
641 return Promise.resolve(this.waitPromise);
642 }
643 /** @internal */
644 async printDebug() {
645 printRoutes(readRoutes(this.el));
646 printRedirects(readRedirects(this.el));
647 }
648 /** @internal */
649 async navChanged(direction) {
650 if (this.busy) {
651 console.warn('[ion-router] router is busy, navChanged was cancelled');
652 return false;
653 }
654 const { ids, outlet } = await readNavState(window.document.body);
655 const routes = readRoutes(this.el);
656 const chain = findChainForIDs(ids, routes);
657 if (!chain) {
658 console.warn('[ion-router] no matching URL for ', ids.map(i => i.id));
659 return false;
660 }
661 const segments = chainToSegments(chain);
662 if (!segments) {
663 console.warn('[ion-router] router could not match path because some required param is missing');
664 return false;
665 }
666 this.setSegments(segments, direction);
667 await this.safeWriteNavState(outlet, chain, ROUTER_INTENT_NONE, segments, null, ids.length);
668 return true;
669 }
670 /** This handler gets called when a `ion-route-redirect` component is added to the DOM or if the from or to property of such node changes. */
671 onRedirectChanged() {
672 const segments = this.getSegments();
673 if (segments && findRouteRedirect(segments, readRedirects(this.el))) {
674 this.writeNavStateRoot(segments, ROUTER_INTENT_NONE);
675 }
676 }
677 /** This handler gets called when a `ion-route` component is added to the DOM or if the from or to property of such node changes. */
678 onRoutesChanged() {
679 return this.writeNavStateRoot(this.getSegments(), ROUTER_INTENT_NONE);
680 }
681 historyDirection() {
682 var _a;
683 const win = window;
684 if (win.history.state === null) {
685 this.state++;
686 win.history.replaceState(this.state, win.document.title, (_a = win.document.location) === null || _a === void 0 ? void 0 : _a.href);
687 }
688 const state = win.history.state;
689 const lastState = this.lastState;
690 this.lastState = state;
691 if (state > lastState || (state >= lastState && lastState > 0)) {
692 return ROUTER_INTENT_FORWARD;
693 }
694 if (state < lastState) {
695 return ROUTER_INTENT_BACK;
696 }
697 return ROUTER_INTENT_NONE;
698 }
699 async writeNavStateRoot(segments, direction, animation) {
700 if (!segments) {
701 console.error('[ion-router] URL is not part of the routing set');
702 return false;
703 }
704 // lookup redirect rule
705 const redirects = readRedirects(this.el);
706 const redirect = findRouteRedirect(segments, redirects);
707 let redirectFrom = null;
708 if (redirect) {
709 const { segments: toSegments, queryString } = redirect.to;
710 this.setSegments(toSegments, direction, queryString);
711 redirectFrom = redirect.from;
712 segments = toSegments;
713 }
714 // lookup route chain
715 const routes = readRoutes(this.el);
716 const chain = findChainForSegments(segments, routes);
717 if (!chain) {
718 console.error('[ion-router] the path does not match any route');
719 return false;
720 }
721 // write DOM give
722 return this.safeWriteNavState(document.body, chain, direction, segments, redirectFrom, 0, animation);
723 }
724 async safeWriteNavState(node, chain, direction, segments, redirectFrom, index = 0, animation) {
725 const unlock = await this.lock();
726 let changed = false;
727 try {
728 changed = await this.writeNavState(node, chain, direction, segments, redirectFrom, index, animation);
729 }
730 catch (e) {
731 console.error(e);
732 }
733 unlock();
734 return changed;
735 }
736 async lock() {
737 const p = this.waitPromise;
738 let resolve;
739 this.waitPromise = new Promise(r => resolve = r);
740 if (p !== undefined) {
741 await p;
742 }
743 return resolve;
744 }
745 /**
746 * Executes the beforeLeave hook of the source route and the beforeEnter hook of the target route if they exist.
747 *
748 * When the beforeLeave hook does not return true (to allow navigating) then that value is returned early and the beforeEnter is executed.
749 * Otherwise the beforeEnterHook hook of the target route is executed.
750 */
751 async runGuards(to = this.getSegments(), from) {
752 if (from === undefined) {
753 from = parsePath(this.previousPath).segments;
754 }
755 if (!to || !from) {
756 return true;
757 }
758 const routes = readRoutes(this.el);
759 const fromChain = findChainForSegments(from, routes);
760 const beforeLeaveHook = fromChain && fromChain[fromChain.length - 1].beforeLeave;
761 const canLeave = beforeLeaveHook ? await beforeLeaveHook() : true;
762 if (canLeave === false || typeof canLeave === 'object') {
763 return canLeave;
764 }
765 const toChain = findChainForSegments(to, routes);
766 const beforeEnterHook = toChain && toChain[toChain.length - 1].beforeEnter;
767 return beforeEnterHook ? beforeEnterHook() : true;
768 }
769 async writeNavState(node, chain, direction, segments, redirectFrom, index = 0, animation) {
770 if (this.busy) {
771 console.warn('[ion-router] router is busy, transition was cancelled');
772 return false;
773 }
774 this.busy = true;
775 // generate route event and emit will change
776 const routeEvent = this.routeChangeEvent(segments, redirectFrom);
777 if (routeEvent) {
778 this.ionRouteWillChange.emit(routeEvent);
779 }
780 const changed = await writeNavState(node, chain, direction, index, false, animation);
781 this.busy = false;
782 // emit did change
783 if (routeEvent) {
784 this.ionRouteDidChange.emit(routeEvent);
785 }
786 return changed;
787 }
788 setSegments(segments, direction, queryString) {
789 this.state++;
790 writeSegments(window.history, this.root, this.useHash, segments, direction, this.state, queryString);
791 }
792 getSegments() {
793 return readSegments(window.location, this.root, this.useHash);
794 }
795 routeChangeEvent(toSegments, redirectFromSegments) {
796 const from = this.previousPath;
797 const to = generatePath(toSegments);
798 this.previousPath = to;
799 if (to === from) {
800 return null;
801 }
802 const redirectedFrom = redirectFromSegments ? generatePath(redirectFromSegments) : null;
803 return {
804 from,
805 redirectedFrom,
806 to,
807 };
808 }
809 get el() { return this; }
810}, [0, "ion-router", {
811 "root": [1],
812 "useHash": [4, "use-hash"],
813 "canTransition": [64],
814 "push": [64],
815 "back": [64],
816 "printDebug": [64],
817 "navChanged": [64]
818 }, [[8, "popstate", "onPopState"], [4, "ionBackButton", "onBackButton"]]]);
819function defineCustomElement$1() {
820 if (typeof customElements === "undefined") {
821 return;
822 }
823 const components = ["ion-router"];
824 components.forEach(tagName => { switch (tagName) {
825 case "ion-router":
826 if (!customElements.get(tagName)) {
827 customElements.define(tagName, Router);
828 }
829 break;
830 } });
831}
832
833const IonRouter = Router;
834const defineCustomElement = defineCustomElement$1;
835
836export { IonRouter, defineCustomElement };