jops-core 1.0.8 → 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
|
@@ -129,7 +129,7 @@ export default class MyView extends View { ... }
|
|
|
129
129
|
| `inflate()` | Converts `this.layout` string into `this.domNode` |
|
|
130
130
|
| `async onLayout()` | DOM is live — add subviews, read initial data |
|
|
131
131
|
| `async onResume()` | View becomes visible — called on first display and on every return navigation |
|
|
132
|
-
| `bindEvents()` | Wires `on*` attributes on `[
|
|
132
|
+
| `bindEvents()` | Wires `on*` attributes on `[jops-event-bind]` elements to class methods |
|
|
133
133
|
| `onBackPressed()` | Called by Router when back navigation is detected on this view |
|
|
134
134
|
|
|
135
135
|
#### Instance Methods
|
|
@@ -148,10 +148,10 @@ export default class MyView extends View { ... }
|
|
|
148
148
|
|
|
149
149
|
#### Declarative Event Binding
|
|
150
150
|
|
|
151
|
-
Mark any element with `
|
|
151
|
+
Mark any element with `jops-event-bind` and add `on*` attributes whose values are method names on the View class:
|
|
152
152
|
|
|
153
153
|
```html
|
|
154
|
-
<button
|
|
154
|
+
<button jops-event-bind onclick="onSave">Save</button>
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
```js
|
|
@@ -203,7 +203,7 @@ raw("<b>Hello</b>"); // passed through as-is inside layout``
|
|
|
203
203
|
|
|
204
204
|
### `Router` — extends `View`
|
|
205
205
|
|
|
206
|
-
Hash-based SPA router. Singleton. Declared in the layout as `<Router
|
|
206
|
+
Hash-based SPA router. Singleton. Declared in the layout as `<Router jops animation="slide">`.
|
|
207
207
|
|
|
208
208
|
#### Two Navigation Paradigms
|
|
209
209
|
|
|
@@ -243,12 +243,14 @@ Animation only occurs on `navigate()` (forward) and `back()` when the nav stack
|
|
|
243
243
|
#### Layout Declaration
|
|
244
244
|
|
|
245
245
|
```html
|
|
246
|
-
<Router
|
|
247
|
-
<view
|
|
248
|
-
<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>
|
|
249
249
|
</Router>
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
+
> **Note:** The legacy `type="jops"` and `type="jops-event-bind"` syntax is still supported for backwards compatibility.
|
|
253
|
+
|
|
252
254
|
#### Query Params
|
|
253
255
|
|
|
254
256
|
```js
|
package/lib/jops-core.d.ts
CHANGED
|
@@ -92,7 +92,7 @@ export declare class View {
|
|
|
92
92
|
*/
|
|
93
93
|
get(id: string): Element;
|
|
94
94
|
/**
|
|
95
|
-
* Wires `on*` HTML attributes on `[
|
|
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 `[
|
|
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
|
|
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
|
|
231
|
-
* <Home path="/home"
|
|
232
|
-
* <Detail path="/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
|
*/
|
package/lib/jops-core.min.js
CHANGED
|
@@ -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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},"")}c(_,"layout");var
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},"")}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
|
@@ -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"
|
|
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"
|
|
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"
|
|
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
|
|
8
|
+
<View jops layout="/src/layout/layout_header.js"></View>
|
|
9
9
|
|
|
10
10
|
<main class="main">
|
|
11
11
|
|
|
12
|
-
<Router
|
|
12
|
+
<Router jops animation="slide">
|
|
13
13
|
|
|
14
|
-
<View path="/home"
|
|
14
|
+
<View path="/home" jops layout="/src/layout/layout_hero.js"></View>
|
|
15
15
|
|
|
16
|
-
<Features path="/features"
|
|
16
|
+
<Features path="/features" jops
|
|
17
17
|
src="/src/Features.js">
|
|
18
18
|
</Features>
|
|
19
19
|
|
|
20
|
-
<Form path="/form"
|
|
20
|
+
<Form path="/form" jops
|
|
21
21
|
src="/src/Form.js">
|
|
22
22
|
</Form>
|
|
23
23
|
|
|
24
|
-
<DataSection path="/data"
|
|
24
|
+
<DataSection path="/data" jops
|
|
25
25
|
src="/src/DataSection.js">
|
|
26
26
|
</DataSection>
|
|
27
27
|
|
|
28
|
-
<Media path="/media"
|
|
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
|
|
36
|
+
<View jops layout="/src/layout/layout_footer.js"></View>
|
|
37
37
|
|
|
38
38
|
</div>
|
|
39
39
|
`;
|