resonantjs 1.1.8 → 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 CHANGED
@@ -3,146 +3,68 @@
3
3
  [![npm version](https://badge.fury.io/js/resonantjs.svg)](https://badge.fury.io/js/resonantjs)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
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.
6
+ **Reactive data-binding for vanilla JavaScript. No build step. No virtual DOM. Just HTML attributes and plain objects.**
7
7
 
8
- > **Zero dependencies • ~17KB minified • Lightning fast • Easy to learn**
8
+ ```html
9
+ <span res="count"></span>
10
+ <button onclick="count++">+1</button>
9
11
 
10
- ## Why Choose ResonantJs?
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
- - **True Reactivity**: Data changes automatically update the DOM with no manual manipulation required
13
- - **Attribute-Based**: Simple HTML attributes create powerful data bindings
14
- - **Deep Object Support**: Full reactivity for nested objects and arrays
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 17KB 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
- ## Quick Start
23
-
24
- ### Installation
23
+ ## Install
25
24
 
26
- #### NPM
27
25
  ```bash
28
26
  npm install resonantjs
29
27
  ```
30
28
 
31
- #### CDN
32
- ```html
33
- <script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
34
- ```
35
-
36
- ### Hello World Example
29
+ Or drop in a script tag:
37
30
 
38
31
  ```html
39
- <!DOCTYPE html>
40
- <html>
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>
32
+ <script src="https://unpkg.com/resonantjs@latest/resonant.min.js"></script>
56
33
  ```
57
34
 
58
- That's it. Your counter automatically updates the DOM and persists to localStorage.
35
+ ~18 KB minified. Zero dependencies.
59
36
 
60
37
  ---
61
38
 
62
- ## Build a Todo App in 5 Minutes
63
-
64
- Copy and paste the snippet below into an `.html` file and open it in your browser.
39
+ ## Why ResonantJs?
65
40
 
66
- ```html
67
- <!doctype html>
68
- <html>
69
- <head>
70
- <meta charset="utf-8" />
71
- <title>ResonantJs 5‑min Todo</title>
72
- <style>
73
- .completed { text-decoration: line-through; color: #888; }
74
- </style>
75
- <script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
76
- </head>
77
- <body>
78
- <h1>Todos (<span res="tasks.length"></span>)</h1>
79
-
80
- <input placeholder="Add a task..." res="newTask" />
81
- <button onclick="addTask()">Add</button>
82
-
83
- <ul>
84
- <li res="tasks" res-display="newTask === '' || name.toLowerCase().includes(newTask.toLowerCase())">
85
- <input type="checkbox" res-prop="completed" />
86
- <span res-prop="name" res-style="completed ? 'completed' : ''"></span>
87
- <button res-onclick="removeTask">Remove</button>
88
- </li>
89
- </ul>
90
-
91
- <script>
92
- const resonant = new Resonant();
93
- resonant.addAll({
94
- newTask: '',
95
- tasks: [
96
- { name: 'Learn ResonantJs', completed: false },
97
- { name: 'Ship a feature', completed: true }
98
- ]
99
- });
100
-
101
- function addTask() {
102
- const title = newTask.trim();
103
- if (!title) return;
104
- tasks.unshift({ name: title, completed: false });
105
- newTask = '';
106
- }
107
-
108
- function removeTask(item) {
109
- const idx = tasks.indexOf(item);
110
- if (idx !== -1) tasks.delete(idx);
111
- }
112
-
113
- // Optional: observe changes
114
- resonant.addCallback('tasks', (list, item, action) => {
115
- console.log('[tasks]', action, item);
116
- });
117
- </script>
118
- </body>
119
- </html>
120
- ```
121
-
122
- ### Key Takeaways
123
-
124
- - Use `res="tasks"` on a template element inside a list container to auto-render each item
125
- - Use `res-prop` inside that template to bind fields of the current item
126
- - Use `res-display` for inline filtering/conditional rendering; inside lists, bare props like `completed` refer to the current item
127
- - `res-style` returns a space-separated class string
128
- - Event handlers referenced by `res-onclick` are global functions and receive `item` when declared with a parameter
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. |
129
49
 
130
50
  ---
131
51
 
132
- ## Core Concepts
133
-
134
- ### Data Binding (`res`)
52
+ ## Quick Tour
135
53
 
136
- Bind HTML elements directly to your JavaScript variables:
54
+ ### Bind a variable
137
55
 
138
56
  ```html
139
- <span res="username"></span> <!-- Simple variable -->
140
- <div res="user.profile.name"></div> <!-- Nested object property -->
57
+ <h1>Hello, <span res="name"></span></h1>
141
58
  ```
142
59
 
143
- ### Object Properties (`res-prop`)
60
+ ```js
61
+ const res = new Resonant();
62
+ res.add('name', 'World');
63
+
64
+ name = 'ResonantJs'; // DOM updates instantly
65
+ ```
144
66
 
145
- Bind to specific properties within objects:
67
+ ### Bind an object
146
68
 
147
69
  ```html
148
70
  <div res="user">
@@ -151,508 +73,246 @@ Bind to specific properties within objects:
151
73
  </div>
152
74
  ```
153
75
 
154
- ### Array Rendering
76
+ ```js
77
+ res.add('user', { name: 'Alice', email: 'alice@example.com' });
155
78
 
156
- Automatically render arrays with template-based elements:
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.
157
85
 
158
86
  ```html
159
87
  <ul>
160
- <li res="todoItems">
88
+ <li res="tasks">
161
89
  <span res-prop="title"></span>
162
- <button res-onclick="removeItem(item)">Delete</button>
90
+ <button res-onclick-remove="id">x</button>
163
91
  </li>
164
92
  </ul>
165
93
  ```
166
94
 
167
- ### Conditional Display (`res-display`)
168
-
169
- Show or hide elements based on conditions:
95
+ ```js
96
+ res.add('tasks', [
97
+ { id: 1, title: 'Learn ResonantJs' },
98
+ { id: 2, title: 'Ship a feature' }
99
+ ]);
170
100
 
171
- ```html
172
- <div res-display="user.isActive">Welcome back!</div>
173
- <div res-display="tasks.length > 0">You have tasks</div>
174
- <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
175
103
  ```
176
104
 
177
- ### Dynamic Styling (`res-style`)
178
-
179
- Apply conditional CSS classes and styles:
105
+ ### Conditional display
180
106
 
181
107
  ```html
182
- <div res-style="task.completed ? 'completed' : 'pending'">Task</div>
183
- <span res-style="'priority-' + task.priority">High Priority</span>
108
+ <div res-display="user.isAdmin">Admin Panel</div>
109
+ <div res-display="tasks.length === 0">No tasks yet.</div>
184
110
  ```
185
111
 
186
- ### Event Handling (`res-onclick`)
187
-
188
- Bind click events with context:
112
+ ### Dynamic classes
189
113
 
190
114
  ```html
191
- <button res-onclick="editTask(item)">Edit</button>
192
- <button res-onclick-remove="true">Delete Item</button>
115
+ <span res-prop="title" res-style="done ? 'completed' : ''"></span>
193
116
  ```
194
117
 
195
- ### Computed Properties
118
+ ### Computed properties
196
119
 
197
- Create reactive derived values that automatically update:
120
+ Derived values that recalculate automatically when dependencies change. Chains work too.
198
121
 
199
- ```javascript
200
- const resonant = new Resonant();
201
- resonant.add('firstName', 'John');
202
- resonant.add('lastName', 'Doe');
122
+ ```js
123
+ res.add('price', 100);
124
+ res.add('taxRate', 0.08);
203
125
 
204
- // Computed property automatically updates when dependencies change
205
- resonant.computed('fullName', () => {
206
- return firstName + ' ' + lastName;
207
- });
126
+ res.computed('tax', () => price * taxRate);
127
+ res.computed('total', () => price + tax); // chains: updates when tax updates
208
128
  ```
209
129
 
210
130
  ```html
211
- <span res="fullName"></span> <!-- Automatically shows "John Doe" -->
131
+ Total: $<span res="total"></span>
212
132
  ```
213
133
 
214
- ### Input Binding
215
-
216
- Two-way data binding for form elements:
134
+ ### Two-way input binding
217
135
 
218
136
  ```html
219
- <input type="text" res="user.name" />
137
+ <input type="text" res="name" />
220
138
  <input type="checkbox" res="settings.darkMode" />
221
- <select res="user.country">
222
- <option value="us">United States</option>
223
- <option value="uk">United Kingdom</option>
224
- </select>
139
+ <select res="country">...</select>
225
140
  ```
226
141
 
227
- ---
228
-
229
- ## Best Practices and Tips
230
-
231
- - **Name your state clearly**: Variables you `add` become globals on `window` (e.g., `tasks`, `user`). Avoid collisions with existing globals.
232
- - **List templates**: In `res="items"` templates, you can reference current item fields directly (`completed`, `name`) or as `item.completed` — both work.
233
- - **Array updates**: Prefer `items.set(i, value)` over direct index assignment for clarity; both are reactive.
234
- - **Batch updates**: When replacing a whole list, use `items.update(newArray)` to emit a single coherent update.
235
- - **Object binding**: Use `res-prop=""` to bind an entire object to a single element when you just want to print it.
236
- - **Event handlers**: `res-onclick` handlers are looked up on `window`. If your handler accepts an argument, Resonant passes the current `item`.
237
- - **Quick removal**: For quick removal buttons, use `res-onclick-remove="idProp"` to delete by a unique key on each item.
238
- - **Computed properties**: Track dependencies automatically. Use plain variable names inside the function (e.g., `firstName`, `lastName`). They are read-only.
239
- - **Conditional expressions**: Keep display and style expressions simple and fast.
240
-
241
- ### Performance Notes
242
-
243
- - Resonant selectively re-renders only changed array items by tracking indices and stable object keys
244
- - Deeply nested objects and arrays are proxied; nested edits still update only affected DOM segments
245
-
246
- ---
247
-
248
- ## API & Attribute Reference
249
-
250
- ### HTML Attributes
251
-
252
- - `res` — bind a variable or array/template root
253
- - `res-prop` — bind an object property within a `res` context; empty value binds the whole item
254
- - `res-display` — boolean expression to show/hide element
255
- - `res-style` — expression returning a space-separated class string
256
- - `res-onclick` — call a global function; if it declares a parameter, it receives the current item
257
- - `res-onclick-remove` — remove from the parent array by matching the given property (e.g., `id`)
258
-
259
- ### JavaScript API
260
-
261
- - `const resonant = new Resonant()`
262
- - `resonant.add(name, value, persist?)`
263
- - `resonant.addAll(objectMap)`
264
- - `resonant.addCallback(name, (newValue, item, action) => void)`
265
- - `resonant.computed(name, () => value)`
266
-
267
- ### Array Helpers
268
-
269
- Reactive arrays include these methods:
142
+ ### Persistence
270
143
 
271
- - `.push`, `.pop`, `.shift`, `.unshift`, `.splice`, `.sort`, `.reverse`
272
- - `.set(index, value)`, `.delete(index)`, `.update(array)`, `.filter(fn)`, `.filterInPlace(fn)`, `.forceUpdate()`
273
-
274
- ### Callback Actions
275
-
276
- - Scalars: `modified`
277
- - Arrays: `added`, `removed`, `modified`, `updated`, `filtered`
278
-
279
- ---
280
-
281
- ## Key Features
282
-
283
- ### Reactive Data Management
284
-
285
- ```javascript
286
- const resonant = new Resonant();
287
-
288
- // Add single variables
289
- resonant.add('counter', 0);
290
- resonant.add('user', { name: 'John', age: 30 });
291
-
292
- // Batch initialization
293
- resonant.addAll({
294
- tasks: [],
295
- settings: { theme: 'light' },
296
- currentUser: { name: 'Alice', role: 'admin' }
297
- });
298
-
299
- // Changes automatically update the UI
300
- user.name = 'Jane'; // DOM updates instantly
301
- 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
302
147
  ```
303
148
 
304
- ### Callback System
305
-
306
- React to data changes with custom logic:
149
+ ### Bind existing variables
307
150
 
308
- ```javascript
309
- resonant.addCallback('tasks', (newValue, item, action) => {
310
- console.log(`Tasks ${action}:`, item);
311
- updateTaskCounter();
312
- saveToAPI();
313
- });
151
+ Already have a variable on `window`? Register it without passing a value.
314
152
 
315
- resonant.addCallback('user', (newValue, item, action) => {
316
- if (action === 'modified') {
317
- showNotification('Profile updated');
318
- }
319
- });
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
320
157
  ```
321
158
 
322
- ### LocalStorage Persistence
323
-
324
- Automatic localStorage integration:
325
-
326
- ```javascript
327
- // Data persists across browser sessions
328
- resonant.add('userPreferences', { theme: 'dark' }, true);
329
- resonant.add('appState', { currentView: 'dashboard' }, true);
159
+ ### Event handling
330
160
 
331
- // Changes are automatically saved
332
- userPreferences.theme = 'light'; // Saved to localStorage
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 -->
333
164
  ```
334
165
 
335
- ### Computed Properties
336
-
337
- Reactive derived values that automatically recalculate:
338
-
339
- ```javascript
340
- resonant.add('firstName', 'John');
341
- resonant.add('lastName', 'Doe');
166
+ ### Callbacks
342
167
 
343
- // Automatically updates when firstName or lastName changes
344
- resonant.computed('fullName', () => {
345
- return firstName + ' ' + lastName;
346
- });
347
-
348
- // Cannot be set directly - read-only
349
- // fullName = 'Something'; // Will log warning and be ignored
350
-
351
- // Chain computed properties
352
- resonant.computed('greeting', () => {
353
- return 'Hello, ' + fullName + '!';
168
+ ```js
169
+ res.addCallback('tasks', (value, item, action) => {
170
+ console.log(action, item); // 'added', 'removed', 'modified', etc.
354
171
  });
355
172
  ```
356
173
 
357
- ### Array Operations
358
-
359
- Full array reactivity with custom methods:
360
-
361
- ```javascript
362
- // All operations trigger UI updates
363
- items.push(newItem); // Add item
364
- items.splice(index, 1); // Remove item
365
- items.update([...newItems]); // Replace entire array
366
- items.set(index, newValue); // Update specific index
367
- items.delete(index); // Delete by index
368
- items.filter(v => v > 0); // Non-mutating; still triggers a 'filtered' callback
369
- items.filterInPlace(fn); // Mutating filter + rerender
370
- items.forceUpdate(); // Force a rerender without changing contents
371
- ```
372
-
373
174
  ---
374
175
 
375
- ## Real-World Examples
176
+ ## Build a Todo App
376
177
 
377
- ### Todo List with Filtering
178
+ Copy this into an `.html` file and open it in your browser.
378
179
 
379
180
  ```html
380
- <div>
381
- <input res="newTask" placeholder="Add task..." />
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" />
382
191
  <button onclick="addTask()">Add</button>
383
-
384
- <select res="filter">
385
- <option value="all">All Tasks</option>
386
- <option value="active">Active</option>
387
- <option value="completed">Completed</option>
388
- </select>
389
-
192
+
390
193
  <ul>
391
- <li res="filteredTasks"
392
- res-display="filter === 'all' || (filter === 'active' && !completed) || (filter === 'completed' && completed)">
393
- <input type="checkbox" res-prop="completed" />
394
- <span res-prop="title" res-style="completed ? 'completed-task' : ''"></span>
395
- <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>
396
198
  </li>
397
199
  </ul>
398
- </div>
399
200
 
400
- <script>
401
- const resonant = new Resonant();
402
- resonant.addAll({
403
- tasks: [
404
- { title: 'Learn ResonantJs', completed: false },
405
- { title: 'Build awesome app', completed: false }
406
- ],
407
- newTask: '',
408
- filter: 'all',
409
- filteredTasks: []
410
- });
411
-
412
- function addTask() {
413
- if (newTask.trim()) {
414
- tasks.push({ title: newTask, completed: false });
415
- newTask = '';
416
- updateFilter();
417
- }
418
- }
419
-
420
- function deleteTask(task) {
421
- const index = tasks.indexOf(task);
422
- tasks.splice(index, 1);
423
- updateFilter();
424
- }
425
-
426
- function updateFilter() {
427
- filteredTasks.splice(0);
428
- tasks.forEach(task => filteredTasks.push(task));
429
- }
430
-
431
- resonant.addCallback('filter', updateFilter);
432
- resonant.addCallback('tasks', updateFilter);
433
- updateFilter();
434
- </script>
435
- ```
436
-
437
- ### Dashboard with Statistics
438
-
439
- ```html
440
- <div class="dashboard">
441
- <div class="stats">
442
- <div class="stat-card">
443
- <h3 res="stats.totalTasks"></h3>
444
- <p>Total Tasks</p>
445
- </div>
446
- <div class="stat-card">
447
- <h3 res="stats.completedTasks"></h3>
448
- <p>Completed</p>
449
- </div>
450
- <div class="stat-card">
451
- <h3 res="stats.completionRate"></h3>
452
- <p>% Complete</p>
453
- </div>
454
- </div>
455
-
456
- <div class="projects">
457
- <div res="projects">
458
- <div res-prop="" class="project-card">
459
- <h3 res-prop="name"></h3>
460
- <div class="progress-bar">
461
- <div class="progress" res-style="'width: ' + progress + '%'"></div>
462
- </div>
463
- <div res-prop="tasks">
464
- <div res-prop="" res-style="'task priority-' + priority">
465
- <span res-prop="title"></span>
466
- <span res-prop="assignee"></span>
467
- </div>
468
- </div>
469
- </div>
470
- </div>
471
- </div>
472
- </div>
473
- ```
474
-
475
- ---
476
-
477
- ## Advanced Patterns
478
-
479
- ### Nested Data Structures
480
-
481
- Handle complex, deeply nested data:
482
-
483
- ```javascript
484
- resonant.add('company', {
485
- departments: [
486
- {
487
- name: 'Engineering',
488
- teams: [
489
- {
490
- name: 'Frontend',
491
- members: [
492
- { name: 'Alice', role: 'Senior Dev', skills: ['React', 'Vue'] },
493
- { name: 'Bob', role: 'Junior Dev', skills: ['HTML', 'CSS'] }
494
- ]
495
- }
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 }
496
208
  ]
209
+ });
210
+
211
+ function addTask() {
212
+ const title = newTask.trim();
213
+ if (!title) return;
214
+ tasks.unshift({ name: title, done: false });
215
+ newTask = '';
497
216
  }
498
- ]
499
- });
500
217
 
501
- // All levels are reactive
502
- company.departments[0].teams[0].members[0].name = 'Alice Johnson';
503
- company.departments[0].teams[0].members.push(newMember);
218
+ function removeTask(item) {
219
+ const idx = tasks.indexOf(item);
220
+ if (idx !== -1) tasks.delete(idx);
221
+ }
222
+ </script>
223
+ </body>
224
+ </html>
504
225
  ```
505
226
 
506
- ### Computed Properties
507
-
508
- Create reactive calculated values that automatically update when dependencies change:
227
+ ---
509
228
 
510
- ```javascript
511
- const resonant = new Resonant();
512
- resonant.add('tasks', [
513
- { title: 'Task 1', completed: true },
514
- { title: 'Task 2', completed: false },
515
- { title: 'Task 3', completed: true }
516
- ]);
229
+ ## API Reference
517
230
 
518
- // Computed properties automatically recalculate when 'tasks' changes
519
- resonant.computed('totalTasks', () => {
520
- return tasks.length;
521
- });
231
+ ### JavaScript
522
232
 
523
- resonant.computed('completedTasks', () => {
524
- return tasks.filter(t => t.completed).length;
525
- });
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 |
526
240
 
527
- resonant.computed('completionRate', () => {
528
- return totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
529
- });
241
+ ### HTML Attributes
530
242
 
531
- // Shopping cart example
532
- resonant.add('items', [
533
- { name: 'Widget', price: 10, quantity: 2 },
534
- { name: 'Gadget', price: 15, quantity: 1 }
535
- ]);
536
- resonant.add('taxRate', 0.08);
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 |
537
251
 
538
- resonant.computed('subtotal', () => {
539
- return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
540
- });
252
+ ### Array Methods
541
253
 
542
- resonant.computed('tax', () => {
543
- return subtotal * taxRate;
544
- });
254
+ Reactive arrays support all standard methods plus:
545
255
 
546
- resonant.computed('total', () => {
547
- return subtotal + tax;
548
- });
549
- ```
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 |
550
263
 
551
- ```html
552
- <!-- These automatically update when items change -->
553
- <div>Subtotal: $<span res="subtotal"></span></div>
554
- <div>Tax: $<span res="tax"></span></div>
555
- <div>Total: $<span res="total"></span></div>
556
- ```
264
+ ### Callback Actions
557
265
 
558
- ### Component-Like Patterns
559
-
560
- Organize code into reusable patterns:
561
-
562
- ```javascript
563
- function createTaskManager(containerId) {
564
- const resonant = new Resonant();
565
-
566
- resonant.addAll({
567
- tasks: [],
568
- filter: 'all',
569
- newTask: ''
570
- });
571
-
572
- resonant.addCallback('tasks', updateStats);
573
-
574
- return {
575
- addTask: () => { /* implementation */ },
576
- removeTask: (task) => { /* implementation */ },
577
- setFilter: (filter) => { /* implementation */ }
578
- };
579
- }
580
- ```
266
+ `added` `removed` `modified` `updated` `filtered`
581
267
 
582
268
  ---
583
269
 
584
- ## Examples & Demos
585
-
586
- Explore our comprehensive examples:
270
+ ## Performance
587
271
 
588
- - **[Basic Counter](./examples/example-basic.html)** - Simple reactive counter
589
- - **[Task Manager](./examples/example-taskmanager.html)** - Complete task management app
590
- - **[Houses Demo](./examples/example-houses.html)** - Complex nested data structures
591
- - **[Tests Showcase](./examples/tests.html)** - Interactive testbed used in CI
592
-
593
- Each example demonstrates different aspects of ResonantJs and can serve as starting points for your projects.
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.
594
276
 
595
277
  ---
596
278
 
597
- ## Performance & Browser Support
279
+ ## Browser Support
598
280
 
599
- ### Performance
281
+ Chrome 60+ / Firefox 55+ / Safari 12+ / Edge 79+ / Mobile browsers
600
282
 
601
- - **Minimal overhead**: Only updates affected DOM elements
602
- - **Efficient diffing**: Smart change detection for nested objects
603
- - **Lazy evaluation**: Conditional expressions only run when dependencies change
604
- - **Memory efficient**: Automatic cleanup of unused observers
283
+ ---
605
284
 
606
- ### Browser Support
285
+ ## Examples
607
286
 
608
- - Chrome 60+
609
- - Firefox 55+
610
- - Safari 12+
611
- - Edge 79+
612
- - 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)
613
291
 
614
292
  ---
615
293
 
616
- ## Contributing
617
-
618
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
619
-
620
- ### Development Setup
294
+ ## Development
621
295
 
622
296
  ```bash
623
297
  git clone https://github.com/amurgola/ResonantJs.git
624
298
  cd ResonantJs
625
299
  npm install
626
- npm test
627
- ```
628
-
629
- ### Running Tests
630
-
631
- ```bash
632
- npm test # Run all tests
633
- npm test -- test/specific.test.js # Run specific test file
300
+ npm test # run all tests
301
+ npm run build # run tests + minify
634
302
  ```
635
303
 
636
304
  ---
637
305
 
638
306
  ## License
639
307
 
640
- ResonantJs is released under the **MIT License**. See [LICENSE](LICENSE) file for details.
641
-
642
- ---
643
-
644
- ## Support & Community
645
-
646
- - **Issues**: [GitHub Issues](https://github.com/amurgola/ResonantJs/issues)
647
- - **Discussions**: [GitHub Discussions](https://github.com/amurgola/ResonantJs/discussions)
648
- - **Documentation**: [Full API Documentation](https://github.com/amurgola/ResonantJs/wiki)
308
+ MIT -- see [LICENSE](LICENSE).
649
309
 
650
310
  ---
651
311
 
652
312
  <div align="center">
653
313
 
654
- **[Star us on GitHub](https://github.com/amurgola/ResonantJs)** **[Try the Demo](./examples/example-taskmanager-simple-demo.html)** **[Read the Docs](https://github.com/amurgola/ResonantJs/wiki)**
314
+ **[GitHub](https://github.com/amurgola/ResonantJs)** · **[npm](https://www.npmjs.com/package/resonantjs)** · **[Issues](https://github.com/amurgola/ResonantJs/issues)**
655
315
 
656
- *Built with care by [Andrew Paul Murgola](https://github.com/amurgola)*
316
+ *Built by [Andrew Paul Murgola](https://github.com/amurgola)*
657
317
 
658
318
  </div>