@sprlab/wccompiler 0.5.12 → 0.5.13
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 +24 -2
- 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
|
@@ -372,7 +372,14 @@ export function walkBranch(html, signalNames, computedNames, propNames) {
|
|
|
372
372
|
// Use walkTree on the branch root to discover bindings/events
|
|
373
373
|
const result = walkTree(branchRoot, signalNames, computedNames, propNames);
|
|
374
374
|
|
|
375
|
-
//
|
|
375
|
+
// Detect nested each directives within the branch template
|
|
376
|
+
const forBlocks = processForBlocks(branchRoot, [], signalNames, computedNames, propNames);
|
|
377
|
+
|
|
378
|
+
// Detect nested if/else-if/else chains within the branch template
|
|
379
|
+
const ifBlocks = processIfChains(branchRoot, [], signalNames, computedNames, propNames);
|
|
380
|
+
|
|
381
|
+
// Capture the processed HTML AFTER all processing (walkTree + processForBlocks + processIfChains)
|
|
382
|
+
// since processForBlocks/processIfChains modify the DOM by replacing elements with comment nodes
|
|
376
383
|
const processedHtml = branchRoot.innerHTML;
|
|
377
384
|
|
|
378
385
|
// Strip the first path segment from all paths since at runtime
|
|
@@ -392,6 +399,17 @@ export function walkBranch(html, signalNames, computedNames, propNames) {
|
|
|
392
399
|
stripFirstSegment(result.slots);
|
|
393
400
|
stripFirstSegment(result.childComponents);
|
|
394
401
|
|
|
402
|
+
// Strip first path segment from nested forBlock/ifBlock anchor paths
|
|
403
|
+
function stripFirstAnchorSegment(items) {
|
|
404
|
+
for (const item of items) {
|
|
405
|
+
if (item.anchorPath && item.anchorPath.length > 0 && item.anchorPath[0].startsWith('childNodes[')) {
|
|
406
|
+
item.anchorPath = item.anchorPath.slice(1);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
stripFirstAnchorSegment(forBlocks);
|
|
411
|
+
stripFirstAnchorSegment(ifBlocks);
|
|
412
|
+
|
|
395
413
|
return {
|
|
396
414
|
bindings: result.bindings,
|
|
397
415
|
events: result.events,
|
|
@@ -400,6 +418,8 @@ export function walkBranch(html, signalNames, computedNames, propNames) {
|
|
|
400
418
|
modelBindings: result.modelBindings,
|
|
401
419
|
slots: result.slots,
|
|
402
420
|
childComponents: result.childComponents,
|
|
421
|
+
forBlocks,
|
|
422
|
+
ifBlocks,
|
|
403
423
|
processedHtml,
|
|
404
424
|
};
|
|
405
425
|
}
|
|
@@ -763,7 +783,7 @@ export function processForBlocks(parent, parentPath, signalNames, computedNames,
|
|
|
763
783
|
const templateHtml = clone.outerHTML;
|
|
764
784
|
|
|
765
785
|
// Process internal bindings/events via partial walk
|
|
766
|
-
const { bindings, events, showBindings, attrBindings, modelBindings, slots, childComponents: forChildComponents, processedHtml } = walkBranch(templateHtml, signalNames, computedNames, propNames);
|
|
786
|
+
const { bindings, events, showBindings, attrBindings, modelBindings, slots, childComponents: forChildComponents, forBlocks: nestedForBlocks, ifBlocks: nestedIfBlocks, processedHtml } = walkBranch(templateHtml, signalNames, computedNames, propNames);
|
|
767
787
|
|
|
768
788
|
// Replace the original element with a comment node <!-- each -->
|
|
769
789
|
const doc = node.ownerDocument;
|
|
@@ -792,6 +812,8 @@ export function processForBlocks(parent, parentPath, signalNames, computedNames,
|
|
|
792
812
|
modelBindings,
|
|
793
813
|
slots,
|
|
794
814
|
childComponents: forChildComponents,
|
|
815
|
+
forBlocks: nestedForBlocks,
|
|
816
|
+
ifBlocks: nestedIfBlocks,
|
|
795
817
|
});
|
|
796
818
|
} else {
|
|
797
819
|
// 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