resonantjs 1.1.4 → 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/.github/workflows/npm-publish.yml +2 -0
- package/README.md +483 -54
- package/aiagent.md +231 -0
- package/examples/example-basic.html +57 -0
- package/examples/example-houses.html +69 -0
- package/package.json +6 -2
- package/resonant.js +928 -544
- package/resonant.min.js +2 -1
- package/resonant.min.js.map +1 -0
- package/scripts/build.js +96 -0
- package/test/additional_features.test.js +197 -0
- package/test/computed_properties.test.js +111 -0
- package/test/deep_nesting.test.js +830 -0
- package/test/html_examples.test.js +496 -0
- package/test/input_binding.test.js +117 -0
- package/test/mockDom.js +189 -0
- package/test/resonant.test.js +165 -0
- package/test/selective_rendering.test.js +370 -0
- package/test/style_and_events.test.js +93 -0
- package/test/template.test.js +137 -0
|
@@ -16,6 +16,7 @@ jobs:
|
|
|
16
16
|
with:
|
|
17
17
|
node-version: 16
|
|
18
18
|
- run: npm ci
|
|
19
|
+
- run: npm run build
|
|
19
20
|
|
|
20
21
|
publish-npm:
|
|
21
22
|
needs: build
|
|
@@ -27,6 +28,7 @@ jobs:
|
|
|
27
28
|
node-version: 16
|
|
28
29
|
registry-url: https://registry.npmjs.org/
|
|
29
30
|
- run: npm ci
|
|
31
|
+
- run: npm run build
|
|
30
32
|
- run: npm publish
|
|
31
33
|
env:
|
|
32
34
|
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
package/README.md
CHANGED
|
@@ -1,82 +1,511 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ResonantJs 🚀
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/resonantjs)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
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
7
|
|
|
7
|
-
|
|
8
|
-
- **Dynamic List Rendering**: Easily render lists that react to data changes.
|
|
9
|
-
- **Bidirectional Input Binding**: Bind HTML input fields directly to your data model.
|
|
10
|
-
- **Efficient Conditional Updates**: Only evaluate conditional expressions tied to specific variable changes.
|
|
11
|
-
- **Lightweight and Easy to Integrate**: Minimal setup required to get started.
|
|
12
|
-
## Installation
|
|
13
|
-
## NPM
|
|
14
|
-
To install via NPM, use the following command:
|
|
8
|
+
> **Zero dependencies • ~11.5KB minified • Lightning fast • Easy to learn**
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
npm i resonantjs
|
|
18
|
-
```
|
|
10
|
+
## ✨ Why Choose ResonantJs?
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
- 🔄 **True Reactivity**: Data changes automatically update the DOM - no manual manipulation needed
|
|
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
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🚀 Quick Start
|
|
23
|
+
|
|
24
|
+
### Installation
|
|
25
|
+
|
|
26
|
+
#### NPM
|
|
27
|
+
```bash
|
|
28
|
+
npm install resonantjs
|
|
25
29
|
```
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
#### CDN
|
|
32
|
+
```html
|
|
33
|
+
<script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
|
|
34
|
+
```
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
Include resonant.js in your HTML file, and use the following example to understand how to integrate it into your web application.
|
|
36
|
+
### Hello World Example
|
|
32
37
|
|
|
33
38
|
```html
|
|
34
39
|
<!DOCTYPE html>
|
|
35
|
-
<html
|
|
40
|
+
<html>
|
|
36
41
|
<head>
|
|
37
|
-
<title>
|
|
42
|
+
<title>ResonantJs Demo</title>
|
|
38
43
|
<script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
|
|
39
44
|
</head>
|
|
40
45
|
<body>
|
|
41
|
-
<h1>
|
|
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
|
+
```
|
|
57
|
+
|
|
58
|
+
That's it! Your counter will automatically update the DOM and persist to localStorage.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 📖 Core Concepts
|
|
63
|
+
|
|
64
|
+
### 1. **Data Binding** (`res`)
|
|
65
|
+
Bind HTML elements directly to your JavaScript variables:
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<span res="username"></span> <!-- Simple variable -->
|
|
69
|
+
<div res="user.profile.name"></div> <!-- Nested object property -->
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. **Object Properties** (`res-prop`)
|
|
73
|
+
Bind to specific properties within objects:
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<div res="user">
|
|
77
|
+
<span res-prop="name"></span>
|
|
78
|
+
<span res-prop="email"></span>
|
|
79
|
+
</div>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. **Array Rendering**
|
|
83
|
+
Automatically render arrays with template-based elements:
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<ul>
|
|
87
|
+
<li res="todoItems">
|
|
88
|
+
<span res-prop="title"></span>
|
|
89
|
+
<button res-onclick="removeItem(item)">Delete</button>
|
|
90
|
+
</li>
|
|
91
|
+
</ul>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4. **Conditional Display** (`res-display`)
|
|
95
|
+
Show/hide elements based on conditions:
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<div res-display="user.isActive">Welcome back!</div>
|
|
99
|
+
<div res-display="tasks.length > 0">You have tasks</div>
|
|
100
|
+
<div res-display="user.role === 'admin'">Admin Panel</div>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 5. **Dynamic Styling** (`res-style`)
|
|
104
|
+
Apply conditional CSS classes and styles:
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<div res-style="task.completed ? 'completed' : 'pending'">Task</div>
|
|
108
|
+
<span res-style="'priority-' + task.priority">High Priority</span>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 6. **Event Handling** (`res-onclick`)
|
|
112
|
+
Bind click events with context:
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<button res-onclick="editTask(item)">Edit</button>
|
|
116
|
+
<button res-onclick-remove="true">Delete Item</button>
|
|
117
|
+
```
|
|
118
|
+
|
|
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**
|
|
138
|
+
Two-way data binding for form elements:
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<input type="text" res="user.name" />
|
|
142
|
+
<input type="checkbox" res="settings.darkMode" />
|
|
143
|
+
<select res="user.country">
|
|
144
|
+
<option value="us">United States</option>
|
|
145
|
+
<option value="uk">United Kingdom</option>
|
|
146
|
+
</select>
|
|
147
|
+
```
|
|
148
|
+
|
|
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 });
|
|
160
|
+
|
|
161
|
+
// Batch initialization
|
|
162
|
+
resonant.addAll({
|
|
163
|
+
tasks: [],
|
|
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
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### **Event Callbacks**
|
|
174
|
+
React to data changes with custom logic:
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
resonant.addCallback('tasks', (newValue, item, action) => {
|
|
178
|
+
console.log(`Tasks ${action}:`, item);
|
|
179
|
+
updateTaskCounter();
|
|
180
|
+
saveToAPI();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
resonant.addCallback('user', (newValue, item, action) => {
|
|
184
|
+
if (action === 'modified') {
|
|
185
|
+
showNotification('Profile updated');
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### **Persistence**
|
|
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);
|
|
197
|
+
|
|
198
|
+
// Changes are automatically saved
|
|
199
|
+
userPreferences.theme = 'light'; // Saved to localStorage
|
|
200
|
+
```
|
|
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
|
+
|
|
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
|
+
---
|
|
236
|
+
|
|
237
|
+
## 🏗️ Real-World Examples
|
|
238
|
+
|
|
239
|
+
### Todo List with Filtering
|
|
240
|
+
```html
|
|
42
241
|
<div>
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
242
|
+
<input res="newTask" placeholder="Add task..." />
|
|
243
|
+
<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
|
+
|
|
251
|
+
<ul>
|
|
252
|
+
<li res="filteredTasks"
|
|
253
|
+
res-display="filter === 'all' || (filter === 'active' && !completed) || (filter === 'completed' && completed)">
|
|
254
|
+
<input type="checkbox" res-prop="completed" />
|
|
255
|
+
<span res-prop="title" res-style="completed ? 'completed-task' : ''"></span>
|
|
256
|
+
<button res-onclick="deleteTask(item)">Delete</button>
|
|
257
|
+
</li>
|
|
258
|
+
</ul>
|
|
46
259
|
</div>
|
|
47
260
|
|
|
48
261
|
<script>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
262
|
+
const resonant = new Resonant();
|
|
263
|
+
resonant.addAll({
|
|
264
|
+
tasks: [
|
|
265
|
+
{ title: 'Learn ResonantJs', completed: false },
|
|
266
|
+
{ title: 'Build awesome app', completed: false }
|
|
267
|
+
],
|
|
268
|
+
newTask: '',
|
|
269
|
+
filter: 'all',
|
|
270
|
+
filteredTasks: []
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
function addTask() {
|
|
274
|
+
if (newTask.trim()) {
|
|
275
|
+
tasks.push({ title: newTask, completed: false });
|
|
276
|
+
newTask = '';
|
|
277
|
+
updateFilter();
|
|
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();
|
|
54
295
|
</script>
|
|
55
|
-
</body>
|
|
56
|
-
</html>
|
|
57
296
|
```
|
|
58
297
|
|
|
59
|
-
|
|
298
|
+
### Dashboard with Statistics
|
|
299
|
+
```html
|
|
300
|
+
<div class="dashboard">
|
|
301
|
+
<div class="stats">
|
|
302
|
+
<div class="stat-card">
|
|
303
|
+
<h3 res="stats.totalTasks"></h3>
|
|
304
|
+
<p>Total Tasks</p>
|
|
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>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 📚 Advanced Patterns
|
|
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
|
+
});
|
|
359
|
+
|
|
360
|
+
// All levels are reactive
|
|
361
|
+
company.departments[0].teams[0].members[0].name = 'Alice Johnson';
|
|
362
|
+
company.departments[0].teams[0].members.push(newMember);
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### **Computed Properties**
|
|
366
|
+
Create reactive calculated values that automatically update when dependencies change:
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
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;
|
|
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>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### **Component-Like Patterns**
|
|
417
|
+
Organize code into reusable patterns:
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
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
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## 🎨 Examples & Demos
|
|
442
|
+
|
|
443
|
+
Explore our comprehensive examples:
|
|
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.
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## 🚀 Performance & Browser Support
|
|
60
455
|
|
|
61
|
-
###
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
- **`res-onclick` Attribute**: Triggers a function when an element is clicked, allowing for custom event handling.
|
|
67
|
-
- **`res-onclick-remove` Attribute**: Removes an item from an array when the associated element is clicked.
|
|
68
|
-
- **`res-style` Attribute**: Dynamically update CSS styles based on data model properties.
|
|
69
|
-
- **Automatic UI Updates**: Changes to your JavaScript objects instantly reflect in the associated UI components, reducing manual DOM manipulation.
|
|
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
|
|
70
461
|
|
|
71
|
-
###
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
462
|
+
### **Browser Support**
|
|
463
|
+
- ✅ Chrome 60+
|
|
464
|
+
- ✅ Firefox 55+
|
|
465
|
+
- ✅ Safari 12+
|
|
466
|
+
- ✅ Edge 79+
|
|
467
|
+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
|
|
76
468
|
|
|
77
|
-
|
|
78
|
-
- The demo HTML file uses the Pico CSS framework for styling. You can find more information about Pico CSS [here](https://picocss.com/).
|
|
469
|
+
---
|
|
79
470
|
|
|
80
|
-
##
|
|
471
|
+
## 🤝 Contributing
|
|
81
472
|
|
|
82
|
-
|
|
473
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
474
|
+
|
|
475
|
+
### Development Setup
|
|
476
|
+
```bash
|
|
477
|
+
git clone https://github.com/amurgola/ResonantJs.git
|
|
478
|
+
cd ResonantJs
|
|
479
|
+
npm install
|
|
480
|
+
npm test
|
|
481
|
+
```
|
|
482
|
+
|
|
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
|
+
---
|
|
496
|
+
|
|
497
|
+
## 🙋♂️ Support & Community
|
|
498
|
+
|
|
499
|
+
- **Issues**: [GitHub Issues](https://github.com/amurgola/ResonantJs/issues)
|
|
500
|
+
- **Discussions**: [GitHub Discussions](https://github.com/amurgola/ResonantJs/discussions)
|
|
501
|
+
- **Documentation**: [Full API Documentation](https://github.com/amurgola/ResonantJs/wiki)
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
<div align="center">
|
|
506
|
+
|
|
507
|
+
**[⭐ 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)**
|
|
508
|
+
|
|
509
|
+
*Built with ❤️ by [Andrew Paul Murgola](https://github.com/amurgola)*
|
|
510
|
+
|
|
511
|
+
</div>
|