resonantjs 1.0.4 → 1.0.6

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/Demo.gif CHANGED
Binary file
package/README.md CHANGED
@@ -60,9 +60,10 @@ Include resonant.js in your HTML file, and use the following example to understa
60
60
  - **`res` and `res-prop` Attributes**: Bind HTML elements to your data model seamlessly.
61
61
  - `res` is used to identify an overarching data model.
62
62
  - `res-prop` links individual properties within that model to corresponding UI elements.
63
- - **`res-conditional` Attribute**: Conditionally display elements based on the data model's properties.
63
+ - **`res-display` Attribute**: Conditionally display elements based on the data model's properties.
64
64
  - **`res-onclick` Attribute**: Triggers a function when an element is clicked, allowing for custom event handling.
65
65
  - **`res-onclick-remove` Attribute**: Removes an item from an array when the associated element is clicked.
66
+ - **`res-style` Attribute**: Dynamically update CSS styles based on data model properties.
66
67
  - **Automatic UI Updates**: Changes to your JavaScript objects instantly reflect in the associated UI components, reducing manual DOM manipulation.
67
68
 
68
69
  ### Advanced Features
@@ -84,72 +85,8 @@ Include resonant.js in your HTML file, and use the following example to understa
84
85
  #### Refined Data Binding
85
86
  - Enhanced data binding between model and view to ensure consistent synchronization without unnecessary updates.
86
87
 
87
- ### Task Manager Example
88
-
89
- Here is an example of a task manager application using Resonant.js:
90
-
91
- ```html
92
- <!DOCTYPE html>
93
- <html lang="en">
94
- <head>
95
- <title>Resonant.js Task Manager Demo</title>
96
- <script src="https://cdn.jsdelivr.net/npm/resonantjs@latest/dist/resonant.min.js"></script>
97
- </head>
98
- <body>
99
- <h1>Resonant.js Task Manager Demo</h1>
100
-
101
- <!-- Task Input -->
102
- <div>
103
- <h2>Add New Task</h2>
104
- <input type="text" placeholder="Task Name" res="taskName" />
105
- <button onclick="addTask()">Add Task</button>
106
- </div>
107
-
108
- <!-- Task List -->
109
- <div>
110
- <h2>Task List</h2>
111
- <ul res="tasks">
112
- <li>
113
- <input type="checkbox" res-prop="done" />
114
- <span res-prop="name"></span>
115
- <button res-onclick-remove="name">Remove</button>
116
- </li>
117
- </ul>
118
- </div>
119
-
120
- <script>
121
- const resonantJs = new Resonant();
122
-
123
- // Initialize variables using a configuration object
124
- resonantJs.addAll({
125
- tasks: [
126
- { name: "Task 1", done: false },
127
- { name: "Task 2", done: true }
128
- ],
129
- taskName: ""
130
- });
131
-
132
- // Add a callback to log actions taken on tasks
133
- resonantJs.addCallback("tasks", (tasks, task, action) => {
134
- console.log(`Action taken: ${action} for ${task.name}`);
135
- });
136
-
137
- // Add a function to add a new task
138
- function addTask() {
139
- const newTask = { name: taskName, done: false };
140
- tasks.push(newTask);
141
- taskName = '';
142
- }
143
- </script>
144
- </body>
145
- </html>
146
- ```
147
-
148
- ## Future Enhancements
149
-
150
- - [ ] Support for nested data structures with deep linking.
151
- - [ ] Advanced filtering and sorting methods for arrays.
152
- - [ ] Compatibility with popular JavaScript frameworks.
88
+ ## Other Information
89
+ - The demo HTML file uses the Pico CSS framework for styling. You can find more information about Pico CSS [here](https://picocss.com/).
153
90
 
154
91
  ## License
155
92
 
@@ -2,52 +2,85 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <title>Resonant.js Practical Demo</title>
5
- <script src="../resonant.js"></script>
5
+ <link
6
+ rel="stylesheet"
7
+ href="https://cdn.jsdelivr.net/npm/@picocss/pico@2.0.6/css/pico.min.css"
8
+ />
9
+ <script src="https://unpkg.com/resonantjs@latest/resonant.min.js"></script>
10
+ <style>
11
+ section.grid {
12
+ padding: 1rem;
13
+ margin-bottom: 2rem;
14
+ border: 1px solid #e0e0e0;
15
+ border-radius: 0.5rem;
16
+ }
17
+ .conditional {
18
+ font-style: italic;
19
+ color: #888;
20
+ }
21
+ </style>
6
22
  </head>
7
23
  <body>
8
- <h1>Resonant.js Practical Demo</h1>
9
24
 
10
- <!-- Display and update a single item -->
11
- <div>
12
- <h2>Counter</h2>
13
- <p>
14
- Current count: <span res="counter"></span>
15
- </p>
16
- <div res-conditional="counter < 10">
17
- Only shows when counter is less than 10
18
- </div>
19
- <div res-conditional="counter >= 10">
20
- Only shows when counter is greater than or equal to 10
21
- </div>
22
- <button onclick="counter++">Increment Counter</button>
23
- </div>
25
+ <main class="container">
26
+ <h1>Resonant.js Basic Demo</h1>
24
27
 
25
- <!-- Demonstrate object property binding -->
26
- <div>
27
- <h2>User Information</h2>
28
- <div res="user">
29
- <span res-prop="firstname"></span>
30
- <span res-prop="lastname"></span>
31
- <br/>
32
- <div res-conditional="user.firstname == 'John' && user.lastname == 'Doe'">
33
- Only shows when firstname is John and lastname is Doe
28
+ <!-- Display and update a single item -->
29
+ <section class="grid">
30
+ <div>
31
+ <h2>Counter: <span res="counter"></span></h2>
32
+ <div res-display="counter < 10" class="conditional">
33
+ Only shows when counter is less than 10
34
+ </div>
35
+ <div res-display="counter >= 10" class="conditional">
36
+ Only shows when counter is greater than or equal to 10
37
+ </div>
34
38
  </div>
35
- <br/>
36
- First Name: <input type="text" res-prop="firstname" />
37
- Last Name: <input type="text" res-prop="lastname" />
38
- </div>
39
- </div>
40
39
 
41
- <!-- Demonstrate dynamic list rendering -->
42
- <div>
43
- <h2>Project Members</h2>
44
- <ul res="projectTeam">
45
- <li>
46
- <span res-prop="name"></span> - <span res-prop="role"></span>
47
- </li>
48
- </ul>
49
- <button onclick="addProjectMember()">Add Project Member</button>
50
- </div>
40
+ <button onclick="counter++">Increment Counter</button>
41
+ </section>
42
+
43
+ <!-- Demonstrate object property binding -->
44
+ <section class="grid" res="user">
45
+ <div>
46
+ <h2>User Information</h2>
47
+ <div res-display="user.firstname == 'John' && user.lastname == 'Doe'" class="conditional">
48
+ Only shows when firstname is John and lastname is Doe
49
+ </div>
50
+
51
+ <p>
52
+ <span res-prop="firstname"></span>
53
+ <span res-prop="lastname"></span>
54
+ </p>
55
+ </div>
56
+
57
+ <div>
58
+
59
+ <div class="form-group">
60
+ <label>First Name:
61
+ <input type="text" res-prop="firstname" />
62
+ </label>
63
+ <label>Last Name:
64
+ <input type="text" res-prop="lastname" />
65
+ </label>
66
+ </div>
67
+ </div>
68
+ </section>
69
+
70
+ <!-- Demonstrate dynamic list rendering -->
71
+ <section class="grid">
72
+ <div>
73
+ <h2>Project Members</h2>
74
+ <ul res="projectTeam">
75
+ <li>
76
+ <span res-prop="name"></span> - <span res-prop="role"></span>
77
+ </li>
78
+ </ul>
79
+ </div>
80
+
81
+ <button onclick="addProjectMember()">Add Project Member</button>
82
+ </section>
83
+ </main>
51
84
 
52
85
  <script>
53
86
  const resonantJs = new Resonant();
@@ -49,7 +49,8 @@
49
49
  });
50
50
 
51
51
  function remove(task) {
52
- const index = tasks.indexOf(task);
52
+ //get by name
53
+ const index = tasks.findIndex(t => t.name === task.name);
53
54
  tasks.splice(index, 1);
54
55
 
55
56
  //You could use as well, still trying to figure out if I want to leave this or not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resonantjs",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
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
@@ -1,12 +1,94 @@
1
+ class ObservableArray extends Array {
2
+ constructor(variableName, resonantInstance, ...args) {
3
+ super(...args);
4
+ this.variableName = variableName;
5
+ this.resonantInstance = resonantInstance;
6
+ this.isDeleting = false;
7
+ }
8
+
9
+ push(...args) {
10
+ const result = super.push(...args);
11
+ this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
12
+ args.forEach((item, index) => {
13
+ this.resonantInstance._queueUpdate(this.variableName, 'added', item, this.length - 1 - index);
14
+ });
15
+ return result;
16
+ }
17
+
18
+ splice(start, deleteCount, ...items) {
19
+ const removedItems = super.splice(start, deleteCount, ...items);
20
+ this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
21
+
22
+ if (deleteCount > 0) {
23
+ removedItems.forEach((item, index) => {
24
+ this.resonantInstance._queueUpdate(this.variableName, 'removed', item, start + index);
25
+ });
26
+ }
27
+
28
+ if (items.length > 0) {
29
+ items.forEach((item, index) => {
30
+ this.resonantInstance._queueUpdate(this.variableName, 'added', item, start + index);
31
+ });
32
+ }
33
+
34
+ return removedItems;
35
+ }
36
+
37
+ set(index, value) {
38
+ if (this[index] !== value) {
39
+ if (this.isDeleting) {
40
+ return true;
41
+ }
42
+
43
+ const originalBeforeChange = this.resonantInstance.arrayDataChangeDetection[this.variableName];
44
+ let action = 'modified';
45
+
46
+ if (index >= originalBeforeChange.length) {
47
+ action = 'added';
48
+ } else if (originalBeforeChange[index] === undefined) {
49
+ action = 'added';
50
+ }
51
+
52
+ this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
53
+
54
+ const oldValue = this[index];
55
+ this[index] = value;
56
+ this.resonantInstance._queueUpdate(this.variableName, action, this[index], index, oldValue);
57
+ }
58
+ return true;
59
+ }
60
+
61
+ delete(index) {
62
+ const oldValue = this[index];
63
+ this.isDeleting = true;
64
+ this.splice(index, 1);
65
+
66
+ this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
67
+
68
+ this.resonantInstance._queueUpdate(this.variableName, 'removed', null, index, oldValue);
69
+ this.isDeleting = false;
70
+ return true;
71
+ }
72
+ }
73
+
1
74
  class Resonant {
2
75
  constructor() {
3
76
  this.data = {};
4
77
  this.callbacks = {};
5
- this.pendingUpdates = new Set();
78
+ this.pendingUpdates = new Map();
79
+ this.arrayDataChangeDetection = {}; // Added to keep track of array state
6
80
  }
7
81
 
8
82
  add(variableName, value) {
9
- this._assignValueToData(variableName, value);
83
+ if (Array.isArray(value)) {
84
+ this.data[variableName] = new ObservableArray(variableName, this, ...value);
85
+ this.arrayDataChangeDetection[variableName] = this.data[variableName].slice();
86
+ } else if (typeof value === 'object') {
87
+ this.data[variableName] = this._createObject(variableName, value);
88
+ } else {
89
+ this.data[variableName] = value;
90
+ }
91
+
10
92
  this._defineProperty(variableName);
11
93
  this.updateElement(variableName);
12
94
  }
@@ -17,16 +99,6 @@ class Resonant {
17
99
  });
18
100
  }
19
101
 
20
- _assignValueToData(variableName, value) {
21
- if (Array.isArray(value)) {
22
- this.data[variableName] = this._createArray(variableName, value);
23
- } else if (typeof value === 'object') {
24
- this.data[variableName] = this._createObject(variableName, value);
25
- } else {
26
- this.data[variableName] = value;
27
- }
28
- }
29
-
30
102
  _createObject(variableName, obj) {
31
103
  obj[Symbol('isProxy')] = true;
32
104
  return new Proxy(obj, {
@@ -41,77 +113,66 @@ class Resonant {
41
113
  });
42
114
  }
43
115
 
44
- _createArray(variableName, arr) {
45
- const self = this;
46
- return new Proxy(arr, {
47
- get(target, index) {
48
- if (typeof target[index] === 'object' && !target[index][Symbol('isProxy')]) {
49
- target[index] = self._createObject(`${variableName}[${index}]`, target[index]);
116
+ _defineProperty(variableName) {
117
+ Object.defineProperty(window, variableName, {
118
+ get: () => this.data[variableName],
119
+ set: (newValue) => {
120
+ if (Array.isArray(newValue)) {
121
+ this.data[variableName] = new ObservableArray(variableName, this, ...newValue);
122
+ this.arrayDataChangeDetection[variableName] = this.data[variableName].slice(); // Create a copy for change detection
123
+
124
+ } else if (typeof newValue === 'object') {
125
+ this.data[variableName] = this._createObject(variableName, newValue);
126
+ } else {
127
+ this.data[variableName] = newValue;
50
128
  }
51
- return target[index];
52
- },
53
- set(target, index, value) {
54
- if (target[index] !== value) {
55
- const action = target.hasOwnProperty(index) ? 'modified' : 'added';
56
- const oldValue = target[index];
57
- target[index] = value;
58
- self._queueUpdate(variableName, action, target[index], index, oldValue);
129
+ this.updateElement(variableName);
130
+ this.updateDisplayConditionalsFor(variableName);
131
+ this.updateStylesFor(variableName);
132
+ if (!Array.isArray(newValue) && typeof newValue !== 'object') {
133
+ this._queueUpdate(variableName, 'modified', this.data[variableName]);
59
134
  }
60
- return true;
61
- },
62
- deleteProperty(target, index) {
63
- const oldValue = target[index];
64
- target.splice(index, 1);
65
- self._queueUpdate(variableName, 'removed', oldValue, index);
66
- return true;
67
135
  }
68
136
  });
69
137
  }
70
138
 
71
139
  _queueUpdate(variableName, action, item, property, oldValue) {
72
-
73
140
  if (!this.pendingUpdates.has(variableName)) {
74
- this.pendingUpdates.add(variableName);
141
+ this.pendingUpdates.set(variableName, []);
142
+ }
143
+
144
+ this.pendingUpdates.get(variableName).push({ action, item, property, oldValue });
145
+
146
+ if (this.pendingUpdates.get(variableName).length === 1) {
75
147
  setTimeout(() => {
148
+ const updates = this.pendingUpdates.get(variableName);
76
149
  this.pendingUpdates.delete(variableName);
77
- this._triggerCallbacks(variableName, action, item, property, oldValue);
150
+ updates.forEach(update => {
151
+ this._triggerCallbacks(variableName, update);
152
+ });
78
153
  this.updateElement(variableName);
79
- this.updateConditionalsFor(variableName);
154
+ this.updateDisplayConditionalsFor(variableName);
80
155
  this.updateStylesFor(variableName);
81
156
  }, 0);
82
157
  }
83
158
  }
84
159
 
85
- _triggerCallbacks(variableName, action, item, property, oldValue) {
160
+ _triggerCallbacks(variableName, callbackData) {
86
161
  if (this.callbacks[variableName]) {
87
- this.callbacks[variableName].forEach(callback => callback(this.data[variableName], item, action, property, oldValue));
162
+ this.callbacks[variableName].forEach(callback => {
163
+ const item = callbackData.item || callbackData.oldValue;
164
+ callback(this.data[variableName], item, callbackData.action);
165
+ });
88
166
  }
89
167
  }
90
168
 
91
- _defineProperty(variableName) {
92
- Object.defineProperty(window, variableName, {
93
- get: () => this.data[variableName],
94
- set: (newValue) => {
95
- this._assignValueToData(variableName, newValue);
96
- this.updateElement(variableName);
97
- this.updateConditionalsFor(variableName);
98
- this.updateStylesFor(variableName);
99
- if (!Array.isArray(newValue) && typeof newValue !== 'object') {
100
- this._queueUpdate(variableName, 'modified', this.data[variableName]);
101
- }
102
- }
103
- });
104
- }
105
-
106
169
  updateElement(variableName) {
107
170
  const elements = document.querySelectorAll(`[res="${variableName}"]`);
108
171
  const value = this.data[variableName];
109
172
 
110
-
111
-
112
173
  elements.forEach(element => {
174
+ element.value = value;
113
175
  if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
114
- element.value = value;
115
176
  if (!element.hasAttribute('data-resonant-bound')) {
116
177
  element.oninput = () => {
117
178
  this.data[variableName] = element.value;
@@ -162,14 +223,14 @@ class Resonant {
162
223
  }
163
224
  });
164
225
 
165
- this.updateConditionalsFor(variableName);
226
+ this.updateDisplayConditionalsFor(variableName);
166
227
  this.updateStylesFor(variableName);
167
228
  }
168
229
 
169
- updateConditionalsFor(variableName) {
170
- const conditionalElements = document.querySelectorAll(`[res-conditional*="${variableName}"]`);
230
+ updateDisplayConditionalsFor(variableName) {
231
+ const conditionalElements = document.querySelectorAll(`[res-display*="${variableName}"]`);
171
232
  conditionalElements.forEach(conditionalElement => {
172
- const condition = conditionalElement.getAttribute('res-conditional');
233
+ const condition = conditionalElement.getAttribute('res-display');
173
234
  try {
174
235
  if (eval(condition)) {
175
236
  conditionalElement.style.display = '';
@@ -280,8 +341,6 @@ class Resonant {
280
341
  const removeKey = onclickEl.getAttribute('res-onclick-remove');
281
342
 
282
343
  if (functionName) {
283
- onclickEl.onclick = null;
284
-
285
344
  onclickEl.onclick = () => {
286
345
  const func = new Function('item', `return ${functionName}(item)`);
287
346
  func(instance);
@@ -289,8 +348,6 @@ class Resonant {
289
348
  }
290
349
 
291
350
  if (removeKey) {
292
- onclickEl.onclick = null;
293
-
294
351
  onclickEl.onclick = () => {
295
352
  const index = this.data[variableName].findIndex(t => t[removeKey] === instance[removeKey]);
296
353
  if (index !== -1) {
package/resonant.min.js CHANGED
@@ -1 +1 @@
1
- class Resonant{constructor(){this.data={},this.callbacks={},this.pendingUpdates=new Set}add(e,t){this._assignValueToData(e,t),this._defineProperty(e),this.updateElement(e)}addAll(e){Object.entries(e).forEach(([e,t])=>{this.add(e,t)})}_assignValueToData(e,t){Array.isArray(t)?this.data[e]=this._createArray(e,t):this.data[e]="object"==typeof t?this._createObject(e,t):t}_createObject(n,e){return e[Symbol("isProxy")]=!0,new Proxy(e,{set:(e,t,a)=>{var s;return e[t]!==a&&(s=e[t],e[t]=a,this._queueUpdate(n,"modified",e,t,s)),!0}})}_createArray(l,e){const i=this;return new Proxy(e,{get(e,t){return"object"!=typeof e[t]||e[t][Symbol("isProxy")]||(e[t]=i._createObject(l+`[${t}]`,e[t])),e[t]},set(e,t,a){var s,n;return e[t]!==a&&(s=e.hasOwnProperty(t)?"modified":"added",n=e[t],e[t]=a,i._queueUpdate(l,s,e[t],t,n)),!0},deleteProperty(e,t){var a=e[t];return e.splice(t,1),i._queueUpdate(l,"removed",a,t),!0}})}_queueUpdate(e,t,a,s,n){this.pendingUpdates.has(e)||(this.pendingUpdates.add(e),setTimeout(()=>{this.pendingUpdates.delete(e),this._triggerCallbacks(e,t,a,s,n),this.updateElement(e),this.updateConditionalsFor(e),this.updateStylesFor(e)},0))}_triggerCallbacks(t,a,s,n,l){this.callbacks[t]&&this.callbacks[t].forEach(e=>e(this.data[t],s,a,n,l))}_defineProperty(t){Object.defineProperty(window,t,{get:()=>this.data[t],set:e=>{this._assignValueToData(t,e),this.updateElement(t),this.updateConditionalsFor(t),this.updateStylesFor(t),Array.isArray(e)||"object"==typeof e||this._queueUpdate(t,"modified",this.data[t])}})}updateElement(a){var e=document.querySelectorAll(`[res="${a}"]`);const s=this.data[a];e.forEach(e=>{"INPUT"===e.tagName||"TEXTAREA"===e.tagName?(e.value=s,e.hasAttribute("data-resonant-bound")||(e.oninput=()=>{this.data[a]=e.value,this._queueUpdate(a,"modified",this.data[a])},e.setAttribute("data-resonant-bound","true"))):Array.isArray(s)?(e.querySelectorAll(`[res="${a}"][res-rendered=true]`).forEach(e=>e.remove()),this._renderArray(a,e)):"object"==typeof s?e.querySelectorAll("[res-prop]").forEach(e=>{const t=e.getAttribute("res-prop");t&&t in s&&(e.hasAttribute("data-resonant-bound")?"INPUT"===e.tagName||"TEXTAREA"===e.tagName?"checkbox"===e.type?e.checked=s[t]:e.value=s[t]:e.innerHTML=s[t]:("INPUT"===e.tagName||"TEXTAREA"===e.tagName?"checkbox"===e.type?(e.checked=s[t],e.onchange=()=>{this.data[a][t]=e.checked}):(e.value=s[t],e.oninput=()=>{this.data[a][t]=e.value}):e.innerHTML=s[t],e.setAttribute("data-resonant-bound","true")))}):e.innerHTML=s}),this.updateConditionalsFor(a),this.updateStylesFor(a)}updateConditionalsFor(variableName){const conditionalElements=document.querySelectorAll(`[res-conditional*="${variableName}"]`);conditionalElements.forEach(conditionalElement=>{const condition=conditionalElement.getAttribute("res-conditional");try{eval(condition)?conditionalElement.style.display="":conditionalElement.style.display="none"}catch(e){}})}updateStylesFor(variableName){const 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;if(null!==index){const item=this.data[variableName][index],styleClass=(styleCondition=styleCondition.replace(new RegExp(`\\b${variableName}\\b`,"g"),"item"),new Function("item","return "+styleCondition)(item));var elementHasStyle;styleClass?styleElement.classList.add(styleClass):(elementHasStyle=styleElement.classList.contains(styleClass),elementHasStyle&&styleElement.classList.remove(styleClass))}else{const styleClass=eval(styleCondition);var elementHasStyle;styleClass?styleElement.classList.add(styleClass):(elementHasStyle=styleElement.classList.contains(styleClass),elementHasStyle&&styleElement.classList.remove(styleClass))}}catch(e){}})}_renderArray(n,l){let i=l.cloneNode(!0);l.innerHTML="",window[n+"_template"]?i=window[n+"_template"]:window[n+"_template"]=i,this.data[n].forEach((s,e)=>{var t=i.cloneNode(!0);t.setAttribute("res-index",e);for(let e in s){const a=t.querySelector(`[res-prop="${e}"]`);a&&(a.hasAttribute("data-resonant-bound")?"INPUT"===a.tagName||"TEXTAREA"===a.tagName?"checkbox"===a.type?a.checked=s[e]:a.value=s[e]:a.innerHTML=s[e]:("INPUT"===a.tagName||"TEXTAREA"===a.tagName?"checkbox"===a.type?(a.checked=s[e],a.onchange=()=>{s[e]=a.checked,this._queueUpdate(n,"modified",s,e,s[e])}):(a.value=s[e],a.oninput=()=>{s[e]=a.value,this._queueUpdate(n,"modified",s,e,s[e])}):a.innerHTML=s[e],a.setAttribute("data-resonant-bound","true")))}t.querySelectorAll("[res-onclick], [res-onclick-remove]").forEach(e=>{const t=e.getAttribute("res-onclick"),a=e.getAttribute("res-onclick-remove");t&&(e.onclick=null,e.onclick=()=>{new Function("item",`return ${t}(item)`)(s)}),a&&(e.onclick=null,e.onclick=()=>{var e=this.data[n].findIndex(e=>e[a]===s[a]);-1!==e&&this.data[n].splice(e,1)})}),t.setAttribute("res-rendered",!0),l.appendChild(t)})}addCallback(e,t){this.callbacks[e]||(this.callbacks[e]=[]),this.callbacks[e].push(t)}}
1
+ class ObservableArray extends Array{constructor(e,t,...a){super(...a),this.variableName=e,this.resonantInstance=t,this.isDeleting=!1}push(...e){var 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(a,e,...t){var s=super.splice(a,e,...t);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),0<e&&s.forEach((e,t)=>{this.resonantInstance._queueUpdate(this.variableName,"removed",e,a+t)}),0<t.length&&t.forEach((e,t)=>{this.resonantInstance._queueUpdate(this.variableName,"added",e,a+t)}),s}set(t,a){if(this[t]!==a){if(this.isDeleting)return!0;var s=this.resonantInstance.arrayDataChangeDetection[this.variableName];let e="modified";(t>=s.length||void 0===s[t])&&(e="added"),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice();s=this[t];this[t]=a,this.resonantInstance._queueUpdate(this.variableName,e,this[t],t,s)}return!0}delete(e){var 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)}}class Resonant{constructor(){this.data={},this.callbacks={},this.pendingUpdates=new Map,this.arrayDataChangeDetection={}}add(e,t){Array.isArray(t)?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):this.data[e]="object"==typeof t?this._createObject(e,t):t,this._defineProperty(e),this.updateElement(e)}addAll(e){Object.entries(e).forEach(([e,t])=>{this.add(e,t)})}_createObject(i,e){return e[Symbol("isProxy")]=!0,new Proxy(e,{set:(e,t,a)=>{var s;return e[t]!==a&&(s=e[t],e[t]=a,this._queueUpdate(i,"modified",e,t,s)),!0}})}_defineProperty(t){Object.defineProperty(window,t,{get:()=>this.data[t],set:e=>{Array.isArray(e)?(this.data[t]=new ObservableArray(t,this,...e),this.arrayDataChangeDetection[t]=this.data[t].slice()):this.data[t]="object"==typeof e?this._createObject(t,e):e,this.updateElement(t),this.updateDisplayConditionalsFor(t),this.updateStylesFor(t),Array.isArray(e)||"object"==typeof e||this._queueUpdate(t,"modified",this.data[t])}})}_queueUpdate(t,e,a,s,i){this.pendingUpdates.has(t)||this.pendingUpdates.set(t,[]),this.pendingUpdates.get(t).push({action:e,item:a,property:s,oldValue:i}),1===this.pendingUpdates.get(t).length&&setTimeout(()=>{var e=this.pendingUpdates.get(t);this.pendingUpdates.delete(t),e.forEach(e=>{this._triggerCallbacks(t,e)}),this.updateElement(t),this.updateDisplayConditionalsFor(t),this.updateStylesFor(t)},0)}_triggerCallbacks(a,s){this.callbacks[a]&&this.callbacks[a].forEach(e=>{var t=s.item||s.oldValue;e(this.data[a],t,s.action)})}updateElement(a){var e=document.querySelectorAll(`[res="${a}"]`);const s=this.data[a];e.forEach(e=>{e.value=s,"INPUT"===e.tagName||"TEXTAREA"===e.tagName?e.hasAttribute("data-resonant-bound")||(e.oninput=()=>{this.data[a]=e.value,this._queueUpdate(a,"modified",this.data[a])},e.setAttribute("data-resonant-bound","true")):Array.isArray(s)?(e.querySelectorAll(`[res="${a}"][res-rendered=true]`).forEach(e=>e.remove()),this._renderArray(a,e)):"object"==typeof s?e.querySelectorAll("[res-prop]").forEach(e=>{const t=e.getAttribute("res-prop");t&&t in s&&(e.hasAttribute("data-resonant-bound")?"INPUT"===e.tagName||"TEXTAREA"===e.tagName?"checkbox"===e.type?e.checked=s[t]:e.value=s[t]:e.innerHTML=s[t]:("INPUT"===e.tagName||"TEXTAREA"===e.tagName?"checkbox"===e.type?(e.checked=s[t],e.onchange=()=>{this.data[a][t]=e.checked}):(e.value=s[t],e.oninput=()=>{this.data[a][t]=e.value}):e.innerHTML=s[t],e.setAttribute("data-resonant-bound","true")))}):e.innerHTML=s}),this.updateDisplayConditionalsFor(a),this.updateStylesFor(a)}updateDisplayConditionalsFor(variableName){const conditionalElements=document.querySelectorAll(`[res-display*="${variableName}"]`);conditionalElements.forEach(conditionalElement=>{const condition=conditionalElement.getAttribute("res-display");try{eval(condition)?conditionalElement.style.display="":conditionalElement.style.display="none"}catch(e){}})}updateStylesFor(variableName){const 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;if(null!==index){const item=this.data[variableName][index],styleClass=(styleCondition=styleCondition.replace(new RegExp(`\\b${variableName}\\b`,"g"),"item"),new Function("item","return "+styleCondition)(item));var elementHasStyle;styleClass?styleElement.classList.add(styleClass):(elementHasStyle=styleElement.classList.contains(styleClass),elementHasStyle&&styleElement.classList.remove(styleClass))}else{const styleClass=eval(styleCondition);var elementHasStyle;styleClass?styleElement.classList.add(styleClass):(elementHasStyle=styleElement.classList.contains(styleClass),elementHasStyle&&styleElement.classList.remove(styleClass))}}catch(e){}})}_renderArray(i,n){let r=n.cloneNode(!0);n.innerHTML="",window[i+"_template"]?r=window[i+"_template"]:window[i+"_template"]=r,this.data[i].forEach((s,e)=>{var t=r.cloneNode(!0);t.setAttribute("res-index",e);for(let e in s){const a=t.querySelector(`[res-prop="${e}"]`);a&&(a.hasAttribute("data-resonant-bound")?"INPUT"===a.tagName||"TEXTAREA"===a.tagName?"checkbox"===a.type?a.checked=s[e]:a.value=s[e]:a.innerHTML=s[e]:("INPUT"===a.tagName||"TEXTAREA"===a.tagName?"checkbox"===a.type?(a.checked=s[e],a.onchange=()=>{s[e]=a.checked,this._queueUpdate(i,"modified",s,e,s[e])}):(a.value=s[e],a.oninput=()=>{s[e]=a.value,this._queueUpdate(i,"modified",s,e,s[e])}):a.innerHTML=s[e],a.setAttribute("data-resonant-bound","true")))}t.querySelectorAll("[res-onclick], [res-onclick-remove]").forEach(e=>{const t=e.getAttribute("res-onclick"),a=e.getAttribute("res-onclick-remove");t&&(e.onclick=()=>{new Function("item",`return ${t}(item)`)(s)}),a&&(e.onclick=()=>{var e=this.data[i].findIndex(e=>e[a]===s[a]);-1!==e&&this.data[i].splice(e,1)})}),t.setAttribute("res-rendered",!0),n.appendChild(t)})}addCallback(e,t){this.callbacks[e]||(this.callbacks[e]=[]),this.callbacks[e].push(t)}}