jops-core 1.0.7 → 1.0.9

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
@@ -68,6 +68,17 @@ npx jops-core init
68
68
 
69
69
  `npx jops-core init` vendors the library to `/lib/jops-core.min.js`, configures `index.html` with the import map, and injects build and postinstall scripts into `package.json`.
70
70
 
71
+ ### Updating the library
72
+
73
+ When a new version of JOPS is released, run both commands:
74
+
75
+ ```bash
76
+ npm i jops-core@latest
77
+ npx jops-core update
78
+ ```
79
+
80
+ `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`.
81
+
71
82
  ### Build for production
72
83
 
73
84
  ```bash
@@ -118,7 +129,7 @@ export default class MyView extends View { ... }
118
129
  | `inflate()` | Converts `this.layout` string into `this.domNode` |
119
130
  | `async onLayout()` | DOM is live — add subviews, read initial data |
120
131
  | `async onResume()` | View becomes visible — called on first display and on every return navigation |
121
- | `bindEvents()` | Wires `on*` attributes on `[type="jops-event-bind"]` elements to class methods |
132
+ | `bindEvents()` | Wires `on*` attributes on `[jops-event-bind]` elements to class methods |
122
133
  | `onBackPressed()` | Called by Router when back navigation is detected on this view |
123
134
 
124
135
  #### Instance Methods
@@ -137,10 +148,10 @@ export default class MyView extends View { ... }
137
148
 
138
149
  #### Declarative Event Binding
139
150
 
140
- Mark any element with `type="jops-event-bind"` and add `on*` attributes whose values are method names on the View class:
151
+ Mark any element with `jops-event-bind` and add `on*` attributes whose values are method names on the View class:
141
152
 
142
153
  ```html
143
- <button type="jops-event-bind" onclick="onSave">Save</button>
154
+ <button jops-event-bind onclick="onSave">Save</button>
144
155
  ```
145
156
 
146
157
  ```js
@@ -192,7 +203,7 @@ raw("<b>Hello</b>"); // passed through as-is inside layout``
192
203
 
193
204
  ### `Router` — extends `View`
194
205
 
195
- Hash-based SPA router. Singleton. Declared in the layout as `<Router type="jops" animation="slide">`.
206
+ Hash-based SPA router. Singleton. Declared in the layout as `<Router jops animation="slide">`.
196
207
 
197
208
  #### Two Navigation Paradigms
198
209
 
@@ -232,12 +243,14 @@ Animation only occurs on `navigate()` (forward) and `back()` when the nav stack
232
243
  #### Layout Declaration
233
244
 
234
245
  ```html
235
- <Router type="jops" animation="slide">
236
- <view type="jops" src="/src/Home.js" path="/home"></view>
237
- <view type="jops" src="/src/Form.js" path="/form"></view>
246
+ <Router jops animation="slide">
247
+ <view jops src="/src/Home.js" path="/home"></view>
248
+ <view jops src="/src/Form.js" path="/form"></view>
238
249
  </Router>
239
250
  ```
240
251
 
252
+ > **Note:** The legacy `type="jops"` and `type="jops-event-bind"` syntax is still supported for backwards compatibility.
253
+
241
254
  #### Query Params
242
255
 
243
256
  ```js
@@ -343,17 +356,6 @@ t.terminate();
343
356
  | `npx jops-core update` | Copy the latest `jops-core.min.js` from `node_modules` to `lib/` |
344
357
  | `npx jops-core build` | Generate production-ready `dist/` folder |
345
358
 
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`.
356
-
357
359
  ---
358
360
 
359
361
  ## Source & Contributing
@@ -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.7",
3
+ "version": "1.0.9",
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
  `;