resonantjs 1.1.2 → 1.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resonantjs",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "A lightweight JavaScript framework that enables reactive data-binding for building dynamic and responsive web applications. It simplifies creating interactive UIs by automatically updating the DOM when your data changes.",
5
5
  "main": "resonant.js",
6
6
  "repository": {
package/resonant.js CHANGED
@@ -13,6 +13,7 @@ class ObservableArray extends Array {
13
13
 
14
14
  this.forEach((item, index) => {
15
15
  if (typeof item === 'object') {
16
+ this._setKeyForObjectAndChildObjects(item, index);
16
17
  this[index] = this._createProxy(item, index);
17
18
  }
18
19
  });
@@ -22,12 +23,53 @@ class ObservableArray extends Array {
22
23
  }
23
24
  }
24
25
 
26
+ _setKeyForObjectAndChildObjects(obj, index) {
27
+ if (typeof obj === 'object') {
28
+ obj.key = index + '-' + this._generateKeyByContentCheckSum(obj);
29
+ Object.keys(obj).forEach(key => {
30
+ this._setKeyForObjectAndChildObjects(obj[key], index);
31
+ });
32
+ }
33
+ }
34
+
35
+ _generateKeyByContentCheckSum(obj) {
36
+ const removeKeysRecursively = (obj) => {
37
+ if (!obj || typeof obj !== 'object') return obj;
38
+
39
+ if (Array.isArray(obj)) {
40
+ return obj.map(item => removeKeysRecursively(item));
41
+ }
42
+
43
+ const newObj = {...obj};
44
+ delete newObj.key;
45
+
46
+ Object.keys(newObj).forEach(key => {
47
+ if (typeof newObj[key] === 'object' && newObj[key] !== null) {
48
+ newObj[key] = removeKeysRecursively(newObj[key]);
49
+ }
50
+ });
51
+
52
+ return newObj;
53
+ };
54
+
55
+ const objForHash = removeKeysRecursively(obj);
56
+ const str = JSON.stringify(objForHash);
57
+ let hash = 0;
58
+ for (let i = 0; i < str.length; i++) {
59
+ const char = str.charCodeAt(i);
60
+ hash = ((hash << 5) - hash) + char;
61
+ hash = hash & hash;
62
+ }
63
+ return Math.abs(hash).toString(36);
64
+ }
65
+
25
66
  _createProxy(item, index) {
26
67
  return new Proxy(item, {
27
68
  set: (target, property, value) => {
28
69
  if (target[property] !== value) {
29
70
  const oldValue = target[property];
30
71
  target[property] = value;
72
+ this._setKeyForObjectAndChildObjects(item, index);
31
73
  this.resonantInstance._queueUpdate(this.variableName, 'modified', target, property, oldValue, index);
32
74
  }
33
75
  return true;
@@ -36,6 +78,10 @@ class ObservableArray extends Array {
36
78
  }
37
79
 
38
80
  forceUpdate() {
81
+ this.forEach((item, index) => {
82
+ console.log('forceUpdate', item, index);
83
+ this._setKeyForObjectAndChildObjects(item, index);
84
+ });
39
85
  this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
40
86
  this.resonantInstance._queueUpdate(this.variableName, 'modified', this.slice());
41
87
  }
@@ -48,6 +94,7 @@ class ObservableArray extends Array {
48
94
  push(...args) {
49
95
  args = args.map((item, index) => {
50
96
  if (typeof item === 'object') {
97
+ this._setKeyForObjectAndChildObjects(item, this.length + index);
51
98
  return this._createProxy(item, this.length + index);
52
99
  }
53
100
  return item;
@@ -372,67 +419,91 @@ class Resonant {
372
419
  }
373
420
 
374
421
  _renderArray(variableName, el) {
375
- let template = el.cloneNode(true);
376
- el.innerHTML = '';
422
+ const container = el.hasAttribute('res') && el.parentElement ? el.parentElement : el;
377
423
 
424
+ let template;
378
425
  if (!window[variableName + "_template"]) {
426
+ template = el.cloneNode(true);
379
427
  window[variableName + "_template"] = template;
428
+ el.style.display = 'none';
429
+ el.setAttribute('res-template', 'true');
380
430
  } else {
381
431
  template = window[variableName + "_template"];
382
432
  }
383
433
 
434
+ const existingElements = new Map();
435
+ container.querySelectorAll(`[res="${variableName}"][res-rendered="true"]`).forEach(element => {
436
+ const key = element.getAttribute('res-key');
437
+ if (key) {
438
+ existingElements.set(key, element);
439
+ }
440
+ });
441
+
442
+ container.querySelectorAll(`[res="${variableName}"][res-rendered="true"]`).forEach(el => el.remove());
443
+
384
444
  this.data[variableName].forEach((instance, index) => {
385
- const clonedEl = template.cloneNode(true);
386
- clonedEl.setAttribute("res-index", index);
387
-
388
- for (let key in instance) {
389
- let overrideInstanceValue = null;
390
- let subEl = clonedEl.querySelector(`[res-prop="${key}"]`);
391
- if (!subEl) {
392
- subEl = clonedEl.querySelector('[res-prop=""]');
393
- overrideInstanceValue = instance;
394
- }
395
- if (subEl) {
396
- const value = this._resolveValue(instance, key, overrideInstanceValue);
397
-
398
- if ((subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') &&
399
- !Array.isArray(value) &&
400
- typeof value !== 'object') {
401
- this._handleInputElement(
402
- subEl,
403
- value,
404
- (newValue) => {
405
- instance[key] = newValue;
406
- this._queueUpdate(variableName, 'modified', instance, key, value);
407
- }
408
- );
409
- }
410
- else if (Array.isArray(value)) {
411
- this._renderNestedArray(subEl, value);
445
+ const elementKey = instance.key;
446
+ let elementToUse;
447
+
448
+ if (existingElements.has(elementKey)) {
449
+ elementToUse = existingElements.get(elementKey);
450
+ existingElements.delete(elementKey);
451
+ elementToUse.setAttribute("res-index", index);
452
+ } else {
453
+ elementToUse = template.cloneNode(true);
454
+ elementToUse.removeAttribute('res-template');
455
+ elementToUse.style.display = '';
456
+ elementToUse.setAttribute("res-rendered", "true");
457
+ elementToUse.setAttribute("res-key", elementKey);
458
+ elementToUse.setAttribute("res-index", index);
459
+
460
+ for (let key in instance) {
461
+ let overrideInstanceValue = null;
462
+ let subEl = elementToUse.querySelector(`[res-prop="${key}"]`);
463
+ if (!subEl) {
464
+ subEl = elementToUse.querySelector('[res-prop=""]');
465
+ overrideInstanceValue = instance;
412
466
  }
413
- else if (typeof value === 'object' && value !== null) {
414
- const nestedElements = subEl.querySelectorAll('[res-prop]');
415
- nestedElements.forEach(nestedEl => {
416
- const nestedKey = nestedEl.getAttribute('res-prop');
417
- if (nestedKey && nestedKey in value) {
418
- this._renderObjectProperty(nestedEl, value[nestedKey], variableName, nestedKey);
419
- }
420
- });
421
- }
422
- else {
423
- subEl.innerHTML = value ?? '';
467
+ if (subEl) {
468
+ const value = this._resolveValue(instance, key, overrideInstanceValue);
469
+
470
+ if ((subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') &&
471
+ !Array.isArray(value) &&
472
+ typeof value !== 'object') {
473
+ this._handleInputElement(
474
+ subEl,
475
+ value,
476
+ (newValue) => {
477
+ instance[key] = newValue;
478
+ this._queueUpdate(variableName, 'modified', instance, key, value);
479
+ }
480
+ );
481
+ }
482
+ else if (Array.isArray(value)) {
483
+ this._renderNestedArray(subEl, value);
484
+ }
485
+ else if (typeof value === 'object' && value !== null) {
486
+ const nestedElements = subEl.querySelectorAll('[res-prop]');
487
+ nestedElements.forEach(nestedEl => {
488
+ const nestedKey = nestedEl.getAttribute('res-prop');
489
+ if (nestedKey && nestedKey in value) {
490
+ this._renderObjectProperty(nestedEl, value[nestedKey], variableName, nestedKey);
491
+ }
492
+ });
493
+ }
494
+ else {
495
+ subEl.innerHTML = value ?? '';
496
+ }
424
497
  }
425
498
  }
426
499
  }
427
500
 
428
- this._handleDisplayElements(clonedEl, instance);
429
- this._bindClickEvents(clonedEl, instance, this.data[variableName]);
501
+ this._handleDisplayElements(elementToUse, instance);
502
+ this._bindClickEvents(elementToUse, instance, this.data[variableName]);
430
503
 
431
- clonedEl.setAttribute("res-rendered", "true");
432
- el.appendChild(clonedEl);
504
+ container.appendChild(elementToUse);
433
505
  });
434
506
  }
435
-
436
507
  _renderNestedArray(subEl, arrayValue) {
437
508
  const template = subEl.cloneNode(true);
438
509
  subEl.innerHTML = '';
@@ -457,7 +528,6 @@ class Resonant {
457
528
  subEl.appendChild(cloned);
458
529
  });
459
530
  }
460
-
461
531
  updateDisplayConditionalsFor(variableName) {
462
532
  const conditionalElements = document.querySelectorAll(`[res-display*="${variableName}"]`);
463
533
  conditionalElements.forEach(conditionalElement => {
package/resonant.min.js CHANGED
@@ -1 +1 @@
1
- class ObservableArray extends Array{constructor(e,t,...a){if(void 0===t)return super(...a);super(...a);var s=void 0===t.data[e];this.variableName=e,this.resonantInstance=t,this.isDeleting=!1,this.forEach((e,t)=>{"object"==typeof e&&(this[t]=this._createProxy(e,t))}),s||this.forceUpdate()}_createProxy(e,t){return new Proxy(e,{set:(e,a,s)=>{if(e[a]!==s){let r=e[a];e[a]=s,this.resonantInstance._queueUpdate(this.variableName,"modified",e,a,r,t)}return!0}})}forceUpdate(){this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"modified",this.slice())}update(e){window[this.variableName]=e,this.resonantInstance._queueUpdate(this.variableName,"updated",e)}push(...e){e=e.map((e,t)=>"object"==typeof e?this._createProxy(e,this.length+t):e);let t=super.push(...e);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),e.forEach((e,t)=>{this.resonantInstance._queueUpdate(this.variableName,"added",e,this.length-1-t)}),t}splice(e,t,...a){a=a.map((t,a)=>"object"==typeof t?this._createProxy(t,e+a):t);let s=super.splice(e,t,...a);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),t>0&&s.forEach((t,a)=>{this.resonantInstance._queueUpdate(this.variableName,"removed",t,e+a)}),a.length>0&&a.forEach((t,a)=>{this.resonantInstance._queueUpdate(this.variableName,"added",t,e+a)}),s}set(e,t){if(this[e]!==t){if(this.isDeleting)return!0;let a=this.resonantInstance.arrayDataChangeDetection[this.variableName],s="modified";e>=a.length?s="added":void 0===a[e]&&(s="added"),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice();let r=this[e];this[e]=t,this.resonantInstance._queueUpdate(this.variableName,s,this[e],e,r)}return!0}delete(e){let t=this[e];return this.isDeleting=!0,this.splice(e,1),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"removed",null,e,t),this.isDeleting=!1,!0}filter(e,t=!0){if(void 0===this.resonantInstance||!1===t)return super.filter(e);let a=super.filter(e);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"filtered"),a}}class Resonant{constructor(){this.data={},this.callbacks={},this.pendingUpdates=new Map,this.arrayDataChangeDetection={}}_handleInputElement(e,t,a){"checkbox"===e.type?(e.checked=t,e.hasAttribute("data-resonant-bound")||(e.onchange=()=>a(e.checked),e.setAttribute("data-resonant-bound","true"))):(e.value=t,e.hasAttribute("data-resonant-bound")||(e.oninput=()=>a(e.value),e.setAttribute("data-resonant-bound","true")))}add(e,t,a){Array.isArray(t=this.persist(e,t,a))?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):"object"==typeof t&&null!==t?this.data[e]=this._createObject(e,t):this.data[e]=t,this._defineProperty(e),this.updateElement(e)}persist(e,t,a){if(void 0===a||!a)return t;var s=localStorage.getItem("res_"+e);return null!=s?JSON.parse(s):(localStorage.setItem("res_"+e,JSON.stringify(t)),t)}updatePersistantData(e){localStorage.getItem("res_"+e)&&localStorage.setItem("res_"+e,JSON.stringify(this.data[e]))}addAll(e){Object.entries(e).forEach(([e,t])=>{this.add(e,t)})}_resolveValue(e,t,a=null){return a??e[t]}_createObject(e,t){return t[Symbol("isProxy")]=!0,new Proxy(t,{set:(t,a,s)=>{if(t[a]!==s){let r=t[a];t[a]=s,this._queueUpdate(e,"modified",t,a,r)}return!0}})}_evaluateDisplayCondition(e,t,a){try{let s=Function("item",`return ${a}`)(t);e.style.display=s?"":"none"}catch(r){console.error(`Error evaluating display condition: ${a}`,r)}}_handleDisplayElements(e,t){let a=e.querySelectorAll("[res-display]");a.forEach(e=>{let a=e.getAttribute("res-display")||"";this._evaluateDisplayCondition(e,t,a)})}_bindClickEvents(e,t,a){let s=e.querySelectorAll("[res-onclick], [res-onclick-remove]");s.forEach(e=>{let s=e.getAttribute("res-onclick"),r=e.getAttribute("res-onclick-remove");s&&(e.onclick=()=>{let e=Function("item",`return ${s}(item)`);e(t)}),r&&(e.onclick=()=>{if(a){let e=a.findIndex(e=>e[r]===t[r]);-1!==e&&a.splice(e,1)}})})}_defineProperty(e){Object.defineProperty(window,e,{get:()=>this.data[e],set:t=>{Array.isArray(t)?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):"object"==typeof t&&null!==t?this.data[e]=this._createObject(e,t):this.data[e]=t,this.updateElement(e),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e),Array.isArray(t)||"object"==typeof t&&null!==t||this._queueUpdate(e,"modified",this.data[e])}})}_queueUpdate(e,t,a,s,r){this.pendingUpdates.has(e)||this.pendingUpdates.set(e,[]),this.pendingUpdates.get(e).push({action:t,item:a,property:s,oldValue:r}),1===this.pendingUpdates.get(e).length&&setTimeout(()=>{let t=this.pendingUpdates.get(e);this.updatePersistantData(e),this.pendingUpdates.delete(e),(t=t.filter((e,t,a)=>a.findIndex(t=>t.property===e.property&&t.action===e.action)===t)).forEach(t=>{this._triggerCallbacks(e,t)}),this.updateElement(e),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e)},0)}_triggerCallbacks(e,t){this.callbacks[e]&&this.callbacks[e].forEach(a=>{let s=t.item||t.oldValue;a(this.data[e],s,t.action)})}updateElement(e){let t=document.querySelectorAll(`[res="${e}"]`),a=this.data[e];t.forEach(t=>{if("INPUT"===t.tagName||"TEXTAREA"===t.tagName)this._handleInputElement(t,a,t=>{this.data[e]=t,this._queueUpdate(e,"modified",this.data[e])});else if(Array.isArray(a))t.querySelectorAll(`[res="${e}"][res-rendered="true"]`).forEach(e=>e.remove()),this._renderArray(e,t);else if("object"==typeof a&&null!==a){let s=t.querySelectorAll("[res-prop]");s.forEach(t=>{let s=t.getAttribute("res-prop");s&&s in a&&this._renderObjectProperty(t,a[s],e,s)})}else t.innerHTML=a??""}),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e)}_renderObjectProperty(e,t,a,s){if("INPUT"!==e.tagName&&"TEXTAREA"!==e.tagName||Array.isArray(t)||"object"==typeof t){if(Array.isArray(t))this._renderNestedArray(e,t);else if("object"==typeof t&&null!==t){let r=e.querySelectorAll("[res-prop]");r.forEach(e=>{let s=e.getAttribute("res-prop");s&&s in t&&this._renderObjectProperty(e,t[s],a,s)})}else e.innerHTML=t??""}else this._handleInputElement(e,t,e=>{window[a][s]=e})}_renderArray(e,t){let a=t.cloneNode(!0);t.innerHTML="",window[e+"_template"]?a=window[e+"_template"]:window[e+"_template"]=a,this.data[e].forEach((s,r)=>{let i=a.cloneNode(!0);for(let n in i.setAttribute("res-index",r),s){let l=null,o=i.querySelector(`[res-prop="${n}"]`);if(o||(o=i.querySelector('[res-prop=""]'),l=s),o){let h=this._resolveValue(s,n,l);if("INPUT"!==o.tagName&&"TEXTAREA"!==o.tagName||Array.isArray(h)||"object"==typeof h){if(Array.isArray(h))this._renderNestedArray(o,h);else if("object"==typeof h&&null!==h){let d=o.querySelectorAll("[res-prop]");d.forEach(t=>{let a=t.getAttribute("res-prop");a&&a in h&&this._renderObjectProperty(t,h[a],e,a)})}else o.innerHTML=h??""}else this._handleInputElement(o,h,t=>{s[n]=t,this._queueUpdate(e,"modified",s,n,h)})}}this._handleDisplayElements(i,s),this._bindClickEvents(i,s,this.data[e]),i.setAttribute("res-rendered","true"),t.appendChild(i)})}_renderNestedArray(e,t){let a=e.cloneNode(!0);e.innerHTML="",e.removeAttribute("res-prop"),t.forEach((s,r)=>{let i=a.cloneNode(!0);i.setAttribute("res-rendered","true");let n=i.querySelectorAll("[res-prop]");n.forEach(e=>{let t=e.getAttribute("res-prop");t&&t in s&&this._renderObjectProperty(e,s[t],null,t)}),this._handleDisplayElements(i,s),this._bindClickEvents(i,s,t),e.appendChild(i)})}updateDisplayConditionalsFor(e){let t=document.querySelectorAll(`[res-display*="${e}"]`);t.forEach(t=>{let a=t.getAttribute("res-display");this._evaluateDisplayCondition(t,this.data[e],a)})}updateStylesFor(variableName){let styleElements=document.querySelectorAll(`[res-style*="${variableName}"]`);styleElements.forEach(styleElement=>{let styleCondition=styleElement.getAttribute("res-style");try{let parent=styleElement,index=null;for(;parent&&!index;)index=parent.getAttribute("res-index"),parent=parent.parentElement;let resStyles=styleElement.getAttribute("res-styles");if(resStyles&&resStyles.split(" ").forEach(e=>{styleElement.classList.remove(e)}),null!==index){let item=this.data[variableName][index];styleCondition=styleCondition.replace(RegExp(`\\b${variableName}\\b`,"g"),"item");let styleClass=Function("item",`return ${styleCondition}`)(item);if(styleClass)styleElement.classList.add(styleClass);else{let elementHasStyle=styleElement.classList.contains(styleClass);elementHasStyle&&styleElement.classList.remove(styleClass)}}else{let styleClass=eval(styleCondition);if(styleClass)styleElement.classList.add(styleClass),styleElement.setAttribute("res-styles",styleClass);else{let elementHasStyle=styleElement.classList.contains(styleClass);elementHasStyle&&styleElement.classList.remove(styleClass)}}}catch(e){console.error(`Error evaluating style for ${variableName}: ${styleCondition}`,e)}})}addCallback(e,t){this.callbacks[e]||(this.callbacks[e]=[]),this.callbacks[e].push(t)}}
1
+ class ObservableArray extends Array{constructor(e,t,...r){if(void 0===t)return super(...r);super(...r);var s=void 0===t.data[e];this.variableName=e,this.resonantInstance=t,this.isDeleting=!1,this.forEach((e,t)=>{"object"==typeof e&&(this._setKeyForObjectAndChildObjects(e,t),this[t]=this._createProxy(e,t))}),s||this.forceUpdate()}_setKeyForObjectAndChildObjects(e,t){"object"==typeof e&&(e.key=t+"-"+this._generateKeyByContentCheckSum(e),Object.keys(e).forEach(r=>{this._setKeyForObjectAndChildObjects(e[r],t)}))}_generateKeyByContentCheckSum(e){let t=e=>{if(!e||"object"!=typeof e)return e;if(Array.isArray(e))return e.map(e=>t(e));let r={...e};return delete r.key,Object.keys(r).forEach(e=>{"object"==typeof r[e]&&null!==r[e]&&(r[e]=t(r[e]))}),r},r=t(e),s=JSON.stringify(r),a=0;for(let i=0;i<s.length;i++){let n=s.charCodeAt(i);a=(a<<5)-a+n,a&=a}return Math.abs(a).toString(36)}_createProxy(e,t){return new Proxy(e,{set:(r,s,a)=>{if(r[s]!==a){let i=r[s];r[s]=a,this._setKeyForObjectAndChildObjects(e,t),this.resonantInstance._queueUpdate(this.variableName,"modified",r,s,i,t)}return!0}})}forceUpdate(){this.forEach((e,t)=>{console.log("forceUpdate",e,t),this._setKeyForObjectAndChildObjects(e,t)}),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"modified",this.slice())}update(e){window[this.variableName]=e,this.resonantInstance._queueUpdate(this.variableName,"updated",e)}push(...e){e=e.map((e,t)=>"object"==typeof e?(this._setKeyForObjectAndChildObjects(e,this.length+t),this._createProxy(e,this.length+t)):e);let t=super.push(...e);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),e.forEach((e,t)=>{this.resonantInstance._queueUpdate(this.variableName,"added",e,this.length-1-t)}),t}splice(e,t,...r){r=r.map((t,r)=>"object"==typeof t?this._createProxy(t,e+r):t);let s=super.splice(e,t,...r);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),t>0&&s.forEach((t,r)=>{this.resonantInstance._queueUpdate(this.variableName,"removed",t,e+r)}),r.length>0&&r.forEach((t,r)=>{this.resonantInstance._queueUpdate(this.variableName,"added",t,e+r)}),s}set(e,t){if(this[e]!==t){if(this.isDeleting)return!0;let r=this.resonantInstance.arrayDataChangeDetection[this.variableName],s="modified";e>=r.length?s="added":void 0===r[e]&&(s="added"),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice();let a=this[e];this[e]=t,this.resonantInstance._queueUpdate(this.variableName,s,this[e],e,a)}return!0}delete(e){let t=this[e];return this.isDeleting=!0,this.splice(e,1),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"removed",null,e,t),this.isDeleting=!1,!0}filter(e,t=!0){if(void 0===this.resonantInstance||!1===t)return super.filter(e);let r=super.filter(e);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"filtered"),r}}class Resonant{constructor(){this.data={},this.callbacks={},this.pendingUpdates=new Map,this.arrayDataChangeDetection={}}_handleInputElement(e,t,r){"checkbox"===e.type?(e.checked=t,e.hasAttribute("data-resonant-bound")||(e.onchange=()=>r(e.checked),e.setAttribute("data-resonant-bound","true"))):(e.value=t,e.hasAttribute("data-resonant-bound")||(e.oninput=()=>r(e.value),e.setAttribute("data-resonant-bound","true")))}add(e,t,r){Array.isArray(t=this.persist(e,t,r))?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):"object"==typeof t&&null!==t?this.data[e]=this._createObject(e,t):this.data[e]=t,this._defineProperty(e),this.updateElement(e)}persist(e,t,r){if(void 0===r||!r)return t;var s=localStorage.getItem("res_"+e);return null!=s?JSON.parse(s):(localStorage.setItem("res_"+e,JSON.stringify(t)),t)}updatePersistantData(e){localStorage.getItem("res_"+e)&&localStorage.setItem("res_"+e,JSON.stringify(this.data[e]))}addAll(e){Object.entries(e).forEach(([e,t])=>{this.add(e,t)})}_resolveValue(e,t,r=null){return r??e[t]}_createObject(e,t){return t[Symbol("isProxy")]=!0,new Proxy(t,{set:(t,r,s)=>{if(t[r]!==s){let a=t[r];t[r]=s,this._queueUpdate(e,"modified",t,r,a)}return!0}})}_evaluateDisplayCondition(e,t,r){try{let s=Function("item",`return ${r}`)(t);e.style.display=s?"":"none"}catch(a){console.error(`Error evaluating display condition: ${r}`,a)}}_handleDisplayElements(e,t){let r=e.querySelectorAll("[res-display]");r.forEach(e=>{let r=e.getAttribute("res-display")||"";this._evaluateDisplayCondition(e,t,r)})}_bindClickEvents(e,t,r){let s=e.querySelectorAll("[res-onclick], [res-onclick-remove]");s.forEach(e=>{let s=e.getAttribute("res-onclick"),a=e.getAttribute("res-onclick-remove");s&&(e.onclick=()=>{let e=Function("item",`return ${s}(item)`);e(t)}),a&&(e.onclick=()=>{if(r){let e=r.findIndex(e=>e[a]===t[a]);-1!==e&&r.splice(e,1)}})})}_defineProperty(e){Object.defineProperty(window,e,{get:()=>this.data[e],set:t=>{Array.isArray(t)?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):"object"==typeof t&&null!==t?this.data[e]=this._createObject(e,t):this.data[e]=t,this.updateElement(e),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e),Array.isArray(t)||"object"==typeof t&&null!==t||this._queueUpdate(e,"modified",this.data[e])}})}_queueUpdate(e,t,r,s,a){this.pendingUpdates.has(e)||this.pendingUpdates.set(e,[]),this.pendingUpdates.get(e).push({action:t,item:r,property:s,oldValue:a}),1===this.pendingUpdates.get(e).length&&setTimeout(()=>{let t=this.pendingUpdates.get(e);this.updatePersistantData(e),this.pendingUpdates.delete(e),(t=t.filter((e,t,r)=>r.findIndex(t=>t.property===e.property&&t.action===e.action)===t)).forEach(t=>{this._triggerCallbacks(e,t)}),this.updateElement(e),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e)},0)}_triggerCallbacks(e,t){this.callbacks[e]&&this.callbacks[e].forEach(r=>{let s=t.item||t.oldValue;r(this.data[e],s,t.action)})}updateElement(e){let t=document.querySelectorAll(`[res="${e}"]`),r=this.data[e];t.forEach(t=>{if("INPUT"===t.tagName||"TEXTAREA"===t.tagName)this._handleInputElement(t,r,t=>{this.data[e]=t,this._queueUpdate(e,"modified",this.data[e])});else if(Array.isArray(r))t.querySelectorAll(`[res="${e}"][res-rendered="true"]`).forEach(e=>e.remove()),this._renderArray(e,t);else if("object"==typeof r&&null!==r){let s=t.querySelectorAll("[res-prop]");s.forEach(t=>{let s=t.getAttribute("res-prop");s&&s in r&&this._renderObjectProperty(t,r[s],e,s)})}else t.innerHTML=r??""}),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e)}_renderObjectProperty(e,t,r,s){if("INPUT"!==e.tagName&&"TEXTAREA"!==e.tagName||Array.isArray(t)||"object"==typeof t){if(Array.isArray(t))this._renderNestedArray(e,t);else if("object"==typeof t&&null!==t){let a=e.querySelectorAll("[res-prop]");a.forEach(e=>{let s=e.getAttribute("res-prop");s&&s in t&&this._renderObjectProperty(e,t[s],r,s)})}else e.innerHTML=t??""}else this._handleInputElement(e,t,e=>{window[r][s]=e})}_renderArray(e,t){let r=t.hasAttribute("res")&&t.parentElement?t.parentElement:t,s;window[e+"_template"]?s=window[e+"_template"]:(s=t.cloneNode(!0),window[e+"_template"]=s,t.style.display="none",t.setAttribute("res-template","true"));let a=new Map;r.querySelectorAll(`[res="${e}"][res-rendered="true"]`).forEach(e=>{let t=e.getAttribute("res-key");t&&a.set(t,e)}),r.querySelectorAll(`[res="${e}"][res-rendered="true"]`).forEach(e=>e.remove()),this.data[e].forEach((t,i)=>{let n=t.key,l;if(a.has(n))l=a.get(n),a.delete(n),l.setAttribute("res-index",i);else for(let o in(l=s.cloneNode(!0)).removeAttribute("res-template"),l.style.display="",l.setAttribute("res-rendered","true"),l.setAttribute("res-key",n),l.setAttribute("res-index",i),t){let h=null,d=l.querySelector(`[res-prop="${o}"]`);if(d||(d=l.querySelector('[res-prop=""]'),h=t),d){let c=this._resolveValue(t,o,h);if("INPUT"!==d.tagName&&"TEXTAREA"!==d.tagName||Array.isArray(c)||"object"==typeof c){if(Array.isArray(c))this._renderNestedArray(d,c);else if("object"==typeof c&&null!==c){let u=d.querySelectorAll("[res-prop]");u.forEach(t=>{let r=t.getAttribute("res-prop");r&&r in c&&this._renderObjectProperty(t,c[r],e,r)})}else d.innerHTML=c??""}else this._handleInputElement(d,c,r=>{t[o]=r,this._queueUpdate(e,"modified",t,o,c)})}}this._handleDisplayElements(l,t),this._bindClickEvents(l,t,this.data[e]),r.appendChild(l)})}_renderNestedArray(e,t){let r=e.cloneNode(!0);e.innerHTML="",e.removeAttribute("res-prop"),t.forEach((s,a)=>{let i=r.cloneNode(!0);i.setAttribute("res-rendered","true");let n=i.querySelectorAll("[res-prop]");n.forEach(e=>{let t=e.getAttribute("res-prop");t&&t in s&&this._renderObjectProperty(e,s[t],null,t)}),this._handleDisplayElements(i,s),this._bindClickEvents(i,s,t),e.appendChild(i)})}updateDisplayConditionalsFor(e){let t=document.querySelectorAll(`[res-display*="${e}"]`);t.forEach(t=>{let r=t.getAttribute("res-display");this._evaluateDisplayCondition(t,this.data[e],r)})}updateStylesFor(variableName){let styleElements=document.querySelectorAll(`[res-style*="${variableName}"]`);styleElements.forEach(styleElement=>{let styleCondition=styleElement.getAttribute("res-style");try{let parent=styleElement,index=null;for(;parent&&!index;)index=parent.getAttribute("res-index"),parent=parent.parentElement;let resStyles=styleElement.getAttribute("res-styles");if(resStyles&&resStyles.split(" ").forEach(e=>{styleElement.classList.remove(e)}),null!==index){let item=this.data[variableName][index];styleCondition=styleCondition.replace(RegExp(`\\b${variableName}\\b`,"g"),"item");let styleClass=Function("item",`return ${styleCondition}`)(item);if(styleClass)styleElement.classList.add(styleClass);else{let elementHasStyle=styleElement.classList.contains(styleClass);elementHasStyle&&styleElement.classList.remove(styleClass)}}else{let styleClass=eval(styleCondition);if(styleClass)styleElement.classList.add(styleClass),styleElement.setAttribute("res-styles",styleClass);else{let elementHasStyle=styleElement.classList.contains(styleClass);elementHasStyle&&styleElement.classList.remove(styleClass)}}}catch(e){console.error(`Error evaluating style for ${variableName}: ${styleCondition}`,e)}})}addCallback(e,t){this.callbacks[e]||(this.callbacks[e]=[]),this.callbacks[e].push(t)}}