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 CHANGED
@@ -1,76 +1,70 @@
1
- # ResonantJs ๐Ÿš€
1
+ # ResonantJs
2
2
 
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 โ€ข ~11.5KB 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 - 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
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
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
- ### Hello World Example
35
+ ~18 KB minified. Zero dependencies.
37
36
 
38
- ```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>
56
- ```
37
+ ---
38
+
39
+ ## Why ResonantJs?
57
40
 
58
- That's it! Your counter will automatically update the DOM and persist to localStorage.
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
- ## ๐Ÿ“– Core Concepts
52
+ ## Quick Tour
63
53
 
64
- ### 1. **Data Binding** (`res`)
65
- Bind HTML elements directly to your JavaScript variables:
54
+ ### Bind a variable
66
55
 
67
56
  ```html
68
- <span res="username"></span> <!-- Simple variable -->
69
- <div res="user.profile.name"></div> <!-- Nested object property -->
57
+ <h1>Hello, <span res="name"></span></h1>
70
58
  ```
71
59
 
72
- ### 2. **Object Properties** (`res-prop`)
73
- Bind to specific properties within objects:
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
- ### 3. **Array Rendering**
83
- Automatically render arrays with template-based elements:
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="todoItems">
88
+ <li res="tasks">
88
89
  <span res-prop="title"></span>
89
- <button res-onclick="removeItem(item)">Delete</button>
90
+ <button res-onclick-remove="id">x</button>
90
91
  </li>
91
92
  </ul>
92
93
  ```
93
94
 
94
- ### 4. **Conditional Display** (`res-display`)
95
- Show/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
+ ]);
96
100
 
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
+ tasks.push({ id: 3, title: 'Profit' }); // new <li> appears
102
+ tasks[0].title = 'Done!'; // only that <li> re-renders
101
103
  ```
102
104
 
103
- ### 5. **Dynamic Styling** (`res-style`)
104
- Apply conditional CSS classes and styles:
105
+ ### Conditional display
105
106
 
106
107
  ```html
107
- <div res-style="task.completed ? 'completed' : 'pending'">Task</div>
108
- <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>
109
110
  ```
110
111
 
111
- ### 6. **Event Handling** (`res-onclick`)
112
- Bind click events with context:
112
+ ### Dynamic classes
113
113
 
114
114
  ```html
115
- <button res-onclick="editTask(item)">Edit</button>
116
- <button res-onclick-remove="true">Delete Item</button>
115
+ <span res-prop="title" res-style="done ? 'completed' : ''"></span>
117
116
  ```
118
117
 
119
- ### 7. **Computed Properties**
120
- Create reactive derived values that automatically update:
118
+ ### Computed properties
121
119
 
122
- ```javascript
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
- // Computed property automatically updates when dependencies change
128
- resonant.computed('fullName', () => {
129
- return firstName + ' ' + lastName;
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
- <span res="fullName"></span> <!-- Automatically shows "John Doe" -->
131
+ Total: $<span res="total"></span>
135
132
  ```
136
133
 
137
- ### 8. **Input Binding**
138
- Two-way data binding for form elements:
134
+ ### Two-way input binding
139
135
 
140
136
  ```html
141
- <input type="text" res="user.name" />
137
+ <input type="text" res="name" />
142
138
  <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>
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
- // 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
144
+ ```js
145
+ res.add('theme', 'light', true); // third arg = persist to localStorage
146
+ theme = 'dark'; // saved automatically
171
147
  ```
172
148
 
173
- ### **Event Callbacks**
174
- React to data changes with custom logic:
149
+ ### Bind existing variables
175
150
 
176
- ```javascript
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
- resonant.addCallback('user', (newValue, item, action) => {
184
- if (action === 'modified') {
185
- showNotification('Profile updated');
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
- ### **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);
159
+ ### Event handling
197
160
 
198
- // Changes are automatically saved
199
- 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 -->
200
164
  ```
201
165
 
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
166
+ ### Callbacks
216
167
 
217
- // Chain computed properties
218
- resonant.computed('greeting', () => {
219
- return 'Hello, ' + fullName + '!';
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
- ## ๐Ÿ—๏ธ Real-World Examples
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
- <div>
242
- <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" />
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="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>
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 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
- });
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
- 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();
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
- ### 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>
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
- ## ๐Ÿ“š 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
- });
229
+ ## API Reference
359
230
 
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
- ```
231
+ ### JavaScript
364
232
 
365
- ### **Computed Properties**
366
- Create reactive calculated values that automatically update when dependencies change:
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
- ```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
- ]);
241
+ ### HTML Attributes
375
242
 
376
- // Computed properties automatically recalculate when 'tasks' changes
377
- resonant.computed('totalTasks', () => {
378
- return tasks.length;
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
- resonant.computed('completedTasks', () => {
382
- return tasks.filter(t => t.completed).length;
383
- });
252
+ ### Array Methods
384
253
 
385
- resonant.computed('completionRate', () => {
386
- return totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
387
- });
254
+ Reactive arrays support all standard methods plus:
388
255
 
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);
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
- resonant.computed('subtotal', () => {
397
- return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
398
- });
264
+ ### Callback Actions
399
265
 
400
- resonant.computed('tax', () => {
401
- return subtotal * taxRate;
402
- });
266
+ `added` `removed` `modified` `updated` `filtered`
403
267
 
404
- resonant.computed('total', () => {
405
- return subtotal + tax;
406
- });
407
- ```
268
+ ---
408
269
 
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
- ```
270
+ ## Performance
415
271
 
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
- ```
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
- ## ๐ŸŽจ Examples & Demos
279
+ ## Browser Support
442
280
 
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.
281
+ Chrome 60+ / Firefox 55+ / Safari 12+ / Edge 79+ / Mobile browsers
451
282
 
452
283
  ---
453
284
 
454
- ## ๐Ÿš€ Performance & Browser Support
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
- ### **Browser Support**
463
- - โœ… Chrome 60+
464
- - โœ… Firefox 55+
465
- - โœ… Safari 12+
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
- ## ๐Ÿค Contributing
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
- ## ๐Ÿ™‹โ€โ™‚๏ธ Support & Community
306
+ ## License
498
307
 
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)
308
+ MIT -- see [LICENSE](LICENSE).
502
309
 
503
310
  ---
504
311
 
505
312
  <div align="center">
506
313
 
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)**
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 with โค๏ธ by [Andrew Paul Murgola](https://github.com/amurgola)*
316
+ *Built by [Andrew Paul Murgola](https://github.com/amurgola)*
510
317
 
511
318
  </div>