@elurjs/ionic 2.0.7 → 2.1.0
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/CHANGELOG.md +15 -0
- package/dist/lib/IonRouterOutlet.d.ts +20 -0
- package/dist/lib/__tests__/devtools.test.d.ts +1 -0
- package/dist/lib/devtools.cjs +2 -0
- package/dist/lib/devtools.cjs.map +1 -0
- package/dist/lib/devtools.d.ts +54 -0
- package/dist/lib/devtools.js +46 -0
- package/dist/lib/devtools.js.map +1 -0
- package/dist/lib/elur-ionic.cjs +1 -1
- package/dist/lib/elur-ionic.cjs.map +1 -1
- package/dist/lib/elur-ionic.js +1 -1
- package/dist/lib/elur-ionic.js.map +1 -1
- package/dist/lib/navigation.cjs +1 -1
- package/dist/lib/navigation.cjs.map +1 -1
- package/dist/lib/navigation.d.ts +9 -9
- package/dist/lib/navigation.js +15 -3
- package/dist/lib/navigation.js.map +1 -1
- package/package.json +6 -1
package/dist/lib/elur-ionic.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,i as n,n as r,o as i,r as a,s as o,t as s}from"./tabs-DjD1_PTM.js";import{NavigationManager as c,StackManager as l}from"./navigation.js";import{actionSheetController as u,alertController as d,confirm as f,createActionSheet as p,createAlert as m,createElurDelegate as h,createLoading as g,createModal as _,createModalController as v,createPicker as y,createPopover as b,createPopoverController as x,createToast as S,loadingController as ee,modalController as C,pickerController as w,popoverController as T,showToast as E,toastController as D,withLoading as O}from"./overlays.js";import{clearAllPageState as te,createPageState as ne,isSerializable as re}from"./page-state.js";import{ElurComponent as k,_hasActiveRouter as A,createRouter as j,effect as M,elurRouter as N,elurRouter as P,signal as F,watch as I}from"@elurjs/core";function L(){return{willEnter:F(0),didEnter:F(0),willLeave:F(0),didLeave:F(0)}}var R=Symbol("elur-ionic:connectLifecycle"),z=class extends k{__lc;__lifecycleDisposers=[];constructor(e){super(),this.__lc=e}[R](){if(this.__lifecycleDisposers.length>0)return()=>this._disposeLifecycle();let e=this.__lc;return this.ionViewWillEnter&&this.__lifecycleDisposers.push(I(e.willEnter,this.ionViewWillEnter.bind(this))),this.ionViewDidEnter&&this.__lifecycleDisposers.push(I(e.didEnter,this.ionViewDidEnter.bind(this))),this.ionViewWillLeave&&this.__lifecycleDisposers.push(I(e.willLeave,this.ionViewWillLeave.bind(this))),this.ionViewDidLeave&&this.__lifecycleDisposers.push(I(e.didLeave,this.ionViewDidLeave.bind(this))),()=>this._disposeLifecycle()}_disposeLifecycle(){for(let e of this.__lifecycleDisposers)e();this.__lifecycleDisposers=[]}};function B(e,t){return I(e.willEnter,t)}function V(e,t){return I(e.didEnter,t)}function H(e,t){return I(e.willLeave,t)}function U(e,t){return I(e.didLeave,t)}function W(e,t){return(i,a)=>{let n=K(e,i),r=G(i);return t({lc:L(),params:n,query:r})}}function G(e){let t=e.indexOf("?");if(-1===t)return{};let i=e.slice(t+1),a={};for(let e of i.split("&")){if(!e)continue;let t=e.indexOf("=");if(-1===t)a[e]="";else{let i=e.slice(0,t);try{a[i]=decodeURIComponent(e.slice(t+1))}catch{a[i]=e.slice(t+1)}}}return a}function K(e,t){let i=e.split("/").filter(Boolean),a=t.split("/").filter(Boolean),n={};for(let e=0;e<i.length&&e<a.length;e++){let t=i[e];if(t.startsWith(":"))try{n[t.slice(1)]=decodeURIComponent(a[e]??"")}catch{n[t.slice(1)]=a[e]??""}}return n}function q(e){return!1===e?{allow:!1}:!0===e||null==e?{allow:!0}:"string"==typeof e?{allow:!1,redirect:e}:"object"==typeof e&&"redirect"in e&&"string"==typeof e.redirect?{allow:!1,redirect:e.redirect}:{allow:!0}}function J(e){return e.map(e=>({path:e.path,component:void 0,beforeEnter:e.beforeEnter?W(e.path,e.beforeEnter):void 0}))}var Y=["ion-page-hidden","can-go-back"];function ie(e){e.classList.remove(...Y),e.style.removeProperty("display"),e.style.removeProperty("visibility"),e.style.removeProperty("opacity"),e.style.removeProperty("transform"),e.style.removeProperty("animation"),e.style.removeProperty("transition"),e.style.removeProperty("pointer-events"),e.style.removeProperty("z-index")}function ae(e){return e.includes(":")}function X(e,t,i){let a=[];return ae(e)&&t&&Object.keys(t).length>0&&a.push(Object.keys(t).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(t[e])}`).join("&")),i&&Object.keys(i).length>0&&a.push(Object.keys(i).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(i[e])}`).join("&")),0===a.length?e:`${e}?${a.join("&")}`}function Z(e,t){e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!1,composed:!0}))}var Q=new WeakMap,oe=class{_byTab=new Map;_onEvict=null;onEvict(e){this._onEvict=e}get(e,t){let i=this._byTab.get(e)?.get(t);return i&&(i.lastAccessed=Date.now()),i}set(e,t,i){let a=this._byTab.get(e);a||(a=new Map,this._byTab.set(e,a)),a.set(t,i)}delete(e,t){let i=this._byTab.get(e);if(!i)return;let a=i.get(t);a?.ttlTimer&&(clearTimeout(a.ttlTimer),a.ttlTimer=null),i.delete(t)}enforcePolicy(e,t,i){let a=this._byTab.get(e);if(!a)return 0;let n=0,r=Date.now();for(let[l,o]of a){let a=this._getEffectivePolicy(o.routePath,i,t);if(!1===a)continue;let s=a.ttl;s&&r-o.createdAt>s&&(this._evict(e,l,o),n++)}let l=t.max;if(l&&a.size>l){let i=t.strategy??"lru",r=[...a.entries()];"lru"===i?r.sort((e,t)=>e[1].lastAccessed-t[1].lastAccessed):r.sort((e,t)=>e[1].createdAt-t[1].createdAt);let o=a.size-l;for(let t=0;t<o;t++){let[i,a]=r[t];this._evict(e,i,a),n++}}return n}_getEffectivePolicy(e,t,i){let a=t.get(e);return!1!==a&&(!0===a?i:a&&"object"==typeof a?a:i)}_evict(e,t,i){i.ttlTimer&&=(clearTimeout(i.ttlTimer),null),this._byTab.get(e)?.delete(t),this._onEvict?.(i)}*all(){for(let[e,t]of this._byTab.entries())for(let[i,a]of t.entries())yield{tabKey:e,cacheKey:i,view:a}}clear(){for(let e of this._byTab.values())for(let t of e.values())t.ttlTimer&&clearTimeout(t.ttlTimer);this._byTab.clear()}};function se(e="/"){return{__isElurTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,i){let a=document.createElement("ion-buttons");a.setAttribute("slot","start");let n=document.createElement("ion-back-button");n.setAttribute("default-href",e);let r=t=>{t.preventDefault(),t.stopPropagation();let i=P();($?$.canGoBack.value:i.canGoBack.value)?i.back():i.replace(e)};return n.addEventListener("click",r),a.appendChild(n),t.insertBefore(a,i),()=>{n.removeEventListener("click",r),a.remove()}}}}var $=null,ce=class extends k{_routesByPath=new Map;_wildcardRoute=null;_enableCache;_cachePolicy;_routeCachePolicies=new Map;_defaultAnimation;_stacks;_cache=new oe;_nav;_activePageEl=null;_activeCacheKey=null;_activeTabKey=null;_outletEl=null;_routeEffectDisposer=null;_isTransitioning=!1;_pendingNav=null;_guardsInCoreRouter;constructor(e,t={}){super(),this._enableCache=t.cache??!0,this._cachePolicy=t.cachePolicy??{},this._defaultAnimation=t.defaultAnimation,t.navigation?(this._nav=t.navigation,this._stacks=t.navigation.stacks,$=t.navigation):(this._nav=null,this._stacks=new l(t.tabs));for(let t of e)"*"!==t.path?(this._routesByPath.has(t.path)&&console.warn(`[elur-ionic] Duplicate route path "${t.path}" — the previous definition will be overwritten. Each route path must be unique.`),this._routesByPath.set(t.path,t),void 0!==t.cache&&this._routeCachePolicies.set(t.path,t.cache)):(this._wildcardRoute&&console.warn('[elur-ionic] Duplicate wildcard route "*" — the previous fallback will be overwritten. Define only one "*" route.'),this._wildcardRoute=t);if(this._cache.onEvict(e=>{try{e.cleanup()}catch{}e.pageEl.parentElement&&e.pageEl.remove()}),this._guardsInCoreRouter=!t.skipAutoBootstrap&&!A(),this._guardsInCoreRouter&&j(J(e)),this._nav)for(let t of e)"*"!==t.path&&this._nav.registerInvalidationHandler(t.path,e=>{if(e)this.invalidateCache(t.path,e);else for(let e of this._cache.all())e.view.routePath===t.path&&this.invalidateCache(t.path,void 0,e.tabKey)})}_resolveRouteDefinition(e){let t=P().resolve(e);if(!t.matched||!t.route)return null;let i=this._routesByPath.get(t.route.path);return i?{def:i,params:t.params}:this._wildcardRoute?{def:this._wildcardRoute,params:t.params}:null}_createPageEl(){let e=document.createElement("ion-page");e.classList.add("ion-page"),e.classList.add("ion-page-invisible");let t=L();return e.addEventListener("ionViewWillEnter",()=>t.willEnter.update(e=>e+1)),e.addEventListener("ionViewDidEnter",()=>t.didEnter.update(e=>e+1)),e.addEventListener("ionViewWillLeave",()=>t.willLeave.update(e=>e+1)),e.addEventListener("ionViewDidLeave",()=>t.didLeave.update(e=>e+1)),{pageEl:e,lc:t}}_mountComponent(e,t,i){let a=t.component(i);if("render"in a&&"function"==typeof a.render){let t=a,i=null;R in t&&(i=t[R]()),t.onInit?.();let n=t.render()._render(e,null),r=t.onMount?.();return()=>{t.onUnmount?.(),"function"==typeof r&&r(),n(),i?.()}}return a._render(e,null)}_hideInactivePages(e){let t=this._outletEl;if(!t)return;let i=Array.from(t.children);for(let t of i)t instanceof HTMLElement&&("ION-PAGE"!==t.tagName&&!t.classList.contains("ion-page")||(t===e?t.classList.remove("ion-page-hidden"):t.classList.add("ion-page-hidden")))}async _transitionTo(e,t){let i=this._outletEl;if(!i)return;let a=this._resolveRouteDefinition(e);if(!a)return;let{def:n,params:r}=a,l=P(),o=l.query.value,s=X(n.path,r,o),c=this._stacks.keyForPath(e);if(this._isTransitioning)return void(this._nav?this._nav.setPendingNav(e,t):this._pendingNav={path:e,intent:t});if(s===this._activeCacheKey&&c===this._activeTabKey)return;this._isTransitioning=!0,this._nav&&this._nav.beginTransition();let h=!1;if(this._nav&&!await this._nav.runBeforeNav(e,t))return this._isTransitioning=!1,void this._nav.endTransition();try{if(n.beforeEnter&&!this._guardsInCoreRouter){let e=this._cache.get(c,s)?.lc??L(),t=q(await Promise.resolve(n.beforeEnter({lc:e,params:r,query:l.query.value})));if(!t.allow)return void(t.redirect?(this._pendingNav=null,P().replace(t.redirect)):h=!0)}let a,u=!1,d=this._routeCachePolicies.get(n.path),p=!1===d,v=this._enableCache&&!p,_=v?this._cache.get(c,s):void 0;if(_)ie(_.pageEl),_.pageEl.classList.remove("ion-page-hidden"),_.pageEl.style.removeProperty("display"),_.pageEl.classList.add("ion-page-invisible"),a=_.pageEl;else{let{pageEl:e,lc:t}=this._createPageEl(),i=this._mountComponent(e,n,{lc:t,params:r,query:o});if(v){let a=Date.now(),r=d&&"object"==typeof d?d:null,l=(r??this._cachePolicy).ttl,o={pageEl:e,lc:t,cleanup:i,cacheKey:s,lastAccessed:a,createdAt:a,routePath:n.path,routePolicy:r,ttlTimer:l?setTimeout(()=>{if(this._activeCacheKey!==s||this._activeTabKey!==c){this._cache.delete(c,s);try{i()}catch{}e.parentElement&&e.remove()}},l):null};this._cache.set(c,s,o),this._cachePolicy.max&&this._cache.enforcePolicy(c,this._cachePolicy,this._routeCachePolicies)}else Q.set(e,i);a=e,u=!0}i.contains(a)||i.appendChild(a);let f=this._stacks.apply(e,t),m=this._activePageEl;if(!m||m===a){this._activePageEl=a,this._activeCacheKey=s,this._activeTabKey=c;let e=a;return Z(e,"ionViewWillEnter"),void(u?requestAnimationFrame(()=>{requestAnimationFrame(()=>{e.classList.remove("ion-page-invisible"),this._hideInactivePages(e),Z(e,"ionViewDidEnter")})}):(e.classList.remove("ion-page-invisible"),this._hideInactivePages(e),Z(e,"ionViewDidEnter")))}let g=t.animation??this._defaultAnimation,y="root"===f||"none"===f,E={duration:y?0:void 0,direction:"back"===f?"back":"forward"===f?"forward":void 0,showGoBack:"forward"===f};g&&(E.animationBuilder=g),y&&(Z(m,"ionViewWillLeave"),Z(a,"ionViewWillEnter")),await i.commit(a,m,E),this._activePageEl=a,this._activeCacheKey=s,this._activeTabKey=c,a.classList.remove("ion-page-invisible"),a.classList.remove("ion-page-hidden"),a.style.removeProperty("display");let b=m&&parseInt(getComputedStyle(m).zIndex,10)||0;a.style.zIndex=String(b+1),this._hideInactivePages(a),y&&(Z(a,"ionViewDidEnter"),Z(m,"ionViewDidLeave"));let w=Q.get(m);w&&m.parentElement===i?(w(),Q.delete(m),m.remove()):!this._enableCache&&m.parentElement===i&&m.remove()}finally{this._isTransitioning=!1,this._nav&&this._nav.endTransition();let i=this._stacks.apply(e,t);this._nav&&!h&&(this._nav.runAfterNav(e,i),this._nav.runTabChangeIfNeeded(e),this._nav.updateCanGoBack());let a=this._nav?this._nav.consumePendingNav():this._pendingNav;if(this._nav||(this._pendingNav=null),a&&!h){let e=P();a.path===e.current.value&&this._transitionTo(a.path,a.intent)}else h&&this._nav&&this._nav.clearPendingNav()}}render(){let e=this;return{__isElurTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,i){let a=document.createElement("ion-router-outlet");e._outletEl=a,a.delegate={attachViewToDom:(e,t)=>(t&&!e.contains(t)&&e.appendChild(t),t),removeViewFromDom:async()=>{}},t.insertBefore(a,i);let n=P(),r=null,l=!1;return e._routeEffectDisposer=M(()=>{let t=n.current.value,i=n.intent.value,a=n.query.value,o=t+(Object.keys(a).length>0?"?"+Object.keys(a).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(a[e])}`).join("&"):"");if(!l)return l=!0,void queueMicrotask(()=>{let t=n.current.value,i=n.intent.value,a=n.query.value;r=t+(Object.keys(a).length>0?"?"+Object.keys(a).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(a[e])}`).join("&"):""),e._transitionTo(t,i)});o!==r?(r=o,e._transitionTo(t,i)):"pop"===i.action&&"none"===i.direction&&queueMicrotask(()=>{let t=n.current.value,i=n.query.value,a=t+(Object.keys(i).length>0?"?"+Object.keys(i).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(i[e])}`).join("&"):"");a!==r&&(r=a,e._transitionTo(t,n.intent.value))})}),()=>{e._routeEffectDisposer?.(),e._routeEffectDisposer=null;for(let{view:t}of e._cache.all())t.cleanup(),t.pageEl.parentElement&&t.pageEl.remove();if(e._cache.clear(),e._activePageEl){let t=Q.get(e._activePageEl);t&&(t(),Q.delete(e._activePageEl))}e._activePageEl=null,e._activeCacheKey=null,e._activeTabKey=null,e._outletEl=null,a.remove()}}}}invalidateCache(e,t,i,a){let n=t||a?X(e,t??{},a):e,r=i??this._stacks.keyForPath(e),l=this._cache.get(r,n);if(l){if(l.pageEl===this._activePageEl)return Q.set(l.pageEl,l.cleanup),this._cache.delete(r,n),void(this._activeCacheKey=null);l.cleanup(),l.pageEl.parentElement&&l.pageEl.remove(),this._cache.delete(r,n)}}clearCache(){let e=[];for(let t of this._cache.all())e.push(t);for(let{tabKey:t,cacheKey:i,view:a}of e)a.pageEl!==this._activePageEl&&(a.cleanup(),a.pageEl.parentElement&&a.pageEl.remove(),this._cache.delete(t,i))}clearTabCache(e){let t=[];for(let i of this._cache.all())i.tabKey===e&&t.push(i);for(let{tabKey:e,cacheKey:i,view:a}of t)a.pageEl!==this._activePageEl&&(a.cleanup(),a.pageEl.parentElement&&a.pageEl.remove(),this._cache.delete(e,i))}get navigation(){return this._nav}};export{se as IonBackButton,z as IonPage,ce as IonRouterOutlet,c as NavigationManager,l as StackManager,R as _connectIonicLifecycle,u as actionSheetController,a as addIcons,d as alertController,te as clearAllPageState,f as confirm,p as createActionSheet,m as createAlert,s as createBottomTabBar,h as createElurDelegate,g as createLoading,_ as createModal,v as createModalController,L as createPageLifecycle,ne as createPageState,y as createPicker,b as createPopover,x as createPopoverController,r as createTabsLayout,S as createToast,N as elurRouter,n as initializeElurIonic,re as isSerializable,ee as loadingController,C as modalController,w as pickerController,T as popoverController,e as registerIonicComponents,i as registerIonicons,o as setAssetPath,t as setupElurIonic,E as showToast,D as toastController,V as useIonViewDidEnter,U as useIonViewDidLeave,B as useIonViewWillEnter,H as useIonViewWillLeave,O as withLoading};
|
|
1
|
+
import{a as e,c as t,i as n,n as r,o as i,r as a,s as o,t as s}from"./tabs-DjD1_PTM.js";import{NavigationManager as c,StackManager as l}from"./navigation.js";import{actionSheetController as u,alertController as d,confirm as f,createActionSheet as p,createAlert as m,createElurDelegate as h,createLoading as g,createModal as _,createModalController as v,createPicker as y,createPopover as b,createPopoverController as x,createToast as S,loadingController as ee,modalController as C,pickerController as w,popoverController as T,showToast as E,toastController as D,withLoading as te}from"./overlays.js";import{clearAllPageState as ne,createPageState as re,isSerializable as ie}from"./page-state.js";import{ElurComponent as O,_hasActiveRouter as ae,createRouter as k,effect as A,elurRouter as j,elurRouter as M,signal as N,watch as P}from"@elurjs/core";function F(){return{willEnter:N(0),didEnter:N(0),willLeave:N(0),didLeave:N(0)}}var I=Symbol("elur-ionic:connectLifecycle"),L=class extends O{__lc;__lifecycleDisposers=[];constructor(e){super(),this.__lc=e}[I](){if(this.__lifecycleDisposers.length>0)return()=>this._disposeLifecycle();let e=this.__lc;return this.ionViewWillEnter&&this.__lifecycleDisposers.push(P(e.willEnter,this.ionViewWillEnter.bind(this))),this.ionViewDidEnter&&this.__lifecycleDisposers.push(P(e.didEnter,this.ionViewDidEnter.bind(this))),this.ionViewWillLeave&&this.__lifecycleDisposers.push(P(e.willLeave,this.ionViewWillLeave.bind(this))),this.ionViewDidLeave&&this.__lifecycleDisposers.push(P(e.didLeave,this.ionViewDidLeave.bind(this))),()=>this._disposeLifecycle()}_disposeLifecycle(){for(let e of this.__lifecycleDisposers)e();this.__lifecycleDisposers=[]}};function R(e,t){return P(e.willEnter,t)}function z(e,t){return P(e.didEnter,t)}function B(e,t){return P(e.willLeave,t)}function V(e,t){return P(e.didLeave,t)}function H(e,t){return(a,i)=>{let n=W(e,a),r=U(a);return t({lc:F(),params:n,query:r})}}function U(e){let t=e.indexOf("?");if(-1===t)return{};let a=e.slice(t+1),i={};for(let e of a.split("&")){if(!e)continue;let t=e.indexOf("=");if(-1===t)i[e]="";else{let a=e.slice(0,t);try{i[a]=decodeURIComponent(e.slice(t+1))}catch{i[a]=e.slice(t+1)}}}return i}function W(e,t){let a=e.split("/").filter(Boolean),i=t.split("/").filter(Boolean),n={};for(let e=0;e<a.length&&e<i.length;e++){let t=a[e];if(t.startsWith(":"))try{n[t.slice(1)]=decodeURIComponent(i[e]??"")}catch{n[t.slice(1)]=i[e]??""}}return n}function G(e){return!1===e?{allow:!1}:!0===e||null==e?{allow:!0}:"string"==typeof e?{allow:!1,redirect:e}:"object"==typeof e&&"redirect"in e&&"string"==typeof e.redirect?{allow:!1,redirect:e.redirect}:{allow:!0}}function K(e){return e.map(e=>({path:e.path,component:void 0,beforeEnter:e.beforeEnter?H(e.path,e.beforeEnter):void 0}))}var q=["ion-page-hidden","can-go-back"];function oe(e){e.classList.remove(...q),e.style.removeProperty("display"),e.style.removeProperty("visibility"),e.style.removeProperty("opacity"),e.style.removeProperty("transform"),e.style.removeProperty("animation"),e.style.removeProperty("transition"),e.style.removeProperty("pointer-events"),e.style.removeProperty("z-index")}function se(e){return e.includes(":")}function J(e,t,a){let i=[];return se(e)&&t&&Object.keys(t).length>0&&i.push(Object.keys(t).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(t[e])}`).join("&")),a&&Object.keys(a).length>0&&i.push(Object.keys(a).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(a[e])}`).join("&")),0===i.length?e:`${e}?${i.join("&")}`}function Y(e,t){e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!1,composed:!0}))}var X=new WeakMap,ce=class{_byTab=new Map;_onEvict=null;onEvict(e){this._onEvict=e}_debugSnapshot(){let e=[];for(let[t,a]of this._byTab)for(let[i,n]of a)e.push({tab:t,key:i,routePath:n.routePath,createdAt:n.createdAt,lastAccessed:n.lastAccessed});return e}get(e,t){let a=this._byTab.get(e)?.get(t);return a&&(a.lastAccessed=Date.now()),a}set(e,t,a){let i=this._byTab.get(e);i||(i=new Map,this._byTab.set(e,i)),i.set(t,a)}delete(e,t){let a=this._byTab.get(e);if(!a)return;let i=a.get(t);i?.ttlTimer&&(clearTimeout(i.ttlTimer),i.ttlTimer=null),a.delete(t)}enforcePolicy(e,t,a){let i=this._byTab.get(e);if(!i)return 0;let n=0,r=Date.now();for(let[l,o]of i){let i=this._getEffectivePolicy(o.routePath,a,t);if(!1===i)continue;let s=i.ttl;s&&r-o.createdAt>s&&(this._evict(e,l,o),n++)}let l=t.max;if(l&&i.size>l){let a=t.strategy??"lru",r=[...i.entries()];"lru"===a?r.sort((e,t)=>e[1].lastAccessed-t[1].lastAccessed):r.sort((e,t)=>e[1].createdAt-t[1].createdAt);let o=i.size-l;for(let t=0;t<o;t++){let[a,i]=r[t];this._evict(e,a,i),n++}}return n}_getEffectivePolicy(e,t,a){let i=t.get(e);return!1!==i&&(!0===i?a:i&&"object"==typeof i?i:a)}_evict(e,t,a){a.ttlTimer&&=(clearTimeout(a.ttlTimer),null),this._byTab.get(e)?.delete(t),this._onEvict?.(a)}*all(){for(let[e,t]of this._byTab.entries())for(let[a,i]of t.entries())yield{tabKey:e,cacheKey:a,view:i}}clear(){for(let e of this._byTab.values())for(let t of e.values())t.ttlTimer&&clearTimeout(t.ttlTimer);this._byTab.clear()}};function le(e="/"){return{__isElurTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,a){let i=document.createElement("ion-buttons");i.setAttribute("slot","start");let n=document.createElement("ion-back-button");n.setAttribute("default-href",e);let r=t=>{t.preventDefault(),t.stopPropagation();let a=M();(Z?Z.canGoBack.value:a.canGoBack.value)?a.back():a.replace(e)};return n.addEventListener("click",r),i.appendChild(n),t.insertBefore(i,a),()=>{n.removeEventListener("click",r),i.remove()}}}}var Z=null,Q=Symbol.for("@elurjs/ionic/outlets");function $(e){let t=globalThis,a=t[Q];a||(a=new Set,t[Q]=a),a.add(e)}var ue=class extends O{_routesByPath=new Map;_wildcardRoute=null;_enableCache;_cachePolicy;_routeCachePolicies=new Map;_defaultAnimation;_stacks;_cache=new ce;_nav;_activePageEl=null;_activeCacheKey=null;_activeTabKey=null;_outletEl=null;_routeEffectDisposer=null;_isTransitioning=!1;_pendingNav=null;_guardsInCoreRouter;constructor(e,t={}){super(),this._enableCache=t.cache??!0,this._cachePolicy=t.cachePolicy??{},this._defaultAnimation=t.defaultAnimation,t.navigation?(this._nav=t.navigation,this._stacks=t.navigation.stacks,Z=t.navigation):(this._nav=null,this._stacks=new l(t.tabs));for(let t of e)"*"!==t.path?(this._routesByPath.has(t.path)&&console.warn(`[elur-ionic] Duplicate route path "${t.path}" — the previous definition will be overwritten. Each route path must be unique.`),this._routesByPath.set(t.path,t),void 0!==t.cache&&this._routeCachePolicies.set(t.path,t.cache)):(this._wildcardRoute&&console.warn('[elur-ionic] Duplicate wildcard route "*" — the previous fallback will be overwritten. Define only one "*" route.'),this._wildcardRoute=t);if(this._cache.onEvict(e=>{try{e.cleanup()}catch{}e.pageEl.parentElement&&e.pageEl.remove()}),this._guardsInCoreRouter=!t.skipAutoBootstrap&&!ae(),this._guardsInCoreRouter&&k(K(e)),this._nav)for(let t of e)"*"!==t.path&&this._nav.registerInvalidationHandler(t.path,e=>{if(e)this.invalidateCache(t.path,e);else for(let e of this._cache.all())e.view.routePath===t.path&&this.invalidateCache(t.path,void 0,e.tabKey)});$(this)}_resolveRouteDefinition(e){let t=M().resolve(e);if(!t.matched||!t.route)return null;let a=this._routesByPath.get(t.route.path);return a?{def:a,params:t.params}:this._wildcardRoute?{def:this._wildcardRoute,params:t.params}:null}_createPageEl(){let e=document.createElement("ion-page");e.classList.add("ion-page"),e.classList.add("ion-page-invisible");let t=F();return e.addEventListener("ionViewWillEnter",()=>t.willEnter.update(e=>e+1)),e.addEventListener("ionViewDidEnter",()=>t.didEnter.update(e=>e+1)),e.addEventListener("ionViewWillLeave",()=>t.willLeave.update(e=>e+1)),e.addEventListener("ionViewDidLeave",()=>t.didLeave.update(e=>e+1)),{pageEl:e,lc:t}}_mountComponent(e,t,a){let i=t.component(a);if("render"in i&&"function"==typeof i.render){let t=i,a=null;I in t&&(a=t[I]()),t.onInit?.();let n=t.render()._render(e,null),r=t.onMount?.();return()=>{t.onUnmount?.(),"function"==typeof r&&r(),n(),a?.()}}return i._render(e,null)}_hideInactivePages(e){let t=this._outletEl;if(!t)return;let a=Array.from(t.children);for(let t of a)t instanceof HTMLElement&&("ION-PAGE"!==t.tagName&&!t.classList.contains("ion-page")||(t===e?t.classList.remove("ion-page-hidden"):t.classList.add("ion-page-hidden")))}async _transitionTo(e,t){let a=this._outletEl;if(!a)return;let i=this._resolveRouteDefinition(e);if(!i)return;let{def:n,params:r}=i,l=M(),o=l.query.value,s=J(n.path,r,o),c=this._stacks.keyForPath(e);if(this._isTransitioning)return void(this._nav?this._nav.setPendingNav(e,t):this._pendingNav={path:e,intent:t});if(s===this._activeCacheKey&&c===this._activeTabKey)return;this._isTransitioning=!0,this._nav&&this._nav.beginTransition();let h=!1;if(this._nav&&!await this._nav.runBeforeNav(e,t))return this._isTransitioning=!1,void this._nav.endTransition();try{if(n.beforeEnter&&!this._guardsInCoreRouter){let e=this._cache.get(c,s)?.lc??F(),t=G(await Promise.resolve(n.beforeEnter({lc:e,params:r,query:l.query.value})));if(!t.allow)return void(t.redirect?(this._pendingNav=null,M().replace(t.redirect)):h=!0)}let i,u=!1,d=this._routeCachePolicies.get(n.path),p=!1===d,v=this._enableCache&&!p,_=v?this._cache.get(c,s):void 0;if(_)oe(_.pageEl),_.pageEl.classList.remove("ion-page-hidden"),_.pageEl.style.removeProperty("display"),_.pageEl.classList.add("ion-page-invisible"),i=_.pageEl;else{let{pageEl:e,lc:t}=this._createPageEl(),a=this._mountComponent(e,n,{lc:t,params:r,query:o});if(v){let i=Date.now(),r=d&&"object"==typeof d?d:null,l=(r??this._cachePolicy).ttl,o={pageEl:e,lc:t,cleanup:a,cacheKey:s,lastAccessed:i,createdAt:i,routePath:n.path,routePolicy:r,ttlTimer:l?setTimeout(()=>{if(this._activeCacheKey!==s||this._activeTabKey!==c){this._cache.delete(c,s);try{a()}catch{}e.parentElement&&e.remove()}},l):null};this._cache.set(c,s,o),this._cachePolicy.max&&this._cache.enforcePolicy(c,this._cachePolicy,this._routeCachePolicies)}else X.set(e,a);i=e,u=!0}a.contains(i)||a.appendChild(i);let f=this._stacks.apply(e,t),g=this._activePageEl;if(!g||g===i){this._activePageEl=i,this._activeCacheKey=s,this._activeTabKey=c;let e=i;return Y(e,"ionViewWillEnter"),void(u?requestAnimationFrame(()=>{requestAnimationFrame(()=>{e.classList.remove("ion-page-invisible"),this._hideInactivePages(e),Y(e,"ionViewDidEnter")})}):(e.classList.remove("ion-page-invisible"),this._hideInactivePages(e),Y(e,"ionViewDidEnter")))}let m=t.animation??this._defaultAnimation,y="root"===f||"none"===f,E={duration:y?0:void 0,direction:"back"===f?"back":"forward"===f?"forward":void 0,showGoBack:"forward"===f};m&&(E.animationBuilder=m),y&&(Y(g,"ionViewWillLeave"),Y(i,"ionViewWillEnter")),await a.commit(i,g,E),this._activePageEl=i,this._activeCacheKey=s,this._activeTabKey=c,i.classList.remove("ion-page-invisible"),i.classList.remove("ion-page-hidden"),i.style.removeProperty("display");let b=g&&parseInt(getComputedStyle(g).zIndex,10)||0;i.style.zIndex=String(b+1),this._hideInactivePages(i),y&&(Y(i,"ionViewDidEnter"),Y(g,"ionViewDidLeave"));let P=X.get(g);P&&g.parentElement===a?(P(),X.delete(g),g.remove()):!this._enableCache&&g.parentElement===a&&g.remove()}finally{this._isTransitioning=!1,this._nav&&this._nav.endTransition();let a=this._stacks.apply(e,t);this._nav&&!h&&(this._nav.runAfterNav(e,a),this._nav.runTabChangeIfNeeded(e),this._nav.updateCanGoBack());let i=this._nav?this._nav.consumePendingNav():this._pendingNav;if(this._nav||(this._pendingNav=null),i&&!h){let e=M();i.path===e.current.value&&this._transitionTo(i.path,i.intent)}else h&&this._nav&&this._nav.clearPendingNav()}}render(){let e=this;return{__isElurTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,a){let i=document.createElement("ion-router-outlet");e._outletEl=i,i.delegate={attachViewToDom:(e,t)=>(t&&!e.contains(t)&&e.appendChild(t),t),removeViewFromDom:async()=>{}},t.insertBefore(i,a);let n=M(),r=null,l=!1;return e._routeEffectDisposer=A(()=>{let t=n.current.value,a=n.intent.value,i=n.query.value,o=t+(Object.keys(i).length>0?"?"+Object.keys(i).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(i[e])}`).join("&"):"");if(!l)return l=!0,void queueMicrotask(()=>{let t=n.current.value,a=n.intent.value,i=n.query.value;r=t+(Object.keys(i).length>0?"?"+Object.keys(i).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(i[e])}`).join("&"):""),e._transitionTo(t,a)});o!==r?(r=o,e._transitionTo(t,a)):"pop"===a.action&&"none"===a.direction&&queueMicrotask(()=>{let t=n.current.value,a=n.query.value,i=t+(Object.keys(a).length>0?"?"+Object.keys(a).sort().map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(a[e])}`).join("&"):"");i!==r&&(r=i,e._transitionTo(t,n.intent.value))})}),()=>{e._routeEffectDisposer?.(),e._routeEffectDisposer=null;for(let{view:t}of e._cache.all())t.cleanup(),t.pageEl.parentElement&&t.pageEl.remove();if(e._cache.clear(),e._activePageEl){let t=X.get(e._activePageEl);t&&(t(),X.delete(e._activePageEl))}e._activePageEl=null,e._activeCacheKey=null,e._activeTabKey=null,e._outletEl=null,i.remove()}}}}invalidateCache(e,t,a,i){let n=t||i?J(e,t??{},i):e,r=a??this._stacks.keyForPath(e),l=this._cache.get(r,n);if(l){if(l.pageEl===this._activePageEl)return X.set(l.pageEl,l.cleanup),this._cache.delete(r,n),void(this._activeCacheKey=null);l.cleanup(),l.pageEl.parentElement&&l.pageEl.remove(),this._cache.delete(r,n)}}clearCache(){let e=[];for(let t of this._cache.all())e.push(t);for(let{tabKey:t,cacheKey:a,view:i}of e)i.pageEl!==this._activePageEl&&(i.cleanup(),i.pageEl.parentElement&&i.pageEl.remove(),this._cache.delete(t,a))}clearTabCache(e){let t=[];for(let a of this._cache.all())a.tabKey===e&&t.push(a);for(let{tabKey:e,cacheKey:a,view:i}of t)i.pageEl!==this._activePageEl&&(i.cleanup(),i.pageEl.parentElement&&i.pageEl.remove(),this._cache.delete(e,a))}get navigation(){return this._nav}_debugSnapshot(){return{cacheEnabled:this._enableCache,cachePolicy:{...this._cachePolicy},views:this._cache._debugSnapshot(),stacks:this._stacks._debugSnapshot()}}};export{le as IonBackButton,L as IonPage,ue as IonRouterOutlet,c as NavigationManager,l as StackManager,I as _connectIonicLifecycle,u as actionSheetController,a as addIcons,d as alertController,ne as clearAllPageState,f as confirm,p as createActionSheet,m as createAlert,s as createBottomTabBar,h as createElurDelegate,g as createLoading,_ as createModal,v as createModalController,F as createPageLifecycle,re as createPageState,y as createPicker,b as createPopover,x as createPopoverController,r as createTabsLayout,S as createToast,j as elurRouter,n as initializeElurIonic,ie as isSerializable,ee as loadingController,C as modalController,w as pickerController,T as popoverController,e as registerIonicComponents,i as registerIonicons,o as setAssetPath,t as setupElurIonic,E as showToast,D as toastController,z as useIonViewDidEnter,V as useIonViewDidLeave,R as useIonViewWillEnter,B as useIonViewWillLeave,te as withLoading};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elur-ionic.js","names":[],"sources":["../../src/lifecycle.ts","../../src/IonRouterOutlet.ts"],"sourcesContent":["/**\n * @elurjs/ionic / lifecycle.ts — v2\n *\n * Page-lifecycle plumbing identical to v1. The hooks (ionViewWillEnter,\n * ionViewDidEnter, ionViewWillLeave, ionViewDidLeave) still come from the\n * native <ion-page> element events — IonRouterOutlet attaches the listeners\n * when it creates each page.\n *\n * Nothing here needed to change for the single-router refactor.\n */\n\nimport { signal, watch } from \"@elurjs/core\";\nimport type { Signal } from \"@elurjs/core\";\nimport { ElurComponent } from \"@elurjs/core\";\n\nexport interface PageLifecycle {\n willEnter: Signal<number>;\n didEnter: Signal<number>;\n willLeave: Signal<number>;\n didLeave: Signal<number>;\n}\n\nexport function createPageLifecycle(): PageLifecycle {\n return {\n willEnter: signal(0),\n didEnter: signal(0),\n willLeave: signal(0),\n didLeave: signal(0),\n };\n}\n\n/**\n * Internal symbol used by IonRouterOutlet's mount adapter to connect the\n * Ionic page lifecycle to an IonPage instance WITHOUT relying on the\n * subclass calling `super.onInit()`. The outlet calls this method directly\n * and stores the returned disposer so the watches are torn down with the view.\n */\nexport const _connectIonicLifecycle = Symbol(\"elur-ionic:connectLifecycle\");\n\n/**\n * Class-based pages. Subclass and implement any of the hooks.\n *\n * class HomePage extends IonPage {\n * constructor(lc: PageLifecycle) { super(lc); }\n * ionViewWillEnter() { this.refreshData(); }\n * render() { return html`...`; }\n * }\n *\n * Lifecycle wiring no longer depends on `onInit()` / `super.onInit()`:\n * the router outlet calls the symbol-based `_connectIonicLifecycle` method\n * directly and disposes the watches when the view is cleaned up. Subclasses\n * may override `onInit` freely for their own setup without calling super.\n */\nexport abstract class IonPage extends ElurComponent {\n private __lc: PageLifecycle;\n private __lifecycleDisposers: Array<() => void> = [];\n\n constructor(lc: PageLifecycle) {\n super();\n this.__lc = lc;\n }\n\n /**\n * Connects the Ionic view lifecycle signals to this page's hooks.\n * Called once by the router outlet's mount adapter. Returns a disposer\n * that tears down all lifecycle watches.\n *\n * Idempotent: calling it more than once is a no-op after the first call.\n */\n public [_connectIonicLifecycle](): () => void {\n if (this.__lifecycleDisposers.length > 0) return () => this._disposeLifecycle();\n const lc = this.__lc;\n if (this.ionViewWillEnter) {\n this.__lifecycleDisposers.push(watch(lc.willEnter, this.ionViewWillEnter.bind(this)));\n }\n if (this.ionViewDidEnter) {\n this.__lifecycleDisposers.push(watch(lc.didEnter, this.ionViewDidEnter.bind(this)));\n }\n if (this.ionViewWillLeave) {\n this.__lifecycleDisposers.push(watch(lc.willLeave, this.ionViewWillLeave.bind(this)));\n }\n if (this.ionViewDidLeave) {\n this.__lifecycleDisposers.push(watch(lc.didLeave, this.ionViewDidLeave.bind(this)));\n }\n return () => this._disposeLifecycle();\n }\n\n private _disposeLifecycle(): void {\n for (const d of this.__lifecycleDisposers) d();\n this.__lifecycleDisposers = [];\n }\n\n ionViewWillEnter?(): void;\n ionViewDidEnter?(): void;\n ionViewWillLeave?(): void;\n ionViewDidLeave?(): void;\n}\n\nexport function useIonViewWillEnter(lc: PageLifecycle, fn: () => void): () => void {\n return watch(lc.willEnter, fn);\n}\n\nexport function useIonViewDidEnter(lc: PageLifecycle, fn: () => void): () => void {\n return watch(lc.didEnter, fn);\n}\n\nexport function useIonViewWillLeave(lc: PageLifecycle, fn: () => void): () => void {\n return watch(lc.willLeave, fn);\n}\n\nexport function useIonViewDidLeave(lc: PageLifecycle, fn: () => void): () => void {\n return watch(lc.didLeave, fn);\n}","/**\n * @elurjs/ionic / IonRouterOutlet.ts — v2.5\n *\n * Architecture: \"core API + ion-router-outlet motor\" (with auto-bootstrap)\n *\n * Changes vs v2.4:\n *\n * (E) Manual lifecycle dispatch on duration-0 transitions.\n * EMPIRICAL FINDING: <ion-router-outlet>.commit() with `duration: 0`\n * (used for direction: \"none\" and \"root\") does NOT fire the lifecycle\n * events. We confirmed this with `replace(\"/login\")` after a logout —\n * the leaving page's `ionViewWillLeave` never ran.\n *\n * The fix: when `direction` is \"none\" or \"root\", we synthesize the\n * events ourselves around the commit() call, in the order Ionic\n * documents:\n * 1. WillLeave on the leaving page (BEFORE commit starts)\n * 2. WillEnter on the entering page (BEFORE commit starts)\n * 3. await commit() (instantaneous when duration=0)\n * 4. DidEnter on the entering page (AFTER commit resolves)\n * 5. DidLeave on the leaving page (AFTER DidEnter — Ionic docs)\n *\n * For animated transitions (\"forward\"/\"back\") we still rely on Ionic\n * to fire the events, since those go through the full animation path\n * where the events ARE wired correctly.\n *\n * Kept from earlier versions:\n * (A) Anti-flash on first mount.\n * (B) StackManager `back` recognition.\n * (C) _hideInactivePages defensive sweep after every transition.\n * (D) Manual lifecycle dispatch on first mount (no leaving page).\n *\n * Subclass note for IonPage users: if you override `onInit()` in a\n * subclass, you MUST call `super.onInit()` first. The base IonPage uses\n * onInit to wire `watch()` calls onto the lifecycle signals — without the\n * super call, your `ionViewWillEnter`/etc. methods never fire.\n */\n\nimport { ElurComponent, effect } from \"@elurjs/core\";\nimport type { ElurTemplate } from \"@elurjs/core\";\nimport {\n elurRouter,\n createRouter,\n _hasActiveRouter,\n type Router,\n type RouteRecord,\n type NavigationGuard,\n type NavigationIntent,\n} from \"@elurjs/core\";\nimport { createPageLifecycle, _connectIonicLifecycle, type PageLifecycle } from \"./lifecycle\";\nimport { NavigationManager, StackManager } from \"./navigation\";\n\nexport type GuardResult =\n | boolean\n | string\n | { redirect: string }\n | void\n | undefined;\n\nexport interface PageContext {\n lc: PageLifecycle;\n params: Record<string, string>;\n query: Record<string, string>;\n}\n\nexport interface RouteDefinition {\n path: string;\n component: (ctx: PageContext) => ElurComponent | ElurTemplate;\n beforeEnter?: (ctx: PageContext) => GuardResult | Promise<GuardResult>;\n /**\n * Per-route cache policy override. When set, takes precedence over the\n * outlet-level policy for this route.\n *\n * - `true` — use the outlet's default cache policy\n * - `false` — never cache this route (cleanup on leave)\n * - `{ max: N }` — cache at most N instances of this route\n * - `{ ttl: N }` — cache entries expire after N milliseconds\n * - `{ max: N, ttl: N }` — both bounds\n */\n cache?: boolean | CachePolicy;\n}\n\n/**\n * Bounded cache policy for cached pages.\n *\n * - `max`: maximum number of cached entries per tab. When exceeded, the\n * least-recently-used entry is evicted. Default: unlimited.\n * - `ttl`: time-to-live in milliseconds. Entries older than this are\n * evicted on next access or by a background timer. Default: unlimited.\n * - `strategy`: \"lru\" (default) evicts the least-recently-used entry when\n * `max` is reached. \"fifo\" evicts the oldest entry.\n */\nexport interface CachePolicy {\n max?: number;\n ttl?: number;\n strategy?: \"lru\" | \"fifo\";\n}\n\nexport interface IonRouterOutletOptions {\n /** Enable/disable caching globally. Set to false to disable all caching. */\n cache?: boolean;\n /**\n * Bounded cache policy applied to all cached routes (unless overridden\n * per-route via `RouteDefinition.cache`).\n */\n cachePolicy?: CachePolicy;\n defaultAnimation?: unknown;\n tabs?: string[];\n skipAutoBootstrap?: boolean;\n /**\n * Optional NavigationManager for centralized navigation state, hooks,\n * and programmatic tab switching. When provided, the outlet delegates\n * stack management and transition state to the manager.\n */\n navigation?: NavigationManager;\n}\n\nfunction adaptGuardForCore(\n routePath: string,\n pageGuard: (ctx: PageContext) => GuardResult | Promise<GuardResult>,\n): NavigationGuard {\n return (to: string, _from: string) => {\n const params = extractParamsFromPath(routePath, to);\n const query = extractQueryFromPath(to);\n const lc = createPageLifecycle();\n return pageGuard({ lc, params, query }) as any;\n };\n}\n\nfunction extractQueryFromPath(path: string): Record<string, string> {\n const qIndex = path.indexOf(\"?\");\n if (qIndex === -1) return {};\n const search = path.slice(qIndex + 1);\n const result: Record<string, string> = {};\n for (const pair of search.split(\"&\")) {\n if (!pair) continue;\n const eq = pair.indexOf(\"=\");\n if (eq === -1) {\n result[pair] = \"\";\n } else {\n const k = pair.slice(0, eq);\n try {\n result[k] = decodeURIComponent(pair.slice(eq + 1));\n } catch {\n result[k] = pair.slice(eq + 1);\n }\n }\n }\n return result;\n}\n\nfunction extractParamsFromPath(pattern: string, actual: string): Record<string, string> {\n const patternParts = pattern.split(\"/\").filter(Boolean);\n const actualParts = actual.split(\"/\").filter(Boolean);\n const params: Record<string, string> = {};\n for (let i = 0; i < patternParts.length && i < actualParts.length; i++) {\n const p = patternParts[i];\n if (p.startsWith(\":\")) {\n try {\n params[p.slice(1)] = decodeURIComponent(actualParts[i] ?? \"\");\n } catch {\n params[p.slice(1)] = actualParts[i] ?? \"\";\n }\n }\n }\n return params;\n}\n\nfunction _parseGuardResult(r: GuardResult): { allow: boolean; redirect?: string } {\n if (r === false) return { allow: false };\n if (r === true || r === undefined || r === null) return { allow: true };\n if (typeof r === \"string\") return { allow: false, redirect: r };\n if (typeof r === \"object\" && \"redirect\" in r && typeof r.redirect === \"string\") {\n return { allow: false, redirect: r.redirect };\n }\n return { allow: true };\n}\n\nfunction buildCoreRouteRecords(routes: RouteDefinition[]): RouteRecord[] {\n return routes.map((r): RouteRecord => ({\n path: r.path,\n component: undefined,\n beforeEnter: r.beforeEnter\n ? adaptGuardForCore(r.path, r.beforeEnter)\n : undefined,\n }));\n}\n\ninterface CachedView {\n pageEl: HTMLElement;\n lc: PageLifecycle;\n cleanup: () => void;\n cacheKey: string;\n /** Timestamp of last access (for LRU). */\n lastAccessed: number;\n /** Timestamp of creation (for TTL). */\n createdAt: number;\n /** Route path (for route-level policy lookup). */\n routePath: string;\n /** Per-route policy override (null = use outlet default). */\n routePolicy: CachePolicy | false | null;\n /** TTL timer handle (if TTL is set). */\n ttlTimer: ReturnType<typeof setTimeout> | null;\n}\n\nconst IONIC_STATE_CLASSES_TO_RESET = [\"ion-page-hidden\", \"can-go-back\"];\n\nfunction _resetCachedPageState(el: HTMLElement): void {\n el.classList.remove(...IONIC_STATE_CLASSES_TO_RESET);\n el.style.removeProperty(\"display\");\n el.style.removeProperty(\"visibility\");\n el.style.removeProperty(\"opacity\");\n el.style.removeProperty(\"transform\");\n el.style.removeProperty(\"animation\");\n el.style.removeProperty(\"transition\");\n el.style.removeProperty(\"pointer-events\");\n el.style.removeProperty(\"z-index\");\n}\n\nfunction _hasDynamicSegments(path: string): boolean {\n return path.includes(\":\");\n}\n\nfunction _buildCacheKey(\n routePath: string,\n params: Record<string, string>,\n query?: Record<string, string>,\n): string {\n const parts: string[] = [];\n\n // Params segment — only when the route has dynamic segments.\n if (_hasDynamicSegments(routePath) && params && Object.keys(params).length > 0) {\n parts.push(\n Object.keys(params)\n .sort()\n .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)\n .join(\"&\"),\n );\n }\n\n // Query segment — encoded to avoid collisions (e.g. x=1&y=2 vs x=1y=2).\n if (query && Object.keys(query).length > 0) {\n parts.push(\n Object.keys(query)\n .sort()\n .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`)\n .join(\"&\"),\n );\n }\n\n if (parts.length === 0) return routePath;\n return `${routePath}?${parts.join(\"&\")}`;\n}\n\n/**\n * Synthesize an Ionic page-lifecycle event on a pageEl. Used when commit()\n * either won't run at all (first mount) or runs with duration:0 (which\n * empirically does not fire lifecycle events in Ionic).\n */\nfunction _dispatchIonicLifecycle(\n pageEl: HTMLElement,\n name: \"ionViewWillEnter\" | \"ionViewDidEnter\" | \"ionViewWillLeave\" | \"ionViewDidLeave\",\n): void {\n pageEl.dispatchEvent(new CustomEvent(name, {\n bubbles: true,\n cancelable: false,\n composed: true,\n }));\n}\n\n/**\n * Tracks cleanup functions for pages that are NOT in the cache (cache:false\n * mode). Without this, effects/lifecycle watchers leak when a non-cached\n * page is removed from the DOM.\n */\nconst _uncachedCleanups = new WeakMap<HTMLElement, () => void>();\n\nclass CacheRegistry {\n private _byTab = new Map<string, Map<string, CachedView>>();\n /** Eviction callback — called when an entry is evicted by policy. */\n private _onEvict: ((view: CachedView) => void) | null = null;\n\n /** Set the eviction callback for policy-driven evictions. */\n onEvict(cb: (view: CachedView) => void): void {\n this._onEvict = cb;\n }\n\n get(tabKey: string, cacheKey: string): CachedView | undefined {\n const view = this._byTab.get(tabKey)?.get(cacheKey);\n if (view) {\n // Update LRU timestamp on access\n view.lastAccessed = Date.now();\n }\n return view;\n }\n\n set(tabKey: string, cacheKey: string, view: CachedView): void {\n let map = this._byTab.get(tabKey);\n if (!map) { map = new Map(); this._byTab.set(tabKey, map); }\n map.set(cacheKey, view);\n }\n\n delete(tabKey: string, cacheKey: string): void {\n const map = this._byTab.get(tabKey);\n if (!map) return;\n const view = map.get(cacheKey);\n if (view?.ttlTimer) {\n clearTimeout(view.ttlTimer);\n view.ttlTimer = null;\n }\n map.delete(cacheKey);\n }\n\n /**\n * Enforce cache policy for a specific tab. Evicts entries that exceed\n * `max` or have expired by `ttl`. Returns the number of evicted entries.\n */\n enforcePolicy(\n tabKey: string,\n policy: CachePolicy,\n routePolicies: Map<string, boolean | CachePolicy>,\n ): number {\n const map = this._byTab.get(tabKey);\n if (!map) return 0;\n\n let evicted = 0;\n\n // 1. TTL expiry — check all entries\n const now = Date.now();\n for (const [cacheKey, view] of map) {\n const effectivePolicy = this._getEffectivePolicy(view.routePath, routePolicies, policy);\n if (effectivePolicy === false) continue; // not cached\n const ttl = effectivePolicy.ttl;\n if (ttl && now - view.createdAt > ttl) {\n this._evict(tabKey, cacheKey, view);\n evicted++;\n }\n }\n\n // 2. Max enforcement — evict LRU/FIFO entries until under max\n const max = policy.max;\n if (max && map.size > max) {\n const strategy = policy.strategy ?? \"lru\";\n const entries = [...map.entries()];\n // Sort by eviction priority\n if (strategy === \"lru\") {\n entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed);\n } else {\n // FIFO — oldest creation first\n entries.sort((a, b) => a[1].createdAt - b[1].createdAt);\n }\n const toEvict = map.size - max;\n for (let i = 0; i < toEvict; i++) {\n const [cacheKey, view] = entries[i];\n this._evict(tabKey, cacheKey, view);\n evicted++;\n }\n }\n\n return evicted;\n }\n\n private _getEffectivePolicy(\n routePath: string,\n routePolicies: Map<string, boolean | CachePolicy>,\n defaultPolicy: CachePolicy,\n ): CachePolicy | false {\n const routeOverride = routePolicies.get(routePath);\n if (routeOverride === false) return false;\n if (routeOverride === true) return defaultPolicy;\n if (routeOverride && typeof routeOverride === \"object\") return routeOverride;\n return defaultPolicy;\n }\n\n private _evict(tabKey: string, cacheKey: string, view: CachedView): void {\n if (view.ttlTimer) {\n clearTimeout(view.ttlTimer);\n view.ttlTimer = null;\n }\n this._byTab.get(tabKey)?.delete(cacheKey);\n // Run cleanup + remove DOM\n this._onEvict?.(view);\n }\n\n *all(): Generator<{ tabKey: string; cacheKey: string; view: CachedView }> {\n for (const [tabKey, map] of this._byTab.entries()) {\n for (const [cacheKey, view] of map.entries()) {\n yield { tabKey, cacheKey, view };\n }\n }\n }\n\n clear(): void {\n for (const map of this._byTab.values()) {\n for (const view of map.values()) {\n if (view.ttlTimer) clearTimeout(view.ttlTimer);\n }\n }\n this._byTab.clear();\n }\n}\n\nexport function IonBackButton(defaultHref: string = \"/\"): ElurTemplate {\n return {\n __isElurTemplate: true as const,\n mount(container: Element | string) {\n const el = typeof container === \"string\"\n ? document.querySelector(container)!\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n // Wrap in <ion-buttons slot=\"start\"> for correct layout.\n // Without ion-buttons, ion-back-button has no flex constraints\n // and can expand to fill the toolbar.\n const buttons = document.createElement(\"ion-buttons\");\n buttons.setAttribute(\"slot\", \"start\");\n\n const btn = document.createElement(\"ion-back-button\");\n btn.setAttribute(\"default-href\", defaultHref);\n const onClick = (ev: Event) => {\n ev.preventDefault();\n ev.stopPropagation();\n const router = elurRouter();\n const nav = _activeNavigationManager;\n const canGoBack = nav ? nav.canGoBack.value : router.canGoBack.value;\n if (canGoBack) {\n router.back();\n } else {\n router.replace(defaultHref);\n }\n };\n btn.addEventListener(\"click\", onClick);\n buttons.appendChild(btn);\n parent.insertBefore(buttons, before);\n return () => {\n btn.removeEventListener(\"click\", onClick);\n buttons.remove();\n };\n },\n };\n}\n\n/**\n * Internal registry for the active NavigationManager, set by IonRouterOutlet\n * when it has one. This allows IonBackButton to use per-tab canGoBack\n * without prop drilling.\n */\nlet _activeNavigationManager: NavigationManager | null = null;\n\nexport class IonRouterOutlet extends ElurComponent {\n private _routesByPath = new Map<string, RouteDefinition>();\n private _wildcardRoute: RouteDefinition | null = null;\n private _enableCache: boolean;\n private _cachePolicy: CachePolicy;\n private _routeCachePolicies = new Map<string, boolean | CachePolicy>();\n private _defaultAnimation: unknown;\n\n private _stacks: StackManager;\n private _cache = new CacheRegistry();\n private _nav: NavigationManager | null;\n\n private _activePageEl: HTMLElement | null = null;\n private _activeCacheKey: string | null = null;\n private _activeTabKey: string | null = null;\n\n private _outletEl: HTMLElement | null = null;\n private _routeEffectDisposer: (() => void) | null = null;\n\n private _isTransitioning = false;\n private _pendingNav: { path: string; intent: NavigationIntent } | null = null;\n // True when the outlet auto-bootstrapped the core router with page guards\n // registered. In that case _transitionTo must NOT re-run beforeEnter\n // (the core router already did) — otherwise guards fire twice.\n private _guardsInCoreRouter: boolean;\n\n constructor(routes: RouteDefinition[], opts: IonRouterOutletOptions = {}) {\n super();\n this._enableCache = opts.cache ?? true;\n this._cachePolicy = opts.cachePolicy ?? {};\n this._defaultAnimation = opts.defaultAnimation;\n\n // Use provided NavigationManager or create an internal one\n if (opts.navigation) {\n this._nav = opts.navigation;\n this._stacks = opts.navigation.stacks;\n _activeNavigationManager = opts.navigation;\n } else {\n this._nav = null;\n this._stacks = new StackManager(opts.tabs);\n }\n\n for (const r of routes) {\n if (r.path === \"*\") {\n if (this._wildcardRoute) {\n console.warn(\n `[elur-ionic] Duplicate wildcard route \"*\" — the previous ` +\n `fallback will be overwritten. Define only one \"*\" route.`,\n );\n }\n this._wildcardRoute = r;\n continue;\n }\n if (this._routesByPath.has(r.path)) {\n console.warn(\n `[elur-ionic] Duplicate route path \"${r.path}\" — the previous ` +\n `definition will be overwritten. Each route path must be unique.`,\n );\n }\n this._routesByPath.set(r.path, r);\n // Collect per-route cache policy overrides\n if (r.cache !== undefined) {\n this._routeCachePolicies.set(r.path, r.cache);\n }\n }\n\n // Set up eviction callback — runs cleanup + removes DOM\n this._cache.onEvict((view) => {\n try {\n view.cleanup();\n } catch { /* ignore */ }\n if (view.pageEl.parentElement) {\n view.pageEl.remove();\n }\n });\n\n this._guardsInCoreRouter = !opts.skipAutoBootstrap && !_hasActiveRouter();\n if (this._guardsInCoreRouter) {\n createRouter(buildCoreRouteRecords(routes));\n }\n\n // Register cache invalidation handlers on the NavigationManager\n if (this._nav) {\n for (const r of routes) {\n if (r.path === \"*\") continue;\n this._nav.registerInvalidationHandler(r.path, (params) => {\n if (params) {\n this.invalidateCache(r.path, params);\n } else {\n // Invalidate all instances of this route\n for (const entry of this._cache.all()) {\n if (entry.view.routePath === r.path) {\n this.invalidateCache(\n r.path,\n undefined,\n entry.tabKey,\n );\n }\n }\n }\n });\n }\n }\n }\n\n private _resolveRouteDefinition(currentPath: string): {\n def: RouteDefinition;\n params: Record<string, string>;\n } | null {\n const router = elurRouter();\n const resolved = router.resolve(currentPath);\n if (!resolved.matched || !resolved.route) return null;\n const def = this._routesByPath.get(resolved.route.path);\n if (def) return { def, params: resolved.params };\n // Fallback to the wildcard route (if any) when the core router matched\n // a \"*\" route that the outlet excluded from _routesByPath.\n if (this._wildcardRoute) {\n return { def: this._wildcardRoute, params: resolved.params };\n }\n return null;\n }\n\n private _createPageEl(): { pageEl: HTMLElement; lc: PageLifecycle } {\n const pageEl = document.createElement(\"ion-page\");\n pageEl.classList.add(\"ion-page\");\n pageEl.classList.add(\"ion-page-invisible\");\n const lc = createPageLifecycle();\n pageEl.addEventListener(\"ionViewWillEnter\", () =>\n lc.willEnter.update((n) => n + 1));\n pageEl.addEventListener(\"ionViewDidEnter\", () =>\n lc.didEnter.update((n) => n + 1));\n pageEl.addEventListener(\"ionViewWillLeave\", () =>\n lc.willLeave.update((n) => n + 1));\n pageEl.addEventListener(\"ionViewDidLeave\", () =>\n lc.didLeave.update((n) => n + 1));\n return { pageEl, lc };\n }\n\n private _mountComponent(\n pageEl: HTMLElement,\n def: RouteDefinition,\n ctx: PageContext,\n ): () => void {\n const node = def.component(ctx);\n if (\"render\" in node && typeof (node as ElurComponent).render === \"function\") {\n const comp = node as ElurComponent;\n // Connect Ionic lifecycle via the symbol-based internal API so\n // the contract does NOT depend on subclasses calling super.onInit().\n // IonPage implements this symbol; other ElurComponents ignore it.\n let lifecycleDispose: (() => void) | null = null;\n if (_connectIonicLifecycle in comp) {\n lifecycleDispose = (comp as any)[_connectIonicLifecycle]();\n }\n comp.onInit?.();\n const renderCleanup = comp.render()._render(pageEl, null);\n const mountRet = comp.onMount?.();\n return () => {\n comp.onUnmount?.();\n if (typeof mountRet === \"function\") mountRet();\n renderCleanup();\n lifecycleDispose?.();\n };\n } else {\n return (node as ElurTemplate)._render(pageEl, null);\n }\n }\n\n private _hideInactivePages(activeEl: HTMLElement | null): void {\n const outletEl = this._outletEl;\n if (!outletEl) return;\n const children = Array.from(outletEl.children);\n for (const child of children) {\n if (!(child instanceof HTMLElement)) continue;\n if (child.tagName !== \"ION-PAGE\" && !child.classList.contains(\"ion-page\")) continue;\n if (child === activeEl) {\n child.classList.remove(\"ion-page-hidden\");\n } else {\n child.classList.add(\"ion-page-hidden\");\n }\n }\n }\n\n private async _transitionTo(\n targetPath: string,\n intent: NavigationIntent,\n ): Promise<void> {\n const outletEl = this._outletEl;\n if (!outletEl) return;\n\n const resolved = this._resolveRouteDefinition(targetPath);\n if (!resolved) return;\n\n const { def, params } = resolved;\n const router = elurRouter();\n const query = router.query.value;\n const cacheKey = _buildCacheKey(def.path, params, query);\n const targetTabKey = this._stacks.keyForPath(targetPath);\n\n if (this._isTransitioning) {\n // Preserve full intent metadata (direction, animation, action)\n // so the deferred navigation behaves identically to the original.\n if (this._nav) {\n this._nav.setPendingNav(targetPath, intent);\n } else {\n this._pendingNav = { path: targetPath, intent };\n }\n return;\n }\n\n if (cacheKey === this._activeCacheKey && targetTabKey === this._activeTabKey) return;\n this._isTransitioning = true;\n if (this._nav) this._nav.beginTransition();\n let transitionCancelled = false;\n\n // Run beforeNav hooks from NavigationManager\n if (this._nav) {\n const allowed = await this._nav.runBeforeNav(targetPath, intent);\n if (!allowed) {\n this._isTransitioning = false;\n this._nav.endTransition();\n return;\n }\n }\n\n try {\n // Page-level guard — only run here if the core router is NOT\n // already handling it (skipAutoBootstrap or external router).\n // When the outlet auto-bootstrapped, guards are registered in the\n // core router and running them again here would double-fire.\n if (def.beforeEnter && !this._guardsInCoreRouter) {\n const cached = this._cache.get(targetTabKey, cacheKey);\n const lcForGuard = cached?.lc ?? createPageLifecycle();\n const guardResult = await Promise.resolve(\n def.beforeEnter({ lc: lcForGuard, params, query: router.query.value }),\n );\n const parsed = _parseGuardResult(guardResult);\n if (!parsed.allow) {\n if (parsed.redirect) {\n // Clear any stale pending nav before redirecting so the\n // only pending nav after this is the one triggered by\n // the redirect itself (which is legitimate).\n this._pendingNav = null;\n elurRouter().replace(parsed.redirect);\n // Don't mark as cancelled — the redirect enqueued a\n // new pending nav via the effect that should process.\n } else {\n // Pure cancel (no redirect) — drop pending navs.\n transitionCancelled = true;\n }\n return;\n }\n }\n\n // Resolve entering page\n let enteringEl: HTMLElement;\n let isNewlyMounted = false;\n\n // Determine if this route should be cached\n const routeCacheOpt = this._routeCachePolicies.get(def.path);\n const routeCacheDisabled = routeCacheOpt === false;\n const shouldCache = this._enableCache && !routeCacheDisabled;\n\n const cached = shouldCache\n ? this._cache.get(targetTabKey, cacheKey)\n : undefined;\n\n if (cached) {\n _resetCachedPageState(cached.pageEl);\n // Remove ion-page-hidden (added by _hideInactivePages when the\n // page was cached) so Ionic's commit() can show it. Keep\n // ion-page-invisible to prevent flash before the transition.\n cached.pageEl.classList.remove(\"ion-page-hidden\");\n cached.pageEl.style.removeProperty(\"display\");\n cached.pageEl.classList.add(\"ion-page-invisible\");\n enteringEl = cached.pageEl;\n } else {\n const { pageEl, lc } = this._createPageEl();\n const cleanup = this._mountComponent(pageEl, def, { lc, params, query });\n if (shouldCache) {\n const now = Date.now();\n const routePolicy = (routeCacheOpt && typeof routeCacheOpt === \"object\")\n ? routeCacheOpt\n : null;\n const effectivePolicy = routePolicy ?? this._cachePolicy;\n const ttl = effectivePolicy.ttl;\n const view: CachedView = {\n pageEl, lc, cleanup, cacheKey,\n lastAccessed: now,\n createdAt: now,\n routePath: def.path,\n routePolicy,\n ttlTimer: ttl ? setTimeout(() => {\n // TTL expired — evict if still in cache and not active\n if (this._activeCacheKey !== cacheKey || this._activeTabKey !== targetTabKey) {\n this._cache.delete(targetTabKey, cacheKey);\n try { cleanup(); } catch { /* ignore */ }\n if (pageEl.parentElement) pageEl.remove();\n }\n }, ttl) : null,\n };\n this._cache.set(targetTabKey, cacheKey, view);\n // Enforce max after inserting\n if (this._cachePolicy.max) {\n this._cache.enforcePolicy(targetTabKey, this._cachePolicy, this._routeCachePolicies);\n }\n } else {\n // cache:false — track cleanup so it runs when the page leaves.\n _uncachedCleanups.set(pageEl, cleanup);\n }\n enteringEl = pageEl;\n isNewlyMounted = true;\n }\n\n if (!outletEl.contains(enteringEl)) {\n outletEl.appendChild(enteringEl);\n }\n\n const direction = this._stacks.apply(targetPath, intent);\n const leavingEl = this._activePageEl;\n\n if (!leavingEl || leavingEl === enteringEl) {\n this._activePageEl = enteringEl;\n this._activeCacheKey = cacheKey;\n this._activeTabKey = targetTabKey;\n\n const finalEl = enteringEl;\n _dispatchIonicLifecycle(finalEl, \"ionViewWillEnter\");\n\n if (isNewlyMounted) {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n finalEl.classList.remove(\"ion-page-invisible\");\n this._hideInactivePages(finalEl);\n _dispatchIonicLifecycle(finalEl, \"ionViewDidEnter\");\n });\n });\n } else {\n finalEl.classList.remove(\"ion-page-invisible\");\n this._hideInactivePages(finalEl);\n _dispatchIonicLifecycle(finalEl, \"ionViewDidEnter\");\n }\n return;\n }\n\n const animationBuilder = (intent.animation ?? this._defaultAnimation) as\n | undefined | unknown;\n\n // duration:0 is what Ionic uses for \"no animation\" navigations.\n // Empirically, this path skips lifecycle event dispatch — so we\n // synthesize them manually around the commit() call.\n const isDuration0 = direction === \"root\" || direction === \"none\";\n\n const commitOpts: any = {\n duration: isDuration0 ? 0 : undefined,\n direction:\n direction === \"back\" ? \"back\"\n : direction === \"forward\" ? \"forward\"\n : undefined,\n showGoBack: direction === \"forward\",\n };\n if (animationBuilder) commitOpts.animationBuilder = animationBuilder;\n\n // Ionic docs: WillLeave fires BEFORE WillEnter.\n // Animated paths (duration > 0): commit() handles dispatch.\n if (isDuration0) {\n _dispatchIonicLifecycle(leavingEl, \"ionViewWillLeave\");\n _dispatchIonicLifecycle(enteringEl, \"ionViewWillEnter\");\n }\n\n await (outletEl as any).commit(enteringEl, leavingEl, commitOpts);\n\n this._activePageEl = enteringEl;\n this._activeCacheKey = cacheKey;\n this._activeTabKey = targetTabKey;\n\n // Remove the invisible/hidden classes that were added when reusing\n // a cached page. Ionic's commit() may remove them during animation,\n // but with reduced-motion or duration-0 transitions they can persist.\n enteringEl.classList.remove(\"ion-page-invisible\");\n enteringEl.classList.remove(\"ion-page-hidden\");\n enteringEl.style.removeProperty(\"display\");\n // Ensure the entering page has a higher z-index than the leaving\n // page. Ionic's commit() with reduced-motion may not swap z-index.\n const leavingZ = leavingEl ? parseInt(getComputedStyle(leavingEl).zIndex, 10) || 0 : 0;\n enteringEl.style.zIndex = String(leavingZ + 1);\n this._hideInactivePages(enteringEl);\n\n // Ionic docs: DidLeave fires AFTER DidEnter (after the new\n // page has fully transitioned in).\n if (isDuration0) {\n _dispatchIonicLifecycle(enteringEl, \"ionViewDidEnter\");\n _dispatchIonicLifecycle(leavingEl, \"ionViewDidLeave\");\n }\n\n // If the leaving page is not in the cache (either because cache is\n // disabled OR because it was invalidated while active), run its\n // cleanup before removing the DOM node, otherwise effects/lifecycle\n // watchers leak.\n const leavingCleanup = _uncachedCleanups.get(leavingEl);\n if (leavingCleanup && leavingEl.parentElement === outletEl) {\n leavingCleanup();\n _uncachedCleanups.delete(leavingEl);\n leavingEl.remove();\n } else if (!this._enableCache && leavingEl.parentElement === outletEl) {\n leavingEl.remove();\n }\n } finally {\n this._isTransitioning = false;\n if (this._nav) this._nav.endTransition();\n\n // Determine the effective direction for afterNav hooks\n const effectiveDirection = this._stacks.apply(targetPath, intent);\n\n // Run afterNav and tabChange hooks\n if (this._nav && !transitionCancelled) {\n this._nav.runAfterNav(targetPath, effectiveDirection);\n this._nav.runTabChangeIfNeeded(targetPath);\n this._nav.updateCanGoBack();\n }\n\n // Only process pending nav if the current transition succeeded.\n // A cancelled/failed transition (guard reject, route not found)\n // must NOT blindly replay a stale pending nav — the router state\n // may have already moved (e.g. redirect) and the pending path\n // could be inconsistent with the new current.\n const pending = this._nav ? this._nav.consumePendingNav() : this._pendingNav;\n if (!this._nav) this._pendingNav = null;\n\n if (pending && !transitionCancelled) {\n // Re-validate against current router state before processing.\n const currentRouter = elurRouter();\n if (pending.path === currentRouter.current.value) {\n void this._transitionTo(pending.path, pending.intent);\n }\n } else if (transitionCancelled && this._nav) {\n this._nav.clearPendingNav();\n }\n }\n }\n\n override render(): ElurTemplate {\n const self = this;\n return {\n __isElurTemplate: true as const,\n\n mount(container: Element | string) {\n const el = typeof container === \"string\"\n ? document.querySelector(container)!\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n\n _render(parent: Node, before: Node | null): () => void {\n const outletEl = document.createElement(\"ion-router-outlet\");\n self._outletEl = outletEl;\n\n (outletEl as any).delegate = {\n attachViewToDom: (\n container: HTMLElement,\n component: HTMLElement,\n ): HTMLElement => {\n if (component && !container.contains(component)) {\n container.appendChild(component);\n }\n return component;\n },\n removeViewFromDom: async (): Promise<void> => { /* no-op */ },\n };\n\n parent.insertBefore(outletEl, before);\n\n const router: Router = elurRouter();\n let lastSeenNavKey: string | null = null;\n let initialDeferred = false;\n\n self._routeEffectDisposer = effect(() => {\n const path = router.current.value;\n const intent = router.intent.value;\n // Observe query so query-only navigation (same path, different\n // query) triggers a transition. The nav key combines path +\n // serialized query; reading router.query.value subscribes\n // the effect to query changes.\n const query = router.query.value;\n const queryStr = Object.keys(query).length > 0\n ? \"?\" + Object.keys(query).sort().map(\n (k) => `${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`,\n ).join(\"&\")\n : \"\";\n const navKey = path + queryStr;\n\n if (!initialDeferred) {\n initialDeferred = true;\n queueMicrotask(() => {\n const settledPath = router.current.value;\n const settledIntent = router.intent.value;\n const settledQuery = router.query.value;\n const settledQueryStr = Object.keys(settledQuery).length > 0\n ? \"?\" + Object.keys(settledQuery).sort().map(\n (k) => `${encodeURIComponent(k)}=${encodeURIComponent(settledQuery[k])}`,\n ).join(\"&\")\n : \"\";\n lastSeenNavKey = settledPath + settledQueryStr;\n void self._transitionTo(settledPath, settledIntent);\n });\n return;\n }\n\n if (navKey === lastSeenNavKey) {\n // The router sets intent.value before current.value,\n // which can trigger the effect with a stale path.\n // If the intent indicates a pop (hashchange back),\n // defer to a microtask to read the settled path.\n if (intent.action === \"pop\" && intent.direction === \"none\") {\n queueMicrotask(() => {\n const settledPath = router.current.value;\n const settledQuery = router.query.value;\n const settledQueryStr = Object.keys(settledQuery).length > 0\n ? \"?\" + Object.keys(settledQuery).sort().map(\n (k) => `${encodeURIComponent(k)}=${encodeURIComponent(settledQuery[k])}`,\n ).join(\"&\")\n : \"\";\n const settledNavKey = settledPath + settledQueryStr;\n if (settledNavKey === lastSeenNavKey) return;\n lastSeenNavKey = settledNavKey;\n void self._transitionTo(settledPath, router.intent.value);\n });\n }\n return;\n }\n lastSeenNavKey = navKey;\n void self._transitionTo(path, intent);\n });\n\n return () => {\n self._routeEffectDisposer?.();\n self._routeEffectDisposer = null;\n for (const { view } of self._cache.all()) {\n view.cleanup();\n if (view.pageEl.parentElement) view.pageEl.remove();\n }\n self._cache.clear();\n // cache:false active page — run its cleanup too.\n if (self._activePageEl) {\n const activeCleanup = _uncachedCleanups.get(self._activePageEl);\n if (activeCleanup) {\n activeCleanup();\n _uncachedCleanups.delete(self._activePageEl);\n }\n }\n self._activePageEl = null;\n self._activeCacheKey = null;\n self._activeTabKey = null;\n self._outletEl = null;\n outletEl.remove();\n };\n },\n };\n }\n\n invalidateCache(\n routePath: string,\n params?: Record<string, string>,\n tabKey?: string,\n query?: Record<string, string>,\n ): void {\n const key = (params || query)\n ? _buildCacheKey(routePath, params ?? {}, query)\n : routePath;\n const targetTabKey = tabKey ?? this._stacks.keyForPath(routePath);\n const cached = this._cache.get(targetTabKey, key);\n if (!cached) return;\n\n if (cached.pageEl === this._activePageEl) {\n // Active page: do NOT dispose reactivity or remove DOM — that would\n // leave a visible-but-dead view. Just drop the cache entry so the\n // next visit remounts a fresh instance. Move the cleanup to the\n // uncached tracker so the normal transition path disposes it when\n // the user navigates away.\n _uncachedCleanups.set(cached.pageEl, cached.cleanup);\n this._cache.delete(targetTabKey, key);\n this._activeCacheKey = null;\n return;\n }\n\n cached.cleanup();\n if (cached.pageEl.parentElement) {\n cached.pageEl.remove();\n }\n this._cache.delete(targetTabKey, key);\n }\n\n clearCache(): void {\n const entries: Array<{ tabKey: string; cacheKey: string; view: CachedView }> = [];\n for (const e of this._cache.all()) entries.push(e);\n for (const { tabKey, cacheKey, view } of entries) {\n if (view.pageEl === this._activePageEl) continue;\n view.cleanup();\n if (view.pageEl.parentElement) view.pageEl.remove();\n this._cache.delete(tabKey, cacheKey);\n }\n }\n\n /**\n * Clear all cached pages for a specific tab. Useful when leaving a tab\n * permanently or for memory management.\n */\n clearTabCache(tabKey: string): void {\n const entries: Array<{ tabKey: string; cacheKey: string; view: CachedView }> = [];\n for (const e of this._cache.all()) {\n if (e.tabKey === tabKey) entries.push(e);\n }\n for (const { tabKey, cacheKey, view } of entries) {\n if (view.pageEl === this._activePageEl) continue;\n view.cleanup();\n if (view.pageEl.parentElement) view.pageEl.remove();\n this._cache.delete(tabKey, cacheKey);\n }\n }\n\n /** The NavigationManager used by this outlet (if any). */\n get navigation(): NavigationManager | null {\n return this._nav;\n }\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"elur-ionic.js","names":[],"sources":["../../src/lifecycle.ts","../../src/IonRouterOutlet.ts"],"sourcesContent":["/**\n * @elurjs/ionic / lifecycle.ts — v2\n *\n * Page-lifecycle plumbing identical to v1. The hooks (ionViewWillEnter,\n * ionViewDidEnter, ionViewWillLeave, ionViewDidLeave) still come from the\n * native <ion-page> element events — IonRouterOutlet attaches the listeners\n * when it creates each page.\n *\n * Nothing here needed to change for the single-router refactor.\n */\n\nimport { signal, watch } from \"@elurjs/core\";\nimport type { Signal } from \"@elurjs/core\";\nimport { ElurComponent } from \"@elurjs/core\";\n\nexport interface PageLifecycle {\n willEnter: Signal<number>;\n didEnter: Signal<number>;\n willLeave: Signal<number>;\n didLeave: Signal<number>;\n}\n\nexport function createPageLifecycle(): PageLifecycle {\n return {\n willEnter: signal(0),\n didEnter: signal(0),\n willLeave: signal(0),\n didLeave: signal(0),\n };\n}\n\n/**\n * Internal symbol used by IonRouterOutlet's mount adapter to connect the\n * Ionic page lifecycle to an IonPage instance WITHOUT relying on the\n * subclass calling `super.onInit()`. The outlet calls this method directly\n * and stores the returned disposer so the watches are torn down with the view.\n */\nexport const _connectIonicLifecycle = Symbol(\"elur-ionic:connectLifecycle\");\n\n/**\n * Class-based pages. Subclass and implement any of the hooks.\n *\n * class HomePage extends IonPage {\n * constructor(lc: PageLifecycle) { super(lc); }\n * ionViewWillEnter() { this.refreshData(); }\n * render() { return html`...`; }\n * }\n *\n * Lifecycle wiring no longer depends on `onInit()` / `super.onInit()`:\n * the router outlet calls the symbol-based `_connectIonicLifecycle` method\n * directly and disposes the watches when the view is cleaned up. Subclasses\n * may override `onInit` freely for their own setup without calling super.\n */\nexport abstract class IonPage extends ElurComponent {\n private __lc: PageLifecycle;\n private __lifecycleDisposers: Array<() => void> = [];\n\n constructor(lc: PageLifecycle) {\n super();\n this.__lc = lc;\n }\n\n /**\n * Connects the Ionic view lifecycle signals to this page's hooks.\n * Called once by the router outlet's mount adapter. Returns a disposer\n * that tears down all lifecycle watches.\n *\n * Idempotent: calling it more than once is a no-op after the first call.\n */\n public [_connectIonicLifecycle](): () => void {\n if (this.__lifecycleDisposers.length > 0) return () => this._disposeLifecycle();\n const lc = this.__lc;\n if (this.ionViewWillEnter) {\n this.__lifecycleDisposers.push(watch(lc.willEnter, this.ionViewWillEnter.bind(this)));\n }\n if (this.ionViewDidEnter) {\n this.__lifecycleDisposers.push(watch(lc.didEnter, this.ionViewDidEnter.bind(this)));\n }\n if (this.ionViewWillLeave) {\n this.__lifecycleDisposers.push(watch(lc.willLeave, this.ionViewWillLeave.bind(this)));\n }\n if (this.ionViewDidLeave) {\n this.__lifecycleDisposers.push(watch(lc.didLeave, this.ionViewDidLeave.bind(this)));\n }\n return () => this._disposeLifecycle();\n }\n\n private _disposeLifecycle(): void {\n for (const d of this.__lifecycleDisposers) d();\n this.__lifecycleDisposers = [];\n }\n\n ionViewWillEnter?(): void;\n ionViewDidEnter?(): void;\n ionViewWillLeave?(): void;\n ionViewDidLeave?(): void;\n}\n\nexport function useIonViewWillEnter(lc: PageLifecycle, fn: () => void): () => void {\n return watch(lc.willEnter, fn);\n}\n\nexport function useIonViewDidEnter(lc: PageLifecycle, fn: () => void): () => void {\n return watch(lc.didEnter, fn);\n}\n\nexport function useIonViewWillLeave(lc: PageLifecycle, fn: () => void): () => void {\n return watch(lc.willLeave, fn);\n}\n\nexport function useIonViewDidLeave(lc: PageLifecycle, fn: () => void): () => void {\n return watch(lc.didLeave, fn);\n}","/**\n * @elurjs/ionic / IonRouterOutlet.ts — v2.5\n *\n * Architecture: \"core API + ion-router-outlet motor\" (with auto-bootstrap)\n *\n * Changes vs v2.4:\n *\n * (E) Manual lifecycle dispatch on duration-0 transitions.\n * EMPIRICAL FINDING: <ion-router-outlet>.commit() with `duration: 0`\n * (used for direction: \"none\" and \"root\") does NOT fire the lifecycle\n * events. We confirmed this with `replace(\"/login\")` after a logout —\n * the leaving page's `ionViewWillLeave` never ran.\n *\n * The fix: when `direction` is \"none\" or \"root\", we synthesize the\n * events ourselves around the commit() call, in the order Ionic\n * documents:\n * 1. WillLeave on the leaving page (BEFORE commit starts)\n * 2. WillEnter on the entering page (BEFORE commit starts)\n * 3. await commit() (instantaneous when duration=0)\n * 4. DidEnter on the entering page (AFTER commit resolves)\n * 5. DidLeave on the leaving page (AFTER DidEnter — Ionic docs)\n *\n * For animated transitions (\"forward\"/\"back\") we still rely on Ionic\n * to fire the events, since those go through the full animation path\n * where the events ARE wired correctly.\n *\n * Kept from earlier versions:\n * (A) Anti-flash on first mount.\n * (B) StackManager `back` recognition.\n * (C) _hideInactivePages defensive sweep after every transition.\n * (D) Manual lifecycle dispatch on first mount (no leaving page).\n *\n * Subclass note for IonPage users: if you override `onInit()` in a\n * subclass, you MUST call `super.onInit()` first. The base IonPage uses\n * onInit to wire `watch()` calls onto the lifecycle signals — without the\n * super call, your `ionViewWillEnter`/etc. methods never fire.\n */\n\nimport { ElurComponent, effect } from \"@elurjs/core\";\nimport type { ElurTemplate } from \"@elurjs/core\";\nimport {\n elurRouter,\n createRouter,\n _hasActiveRouter,\n type Router,\n type RouteRecord,\n type NavigationGuard,\n type NavigationIntent,\n} from \"@elurjs/core\";\nimport { createPageLifecycle, _connectIonicLifecycle, type PageLifecycle } from \"./lifecycle\";\nimport { NavigationManager, StackManager } from \"./navigation\";\n\nexport type GuardResult =\n | boolean\n | string\n | { redirect: string }\n | void\n | undefined;\n\nexport interface PageContext {\n lc: PageLifecycle;\n params: Record<string, string>;\n query: Record<string, string>;\n}\n\nexport interface RouteDefinition {\n path: string;\n component: (ctx: PageContext) => ElurComponent | ElurTemplate;\n beforeEnter?: (ctx: PageContext) => GuardResult | Promise<GuardResult>;\n /**\n * Per-route cache policy override. When set, takes precedence over the\n * outlet-level policy for this route.\n *\n * - `true` — use the outlet's default cache policy\n * - `false` — never cache this route (cleanup on leave)\n * - `{ max: N }` — cache at most N instances of this route\n * - `{ ttl: N }` — cache entries expire after N milliseconds\n * - `{ max: N, ttl: N }` — both bounds\n */\n cache?: boolean | CachePolicy;\n}\n\n/**\n * Bounded cache policy for cached pages.\n *\n * - `max`: maximum number of cached entries per tab. When exceeded, the\n * least-recently-used entry is evicted. Default: unlimited.\n * - `ttl`: time-to-live in milliseconds. Entries older than this are\n * evicted on next access or by a background timer. Default: unlimited.\n * - `strategy`: \"lru\" (default) evicts the least-recently-used entry when\n * `max` is reached. \"fifo\" evicts the oldest entry.\n */\nexport interface CachePolicy {\n max?: number;\n ttl?: number;\n strategy?: \"lru\" | \"fifo\";\n}\n\nexport interface IonRouterOutletOptions {\n /** Enable/disable caching globally. Set to false to disable all caching. */\n cache?: boolean;\n /**\n * Bounded cache policy applied to all cached routes (unless overridden\n * per-route via `RouteDefinition.cache`).\n */\n cachePolicy?: CachePolicy;\n defaultAnimation?: unknown;\n tabs?: string[];\n skipAutoBootstrap?: boolean;\n /**\n * Optional NavigationManager for centralized navigation state, hooks,\n * and programmatic tab switching. When provided, the outlet delegates\n * stack management and transition state to the manager.\n */\n navigation?: NavigationManager;\n}\n\nfunction adaptGuardForCore(\n routePath: string,\n pageGuard: (ctx: PageContext) => GuardResult | Promise<GuardResult>,\n): NavigationGuard {\n return (to: string, _from: string) => {\n const params = extractParamsFromPath(routePath, to);\n const query = extractQueryFromPath(to);\n const lc = createPageLifecycle();\n return pageGuard({ lc, params, query }) as any;\n };\n}\n\nfunction extractQueryFromPath(path: string): Record<string, string> {\n const qIndex = path.indexOf(\"?\");\n if (qIndex === -1) return {};\n const search = path.slice(qIndex + 1);\n const result: Record<string, string> = {};\n for (const pair of search.split(\"&\")) {\n if (!pair) continue;\n const eq = pair.indexOf(\"=\");\n if (eq === -1) {\n result[pair] = \"\";\n } else {\n const k = pair.slice(0, eq);\n try {\n result[k] = decodeURIComponent(pair.slice(eq + 1));\n } catch {\n result[k] = pair.slice(eq + 1);\n }\n }\n }\n return result;\n}\n\nfunction extractParamsFromPath(pattern: string, actual: string): Record<string, string> {\n const patternParts = pattern.split(\"/\").filter(Boolean);\n const actualParts = actual.split(\"/\").filter(Boolean);\n const params: Record<string, string> = {};\n for (let i = 0; i < patternParts.length && i < actualParts.length; i++) {\n const p = patternParts[i];\n if (p.startsWith(\":\")) {\n try {\n params[p.slice(1)] = decodeURIComponent(actualParts[i] ?? \"\");\n } catch {\n params[p.slice(1)] = actualParts[i] ?? \"\";\n }\n }\n }\n return params;\n}\n\nfunction _parseGuardResult(r: GuardResult): { allow: boolean; redirect?: string } {\n if (r === false) return { allow: false };\n if (r === true || r === undefined || r === null) return { allow: true };\n if (typeof r === \"string\") return { allow: false, redirect: r };\n if (typeof r === \"object\" && \"redirect\" in r && typeof r.redirect === \"string\") {\n return { allow: false, redirect: r.redirect };\n }\n return { allow: true };\n}\n\nfunction buildCoreRouteRecords(routes: RouteDefinition[]): RouteRecord[] {\n return routes.map((r): RouteRecord => ({\n path: r.path,\n component: undefined,\n beforeEnter: r.beforeEnter\n ? adaptGuardForCore(r.path, r.beforeEnter)\n : undefined,\n }));\n}\n\ninterface CachedView {\n pageEl: HTMLElement;\n lc: PageLifecycle;\n cleanup: () => void;\n cacheKey: string;\n /** Timestamp of last access (for LRU). */\n lastAccessed: number;\n /** Timestamp of creation (for TTL). */\n createdAt: number;\n /** Route path (for route-level policy lookup). */\n routePath: string;\n /** Per-route policy override (null = use outlet default). */\n routePolicy: CachePolicy | false | null;\n /** TTL timer handle (if TTL is set). */\n ttlTimer: ReturnType<typeof setTimeout> | null;\n}\n\nconst IONIC_STATE_CLASSES_TO_RESET = [\"ion-page-hidden\", \"can-go-back\"];\n\nfunction _resetCachedPageState(el: HTMLElement): void {\n el.classList.remove(...IONIC_STATE_CLASSES_TO_RESET);\n el.style.removeProperty(\"display\");\n el.style.removeProperty(\"visibility\");\n el.style.removeProperty(\"opacity\");\n el.style.removeProperty(\"transform\");\n el.style.removeProperty(\"animation\");\n el.style.removeProperty(\"transition\");\n el.style.removeProperty(\"pointer-events\");\n el.style.removeProperty(\"z-index\");\n}\n\nfunction _hasDynamicSegments(path: string): boolean {\n return path.includes(\":\");\n}\n\nfunction _buildCacheKey(\n routePath: string,\n params: Record<string, string>,\n query?: Record<string, string>,\n): string {\n const parts: string[] = [];\n\n // Params segment — only when the route has dynamic segments.\n if (_hasDynamicSegments(routePath) && params && Object.keys(params).length > 0) {\n parts.push(\n Object.keys(params)\n .sort()\n .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)\n .join(\"&\"),\n );\n }\n\n // Query segment — encoded to avoid collisions (e.g. x=1&y=2 vs x=1y=2).\n if (query && Object.keys(query).length > 0) {\n parts.push(\n Object.keys(query)\n .sort()\n .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`)\n .join(\"&\"),\n );\n }\n\n if (parts.length === 0) return routePath;\n return `${routePath}?${parts.join(\"&\")}`;\n}\n\n/**\n * Synthesize an Ionic page-lifecycle event on a pageEl. Used when commit()\n * either won't run at all (first mount) or runs with duration:0 (which\n * empirically does not fire lifecycle events in Ionic).\n */\nfunction _dispatchIonicLifecycle(\n pageEl: HTMLElement,\n name: \"ionViewWillEnter\" | \"ionViewDidEnter\" | \"ionViewWillLeave\" | \"ionViewDidLeave\",\n): void {\n pageEl.dispatchEvent(new CustomEvent(name, {\n bubbles: true,\n cancelable: false,\n composed: true,\n }));\n}\n\n/**\n * Tracks cleanup functions for pages that are NOT in the cache (cache:false\n * mode). Without this, effects/lifecycle watchers leak when a non-cached\n * page is removed from the DOM.\n */\nconst _uncachedCleanups = new WeakMap<HTMLElement, () => void>();\n\nclass CacheRegistry {\n private _byTab = new Map<string, Map<string, CachedView>>();\n /** Eviction callback — called when an entry is evicted by policy. */\n private _onEvict: ((view: CachedView) => void) | null = null;\n\n /** Set the eviction callback for policy-driven evictions. */\n onEvict(cb: (view: CachedView) => void): void {\n this._onEvict = cb;\n }\n\n /**\n * @internal — Debug snapshot of all cached views for\n * `@elurjs/ionic/devtools`. Plain data, computed on demand.\n */\n _debugSnapshot(): Array<{\n tab: string;\n key: string;\n routePath: string;\n createdAt: number;\n lastAccessed: number;\n }> {\n const out: Array<{\n tab: string;\n key: string;\n routePath: string;\n createdAt: number;\n lastAccessed: number;\n }> = [];\n for (const [tab, views] of this._byTab) {\n for (const [key, view] of views) {\n out.push({\n tab,\n key,\n routePath: view.routePath,\n createdAt: view.createdAt,\n lastAccessed: view.lastAccessed,\n });\n }\n }\n return out;\n }\n\n get(tabKey: string, cacheKey: string): CachedView | undefined {\n const view = this._byTab.get(tabKey)?.get(cacheKey);\n if (view) {\n // Update LRU timestamp on access\n view.lastAccessed = Date.now();\n }\n return view;\n }\n\n set(tabKey: string, cacheKey: string, view: CachedView): void {\n let map = this._byTab.get(tabKey);\n if (!map) { map = new Map(); this._byTab.set(tabKey, map); }\n map.set(cacheKey, view);\n }\n\n delete(tabKey: string, cacheKey: string): void {\n const map = this._byTab.get(tabKey);\n if (!map) return;\n const view = map.get(cacheKey);\n if (view?.ttlTimer) {\n clearTimeout(view.ttlTimer);\n view.ttlTimer = null;\n }\n map.delete(cacheKey);\n }\n\n /**\n * Enforce cache policy for a specific tab. Evicts entries that exceed\n * `max` or have expired by `ttl`. Returns the number of evicted entries.\n */\n enforcePolicy(\n tabKey: string,\n policy: CachePolicy,\n routePolicies: Map<string, boolean | CachePolicy>,\n ): number {\n const map = this._byTab.get(tabKey);\n if (!map) return 0;\n\n let evicted = 0;\n\n // 1. TTL expiry — check all entries\n const now = Date.now();\n for (const [cacheKey, view] of map) {\n const effectivePolicy = this._getEffectivePolicy(view.routePath, routePolicies, policy);\n if (effectivePolicy === false) continue; // not cached\n const ttl = effectivePolicy.ttl;\n if (ttl && now - view.createdAt > ttl) {\n this._evict(tabKey, cacheKey, view);\n evicted++;\n }\n }\n\n // 2. Max enforcement — evict LRU/FIFO entries until under max\n const max = policy.max;\n if (max && map.size > max) {\n const strategy = policy.strategy ?? \"lru\";\n const entries = [...map.entries()];\n // Sort by eviction priority\n if (strategy === \"lru\") {\n entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed);\n } else {\n // FIFO — oldest creation first\n entries.sort((a, b) => a[1].createdAt - b[1].createdAt);\n }\n const toEvict = map.size - max;\n for (let i = 0; i < toEvict; i++) {\n const [cacheKey, view] = entries[i];\n this._evict(tabKey, cacheKey, view);\n evicted++;\n }\n }\n\n return evicted;\n }\n\n private _getEffectivePolicy(\n routePath: string,\n routePolicies: Map<string, boolean | CachePolicy>,\n defaultPolicy: CachePolicy,\n ): CachePolicy | false {\n const routeOverride = routePolicies.get(routePath);\n if (routeOverride === false) return false;\n if (routeOverride === true) return defaultPolicy;\n if (routeOverride && typeof routeOverride === \"object\") return routeOverride;\n return defaultPolicy;\n }\n\n private _evict(tabKey: string, cacheKey: string, view: CachedView): void {\n if (view.ttlTimer) {\n clearTimeout(view.ttlTimer);\n view.ttlTimer = null;\n }\n this._byTab.get(tabKey)?.delete(cacheKey);\n // Run cleanup + remove DOM\n this._onEvict?.(view);\n }\n\n *all(): Generator<{ tabKey: string; cacheKey: string; view: CachedView }> {\n for (const [tabKey, map] of this._byTab.entries()) {\n for (const [cacheKey, view] of map.entries()) {\n yield { tabKey, cacheKey, view };\n }\n }\n }\n\n clear(): void {\n for (const map of this._byTab.values()) {\n for (const view of map.values()) {\n if (view.ttlTimer) clearTimeout(view.ttlTimer);\n }\n }\n this._byTab.clear();\n }\n}\n\nexport function IonBackButton(defaultHref: string = \"/\"): ElurTemplate {\n return {\n __isElurTemplate: true as const,\n mount(container: Element | string) {\n const el = typeof container === \"string\"\n ? document.querySelector(container)!\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n // Wrap in <ion-buttons slot=\"start\"> for correct layout.\n // Without ion-buttons, ion-back-button has no flex constraints\n // and can expand to fill the toolbar.\n const buttons = document.createElement(\"ion-buttons\");\n buttons.setAttribute(\"slot\", \"start\");\n\n const btn = document.createElement(\"ion-back-button\");\n btn.setAttribute(\"default-href\", defaultHref);\n const onClick = (ev: Event) => {\n ev.preventDefault();\n ev.stopPropagation();\n const router = elurRouter();\n const nav = _activeNavigationManager;\n const canGoBack = nav ? nav.canGoBack.value : router.canGoBack.value;\n if (canGoBack) {\n router.back();\n } else {\n router.replace(defaultHref);\n }\n };\n btn.addEventListener(\"click\", onClick);\n buttons.appendChild(btn);\n parent.insertBefore(buttons, before);\n return () => {\n btn.removeEventListener(\"click\", onClick);\n buttons.remove();\n };\n },\n };\n}\n\n/**\n * Internal registry for the active NavigationManager, set by IonRouterOutlet\n * when it has one. This allows IonBackButton to use per-tab canGoBack\n * without prop drilling.\n */\nlet _activeNavigationManager: NavigationManager | null = null;\n\n/**\n * @internal — Global registry of live IonRouterOutlet instances for\n * devtools. Shared via `Symbol.for` so it survives module duplication.\n */\nconst _outletRegistryKey = Symbol.for(\"@elurjs/ionic/outlets\");\n\nfunction _registerOutlet(instance: IonRouterOutlet): void {\n const g = globalThis as Record<PropertyKey, unknown>;\n let set = g[_outletRegistryKey] as Set<IonRouterOutlet> | undefined;\n if (!set) {\n set = new Set();\n g[_outletRegistryKey] = set;\n }\n set.add(instance);\n}\n\nexport class IonRouterOutlet extends ElurComponent {\n private _routesByPath = new Map<string, RouteDefinition>();\n private _wildcardRoute: RouteDefinition | null = null;\n private _enableCache: boolean;\n private _cachePolicy: CachePolicy;\n private _routeCachePolicies = new Map<string, boolean | CachePolicy>();\n private _defaultAnimation: unknown;\n\n private _stacks: StackManager;\n private _cache = new CacheRegistry();\n private _nav: NavigationManager | null;\n\n private _activePageEl: HTMLElement | null = null;\n private _activeCacheKey: string | null = null;\n private _activeTabKey: string | null = null;\n\n private _outletEl: HTMLElement | null = null;\n private _routeEffectDisposer: (() => void) | null = null;\n\n private _isTransitioning = false;\n private _pendingNav: { path: string; intent: NavigationIntent } | null = null;\n // True when the outlet auto-bootstrapped the core router with page guards\n // registered. In that case _transitionTo must NOT re-run beforeEnter\n // (the core router already did) — otherwise guards fire twice.\n private _guardsInCoreRouter: boolean;\n\n constructor(routes: RouteDefinition[], opts: IonRouterOutletOptions = {}) {\n super();\n this._enableCache = opts.cache ?? true;\n this._cachePolicy = opts.cachePolicy ?? {};\n this._defaultAnimation = opts.defaultAnimation;\n\n // Use provided NavigationManager or create an internal one\n if (opts.navigation) {\n this._nav = opts.navigation;\n this._stacks = opts.navigation.stacks;\n _activeNavigationManager = opts.navigation;\n } else {\n this._nav = null;\n this._stacks = new StackManager(opts.tabs);\n }\n\n for (const r of routes) {\n if (r.path === \"*\") {\n if (this._wildcardRoute) {\n console.warn(\n `[elur-ionic] Duplicate wildcard route \"*\" — the previous ` +\n `fallback will be overwritten. Define only one \"*\" route.`,\n );\n }\n this._wildcardRoute = r;\n continue;\n }\n if (this._routesByPath.has(r.path)) {\n console.warn(\n `[elur-ionic] Duplicate route path \"${r.path}\" — the previous ` +\n `definition will be overwritten. Each route path must be unique.`,\n );\n }\n this._routesByPath.set(r.path, r);\n // Collect per-route cache policy overrides\n if (r.cache !== undefined) {\n this._routeCachePolicies.set(r.path, r.cache);\n }\n }\n\n // Set up eviction callback — runs cleanup + removes DOM\n this._cache.onEvict((view) => {\n try {\n view.cleanup();\n } catch { /* ignore */ }\n if (view.pageEl.parentElement) {\n view.pageEl.remove();\n }\n });\n\n this._guardsInCoreRouter = !opts.skipAutoBootstrap && !_hasActiveRouter();\n if (this._guardsInCoreRouter) {\n createRouter(buildCoreRouteRecords(routes));\n }\n\n // Register cache invalidation handlers on the NavigationManager\n if (this._nav) {\n for (const r of routes) {\n if (r.path === \"*\") continue;\n this._nav.registerInvalidationHandler(r.path, (params) => {\n if (params) {\n this.invalidateCache(r.path, params);\n } else {\n // Invalidate all instances of this route\n for (const entry of this._cache.all()) {\n if (entry.view.routePath === r.path) {\n this.invalidateCache(\n r.path,\n undefined,\n entry.tabKey,\n );\n }\n }\n }\n });\n }\n }\n\n _registerOutlet(this);\n }\n\n private _resolveRouteDefinition(currentPath: string): {\n def: RouteDefinition;\n params: Record<string, string>;\n } | null {\n const router = elurRouter();\n const resolved = router.resolve(currentPath);\n if (!resolved.matched || !resolved.route) return null;\n const def = this._routesByPath.get(resolved.route.path);\n if (def) return { def, params: resolved.params };\n // Fallback to the wildcard route (if any) when the core router matched\n // a \"*\" route that the outlet excluded from _routesByPath.\n if (this._wildcardRoute) {\n return { def: this._wildcardRoute, params: resolved.params };\n }\n return null;\n }\n\n private _createPageEl(): { pageEl: HTMLElement; lc: PageLifecycle } {\n const pageEl = document.createElement(\"ion-page\");\n pageEl.classList.add(\"ion-page\");\n pageEl.classList.add(\"ion-page-invisible\");\n const lc = createPageLifecycle();\n pageEl.addEventListener(\"ionViewWillEnter\", () =>\n lc.willEnter.update((n) => n + 1));\n pageEl.addEventListener(\"ionViewDidEnter\", () =>\n lc.didEnter.update((n) => n + 1));\n pageEl.addEventListener(\"ionViewWillLeave\", () =>\n lc.willLeave.update((n) => n + 1));\n pageEl.addEventListener(\"ionViewDidLeave\", () =>\n lc.didLeave.update((n) => n + 1));\n return { pageEl, lc };\n }\n\n private _mountComponent(\n pageEl: HTMLElement,\n def: RouteDefinition,\n ctx: PageContext,\n ): () => void {\n const node = def.component(ctx);\n if (\"render\" in node && typeof (node as ElurComponent).render === \"function\") {\n const comp = node as ElurComponent;\n // Connect Ionic lifecycle via the symbol-based internal API so\n // the contract does NOT depend on subclasses calling super.onInit().\n // IonPage implements this symbol; other ElurComponents ignore it.\n let lifecycleDispose: (() => void) | null = null;\n if (_connectIonicLifecycle in comp) {\n lifecycleDispose = (comp as any)[_connectIonicLifecycle]();\n }\n comp.onInit?.();\n const renderCleanup = comp.render()._render(pageEl, null);\n const mountRet = comp.onMount?.();\n return () => {\n comp.onUnmount?.();\n if (typeof mountRet === \"function\") mountRet();\n renderCleanup();\n lifecycleDispose?.();\n };\n } else {\n return (node as ElurTemplate)._render(pageEl, null);\n }\n }\n\n private _hideInactivePages(activeEl: HTMLElement | null): void {\n const outletEl = this._outletEl;\n if (!outletEl) return;\n const children = Array.from(outletEl.children);\n for (const child of children) {\n if (!(child instanceof HTMLElement)) continue;\n if (child.tagName !== \"ION-PAGE\" && !child.classList.contains(\"ion-page\")) continue;\n if (child === activeEl) {\n child.classList.remove(\"ion-page-hidden\");\n } else {\n child.classList.add(\"ion-page-hidden\");\n }\n }\n }\n\n private async _transitionTo(\n targetPath: string,\n intent: NavigationIntent,\n ): Promise<void> {\n const outletEl = this._outletEl;\n if (!outletEl) return;\n\n const resolved = this._resolveRouteDefinition(targetPath);\n if (!resolved) return;\n\n const { def, params } = resolved;\n const router = elurRouter();\n const query = router.query.value;\n const cacheKey = _buildCacheKey(def.path, params, query);\n const targetTabKey = this._stacks.keyForPath(targetPath);\n\n if (this._isTransitioning) {\n // Preserve full intent metadata (direction, animation, action)\n // so the deferred navigation behaves identically to the original.\n if (this._nav) {\n this._nav.setPendingNav(targetPath, intent);\n } else {\n this._pendingNav = { path: targetPath, intent };\n }\n return;\n }\n\n if (cacheKey === this._activeCacheKey && targetTabKey === this._activeTabKey) return;\n this._isTransitioning = true;\n if (this._nav) this._nav.beginTransition();\n let transitionCancelled = false;\n\n // Run beforeNav hooks from NavigationManager\n if (this._nav) {\n const allowed = await this._nav.runBeforeNav(targetPath, intent);\n if (!allowed) {\n this._isTransitioning = false;\n this._nav.endTransition();\n return;\n }\n }\n\n try {\n // Page-level guard — only run here if the core router is NOT\n // already handling it (skipAutoBootstrap or external router).\n // When the outlet auto-bootstrapped, guards are registered in the\n // core router and running them again here would double-fire.\n if (def.beforeEnter && !this._guardsInCoreRouter) {\n const cached = this._cache.get(targetTabKey, cacheKey);\n const lcForGuard = cached?.lc ?? createPageLifecycle();\n const guardResult = await Promise.resolve(\n def.beforeEnter({ lc: lcForGuard, params, query: router.query.value }),\n );\n const parsed = _parseGuardResult(guardResult);\n if (!parsed.allow) {\n if (parsed.redirect) {\n // Clear any stale pending nav before redirecting so the\n // only pending nav after this is the one triggered by\n // the redirect itself (which is legitimate).\n this._pendingNav = null;\n elurRouter().replace(parsed.redirect);\n // Don't mark as cancelled — the redirect enqueued a\n // new pending nav via the effect that should process.\n } else {\n // Pure cancel (no redirect) — drop pending navs.\n transitionCancelled = true;\n }\n return;\n }\n }\n\n // Resolve entering page\n let enteringEl: HTMLElement;\n let isNewlyMounted = false;\n\n // Determine if this route should be cached\n const routeCacheOpt = this._routeCachePolicies.get(def.path);\n const routeCacheDisabled = routeCacheOpt === false;\n const shouldCache = this._enableCache && !routeCacheDisabled;\n\n const cached = shouldCache\n ? this._cache.get(targetTabKey, cacheKey)\n : undefined;\n\n if (cached) {\n _resetCachedPageState(cached.pageEl);\n // Remove ion-page-hidden (added by _hideInactivePages when the\n // page was cached) so Ionic's commit() can show it. Keep\n // ion-page-invisible to prevent flash before the transition.\n cached.pageEl.classList.remove(\"ion-page-hidden\");\n cached.pageEl.style.removeProperty(\"display\");\n cached.pageEl.classList.add(\"ion-page-invisible\");\n enteringEl = cached.pageEl;\n } else {\n const { pageEl, lc } = this._createPageEl();\n const cleanup = this._mountComponent(pageEl, def, { lc, params, query });\n if (shouldCache) {\n const now = Date.now();\n const routePolicy = (routeCacheOpt && typeof routeCacheOpt === \"object\")\n ? routeCacheOpt\n : null;\n const effectivePolicy = routePolicy ?? this._cachePolicy;\n const ttl = effectivePolicy.ttl;\n const view: CachedView = {\n pageEl, lc, cleanup, cacheKey,\n lastAccessed: now,\n createdAt: now,\n routePath: def.path,\n routePolicy,\n ttlTimer: ttl ? setTimeout(() => {\n // TTL expired — evict if still in cache and not active\n if (this._activeCacheKey !== cacheKey || this._activeTabKey !== targetTabKey) {\n this._cache.delete(targetTabKey, cacheKey);\n try { cleanup(); } catch { /* ignore */ }\n if (pageEl.parentElement) pageEl.remove();\n }\n }, ttl) : null,\n };\n this._cache.set(targetTabKey, cacheKey, view);\n // Enforce max after inserting\n if (this._cachePolicy.max) {\n this._cache.enforcePolicy(targetTabKey, this._cachePolicy, this._routeCachePolicies);\n }\n } else {\n // cache:false — track cleanup so it runs when the page leaves.\n _uncachedCleanups.set(pageEl, cleanup);\n }\n enteringEl = pageEl;\n isNewlyMounted = true;\n }\n\n if (!outletEl.contains(enteringEl)) {\n outletEl.appendChild(enteringEl);\n }\n\n const direction = this._stacks.apply(targetPath, intent);\n const leavingEl = this._activePageEl;\n\n if (!leavingEl || leavingEl === enteringEl) {\n this._activePageEl = enteringEl;\n this._activeCacheKey = cacheKey;\n this._activeTabKey = targetTabKey;\n\n const finalEl = enteringEl;\n _dispatchIonicLifecycle(finalEl, \"ionViewWillEnter\");\n\n if (isNewlyMounted) {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n finalEl.classList.remove(\"ion-page-invisible\");\n this._hideInactivePages(finalEl);\n _dispatchIonicLifecycle(finalEl, \"ionViewDidEnter\");\n });\n });\n } else {\n finalEl.classList.remove(\"ion-page-invisible\");\n this._hideInactivePages(finalEl);\n _dispatchIonicLifecycle(finalEl, \"ionViewDidEnter\");\n }\n return;\n }\n\n const animationBuilder = (intent.animation ?? this._defaultAnimation) as\n | undefined | unknown;\n\n // duration:0 is what Ionic uses for \"no animation\" navigations.\n // Empirically, this path skips lifecycle event dispatch — so we\n // synthesize them manually around the commit() call.\n const isDuration0 = direction === \"root\" || direction === \"none\";\n\n const commitOpts: any = {\n duration: isDuration0 ? 0 : undefined,\n direction:\n direction === \"back\" ? \"back\"\n : direction === \"forward\" ? \"forward\"\n : undefined,\n showGoBack: direction === \"forward\",\n };\n if (animationBuilder) commitOpts.animationBuilder = animationBuilder;\n\n // Ionic docs: WillLeave fires BEFORE WillEnter.\n // Animated paths (duration > 0): commit() handles dispatch.\n if (isDuration0) {\n _dispatchIonicLifecycle(leavingEl, \"ionViewWillLeave\");\n _dispatchIonicLifecycle(enteringEl, \"ionViewWillEnter\");\n }\n\n await (outletEl as any).commit(enteringEl, leavingEl, commitOpts);\n\n this._activePageEl = enteringEl;\n this._activeCacheKey = cacheKey;\n this._activeTabKey = targetTabKey;\n\n // Remove the invisible/hidden classes that were added when reusing\n // a cached page. Ionic's commit() may remove them during animation,\n // but with reduced-motion or duration-0 transitions they can persist.\n enteringEl.classList.remove(\"ion-page-invisible\");\n enteringEl.classList.remove(\"ion-page-hidden\");\n enteringEl.style.removeProperty(\"display\");\n // Ensure the entering page has a higher z-index than the leaving\n // page. Ionic's commit() with reduced-motion may not swap z-index.\n const leavingZ = leavingEl ? parseInt(getComputedStyle(leavingEl).zIndex, 10) || 0 : 0;\n enteringEl.style.zIndex = String(leavingZ + 1);\n this._hideInactivePages(enteringEl);\n\n // Ionic docs: DidLeave fires AFTER DidEnter (after the new\n // page has fully transitioned in).\n if (isDuration0) {\n _dispatchIonicLifecycle(enteringEl, \"ionViewDidEnter\");\n _dispatchIonicLifecycle(leavingEl, \"ionViewDidLeave\");\n }\n\n // If the leaving page is not in the cache (either because cache is\n // disabled OR because it was invalidated while active), run its\n // cleanup before removing the DOM node, otherwise effects/lifecycle\n // watchers leak.\n const leavingCleanup = _uncachedCleanups.get(leavingEl);\n if (leavingCleanup && leavingEl.parentElement === outletEl) {\n leavingCleanup();\n _uncachedCleanups.delete(leavingEl);\n leavingEl.remove();\n } else if (!this._enableCache && leavingEl.parentElement === outletEl) {\n leavingEl.remove();\n }\n } finally {\n this._isTransitioning = false;\n if (this._nav) this._nav.endTransition();\n\n // Determine the effective direction for afterNav hooks\n const effectiveDirection = this._stacks.apply(targetPath, intent);\n\n // Run afterNav and tabChange hooks\n if (this._nav && !transitionCancelled) {\n this._nav.runAfterNav(targetPath, effectiveDirection);\n this._nav.runTabChangeIfNeeded(targetPath);\n this._nav.updateCanGoBack();\n }\n\n // Only process pending nav if the current transition succeeded.\n // A cancelled/failed transition (guard reject, route not found)\n // must NOT blindly replay a stale pending nav — the router state\n // may have already moved (e.g. redirect) and the pending path\n // could be inconsistent with the new current.\n const pending = this._nav ? this._nav.consumePendingNav() : this._pendingNav;\n if (!this._nav) this._pendingNav = null;\n\n if (pending && !transitionCancelled) {\n // Re-validate against current router state before processing.\n const currentRouter = elurRouter();\n if (pending.path === currentRouter.current.value) {\n void this._transitionTo(pending.path, pending.intent);\n }\n } else if (transitionCancelled && this._nav) {\n this._nav.clearPendingNav();\n }\n }\n }\n\n override render(): ElurTemplate {\n const self = this;\n return {\n __isElurTemplate: true as const,\n\n mount(container: Element | string) {\n const el = typeof container === \"string\"\n ? document.querySelector(container)!\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n\n _render(parent: Node, before: Node | null): () => void {\n const outletEl = document.createElement(\"ion-router-outlet\");\n self._outletEl = outletEl;\n\n (outletEl as any).delegate = {\n attachViewToDom: (\n container: HTMLElement,\n component: HTMLElement,\n ): HTMLElement => {\n if (component && !container.contains(component)) {\n container.appendChild(component);\n }\n return component;\n },\n removeViewFromDom: async (): Promise<void> => { /* no-op */ },\n };\n\n parent.insertBefore(outletEl, before);\n\n const router: Router = elurRouter();\n let lastSeenNavKey: string | null = null;\n let initialDeferred = false;\n\n self._routeEffectDisposer = effect(() => {\n const path = router.current.value;\n const intent = router.intent.value;\n // Observe query so query-only navigation (same path, different\n // query) triggers a transition. The nav key combines path +\n // serialized query; reading router.query.value subscribes\n // the effect to query changes.\n const query = router.query.value;\n const queryStr = Object.keys(query).length > 0\n ? \"?\" + Object.keys(query).sort().map(\n (k) => `${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`,\n ).join(\"&\")\n : \"\";\n const navKey = path + queryStr;\n\n if (!initialDeferred) {\n initialDeferred = true;\n queueMicrotask(() => {\n const settledPath = router.current.value;\n const settledIntent = router.intent.value;\n const settledQuery = router.query.value;\n const settledQueryStr = Object.keys(settledQuery).length > 0\n ? \"?\" + Object.keys(settledQuery).sort().map(\n (k) => `${encodeURIComponent(k)}=${encodeURIComponent(settledQuery[k])}`,\n ).join(\"&\")\n : \"\";\n lastSeenNavKey = settledPath + settledQueryStr;\n void self._transitionTo(settledPath, settledIntent);\n });\n return;\n }\n\n if (navKey === lastSeenNavKey) {\n // The router sets intent.value before current.value,\n // which can trigger the effect with a stale path.\n // If the intent indicates a pop (hashchange back),\n // defer to a microtask to read the settled path.\n if (intent.action === \"pop\" && intent.direction === \"none\") {\n queueMicrotask(() => {\n const settledPath = router.current.value;\n const settledQuery = router.query.value;\n const settledQueryStr = Object.keys(settledQuery).length > 0\n ? \"?\" + Object.keys(settledQuery).sort().map(\n (k) => `${encodeURIComponent(k)}=${encodeURIComponent(settledQuery[k])}`,\n ).join(\"&\")\n : \"\";\n const settledNavKey = settledPath + settledQueryStr;\n if (settledNavKey === lastSeenNavKey) return;\n lastSeenNavKey = settledNavKey;\n void self._transitionTo(settledPath, router.intent.value);\n });\n }\n return;\n }\n lastSeenNavKey = navKey;\n void self._transitionTo(path, intent);\n });\n\n return () => {\n self._routeEffectDisposer?.();\n self._routeEffectDisposer = null;\n for (const { view } of self._cache.all()) {\n view.cleanup();\n if (view.pageEl.parentElement) view.pageEl.remove();\n }\n self._cache.clear();\n // cache:false active page — run its cleanup too.\n if (self._activePageEl) {\n const activeCleanup = _uncachedCleanups.get(self._activePageEl);\n if (activeCleanup) {\n activeCleanup();\n _uncachedCleanups.delete(self._activePageEl);\n }\n }\n self._activePageEl = null;\n self._activeCacheKey = null;\n self._activeTabKey = null;\n self._outletEl = null;\n outletEl.remove();\n };\n },\n };\n }\n\n invalidateCache(\n routePath: string,\n params?: Record<string, string>,\n tabKey?: string,\n query?: Record<string, string>,\n ): void {\n const key = (params || query)\n ? _buildCacheKey(routePath, params ?? {}, query)\n : routePath;\n const targetTabKey = tabKey ?? this._stacks.keyForPath(routePath);\n const cached = this._cache.get(targetTabKey, key);\n if (!cached) return;\n\n if (cached.pageEl === this._activePageEl) {\n // Active page: do NOT dispose reactivity or remove DOM — that would\n // leave a visible-but-dead view. Just drop the cache entry so the\n // next visit remounts a fresh instance. Move the cleanup to the\n // uncached tracker so the normal transition path disposes it when\n // the user navigates away.\n _uncachedCleanups.set(cached.pageEl, cached.cleanup);\n this._cache.delete(targetTabKey, key);\n this._activeCacheKey = null;\n return;\n }\n\n cached.cleanup();\n if (cached.pageEl.parentElement) {\n cached.pageEl.remove();\n }\n this._cache.delete(targetTabKey, key);\n }\n\n clearCache(): void {\n const entries: Array<{ tabKey: string; cacheKey: string; view: CachedView }> = [];\n for (const e of this._cache.all()) entries.push(e);\n for (const { tabKey, cacheKey, view } of entries) {\n if (view.pageEl === this._activePageEl) continue;\n view.cleanup();\n if (view.pageEl.parentElement) view.pageEl.remove();\n this._cache.delete(tabKey, cacheKey);\n }\n }\n\n /**\n * Clear all cached pages for a specific tab. Useful when leaving a tab\n * permanently or for memory management.\n */\n clearTabCache(tabKey: string): void {\n const entries: Array<{ tabKey: string; cacheKey: string; view: CachedView }> = [];\n for (const e of this._cache.all()) {\n if (e.tabKey === tabKey) entries.push(e);\n }\n for (const { tabKey, cacheKey, view } of entries) {\n if (view.pageEl === this._activePageEl) continue;\n view.cleanup();\n if (view.pageEl.parentElement) view.pageEl.remove();\n this._cache.delete(tabKey, cacheKey);\n }\n }\n\n /** The NavigationManager used by this outlet (if any). */\n get navigation(): NavigationManager | null {\n return this._nav;\n }\n\n /**\n * @internal — Debug snapshot for `@elurjs/ionic/devtools`.\n * Plain data, computed on demand.\n */\n _debugSnapshot(): {\n cacheEnabled: boolean;\n cachePolicy: CachePolicy;\n views: Array<{\n tab: string;\n key: string;\n routePath: string;\n createdAt: number;\n lastAccessed: number;\n }>;\n stacks: Array<{ prefix: string; depth: number; entries: string[] }>;\n } {\n return {\n cacheEnabled: this._enableCache,\n cachePolicy: { ...this._cachePolicy },\n views: this._cache._debugSnapshot(),\n stacks: this._stacks._debugSnapshot(),\n };\n }\n}"],"mappings":"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"}
|
package/dist/lib/navigation.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require("./vite-plugin.cjs");let e=require("@elurjs/core");var t=class{_stacks=new Map;_activeTabKey;_tabPrefixes;constructor(e){if(this._stacks.set(``,{prefix:``,entries:[]}),e?.length){for(let t of e){let e=this._normalize(t);this._stacks.set(e,{prefix:e,entries:[]})}this._tabPrefixes=e.map(e=>this._normalize(e)).sort((e,t)=>t.length-e.length)}else this._tabPrefixes=[];this._activeTabKey=``}keyForPath(e){for(let t of this._tabPrefixes)if(e===t||e.startsWith(t+`/`))return t;return``}get activeTabKey(){return this._activeTabKey}get tabPrefixes(){return this._tabPrefixes}stackDepth(e){let t=e??this._activeTabKey;return this._stacks.get(t)?.entries.length??0}stackTop(e){let t=e??this._activeTabKey,n=this._stacks.get(t);return!n||n.entries.length===0?null:n.entries[n.entries.length-1]}stackEntries(e){let t=e??this._activeTabKey;return[...this._stacks.get(t)?.entries??[]]}apply(e,t){let n=this.keyForPath(e),r=this._stacks.get(n);if(n!==this._activeTabKey)return this._activeTabKey=n,r.entries[r.entries.length-1]!==e&&r.entries.push(e),`none`;if(t.direction===`back`){for(;r.entries.length>0&&r.entries[r.entries.length-1]!==e;)r.entries.pop();return r.entries.length===0&&r.entries.push(e),`back`}return t.action===`replace`||t.direction===`root`?(r.entries.length===0?r.entries.push(e):r.entries[r.entries.length-1]=e,t.direction===`forward`?`forward`:`root`):t.action===`initial`?(r.entries.length===0&&r.entries.push(e),`none`):(r.entries[r.entries.length-1]!==e&&r.entries.push(e),t.direction)}resetTab(e){let t=this._stacks.get(e);t&&(t.entries=[])}clearTabStack(e){let t=this._stacks.get(e);if(!t)return[];let n=[...t.entries];return t.entries=[],n}resetAll(){for(let e of this._stacks.values())e.entries=[];this._activeTabKey=``}_normalize(e){return!e||e===`/`?`/`:e.endsWith(`/`)?e.slice(0,-1):e}},n=class{_stacks;_beforeNavHooks=[];_afterNavHooks=[];_tabChangeHooks=[];_cacheInvalidationHandlers=new Map;_isTransitioning=!1;_pendingNav=null;_lastTabKey=null;canGoBack;constructor(n={}){this._stacks=new t(n.tabs),this.canGoBack=(0,e.signal)(!1)}get stacks(){return this._stacks}get activeTab(){return this._stacks.activeTabKey}get tabPrefixes(){return this._stacks.tabPrefixes}stackDepth(e){return this._stacks.stackDepth(e)}stackTop(e){return this._stacks.stackTop(e)}stackEntries(e){return this._stacks.stackEntries(e)}get isTransitioning(){return this._isTransitioning}get pendingNav(){return this._pendingNav}beginTransition(){this._isTransitioning=!0}endTransition(){this._isTransitioning=!1}setPendingNav(e,t){this._pendingNav={path:e,intent:t}}clearPendingNav(){this._pendingNav=null}consumePendingNav(){let e=this._pendingNav;return this._pendingNav=null,e}beforeNav(e){return this._beforeNavHooks.push(e),()=>{let t=this._beforeNavHooks.indexOf(e);t>=0&&this._beforeNavHooks.splice(t,1)}}afterNav(e){return this._afterNavHooks.push(e),()=>{let t=this._afterNavHooks.indexOf(e);t>=0&&this._afterNavHooks.splice(t,1)}}onTabChange(e){return this._tabChangeHooks.push(e),()=>{let t=this._tabChangeHooks.indexOf(e);t>=0&&this._tabChangeHooks.splice(t,1)}}async runBeforeNav(e,t){for(let n of this._beforeNavHooks)try{if(await n(e,t)===!1)return!1}catch{}return!0}runAfterNav(e,t){for(let n of this._afterNavHooks)try{n(e,t)}catch{}}runTabChangeIfNeeded(e){let t=this._stacks.keyForPath(e);if(t!==this._lastTabKey){let e=this._lastTabKey;this._lastTabKey=t;for(let n of this._tabChangeHooks)try{n(t,e)}catch{}}}updateCanGoBack(){this.canGoBack.value=this._stacks.stackDepth()>1}switchTab(e){let t=this._stacks.keyForPath(e);return t===this._stacks.activeTabKey?null:this._stacks.stackTop(t)??e}registerInvalidationHandler(e,t){return this._cacheInvalidationHandlers.set(e,t),()=>{this._cacheInvalidationHandlers.delete(e)}}invalidateRoute(e,t){let n=this._cacheInvalidationHandlers.get(e);n&&n(t)}invalidatePattern(e){let t=e.endsWith(`/*`),n=t?e.slice(0,-2):e;for(let r of this._cacheInvalidationHandlers.keys())t?(r===n||r.startsWith(n+`/`)||r.startsWith(n+`:`))&&this._cacheInvalidationHandlers.get(r)?.():r===e&&this._cacheInvalidationHandlers.get(r)?.()}dispose(){this._stacks.resetAll(),this._beforeNavHooks=[],this._afterNavHooks=[],this._tabChangeHooks=[],this._cacheInvalidationHandlers.clear(),this._pendingNav=null,this._isTransitioning=!1,this._lastTabKey=null}};exports.NavigationManager=
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require("./vite-plugin.cjs");let e=require("@elurjs/core");var t=class{_stacks=new Map;_activeTabKey;_tabPrefixes;constructor(e){if(this._stacks.set(``,{prefix:``,entries:[]}),e?.length){for(let t of e){let e=this._normalize(t);this._stacks.set(e,{prefix:e,entries:[]})}this._tabPrefixes=e.map(e=>this._normalize(e)).sort((e,t)=>t.length-e.length)}else this._tabPrefixes=[];this._activeTabKey=``}keyForPath(e){for(let t of this._tabPrefixes)if(e===t||e.startsWith(t+`/`))return t;return``}get activeTabKey(){return this._activeTabKey}get tabPrefixes(){return this._tabPrefixes}stackDepth(e){let t=e??this._activeTabKey;return this._stacks.get(t)?.entries.length??0}stackTop(e){let t=e??this._activeTabKey,n=this._stacks.get(t);return!n||n.entries.length===0?null:n.entries[n.entries.length-1]}stackEntries(e){let t=e??this._activeTabKey;return[...this._stacks.get(t)?.entries??[]]}_debugSnapshot(){return Array.from(this._stacks.values()).map(e=>({prefix:e.prefix,depth:e.entries.length,entries:[...e.entries]}))}apply(e,t){let n=this.keyForPath(e),r=this._stacks.get(n);if(n!==this._activeTabKey)return this._activeTabKey=n,r.entries[r.entries.length-1]!==e&&r.entries.push(e),`none`;if(t.direction===`back`){for(;r.entries.length>0&&r.entries[r.entries.length-1]!==e;)r.entries.pop();return r.entries.length===0&&r.entries.push(e),`back`}return t.action===`replace`||t.direction===`root`?(r.entries.length===0?r.entries.push(e):r.entries[r.entries.length-1]=e,t.direction===`forward`?`forward`:`root`):t.action===`initial`?(r.entries.length===0&&r.entries.push(e),`none`):(r.entries[r.entries.length-1]!==e&&r.entries.push(e),t.direction)}resetTab(e){let t=this._stacks.get(e);t&&(t.entries=[])}clearTabStack(e){let t=this._stacks.get(e);if(!t)return[];let n=[...t.entries];return t.entries=[],n}resetAll(){for(let e of this._stacks.values())e.entries=[];this._activeTabKey=``}_normalize(e){return!e||e===`/`?`/`:e.endsWith(`/`)?e.slice(0,-1):e}},n=Symbol.for(`@elurjs/ionic/navigation`);function r(e){let t=globalThis,r=t[n];r||(r=new Set,t[n]=r),r.add(e)}var i=class{_stacks;_beforeNavHooks=[];_afterNavHooks=[];_tabChangeHooks=[];_cacheInvalidationHandlers=new Map;_isTransitioning=!1;_pendingNav=null;_lastTabKey=null;canGoBack;constructor(n={}){this._stacks=new t(n.tabs),this.canGoBack=(0,e.signal)(!1),r(this)}get stacks(){return this._stacks}get activeTab(){return this._stacks.activeTabKey}get tabPrefixes(){return this._stacks.tabPrefixes}stackDepth(e){return this._stacks.stackDepth(e)}stackTop(e){return this._stacks.stackTop(e)}stackEntries(e){return this._stacks.stackEntries(e)}get isTransitioning(){return this._isTransitioning}get pendingNav(){return this._pendingNav}beginTransition(){this._isTransitioning=!0}endTransition(){this._isTransitioning=!1}setPendingNav(e,t){this._pendingNav={path:e,intent:t}}clearPendingNav(){this._pendingNav=null}consumePendingNav(){let e=this._pendingNav;return this._pendingNav=null,e}beforeNav(e){return this._beforeNavHooks.push(e),()=>{let t=this._beforeNavHooks.indexOf(e);t>=0&&this._beforeNavHooks.splice(t,1)}}afterNav(e){return this._afterNavHooks.push(e),()=>{let t=this._afterNavHooks.indexOf(e);t>=0&&this._afterNavHooks.splice(t,1)}}onTabChange(e){return this._tabChangeHooks.push(e),()=>{let t=this._tabChangeHooks.indexOf(e);t>=0&&this._tabChangeHooks.splice(t,1)}}async runBeforeNav(e,t){for(let n of this._beforeNavHooks)try{if(await n(e,t)===!1)return!1}catch{}return!0}runAfterNav(e,t){for(let n of this._afterNavHooks)try{n(e,t)}catch{}}runTabChangeIfNeeded(e){let t=this._stacks.keyForPath(e);if(t!==this._lastTabKey){let e=this._lastTabKey;this._lastTabKey=t;for(let n of this._tabChangeHooks)try{n(t,e)}catch{}}}updateCanGoBack(){this.canGoBack.value=this._stacks.stackDepth()>1}switchTab(e){let t=this._stacks.keyForPath(e);return t===this._stacks.activeTabKey?null:this._stacks.stackTop(t)??e}registerInvalidationHandler(e,t){return this._cacheInvalidationHandlers.set(e,t),()=>{this._cacheInvalidationHandlers.delete(e)}}invalidateRoute(e,t){let n=this._cacheInvalidationHandlers.get(e);n&&n(t)}invalidatePattern(e){let t=e.endsWith(`/*`),n=t?e.slice(0,-2):e;for(let r of this._cacheInvalidationHandlers.keys())t?(r===n||r.startsWith(n+`/`)||r.startsWith(n+`:`))&&this._cacheInvalidationHandlers.get(r)?.():r===e&&this._cacheInvalidationHandlers.get(r)?.()}dispose(){this._stacks.resetAll(),this._beforeNavHooks=[],this._afterNavHooks=[],this._tabChangeHooks=[],this._cacheInvalidationHandlers.clear(),this._pendingNav=null,this._isTransitioning=!1,this._lastTabKey=null}};exports.NavigationManager=i,exports.StackManager=t;
|
|
2
2
|
//# sourceMappingURL=navigation.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.cjs","names":[],"sources":["../../src/navigation.ts"],"sourcesContent":["/**\n * @elurjs/ionic / navigation.ts\n *\n * NavigationManager — a single coordination authority for Ionic navigation.\n *\n * The IonRouterOutlet handles DOM rendering, transitions, and cache. The\n * NavigationManager handles:\n * - Per-tab navigation stacks (extracted from the outlet)\n * - Navigation state tracking (isTransitioning, pending navigation)\n * - Navigation hooks (beforeNav, afterNav, onTabChange)\n * - Programmatic tab switching\n * - Route-pattern-based cache invalidation hooks\n * - Guard coordination (delegated to the core router when auto-bootstrapped)\n *\n * The outlet delegates navigation state to the manager, keeping a clean\n * separation: navigation logic lives here, rendering lives in the outlet.\n *\n * @example\n * ```ts\n * import { NavigationManager } from \"@elurjs/ionic\";\n *\n * const nav = new NavigationManager({ tabs: [\"/home\", \"/search\", \"/profile\"] });\n *\n * // Navigation hooks\n * nav.beforeNav((path, intent) => {\n * console.log(\"navigating to\", path);\n * // return false to cancel\n * });\n * nav.afterNav((path) => {\n * analytics.track(\"page_view\", { path });\n * });\n * nav.onTabChange((tab) => {\n * console.log(\"active tab:\", tab);\n * });\n *\n * // Programmatic tab switching\n * nav.switchTab(\"/search\");\n *\n * // The outlet uses the manager for stack/state:\n * const outlet = new IonRouterOutlet(routes, { navigation: nav });\n * ```\n */\n\nimport { signal, type Signal, type NavigationIntent, type NavigationDirection } from \"@elurjs/core\";\n\n// --- Types ---\n\n/** A function called before navigation completes. Return false to cancel. */\nexport type BeforeNavHook = (\n path: string,\n intent: NavigationIntent,\n) => boolean | void | Promise<boolean | void>;\n\n/** A function called after navigation completes. */\nexport type AfterNavHook = (\n path: string,\n direction: NavigationDirection,\n) => void;\n\n/** A function called when the active tab changes. */\nexport type TabChangeHook = (\n tab: string,\n previousTab: string | null,\n) => void;\n\n/** Options for NavigationManager construction. */\nexport interface NavigationManagerOptions {\n /** Tab prefixes for per-tab stack management. */\n tabs?: string[];\n}\n\n// --- Per-tab stack management (extracted from IonRouterOutlet) ---\n\ninterface TabStack {\n prefix: string;\n entries: string[];\n}\n\n/**\n * Manages per-tab navigation stacks. Each tab has its own back/forward\n * stack. When the user switches tabs, the stack for the new tab is\n * restored.\n */\nexport class StackManager {\n private _stacks = new Map<string, TabStack>();\n private _activeTabKey: string;\n private _tabPrefixes: string[];\n\n constructor(tabs: string[] | undefined) {\n this._stacks.set(\"\", { prefix: \"\", entries: [] });\n if (tabs?.length) {\n for (const prefix of tabs) {\n const norm = this._normalize(prefix);\n this._stacks.set(norm, { prefix: norm, entries: [] });\n }\n this._tabPrefixes = tabs.map((t) => this._normalize(t))\n .sort((a, b) => b.length - a.length);\n } else {\n this._tabPrefixes = [];\n }\n this._activeTabKey = \"\";\n }\n\n /** Get the tab key for a given path. */\n keyForPath(path: string): string {\n for (const prefix of this._tabPrefixes) {\n if (path === prefix || path.startsWith(prefix + \"/\")) {\n return prefix;\n }\n }\n return \"\";\n }\n\n /** Get the active tab key. */\n get activeTabKey(): string {\n return this._activeTabKey;\n }\n\n /** Get all registered tab prefixes. */\n get tabPrefixes(): readonly string[] {\n return this._tabPrefixes;\n }\n\n /** Get the current stack depth for a tab. */\n stackDepth(tabKey?: string): number {\n const key = tabKey ?? this._activeTabKey;\n return this._stacks.get(key)?.entries.length ?? 0;\n }\n\n /** Get the top of the stack for a tab. */\n stackTop(tabKey?: string): string | null {\n const key = tabKey ?? this._activeTabKey;\n const stack = this._stacks.get(key);\n if (!stack || stack.entries.length === 0) return null;\n return stack.entries[stack.entries.length - 1];\n }\n\n /** Get all entries in a tab's stack (copy). */\n stackEntries(tabKey?: string): string[] {\n const key = tabKey ?? this._activeTabKey;\n return [...(this._stacks.get(key)?.entries ?? [])];\n }\n\n /**\n * Apply a navigation to the stacks. Returns the effective direction.\n * This is the same logic that was in IonRouterOutlet._stacks.apply().\n */\n apply(path: string, intent: NavigationIntent): NavigationDirection {\n const targetKey = this.keyForPath(path);\n const stack = this._stacks.get(targetKey)!;\n\n if (targetKey !== this._activeTabKey) {\n this._activeTabKey = targetKey;\n const top = stack.entries[stack.entries.length - 1];\n if (top !== path) stack.entries.push(path);\n return \"none\";\n }\n\n if (intent.direction === \"back\") {\n while (stack.entries.length > 0\n && stack.entries[stack.entries.length - 1] !== path) {\n stack.entries.pop();\n }\n if (stack.entries.length === 0) stack.entries.push(path);\n return \"back\";\n }\n\n if (intent.action === \"replace\" || intent.direction === \"root\") {\n if (stack.entries.length === 0) stack.entries.push(path);\n else stack.entries[stack.entries.length - 1] = path;\n return intent.direction === \"forward\" ? \"forward\" : \"root\";\n }\n\n if (intent.action === \"initial\") {\n if (stack.entries.length === 0) stack.entries.push(path);\n return \"none\";\n }\n\n const top = stack.entries[stack.entries.length - 1];\n if (top !== path) stack.entries.push(path);\n return intent.direction;\n }\n\n /** Reset a specific tab's stack. */\n resetTab(tabKey: string): void {\n const stack = this._stacks.get(tabKey);\n if (stack) stack.entries = [];\n }\n\n /** Clear a specific tab's stack and return the paths that were in it. */\n clearTabStack(tabKey: string): string[] {\n const stack = this._stacks.get(tabKey);\n if (!stack) return [];\n const entries = [...stack.entries];\n stack.entries = [];\n return entries;\n }\n\n /** Reset all stacks. */\n resetAll(): void {\n for (const stack of this._stacks.values()) {\n stack.entries = [];\n }\n this._activeTabKey = \"\";\n }\n\n private _normalize(p: string): string {\n if (!p || p === \"/\") return \"/\";\n return p.endsWith(\"/\") ? p.slice(0, -1) : p;\n }\n}\n\n// --- NavigationManager ---\n\n/**\n * Single coordination authority for Ionic navigation.\n *\n * Wraps the per-tab stack manager and adds:\n * - Navigation hooks (beforeNav, afterNav, onTabChange)\n * - Transition state tracking (isTransitioning, pending)\n * - Programmatic tab switching\n * - Route-pattern-based cache invalidation hooks\n */\nexport class NavigationManager {\n private _stacks: StackManager;\n private _beforeNavHooks: BeforeNavHook[] = [];\n private _afterNavHooks: AfterNavHook[] = [];\n private _tabChangeHooks: TabChangeHook[] = [];\n private _cacheInvalidationHandlers = new Map<string, (params?: Record<string, string>) => void>();\n\n private _isTransitioning = false;\n private _pendingNav: { path: string; intent: NavigationIntent } | null = null;\n private _lastTabKey: string | null = null;\n\n /** Reactive signal: true when the active tab has a back stack. */\n readonly canGoBack: Signal<boolean>;\n\n constructor(options: NavigationManagerOptions = {}) {\n this._stacks = new StackManager(options.tabs);\n this.canGoBack = signal(false);\n }\n\n // --- Stack access ---\n\n get stacks(): StackManager {\n return this._stacks;\n }\n\n get activeTab(): string {\n return this._stacks.activeTabKey;\n }\n\n get tabPrefixes(): readonly string[] {\n return this._stacks.tabPrefixes;\n }\n\n stackDepth(tabKey?: string): number {\n return this._stacks.stackDepth(tabKey);\n }\n\n stackTop(tabKey?: string): string | null {\n return this._stacks.stackTop(tabKey);\n }\n\n stackEntries(tabKey?: string): string[] {\n return this._stacks.stackEntries(tabKey);\n }\n\n // --- Transition state ---\n\n get isTransitioning(): boolean {\n return this._isTransitioning;\n }\n\n get pendingNav(): { path: string; intent: NavigationIntent } | null {\n return this._pendingNav;\n }\n\n /** Called by the outlet when a transition starts. */\n beginTransition(): void {\n this._isTransitioning = true;\n }\n\n /** Called by the outlet when a transition ends. */\n endTransition(): void {\n this._isTransitioning = false;\n }\n\n /** Queue a pending navigation (called by the outlet when already transitioning). */\n setPendingNav(path: string, intent: NavigationIntent): void {\n this._pendingNav = { path, intent };\n }\n\n /** Clear pending navigation. */\n clearPendingNav(): void {\n this._pendingNav = null;\n }\n\n /** Consume and return the pending navigation, if any. */\n consumePendingNav(): { path: string; intent: NavigationIntent } | null {\n const pending = this._pendingNav;\n this._pendingNav = null;\n return pending;\n }\n\n // --- Navigation hooks ---\n\n /**\n * Register a hook called before navigation completes.\n * Return `false` to cancel the navigation.\n * Multiple hooks are called in registration order.\n */\n beforeNav(hook: BeforeNavHook): () => void {\n this._beforeNavHooks.push(hook);\n return () => {\n const i = this._beforeNavHooks.indexOf(hook);\n if (i >= 0) this._beforeNavHooks.splice(i, 1);\n };\n }\n\n /**\n * Register a hook called after navigation completes.\n */\n afterNav(hook: AfterNavHook): () => void {\n this._afterNavHooks.push(hook);\n return () => {\n const i = this._afterNavHooks.indexOf(hook);\n if (i >= 0) this._afterNavHooks.splice(i, 1);\n };\n }\n\n /**\n * Register a hook called when the active tab changes.\n */\n onTabChange(hook: TabChangeHook): () => void {\n this._tabChangeHooks.push(hook);\n return () => {\n const i = this._tabChangeHooks.indexOf(hook);\n if (i >= 0) this._tabChangeHooks.splice(i, 1);\n };\n }\n\n /**\n * Run beforeNav hooks. Returns true if all hooks allow navigation,\n * false if any hook cancelled.\n */\n async runBeforeNav(path: string, intent: NavigationIntent): Promise<boolean> {\n for (const hook of this._beforeNavHooks) {\n try {\n const result = await hook(path, intent);\n if (result === false) return false;\n } catch {\n // Hook error — don't block navigation\n }\n }\n return true;\n }\n\n /**\n * Run afterNav hooks. Called by the outlet after a transition completes.\n */\n runAfterNav(path: string, direction: NavigationDirection): void {\n for (const hook of this._afterNavHooks) {\n try {\n hook(path, direction);\n } catch { /* ignore */ }\n }\n }\n\n /**\n * Run tab change hooks if the tab actually changed.\n */\n runTabChangeIfNeeded(currentPath: string): void {\n const newTab = this._stacks.keyForPath(currentPath);\n if (newTab !== this._lastTabKey) {\n const prev = this._lastTabKey;\n this._lastTabKey = newTab;\n for (const hook of this._tabChangeHooks) {\n try {\n hook(newTab, prev);\n } catch { /* ignore */ }\n }\n }\n }\n\n /**\n * Update the reactive `canGoBack` signal based on the active tab's\n * stack depth. Called by the outlet after each transition.\n */\n updateCanGoBack(): void {\n this.canGoBack.value = this._stacks.stackDepth() > 1;\n }\n\n // --- Programmatic tab switching ---\n\n /**\n * Switch to a specific tab. Navigates to the tab's current stack top,\n * or the tab prefix if the stack is empty.\n *\n * @example\n * ```ts\n * nav.switchTab(\"/search\"); // switches to the search tab\n * ```\n */\n switchTab(tabPrefix: string): string | null {\n const normalized = this._stacks.keyForPath(tabPrefix);\n if (normalized === this._stacks.activeTabKey) return null;\n const top = this._stacks.stackTop(normalized);\n return top ?? tabPrefix;\n }\n\n // --- Cache invalidation by route pattern ---\n\n /**\n * Register a cache invalidation handler for a route pattern.\n * The outlet registers handlers for each route on construction.\n *\n * @example\n * ```ts\n * nav.registerInvalidationHandler(\"/user/:id\", (params) => {\n * // called when invalidateRoute(\"/user/:id\", { id: \"42\" }) is invoked\n * });\n * ```\n */\n registerInvalidationHandler(\n routePattern: string,\n handler: (params?: Record<string, string>) => void,\n ): () => void {\n this._cacheInvalidationHandlers.set(routePattern, handler);\n return () => {\n this._cacheInvalidationHandlers.delete(routePattern);\n };\n }\n\n /**\n * Invalidate cached pages for a specific route pattern + params.\n * If the route has dynamic segments, provide params to target a\n * specific instance. Omit params to invalidate all instances.\n *\n * @example\n * ```ts\n * nav.invalidateRoute(\"/user/:id\", { id: \"42\" }); // invalidate user 42\n * nav.invalidateRoute(\"/search\"); // invalidate all search instances\n * ```\n */\n invalidateRoute(\n routePattern: string,\n params?: Record<string, string>,\n ): void {\n const handler = this._cacheInvalidationHandlers.get(routePattern);\n if (handler) handler(params);\n }\n\n /**\n * Invalidate all cached pages matching a glob pattern.\n * Supports `*` as a wildcard suffix.\n *\n * @example\n * ```ts\n * nav.invalidatePattern(\"/admin/*\"); // invalidate all admin pages\n * nav.invalidatePattern(\"/*\"); // invalidate everything\n * ```\n */\n invalidatePattern(pattern: string): void {\n const isWildcard = pattern.endsWith(\"/*\");\n const prefix = isWildcard ? pattern.slice(0, -2) : pattern;\n\n for (const routePattern of this._cacheInvalidationHandlers.keys()) {\n if (isWildcard) {\n if (routePattern === prefix || routePattern.startsWith(prefix + \"/\") ||\n routePattern.startsWith(prefix + \":\")) {\n this._cacheInvalidationHandlers.get(routePattern)?.();\n }\n } else if (routePattern === pattern) {\n this._cacheInvalidationHandlers.get(routePattern)?.();\n }\n }\n }\n\n // --- Cleanup ---\n\n /** Reset all navigation state (stacks, hooks, pending). */\n dispose(): void {\n this._stacks.resetAll();\n this._beforeNavHooks = [];\n this._afterNavHooks = [];\n this._tabChangeHooks = [];\n this._cacheInvalidationHandlers.clear();\n this._pendingNav = null;\n this._isTransitioning = false;\n this._lastTabKey = null;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"navigation.cjs","names":[],"sources":["../../src/navigation.ts"],"sourcesContent":["/**\n * @elurjs/ionic / navigation.ts\n *\n * NavigationManager — a single coordination authority for Ionic navigation.\n *\n * The IonRouterOutlet handles DOM rendering, transitions, and cache. The\n * NavigationManager handles:\n * - Per-tab navigation stacks (extracted from the outlet)\n * - Navigation state tracking (isTransitioning, pending navigation)\n * - Navigation hooks (beforeNav, afterNav, onTabChange)\n * - Programmatic tab switching\n * - Route-pattern-based cache invalidation hooks\n * - Guard coordination (delegated to the core router when auto-bootstrapped)\n *\n * The outlet delegates navigation state to the manager, keeping a clean\n * separation: navigation logic lives here, rendering lives in the outlet.\n *\n * @example\n * ```ts\n * import { NavigationManager } from \"@elurjs/ionic\";\n *\n * const nav = new NavigationManager({ tabs: [\"/home\", \"/search\", \"/profile\"] });\n *\n * // Navigation hooks\n * nav.beforeNav((path, intent) => {\n * console.log(\"navigating to\", path);\n * // return false to cancel\n * });\n * nav.afterNav((path) => {\n * analytics.track(\"page_view\", { path });\n * });\n * nav.onTabChange((tab) => {\n * console.log(\"active tab:\", tab);\n * });\n *\n * // Programmatic tab switching\n * nav.switchTab(\"/search\");\n *\n * // The outlet uses the manager for stack/state:\n * const outlet = new IonRouterOutlet(routes, { navigation: nav });\n * ```\n */\n\nimport { signal, type Signal, type NavigationIntent, type NavigationDirection } from \"@elurjs/core\";\n\n// --- Types ---\n\n/** A function called before navigation completes. Return false to cancel. */\nexport type BeforeNavHook = (\n path: string,\n intent: NavigationIntent,\n) => boolean | void | Promise<boolean | void>;\n\n/** A function called after navigation completes. */\nexport type AfterNavHook = (\n path: string,\n direction: NavigationDirection,\n) => void;\n\n/** A function called when the active tab changes. */\nexport type TabChangeHook = (\n tab: string,\n previousTab: string | null,\n) => void;\n\n/** Options for NavigationManager construction. */\nexport interface NavigationManagerOptions {\n /** Tab prefixes for per-tab stack management. */\n tabs?: string[];\n}\n\n// --- Per-tab stack management (extracted from IonRouterOutlet) ---\n\ninterface TabStack {\n prefix: string;\n entries: string[];\n}\n\n/**\n * Manages per-tab navigation stacks. Each tab has its own back/forward\n * stack. When the user switches tabs, the stack for the new tab is\n * restored.\n */\nexport class StackManager {\n private _stacks = new Map<string, TabStack>();\n private _activeTabKey: string;\n private _tabPrefixes: string[];\n\n constructor(tabs: string[] | undefined) {\n this._stacks.set(\"\", { prefix: \"\", entries: [] });\n if (tabs?.length) {\n for (const prefix of tabs) {\n const norm = this._normalize(prefix);\n this._stacks.set(norm, { prefix: norm, entries: [] });\n }\n this._tabPrefixes = tabs.map((t) => this._normalize(t))\n .sort((a, b) => b.length - a.length);\n } else {\n this._tabPrefixes = [];\n }\n this._activeTabKey = \"\";\n }\n\n /** Get the tab key for a given path. */\n keyForPath(path: string): string {\n for (const prefix of this._tabPrefixes) {\n if (path === prefix || path.startsWith(prefix + \"/\")) {\n return prefix;\n }\n }\n return \"\";\n }\n\n /** Get the active tab key. */\n get activeTabKey(): string {\n return this._activeTabKey;\n }\n\n /** Get all registered tab prefixes. */\n get tabPrefixes(): readonly string[] {\n return this._tabPrefixes;\n }\n\n /** Get the current stack depth for a tab. */\n stackDepth(tabKey?: string): number {\n const key = tabKey ?? this._activeTabKey;\n return this._stacks.get(key)?.entries.length ?? 0;\n }\n\n /** Get the top of the stack for a tab. */\n stackTop(tabKey?: string): string | null {\n const key = tabKey ?? this._activeTabKey;\n const stack = this._stacks.get(key);\n if (!stack || stack.entries.length === 0) return null;\n return stack.entries[stack.entries.length - 1];\n }\n\n /** Get all entries in a tab's stack (copy). */\n stackEntries(tabKey?: string): string[] {\n const key = tabKey ?? this._activeTabKey;\n return [...(this._stacks.get(key)?.entries ?? [])];\n }\n\n /**\n * @internal — Debug snapshot of all tab stacks for\n * `@elurjs/ionic/devtools`. Plain data, computed on demand.\n */\n _debugSnapshot(): Array<{ prefix: string; depth: number; entries: string[] }> {\n return Array.from(this._stacks.values()).map((stack) => ({\n prefix: stack.prefix,\n depth: stack.entries.length,\n entries: [...stack.entries],\n }));\n }\n\n /**\n * Apply a navigation to the stacks. Returns the effective direction.\n * This is the same logic that was in IonRouterOutlet._stacks.apply().\n */\n apply(path: string, intent: NavigationIntent): NavigationDirection {\n const targetKey = this.keyForPath(path);\n const stack = this._stacks.get(targetKey)!;\n\n if (targetKey !== this._activeTabKey) {\n this._activeTabKey = targetKey;\n const top = stack.entries[stack.entries.length - 1];\n if (top !== path) stack.entries.push(path);\n return \"none\";\n }\n\n if (intent.direction === \"back\") {\n while (stack.entries.length > 0\n && stack.entries[stack.entries.length - 1] !== path) {\n stack.entries.pop();\n }\n if (stack.entries.length === 0) stack.entries.push(path);\n return \"back\";\n }\n\n if (intent.action === \"replace\" || intent.direction === \"root\") {\n if (stack.entries.length === 0) stack.entries.push(path);\n else stack.entries[stack.entries.length - 1] = path;\n return intent.direction === \"forward\" ? \"forward\" : \"root\";\n }\n\n if (intent.action === \"initial\") {\n if (stack.entries.length === 0) stack.entries.push(path);\n return \"none\";\n }\n\n const top = stack.entries[stack.entries.length - 1];\n if (top !== path) stack.entries.push(path);\n return intent.direction;\n }\n\n /** Reset a specific tab's stack. */\n resetTab(tabKey: string): void {\n const stack = this._stacks.get(tabKey);\n if (stack) stack.entries = [];\n }\n\n /** Clear a specific tab's stack and return the paths that were in it. */\n clearTabStack(tabKey: string): string[] {\n const stack = this._stacks.get(tabKey);\n if (!stack) return [];\n const entries = [...stack.entries];\n stack.entries = [];\n return entries;\n }\n\n /** Reset all stacks. */\n resetAll(): void {\n for (const stack of this._stacks.values()) {\n stack.entries = [];\n }\n this._activeTabKey = \"\";\n }\n\n private _normalize(p: string): string {\n if (!p || p === \"/\") return \"/\";\n return p.endsWith(\"/\") ? p.slice(0, -1) : p;\n }\n}\n\n// --- NavigationManager ---\n\n/**\n * Single coordination authority for Ionic navigation.\n *\n * Wraps the per-tab stack manager and adds:\n * - Navigation hooks (beforeNav, afterNav, onTabChange)\n * - Transition state tracking (isTransitioning, pending)\n * - Programmatic tab switching\n * - Route-pattern-based cache invalidation hooks\n */\n/**\n * @internal — Global registry of live NavigationManager instances for\n * devtools. Shared via `Symbol.for` so it survives module duplication.\n */\nconst _navigationRegistryKey = Symbol.for(\"@elurjs/ionic/navigation\");\n\nfunction _registerNavigationManager(instance: NavigationManager): void {\n const g = globalThis as Record<PropertyKey, unknown>;\n let set = g[_navigationRegistryKey] as Set<NavigationManager> | undefined;\n if (!set) {\n set = new Set();\n g[_navigationRegistryKey] = set;\n }\n set.add(instance);\n}\n\nexport class NavigationManager {\n private _stacks: StackManager;\n private _beforeNavHooks: BeforeNavHook[] = [];\n private _afterNavHooks: AfterNavHook[] = [];\n private _tabChangeHooks: TabChangeHook[] = [];\n private _cacheInvalidationHandlers = new Map<string, (params?: Record<string, string>) => void>();\n\n private _isTransitioning = false;\n private _pendingNav: { path: string; intent: NavigationIntent } | null = null;\n private _lastTabKey: string | null = null;\n\n /** Reactive signal: true when the active tab has a back stack. */\n readonly canGoBack: Signal<boolean>;\n\n constructor(options: NavigationManagerOptions = {}) {\n this._stacks = new StackManager(options.tabs);\n this.canGoBack = signal(false);\n _registerNavigationManager(this);\n }\n\n // --- Stack access ---\n\n get stacks(): StackManager {\n return this._stacks;\n }\n\n get activeTab(): string {\n return this._stacks.activeTabKey;\n }\n\n get tabPrefixes(): readonly string[] {\n return this._stacks.tabPrefixes;\n }\n\n stackDepth(tabKey?: string): number {\n return this._stacks.stackDepth(tabKey);\n }\n\n stackTop(tabKey?: string): string | null {\n return this._stacks.stackTop(tabKey);\n }\n\n stackEntries(tabKey?: string): string[] {\n return this._stacks.stackEntries(tabKey);\n }\n\n // --- Transition state ---\n\n get isTransitioning(): boolean {\n return this._isTransitioning;\n }\n\n get pendingNav(): { path: string; intent: NavigationIntent } | null {\n return this._pendingNav;\n }\n\n /** Called by the outlet when a transition starts. */\n beginTransition(): void {\n this._isTransitioning = true;\n }\n\n /** Called by the outlet when a transition ends. */\n endTransition(): void {\n this._isTransitioning = false;\n }\n\n /** Queue a pending navigation (called by the outlet when already transitioning). */\n setPendingNav(path: string, intent: NavigationIntent): void {\n this._pendingNav = { path, intent };\n }\n\n /** Clear pending navigation. */\n clearPendingNav(): void {\n this._pendingNav = null;\n }\n\n /** Consume and return the pending navigation, if any. */\n consumePendingNav(): { path: string; intent: NavigationIntent } | null {\n const pending = this._pendingNav;\n this._pendingNav = null;\n return pending;\n }\n\n // --- Navigation hooks ---\n\n /**\n * Register a hook called before navigation completes.\n * Return `false` to cancel the navigation.\n * Multiple hooks are called in registration order.\n */\n beforeNav(hook: BeforeNavHook): () => void {\n this._beforeNavHooks.push(hook);\n return () => {\n const i = this._beforeNavHooks.indexOf(hook);\n if (i >= 0) this._beforeNavHooks.splice(i, 1);\n };\n }\n\n /**\n * Register a hook called after navigation completes.\n */\n afterNav(hook: AfterNavHook): () => void {\n this._afterNavHooks.push(hook);\n return () => {\n const i = this._afterNavHooks.indexOf(hook);\n if (i >= 0) this._afterNavHooks.splice(i, 1);\n };\n }\n\n /**\n * Register a hook called when the active tab changes.\n */\n onTabChange(hook: TabChangeHook): () => void {\n this._tabChangeHooks.push(hook);\n return () => {\n const i = this._tabChangeHooks.indexOf(hook);\n if (i >= 0) this._tabChangeHooks.splice(i, 1);\n };\n }\n\n /**\n * Run beforeNav hooks. Returns true if all hooks allow navigation,\n * false if any hook cancelled.\n */\n async runBeforeNav(path: string, intent: NavigationIntent): Promise<boolean> {\n for (const hook of this._beforeNavHooks) {\n try {\n const result = await hook(path, intent);\n if (result === false) return false;\n } catch {\n // Hook error — don't block navigation\n }\n }\n return true;\n }\n\n /**\n * Run afterNav hooks. Called by the outlet after a transition completes.\n */\n runAfterNav(path: string, direction: NavigationDirection): void {\n for (const hook of this._afterNavHooks) {\n try {\n hook(path, direction);\n } catch { /* ignore */ }\n }\n }\n\n /**\n * Run tab change hooks if the tab actually changed.\n */\n runTabChangeIfNeeded(currentPath: string): void {\n const newTab = this._stacks.keyForPath(currentPath);\n if (newTab !== this._lastTabKey) {\n const prev = this._lastTabKey;\n this._lastTabKey = newTab;\n for (const hook of this._tabChangeHooks) {\n try {\n hook(newTab, prev);\n } catch { /* ignore */ }\n }\n }\n }\n\n /**\n * Update the reactive `canGoBack` signal based on the active tab's\n * stack depth. Called by the outlet after each transition.\n */\n updateCanGoBack(): void {\n this.canGoBack.value = this._stacks.stackDepth() > 1;\n }\n\n // --- Programmatic tab switching ---\n\n /**\n * Switch to a specific tab. Navigates to the tab's current stack top,\n * or the tab prefix if the stack is empty.\n *\n * @example\n * ```ts\n * nav.switchTab(\"/search\"); // switches to the search tab\n * ```\n */\n switchTab(tabPrefix: string): string | null {\n const normalized = this._stacks.keyForPath(tabPrefix);\n if (normalized === this._stacks.activeTabKey) return null;\n const top = this._stacks.stackTop(normalized);\n return top ?? tabPrefix;\n }\n\n // --- Cache invalidation by route pattern ---\n\n /**\n * Register a cache invalidation handler for a route pattern.\n * The outlet registers handlers for each route on construction.\n *\n * @example\n * ```ts\n * nav.registerInvalidationHandler(\"/user/:id\", (params) => {\n * // called when invalidateRoute(\"/user/:id\", { id: \"42\" }) is invoked\n * });\n * ```\n */\n registerInvalidationHandler(\n routePattern: string,\n handler: (params?: Record<string, string>) => void,\n ): () => void {\n this._cacheInvalidationHandlers.set(routePattern, handler);\n return () => {\n this._cacheInvalidationHandlers.delete(routePattern);\n };\n }\n\n /**\n * Invalidate cached pages for a specific route pattern + params.\n * If the route has dynamic segments, provide params to target a\n * specific instance. Omit params to invalidate all instances.\n *\n * @example\n * ```ts\n * nav.invalidateRoute(\"/user/:id\", { id: \"42\" }); // invalidate user 42\n * nav.invalidateRoute(\"/search\"); // invalidate all search instances\n * ```\n */\n invalidateRoute(\n routePattern: string,\n params?: Record<string, string>,\n ): void {\n const handler = this._cacheInvalidationHandlers.get(routePattern);\n if (handler) handler(params);\n }\n\n /**\n * Invalidate all cached pages matching a glob pattern.\n * Supports `*` as a wildcard suffix.\n *\n * @example\n * ```ts\n * nav.invalidatePattern(\"/admin/*\"); // invalidate all admin pages\n * nav.invalidatePattern(\"/*\"); // invalidate everything\n * ```\n */\n invalidatePattern(pattern: string): void {\n const isWildcard = pattern.endsWith(\"/*\");\n const prefix = isWildcard ? pattern.slice(0, -2) : pattern;\n\n for (const routePattern of this._cacheInvalidationHandlers.keys()) {\n if (isWildcard) {\n if (routePattern === prefix || routePattern.startsWith(prefix + \"/\") ||\n routePattern.startsWith(prefix + \":\")) {\n this._cacheInvalidationHandlers.get(routePattern)?.();\n }\n } else if (routePattern === pattern) {\n this._cacheInvalidationHandlers.get(routePattern)?.();\n }\n }\n }\n\n // --- Cleanup ---\n\n /** Reset all navigation state (stacks, hooks, pending). */\n dispose(): void {\n this._stacks.resetAll();\n this._beforeNavHooks = [];\n this._afterNavHooks = [];\n this._tabChangeHooks = [];\n this._cacheInvalidationHandlers.clear();\n this._pendingNav = null;\n this._isTransitioning = false;\n this._lastTabKey = null;\n }\n}\n"],"mappings":"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"}
|
package/dist/lib/navigation.d.ts
CHANGED
|
@@ -74,6 +74,15 @@ export declare class StackManager {
|
|
|
74
74
|
stackTop(tabKey?: string): string | null;
|
|
75
75
|
/** Get all entries in a tab's stack (copy). */
|
|
76
76
|
stackEntries(tabKey?: string): string[];
|
|
77
|
+
/**
|
|
78
|
+
* @internal — Debug snapshot of all tab stacks for
|
|
79
|
+
* `@elurjs/ionic/devtools`. Plain data, computed on demand.
|
|
80
|
+
*/
|
|
81
|
+
_debugSnapshot(): Array<{
|
|
82
|
+
prefix: string;
|
|
83
|
+
depth: number;
|
|
84
|
+
entries: string[];
|
|
85
|
+
}>;
|
|
77
86
|
/**
|
|
78
87
|
* Apply a navigation to the stacks. Returns the effective direction.
|
|
79
88
|
* This is the same logic that was in IonRouterOutlet._stacks.apply().
|
|
@@ -87,15 +96,6 @@ export declare class StackManager {
|
|
|
87
96
|
resetAll(): void;
|
|
88
97
|
private _normalize;
|
|
89
98
|
}
|
|
90
|
-
/**
|
|
91
|
-
* Single coordination authority for Ionic navigation.
|
|
92
|
-
*
|
|
93
|
-
* Wraps the per-tab stack manager and adds:
|
|
94
|
-
* - Navigation hooks (beforeNav, afterNav, onTabChange)
|
|
95
|
-
* - Transition state tracking (isTransitioning, pending)
|
|
96
|
-
* - Programmatic tab switching
|
|
97
|
-
* - Route-pattern-based cache invalidation hooks
|
|
98
|
-
*/
|
|
99
99
|
export declare class NavigationManager {
|
|
100
100
|
private _stacks;
|
|
101
101
|
private _beforeNavHooks;
|