jops-core 1.0.8 → 1.0.10

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
@@ -23,6 +23,14 @@ No Webpack. No Babel. No compiler. Just `<script type="module">` and a browser.
23
23
 
24
24
  ---
25
25
 
26
+ ## JOPS Demos
27
+
28
+ | Demo | Description |
29
+ | ---- | ----------- |
30
+ | [JOPS Hello World](https://cdn.ui-db.com/hello-world/index.html) | A minimal starter app showing views, layouts, routing, and event binding |
31
+
32
+ ---
33
+
26
34
  ## Installation
27
35
 
28
36
  ### CDN
@@ -129,7 +137,7 @@ export default class MyView extends View { ... }
129
137
  | `inflate()` | Converts `this.layout` string into `this.domNode` |
130
138
  | `async onLayout()` | DOM is live — add subviews, read initial data |
131
139
  | `async onResume()` | View becomes visible — called on first display and on every return navigation |
132
- | `bindEvents()` | Wires `on*` attributes on `[type="jops-event-bind"]` elements to class methods |
140
+ | `bindEvents()` | Wires `on*` attributes on `[jops-event-bind]` elements to class methods |
133
141
  | `onBackPressed()` | Called by Router when back navigation is detected on this view |
134
142
 
135
143
  #### Instance Methods
@@ -148,10 +156,10 @@ export default class MyView extends View { ... }
148
156
 
149
157
  #### Declarative Event Binding
150
158
 
151
- Mark any element with `type="jops-event-bind"` and add `on*` attributes whose values are method names on the View class:
159
+ Mark any element with `jops-event-bind` and add `on*` attributes whose values are method names on the View class:
152
160
 
153
161
  ```html
154
- <button type="jops-event-bind" onclick="onSave">Save</button>
162
+ <button jops-event-bind onclick="onSave">Save</button>
155
163
  ```
156
164
 
157
165
  ```js
@@ -203,7 +211,7 @@ raw("<b>Hello</b>"); // passed through as-is inside layout``
203
211
 
204
212
  ### `Router` — extends `View`
205
213
 
206
- Hash-based SPA router. Singleton. Declared in the layout as `<Router type="jops" animation="slide">`.
214
+ Hash-based SPA router. Singleton. Declared in the layout as `<Router jops animation="slide">`.
207
215
 
208
216
  #### Two Navigation Paradigms
209
217
 
@@ -243,12 +251,14 @@ Animation only occurs on `navigate()` (forward) and `back()` when the nav stack
243
251
  #### Layout Declaration
244
252
 
245
253
  ```html
246
- <Router type="jops" animation="slide">
247
- <view type="jops" src="/src/Home.js" path="/home"></view>
248
- <view type="jops" src="/src/Form.js" path="/form"></view>
254
+ <Router jops animation="slide">
255
+ <view jops src="/src/Home.js" path="/home"></view>
256
+ <view jops src="/src/Form.js" path="/form"></view>
249
257
  </Router>
250
258
  ```
251
259
 
260
+ > **Note:** The legacy `type="jops"` and `type="jops-event-bind"` syntax is still supported for backwards compatibility.
261
+
252
262
  #### Query Params
253
263
 
254
264
  ```js
@@ -92,7 +92,7 @@ export declare class View {
92
92
  */
93
93
  get(id: string): Element;
94
94
  /**
95
- * Wires `on*` HTML attributes on `[type="jops-event-bind"]` elements to
95
+ * Wires `on*` HTML attributes on `[jops-event-bind]` elements to
96
96
  * same-named methods on this view instance.
97
97
  * Called automatically after `onResume()`. Override to add custom wiring.
98
98
  */
@@ -144,7 +144,7 @@ export declare class View {
144
144
  */
145
145
  insertSubView(view: View, index: number): Promise<void>;
146
146
  /**
147
- * Mounts the view to the DOM, resolves all nested `[type="jops"]` view
147
+ * Mounts the view to the DOM, resolves all nested `[jops]` view
148
148
  * elements and `<Router>` declarations, and runs each one's full lifecycle.
149
149
  * Call once on the root view; nested views are handled automatically.
150
150
  * Idempotent — safe to call multiple times.
@@ -218,7 +218,7 @@ export declare class Store {
218
218
  }
219
219
  /**
220
220
  * Hash-based SPA router. Singleton. Extends {@link View}.
221
- * Declared in a layout string as `<Router type="jops" animation="slide">`.
221
+ * Declared in a layout string as `<Router jops animation="slide">`.
222
222
  *
223
223
  * Supports two navigation paradigms:
224
224
  * - **Flat** (`href="#path"`) — reuses pre-existing view instances (tab navigation).
@@ -227,9 +227,9 @@ export declare class Store {
227
227
  * @example
228
228
  * // In a layout file:
229
229
  * layout`
230
- * <Router type="jops" animation="slide">
231
- * <Home path="/home" type="jops" src="/src/Home.js"></Home>
232
- * <Detail path="/detail" type="jops" src="/src/Detail.js"></Detail>
230
+ * <Router jops animation="slide">
231
+ * <Home path="/home" jops src="/src/Home.js"></Home>
232
+ * <Detail path="/detail" jops src="/src/Detail.js"></Detail>
233
233
  * </Router>
234
234
  * `
235
235
  */
@@ -1 +1 @@
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,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;")},"")}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 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 h=(await import(new URL(o,document.baseURI).href)).default;r=new h}else r=new a;this.subViews.push(r),await r.loadLayout(n);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("."),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{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 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(),l=i.left-r.left,u=i.top-r.top,d=i.width,h=n.offsetWidth-l,w=`position:absolute;left:${l}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,R=o?`${h}px`:"0px",L=o?"0px":`${h}px`,N=x.animate([{transform:`translateX(${R})`},{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="",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,l=o.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,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}"`)}},S=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=this.run.toString();!e.startsWith("function")&&!e.startsWith("(")&&!e.startsWith("async")&&(e="function "+e);let s=new Blob([`const run = ${e};`,"self.onmessage = (e) => { run(e.data); };"],{type:"application/javascript"}),n=URL.createObjectURL(s);this.#t=new Worker(n),URL.revokeObjectURL(n),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,S as Thread,p as View,_ as layout,k 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,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;")},"")}c(_,"layout");var p=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}},m=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 p(this.domNode,t)}bindEvents(){if(!this.domNode)return;let t=[...this.domNode.querySelectorAll("[jops-event-bind], [type='jops-event-bind']")];(this.domNode.hasAttribute("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("[jops], [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 h=(await import(new URL(o,document.baseURI).href)).default;r=new h}else r=new a;this.subViews.push(r),await r.loadLayout(n);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?.()}))}},N=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("."),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 m{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 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(),l=i.left-r.left,u=i.top-r.top,d=i.width,h=n.offsetWidth-l,w=`position:absolute;left:${l}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,R=o?`${h}px`:"0px",L=o?"0px":`${h}px`,b=x.animate([{transform:`translateX(${R})`},{transform:`translateX(${L})`}],{duration:300,easing:"ease",fill:"forwards"});await b.finished,b.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,l=o.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,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}"`)}},S=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=this.run.toString();!e.startsWith("function")&&!e.startsWith("(")&&!e.startsWith("async")&&(e="function "+e);let s=new Blob([`const run = ${e};`,"self.onmessage = (e) => { run(e.data); };"],{type:"application/javascript"}),n=URL.createObjectURL(s);this.#t=new Worker(n),URL.revokeObjectURL(n),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{N as EventBus,y as Router,T as Store,S as Thread,m 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.8",
3
+ "version": "1.0.10",
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",
@@ -25,7 +25,7 @@ class MediaCard extends View {
25
25
  this.imgSrc = imgSrc;
26
26
  this.caption = caption;
27
27
  this.layout = layout`
28
- <div class="media-card" type="jops-event-bind" onclick="showMedia">
28
+ <div class="media-card" jops-event-bind onclick="showMedia">
29
29
  <div id="imgSrc" class="media-card__img-placeholder"></div>
30
30
  <p id="caption" class="media-card__caption"></p>
31
31
  </div>
@@ -53,7 +53,7 @@ class MediaDialog extends View {
53
53
  super();
54
54
  this.layout = layout`
55
55
  <div class="media-dialog">
56
- <button class="media-dialog__back btn btn--secondary" type="jops-event-bind" onclick="onBack">← Back</button>
56
+ <button class="media-dialog__back btn btn--secondary" jops-event-bind onclick="onBack">← Back</button>
57
57
  <div id="dialogImage" class="media-dialog__image"></div>
58
58
  <p id="dialogCaption" class="media-dialog__caption"></p>
59
59
  </div>
@@ -3,7 +3,7 @@ import { layout } from "jops-core";
3
3
  export default layout`
4
4
  <section id="form" class="form-section">
5
5
  <h2 class="section__title">Sample Form</h2>
6
- <form class="form" type="jops-event-bind" onsubmit="onSubmit">
6
+ <form class="form" jops-event-bind onsubmit="onSubmit">
7
7
  <div class="form__group">
8
8
  <label class="form__label" for="fullname">Full Name</label>
9
9
  <input class="form__input" type="text" id="fullname" name="fullname" placeholder="Enter your full name">
@@ -5,27 +5,27 @@ export default layout`
5
5
 
6
6
  <div class="jops-root">
7
7
 
8
- <View type="jops" layout="/src/layout/layout_header.js"></View>
8
+ <View jops layout="/src/layout/layout_header.js"></View>
9
9
 
10
10
  <main class="main">
11
11
 
12
- <Router type="jops" animation="slide">
12
+ <Router jops animation="slide">
13
13
 
14
- <View path="/home" type="jops" layout="/src/layout/layout_hero.js"></View>
14
+ <View path="/home" jops layout="/src/layout/layout_hero.js"></View>
15
15
 
16
- <Features path="/features" type="jops"
16
+ <Features path="/features" jops
17
17
  src="/src/Features.js">
18
18
  </Features>
19
19
 
20
- <Form path="/form" type="jops"
20
+ <Form path="/form" jops
21
21
  src="/src/Form.js">
22
22
  </Form>
23
23
 
24
- <DataSection path="/data" type="jops"
24
+ <DataSection path="/data" jops
25
25
  src="/src/DataSection.js">
26
26
  </DataSection>
27
27
 
28
- <Media path="/media" type="jops"
28
+ <Media path="/media" jops
29
29
  src="/src/Media.js">
30
30
  </Media>
31
31
 
@@ -33,7 +33,7 @@ export default layout`
33
33
 
34
34
  </main>
35
35
 
36
- <View type="jops" layout="/src/layout/layout_footer.js"></View>
36
+ <View jops layout="/src/layout/layout_footer.js"></View>
37
37
 
38
38
  </div>
39
39
  `;