resonantjs 1.1.0 → 1.1.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.
@@ -6,7 +6,7 @@
6
6
  rel="stylesheet"
7
7
  href="https://cdn.jsdelivr.net/npm/@picocss/pico@2.0.6/css/pico.min.css"
8
8
  />
9
- <script src="https://unpkg.com/resonantjs@latest/resonant.min.js"></script>
9
+ <script src="../resonant.js"></script>
10
10
  <style>
11
11
  section.grid {
12
12
  padding: 1rem;
@@ -40,6 +40,17 @@
40
40
  <button onclick="counter++">Increment Counter</button>
41
41
  </section>
42
42
 
43
+ <section class="grid">
44
+ <code class="code">
45
+ resonantJs.add("counter", <span res="counter">0</span>);
46
+ </code>
47
+ <code class="code">
48
+ Counter: &lt;span res="counter"&gt;&lt;/span&gt;<br/><br/>
49
+ &lt;button onclick="counter++"&gt;Increment Counter&lt;/button&gt;<br/><br/>
50
+ &lt;div res-display="counter &lt; 10"&gt;contents&lt;/div&gt;<br/>
51
+ </code>
52
+ </section>
53
+
43
54
  <!-- Demonstrate object property binding -->
44
55
  <section class="grid" res="user">
45
56
  <div>
@@ -67,6 +78,26 @@
67
78
  </div>
68
79
  </section>
69
80
 
81
+ <section class="grid" res="user">
82
+ <code class="code">
83
+ resonantJs.add({<br/>
84
+ &nbsp;&nbsp;user: {<br/>
85
+ &nbsp;&nbsp;&nbsp;&nbsp;firstname: "<span res-prop="firstname"></span>",<br/>
86
+ &nbsp;&nbsp;&nbsp;&nbsp;lastname: "<span res-prop="lastname"></span>"<br/>
87
+ &nbsp;&nbsp;}<br/>
88
+ });
89
+ </code>
90
+ <code class="code">
91
+ &lt;div res="user"&gt;<br/>
92
+ &nbsp;&nbsp;&nbsp;&nbsp;&lt;input type="text" res-prop="firstname" /&gt;<br/>
93
+ &nbsp;&nbsp;&nbsp;&nbsp;&lt;input type="text" res-prop="lastname" /&gt;<br/>
94
+ &nbsp;&nbsp;&nbsp;&nbsp;&lt;span res-prop="firstname"&gt;&lt;/span&gt;<br/>
95
+ &nbsp;&nbsp;&nbsp;&nbsp;&lt;span res-prop="lastname"&gt;&lt;/span&gt;<br/>
96
+ &lt;/div&gt;
97
+ <br/><br/>
98
+ </code>
99
+ </section>
100
+
70
101
  <!-- Demonstrate dynamic list rendering -->
71
102
  <section class="grid">
72
103
  <div>
@@ -77,8 +108,84 @@
77
108
  </li>
78
109
  </ul>
79
110
  </div>
111
+ <div>
112
+ <button onclick="addProjectMember()">Add</button>
113
+ <button onclick="toggleProjectMemberName()">Toggle Alice's Name</button>
114
+ </div>
80
115
 
81
- <button onclick="addProjectMember()">Add Project Member</button>
116
+ </section>
117
+
118
+ <section class="grid">
119
+ <code class="code">
120
+ resonantJs.add({<br/>
121
+ &nbsp;&nbsp;projectTeam: [<br/>
122
+ &nbsp;&nbsp;&nbsp;&nbsp;{ name: "Alice", role: "Developer" },<br/>
123
+ &nbsp;&nbsp;&nbsp;&nbsp;{ name: "Bob", role: "Designer" }<br/>
124
+ &nbsp;&nbsp;]<br/>
125
+ });<br/><br/>
126
+
127
+ function addProjectMember() {<br/>
128
+ &nbsp;&nbsp;const newMember = { name: "Charlie", role: "Product Manager" };<br/>
129
+ &nbsp;&nbsp;projectTeam.push(newMember);<br/>
130
+ }
131
+ </code>
132
+ <code class="code">
133
+ &lt;ul res="projectTeam"&gt;<br/>
134
+ &nbsp;&nbsp;&lt;li&gt;<br/>
135
+ &nbsp;&nbsp;&nbsp;&nbsp;&lt;span res-prop="name"&gt;&lt;/span&gt - &lt;span res-prop="role"&gt;&lt;/span&gt<br/>
136
+ &nbsp;&nbsp;&lt;/li&gt;<br/>
137
+ &lt;/ul&gt;<br/><br/>
138
+ &lt;button onclick="addProjectMember()"&gt;Add Project Member&lt;/button&gt;
139
+ </code>
140
+ </section>
141
+ <section>
142
+ <h1>Summary</h1>
143
+ <p>
144
+ Resonant.js is a simple library that allows you to bind JavaScript variables to HTML elements.
145
+ This allows you to create dynamic web applications without the need for complex frameworks.
146
+ </p>
147
+ <h2>HTML properties</h2>
148
+ <table>
149
+ <tr>
150
+ <td>res</td>
151
+ <td>Bind an object to a section of HTML</td>
152
+ </tr>
153
+ <tr>
154
+ <td>res-prop</td>
155
+ <td>Bind an object property to an HTML element, used when the defined variable is an object array</td>
156
+ </tr>
157
+ <tr>
158
+ <td>res-display</td>
159
+ <td>Conditionally display an element based on a JavaScript expression</td>
160
+ </tr>
161
+ <tr>
162
+ <td>res-style, res-styles</td>
163
+ <td>Bind a style or styles object to an HTML element conditionally</td>
164
+ </tr>
165
+ <tr>
166
+ <td>res-onclick, res-onclick-remove</td>
167
+ <td>Bind an onclick event to an HTML element</td>
168
+ </tr>
169
+ </table>
170
+ <h2>Javascript Functions</h2>
171
+ <table>
172
+ <tr>
173
+ <td>const resonantJs = new Resonant();</td>
174
+ <td>Initialize the Resonant object</td>
175
+ </tr>
176
+ <tr>
177
+ <td>resonantJs.add("variableName", optionalBooleanForPersistence)</td>
178
+ <td>Bind a variable to the Resonant object</td>
179
+ </tr>
180
+ <tr>
181
+ <td>resonantJs.addAll()</td>
182
+ <td>Bind multiple variables to the Resonant object at once</td>
183
+ </tr>
184
+ <tr>
185
+ <td>resonantJs.addCallback("variableName", (objectReturned) => {})</td>
186
+ <td>Bind a callback function to a variable</td>
187
+ </tr>
188
+ </table>
82
189
  </section>
83
190
  </main>
84
191
 
@@ -92,8 +199,7 @@
92
199
  resonantJs.addAll({
93
200
  user: {
94
201
  firstname: "John",
95
- lastname: "Doe",
96
- email: ""
202
+ lastname: "Doe"
97
203
  },
98
204
  projectTeam: [
99
205
  { name: "Alice", role: "Developer" },
@@ -116,6 +222,12 @@
116
222
  const newMember = { name: "Charlie", role: "Product Manager" };
117
223
  projectTeam.push(newMember);
118
224
  }
225
+
226
+ //Silly function to demonstrate dynamic list rerendering
227
+ function toggleProjectMemberName() {
228
+ const alice = projectTeam[0];
229
+ alice.name = alice.name === "Alice" ? "Alicia" : "Alice";
230
+ }
119
231
  </script>
120
232
  </body>
121
233
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resonantjs",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
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,6 +1,6 @@
1
1
  class ObservableArray extends Array {
2
2
  constructor(variableName, resonantInstance, ...args) {
3
- if(resonantInstance === undefined) {
3
+ if (resonantInstance === undefined) {
4
4
  return super(...args);
5
5
  }
6
6
  super(...args);
@@ -35,7 +35,6 @@ class ObservableArray extends Array {
35
35
  });
36
36
  }
37
37
 
38
- //temp fix for issues
39
38
  forceUpdate() {
40
39
  this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
41
40
  this.resonantInstance._queueUpdate(this.variableName, 'modified', this.slice());
@@ -102,9 +101,9 @@ class ObservableArray extends Array {
102
101
  }
103
102
 
104
103
  this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
105
-
106
104
  const oldValue = this[index];
107
105
  this[index] = value;
106
+
108
107
  this.resonantInstance._queueUpdate(this.variableName, action, this[index], index, oldValue);
109
108
  }
110
109
  return true;
@@ -114,9 +113,7 @@ class ObservableArray extends Array {
114
113
  const oldValue = this[index];
115
114
  this.isDeleting = true;
116
115
  this.splice(index, 1);
117
-
118
116
  this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
119
-
120
117
  this.resonantInstance._queueUpdate(this.variableName, 'removed', null, index, oldValue);
121
118
  this.isDeleting = false;
122
119
  return true;
@@ -126,7 +123,6 @@ class ObservableArray extends Array {
126
123
  if(this.resonantInstance === undefined || actuallyFilter === false) {
127
124
  return super.filter(filter);
128
125
  }
129
-
130
126
  const result = super.filter(filter);
131
127
  this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
132
128
  this.resonantInstance._queueUpdate(this.variableName, 'filtered');
@@ -142,12 +138,28 @@ class Resonant {
142
138
  this.arrayDataChangeDetection = {};
143
139
  }
144
140
 
141
+ _handleInputElement(element, value, onChangeCallback) {
142
+ if (element.type === 'checkbox') {
143
+ element.checked = value;
144
+ if (!element.hasAttribute('data-resonant-bound')) {
145
+ element.onchange = () => onChangeCallback(element.checked);
146
+ element.setAttribute('data-resonant-bound', 'true');
147
+ }
148
+ } else {
149
+ element.value = value;
150
+ if (!element.hasAttribute('data-resonant-bound')) {
151
+ element.oninput = () => onChangeCallback(element.value);
152
+ element.setAttribute('data-resonant-bound', 'true');
153
+ }
154
+ }
155
+ }
156
+
145
157
  add(variableName, value, persist) {
146
158
  value = this.persist(variableName, value, persist);
147
159
  if (Array.isArray(value)) {
148
160
  this.data[variableName] = new ObservableArray(variableName, this, ...value);
149
161
  this.arrayDataChangeDetection[variableName] = this.data[variableName].slice();
150
- } else if (typeof value === 'object') {
162
+ } else if (typeof value === 'object' && value !== null) {
151
163
  this.data[variableName] = this._createObject(variableName, value);
152
164
  } else {
153
165
  this.data[variableName] = value;
@@ -162,8 +174,8 @@ class Resonant {
162
174
  return value;
163
175
  }
164
176
  var found = localStorage.getItem('res_' + variableName);
165
- if (found !== null && found !== undefined){
166
- return JSON.parse(localStorage.getItem('res_' + variableName));
177
+ if (found !== null && found !== undefined) {
178
+ return JSON.parse(found);
167
179
  } else {
168
180
  localStorage.setItem('res_' + variableName, JSON.stringify(value));
169
181
  return value;
@@ -182,6 +194,10 @@ class Resonant {
182
194
  });
183
195
  }
184
196
 
197
+ _resolveValue(instance, key, override = null) {
198
+ return override ?? instance[key];
199
+ }
200
+
185
201
  _createObject(variableName, obj) {
186
202
  obj[Symbol('isProxy')] = true;
187
203
  return new Proxy(obj, {
@@ -196,15 +212,56 @@ class Resonant {
196
212
  });
197
213
  }
198
214
 
215
+ _evaluateDisplayCondition(element, item, condition) {
216
+ try {
217
+ const show = new Function('item', `return ${condition}`)(item);
218
+ element.style.display = show ? '' : 'none';
219
+ } catch (e) {
220
+ console.error(`Error evaluating display condition: ${condition}`, e);
221
+ }
222
+ }
223
+
224
+ _handleDisplayElements(parentElement, instance) {
225
+ const displayElements = parentElement.querySelectorAll('[res-display]');
226
+ displayElements.forEach(displayEl => {
227
+ const condition = displayEl.getAttribute('res-display') || '';
228
+ this._evaluateDisplayCondition(displayEl, instance, condition);
229
+ });
230
+ }
231
+
232
+ _bindClickEvents(parentElement, instance, arrayValue) {
233
+ const onclickElements = parentElement.querySelectorAll('[res-onclick], [res-onclick-remove]');
234
+ onclickElements.forEach(onclickEl => {
235
+ const functionName = onclickEl.getAttribute('res-onclick');
236
+ const removeKey = onclickEl.getAttribute('res-onclick-remove');
237
+
238
+ if (functionName) {
239
+ onclickEl.onclick = () => {
240
+ const func = new Function('item', `return ${functionName}(item)`);
241
+ func(instance);
242
+ };
243
+ }
244
+ if (removeKey) {
245
+ onclickEl.onclick = () => {
246
+ if (arrayValue) {
247
+ const removeIdx = arrayValue.findIndex(t => t[removeKey] === instance[removeKey]);
248
+ if (removeIdx !== -1) {
249
+ arrayValue.splice(removeIdx, 1);
250
+ }
251
+ }
252
+ };
253
+ }
254
+ });
255
+ }
256
+
199
257
  _defineProperty(variableName) {
200
258
  Object.defineProperty(window, variableName, {
201
259
  get: () => this.data[variableName],
202
260
  set: (newValue) => {
203
261
  if (Array.isArray(newValue)) {
204
262
  this.data[variableName] = new ObservableArray(variableName, this, ...newValue);
205
- this.arrayDataChangeDetection[variableName] = this.data[variableName].slice(); // Create a copy for change detection
206
-
207
- } else if (typeof newValue === 'object') {
263
+ this.arrayDataChangeDetection[variableName] = this.data[variableName].slice();
264
+ } else if (typeof newValue === 'object' && newValue !== null) {
208
265
  this.data[variableName] = this._createObject(variableName, newValue);
209
266
  } else {
210
267
  this.data[variableName] = newValue;
@@ -212,7 +269,8 @@ class Resonant {
212
269
  this.updateElement(variableName);
213
270
  this.updateDisplayConditionalsFor(variableName);
214
271
  this.updateStylesFor(variableName);
215
- if (!Array.isArray(newValue) && typeof newValue !== 'object') {
272
+
273
+ if (!Array.isArray(newValue) && (typeof newValue !== 'object' || newValue === null)) {
216
274
  this._queueUpdate(variableName, 'modified', this.data[variableName]);
217
275
  }
218
276
  }
@@ -223,7 +281,6 @@ class Resonant {
223
281
  if (!this.pendingUpdates.has(variableName)) {
224
282
  this.pendingUpdates.set(variableName, []);
225
283
  }
226
-
227
284
  this.pendingUpdates.get(variableName).push({ action, item, property, oldValue });
228
285
 
229
286
  if (this.pendingUpdates.get(variableName).length === 1) {
@@ -232,16 +289,19 @@ class Resonant {
232
289
  this.updatePersistantData(variableName);
233
290
  this.pendingUpdates.delete(variableName);
234
291
 
235
- updates = updates.filter((v, i, a) => a.findIndex(t => (t.property === v.property && t.action === v.action)) === i);
292
+ updates = updates.filter((v, i, a) =>
293
+ a.findIndex(t => (t.property === v.property && t.action === v.action)) === i
294
+ );
295
+
236
296
  updates.forEach(update => {
237
297
  this._triggerCallbacks(variableName, update);
238
298
  });
299
+
239
300
  this.updateElement(variableName);
240
301
  this.updateDisplayConditionalsFor(variableName);
241
302
  this.updateStylesFor(variableName);
242
303
  }, 0);
243
304
  }
244
-
245
305
  }
246
306
 
247
307
  _triggerCallbacks(variableName, callbackData) {
@@ -258,55 +318,27 @@ class Resonant {
258
318
  const value = this.data[variableName];
259
319
 
260
320
  elements.forEach(element => {
261
- element.value = value;
262
321
  if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
263
- if (!element.hasAttribute('data-resonant-bound')) {
264
- element.oninput = () => {
265
- this.data[variableName] = element.value;
266
- this._queueUpdate(variableName, 'modified', this.data[variableName]);
267
- };
268
- element.setAttribute('data-resonant-bound', 'true');
269
- }
270
- } else if (Array.isArray(value)) {
271
- element.querySelectorAll(`[res="${variableName}"][res-rendered=true]`).forEach(el => el.remove());
322
+ this._handleInputElement(element, value, (newValue) => {
323
+ this.data[variableName] = newValue;
324
+ this._queueUpdate(variableName, 'modified', this.data[variableName]);
325
+ });
326
+ }
327
+ else if (Array.isArray(value)) {
328
+ element.querySelectorAll(`[res="${variableName}"][res-rendered="true"]`).forEach(el => el.remove());
272
329
  this._renderArray(variableName, element);
273
- } else if (typeof value === 'object') {
274
- const subElements = element.querySelectorAll(`[res-prop]`);
330
+ }
331
+ else if (typeof value === 'object' && value !== null) {
332
+ const subElements = element.querySelectorAll('[res-prop]');
275
333
  subElements.forEach(subEl => {
276
334
  const key = subEl.getAttribute('res-prop');
277
335
  if (key && key in value) {
278
- if (!subEl.hasAttribute('data-resonant-bound')) {
279
- if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
280
- if (subEl.type === 'checkbox') {
281
- subEl.checked = value[key];
282
- subEl.onchange = () => {
283
- this.data[variableName][key] = subEl.checked;
284
- };
285
- } else {
286
- subEl.value = value[key];
287
- subEl.oninput = () => {
288
- this.data[variableName][key] = subEl.value;
289
- };
290
- }
291
- } else {
292
- subEl.innerHTML = value[key];
293
- }
294
- subEl.setAttribute('data-resonant-bound', 'true');
295
- } else {
296
- if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
297
- if (subEl.type === 'checkbox') {
298
- subEl.checked = value[key];
299
- } else {
300
- subEl.value = value[key];
301
- }
302
- } else {
303
- subEl.innerHTML = value[key];
304
- }
305
- }
336
+ this._renderObjectProperty(subEl, value[key], variableName, key);
306
337
  }
307
338
  });
308
- } else {
309
- element.innerHTML = value;
339
+ }
340
+ else {
341
+ element.innerHTML = value ?? '';
310
342
  }
311
343
  });
312
344
 
@@ -314,25 +346,128 @@ class Resonant {
314
346
  this.updateStylesFor(variableName);
315
347
  }
316
348
 
349
+ _renderObjectProperty(subEl, propValue, parentVarName, key) {
350
+ if ((subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') &&
351
+ !Array.isArray(propValue) &&
352
+ typeof propValue !== 'object') {
353
+ this._handleInputElement(subEl, propValue, (newValue) => {
354
+ window[parentVarName][key] = newValue;
355
+ });
356
+ }
357
+ else if (Array.isArray(propValue)) {
358
+ this._renderNestedArray(subEl, propValue);
359
+ }
360
+ else if (typeof propValue === 'object' && propValue !== null) {
361
+ const nestedElements = subEl.querySelectorAll('[res-prop]');
362
+ nestedElements.forEach(nestedEl => {
363
+ const nestedKey = nestedEl.getAttribute('res-prop');
364
+ if (nestedKey && nestedKey in propValue) {
365
+ this._renderObjectProperty(nestedEl, propValue[nestedKey], parentVarName, nestedKey);
366
+ }
367
+ });
368
+ }
369
+ else {
370
+ subEl.innerHTML = propValue ?? '';
371
+ }
372
+ }
373
+
374
+ _renderArray(variableName, el) {
375
+ let template = el.cloneNode(true);
376
+ el.innerHTML = '';
377
+
378
+ if (!window[variableName + "_template"]) {
379
+ window[variableName + "_template"] = template;
380
+ } else {
381
+ template = window[variableName + "_template"];
382
+ }
383
+
384
+ 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);
412
+ }
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 ?? '';
424
+ }
425
+ }
426
+ }
427
+
428
+ this._handleDisplayElements(clonedEl, instance);
429
+ this._bindClickEvents(clonedEl, instance, this.data[variableName]);
430
+
431
+ clonedEl.setAttribute("res-rendered", "true");
432
+ el.appendChild(clonedEl);
433
+ });
434
+ }
435
+
436
+ _renderNestedArray(subEl, arrayValue) {
437
+ const template = subEl.cloneNode(true);
438
+ subEl.innerHTML = '';
439
+
440
+ subEl.removeAttribute('res-prop');
441
+
442
+ arrayValue.forEach((item, idx) => {
443
+ const cloned = template.cloneNode(true);
444
+ cloned.setAttribute('res-rendered', 'true');
445
+
446
+ const nestedEls = cloned.querySelectorAll('[res-prop]');
447
+ nestedEls.forEach(nestedEl => {
448
+ const nestedKey = nestedEl.getAttribute('res-prop');
449
+ if (nestedKey && nestedKey in item) {
450
+ this._renderObjectProperty(nestedEl, item[nestedKey], null, nestedKey);
451
+ }
452
+ });
453
+
454
+ this._handleDisplayElements(cloned, item);
455
+ this._bindClickEvents(cloned, item, arrayValue);
456
+
457
+ subEl.appendChild(cloned);
458
+ });
459
+ }
460
+
317
461
  updateDisplayConditionalsFor(variableName) {
318
462
  const conditionalElements = document.querySelectorAll(`[res-display*="${variableName}"]`);
319
463
  conditionalElements.forEach(conditionalElement => {
320
464
  const condition = conditionalElement.getAttribute('res-display');
321
- try {
322
- if (eval(condition)) {
323
- conditionalElement.style.display = '';
324
- } else {
325
- conditionalElement.style.display = 'none';
326
- }
327
- } catch (e) {
328
- console.error(`Error evaluating condition for ${variableName}: ${condition}`, e);
329
- }
465
+ this._evaluateDisplayCondition(conditionalElement, this.data[variableName], condition);
330
466
  });
331
467
  }
332
468
 
333
469
  updateStylesFor(variableName) {
334
470
  const styleElements = document.querySelectorAll(`[res-style*="${variableName}"]`);
335
-
336
471
  styleElements.forEach(styleElement => {
337
472
  let styleCondition = styleElement.getAttribute('res-style');
338
473
  try {
@@ -355,23 +490,21 @@ class Resonant {
355
490
  const item = this.data[variableName][index];
356
491
  styleCondition = styleCondition.replace(new RegExp(`\\b${variableName}\\b`, 'g'), 'item');
357
492
  const styleClass = new Function('item', `return ${styleCondition}`)(item);
358
-
359
493
  if (styleClass) {
360
494
  styleElement.classList.add(styleClass);
361
495
  } else {
362
- var elementHasStyle = styleElement.classList.contains(styleClass);
496
+ const elementHasStyle = styleElement.classList.contains(styleClass);
363
497
  if (elementHasStyle) {
364
498
  styleElement.classList.remove(styleClass);
365
499
  }
366
500
  }
367
501
  } else {
368
502
  const styleClass = eval(styleCondition);
369
-
370
503
  if (styleClass) {
371
504
  styleElement.classList.add(styleClass);
372
505
  styleElement.setAttribute('res-styles', styleClass);
373
506
  } else {
374
- var elementHasStyle = styleElement.classList.contains(styleClass);
507
+ const elementHasStyle = styleElement.classList.contains(styleClass);
375
508
  if (elementHasStyle) {
376
509
  styleElement.classList.remove(styleClass);
377
510
  }
@@ -383,81 +516,6 @@ class Resonant {
383
516
  });
384
517
  }
385
518
 
386
- _renderArray(variableName, el) {
387
- let template = el.cloneNode(true);
388
- el.innerHTML = '';
389
-
390
- if (!window[variableName + "_template"]) {
391
- window[variableName + "_template"] = template;
392
- } else {
393
- template = window[variableName + "_template"];
394
- }
395
-
396
- this.data[variableName].forEach((instance, index) => {
397
- const clonedEl = template.cloneNode(true);
398
- clonedEl.setAttribute("res-index", index);
399
- for (let key in instance) {
400
- const subEl = clonedEl.querySelector(`[res-prop="${key}"]`);
401
- if (subEl) {
402
- if (!subEl.hasAttribute('data-resonant-bound')) {
403
- if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
404
- if (subEl.type === 'checkbox') {
405
- subEl.checked = instance[key];
406
- subEl.onchange = () => {
407
- instance[key] = subEl.checked;
408
- this._queueUpdate(variableName, 'modified', instance, key, instance[key]);
409
- };
410
- } else {
411
- subEl.value = instance[key];
412
- subEl.oninput = () => {
413
- instance[key] = subEl.value;
414
- this._queueUpdate(variableName, 'modified', instance, key, instance[key]);
415
- };
416
- }
417
- } else {
418
- subEl.innerHTML = instance[key];
419
- }
420
- subEl.setAttribute('data-resonant-bound', 'true');
421
- } else {
422
- if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
423
- if (subEl.type === 'checkbox') {
424
- subEl.checked = instance[key];
425
- } else {
426
- subEl.value = instance[key];
427
- }
428
- } else {
429
- subEl.innerHTML = instance[key];
430
- }
431
- }
432
- }
433
- }
434
-
435
- const onclickElements = clonedEl.querySelectorAll('[res-onclick], [res-onclick-remove]');
436
- onclickElements.forEach(onclickEl => {
437
- const functionName = onclickEl.getAttribute('res-onclick');
438
- const removeKey = onclickEl.getAttribute('res-onclick-remove');
439
- if (functionName) {
440
- onclickEl.onclick = () => {
441
- const func = new Function('item', `return ${functionName}(item)`);
442
- func(instance);
443
- };
444
- }
445
-
446
- if (removeKey) {
447
- onclickEl.onclick = () => {
448
- const index = this.data[variableName].findIndex(t => t[removeKey] === instance[removeKey]);
449
- if (index !== -1) {
450
- this.data[variableName].splice(index, 1);
451
- }
452
- };
453
- }
454
- });
455
-
456
- clonedEl.setAttribute("res-rendered", true);
457
- el.appendChild(clonedEl);
458
- });
459
- }
460
-
461
519
  addCallback(variableName, method) {
462
520
  if (!this.callbacks[variableName]) {
463
521
  this.callbacks[variableName] = [];
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){const i=e[a];e[a]=s,this.resonantInstance._queueUpdate(this.variableName,"modified",e,a,i,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));const 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));const 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;const a=this.resonantInstance.arrayDataChangeDetection[this.variableName];let s="modified";(e>=a.length||void 0===a[e])&&(s="added"),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice();const i=this[e];this[e]=t,this.resonantInstance._queueUpdate(this.variableName,s,this[e],e,i)}return!0}delete(e){const 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);const 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={}}add(e,t,a){t=this.persist(e,t,a),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)}persist(e,t,a){if(void 0===a||!a)return t;var s=localStorage.getItem("res_"+e);return null!=s?JSON.parse(localStorage.getItem("res_"+e)):(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)}))}_createObject(e,t){return t[Symbol("isProxy")]=!0,new Proxy(t,{set:(t,a,s)=>{if(t[a]!==s){const i=t[a];t[a]=s,this._queueUpdate(e,"modified",t,a,i)}return!0}})}_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()):this.data[e]="object"==typeof t?this._createObject(e,t):t,this.updateElement(e),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e),Array.isArray(t)||"object"==typeof t||this._queueUpdate(e,"modified",this.data[e])}})}_queueUpdate(e,t,a,s,i){this.pendingUpdates.has(e)||this.pendingUpdates.set(e,[]),this.pendingUpdates.get(e).push({action:t,item:a,property:s,oldValue:i}),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)),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=>{const s=t.item||t.oldValue;a(this.data[e],s,t.action)}))}updateElement(e){const t=document.querySelectorAll(`[res="${e}"]`),a=this.data[e];t.forEach((t=>{if(t.value=a,"INPUT"===t.tagName||"TEXTAREA"===t.tagName)t.hasAttribute("data-resonant-bound")||(t.oninput=()=>{this.data[e]=t.value,this._queueUpdate(e,"modified",this.data[e])},t.setAttribute("data-resonant-bound","true"));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){t.querySelectorAll("[res-prop]").forEach((t=>{const s=t.getAttribute("res-prop");s&&s in a&&(t.hasAttribute("data-resonant-bound")?"INPUT"===t.tagName||"TEXTAREA"===t.tagName?"checkbox"===t.type?t.checked=a[s]:t.value=a[s]:t.innerHTML=a[s]:("INPUT"===t.tagName||"TEXTAREA"===t.tagName?"checkbox"===t.type?(t.checked=a[s],t.onchange=()=>{this.data[e][s]=t.checked}):(t.value=a[s],t.oninput=()=>{this.data[e][s]=t.value}):t.innerHTML=a[s],t.setAttribute("data-resonant-bound","true")))}))}else t.innerHTML=a})),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e)}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){console.error(`Error evaluating condition for ${variableName}: ${condition}`,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;let resStyles=styleElement.getAttribute("res-styles");if(resStyles){let e=resStyles.split(" ");e.forEach((e=>{styleElement.classList.remove(e)}))}if(null!==index){const e=this.data[variableName][index];styleCondition=styleCondition.replace(new RegExp(`\\b${variableName}\\b`,"g"),"item");const t=new Function("item",`return ${styleCondition}`)(e);if(t)styleElement.classList.add(t);else{var elementHasStyle=styleElement.classList.contains(t);elementHasStyle&&styleElement.classList.remove(t)}}else{const styleClass=eval(styleCondition);if(styleClass)styleElement.classList.add(styleClass),styleElement.setAttribute("res-styles",styleClass);else{var elementHasStyle=styleElement.classList.contains(styleClass);elementHasStyle&&styleElement.classList.remove(styleClass)}}}catch(e){console.error(`Error evaluating style for ${variableName}: ${styleCondition}`,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,i)=>{const n=a.cloneNode(!0);n.setAttribute("res-index",i);for(let t in s){const a=n.querySelector(`[res-prop="${t}"]`);a&&(a.hasAttribute("data-resonant-bound")?"INPUT"===a.tagName||"TEXTAREA"===a.tagName?"checkbox"===a.type?a.checked=s[t]:a.value=s[t]:a.innerHTML=s[t]:("INPUT"===a.tagName||"TEXTAREA"===a.tagName?"checkbox"===a.type?(a.checked=s[t],a.onchange=()=>{s[t]=a.checked,this._queueUpdate(e,"modified",s,t,s[t])}):(a.value=s[t],a.oninput=()=>{s[t]=a.value,this._queueUpdate(e,"modified",s,t,s[t])}):a.innerHTML=s[t],a.setAttribute("data-resonant-bound","true")))}n.querySelectorAll("[res-onclick], [res-onclick-remove]").forEach((t=>{const a=t.getAttribute("res-onclick"),i=t.getAttribute("res-onclick-remove");a&&(t.onclick=()=>{new Function("item",`return ${a}(item)`)(s)}),i&&(t.onclick=()=>{const t=this.data[e].findIndex((e=>e[i]===s[i]));-1!==t&&this.data[e].splice(t,1)})})),n.setAttribute("res-rendered",!0),t.appendChild(n)}))}addCallback(e,t){this.callbacks[e]||(this.callbacks[e]=[]),this.callbacks[e].push(t)}}
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)}}