resonantjs 1.1.5 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -10
- package/aiagent.md +231 -0
- package/examples/example-basic.html +57 -0
- package/package.json +1 -1
- package/resonant.js +927 -577
- package/resonant.min.js +2 -1
- package/resonant.min.js.map +1 -0
- package/scripts/build.js +57 -4
- package/test/computed_properties.test.js +111 -0
- package/test/mockDom.js +118 -6
- package/test/selective_rendering.test.js +370 -0
- package/test/style_and_events.test.js +93 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
**ResonantJs** is a lightweight, powerful JavaScript framework that brings **reactive data-binding** to vanilla JavaScript applications. Build dynamic, responsive UIs with minimal code and zero dependencies.
|
|
7
7
|
|
|
8
|
-
> **Zero dependencies • ~
|
|
8
|
+
> **Zero dependencies • ~11.5KB minified • Lightning fast • Easy to learn**
|
|
9
9
|
|
|
10
10
|
## ✨ Why Choose ResonantJs?
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- 💾 **Built-in Persistence**: Automatic localStorage integration for data persistence
|
|
16
16
|
- 🎨 **Dynamic Styling**: Conditional CSS classes and styles based on your data
|
|
17
17
|
- ⚡ **Performance**: Efficient updates with minimal overhead
|
|
18
|
-
- 📦 **Tiny Footprint**: Under
|
|
18
|
+
- 📦 **Tiny Footprint**: Under 11.5KB minified - perfect for any project size
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -116,7 +116,25 @@ Bind click events with context:
|
|
|
116
116
|
<button res-onclick-remove="true">Delete Item</button>
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
### 7. **
|
|
119
|
+
### 7. **Computed Properties**
|
|
120
|
+
Create reactive derived values that automatically update:
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const resonant = new Resonant();
|
|
124
|
+
resonant.add('firstName', 'John');
|
|
125
|
+
resonant.add('lastName', 'Doe');
|
|
126
|
+
|
|
127
|
+
// Computed property automatically updates when dependencies change
|
|
128
|
+
resonant.computed('fullName', () => {
|
|
129
|
+
return firstName + ' ' + lastName;
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<span res="fullName"></span> <!-- Automatically shows "John Doe" -->
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 8. **Input Binding**
|
|
120
138
|
Two-way data binding for form elements:
|
|
121
139
|
|
|
122
140
|
```html
|
|
@@ -181,6 +199,27 @@ resonant.add('appState', { currentView: 'dashboard' }, true);
|
|
|
181
199
|
userPreferences.theme = 'light'; // Saved to localStorage
|
|
182
200
|
```
|
|
183
201
|
|
|
202
|
+
### **Computed Properties**
|
|
203
|
+
Reactive derived values that automatically recalculate:
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
resonant.add('firstName', 'John');
|
|
207
|
+
resonant.add('lastName', 'Doe');
|
|
208
|
+
|
|
209
|
+
// Automatically updates when firstName or lastName changes
|
|
210
|
+
resonant.computed('fullName', () => {
|
|
211
|
+
return firstName + ' ' + lastName;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Cannot be set directly - read-only
|
|
215
|
+
// fullName = 'Something'; // Will log warning and be ignored
|
|
216
|
+
|
|
217
|
+
// Chain computed properties
|
|
218
|
+
resonant.computed('greeting', () => {
|
|
219
|
+
return 'Hello, ' + fullName + '!';
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
184
223
|
### **Advanced Array Operations**
|
|
185
224
|
Full array reactivity with custom methods:
|
|
186
225
|
|
|
@@ -324,16 +363,54 @@ company.departments[0].teams[0].members.push(newMember);
|
|
|
324
363
|
```
|
|
325
364
|
|
|
326
365
|
### **Computed Properties**
|
|
327
|
-
Create reactive calculated values:
|
|
366
|
+
Create reactive calculated values that automatically update when dependencies change:
|
|
328
367
|
|
|
329
368
|
```javascript
|
|
330
|
-
resonant
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
369
|
+
const resonant = new Resonant();
|
|
370
|
+
resonant.add('tasks', [
|
|
371
|
+
{ title: 'Task 1', completed: true },
|
|
372
|
+
{ title: 'Task 2', completed: false },
|
|
373
|
+
{ title: 'Task 3', completed: true }
|
|
374
|
+
]);
|
|
375
|
+
|
|
376
|
+
// Computed properties automatically recalculate when 'tasks' changes
|
|
377
|
+
resonant.computed('totalTasks', () => {
|
|
378
|
+
return tasks.length;
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
resonant.computed('completedTasks', () => {
|
|
382
|
+
return tasks.filter(t => t.completed).length;
|
|
336
383
|
});
|
|
384
|
+
|
|
385
|
+
resonant.computed('completionRate', () => {
|
|
386
|
+
return totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Shopping cart example
|
|
390
|
+
resonant.add('items', [
|
|
391
|
+
{ name: 'Widget', price: 10, quantity: 2 },
|
|
392
|
+
{ name: 'Gadget', price: 15, quantity: 1 }
|
|
393
|
+
]);
|
|
394
|
+
resonant.add('taxRate', 0.08);
|
|
395
|
+
|
|
396
|
+
resonant.computed('subtotal', () => {
|
|
397
|
+
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
resonant.computed('tax', () => {
|
|
401
|
+
return subtotal * taxRate;
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
resonant.computed('total', () => {
|
|
405
|
+
return subtotal + tax;
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
```html
|
|
410
|
+
<!-- These automatically update when items change -->
|
|
411
|
+
<div>Subtotal: $<span res="subtotal"></span></div>
|
|
412
|
+
<div>Tax: $<span res="tax"></span></div>
|
|
413
|
+
<div>Total: $<span res="total"></span></div>
|
|
337
414
|
```
|
|
338
415
|
|
|
339
416
|
### **Component-Like Patterns**
|
package/aiagent.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Using ResonantJs with AI Agents
|
|
2
|
+
|
|
3
|
+
ResonantJs is a lightweight framework that adds reactive data-binding to vanilla JavaScript applications. The library can be leveraged by AI agents when generating or manipulating front-end code. Below is a high-level overview of its features and how an agent might use them.
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
- **Data Binding (`res`)** – Attach a JavaScript variable to an element so that UI updates whenever the value changes.
|
|
8
|
+
- **Object Properties (`res-prop`)** – Bind specific properties of an object to nested elements.
|
|
9
|
+
- **Array Rendering** – Automatically generate a list from an array of data using a template element.
|
|
10
|
+
- **Conditional Display (`res-display`)** – Show or hide elements based on expressions.
|
|
11
|
+
- **Dynamic Styling (`res-style`)** – Conditionally apply CSS classes or inline styles.
|
|
12
|
+
- **Event Handling (`res-onclick`)** – Link element events to functions with access to bound data.
|
|
13
|
+
- **Input Binding** – Two-way binding for input elements such as text fields, checkboxes, or select menus.
|
|
14
|
+
|
|
15
|
+
## Key Features
|
|
16
|
+
|
|
17
|
+
- **Reactive Data Management** – Initialize variables with `add` or `addAll`. Any update to these values immediately reflects in the DOM.
|
|
18
|
+
- **Event Callbacks** – Register callbacks via `addCallback` to react to data changes and chain side effects.
|
|
19
|
+
- **Persistence** – Optionally persist values to `localStorage` so state survives page reloads.
|
|
20
|
+
- **Advanced Array Operations** – Observable arrays expose methods like `update`, `set`, and `delete` to trigger UI refreshes when items change.
|
|
21
|
+
- **Child Object Support** – Nested objects (e.g. `user.profile.email`) remain reactive through automatic proxy wrapping.
|
|
22
|
+
- **Computed Properties** – Define reactive derived values using `computed()` that automatically update when their dependencies change.
|
|
23
|
+
- **Component Patterns** – Encapsulate related variables and callbacks in functions for reuse across the page.
|
|
24
|
+
|
|
25
|
+
## Example Workflow for an AI Agent
|
|
26
|
+
|
|
27
|
+
1. **Initialize** a new `Resonant` instance when generating the page.
|
|
28
|
+
2. **Add Variables** using `add` or `addAll` for any data that needs to be reactive. Enable persistence if needed.
|
|
29
|
+
3. **Define Computed Properties** using `computed()` for derived values that should automatically update when dependencies change.
|
|
30
|
+
4. **Bind Elements** in generated HTML using `res`, `res-prop`, and related attributes so DOM updates automatically.
|
|
31
|
+
5. **Attach Callbacks** with `addCallback` when additional logic is required after data changes. This can be used to interact with other services.
|
|
32
|
+
6. **Manipulate Data** directly in generated scripts. Changes propagate to the interface with no manual DOM manipulation.
|
|
33
|
+
|
|
34
|
+
## When to Use
|
|
35
|
+
|
|
36
|
+
- Building dynamic UI components without importing a heavy framework.
|
|
37
|
+
- Keeping local state in sync with DOM elements in generated pages.
|
|
38
|
+
- Rapidly prototyping interactive features that persist across sessions using localStorage.
|
|
39
|
+
|
|
40
|
+
For more detailed examples, see the `examples` folder and the full README documentation in this repository.
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
### Simple Counter
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<h1>Counter: <span res="counter"></span></h1>
|
|
48
|
+
<button onclick="counter++">Increment</button>
|
|
49
|
+
|
|
50
|
+
<script>
|
|
51
|
+
const r = new Resonant();
|
|
52
|
+
r.add('counter', 0, true); // persisted value
|
|
53
|
+
</script>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Todo List
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<input res="newTodo" placeholder="Add task" />
|
|
60
|
+
<button onclick="addTodo()">Add</button>
|
|
61
|
+
|
|
62
|
+
<ul>
|
|
63
|
+
<li res="todos">
|
|
64
|
+
<input type="checkbox" res-prop="done" />
|
|
65
|
+
<span res-prop="title"></span>
|
|
66
|
+
</li>
|
|
67
|
+
</ul>
|
|
68
|
+
|
|
69
|
+
<script>
|
|
70
|
+
const r = new Resonant();
|
|
71
|
+
r.addAll({
|
|
72
|
+
todos: [{ title: 'Try Resonant', done: false }],
|
|
73
|
+
newTodo: ''
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function addTodo() {
|
|
77
|
+
if (newTodo.trim()) {
|
|
78
|
+
todos.push({ title: newTodo, done: false });
|
|
79
|
+
newTodo = '';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Callbacks
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<ul>
|
|
89
|
+
<li res="tasks">
|
|
90
|
+
<span res-prop="name"></span>
|
|
91
|
+
<button onclick="remove(item)">Remove</button>
|
|
92
|
+
</li>
|
|
93
|
+
</ul>
|
|
94
|
+
|
|
95
|
+
<script>
|
|
96
|
+
const r = new Resonant();
|
|
97
|
+
r.addAll({ tasks: [] });
|
|
98
|
+
r.addCallback('tasks', (tasks, task, action) => {
|
|
99
|
+
console.log(`Action: ${action}`, task);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
function remove(task) {
|
|
103
|
+
const idx = tasks.indexOf(task);
|
|
104
|
+
tasks.delete(idx);
|
|
105
|
+
}
|
|
106
|
+
</script>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Nested Child Objects
|
|
110
|
+
|
|
111
|
+
```html
|
|
112
|
+
<div res="company.departments">
|
|
113
|
+
<h3 res-prop="name"></h3>
|
|
114
|
+
<div res-prop="teams">
|
|
115
|
+
<span res-prop="name"></span>
|
|
116
|
+
<ul res-prop="members">
|
|
117
|
+
<li res-prop="name"></li>
|
|
118
|
+
</ul>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<script>
|
|
123
|
+
const r = new Resonant();
|
|
124
|
+
r.add('company', {
|
|
125
|
+
departments: [
|
|
126
|
+
{
|
|
127
|
+
name: 'Engineering',
|
|
128
|
+
teams: [
|
|
129
|
+
{ name: 'Frontend', members: [{ name: 'Alice' }] }
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
});
|
|
134
|
+
</script>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Computed Properties
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<div>
|
|
141
|
+
<input res="firstName" placeholder="First name" />
|
|
142
|
+
<input res="lastName" placeholder="Last name" />
|
|
143
|
+
<h2>Welcome, <span res="fullName"></span>!</h2>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<script>
|
|
147
|
+
const r = new Resonant();
|
|
148
|
+
r.addAll({
|
|
149
|
+
firstName: 'John',
|
|
150
|
+
lastName: 'Doe'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Computed property automatically updates when firstName or lastName changes
|
|
154
|
+
r.computed('fullName', () => {
|
|
155
|
+
return firstName + ' ' + lastName;
|
|
156
|
+
});
|
|
157
|
+
</script>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Shopping Cart with Computed Totals
|
|
161
|
+
|
|
162
|
+
```html
|
|
163
|
+
<div res="items">
|
|
164
|
+
<div>
|
|
165
|
+
<span res-prop="name"></span> -
|
|
166
|
+
$<span res-prop="price"></span> x
|
|
167
|
+
<span res-prop="quantity"></span>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
<div>
|
|
171
|
+
<strong>Subtotal: $<span res="subtotal"></span></strong><br>
|
|
172
|
+
<strong>Tax: $<span res="tax"></span></strong><br>
|
|
173
|
+
<strong>Total: $<span res="total"></span></strong>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<script>
|
|
177
|
+
const r = new Resonant();
|
|
178
|
+
r.addAll({
|
|
179
|
+
items: [
|
|
180
|
+
{ name: 'Widget', price: 10, quantity: 2 },
|
|
181
|
+
{ name: 'Gadget', price: 15, quantity: 1 }
|
|
182
|
+
],
|
|
183
|
+
taxRate: 0.08
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Computed properties automatically recalculate when items change
|
|
187
|
+
r.computed('subtotal', () => {
|
|
188
|
+
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
r.computed('tax', () => {
|
|
192
|
+
return subtotal * taxRate;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
r.computed('total', () => {
|
|
196
|
+
return subtotal + tax;
|
|
197
|
+
});
|
|
198
|
+
</script>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### User Profile with Computed Display
|
|
202
|
+
|
|
203
|
+
```html
|
|
204
|
+
<div>
|
|
205
|
+
<input res="user.age" type="number" placeholder="Age" />
|
|
206
|
+
<input res="user.memberSince" type="number" placeholder="Member since year" />
|
|
207
|
+
<div>Status: <span res="membershipStatus"></span></div>
|
|
208
|
+
<div>Category: <span res="ageCategory"></span></div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<script>
|
|
212
|
+
const r = new Resonant();
|
|
213
|
+
r.add('user', {
|
|
214
|
+
age: 25,
|
|
215
|
+
memberSince: 2020
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
r.computed('membershipStatus', () => {
|
|
219
|
+
const yearsAsMember = new Date().getFullYear() - user.memberSince;
|
|
220
|
+
if (yearsAsMember >= 5) return 'Gold Member';
|
|
221
|
+
if (yearsAsMember >= 2) return 'Silver Member';
|
|
222
|
+
return 'Bronze Member';
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
r.computed('ageCategory', () => {
|
|
226
|
+
if (user.age < 18) return 'Minor';
|
|
227
|
+
if (user.age < 65) return 'Adult';
|
|
228
|
+
return 'Senior';
|
|
229
|
+
});
|
|
230
|
+
</script>
|
|
231
|
+
```
|
|
@@ -98,6 +98,48 @@
|
|
|
98
98
|
</code>
|
|
99
99
|
</section>
|
|
100
100
|
|
|
101
|
+
<!-- Demonstrate computed properties -->
|
|
102
|
+
<section class="grid">
|
|
103
|
+
<div>
|
|
104
|
+
<h2>Computed Properties</h2>
|
|
105
|
+
<p>Full Name: <strong res="fullName"></strong></p>
|
|
106
|
+
<p>Greeting: <em res="greeting"></em></p>
|
|
107
|
+
<p>Initials: <span res="initials"></span></p>
|
|
108
|
+
<div res-display="fullName.length > 10" class="conditional">
|
|
109
|
+
Full name is longer than 10 characters
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<div>
|
|
113
|
+
<p style="color: #666; font-size: 0.9em;">
|
|
114
|
+
These values automatically update when you change the first or last name above.
|
|
115
|
+
Computed properties cannot be edited directly.
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
</section>
|
|
119
|
+
|
|
120
|
+
<section class="grid">
|
|
121
|
+
<code class="code">
|
|
122
|
+
// Computed properties automatically update<br/>
|
|
123
|
+
resonantJs.computed('fullName', () => {<br/>
|
|
124
|
+
return user.firstname + ' ' + user.lastname;<br/>
|
|
125
|
+
});<br/><br/>
|
|
126
|
+
|
|
127
|
+
resonantJs.computed('greeting', () => {<br/>
|
|
128
|
+
return 'Hello, ' + fullName + '!';<br/>
|
|
129
|
+
});<br/><br/>
|
|
130
|
+
|
|
131
|
+
resonantJs.computed('initials', () => {<br/>
|
|
132
|
+
return (user.firstname[0] || '') + (user.lastname[0] || '');<br/>
|
|
133
|
+
});
|
|
134
|
+
</code>
|
|
135
|
+
<code class="code">
|
|
136
|
+
<p>Full Name: <strong res="fullName"></strong></p><br/>
|
|
137
|
+
<p>Greeting: <em res="greeting"></em></p><br/>
|
|
138
|
+
<p>Initials: <span res="initials"></span></p><br/>
|
|
139
|
+
<div res-display="fullName.length > 10">...</div>
|
|
140
|
+
</code>
|
|
141
|
+
</section>
|
|
142
|
+
|
|
101
143
|
<!-- Demonstrate dynamic list rendering -->
|
|
102
144
|
<section class="grid">
|
|
103
145
|
<div>
|
|
@@ -185,6 +227,10 @@
|
|
|
185
227
|
<td>resonantJs.addCallback("variableName", (objectReturned) => {})</td>
|
|
186
228
|
<td>Bind a callback function to a variable</td>
|
|
187
229
|
</tr>
|
|
230
|
+
<tr>
|
|
231
|
+
<td>resonantJs.computed("computedName", () => { return expression; })</td>
|
|
232
|
+
<td>Create a computed property that automatically updates when dependencies change</td>
|
|
233
|
+
</tr>
|
|
188
234
|
</table>
|
|
189
235
|
</section>
|
|
190
236
|
</main>
|
|
@@ -207,7 +253,18 @@
|
|
|
207
253
|
]
|
|
208
254
|
});
|
|
209
255
|
|
|
256
|
+
// Define computed properties that automatically update
|
|
257
|
+
resonantJs.computed('fullName', () => {
|
|
258
|
+
return user.firstname + ' ' + user.lastname;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
resonantJs.computed('greeting', () => {
|
|
262
|
+
return 'Hello, ' + fullName + '!';
|
|
263
|
+
});
|
|
210
264
|
|
|
265
|
+
resonantJs.computed('initials', () => {
|
|
266
|
+
return (user.firstname[0] || '') + (user.lastname[0] || '');
|
|
267
|
+
});
|
|
211
268
|
|
|
212
269
|
// Chain together callbacks
|
|
213
270
|
resonantJs.addCallback("user", (user) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resonantjs",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
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": {
|