lume-js 0.4.1 โ†’ 1.0.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
@@ -2,826 +2,107 @@
2
2
 
3
3
  **Reactivity that follows web standards.**
4
4
 
5
- Minimal reactive state management using only standard JavaScript and HTML - no custom syntax, no build step required, no framework lock-in.
5
+ Minimal reactive state management using only standard JavaScript and HTML. No custom syntax, no build step required, no framework lock-in.
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
- [![Version](https://img.shields.io/badge/version-0.4.1-green.svg)](package.json)
8
+ [![Version](https://img.shields.io/badge/version-1.0.0-green.svg)](package.json)
9
+ [![Tests](https://img.shields.io/badge/tests-114%20passing-brightgreen.svg)](tests/)
10
+ [![Size](https://img.shields.io/badge/size-%3C2KB-blue.svg)](dist/)
9
11
 
10
12
  ## Why Lume.js?
11
13
 
12
- - ๐ŸŽฏ **Standards-Only** - Uses only `data-*` attributes and standard JavaScript
13
- - ๐Ÿ“ฆ **Tiny** - <2KB gzipped
14
- - โšก **Fast** - Direct DOM updates, no virtual DOM overhead
15
- - ๐Ÿ”ง **No Build Step** - Works directly in the browser
16
- - ๐ŸŽจ **No Lock-in** - Works with any library (GSAP, jQuery, D3, etc.)
17
- - โ™ฟ **Accessible** - HTML validators love it, screen readers work perfectly
18
- - ๐Ÿงน **Clean API** - Cleanup functions prevent memory leaks
19
14
 
20
- ### vs Other Libraries
15
+ > **Note:** The `repeat` addon is *experimental* in v1.0.0. Its API may evolve in future releases as it is refined to best fit Lume.js philosophy.
21
16
 
22
17
  | Feature | Lume.js | Alpine.js | Vue | React |
23
18
  |---------|---------|-----------|-----|-------|
24
- | Custom Syntax | โŒ No | โœ… `x-data`, `@click` | โœ… `v-bind`, `v-model` | โœ… JSX |
19
+ | Custom Syntax | โŒ No | โœ… `x-data` | โœ… `v-bind` | โœ… JSX |
25
20
  | Build Step | โŒ Optional | โŒ Optional | โš ๏ธ Recommended | โœ… Required |
26
21
  | Bundle Size | ~2KB | ~15KB | ~35KB | ~45KB |
27
22
  | HTML Validation | โœ… Pass | โš ๏ธ Warnings | โš ๏ธ Warnings | โŒ JSX |
28
- | Cleanup API | โœ… Yes | โš ๏ธ Limited | โœ… Yes | โœ… Yes |
29
23
 
30
24
  **Lume.js is essentially "Modern Knockout.js" - standards-only reactivity for 2025.**
31
25
 
32
- ๐Ÿ“– **New to the project?** Read [DESIGN_DECISIONS.md](DESIGN_DECISIONS.md) to understand our design philosophy and why certain choices were made.
33
-
34
26
  ---
35
27
 
36
28
  ## Installation
37
29
 
38
- ### Via npm
39
-
40
- ```bash
41
- npm install lume-js
42
- ```
43
-
44
- ### Via CDN
30
+ ### Via CDN (Recommended for simple projects)
45
31
 
46
32
  ```html
47
33
  <script type="module">
48
34
  import { state, bindDom, effect } from 'https://cdn.jsdelivr.net/npm/lume-js/src/index.js';
49
-
50
- // For addons:
51
- import { computed, watch } from 'https://cdn.jsdelivr.net/npm/lume-js/src/addons/index.js';
52
35
  </script>
53
36
  ```
54
37
 
55
- ---
56
-
57
- ## Quick Start
58
-
59
- ### Examples
60
-
61
- Run the live examples (including the new Todo app) with Vite:
62
-
63
- ```bash
64
- npm run dev
65
- ```
66
-
67
- Then open the Examples index (Vite will auto-open): `http://localhost:5173/examples/`
68
-
69
- **HTML:**
70
- ```html
71
- <div>
72
- <p>Count: <span data-bind="count"></span></p>
73
- <input data-bind="name" placeholder="Enter name">
74
- <p>Hello, <span data-bind="name"></span>!</p>
75
-
76
- <button id="increment">+</button>
77
- </div>
78
- ```
79
-
80
- **JavaScript:**
81
- ```javascript
82
- import { state, bindDom, effect } from 'lume-js';
83
-
84
- // Create reactive state
85
- const store = state({
86
- count: 0,
87
- name: 'World'
88
- });
89
-
90
- // Bind to DOM (updates on state changes)
91
- const cleanup = bindDom(document.body, store);
92
-
93
- // Auto-update document title when name changes
94
- const effectCleanup = effect(() => {
95
- document.title = `Hello, ${store.name}!`;
96
- });
97
-
98
- // Update state with standard JavaScript
99
- document.getElementById('increment').addEventListener('click', () => {
100
- store.count++;
101
- });
102
-
103
- // Cleanup when done (important!)
104
- window.addEventListener('beforeunload', () => {
105
- cleanup();
106
- effectCleanup();
107
- });
108
- ```
109
-
110
- That's it! No build step, no custom syntax, just HTML and JavaScript.
111
-
112
- ---
113
-
114
- ## Core API
115
-
116
- ### `state(object)`
117
-
118
- Creates a reactive state object using Proxy with automatic dependency tracking.
119
-
120
- ```javascript
121
- const store = state({
122
- count: 0,
123
- user: state({ // Nested state must be wrapped
124
- name: 'Alice',
125
- email: 'alice@example.com'
126
- })
127
- });
128
-
129
- // Update state
130
- store.count++;
131
- store.user.name = 'Bob';
132
- ```
133
-
134
- **Features:**
135
- - โœ… Automatic dependency tracking for effects
136
- - โœ… Per-state microtask batching for performance
137
- - โœ… Validates input (must be plain object)
138
- - โœ… Only triggers updates when value actually changes
139
- - โœ… Returns cleanup function from `$subscribe`
140
- - โœ… Deduplicates effect runs per flush cycle
141
-
142
- ### `effect(fn)`
143
-
144
- Creates an effect that automatically tracks dependencies and re-runs when they change.
145
-
146
- ```javascript
147
- const store = state({ count: 0, name: 'Alice' });
148
-
149
- const cleanup = effect(() => {
150
- // Only tracks 'count' (name is not accessed)
151
- console.log(`Count: ${store.count}`);
152
- });
153
-
154
- store.count = 5; // Effect re-runs
155
- store.name = 'Bob'; // Effect does NOT re-run
156
-
157
- cleanup(); // Stop the effect
158
- ```
159
-
160
- **Features:**
161
- - โœ… Automatic dependency collection (tracks what you actually access)
162
- - โœ… Dynamic dependencies (re-tracks on every execution)
163
- - โœ… Returns cleanup function
164
- - โœ… Prevents infinite recursion
165
- - โœ… Integrates with per-state batching (no global scheduler)
166
-
167
- **How it works:** Effects use `globalThis.__LUME_CURRENT_EFFECT__` to track which state properties are accessed during execution. When any tracked property changes, the effect is queued in that state's pending effects set and runs once in the next microtask.
168
-
169
- ### `bindDom(root, store, options?)`
170
-
171
- Binds reactive state to DOM elements with `data-bind` attributes.
172
-
173
- **Automatically waits for DOMContentLoaded** if the document is still loading, making it safe to call from anywhere (even in `<head>`).
174
-
175
- ```javascript
176
- // Default: Auto-waits for DOM (safe anywhere)
177
- const cleanup = bindDom(document.body, store);
178
-
179
- // Advanced: Force immediate binding (no auto-wait)
180
- const cleanup = bindDom(myElement, store, { immediate: true });
181
-
182
- // Later: cleanup all bindings
183
- cleanup();
184
- ```
185
-
186
- **Parameters:**
187
- - `root` (HTMLElement) - Root element to scan for `[data-bind]` attributes
188
- - `store` (Object) - Reactive state object
189
- - `options` (Object, optional)
190
- - `immediate` (Boolean) - Skip auto-wait, bind immediately. Default: `false`
191
-
192
- **Supports:**
193
- - โœ… Text content: `<span data-bind="count"></span>`
194
- - โœ… Input values: `<input data-bind="name">`
195
- - โœ… Textareas: `<textarea data-bind="bio"></textarea>`
196
- - โœ… Selects: `<select data-bind="theme"></select>`
197
- - โœ… Checkboxes: `<input type="checkbox" data-bind="enabled">`
198
- - โœ… Numbers: `<input type="number" data-bind="age">`
199
- - โœ… Radio buttons: `<input type="radio" data-bind="choice">`
200
- - โœ… Nested paths: `<span data-bind="user.name"></span>`
201
-
202
- **Multiple Checkboxes Pattern:**
203
-
204
- For multiple checkboxes, use nested state instead of arrays:
205
-
206
- ```javascript
207
- // โœ… Recommended: Nested state objects
208
- const store = state({
209
- tags: state({
210
- javascript: true,
211
- python: false,
212
- go: true
213
- })
214
- });
215
- ```
216
-
38
+ **Version Pinning:**
217
39
  ```html
218
- <input type="checkbox" data-bind="tags.javascript"> JavaScript
219
- <input type="checkbox" data-bind="tags.python"> Python
220
- <input type="checkbox" data-bind="tags.go"> Go
221
- ```
222
-
223
- Nested state is **more explicit and easier to validate** than array-based bindings. See [DESIGN_DECISIONS.md](DESIGN_DECISIONS.md#why-nested-state-for-multiple-checkboxes-instead-of-arrays) for the full rationale.
224
-
225
- **Features:**
226
- - โœ… Auto-waits for DOM if needed (no timing issues!)
227
- - โœ… Returns cleanup function
228
- - โœ… Better error messages with `[Lume.js]` prefix
229
- - โœ… Handles edge cases (empty bindings, invalid paths)
230
- - โœ… Two-way binding for form inputs
231
-
232
- ### `isReactive(obj)`
233
-
234
- Checks whether a value is a reactive proxy created by `state()`.
235
-
236
- ```javascript
237
- import { state, isReactive } from 'lume-js';
238
-
239
- const original = { count: 1 };
240
- const store = state(original);
241
-
242
- isReactive(store); // true
243
- isReactive(original); // false
244
- isReactive(null); // false
245
- ```
246
-
247
- **How it works:**
248
- Lume.js uses an internal `Symbol` checked via the Proxy `get` trap rather than mutating the proxy or storing external WeakSet state. Accessing `obj[REACTIVE_SYMBOL]` returns `true` only for reactive proxies, and the symbol is not enumerable or visible via `Object.getOwnPropertySymbols`.
249
-
250
- **Characteristics:**
251
- - โœ… Zero mutation of the proxy
252
- - โœ… Invisible to enumeration and reflection
253
- - โœ… Fast: single symbol identity check in the `get` path
254
- - โœ… Supports nested reactive states naturally
255
- - โœ… Skips tracking meta `$`-prefixed methods (e.g. `$subscribe`)
256
-
257
- **When to use:** Utility/debugging, conditional wrapping patterns like:
258
- ```javascript
259
- function ensureReactive(val) {
260
- return isReactive(val) ? val : state(val);
261
- }
262
- ```
263
-
264
- **Why Auto-Ready?**
265
-
266
- Works seamlessly regardless of script placement:
267
-
268
- ```html
269
- <!-- โœ… Works in <head> -->
270
40
  <script type="module">
271
- import { state, bindDom } from 'lume-js';
272
- const store = state({ count: 0 });
273
- bindDom(document.body, store); // Auto-waits for DOM!
41
+ import { state } from 'https://cdn.jsdelivr.net/npm/lume-js@1.0.0/src/index.js';
274
42
  </script>
275
-
276
- <!-- โœ… Works inline in <body> -->
277
- <body>
278
- <span data-bind="count"></span>
279
- <script type="module">
280
- // bindDom() waits for DOMContentLoaded automatically
281
- </script>
282
- </body>
283
-
284
- <!-- โœ… Works with defer -->
285
- <script type="module" defer>
286
- // Already loaded, executes immediately
287
- </script>
288
- ```
289
-
290
- **When to use `immediate: true`:**
291
-
292
- Rare scenarios where you're dynamically creating DOM or need precise control:
293
-
294
- ```javascript
295
- // Dynamic DOM injection
296
- const container = document.createElement('div');
297
- container.innerHTML = '<span data-bind="count"></span>';
298
- document.body.appendChild(container);
299
-
300
- // Bind immediately (DOM already exists)
301
- bindDom(container, store, { immediate: true });
302
- ```
303
-
304
- ### `$subscribe(key, callback)`
305
-
306
- Manually subscribe to state changes. Calls callback immediately with current value, then on every change.
307
-
308
- ```javascript
309
- const unsubscribe = store.$subscribe('count', (value) => {
310
- console.log('Count changed:', value);
311
-
312
- // Integrate with other libraries
313
- if (value > 10) {
314
- showNotification('Count is high!');
315
- }
316
- });
317
-
318
- // Cleanup
319
- unsubscribe();
320
43
  ```
321
44
 
322
- **Features:**
323
- - โœ… Returns unsubscribe function
324
- - โœ… Validates callback is a function
325
- - โœ… Calls immediately with current value (not batched)
326
- - โœ… Only notifies on actual value changes (via batching)
327
-
328
- ---
329
-
330
- ## Addons
331
-
332
- Lume.js provides optional addons for advanced reactivity patterns. Import from `lume-js/addons`.
333
-
334
- ### `computed(fn)`
335
-
336
- Creates a computed value that automatically updates when its dependencies change.
337
-
338
- ```javascript
339
- import { state, effect } from 'lume-js';
340
- import { computed } from 'lume-js/addons';
341
-
342
- const store = state({ count: 5 });
343
-
344
- const doubled = computed(() => store.count * 2);
345
- console.log(doubled.value); // 10
346
-
347
- store.count = 10;
348
- // After microtask:
349
- console.log(doubled.value); // 20 (auto-updated)
350
-
351
- // Subscribe to changes
352
- const unsub = doubled.subscribe(value => {
353
- console.log('Doubled:', value);
354
- });
355
-
356
- // Cleanup
357
- doubled.dispose();
358
- unsub();
359
- ```
360
-
361
- **Features:**
362
- - โœ… Automatic dependency tracking using `effect()`
363
- - โœ… Cached values (only recomputes when dependencies change)
364
- - โœ… Subscribe to changes with `.subscribe(callback)`
365
- - โœ… Cleanup with `.dispose()`
366
- - โœ… Error handling (sets to undefined on error)
367
-
368
- ### `watch(store, key, callback)`
369
-
370
- Alias for `$subscribe` - observes changes to a specific state key.
371
-
372
- ```javascript
373
- import { state } from 'lume-js';
374
- import { watch } from 'lume-js/addons';
375
-
376
- const store = state({ count: 0 });
377
-
378
- const unwatch = watch(store, 'count', (value) => {
379
- console.log('Count is now:', value);
380
- });
381
-
382
- // Cleanup
383
- unwatch();
384
- ```
45
+ ### Via NPM (Recommended for bundlers)
385
46
 
386
- **Note:** `watch()` is just a convenience wrapper around `store.$subscribe()`. Use whichever feels more natural.
387
-
388
- ---
389
-
390
- ## Choosing the Right Reactive Pattern
391
-
392
- Lume.js provides three ways to react to state changes. Here's when to use each:
393
-
394
- | Pattern | Use When | Pros | Cons |
395
- |---------|----------|------|------|
396
- | **`bindDom()`** | Syncing state โ†” DOM | Zero code, declarative HTML | DOM-only, no custom logic |
397
- | **`$subscribe()`** | Listening to specific keys | Explicit, immediate, simple | Manual dependency tracking |
398
- | **`effect()`** | Auto-run code on any state access | Automatic dependencies, concise | Microtask delay, can infinite loop |
399
- | **`computed()`** | Deriving values from state | Cached, automatic recompute | Addon import, slight overhead |
400
-
401
- **Quick Decision Tree:**
402
-
403
- ```
404
- Need to update DOM?
405
- โ”œโ”€ Yes, just sync form/text โ†’ Use bindDom()
406
- โ””โ”€ No, custom logic needed
407
- โ”œโ”€ Watch single key? โ†’ Use $subscribe()
408
- โ”œโ”€ Watch multiple keys dynamically? โ†’ Use effect()
409
- โ””โ”€ Derive a value? โ†’ Use computed()
47
+ ```bash
48
+ npm install lume-js
410
49
  ```
411
50
 
412
- **Examples:**
413
-
414
51
  ```javascript
415
- // 1. bindDom - Zero code DOM sync
416
- <span data-bind="count"></span>
417
- bindDom(document.body, store);
418
-
419
- // 2. $subscribe - Specific key, immediate execution
420
- store.$subscribe('count', (val) => {
421
- if (val > 10) showNotification('High!');
422
- });
423
-
424
- // 3. effect - Multiple keys, automatic tracking
425
- effect(() => {
426
- document.title = `${store.user.name}: ${store.count}`;
427
- // Tracks both user.name and count automatically
428
- });
429
-
430
- // 4. computed - Derive cached value
431
- import { computed } from 'lume-js/addons';
432
- const total = computed(() => store.items.reduce((sum, i) => sum + i.price, 0));
433
- console.log(total.value);
52
+ import { state, bindDom } from 'lume-js';
434
53
  ```
435
54
 
436
- **Gotchas:**
437
-
438
- - โš ๏ธ **effect()** runs in next microtask (~0.002ms delay). Use `$subscribe()` for immediate execution.
439
- - โš ๏ธ **Don't mutate tracked state inside effect** - causes infinite loops:
440
- ```javascript
441
- // โŒ BAD - Infinite loop
442
- effect(() => {
443
- store.count++; // Writes to what it reads!
444
- });
445
-
446
- // โœ… GOOD - Read-only or separate keys
447
- effect(() => {
448
- store.displayCount = store.count * 2; // Different keys
449
- });
450
- ```
55
+ ### Browser Support
56
+ Works in all modern browsers (Chrome 49+, Firefox 18+, Safari 10+, Edge 79+). **IE11 is NOT supported.**
451
57
 
452
58
  ---
453
59
 
454
- ## Examples
455
-
456
- ### Basic Counter
457
-
458
- ```javascript
459
- const store = state({ count: 0 });
460
- const cleanup = bindDom(document.body, store);
461
-
462
- document.getElementById('inc').addEventListener('click', () => {
463
- store.count++;
464
- });
465
-
466
- // Cleanup on unmount
467
- window.addEventListener('beforeunload', () => cleanup());
468
- ```
469
-
470
- ```html
471
- <p>Count: <span data-bind="count"></span></p>
472
- <button id="inc">Increment</button>
473
- ```
474
-
475
- ### Form Handling with Validation
476
-
477
- ```javascript
478
- const form = state({
479
- email: '',
480
- age: 25,
481
- theme: 'light',
482
- errors: {}
483
- });
484
-
485
- const cleanup = bindDom(document.querySelector('form'), form);
486
-
487
- // Validate on change
488
- const unsubEmail = form.$subscribe('email', (value) => {
489
- const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
490
- form.errors = {
491
- ...form.errors,
492
- email: isValid ? '' : 'Invalid email'
493
- };
494
- });
495
-
496
- // Cleanup
497
- window.addEventListener('beforeunload', () => {
498
- cleanup();
499
- unsubEmail();
500
- });
501
- ```
502
-
503
- ```html
504
- <form>
505
- <input type="email" data-bind="email">
506
- <span data-bind="errors.email" style="color: red;"></span>
507
-
508
- <input type="number" data-bind="age">
509
-
510
- <select data-bind="theme">
511
- <option value="light">Light</option>
512
- <option value="dark">Dark</option>
513
- </select>
514
- </form>
515
- ```
516
-
517
- ### Nested State
518
-
519
- ```javascript
520
- const store = state({
521
- user: state({
522
- profile: state({
523
- name: 'Alice',
524
- bio: 'Developer'
525
- }),
526
- settings: state({
527
- notifications: true
528
- })
529
- })
530
- });
531
-
532
- const cleanup = bindDom(document.body, store);
533
-
534
- // Subscribe to nested changes
535
- const unsub = store.user.profile.$subscribe('name', (name) => {
536
- console.log('Profile name changed:', name);
537
- });
538
-
539
- // Cleanup
540
- window.addEventListener('beforeunload', () => {
541
- cleanup();
542
- unsub();
543
- });
544
- ```
60
+ ## Quick Start
545
61
 
62
+ **HTML:**
546
63
  ```html
547
- <input data-bind="user.profile.name">
548
- <textarea data-bind="user.profile.bio"></textarea>
549
- <input type="checkbox" data-bind="user.settings.notifications">
550
- ```
551
-
552
- ### Using Effects for Auto-Updates
553
-
554
- ```javascript
555
- import { state, effect } from 'lume-js';
556
-
557
- const store = state({
558
- firstName: 'Alice',
559
- lastName: 'Smith'
560
- });
561
-
562
- // Auto-update title when name changes
563
- effect(() => {
564
- document.title = `${store.firstName} ${store.lastName}`;
565
- });
566
-
567
- store.firstName = 'Bob';
568
- // Title automatically updates to "Bob Smith" in next microtask
569
- ```
570
-
571
- ### Computed Values
572
-
573
- ```javascript
574
- import { state } from 'lume-js';
575
- import { computed } from 'lume-js/addons';
576
-
577
- const cart = state({
578
- items: state([
579
- state({ price: 10, quantity: 2 }),
580
- state({ price: 15, quantity: 1 })
581
- ])
582
- });
583
-
584
- const total = computed(() => {
585
- return cart.items.reduce((sum, item) =>
586
- sum + (item.price * item.quantity), 0
587
- );
588
- });
589
-
590
- console.log(total.value); // 35
591
-
592
- cart.items[0].quantity = 3;
593
- // After microtask:
594
- console.log(total.value); // 45
595
- ```
596
-
597
- ### Integration with GSAP
598
-
599
- ```javascript
600
- import gsap from 'gsap';
601
- import { state, effect } from 'lume-js';
602
-
603
- const ui = state({ x: 0, y: 0 });
604
-
605
- // Use effect for automatic animation updates
606
- effect(() => {
607
- gsap.to('.box', { x: ui.x, y: ui.y, duration: 0.5 });
608
- });
609
-
610
- // Or use $subscribe
611
- const unsubX = ui.$subscribe('x', (value) => {
612
- gsap.to('.box', { x: value, duration: 0.5 });
613
- });
614
-
615
- // Now ui.x = 100 triggers smooth animation
616
-
617
- // Cleanup
618
- window.addEventListener('beforeunload', () => unsubX());
619
- ```
620
-
621
- ### Cleanup Pattern (Important!)
622
-
623
- ```javascript
624
- import { state, effect, bindDom } from 'lume-js';
625
- import { computed } from 'lume-js/addons';
626
-
627
- const store = state({ data: [] });
628
- const cleanup = bindDom(root, store);
629
-
630
- const unsub1 = store.$subscribe('data', handleData);
631
- const unsub2 = store.$subscribe('status', handleStatus);
632
-
633
- const effectCleanup = effect(() => {
634
- console.log('Data length:', store.data.length);
635
- });
636
-
637
- const total = computed(() => store.data.length * 2);
638
-
639
- // Cleanup when component unmounts
640
- function destroy() {
641
- cleanup(); // Remove DOM bindings
642
- unsub1(); // Remove subscription 1
643
- unsub2(); // Remove subscription 2
644
- effectCleanup(); // Stop effect
645
- total.dispose(); // Stop computed
646
- }
647
-
648
- // For SPA frameworks
649
- onUnmount(destroy);
650
-
651
- // For vanilla JS
652
- window.addEventListener('beforeunload', destroy);
64
+ <div>
65
+ <h1>Hello, <span data-bind="name"></span>!</h1>
66
+ <input data-bind="name" placeholder="Enter your name">
67
+ </div>
653
68
  ```
654
69
 
655
- ---
656
-
657
- ## Philosophy
658
-
659
- ### Standards-Only
660
- - Uses only `data-*` attributes (HTML5 standard)
661
- - Uses only standard JavaScript APIs (Proxy, addEventListener)
662
- - No custom syntax that breaks validators
663
- - Works with any tool/library
664
-
665
- ### No Artificial Limitations
70
+ **JavaScript:**
666
71
  ```javascript
667
- // โœ… Use with jQuery
668
- $('.modal').fadeIn();
669
- store.modalOpen = true;
72
+ import { state, bindDom } from 'lume-js';
670
73
 
671
- // โœ… Use with GSAP
672
- gsap.to(el, { x: store.position });
74
+ // 1. Create state
75
+ const store = state({ name: 'World' });
673
76
 
674
- // โœ… Use with any router
675
- router.on('/home', () => store.route = 'home');
676
-
677
- // โœ… Mix with vanilla JS
678
- document.addEventListener('click', () => store.clicks++);
77
+ // 2. Bind to DOM
78
+ bindDom(document.body, store);
679
79
  ```
680
80
 
681
- **Lume.js doesn't hijack your architecture - it enhances it.**
682
-
683
- ### Progressive Enhancement
684
-
685
- ```html
686
- <!-- Works without JavaScript (server-rendered) -->
687
- <form action="/submit" method="POST">
688
- <input name="email" value="alice@example.com">
689
- <button type="submit">Save</button>
690
- </form>
691
-
692
- <script type="module">
693
- // Enhanced when JS loads
694
- import { state, bindDom } from 'lume-js';
695
-
696
- const form = state({ email: 'alice@example.com' });
697
- const cleanup = bindDom(document.querySelector('form'), form);
698
-
699
- // Prevent default, use AJAX
700
- document.querySelector('form').addEventListener('submit', async (e) => {
701
- e.preventDefault();
702
- await fetch('/submit', {
703
- method: 'POST',
704
- body: JSON.stringify({ email: form.email })
705
- });
706
- });
707
-
708
- window.addEventListener('beforeunload', () => cleanup());
709
- </script>
710
- ```
81
+ **What just happened?**
82
+ 1. **`state()`** created a reactive object.
83
+ 2. **`bindDom()`** scanned the document for `data-bind="name"`.
84
+ 3. It set up a two-way binding: typing in the input updates the state, and the state updates the text.
711
85
 
712
86
  ---
713
87
 
714
- ## Who Should Use Lume.js?
88
+ ## Documentation
715
89
 
716
- ### โœ… Perfect For:
717
- - WordPress/Shopify theme developers
718
- - Accessibility-focused teams (government, healthcare, education)
719
- - Legacy codebases that can't do full rewrites
720
- - Developers who hate learning framework-specific syntax
721
- - Progressive enhancement advocates
722
- - Projects requiring HTML validation
723
- - Adding reactivity to server-rendered apps
90
+ Full documentation is available in the [docs/](docs/) directory:
724
91
 
725
- ### โš ๏ธ Consider Alternatives:
726
- - **Complex SPAs** โ†’ Use React, Vue, or Svelte
727
- - **Need routing/SSR** โ†’ Use Next.js, Nuxt, or SvelteKit
728
- - **Prefer terse syntax** โ†’ Use Alpine.js (if custom syntax is okay)
729
-
730
- ---
731
-
732
- ## What's New
733
-
734
- ### Core Features
735
- - โœ… **Automatic dependency tracking** - `effect()` automatically tracks which state properties are accessed
736
- - โœ… **Per-state batching** - Each state object maintains its own microtask flush for optimal performance
737
- - โœ… **Effect deduplication** - Effects only run once per flush cycle, even if multiple dependencies change
738
- - โœ… **TypeScript support** - Full type definitions in `index.d.ts`
739
- - โœ… **Cleanup functions** - All reactive APIs return cleanup/unsubscribe functions
740
-
741
- ### Addons
742
- - โœ… **`computed()`** - Memoized computed values with automatic dependency tracking
743
- - โœ… **`watch()`** - Convenience alias for `$subscribe`
744
-
745
- ### API Design
746
- - โœ… `state()` - Create reactive state with automatic tracking support
747
- - โœ… `effect()` - Core reactivity primitive (automatic dependency collection)
748
- - โœ… `bindDom()` - DOM binding with two-way sync for form inputs
749
- - โœ… `$subscribe()` - Manual subscriptions (calls immediately with current value)
750
- - โœ… All functions return cleanup/unsubscribe functions
751
- - โœ… Better error handling with `[Lume.js]` prefix
752
- - โœ… Input validation on all public APIs
753
-
754
- ---
755
-
756
- ## Browser Support
757
-
758
- - Chrome/Edge 49+
759
- - Firefox 18+
760
- - Safari 10+
761
- - No IE11 (Proxy can't be polyfilled)
762
-
763
- **Basically: Modern browsers with ES6 Proxy support.**
764
-
765
- ---
766
-
767
- ## Testing
768
-
769
- Lume.js uses [Vitest](https://vitest.dev) with a jsdom environment. The test suite mirrors the source tree: files under `tests/core/**` map to `src/core/**`, and `tests/addons/**` map to `src/addons/**`.
770
-
771
- ```bash
772
- # Run tests
773
- npm test
774
-
775
- # Watch mode
776
- npm run test:watch
777
-
778
- # Coverage with HTML report in ./coverage
779
- npm run coverage
780
- ```
781
-
782
- **Import alias:** Tests use an alias so you can import from `src/...` without relative `../../` paths.
783
-
784
- ```js
785
- // vitest.config.js
786
- resolve: {
787
- alias: {
788
- src: fileURLToPath(new URL('./src', import.meta.url))
789
- }
790
- }
791
- ```
792
-
793
- **Current coverage:**
794
- - 100% statements, functions, and lines
795
- - 100% branches (including edge-case paths)
796
- - 67 tests covering core behavior, addons, inputs (text/checkbox/radio/number/range/select/textarea), nested state, reactive identity, and cleanup semantics
92
+ - **[Tutorial: Build a Todo App](docs/tutorials/build-todo-app.md)**
93
+ - **[Tutorial: Build Tic-Tac-Toe](docs/tutorials/build-tic-tac-toe.md)**
94
+ - **[Working with Arrays](docs/tutorials/working-with-arrays.md)**
95
+ - **API Reference**
96
+ - [Core (state, bindDom, effect)](docs/api/core/state.md)
97
+ - [Addons (computed, repeat)](docs/api/addons/computed.md)
98
+ - **[Design Philosophy](docs/design/design-decisions.md)**
797
99
 
798
100
  ---
799
101
 
800
102
  ## Contributing
801
103
 
802
- We welcome contributions! Please:
803
-
804
- 1. **Focus on:** Examples, documentation, bug fixes, performance
805
- 2. **Avoid:** Adding core features without discussion (keep it minimal!)
806
- 3. **Read:** [DESIGN_DECISIONS.md](DESIGN_DECISIONS.md) to understand our philosophy and why certain choices were made
807
- 4. **Propose alternatives:** If you think a design decision should be reconsidered, open an issue with your reasoning
808
-
809
- Before suggesting new features, check if they align with Lume's core principles: standards-only, minimal API, no build step required.
810
-
811
- ---
104
+ We welcome contributions! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details.
812
105
 
813
106
  ## License
814
107
 
815
- MIT ยฉ Sathvik C
816
-
817
- ---
818
-
819
- ## Inspiration
820
-
821
- Lume.js is inspired by:
822
- - **Knockout.js** - The original `data-bind` approach
823
- - **Alpine.js** - Minimal, HTML-first philosophy
824
- - **Go** - Simplicity and explicit design
825
- - **The Web Platform** - Standards over abstractions
826
-
827
- **Built for developers who want reactivity without the framework tax.**
108
+ MIT ยฉ [Sathvik C](https://github.com/sathvikc)