jops-core 1.0.4 → 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 +57 -7
- package/bin/init.js +22 -3
- package/lib/jops-core.d.ts +45 -0
- package/lib/jops-core.min.js +1 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,14 +37,16 @@ The fastest way to get started. Add these two tags to any HTML page:
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
</script>
|
|
40
|
-
<script src="https://cdn.jsdelivr.net/npm/jops-core@latest/lib/jops-core.min.js" data-app="
|
|
40
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/jops-core@latest/lib/jops-core.min.js" data-app="./src/App.js"></script>
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
The import map lets your app modules use `import { View } from "jops-core"`. The script tag bootstraps the app entry point specified in `data-app`.
|
|
43
|
+
The import map lets your app modules use `import { View } from "jops-core"`. The module script tag bootstraps the app entry point specified in `data-app`.
|
|
44
44
|
|
|
45
45
|
`App.js` is your entry point — a file you provide that instantiates your root `View` and mounts it to the page. Point `data-app` to wherever you place it.
|
|
46
46
|
|
|
47
|
-
Replace `@latest` with a specific version (e.g. `@1.0.
|
|
47
|
+
Replace `@latest` with a specific version (e.g. `@1.0.4`) to pin. No build step, no npm, no bundler.
|
|
48
|
+
|
|
49
|
+
> **Note:** ES modules require an HTTP server — open your project with `npx serve` (or any static server) rather than double-clicking `index.html`. Browsers block `import` on `file://` URLs.
|
|
48
50
|
|
|
49
51
|
For editor autocomplete and inline docs without installing the full package, add jops-core as a dev dependency:
|
|
50
52
|
|
|
@@ -297,12 +299,60 @@ EventBus.unsubscribe("user:login", handler);
|
|
|
297
299
|
|
|
298
300
|
---
|
|
299
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
|
+
|
|
300
338
|
## CLI
|
|
301
339
|
|
|
302
|
-
| Command
|
|
303
|
-
|
|
|
304
|
-
| `npx jops-core init`
|
|
305
|
-
| `npx jops-core
|
|
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`.
|
|
306
356
|
|
|
307
357
|
---
|
|
308
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
|
}
|
|
@@ -175,7 +176,7 @@ ${MODULE_SCRIPT}
|
|
|
175
176
|
let html = fs.readFileSync(indexPath, "utf8");
|
|
176
177
|
html = html.replace(
|
|
177
178
|
/(<script[^>]+src=["'][^"']*jops-core\.min\.js["'])([^>]*>)/,
|
|
178
|
-
(_, open, rest) => open.includes("data-app") ? open + rest : open + ' data-app="
|
|
179
|
+
(_, open, rest) => open.includes("data-app") ? open + rest : open + ' data-app="./src/App.js"' + rest
|
|
179
180
|
);
|
|
180
181
|
fs.writeFileSync(indexPath, html, "utf8");
|
|
181
182
|
console.log("[jops-core] Created empty JOPS project skeleton.");
|
|
@@ -184,7 +185,7 @@ ${MODULE_SCRIPT}
|
|
|
184
185
|
let html = fs.readFileSync(indexPath, "utf8");
|
|
185
186
|
html = html.replace(
|
|
186
187
|
/(<script[^>]+src=["'][^"']*jops-core\.min\.js["'])([^>]*>)/,
|
|
187
|
-
(_, open, rest) => open.includes("data-app") ? open + rest : open + ' data-app="
|
|
188
|
+
(_, open, rest) => open.includes("data-app") ? open + rest : open + ' data-app="./src/App.js"' + rest
|
|
188
189
|
);
|
|
189
190
|
fs.writeFileSync(indexPath, html, "utf8");
|
|
190
191
|
console.log("[jops-core] Created Hello World JOPS project.");
|
|
@@ -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") {
|
package/lib/jops-core.d.ts
CHANGED
|
@@ -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 {};
|
package/lib/jops-core.min.js
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
var
|
|
2
|
-
import('${a}')
|
|
3
|
-
.then(m => new m.default())
|
|
4
|
-
.catch(err => console.error("JOPS: App.js not found or failed to load", err));
|
|
5
|
-
`,document.body.appendChild(t)})();var f=class{static{h(this,"RawHTML")}constructor(t){this.html=t}},R=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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},"")}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(t);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(o)).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",V=o?"0px":`${l}px`,N=x.animate([{transform:`translateX(${S})`},{transform:`translateX(${V})`}],{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,R 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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},"")}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};
|