text: import { h } from '../ionic.core.js';
  
  import { j as debounce } from './chunk-6d7d2f8c.js';
  import { a as attachComponent, b as detachComponent } from './chunk-99929188.js';
  import './chunk-90d954cd.js';
  import { c as transition } from './chunk-da1efb5f.js';
  
  class Route {
      constructor() {
          this.url = '';
      }
      onUpdate(newValue) {
          this.ionRouteDataChanged.emit(newValue);
      }
      onComponentProps(newValue, oldValue) {
          if (newValue === oldValue) {
              return;
          }
          const keys1 = newValue ? Object.keys(newValue) : [];
          const keys2 = oldValue ? Object.keys(oldValue) : [];
          if (keys1.length !== keys2.length) {
              this.onUpdate(newValue);
              return;
          }
          for (const key of keys1) {
              if (newValue[key] !== oldValue[key]) {
                  this.onUpdate(newValue);
                  return;
              }
          }
      }
      componentDidLoad() {
          this.ionRouteDataChanged.emit();
      }
      componentDidUnload() {
          this.ionRouteDataChanged.emit();
      }
      static get is() { return "ion-route"; }
      static get properties() { return {
          "component": {
              "type": String,
              "attr": "component",
              "watchCallbacks": ["onUpdate"]
          },
          "componentProps": {
              "type": "Any",
              "attr": "component-props",
              "watchCallbacks": ["onComponentProps"]
          },
          "url": {
              "type": String,
              "attr": "url",
              "watchCallbacks": ["onUpdate"]
          }
      }; }
      static get events() { return [{
              "name": "ionRouteDataChanged",
              "method": "ionRouteDataChanged",
              "bubbles": true,
              "cancelable": true,
              "composed": true
          }]; }
  }
  
  class RouteRedirect {
      propDidChange() {
          this.ionRouteRedirectChanged.emit();
      }
      componentDidLoad() {
          this.ionRouteRedirectChanged.emit();
      }
      componentDidUnload() {
          this.ionRouteRedirectChanged.emit();
      }
      static get is() { return "ion-route-redirect"; }
      static get properties() { return {
          "from": {
              "type": String,
              "attr": "from",
              "watchCallbacks": ["propDidChange"]
          },
          "to": {
              "type": String,
              "attr": "to",
              "watchCallbacks": ["propDidChange"]
          }
      }; }
      static get events() { return [{
              "name": "ionRouteRedirectChanged",
              "method": "ionRouteRedirectChanged",
              "bubbles": true,
              "cancelable": true,
              "composed": true
          }]; }
  }
  
  const ROUTER_INTENT_NONE = 'root';
  const ROUTER_INTENT_FORWARD = 'forward';
  const ROUTER_INTENT_BACK = 'back';
  
  function generatePath(segments) {
      const path = segments
          .filter(s => s.length > 0)
          .join('/');
      return '/' + path;
  }
  function chainToPath(chain) {
      const path = [];
      for (const route of chain) {
          for (const segment of route.path) {
              if (segment[0] === ':') {
                  const param = route.params && route.params[segment.slice(1)];
                  if (!param) {
                      return null;
                  }
                  path.push(param);
              }
              else if (segment !== '') {
                  path.push(segment);
              }
          }
      }
      return path;
  }
  function writePath(history, root, useHash, path, direction, state) {
      let url = generatePath([
          ...parsePath(root),
          ...path
      ]);
      if (useHash) {
          url = '#' + url;
      }
      if (direction === ROUTER_INTENT_FORWARD) {
          history.pushState(state, '', url);
      }
      else {
          history.replaceState(state, '', url);
      }
  }
  function removePrefix(prefix, path) {
      if (prefix.length > path.length) {
          return null;
      }
      if (prefix.length <= 1 && prefix[0] === '') {
          return path;
      }
      for (let i = 0; i < prefix.length; i++) {
          if (prefix[i].length > 0 && prefix[i] !== path[i]) {
              return null;
          }
      }
      if (path.length === prefix.length) {
          return [''];
      }
      return path.slice(prefix.length);
  }
  function readPath(loc, root, useHash) {
      let pathname = loc.pathname;
      if (useHash) {
          const hash = loc.hash;
          pathname = (hash[0] === '#')
              ? hash.slice(1)
              : '';
      }
      const prefix = parsePath(root);
      const path = parsePath(pathname);
      return removePrefix(prefix, path);
  }
  function parsePath(path) {
      if (path == null) {
          return [''];
      }
      const segments = path.split('/')
          .map(s => s.trim())
          .filter(s => s.length > 0);
      if (segments.length === 0) {
          return [''];
      }
      else {
          return segments;
      }
  }
  
  function printRoutes(routes) {
      console.group(`[ion-core] ROUTES[${routes.length}]`);
      for (const chain of routes) {
          const path = [];
          chain.forEach(r => path.push(...r.path));
          const ids = chain.map(r => r.id);
          console.debug(`%c ${generatePath(path)}`, 'font-weight: bold; padding-left: 20px', '=>\t', `(${ids.join(', ')})`);
      }
      console.groupEnd();
  }
  function printRedirects(redirects) {
      console.group(`[ion-core] REDIRECTS[${redirects.length}]`);
      for (const redirect of redirects) {
          if (redirect.to) {
              console.debug('FROM: ', `$c ${generatePath(redirect.from)}`, 'font-weight: bold', ' TO: ', `$c ${generatePath(redirect.to)}`, 'font-weight: bold');
          }
      }
      console.groupEnd();
  }
  
  async function writeNavState(root, chain, direction, index, changed = false) {
      try {
          const outlet = searchNavNode(root);
          if (index >= chain.length || !outlet) {
              return changed;
          }
          await outlet.componentOnReady();
          const route = chain[index];
          const result = await outlet.setRouteId(route.id, route.params, direction);
          if (result.changed) {
              direction = ROUTER_INTENT_NONE;
              changed = true;
          }
          changed = await writeNavState(result.element, chain, direction, index + 1, changed);
          if (result.markVisible) {
              await result.markVisible();
          }
          return changed;
      }
      catch (e) {
          console.error(e);
          return false;
      }
  }
  async function readNavState(root) {
      const ids = [];
      let outlet;
      let node = root;
      while (true) {
          outlet = searchNavNode(node);
          if (outlet) {
              const id = await outlet.getRouteId();
              if (id) {
                  node = id.element;
                  id.element = undefined;
                  ids.push(id);
              }
              else {
                  break;
              }
          }
          else {
              break;
          }
      }
      return { ids, outlet };
  }
  function waitUntilNavNode(win) {
      if (searchNavNode(win.document.body)) {
          return Promise.resolve();
      }
      return new Promise(resolve => {
          win.addEventListener('ionNavWillLoad', resolve, { once: true });
      });
  }
  const QUERY = ':not([no-router]) ion-nav, :not([no-router]) ion-tabs, :not([no-router]) ion-router-outlet';
  function searchNavNode(root) {
      if (!root) {
          return undefined;
      }
      if (root.matches(QUERY)) {
          return root;
      }
      const outlet = root.querySelector(QUERY);
      return outlet ? outlet : undefined;
  }
  
  function matchesRedirect(input, route) {
      const { from, to } = route;
      if (to === undefined) {
          return false;
      }
      if (from.length > input.length) {
          return false;
      }
      for (let i = 0; i < from.length; i++) {
          const expected = from[i];
          if (expected === '*') {
              return true;
          }
          if (expected !== input[i]) {
              return false;
          }
      }
      return from.length === input.length;
  }
  function routeRedirect(path, routes) {
      return routes.find(route => matchesRedirect(path, route));
  }
  function matchesIDs(ids, chain) {
      const len = Math.min(ids.length, chain.length);
      let i = 0;
      for (; i < len; i++) {
          if (ids[i].toLowerCase() !== chain[i].id) {
              break;
          }
      }
      return i;
  }
  function matchesPath(inputPath, chain) {
      const segments = new RouterSegments(inputPath);
      let matchesDefault = false;
      let allparams;
      for (let i = 0; i < chain.length; i++) {
          const path = chain[i].path;
          if (path[0] === '') {
              matchesDefault = true;
          }
          else {
              for (const segment of path) {
                  const data = segments.next();
                  if (segment[0] === ':') {
                      if (data === '') {
                          return null;
                      }
                      allparams = allparams || [];
                      const params = allparams[i] || (allparams[i] = {});
                      params[segment.slice(1)] = data;
                  }
                  else if (data !== segment) {
                      return null;
                  }
              }
              matchesDefault = false;
          }
      }
      const matches = (matchesDefault)
          ? matchesDefault === (segments.next() === '')
          : true;
      if (!matches) {
          return null;
      }
      if (allparams) {
          return chain.map((route, i) => ({
              id: route.id,
              path: route.path,
              params: mergeParams(route.params, allparams[i])
          }));
      }
      return chain;
  }
  function mergeParams(a, b) {
      if (!a && b) {
          return b;
      }
      else if (a && !b) {
          return a;
      }
      else if (a && b) {
          return Object.assign({}, a, b);
      }
      return undefined;
  }
  function routerIDsToChain(ids, chains) {
      let match = null;
      let maxMatches = 0;
      const plainIDs = ids.map(i => i.id);
      for (const chain of chains) {
          const score = matchesIDs(plainIDs, chain);
          if (score > maxMatches) {
              match = chain;
              maxMatches = score;
          }
      }
      if (match) {
          return match.map((route, i) => ({
              id: route.id,
              path: route.path,
              params: mergeParams(route.params, ids[i] && ids[i].params)
          }));
      }
      return null;
  }
  function routerPathToChain(path, chains) {
      let match = null;
      let matches = 0;
      for (const chain of chains) {
          const matchedChain = matchesPath(path, chain);
          if (matchedChain !== null) {
              const score = computePriority(matchedChain);
              if (score > matches) {
                  matches = score;
                  match = matchedChain;
              }
          }
      }
      return match;
  }
  function computePriority(chain) {
      let score = 1;
      let level = 1;
      for (const route of chain) {
          for (const path of route.path) {
              if (path[0] === ':') {
                  score += Math.pow(1, level);
              }
              else if (path !== '') {
                  score += Math.pow(2, level);
              }
              level++;
          }
      }
      return score;
  }
  class RouterSegments {
      constructor(path) {
          this.path = path.slice();
      }
      next() {
          if (this.path.length > 0) {
              return this.path.shift();
          }
          return '';
      }
  }
  
  function readRedirects(root) {
      return Array.from(root.children)
          .filter(el => el.tagName === 'ION-ROUTE-REDIRECT')
          .map(el => {
          const to = readProp(el, 'to');
          return {
              from: parsePath(readProp(el, 'from')),
              to: to == null ? undefined : parsePath(to),
          };
      });
  }
  function readRoutes(root) {
      return flattenRouterTree(readRouteNodes(root));
  }
  function readRouteNodes(root, node = root) {
      return Array.from(node.children)
          .filter(el => el.tagName === 'ION-ROUTE' && el.component)
          .map(el => {
          const component = readProp(el, 'component');
          if (component == null) {
              throw new Error('component missing in ion-route');
          }
          return {
              path: parsePath(readProp(el, 'url')),
              id: component.toLowerCase(),
              params: el.componentProps,
              children: readRouteNodes(root, el)
          };
      });
  }
  function readProp(el, prop) {
      if (prop in el) {
          return el[prop];
      }
      if (el.hasAttribute(prop)) {
          return el.getAttribute(prop);
      }
      return null;
  }
  function flattenRouterTree(nodes) {
      const routes = [];
      for (const node of nodes) {
          flattenNode([], routes, node);
      }
      return routes;
  }
  function flattenNode(chain, routes, node) {
      const s = chain.slice();
      s.push({
          id: node.id,
          path: node.path,
          params: node.params
      });
      if (node.children.length === 0) {
          routes.push(s);
          return;
      }
      for (const sub of node.children) {
          flattenNode(s, routes, sub);
      }
  }
  
  class Router {
      constructor() {
          this.previousPath = null;
          this.busy = false;
          this.state = 0;
          this.lastState = 0;
          this.root = '/';
          this.useHash = true;
      }
      async componentWillLoad() {
          console.debug('[ion-router] router will load');
          await waitUntilNavNode(this.win);
          console.debug('[ion-router] found nav');
          await this.onRoutesChanged();
      }
      componentDidLoad() {
          this.win.addEventListener('ionRouteRedirectChanged', debounce(this.onRedirectChanged.bind(this), 10));
          this.win.addEventListener('ionRouteDataChanged', debounce(this.onRoutesChanged.bind(this), 100));
      }
      onPopState() {
          const direction = this.historyDirection();
          const path = this.getPath();
          console.debug('[ion-router] URL changed -> update nav', path, direction);
          return this.writeNavStateRoot(path, direction);
      }
      onBackButton(ev) {
          ev.detail.register(0, () => this.back());
      }
      push(url, direction = 'forward') {
          if (url.startsWith('.')) {
              url = (new URL(url, window.location.href)).pathname;
          }
          console.debug('[ion-router] URL pushed -> updating nav', url, direction);
          const path = parsePath(url);
          this.setPath(path, direction);
          return this.writeNavStateRoot(path, direction);
      }
      back() {
          this.win.history.back();
          return Promise.resolve(this.waitPromise);
      }
      printDebug() {
          console.debug('CURRENT PATH', this.getPath());
          console.debug('PREVIOUS PATH', this.previousPath);
          printRoutes(readRoutes(this.el));
          printRedirects(readRedirects(this.el));
      }
      async navChanged(direction) {
          if (this.busy) {
              console.warn('[ion-router] router is busy, navChanged was cancelled');
              return false;
          }
          const { ids, outlet } = await readNavState(this.win.document.body);
          const routes = readRoutes(this.el);
          const chain = routerIDsToChain(ids, routes);
          if (!chain) {
              console.warn('[ion-router] no matching URL for ', ids.map(i => i.id));
              return false;
          }
          const path = chainToPath(chain);
          if (!path) {
              console.warn('[ion-router] router could not match path because some required param is missing');
              return false;
          }
          console.debug('[ion-router] nav changed -> update URL', ids, path);
          this.setPath(path, direction);
          await this.safeWriteNavState(outlet, chain, ROUTER_INTENT_NONE, path, null, ids.length);
          return true;
      }
      onRedirectChanged() {
          const path = this.getPath();
          if (path && routeRedirect(path, readRedirects(this.el))) {
              this.writeNavStateRoot(path, ROUTER_INTENT_NONE);
          }
      }
      onRoutesChanged() {
          return this.writeNavStateRoot(this.getPath(), ROUTER_INTENT_NONE);
      }
      historyDirection() {
          const win = this.win;
          if (win.history.state === null) {
              this.state++;
              win.history.replaceState(this.state, win.document.title, win.document.location && win.document.location.href);
          }
          const state = win.history.state;
          const lastState = this.lastState;
          this.lastState = state;
          if (state > lastState) {
              return ROUTER_INTENT_FORWARD;
          }
          else if (state < lastState) {
              return ROUTER_INTENT_BACK;
          }
          else {
              return ROUTER_INTENT_NONE;
          }
      }
      async writeNavStateRoot(path, direction) {
          if (!path) {
              console.error('[ion-router] URL is not part of the routing set');
              return false;
          }
          const redirects = readRedirects(this.el);
          const redirect = routeRedirect(path, redirects);
          let redirectFrom = null;
          if (redirect) {
              this.setPath(redirect.to, direction);
              redirectFrom = redirect.from;
              path = redirect.to;
          }
          const routes = readRoutes(this.el);
          const chain = routerPathToChain(path, routes);
          if (!chain) {
              console.error('[ion-router] the path does not match any route');
              return false;
          }
          return this.safeWriteNavState(this.win.document.body, chain, direction, path, redirectFrom);
      }
      async safeWriteNavState(node, chain, direction, path, redirectFrom, index = 0) {
          const unlock = await this.lock();
          let changed = false;
          try {
              changed = await this.writeNavState(node, chain, direction, path, redirectFrom, index);
          }
          catch (e) {
              console.error(e);
          }
          unlock();
          return changed;
      }
      async lock() {
          const p = this.waitPromise;
          let resolve;
          this.waitPromise = new Promise(r => resolve = r);
          if (p !== undefined) {
              await p;
          }
          return resolve;
      }
      async writeNavState(node, chain, direction, path, redirectFrom, index = 0) {
          if (this.busy) {
              console.warn('[ion-router] router is busy, transition was cancelled');
              return false;
          }
          this.busy = true;
          const routeEvent = this.routeChangeEvent(path, redirectFrom);
          if (routeEvent) {
              this.ionRouteWillChange.emit(routeEvent);
          }
          const changed = await writeNavState(node, chain, direction, index);
          this.busy = false;
          if (changed) {
              console.debug('[ion-router] route changed', path);
          }
          if (routeEvent) {
              this.ionRouteDidChange.emit(routeEvent);
          }
          return changed;
      }
      setPath(path, direction) {
          this.state++;
          writePath(this.win.history, this.root, this.useHash, path, direction, this.state);
      }
      getPath() {
          return readPath(this.win.location, this.root, this.useHash);
      }
      routeChangeEvent(path, redirectFromPath) {
          const from = this.previousPath;
          const to = generatePath(path);
          this.previousPath = to;
          if (to === from) {
              return null;
          }
          const redirectedFrom = redirectFromPath ? generatePath(redirectFromPath) : null;
          return {
              from,
              redirectedFrom,
              to,
          };
      }
      static get is() { return "ion-router"; }
      static get properties() { return {
          "back": {
              "method": true
          },
          "config": {
              "context": "config"
          },
          "el": {
              "elementRef": true
          },
          "navChanged": {
              "method": true
          },
          "printDebug": {
              "method": true
          },
          "push": {
              "method": true
          },
          "queue": {
              "context": "queue"
          },
          "root": {
              "type": String,
              "attr": "root"
          },
          "useHash": {
              "type": Boolean,
              "attr": "use-hash"
          },
          "win": {
              "context": "window"
          }
      }; }
      static get events() { return [{
              "name": "ionRouteWillChange",
              "method": "ionRouteWillChange",
              "bubbles": true,
              "cancelable": true,
              "composed": true
          }, {
              "name": "ionRouteDidChange",
              "method": "ionRouteDidChange",
              "bubbles": true,
              "cancelable": true,
              "composed": true
          }]; }
      static get listeners() { return [{
              "name": "window:popstate",
              "method": "onPopState"
          }, {
              "name": "document:ionBackButton",
              "method": "onBackButton"
          }]; }
  }
  
  class RouterOutlet {
      constructor() {
          this.animated = true;
      }
      swipeHandlerChanged() {
          if (this.gesture) {
              this.gesture.setDisabled(this.swipeHandler === undefined);
          }
      }
      componentWillLoad() {
          this.ionNavWillLoad.emit();
      }
      async componentDidLoad() {
          this.gesture = (await import('./chunk-ca529fbc.js')).createSwipeBackGesture(this.el, this.queue, () => !!this.swipeHandler && this.swipeHandler.canStart(), () => this.swipeHandler && this.swipeHandler.onStart(), step => this.ani && this.ani.progressStep(step), (shouldComplete, step, dur) => {
              if (this.ani) {
                  this.ani.progressEnd(shouldComplete, step, dur);
              }
              if (this.swipeHandler) {
                  this.swipeHandler.onEnd(shouldComplete);
              }
          });
          this.swipeHandlerChanged();
      }
      componentDidUnload() {
          this.activeEl = this.activeComponent = undefined;
          if (this.gesture) {
              this.gesture.destroy();
              this.gesture = undefined;
          }
      }
      async commit(enteringEl, leavingEl, opts) {
          const unlock = await this.lock();
          let changed = false;
          try {
              changed = await this.transition(enteringEl, leavingEl, opts);
          }
          catch (e) {
              console.error(e);
          }
          unlock();
          return changed;
      }
      async setRouteId(id, params, direction) {
          const changed = await this.setRoot(id, params, {
              duration: direction === 'root' ? 0 : undefined,
              direction: direction === 'back' ? 'back' : 'forward',
          });
          return {
              changed,
              element: this.activeEl
          };
      }
      async getRouteId() {
          const active = this.activeEl;
          return active ? {
              id: active.tagName,
              element: active,
          } : undefined;
      }
      async setRoot(component, params, opts) {
          if (this.activeComponent === component) {
              return false;
          }
          const leavingEl = this.activeEl;
          const enteringEl = await attachComponent(this.delegate, this.el, component, ['ion-page', 'ion-page-invisible'], params);
          this.activeComponent = component;
          this.activeEl = enteringEl;
          await this.commit(enteringEl, leavingEl, opts);
          await detachComponent(this.delegate, leavingEl);
          return true;
      }
      async transition(enteringEl, leavingEl, opts = {}) {
          if (leavingEl === enteringEl) {
              return false;
          }
          this.ionNavWillChange.emit();
          const { mode, queue, win, el } = this;
          const animated = this.animated && this.config.getBoolean('animated', true);
          const animationBuilder = this.animation || opts.animationBuilder || this.config.get('navAnimation');
          await transition(Object.assign({ mode,
              queue,
              animated,
              animationBuilder, window: win, enteringEl,
              leavingEl, baseEl: el, progressCallback: (opts.progressAnimation
                  ? ani => this.ani = ani
                  : undefined) }, opts));
          this.ionNavDidChange.emit();
          return true;
      }
      async lock() {
          const p = this.waitPromise;
          let resolve;
          this.waitPromise = new Promise(r => resolve = r);
          if (p !== undefined) {
              await p;
          }
          return resolve;
      }
      render() {
          return (h("slot", null));
      }
      static get is() { return "ion-router-outlet"; }
      static get encapsulation() { return "shadow"; }
      static get properties() { return {
          "animated": {
              "type": Boolean,
              "attr": "animated"
          },
          "animation": {
              "type": "Any",
              "attr": "animation"
          },
          "commit": {
              "method": true
          },
          "config": {
              "context": "config"
          },
          "delegate": {
              "type": "Any",
              "attr": "delegate"
          },
          "el": {
              "elementRef": true
          },
          "getRouteId": {
              "method": true
          },
          "mode": {
              "type": String,
              "attr": "mode"
          },
          "queue": {
              "context": "queue"
          },
          "setRouteId": {
              "method": true
          },
          "swipeHandler": {
              "type": "Any",
              "attr": "swipe-handler",
              "watchCallbacks": ["swipeHandlerChanged"]
          },
          "win": {
              "context": "window"
          }
      }; }
      static get events() { return [{
              "name": "ionNavWillLoad",
              "method": "ionNavWillLoad",
              "bubbles": true,
              "cancelable": true,
              "composed": true
          }, {
              "name": "ionNavWillChange",
              "method": "ionNavWillChange",
              "bubbles": false,
              "cancelable": true,
              "composed": true
          }, {
              "name": "ionNavDidChange",
              "method": "ionNavDidChange",
              "bubbles": false,
              "cancelable": true,
              "composed": true
          }]; }
      static get style() { return ":host{left:0;right:0;top:0;bottom:0;position:absolute;contain:layout size style;overflow:hidden;z-index:0}"; }
  }
  
  export { Route as IonRoute, RouteRedirect as IonRouteRedirect, Router as IonRouter, RouterOutlet as IonRouterOutlet };
  