@sprlab/wccompiler 0.5.12 → 0.5.14
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/lib/codegen.js +319 -0
- package/lib/tree-walker.js +27 -3
- package/lib/types.js +2 -0
- package/package.json +1 -1
package/lib/codegen.js
CHANGED
|
@@ -479,6 +479,325 @@ function generateItemSetup(lines, forBlock, itemVar, indexVar, propNames, signal
|
|
|
479
479
|
lines.push(`${indent} }`);
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
|
+
|
|
483
|
+
// Nested each directives (forBlocks)
|
|
484
|
+
for (const innerFor of (forBlock.forBlocks || [])) {
|
|
485
|
+
const innerVn = innerFor.varName;
|
|
486
|
+
const innerItemVar = innerFor.itemVar;
|
|
487
|
+
const innerIndexVar = innerFor.indexVar;
|
|
488
|
+
const innerSource = innerFor.source;
|
|
489
|
+
const innerKeyExpr = innerFor.keyExpr;
|
|
490
|
+
|
|
491
|
+
// Build excludeSet that includes BOTH outer and inner loop variables
|
|
492
|
+
// so transformForExpr does not rewrite them as signals
|
|
493
|
+
const outerExcludeVars = [itemVar];
|
|
494
|
+
if (indexVar) outerExcludeVars.push(indexVar);
|
|
495
|
+
const innerExcludeVars = [innerItemVar];
|
|
496
|
+
if (innerIndexVar) innerExcludeVars.push(innerIndexVar);
|
|
497
|
+
|
|
498
|
+
// Create inner template element
|
|
499
|
+
lines.push(`${indent} const ${innerVn}_tpl = document.createElement('template');`);
|
|
500
|
+
lines.push(`${indent} ${innerVn}_tpl.innerHTML = \`${innerFor.templateHtml}\`;`);
|
|
501
|
+
|
|
502
|
+
// Find inner anchor comment in the cloned outer item node
|
|
503
|
+
lines.push(`${indent} const ${innerVn}_anchor = ${pathExpr(innerFor.anchorPath, 'node')};`);
|
|
504
|
+
|
|
505
|
+
// Transform the inner source expression (may reference outer item var)
|
|
506
|
+
const innerSourceExpr = transformForExpr(innerSource, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
507
|
+
|
|
508
|
+
// Determine if inner source is static (only references outer loop vars)
|
|
509
|
+
const innerSourceIsStatic = isStaticForExpr(innerSource, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
510
|
+
|
|
511
|
+
if (innerKeyExpr) {
|
|
512
|
+
// ── Keyed reconciliation for nested each ──
|
|
513
|
+
lines.push(`${indent} const ${innerVn}_source = ${innerSourceIsStatic ? innerSource : innerSourceExpr};`);
|
|
514
|
+
lines.push(`${indent} const ${innerVn}_iter = typeof ${innerVn}_source === 'number'`);
|
|
515
|
+
lines.push(`${indent} ? Array.from({ length: ${innerVn}_source }, (_, i) => i + 1)`);
|
|
516
|
+
lines.push(`${indent} : (${innerVn}_source || []);`);
|
|
517
|
+
lines.push(`${indent} const ${innerVn}_newNodes = [];`);
|
|
518
|
+
lines.push(`${indent} ${innerVn}_iter.forEach((${innerItemVar}, ${innerIndexVar || '__idx'}) => {`);
|
|
519
|
+
lines.push(`${indent} const __key = ${innerKeyExpr};`);
|
|
520
|
+
lines.push(`${indent} const clone = ${innerVn}_tpl.content.cloneNode(true);`);
|
|
521
|
+
lines.push(`${indent} const innerNode = clone.firstChild;`);
|
|
522
|
+
|
|
523
|
+
// Generate inner item bindings with combined excludeSet
|
|
524
|
+
generateNestedItemSetup(lines, innerFor, itemVar, indexVar, innerItemVar, innerIndexVar, propNames, signalNamesSet, computedNamesSet, indent + ' ');
|
|
525
|
+
|
|
526
|
+
lines.push(`${indent} ${innerVn}_newNodes.push(innerNode);`);
|
|
527
|
+
lines.push(`${indent} });`);
|
|
528
|
+
lines.push(`${indent} for (const n of ${innerVn}_newNodes) { ${innerVn}_anchor.parentNode.insertBefore(n, ${innerVn}_anchor); }`);
|
|
529
|
+
} else {
|
|
530
|
+
// ── Non-keyed nested each: iterate and clone ──
|
|
531
|
+
lines.push(`${indent} const ${innerVn}_source = ${innerSourceIsStatic ? innerSource : innerSourceExpr};`);
|
|
532
|
+
lines.push(`${indent} const ${innerVn}_iter = typeof ${innerVn}_source === 'number'`);
|
|
533
|
+
lines.push(`${indent} ? Array.from({ length: ${innerVn}_source }, (_, i) => i + 1)`);
|
|
534
|
+
lines.push(`${indent} : (${innerVn}_source || []);`);
|
|
535
|
+
lines.push(`${indent} ${innerVn}_iter.forEach((${innerItemVar}, ${innerIndexVar || '__idx'}) => {`);
|
|
536
|
+
lines.push(`${indent} const clone = ${innerVn}_tpl.content.cloneNode(true);`);
|
|
537
|
+
lines.push(`${indent} const innerNode = clone.firstChild;`);
|
|
538
|
+
|
|
539
|
+
// Generate inner item bindings with combined excludeSet
|
|
540
|
+
generateNestedItemSetup(lines, innerFor, itemVar, indexVar, innerItemVar, innerIndexVar, propNames, signalNamesSet, computedNamesSet, indent + ' ');
|
|
541
|
+
|
|
542
|
+
lines.push(`${indent} ${innerVn}_anchor.parentNode.insertBefore(innerNode, ${innerVn}_anchor);`);
|
|
543
|
+
lines.push(`${indent} });`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Nested if/else-if/else chains (ifBlocks)
|
|
548
|
+
for (const ifBlock of (forBlock.ifBlocks || [])) {
|
|
549
|
+
const vn = ifBlock.varName;
|
|
550
|
+
const branches = ifBlock.branches;
|
|
551
|
+
|
|
552
|
+
// 3.1: Create template elements for each branch
|
|
553
|
+
for (let i = 0; i < branches.length; i++) {
|
|
554
|
+
const branch = branches[i];
|
|
555
|
+
lines.push(`${indent} const ${vn}_t${i} = document.createElement('template');`);
|
|
556
|
+
lines.push(`${indent} ${vn}_t${i}.innerHTML = \`${branch.templateHtml}\`;`);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// 3.1: Find anchor comment in the cloned node
|
|
560
|
+
lines.push(`${indent} const ${vn}_anchor = ${pathExpr(ifBlock.anchorPath, 'node')};`);
|
|
561
|
+
|
|
562
|
+
// 3.2: Generate per-item conditional evaluation (static, not reactive)
|
|
563
|
+
lines.push(`${indent} let ${vn}_branch = null;`);
|
|
564
|
+
for (let i = 0; i < branches.length; i++) {
|
|
565
|
+
const branch = branches[i];
|
|
566
|
+
if (branch.type === 'if') {
|
|
567
|
+
lines.push(`${indent} if (${branch.expression}) { ${vn}_branch = ${i}; }`);
|
|
568
|
+
} else if (branch.type === 'else-if') {
|
|
569
|
+
lines.push(`${indent} else if (${branch.expression}) { ${vn}_branch = ${i}; }`);
|
|
570
|
+
} else {
|
|
571
|
+
// else
|
|
572
|
+
lines.push(`${indent} else { ${vn}_branch = ${i}; }`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// 3.3: Insert only the matching branch node and apply branch bindings/events/show/attr/model
|
|
577
|
+
lines.push(`${indent} if (${vn}_branch !== null) {`);
|
|
578
|
+
const tplArray = branches.map((_, i) => `${vn}_t${i}`).join(', ');
|
|
579
|
+
lines.push(`${indent} const ${vn}_tpl = [${tplArray}][${vn}_branch];`);
|
|
580
|
+
lines.push(`${indent} const ${vn}_clone = ${vn}_tpl.content.cloneNode(true);`);
|
|
581
|
+
lines.push(`${indent} const ${vn}_node = ${vn}_clone.firstChild;`);
|
|
582
|
+
lines.push(`${indent} ${vn}_anchor.parentNode.insertBefore(${vn}_node, ${vn}_anchor);`);
|
|
583
|
+
|
|
584
|
+
// Apply branch bindings/events/show/attr/model using the outer loop's item variable
|
|
585
|
+
const hasSetup = branches.some(b =>
|
|
586
|
+
(b.bindings && b.bindings.length > 0) ||
|
|
587
|
+
(b.events && b.events.length > 0) ||
|
|
588
|
+
(b.showBindings && b.showBindings.length > 0) ||
|
|
589
|
+
(b.attrBindings && b.attrBindings.length > 0) ||
|
|
590
|
+
(b.modelBindings && b.modelBindings.length > 0)
|
|
591
|
+
);
|
|
592
|
+
if (hasSetup) {
|
|
593
|
+
// Generate per-branch setup inline (static evaluation using item variable)
|
|
594
|
+
for (let i = 0; i < branches.length; i++) {
|
|
595
|
+
const branch = branches[i];
|
|
596
|
+
const hasBranchSetup =
|
|
597
|
+
(branch.bindings && branch.bindings.length > 0) ||
|
|
598
|
+
(branch.events && branch.events.length > 0) ||
|
|
599
|
+
(branch.showBindings && branch.showBindings.length > 0) ||
|
|
600
|
+
(branch.attrBindings && branch.attrBindings.length > 0) ||
|
|
601
|
+
(branch.modelBindings && branch.modelBindings.length > 0);
|
|
602
|
+
if (!hasBranchSetup) continue;
|
|
603
|
+
|
|
604
|
+
const keyword = i === 0 ? 'if' : 'else if';
|
|
605
|
+
lines.push(`${indent} ${keyword} (${vn}_branch === ${i}) {`);
|
|
606
|
+
|
|
607
|
+
// Bindings (static: use item var directly)
|
|
608
|
+
for (const b of branch.bindings) {
|
|
609
|
+
const nodeRef = pathExpr(b.path, `${vn}_node`);
|
|
610
|
+
if (isStaticForBinding(b.name, itemVar, indexVar)) {
|
|
611
|
+
lines.push(`${indent} ${nodeRef}.textContent = ${b.name} ?? '';`);
|
|
612
|
+
} else {
|
|
613
|
+
const expr = transformForExpr(b.name, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
614
|
+
lines.push(`${indent} __effect(() => { ${nodeRef}.textContent = ${expr} ?? ''; });`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Events
|
|
619
|
+
for (const e of branch.events) {
|
|
620
|
+
const nodeRef = pathExpr(e.path, `${vn}_node`);
|
|
621
|
+
const handlerExpr = generateForEventHandler(e.handler, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
622
|
+
lines.push(`${indent} ${nodeRef}.addEventListener('${e.event}', ${handlerExpr});`);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Show bindings
|
|
626
|
+
for (const sb of (branch.showBindings || [])) {
|
|
627
|
+
const nodeRef = pathExpr(sb.path, `${vn}_node`);
|
|
628
|
+
if (isStaticForExpr(sb.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet)) {
|
|
629
|
+
lines.push(`${indent} ${nodeRef}.style.display = (${sb.expression}) ? '' : 'none';`);
|
|
630
|
+
} else {
|
|
631
|
+
const expr = transformForExpr(sb.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
632
|
+
lines.push(`${indent} __effect(() => { ${nodeRef}.style.display = (${expr}) ? '' : 'none'; });`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Attr bindings
|
|
637
|
+
for (const ab of (branch.attrBindings || [])) {
|
|
638
|
+
const nodeRef = pathExpr(ab.path, `${vn}_node`);
|
|
639
|
+
if (isStaticForExpr(ab.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet)) {
|
|
640
|
+
lines.push(`${indent} const __val_${ab.varName} = ${ab.expression};`);
|
|
641
|
+
lines.push(`${indent} if (__val_${ab.varName} != null && __val_${ab.varName} !== false) { ${nodeRef}.setAttribute('${ab.attr}', __val_${ab.varName}); }`);
|
|
642
|
+
} else {
|
|
643
|
+
const expr = transformForExpr(ab.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
644
|
+
lines.push(`${indent} __effect(() => {`);
|
|
645
|
+
lines.push(`${indent} const __val = ${expr};`);
|
|
646
|
+
lines.push(`${indent} if (__val == null || __val === false) { ${nodeRef}.removeAttribute('${ab.attr}'); }`);
|
|
647
|
+
lines.push(`${indent} else { ${nodeRef}.setAttribute('${ab.attr}', __val); }`);
|
|
648
|
+
lines.push(`${indent} });`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Model bindings
|
|
653
|
+
for (const mb of (branch.modelBindings || [])) {
|
|
654
|
+
const nodeRef = pathExpr(mb.path, `${vn}_node`);
|
|
655
|
+
lines.push(`${indent} __effect(() => {`);
|
|
656
|
+
if (mb.prop === 'checked' && mb.radioValue !== null) {
|
|
657
|
+
lines.push(`${indent} ${nodeRef}.checked = (this._${mb.signal}() === '${mb.radioValue}');`);
|
|
658
|
+
} else if (mb.prop === 'checked') {
|
|
659
|
+
lines.push(`${indent} ${nodeRef}.checked = !!this._${mb.signal}();`);
|
|
660
|
+
} else {
|
|
661
|
+
lines.push(`${indent} ${nodeRef}.value = this._${mb.signal}() ?? '';`);
|
|
662
|
+
}
|
|
663
|
+
lines.push(`${indent} });`);
|
|
664
|
+
if (mb.prop === 'checked' && mb.radioValue === null) {
|
|
665
|
+
lines.push(`${indent} ${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.checked); });`);
|
|
666
|
+
} else if (mb.coerce) {
|
|
667
|
+
lines.push(`${indent} ${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(Number(e.target.value)); });`);
|
|
668
|
+
} else {
|
|
669
|
+
lines.push(`${indent} ${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.value); });`);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
lines.push(`${indent} }`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
lines.push(`${indent} }`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Generate inner item bindings/events/show/attr/model for a nested each directive.
|
|
682
|
+
* Uses transformForExpr with an excludeSet that includes BOTH outer and inner loop variables.
|
|
683
|
+
*
|
|
684
|
+
* @param {string[]} lines - Output lines array
|
|
685
|
+
* @param {ForBlock} innerFor - The nested ForBlock
|
|
686
|
+
* @param {string} outerItemVar - Outer loop item variable
|
|
687
|
+
* @param {string|null} outerIndexVar - Outer loop index variable
|
|
688
|
+
* @param {string} innerItemVar - Inner loop item variable
|
|
689
|
+
* @param {string|null} innerIndexVar - Inner loop index variable
|
|
690
|
+
* @param {Set<string>} propNames - Prop names set
|
|
691
|
+
* @param {Set<string>} signalNamesSet - Signal names set
|
|
692
|
+
* @param {Set<string>} computedNamesSet - Computed names set
|
|
693
|
+
* @param {string} indent - Current indentation
|
|
694
|
+
*/
|
|
695
|
+
function generateNestedItemSetup(lines, innerFor, outerItemVar, outerIndexVar, innerItemVar, innerIndexVar, propNames, signalNamesSet, computedNamesSet, indent) {
|
|
696
|
+
// Build combined exclude set with both outer and inner loop variables
|
|
697
|
+
const combinedExcludeItemVar = innerItemVar;
|
|
698
|
+
const combinedExcludeIndexVar = innerIndexVar;
|
|
699
|
+
|
|
700
|
+
// For transformForExpr, we need to ensure both outer and inner vars are excluded.
|
|
701
|
+
// We create a modified propNames/signalNamesSet/computedNamesSet that doesn't include
|
|
702
|
+
// any of the loop variables. transformForExpr already excludes itemVar/indexVar,
|
|
703
|
+
// but we also need to exclude the outer loop variables.
|
|
704
|
+
// Strategy: filter out outer loop vars from the sets passed to transformForExpr
|
|
705
|
+
const filteredSignalNames = new Set([...signalNamesSet].filter(n => n !== outerItemVar && n !== outerIndexVar));
|
|
706
|
+
const filteredComputedNames = new Set([...computedNamesSet].filter(n => n !== outerItemVar && n !== outerIndexVar));
|
|
707
|
+
const filteredPropNames = new Set([...propNames].filter(n => n !== outerItemVar && n !== outerIndexVar));
|
|
708
|
+
|
|
709
|
+
// Helper: check if expression is static (only references inner/outer loop vars, no signals/computeds/props)
|
|
710
|
+
function isNestedStatic(expr) {
|
|
711
|
+
// An expression is static if it only references the loop variables (outer + inner)
|
|
712
|
+
const allExclude = new Set([innerItemVar, outerItemVar]);
|
|
713
|
+
if (innerIndexVar) allExclude.add(innerIndexVar);
|
|
714
|
+
if (outerIndexVar) allExclude.add(outerIndexVar);
|
|
715
|
+
|
|
716
|
+
for (const p of propNames) {
|
|
717
|
+
if (allExclude.has(p)) continue;
|
|
718
|
+
if (new RegExp(`\\b${p}\\b`).test(expr)) return false;
|
|
719
|
+
}
|
|
720
|
+
for (const n of signalNamesSet) {
|
|
721
|
+
if (allExclude.has(n)) continue;
|
|
722
|
+
if (new RegExp(`\\b${n}\\b`).test(expr)) return false;
|
|
723
|
+
}
|
|
724
|
+
for (const n of computedNamesSet) {
|
|
725
|
+
if (allExclude.has(n)) continue;
|
|
726
|
+
if (new RegExp(`\\b${n}\\b`).test(expr)) return false;
|
|
727
|
+
}
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Helper: transform expression excluding both outer and inner loop vars
|
|
732
|
+
function transformNested(expr) {
|
|
733
|
+
return transformForExpr(expr, innerItemVar, innerIndexVar, filteredPropNames, filteredSignalNames, filteredComputedNames);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Bindings
|
|
737
|
+
for (const b of innerFor.bindings) {
|
|
738
|
+
const nodeRef = pathExpr(b.path, 'innerNode');
|
|
739
|
+
if (isNestedStatic(b.name)) {
|
|
740
|
+
lines.push(`${indent}${nodeRef}.textContent = ${b.name} ?? '';`);
|
|
741
|
+
} else {
|
|
742
|
+
const expr = transformNested(b.name);
|
|
743
|
+
lines.push(`${indent}__effect(() => { ${nodeRef}.textContent = ${expr} ?? ''; });`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Events
|
|
748
|
+
for (const e of innerFor.events) {
|
|
749
|
+
const nodeRef = pathExpr(e.path, 'innerNode');
|
|
750
|
+
const handlerExpr = generateForEventHandler(e.handler, innerItemVar, innerIndexVar, filteredPropNames, filteredSignalNames, filteredComputedNames);
|
|
751
|
+
lines.push(`${indent}${nodeRef}.addEventListener('${e.event}', ${handlerExpr});`);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Show
|
|
755
|
+
for (const sb of innerFor.showBindings) {
|
|
756
|
+
const nodeRef = pathExpr(sb.path, 'innerNode');
|
|
757
|
+
if (isNestedStatic(sb.expression)) {
|
|
758
|
+
lines.push(`${indent}${nodeRef}.style.display = (${sb.expression}) ? '' : 'none';`);
|
|
759
|
+
} else {
|
|
760
|
+
const expr = transformNested(sb.expression);
|
|
761
|
+
lines.push(`${indent}__effect(() => { ${nodeRef}.style.display = (${expr}) ? '' : 'none'; });`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Attr bindings
|
|
766
|
+
for (const ab of innerFor.attrBindings) {
|
|
767
|
+
const nodeRef = pathExpr(ab.path, 'innerNode');
|
|
768
|
+
if (isNestedStatic(ab.expression)) {
|
|
769
|
+
lines.push(`${indent}const __val_${ab.varName} = ${ab.expression};`);
|
|
770
|
+
lines.push(`${indent}if (__val_${ab.varName} != null && __val_${ab.varName} !== false) { ${nodeRef}.setAttribute('${ab.attr}', __val_${ab.varName}); }`);
|
|
771
|
+
} else {
|
|
772
|
+
const expr = transformNested(ab.expression);
|
|
773
|
+
lines.push(`${indent}__effect(() => {`);
|
|
774
|
+
lines.push(`${indent} const __val = ${expr};`);
|
|
775
|
+
lines.push(`${indent} if (__val == null || __val === false) { ${nodeRef}.removeAttribute('${ab.attr}'); }`);
|
|
776
|
+
lines.push(`${indent} else { ${nodeRef}.setAttribute('${ab.attr}', __val); }`);
|
|
777
|
+
lines.push(`${indent}});`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Model bindings
|
|
782
|
+
for (const mb of (innerFor.modelBindings || [])) {
|
|
783
|
+
const nodeRef = pathExpr(mb.path, 'innerNode');
|
|
784
|
+
lines.push(`${indent}__effect(() => {`);
|
|
785
|
+
if (mb.prop === 'checked' && mb.radioValue !== null) {
|
|
786
|
+
lines.push(`${indent} ${nodeRef}.checked = (this._${mb.signal}() === '${mb.radioValue}');`);
|
|
787
|
+
} else if (mb.prop === 'checked') {
|
|
788
|
+
lines.push(`${indent} ${nodeRef}.checked = !!this._${mb.signal}();`);
|
|
789
|
+
} else {
|
|
790
|
+
lines.push(`${indent} ${nodeRef}.value = this._${mb.signal}() ?? '';`);
|
|
791
|
+
}
|
|
792
|
+
lines.push(`${indent}});`);
|
|
793
|
+
if (mb.prop === 'checked' && mb.radioValue === null) {
|
|
794
|
+
lines.push(`${indent}${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.checked); });`);
|
|
795
|
+
} else if (mb.coerce) {
|
|
796
|
+
lines.push(`${indent}${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(Number(e.target.value)); });`);
|
|
797
|
+
} else {
|
|
798
|
+
lines.push(`${indent}${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.value); });`);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
482
801
|
}
|
|
483
802
|
|
|
484
803
|
/**
|
package/lib/tree-walker.js
CHANGED
|
@@ -369,10 +369,19 @@ export function walkBranch(html, signalNames, computedNames, propNames) {
|
|
|
369
369
|
const { document } = parseHTML(`<div id="__branchRoot">${html}</div>`);
|
|
370
370
|
const branchRoot = document.getElementById('__branchRoot');
|
|
371
371
|
|
|
372
|
-
//
|
|
372
|
+
// Process nested structural directives FIRST (before walkTree modifies the DOM).
|
|
373
|
+
// This is critical because walkTree clears textContent of elements with sole
|
|
374
|
+
// {{interpolation}} children, which would destroy content needed by
|
|
375
|
+
// processForBlocks/processIfChains when they clone nested elements for their
|
|
376
|
+
// own walkBranch calls.
|
|
377
|
+
const forBlocks = processForBlocks(branchRoot, [], signalNames, computedNames, propNames);
|
|
378
|
+
const ifBlocks = processIfChains(branchRoot, [], signalNames, computedNames, propNames);
|
|
379
|
+
|
|
380
|
+
// Now run walkTree on the remaining DOM (nested directive elements have been
|
|
381
|
+
// replaced with comment nodes, so walkTree won't process their contents).
|
|
373
382
|
const result = walkTree(branchRoot, signalNames, computedNames, propNames);
|
|
374
383
|
|
|
375
|
-
// Capture the processed HTML AFTER
|
|
384
|
+
// Capture the processed HTML AFTER all processing
|
|
376
385
|
const processedHtml = branchRoot.innerHTML;
|
|
377
386
|
|
|
378
387
|
// Strip the first path segment from all paths since at runtime
|
|
@@ -392,6 +401,17 @@ export function walkBranch(html, signalNames, computedNames, propNames) {
|
|
|
392
401
|
stripFirstSegment(result.slots);
|
|
393
402
|
stripFirstSegment(result.childComponents);
|
|
394
403
|
|
|
404
|
+
// Strip first path segment from nested forBlock/ifBlock anchor paths
|
|
405
|
+
function stripFirstAnchorSegment(items) {
|
|
406
|
+
for (const item of items) {
|
|
407
|
+
if (item.anchorPath && item.anchorPath.length > 0 && item.anchorPath[0].startsWith('childNodes[')) {
|
|
408
|
+
item.anchorPath = item.anchorPath.slice(1);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
stripFirstAnchorSegment(forBlocks);
|
|
413
|
+
stripFirstAnchorSegment(ifBlocks);
|
|
414
|
+
|
|
395
415
|
return {
|
|
396
416
|
bindings: result.bindings,
|
|
397
417
|
events: result.events,
|
|
@@ -400,6 +420,8 @@ export function walkBranch(html, signalNames, computedNames, propNames) {
|
|
|
400
420
|
modelBindings: result.modelBindings,
|
|
401
421
|
slots: result.slots,
|
|
402
422
|
childComponents: result.childComponents,
|
|
423
|
+
forBlocks,
|
|
424
|
+
ifBlocks,
|
|
403
425
|
processedHtml,
|
|
404
426
|
};
|
|
405
427
|
}
|
|
@@ -763,7 +785,7 @@ export function processForBlocks(parent, parentPath, signalNames, computedNames,
|
|
|
763
785
|
const templateHtml = clone.outerHTML;
|
|
764
786
|
|
|
765
787
|
// Process internal bindings/events via partial walk
|
|
766
|
-
const { bindings, events, showBindings, attrBindings, modelBindings, slots, childComponents: forChildComponents, processedHtml } = walkBranch(templateHtml, signalNames, computedNames, propNames);
|
|
788
|
+
const { bindings, events, showBindings, attrBindings, modelBindings, slots, childComponents: forChildComponents, forBlocks: nestedForBlocks, ifBlocks: nestedIfBlocks, processedHtml } = walkBranch(templateHtml, signalNames, computedNames, propNames);
|
|
767
789
|
|
|
768
790
|
// Replace the original element with a comment node <!-- each -->
|
|
769
791
|
const doc = node.ownerDocument;
|
|
@@ -792,6 +814,8 @@ export function processForBlocks(parent, parentPath, signalNames, computedNames,
|
|
|
792
814
|
modelBindings,
|
|
793
815
|
slots,
|
|
794
816
|
childComponents: forChildComponents,
|
|
817
|
+
forBlocks: nestedForBlocks,
|
|
818
|
+
ifBlocks: nestedIfBlocks,
|
|
795
819
|
});
|
|
796
820
|
} else {
|
|
797
821
|
// Recurse into non-each elements to find nested each
|
package/lib/types.js
CHANGED
|
@@ -153,6 +153,8 @@
|
|
|
153
153
|
* @property {AttrBinding[]} attrBindings — :attr bindings within item
|
|
154
154
|
* @property {ModelBinding[]} modelBindings — model bindings within item
|
|
155
155
|
* @property {SlotBinding[]} slots — slot bindings within item
|
|
156
|
+
* @property {ForBlock[]} [forBlocks] — Nested each directives within item
|
|
157
|
+
* @property {IfBlock[]} [ifBlocks] — Nested if/else-if/else chains within item
|
|
156
158
|
*/
|
|
157
159
|
|
|
158
160
|
/**
|
package/package.json
CHANGED