olova 1.0.1 → 1.0.2
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/dist/index.js +1 -0
- package/package.json +1 -1
- package/dist/olova.js +0 -1000
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class olova{constructor(){this.$elements={},this.listeners={},this.components={},this.plugins={},this.refs={},this.$refs=new Proxy({},{get:(e,t)=>this.refs[t]}),this.data={},this.dependencies=new Map,this.subscribers=new Map,this.effects=[],this.cleanupEffects=[]}registerPlugin(e,t){this.plugins[e]=t}registerComponent(e,t){this.components[e]=t,this.defineCustomElement(e)}defineCustomElement(e){const t=this;class i extends HTMLElement{connectedCallback(){this.render()}render(){const i=this.getProps(),n=(0,t.components[e])(i),s=document.createElement("div");s.innerHTML=n.trim();const r=this.parentNode,o=s.firstChild;o&&(r.replaceChild(o,this),t.applyDirectivesToElement(o),t.initReactiveSystem(),t.runEffects())}getProps(){const e={};for(const t of this.attributes)e[t.name]=t.value;return e}static get observedAttributes(){return Object.keys(t.components[e]({})||{})}attributeChangedCallback(e,t,i){this.render()}}customElements.define(e,i)}applyDirectivesToElement(e){this.applyShowDirective(e),this.applyIfDirective(e),this.applyForDirective(e),this.applyModelDirective(e),this.applyAttrDirective(e),this.applyClassDirective(e),this.applyStyleDirective(e),this.applyBindDirective(e),this.applyRefDirective(e),e.querySelectorAll("*").forEach((e=>{this.applyShowDirective(e),this.applyIfDirective(e),this.applyForDirective(e),this.applyModelDirective(e),this.applyAttrDirective(e),this.applyClassDirective(e),this.applyStyleDirective(e),this.applyBindDirective(e),this.applyRefDirective(e)}))}applyShowDirective(e){if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$show");t&&this.effect((()=>{const i=this.evaluateExpression(t);e.style.display=i?"":"none"}))}applyIfDirective(e){if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$if");if(!t)return;const i=e.parentNode,n=document.createComment("$if placeholder");i?.insertBefore(n,e);let s=e.nextElementSibling;for(;s&&!s.hasAttribute("$else");)s=s.nextElementSibling;s&&i?.removeChild(s),this.effect((()=>{this.evaluateExpression(t)?(e.style.display="",e.parentNode!==i&&i?.insertBefore(e,n.nextSibling),s&&(s.style.display="none",s.parentNode===i&&i?.removeChild(s))):(e.style.display="none",e.parentNode===i&&i?.removeChild(e),s&&(s.style.display="",s.parentNode!==i&&i?.insertBefore(s,n.nextSibling)))}))}applyForDirective(e){if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$for");if(!t)return;const[i,n]=t.split(" in "),s=e.parentNode,r=document.createComment("$for placeholder");s?.insertBefore(r,e),e.remove(),this.effect((()=>{const t=this.evaluateExpression(n),o=new Set;let c=r.nextSibling;for(;c&&c.nodeType===Node.ELEMENT_NODE;)o.add(c),c=c.nextSibling;t.forEach(((t,n)=>{let c=Array.from(o).find((e=>e.getAttribute("key")===String(n)));c?o.delete(c):(c=e.cloneNode(!0),c.setAttribute("key",String(n)),s?.insertBefore(c,r.nextSibling)),this.interpolateElement(c,{[i]:t,index:n})})),o.forEach((e=>e.remove()))}))}applyModelDirective(e){if(!(e instanceof HTMLInputElement))return;const t=e.getAttribute("$model");t&&(e.addEventListener("input",(e=>{this.setExpressionValue(t,e.target.value)})),this.effect((()=>{e.value=this.evaluateExpression(t)})))}applyAttrDirective(e){if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$attr")?.split(",");t&&t.forEach((t=>{const[i,n]=t.split(":");this.effect((()=>{e.setAttribute(i,this.evaluateExpression(n))}))}))}applyClassDirective(e){if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$class");t&&this.effect((()=>{const i=this.evaluateExpression(t);"object"==typeof i&&null!==i?Object.entries(i).forEach((([t,i])=>{i?e.classList.add(t):e.classList.remove(t)})):"string"==typeof i&&(e.className=i)}))}applyStyleDirective(e){if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$style");t&&this.effect((()=>{const i=this.evaluateExpression(t);"object"==typeof i&&null!==i?Object.keys(i).forEach((t=>{e.style[t]=i[t]})):"string"==typeof i&&e.setAttribute("style",i)}))}applyBindDirective(e){if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$bind");t&&(this.effect((()=>{const i=this.evaluateExpression(t);this.setElementValue(e,i)})),e.addEventListener("input",(e=>{const i=this.getElementValue(e.target);this.setExpressionValue(t,i)})),"checkbox"!==e.type&&"radio"!==e.type||e.addEventListener("change",(e=>{const i=this.getElementValue(e.target);this.setExpressionValue(t,i)})))}applyRefDirective(e){if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$ref");t&&this.registerRef(t,e)}setElementValue(e,t){e instanceof HTMLInputElement?"checkbox"===e.type?e.checked=!!t:"radio"===e.type?e.checked=e.value===t:e.value=t:(e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement)&&(e.value=t)}getElementValue(e){return e instanceof HTMLInputElement?"checkbox"===e.type?e.checked:"radio"===e.type?e.checked?e.value:null:e.value:e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement?e.value:void 0}useEffect(e){this.effects.push(e)}runEffects(){this.effects.forEach((e=>{const t=e();"function"==typeof t&&this.cleanupEffects.push(t)})),this.effects=[]}cleanup(){this.cleanupEffects.forEach((e=>e())),this.cleanupEffects=[]}getRef(e){return void 0!==this.refs[e]?this.refs[e]:(console.warn(`Ref '${e}' not found.`),null)}registerRef(e,t){this.refs[e]=t}defineReactive(e,t,i){this.listeners[t]=[],Object.defineProperty(e,t,{get:()=>(this.track(t),i),set:e=>{i!==e&&(i=e,this.notify(t))}})}watch(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].push(t)}track(e){olova.activeEffect&&(this.dependencies.has(e)||this.dependencies.set(e,new Set),this.dependencies.get(e).add(olova.activeEffect))}notify(e){this.dependencies.has(e)&&this.dependencies.get(e).forEach((e=>e())),this.listeners[e]&&this.listeners[e].forEach((e=>e()))}effect(e){olova.activeEffect=e,e(),olova.activeEffect=null}makeReactive(e){for(const t in e)e.hasOwnProperty(t)&&this.defineReactive(e,t,e[t])}initReactiveSystem(){this.makeReactive(window.useSignal),this.initDirectives(),this.initEvents(),this.initDataBinding()}initDirectives(){this.initIfDirective(),this.initRefDirective(),this.initShowDirective(),this.initForDirective(),this.initModelDirective(),this.initAttrDirective(),this.initClassDirective(),this.initStyleDirective(),this.initBindDirective(),this.initHtmlDirective(),Object.keys(this.plugins).forEach((e=>{document.querySelectorAll(`[\\$${e}]`).forEach((t=>{const i=t.getAttribute(`$${e}`);this.effect((()=>{const n=this.evaluateExpression(i);this.plugins[e](t,n,this)}))}))}))}initHtmlDirective(){document.querySelectorAll("[\\$html]").forEach((e=>{if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$html");t&&this.effect((()=>{const i=this.evaluateExpression(t);e.innerHTML=i,e.removeAttribute("$html")}))}))}initClassDirective(){document.querySelectorAll("[\\$class]").forEach((e=>{if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$class");t&&this.effect((()=>{const i=this.evaluateExpression(t);"object"==typeof i&&null!==i?Object.entries(i).forEach((([t,i])=>{i?e.classList.add(t):e.classList.remove(t)})):"string"==typeof i&&(e.className=i)}))}))}initShowDirective(){document.querySelectorAll("[\\$show]").forEach((e=>{if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$show");t&&this.effect((()=>{const i=this.evaluateExpression(t);e.style.display=i?"":"none"}))}))}initBindDirective(){document.querySelectorAll("[\\$bind]").forEach((e=>{if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$bind");t&&(this.effect((()=>{const i=this.evaluateExpression(t);this.setElementValue(e,i)})),e.addEventListener("input",(e=>{const i=this.getElementValue(e.target);this.setExpressionValue(t,i)})),"checkbox"!==e.type&&"radio"!==e.type||e.addEventListener("change",(e=>{const i=this.getElementValue(e.target);this.setExpressionValue(t,i)})))}))}setElementValue(e,t){e instanceof HTMLInputElement?"checkbox"===e.type?e.checked=!!t:"radio"===e.type?e.checked=e.value===t:e.value=t:(e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement)&&(e.value=t)}getElementValue(e){return e instanceof HTMLInputElement?"checkbox"===e.type?e.checked:"radio"===e.type?e.checked?e.value:null:e.value:e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement?e.value:void 0}initStyleDirective(){document.querySelectorAll("[\\$style]").forEach((e=>{if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$style");t&&this.effect((()=>{const i=this.evaluateExpression(t);"object"==typeof i&&null!==i?Object.keys(i).forEach((t=>{e.style[t]=i[t]})):"string"==typeof i&&e.setAttribute("style",i)}))}))}initIfDirective(){document.querySelectorAll("[\\$if]").forEach((e=>{if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$if");if(!t)return;const i=e.parentNode,n=document.createComment("$if placeholder");i?.insertBefore(n,e);let s=e.nextElementSibling;for(;s&&!s.hasAttribute("$else");)s=s.nextElementSibling;s&&i?.removeChild(s),this.effect((()=>{this.evaluateExpression(t)?(e.style.display="",e.parentNode!==i&&i?.insertBefore(e,n.nextSibling),s&&(s.style.display="none",s.parentNode===i&&i?.removeChild(s))):(e.style.display="none",e.parentNode===i&&i?.removeChild(e),s&&(s.style.display="",s.parentNode!==i&&i?.insertBefore(s,n.nextSibling)))}))}))}initRefDirective(){document.querySelectorAll("[\\$ref]").forEach((e=>{if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$ref");t&&this.registerRef(t,e),e.removeAttribute("$ref")}))}initForDirective(){document.querySelectorAll("[\\$for]").forEach((e=>{if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$for");if(!t)return;const[i,n]=t.split(" in "),s=e.parentNode,r=document.createComment("$for placeholder");s?.insertBefore(r,e),e.removeAttribute("$for");const o=e.cloneNode(!0);e.remove(),this.effect((()=>{const e=this.evaluateExpression(n),t=new Set;let c=r.nextSibling;for(;c&&c.nodeType===Node.ELEMENT_NODE;)t.add(c),c=c.nextSibling;e.forEach(((e,n)=>{let c=Array.from(t).find((e=>e.getAttribute("key")===String(n)));c?t.delete(c):(c=o.cloneNode(!0),c.setAttribute("key",String(n)),s?.insertBefore(c,r.nextSibling)),this.interpolateElement(c,{[i]:e,index:n})})),t.forEach((e=>e.remove()))}))}))}initModelDirective(){document.querySelectorAll("[\\$model]").forEach((e=>{if(!(e instanceof HTMLInputElement))return;const t=e.getAttribute("$model");t&&(e.addEventListener("input",(e=>{this.setExpressionValue(t,e.target.value)})),this.effect((()=>{e.value=this.evaluateExpression(t)})))}))}initAttrDirective(){document.querySelectorAll("[\\$attr]").forEach((e=>{if(!(e instanceof HTMLElement))return;const t=e.getAttribute("$attr")?.split(",");t&&t.forEach((t=>{const[i,n]=t.split(":");this.effect((()=>{e.setAttribute(i,this.evaluateExpression(n))}))}))}))}initEvents(){["click","input","change","mouseover","mousemove","mouseout","keydown","keyup","mousedown","mouseup","dblclick","focus","blur","contextmenu","drag","drop","scroll","resize","touchstart","touchmove","touchend","paste","copy","cut","error","load","submit","animationstart","animationend","animationiteration","transitionend","hashchange","beforeunload","fullscreenchange","message","canplay","canplaythrough","durationchange","emptied","ended","invalid","progress","ratechange","waiting","volumechange","wheel","focusin","focusout","pointerenter","pointerleave","touchcancel"].forEach((e=>{document.querySelectorAll(`[\\@${e}]`).forEach((t=>{if(!(t instanceof HTMLElement))return;const i=t.getAttribute(`@${e}`);i&&t.addEventListener(e,(t=>{try{let e=i.trim();if(e in window.useSignal&&"function"==typeof window.useSignal[e])window.useSignal[e](t);else{new Function("event, $",`with($) { ${e} }`)(t,window.useSignal)}}catch(t){console.error(`Error executing handler '${i}' for event '${e}':`,t)}}))}))}))}initDataBinding(){this.compileTextNodes(document.body)}compileTextNodes(e){const t=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);let i;const n=[];for(;i=t.nextNode();)i.textContent.includes("{")&&i.textContent.includes("}")&&n.push(i);n.forEach((e=>{const t=e.textContent.split(/(\{.*?\})/),i=document.createDocumentFragment();t.forEach((e=>{if(e.startsWith("{")&&e.endsWith("}")){const t=document.createTextNode(""),n=e.slice(2,-2).trim();this.effect((()=>{t.textContent=this.evaluateExpression(n)})),i.appendChild(t)}else i.appendChild(document.createTextNode(e))})),e.parentNode.replaceChild(i,e)}))}interpolateTextNodes(e,t){const i=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);let n;const s=[];for(;n=i.nextNode();)n.textContent.includes("{")&&n.textContent.includes("}")&&s.push(n);s.forEach((e=>{const i=e.textContent.split(/(\{.*?\})/),n=document.createDocumentFragment();i.forEach((e=>{if(e.startsWith("{")&&e.endsWith("}")){const i=document.createTextNode(""),s=e.slice(2,-2).trim();this.effect((()=>{i.textContent=this.evaluateExpression(s,t)})),n.appendChild(i)}else n.appendChild(document.createTextNode(e))})),e.parentNode.replaceChild(n,e)}))}evaluateExpression(e,t={}){try{return new Function("useSignal","with(useSignal) { return "+e+" }")({...window.useSignal,...t})}catch(t){return console.error(`Error evaluating expression: ${e}`,t),""}}setExpressionValue(e,t){try{new Function("useSignal","value",`with(useSignal) { ${e} = value }`)(window.useSignal,t)}catch(t){console.error(`Error setting value for expression: ${e}`,t)}}interpolateElement(e,t){this.interpolateAttributes(e,t),this.interpolateTextNodes(e,t),Array.from(e.children).forEach((e=>this.interpolateElement(e,t)))}interpolateAttributes(e,t){Array.from(e.attributes).forEach((e=>{e.value.includes("{")&&e.value.includes("}")&&this.effect((()=>{e.value=this.interpolate(e.value,t)}))}))}interpolateTextNodes(e,t){const i=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);let n;const s=[];for(;n=i.nextNode();)n.textContent.includes("{")&&n.textContent.includes("}")&&s.push(n);s.forEach((e=>{const i=document.createDocumentFragment();e.textContent.split(/(\{.*?\})/).forEach((e=>{if(e.startsWith("{")&&e.endsWith("}")){const n=document.createElement("span"),s=e.slice(2,-2).trim();this.effect((()=>{n.textContent=this.evaluateExpression(s,t)})),i.appendChild(n)}else i.appendChild(document.createTextNode(e))})),e.parentNode.replaceChild(i,e)}))}interpolate(e,t){return e.replace(/\{\{(.*?)\}\}/g,((e,i)=>this.evaluateExpression(i,t)))}static init(){const e=new olova;window.$refs=e.$refs,window.$=t=>e.$(t);return window.useSignal=new Proxy(((e={})=>{Object.assign(window.useSignal,e)}),{get:(t,i)=>("call"===i||"apply"===i||e.track(i),Reflect.get(t,i)),set:(t,i,n)=>(Reflect.get(t,i)!==n&&(Reflect.set(t,i,n),e.notify(i),e.runEffects()),!0)}),document.addEventListener("DOMContentLoaded",(()=>{e.renderTemplate()})),window.addEventListener("beforeunload",(()=>{e.cleanup()})),window.useEffect=t=>{e.useEffect(t)},window.component=(t,i)=>{e.registerComponent(t,i)},window.plugin=(t,i)=>e.registerPlugin(t,i),e}renderTemplate(){const e=document.getElementById("root"),t=document.querySelector("template");if(e&&t){let i=t.innerHTML;i=i.replace(/<([a-z-]+)><\/\1>/g,""),e.innerHTML=i,t.remove(),this.initReactiveSystem(),this.runEffects()}else console.error("Root element or app template not found")}}olova.activeEffect=null;export default olova;
|
package/package.json
CHANGED
package/dist/olova.js
DELETED
|
@@ -1,1000 +0,0 @@
|
|
|
1
|
-
class olova {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.$elements = {};
|
|
4
|
-
this.listeners = {};
|
|
5
|
-
this.components = {};
|
|
6
|
-
this.plugins = {};
|
|
7
|
-
this.refs = {};
|
|
8
|
-
this.$refs = new Proxy(
|
|
9
|
-
{},
|
|
10
|
-
{
|
|
11
|
-
get: (target, key) => {
|
|
12
|
-
return this.refs[key];
|
|
13
|
-
},
|
|
14
|
-
}
|
|
15
|
-
);
|
|
16
|
-
this.data = {};
|
|
17
|
-
this.dependencies = new Map();
|
|
18
|
-
this.subscribers = new Map();
|
|
19
|
-
this.effects = [];
|
|
20
|
-
this.cleanupEffects = [];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
registerPlugin(name, handler) {
|
|
24
|
-
this.plugins[name] = handler;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
registerComponent(name, componentFunction) {
|
|
28
|
-
this.components[name] = componentFunction;
|
|
29
|
-
this.defineCustomElement(name);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
defineCustomElement(name) {
|
|
33
|
-
const deshi = this;
|
|
34
|
-
class CustomElement extends HTMLElement {
|
|
35
|
-
connectedCallback() {
|
|
36
|
-
this.render();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
render() {
|
|
40
|
-
const props = this.getProps();
|
|
41
|
-
const componentFunction = deshi.components[name];
|
|
42
|
-
const renderedContent = componentFunction(props);
|
|
43
|
-
|
|
44
|
-
const tempContainer = document.createElement("div");
|
|
45
|
-
tempContainer.innerHTML = renderedContent.trim();
|
|
46
|
-
|
|
47
|
-
const parent = this.parentNode;
|
|
48
|
-
const renderedElement = tempContainer.firstChild;
|
|
49
|
-
|
|
50
|
-
if (renderedElement) {
|
|
51
|
-
parent.replaceChild(renderedElement, this);
|
|
52
|
-
deshi.applyDirectivesToElement(renderedElement);
|
|
53
|
-
deshi.initReactiveSystem();
|
|
54
|
-
deshi.runEffects();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
getProps() {
|
|
59
|
-
const props = {};
|
|
60
|
-
for (const attr of this.attributes) {
|
|
61
|
-
props[attr.name] = attr.value;
|
|
62
|
-
}
|
|
63
|
-
return props;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
static get observedAttributes() {
|
|
67
|
-
return Object.keys(deshi.components[name]({}) || {});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
71
|
-
this.render();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
customElements.define(name, CustomElement);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
applyDirectivesToElement(element) {
|
|
79
|
-
this.applyShowDirective(element);
|
|
80
|
-
this.applyIfDirective(element);
|
|
81
|
-
this.applyForDirective(element);
|
|
82
|
-
this.applyModelDirective(element);
|
|
83
|
-
this.applyAttrDirective(element);
|
|
84
|
-
this.applyClassDirective(element);
|
|
85
|
-
this.applyStyleDirective(element);
|
|
86
|
-
this.applyBindDirective(element);
|
|
87
|
-
this.applyRefDirective(element);
|
|
88
|
-
|
|
89
|
-
element.querySelectorAll("*").forEach((el) => {
|
|
90
|
-
this.applyShowDirective(el);
|
|
91
|
-
this.applyIfDirective(el);
|
|
92
|
-
this.applyForDirective(el);
|
|
93
|
-
this.applyModelDirective(el);
|
|
94
|
-
this.applyAttrDirective(el);
|
|
95
|
-
this.applyClassDirective(el);
|
|
96
|
-
this.applyStyleDirective(el);
|
|
97
|
-
this.applyBindDirective(el);
|
|
98
|
-
this.applyRefDirective(el);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
applyShowDirective(el) {
|
|
103
|
-
if (!(el instanceof HTMLElement)) return;
|
|
104
|
-
const showExpression = el.getAttribute("$show");
|
|
105
|
-
if (!showExpression) return;
|
|
106
|
-
|
|
107
|
-
this.effect(() => {
|
|
108
|
-
const shouldShow = this.evaluateExpression(showExpression);
|
|
109
|
-
el.style.display = shouldShow ? "" : "none";
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
applyIfDirective(el) {
|
|
114
|
-
if (!(el instanceof HTMLElement)) return;
|
|
115
|
-
const key = el.getAttribute("$if");
|
|
116
|
-
if (!key) return;
|
|
117
|
-
|
|
118
|
-
const parent = el.parentNode;
|
|
119
|
-
const comment = document.createComment("$if placeholder");
|
|
120
|
-
parent?.insertBefore(comment, el);
|
|
121
|
-
|
|
122
|
-
let elseElement = el.nextElementSibling;
|
|
123
|
-
while (elseElement && !elseElement.hasAttribute("$else")) {
|
|
124
|
-
elseElement = elseElement.nextElementSibling;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (elseElement) {
|
|
128
|
-
parent?.removeChild(elseElement);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
this.effect(() => {
|
|
132
|
-
if (this.evaluateExpression(key)) {
|
|
133
|
-
el.style.display = "";
|
|
134
|
-
if (el.parentNode !== parent) {
|
|
135
|
-
parent?.insertBefore(el, comment.nextSibling);
|
|
136
|
-
}
|
|
137
|
-
if (elseElement) {
|
|
138
|
-
elseElement.style.display = "none";
|
|
139
|
-
if (elseElement.parentNode === parent) {
|
|
140
|
-
parent?.removeChild(elseElement);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
} else {
|
|
144
|
-
el.style.display = "none";
|
|
145
|
-
if (el.parentNode === parent) {
|
|
146
|
-
parent?.removeChild(el);
|
|
147
|
-
}
|
|
148
|
-
if (elseElement) {
|
|
149
|
-
elseElement.style.display = "";
|
|
150
|
-
if (elseElement.parentNode !== parent) {
|
|
151
|
-
parent?.insertBefore(elseElement, comment.nextSibling);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
applyForDirective(el) {
|
|
159
|
-
if (!(el instanceof HTMLElement)) return;
|
|
160
|
-
const expression = el.getAttribute("$for");
|
|
161
|
-
if (!expression) return;
|
|
162
|
-
|
|
163
|
-
const [item, arr] = expression.split(" in ");
|
|
164
|
-
const parent = el.parentNode;
|
|
165
|
-
const comment = document.createComment("$for placeholder");
|
|
166
|
-
parent?.insertBefore(comment, el);
|
|
167
|
-
el.remove();
|
|
168
|
-
|
|
169
|
-
this.effect(() => {
|
|
170
|
-
const items = this.evaluateExpression(arr);
|
|
171
|
-
const existingElements = new Set();
|
|
172
|
-
let currentElement = comment.nextSibling;
|
|
173
|
-
while (currentElement && currentElement.nodeType === Node.ELEMENT_NODE) {
|
|
174
|
-
existingElements.add(currentElement);
|
|
175
|
-
currentElement = currentElement.nextSibling;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
items.forEach((value, index) => {
|
|
179
|
-
let element = Array.from(existingElements).find(
|
|
180
|
-
(el) => el.getAttribute("key") === String(index)
|
|
181
|
-
);
|
|
182
|
-
if (!element) {
|
|
183
|
-
element = el.cloneNode(true);
|
|
184
|
-
element.setAttribute("key", String(index));
|
|
185
|
-
parent?.insertBefore(element, comment.nextSibling);
|
|
186
|
-
} else {
|
|
187
|
-
existingElements.delete(element);
|
|
188
|
-
}
|
|
189
|
-
this.interpolateElement(element, { [item]: value, index });
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
existingElements.forEach((el) => el.remove());
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
applyModelDirective(el) {
|
|
197
|
-
if (!(el instanceof HTMLInputElement)) return;
|
|
198
|
-
const key = el.getAttribute("$model");
|
|
199
|
-
if (!key) return;
|
|
200
|
-
|
|
201
|
-
el.addEventListener("input", (e) => {
|
|
202
|
-
this.setExpressionValue(key, e.target.value);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
this.effect(() => {
|
|
206
|
-
el.value = this.evaluateExpression(key);
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
applyAttrDirective(el) {
|
|
211
|
-
if (!(el instanceof HTMLElement)) return;
|
|
212
|
-
const bindings = el.getAttribute("$attr")?.split(",");
|
|
213
|
-
if (!bindings) return;
|
|
214
|
-
|
|
215
|
-
bindings.forEach((binding) => {
|
|
216
|
-
const [attr, expression] = binding.split(":");
|
|
217
|
-
this.effect(() => {
|
|
218
|
-
el.setAttribute(attr, this.evaluateExpression(expression));
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
applyClassDirective(el) {
|
|
224
|
-
if (!(el instanceof HTMLElement)) return;
|
|
225
|
-
const classBinding = el.getAttribute("$class");
|
|
226
|
-
if (!classBinding) return;
|
|
227
|
-
|
|
228
|
-
this.effect(() => {
|
|
229
|
-
const classObj = this.evaluateExpression(classBinding);
|
|
230
|
-
if (typeof classObj === "object" && classObj !== null) {
|
|
231
|
-
Object.entries(classObj).forEach(([className, condition]) => {
|
|
232
|
-
if (condition) {
|
|
233
|
-
el.classList.add(className);
|
|
234
|
-
} else {
|
|
235
|
-
el.classList.remove(className);
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
} else if (typeof classObj === "string") {
|
|
239
|
-
el.className = classObj;
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
applyStyleDirective(el) {
|
|
245
|
-
if (!(el instanceof HTMLElement)) return;
|
|
246
|
-
const styleBinding = el.getAttribute("$style");
|
|
247
|
-
if (!styleBinding) return;
|
|
248
|
-
|
|
249
|
-
this.effect(() => {
|
|
250
|
-
const styleObj = this.evaluateExpression(styleBinding);
|
|
251
|
-
if (typeof styleObj === "object" && styleObj !== null) {
|
|
252
|
-
Object.keys(styleObj).forEach((prop) => {
|
|
253
|
-
el.style[prop] = styleObj[prop];
|
|
254
|
-
});
|
|
255
|
-
} else if (typeof styleObj === "string") {
|
|
256
|
-
el.setAttribute("style", styleObj);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
applyBindDirective(el) {
|
|
262
|
-
if (!(el instanceof HTMLElement)) return;
|
|
263
|
-
const bindExpression = el.getAttribute("$bind");
|
|
264
|
-
if (!bindExpression) return;
|
|
265
|
-
|
|
266
|
-
this.effect(() => {
|
|
267
|
-
const value = this.evaluateExpression(bindExpression);
|
|
268
|
-
this.setElementValue(el, value);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
el.addEventListener("input", (event) => {
|
|
272
|
-
const value = this.getElementValue(event.target);
|
|
273
|
-
this.setExpressionValue(bindExpression, value);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
if (el.type === "checkbox" || el.type === "radio") {
|
|
277
|
-
el.addEventListener("change", (event) => {
|
|
278
|
-
const value = this.getElementValue(event.target);
|
|
279
|
-
this.setExpressionValue(bindExpression, value);
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
applyRefDirective(el) {
|
|
285
|
-
if (!(el instanceof HTMLElement)) return;
|
|
286
|
-
const key = el.getAttribute("$ref");
|
|
287
|
-
if (key) {
|
|
288
|
-
this.registerRef(key, el);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
setElementValue(el, value) {
|
|
293
|
-
if (el instanceof HTMLInputElement) {
|
|
294
|
-
if (el.type === "checkbox") {
|
|
295
|
-
el.checked = !!value;
|
|
296
|
-
} else if (el.type === "radio") {
|
|
297
|
-
el.checked = el.value === value;
|
|
298
|
-
} else {
|
|
299
|
-
el.value = value;
|
|
300
|
-
}
|
|
301
|
-
} else if (
|
|
302
|
-
el instanceof HTMLTextAreaElement ||
|
|
303
|
-
el instanceof HTMLSelectElement
|
|
304
|
-
) {
|
|
305
|
-
el.value = value;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
getElementValue(el) {
|
|
310
|
-
if (el instanceof HTMLInputElement) {
|
|
311
|
-
if (el.type === "checkbox") {
|
|
312
|
-
return el.checked;
|
|
313
|
-
} else if (el.type === "radio") {
|
|
314
|
-
return el.checked ? el.value : null;
|
|
315
|
-
} else {
|
|
316
|
-
return el.value;
|
|
317
|
-
}
|
|
318
|
-
} else if (
|
|
319
|
-
el instanceof HTMLTextAreaElement ||
|
|
320
|
-
el instanceof HTMLSelectElement
|
|
321
|
-
) {
|
|
322
|
-
return el.value;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
useEffect(callback) {
|
|
327
|
-
this.effects.push(callback);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
runEffects() {
|
|
331
|
-
this.effects.forEach((effect) => {
|
|
332
|
-
const cleanup = effect();
|
|
333
|
-
if (typeof cleanup === "function") {
|
|
334
|
-
this.cleanupEffects.push(cleanup);
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
this.effects = [];
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
cleanup() {
|
|
341
|
-
this.cleanupEffects.forEach((cleanup) => cleanup());
|
|
342
|
-
this.cleanupEffects = [];
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
getRef(key) {
|
|
346
|
-
if (this.refs[key] !== undefined) {
|
|
347
|
-
return this.refs[key];
|
|
348
|
-
} else {
|
|
349
|
-
console.warn(`Ref '${key}' not found.`);
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
registerRef(key, ref) {
|
|
355
|
-
this.refs[key] = ref;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
defineReactive(obj, key, val) {
|
|
359
|
-
this.listeners[key] = [];
|
|
360
|
-
Object.defineProperty(obj, key, {
|
|
361
|
-
get: () => {
|
|
362
|
-
this.track(key);
|
|
363
|
-
return val;
|
|
364
|
-
},
|
|
365
|
-
set: (newVal) => {
|
|
366
|
-
if (val !== newVal) {
|
|
367
|
-
val = newVal;
|
|
368
|
-
this.notify(key);
|
|
369
|
-
}
|
|
370
|
-
},
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
watch(key, listener) {
|
|
375
|
-
if (!this.listeners[key]) {
|
|
376
|
-
this.listeners[key] = [];
|
|
377
|
-
}
|
|
378
|
-
this.listeners[key].push(listener);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
track(key) {
|
|
382
|
-
if (olova.activeEffect) {
|
|
383
|
-
if (!this.dependencies.has(key)) {
|
|
384
|
-
this.dependencies.set(key, new Set());
|
|
385
|
-
}
|
|
386
|
-
this.dependencies.get(key).add(olova.activeEffect);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
notify(key) {
|
|
391
|
-
if (this.dependencies.has(key)) {
|
|
392
|
-
this.dependencies.get(key).forEach((effect) => effect());
|
|
393
|
-
}
|
|
394
|
-
if (this.listeners[key]) {
|
|
395
|
-
this.listeners[key].forEach((listener) => listener());
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
effect(fn) {
|
|
400
|
-
olova.activeEffect = fn;
|
|
401
|
-
fn();
|
|
402
|
-
olova.activeEffect = null;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
makeReactive(obj) {
|
|
406
|
-
for (const key in obj) {
|
|
407
|
-
if (obj.hasOwnProperty(key)) {
|
|
408
|
-
this.defineReactive(obj, key, obj[key]);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
initReactiveSystem() {
|
|
414
|
-
this.makeReactive(window.useSignal);
|
|
415
|
-
this.initDirectives();
|
|
416
|
-
this.initEvents();
|
|
417
|
-
this.initDataBinding();
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
initDirectives() {
|
|
421
|
-
this.initIfDirective();
|
|
422
|
-
this.initRefDirective();
|
|
423
|
-
this.initShowDirective();
|
|
424
|
-
this.initForDirective();
|
|
425
|
-
this.initModelDirective();
|
|
426
|
-
this.initAttrDirective();
|
|
427
|
-
this.initClassDirective();
|
|
428
|
-
this.initStyleDirective();
|
|
429
|
-
this.initBindDirective();
|
|
430
|
-
this.initHtmlDirective();
|
|
431
|
-
Object.keys(this.plugins).forEach((pluginName) => {
|
|
432
|
-
document.querySelectorAll(`[\\$${pluginName}]`).forEach((el) => {
|
|
433
|
-
const value = el.getAttribute(`$${pluginName}`);
|
|
434
|
-
this.effect(() => {
|
|
435
|
-
const result = this.evaluateExpression(value);
|
|
436
|
-
this.plugins[pluginName](el, result, this);
|
|
437
|
-
});
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
initHtmlDirective() {
|
|
442
|
-
document.querySelectorAll("[\\$html]").forEach((el) => {
|
|
443
|
-
if (!(el instanceof HTMLElement)) return;
|
|
444
|
-
const htmlExpression = el.getAttribute("$html");
|
|
445
|
-
if (!htmlExpression) return;
|
|
446
|
-
|
|
447
|
-
this.effect(() => {
|
|
448
|
-
const htmlContent = this.evaluateExpression(htmlExpression);
|
|
449
|
-
el.innerHTML = htmlContent;
|
|
450
|
-
el.removeAttribute("$html");
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
initClassDirective() {
|
|
455
|
-
document.querySelectorAll("[\\$class]").forEach((el) => {
|
|
456
|
-
if (!(el instanceof HTMLElement)) return;
|
|
457
|
-
const classBinding = el.getAttribute("$class");
|
|
458
|
-
if (!classBinding) return;
|
|
459
|
-
|
|
460
|
-
this.effect(() => {
|
|
461
|
-
const classObj = this.evaluateExpression(classBinding);
|
|
462
|
-
if (typeof classObj === "object" && classObj !== null) {
|
|
463
|
-
Object.entries(classObj).forEach(([className, condition]) => {
|
|
464
|
-
if (condition) {
|
|
465
|
-
el.classList.add(className);
|
|
466
|
-
} else {
|
|
467
|
-
el.classList.remove(className);
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
} else if (typeof classObj === "string") {
|
|
471
|
-
el.className = classObj;
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
initShowDirective() {
|
|
478
|
-
document.querySelectorAll("[\\$show]").forEach((el) => {
|
|
479
|
-
if (!(el instanceof HTMLElement)) return;
|
|
480
|
-
const showExpression = el.getAttribute("$show");
|
|
481
|
-
if (!showExpression) return;
|
|
482
|
-
|
|
483
|
-
this.effect(() => {
|
|
484
|
-
const shouldShow = this.evaluateExpression(showExpression);
|
|
485
|
-
el.style.display = shouldShow ? "" : "none";
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
initBindDirective() {
|
|
490
|
-
document.querySelectorAll("[\\$bind]").forEach((el) => {
|
|
491
|
-
if (!(el instanceof HTMLElement)) return;
|
|
492
|
-
const bindExpression = el.getAttribute("$bind");
|
|
493
|
-
if (!bindExpression) return;
|
|
494
|
-
|
|
495
|
-
this.effect(() => {
|
|
496
|
-
const value = this.evaluateExpression(bindExpression);
|
|
497
|
-
this.setElementValue(el, value);
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
el.addEventListener("input", (event) => {
|
|
501
|
-
const value = this.getElementValue(event.target);
|
|
502
|
-
this.setExpressionValue(bindExpression, value);
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
if (el.type === "checkbox" || el.type === "radio") {
|
|
506
|
-
el.addEventListener("change", (event) => {
|
|
507
|
-
const value = this.getElementValue(event.target);
|
|
508
|
-
this.setExpressionValue(bindExpression, value);
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
setElementValue(el, value) {
|
|
515
|
-
if (el instanceof HTMLInputElement) {
|
|
516
|
-
if (el.type === "checkbox") {
|
|
517
|
-
el.checked = !!value;
|
|
518
|
-
} else if (el.type === "radio") {
|
|
519
|
-
el.checked = el.value === value;
|
|
520
|
-
} else {
|
|
521
|
-
el.value = value;
|
|
522
|
-
}
|
|
523
|
-
} else if (
|
|
524
|
-
el instanceof HTMLTextAreaElement ||
|
|
525
|
-
el instanceof HTMLSelectElement
|
|
526
|
-
) {
|
|
527
|
-
el.value = value;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
getElementValue(el) {
|
|
532
|
-
if (el instanceof HTMLInputElement) {
|
|
533
|
-
if (el.type === "checkbox") {
|
|
534
|
-
return el.checked;
|
|
535
|
-
} else if (el.type === "radio") {
|
|
536
|
-
return el.checked ? el.value : null;
|
|
537
|
-
} else {
|
|
538
|
-
return el.value;
|
|
539
|
-
}
|
|
540
|
-
} else if (
|
|
541
|
-
el instanceof HTMLTextAreaElement ||
|
|
542
|
-
el instanceof HTMLSelectElement
|
|
543
|
-
) {
|
|
544
|
-
return el.value;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
initStyleDirective() {
|
|
548
|
-
document.querySelectorAll("[\\$style]").forEach((el) => {
|
|
549
|
-
if (!(el instanceof HTMLElement)) return;
|
|
550
|
-
const styleBinding = el.getAttribute("$style");
|
|
551
|
-
if (!styleBinding) return;
|
|
552
|
-
|
|
553
|
-
this.effect(() => {
|
|
554
|
-
const styleObj = this.evaluateExpression(styleBinding);
|
|
555
|
-
if (typeof styleObj === "object" && styleObj !== null) {
|
|
556
|
-
Object.keys(styleObj).forEach((prop) => {
|
|
557
|
-
el.style[prop] = styleObj[prop];
|
|
558
|
-
});
|
|
559
|
-
} else if (typeof styleObj === "string") {
|
|
560
|
-
el.setAttribute("style", styleObj);
|
|
561
|
-
}
|
|
562
|
-
});
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
initIfDirective() {
|
|
566
|
-
document.querySelectorAll("[\\$if]").forEach((el) => {
|
|
567
|
-
if (!(el instanceof HTMLElement)) return;
|
|
568
|
-
const key = el.getAttribute("$if");
|
|
569
|
-
if (!key) return;
|
|
570
|
-
const parent = el.parentNode;
|
|
571
|
-
const comment = document.createComment("$if placeholder");
|
|
572
|
-
parent?.insertBefore(comment, el);
|
|
573
|
-
|
|
574
|
-
let elseElement = el.nextElementSibling;
|
|
575
|
-
while (elseElement && !elseElement.hasAttribute("$else")) {
|
|
576
|
-
elseElement = elseElement.nextElementSibling;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (elseElement) {
|
|
580
|
-
parent?.removeChild(elseElement);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
this.effect(() => {
|
|
584
|
-
if (this.evaluateExpression(key)) {
|
|
585
|
-
el.style.display = "";
|
|
586
|
-
if (el.parentNode !== parent) {
|
|
587
|
-
parent?.insertBefore(el, comment.nextSibling);
|
|
588
|
-
}
|
|
589
|
-
if (elseElement) {
|
|
590
|
-
elseElement.style.display = "none";
|
|
591
|
-
if (elseElement.parentNode === parent) {
|
|
592
|
-
parent?.removeChild(elseElement);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
} else {
|
|
596
|
-
el.style.display = "none";
|
|
597
|
-
if (el.parentNode === parent) {
|
|
598
|
-
parent?.removeChild(el);
|
|
599
|
-
}
|
|
600
|
-
if (elseElement) {
|
|
601
|
-
elseElement.style.display = "";
|
|
602
|
-
if (elseElement.parentNode !== parent) {
|
|
603
|
-
parent?.insertBefore(elseElement, comment.nextSibling);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
});
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
initRefDirective() {
|
|
612
|
-
document.querySelectorAll("[\\$ref]").forEach((el) => {
|
|
613
|
-
if (!(el instanceof HTMLElement)) return;
|
|
614
|
-
const key = el.getAttribute("$ref");
|
|
615
|
-
if (key) {
|
|
616
|
-
this.registerRef(key, el);
|
|
617
|
-
}
|
|
618
|
-
el.removeAttribute("$ref");
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
initForDirective() {
|
|
623
|
-
document.querySelectorAll("[\\$for]").forEach((el) => {
|
|
624
|
-
if (!(el instanceof HTMLElement)) return;
|
|
625
|
-
const expression = el.getAttribute("$for");
|
|
626
|
-
if (!expression) return;
|
|
627
|
-
const [item, arr] = expression.split(" in ");
|
|
628
|
-
const parent = el.parentNode;
|
|
629
|
-
const comment = document.createComment("$for placeholder");
|
|
630
|
-
parent?.insertBefore(comment, el);
|
|
631
|
-
|
|
632
|
-
// Remove the $for attribute
|
|
633
|
-
el.removeAttribute("$for");
|
|
634
|
-
|
|
635
|
-
const template = el.cloneNode(true);
|
|
636
|
-
el.remove();
|
|
637
|
-
|
|
638
|
-
this.effect(() => {
|
|
639
|
-
const items = this.evaluateExpression(arr);
|
|
640
|
-
const existingElements = new Set();
|
|
641
|
-
let currentElement = comment.nextSibling;
|
|
642
|
-
while (
|
|
643
|
-
currentElement &&
|
|
644
|
-
currentElement.nodeType === Node.ELEMENT_NODE
|
|
645
|
-
) {
|
|
646
|
-
existingElements.add(currentElement);
|
|
647
|
-
currentElement = currentElement.nextSibling;
|
|
648
|
-
}
|
|
649
|
-
items.forEach((value, index) => {
|
|
650
|
-
let element = Array.from(existingElements).find(
|
|
651
|
-
(el) => el.getAttribute("key") === String(index)
|
|
652
|
-
);
|
|
653
|
-
if (!element) {
|
|
654
|
-
element = template.cloneNode(true);
|
|
655
|
-
element.setAttribute("key", String(index));
|
|
656
|
-
parent?.insertBefore(element, comment.nextSibling);
|
|
657
|
-
} else {
|
|
658
|
-
existingElements.delete(element);
|
|
659
|
-
}
|
|
660
|
-
this.interpolateElement(element, { [item]: value, index });
|
|
661
|
-
});
|
|
662
|
-
existingElements.forEach((el) => el.remove());
|
|
663
|
-
});
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
initModelDirective() {
|
|
667
|
-
document.querySelectorAll("[\\$model]").forEach((el) => {
|
|
668
|
-
if (!(el instanceof HTMLInputElement)) return;
|
|
669
|
-
const key = el.getAttribute("$model");
|
|
670
|
-
if (!key) return;
|
|
671
|
-
el.addEventListener("input", (e) => {
|
|
672
|
-
this.setExpressionValue(key, e.target.value);
|
|
673
|
-
});
|
|
674
|
-
this.effect(() => {
|
|
675
|
-
el.value = this.evaluateExpression(key);
|
|
676
|
-
});
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
initAttrDirective() {
|
|
681
|
-
document.querySelectorAll("[\\$attr]").forEach((el) => {
|
|
682
|
-
if (!(el instanceof HTMLElement)) return;
|
|
683
|
-
const bindings = el.getAttribute("$attr")?.split(",");
|
|
684
|
-
if (!bindings) return;
|
|
685
|
-
bindings.forEach((binding) => {
|
|
686
|
-
const [attr, expression] = binding.split(":");
|
|
687
|
-
this.effect(() => {
|
|
688
|
-
el.setAttribute(attr, this.evaluateExpression(expression));
|
|
689
|
-
});
|
|
690
|
-
});
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
initEvents() {
|
|
695
|
-
const eventTypes = [
|
|
696
|
-
"click",
|
|
697
|
-
"input",
|
|
698
|
-
"change",
|
|
699
|
-
"mouseover",
|
|
700
|
-
"mousemove",
|
|
701
|
-
"mouseout",
|
|
702
|
-
"keydown",
|
|
703
|
-
"keyup",
|
|
704
|
-
"mousedown",
|
|
705
|
-
"mouseup",
|
|
706
|
-
"dblclick",
|
|
707
|
-
"focus",
|
|
708
|
-
"blur",
|
|
709
|
-
"contextmenu",
|
|
710
|
-
"drag",
|
|
711
|
-
"drop",
|
|
712
|
-
"scroll",
|
|
713
|
-
"resize",
|
|
714
|
-
"touchstart",
|
|
715
|
-
"touchmove",
|
|
716
|
-
"touchend",
|
|
717
|
-
"paste",
|
|
718
|
-
"copy",
|
|
719
|
-
"cut",
|
|
720
|
-
"error",
|
|
721
|
-
"load",
|
|
722
|
-
"submit",
|
|
723
|
-
"animationstart",
|
|
724
|
-
"animationend",
|
|
725
|
-
"animationiteration",
|
|
726
|
-
"transitionend",
|
|
727
|
-
"hashchange",
|
|
728
|
-
"beforeunload",
|
|
729
|
-
"fullscreenchange",
|
|
730
|
-
"message",
|
|
731
|
-
"canplay",
|
|
732
|
-
"canplaythrough",
|
|
733
|
-
"durationchange",
|
|
734
|
-
"emptied",
|
|
735
|
-
"ended",
|
|
736
|
-
"invalid",
|
|
737
|
-
"progress",
|
|
738
|
-
"ratechange",
|
|
739
|
-
"waiting",
|
|
740
|
-
"volumechange",
|
|
741
|
-
"wheel",
|
|
742
|
-
"focusin",
|
|
743
|
-
"focusout",
|
|
744
|
-
"pointerenter",
|
|
745
|
-
"pointerleave",
|
|
746
|
-
"touchcancel",
|
|
747
|
-
];
|
|
748
|
-
|
|
749
|
-
eventTypes.forEach((eventType) => {
|
|
750
|
-
document.querySelectorAll(`[\\@${eventType}]`).forEach((el) => {
|
|
751
|
-
if (!(el instanceof HTMLElement)) return;
|
|
752
|
-
|
|
753
|
-
const handlerExpression = el.getAttribute(`@${eventType}`);
|
|
754
|
-
if (handlerExpression) {
|
|
755
|
-
el.addEventListener(eventType, (event) => {
|
|
756
|
-
try {
|
|
757
|
-
let cleanedExpression = handlerExpression.trim();
|
|
758
|
-
|
|
759
|
-
// If the expression is a function reference, invoke it
|
|
760
|
-
if (
|
|
761
|
-
cleanedExpression in window.useSignal &&
|
|
762
|
-
typeof window.useSignal[cleanedExpression] === "function"
|
|
763
|
-
) {
|
|
764
|
-
window.useSignal[cleanedExpression](event);
|
|
765
|
-
} else {
|
|
766
|
-
// If it's not a direct function reference, treat it as inline JS
|
|
767
|
-
const func = new Function(
|
|
768
|
-
"event, $",
|
|
769
|
-
`with($) { ${cleanedExpression} }`
|
|
770
|
-
);
|
|
771
|
-
func(event, window.useSignal);
|
|
772
|
-
}
|
|
773
|
-
} catch (error) {
|
|
774
|
-
console.error(
|
|
775
|
-
`Error executing handler '${handlerExpression}' for event '${eventType}':`,
|
|
776
|
-
error
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
});
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
initDataBinding() {
|
|
786
|
-
this.compileTextNodes(document.body);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
compileTextNodes(el) {
|
|
790
|
-
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
791
|
-
let node;
|
|
792
|
-
const nodesToReplace = [];
|
|
793
|
-
|
|
794
|
-
while ((node = walker.nextNode())) {
|
|
795
|
-
if (node.textContent.includes("{") && node.textContent.includes("}")) {
|
|
796
|
-
nodesToReplace.push(node);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
nodesToReplace.forEach((node) => {
|
|
801
|
-
const parts = node.textContent.split(/(\{.*?\})/);
|
|
802
|
-
const fragment = document.createDocumentFragment();
|
|
803
|
-
|
|
804
|
-
parts.forEach((part) => {
|
|
805
|
-
if (part.startsWith("{") && part.endsWith("}")) {
|
|
806
|
-
const textNode = document.createTextNode("");
|
|
807
|
-
const expression = part.slice(2, -2).trim();
|
|
808
|
-
this.effect(() => {
|
|
809
|
-
textNode.textContent = this.evaluateExpression(expression);
|
|
810
|
-
});
|
|
811
|
-
fragment.appendChild(textNode);
|
|
812
|
-
} else {
|
|
813
|
-
fragment.appendChild(document.createTextNode(part));
|
|
814
|
-
}
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
node.parentNode.replaceChild(fragment, node);
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
interpolateTextNodes(el, context) {
|
|
822
|
-
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
823
|
-
let node;
|
|
824
|
-
const nodesToReplace = [];
|
|
825
|
-
|
|
826
|
-
while ((node = walker.nextNode())) {
|
|
827
|
-
if (node.textContent.includes("{") && node.textContent.includes("}")) {
|
|
828
|
-
nodesToReplace.push(node);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
nodesToReplace.forEach((node) => {
|
|
833
|
-
const parts = node.textContent.split(/(\{.*?\})/);
|
|
834
|
-
const fragment = document.createDocumentFragment();
|
|
835
|
-
|
|
836
|
-
parts.forEach((part) => {
|
|
837
|
-
if (part.startsWith("{") && part.endsWith("}")) {
|
|
838
|
-
const textNode = document.createTextNode("");
|
|
839
|
-
const expression = part.slice(2, -2).trim();
|
|
840
|
-
this.effect(() => {
|
|
841
|
-
textNode.textContent = this.evaluateExpression(expression, context);
|
|
842
|
-
});
|
|
843
|
-
fragment.appendChild(textNode);
|
|
844
|
-
} else {
|
|
845
|
-
fragment.appendChild(document.createTextNode(part));
|
|
846
|
-
}
|
|
847
|
-
});
|
|
848
|
-
|
|
849
|
-
node.parentNode.replaceChild(fragment, node);
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
evaluateExpression(expression, context = {}) {
|
|
854
|
-
try {
|
|
855
|
-
const func = new Function(
|
|
856
|
-
"useSignal",
|
|
857
|
-
"with(useSignal) { return " + expression + " }"
|
|
858
|
-
);
|
|
859
|
-
return func({ ...window.useSignal, ...context });
|
|
860
|
-
} catch (error) {
|
|
861
|
-
console.error(`Error evaluating expression: ${expression}`, error);
|
|
862
|
-
return "";
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
setExpressionValue(expression, value) {
|
|
867
|
-
try {
|
|
868
|
-
const func = new Function(
|
|
869
|
-
"useSignal",
|
|
870
|
-
"value",
|
|
871
|
-
`with(useSignal) { ${expression} = value }`
|
|
872
|
-
);
|
|
873
|
-
func(window.useSignal, value);
|
|
874
|
-
} catch (error) {
|
|
875
|
-
console.error(`Error setting value for expression: ${expression}`, error);
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
interpolateElement(el, context) {
|
|
880
|
-
this.interpolateAttributes(el, context);
|
|
881
|
-
this.interpolateTextNodes(el, context);
|
|
882
|
-
Array.from(el.children).forEach((child) =>
|
|
883
|
-
this.interpolateElement(child, context)
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
interpolateAttributes(el, context) {
|
|
888
|
-
Array.from(el.attributes).forEach((attr) => {
|
|
889
|
-
if (attr.value.includes("{") && attr.value.includes("}")) {
|
|
890
|
-
this.effect(() => {
|
|
891
|
-
attr.value = this.interpolate(attr.value, context);
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
});
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
interpolateTextNodes(el, context) {
|
|
898
|
-
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
899
|
-
let node;
|
|
900
|
-
const nodesToReplace = [];
|
|
901
|
-
|
|
902
|
-
while ((node = walker.nextNode())) {
|
|
903
|
-
if (node.textContent.includes("{") && node.textContent.includes("}")) {
|
|
904
|
-
nodesToReplace.push(node);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
nodesToReplace.forEach((node) => {
|
|
909
|
-
const fragment = document.createDocumentFragment();
|
|
910
|
-
const parts = node.textContent.split(/(\{.*?\})/);
|
|
911
|
-
|
|
912
|
-
parts.forEach((part) => {
|
|
913
|
-
if (part.startsWith("{") && part.endsWith("}")) {
|
|
914
|
-
const span = document.createElement("span");
|
|
915
|
-
const expression = part.slice(2, -2).trim();
|
|
916
|
-
this.effect(() => {
|
|
917
|
-
span.textContent = this.evaluateExpression(expression, context);
|
|
918
|
-
});
|
|
919
|
-
fragment.appendChild(span);
|
|
920
|
-
} else {
|
|
921
|
-
fragment.appendChild(document.createTextNode(part));
|
|
922
|
-
}
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
node.parentNode.replaceChild(fragment, node);
|
|
926
|
-
});
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
interpolate(template, context) {
|
|
930
|
-
return template.replace(/\{\{(.*?)\}\}/g, (match, expression) => {
|
|
931
|
-
return this.evaluateExpression(expression, context);
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
static init() {
|
|
936
|
-
const deshi = new olova();
|
|
937
|
-
window.$refs = deshi.$refs;
|
|
938
|
-
window.$ = (selector) => deshi.$(selector);
|
|
939
|
-
// Create the useSignal function
|
|
940
|
-
const useSignalFunction = (obj = {}) => {
|
|
941
|
-
Object.assign(window.useSignal, obj);
|
|
942
|
-
};
|
|
943
|
-
// Create the useSignal object with the function
|
|
944
|
-
window.useSignal = new Proxy(useSignalFunction, {
|
|
945
|
-
get(target, key) {
|
|
946
|
-
if (key === "call" || key === "apply") {
|
|
947
|
-
return Reflect.get(target, key);
|
|
948
|
-
}
|
|
949
|
-
deshi.track(key);
|
|
950
|
-
return Reflect.get(target, key);
|
|
951
|
-
},
|
|
952
|
-
set(target, key, value) {
|
|
953
|
-
if (Reflect.get(target, key) !== value) {
|
|
954
|
-
Reflect.set(target, key, value);
|
|
955
|
-
deshi.notify(key);
|
|
956
|
-
deshi.runEffects();
|
|
957
|
-
}
|
|
958
|
-
return true;
|
|
959
|
-
},
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
963
|
-
deshi.renderTemplate();
|
|
964
|
-
});
|
|
965
|
-
|
|
966
|
-
window.addEventListener("beforeunload", () => {
|
|
967
|
-
deshi.cleanup();
|
|
968
|
-
});
|
|
969
|
-
|
|
970
|
-
window.useEffect = (callback) => {
|
|
971
|
-
deshi.useEffect(callback);
|
|
972
|
-
};
|
|
973
|
-
window.component = (name, componentFunction) => {
|
|
974
|
-
deshi.registerComponent(name, componentFunction);
|
|
975
|
-
};
|
|
976
|
-
|
|
977
|
-
window.plugin = (name, handler) => deshi.registerPlugin(name, handler);
|
|
978
|
-
|
|
979
|
-
return deshi;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
renderTemplate() {
|
|
983
|
-
const root = document.getElementById("root");
|
|
984
|
-
const template = document.querySelector("template");
|
|
985
|
-
if (root && template) {
|
|
986
|
-
let content = template.innerHTML;
|
|
987
|
-
content = content.replace(/<([a-z-]+)><\/\1>/g, "");
|
|
988
|
-
root.innerHTML = content;
|
|
989
|
-
template.remove();
|
|
990
|
-
this.initReactiveSystem();
|
|
991
|
-
this.runEffects();
|
|
992
|
-
} else {
|
|
993
|
-
console.error("Root element or app template not found");
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
olova.activeEffect = null;
|
|
999
|
-
|
|
1000
|
-
export default olova;
|