rip-lang 3.13.23 → 3.13.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.13.23-blue.svg" alt="Version"></a>
12
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.13.24-blue.svg" alt="Version"></a>
13
13
  <a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
14
14
  <a href="#"><img src="https://img.shields.io/badge/tests-1%2C265%2F1%2C265-brightgreen.svg" alt="Tests"></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.13.23",
3
+ "version": "3.13.24",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
package/src/components.js CHANGED
@@ -106,23 +106,6 @@ function isPublicProp(target) {
106
106
  return Array.isArray(target) && target[0] === '.' && target[1] === 'this';
107
107
  }
108
108
 
109
- /**
110
- * Detect fragment root and collect direct child variables for proper removal.
111
- * After insertBefore, a DocumentFragment is empty — .remove() is a no-op.
112
- * Callers must remove each child element individually.
113
- */
114
- function getFragChildren(rootVar, createLines, localizeVar) {
115
- const root = localizeVar(rootVar);
116
- if (!/_frag\d+$/.test(root)) return null;
117
- const children = [];
118
- const re = new RegExp(`^${root.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\.appendChild\\(([^)]+)\\);`);
119
- for (const line of createLines) {
120
- const m = localizeVar(line).match(re);
121
- if (m) children.push(m[1]);
122
- }
123
- return children.length > 0 ? children : null;
124
- }
125
-
126
109
  // ============================================================================
127
110
  // Prototype Installation
128
111
  // ============================================================================
@@ -398,16 +381,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
398
381
  // Implicit nesting (inject -> before INDENT)
399
382
  // ─────────────────────────────────────────────────────────────────────
400
383
  if (nextToken && nextToken[0] === 'INDENT') {
401
- // Skip fromThen INDENTs inside string interpolation (e.g. "#{if x then y else z}")
402
- if (nextToken.fromThen) {
403
- let depth = 0;
404
- for (let j = i; j >= 0; j--) {
405
- let jt = tokens[j][0];
406
- if (jt === 'INTERPOLATION_END' || jt === 'STRING_END') depth++;
407
- if (jt === 'INTERPOLATION_START' || jt === 'STRING_START') depth--;
408
- if (depth < 0) { return 1; }
409
- }
410
- }
384
+ // fromThen INDENTs are inline conditional values (if x then y else z),
385
+ // never template nesting — normalizeLines only creates them for single-line then clauses
386
+ if (nextToken.fromThen) return 1;
411
387
  if (tag === '->' || tag === '=>' || tag === 'CALL_START' || tag === '(') {
412
388
  return 1;
413
389
  }
@@ -437,31 +413,17 @@ export function installComponentSupport(CodeGenerator, Lexer) {
437
413
  isTemplateElement = true;
438
414
  } else if (tag === 'IDENTIFIER' && isTemplateTag(token[1]) && !isAfterControlFlow) {
439
415
  isTemplateElement = true;
440
- } else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'STRING_END' || tag === 'IDENTIFIER' || tag === 'NUMBER' || tag === 'BOOL' || tag === 'CALL_END' || tag === ')') {
416
+ } else if (tag === 'IDENTIFIER' && !isAfterControlFlow) {
417
+ isTemplateElement = startsWithTag(tokens, i);
418
+ } else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'STRING_END' || tag === 'NUMBER' || tag === 'BOOL' || tag === 'CALL_END' || tag === ')') {
441
419
  isTemplateElement = startsWithTag(tokens, i);
442
- }
443
- else if (tag === 'IDENTIFIER' && i > 1 && tokens[i - 1][0] === '...') {
444
- if (startsWithTag(tokens, i)) {
445
- let commaToken = gen(',', ',', token);
446
- let arrowToken = gen('->', '->', token);
447
- arrowToken.newLine = true;
448
- tokens.splice(i + 1, 0, commaToken, arrowToken);
449
- return 3;
450
- }
451
420
  }
452
421
 
453
422
  if (isTemplateElement) {
454
423
  let isClassOrIdTail = tag === 'PROPERTY' && i > 0 && (tokens[i - 1][0] === '.' || tokens[i - 1][0] === '#');
424
+ let isBareTag = isClsxCallEnd || (tag === 'IDENTIFIER' && isTemplateTag(token[1])) || isClassOrIdTail;
455
425
 
456
- if (isClsxCallEnd) {
457
- let callStartToken = gen('CALL_START', '(', token);
458
- let arrowToken = gen('->', '->', token);
459
- arrowToken.newLine = true;
460
- tokens.splice(i + 1, 0, callStartToken, arrowToken);
461
- pendingCallEnds.push(currentIndent + 1);
462
- return 3;
463
- } else if ((tag === 'IDENTIFIER' && isTemplateTag(token[1])) || isClassOrIdTail) {
464
- // Bare tag or tag.class/tag#id (no other args): inject CALL_START -> and manage CALL_END
426
+ if (isBareTag) {
465
427
  let callStartToken = gen('CALL_START', '(', token);
466
428
  let arrowToken = gen('->', '->', token);
467
429
  arrowToken.newLine = true;
@@ -469,7 +431,6 @@ export function installComponentSupport(CodeGenerator, Lexer) {
469
431
  pendingCallEnds.push(currentIndent + 1);
470
432
  return 3;
471
433
  } else {
472
- // Tag with args: inject , -> (call wrapping handled by addImplicitBracesAndParens)
473
434
  let commaToken = gen(',', ',', token);
474
435
  let arrowToken = gen('->', '->', token);
475
436
  arrowToken.newLine = true;
@@ -503,16 +464,6 @@ export function installComponentSupport(CodeGenerator, Lexer) {
503
464
  // Utilities
504
465
  // ==========================================================================
505
466
 
506
- /**
507
- * Localize variable references for block factories.
508
- * Converts this._elN to _elN and this.x to ctx.x.
509
- */
510
- proto.localizeVar = function(line) {
511
- let result = line.replace(/this\.(_el\d+|_t\d+|_anchor\d+|_frag\d+|_slot\d+|_c\d+|_inst\d+|_empty\d+)/g, '$1');
512
- result = result.replace(/\bthis\b/g, 'ctx');
513
- return result;
514
- };
515
-
516
467
  /**
517
468
  * Check if name is an HTML/SVG tag
518
469
  */
@@ -560,12 +511,13 @@ export function installComponentSupport(CodeGenerator, Lexer) {
560
511
  * For component context where state variables are signals.
561
512
  */
562
513
  proto.transformComponentMembers = function(sexpr) {
514
+ const self = this._self;
563
515
  if (!Array.isArray(sexpr)) {
564
516
  if (typeof sexpr === 'string' && this.reactiveMembers && this.reactiveMembers.has(sexpr)) {
565
- return ['.', ['.', 'this', sexpr], 'value'];
517
+ return ['.', ['.', self, sexpr], 'value'];
566
518
  }
567
519
  if (typeof sexpr === 'string' && this.componentMembers && this.componentMembers.has(sexpr)) {
568
- return ['.', 'this', sexpr];
520
+ return ['.', self, sexpr];
569
521
  }
570
522
  return sexpr;
571
523
  }
@@ -574,9 +526,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
574
526
  if (sexpr[0] === '.' && sexpr[1] === 'this' && typeof sexpr[2] === 'string') {
575
527
  const memberName = sexpr[2];
576
528
  if (this.reactiveMembers && this.reactiveMembers.has(memberName)) {
577
- return ['.', sexpr, 'value']; // this.X → this.X.value
529
+ return ['.', ['.', self, memberName], 'value'];
578
530
  }
579
- return sexpr;
531
+ return this._factoryMode ? ['.', self, sexpr[2]] : sexpr;
580
532
  }
581
533
 
582
534
  // Dot access: transform the object but not the property name
@@ -819,10 +771,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
819
771
  */
820
772
  proto.generateInComponent = function(sexpr, context) {
821
773
  if (typeof sexpr === 'string' && this.reactiveMembers && this.reactiveMembers.has(sexpr)) {
822
- return `this.${sexpr}.value`;
774
+ return `${this._self}.${sexpr}.value`;
823
775
  }
824
776
  if (typeof sexpr === 'string' && this.componentMembers && this.componentMembers.has(sexpr)) {
825
- return `this.${sexpr}`;
777
+ return `${this._self}.${sexpr}`;
826
778
  }
827
779
  if (Array.isArray(sexpr) && this.reactiveMembers) {
828
780
  const transformed = this.transformComponentMembers(sexpr);
@@ -854,6 +806,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
854
806
  this._setupLines = [];
855
807
  this._blockFactories = [];
856
808
  this._loopVarStack = [];
809
+ this._factoryMode = false;
810
+ this._factoryVars = null;
811
+ this._fragChildren = new Map();
857
812
 
858
813
  const statements = this.is(body, 'block') ? body.slice(1) : [body];
859
814
 
@@ -865,10 +820,13 @@ export function installComponentSupport(CodeGenerator, Lexer) {
865
820
  } else {
866
821
  rootVar = this.newElementVar('frag');
867
822
  this._createLines.push(`${rootVar} = document.createDocumentFragment();`);
823
+ const children = [];
868
824
  for (const stmt of statements) {
869
825
  const childVar = this.generateNode(stmt);
870
826
  this._createLines.push(`${rootVar}.appendChild(${childVar});`);
827
+ children.push(childVar);
871
828
  }
829
+ this._fragChildren.set(rootVar, children);
872
830
  }
873
831
 
874
832
  return {
@@ -886,12 +844,30 @@ export function installComponentSupport(CodeGenerator, Lexer) {
886
844
 
887
845
  /** Generate a unique element variable name */
888
846
  proto.newElementVar = function(hint = 'el') {
889
- return `this._${hint}${this._elementCount++}`;
847
+ const name = `_${hint}${this._elementCount++}`;
848
+ if (this._factoryVars) this._factoryVars.add(name);
849
+ return this._factoryMode ? name : `this.${name}`;
890
850
  };
891
851
 
892
852
  /** Generate a unique text node variable name */
893
853
  proto.newTextVar = function() {
894
- return `this._t${this._textCount++}`;
854
+ const name = `_t${this._textCount++}`;
855
+ if (this._factoryVars) this._factoryVars.add(name);
856
+ return this._factoryMode ? name : `this.${name}`;
857
+ };
858
+
859
+ /** Context reference — 'this' in component body, 'ctx' in block factories */
860
+ Object.defineProperty(proto, '_self', {
861
+ get() { return this._factoryMode ? 'ctx' : 'this'; }
862
+ });
863
+
864
+ /** Push an effect line, wrapping with disposer tracking in factory mode */
865
+ proto._pushEffect = function(body) {
866
+ if (this._factoryMode) {
867
+ this._setupLines.push(`disposers.push(__effect(() => { ${body} }));`);
868
+ } else {
869
+ this._setupLines.push(`__effect(() => { ${body} });`);
870
+ }
895
871
  };
896
872
 
897
873
  // --------------------------------------------------------------------------
@@ -911,7 +887,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
911
887
  if (this.reactiveMembers && this.reactiveMembers.has(str)) {
912
888
  const textVar = this.newTextVar();
913
889
  this._createLines.push(`${textVar} = document.createTextNode('');`);
914
- this._setupLines.push(`__effect(() => { ${textVar}.data = this.${str}.value; });`);
890
+ this._pushEffect(`${textVar}.data = ${this._self}.${str}.value;`);
915
891
  return textVar;
916
892
  }
917
893
  // Static tag without content (possibly with #id)
@@ -953,15 +929,15 @@ export function installComponentSupport(CodeGenerator, Lexer) {
953
929
 
954
930
  // Property access on this (e.g., @prop, @children)
955
931
  if (obj === 'this' && typeof prop === 'string') {
932
+ const s = this._self;
956
933
  if (this.reactiveMembers && this.reactiveMembers.has(prop)) {
957
934
  const textVar = this.newTextVar();
958
935
  this._createLines.push(`${textVar} = document.createTextNode('');`);
959
- this._setupLines.push(`__effect(() => { ${textVar}.data = this.${prop}.value; });`);
936
+ this._pushEffect(`${textVar}.data = ${s}.${prop}.value;`);
960
937
  return textVar;
961
938
  }
962
- // Slot/prop — handle DOM nodes (children) and plain values
963
939
  const slotVar = this.newElementVar('slot');
964
- this._createLines.push(`${slotVar} = this.${prop} instanceof Node ? this.${prop} : (this.${prop} != null ? document.createTextNode(String(this.${prop})) : document.createComment(''));`);
940
+ this._createLines.push(`${slotVar} = ${s}.${prop} instanceof Node ? ${s}.${prop} : (${s}.${prop} != null ? document.createTextNode(String(${s}.${prop})) : document.createComment(''));`);
965
941
  return slotVar;
966
942
  }
967
943
 
@@ -1018,7 +994,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1018
994
  const exprCode = this.generateInComponent(sexpr, 'value');
1019
995
  if (this.hasReactiveDeps(sexpr)) {
1020
996
  this._createLines.push(`${textVar} = document.createTextNode('');`);
1021
- this._setupLines.push(`__effect(() => { ${textVar}.data = ${exprCode}; });`);
997
+ this._pushEffect(`${textVar}.data = ${exprCode};`);
1022
998
  } else {
1023
999
  this._createLines.push(`${textVar} = document.createTextNode(String(${exprCode}));`);
1024
1000
  }
@@ -1057,9 +1033,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1057
1033
  this._createLines.push(`${textVar} = document.createTextNode(${val});`);
1058
1034
  } else if (this.reactiveMembers && this.reactiveMembers.has(val)) {
1059
1035
  this._createLines.push(`${textVar} = document.createTextNode('');`);
1060
- this._setupLines.push(`__effect(() => { ${textVar}.data = this.${val}.value; });`);
1036
+ this._pushEffect(`${textVar}.data = ${this._self}.${val}.value;`);
1061
1037
  } else if (this.componentMembers && this.componentMembers.has(val)) {
1062
- this._createLines.push(`${textVar} = document.createTextNode(String(this.${val}));`);
1038
+ this._createLines.push(`${textVar} = document.createTextNode(String(${this._self}.${val}));`);
1063
1039
  } else {
1064
1040
  this._createLines.push(`${textVar} = document.createTextNode(${this.generateInComponent(arg, 'value')});`);
1065
1041
  }
@@ -1112,9 +1088,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1112
1088
  } else {
1113
1089
  const combined = this._pendingClassArgs.join(', ');
1114
1090
  if (isSvg) {
1115
- this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('class', __clsx(${combined})); });`);
1091
+ this._pushEffect(`${elVar}.setAttribute('class', __clsx(${combined}));`);
1116
1092
  } else {
1117
- this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${combined}); });`);
1093
+ this._pushEffect(`${elVar}.className = __clsx(${combined});`);
1118
1094
  }
1119
1095
  }
1120
1096
  this._pendingClassArgs = prevClassArgs;
@@ -1151,9 +1127,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1151
1127
  const combined = this._pendingClassArgs.join(', ');
1152
1128
  const isSvg = SVG_TAGS.has(tag) || this._svgDepth > 0;
1153
1129
  if (isSvg) {
1154
- this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('class', __clsx(${combined})); });`);
1130
+ this._pushEffect(`${elVar}.setAttribute('class', __clsx(${combined}));`);
1155
1131
  } else {
1156
- this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${combined}); });`);
1132
+ this._pushEffect(`${elVar}.className = __clsx(${combined});`);
1157
1133
  }
1158
1134
  }
1159
1135
  this._pendingClassArgs = prevClassArgs;
@@ -1175,9 +1151,8 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1175
1151
  // Event handler: @click or (. this eventName)
1176
1152
  if (this.is(key, '.') && key[1] === 'this') {
1177
1153
  const eventName = key[2];
1178
- // Bind method references to this
1179
1154
  if (typeof value === 'string' && this.componentMembers?.has(value)) {
1180
- this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => this.${value}(e)));`);
1155
+ this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => ${this._self}.${value}(e)));`);
1181
1156
  } else {
1182
1157
  const handlerCode = this.generateInComponent(value, 'value');
1183
1158
  this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => (${handlerCode})(e)));`);
@@ -1199,9 +1174,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1199
1174
  this._pendingClassArgs.push(valueCode);
1200
1175
  } else if (this.hasReactiveDeps(value)) {
1201
1176
  if (this._svgDepth > 0) {
1202
- this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('class', __clsx(${valueCode})); });`);
1177
+ this._pushEffect(`${elVar}.setAttribute('class', __clsx(${valueCode}));`);
1203
1178
  } else {
1204
- this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${valueCode}); });`);
1179
+ this._pushEffect(`${elVar}.className = __clsx(${valueCode});`);
1205
1180
  }
1206
1181
  } else {
1207
1182
  if (this._svgDepth > 0) {
@@ -1216,7 +1191,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1216
1191
  // Element ref: ref: "name" → this.name = element
1217
1192
  if (key === 'ref') {
1218
1193
  const refName = String(value).replace(/^["']|["']$/g, '');
1219
- this._createLines.push(`this.${refName} = ${elVar};`);
1194
+ this._createLines.push(`${this._self}.${refName} = ${elVar};`);
1220
1195
  continue;
1221
1196
  }
1222
1197
 
@@ -1235,11 +1210,11 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1235
1210
  ? 'e.target.valueAsNumber' : 'e.target.value';
1236
1211
  }
1237
1212
 
1238
- this._setupLines.push(`__effect(() => { ${elVar}.${prop} = ${valueCode}; });`);
1213
+ this._pushEffect(`${elVar}.${prop} = ${valueCode};`);
1239
1214
  let assignCode = `${valueCode} = ${valueAccessor}`;
1240
1215
  const rootMember = !this.isSimpleAssignable(value) && this.findRootReactiveMember(value);
1241
1216
  if (rootMember) {
1242
- assignCode += `; this.${rootMember}.touch?.()`;
1217
+ assignCode += `; ${this._self}.${rootMember}.touch?.()`;
1243
1218
  }
1244
1219
  this._createLines.push(`${elVar}.addEventListener('${event}', (e) => { ${assignCode}; });`);
1245
1220
  continue;
@@ -1249,9 +1224,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1249
1224
 
1250
1225
  // Smart two-way binding for value/checked when bound to reactive state
1251
1226
  if ((key === 'value' || key === 'checked') && this.hasReactiveDeps(value)) {
1252
- this._setupLines.push(`__effect(() => { ${elVar}.${key} = ${valueCode}; });`);
1253
- // Generate reverse binding for simple assignable targets or nested
1254
- // reactive paths (with touch() for Svelte-style invalidation)
1227
+ this._pushEffect(`${elVar}.${key} = ${valueCode};`);
1255
1228
  const rootMemberImplicit = !this.isSimpleAssignable(value) && this.findRootReactiveMember(value);
1256
1229
  if (this.isSimpleAssignable(value) || rootMemberImplicit) {
1257
1230
  const event = key === 'checked' ? 'change' : 'input';
@@ -1260,7 +1233,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1260
1233
  : 'e.target.value';
1261
1234
  let assignCode = `${valueCode} = ${accessor}`;
1262
1235
  if (rootMemberImplicit) {
1263
- assignCode += `; this.${rootMemberImplicit}.touch?.()`;
1236
+ assignCode += `; ${this._self}.${rootMemberImplicit}.touch?.()`;
1264
1237
  }
1265
1238
  this._createLines.push(`${elVar}.addEventListener('${event}', (e) => { ${assignCode}; });`);
1266
1239
  }
@@ -1269,18 +1242,18 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1269
1242
 
1270
1243
  if (key === 'innerHTML' || key === 'textContent' || key === 'innerText') {
1271
1244
  if (this.hasReactiveDeps(value)) {
1272
- this._setupLines.push(`__effect(() => { ${elVar}.${key} = ${valueCode}; });`);
1245
+ this._pushEffect(`${elVar}.${key} = ${valueCode};`);
1273
1246
  } else {
1274
1247
  this._createLines.push(`${elVar}.${key} = ${valueCode};`);
1275
1248
  }
1276
1249
  } else if (BOOLEAN_ATTRS.has(key)) {
1277
1250
  if (this.hasReactiveDeps(value)) {
1278
- this._setupLines.push(`__effect(() => { ${elVar}.toggleAttribute('${key}', !!${valueCode}); });`);
1251
+ this._pushEffect(`${elVar}.toggleAttribute('${key}', !!${valueCode});`);
1279
1252
  } else {
1280
1253
  this._createLines.push(`if (${valueCode}) ${elVar}.setAttribute('${key}', '');`);
1281
1254
  }
1282
1255
  } else if (this.hasReactiveDeps(value)) {
1283
- this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('${key}', ${valueCode}); });`);
1256
+ this._pushEffect(`${elVar}.setAttribute('${key}', ${valueCode});`);
1284
1257
  } else {
1285
1258
  this._createLines.push(`${elVar}.setAttribute('${key}', ${valueCode});`);
1286
1259
  }
@@ -1309,10 +1282,13 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1309
1282
 
1310
1283
  const fragVar = this.newElementVar('frag');
1311
1284
  this._createLines.push(`${fragVar} = document.createDocumentFragment();`);
1285
+ const children = [];
1312
1286
  for (const stmt of statements) {
1313
1287
  const childVar = this.generateNode(stmt);
1314
1288
  this._createLines.push(`${fragVar}.appendChild(${childVar});`);
1289
+ children.push(childVar);
1315
1290
  }
1291
+ this._fragChildren.set(fragVar, children);
1316
1292
  return fragVar;
1317
1293
  };
1318
1294
 
@@ -1328,9 +1304,8 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1328
1304
 
1329
1305
  const condCode = this.generateInComponent(condition, 'value');
1330
1306
 
1331
- // Collect loop variables from enclosing for-loops
1332
- const loopParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
1333
- const extraArgs = loopParams ? `, ${loopParams}` : '';
1307
+ const outerParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
1308
+ const outerExtra = outerParams ? `, ${outerParams}` : '';
1334
1309
 
1335
1310
  const thenBlockName = this.newBlockVar();
1336
1311
  this.generateConditionBranch(thenBlockName, thenBlock);
@@ -1347,7 +1322,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1347
1322
  setupLines.push(` const anchor = ${anchorVar};`);
1348
1323
  setupLines.push(` let currentBlock = null;`);
1349
1324
  setupLines.push(` let showing = null;`);
1350
- setupLines.push(` __effect(() => {`);
1325
+ const effOpen = this._factoryMode ? 'disposers.push(__effect(() => {' : '__effect(() => {';
1326
+ const effClose = this._factoryMode ? '}));' : '});';
1327
+ setupLines.push(` ${effOpen}`);
1351
1328
  setupLines.push(` const show = !!(${condCode});`);
1352
1329
  setupLines.push(` const want = show ? 'then' : ${elseBlock ? "'else'" : 'null'};`);
1353
1330
  setupLines.push(` if (want === showing) return;`);
@@ -1359,20 +1336,20 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1359
1336
  setupLines.push(` showing = want;`);
1360
1337
  setupLines.push(``);
1361
1338
  setupLines.push(` if (want === 'then') {`);
1362
- setupLines.push(` currentBlock = ${thenBlockName}(this${extraArgs});`);
1339
+ setupLines.push(` currentBlock = ${thenBlockName}(${this._self}${outerExtra});`);
1363
1340
  setupLines.push(` currentBlock.c();`);
1364
1341
  setupLines.push(` if (anchor.parentNode) currentBlock.m(anchor.parentNode, anchor.nextSibling);`);
1365
- setupLines.push(` currentBlock.p(this${extraArgs});`);
1342
+ setupLines.push(` currentBlock.p(${this._self}${outerExtra});`);
1366
1343
  setupLines.push(` }`);
1367
1344
  if (elseBlock) {
1368
1345
  setupLines.push(` if (want === 'else') {`);
1369
- setupLines.push(` currentBlock = ${elseBlockName}(this${extraArgs});`);
1346
+ setupLines.push(` currentBlock = ${elseBlockName}(${this._self}${outerExtra});`);
1370
1347
  setupLines.push(` currentBlock.c();`);
1371
1348
  setupLines.push(` if (anchor.parentNode) currentBlock.m(anchor.parentNode, anchor.nextSibling);`);
1372
- setupLines.push(` currentBlock.p(this${extraArgs});`);
1349
+ setupLines.push(` currentBlock.p(${this._self}${outerExtra});`);
1373
1350
  setupLines.push(` }`);
1374
1351
  }
1375
- setupLines.push(` });`);
1352
+ setupLines.push(` ${effClose}`);
1376
1353
  setupLines.push(`}`);
1377
1354
 
1378
1355
  this._setupLines.push(setupLines.join('\n '));
@@ -1385,36 +1362,36 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1385
1362
  // --------------------------------------------------------------------------
1386
1363
 
1387
1364
  proto.generateConditionBranch = function(blockName, block) {
1388
- const savedCreateLines = this._createLines;
1389
- const savedSetupLines = this._setupLines;
1365
+ const saved = [this._createLines, this._setupLines, this._factoryMode, this._factoryVars];
1390
1366
 
1391
1367
  this._createLines = [];
1392
1368
  this._setupLines = [];
1369
+ this._factoryMode = true;
1370
+ this._factoryVars = new Set();
1393
1371
 
1394
1372
  const rootVar = this.generateTemplateBlock(block);
1395
1373
  const createLines = this._createLines;
1396
1374
  const setupLines = this._setupLines;
1375
+ const factoryVars = this._factoryVars;
1397
1376
 
1398
- this._createLines = savedCreateLines;
1399
- this._setupLines = savedSetupLines;
1377
+ [this._createLines, this._setupLines, this._factoryMode, this._factoryVars] = saved;
1400
1378
 
1401
- const localizeVar = (line) => this.localizeVar(line);
1379
+ const outerParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
1380
+ const extraParams = outerParams ? `, ${outerParams}` : '';
1402
1381
 
1403
- // Include enclosing loop variables in the factory signature
1404
- const loopParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
1405
- const extraParams = loopParams ? `, ${loopParams}` : '';
1382
+ this.emitBlockFactory(blockName, `ctx${extraParams}`, rootVar, createLines, setupLines, factoryVars);
1383
+ };
1406
1384
 
1385
+ // --------------------------------------------------------------------------
1386
+ // emitBlockFactory — shared factory generation for conditionals and loops
1387
+ // --------------------------------------------------------------------------
1388
+
1389
+ proto.emitBlockFactory = function(blockName, params, rootVar, createLines, setupLines, factoryVars) {
1407
1390
  const factoryLines = [];
1408
- factoryLines.push(`function ${blockName}(ctx${extraParams}) {`);
1391
+ factoryLines.push(`function ${blockName}(${params}) {`);
1409
1392
 
1410
- // Declare local variables
1411
- const localVars = new Set();
1412
- for (const line of createLines) {
1413
- const match = line.match(/^this\.(_(?:el|t|anchor|frag|slot|c|inst|empty)\d+)\s*=/);
1414
- if (match) localVars.add(match[1]);
1415
- }
1416
- if (localVars.size > 0) {
1417
- factoryLines.push(` let ${[...localVars].join(', ')};`);
1393
+ if (factoryVars.size > 0) {
1394
+ factoryLines.push(` let ${[...factoryVars].join(', ')};`);
1418
1395
  }
1419
1396
 
1420
1397
  const hasEffects = setupLines.length > 0;
@@ -1424,49 +1401,43 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1424
1401
 
1425
1402
  factoryLines.push(` return {`);
1426
1403
 
1427
- // c() - create
1428
1404
  factoryLines.push(` c() {`);
1429
1405
  for (const line of createLines) {
1430
- factoryLines.push(` ${localizeVar(line)}`);
1406
+ factoryLines.push(` ${line}`);
1431
1407
  }
1432
1408
  factoryLines.push(` },`);
1433
1409
 
1434
- // m() - mount
1410
+ const fragChildren = this._fragChildren.get(rootVar);
1435
1411
  factoryLines.push(` m(target, anchor) {`);
1436
- factoryLines.push(` target.insertBefore(${localizeVar(rootVar)}, anchor);`);
1412
+ if (fragChildren) {
1413
+ for (const child of fragChildren) {
1414
+ factoryLines.push(` if (target) target.insertBefore(${child}, anchor);`);
1415
+ }
1416
+ } else {
1417
+ factoryLines.push(` if (target) target.insertBefore(${rootVar}, anchor);`);
1418
+ }
1437
1419
  factoryLines.push(` },`);
1438
1420
 
1439
- // p() - update/patch
1440
- factoryLines.push(` p(ctx${extraParams}) {`);
1421
+ factoryLines.push(` p(${params}) {`);
1441
1422
  if (hasEffects) {
1442
1423
  factoryLines.push(` disposers.forEach(d => d());`);
1443
1424
  factoryLines.push(` disposers = [];`);
1444
1425
  for (const line of setupLines) {
1445
- const localizedLine = localizeVar(line);
1446
- const wrappedLine = localizedLine.replace(
1447
- /__effect\(\(\) => \{/g,
1448
- 'disposers.push(__effect(() => {'
1449
- ).replace(
1450
- /\}\);$/gm,
1451
- '}));'
1452
- );
1453
- factoryLines.push(` ${wrappedLine}`);
1426
+ factoryLines.push(` ${line}`);
1454
1427
  }
1455
1428
  }
1456
1429
  factoryLines.push(` },`);
1457
1430
 
1458
- // d() - destroy
1459
1431
  factoryLines.push(` d(detaching) {`);
1460
1432
  if (hasEffects) {
1461
1433
  factoryLines.push(` disposers.forEach(d => d());`);
1462
1434
  }
1463
- const condFragChildren = getFragChildren(rootVar, createLines, localizeVar);
1464
- if (condFragChildren) {
1465
- for (const child of condFragChildren) {
1435
+ if (fragChildren) {
1436
+ for (const child of fragChildren) {
1466
1437
  factoryLines.push(` if (detaching && ${child}) ${child}.remove();`);
1467
1438
  }
1468
1439
  } else {
1469
- factoryLines.push(` if (detaching && ${localizeVar(rootVar)}) ${localizeVar(rootVar)}.remove();`);
1440
+ factoryLines.push(` if (detaching && ${rootVar}) ${rootVar}.remove();`);
1470
1441
  }
1471
1442
  factoryLines.push(` }`);
1472
1443
 
@@ -1514,104 +1485,27 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1514
1485
  }
1515
1486
  }
1516
1487
 
1517
- // Save state and generate item template in isolation
1518
- const savedCreateLines = this._createLines;
1519
- const savedSetupLines = this._setupLines;
1488
+ const saved = [this._createLines, this._setupLines, this._factoryMode, this._factoryVars];
1520
1489
 
1521
1490
  this._createLines = [];
1522
1491
  this._setupLines = [];
1492
+ this._factoryMode = true;
1493
+ this._factoryVars = new Set();
1523
1494
 
1524
- // Capture enclosing loop variables before pushing current loop
1525
- const outerLoopParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
1526
- const outerExtra = outerLoopParams ? `, ${outerLoopParams}` : '';
1495
+ const outerParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
1496
+ const outerExtra = outerParams ? `, ${outerParams}` : '';
1527
1497
 
1528
1498
  this._loopVarStack.push({ itemVar, indexVar });
1529
1499
  const itemNode = this.generateTemplateBlock(body);
1530
1500
  this._loopVarStack.pop();
1531
1501
  const itemCreateLines = this._createLines;
1532
1502
  const itemSetupLines = this._setupLines;
1503
+ const itemFactoryVars = this._factoryVars;
1533
1504
 
1534
- this._createLines = savedCreateLines;
1535
- this._setupLines = savedSetupLines;
1536
-
1537
- const localizeVar = (line) => this.localizeVar(line);
1538
-
1539
- // Generate block factory
1540
- const factoryLines = [];
1541
- factoryLines.push(`function ${blockName}(ctx, ${itemVar}, ${indexVar}${outerExtra}) {`);
1542
-
1543
- const localVars = new Set();
1544
- for (const line of itemCreateLines) {
1545
- const match = line.match(/^this\.(_(?:el|t|anchor|frag|slot|c|inst|empty)\d+)\s*=/);
1546
- if (match) localVars.add(match[1]);
1547
- }
1548
- if (localVars.size > 0) {
1549
- factoryLines.push(` let ${[...localVars].join(', ')};`);
1550
- }
1505
+ [this._createLines, this._setupLines, this._factoryMode, this._factoryVars] = saved;
1551
1506
 
1552
- const hasEffects = itemSetupLines.length > 0;
1553
- if (hasEffects) {
1554
- factoryLines.push(` let disposers = [];`);
1555
- }
1556
-
1557
- factoryLines.push(` return {`);
1558
-
1559
- // c() - create
1560
- factoryLines.push(` c() {`);
1561
- for (const line of itemCreateLines) {
1562
- factoryLines.push(` ${localizeVar(line)}`);
1563
- }
1564
- factoryLines.push(` },`);
1565
-
1566
- // m() - mount (also repositions already-mounted blocks)
1567
- const loopFragChildren = getFragChildren(itemNode, itemCreateLines, localizeVar);
1568
- factoryLines.push(` m(target, anchor) {`);
1569
- if (loopFragChildren) {
1570
- for (const child of loopFragChildren) {
1571
- factoryLines.push(` if (target) target.insertBefore(${child}, anchor);`);
1572
- }
1573
- } else {
1574
- factoryLines.push(` if (target) target.insertBefore(${localizeVar(itemNode)}, anchor);`);
1575
- }
1576
- factoryLines.push(` },`);
1577
-
1578
- // p() - update
1579
- factoryLines.push(` p(ctx, ${itemVar}, ${indexVar}${outerExtra}) {`);
1580
- if (hasEffects) {
1581
- factoryLines.push(` disposers.forEach(d => d());`);
1582
- factoryLines.push(` disposers = [];`);
1583
- for (const line of itemSetupLines) {
1584
- const localizedLine = localizeVar(line);
1585
- const wrappedLine = localizedLine.replace(
1586
- /__effect\(\(\) => \{/g,
1587
- 'disposers.push(__effect(() => {'
1588
- ).replace(
1589
- /\}\);$/gm,
1590
- '}));'
1591
- );
1592
- factoryLines.push(` ${wrappedLine}`);
1593
- }
1594
- }
1595
- factoryLines.push(` },`);
1596
-
1597
- // d() - destroy
1598
- factoryLines.push(` d(detaching) {`);
1599
- if (hasEffects) {
1600
- factoryLines.push(` disposers.forEach(d => d());`);
1601
- }
1602
- if (loopFragChildren) {
1603
- for (const child of loopFragChildren) {
1604
- factoryLines.push(` if (detaching && ${child}) ${child}.remove();`);
1605
- }
1606
- } else {
1607
- factoryLines.push(` if (detaching && ${localizeVar(itemNode)}) ${localizeVar(itemNode)}.remove();`);
1608
- }
1609
- factoryLines.push(` }`);
1610
-
1611
- factoryLines.push(` };`);
1612
- factoryLines.push(`}`);
1613
-
1614
- this._blockFactories.push(factoryLines.join('\n'));
1507
+ const loopParams = `ctx, ${itemVar}, ${indexVar}${outerExtra}`;
1508
+ this.emitBlockFactory(blockName, loopParams, itemNode, itemCreateLines, itemSetupLines, itemFactoryVars);
1615
1509
 
1616
1510
  // Generate reconciliation code in _setup()
1617
1511
  const setupLines = [];
@@ -1619,7 +1513,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1619
1513
  setupLines.push(`{`);
1620
1514
  setupLines.push(` const __anchor = ${anchorVar};`);
1621
1515
  setupLines.push(` const __map = new Map();`);
1622
- setupLines.push(` __effect(() => {`);
1516
+ const effOpen = this._factoryMode ? 'disposers.push(__effect(() => {' : '__effect(() => {';
1517
+ const effClose = this._factoryMode ? '}));' : '});';
1518
+ setupLines.push(` ${effOpen}`);
1623
1519
  setupLines.push(` const __items = ${collectionCode};`);
1624
1520
  setupLines.push(` const __parent = __anchor.parentNode;`);
1625
1521
  setupLines.push(` const __newMap = new Map();`);
@@ -1629,11 +1525,11 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1629
1525
  setupLines.push(` const __key = ${keyExpr};`);
1630
1526
  setupLines.push(` let __block = __map.get(__key);`);
1631
1527
  setupLines.push(` if (!__block) {`);
1632
- setupLines.push(` __block = ${blockName}(this, ${itemVar}, ${indexVar}${outerExtra});`);
1528
+ setupLines.push(` __block = ${blockName}(${this._self}, ${itemVar}, ${indexVar}${outerExtra});`);
1633
1529
  setupLines.push(` __block.c();`);
1634
1530
  setupLines.push(` }`);
1635
1531
  setupLines.push(` __block.m(__parent, __anchor);`);
1636
- setupLines.push(` __block.p(this, ${itemVar}, ${indexVar}${outerExtra});`);
1532
+ setupLines.push(` __block.p(${this._self}, ${itemVar}, ${indexVar}${outerExtra});`);
1637
1533
  setupLines.push(` __newMap.set(__key, __block);`);
1638
1534
  setupLines.push(` }`);
1639
1535
  setupLines.push(``);
@@ -1643,7 +1539,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1643
1539
  setupLines.push(``);
1644
1540
  setupLines.push(` __map.clear();`);
1645
1541
  setupLines.push(` for (const [__k, __v] of __newMap) __map.set(__k, __v);`);
1646
- setupLines.push(` });`);
1542
+ setupLines.push(` ${effClose}`);
1647
1543
  setupLines.push(`}`);
1648
1544
 
1649
1545
  this._setupLines.push(setupLines.join('\n '));
@@ -1660,15 +1556,16 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1660
1556
  const elVar = this.newElementVar('el');
1661
1557
  const { propsCode, reactiveProps, childrenSetupLines } = this.buildComponentProps(args);
1662
1558
 
1559
+ const s = this._self;
1663
1560
  this._createLines.push(`${instVar} = new ${componentName}(${propsCode});`);
1664
1561
  this._createLines.push(`${elVar} = ${instVar}._create();`);
1665
- this._createLines.push(`(this._children || (this._children = [])).push(${instVar});`);
1562
+ this._createLines.push(`(${s}._children || (${s}._children = [])).push(${instVar});`);
1666
1563
 
1667
1564
  this._setupLines.push(`if (${instVar}._setup) ${instVar}._setup();`);
1668
1565
  this._setupLines.push(`if (${instVar}.mounted) ${instVar}.mounted();`);
1669
1566
 
1670
1567
  for (const { key, valueCode } of reactiveProps) {
1671
- this._setupLines.push(`__effect(() => { if (${instVar}.${key}) ${instVar}.${key}.value = ${valueCode}; });`);
1568
+ this._pushEffect(`if (${instVar}.${key}) ${instVar}.${key}.value = ${valueCode};`);
1672
1569
  }
1673
1570
 
1674
1571
  for (const line of childrenSetupLines) {
@@ -1688,56 +1585,43 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1688
1585
  let childrenVar = null;
1689
1586
  const childrenSetupLines = [];
1690
1587
 
1588
+ // Simple reactive values pass the signal directly for shared reactivity;
1589
+ // complex expressions use normal .value unwrapping to compute the value.
1590
+ const addProp = (key, value) => {
1591
+ const isDirectSignal = this.reactiveMembers && (
1592
+ (typeof value === 'string' && this.reactiveMembers.has(value)) ||
1593
+ (Array.isArray(value) && value[0] === '.' && value[1] === 'this' && typeof value[2] === 'string' && this.reactiveMembers.has(value[2]))
1594
+ );
1595
+ if (isDirectSignal) {
1596
+ const member = typeof value === 'string' ? value : value[2];
1597
+ props.push(`${key}: ${this._self}.${member}`);
1598
+ } else {
1599
+ const valueCode = this.generateInComponent(value, 'value');
1600
+ props.push(`${key}: ${valueCode}`);
1601
+ if (this.hasReactiveDeps(value)) {
1602
+ reactiveProps.push({ key, valueCode });
1603
+ }
1604
+ }
1605
+ };
1606
+
1607
+ const addObjectProps = (objExpr) => {
1608
+ for (let i = 1; i < objExpr.length; i++) {
1609
+ const [key, value] = objExpr[i];
1610
+ if (typeof key === 'string') addProp(key, value);
1611
+ }
1612
+ };
1613
+
1691
1614
  for (const arg of args) {
1692
1615
  if (this.is(arg, 'object')) {
1693
- for (let i = 1; i < arg.length; i++) {
1694
- const [key, value] = arg[i];
1695
- if (typeof key === 'string') {
1696
- // Simple reactive identifier — pass signal directly for shared reactivity.
1697
- // Complex expressions — use normal .value unwrapping to compute the value.
1698
- const isSimpleReactive = this.reactiveMembers && (
1699
- (typeof value === 'string' && this.reactiveMembers.has(value)) ||
1700
- (Array.isArray(value) && value[0] === '.' && value[1] === 'this' && typeof value[2] === 'string' && this.reactiveMembers.has(value[2]))
1701
- );
1702
- if (isSimpleReactive) {
1703
- const member = typeof value === 'string' ? value : value[2];
1704
- props.push(`${key}: this.${member}`);
1705
- } else {
1706
- const valueCode = this.generateInComponent(value, 'value');
1707
- props.push(`${key}: ${valueCode}`);
1708
- if (this.hasReactiveDeps(value)) {
1709
- reactiveProps.push({ key, valueCode });
1710
- }
1711
- }
1712
- }
1713
- }
1616
+ addObjectProps(arg);
1714
1617
  } else if (Array.isArray(arg) && (arg[0] === '->' || arg[0] === '=>')) {
1715
1618
  let block = arg[2];
1716
1619
  if (block) {
1717
- // Indented attributes: extract object nodes from block as props
1718
1620
  if (this.is(block, 'block')) {
1719
1621
  const domChildren = [];
1720
1622
  for (const child of block.slice(1)) {
1721
1623
  if (this.is(child, 'object')) {
1722
- for (let i = 1; i < child.length; i++) {
1723
- const [key, value] = child[i];
1724
- if (typeof key === 'string') {
1725
- const isSimpleReactive = this.reactiveMembers && (
1726
- (typeof value === 'string' && this.reactiveMembers.has(value)) ||
1727
- (Array.isArray(value) && value[0] === '.' && value[1] === 'this' && typeof value[2] === 'string' && this.reactiveMembers.has(value[2]))
1728
- );
1729
- if (isSimpleReactive) {
1730
- const member = typeof value === 'string' ? value : value[2];
1731
- props.push(`${key}: this.${member}`);
1732
- } else {
1733
- const valueCode = this.generateInComponent(value, 'value');
1734
- props.push(`${key}: ${valueCode}`);
1735
- if (this.hasReactiveDeps(value)) {
1736
- reactiveProps.push({ key, valueCode });
1737
- }
1738
- }
1739
- }
1740
- }
1624
+ addObjectProps(child);
1741
1625
  } else {
1742
1626
  domChildren.push(child);
1743
1627
  }