@cruxjs/client 0.0.8 → 0.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/README.md +79 -3
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<div align="center">
|
|
11
|
-
<img src="https://img.shields.io/badge/v-0.0
|
|
11
|
+
<img src="https://img.shields.io/badge/v-0.1.0-black"/>
|
|
12
12
|
<a href="https://github.com/cruxjs-org"><img src="https://img.shields.io/badge/🔥-@cruxjs-black"/></a>
|
|
13
13
|
<br>
|
|
14
14
|
<img src="https://img.shields.io/badge/coverage-~%25-brightgreen" alt="Test Coverage" />
|
|
@@ -127,6 +127,36 @@
|
|
|
127
127
|
<a href="/about" onclick={handleClick}>About Us</a>
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
- #### Root Layout
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { JSXElement } from '@minejs/jsx';
|
|
134
|
+
|
|
135
|
+
// Create a root layout that wraps all pages
|
|
136
|
+
function AppLayout(): JSXElement {
|
|
137
|
+
return (
|
|
138
|
+
<div class="app-wrapper">
|
|
139
|
+
<header class="app-header">
|
|
140
|
+
<h1>My App</h1>
|
|
141
|
+
<nav>{/* navigation */}</nav>
|
|
142
|
+
</header>
|
|
143
|
+
<main data-page-slot></main>
|
|
144
|
+
{/* Pages are rendered in [data-page-slot] */}
|
|
145
|
+
<footer class="app-footer">
|
|
146
|
+
<p>© 2026 My App</p>
|
|
147
|
+
</footer>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const config: ClientManagerConfig = {
|
|
153
|
+
routes: { /* ... */ },
|
|
154
|
+
rootLayout: AppLayout, // Optional: wraps all pages
|
|
155
|
+
};
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
> The `rootLayout` is rendered once and pages are mounted inside the element with `data-page-slot` attribute. This allows you to have persistent headers, footers, navigation, and modals that don't remount when routes change.
|
|
159
|
+
|
|
130
160
|
- #### Lifecycle Hooks
|
|
131
161
|
|
|
132
162
|
```typescript
|
|
@@ -385,10 +415,22 @@
|
|
|
385
415
|
```typescript
|
|
386
416
|
// src/app/client.ts
|
|
387
417
|
import { ClientManagerConfig, start } from '@cruxjs/client';
|
|
418
|
+
import { JSXElement } from '@minejs/jsx';
|
|
388
419
|
|
|
389
420
|
import { HomePage } from './ui/pages/home';
|
|
390
421
|
import { ErrorPage } from './ui/pages/error';
|
|
391
422
|
|
|
423
|
+
// Root layout wraps all pages
|
|
424
|
+
function AppLayout(): JSXElement {
|
|
425
|
+
return (
|
|
426
|
+
<div class="app-container">
|
|
427
|
+
<header>Header Content</header>
|
|
428
|
+
<main data-page-slot></main>
|
|
429
|
+
<footer>Footer Content</footer>
|
|
430
|
+
</div>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
392
434
|
const config: ClientManagerConfig = {
|
|
393
435
|
debug : true,
|
|
394
436
|
|
|
@@ -398,6 +440,8 @@
|
|
|
398
440
|
|
|
399
441
|
notFoundComponent : ErrorPage,
|
|
400
442
|
|
|
443
|
+
rootLayout : AppLayout, // Optional: wraps all pages
|
|
444
|
+
|
|
401
445
|
lifecycle: {
|
|
402
446
|
onBoot : () => {
|
|
403
447
|
console.log('[App] BOOT phase - Initializing');
|
|
@@ -491,6 +535,7 @@
|
|
|
491
535
|
interface ClientManagerConfig {
|
|
492
536
|
routes : Record<string, RouteComponent>;
|
|
493
537
|
notFoundComponent? : RouteComponent;
|
|
538
|
+
rootLayout? : () => JSXElement | null;
|
|
494
539
|
debug? : boolean;
|
|
495
540
|
lifecycle? : ClientManagerHooks;
|
|
496
541
|
plugins? : ClientPlugin[];
|
|
@@ -542,8 +587,9 @@
|
|
|
542
587
|
|
|
543
588
|
- #### `mount(selector)`
|
|
544
589
|
|
|
545
|
-
> Mount router to DOM element
|
|
546
|
-
> Sets up reactive routing and initial render
|
|
590
|
+
> Mount router to DOM element with optional root layout
|
|
591
|
+
> Sets up reactive routing, mounts root layout if provided, and initial render
|
|
592
|
+
> Pages render inside `[data-page-slot]` if rootLayout exists, otherwise in the selector
|
|
547
593
|
|
|
548
594
|
```typescript
|
|
549
595
|
manager.mount('body');
|
|
@@ -803,6 +849,36 @@
|
|
|
803
849
|
// Manual cleanup required
|
|
804
850
|
```
|
|
805
851
|
|
|
852
|
+
- #### Root Layout
|
|
853
|
+
|
|
854
|
+
```typescript
|
|
855
|
+
// ✅ DO: Use root layout for persistent UI
|
|
856
|
+
function AppLayout(): JSXElement {
|
|
857
|
+
return (
|
|
858
|
+
<div>
|
|
859
|
+
<header>Persistent Header</header>
|
|
860
|
+
<main data-page-slot></main>
|
|
861
|
+
<footer>Persistent Footer</footer>
|
|
862
|
+
</div>
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const config = {
|
|
867
|
+
routes: { /* ... */ },
|
|
868
|
+
rootLayout: AppLayout, // Pages render inside [data-page-slot]
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// ❌ DON'T: Create layout inside each page
|
|
872
|
+
export function HomePage(): JSXElement {
|
|
873
|
+
return (
|
|
874
|
+
<div>
|
|
875
|
+
<header>Recreated on every navigation</header>
|
|
876
|
+
<div>Page content</div>
|
|
877
|
+
</div>
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
```
|
|
881
|
+
|
|
806
882
|
- #### Navigation
|
|
807
883
|
|
|
808
884
|
```typescript
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var signals=require('@minejs/signals'),i18n=require('@minejs/i18n'),jsx=require('@minejs/jsx'),browser=require('@minejs/browser');var
|
|
1
|
+
'use strict';var signals=require('@minejs/signals'),i18n=require('@minejs/i18n'),jsx=require('@minejs/jsx'),browser=require('@minejs/browser');var h,s=class{constructor(e){this.lifecycle="booting";this.hooks={};this.plugins=[];this.routeComponents={};this.currentPathSignal=signals.signal(window.location.pathname??"/");this.config=e,this.debug=e.debug??false,this.log("[INIT] Creating ClientManager"),e.lifecycle&&(this.hooks={...e.lifecycle}),this.plugins=e.plugins??[],this.routeComponents=e.routes,this.eventsManager=new browser.EventsManager,this.windowManager=new browser.WindowManager;let t=Object.entries(e.routes).map(([n,r])=>({path:n,component:r}));this.router=browser.createRouter({routes:t,notFoundComponent:e.notFoundComponent}),this.router.afterEach(n=>{this.currentPathSignal.set(n.path);}),h=this,this.log("[INIT] ClientManager created");}on(e,t,n,r){return typeof e=="string"&&e.startsWith("on")?(this.hooks[e]=t,this):this.eventsManager.on(e,t,n,r)}async boot(){if(this.lifecycle!=="booting"){console.warn("[ClientManager] Already booted or destroyed");return}this.log("\u26A1 Phase: BOOT");try{for(let e of this.plugins)e.onBoot&&(this.log(`\u2192 Plugin onBoot: ${e.name}`),await e.onBoot({debug:this.debug,config:this.config}));this.hooks.onBoot&&(this.log("\u2192 Calling onBoot hook"),await this.hooks.onBoot()),this.log("\u2713 BOOT phase complete");}catch(e){throw console.error("[ClientManager] Boot failed:",e),e}}async ready(){if(this.lifecycle!=="booting"){console.warn("[ClientManager] Cannot ready - not in booting phase");return}this.log("\u26A1 Phase: READY");try{let e="body";document.querySelector(e).id="root",this.mount(e),this.log("\u2192 Router mounted");for(let t of this.plugins)t.onReady&&(this.log(`\u2192 Plugin onReady: ${t.name}`),await t.onReady({debug:this.debug,config:this.config}));this.hooks.onReady&&(this.log("\u2192 Calling onReady hook"),await this.hooks.onReady()),this.lifecycle="ready",this.log("\u2713 READY phase complete"),this.log("\u2713 App is ready!");}catch(e){throw console.error("[ClientManager] Ready failed:",e),e}}async destroy(){if(this.lifecycle==="destroyed"){console.warn("[ClientManager] Already destroyed");return}this.lifecycle="destroying",this.log("\u26A1 Phase: DESTROY");try{for(let e=this.plugins.length-1;e>=0;e--){let t=this.plugins[e];t.onDestroy&&(this.log(`\u2192 Plugin onDestroy: ${t.name}`),await t.onDestroy({debug:this.debug,config:this.config}));}this.hooks.onDestroy&&(this.log("\u2192 Calling onDestroy hook"),await this.hooks.onDestroy()),this.eventsManager.destroy(),this.windowManager.destroy(),this.lifecycle="destroyed",this.log("\u2713 DESTROY phase complete");}catch(e){throw console.error("[ClientManager] Destroy failed:",e),e}}navigate(e){this.router.push(e);}mount(e){let t=typeof e=="string"?document.querySelector(e):e;if(!t){console.warn("[ClientManager] Mount target not found:",e);return}if(this.config.rootLayout)try{let n=this.config.rootLayout();n&&(jsx.mount(n,t),this.log("\u2192 Root layout mounted"));}catch(n){console.error("[ClientManager] Error rendering root layout:",n),t.innerHTML="<p>Error loading root layout</p>";}signals.effect(()=>{let n=this.currentPathSignal(),r=this.routeComponents[n]||this.config.notFoundComponent||null,i=t;if(this.config.rootLayout){let o=t.querySelector("[data-page-slot]");o&&(i=o);}if(i.innerHTML="",r)try{let o=r();o&&jsx.mount(o,i);}catch(o){console.error("[ClientManager] Error rendering component:",n,o),i.innerHTML="<p>Error loading component</p>";}else i.innerHTML="<p>No component found for this route</p>";this.log(`\u2192 Route changed to: ${n}`);}),this.router.push(this.currentPathSignal()),this.log("\u2192 Routing setup complete");}getCurrentPath(){return this.currentPathSignal}createLinkHandler(e){return t=>{t.preventDefault(),this.navigate(e);}}getRouter(){return this.router}off(e,t,n){this.eventsManager.off(e,t,n);}getEventsManager(){return this.eventsManager}getViewport(){return this.windowManager.getViewport()}getWindowManager(){return this.windowManager}getI18n(){return i18n.getI18n()}t(e,t){let n=i18n.getI18n();return n?n.t(e)??t??e:(console.warn("[ClientManager] i18n not initialized. Using default value or key."),t??e)}getPhase(){return this.lifecycle}isReady(){return this.lifecycle==="ready"}log(e){this.debug&&console.log(`[ClientManager] ${e}`);}};function E(){return h}async function b(a){let e=document.querySelector('meta[name="app-i18n"]');if(e){let n=JSON.parse(e.getAttribute("content")||"{}");a.i18n=n;}let t=new s(a);return await i18n.setupI18n(a.i18n||{defaultLanguage:"en",supportedLanguages:["en"]}),t.boot(),t.ready(),window.addEventListener("beforeunload",async()=>{await t.destroy();}),t}Object.defineProperty(exports,"t",{enumerable:true,get:function(){return i18n.t}});exports.ClientManager=s;exports.getGlobalClientManager=E;exports.start=b;//# sourceMappingURL=index.cjs.map
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["globalClientManagerInstance","ClientManager","config","signal","EventsManager","WindowManager","routesArray","path","component","createRouter","to","eventOrTarget","callbackOrEvent","handler","options","plugin","err","selector","i","container","effect","currentPath","Component","jsx","mountJSX","e","target","event","getI18n","key","defaultValue","i18n","message","getGlobalClientManager","start","metaI18n","i18nData","manager","setupI18n"],"mappings":"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","file":"index.cjs","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from './types';\r\n import { signal, effect } from '@minejs/signals';\r\n import { setupI18n, getI18n } from '@minejs/i18n';\r\n import { mount as mountJSX } from '@minejs/jsx';\r\n import { EventsManager, Router, WindowManager, createRouter } from '@minejs/browser';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n let globalClientManagerInstance: ClientManager | undefined;\r\n\r\n export class ClientManager {\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n private router : Router;\r\n private eventsManager : EventsManager;\r\n private windowManager : WindowManager;\r\n private lifecycle : 'booting' | 'ready' | 'destroying' | 'destroyed' = 'booting';\r\n private config : types.ClientManagerConfig;\r\n private hooks : types.ClientManagerHooks = {};\r\n private plugins : types.ClientPlugin[] = [];\r\n private debug : boolean;\r\n private routeComponents : Record<string, types.RouteComponent> = {};\r\n private currentPathSignal = signal<string>(window.location.pathname ?? '/');\r\n\r\n constructor(config: types.ClientManagerConfig) {\r\n this.config = config;\r\n this.debug = config.debug ?? false;\r\n\r\n this.log('[INIT] Creating ClientManager');\r\n\r\n // Merge lifecycle hooks from config\r\n if (config.lifecycle) {\r\n this.hooks = { ...config.lifecycle };\r\n }\r\n\r\n // Store plugins from config\r\n this.plugins = config.plugins ?? [];\r\n\r\n // Store route components provided by user\r\n this.routeComponents = config.routes;\r\n\r\n // Initialize managers from @minejs/browser\r\n this.eventsManager = new EventsManager();\r\n this.windowManager = new WindowManager();\r\n\r\n // Initialize router with user-provided routes\r\n const routesArray = Object.entries(config.routes).map(([path, component]) => ({\r\n path,\r\n component\r\n }));\r\n\r\n this.router = createRouter({\r\n routes: routesArray,\r\n notFoundComponent: config.notFoundComponent,\r\n });\r\n\r\n // Connect router changes to signal for automatic re-rendering\r\n this.router.afterEach((to) => {\r\n this.currentPathSignal.set(to.path);\r\n });\r\n\r\n // Store clientManager instance\r\n // eslint-disable-next-line @typescript-eslint/no-this-alias\r\n globalClientManagerInstance = this;\r\n\r\n this.log('[INIT] ClientManager created');\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Setup lifecycle hooks OR bind events\r\n * Overloaded: on(event: 'onBoot'|'onReady'|'onDestroy', callback) - lifecycle\r\n * on(target, event, handler) - event binding\r\n */\r\n on(event: keyof types.ClientManagerHooks, callback: any): this;\r\n on<K extends keyof HTMLElementEventMap>(\r\n target: EventTarget,\r\n event: K | string,\r\n handler: EventListener,\r\n options?: AddEventListenerOptions\r\n ): () => void;\r\n on(\r\n eventOrTarget: keyof types.ClientManagerHooks | EventTarget,\r\n callbackOrEvent?: any,\r\n handler?: EventListener,\r\n options?: AddEventListenerOptions\r\n ): this | (() => void) {\r\n // Check if this is a lifecycle hook call\r\n if (typeof eventOrTarget === 'string' && eventOrTarget.startsWith('on')) {\r\n this.hooks[eventOrTarget as keyof types.ClientManagerHooks] = callbackOrEvent;\r\n return this;\r\n }\r\n\r\n // Otherwise it's an event binding\r\n return this.eventsManager.on(\r\n eventOrTarget as EventTarget,\r\n callbackOrEvent as string,\r\n handler as EventListener,\r\n options\r\n );\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Bootstrap the app - Phase 1: BOOT\r\n */\r\n async boot(): Promise<void> {\r\n if (this.lifecycle !== 'booting') {\r\n console.warn('[ClientManager] Already booted or destroyed');\r\n return;\r\n }\r\n\r\n this.log('⚡ Phase: BOOT');\r\n\r\n try {\r\n // Call plugins onBoot hooks\r\n for (const plugin of this.plugins) {\r\n if (plugin.onBoot) {\r\n this.log(`→ Plugin onBoot: ${plugin.name}`);\r\n await plugin.onBoot({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onBoot hook\r\n if (this.hooks.onBoot) {\r\n this.log('→ Calling onBoot hook');\r\n await this.hooks.onBoot();\r\n }\r\n\r\n this.log('✓ BOOT phase complete');\r\n } catch (err) {\r\n console.error('[ClientManager] Boot failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Ready the app - Phase 2: READY\r\n * Mount to DOM and make everything live\r\n *\r\n * Root selector is always 'body'.\r\n */\r\n async ready(): Promise<void> {\r\n if (this.lifecycle !== 'booting') {\r\n console.warn('[ClientManager] Cannot ready - not in booting phase');\r\n return;\r\n }\r\n\r\n this.log('⚡ Phase: READY');\r\n\r\n try {\r\n const selector = 'body';\r\n\r\n // Set body id to 'root' for mounting\r\n (document.querySelector(selector) as HTMLElement).id = 'root';\r\n\r\n // Mount router\r\n this.mount(selector);\r\n\r\n this.log('→ Router mounted');\r\n\r\n // Call plugins onReady hooks\r\n for (const plugin of this.plugins) {\r\n if (plugin.onReady) {\r\n this.log(`→ Plugin onReady: ${plugin.name}`);\r\n await plugin.onReady({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onReady hook\r\n if (this.hooks.onReady) {\r\n this.log('→ Calling onReady hook');\r\n await this.hooks.onReady();\r\n }\r\n\r\n this.lifecycle = 'ready';\r\n this.log('✓ READY phase complete');\r\n this.log('✓ App is ready!');\r\n } catch (err) {\r\n console.error('[ClientManager] Ready failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown the app - Phase 3: DESTROY\r\n */\r\n async destroy(): Promise<void> {\r\n if (this.lifecycle === 'destroyed') {\r\n console.warn('[ClientManager] Already destroyed');\r\n return;\r\n }\r\n\r\n this.lifecycle = 'destroying';\r\n this.log('⚡ Phase: DESTROY');\r\n\r\n try {\r\n // Call plugins onDestroy hooks (in reverse order)\r\n for (let i = this.plugins.length - 1; i >= 0; i--) {\r\n const plugin = this.plugins[i];\r\n if (plugin.onDestroy) {\r\n this.log(`→ Plugin onDestroy: ${plugin.name}`);\r\n await plugin.onDestroy({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onDestroy hook\r\n if (this.hooks.onDestroy) {\r\n this.log('→ Calling onDestroy hook');\r\n await this.hooks.onDestroy();\r\n }\r\n\r\n // Cleanup managers\r\n this.eventsManager.destroy();\r\n this.windowManager.destroy();\r\n\r\n this.lifecycle = 'destroyed';\r\n this.log('✓ DESTROY phase complete');\r\n } catch (err) {\r\n console.error('[ClientManager] Destroy failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Navigate to path\r\n */\r\n navigate(path: string): void {\r\n this.router.push(path);\r\n }\r\n\r\n /**\r\n * Mount router to DOM element and setup reactive routing\r\n * Automatically re-renders when route changes\r\n */\r\n mount(selector: string | HTMLElement): void {\r\n const container = typeof selector === 'string'\r\n ? (document.querySelector(selector) as HTMLElement)\r\n : selector;\r\n\r\n if (!container) {\r\n console.warn('[ClientManager] Mount target not found:', selector);\r\n return;\r\n }\r\n\r\n // Setup reactive routing effect - re-renders when currentPathSignal changes\r\n effect(() => {\r\n const currentPath = this.currentPathSignal();\r\n const Component = this.routeComponents[currentPath]\r\n || this.config.notFoundComponent\r\n || null;\r\n\r\n // Clear container\r\n container.innerHTML = '';\r\n\r\n if (Component) {\r\n try {\r\n const jsx = Component();\r\n // Use @minejs/jsx mount function to properly render JSX\r\n if (jsx) {\r\n mountJSX(jsx, container);\r\n }\r\n } catch (err) {\r\n console.error('[ClientManager] Error rendering component:', currentPath, err);\r\n container.innerHTML = '<p>Error loading component</p>';\r\n }\r\n } else {\r\n container.innerHTML = '<p>No component found for this route</p>';\r\n }\r\n\r\n this.log(`→ Route changed to: ${currentPath}`);\r\n });\r\n\r\n // Trigger initial render by pushing the current path\r\n this.router.push(this.currentPathSignal());\r\n this.log('→ Routing setup complete');\r\n }\r\n\r\n /**\r\n * Get current path signal for reactivity\r\n */\r\n getCurrentPath() {\r\n return this.currentPathSignal;\r\n }\r\n\r\n /**\r\n * Create navigation link handler\r\n */\r\n createLinkHandler(path: string) {\r\n return (e: MouseEvent) => {\r\n e.preventDefault();\r\n this.navigate(path);\r\n };\r\n }\r\n\r\n /**\r\n * Get underlying router for advanced usage\r\n */\r\n getRouter() {\r\n return this.router;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Unbind event\r\n */\r\n off(target: EventTarget, event: string, handler: EventListener): void {\r\n this.eventsManager.off(target, event, handler);\r\n }\r\n\r\n /**\r\n * Get events manager directly\r\n */\r\n getEventsManager(): EventsManager {\r\n return this.eventsManager;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get viewport info as reactive signal\r\n */\r\n getViewport() {\r\n return this.windowManager.getViewport();\r\n }\r\n\r\n /**\r\n * Get window manager directly\r\n */\r\n getWindowManager(): WindowManager {\r\n return this.windowManager;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get i18n instance for translations\r\n */\r\n getI18n() {\r\n return getI18n();\r\n }\r\n\r\n /**\r\n * Get translation string\r\n */\r\n t(key: string, defaultValue?: string) {\r\n const i18n = getI18n();\r\n if (!i18n) {\r\n console.warn('[ClientManager] i18n not initialized. Using default value or key.');\r\n return defaultValue ?? key;\r\n }\r\n return i18n.t(key) ?? defaultValue ?? key;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get lifecycle phase\r\n */\r\n getPhase() {\r\n return this.lifecycle;\r\n }\r\n\r\n /**\r\n * Check if ready\r\n */\r\n isReady(): boolean {\r\n return this.lifecycle === 'ready';\r\n }\r\n\r\n /**\r\n * Internal logging\r\n */\r\n private log(message: string): void {\r\n if (this.debug) {\r\n console.log(`[ClientManager] ${message}`);\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n /**\r\n * Get ClientManager instance if available\r\n */\r\n export function getGlobalClientManager(): ClientManager | undefined {\r\n return globalClientManagerInstance;\r\n }\r\n\r\n export async function start(config: types.ClientManagerConfig): Promise<ClientManager> {\r\n // Read i18n config from HTML meta tag (injected by server)\r\n const metaI18n = document.querySelector('meta[name=\"app-i18n\"]');\r\n if (metaI18n) {\r\n const i18nData = JSON.parse(metaI18n.getAttribute('content') || '{}');\r\n config.i18n = i18nData;\r\n }\r\n\r\n // Create ClientManager instance\r\n const manager = new ClientManager(config);\r\n\r\n // Phase 0: I18N Setup\r\n await setupI18n(config.i18n || {\r\n defaultLanguage: 'en',\r\n supportedLanguages: ['en'],\r\n });\r\n\r\n // Phase 1: BOOT\r\n manager.boot();\r\n\r\n // Phase 2: READY\r\n manager.ready();\r\n\r\n // Handle cleanup on page unload\r\n window.addEventListener('beforeunload', async () => {\r\n await manager.destroy();\r\n });\r\n\r\n return manager;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export * from './types';\r\n export { t } from '@minejs/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["globalClientManagerInstance","ClientManager","config","signal","EventsManager","WindowManager","routesArray","path","component","createRouter","to","eventOrTarget","callbackOrEvent","handler","options","plugin","err","selector","i","container","layoutJsx","mountJSX","effect","currentPath","Component","pageContainer","pageSlot","jsx","e","target","event","getI18n","key","defaultValue","i18n","message","getGlobalClientManager","start","metaI18n","i18nData","manager","setupI18n"],"mappings":"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","file":"index.cjs","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from './types';\r\n import { signal, effect } from '@minejs/signals';\r\n import { setupI18n, getI18n } from '@minejs/i18n';\r\n import { mount as mountJSX } from '@minejs/jsx';\r\n import { EventsManager, Router, WindowManager, createRouter } from '@minejs/browser';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n let globalClientManagerInstance: ClientManager | undefined;\r\n\r\n export class ClientManager {\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n private router : Router;\r\n private eventsManager : EventsManager;\r\n private windowManager : WindowManager;\r\n private lifecycle : 'booting' | 'ready' | 'destroying' | 'destroyed' = 'booting';\r\n private config : types.ClientManagerConfig;\r\n private hooks : types.ClientManagerHooks = {};\r\n private plugins : types.ClientPlugin[] = [];\r\n private debug : boolean;\r\n private routeComponents : Record<string, types.RouteComponent> = {};\r\n private currentPathSignal = signal<string>(window.location.pathname ?? '/');\r\n\r\n constructor(config: types.ClientManagerConfig) {\r\n this.config = config;\r\n this.debug = config.debug ?? false;\r\n\r\n this.log('[INIT] Creating ClientManager');\r\n\r\n // Merge lifecycle hooks from config\r\n if (config.lifecycle) {\r\n this.hooks = { ...config.lifecycle };\r\n }\r\n\r\n // Store plugins from config\r\n this.plugins = config.plugins ?? [];\r\n\r\n // Store route components provided by user\r\n this.routeComponents = config.routes;\r\n\r\n // Initialize managers from @minejs/browser\r\n this.eventsManager = new EventsManager();\r\n this.windowManager = new WindowManager();\r\n\r\n // Initialize router with user-provided routes\r\n const routesArray = Object.entries(config.routes).map(([path, component]) => ({\r\n path,\r\n component\r\n }));\r\n\r\n this.router = createRouter({\r\n routes: routesArray,\r\n notFoundComponent: config.notFoundComponent,\r\n });\r\n\r\n // Connect router changes to signal for automatic re-rendering\r\n this.router.afterEach((to) => {\r\n this.currentPathSignal.set(to.path);\r\n });\r\n\r\n // Store clientManager instance\r\n // eslint-disable-next-line @typescript-eslint/no-this-alias\r\n globalClientManagerInstance = this;\r\n\r\n this.log('[INIT] ClientManager created');\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Setup lifecycle hooks OR bind events\r\n * Overloaded: on(event: 'onBoot'|'onReady'|'onDestroy', callback) - lifecycle\r\n * on(target, event, handler) - event binding\r\n */\r\n on(event: keyof types.ClientManagerHooks, callback: any): this;\r\n on<K extends keyof HTMLElementEventMap>(\r\n target: EventTarget,\r\n event: K | string,\r\n handler: EventListener,\r\n options?: AddEventListenerOptions\r\n ): () => void;\r\n on(\r\n eventOrTarget: keyof types.ClientManagerHooks | EventTarget,\r\n callbackOrEvent?: any,\r\n handler?: EventListener,\r\n options?: AddEventListenerOptions\r\n ): this | (() => void) {\r\n // Check if this is a lifecycle hook call\r\n if (typeof eventOrTarget === 'string' && eventOrTarget.startsWith('on')) {\r\n this.hooks[eventOrTarget as keyof types.ClientManagerHooks] = callbackOrEvent;\r\n return this;\r\n }\r\n\r\n // Otherwise it's an event binding\r\n return this.eventsManager.on(\r\n eventOrTarget as EventTarget,\r\n callbackOrEvent as string,\r\n handler as EventListener,\r\n options\r\n );\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Bootstrap the app - Phase 1: BOOT\r\n */\r\n async boot(): Promise<void> {\r\n if (this.lifecycle !== 'booting') {\r\n console.warn('[ClientManager] Already booted or destroyed');\r\n return;\r\n }\r\n\r\n this.log('⚡ Phase: BOOT');\r\n\r\n try {\r\n // Call plugins onBoot hooks\r\n for (const plugin of this.plugins) {\r\n if (plugin.onBoot) {\r\n this.log(`→ Plugin onBoot: ${plugin.name}`);\r\n await plugin.onBoot({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onBoot hook\r\n if (this.hooks.onBoot) {\r\n this.log('→ Calling onBoot hook');\r\n await this.hooks.onBoot();\r\n }\r\n\r\n this.log('✓ BOOT phase complete');\r\n } catch (err) {\r\n console.error('[ClientManager] Boot failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Ready the app - Phase 2: READY\r\n * Mount to DOM and make everything live\r\n *\r\n * Root selector is always 'body'.\r\n */\r\n async ready(): Promise<void> {\r\n if (this.lifecycle !== 'booting') {\r\n console.warn('[ClientManager] Cannot ready - not in booting phase');\r\n return;\r\n }\r\n\r\n this.log('⚡ Phase: READY');\r\n\r\n try {\r\n const selector = 'body';\r\n\r\n // Set body id to 'root' for mounting\r\n (document.querySelector(selector) as HTMLElement).id = 'root';\r\n\r\n // Mount router\r\n this.mount(selector);\r\n\r\n this.log('→ Router mounted');\r\n\r\n // Call plugins onReady hooks\r\n for (const plugin of this.plugins) {\r\n if (plugin.onReady) {\r\n this.log(`→ Plugin onReady: ${plugin.name}`);\r\n await plugin.onReady({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onReady hook\r\n if (this.hooks.onReady) {\r\n this.log('→ Calling onReady hook');\r\n await this.hooks.onReady();\r\n }\r\n\r\n this.lifecycle = 'ready';\r\n this.log('✓ READY phase complete');\r\n this.log('✓ App is ready!');\r\n } catch (err) {\r\n console.error('[ClientManager] Ready failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown the app - Phase 3: DESTROY\r\n */\r\n async destroy(): Promise<void> {\r\n if (this.lifecycle === 'destroyed') {\r\n console.warn('[ClientManager] Already destroyed');\r\n return;\r\n }\r\n\r\n this.lifecycle = 'destroying';\r\n this.log('⚡ Phase: DESTROY');\r\n\r\n try {\r\n // Call plugins onDestroy hooks (in reverse order)\r\n for (let i = this.plugins.length - 1; i >= 0; i--) {\r\n const plugin = this.plugins[i];\r\n if (plugin.onDestroy) {\r\n this.log(`→ Plugin onDestroy: ${plugin.name}`);\r\n await plugin.onDestroy({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onDestroy hook\r\n if (this.hooks.onDestroy) {\r\n this.log('→ Calling onDestroy hook');\r\n await this.hooks.onDestroy();\r\n }\r\n\r\n // Cleanup managers\r\n this.eventsManager.destroy();\r\n this.windowManager.destroy();\r\n\r\n this.lifecycle = 'destroyed';\r\n this.log('✓ DESTROY phase complete');\r\n } catch (err) {\r\n console.error('[ClientManager] Destroy failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Navigate to path\r\n */\r\n navigate(path: string): void {\r\n this.router.push(path);\r\n }\r\n\r\n /**\r\n * Mount router to DOM element and setup reactive routing\r\n * Automatically re-renders when route changes\r\n * If rootLayout is provided, it wraps all pages\r\n */\r\n mount(selector: string | HTMLElement): void {\r\n const container = typeof selector === 'string'\r\n ? (document.querySelector(selector) as HTMLElement)\r\n : selector;\r\n\r\n if (!container) {\r\n console.warn('[ClientManager] Mount target not found:', selector);\r\n return;\r\n }\r\n\r\n // If rootLayout is provided, mount it first\r\n if (this.config.rootLayout) {\r\n try {\r\n const layoutJsx = this.config.rootLayout();\r\n if (layoutJsx) {\r\n mountJSX(layoutJsx, container);\r\n this.log('→ Root layout mounted');\r\n }\r\n } catch (err) {\r\n console.error('[ClientManager] Error rendering root layout:', err);\r\n container.innerHTML = '<p>Error loading root layout</p>';\r\n }\r\n }\r\n\r\n // Setup reactive routing effect - re-renders when currentPathSignal changes\r\n effect(() => {\r\n const currentPath = this.currentPathSignal();\r\n const Component = this.routeComponents[currentPath]\r\n || this.config.notFoundComponent\r\n || null;\r\n\r\n // Determine where to render the page component\r\n // If rootLayout exists, find the page slot; otherwise use the main container\r\n let pageContainer = container;\r\n if (this.config.rootLayout) {\r\n const pageSlot = container.querySelector('[data-page-slot]');\r\n if (pageSlot) {\r\n pageContainer = pageSlot as HTMLElement;\r\n }\r\n }\r\n\r\n // Clear page container\r\n pageContainer.innerHTML = '';\r\n\r\n if (Component) {\r\n try {\r\n const jsx = Component();\r\n // Use @minejs/jsx mount function to properly render JSX\r\n if (jsx) {\r\n mountJSX(jsx, pageContainer);\r\n }\r\n } catch (err) {\r\n console.error('[ClientManager] Error rendering component:', currentPath, err);\r\n pageContainer.innerHTML = '<p>Error loading component</p>';\r\n }\r\n } else {\r\n pageContainer.innerHTML = '<p>No component found for this route</p>';\r\n }\r\n\r\n this.log(`→ Route changed to: ${currentPath}`);\r\n });\r\n\r\n // Trigger initial render by pushing the current path\r\n this.router.push(this.currentPathSignal());\r\n this.log('→ Routing setup complete');\r\n }\r\n\r\n /**\r\n * Get current path signal for reactivity\r\n */\r\n getCurrentPath() {\r\n return this.currentPathSignal;\r\n }\r\n\r\n /**\r\n * Create navigation link handler\r\n */\r\n createLinkHandler(path: string) {\r\n return (e: MouseEvent) => {\r\n e.preventDefault();\r\n this.navigate(path);\r\n };\r\n }\r\n\r\n /**\r\n * Get underlying router for advanced usage\r\n */\r\n getRouter() {\r\n return this.router;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Unbind event\r\n */\r\n off(target: EventTarget, event: string, handler: EventListener): void {\r\n this.eventsManager.off(target, event, handler);\r\n }\r\n\r\n /**\r\n * Get events manager directly\r\n */\r\n getEventsManager(): EventsManager {\r\n return this.eventsManager;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get viewport info as reactive signal\r\n */\r\n getViewport() {\r\n return this.windowManager.getViewport();\r\n }\r\n\r\n /**\r\n * Get window manager directly\r\n */\r\n getWindowManager(): WindowManager {\r\n return this.windowManager;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get i18n instance for translations\r\n */\r\n getI18n() {\r\n return getI18n();\r\n }\r\n\r\n /**\r\n * Get translation string\r\n */\r\n t(key: string, defaultValue?: string) {\r\n const i18n = getI18n();\r\n if (!i18n) {\r\n console.warn('[ClientManager] i18n not initialized. Using default value or key.');\r\n return defaultValue ?? key;\r\n }\r\n return i18n.t(key) ?? defaultValue ?? key;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get lifecycle phase\r\n */\r\n getPhase() {\r\n return this.lifecycle;\r\n }\r\n\r\n /**\r\n * Check if ready\r\n */\r\n isReady(): boolean {\r\n return this.lifecycle === 'ready';\r\n }\r\n\r\n /**\r\n * Internal logging\r\n */\r\n private log(message: string): void {\r\n if (this.debug) {\r\n console.log(`[ClientManager] ${message}`);\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n /**\r\n * Get ClientManager instance if available\r\n */\r\n export function getGlobalClientManager(): ClientManager | undefined {\r\n return globalClientManagerInstance;\r\n }\r\n\r\n export async function start(config: types.ClientManagerConfig): Promise<ClientManager> {\r\n // Read i18n config from HTML meta tag (injected by server)\r\n const metaI18n = document.querySelector('meta[name=\"app-i18n\"]');\r\n if (metaI18n) {\r\n const i18nData = JSON.parse(metaI18n.getAttribute('content') || '{}');\r\n config.i18n = i18nData;\r\n }\r\n\r\n // Create ClientManager instance\r\n const manager = new ClientManager(config);\r\n\r\n // Phase 0: I18N Setup\r\n await setupI18n(config.i18n || {\r\n defaultLanguage: 'en',\r\n supportedLanguages: ['en'],\r\n });\r\n\r\n // Phase 1: BOOT\r\n manager.boot();\r\n\r\n // Phase 2: READY\r\n manager.ready();\r\n\r\n // Handle cleanup on page unload\r\n window.addEventListener('beforeunload', async () => {\r\n await manager.destroy();\r\n });\r\n\r\n return manager;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export * from './types';\r\n export { t } from '@minejs/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -40,6 +40,7 @@ interface ClientManagerHooks {
|
|
|
40
40
|
interface ClientManagerConfig {
|
|
41
41
|
routes: Record<string, RouteComponent>;
|
|
42
42
|
notFoundComponent?: RouteComponent;
|
|
43
|
+
rootLayout?: () => JSXElement | null;
|
|
43
44
|
debug?: boolean;
|
|
44
45
|
lifecycle?: ClientManagerHooks;
|
|
45
46
|
plugins?: ClientPlugin[];
|
|
@@ -87,6 +88,7 @@ declare class ClientManager {
|
|
|
87
88
|
/**
|
|
88
89
|
* Mount router to DOM element and setup reactive routing
|
|
89
90
|
* Automatically re-renders when route changes
|
|
91
|
+
* If rootLayout is provided, it wraps all pages
|
|
90
92
|
*/
|
|
91
93
|
mount(selector: string | HTMLElement): void;
|
|
92
94
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ interface ClientManagerHooks {
|
|
|
40
40
|
interface ClientManagerConfig {
|
|
41
41
|
routes: Record<string, RouteComponent>;
|
|
42
42
|
notFoundComponent?: RouteComponent;
|
|
43
|
+
rootLayout?: () => JSXElement | null;
|
|
43
44
|
debug?: boolean;
|
|
44
45
|
lifecycle?: ClientManagerHooks;
|
|
45
46
|
plugins?: ClientPlugin[];
|
|
@@ -87,6 +88,7 @@ declare class ClientManager {
|
|
|
87
88
|
/**
|
|
88
89
|
* Mount router to DOM element and setup reactive routing
|
|
89
90
|
* Automatically re-renders when route changes
|
|
91
|
+
* If rootLayout is provided, it wraps all pages
|
|
90
92
|
*/
|
|
91
93
|
mount(selector: string | HTMLElement): void;
|
|
92
94
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {signal,effect}from'@minejs/signals';import {getI18n,setupI18n}from'@minejs/i18n';export{t}from'@minejs/i18n';import {mount}from'@minejs/jsx';import {EventsManager,WindowManager,createRouter}from'@minejs/browser';var
|
|
1
|
+
import {signal,effect}from'@minejs/signals';import {getI18n,setupI18n}from'@minejs/i18n';export{t}from'@minejs/i18n';import {mount}from'@minejs/jsx';import {EventsManager,WindowManager,createRouter}from'@minejs/browser';var h,s=class{constructor(e){this.lifecycle="booting";this.hooks={};this.plugins=[];this.routeComponents={};this.currentPathSignal=signal(window.location.pathname??"/");this.config=e,this.debug=e.debug??false,this.log("[INIT] Creating ClientManager"),e.lifecycle&&(this.hooks={...e.lifecycle}),this.plugins=e.plugins??[],this.routeComponents=e.routes,this.eventsManager=new EventsManager,this.windowManager=new WindowManager;let t=Object.entries(e.routes).map(([n,r])=>({path:n,component:r}));this.router=createRouter({routes:t,notFoundComponent:e.notFoundComponent}),this.router.afterEach(n=>{this.currentPathSignal.set(n.path);}),h=this,this.log("[INIT] ClientManager created");}on(e,t,n,r){return typeof e=="string"&&e.startsWith("on")?(this.hooks[e]=t,this):this.eventsManager.on(e,t,n,r)}async boot(){if(this.lifecycle!=="booting"){console.warn("[ClientManager] Already booted or destroyed");return}this.log("\u26A1 Phase: BOOT");try{for(let e of this.plugins)e.onBoot&&(this.log(`\u2192 Plugin onBoot: ${e.name}`),await e.onBoot({debug:this.debug,config:this.config}));this.hooks.onBoot&&(this.log("\u2192 Calling onBoot hook"),await this.hooks.onBoot()),this.log("\u2713 BOOT phase complete");}catch(e){throw console.error("[ClientManager] Boot failed:",e),e}}async ready(){if(this.lifecycle!=="booting"){console.warn("[ClientManager] Cannot ready - not in booting phase");return}this.log("\u26A1 Phase: READY");try{let e="body";document.querySelector(e).id="root",this.mount(e),this.log("\u2192 Router mounted");for(let t of this.plugins)t.onReady&&(this.log(`\u2192 Plugin onReady: ${t.name}`),await t.onReady({debug:this.debug,config:this.config}));this.hooks.onReady&&(this.log("\u2192 Calling onReady hook"),await this.hooks.onReady()),this.lifecycle="ready",this.log("\u2713 READY phase complete"),this.log("\u2713 App is ready!");}catch(e){throw console.error("[ClientManager] Ready failed:",e),e}}async destroy(){if(this.lifecycle==="destroyed"){console.warn("[ClientManager] Already destroyed");return}this.lifecycle="destroying",this.log("\u26A1 Phase: DESTROY");try{for(let e=this.plugins.length-1;e>=0;e--){let t=this.plugins[e];t.onDestroy&&(this.log(`\u2192 Plugin onDestroy: ${t.name}`),await t.onDestroy({debug:this.debug,config:this.config}));}this.hooks.onDestroy&&(this.log("\u2192 Calling onDestroy hook"),await this.hooks.onDestroy()),this.eventsManager.destroy(),this.windowManager.destroy(),this.lifecycle="destroyed",this.log("\u2713 DESTROY phase complete");}catch(e){throw console.error("[ClientManager] Destroy failed:",e),e}}navigate(e){this.router.push(e);}mount(e){let t=typeof e=="string"?document.querySelector(e):e;if(!t){console.warn("[ClientManager] Mount target not found:",e);return}if(this.config.rootLayout)try{let n=this.config.rootLayout();n&&(mount(n,t),this.log("\u2192 Root layout mounted"));}catch(n){console.error("[ClientManager] Error rendering root layout:",n),t.innerHTML="<p>Error loading root layout</p>";}effect(()=>{let n=this.currentPathSignal(),r=this.routeComponents[n]||this.config.notFoundComponent||null,i=t;if(this.config.rootLayout){let o=t.querySelector("[data-page-slot]");o&&(i=o);}if(i.innerHTML="",r)try{let o=r();o&&mount(o,i);}catch(o){console.error("[ClientManager] Error rendering component:",n,o),i.innerHTML="<p>Error loading component</p>";}else i.innerHTML="<p>No component found for this route</p>";this.log(`\u2192 Route changed to: ${n}`);}),this.router.push(this.currentPathSignal()),this.log("\u2192 Routing setup complete");}getCurrentPath(){return this.currentPathSignal}createLinkHandler(e){return t=>{t.preventDefault(),this.navigate(e);}}getRouter(){return this.router}off(e,t,n){this.eventsManager.off(e,t,n);}getEventsManager(){return this.eventsManager}getViewport(){return this.windowManager.getViewport()}getWindowManager(){return this.windowManager}getI18n(){return getI18n()}t(e,t){let n=getI18n();return n?n.t(e)??t??e:(console.warn("[ClientManager] i18n not initialized. Using default value or key."),t??e)}getPhase(){return this.lifecycle}isReady(){return this.lifecycle==="ready"}log(e){this.debug&&console.log(`[ClientManager] ${e}`);}};function E(){return h}async function b(a){let e=document.querySelector('meta[name="app-i18n"]');if(e){let n=JSON.parse(e.getAttribute("content")||"{}");a.i18n=n;}let t=new s(a);return await setupI18n(a.i18n||{defaultLanguage:"en",supportedLanguages:["en"]}),t.boot(),t.ready(),window.addEventListener("beforeunload",async()=>{await t.destroy();}),t}export{s as ClientManager,E as getGlobalClientManager,b as start};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["globalClientManagerInstance","ClientManager","config","signal","EventsManager","WindowManager","routesArray","path","component","createRouter","to","eventOrTarget","callbackOrEvent","handler","options","plugin","err","selector","i","container","effect","currentPath","Component","jsx","mountJSX","e","target","event","getI18n","key","defaultValue","i18n","message","getGlobalClientManager","start","metaI18n","i18nData","manager","setupI18n"],"mappings":"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","file":"index.js","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from './types';\r\n import { signal, effect } from '@minejs/signals';\r\n import { setupI18n, getI18n } from '@minejs/i18n';\r\n import { mount as mountJSX } from '@minejs/jsx';\r\n import { EventsManager, Router, WindowManager, createRouter } from '@minejs/browser';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n let globalClientManagerInstance: ClientManager | undefined;\r\n\r\n export class ClientManager {\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n private router : Router;\r\n private eventsManager : EventsManager;\r\n private windowManager : WindowManager;\r\n private lifecycle : 'booting' | 'ready' | 'destroying' | 'destroyed' = 'booting';\r\n private config : types.ClientManagerConfig;\r\n private hooks : types.ClientManagerHooks = {};\r\n private plugins : types.ClientPlugin[] = [];\r\n private debug : boolean;\r\n private routeComponents : Record<string, types.RouteComponent> = {};\r\n private currentPathSignal = signal<string>(window.location.pathname ?? '/');\r\n\r\n constructor(config: types.ClientManagerConfig) {\r\n this.config = config;\r\n this.debug = config.debug ?? false;\r\n\r\n this.log('[INIT] Creating ClientManager');\r\n\r\n // Merge lifecycle hooks from config\r\n if (config.lifecycle) {\r\n this.hooks = { ...config.lifecycle };\r\n }\r\n\r\n // Store plugins from config\r\n this.plugins = config.plugins ?? [];\r\n\r\n // Store route components provided by user\r\n this.routeComponents = config.routes;\r\n\r\n // Initialize managers from @minejs/browser\r\n this.eventsManager = new EventsManager();\r\n this.windowManager = new WindowManager();\r\n\r\n // Initialize router with user-provided routes\r\n const routesArray = Object.entries(config.routes).map(([path, component]) => ({\r\n path,\r\n component\r\n }));\r\n\r\n this.router = createRouter({\r\n routes: routesArray,\r\n notFoundComponent: config.notFoundComponent,\r\n });\r\n\r\n // Connect router changes to signal for automatic re-rendering\r\n this.router.afterEach((to) => {\r\n this.currentPathSignal.set(to.path);\r\n });\r\n\r\n // Store clientManager instance\r\n // eslint-disable-next-line @typescript-eslint/no-this-alias\r\n globalClientManagerInstance = this;\r\n\r\n this.log('[INIT] ClientManager created');\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Setup lifecycle hooks OR bind events\r\n * Overloaded: on(event: 'onBoot'|'onReady'|'onDestroy', callback) - lifecycle\r\n * on(target, event, handler) - event binding\r\n */\r\n on(event: keyof types.ClientManagerHooks, callback: any): this;\r\n on<K extends keyof HTMLElementEventMap>(\r\n target: EventTarget,\r\n event: K | string,\r\n handler: EventListener,\r\n options?: AddEventListenerOptions\r\n ): () => void;\r\n on(\r\n eventOrTarget: keyof types.ClientManagerHooks | EventTarget,\r\n callbackOrEvent?: any,\r\n handler?: EventListener,\r\n options?: AddEventListenerOptions\r\n ): this | (() => void) {\r\n // Check if this is a lifecycle hook call\r\n if (typeof eventOrTarget === 'string' && eventOrTarget.startsWith('on')) {\r\n this.hooks[eventOrTarget as keyof types.ClientManagerHooks] = callbackOrEvent;\r\n return this;\r\n }\r\n\r\n // Otherwise it's an event binding\r\n return this.eventsManager.on(\r\n eventOrTarget as EventTarget,\r\n callbackOrEvent as string,\r\n handler as EventListener,\r\n options\r\n );\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Bootstrap the app - Phase 1: BOOT\r\n */\r\n async boot(): Promise<void> {\r\n if (this.lifecycle !== 'booting') {\r\n console.warn('[ClientManager] Already booted or destroyed');\r\n return;\r\n }\r\n\r\n this.log('⚡ Phase: BOOT');\r\n\r\n try {\r\n // Call plugins onBoot hooks\r\n for (const plugin of this.plugins) {\r\n if (plugin.onBoot) {\r\n this.log(`→ Plugin onBoot: ${plugin.name}`);\r\n await plugin.onBoot({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onBoot hook\r\n if (this.hooks.onBoot) {\r\n this.log('→ Calling onBoot hook');\r\n await this.hooks.onBoot();\r\n }\r\n\r\n this.log('✓ BOOT phase complete');\r\n } catch (err) {\r\n console.error('[ClientManager] Boot failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Ready the app - Phase 2: READY\r\n * Mount to DOM and make everything live\r\n *\r\n * Root selector is always 'body'.\r\n */\r\n async ready(): Promise<void> {\r\n if (this.lifecycle !== 'booting') {\r\n console.warn('[ClientManager] Cannot ready - not in booting phase');\r\n return;\r\n }\r\n\r\n this.log('⚡ Phase: READY');\r\n\r\n try {\r\n const selector = 'body';\r\n\r\n // Set body id to 'root' for mounting\r\n (document.querySelector(selector) as HTMLElement).id = 'root';\r\n\r\n // Mount router\r\n this.mount(selector);\r\n\r\n this.log('→ Router mounted');\r\n\r\n // Call plugins onReady hooks\r\n for (const plugin of this.plugins) {\r\n if (plugin.onReady) {\r\n this.log(`→ Plugin onReady: ${plugin.name}`);\r\n await plugin.onReady({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onReady hook\r\n if (this.hooks.onReady) {\r\n this.log('→ Calling onReady hook');\r\n await this.hooks.onReady();\r\n }\r\n\r\n this.lifecycle = 'ready';\r\n this.log('✓ READY phase complete');\r\n this.log('✓ App is ready!');\r\n } catch (err) {\r\n console.error('[ClientManager] Ready failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown the app - Phase 3: DESTROY\r\n */\r\n async destroy(): Promise<void> {\r\n if (this.lifecycle === 'destroyed') {\r\n console.warn('[ClientManager] Already destroyed');\r\n return;\r\n }\r\n\r\n this.lifecycle = 'destroying';\r\n this.log('⚡ Phase: DESTROY');\r\n\r\n try {\r\n // Call plugins onDestroy hooks (in reverse order)\r\n for (let i = this.plugins.length - 1; i >= 0; i--) {\r\n const plugin = this.plugins[i];\r\n if (plugin.onDestroy) {\r\n this.log(`→ Plugin onDestroy: ${plugin.name}`);\r\n await plugin.onDestroy({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onDestroy hook\r\n if (this.hooks.onDestroy) {\r\n this.log('→ Calling onDestroy hook');\r\n await this.hooks.onDestroy();\r\n }\r\n\r\n // Cleanup managers\r\n this.eventsManager.destroy();\r\n this.windowManager.destroy();\r\n\r\n this.lifecycle = 'destroyed';\r\n this.log('✓ DESTROY phase complete');\r\n } catch (err) {\r\n console.error('[ClientManager] Destroy failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Navigate to path\r\n */\r\n navigate(path: string): void {\r\n this.router.push(path);\r\n }\r\n\r\n /**\r\n * Mount router to DOM element and setup reactive routing\r\n * Automatically re-renders when route changes\r\n */\r\n mount(selector: string | HTMLElement): void {\r\n const container = typeof selector === 'string'\r\n ? (document.querySelector(selector) as HTMLElement)\r\n : selector;\r\n\r\n if (!container) {\r\n console.warn('[ClientManager] Mount target not found:', selector);\r\n return;\r\n }\r\n\r\n // Setup reactive routing effect - re-renders when currentPathSignal changes\r\n effect(() => {\r\n const currentPath = this.currentPathSignal();\r\n const Component = this.routeComponents[currentPath]\r\n || this.config.notFoundComponent\r\n || null;\r\n\r\n // Clear container\r\n container.innerHTML = '';\r\n\r\n if (Component) {\r\n try {\r\n const jsx = Component();\r\n // Use @minejs/jsx mount function to properly render JSX\r\n if (jsx) {\r\n mountJSX(jsx, container);\r\n }\r\n } catch (err) {\r\n console.error('[ClientManager] Error rendering component:', currentPath, err);\r\n container.innerHTML = '<p>Error loading component</p>';\r\n }\r\n } else {\r\n container.innerHTML = '<p>No component found for this route</p>';\r\n }\r\n\r\n this.log(`→ Route changed to: ${currentPath}`);\r\n });\r\n\r\n // Trigger initial render by pushing the current path\r\n this.router.push(this.currentPathSignal());\r\n this.log('→ Routing setup complete');\r\n }\r\n\r\n /**\r\n * Get current path signal for reactivity\r\n */\r\n getCurrentPath() {\r\n return this.currentPathSignal;\r\n }\r\n\r\n /**\r\n * Create navigation link handler\r\n */\r\n createLinkHandler(path: string) {\r\n return (e: MouseEvent) => {\r\n e.preventDefault();\r\n this.navigate(path);\r\n };\r\n }\r\n\r\n /**\r\n * Get underlying router for advanced usage\r\n */\r\n getRouter() {\r\n return this.router;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Unbind event\r\n */\r\n off(target: EventTarget, event: string, handler: EventListener): void {\r\n this.eventsManager.off(target, event, handler);\r\n }\r\n\r\n /**\r\n * Get events manager directly\r\n */\r\n getEventsManager(): EventsManager {\r\n return this.eventsManager;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get viewport info as reactive signal\r\n */\r\n getViewport() {\r\n return this.windowManager.getViewport();\r\n }\r\n\r\n /**\r\n * Get window manager directly\r\n */\r\n getWindowManager(): WindowManager {\r\n return this.windowManager;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get i18n instance for translations\r\n */\r\n getI18n() {\r\n return getI18n();\r\n }\r\n\r\n /**\r\n * Get translation string\r\n */\r\n t(key: string, defaultValue?: string) {\r\n const i18n = getI18n();\r\n if (!i18n) {\r\n console.warn('[ClientManager] i18n not initialized. Using default value or key.');\r\n return defaultValue ?? key;\r\n }\r\n return i18n.t(key) ?? defaultValue ?? key;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get lifecycle phase\r\n */\r\n getPhase() {\r\n return this.lifecycle;\r\n }\r\n\r\n /**\r\n * Check if ready\r\n */\r\n isReady(): boolean {\r\n return this.lifecycle === 'ready';\r\n }\r\n\r\n /**\r\n * Internal logging\r\n */\r\n private log(message: string): void {\r\n if (this.debug) {\r\n console.log(`[ClientManager] ${message}`);\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n /**\r\n * Get ClientManager instance if available\r\n */\r\n export function getGlobalClientManager(): ClientManager | undefined {\r\n return globalClientManagerInstance;\r\n }\r\n\r\n export async function start(config: types.ClientManagerConfig): Promise<ClientManager> {\r\n // Read i18n config from HTML meta tag (injected by server)\r\n const metaI18n = document.querySelector('meta[name=\"app-i18n\"]');\r\n if (metaI18n) {\r\n const i18nData = JSON.parse(metaI18n.getAttribute('content') || '{}');\r\n config.i18n = i18nData;\r\n }\r\n\r\n // Create ClientManager instance\r\n const manager = new ClientManager(config);\r\n\r\n // Phase 0: I18N Setup\r\n await setupI18n(config.i18n || {\r\n defaultLanguage: 'en',\r\n supportedLanguages: ['en'],\r\n });\r\n\r\n // Phase 1: BOOT\r\n manager.boot();\r\n\r\n // Phase 2: READY\r\n manager.ready();\r\n\r\n // Handle cleanup on page unload\r\n window.addEventListener('beforeunload', async () => {\r\n await manager.destroy();\r\n });\r\n\r\n return manager;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export * from './types';\r\n export { t } from '@minejs/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["globalClientManagerInstance","ClientManager","config","signal","EventsManager","WindowManager","routesArray","path","component","createRouter","to","eventOrTarget","callbackOrEvent","handler","options","plugin","err","selector","i","container","layoutJsx","mountJSX","effect","currentPath","Component","pageContainer","pageSlot","jsx","e","target","event","getI18n","key","defaultValue","i18n","message","getGlobalClientManager","start","metaI18n","i18nData","manager","setupI18n"],"mappings":"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","file":"index.js","sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import * as types from './types';\r\n import { signal, effect } from '@minejs/signals';\r\n import { setupI18n, getI18n } from '@minejs/i18n';\r\n import { mount as mountJSX } from '@minejs/jsx';\r\n import { EventsManager, Router, WindowManager, createRouter } from '@minejs/browser';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n let globalClientManagerInstance: ClientManager | undefined;\r\n\r\n export class ClientManager {\r\n\r\n // ┌──────────────────────────────── INIT ──────────────────────────────┐\r\n\r\n private router : Router;\r\n private eventsManager : EventsManager;\r\n private windowManager : WindowManager;\r\n private lifecycle : 'booting' | 'ready' | 'destroying' | 'destroyed' = 'booting';\r\n private config : types.ClientManagerConfig;\r\n private hooks : types.ClientManagerHooks = {};\r\n private plugins : types.ClientPlugin[] = [];\r\n private debug : boolean;\r\n private routeComponents : Record<string, types.RouteComponent> = {};\r\n private currentPathSignal = signal<string>(window.location.pathname ?? '/');\r\n\r\n constructor(config: types.ClientManagerConfig) {\r\n this.config = config;\r\n this.debug = config.debug ?? false;\r\n\r\n this.log('[INIT] Creating ClientManager');\r\n\r\n // Merge lifecycle hooks from config\r\n if (config.lifecycle) {\r\n this.hooks = { ...config.lifecycle };\r\n }\r\n\r\n // Store plugins from config\r\n this.plugins = config.plugins ?? [];\r\n\r\n // Store route components provided by user\r\n this.routeComponents = config.routes;\r\n\r\n // Initialize managers from @minejs/browser\r\n this.eventsManager = new EventsManager();\r\n this.windowManager = new WindowManager();\r\n\r\n // Initialize router with user-provided routes\r\n const routesArray = Object.entries(config.routes).map(([path, component]) => ({\r\n path,\r\n component\r\n }));\r\n\r\n this.router = createRouter({\r\n routes: routesArray,\r\n notFoundComponent: config.notFoundComponent,\r\n });\r\n\r\n // Connect router changes to signal for automatic re-rendering\r\n this.router.afterEach((to) => {\r\n this.currentPathSignal.set(to.path);\r\n });\r\n\r\n // Store clientManager instance\r\n // eslint-disable-next-line @typescript-eslint/no-this-alias\r\n globalClientManagerInstance = this;\r\n\r\n this.log('[INIT] ClientManager created');\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Setup lifecycle hooks OR bind events\r\n * Overloaded: on(event: 'onBoot'|'onReady'|'onDestroy', callback) - lifecycle\r\n * on(target, event, handler) - event binding\r\n */\r\n on(event: keyof types.ClientManagerHooks, callback: any): this;\r\n on<K extends keyof HTMLElementEventMap>(\r\n target: EventTarget,\r\n event: K | string,\r\n handler: EventListener,\r\n options?: AddEventListenerOptions\r\n ): () => void;\r\n on(\r\n eventOrTarget: keyof types.ClientManagerHooks | EventTarget,\r\n callbackOrEvent?: any,\r\n handler?: EventListener,\r\n options?: AddEventListenerOptions\r\n ): this | (() => void) {\r\n // Check if this is a lifecycle hook call\r\n if (typeof eventOrTarget === 'string' && eventOrTarget.startsWith('on')) {\r\n this.hooks[eventOrTarget as keyof types.ClientManagerHooks] = callbackOrEvent;\r\n return this;\r\n }\r\n\r\n // Otherwise it's an event binding\r\n return this.eventsManager.on(\r\n eventOrTarget as EventTarget,\r\n callbackOrEvent as string,\r\n handler as EventListener,\r\n options\r\n );\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Bootstrap the app - Phase 1: BOOT\r\n */\r\n async boot(): Promise<void> {\r\n if (this.lifecycle !== 'booting') {\r\n console.warn('[ClientManager] Already booted or destroyed');\r\n return;\r\n }\r\n\r\n this.log('⚡ Phase: BOOT');\r\n\r\n try {\r\n // Call plugins onBoot hooks\r\n for (const plugin of this.plugins) {\r\n if (plugin.onBoot) {\r\n this.log(`→ Plugin onBoot: ${plugin.name}`);\r\n await plugin.onBoot({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onBoot hook\r\n if (this.hooks.onBoot) {\r\n this.log('→ Calling onBoot hook');\r\n await this.hooks.onBoot();\r\n }\r\n\r\n this.log('✓ BOOT phase complete');\r\n } catch (err) {\r\n console.error('[ClientManager] Boot failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Ready the app - Phase 2: READY\r\n * Mount to DOM and make everything live\r\n *\r\n * Root selector is always 'body'.\r\n */\r\n async ready(): Promise<void> {\r\n if (this.lifecycle !== 'booting') {\r\n console.warn('[ClientManager] Cannot ready - not in booting phase');\r\n return;\r\n }\r\n\r\n this.log('⚡ Phase: READY');\r\n\r\n try {\r\n const selector = 'body';\r\n\r\n // Set body id to 'root' for mounting\r\n (document.querySelector(selector) as HTMLElement).id = 'root';\r\n\r\n // Mount router\r\n this.mount(selector);\r\n\r\n this.log('→ Router mounted');\r\n\r\n // Call plugins onReady hooks\r\n for (const plugin of this.plugins) {\r\n if (plugin.onReady) {\r\n this.log(`→ Plugin onReady: ${plugin.name}`);\r\n await plugin.onReady({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onReady hook\r\n if (this.hooks.onReady) {\r\n this.log('→ Calling onReady hook');\r\n await this.hooks.onReady();\r\n }\r\n\r\n this.lifecycle = 'ready';\r\n this.log('✓ READY phase complete');\r\n this.log('✓ App is ready!');\r\n } catch (err) {\r\n console.error('[ClientManager] Ready failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n /**\r\n * Shutdown the app - Phase 3: DESTROY\r\n */\r\n async destroy(): Promise<void> {\r\n if (this.lifecycle === 'destroyed') {\r\n console.warn('[ClientManager] Already destroyed');\r\n return;\r\n }\r\n\r\n this.lifecycle = 'destroying';\r\n this.log('⚡ Phase: DESTROY');\r\n\r\n try {\r\n // Call plugins onDestroy hooks (in reverse order)\r\n for (let i = this.plugins.length - 1; i >= 0; i--) {\r\n const plugin = this.plugins[i];\r\n if (plugin.onDestroy) {\r\n this.log(`→ Plugin onDestroy: ${plugin.name}`);\r\n await plugin.onDestroy({\r\n debug: this.debug,\r\n config: this.config\r\n });\r\n }\r\n }\r\n\r\n // Call user onDestroy hook\r\n if (this.hooks.onDestroy) {\r\n this.log('→ Calling onDestroy hook');\r\n await this.hooks.onDestroy();\r\n }\r\n\r\n // Cleanup managers\r\n this.eventsManager.destroy();\r\n this.windowManager.destroy();\r\n\r\n this.lifecycle = 'destroyed';\r\n this.log('✓ DESTROY phase complete');\r\n } catch (err) {\r\n console.error('[ClientManager] Destroy failed:', err);\r\n throw err;\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Navigate to path\r\n */\r\n navigate(path: string): void {\r\n this.router.push(path);\r\n }\r\n\r\n /**\r\n * Mount router to DOM element and setup reactive routing\r\n * Automatically re-renders when route changes\r\n * If rootLayout is provided, it wraps all pages\r\n */\r\n mount(selector: string | HTMLElement): void {\r\n const container = typeof selector === 'string'\r\n ? (document.querySelector(selector) as HTMLElement)\r\n : selector;\r\n\r\n if (!container) {\r\n console.warn('[ClientManager] Mount target not found:', selector);\r\n return;\r\n }\r\n\r\n // If rootLayout is provided, mount it first\r\n if (this.config.rootLayout) {\r\n try {\r\n const layoutJsx = this.config.rootLayout();\r\n if (layoutJsx) {\r\n mountJSX(layoutJsx, container);\r\n this.log('→ Root layout mounted');\r\n }\r\n } catch (err) {\r\n console.error('[ClientManager] Error rendering root layout:', err);\r\n container.innerHTML = '<p>Error loading root layout</p>';\r\n }\r\n }\r\n\r\n // Setup reactive routing effect - re-renders when currentPathSignal changes\r\n effect(() => {\r\n const currentPath = this.currentPathSignal();\r\n const Component = this.routeComponents[currentPath]\r\n || this.config.notFoundComponent\r\n || null;\r\n\r\n // Determine where to render the page component\r\n // If rootLayout exists, find the page slot; otherwise use the main container\r\n let pageContainer = container;\r\n if (this.config.rootLayout) {\r\n const pageSlot = container.querySelector('[data-page-slot]');\r\n if (pageSlot) {\r\n pageContainer = pageSlot as HTMLElement;\r\n }\r\n }\r\n\r\n // Clear page container\r\n pageContainer.innerHTML = '';\r\n\r\n if (Component) {\r\n try {\r\n const jsx = Component();\r\n // Use @minejs/jsx mount function to properly render JSX\r\n if (jsx) {\r\n mountJSX(jsx, pageContainer);\r\n }\r\n } catch (err) {\r\n console.error('[ClientManager] Error rendering component:', currentPath, err);\r\n pageContainer.innerHTML = '<p>Error loading component</p>';\r\n }\r\n } else {\r\n pageContainer.innerHTML = '<p>No component found for this route</p>';\r\n }\r\n\r\n this.log(`→ Route changed to: ${currentPath}`);\r\n });\r\n\r\n // Trigger initial render by pushing the current path\r\n this.router.push(this.currentPathSignal());\r\n this.log('→ Routing setup complete');\r\n }\r\n\r\n /**\r\n * Get current path signal for reactivity\r\n */\r\n getCurrentPath() {\r\n return this.currentPathSignal;\r\n }\r\n\r\n /**\r\n * Create navigation link handler\r\n */\r\n createLinkHandler(path: string) {\r\n return (e: MouseEvent) => {\r\n e.preventDefault();\r\n this.navigate(path);\r\n };\r\n }\r\n\r\n /**\r\n * Get underlying router for advanced usage\r\n */\r\n getRouter() {\r\n return this.router;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Unbind event\r\n */\r\n off(target: EventTarget, event: string, handler: EventListener): void {\r\n this.eventsManager.off(target, event, handler);\r\n }\r\n\r\n /**\r\n * Get events manager directly\r\n */\r\n getEventsManager(): EventsManager {\r\n return this.eventsManager;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get viewport info as reactive signal\r\n */\r\n getViewport() {\r\n return this.windowManager.getViewport();\r\n }\r\n\r\n /**\r\n * Get window manager directly\r\n */\r\n getWindowManager(): WindowManager {\r\n return this.windowManager;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get i18n instance for translations\r\n */\r\n getI18n() {\r\n return getI18n();\r\n }\r\n\r\n /**\r\n * Get translation string\r\n */\r\n t(key: string, defaultValue?: string) {\r\n const i18n = getI18n();\r\n if (!i18n) {\r\n console.warn('[ClientManager] i18n not initialized. Using default value or key.');\r\n return defaultValue ?? key;\r\n }\r\n return i18n.t(key) ?? defaultValue ?? key;\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n\r\n // ┌──────────────────────────────── ──── ──────────────────────────────┐\r\n\r\n /**\r\n * Get lifecycle phase\r\n */\r\n getPhase() {\r\n return this.lifecycle;\r\n }\r\n\r\n /**\r\n * Check if ready\r\n */\r\n isReady(): boolean {\r\n return this.lifecycle === 'ready';\r\n }\r\n\r\n /**\r\n * Internal logging\r\n */\r\n private log(message: string): void {\r\n if (this.debug) {\r\n console.log(`[ClientManager] ${message}`);\r\n }\r\n }\r\n\r\n // └────────────────────────────────────────────────────────────────────┘\r\n\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n /**\r\n * Get ClientManager instance if available\r\n */\r\n export function getGlobalClientManager(): ClientManager | undefined {\r\n return globalClientManagerInstance;\r\n }\r\n\r\n export async function start(config: types.ClientManagerConfig): Promise<ClientManager> {\r\n // Read i18n config from HTML meta tag (injected by server)\r\n const metaI18n = document.querySelector('meta[name=\"app-i18n\"]');\r\n if (metaI18n) {\r\n const i18nData = JSON.parse(metaI18n.getAttribute('content') || '{}');\r\n config.i18n = i18nData;\r\n }\r\n\r\n // Create ClientManager instance\r\n const manager = new ClientManager(config);\r\n\r\n // Phase 0: I18N Setup\r\n await setupI18n(config.i18n || {\r\n defaultLanguage: 'en',\r\n supportedLanguages: ['en'],\r\n });\r\n\r\n // Phase 1: BOOT\r\n manager.boot();\r\n\r\n // Phase 2: READY\r\n manager.ready();\r\n\r\n // Handle cleanup on page unload\r\n window.addEventListener('beforeunload', async () => {\r\n await manager.destroy();\r\n });\r\n\r\n return manager;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ ════ ════════════════════════════════════════╗\r\n\r\n export * from './types';\r\n export { t } from '@minejs/i18n';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cruxjs/client",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Client application manager with routing, lifecycle hooks, and plugin-based extensibility.",
|
|
5
5
|
"keywords": ["cruxjs", "client"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@minejs/browser": "^0.0.4",
|
|
44
|
-
"@minejs/i18n": "^0.0
|
|
44
|
+
"@minejs/i18n": "^0.1.0",
|
|
45
45
|
"@minejs/jsx": "^0.0.8",
|
|
46
46
|
"@minejs/signals": "^0.0.6"
|
|
47
47
|
},
|