rip-lang 3.13.133 → 3.13.135

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/src/components.js CHANGED
@@ -3,9 +3,9 @@ import { HTML_TAGS, SVG_TAGS, TEMPLATE_TAGS } from './generated/dom-tags.js';
3
3
 
4
4
  // Component System — Fine-grained reactive components for Rip
5
5
  //
6
- // Architecture: installComponentSupport(CodeGenerator, Lexer) adds methods to
6
+ // Architecture: installComponentSupport(CodeEmitter, Lexer) adds methods to
7
7
  // both prototypes — render rewriting on the Lexer, component code generation
8
- // on the CodeGenerator. A separate getComponentRuntime() emits runtime helpers
8
+ // on the CodeEmitter. A separate getComponentRuntime() emits runtime helpers
9
9
  // only when components are used.
10
10
  //
11
11
  // Naming: All render-tree generators use generate* (consistent with compiler).
@@ -83,7 +83,7 @@ function getMemberType(target) {
83
83
  // Prototype Installation
84
84
  // ============================================================================
85
85
 
86
- export function installComponentSupport(CodeGenerator, Lexer) {
86
+ export function installComponentSupport(CodeEmitter, Lexer) {
87
87
 
88
88
  let meta = (node, key) => node instanceof String ? node[key] : undefined;
89
89
 
@@ -516,10 +516,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
516
516
  };
517
517
 
518
518
  // ==========================================================================
519
- // CodeGenerator: Component compilation
519
+ // CodeEmitter: Component compilation
520
520
  // ==========================================================================
521
521
 
522
- const proto = CodeGenerator.prototype;
522
+ const proto = CodeEmitter.prototype;
523
523
 
524
524
  // ==========================================================================
525
525
  // Utilities
@@ -667,7 +667,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
667
667
  * Generate component: produces an anonymous ES6 class expression.
668
668
  * Pattern: ["component", null, ["block", ...statements]]
669
669
  */
670
- proto.generateComponent = function(head, rest, context, sexpr) {
670
+ proto.emitComponent = function(head, rest, context, sexpr) {
671
671
  const [, body] = rest;
672
672
 
673
673
  // Extract component body statements
@@ -724,7 +724,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
724
724
  } else if (op === 'computed') {
725
725
  const varName = getMemberName(stmt[1]);
726
726
  if (varName) {
727
- derivedVars.push({ name: varName, expr: stmt[2] });
727
+ derivedVars.push({ name: varName, expr: stmt[2], type: getMemberType(stmt[1]) });
728
728
  memberNames.add(varName);
729
729
  reactiveMembers.add(varName);
730
730
  }
@@ -803,8 +803,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
803
803
  .replace(/(\w+(?:<[^>]+>)?)\!/g, 'NonNullable<$1>') : null;
804
804
 
805
805
  const sl = [];
806
- sl.push('class {');
807
- sl.push(' declare _root: Element | null;');
806
+ const componentTypeParams = this._componentTypeParams || '';
807
+ sl.push(`class ${componentTypeParams}{`);
808
+ sl.push(' declare _root: Element | null; declare app: any;');
808
809
  sl.push(' emit(_name: string, _detail?: any): void {}');
809
810
 
810
811
  // Constructor — typed props for public state/readonly (matches DTS)
@@ -849,31 +850,33 @@ export function installComponentSupport(CodeGenerator, Lexer) {
849
850
  const ts = expandType(type) || inferLiteralType(value);
850
851
  sl.push(ts ? ` declare ${name}: ${ts};` : ` declare ${name}: any;`);
851
852
  }
852
- for (const { name, expr } of derivedVars) {
853
+ for (const { name, expr, type } of derivedVars) {
854
+ const ts = expandType(type);
855
+ const typeAnnot = ts ? `: Computed<${ts}>` : '';
853
856
  if (this.is(expr, 'block')) {
854
857
  const transformed = this.transformComponentMembers(expr);
855
- const body = this.generateFunctionBody(transformed);
856
- sl.push(` ${name} = __computed(() => ${body});`);
858
+ const body = this.emitFunctionBody(transformed);
859
+ sl.push(` ${name}${typeAnnot} = __computed(() => ${body});`);
857
860
  } else {
858
- const val = this.generateInComponent(expr, 'value');
859
- sl.push(` ${name} = __computed(() => ${val});`);
861
+ const val = this.emitInComponent(expr, 'value');
862
+ sl.push(` ${name}${typeAnnot} = __computed(() => ${val});`);
860
863
  }
861
864
  }
862
865
 
863
866
  // _init body — readonly, state, computed assignments (skip accepted/offered)
864
867
  sl.push(' _init(props) {');
865
868
  for (const { name, value, isPublic } of readonlyVars) {
866
- const val = this.generateInComponent(value, 'value');
869
+ const val = this.emitInComponent(value, 'value');
867
870
  sl.push(isPublic ? ` this.${name} = props.${name} ?? ${val};` : ` this.${name} = ${val};`);
868
871
  }
869
872
  for (const { name, value, isPublic, required, type } of stateVars) {
870
873
  if (isPublic && required) {
871
874
  sl.push(` this.${name} = __state(props.__bind_${name}__ ?? props.${name});`);
872
875
  } else if (isPublic) {
873
- const val = this.generateInComponent(value, 'value');
876
+ const val = this.emitInComponent(value, 'value');
874
877
  sl.push(` this.${name} = __state(props.__bind_${name}__ ?? props.${name} ?? ${val});`);
875
878
  } else {
876
- const val = this.generateInComponent(value, 'value');
879
+ const val = this.emitInComponent(value, 'value');
877
880
  sl.push(` this.${name} = __state(${val});`);
878
881
  }
879
882
  }
@@ -883,10 +886,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
883
886
  const isAsync = this.containsAwait(effectBody) ? 'async ' : '';
884
887
  if (this.is(effectBody, 'block')) {
885
888
  const transformed = this.transformComponentMembers(effectBody);
886
- const body = this.generateFunctionBody(transformed, [], true);
889
+ const body = this.emitFunctionBody(transformed, [], true);
887
890
  sl.push(` __effect(${isAsync}() => ${body});`);
888
891
  } else {
889
- const effectCode = this.generateInComponent(effectBody, 'value');
892
+ const effectCode = this.emitInComponent(effectBody, 'value');
890
893
  sl.push(` __effect(${isAsync}() => { ${effectCode}; });`);
891
894
  }
892
895
  }
@@ -953,7 +956,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
953
956
  }
954
957
  const transformed = this.reactiveMembers ? this.transformComponentMembers(methodBody) : methodBody;
955
958
  const isAsync = this.containsAwait(methodBody);
956
- const bodyCode = this.generateFunctionBody(transformed, params || []);
959
+ const bodyCode = this.emitFunctionBody(transformed, params || []);
957
960
  sl.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
958
961
  }
959
962
  }
@@ -965,7 +968,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
965
968
  const paramStr = Array.isArray(params) ? params.map(p => this.formatParam(p)).join(', ') : '';
966
969
  const transformed = this.reactiveMembers ? this.transformComponentMembers(hookBody) : hookBody;
967
970
  const isAsync = this.containsAwait(hookBody);
968
- const bodyCode = this.generateFunctionBody(transformed, params || []);
971
+ const bodyCode = this.emitFunctionBody(transformed, params || []);
969
972
  sl.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
970
973
  }
971
974
  }
@@ -975,6 +978,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
975
978
  if (renderBlock) {
976
979
  const constructions = [];
977
980
  let constructionIdx = 0;
981
+ const sourceLines = this.options.source?.split('\n');
978
982
  const extractProps = (args) => {
979
983
  const props = [];
980
984
  for (const arg of args) {
@@ -995,10 +999,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
995
999
  const srcLine = pair.loc?.r ?? obj.loc?.r;
996
1000
  if (key.startsWith('__bind_') && key.endsWith('__')) {
997
1001
  // Two-way binding: emit the Signal object (this.xxx), not this.xxx.value
998
- const member = typeof value === 'string' && this.reactiveMembers?.has(value) ? `this.${value}` : this.generateInComponent(value, 'value');
1002
+ const member = typeof value === 'string' && this.reactiveMembers?.has(value) ? `this.${value}` : this.emitInComponent(value, 'value');
999
1003
  props.push({ code: `${key}: ${member}`, srcLine });
1000
1004
  } else {
1001
- const val = this.generateInComponent(value, 'value');
1005
+ const val = this.emitInComponent(value, 'value');
1002
1006
  props.push({ code: `${key}: ${val}`, srcLine });
1003
1007
  }
1004
1008
  }
@@ -1028,16 +1032,23 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1028
1032
  let memberName = typeof key[2] === 'string' ? key[2] : key[2]?.valueOf?.();
1029
1033
  if (!memberName) continue;
1030
1034
  const eventKey = '@' + memberName.split('.')[0];
1031
- const val = this.generateInComponent(value, 'value');
1035
+ const val = this.emitInComponent(value, 'value');
1032
1036
  props.push({ code: `'${eventKey}': ${val}`, srcLine });
1033
1037
  } else if (typeof key === 'string') {
1034
- if (key === 'key') continue;
1038
+ if (key === 'key') {
1039
+ // key: is not an HTML attribute, but emit its value
1040
+ // expression for type-checking and semantic tokens
1041
+ const val = this.emitInComponent(value, 'value');
1042
+ const marker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
1043
+ constructions.push(` (${val});${marker}`);
1044
+ continue;
1045
+ }
1035
1046
  if (key.startsWith('__bind_') && key.endsWith('__')) {
1036
1047
  const propName = key.slice(7, -2);
1037
- const val = this.generateInComponent(value, 'value');
1048
+ const val = this.emitInComponent(value, 'value');
1038
1049
  props.push({ code: `${propName}: ${val}`, srcLine });
1039
1050
  } else {
1040
- const val = this.generateInComponent(value, 'value');
1051
+ const val = this.emitInComponent(value, 'value');
1041
1052
  props.push({ code: `${key}: ${val}`, srcLine });
1042
1053
  }
1043
1054
  }
@@ -1049,6 +1060,146 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1049
1060
  const walkRender = (node) => {
1050
1061
  if (!Array.isArray(node)) return;
1051
1062
  const head = node[0]?.valueOf?.() ?? node[0];
1063
+
1064
+ // Object nodes are property bags (key-value pairs) — their values
1065
+ // are code expressions (event handlers, bindings, literals), not
1066
+ // render template. extractIntrinsicProps handles them separately.
1067
+ // Walking into them would treat function bodies as template content
1068
+ // (e.g. `@blur: (e) -> p(e)` would emit `e;` and `__ripEl('p')`).
1069
+ if (head === 'object') return;
1070
+
1071
+ // Type-check conditional and loop expressions in render blocks.
1072
+ // Without this, `if labelz` (a typo for `label`) silently evaluates
1073
+ // as undefined and skips the block — the condition goes unchecked.
1074
+ // Similarly, `switch statusz` and `for item in itemsz` go unchecked.
1075
+ if (head === 'if' || head === 'unless') {
1076
+ const condition = node[1];
1077
+ if (condition != null) {
1078
+ const condCode = this.emitInComponent(condition, 'value');
1079
+ const srcLine = node.loc?.r;
1080
+ const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
1081
+ constructions.push(` ${condCode};${srcMarker}`);
1082
+ }
1083
+ } else if (head === '?:') {
1084
+ // Emit the full ternary so all branches are type-checked
1085
+ const ternCode = this.emitInComponent(node, 'value');
1086
+ const srcLine = node.loc?.r;
1087
+ const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
1088
+ constructions.push(` ${ternCode};${srcMarker}`);
1089
+ } else if (head === 'switch') {
1090
+ const discriminant = node[1];
1091
+ if (discriminant != null) {
1092
+ const discCode = this.emitInComponent(discriminant, 'value');
1093
+ const srcLine = node.loc?.r;
1094
+ const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
1095
+ constructions.push(` ${discCode};${srcMarker}`);
1096
+ }
1097
+ } else if (head === 'for-in' || head === 'for-of' || head === 'for-as') {
1098
+ // Emit a real for-loop so the loop variable is in scope for the body.
1099
+ // node: [head, vars, iterable, step, guard, body]
1100
+ const vars = node[1];
1101
+ const iterable = node[2];
1102
+ if (iterable != null) {
1103
+ const iterCode = this.emitInComponent(iterable, 'value');
1104
+ const srcLine = node.loc?.r;
1105
+ const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
1106
+ // Extract loop variable pattern
1107
+ let varPattern;
1108
+ if (Array.isArray(vars)) {
1109
+ if (vars.length === 1) {
1110
+ const v = vars[0];
1111
+ varPattern = Array.isArray(v) ? this.emitDestructuringPattern(v) : String(v);
1112
+ } else if (head === 'for-of') {
1113
+ // for key, val of obj — destructure as [key, val] from Object.entries
1114
+ varPattern = `[${vars.map(v => String(v)).join(', ')}]`;
1115
+ } else {
1116
+ // for item, index in arr — first is the item
1117
+ varPattern = String(vars[0]);
1118
+ }
1119
+ } else {
1120
+ varPattern = String(vars);
1121
+ }
1122
+ if (head === 'for-of') {
1123
+ constructions.push(` for (const ${varPattern} of Object.entries(${iterCode})) {${srcMarker}`);
1124
+ } else {
1125
+ constructions.push(` for (const ${varPattern} of ${iterCode}) {${srcMarker}`);
1126
+ }
1127
+ // Walk body children (indices 3+ may contain guard, body, etc.)
1128
+ for (let bi = 3; bi < node.length; bi++) {
1129
+ if (node[bi] != null) walkRender(node[bi]);
1130
+ }
1131
+ constructions.push(` }`);
1132
+ return; // Don't walk children again below
1133
+ }
1134
+ } else if (head === '__text__') {
1135
+ // = expr — text expression: emit the expression for type-checking
1136
+ const textExpr = node[1];
1137
+ if (textExpr != null) {
1138
+ const exprCode = this.emitInComponent(textExpr, 'value');
1139
+ const srcLine = node.loc?.r;
1140
+ const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
1141
+ constructions.push(` ${exprCode};${srcMarker}`);
1142
+ }
1143
+ }
1144
+
1145
+ // Emit a bare lowercase identifier as either a property access
1146
+ // (component member used as text), __ripEl (tag name check when at
1147
+ // block level), or a plain variable reference (text child of a tag).
1148
+ const emitBareIdent = (child, parentNode, isTextChild) => {
1149
+ if (typeof child !== 'string' || !/^[a-z][\w-]*$/.test(child)) return;
1150
+ if (CodeEmitter.GENERATORS[child]) return;
1151
+ if (child === 'null' || child === 'undefined' || child === 'true' || child === 'false') return;
1152
+ let srcLine = parentNode.loc?.r;
1153
+ if (srcLine != null && sourceLines) {
1154
+ const re = new RegExp(`\\b${child}\\b`);
1155
+ for (let ln = srcLine; ln < sourceLines.length; ln++) {
1156
+ if (re.test(sourceLines[ln])) { srcLine = ln; break; }
1157
+ }
1158
+ }
1159
+ const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
1160
+ if (this.componentMembers && this.componentMembers.has(child)) {
1161
+ constructions.push(` this.${child};${srcMarker}`);
1162
+ } else if (isTextChild) {
1163
+ // Text child of a tag — emit as variable reference so TS
1164
+ // reports "Cannot find name 'x'" instead of "not a known element"
1165
+ constructions.push(` ${child};${srcMarker}`);
1166
+ } else {
1167
+ constructions.push(` __ripEl('${child}');${srcMarker}`);
1168
+ }
1169
+ };
1170
+
1171
+ // Bare lowercase identifiers inside a block or as children of tag nodes
1172
+ // — emit __ripEl so TS catches tag typos (e.g., slotz for slot), or
1173
+ // emit this.prop for component member text references.
1174
+ const isTagHead = typeof head === 'string' && /^[a-z][\w-]*$/.test(head) &&
1175
+ !CodeEmitter.GENERATORS[head] && TEMPLATE_TAGS.has(head.split(/[.#]/)[0]);
1176
+ if (head === 'block') {
1177
+ for (let i = 1; i < node.length; i++) emitBareIdent(node[i], node, false);
1178
+ } else if (isTagHead) {
1179
+ for (let i = 1; i < node.length; i++) emitBareIdent(node[i], node, true);
1180
+ // Emit expression children of intrinsic tags for type-checking.
1181
+ // Without this, text content like "#{item.name}" in `li "#{item.name}"`
1182
+ // is invisible to TypeScript and loop variables appear unused (TS 6133).
1183
+ for (let i = 1; i < node.length; i++) {
1184
+ const child = node[i];
1185
+ if (!Array.isArray(child)) continue;
1186
+ const ch = child[0]?.valueOf?.() ?? child[0];
1187
+ if (ch === 'object' || ch === 'block' || ch === '__text__') continue;
1188
+ if (typeof ch === 'string') {
1189
+ if (/^[A-Z]/.test(ch)) continue;
1190
+ if (TEMPLATE_TAGS.has(ch.split(/[.#]/)[0])) continue;
1191
+ if (/^[a-z][\w-]*$/.test(ch) && !CodeEmitter.GENERATORS[ch]) continue;
1192
+ if (/^(if|unless|switch|for-in|for-of|for-as|while|until|loop|loop-n|try|throw|break|continue|break-if|continue-if|control|when|return|def|->|=>|class|enum|state|computed|readonly|effect|=|program)$/.test(ch)) continue;
1193
+ }
1194
+ try {
1195
+ const exprCode = this.emitInComponent(child, 'value');
1196
+ const srcLine = child.loc?.r ?? node.loc?.r;
1197
+ const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
1198
+ constructions.push(` ${exprCode};${srcMarker}`);
1199
+ } catch {}
1200
+ }
1201
+ }
1202
+ for (let i = 1; i < node.length; i++) walkRender(node[i]);
1052
1203
  if (typeof head === 'string' && /^[A-Z]/.test(head)) {
1053
1204
  const props = extractProps(node.slice(1));
1054
1205
  const varName = `_${constructionIdx++}`;
@@ -1073,7 +1224,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1073
1224
  constructions.push(` };`);
1074
1225
  }
1075
1226
  }
1076
- } else if (typeof head === 'string' && !CodeGenerator.GENERATORS[head] && (TEMPLATE_TAGS.has(head.split(/[.#]/)[0]) ||
1227
+ } else if (typeof head === 'string' && !CodeEmitter.GENERATORS[head] && (TEMPLATE_TAGS.has(head.split(/[.#]/)[0]) ||
1077
1228
  (/^[a-z][\w-]*$/.test(head) && node.length > 1))) {
1078
1229
  const tagName = head.split(/[.#]/)[0];
1079
1230
  const iProps = extractIntrinsicProps(node.slice(1));
@@ -1100,7 +1251,6 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1100
1251
  }
1101
1252
  }
1102
1253
  }
1103
- for (let i = 1; i < node.length; i++) walkRender(node[i]);
1104
1254
  };
1105
1255
  walkRender(renderBlock);
1106
1256
  if (constructions.length > 0) {
@@ -1132,7 +1282,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1132
1282
 
1133
1283
  // Constants (readonly)
1134
1284
  for (const { name, value, isPublic } of readonlyVars) {
1135
- const val = this.generateInComponent(value, 'value');
1285
+ const val = this.emitInComponent(value, 'value');
1136
1286
  lines.push(isPublic
1137
1287
  ? ` this.${name} = props.${name} ?? ${val};`
1138
1288
  : ` this.${name} = ${val};`);
@@ -1148,10 +1298,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1148
1298
  if (isPublic && required) {
1149
1299
  lines.push(` this.${name} = __state(props.__bind_${name}__ ?? props.${name});`);
1150
1300
  } else if (isPublic) {
1151
- const val = this.generateInComponent(value, 'value');
1301
+ const val = this.emitInComponent(value, 'value');
1152
1302
  lines.push(` this.${name} = __state(props.__bind_${name}__ ?? props.${name} ?? ${val});`);
1153
1303
  } else {
1154
- const val = this.generateInComponent(value, 'value');
1304
+ const val = this.emitInComponent(value, 'value');
1155
1305
  lines.push(` this.${name} = __state(${val});`);
1156
1306
  }
1157
1307
  }
@@ -1172,10 +1322,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1172
1322
  for (const { name, expr } of derivedVars) {
1173
1323
  if (this.is(expr, 'block')) {
1174
1324
  const transformed = this.transformComponentMembers(expr);
1175
- const body = this.generateFunctionBody(transformed);
1325
+ const body = this.emitFunctionBody(transformed);
1176
1326
  lines.push(` this.${name} = __computed(() => ${body});`);
1177
1327
  } else {
1178
- const val = this.generateInComponent(expr, 'value');
1328
+ const val = this.emitInComponent(expr, 'value');
1179
1329
  lines.push(` this.${name} = __computed(() => ${val});`);
1180
1330
  }
1181
1331
  }
@@ -1191,10 +1341,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1191
1341
  const isAsync = this.containsAwait(effectBody) ? 'async ' : '';
1192
1342
  if (this.is(effectBody, 'block')) {
1193
1343
  const transformed = this.transformComponentMembers(effectBody);
1194
- const body = this.generateFunctionBody(transformed, [], true);
1344
+ const body = this.emitFunctionBody(transformed, [], true);
1195
1345
  lines.push(` __effect(${isAsync}() => ${body});`);
1196
1346
  } else {
1197
- const effectCode = this.generateInComponent(effectBody, 'value');
1347
+ const effectCode = this.emitInComponent(effectBody, 'value');
1198
1348
  lines.push(` __effect(${isAsync}() => { ${effectCode}; });`);
1199
1349
  }
1200
1350
  }
@@ -1267,7 +1417,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1267
1417
  const paramStr = Array.isArray(params) ? params.map(p => this.formatParam(p)).join(', ') : '';
1268
1418
  const transformed = this.reactiveMembers ? this.transformComponentMembers(methodBody) : methodBody;
1269
1419
  const isAsync = this.containsAwait(methodBody);
1270
- const bodyCode = this.generateFunctionBody(transformed, params || []);
1420
+ const bodyCode = this.emitFunctionBody(transformed, params || []);
1271
1421
  lines.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
1272
1422
  }
1273
1423
  }
@@ -1279,7 +1429,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1279
1429
  const paramStr = Array.isArray(params) ? params.map(p => this.formatParam(p)).join(', ') : '';
1280
1430
  const transformed = this.reactiveMembers ? this.transformComponentMembers(hookBody) : hookBody;
1281
1431
  const isAsync = this.containsAwait(hookBody);
1282
- const bodyCode = this.generateFunctionBody(transformed, params || []);
1432
+ const bodyCode = this.emitFunctionBody(transformed, params || []);
1283
1433
  lines.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
1284
1434
  }
1285
1435
  }
@@ -1328,7 +1478,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1328
1478
  /**
1329
1479
  * Generate code inside component context (transforms member access to this.X.value)
1330
1480
  */
1331
- proto.generateInComponent = function(sexpr, context) {
1481
+ proto.emitInComponent = function(sexpr, context) {
1332
1482
  if (typeof sexpr === 'string' && this.reactiveMembers && this.reactiveMembers.has(sexpr)) {
1333
1483
  return `${this._self}.${sexpr}.value`;
1334
1484
  }
@@ -1337,24 +1487,24 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1337
1487
  }
1338
1488
  if (Array.isArray(sexpr) && this.reactiveMembers) {
1339
1489
  const transformed = this.transformComponentMembers(sexpr);
1340
- return this.generate(transformed, context);
1490
+ return this.emit(transformed, context);
1341
1491
  }
1342
- return this.generate(sexpr, context);
1492
+ return this.emit(sexpr, context);
1343
1493
  };
1344
1494
 
1345
1495
  /**
1346
1496
  * Handle standalone render (outside component): error
1347
1497
  */
1348
- proto.generateRender = function(head, rest, context, sexpr) {
1349
- throw new Error('render blocks can only be used inside a component');
1498
+ proto.emitRender = function(head, rest, context, sexpr) {
1499
+ this.error('render blocks can only be used inside a component', sexpr);
1350
1500
  };
1351
1501
 
1352
- proto.generateOffer = function(head, rest, context, sexpr) {
1353
- throw new Error('offer can only be used inside a component');
1502
+ proto.emitOffer = function(head, rest, context, sexpr) {
1503
+ this.error('offer can only be used inside a component', sexpr);
1354
1504
  };
1355
1505
 
1356
- proto.generateAccept = function(head, rest, context, sexpr) {
1357
- throw new Error('accept can only be used inside a component');
1506
+ proto.emitAccept = function(head, rest, context, sexpr) {
1507
+ this.error('accept can only be used inside a component', sexpr);
1358
1508
  };
1359
1509
 
1360
1510
  // ==========================================================================
@@ -1388,14 +1538,14 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1388
1538
  rootVar = 'null';
1389
1539
  } else if (statements.length === 1) {
1390
1540
  this._pendingAutoWire = !!this._autoEventHandlers;
1391
- rootVar = this.generateNode(statements[0]);
1541
+ rootVar = this.emitNode(statements[0]);
1392
1542
  this._pendingAutoWire = false;
1393
1543
  } else {
1394
1544
  rootVar = this.newElementVar('frag');
1395
1545
  this._createLines.push(`${rootVar} = document.createDocumentFragment();`);
1396
1546
  const children = [];
1397
1547
  for (const stmt of statements) {
1398
- const childVar = this.generateNode(stmt);
1548
+ const childVar = this.emitNode(stmt);
1399
1549
  this._createLines.push(`${rootVar}.appendChild(${childVar});`);
1400
1550
  children.push(childVar);
1401
1551
  }
@@ -1444,10 +1594,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1444
1594
  };
1445
1595
 
1446
1596
  // --------------------------------------------------------------------------
1447
- // generateNode — main dispatch for all render tree nodes
1597
+ // emitNode — main dispatch for all render tree nodes
1448
1598
  // --------------------------------------------------------------------------
1449
1599
 
1450
- proto.generateNode = function(sexpr) {
1600
+ proto.emitNode = function(sexpr) {
1451
1601
  // String literal → text node (handle both primitive and String objects)
1452
1602
  if (typeof sexpr === 'string' || sexpr instanceof String) {
1453
1603
  const str = sexpr.valueOf();
@@ -1495,7 +1645,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1495
1645
 
1496
1646
  // Component instantiation (PascalCase)
1497
1647
  if (headStr && this.isComponent(headStr)) {
1498
- return this.generateChildComponent(headStr, rest);
1648
+ return this.emitChildComponent(headStr, rest);
1499
1649
  }
1500
1650
 
1501
1651
  // Slot projection — replace <slot> with @children in component render
@@ -1525,8 +1675,8 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1525
1675
  chain = ['if', cond, body, chain];
1526
1676
  }
1527
1677
  if (chain) {
1528
- if (Array.isArray(chain) && chain[0] === 'if') return this.generateConditional(chain);
1529
- return this.generateTemplateBlock(chain);
1678
+ if (Array.isArray(chain) && chain[0] === 'if') return this.emitConditional(chain);
1679
+ return this.emitTemplateBlock(chain);
1530
1680
  }
1531
1681
  const cv = this.newElementVar('c');
1532
1682
  this._createLines.push(`${cv} = document.createComment('switch');`);
@@ -1536,7 +1686,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1536
1686
  // HTML tag (possibly with #id, e.g. div#content)
1537
1687
  if (headStr && this.isHtmlTag(headStr) && !meta(head, 'text')) {
1538
1688
  let [tagName, id] = headStr.split('#');
1539
- return this.generateTag(tagName || 'div', [], rest, id);
1689
+ return this.emitTag(tagName || 'div', [], rest, id);
1540
1690
  }
1541
1691
 
1542
1692
  // Property chain (div.class or item.name)
@@ -1560,12 +1710,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1560
1710
  // HTML tag with classes (div.class) — skip if base is marked .text by = prefix
1561
1711
  const { tag, classes, id, base } = this.collectTemplateClasses(sexpr);
1562
1712
  if (!meta(base, 'text') && tag && this.isHtmlTag(tag)) {
1563
- return this.generateTag(tag, classes, [], id);
1713
+ return this.emitTag(tag, classes, [], id);
1564
1714
  }
1565
1715
 
1566
1716
  // General property access (e.g., item.name in a loop)
1567
1717
  const textVar = this.newTextVar();
1568
- const exprCode = this.generateInComponent(sexpr, 'value');
1718
+ const exprCode = this.emitInComponent(sexpr, 'value');
1569
1719
  this._createLines.push(`${textVar} = document.createTextNode(String(${exprCode}));`);
1570
1720
  return textVar;
1571
1721
  }
@@ -1582,11 +1732,11 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1582
1732
  const { tag, classes, id } = this.collectTemplateClasses(tagExpr);
1583
1733
  if (tag) {
1584
1734
  const staticArgs = classes.map(c => `"${c}"`);
1585
- return this.generateDynamicTag(tag, classExprs, rest, staticArgs, id);
1735
+ return this.emitDynamicTag(tag, classExprs, rest, staticArgs, id);
1586
1736
  }
1587
1737
  }
1588
1738
  const tag = typeof tagExpr === 'string' ? tagExpr : tagExpr.valueOf();
1589
- return this.generateDynamicTag(tag, classExprs, rest);
1739
+ return this.emitDynamicTag(tag, classExprs, rest);
1590
1740
  }
1591
1741
 
1592
1742
  const { tag, classes, id } = this.collectTemplateClasses(head);
@@ -1595,32 +1745,32 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1595
1745
  if (classes.length > 0 && classes[classes.length - 1] === '__clsx') {
1596
1746
  const staticClasses = classes.slice(0, -1);
1597
1747
  const staticArgs = staticClasses.map(c => `"${c}"`);
1598
- return this.generateDynamicTag(tag, rest, [], staticArgs, id);
1748
+ return this.emitDynamicTag(tag, rest, [], staticArgs, id);
1599
1749
  }
1600
- return this.generateTag(tag, classes, rest, id);
1750
+ return this.emitTag(tag, classes, rest, id);
1601
1751
  }
1602
1752
  }
1603
1753
 
1604
1754
  // Arrow function (children block)
1605
1755
  if (headStr === '->' || headStr === '=>') {
1606
- return this.generateTemplateBlock(rest[1]);
1756
+ return this.emitTemplateBlock(rest[1]);
1607
1757
  }
1608
1758
 
1609
1759
  // Conditional: if/else
1610
1760
  if (headStr === 'if') {
1611
- return this.generateConditional(sexpr);
1761
+ return this.emitConditional(sexpr);
1612
1762
  }
1613
1763
 
1614
1764
  // For loop
1615
1765
  if (headStr === 'for' || headStr === 'for-in' || headStr === 'for-of' || headStr === 'for-as') {
1616
- return this.generateTemplateLoop(sexpr);
1766
+ return this.emitTemplateLoop(sexpr);
1617
1767
  }
1618
1768
 
1619
1769
  // Synthetic text node inserted by rewriteRender for `= expr`
1620
1770
  if (headStr === '__text__') {
1621
1771
  const expr = rest[0] ?? 'undefined';
1622
1772
  const textVar = this.newTextVar();
1623
- const exprCode = this.generateInComponent(expr, 'value');
1773
+ const exprCode = this.emitInComponent(expr, 'value');
1624
1774
  if (this.hasReactiveDeps(expr)) {
1625
1775
  this._createLines.push(`${textVar} = document.createTextNode('');`);
1626
1776
  this._pushEffect(`${textVar}.data = String(${exprCode});`);
@@ -1632,7 +1782,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1632
1782
 
1633
1783
  // General expression (computed value, function call, binary op, etc.)
1634
1784
  const textVar = this.newTextVar();
1635
- const exprCode = this.generateInComponent(sexpr, 'value');
1785
+ const exprCode = this.emitInComponent(sexpr, 'value');
1636
1786
  if (this.hasReactiveDeps(sexpr)) {
1637
1787
  this._createLines.push(`${textVar} = document.createTextNode('');`);
1638
1788
  this._pushEffect(`${textVar}.data = ${exprCode};`);
@@ -1643,7 +1793,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1643
1793
  };
1644
1794
 
1645
1795
  // --------------------------------------------------------------------------
1646
- // appendChildren — shared child-processing loop for generateTag/generateDynamicTag
1796
+ // appendChildren — shared child-processing loop for emitTag/emitDynamicTag
1647
1797
  // --------------------------------------------------------------------------
1648
1798
 
1649
1799
  proto.appendChildren = function(elVar, args) {
@@ -1653,26 +1803,26 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1653
1803
  if (this.is(block, 'block')) {
1654
1804
  for (const child of block.slice(1)) {
1655
1805
  if (this.is(child, 'object')) {
1656
- this.generateAttributes(elVar, child);
1806
+ this.emitAttributes(elVar, child);
1657
1807
  } else {
1658
- const childVar = this.generateNode(child);
1808
+ const childVar = this.emitNode(child);
1659
1809
  this._createLines.push(`${elVar}.appendChild(${childVar});`);
1660
1810
  }
1661
1811
  }
1662
1812
  } else if (block) {
1663
- const childVar = this.generateNode(block);
1813
+ const childVar = this.emitNode(block);
1664
1814
  this._createLines.push(`${elVar}.appendChild(${childVar});`);
1665
1815
  }
1666
1816
  }
1667
1817
  else if (this.is(arg, 'object')) {
1668
- this.generateAttributes(elVar, arg);
1818
+ this.emitAttributes(elVar, arg);
1669
1819
  }
1670
1820
  else if (typeof arg === 'string' || arg instanceof String) {
1671
1821
  const val = arg.valueOf();
1672
1822
  // Template tag appearing as a string arg (e.g., slot after multi-line attrs)
1673
1823
  const baseName = val.split(/[#.]/)[0];
1674
1824
  if (this.isHtmlTag(baseName || 'div') || this.isComponent(baseName)) {
1675
- const childVar = this.generateNode(arg);
1825
+ const childVar = this.emitNode(arg);
1676
1826
  this._createLines.push(`${elVar}.appendChild(${childVar});`);
1677
1827
  } else {
1678
1828
  const textVar = this.newTextVar();
@@ -1684,13 +1834,13 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1684
1834
  } else if (this.componentMembers && this.componentMembers.has(val)) {
1685
1835
  this._createLines.push(`${textVar} = document.createTextNode(String(${this._self}.${val}));`);
1686
1836
  } else {
1687
- this._createLines.push(`${textVar} = document.createTextNode(${this.generateInComponent(arg, 'value')});`);
1837
+ this._createLines.push(`${textVar} = document.createTextNode(${this.emitInComponent(arg, 'value')});`);
1688
1838
  }
1689
1839
  this._createLines.push(`${elVar}.appendChild(${textVar});`);
1690
1840
  }
1691
1841
  }
1692
1842
  else if (arg) {
1693
- const childVar = this.generateNode(arg);
1843
+ const childVar = this.emitNode(arg);
1694
1844
  this._createLines.push(`${elVar}.appendChild(${childVar});`);
1695
1845
  }
1696
1846
  }
@@ -1728,10 +1878,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1728
1878
  };
1729
1879
 
1730
1880
  // --------------------------------------------------------------------------
1731
- // generateTag — HTML element with static classes and children
1881
+ // emitTag — HTML element with static classes and children
1732
1882
  // --------------------------------------------------------------------------
1733
1883
 
1734
- proto.generateTag = function(tag, classes, args, id) {
1884
+ proto.emitTag = function(tag, classes, args, id) {
1735
1885
  const elVar = this.newElementVar();
1736
1886
  const isSvg = SVG_TAGS.has(tag) || this._svgDepth > 0;
1737
1887
  if (isSvg) {
@@ -1789,10 +1939,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1789
1939
  };
1790
1940
 
1791
1941
  // --------------------------------------------------------------------------
1792
- // generateDynamicTag — tag with .() CLSX dynamic classes
1942
+ // emitDynamicTag — tag with .() CLSX dynamic classes
1793
1943
  // --------------------------------------------------------------------------
1794
1944
 
1795
- proto.generateDynamicTag = function(tag, classExprs, children, staticClassArgs, id) {
1945
+ proto.emitDynamicTag = function(tag, classExprs, children, staticClassArgs, id) {
1796
1946
  const elVar = this.newElementVar();
1797
1947
  if (SVG_TAGS.has(tag) || this._svgDepth > 0) {
1798
1948
  this._createLines.push(`${elVar} = document.createElementNS('${SVG_NS}', '${tag}');`);
@@ -1805,7 +1955,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1805
1955
  const autoWireClaimed = this._claimAutoWire(elVar);
1806
1956
 
1807
1957
  // Defer className emission so class: attributes can merge with .() classes
1808
- const classArgs = [...(staticClassArgs || []), ...classExprs.map(e => this.generateInComponent(e, 'value'))];
1958
+ const classArgs = [...(staticClassArgs || []), ...classExprs.map(e => this.emitInComponent(e, 'value'))];
1809
1959
  const prevClassArgs = this._pendingClassArgs;
1810
1960
  const prevClassEl = this._pendingClassEl;
1811
1961
  this._pendingClassArgs = classArgs;
@@ -1833,10 +1983,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1833
1983
  };
1834
1984
 
1835
1985
  // --------------------------------------------------------------------------
1836
- // generateAttributes — attributes, events, and bindings on an element
1986
+ // emitAttributes — attributes, events, and bindings on an element
1837
1987
  // --------------------------------------------------------------------------
1838
1988
 
1839
- proto.generateAttributes = function(elVar, objExpr) {
1989
+ proto.emitAttributes = function(elVar, objExpr) {
1840
1990
  const inputType = extractInputType(objExpr.slice(1));
1841
1991
 
1842
1992
  for (let i = 1; i < objExpr.length; i++) {
@@ -1851,7 +2001,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1851
2001
  if (typeof value === 'string' && this.componentMembers?.has(value)) {
1852
2002
  this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => ${this._self}.${value}(e)));`);
1853
2003
  } else {
1854
- const handlerCode = this.generateInComponent(value, 'value');
2004
+ const handlerCode = this.emitInComponent(value, 'value');
1855
2005
  this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => (${handlerCode})(e)));`);
1856
2006
  }
1857
2007
  continue;
@@ -1866,7 +2016,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1866
2016
 
1867
2017
  // Class merging: class: values merge with .() dynamic classes
1868
2018
  if (key === 'class' || key === 'className') {
1869
- const valueCode = this.generateInComponent(value, 'value');
2019
+ const valueCode = this.emitInComponent(value, 'value');
1870
2020
  if (this._pendingClassArgs && this._pendingClassEl === elVar) {
1871
2021
  this._pendingClassArgs.push(valueCode);
1872
2022
  } else if (this.hasReactiveDeps(value)) {
@@ -1902,7 +2052,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1902
2052
  // Two-way binding: __bind_value__ pattern
1903
2053
  if (key.startsWith(BIND_PREFIX) && key.endsWith(BIND_SUFFIX)) {
1904
2054
  const prop = key.slice(BIND_PREFIX.length, -BIND_SUFFIX.length);
1905
- const valueCode = this.generateInComponent(value, 'value');
2055
+ const valueCode = this.emitInComponent(value, 'value');
1906
2056
 
1907
2057
  let event, valueAccessor;
1908
2058
  if (prop === 'checked') {
@@ -1924,7 +2074,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1924
2074
  continue;
1925
2075
  }
1926
2076
 
1927
- const valueCode = this.generateInComponent(value, 'value');
2077
+ const valueCode = this.emitInComponent(value, 'value');
1928
2078
 
1929
2079
  // value/checked with reactive deps: one-way push (use <=> for two-way)
1930
2080
  if ((key === 'value' || key === 'checked') && this.hasReactiveDeps(value)) {
@@ -1962,12 +2112,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1962
2112
  };
1963
2113
 
1964
2114
  // --------------------------------------------------------------------------
1965
- // generateTemplateBlock — a block of template children
2115
+ // emitTemplateBlock — a block of template children
1966
2116
  // --------------------------------------------------------------------------
1967
2117
 
1968
- proto.generateTemplateBlock = function(body) {
2118
+ proto.emitTemplateBlock = function(body) {
1969
2119
  if (!Array.isArray(body) || body[0] !== 'block') {
1970
- return this.generateNode(body);
2120
+ return this.emitNode(body);
1971
2121
  }
1972
2122
 
1973
2123
  const statements = body.slice(1);
@@ -1977,14 +2127,14 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1977
2127
  return commentVar;
1978
2128
  }
1979
2129
  if (statements.length === 1) {
1980
- return this.generateNode(statements[0]);
2130
+ return this.emitNode(statements[0]);
1981
2131
  }
1982
2132
 
1983
2133
  const fragVar = this.newElementVar('frag');
1984
2134
  this._createLines.push(`${fragVar} = document.createDocumentFragment();`);
1985
2135
  const children = [];
1986
2136
  for (const stmt of statements) {
1987
- const childVar = this.generateNode(stmt);
2137
+ const childVar = this.emitNode(stmt);
1988
2138
  this._createLines.push(`${fragVar}.appendChild(${childVar});`);
1989
2139
  children.push(childVar);
1990
2140
  }
@@ -1993,28 +2143,40 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1993
2143
  };
1994
2144
 
1995
2145
  // --------------------------------------------------------------------------
1996
- // generateConditional — reactive if/else using block factories
2146
+ // emitConditional — reactive if/else using block factories
1997
2147
  // --------------------------------------------------------------------------
1998
2148
 
1999
- proto.generateConditional = function(sexpr) {
2149
+ proto.emitConditional = function(sexpr) {
2000
2150
  this._pendingAutoWire = false;
2151
+
2152
+ // Fold flat else-if chains into nested structure.
2153
+ // Parser emits: ['if', c1, t1, ['if', c2, t2], ..., finalElse]
2154
+ // We need: ['if', c1, t1, ['if', c2, t2, [..., finalElse]]]
2155
+ if (sexpr.length > 4) {
2156
+ let chain = sexpr[sexpr.length - 1];
2157
+ for (let i = sexpr.length - 2; i >= 3; i--) {
2158
+ chain = [...sexpr[i], chain];
2159
+ }
2160
+ sexpr = [sexpr[0], sexpr[1], sexpr[2], chain];
2161
+ }
2162
+
2001
2163
  const [, condition, thenBlock, elseBlock] = sexpr;
2002
2164
 
2003
2165
  const anchorVar = this.newElementVar('anchor');
2004
2166
  this._createLines.push(`${anchorVar} = document.createComment('if');`);
2005
2167
 
2006
- const condCode = this.generateInComponent(condition, 'value');
2168
+ const condCode = this.emitInComponent(condition, 'value');
2007
2169
 
2008
2170
  const outerParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
2009
2171
  const outerExtra = outerParams ? `, ${outerParams}` : '';
2010
2172
 
2011
2173
  const thenBlockName = this.newBlockVar();
2012
- this.generateConditionBranch(thenBlockName, thenBlock);
2174
+ this.emitConditionBranch(thenBlockName, thenBlock);
2013
2175
 
2014
2176
  let elseBlockName = null;
2015
2177
  if (elseBlock) {
2016
2178
  elseBlockName = this.newBlockVar();
2017
- this.generateConditionBranch(elseBlockName, elseBlock);
2179
+ this.emitConditionBranch(elseBlockName, elseBlock);
2018
2180
  }
2019
2181
 
2020
2182
  const setupLines = [];
@@ -2055,6 +2217,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2055
2217
  setupLines.push(` }`);
2056
2218
  }
2057
2219
  setupLines.push(` ${effClose}`);
2220
+ if (this._factoryMode) {
2221
+ setupLines.push(` disposers.push(() => { if (currentBlock) { currentBlock.d(true); currentBlock = null; } });`);
2222
+ }
2058
2223
  setupLines.push(`}`);
2059
2224
 
2060
2225
  this._setupLines.push(setupLines.join('\n '));
@@ -2063,10 +2228,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2063
2228
  };
2064
2229
 
2065
2230
  // --------------------------------------------------------------------------
2066
- // generateConditionBranch — block factory for a conditional branch
2231
+ // emitConditionBranch — block factory for a conditional branch
2067
2232
  // --------------------------------------------------------------------------
2068
2233
 
2069
- proto.generateConditionBranch = function(blockName, block) {
2234
+ proto.emitConditionBranch = function(blockName, block) {
2070
2235
  const saved = [this._createLines, this._setupLines, this._factoryMode, this._factoryVars];
2071
2236
 
2072
2237
  this._createLines = [];
@@ -2074,7 +2239,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2074
2239
  this._factoryMode = true;
2075
2240
  this._factoryVars = new Set();
2076
2241
 
2077
- const rootVar = this.generateTemplateBlock(block);
2242
+ const rootVar = this.emitTemplateBlock(block);
2078
2243
  const createLines = this._createLines;
2079
2244
  const setupLines = this._setupLines;
2080
2245
  const factoryVars = this._factoryVars;
@@ -2160,10 +2325,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2160
2325
  };
2161
2326
 
2162
2327
  // --------------------------------------------------------------------------
2163
- // generateTemplateLoop — reactive for-loop with keyed reconciliation
2328
+ // emitTemplateLoop — reactive for-loop with keyed reconciliation
2164
2329
  // --------------------------------------------------------------------------
2165
2330
 
2166
- proto.generateTemplateLoop = function(sexpr) {
2331
+ proto.emitTemplateLoop = function(sexpr) {
2167
2332
  this._pendingAutoWire = false;
2168
2333
  const [head, vars, collection, guard, step, body] = sexpr;
2169
2334
 
@@ -2184,7 +2349,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2184
2349
  indexVar = indexVar || `_i${this._loopVarStack.length}`;
2185
2350
  }
2186
2351
 
2187
- const collectionCode = this.generateInComponent(collection, 'value');
2352
+ const collectionCode = this.emitInComponent(collection, 'value');
2188
2353
 
2189
2354
  // Extract key expression from body if present
2190
2355
  let keyExpr = itemVar;
@@ -2196,7 +2361,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2196
2361
  for (let i = 1; i < arg.length; i++) {
2197
2362
  const [k, v] = arg[i];
2198
2363
  if (k === 'key') {
2199
- keyExpr = this.generate(v, 'value');
2364
+ keyExpr = this.emit(v, 'value');
2200
2365
  break;
2201
2366
  }
2202
2367
  }
@@ -2217,7 +2382,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2217
2382
  const outerExtra = outerParams ? `, ${outerParams}` : '';
2218
2383
 
2219
2384
  this._loopVarStack.push({ itemVar, indexVar });
2220
- const itemNode = this.generateTemplateBlock(body);
2385
+ const itemNode = this.emitTemplateBlock(body);
2221
2386
  this._loopVarStack.pop();
2222
2387
  const itemCreateLines = this._createLines;
2223
2388
  const itemSetupLines = this._setupLines;
@@ -2254,10 +2419,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2254
2419
  };
2255
2420
 
2256
2421
  // --------------------------------------------------------------------------
2257
- // generateChildComponent — instantiate a child component
2422
+ // emitChildComponent — instantiate a child component
2258
2423
  // --------------------------------------------------------------------------
2259
2424
 
2260
- proto.generateChildComponent = function(componentName, args) {
2425
+ proto.emitChildComponent = function(componentName, args) {
2261
2426
  this._pendingAutoWire = false;
2262
2427
  const instVar = this.newElementVar('inst');
2263
2428
  const elVar = this.newElementVar('el');
@@ -2271,7 +2436,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2271
2436
  this._createLines.push(`} finally { __popComponent(__prev); } }`);
2272
2437
 
2273
2438
  for (const { event, value } of eventBindings) {
2274
- const handlerCode = this.generateInComponent(value, 'value');
2439
+ const handlerCode = this.emitInComponent(value, 'value');
2275
2440
  this._createLines.push(`${elVar}.addEventListener('${event}', (e) => __batch(() => (${handlerCode})(e)));`);
2276
2441
  }
2277
2442
 
@@ -2314,7 +2479,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2314
2479
  const member = typeof value === 'string' ? value : value[2];
2315
2480
  props.push(`${key}: ${this._self}.${member}`);
2316
2481
  } else {
2317
- const valueCode = this.generateInComponent(value, 'value');
2482
+ const valueCode = this.emitInComponent(value, 'value');
2318
2483
  props.push(`${key}: ${valueCode}`);
2319
2484
  if (this.hasReactiveDeps(value)) {
2320
2485
  reactiveProps.push({ key, valueCode });
@@ -2357,7 +2522,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2357
2522
  this._createLines = [];
2358
2523
  this._setupLines = [];
2359
2524
 
2360
- childrenVar = this.generateTemplateBlock(block);
2525
+ childrenVar = this.emitTemplateBlock(block);
2361
2526
 
2362
2527
  const childCreateLines = this._createLines;
2363
2528
  const childSetupLinesCopy = this._setupLines;
@@ -2374,7 +2539,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
2374
2539
  }
2375
2540
  } else if (arg && !childrenVar) {
2376
2541
  const textVar = this.newTextVar();
2377
- const exprCode = this.generateInComponent(arg, 'value');
2542
+ const exprCode = this.emitInComponent(arg, 'value');
2378
2543
  if (this.hasReactiveDeps(arg)) {
2379
2544
  this._createLines.push(`${textVar} = document.createTextNode('');`);
2380
2545
  const body = `${textVar}.data = ${exprCode};`;