jops-core 1.0.5 → 1.0.6

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 CHANGED
@@ -299,12 +299,60 @@ EventBus.unsubscribe("user:login", handler);
299
299
 
300
300
  ---
301
301
 
302
+ ### `Thread` — Web Worker Base Class
303
+
304
+ Base class for running CPU-bound work on a background thread. Extend and override `run()` and `onMessage()`.
305
+
306
+ | Method | Description |
307
+ | ------------------- | ------------------------------------------------------------------------------------------------ |
308
+ | `start(params?)` | Spawns a Web Worker, runs `run(params)` on it, and wires `onMessage` for results |
309
+ | `run(params)` | Override to define the work. Runs inside the Worker — self-contained, no outer scope access |
310
+ | `onMessage(event)` | Override to handle results posted back from the worker via `self.postMessage()` |
311
+ | `terminate()` | Terminates the worker immediately and clears the internal reference |
312
+
313
+ ```js
314
+ import { Thread } from "jops-core";
315
+
316
+ class HashThread extends Thread {
317
+ run(params) {
318
+ // runs on a background thread — no access to the main thread or class instance
319
+ let hash = 0;
320
+ for (let i = 0; i < params.data.length; i++) hash ^= params.data.charCodeAt(i);
321
+ self.postMessage(hash);
322
+ }
323
+ onMessage(event) {
324
+ console.log("Hash result:", event.data);
325
+ }
326
+ }
327
+
328
+ const t = new HashThread();
329
+ t.start({ data: "hello world" });
330
+ // later:
331
+ t.terminate();
332
+ ```
333
+
334
+ > **Note:** Each `Thread` instance spawns one OS thread. Avoid running more concurrent threads than the device has logical CPU cores — excess threads add context-switching overhead with no throughput gain. Check `navigator.hardwareConcurrency` for the core count. A `ThreadPool` class that manages a fixed worker pool automatically is planned for a future JOPS release.
335
+
336
+ ---
337
+
302
338
  ## CLI
303
339
 
304
- | Command | Description |
305
- | --------------------- | -------------------------------------------------------------------------- |
306
- | `npx jops-core init` | Vendor library, configure `index.html`, inject scripts into `package.json` |
307
- | `npx jops-core build` | Generate production-ready `dist/` folder |
340
+ | Command | Description |
341
+ | ---------------------- | -------------------------------------------------------------------------- |
342
+ | `npx jops-core init` | Vendor library, configure `index.html`, inject scripts into `package.json` |
343
+ | `npx jops-core update` | Copy the latest `jops-core.min.js` from `node_modules` to `lib/` |
344
+ | `npx jops-core build` | Generate production-ready `dist/` folder |
345
+
346
+ ### Updating the library
347
+
348
+ When a new version of JOPS is released, run both commands:
349
+
350
+ ```bash
351
+ npm i jops-core@latest
352
+ npx jops-core update
353
+ ```
354
+
355
+ `npm i` updates `node_modules`; `npx jops-core update` copies the new `lib/jops-core.min.js` into your project so the browser picks it up. Running `update` without `npm i` first has no effect — it copies whatever version is already in `node_modules`.
308
356
 
309
357
  ---
310
358
 
package/bin/init.js CHANGED
@@ -6,9 +6,10 @@ import { fileURLToPath } from "url";
6
6
 
7
7
  const [,, command] = process.argv;
8
8
 
9
- if (command !== "init" && command !== "build") {
9
+ if (command !== "init" && command !== "build" && command !== "update") {
10
10
  console.log("Usage:");
11
11
  console.log(" npx jops-core init — vendor library and configure index.html");
12
+ console.log(" npx jops-core update — copy the latest jops-core.min.js to lib/");
12
13
  console.log(" npx jops-core build — generate production-ready dist/ folder");
13
14
  process.exit(0);
14
15
  }
@@ -193,6 +194,24 @@ ${MODULE_SCRIPT}
193
194
  }
194
195
  }
195
196
 
197
+ // ─── update ──────────────────────────────────────────────────────────────────
198
+
199
+ if (command === "update") {
200
+ const libDir = path.join(consumerRoot, "lib");
201
+ const vendorDest = path.join(libDir, "jops-core.min.js");
202
+ const vendorSrc = path.join(__dirname, "../lib/jops-core.min.js");
203
+
204
+ if (!fs.existsSync(libDir)) {
205
+ console.error("[jops-core] lib/ folder not found. Run 'npx jops-core init' first.");
206
+ process.exit(1);
207
+ }
208
+
209
+ fs.copyFileSync(vendorSrc, vendorDest);
210
+
211
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"), "utf8"));
212
+ console.log(`[jops-core] Updated lib/jops-core.min.js to v${pkg.version}`);
213
+ }
214
+
196
215
  // ─── build ───────────────────────────────────────────────────────────────────
197
216
 
198
217
  if (command === "build") {
@@ -288,4 +288,49 @@ export declare class Router extends View {
288
288
  */
289
289
  replace(path: string, ClassDef?: typeof View): Promise<void>;
290
290
  }
291
+ /**
292
+ * Base class for running work on a Web Worker (background thread).
293
+ * Extend and override {@link Thread#run} and {@link Thread#onMessage}.
294
+ *
295
+ * @example
296
+ * class MyThread extends Thread {
297
+ * run(params) {
298
+ * const result = params.a + params.b;
299
+ * self.postMessage(result);
300
+ * }
301
+ * onMessage(event) {
302
+ * console.log("result:", event.data);
303
+ * }
304
+ * }
305
+ * const t = new MyThread();
306
+ * t.start({ a: 1, b: 2 });
307
+ * // later:
308
+ * t.terminate();
309
+ */
310
+ export declare class Thread {
311
+ #private;
312
+ /**
313
+ * Override to define the work the thread performs.
314
+ * Runs inside a Web Worker — must be self-contained (no access to outer scope or class instance).
315
+ * Call `self.postMessage(result)` to send a result back to the main thread.
316
+ * @param {*} params - The value passed to {@link Thread#start}.
317
+ */
318
+ run(params: any): void;
319
+ /**
320
+ * Override to handle messages posted back from the worker via `self.postMessage()`.
321
+ * @param {MessageEvent} event
322
+ */
323
+ onMessage(event: MessageEvent): void;
324
+ /**
325
+ * Spawns the Web Worker, serializes {@link Thread#run}, and passes `params` as the initial input.
326
+ * @param {*} [params] - Optional data forwarded to `run(params)` inside the worker.
327
+ */
328
+ start(params?: any): void;
329
+ /**
330
+ * Terminates the Web Worker immediately and clears the internal reference.
331
+ * Safe to call even if the worker has already finished.
332
+ * After terminating, `start()` can be called again to spawn a new worker.
333
+ */
334
+ terminate(): void;
335
+ }
291
336
  export {};
@@ -1 +1 @@
1
- var L=Object.defineProperty;var h=(a,t)=>L(a,"name",{value:t,configurable:!0});(()=>{console.log("JOPS initialized");let a=document.querySelector("script[data-app]")?.dataset.app??"./src/App.js";import(new URL(a,document.baseURI).href).then(t=>new t.default).catch(t=>console.error("JOPS: App.js not found or failed to load",t))})();var f=class{static{h(this,"RawHTML")}constructor(t){this.html=t}},_=h(a=>new f(a),"raw");function E(a,...t){return a.reduce((e,s,n)=>{if(n>=t.length)return e+s;let o=t[n];return o==null?e+s:o instanceof f?e+s+o.html:e+s+String(o).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},"")}h(E,"layout");var m=class{static{h(this,"Element")}constructor(t,e){this.element=t?.id===e?t:t.querySelector(`#${e}`)}set(t){this.element.innerHTML=t}value(){return this.element.innerHTML}},p=class a{static{h(this,"View")}#t=!1;#e=!1;constructor(t){this.loadLayout(t),this.subViews=[]}async loadLayout(t){if(t)if(t.endsWith(".js"))try{let e=await import(new URL(t,document.baseURI).href);this.layout=e.default}catch(e){console.error("JOPS: failed to load layout:",t,e)}else this.layout=t}setStyle(t){if(t)if(this.style=t,t.endsWith(".css")){if(document.querySelector(`link[href="${t}"]`))return;let e=document.createElement("link");e.rel="stylesheet",e.href=t,document.head.appendChild(e)}else{if(document.querySelector(`style[data-jops="${t}"]`))return;let e=document.createElement("style");e.setAttribute("data-jops",t),e.textContent=t,document.head.appendChild(e)}}get(t){return new m(this.domNode,t)}bindEvents(){if(!this.domNode)return;let t=[...this.domNode.querySelectorAll("[type='jops-event-bind']")];this.domNode.getAttribute("type")==="jops-event-bind"&&t.unshift(this.domNode);for(let e of t)for(let s of e.attributes){if(!s.name.startsWith("on"))continue;let n=s.value.trim();typeof this[n]=="function"&&(e[s.name]=o=>this[n](o))}}inflate(){if(!this.layout||this.#t)return;this.#t=!0;let t=document.createElement("template");t.innerHTML=this.layout;let s=t.content.cloneNode(!0).firstElementChild;this.domNode=s}async addSubView(t,e){this.subViews.push(e),e.domNode||(await e.loadLayout(),e.inflate()),this.domNode.querySelector(`#${t}`).appendChild(e.domNode),await e?.onLayout?.(),await e?.onResume?.(),e?.bindEvents?.()}hide(){this.domNode.style.display="none"}show(){this.domNode.style.display=""}find(t){let e=[];for(let s of this.subViews)t(s)&&e.push(s),e.push(...s.find?.(t)??[]);return e}destroy(){this.subViews.forEach(t=>t.destroy?.()),this.subViews=[],this.domNode?.remove(),this.domNode=null,this.layout=null}removeSubView(t){t.destroy?.(),this.subViews=this.subViews.filter(e=>e!==t)}async insertSubView(t,e){t.domNode||(await t.loadLayout(),t.inflate());let s=this.domNode.children[e]??null;this.domNode.insertBefore(t.domNode,s),this.subViews.splice(e,0,t),await t?.onLayout?.(),await t?.onResume?.(),t?.bindEvents?.()}async render(){if(!this.layout||this.#e)return;this.#e=!0;let t=this.domNode??document.body;t===document.body&&(t.innerHTML=this.layout);let e=Array.from(t.querySelectorAll("[type='jops']")).filter(s=>s.tagName==="ROUTER"||!s.closest("router")||s.closest("router")===t);e.length!==0&&await Promise.all(e.map(async s=>{if(s.tagName==="ROUTER"){let d=new y(s);await d.render(),await d.onLayout();return}let n=s.getAttribute("layout"),o=s.getAttribute("src");if(!o&&!n)return;let r;if(o){let l=(await import(new URL(o,document.baseURI).href)).default;r=new l}else r=new a;this.subViews.push(r),await r.loadLayout(n);let i=document.createElement("template");i.innerHTML=r.layout??"";let c=i.content.cloneNode(!0);if(s.children.length>0){let d=c.firstElementChild??c;Array.from(s.children).forEach(l=>d.appendChild(l))}let u=c.firstElementChild;r.routePath=s.getAttribute("path"),s.replaceWith(c),r.domNode=u,await r.render(),await r?.onLayout?.(),await r?.onResume?.(),r?.bindEvents?.()}))}},b=class a{static{h(this,"EventBus")}static#t={};static subscribe(t,e){a.#t[t]||(a.#t[t]=[]),a.#t[t].push(e)}static send(t,e){a.#t[t]?.forEach(s=>s(e))}static unsubscribe(t,e){a.#t[t]&&(a.#t[t]=a.#t[t].filter(s=>s!==e))}},T=class a{static{h(this,"Store")}static#t=null;static singleton(){return a.#t||(a.#t=new a),a.#t}constructor(){this._state={}}get(t){return t.split(".").reduce((e,s)=>e?.[s],this._state)}set(t,e){let s=t.split("."),n=s.pop(),o=s.reduce((r,i)=>(r[i]||(r[i]={}),r[i]),this._state);o[n]=e}clear(){this._state={}}},y=class a extends p{static{h(this,"Router")}static#t=null;static singleton(){return a.#t}constructor(t){super(),a.#t=this,this.rootNode=t.parentNode,this.routerDomNode=t,this.domNode=t,this.layout=t.innerHTML,this.animation=t.getAttribute("animation")??"none"}async onLayout(){let t=document.createDocumentFragment();for(;this.routerDomNode.firstChild;)t.appendChild(this.routerDomNode.firstChild);this.routerDomNode.replaceWith(t),this.domNode=this.rootNode,this.initRouter()}initRouter(){this.routes={};for(let e of this.subViews)if(e.routePath){let s=e.routePath.slice(1).split("?")[0];this.routes[s]=e}this._navStack=[],this._baseView=null;let t=Object.keys(this.routes)[0];history.replaceState(null,"","#"+t),window.addEventListener("hashchange",()=>{let e=window.location.hash.slice(1),s=e.split("?")[0];if(!(!s||!this.routes[s])){if(this._pendingBack){this._pendingBack=!1;return}this._switchTo(e)}}),window.addEventListener("popstate",()=>{if(this._navStack.length>0){let n=this._navStack.pop(),o=this._navStack.at(-1)??this._baseView;n?.onBackPressed?.(),this._animateTransition(n,o,"back").then(()=>n.destroy());return}this._currentView?.onBackPressed?.();let e=window.location.hash.slice(1),s=e.split("?")[0];s&&this.routes[s]&&(this._pendingBack=!0,this._switchTo(e))}),this._switchTo(t)}async _animateTransition(t,e,s){this.animation==="slide"?await this._slideTransition(t,e,s):this.animation==="fade"?await this._fadeTransition(t,e):(t.domNode.hidden=!0,e.domNode.hidden=!1,this._currentView=e,e?.onResume?.())}async _slideTransition(t,e,s){let n=this.domNode,o=s==="forward",r=n.getBoundingClientRect(),i=t.domNode.getBoundingClientRect(),c=i.left-r.left,u=i.top-r.top,d=i.width,l=n.offsetWidth-c,w=`position:absolute;left:${c}px;top:${u}px;width:${d}px`;n.style.cssText=`position:relative;overflow:hidden;height:${n.offsetHeight}px`,t.domNode.style.cssText=`${w};z-index:${o?1:2}`,t.domNode.hidden=!1,e.domNode.style.cssText=`${w};z-index:${o?2:1}`,e.domNode.hidden=!1;let x=o?e.domNode:t.domNode,S=o?`${l}px`:"0px",R=o?"0px":`${l}px`,N=x.animate([{transform:`translateX(${S})`},{transform:`translateX(${R})`}],{duration:300,easing:"ease",fill:"forwards"});await N.finished,N.cancel(),t.domNode.hidden=!0,t.domNode.style.cssText="",e.domNode.style.cssText="",n.style.cssText="",this._currentView=e,e?.onResume?.()}async _fadeTransition(t,e){let s=this.domNode,n=s.getBoundingClientRect(),o=t.domNode.getBoundingClientRect(),r=o.left-n.left,i=o.top-n.top,c=o.width,u=`position:absolute;left:${r}px;top:${i}px;width:${c}px`;s.style.cssText=`position:relative;overflow:hidden;height:${s.offsetHeight}px`,t.domNode.style.cssText=u,t.domNode.hidden=!1,e.domNode.style.cssText=u,e.domNode.hidden=!1;let d=t.domNode.animate([{opacity:1},{opacity:0}],{duration:450,easing:"ease",fill:"forwards"}),l=e.domNode.animate([{opacity:0},{opacity:1}],{duration:450,easing:"ease",fill:"forwards"});await Promise.all([d.finished,l.finished]),d.cancel(),l.cancel(),t.domNode.hidden=!0,t.domNode.style.cssText="",e.domNode.style.cssText="",s.style.cssText="",this._currentView=e,e?.onResume?.()}_switchTo(t){let e=t.split("?")[0];for(let[s,n]of Object.entries(this.routes)){let o=s===e;n.domNode.hidden=!o,o&&(this._currentView=n,n?.onResume?.())}}back(){history.back()}getParams(){let t=window.location.hash.split("?")[1];return t?Object.fromEntries(new URLSearchParams(t)):{}}async navigate(t,e){let[s,n]=t.replace(/^\//,"").split("?"),o=s,r=e??this.routes[o]?.constructor;if(!r){console.error(`JOPS Router: no class found for path "${t}"`);return}this._navStack.length===0&&(this._baseView=this._currentView);let i=new r;await i.loadLayout(),i.inflate(),this.domNode.appendChild(i.domNode),history.pushState({jopsNav:!0},"","#"+o+(n?"?"+n:"")),await i.onLayout?.(),await i.onResume?.(),i.bindEvents?.(),this._navStack.push(i),this._animateTransition(this._currentView,i,"forward")}async replace(t,e){this._navStack.forEach(i=>i.destroy?.()),this._navStack=[];let[s,n]=t.replace(/^\//,"").split("?"),o=s,r="#"+o+(n?"?"+n:"");if(this.routes[o])history.replaceState(null,"",r),this._switchTo(o);else if(e){let i=new e;await i.loadLayout(),i.inflate(),this.domNode.appendChild(i.domNode),await i.onLayout?.(),await i.onResume?.(),i.bindEvents?.(),this._currentView&&(this._currentView.domNode.hidden=!0),this._currentView=i,history.replaceState(null,"",r)}else console.error(`JOPS Router: no instance or class found for path "${t}"`)}};export{b as EventBus,y as Router,T as Store,p as View,E as layout,_ as raw};
1
+ var V=Object.defineProperty;var c=(a,t)=>V(a,"name",{value:t,configurable:!0});(()=>{console.log("JOPS initialized");let a=document.querySelector("script[data-app]")?.dataset.app??"./src/App.js";import(new URL(a,document.baseURI).href).then(t=>new t.default).catch(t=>console.error("JOPS: App.js not found or failed to load",t))})();var f=class{static{c(this,"RawHTML")}constructor(t){this.html=t}},k=c(a=>new f(a),"raw");function _(a,...t){return a.reduce((e,s,o)=>{if(o>=t.length)return e+s;let n=t[o];return n==null?e+s:n instanceof f?e+s+n.html:e+s+String(n).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},"")}c(_,"layout");var m=class{static{c(this,"Element")}constructor(t,e){this.element=t?.id===e?t:t.querySelector(`#${e}`)}set(t){this.element.innerHTML=t}value(){return this.element.innerHTML}},p=class a{static{c(this,"View")}#t=!1;#e=!1;constructor(t){this.loadLayout(t),this.subViews=[]}async loadLayout(t){if(t)if(t.endsWith(".js"))try{let e=await import(new URL(t,document.baseURI).href);this.layout=e.default}catch(e){console.error("JOPS: failed to load layout:",t,e)}else this.layout=t}setStyle(t){if(t)if(this.style=t,t.endsWith(".css")){if(document.querySelector(`link[href="${t}"]`))return;let e=document.createElement("link");e.rel="stylesheet",e.href=t,document.head.appendChild(e)}else{if(document.querySelector(`style[data-jops="${t}"]`))return;let e=document.createElement("style");e.setAttribute("data-jops",t),e.textContent=t,document.head.appendChild(e)}}get(t){return new m(this.domNode,t)}bindEvents(){if(!this.domNode)return;let t=[...this.domNode.querySelectorAll("[type='jops-event-bind']")];this.domNode.getAttribute("type")==="jops-event-bind"&&t.unshift(this.domNode);for(let e of t)for(let s of e.attributes){if(!s.name.startsWith("on"))continue;let o=s.value.trim();typeof this[o]=="function"&&(e[s.name]=n=>this[o](n))}}inflate(){if(!this.layout||this.#t)return;this.#t=!0;let t=document.createElement("template");t.innerHTML=this.layout;let s=t.content.cloneNode(!0).firstElementChild;this.domNode=s}async addSubView(t,e){this.subViews.push(e),e.domNode||(await e.loadLayout(),e.inflate()),this.domNode.querySelector(`#${t}`).appendChild(e.domNode),await e?.onLayout?.(),await e?.onResume?.(),e?.bindEvents?.()}hide(){this.domNode.style.display="none"}show(){this.domNode.style.display=""}find(t){let e=[];for(let s of this.subViews)t(s)&&e.push(s),e.push(...s.find?.(t)??[]);return e}destroy(){this.subViews.forEach(t=>t.destroy?.()),this.subViews=[],this.domNode?.remove(),this.domNode=null,this.layout=null}removeSubView(t){t.destroy?.(),this.subViews=this.subViews.filter(e=>e!==t)}async insertSubView(t,e){t.domNode||(await t.loadLayout(),t.inflate());let s=this.domNode.children[e]??null;this.domNode.insertBefore(t.domNode,s),this.subViews.splice(e,0,t),await t?.onLayout?.(),await t?.onResume?.(),t?.bindEvents?.()}async render(){if(!this.layout||this.#e)return;this.#e=!0;let t=this.domNode??document.body;t===document.body&&(t.innerHTML=this.layout);let e=Array.from(t.querySelectorAll("[type='jops']")).filter(s=>s.tagName==="ROUTER"||!s.closest("router")||s.closest("router")===t);e.length!==0&&await Promise.all(e.map(async s=>{if(s.tagName==="ROUTER"){let d=new y(s);await d.render(),await d.onLayout();return}let o=s.getAttribute("layout"),n=s.getAttribute("src");if(!n&&!o)return;let r;if(n){let h=(await import(new URL(n,document.baseURI).href)).default;r=new h}else r=new a;this.subViews.push(r),await r.loadLayout(o);let i=document.createElement("template");i.innerHTML=r.layout??"";let l=i.content.cloneNode(!0);if(s.children.length>0){let d=l.firstElementChild??l;Array.from(s.children).forEach(h=>d.appendChild(h))}let u=l.firstElementChild;r.routePath=s.getAttribute("path"),s.replaceWith(l),r.domNode=u,await r.render(),await r?.onLayout?.(),await r?.onResume?.(),r?.bindEvents?.()}))}},b=class a{static{c(this,"EventBus")}static#t={};static subscribe(t,e){a.#t[t]||(a.#t[t]=[]),a.#t[t].push(e)}static send(t,e){a.#t[t]?.forEach(s=>s(e))}static unsubscribe(t,e){a.#t[t]&&(a.#t[t]=a.#t[t].filter(s=>s!==e))}},T=class a{static{c(this,"Store")}static#t=null;static singleton(){return a.#t||(a.#t=new a),a.#t}constructor(){this._state={}}get(t){return t.split(".").reduce((e,s)=>e?.[s],this._state)}set(t,e){let s=t.split("."),o=s.pop(),n=s.reduce((r,i)=>(r[i]||(r[i]={}),r[i]),this._state);n[o]=e}clear(){this._state={}}},y=class a extends p{static{c(this,"Router")}static#t=null;static singleton(){return a.#t}constructor(t){super(),a.#t=this,this.rootNode=t.parentNode,this.routerDomNode=t,this.domNode=t,this.layout=t.innerHTML,this.animation=t.getAttribute("animation")??"none"}async onLayout(){let t=document.createDocumentFragment();for(;this.routerDomNode.firstChild;)t.appendChild(this.routerDomNode.firstChild);this.routerDomNode.replaceWith(t),this.domNode=this.rootNode,this.initRouter()}initRouter(){this.routes={};for(let e of this.subViews)if(e.routePath){let s=e.routePath.slice(1).split("?")[0];this.routes[s]=e}this._navStack=[],this._baseView=null;let t=Object.keys(this.routes)[0];history.replaceState(null,"","#"+t),window.addEventListener("hashchange",()=>{let e=window.location.hash.slice(1),s=e.split("?")[0];if(!(!s||!this.routes[s])){if(this._pendingBack){this._pendingBack=!1;return}this._switchTo(e)}}),window.addEventListener("popstate",()=>{if(this._navStack.length>0){let o=this._navStack.pop(),n=this._navStack.at(-1)??this._baseView;o?.onBackPressed?.(),this._animateTransition(o,n,"back").then(()=>o.destroy());return}this._currentView?.onBackPressed?.();let e=window.location.hash.slice(1),s=e.split("?")[0];s&&this.routes[s]&&(this._pendingBack=!0,this._switchTo(e))}),this._switchTo(t)}async _animateTransition(t,e,s){this.animation==="slide"?await this._slideTransition(t,e,s):this.animation==="fade"?await this._fadeTransition(t,e):(t.domNode.hidden=!0,e.domNode.hidden=!1,this._currentView=e,e?.onResume?.())}async _slideTransition(t,e,s){let o=this.domNode,n=s==="forward",r=o.getBoundingClientRect(),i=t.domNode.getBoundingClientRect(),l=i.left-r.left,u=i.top-r.top,d=i.width,h=o.offsetWidth-l,w=`position:absolute;left:${l}px;top:${u}px;width:${d}px`;o.style.cssText=`position:relative;overflow:hidden;height:${o.offsetHeight}px`,t.domNode.style.cssText=`${w};z-index:${n?1:2}`,t.domNode.hidden=!1,e.domNode.style.cssText=`${w};z-index:${n?2:1}`,e.domNode.hidden=!1;let R=n?e.domNode:t.domNode,S=n?`${h}px`:"0px",L=n?"0px":`${h}px`,N=R.animate([{transform:`translateX(${S})`},{transform:`translateX(${L})`}],{duration:300,easing:"ease",fill:"forwards"});await N.finished,N.cancel(),t.domNode.hidden=!0,t.domNode.style.cssText="",e.domNode.style.cssText="",o.style.cssText="",this._currentView=e,e?.onResume?.()}async _fadeTransition(t,e){let s=this.domNode,o=s.getBoundingClientRect(),n=t.domNode.getBoundingClientRect(),r=n.left-o.left,i=n.top-o.top,l=n.width,u=`position:absolute;left:${r}px;top:${i}px;width:${l}px`;s.style.cssText=`position:relative;overflow:hidden;height:${s.offsetHeight}px`,t.domNode.style.cssText=u,t.domNode.hidden=!1,e.domNode.style.cssText=u,e.domNode.hidden=!1;let d=t.domNode.animate([{opacity:1},{opacity:0}],{duration:450,easing:"ease",fill:"forwards"}),h=e.domNode.animate([{opacity:0},{opacity:1}],{duration:450,easing:"ease",fill:"forwards"});await Promise.all([d.finished,h.finished]),d.cancel(),h.cancel(),t.domNode.hidden=!0,t.domNode.style.cssText="",e.domNode.style.cssText="",s.style.cssText="",this._currentView=e,e?.onResume?.()}_switchTo(t){let e=t.split("?")[0];for(let[s,o]of Object.entries(this.routes)){let n=s===e;o.domNode.hidden=!n,n&&(this._currentView=o,o?.onResume?.())}}back(){history.back()}getParams(){let t=window.location.hash.split("?")[1];return t?Object.fromEntries(new URLSearchParams(t)):{}}async navigate(t,e){let[s,o]=t.replace(/^\//,"").split("?"),n=s,r=e??this.routes[n]?.constructor;if(!r){console.error(`JOPS Router: no class found for path "${t}"`);return}this._navStack.length===0&&(this._baseView=this._currentView);let i=new r;await i.loadLayout(),i.inflate(),this.domNode.appendChild(i.domNode),history.pushState({jopsNav:!0},"","#"+n+(o?"?"+o:"")),await i.onLayout?.(),await i.onResume?.(),i.bindEvents?.(),this._navStack.push(i),this._animateTransition(this._currentView,i,"forward")}async replace(t,e){this._navStack.forEach(i=>i.destroy?.()),this._navStack=[];let[s,o]=t.replace(/^\//,"").split("?"),n=s,r="#"+n+(o?"?"+o:"");if(this.routes[n])history.replaceState(null,"",r),this._switchTo(n);else if(e){let i=new e;await i.loadLayout(),i.inflate(),this.domNode.appendChild(i.domNode),await i.onLayout?.(),await i.onResume?.(),i.bindEvents?.(),this._currentView&&(this._currentView.domNode.hidden=!0),this._currentView=i,history.replaceState(null,"",r)}else console.error(`JOPS Router: no instance or class found for path "${t}"`)}},x=class{static{c(this,"Thread")}#t=null;run(t){}onMessage(t){}start(t){if(typeof Worker>"u"){console.warn("JOPS Thread: Web Workers are not supported in this environment.");return}let e=new Blob([`const run = ${this.run.toString()};`,"self.onmessage = (e) => { run(e.data); };"],{type:"application/javascript"}),s=URL.createObjectURL(e);this.#t=new Worker(s),URL.revokeObjectURL(s),this.#t.onmessage=o=>this.onMessage(o),this.#t.postMessage(t)}terminate(){if(!this.#t){console.warn("JOPS Thread: no active worker to terminate.");return}this.#t.terminate(),this.#t=null}};export{b as EventBus,y as Router,T as Store,x as Thread,p as View,_ as layout,k as raw};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jops-core",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "A no-bundler, OOP-first SPA framework with Android-style view composition",
5
5
  "type": "module",
6
6
  "exports": "./lib/jops-core.min.js",