resonantjs 1.1.7 โ 1.2.0
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 +181 -374
- package/aiagent.md +207 -165
- package/package.json +1 -1
- package/resonant.js +136 -74
- package/resonant.min.js +1 -1
- package/resonant.min.js.map +1 -1
- package/scripts/build.js +8 -1
- package/test/additional_features.test.js +2 -2
- package/test/array_methods.test.js +401 -0
- package/test/complex_scenarios.test.js +413 -0
- package/test/deep_nesting.test.js +2 -2
- package/test/display_selective_rendering.test.js +172 -0
- package/test/edge_cases.test.js +627 -0
- package/test/error_handling.test.js +304 -0
- package/test/existing_variable_binding.test.js +151 -0
- package/test/html_examples.test.js +1 -1
- package/test/input_binding_advanced.test.js +323 -0
- package/test/mockDom.js +7 -5
- package/test/resonant.test.js +1 -1
- package/test/selective_rendering.test.js +141 -1
- package/test/style_and_events.test.js +1 -1
- package/test/template.test.js +1 -1
- package/Demo.gif +0 -0
package/README.md
CHANGED
|
@@ -1,76 +1,70 @@
|
|
|
1
|
-
# ResonantJs
|
|
1
|
+
# ResonantJs
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/resonantjs)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
**
|
|
6
|
+
**Reactive data-binding for vanilla JavaScript. No build step. No virtual DOM. Just HTML attributes and plain objects.**
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
```html
|
|
9
|
+
<span res="count"></span>
|
|
10
|
+
<button onclick="count++">+1</button>
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
<script src="https://unpkg.com/resonantjs@latest/resonant.min.js"></script>
|
|
13
|
+
<script>
|
|
14
|
+
const res = new Resonant();
|
|
15
|
+
res.add('count', 0);
|
|
16
|
+
</script>
|
|
17
|
+
```
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
- ๐ฏ **Attribute-Based**: Use simple HTML attributes to create powerful data bindings
|
|
14
|
-
- ๐๏ธ **Deep Object Support**: Handles nested objects and arrays with full reactivity
|
|
15
|
-
- ๐พ **Built-in Persistence**: Automatic localStorage integration for data persistence
|
|
16
|
-
- ๐จ **Dynamic Styling**: Conditional CSS classes and styles based on your data
|
|
17
|
-
- โก **Performance**: Efficient updates with minimal overhead
|
|
18
|
-
- ๐ฆ **Tiny Footprint**: Under 11.5KB minified - perfect for any project size
|
|
19
|
+
Change `count` anywhere in your code and the DOM updates automatically.
|
|
19
20
|
|
|
20
21
|
---
|
|
21
22
|
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
### Installation
|
|
23
|
+
## Install
|
|
25
24
|
|
|
26
|
-
#### NPM
|
|
27
25
|
```bash
|
|
28
26
|
npm install resonantjs
|
|
29
27
|
```
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
Or drop in a script tag:
|
|
30
|
+
|
|
32
31
|
```html
|
|
33
|
-
<script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
|
|
32
|
+
<script src="https://unpkg.com/resonantjs@latest/resonant.min.js"></script>
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
~18 KB minified. Zero dependencies.
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<head>
|
|
42
|
-
<title>ResonantJs Demo</title>
|
|
43
|
-
<script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
|
|
44
|
-
</head>
|
|
45
|
-
<body>
|
|
46
|
-
<h1>Counter: <span res="counter"></span></h1>
|
|
47
|
-
<button onclick="counter++">Increment</button>
|
|
48
|
-
<button onclick="counter--">Decrement</button>
|
|
49
|
-
|
|
50
|
-
<script>
|
|
51
|
-
const resonant = new Resonant();
|
|
52
|
-
resonant.add("counter", 0, true); // value, localStorage persistence
|
|
53
|
-
</script>
|
|
54
|
-
</body>
|
|
55
|
-
</html>
|
|
56
|
-
```
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Why ResonantJs?
|
|
57
40
|
|
|
58
|
-
|
|
41
|
+
| | |
|
|
42
|
+
|---|---|
|
|
43
|
+
| **No build tools** | Works with a single `<script>` tag. Ship today. |
|
|
44
|
+
| **Familiar mental model** | Plain objects, plain arrays, plain HTML. No JSX, no templates, no compilation. |
|
|
45
|
+
| **Automatic DOM updates** | Change a value, the page updates. Arrays, nested objects, computed properties -- all reactive. |
|
|
46
|
+
| **Selective re-rendering** | Only the changed array item re-renders. Siblings stay untouched. |
|
|
47
|
+
| **Built-in persistence** | One flag to sync any variable to `localStorage`. |
|
|
48
|
+
| **Tiny footprint** | ~18 KB minified, zero dependencies. |
|
|
59
49
|
|
|
60
50
|
---
|
|
61
51
|
|
|
62
|
-
##
|
|
52
|
+
## Quick Tour
|
|
63
53
|
|
|
64
|
-
###
|
|
65
|
-
Bind HTML elements directly to your JavaScript variables:
|
|
54
|
+
### Bind a variable
|
|
66
55
|
|
|
67
56
|
```html
|
|
68
|
-
<span res="
|
|
69
|
-
<div res="user.profile.name"></div> <!-- Nested object property -->
|
|
57
|
+
<h1>Hello, <span res="name"></span></h1>
|
|
70
58
|
```
|
|
71
59
|
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
```js
|
|
61
|
+
const res = new Resonant();
|
|
62
|
+
res.add('name', 'World');
|
|
63
|
+
|
|
64
|
+
name = 'ResonantJs'; // DOM updates instantly
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Bind an object
|
|
74
68
|
|
|
75
69
|
```html
|
|
76
70
|
<div res="user">
|
|
@@ -79,433 +73,246 @@ Bind to specific properties within objects:
|
|
|
79
73
|
</div>
|
|
80
74
|
```
|
|
81
75
|
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
```js
|
|
77
|
+
res.add('user', { name: 'Alice', email: 'alice@example.com' });
|
|
78
|
+
|
|
79
|
+
user.name = 'Bob'; // only the name span updates
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Render an array
|
|
83
|
+
|
|
84
|
+
Place `res` on a template element inside a list container. ResonantJs clones it once per item.
|
|
84
85
|
|
|
85
86
|
```html
|
|
86
87
|
<ul>
|
|
87
|
-
<li res="
|
|
88
|
+
<li res="tasks">
|
|
88
89
|
<span res-prop="title"></span>
|
|
89
|
-
<button res-onclick="
|
|
90
|
+
<button res-onclick-remove="id">x</button>
|
|
90
91
|
</li>
|
|
91
92
|
</ul>
|
|
92
93
|
```
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
```js
|
|
96
|
+
res.add('tasks', [
|
|
97
|
+
{ id: 1, title: 'Learn ResonantJs' },
|
|
98
|
+
{ id: 2, title: 'Ship a feature' }
|
|
99
|
+
]);
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<div res-display="tasks.length > 0">You have tasks</div>
|
|
100
|
-
<div res-display="user.role === 'admin'">Admin Panel</div>
|
|
101
|
+
tasks.push({ id: 3, title: 'Profit' }); // new <li> appears
|
|
102
|
+
tasks[0].title = 'Done!'; // only that <li> re-renders
|
|
101
103
|
```
|
|
102
104
|
|
|
103
|
-
###
|
|
104
|
-
Apply conditional CSS classes and styles:
|
|
105
|
+
### Conditional display
|
|
105
106
|
|
|
106
107
|
```html
|
|
107
|
-
<div res-
|
|
108
|
-
<
|
|
108
|
+
<div res-display="user.isAdmin">Admin Panel</div>
|
|
109
|
+
<div res-display="tasks.length === 0">No tasks yet.</div>
|
|
109
110
|
```
|
|
110
111
|
|
|
111
|
-
###
|
|
112
|
-
Bind click events with context:
|
|
112
|
+
### Dynamic classes
|
|
113
113
|
|
|
114
114
|
```html
|
|
115
|
-
<
|
|
116
|
-
<button res-onclick-remove="true">Delete Item</button>
|
|
115
|
+
<span res-prop="title" res-style="done ? 'completed' : ''"></span>
|
|
117
116
|
```
|
|
118
117
|
|
|
119
|
-
###
|
|
120
|
-
Create reactive derived values that automatically update:
|
|
118
|
+
### Computed properties
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
const resonant = new Resonant();
|
|
124
|
-
resonant.add('firstName', 'John');
|
|
125
|
-
resonant.add('lastName', 'Doe');
|
|
120
|
+
Derived values that recalculate automatically when dependencies change. Chains work too.
|
|
126
121
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
```js
|
|
123
|
+
res.add('price', 100);
|
|
124
|
+
res.add('taxRate', 0.08);
|
|
125
|
+
|
|
126
|
+
res.computed('tax', () => price * taxRate);
|
|
127
|
+
res.computed('total', () => price + tax); // chains: updates when tax updates
|
|
131
128
|
```
|
|
132
129
|
|
|
133
130
|
```html
|
|
134
|
-
|
|
131
|
+
Total: $<span res="total"></span>
|
|
135
132
|
```
|
|
136
133
|
|
|
137
|
-
###
|
|
138
|
-
Two-way data binding for form elements:
|
|
134
|
+
### Two-way input binding
|
|
139
135
|
|
|
140
136
|
```html
|
|
141
|
-
<input type="text" res="
|
|
137
|
+
<input type="text" res="name" />
|
|
142
138
|
<input type="checkbox" res="settings.darkMode" />
|
|
143
|
-
<select res="
|
|
144
|
-
<option value="us">United States</option>
|
|
145
|
-
<option value="uk">United Kingdom</option>
|
|
146
|
-
</select>
|
|
139
|
+
<select res="country">...</select>
|
|
147
140
|
```
|
|
148
141
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
## ๐ฏ Key Features
|
|
152
|
-
|
|
153
|
-
### **Reactive Data Management**
|
|
154
|
-
```javascript
|
|
155
|
-
const resonant = new Resonant();
|
|
156
|
-
|
|
157
|
-
// Add single variables
|
|
158
|
-
resonant.add('counter', 0);
|
|
159
|
-
resonant.add('user', { name: 'John', age: 30 });
|
|
142
|
+
### Persistence
|
|
160
143
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
settings: { theme: 'light' },
|
|
165
|
-
currentUser: { name: 'Alice', role: 'admin' }
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Changes automatically update the UI
|
|
169
|
-
user.name = 'Jane'; // DOM updates instantly
|
|
170
|
-
tasks.push({ title: 'New task' }); // Array renders new item
|
|
144
|
+
```js
|
|
145
|
+
res.add('theme', 'light', true); // third arg = persist to localStorage
|
|
146
|
+
theme = 'dark'; // saved automatically
|
|
171
147
|
```
|
|
172
148
|
|
|
173
|
-
###
|
|
174
|
-
React to data changes with custom logic:
|
|
149
|
+
### Bind existing variables
|
|
175
150
|
|
|
176
|
-
|
|
177
|
-
resonant.addCallback('tasks', (newValue, item, action) => {
|
|
178
|
-
console.log(`Tasks ${action}:`, item);
|
|
179
|
-
updateTaskCounter();
|
|
180
|
-
saveToAPI();
|
|
181
|
-
});
|
|
151
|
+
Already have a variable on `window`? Register it without passing a value.
|
|
182
152
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
});
|
|
153
|
+
```js
|
|
154
|
+
window.username = 'Alice';
|
|
155
|
+
res.add('username'); // picks up 'Alice', makes it reactive
|
|
156
|
+
res.add('username', true); // same, but also persists to localStorage
|
|
188
157
|
```
|
|
189
158
|
|
|
190
|
-
###
|
|
191
|
-
Automatic localStorage integration:
|
|
192
|
-
|
|
193
|
-
```javascript
|
|
194
|
-
// Data persists across browser sessions
|
|
195
|
-
resonant.add('userPreferences', { theme: 'dark' }, true);
|
|
196
|
-
resonant.add('appState', { currentView: 'dashboard' }, true);
|
|
159
|
+
### Event handling
|
|
197
160
|
|
|
198
|
-
|
|
199
|
-
|
|
161
|
+
```html
|
|
162
|
+
<button res-onclick="editTask">Edit</button> <!-- receives the current item -->
|
|
163
|
+
<button res-onclick-remove="id">Delete</button> <!-- removes item by matching property -->
|
|
200
164
|
```
|
|
201
165
|
|
|
202
|
-
###
|
|
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
|
|
166
|
+
### Callbacks
|
|
216
167
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
168
|
+
```js
|
|
169
|
+
res.addCallback('tasks', (value, item, action) => {
|
|
170
|
+
console.log(action, item); // 'added', 'removed', 'modified', etc.
|
|
220
171
|
});
|
|
221
172
|
```
|
|
222
173
|
|
|
223
|
-
### **Advanced Array Operations**
|
|
224
|
-
Full array reactivity with custom methods:
|
|
225
|
-
|
|
226
|
-
```javascript
|
|
227
|
-
// All operations trigger UI updates
|
|
228
|
-
items.push(newItem); // Add item
|
|
229
|
-
items.splice(index, 1); // Remove item
|
|
230
|
-
items.update([...newItems]); // Replace entire array
|
|
231
|
-
items.set(index, newValue); // Update specific index
|
|
232
|
-
items.delete(index); // Delete by index
|
|
233
|
-
```
|
|
234
|
-
|
|
235
174
|
---
|
|
236
175
|
|
|
237
|
-
##
|
|
176
|
+
## Build a Todo App
|
|
177
|
+
|
|
178
|
+
Copy this into an `.html` file and open it in your browser.
|
|
238
179
|
|
|
239
|
-
### Todo List with Filtering
|
|
240
180
|
```html
|
|
241
|
-
|
|
242
|
-
|
|
181
|
+
<!doctype html>
|
|
182
|
+
<html>
|
|
183
|
+
<head>
|
|
184
|
+
<style>.done { text-decoration: line-through; color: #999; }</style>
|
|
185
|
+
<script src="https://unpkg.com/resonantjs@latest/resonant.min.js"></script>
|
|
186
|
+
</head>
|
|
187
|
+
<body>
|
|
188
|
+
<h1>Todos (<span res="tasks.length"></span>)</h1>
|
|
189
|
+
|
|
190
|
+
<input placeholder="Add a task..." res="newTask" />
|
|
243
191
|
<button onclick="addTask()">Add</button>
|
|
244
|
-
|
|
245
|
-
<select res="filter">
|
|
246
|
-
<option value="all">All Tasks</option>
|
|
247
|
-
<option value="active">Active</option>
|
|
248
|
-
<option value="completed">Completed</option>
|
|
249
|
-
</select>
|
|
250
|
-
|
|
192
|
+
|
|
251
193
|
<ul>
|
|
252
|
-
<li res="
|
|
253
|
-
|
|
254
|
-
<
|
|
255
|
-
<
|
|
256
|
-
<button res-onclick="deleteTask(item)">Delete</button>
|
|
194
|
+
<li res="tasks">
|
|
195
|
+
<input type="checkbox" res-prop="done" />
|
|
196
|
+
<span res-prop="name" res-style="done ? 'done' : ''"></span>
|
|
197
|
+
<button res-onclick="removeTask">x</button>
|
|
257
198
|
</li>
|
|
258
199
|
</ul>
|
|
259
|
-
</div>
|
|
260
200
|
|
|
261
|
-
<script>
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
filteredTasks: []
|
|
271
|
-
});
|
|
201
|
+
<script>
|
|
202
|
+
const res = new Resonant();
|
|
203
|
+
res.addAll({
|
|
204
|
+
newTask: '',
|
|
205
|
+
tasks: [
|
|
206
|
+
{ name: 'Learn ResonantJs', done: false },
|
|
207
|
+
{ name: 'Ship a feature', done: true }
|
|
208
|
+
]
|
|
209
|
+
});
|
|
272
210
|
|
|
273
|
-
function addTask() {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function deleteTask(task) {
|
|
282
|
-
const index = tasks.indexOf(task);
|
|
283
|
-
tasks.splice(index, 1);
|
|
284
|
-
updateFilter();
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function updateFilter() {
|
|
288
|
-
filteredTasks.splice(0);
|
|
289
|
-
tasks.forEach(task => filteredTasks.push(task));
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
resonant.addCallback('filter', updateFilter);
|
|
293
|
-
resonant.addCallback('tasks', updateFilter);
|
|
294
|
-
updateFilter();
|
|
295
|
-
</script>
|
|
296
|
-
```
|
|
211
|
+
function addTask() {
|
|
212
|
+
const title = newTask.trim();
|
|
213
|
+
if (!title) return;
|
|
214
|
+
tasks.unshift({ name: title, done: false });
|
|
215
|
+
newTask = '';
|
|
216
|
+
}
|
|
297
217
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
</div>
|
|
306
|
-
<div class="stat-card">
|
|
307
|
-
<h3 res="stats.completedTasks"></h3>
|
|
308
|
-
<p>Completed</p>
|
|
309
|
-
</div>
|
|
310
|
-
<div class="stat-card">
|
|
311
|
-
<h3 res="stats.completionRate"></h3>
|
|
312
|
-
<p>% Complete</p>
|
|
313
|
-
</div>
|
|
314
|
-
</div>
|
|
315
|
-
|
|
316
|
-
<div class="projects">
|
|
317
|
-
<div res="projects">
|
|
318
|
-
<div res-prop="" class="project-card">
|
|
319
|
-
<h3 res-prop="name"></h3>
|
|
320
|
-
<div class="progress-bar">
|
|
321
|
-
<div class="progress" res-style="'width: ' + progress + '%'"></div>
|
|
322
|
-
</div>
|
|
323
|
-
<div res-prop="tasks">
|
|
324
|
-
<div res-prop="" res-style="'task priority-' + priority">
|
|
325
|
-
<span res-prop="title"></span>
|
|
326
|
-
<span res-prop="assignee"></span>
|
|
327
|
-
</div>
|
|
328
|
-
</div>
|
|
329
|
-
</div>
|
|
330
|
-
</div>
|
|
331
|
-
</div>
|
|
332
|
-
</div>
|
|
218
|
+
function removeTask(item) {
|
|
219
|
+
const idx = tasks.indexOf(item);
|
|
220
|
+
if (idx !== -1) tasks.delete(idx);
|
|
221
|
+
}
|
|
222
|
+
</script>
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
|
333
225
|
```
|
|
334
226
|
|
|
335
227
|
---
|
|
336
228
|
|
|
337
|
-
##
|
|
338
|
-
|
|
339
|
-
### **Nested Data Structures**
|
|
340
|
-
Handle complex, deeply nested data:
|
|
341
|
-
|
|
342
|
-
```javascript
|
|
343
|
-
resonant.add('company', {
|
|
344
|
-
departments: [
|
|
345
|
-
{
|
|
346
|
-
name: 'Engineering',
|
|
347
|
-
teams: [
|
|
348
|
-
{
|
|
349
|
-
name: 'Frontend',
|
|
350
|
-
members: [
|
|
351
|
-
{ name: 'Alice', role: 'Senior Dev', skills: ['React', 'Vue'] },
|
|
352
|
-
{ name: 'Bob', role: 'Junior Dev', skills: ['HTML', 'CSS'] }
|
|
353
|
-
]
|
|
354
|
-
}
|
|
355
|
-
]
|
|
356
|
-
}
|
|
357
|
-
]
|
|
358
|
-
});
|
|
229
|
+
## API Reference
|
|
359
230
|
|
|
360
|
-
|
|
361
|
-
company.departments[0].teams[0].members[0].name = 'Alice Johnson';
|
|
362
|
-
company.departments[0].teams[0].members.push(newMember);
|
|
363
|
-
```
|
|
231
|
+
### JavaScript
|
|
364
232
|
|
|
365
|
-
|
|
366
|
-
|
|
233
|
+
| Method | Description |
|
|
234
|
+
|---|---|
|
|
235
|
+
| `new Resonant()` | Create an instance |
|
|
236
|
+
| `.add(name, value?, persist?)` | Add a reactive variable. Omit `value` to bind an existing `window` variable. Pass `true` as second or third arg to persist to `localStorage`. |
|
|
237
|
+
| `.addAll({ name: value, ... })` | Add multiple variables at once |
|
|
238
|
+
| `.addCallback(name, fn)` | Listen for changes. `fn(currentValue, item, action)` |
|
|
239
|
+
| `.computed(name, fn)` | Define a read-only derived value |
|
|
367
240
|
|
|
368
|
-
|
|
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
|
-
]);
|
|
241
|
+
### HTML Attributes
|
|
375
242
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
243
|
+
| Attribute | Description |
|
|
244
|
+
|---|---|
|
|
245
|
+
| `res="varName"` | Bind element to a variable (scalar, object, or array template) |
|
|
246
|
+
| `res-prop="key"` | Bind to an object property within a `res` context |
|
|
247
|
+
| `res-display="expr"` | Show/hide element based on a JS expression |
|
|
248
|
+
| `res-style="expr"` | Apply CSS classes from a JS expression |
|
|
249
|
+
| `res-onclick="fnName"` | Call a global function on click; receives current item if in an array |
|
|
250
|
+
| `res-onclick-remove="prop"` | Remove the current item from its parent array by matching property |
|
|
380
251
|
|
|
381
|
-
|
|
382
|
-
return tasks.filter(t => t.completed).length;
|
|
383
|
-
});
|
|
252
|
+
### Array Methods
|
|
384
253
|
|
|
385
|
-
|
|
386
|
-
return totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
|
|
387
|
-
});
|
|
254
|
+
Reactive arrays support all standard methods plus:
|
|
388
255
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
256
|
+
| Method | Description |
|
|
257
|
+
|---|---|
|
|
258
|
+
| `.set(index, value)` | Update item at index |
|
|
259
|
+
| `.delete(index)` | Remove item at index |
|
|
260
|
+
| `.update(newArray)` | Replace entire array contents |
|
|
261
|
+
| `.filterInPlace(fn)` | Mutating filter |
|
|
262
|
+
| `.forceUpdate()` | Force re-render without changing data |
|
|
395
263
|
|
|
396
|
-
|
|
397
|
-
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
|
398
|
-
});
|
|
264
|
+
### Callback Actions
|
|
399
265
|
|
|
400
|
-
|
|
401
|
-
return subtotal * taxRate;
|
|
402
|
-
});
|
|
266
|
+
`added` `removed` `modified` `updated` `filtered`
|
|
403
267
|
|
|
404
|
-
|
|
405
|
-
return subtotal + tax;
|
|
406
|
-
});
|
|
407
|
-
```
|
|
268
|
+
---
|
|
408
269
|
|
|
409
|
-
|
|
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>
|
|
414
|
-
```
|
|
270
|
+
## Performance
|
|
415
271
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
function createTaskManager(containerId) {
|
|
421
|
-
const resonant = new Resonant();
|
|
422
|
-
|
|
423
|
-
resonant.addAll({
|
|
424
|
-
tasks: [],
|
|
425
|
-
filter: 'all',
|
|
426
|
-
newTask: ''
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
resonant.addCallback('tasks', updateStats);
|
|
430
|
-
|
|
431
|
-
return {
|
|
432
|
-
addTask: () => { /* implementation */ },
|
|
433
|
-
removeTask: (task) => { /* implementation */ },
|
|
434
|
-
setFilter: (filter) => { /* implementation */ }
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
```
|
|
272
|
+
- **Selective array re-rendering** -- when a property on one array item changes, only that item's DOM subtree is updated. Siblings are untouched, including their `res-display` and `res-style` evaluations.
|
|
273
|
+
- **Batched updates** -- rapid changes within the same tick are coalesced into a single DOM update.
|
|
274
|
+
- **Computed property chains** -- cascading computed properties resolve in dependency order within a single pass.
|
|
275
|
+
- **Stable keys** -- array items are tracked by stable keys for efficient reuse during re-renders.
|
|
438
276
|
|
|
439
277
|
---
|
|
440
278
|
|
|
441
|
-
##
|
|
279
|
+
## Browser Support
|
|
442
280
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
- **[Basic Counter](./examples/example-basic.html)** - Simple reactive counter
|
|
446
|
-
- **[Task Manager](./examples/example-taskmanager.html)** - Complete task management app
|
|
447
|
-
- **[Houses Demo](./examples/example-houses.html)** - Complex nested data structures
|
|
448
|
-
- **[Advanced Demo](./examples/example-taskmanager-simple-demo.html)** - Full-featured application
|
|
449
|
-
|
|
450
|
-
Each example demonstrates different aspects of ResonantJs and can serve as starting points for your projects.
|
|
281
|
+
Chrome 60+ / Firefox 55+ / Safari 12+ / Edge 79+ / Mobile browsers
|
|
451
282
|
|
|
452
283
|
---
|
|
453
284
|
|
|
454
|
-
##
|
|
455
|
-
|
|
456
|
-
### **Performance**
|
|
457
|
-
- **Minimal overhead**: Only updates affected DOM elements
|
|
458
|
-
- **Efficient diffing**: Smart change detection for nested objects
|
|
459
|
-
- **Lazy evaluation**: Conditional expressions only run when dependencies change
|
|
460
|
-
- **Memory efficient**: Automatic cleanup of unused observers
|
|
285
|
+
## Examples
|
|
461
286
|
|
|
462
|
-
|
|
463
|
-
-
|
|
464
|
-
-
|
|
465
|
-
-
|
|
466
|
-
- โ
Edge 79+
|
|
467
|
-
- โ
Mobile browsers (iOS Safari, Chrome Mobile)
|
|
287
|
+
- [Basic Counter](./examples/example-basic.html)
|
|
288
|
+
- [Task Manager](./examples/example-taskmanager.html)
|
|
289
|
+
- [Nested Data (Houses)](./examples/example-houses.html)
|
|
290
|
+
- [Tests Showcase](./examples/tests.html)
|
|
468
291
|
|
|
469
292
|
---
|
|
470
293
|
|
|
471
|
-
##
|
|
294
|
+
## Development
|
|
472
295
|
|
|
473
|
-
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
474
|
-
|
|
475
|
-
### Development Setup
|
|
476
296
|
```bash
|
|
477
297
|
git clone https://github.com/amurgola/ResonantJs.git
|
|
478
298
|
cd ResonantJs
|
|
479
299
|
npm install
|
|
480
|
-
npm test
|
|
300
|
+
npm test # run all tests
|
|
301
|
+
npm run build # run tests + minify
|
|
481
302
|
```
|
|
482
303
|
|
|
483
|
-
### Running Tests
|
|
484
|
-
```bash
|
|
485
|
-
npm test # Run all tests
|
|
486
|
-
npm test -- test/specific.test.js # Run specific test file
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
---
|
|
490
|
-
|
|
491
|
-
## ๐ License
|
|
492
|
-
|
|
493
|
-
ResonantJs is released under the **MIT License**. See [LICENSE](LICENSE) file for details.
|
|
494
|
-
|
|
495
304
|
---
|
|
496
305
|
|
|
497
|
-
##
|
|
306
|
+
## License
|
|
498
307
|
|
|
499
|
-
|
|
500
|
-
- **Discussions**: [GitHub Discussions](https://github.com/amurgola/ResonantJs/discussions)
|
|
501
|
-
- **Documentation**: [Full API Documentation](https://github.com/amurgola/ResonantJs/wiki)
|
|
308
|
+
MIT -- see [LICENSE](LICENSE).
|
|
502
309
|
|
|
503
310
|
---
|
|
504
311
|
|
|
505
312
|
<div align="center">
|
|
506
313
|
|
|
507
|
-
**[
|
|
314
|
+
**[GitHub](https://github.com/amurgola/ResonantJs)** ยท **[npm](https://www.npmjs.com/package/resonantjs)** ยท **[Issues](https://github.com/amurgola/ResonantJs/issues)**
|
|
508
315
|
|
|
509
|
-
*Built
|
|
316
|
+
*Built by [Andrew Paul Murgola](https://github.com/amurgola)*
|
|
510
317
|
|
|
511
318
|
</div>
|