@deijose/nix-ionic 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* 2. ion-router activa el custom element según la URL
|
|
9
9
|
* 3. ion-router-outlet gestiona: caché, animaciones, back button, swipe back
|
|
10
10
|
* 4. connectedCallback del custom element monta el componente Nix adentro
|
|
11
|
-
* 5.
|
|
11
|
+
* 5. El flag _mounted evita destruir el componente cuando ion-router-outlet
|
|
12
|
+
* desconecta el elemento temporalmente (caché). Solo se destruye si
|
|
13
|
+
* el elemento no vuelve a conectarse en los siguientes 100ms.
|
|
12
14
|
*/
|
|
13
15
|
import { NixComponent } from "@deijose/nix-js";
|
|
14
16
|
import type { NixTemplate, Signal } from "@deijose/nix-js";
|
package/dist/lib/nix-ionic.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let e=require("@deijose/nix-js");function t(){return{willEnter:(0,e.signal)(0),didEnter:(0,e.signal)(0),willLeave:(0,e.signal)(0),didLeave:(0,e.signal)(0)}}var n=class extends e.NixComponent{__lc;constructor(e){super(),this.__lc=e}onInit(){let t=this.__lc;this.ionViewWillEnter&&(0,e.watch)(t.willEnter,this.ionViewWillEnter.bind(this)),this.ionViewDidEnter&&(0,e.watch)(t.didEnter,this.ionViewDidEnter.bind(this)),this.ionViewWillLeave&&(0,e.watch)(t.willLeave,this.ionViewWillLeave.bind(this)),this.ionViewDidLeave&&(0,e.watch)(t.didLeave,this.ionViewDidLeave.bind(this))}};function r(t,n){(0,e.watch)(t.willEnter,n)}function i(t,n){(0,e.watch)(t.didEnter,n)}function a(t,n){(0,e.watch)(t.willLeave,n)}function o(t,n){(0,e.watch)(t.didLeave,n)}var s=null;function c(){if(!s)throw Error("[nix-ionic] useRouter() llamado antes de montar IonRouterOutlet");return s}function l(e){return{__isNixTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,n){let i=document.createElement("ion-back-button");i.setAttribute("default-href",e
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});let e=require("@deijose/nix-js");function t(){return{willEnter:(0,e.signal)(0),didEnter:(0,e.signal)(0),willLeave:(0,e.signal)(0),didLeave:(0,e.signal)(0)}}var n=class extends e.NixComponent{__lc;constructor(e){super(),this.__lc=e}onInit(){let t=this.__lc;this.ionViewWillEnter&&(0,e.watch)(t.willEnter,this.ionViewWillEnter.bind(this)),this.ionViewDidEnter&&(0,e.watch)(t.didEnter,this.ionViewDidEnter.bind(this)),this.ionViewWillLeave&&(0,e.watch)(t.willLeave,this.ionViewWillLeave.bind(this)),this.ionViewDidLeave&&(0,e.watch)(t.didLeave,this.ionViewDidLeave.bind(this))}};function r(t,n){(0,e.watch)(t.willEnter,n)}function i(t,n){(0,e.watch)(t.didEnter,n)}function a(t,n){(0,e.watch)(t.willLeave,n)}function o(t,n){(0,e.watch)(t.didLeave,n)}var s=null;function c(){if(!s)throw Error("[nix-ionic] useRouter() llamado antes de montar IonRouterOutlet");return s}function l(e){return{__isNixTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,n){let i=document.createElement("ion-back-button");return e&&i.setAttribute("default-href",e),t.insertBefore(i,n),()=>i.remove()}}}var u=new Map,d=class extends e.NixComponent{routes;ionRouterEl=null;_currentPath=(0,e.signal)(location.pathname);_canGoBack=(0,e.signal)(!1);_params=(0,e.signal)({});constructor(e){super(),this.routes=e,s={navigate:e=>this.navigate(e),replace:e=>this.replace(e),back:()=>this.back(),canGoBack:this._canGoBack,params:this._params,path:this._currentPath},this._registerCustomElements()}onInit(){window.addEventListener("popstate",this._handlePopState)}onMount(){this.ionRouterEl=document.querySelector("ion-router");let e=e=>{let{to:t,from:n}=e.detail;this._handleWillChange(t,n??"")},t=e=>{let{to:t}=e.detail;this._handleDidChange(t)},n=e=>{e.detail.register(100,e=>{this._canGoBack.value?this.back():e()})};return window.addEventListener("ionRouteWillChange",e),window.addEventListener("ionRouteDidChange",t),document.addEventListener("ionBackButton",n),()=>{window.removeEventListener("popstate",this._handlePopState),window.removeEventListener("ionRouteWillChange",e),window.removeEventListener("ionRouteDidChange",t),document.removeEventListener("ionBackButton",n),s=null}}navigate(e){e!==location.pathname&&this.ionRouterEl?.push(e,"forward")}replace(e){this.ionRouterEl?.push(e,"root")}back(){this.ionRouterEl?.back()}_handlePopState=()=>{this._handleWillChange(location.pathname,this._currentPath.peek())};_handleWillChange(e,t){let n=this._matchRoute(e);if(!n)return;if(n.beforeEnter){let i=n.beforeEnter(e,t);if(!1===i)return void this.back();if("string"==typeof i)return void this.navigate(i)}this._currentPath.value=e,this._params.value=this._extractParams(n.path,e),u.get(this._pathToTag(t))?.willLeave.update(e=>e+1);let i=this._pathToTag(e),a=u.get(i);a&&(a.willEnter.update(e=>e+1),a._pendingEnter=!0)}_handleDidChange(e){let t=document.querySelector("ion-nav");t&&"function"==typeof t.canGoBack?t.canGoBack().then(e=>{this._canGoBack.value=e}):this._canGoBack.value=!1,u.forEach((t,n)=>{n!==this._pathToTag(e)&&t.didLeave.update(e=>e+1)});let n=this._pathToTag(e),i=u.get(n);i&&i._pendingEnter&&(i.didEnter.update(e=>e+1),i._pendingEnter=!1)}_matchRoute(e){let t=this.routes.find(t=>t.path===e);if(t)return t;for(let t of this.routes)if(RegExp(`^${t.path.replace(/:[^/]+/g,"([^/]+)")}$`).test(e))return t;return this.routes.find(e=>"*"===e.path)}_extractParams(e,t){let n={},i=e.split("/"),a=t.split("/");for(let e=0;e<i.length;e++)i[e].startsWith(":")&&(n[i[e].slice(1)]=a[e]??"");return n}_pathToTag(e){return e&&"/"!==e?`nix-page-${e.replace(/\/:?[^/]+/g,e=>"-"+e.replace(/\//g,"").replace(/:/g,"")).replace(/^\//,"").replace(/\//g,"-")}`:"nix-page-home"}_registerCustomElements(){for(let e of this.routes){if("*"===e.path)continue;let n=this._pathToTag(e.path),i=e,a=this;customElements.get(n)||customElements.define(n,class extends HTMLElement{_cleanup=null;_lc=null;_isInitialized=!1;connectedCallback(){if(this._isInitialized)return;this._isInitialized=!0;let e=a._extractParams(i.path,location.pathname),o=t();this._lc=o,u.set(n,o),this.classList.add("ion-page");let r=i.component({lc:o,params:e});if("render"in r&&"function"==typeof r.render){let e=r;e.onInit?.();let t=e.render()._render(this,null),n=e.onMount?.();this._cleanup=()=>{e.onUnmount?.(),"function"==typeof n&&n(),t()}}else this._cleanup=r._render(this,null);o.willEnter.update(e=>e+1);let l=e=>{a._pathToTag(e.detail.to)===n&&(o.didEnter.update(e=>e+1),window.removeEventListener("ionRouteDidChange",l))};window.addEventListener("ionRouteDidChange",l)}disconnectedCallback(){let e=this._lc;setTimeout(()=>{this.isConnected||(u.delete(n),e?.willLeave.update(e=>e+1),e?.didLeave.update(e=>e+1),this._cleanup?.(),this._cleanup=null,this._lc=null,this._isInitialized=!1)},100)}})}}render(){let e=this;return{__isNixTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,n){let i=document.createElement("ion-router");i.setAttribute("use-hash","false"),i.innerHTML=e.routes.filter(e=>"*"!==e.path).map(t=>`<ion-route url="${t.path}" component="${e._pathToTag(t.path)}"></ion-route>`).join("");let a=document.createElement("ion-nav");return t.insertBefore(i,n),t.insertBefore(a,n),e.ionRouterEl=i,()=>{i.remove(),a.remove()}}}}onUnmount(){u.clear(),s=null}};exports.IonBackButton=l,exports.IonPage=n,exports.IonRouterOutlet=d,exports.createPageLifecycle=t,exports.useIonViewDidEnter=i,exports.useIonViewDidLeave=o,exports.useIonViewWillEnter=r,exports.useIonViewWillLeave=a,exports.useRouter=c;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nix-ionic.cjs","names":[],"sources":["../../src/lifecycle.ts","../../src/IonRouterOutlet.ts"],"sourcesContent":["/**\n * ionic-nix/lifecycle.ts\n *\n * Sistema de ciclo de vida de navegación, análogo a los hooks de Ionic:\n * ionViewWillEnter / ionViewDidEnter / ionViewWillLeave / ionViewDidLeave\n *\n * Cómo funciona (sin provide/inject):\n * 1. IonRouterOutlet crea un `PageLifecycle` por cada ruta.\n * 2. Lo pasa directamente al factory de la ruta como argumento.\n * 3. El factory llama a `new MiPagina(lc)` o `MiPagina(lc)`.\n * 4. IonPage/composables registran watchers sobre las señales del lc.\n * 5. Cuando el router navega, incrementa las señales → watchers se disparan.\n */\n\nimport { signal, watch } from \"@deijose/nix-js\";\nimport type { Signal } from \"@deijose/nix-js\";\nimport { NixComponent } from \"@deijose/nix-js\";\n\n// --------------------------------------------------------------------------\n// Tipos públicos\n// --------------------------------------------------------------------------\n\nexport interface PageLifecycle {\n willEnter: Signal<number>;\n didEnter: Signal<number>;\n willLeave: Signal<number>;\n didLeave: Signal<number>;\n}\n\n/** Crea un nuevo PageLifecycle con señales en 0. */\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// IonPage — clase base para páginas con hooks de navegación\n// --------------------------------------------------------------------------\n//\n// Uso:\n// class HomePage extends IonPage {\n// constructor(lc: PageLifecycle) { super(lc); }\n//\n// ionViewWillEnter() { /* fetch de datos frescos */ }\n// render() { return html`...`; }\n// }\n\nexport abstract class IonPage extends NixComponent {\n private __lc: PageLifecycle;\n\n constructor(lc: PageLifecycle) {\n super();\n this.__lc = lc;\n }\n\n override onInit(): void {\n const lc = this.__lc;\n // watch no corre en init (immediate: false), así que ionViewWillEnter\n // solo se llama cuando el outlet incrementa la señal, no al construir.\n if (this.ionViewWillEnter) watch(lc.willEnter, this.ionViewWillEnter.bind(this));\n if (this.ionViewDidEnter) watch(lc.didEnter, this.ionViewDidEnter.bind(this));\n if (this.ionViewWillLeave) watch(lc.willLeave, this.ionViewWillLeave.bind(this));\n if (this.ionViewDidLeave) watch(lc.didLeave, this.ionViewDidLeave.bind(this));\n }\n\n ionViewWillEnter?(): void;\n ionViewDidEnter?(): void;\n ionViewWillLeave?(): void;\n ionViewDidLeave?(): void;\n}\n\n// --------------------------------------------------------------------------\n// Composables para function components\n// --------------------------------------------------------------------------\n//\n// Uso:\n// function ProfilePage(lc: PageLifecycle): NixTemplate {\n// useIonViewWillEnter(lc, () => { /* fetch */ });\n// return html`...`;\n// }\n\nexport function useIonViewWillEnter(lc: PageLifecycle, fn: () => void): void {\n watch(lc.willEnter, fn);\n}\n\nexport function useIonViewDidEnter(lc: PageLifecycle, fn: () => void): void {\n watch(lc.didEnter, fn);\n}\n\nexport function useIonViewWillLeave(lc: PageLifecycle, fn: () => void): void {\n watch(lc.willLeave, fn);\n}\n\nexport function useIonViewDidLeave(lc: PageLifecycle, fn: () => void): void {\n watch(lc.didLeave, fn);\n}","/**\n * ionic-nix/IonRouterOutlet.ts\n *\n * Bridge entre Nix.js e ion-router (API pública oficial para vanilla JS).\n *\n * Arquitectura:\n * 1. Por cada ruta registramos un Custom Element (nix-page-home, etc.)\n * 2. ion-router activa el custom element según la URL\n * 3. ion-router-outlet gestiona: caché, animaciones, back button, swipe back\n * 4. connectedCallback del custom element monta el componente Nix adentro\n * 5. ionRouteWillChange / ionRouteDidChange disparan el ciclo de vida Nix\n */\n\nimport { NixComponent, signal } from \"@deijose/nix-js\";\nimport type { NixTemplate, Signal } from \"@deijose/nix-js\";\nimport { createPageLifecycle, type PageLifecycle } from \"./lifecycle\";\n\n// --------------------------------------------------------------------------\n// Router store singleton\n// --------------------------------------------------------------------------\n\ninterface RouterStore {\n navigate: (path: string) => void;\n replace: (path: string) => void;\n back: () => void;\n canGoBack: Signal<boolean>;\n params: Signal<Record<string, string>>;\n path: Signal<string>;\n}\n\nlet _router: RouterStore | null = null;\n\nexport function useRouter(): RouterStore {\n if (!_router) throw new Error(\"[nix-ionic] useRouter() llamado antes de montar IonRouterOutlet\");\n return _router;\n}\n\n// --------------------------------------------------------------------------\n// IonBackButton — intercepta el click antes del shadow DOM\n// --------------------------------------------------------------------------\n\nexport function IonBackButton(defaultHref?: string): NixTemplate {\n return {\n __isNixTemplate: true as const,\n mount(container) {\n const el = typeof container === \"string\" ? document.querySelector(container)! : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n const btn = document.createElement(\"ion-back-button\") as HTMLElement;\n btn.setAttribute(\"default-href\", defaultHref ?? \"/\");\n const handler = (e: Event) => {\n e.stopPropagation();\n e.preventDefault();\n if (_router?.canGoBack.value) _router.back();\n else if (defaultHref) _router?.navigate(defaultHref);\n };\n btn.addEventListener(\"click\", handler);\n parent.insertBefore(btn, before);\n return () => {\n btn.removeEventListener(\"click\", handler);\n btn.remove();\n };\n },\n };\n}\n\n// --------------------------------------------------------------------------\n// Tipos públicos\n// --------------------------------------------------------------------------\n\nexport interface PageContext {\n lc: PageLifecycle;\n params: Record<string, string>;\n}\n\nexport interface RouteDefinition {\n path: string;\n component: (ctx: PageContext) => NixComponent | NixTemplate;\n beforeEnter?: (to: string, from: string) => boolean | string | void;\n}\n\n// --------------------------------------------------------------------------\n// Mapa global de lifecycles activos por tag\n// --------------------------------------------------------------------------\n\nconst _lifecycleRegistry = new Map<string, PageLifecycle>();\n\n// --------------------------------------------------------------------------\n// IonRouterOutlet\n// --------------------------------------------------------------------------\n\nexport class IonRouterOutlet extends NixComponent {\n private routes: RouteDefinition[];\n private ionRouterEl: HTMLElement | null = null;\n\n private _currentPath = signal(location.pathname);\n private _canGoBack = signal(false);\n private _params = signal<Record<string, string>>({});\n\n constructor(routes: RouteDefinition[]) {\n super();\n this.routes = routes;\n\n // Store disponible desde el constructor — antes que cualquier\n // connectedCallback de custom element\n _router = {\n navigate: (path) => this.navigate(path),\n replace: (path) => this.replace(path),\n back: () => this.back(),\n canGoBack: this._canGoBack,\n params: this._params,\n path: this._currentPath,\n };\n\n this._registerCustomElements();\n }\n\n override onInit(): void {\n window.addEventListener(\"popstate\", this._handlePopState);\n }\n\n override onMount(): () => void {\n this.ionRouterEl = document.querySelector(\"ion-router\");\n\n const onWillChange = (ev: Event) => {\n const { to, from } = (ev as CustomEvent).detail as { to: string; from: string };\n this._handleWillChange(to, from ?? \"\");\n };\n const onDidChange = (ev: Event) => {\n const { to } = (ev as CustomEvent).detail as { to: string };\n this._handleDidChange(to);\n };\n const onIonBack = (ev: Event) => {\n (ev as CustomEvent).detail.register(100, (next: () => void) => {\n if (this._canGoBack.value) this.back(); else next();\n });\n };\n\n window.addEventListener(\"ionRouteWillChange\", onWillChange);\n window.addEventListener(\"ionRouteDidChange\", onDidChange);\n document.addEventListener(\"ionBackButton\", onIonBack);\n\n return () => {\n window.removeEventListener(\"popstate\", this._handlePopState);\n window.removeEventListener(\"ionRouteWillChange\", onWillChange);\n window.removeEventListener(\"ionRouteDidChange\", onDidChange);\n document.removeEventListener(\"ionBackButton\", onIonBack);\n _router = null;\n };\n }\n\n // -------------------------------------------------------------------------\n // API pública\n // -------------------------------------------------------------------------\n\n navigate(path: string): void {\n if (path === location.pathname) return;\n (this.ionRouterEl as any)?.push(path, \"forward\");\n }\n\n replace(path: string): void {\n (this.ionRouterEl as any)?.push(path, \"root\");\n }\n\n back(): void {\n (this.ionRouterEl as any)?.back();\n }\n\n // -------------------------------------------------------------------------\n // Handlers privados\n // -------------------------------------------------------------------------\n\n private _handlePopState = (): void => {\n // El browser navegó con sus propios botones — sincronizar\n this._handleWillChange(location.pathname, this._currentPath.peek());\n };\n\n private _handleWillChange(to: string, from: string): void {\n const route = this._matchRoute(to);\n if (!route) return;\n\n // Guards\n if (route.beforeEnter) {\n const result = route.beforeEnter(to, from);\n if (result === false) { this.back(); return; }\n if (typeof result === \"string\") { this.navigate(result); return; }\n }\n\n this._currentPath.value = to;\n this._params.value = this._extractParams(route.path, to);\n\n // willLeave en la vista que sale (ya tiene lc registrado)\n const fromTag = this._pathToTag(from);\n _lifecycleRegistry.get(fromTag)?.willLeave.update((n) => n + 1);\n }\n\n private _handleDidChange(to: string): void {\n const outlet = document.querySelector(\"ion-router-outlet\") as any;\n this._canGoBack.value = outlet?.canGoBack?.() ?? false;\n\n // didLeave en la vista que salió\n _lifecycleRegistry.forEach((lc, tag) => {\n if (tag !== this._pathToTag(to)) lc.didLeave.update((n) => n + 1);\n });\n\n // Para vistas CACHEADAS (ya existían en el DOM, connectedCallback no se llama de nuevo)\n // disparamos willEnter/didEnter aquí.\n // Para vistas NUEVAS, connectedCallback ya lo hizo — disparar de nuevo haría doble.\n // Distinguimos: si el tag ya estaba en el registry ANTES de este didChange,\n // es una vista cacheada. Usamos un flag en el custom element.\n const toTag = this._pathToTag(to);\n const toLc = _lifecycleRegistry.get(toTag);\n const toEl = document.querySelector(toTag) as any;\n if (toLc && toEl?._isCached) {\n toLc.willEnter.update((n) => n + 1);\n requestAnimationFrame(() => toLc.didEnter.update((n) => n + 1));\n }\n }\n\n // -------------------------------------------------------------------------\n // Helpers\n // -------------------------------------------------------------------------\n\n private _matchRoute(path: string): RouteDefinition | undefined {\n const exact = this.routes.find((r) => r.path === path);\n if (exact) return exact;\n for (const route of this.routes) {\n const re = new RegExp(`^${route.path.replace(/:[^/]+/g, \"([^/]+)\")}$`);\n if (re.test(path)) return route;\n }\n return this.routes.find((r) => r.path === \"*\");\n }\n\n private _extractParams(routePath: string, realPath: string): Record<string, string> {\n const params: Record<string, string> = {};\n const rP = routePath.split(\"/\");\n const uP = realPath.split(\"/\");\n for (let i = 0; i < rP.length; i++) {\n if (rP[i].startsWith(\":\")) params[rP[i].slice(1)] = uP[i] ?? \"\";\n }\n return params;\n }\n\n private _pathToTag(path: string): string {\n if (!path || path === \"/\") return \"nix-page-home\";\n const clean = path\n .replace(/\\/:?[^/]+/g, (m) => \"-\" + m.replace(/\\//g, \"\").replace(/:/g, \"\"))\n .replace(/^\\//, \"\")\n .replace(/\\//g, \"-\");\n return `nix-page-${clean}`;\n }\n\n // -------------------------------------------------------------------------\n // Registro de Custom Elements\n // -------------------------------------------------------------------------\n\n private _registerCustomElements(): void {\n for (const route of this.routes) {\n if (route.path === \"*\") continue;\n\n const tag = this._pathToTag(route.path);\n const routeDef = route;\n const self = this;\n\n if (customElements.get(tag)) continue;\n\n customElements.define(tag, class extends HTMLElement {\n private _cleanup: (() => void) | null = null;\n\n connectedCallback(): void {\n const params = self._extractParams(routeDef.path, location.pathname);\n const lc = createPageLifecycle();\n\n _lifecycleRegistry.set(tag, lc);\n this.classList.add(\"ion-page\");\n\n const pageNode = routeDef.component({ lc, params });\n\n if (\"render\" in pageNode && typeof (pageNode as NixComponent).render === \"function\") {\n const comp = pageNode as NixComponent;\n comp.onInit?.();\n const renderCleanup = comp.render()._render(this, null);\n const mountRet = comp.onMount?.();\n this._cleanup = () => {\n comp.onUnmount?.();\n if (typeof mountRet === \"function\") mountRet();\n renderCleanup();\n };\n } else {\n this._cleanup = (pageNode as NixTemplate)._render(this, null);\n }\n\n // Disparar willEnter/didEnter aquí — el lc ya está registrado\n // y el componente ya está inicializado con sus watchers\n lc.willEnter.update((n) => n + 1);\n requestAnimationFrame(() => {\n lc.didEnter.update((n) => n + 1);\n // Marcar como cacheado para que ionRouteDidChange pueda\n // disparar willEnter en visitas subsiguientes\n (this as any)._isCached = true;\n });\n }\n\n disconnectedCallback(): void {\n // Disparar willLeave/didLeave al salir\n const lc = _lifecycleRegistry.get(tag);\n if (lc) {\n lc.willLeave.update((n) => n + 1);\n lc.didLeave.update((n) => n + 1);\n }\n this._cleanup?.();\n this._cleanup = null;\n _lifecycleRegistry.delete(tag);\n }\n });\n }\n }\n\n // -------------------------------------------------------------------------\n // Render — crea ion-router + ion-router-outlet como elementos DOM reales\n // -------------------------------------------------------------------------\n\n override render(): NixTemplate {\n const self = this;\n return {\n __isNixTemplate: true as const,\n mount(container: Element | string): { unmount(): void } {\n const el = typeof container === \"string\"\n ? (document.querySelector(container) as Element)\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n // ion-router con las rutas declarativas\n const routerEl = document.createElement(\"ion-router\");\n routerEl.setAttribute(\"use-hash\", \"false\");\n routerEl.innerHTML = self.routes\n .filter((r) => r.path !== \"*\")\n .map((r) => `<ion-route url=\"${r.path}\" component=\"${self._pathToTag(r.path)}\"></ion-route>`)\n .join(\"\");\n\n const outletEl = document.createElement(\"ion-router-outlet\");\n\n parent.insertBefore(routerEl, before);\n parent.insertBefore(outletEl, before);\n\n self.ionRouterEl = routerEl;\n\n return () => { routerEl.remove(); outletEl.remove(); };\n },\n };\n }\n\n override onUnmount(): void {\n _lifecycleRegistry.clear();\n _router = null;\n }\n}"],"mappings":"oGA8BA,SAAgB,GAAqC,CACnD,MAAO,CACL,WAAA,EAAA,EAAA,QAAkB,EAAE,CACpB,UAAA,EAAA,EAAA,QAAkB,EAAE,CACpB,WAAA,EAAA,EAAA,QAAkB,EAAE,CACpB,UAAA,EAAA,EAAA,QAAkB,EAAE,CACrB,CAeH,IAAsB,EAAtB,cAAsC,EAAA,YAAa,CACjD,KAEA,YAAY,EAAmB,CAC7B,OAAO,CACP,KAAK,KAAO,EAGd,QAAwB,CACtB,IAAM,EAAK,KAAK,KAGZ,KAAK,mBAAkB,EAAA,EAAA,OAAM,EAAG,UAAW,KAAK,iBAAiB,KAAK,KAAK,CAAC,CAC5E,KAAK,kBAAkB,EAAA,EAAA,OAAM,EAAG,SAAW,KAAK,gBAAgB,KAAK,KAAK,CAAC,CAC3E,KAAK,mBAAkB,EAAA,EAAA,OAAM,EAAG,UAAW,KAAK,iBAAiB,KAAK,KAAK,CAAC,CAC5E,KAAK,kBAAkB,EAAA,EAAA,OAAM,EAAG,SAAW,KAAK,gBAAgB,KAAK,KAAK,CAAC,GAmBnF,SAAgB,EAAoB,EAAmB,EAAsB,EAC3E,EAAA,EAAA,OAAM,EAAG,UAAW,EAAG,CAGzB,SAAgB,EAAmB,EAAmB,EAAsB,EAC1E,EAAA,EAAA,OAAM,EAAG,SAAU,EAAG,CAGxB,SAAgB,EAAoB,EAAmB,EAAsB,EAC3E,EAAA,EAAA,OAAM,EAAG,UAAW,EAAG,CAGzB,SAAgB,EAAmB,EAAmB,EAAsB,EAC1E,EAAA,EAAA,OAAM,EAAG,SAAU,EAAG,CCpExB,IAAI,EAA8B,KAElC,SAAgB,GAAyB,CACvC,GAAI,CAAC,EAAS,MAAU,MAAM,kEAAkE,CAChG,OAAO,EAOT,SAAgB,EAAc,EAAmC,CAC/D,MAAO,CACL,gBAAiB,GACjB,MAAM,EAAW,CACf,IAAM,EAAK,OAAO,GAAc,SAAW,SAAS,cAAc,EAAU,CAAI,EAEhF,MAAO,CAAE,QADO,KAAK,QAAQ,EAAI,KAAK,CACX,EAE7B,QAAQ,EAAc,EAAiC,CACrD,IAAM,EAAM,SAAS,cAAc,kBAAkB,CACrD,EAAI,aAAa,eAAgB,GAAe,IAAI,CACpD,IAAM,EAAW,GAAa,CAC5B,EAAE,iBAAiB,CACnB,EAAE,gBAAgB,CACd,GAAS,UAAU,MAAO,EAAQ,MAAM,CACnC,GAAa,GAAS,SAAS,EAAY,EAItD,OAFA,EAAI,iBAAiB,QAAS,EAAQ,CACtC,EAAO,aAAa,EAAK,EAAO,KACnB,CACX,EAAI,oBAAoB,QAAS,EAAQ,CACzC,EAAI,QAAQ,GAGjB,CAsBH,IAAM,EAAqB,IAAI,IAMlB,EAAb,cAAqC,EAAA,YAAa,CAChD,OACA,YAA0C,KAE1C,cAAA,EAAA,EAAA,QAA8B,SAAS,SAAS,CAChD,YAAA,EAAA,EAAA,QAA8B,GAAM,CACpC,SAAA,EAAA,EAAA,QAAsD,EAAE,CAAC,CAEzD,YAAY,EAA2B,CACrC,OAAO,CACP,KAAK,OAAS,EAId,EAAU,CACR,SAAY,GAAS,KAAK,SAAS,EAAK,CACxC,QAAY,GAAS,KAAK,QAAQ,EAAK,CACvC,SAAqB,KAAK,MAAM,CAChC,UAAW,KAAK,WAChB,OAAW,KAAK,QAChB,KAAW,KAAK,aACjB,CAED,KAAK,yBAAyB,CAGhC,QAAwB,CACtB,OAAO,iBAAiB,WAAY,KAAK,gBAAgB,CAG3D,SAA+B,CAC7B,KAAK,YAAc,SAAS,cAAc,aAAa,CAEvD,IAAM,EAAgB,GAAc,CAClC,GAAM,CAAE,KAAI,QAAU,EAAmB,OACzC,KAAK,kBAAkB,EAAI,GAAQ,GAAG,EAElC,EAAe,GAAc,CACjC,GAAM,CAAE,MAAQ,EAAmB,OACnC,KAAK,iBAAiB,EAAG,EAErB,EAAa,GAAc,CAC9B,EAAmB,OAAO,SAAS,IAAM,GAAqB,CACzD,KAAK,WAAW,MAAO,KAAK,MAAM,CAAO,GAAM,EACnD,EAOJ,OAJA,OAAO,iBAAiB,qBAAsB,EAAa,CAC3D,OAAO,iBAAiB,oBAAsB,EAAY,CAC1D,SAAS,iBAAiB,gBAAoB,EAAU,KAE3C,CACX,OAAO,oBAAoB,WAAqB,KAAK,gBAAgB,CACrE,OAAO,oBAAoB,qBAAsB,EAAa,CAC9D,OAAO,oBAAoB,oBAAsB,EAAY,CAC7D,SAAS,oBAAoB,gBAAoB,EAAU,CAC3D,EAAU,MAQd,SAAS,EAAoB,CACvB,IAAS,SAAS,UACrB,KAAK,aAAqB,KAAK,EAAM,UAAU,CAGlD,QAAQ,EAAoB,CACzB,KAAK,aAAqB,KAAK,EAAM,OAAO,CAG/C,MAAa,CACV,KAAK,aAAqB,MAAM,CAOnC,oBAAsC,CAEpC,KAAK,kBAAkB,SAAS,SAAU,KAAK,aAAa,MAAM,CAAC,EAGrE,kBAA0B,EAAY,EAAoB,CACxD,IAAM,EAAQ,KAAK,YAAY,EAAG,CAClC,GAAI,CAAC,EAAO,OAGZ,GAAI,EAAM,YAAa,CACrB,IAAM,EAAS,EAAM,YAAY,EAAI,EAAK,CAC1C,GAAI,IAAW,GAAO,CAAE,KAAK,MAAM,CAAE,OACrC,GAAI,OAAO,GAAW,SAAU,CAAE,KAAK,SAAS,EAAO,CAAE,QAG3D,KAAK,aAAa,MAAQ,EAC1B,KAAK,QAAQ,MAAa,KAAK,eAAe,EAAM,KAAM,EAAG,CAG7D,IAAM,EAAU,KAAK,WAAW,EAAK,CACrC,EAAmB,IAAI,EAAQ,EAAE,UAAU,OAAQ,GAAM,EAAI,EAAE,CAGjE,iBAAyB,EAAkB,CACzC,IAAM,EAAS,SAAS,cAAc,oBAAoB,CAC1D,KAAK,WAAW,MAAQ,GAAQ,aAAa,EAAI,GAGjD,EAAmB,SAAS,EAAI,IAAQ,CAClC,IAAQ,KAAK,WAAW,EAAG,EAAE,EAAG,SAAS,OAAQ,GAAM,EAAI,EAAE,EACjE,CAOF,IAAM,EAAS,KAAK,WAAW,EAAG,CAC5B,EAAS,EAAmB,IAAI,EAAM,CACtC,EAAS,SAAS,cAAc,EAAM,CACxC,GAAQ,GAAM,YAChB,EAAK,UAAU,OAAQ,GAAM,EAAI,EAAE,CACnC,0BAA4B,EAAK,SAAS,OAAQ,GAAM,EAAI,EAAE,CAAC,EAQnE,YAAoB,EAA2C,CAC7D,IAAM,EAAQ,KAAK,OAAO,KAAM,GAAM,EAAE,OAAS,EAAK,CACtD,GAAI,EAAO,OAAO,EAClB,IAAK,IAAM,KAAS,KAAK,OAEvB,GADe,OAAO,IAAI,EAAM,KAAK,QAAQ,UAAW,UAAU,CAAC,GAAG,CAC/D,KAAK,EAAK,CAAE,OAAO,EAE5B,OAAO,KAAK,OAAO,KAAM,GAAM,EAAE,OAAS,IAAI,CAGhD,eAAuB,EAAmB,EAA0C,CAClF,IAAM,EAAiC,EAAE,CACnC,EAAK,EAAU,MAAM,IAAI,CACzB,EAAK,EAAS,MAAM,IAAI,CAC9B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,OAAQ,IACzB,EAAG,GAAG,WAAW,IAAI,GAAE,EAAO,EAAG,GAAG,MAAM,EAAE,EAAI,EAAG,IAAM,IAE/D,OAAO,EAGT,WAAmB,EAAsB,CAMvC,MALI,CAAC,GAAQ,IAAS,IAAY,gBAK3B,YAJO,EACX,QAAQ,aAAe,GAAM,IAAM,EAAE,QAAQ,MAAO,GAAG,CAAC,QAAQ,KAAM,GAAG,CAAC,CAC1E,QAAQ,MAAO,GAAG,CAClB,QAAQ,MAAO,IAAI,GAQxB,yBAAwC,CACtC,IAAK,IAAM,KAAS,KAAK,OAAQ,CAC/B,GAAI,EAAM,OAAS,IAAK,SAExB,IAAM,EAAW,KAAK,WAAW,EAAM,KAAK,CACtC,EAAW,EACX,EAAW,KAEb,eAAe,IAAI,EAAI,EAE3B,eAAe,OAAO,EAAK,cAAc,WAAY,CACnD,SAAwC,KAExC,mBAA0B,CACxB,IAAM,EAAS,EAAK,eAAe,EAAS,KAAM,SAAS,SAAS,CAC9D,EAAS,GAAqB,CAEpC,EAAmB,IAAI,EAAK,EAAG,CAC/B,KAAK,UAAU,IAAI,WAAW,CAE9B,IAAM,EAAW,EAAS,UAAU,CAAE,KAAI,SAAQ,CAAC,CAEnD,GAAI,WAAY,GAAY,OAAQ,EAA0B,QAAW,WAAY,CACnF,IAAM,EAAO,EACb,EAAK,UAAU,CACf,IAAM,EAAgB,EAAK,QAAQ,CAAC,QAAQ,KAAM,KAAK,CACjD,EAAgB,EAAK,WAAW,CACtC,KAAK,aAAiB,CACpB,EAAK,aAAa,CACd,OAAO,GAAa,YAAY,GAAU,CAC9C,GAAe,OAGjB,KAAK,SAAY,EAAyB,QAAQ,KAAM,KAAK,CAK/D,EAAG,UAAU,OAAQ,GAAM,EAAI,EAAE,CACjC,0BAA4B,CAC1B,EAAG,SAAS,OAAQ,GAAM,EAAI,EAAE,CAG/B,KAAa,UAAY,IAC1B,CAGJ,sBAA6B,CAE3B,IAAM,EAAK,EAAmB,IAAI,EAAI,CAClC,IACF,EAAG,UAAU,OAAQ,GAAM,EAAI,EAAE,CACjC,EAAG,SAAS,OAAQ,GAAM,EAAI,EAAE,EAElC,KAAK,YAAY,CACjB,KAAK,SAAW,KAChB,EAAmB,OAAO,EAAI,GAEhC,EAQN,QAA+B,CAC7B,IAAM,EAAO,KACb,MAAO,CACL,gBAAiB,GACjB,MAAM,EAAkD,CACtD,IAAM,EAAK,OAAO,GAAc,SAC3B,SAAS,cAAc,EAAU,CAClC,EAEJ,MAAO,CAAE,QADO,KAAK,QAAQ,EAAI,KAAK,CACX,EAE7B,QAAQ,EAAc,EAAiC,CAErD,IAAM,EAAW,SAAS,cAAc,aAAa,CACrD,EAAS,aAAa,WAAY,QAAQ,CAC1C,EAAS,UAAY,EAAK,OACvB,OAAQ,GAAM,EAAE,OAAS,IAAI,CAC7B,IAAK,GAAM,mBAAmB,EAAE,KAAK,eAAe,EAAK,WAAW,EAAE,KAAK,CAAC,gBAAgB,CAC5F,KAAK,GAAG,CAEX,IAAM,EAAW,SAAS,cAAc,oBAAoB,CAO5D,OALA,EAAO,aAAa,EAAU,EAAO,CACrC,EAAO,aAAa,EAAU,EAAO,CAErC,EAAK,YAAc,MAEN,CAAE,EAAS,QAAQ,CAAE,EAAS,QAAQ,GAEtD,CAGH,WAA2B,CACzB,EAAmB,OAAO,CAC1B,EAAU"}
|
|
1
|
+
{"version":3,"file":"nix-ionic.cjs","names":[],"sources":["../../src/lifecycle.ts","../../src/IonRouterOutlet.ts"],"sourcesContent":["/**\n * ionic-nix/lifecycle.ts\n *\n * Sistema de ciclo de vida de navegación, análogo a los hooks de Ionic:\n * ionViewWillEnter / ionViewDidEnter / ionViewWillLeave / ionViewDidLeave\n *\n * Cómo funciona (sin provide/inject):\n * 1. IonRouterOutlet crea un `PageLifecycle` por cada ruta.\n * 2. Lo pasa directamente al factory de la ruta como argumento.\n * 3. El factory llama a `new MiPagina(lc)` o `MiPagina(lc)`.\n * 4. IonPage/composables registran watchers sobre las señales del lc.\n * 5. Cuando el router navega, incrementa las señales → watchers se disparan.\n */\n\nimport { signal, watch } from \"@deijose/nix-js\";\nimport type { Signal } from \"@deijose/nix-js\";\nimport { NixComponent } from \"@deijose/nix-js\";\n\n// --------------------------------------------------------------------------\n// Tipos públicos\n// --------------------------------------------------------------------------\n\nexport interface PageLifecycle {\n willEnter: Signal<number>;\n didEnter: Signal<number>;\n willLeave: Signal<number>;\n didLeave: Signal<number>;\n}\n\n/** Crea un nuevo PageLifecycle con señales en 0. */\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// IonPage — clase base para páginas con hooks de navegación\n// --------------------------------------------------------------------------\n//\n// Uso:\n// class HomePage extends IonPage {\n// constructor(lc: PageLifecycle) { super(lc); }\n//\n// ionViewWillEnter() { /* fetch de datos frescos */ }\n// render() { return html`...`; }\n// }\n\nexport abstract class IonPage extends NixComponent {\n private __lc: PageLifecycle;\n\n constructor(lc: PageLifecycle) {\n super();\n this.__lc = lc;\n }\n\n override onInit(): void {\n const lc = this.__lc;\n // watch no corre en init (immediate: false), así que ionViewWillEnter\n // solo se llama cuando el outlet incrementa la señal, no al construir.\n if (this.ionViewWillEnter) watch(lc.willEnter, this.ionViewWillEnter.bind(this));\n if (this.ionViewDidEnter) watch(lc.didEnter, this.ionViewDidEnter.bind(this));\n if (this.ionViewWillLeave) watch(lc.willLeave, this.ionViewWillLeave.bind(this));\n if (this.ionViewDidLeave) watch(lc.didLeave, this.ionViewDidLeave.bind(this));\n }\n\n ionViewWillEnter?(): void;\n ionViewDidEnter?(): void;\n ionViewWillLeave?(): void;\n ionViewDidLeave?(): void;\n}\n\n// --------------------------------------------------------------------------\n// Composables para function components\n// --------------------------------------------------------------------------\n//\n// Uso:\n// function ProfilePage(lc: PageLifecycle): NixTemplate {\n// useIonViewWillEnter(lc, () => { /* fetch */ });\n// return html`...`;\n// }\n\nexport function useIonViewWillEnter(lc: PageLifecycle, fn: () => void): void {\n watch(lc.willEnter, fn);\n}\n\nexport function useIonViewDidEnter(lc: PageLifecycle, fn: () => void): void {\n watch(lc.didEnter, fn);\n}\n\nexport function useIonViewWillLeave(lc: PageLifecycle, fn: () => void): void {\n watch(lc.willLeave, fn);\n}\n\nexport function useIonViewDidLeave(lc: PageLifecycle, fn: () => void): void {\n watch(lc.didLeave, fn);\n}","/**\n * ionic-nix/IonRouterOutlet.ts\n *\n * Bridge entre Nix.js e ion-router (API pública oficial para vanilla JS).\n *\n * Arquitectura:\n * 1. Por cada ruta registramos un Custom Element (nix-page-home, etc.)\n * 2. ion-router activa el custom element según la URL\n * 3. ion-router-outlet gestiona: caché, animaciones, back button, swipe back\n * 4. connectedCallback del custom element monta el componente Nix adentro\n * 5. El flag _mounted evita destruir el componente cuando ion-router-outlet\n * desconecta el elemento temporalmente (caché). Solo se destruye si\n * el elemento no vuelve a conectarse en los siguientes 100ms.\n */\n\nimport { NixComponent, signal } from \"@deijose/nix-js\";\nimport type { NixTemplate, Signal } from \"@deijose/nix-js\";\nimport { createPageLifecycle, type PageLifecycle } from \"./lifecycle\";\n\n// --------------------------------------------------------------------------\n// Router store singleton\n// --------------------------------------------------------------------------\n\ninterface RouterStore {\n navigate: (path: string) => void;\n replace: (path: string) => void;\n back: () => void;\n canGoBack: Signal<boolean>;\n params: Signal<Record<string, string>>;\n path: Signal<string>;\n}\n\nlet _router: RouterStore | null = null;\n\nexport function useRouter(): RouterStore {\n if (!_router) throw new Error(\"[nix-ionic] useRouter() llamado antes de montar IonRouterOutlet\");\n return _router;\n}\n\n// --------------------------------------------------------------------------\n// IonBackButton\n// --------------------------------------------------------------------------\n\nexport function IonBackButton(defaultHref?: string): NixTemplate {\n return {\n __isNixTemplate: true as const,\n mount(container) {\n const el = typeof container === \"string\"\n ? document.querySelector(container)! as Element\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n const btn = document.createElement(\"ion-back-button\") as HTMLElement;\n \n // Solo asigna si existe. Si no, Ionic lo oculta de forma inteligente.\n if (defaultHref) {\n btn.setAttribute(\"default-href\", defaultHref);\n }\n\n parent.insertBefore(btn, before);\n return () => btn.remove();\n },\n };\n}\n\n// --------------------------------------------------------------------------\n// Tipos públicos\n// --------------------------------------------------------------------------\n\nexport interface PageContext {\n lc: PageLifecycle;\n params: Record<string, string>;\n}\n\nexport interface RouteDefinition {\n path: string;\n component: (ctx: PageContext) => NixComponent | NixTemplate;\n beforeEnter?: (to: string, from: string) => boolean | string | void;\n}\n\n// --------------------------------------------------------------------------\n// Mapa global de lifecycles activos por tag\n// --------------------------------------------------------------------------\n\nconst _lifecycleRegistry = new Map<string, PageLifecycle>();\n\n// --------------------------------------------------------------------------\n// IonRouterOutlet\n// --------------------------------------------------------------------------\n\nexport class IonRouterOutlet extends NixComponent {\n private routes: RouteDefinition[];\n private ionRouterEl: HTMLElement | null = null;\n\n private _currentPath = signal(location.pathname);\n private _canGoBack = signal(false);\n private _params = signal<Record<string, string>>({});\n\n constructor(routes: RouteDefinition[]) {\n super();\n this.routes = routes;\n\n _router = {\n navigate: (path) => this.navigate(path),\n replace: (path) => this.replace(path),\n back: () => this.back(),\n canGoBack: this._canGoBack,\n params: this._params,\n path: this._currentPath,\n };\n\n this._registerCustomElements();\n }\n\n override onInit(): void {\n window.addEventListener(\"popstate\", this._handlePopState);\n }\n\n override onMount(): () => void {\n this.ionRouterEl = document.querySelector(\"ion-router\");\n\n const onWillChange = (ev: Event) => {\n const { to, from } = (ev as CustomEvent).detail as { to: string; from: string };\n this._handleWillChange(to, from ?? \"\");\n };\n const onDidChange = (ev: Event) => {\n const { to } = (ev as CustomEvent).detail as { to: string };\n this._handleDidChange(to);\n };\n const onIonBack = (ev: Event) => {\n (ev as CustomEvent).detail.register(100, (next: () => void) => {\n if (this._canGoBack.value) this.back(); else next();\n });\n };\n\n window.addEventListener(\"ionRouteWillChange\", onWillChange);\n window.addEventListener(\"ionRouteDidChange\", onDidChange);\n document.addEventListener(\"ionBackButton\", onIonBack);\n\n return () => {\n window.removeEventListener(\"popstate\", this._handlePopState);\n window.removeEventListener(\"ionRouteWillChange\", onWillChange);\n window.removeEventListener(\"ionRouteDidChange\", onDidChange);\n document.removeEventListener(\"ionBackButton\", onIonBack);\n _router = null;\n };\n }\n\n // -------------------------------------------------------------------------\n // API pública\n // -------------------------------------------------------------------------\n\n navigate(path: string): void {\n if (path === location.pathname) return;\n (this.ionRouterEl as any)?.push(path, \"forward\");\n }\n\n replace(path: string): void {\n (this.ionRouterEl as any)?.push(path, \"root\");\n }\n\n back(): void {\n (this.ionRouterEl as any)?.back();\n }\n\n // -------------------------------------------------------------------------\n // Handlers privados\n // -------------------------------------------------------------------------\n\n private _handlePopState = (): void => {\n this._handleWillChange(location.pathname, this._currentPath.peek());\n };\n\n private _handleWillChange(to: string, from: string): void {\n const route = this._matchRoute(to);\n if (!route) return;\n\n if (route.beforeEnter) {\n const result = route.beforeEnter(to, from);\n if (result === false) { this.back(); return; }\n if (typeof result === \"string\") { this.navigate(result); return; }\n }\n\n this._currentPath.value = to;\n this._params.value = this._extractParams(route.path, to);\n\n // Despide a la vista que sale\n _lifecycleRegistry.get(this._pathToTag(from))?.willLeave.update((n) => n + 1);\n\n // Si la vista a la que vamos ya existe en el registro, significa que ESTÁ EN CACHÉ.\n const toTag = this._pathToTag(to);\n const toLc = _lifecycleRegistry.get(toTag);\n if (toLc) {\n toLc.willEnter.update((n) => n + 1);\n (toLc as any)._pendingEnter = true; // Flag para que el didChange dispare el didEnter\n }\n }\n\n private _handleDidChange(to: string): void {\n const nav = document.querySelector(\"ion-nav\") as any;\n \n // ion-nav.canGoBack() retorna una Promesa, la resolvemos para tu señal\n if (nav && typeof nav.canGoBack === \"function\") {\n nav.canGoBack().then((res: boolean) => {\n this._canGoBack.value = res;\n });\n } else {\n this._canGoBack.value = false;\n }\n\n _lifecycleRegistry.forEach((lc, tag) => {\n if (tag !== this._pathToTag(to)) lc.didLeave.update((n) => n + 1);\n });\n\n // Avisa a la vista cacheada que la transición ya terminó\n const toTag = this._pathToTag(to);\n const toLc = _lifecycleRegistry.get(toTag);\n if (toLc && (toLc as any)._pendingEnter) {\n toLc.didEnter.update((n) => n + 1);\n (toLc as any)._pendingEnter = false;\n }\n }\n\n // -------------------------------------------------------------------------\n // Helpers\n // -------------------------------------------------------------------------\n\n private _matchRoute(path: string): RouteDefinition | undefined {\n const exact = this.routes.find((r) => r.path === path);\n if (exact) return exact;\n for (const route of this.routes) {\n const re = new RegExp(`^${route.path.replace(/:[^/]+/g, \"([^/]+)\")}$`);\n if (re.test(path)) return route;\n }\n return this.routes.find((r) => r.path === \"*\");\n }\n\n private _extractParams(routePath: string, realPath: string): Record<string, string> {\n const params: Record<string, string> = {};\n const rP = routePath.split(\"/\");\n const uP = realPath.split(\"/\");\n for (let i = 0; i < rP.length; i++) {\n if (rP[i].startsWith(\":\")) params[rP[i].slice(1)] = uP[i] ?? \"\";\n }\n return params;\n }\n\n private _pathToTag(path: string): string {\n if (!path || path === \"/\") return \"nix-page-home\";\n const clean = path\n .replace(/\\/:?[^/]+/g, (m) => \"-\" + m.replace(/\\//g, \"\").replace(/:/g, \"\"))\n .replace(/^\\//, \"\")\n .replace(/\\//g, \"-\");\n return `nix-page-${clean}`;\n }\n\n // -------------------------------------------------------------------------\n // Registro de Custom Elements\n // -------------------------------------------------------------------------\n\n private _registerCustomElements(): void {\n for (const route of this.routes) {\n if (route.path === \"*\") continue;\n\n const tag = this._pathToTag(route.path);\n const routeDef = route;\n const self = this;\n\n if (customElements.get(tag)) continue;\n\n customElements.define(tag, class extends HTMLElement {\n private _cleanup: (() => void) | null = null;\n private _lc: PageLifecycle | null = null;\n private _isInitialized = false; // Solo necesitamos este flag\n\n connectedCallback(): void {\n if (this._isInitialized) return;\n this._isInitialized = true;\n\n const params = self._extractParams(routeDef.path, location.pathname);\n const lc = createPageLifecycle();\n this._lc = lc;\n\n _lifecycleRegistry.set(tag, lc);\n this.classList.add(\"ion-page\");\n\n const pageNode = routeDef.component({ lc, params });\n\n if (\"render\" in pageNode && typeof (pageNode as NixComponent).render === \"function\") {\n const comp = pageNode as NixComponent;\n comp.onInit?.();\n const renderCleanup = comp.render()._render(this, null);\n const mountRet = comp.onMount?.();\n this._cleanup = () => {\n comp.onUnmount?.();\n if (typeof mountRet === \"function\") mountRet();\n renderCleanup();\n };\n } else {\n this._cleanup = (pageNode as NixTemplate)._render(this, null);\n }\n\n // Disparar hooks de la primera vez\n lc.willEnter.update((n) => n + 1);\n \n // Esperar al cambio de ruta para disparar didEnter de forma segura\n const onReady = (e: Event) => {\n if (self._pathToTag((e as CustomEvent).detail.to) === tag) {\n lc.didEnter.update((n) => n + 1);\n window.removeEventListener(\"ionRouteDidChange\", onReady);\n }\n };\n window.addEventListener(\"ionRouteDidChange\", onReady);\n }\n\n disconnectedCallback(): void {\n const lc = this._lc;\n \n setTimeout(() => {\n // Si después de 100ms sigue conectado, fue solo una animación de Ionic\n if (this.isConnected) return; \n\n // Destrucción real (se sacó del stack)\n _lifecycleRegistry.delete(tag);\n lc?.willLeave.update((n) => n + 1);\n lc?.didLeave.update((n) => n + 1);\n this._cleanup?.();\n this._cleanup = null;\n this._lc = null;\n this._isInitialized = false; // Permite que se recree en el futuro\n }, 100);\n }\n });\n }\n }\n\n // -------------------------------------------------------------------------\n // Render\n // -------------------------------------------------------------------------\n\n override render(): NixTemplate {\n const self = this;\n return {\n __isNixTemplate: true as const,\n mount(container: Element | string): { unmount(): void } {\n const el = typeof container === \"string\"\n ? (document.querySelector(container) as Element)\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n const routerEl = document.createElement(\"ion-router\");\n routerEl.setAttribute(\"use-hash\", \"false\");\n routerEl.innerHTML = self.routes\n .filter((r) => r.path !== \"*\")\n .map((r) => `<ion-route url=\"${r.path}\" component=\"${self._pathToTag(r.path)}\"></ion-route>`)\n .join(\"\");\n\n // 🚀 EL FIX MÁGICO: ion-nav mantiene los nodos en el DOM nativamente\n const navEl = document.createElement(\"ion-nav\");\n\n parent.insertBefore(routerEl, before);\n parent.insertBefore(navEl, before);\n\n self.ionRouterEl = routerEl;\n\n return () => { routerEl.remove(); navEl.remove(); };\n },\n };\n }\n\n override onUnmount(): void {\n _lifecycleRegistry.clear();\n _router = null;\n }\n}"],"mappings":"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"}
|
package/dist/lib/nix-ionic.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{NixComponent as e,signal as t,watch as n}from"@deijose/nix-js";function r(){return{willEnter:t(0),didEnter:t(0),willLeave:t(0),didLeave:t(0)}}var i=class extends e{__lc;constructor(e){super(),this.__lc=e}onInit(){let e=this.__lc;this.ionViewWillEnter&&n(e.willEnter,this.ionViewWillEnter.bind(this)),this.ionViewDidEnter&&n(e.didEnter,this.ionViewDidEnter.bind(this)),this.ionViewWillLeave&&n(e.willLeave,this.ionViewWillLeave.bind(this)),this.ionViewDidLeave&&n(e.didLeave,this.ionViewDidLeave.bind(this))}};function a(e,t){n(e.willEnter,t)}function o(e,t){n(e.didEnter,t)}function s(e,t){n(e.willLeave,t)}function c(e,t){n(e.didLeave,t)}var l=null;function u(){if(!l)throw Error("[nix-ionic] useRouter() llamado antes de montar IonRouterOutlet");return l}function d(e){return{__isNixTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,n){let i=document.createElement("ion-back-button");i.setAttribute("default-href",e
|
|
1
|
+
import{NixComponent as e,signal as t,watch as n}from"@deijose/nix-js";function r(){return{willEnter:t(0),didEnter:t(0),willLeave:t(0),didLeave:t(0)}}var i=class extends e{__lc;constructor(e){super(),this.__lc=e}onInit(){let e=this.__lc;this.ionViewWillEnter&&n(e.willEnter,this.ionViewWillEnter.bind(this)),this.ionViewDidEnter&&n(e.didEnter,this.ionViewDidEnter.bind(this)),this.ionViewWillLeave&&n(e.willLeave,this.ionViewWillLeave.bind(this)),this.ionViewDidLeave&&n(e.didLeave,this.ionViewDidLeave.bind(this))}};function a(e,t){n(e.willEnter,t)}function o(e,t){n(e.didEnter,t)}function s(e,t){n(e.willLeave,t)}function c(e,t){n(e.didLeave,t)}var l=null;function u(){if(!l)throw Error("[nix-ionic] useRouter() llamado antes de montar IonRouterOutlet");return l}function d(e){return{__isNixTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,n){let i=document.createElement("ion-back-button");return e&&i.setAttribute("default-href",e),t.insertBefore(i,n),()=>i.remove()}}}var f=new Map,p=class extends e{routes;ionRouterEl=null;_currentPath=t(location.pathname);_canGoBack=t(!1);_params=t({});constructor(e){super(),this.routes=e,l={navigate:e=>this.navigate(e),replace:e=>this.replace(e),back:()=>this.back(),canGoBack:this._canGoBack,params:this._params,path:this._currentPath},this._registerCustomElements()}onInit(){window.addEventListener("popstate",this._handlePopState)}onMount(){this.ionRouterEl=document.querySelector("ion-router");let e=e=>{let{to:t,from:n}=e.detail;this._handleWillChange(t,n??"")},t=e=>{let{to:t}=e.detail;this._handleDidChange(t)},n=e=>{e.detail.register(100,e=>{this._canGoBack.value?this.back():e()})};return window.addEventListener("ionRouteWillChange",e),window.addEventListener("ionRouteDidChange",t),document.addEventListener("ionBackButton",n),()=>{window.removeEventListener("popstate",this._handlePopState),window.removeEventListener("ionRouteWillChange",e),window.removeEventListener("ionRouteDidChange",t),document.removeEventListener("ionBackButton",n),l=null}}navigate(e){e!==location.pathname&&this.ionRouterEl?.push(e,"forward")}replace(e){this.ionRouterEl?.push(e,"root")}back(){this.ionRouterEl?.back()}_handlePopState=()=>{this._handleWillChange(location.pathname,this._currentPath.peek())};_handleWillChange(e,t){let n=this._matchRoute(e);if(!n)return;if(n.beforeEnter){let i=n.beforeEnter(e,t);if(!1===i)return void this.back();if("string"==typeof i)return void this.navigate(i)}this._currentPath.value=e,this._params.value=this._extractParams(n.path,e),f.get(this._pathToTag(t))?.willLeave.update(e=>e+1);let i=this._pathToTag(e),a=f.get(i);a&&(a.willEnter.update(e=>e+1),a._pendingEnter=!0)}_handleDidChange(e){let t=document.querySelector("ion-nav");t&&"function"==typeof t.canGoBack?t.canGoBack().then(e=>{this._canGoBack.value=e}):this._canGoBack.value=!1,f.forEach((t,n)=>{n!==this._pathToTag(e)&&t.didLeave.update(e=>e+1)});let n=this._pathToTag(e),i=f.get(n);i&&i._pendingEnter&&(i.didEnter.update(e=>e+1),i._pendingEnter=!1)}_matchRoute(e){let t=this.routes.find(t=>t.path===e);if(t)return t;for(let t of this.routes)if(RegExp(`^${t.path.replace(/:[^/]+/g,"([^/]+)")}$`).test(e))return t;return this.routes.find(e=>"*"===e.path)}_extractParams(e,t){let n={},i=e.split("/"),a=t.split("/");for(let e=0;e<i.length;e++)i[e].startsWith(":")&&(n[i[e].slice(1)]=a[e]??"");return n}_pathToTag(e){return e&&"/"!==e?`nix-page-${e.replace(/\/:?[^/]+/g,e=>"-"+e.replace(/\//g,"").replace(/:/g,"")).replace(/^\//,"").replace(/\//g,"-")}`:"nix-page-home"}_registerCustomElements(){for(let e of this.routes){if("*"===e.path)continue;let t=this._pathToTag(e.path),n=e,i=this;customElements.get(t)||customElements.define(t,class extends HTMLElement{_cleanup=null;_lc=null;_isInitialized=!1;connectedCallback(){if(this._isInitialized)return;this._isInitialized=!0;let e=i._extractParams(n.path,location.pathname),a=r();this._lc=a,f.set(t,a),this.classList.add("ion-page");let o=n.component({lc:a,params:e});if("render"in o&&"function"==typeof o.render){let e=o;e.onInit?.();let t=e.render()._render(this,null),n=e.onMount?.();this._cleanup=()=>{e.onUnmount?.(),"function"==typeof n&&n(),t()}}else this._cleanup=o._render(this,null);a.willEnter.update(e=>e+1);let l=e=>{i._pathToTag(e.detail.to)===t&&(a.didEnter.update(e=>e+1),window.removeEventListener("ionRouteDidChange",l))};window.addEventListener("ionRouteDidChange",l)}disconnectedCallback(){let e=this._lc;setTimeout(()=>{this.isConnected||(f.delete(t),e?.willLeave.update(e=>e+1),e?.didLeave.update(e=>e+1),this._cleanup?.(),this._cleanup=null,this._lc=null,this._isInitialized=!1)},100)}})}}render(){let e=this;return{__isNixTemplate:!0,mount(e){let t="string"==typeof e?document.querySelector(e):e;return{unmount:this._render(t,null)}},_render(t,n){let i=document.createElement("ion-router");i.setAttribute("use-hash","false"),i.innerHTML=e.routes.filter(e=>"*"!==e.path).map(t=>`<ion-route url="${t.path}" component="${e._pathToTag(t.path)}"></ion-route>`).join("");let a=document.createElement("ion-nav");return t.insertBefore(i,n),t.insertBefore(a,n),e.ionRouterEl=i,()=>{i.remove(),a.remove()}}}}onUnmount(){f.clear(),l=null}};export{d as IonBackButton,i as IonPage,p as IonRouterOutlet,r as createPageLifecycle,o as useIonViewDidEnter,c as useIonViewDidLeave,a as useIonViewWillEnter,s as useIonViewWillLeave,u as useRouter};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nix-ionic.js","names":[],"sources":["../../src/lifecycle.ts","../../src/IonRouterOutlet.ts"],"sourcesContent":["/**\n * ionic-nix/lifecycle.ts\n *\n * Sistema de ciclo de vida de navegación, análogo a los hooks de Ionic:\n * ionViewWillEnter / ionViewDidEnter / ionViewWillLeave / ionViewDidLeave\n *\n * Cómo funciona (sin provide/inject):\n * 1. IonRouterOutlet crea un `PageLifecycle` por cada ruta.\n * 2. Lo pasa directamente al factory de la ruta como argumento.\n * 3. El factory llama a `new MiPagina(lc)` o `MiPagina(lc)`.\n * 4. IonPage/composables registran watchers sobre las señales del lc.\n * 5. Cuando el router navega, incrementa las señales → watchers se disparan.\n */\n\nimport { signal, watch } from \"@deijose/nix-js\";\nimport type { Signal } from \"@deijose/nix-js\";\nimport { NixComponent } from \"@deijose/nix-js\";\n\n// --------------------------------------------------------------------------\n// Tipos públicos\n// --------------------------------------------------------------------------\n\nexport interface PageLifecycle {\n willEnter: Signal<number>;\n didEnter: Signal<number>;\n willLeave: Signal<number>;\n didLeave: Signal<number>;\n}\n\n/** Crea un nuevo PageLifecycle con señales en 0. */\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// IonPage — clase base para páginas con hooks de navegación\n// --------------------------------------------------------------------------\n//\n// Uso:\n// class HomePage extends IonPage {\n// constructor(lc: PageLifecycle) { super(lc); }\n//\n// ionViewWillEnter() { /* fetch de datos frescos */ }\n// render() { return html`...`; }\n// }\n\nexport abstract class IonPage extends NixComponent {\n private __lc: PageLifecycle;\n\n constructor(lc: PageLifecycle) {\n super();\n this.__lc = lc;\n }\n\n override onInit(): void {\n const lc = this.__lc;\n // watch no corre en init (immediate: false), así que ionViewWillEnter\n // solo se llama cuando el outlet incrementa la señal, no al construir.\n if (this.ionViewWillEnter) watch(lc.willEnter, this.ionViewWillEnter.bind(this));\n if (this.ionViewDidEnter) watch(lc.didEnter, this.ionViewDidEnter.bind(this));\n if (this.ionViewWillLeave) watch(lc.willLeave, this.ionViewWillLeave.bind(this));\n if (this.ionViewDidLeave) watch(lc.didLeave, this.ionViewDidLeave.bind(this));\n }\n\n ionViewWillEnter?(): void;\n ionViewDidEnter?(): void;\n ionViewWillLeave?(): void;\n ionViewDidLeave?(): void;\n}\n\n// --------------------------------------------------------------------------\n// Composables para function components\n// --------------------------------------------------------------------------\n//\n// Uso:\n// function ProfilePage(lc: PageLifecycle): NixTemplate {\n// useIonViewWillEnter(lc, () => { /* fetch */ });\n// return html`...`;\n// }\n\nexport function useIonViewWillEnter(lc: PageLifecycle, fn: () => void): void {\n watch(lc.willEnter, fn);\n}\n\nexport function useIonViewDidEnter(lc: PageLifecycle, fn: () => void): void {\n watch(lc.didEnter, fn);\n}\n\nexport function useIonViewWillLeave(lc: PageLifecycle, fn: () => void): void {\n watch(lc.willLeave, fn);\n}\n\nexport function useIonViewDidLeave(lc: PageLifecycle, fn: () => void): void {\n watch(lc.didLeave, fn);\n}","/**\n * ionic-nix/IonRouterOutlet.ts\n *\n * Bridge entre Nix.js e ion-router (API pública oficial para vanilla JS).\n *\n * Arquitectura:\n * 1. Por cada ruta registramos un Custom Element (nix-page-home, etc.)\n * 2. ion-router activa el custom element según la URL\n * 3. ion-router-outlet gestiona: caché, animaciones, back button, swipe back\n * 4. connectedCallback del custom element monta el componente Nix adentro\n * 5. ionRouteWillChange / ionRouteDidChange disparan el ciclo de vida Nix\n */\n\nimport { NixComponent, signal } from \"@deijose/nix-js\";\nimport type { NixTemplate, Signal } from \"@deijose/nix-js\";\nimport { createPageLifecycle, type PageLifecycle } from \"./lifecycle\";\n\n// --------------------------------------------------------------------------\n// Router store singleton\n// --------------------------------------------------------------------------\n\ninterface RouterStore {\n navigate: (path: string) => void;\n replace: (path: string) => void;\n back: () => void;\n canGoBack: Signal<boolean>;\n params: Signal<Record<string, string>>;\n path: Signal<string>;\n}\n\nlet _router: RouterStore | null = null;\n\nexport function useRouter(): RouterStore {\n if (!_router) throw new Error(\"[nix-ionic] useRouter() llamado antes de montar IonRouterOutlet\");\n return _router;\n}\n\n// --------------------------------------------------------------------------\n// IonBackButton — intercepta el click antes del shadow DOM\n// --------------------------------------------------------------------------\n\nexport function IonBackButton(defaultHref?: string): NixTemplate {\n return {\n __isNixTemplate: true as const,\n mount(container) {\n const el = typeof container === \"string\" ? document.querySelector(container)! : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n const btn = document.createElement(\"ion-back-button\") as HTMLElement;\n btn.setAttribute(\"default-href\", defaultHref ?? \"/\");\n const handler = (e: Event) => {\n e.stopPropagation();\n e.preventDefault();\n if (_router?.canGoBack.value) _router.back();\n else if (defaultHref) _router?.navigate(defaultHref);\n };\n btn.addEventListener(\"click\", handler);\n parent.insertBefore(btn, before);\n return () => {\n btn.removeEventListener(\"click\", handler);\n btn.remove();\n };\n },\n };\n}\n\n// --------------------------------------------------------------------------\n// Tipos públicos\n// --------------------------------------------------------------------------\n\nexport interface PageContext {\n lc: PageLifecycle;\n params: Record<string, string>;\n}\n\nexport interface RouteDefinition {\n path: string;\n component: (ctx: PageContext) => NixComponent | NixTemplate;\n beforeEnter?: (to: string, from: string) => boolean | string | void;\n}\n\n// --------------------------------------------------------------------------\n// Mapa global de lifecycles activos por tag\n// --------------------------------------------------------------------------\n\nconst _lifecycleRegistry = new Map<string, PageLifecycle>();\n\n// --------------------------------------------------------------------------\n// IonRouterOutlet\n// --------------------------------------------------------------------------\n\nexport class IonRouterOutlet extends NixComponent {\n private routes: RouteDefinition[];\n private ionRouterEl: HTMLElement | null = null;\n\n private _currentPath = signal(location.pathname);\n private _canGoBack = signal(false);\n private _params = signal<Record<string, string>>({});\n\n constructor(routes: RouteDefinition[]) {\n super();\n this.routes = routes;\n\n // Store disponible desde el constructor — antes que cualquier\n // connectedCallback de custom element\n _router = {\n navigate: (path) => this.navigate(path),\n replace: (path) => this.replace(path),\n back: () => this.back(),\n canGoBack: this._canGoBack,\n params: this._params,\n path: this._currentPath,\n };\n\n this._registerCustomElements();\n }\n\n override onInit(): void {\n window.addEventListener(\"popstate\", this._handlePopState);\n }\n\n override onMount(): () => void {\n this.ionRouterEl = document.querySelector(\"ion-router\");\n\n const onWillChange = (ev: Event) => {\n const { to, from } = (ev as CustomEvent).detail as { to: string; from: string };\n this._handleWillChange(to, from ?? \"\");\n };\n const onDidChange = (ev: Event) => {\n const { to } = (ev as CustomEvent).detail as { to: string };\n this._handleDidChange(to);\n };\n const onIonBack = (ev: Event) => {\n (ev as CustomEvent).detail.register(100, (next: () => void) => {\n if (this._canGoBack.value) this.back(); else next();\n });\n };\n\n window.addEventListener(\"ionRouteWillChange\", onWillChange);\n window.addEventListener(\"ionRouteDidChange\", onDidChange);\n document.addEventListener(\"ionBackButton\", onIonBack);\n\n return () => {\n window.removeEventListener(\"popstate\", this._handlePopState);\n window.removeEventListener(\"ionRouteWillChange\", onWillChange);\n window.removeEventListener(\"ionRouteDidChange\", onDidChange);\n document.removeEventListener(\"ionBackButton\", onIonBack);\n _router = null;\n };\n }\n\n // -------------------------------------------------------------------------\n // API pública\n // -------------------------------------------------------------------------\n\n navigate(path: string): void {\n if (path === location.pathname) return;\n (this.ionRouterEl as any)?.push(path, \"forward\");\n }\n\n replace(path: string): void {\n (this.ionRouterEl as any)?.push(path, \"root\");\n }\n\n back(): void {\n (this.ionRouterEl as any)?.back();\n }\n\n // -------------------------------------------------------------------------\n // Handlers privados\n // -------------------------------------------------------------------------\n\n private _handlePopState = (): void => {\n // El browser navegó con sus propios botones — sincronizar\n this._handleWillChange(location.pathname, this._currentPath.peek());\n };\n\n private _handleWillChange(to: string, from: string): void {\n const route = this._matchRoute(to);\n if (!route) return;\n\n // Guards\n if (route.beforeEnter) {\n const result = route.beforeEnter(to, from);\n if (result === false) { this.back(); return; }\n if (typeof result === \"string\") { this.navigate(result); return; }\n }\n\n this._currentPath.value = to;\n this._params.value = this._extractParams(route.path, to);\n\n // willLeave en la vista que sale (ya tiene lc registrado)\n const fromTag = this._pathToTag(from);\n _lifecycleRegistry.get(fromTag)?.willLeave.update((n) => n + 1);\n }\n\n private _handleDidChange(to: string): void {\n const outlet = document.querySelector(\"ion-router-outlet\") as any;\n this._canGoBack.value = outlet?.canGoBack?.() ?? false;\n\n // didLeave en la vista que salió\n _lifecycleRegistry.forEach((lc, tag) => {\n if (tag !== this._pathToTag(to)) lc.didLeave.update((n) => n + 1);\n });\n\n // Para vistas CACHEADAS (ya existían en el DOM, connectedCallback no se llama de nuevo)\n // disparamos willEnter/didEnter aquí.\n // Para vistas NUEVAS, connectedCallback ya lo hizo — disparar de nuevo haría doble.\n // Distinguimos: si el tag ya estaba en el registry ANTES de este didChange,\n // es una vista cacheada. Usamos un flag en el custom element.\n const toTag = this._pathToTag(to);\n const toLc = _lifecycleRegistry.get(toTag);\n const toEl = document.querySelector(toTag) as any;\n if (toLc && toEl?._isCached) {\n toLc.willEnter.update((n) => n + 1);\n requestAnimationFrame(() => toLc.didEnter.update((n) => n + 1));\n }\n }\n\n // -------------------------------------------------------------------------\n // Helpers\n // -------------------------------------------------------------------------\n\n private _matchRoute(path: string): RouteDefinition | undefined {\n const exact = this.routes.find((r) => r.path === path);\n if (exact) return exact;\n for (const route of this.routes) {\n const re = new RegExp(`^${route.path.replace(/:[^/]+/g, \"([^/]+)\")}$`);\n if (re.test(path)) return route;\n }\n return this.routes.find((r) => r.path === \"*\");\n }\n\n private _extractParams(routePath: string, realPath: string): Record<string, string> {\n const params: Record<string, string> = {};\n const rP = routePath.split(\"/\");\n const uP = realPath.split(\"/\");\n for (let i = 0; i < rP.length; i++) {\n if (rP[i].startsWith(\":\")) params[rP[i].slice(1)] = uP[i] ?? \"\";\n }\n return params;\n }\n\n private _pathToTag(path: string): string {\n if (!path || path === \"/\") return \"nix-page-home\";\n const clean = path\n .replace(/\\/:?[^/]+/g, (m) => \"-\" + m.replace(/\\//g, \"\").replace(/:/g, \"\"))\n .replace(/^\\//, \"\")\n .replace(/\\//g, \"-\");\n return `nix-page-${clean}`;\n }\n\n // -------------------------------------------------------------------------\n // Registro de Custom Elements\n // -------------------------------------------------------------------------\n\n private _registerCustomElements(): void {\n for (const route of this.routes) {\n if (route.path === \"*\") continue;\n\n const tag = this._pathToTag(route.path);\n const routeDef = route;\n const self = this;\n\n if (customElements.get(tag)) continue;\n\n customElements.define(tag, class extends HTMLElement {\n private _cleanup: (() => void) | null = null;\n\n connectedCallback(): void {\n const params = self._extractParams(routeDef.path, location.pathname);\n const lc = createPageLifecycle();\n\n _lifecycleRegistry.set(tag, lc);\n this.classList.add(\"ion-page\");\n\n const pageNode = routeDef.component({ lc, params });\n\n if (\"render\" in pageNode && typeof (pageNode as NixComponent).render === \"function\") {\n const comp = pageNode as NixComponent;\n comp.onInit?.();\n const renderCleanup = comp.render()._render(this, null);\n const mountRet = comp.onMount?.();\n this._cleanup = () => {\n comp.onUnmount?.();\n if (typeof mountRet === \"function\") mountRet();\n renderCleanup();\n };\n } else {\n this._cleanup = (pageNode as NixTemplate)._render(this, null);\n }\n\n // Disparar willEnter/didEnter aquí — el lc ya está registrado\n // y el componente ya está inicializado con sus watchers\n lc.willEnter.update((n) => n + 1);\n requestAnimationFrame(() => {\n lc.didEnter.update((n) => n + 1);\n // Marcar como cacheado para que ionRouteDidChange pueda\n // disparar willEnter en visitas subsiguientes\n (this as any)._isCached = true;\n });\n }\n\n disconnectedCallback(): void {\n // Disparar willLeave/didLeave al salir\n const lc = _lifecycleRegistry.get(tag);\n if (lc) {\n lc.willLeave.update((n) => n + 1);\n lc.didLeave.update((n) => n + 1);\n }\n this._cleanup?.();\n this._cleanup = null;\n _lifecycleRegistry.delete(tag);\n }\n });\n }\n }\n\n // -------------------------------------------------------------------------\n // Render — crea ion-router + ion-router-outlet como elementos DOM reales\n // -------------------------------------------------------------------------\n\n override render(): NixTemplate {\n const self = this;\n return {\n __isNixTemplate: true as const,\n mount(container: Element | string): { unmount(): void } {\n const el = typeof container === \"string\"\n ? (document.querySelector(container) as Element)\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n // ion-router con las rutas declarativas\n const routerEl = document.createElement(\"ion-router\");\n routerEl.setAttribute(\"use-hash\", \"false\");\n routerEl.innerHTML = self.routes\n .filter((r) => r.path !== \"*\")\n .map((r) => `<ion-route url=\"${r.path}\" component=\"${self._pathToTag(r.path)}\"></ion-route>`)\n .join(\"\");\n\n const outletEl = document.createElement(\"ion-router-outlet\");\n\n parent.insertBefore(routerEl, before);\n parent.insertBefore(outletEl, before);\n\n self.ionRouterEl = routerEl;\n\n return () => { routerEl.remove(); outletEl.remove(); };\n },\n };\n }\n\n override onUnmount(): void {\n _lifecycleRegistry.clear();\n _router = null;\n }\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nix-ionic.js","names":[],"sources":["../../src/lifecycle.ts","../../src/IonRouterOutlet.ts"],"sourcesContent":["/**\n * ionic-nix/lifecycle.ts\n *\n * Sistema de ciclo de vida de navegación, análogo a los hooks de Ionic:\n * ionViewWillEnter / ionViewDidEnter / ionViewWillLeave / ionViewDidLeave\n *\n * Cómo funciona (sin provide/inject):\n * 1. IonRouterOutlet crea un `PageLifecycle` por cada ruta.\n * 2. Lo pasa directamente al factory de la ruta como argumento.\n * 3. El factory llama a `new MiPagina(lc)` o `MiPagina(lc)`.\n * 4. IonPage/composables registran watchers sobre las señales del lc.\n * 5. Cuando el router navega, incrementa las señales → watchers se disparan.\n */\n\nimport { signal, watch } from \"@deijose/nix-js\";\nimport type { Signal } from \"@deijose/nix-js\";\nimport { NixComponent } from \"@deijose/nix-js\";\n\n// --------------------------------------------------------------------------\n// Tipos públicos\n// --------------------------------------------------------------------------\n\nexport interface PageLifecycle {\n willEnter: Signal<number>;\n didEnter: Signal<number>;\n willLeave: Signal<number>;\n didLeave: Signal<number>;\n}\n\n/** Crea un nuevo PageLifecycle con señales en 0. */\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// IonPage — clase base para páginas con hooks de navegación\n// --------------------------------------------------------------------------\n//\n// Uso:\n// class HomePage extends IonPage {\n// constructor(lc: PageLifecycle) { super(lc); }\n//\n// ionViewWillEnter() { /* fetch de datos frescos */ }\n// render() { return html`...`; }\n// }\n\nexport abstract class IonPage extends NixComponent {\n private __lc: PageLifecycle;\n\n constructor(lc: PageLifecycle) {\n super();\n this.__lc = lc;\n }\n\n override onInit(): void {\n const lc = this.__lc;\n // watch no corre en init (immediate: false), así que ionViewWillEnter\n // solo se llama cuando el outlet incrementa la señal, no al construir.\n if (this.ionViewWillEnter) watch(lc.willEnter, this.ionViewWillEnter.bind(this));\n if (this.ionViewDidEnter) watch(lc.didEnter, this.ionViewDidEnter.bind(this));\n if (this.ionViewWillLeave) watch(lc.willLeave, this.ionViewWillLeave.bind(this));\n if (this.ionViewDidLeave) watch(lc.didLeave, this.ionViewDidLeave.bind(this));\n }\n\n ionViewWillEnter?(): void;\n ionViewDidEnter?(): void;\n ionViewWillLeave?(): void;\n ionViewDidLeave?(): void;\n}\n\n// --------------------------------------------------------------------------\n// Composables para function components\n// --------------------------------------------------------------------------\n//\n// Uso:\n// function ProfilePage(lc: PageLifecycle): NixTemplate {\n// useIonViewWillEnter(lc, () => { /* fetch */ });\n// return html`...`;\n// }\n\nexport function useIonViewWillEnter(lc: PageLifecycle, fn: () => void): void {\n watch(lc.willEnter, fn);\n}\n\nexport function useIonViewDidEnter(lc: PageLifecycle, fn: () => void): void {\n watch(lc.didEnter, fn);\n}\n\nexport function useIonViewWillLeave(lc: PageLifecycle, fn: () => void): void {\n watch(lc.willLeave, fn);\n}\n\nexport function useIonViewDidLeave(lc: PageLifecycle, fn: () => void): void {\n watch(lc.didLeave, fn);\n}","/**\n * ionic-nix/IonRouterOutlet.ts\n *\n * Bridge entre Nix.js e ion-router (API pública oficial para vanilla JS).\n *\n * Arquitectura:\n * 1. Por cada ruta registramos un Custom Element (nix-page-home, etc.)\n * 2. ion-router activa el custom element según la URL\n * 3. ion-router-outlet gestiona: caché, animaciones, back button, swipe back\n * 4. connectedCallback del custom element monta el componente Nix adentro\n * 5. El flag _mounted evita destruir el componente cuando ion-router-outlet\n * desconecta el elemento temporalmente (caché). Solo se destruye si\n * el elemento no vuelve a conectarse en los siguientes 100ms.\n */\n\nimport { NixComponent, signal } from \"@deijose/nix-js\";\nimport type { NixTemplate, Signal } from \"@deijose/nix-js\";\nimport { createPageLifecycle, type PageLifecycle } from \"./lifecycle\";\n\n// --------------------------------------------------------------------------\n// Router store singleton\n// --------------------------------------------------------------------------\n\ninterface RouterStore {\n navigate: (path: string) => void;\n replace: (path: string) => void;\n back: () => void;\n canGoBack: Signal<boolean>;\n params: Signal<Record<string, string>>;\n path: Signal<string>;\n}\n\nlet _router: RouterStore | null = null;\n\nexport function useRouter(): RouterStore {\n if (!_router) throw new Error(\"[nix-ionic] useRouter() llamado antes de montar IonRouterOutlet\");\n return _router;\n}\n\n// --------------------------------------------------------------------------\n// IonBackButton\n// --------------------------------------------------------------------------\n\nexport function IonBackButton(defaultHref?: string): NixTemplate {\n return {\n __isNixTemplate: true as const,\n mount(container) {\n const el = typeof container === \"string\"\n ? document.querySelector(container)! as Element\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n const btn = document.createElement(\"ion-back-button\") as HTMLElement;\n \n // Solo asigna si existe. Si no, Ionic lo oculta de forma inteligente.\n if (defaultHref) {\n btn.setAttribute(\"default-href\", defaultHref);\n }\n\n parent.insertBefore(btn, before);\n return () => btn.remove();\n },\n };\n}\n\n// --------------------------------------------------------------------------\n// Tipos públicos\n// --------------------------------------------------------------------------\n\nexport interface PageContext {\n lc: PageLifecycle;\n params: Record<string, string>;\n}\n\nexport interface RouteDefinition {\n path: string;\n component: (ctx: PageContext) => NixComponent | NixTemplate;\n beforeEnter?: (to: string, from: string) => boolean | string | void;\n}\n\n// --------------------------------------------------------------------------\n// Mapa global de lifecycles activos por tag\n// --------------------------------------------------------------------------\n\nconst _lifecycleRegistry = new Map<string, PageLifecycle>();\n\n// --------------------------------------------------------------------------\n// IonRouterOutlet\n// --------------------------------------------------------------------------\n\nexport class IonRouterOutlet extends NixComponent {\n private routes: RouteDefinition[];\n private ionRouterEl: HTMLElement | null = null;\n\n private _currentPath = signal(location.pathname);\n private _canGoBack = signal(false);\n private _params = signal<Record<string, string>>({});\n\n constructor(routes: RouteDefinition[]) {\n super();\n this.routes = routes;\n\n _router = {\n navigate: (path) => this.navigate(path),\n replace: (path) => this.replace(path),\n back: () => this.back(),\n canGoBack: this._canGoBack,\n params: this._params,\n path: this._currentPath,\n };\n\n this._registerCustomElements();\n }\n\n override onInit(): void {\n window.addEventListener(\"popstate\", this._handlePopState);\n }\n\n override onMount(): () => void {\n this.ionRouterEl = document.querySelector(\"ion-router\");\n\n const onWillChange = (ev: Event) => {\n const { to, from } = (ev as CustomEvent).detail as { to: string; from: string };\n this._handleWillChange(to, from ?? \"\");\n };\n const onDidChange = (ev: Event) => {\n const { to } = (ev as CustomEvent).detail as { to: string };\n this._handleDidChange(to);\n };\n const onIonBack = (ev: Event) => {\n (ev as CustomEvent).detail.register(100, (next: () => void) => {\n if (this._canGoBack.value) this.back(); else next();\n });\n };\n\n window.addEventListener(\"ionRouteWillChange\", onWillChange);\n window.addEventListener(\"ionRouteDidChange\", onDidChange);\n document.addEventListener(\"ionBackButton\", onIonBack);\n\n return () => {\n window.removeEventListener(\"popstate\", this._handlePopState);\n window.removeEventListener(\"ionRouteWillChange\", onWillChange);\n window.removeEventListener(\"ionRouteDidChange\", onDidChange);\n document.removeEventListener(\"ionBackButton\", onIonBack);\n _router = null;\n };\n }\n\n // -------------------------------------------------------------------------\n // API pública\n // -------------------------------------------------------------------------\n\n navigate(path: string): void {\n if (path === location.pathname) return;\n (this.ionRouterEl as any)?.push(path, \"forward\");\n }\n\n replace(path: string): void {\n (this.ionRouterEl as any)?.push(path, \"root\");\n }\n\n back(): void {\n (this.ionRouterEl as any)?.back();\n }\n\n // -------------------------------------------------------------------------\n // Handlers privados\n // -------------------------------------------------------------------------\n\n private _handlePopState = (): void => {\n this._handleWillChange(location.pathname, this._currentPath.peek());\n };\n\n private _handleWillChange(to: string, from: string): void {\n const route = this._matchRoute(to);\n if (!route) return;\n\n if (route.beforeEnter) {\n const result = route.beforeEnter(to, from);\n if (result === false) { this.back(); return; }\n if (typeof result === \"string\") { this.navigate(result); return; }\n }\n\n this._currentPath.value = to;\n this._params.value = this._extractParams(route.path, to);\n\n // Despide a la vista que sale\n _lifecycleRegistry.get(this._pathToTag(from))?.willLeave.update((n) => n + 1);\n\n // Si la vista a la que vamos ya existe en el registro, significa que ESTÁ EN CACHÉ.\n const toTag = this._pathToTag(to);\n const toLc = _lifecycleRegistry.get(toTag);\n if (toLc) {\n toLc.willEnter.update((n) => n + 1);\n (toLc as any)._pendingEnter = true; // Flag para que el didChange dispare el didEnter\n }\n }\n\n private _handleDidChange(to: string): void {\n const nav = document.querySelector(\"ion-nav\") as any;\n \n // ion-nav.canGoBack() retorna una Promesa, la resolvemos para tu señal\n if (nav && typeof nav.canGoBack === \"function\") {\n nav.canGoBack().then((res: boolean) => {\n this._canGoBack.value = res;\n });\n } else {\n this._canGoBack.value = false;\n }\n\n _lifecycleRegistry.forEach((lc, tag) => {\n if (tag !== this._pathToTag(to)) lc.didLeave.update((n) => n + 1);\n });\n\n // Avisa a la vista cacheada que la transición ya terminó\n const toTag = this._pathToTag(to);\n const toLc = _lifecycleRegistry.get(toTag);\n if (toLc && (toLc as any)._pendingEnter) {\n toLc.didEnter.update((n) => n + 1);\n (toLc as any)._pendingEnter = false;\n }\n }\n\n // -------------------------------------------------------------------------\n // Helpers\n // -------------------------------------------------------------------------\n\n private _matchRoute(path: string): RouteDefinition | undefined {\n const exact = this.routes.find((r) => r.path === path);\n if (exact) return exact;\n for (const route of this.routes) {\n const re = new RegExp(`^${route.path.replace(/:[^/]+/g, \"([^/]+)\")}$`);\n if (re.test(path)) return route;\n }\n return this.routes.find((r) => r.path === \"*\");\n }\n\n private _extractParams(routePath: string, realPath: string): Record<string, string> {\n const params: Record<string, string> = {};\n const rP = routePath.split(\"/\");\n const uP = realPath.split(\"/\");\n for (let i = 0; i < rP.length; i++) {\n if (rP[i].startsWith(\":\")) params[rP[i].slice(1)] = uP[i] ?? \"\";\n }\n return params;\n }\n\n private _pathToTag(path: string): string {\n if (!path || path === \"/\") return \"nix-page-home\";\n const clean = path\n .replace(/\\/:?[^/]+/g, (m) => \"-\" + m.replace(/\\//g, \"\").replace(/:/g, \"\"))\n .replace(/^\\//, \"\")\n .replace(/\\//g, \"-\");\n return `nix-page-${clean}`;\n }\n\n // -------------------------------------------------------------------------\n // Registro de Custom Elements\n // -------------------------------------------------------------------------\n\n private _registerCustomElements(): void {\n for (const route of this.routes) {\n if (route.path === \"*\") continue;\n\n const tag = this._pathToTag(route.path);\n const routeDef = route;\n const self = this;\n\n if (customElements.get(tag)) continue;\n\n customElements.define(tag, class extends HTMLElement {\n private _cleanup: (() => void) | null = null;\n private _lc: PageLifecycle | null = null;\n private _isInitialized = false; // Solo necesitamos este flag\n\n connectedCallback(): void {\n if (this._isInitialized) return;\n this._isInitialized = true;\n\n const params = self._extractParams(routeDef.path, location.pathname);\n const lc = createPageLifecycle();\n this._lc = lc;\n\n _lifecycleRegistry.set(tag, lc);\n this.classList.add(\"ion-page\");\n\n const pageNode = routeDef.component({ lc, params });\n\n if (\"render\" in pageNode && typeof (pageNode as NixComponent).render === \"function\") {\n const comp = pageNode as NixComponent;\n comp.onInit?.();\n const renderCleanup = comp.render()._render(this, null);\n const mountRet = comp.onMount?.();\n this._cleanup = () => {\n comp.onUnmount?.();\n if (typeof mountRet === \"function\") mountRet();\n renderCleanup();\n };\n } else {\n this._cleanup = (pageNode as NixTemplate)._render(this, null);\n }\n\n // Disparar hooks de la primera vez\n lc.willEnter.update((n) => n + 1);\n \n // Esperar al cambio de ruta para disparar didEnter de forma segura\n const onReady = (e: Event) => {\n if (self._pathToTag((e as CustomEvent).detail.to) === tag) {\n lc.didEnter.update((n) => n + 1);\n window.removeEventListener(\"ionRouteDidChange\", onReady);\n }\n };\n window.addEventListener(\"ionRouteDidChange\", onReady);\n }\n\n disconnectedCallback(): void {\n const lc = this._lc;\n \n setTimeout(() => {\n // Si después de 100ms sigue conectado, fue solo una animación de Ionic\n if (this.isConnected) return; \n\n // Destrucción real (se sacó del stack)\n _lifecycleRegistry.delete(tag);\n lc?.willLeave.update((n) => n + 1);\n lc?.didLeave.update((n) => n + 1);\n this._cleanup?.();\n this._cleanup = null;\n this._lc = null;\n this._isInitialized = false; // Permite que se recree en el futuro\n }, 100);\n }\n });\n }\n }\n\n // -------------------------------------------------------------------------\n // Render\n // -------------------------------------------------------------------------\n\n override render(): NixTemplate {\n const self = this;\n return {\n __isNixTemplate: true as const,\n mount(container: Element | string): { unmount(): void } {\n const el = typeof container === \"string\"\n ? (document.querySelector(container) as Element)\n : container;\n const cleanup = this._render(el, null);\n return { unmount: cleanup };\n },\n _render(parent: Node, before: Node | null): () => void {\n const routerEl = document.createElement(\"ion-router\");\n routerEl.setAttribute(\"use-hash\", \"false\");\n routerEl.innerHTML = self.routes\n .filter((r) => r.path !== \"*\")\n .map((r) => `<ion-route url=\"${r.path}\" component=\"${self._pathToTag(r.path)}\"></ion-route>`)\n .join(\"\");\n\n // 🚀 EL FIX MÁGICO: ion-nav mantiene los nodos en el DOM nativamente\n const navEl = document.createElement(\"ion-nav\");\n\n parent.insertBefore(routerEl, before);\n parent.insertBefore(navEl, before);\n\n self.ionRouterEl = routerEl;\n\n return () => { routerEl.remove(); navEl.remove(); };\n },\n };\n }\n\n override onUnmount(): void {\n _lifecycleRegistry.clear();\n _router = null;\n }\n}"],"mappings":"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"}
|