@sprlab/wccompiler 0.16.16 → 0.16.17

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
@@ -359,6 +359,32 @@ The source expression calls the signal (`items()`) to read the current array. Su
359
359
  <li each="item in items()" :key="item.id">{{item.name}}</li>
360
360
  ```
361
361
 
362
+ **Keyed Reconciliation with Full Reactivity:**
363
+
364
+ When using `:key`, the compiler optimizes rendering by reusing DOM nodes instead of destroying and recreating them. All dynamic bindings (text content, attributes, event handlers) are automatically updated when signals change, ensuring the UI always reflects the current data state.
365
+
366
+ ```html
367
+ <!-- UI updates immediately when item.active changes -->
368
+ <li each="item in items()" :key="item.id">
369
+ <span>{{ item.name }}</span>
370
+ <span>{{ item.active ? '✓ Active' : '✗ Inactive' }}</span>
371
+ <button @click="() => toggleActive(item.id)">Toggle</button>
372
+ </li>
373
+ ```
374
+
375
+ **Keyed Reconciliation with Full Reactivity:**
376
+
377
+ When using `:key`, the compiler optimizes rendering by reusing DOM nodes instead of destroying and recreating them. All dynamic bindings (text content, attributes, event handlers) are automatically updated when signals change, ensuring the UI always reflects the current data state.
378
+
379
+ ```html
380
+ <!-- UI updates immediately when item.active changes -->
381
+ <li each="item in items()" :key="item.id">
382
+ <span>{{ item.name }}</span>
383
+ <span>{{ item.active ? '✓ Active' : '✗ Inactive' }}</span>
384
+ <button @click="() => toggleActive(item.id)">Toggle</button>
385
+ </li>
386
+ ```
387
+
362
388
  Numeric ranges are also supported:
363
389
 
364
390
  ```html
@@ -406,6 +432,81 @@ Directives work inside `each` blocks — including conditionals and nested loops
406
432
  <div :style="{ color: textColor() }">...</div>
407
433
  ```
408
434
 
435
+ #### Class Directive (`:class`)
436
+
437
+ Supports multiple syntaxes for dynamic class binding:
438
+
439
+ **Object Syntax:**
440
+ ```html
441
+ <div :class="{ active: isActive(), 'text-large': isLarge() }">Content</div>
442
+ ```
443
+
444
+ **Ternary Expressions:**
445
+ ```html
446
+ <div :class="theme() === 'light' ? 'light-theme' : 'dark-theme'">Content</div>
447
+ ```
448
+
449
+ **Template Literals:**
450
+ ```html
451
+ <div :class="`${theme()}-theme ${size()}-size`">Content</div>
452
+ ```
453
+
454
+ **Mixed Static and Dynamic:**
455
+ ```html
456
+ <div class="base-class" :class="isActive() ? 'active' : ''">Content</div>
457
+ ```
458
+
459
+ All string literals are preserved correctly - signal names inside strings are NOT transformed.
460
+
461
+ #### Class Directive (`:class`)
462
+
463
+ Supports multiple syntaxes for dynamic class binding:
464
+
465
+ **Object Syntax:**
466
+ ```html
467
+ <div :class="{ active: isActive(), 'text-large': isLarge() }">Content</div>
468
+ ```
469
+
470
+ **Ternary Expressions:**
471
+ ```html
472
+ <div :class="theme() === 'light' ? 'light-theme' : 'dark-theme'">Content</div>
473
+ ```
474
+
475
+ **Template Literals:**
476
+ ```html
477
+ <div :class="`${theme()}-theme ${size()}-size`">Content</div>
478
+ ```
479
+
480
+ **Mixed Static and Dynamic:**
481
+ ```html
482
+ <div class="base-class" :class="isActive() ? 'active' : ''">Content</div>
483
+ ```
484
+
485
+ All string literals are preserved correctly - signal names inside strings are NOT transformed.
486
+
487
+ #### Style Directive (`:style`)
488
+
489
+ Supports single and multiple CSS properties:
490
+
491
+ **Single Property:**
492
+ ```html
493
+ <div :style="{ color: textColor() }">Text</div>
494
+ ```
495
+
496
+ **Multiple Properties:**
497
+ ```html
498
+ <div :style="{ color: textColor(), fontSize: fontSize() + 'px', backgroundColor: bgColor() }">
499
+ Styled Text
500
+ </div>
501
+ ```
502
+
503
+ **Kebab-case Properties:**
504
+ ```html
505
+ <div :style="{ 'font-size': size() + 'px', 'background-color': bgColor() }">Text</div>
506
+ ```
507
+
508
+ Object literal keys are preserved correctly - only values are transformed to reactive calls.
509
+
409
510
  ### Template Refs
410
511
 
411
512
  ```js
package/lib/codegen.js CHANGED
@@ -1788,10 +1788,15 @@ export function generateComponent(parseResult, options = {}) {
1788
1788
  lines.push(` __iter.forEach((${itemVar}, ${indexVar || '__idx'}) => {`);
1789
1789
  lines.push(` const __key = ${keyExpr};`);
1790
1790
  lines.push(' if (__oldMap.has(__key)) {');
1791
- lines.push(' const node = __oldMap.get(__key);');
1791
+ lines.push(' const oldNode = __oldMap.get(__key);');
1792
1792
 
1793
- // BUG-0012 FIX: Update all dynamic properties when reusing nodes
1794
- // Re-run setup to ensure bindings reflect current item data
1793
+ // BUG-0012 FIX: When reusing nodes, we must recreate them to get fresh effects
1794
+ // Old effects capture stale item references and fail on subsequent updates
1795
+ // Strategy: Remove old node, create fresh clone with new effects
1796
+ lines.push(' oldNode.remove();');
1797
+ lines.push(` const node = this.${vn}_tpl.content.cloneNode(true).firstChild;`);
1798
+
1799
+ // Setup bindings/events for the recreated node (uses 'node' variable)
1795
1800
  generateItemSetup(lines, forBlock, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet, methodNames);
1796
1801
 
1797
1802
  lines.push(' __newMap.set(__key, node);');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.16.16",
3
+ "version": "0.16.17",
4
4
  "description": "Zero-runtime compiler that transforms .wcc single-file components into native web components with signals-based reactivity",
5
5
  "type": "module",
6
6
  "exports": {