@sprlab/wccompiler 0.2.0 → 0.3.0
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 +27 -0
- package/lib/codegen.js +245 -101
- package/lib/compiler-browser.js +505 -0
- package/lib/compiler.js +79 -2
- package/lib/dev-server.js +55 -17
- package/lib/parser-extractors.js +1030 -0
- package/lib/parser.js +36 -929
- package/lib/reactive-runtime.js +35 -4
- package/lib/tree-walker.js +42 -3
- package/lib/types.js +33 -0
- package/package.json +1 -1
- package/types/wcc.d.ts +3 -2
package/README.md
CHANGED
|
@@ -82,6 +82,26 @@ effect(() => {
|
|
|
82
82
|
})
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
Effects support cleanup — return a function to run before re-execution:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
effect(() => {
|
|
89
|
+
const id = setInterval(() => tick.set(tick() + 1), 1000)
|
|
90
|
+
return () => clearInterval(id) // called before re-run
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Watch
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
watch('count', (newVal, oldVal) => {
|
|
98
|
+
console.log(`Changed from ${oldVal} to ${newVal}`)
|
|
99
|
+
if (newVal > 10) api.save(newVal)
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`watch` observes a specific signal/prop/computed and provides both old and new values. The callback does not run on initial mount — only on subsequent changes.
|
|
104
|
+
|
|
85
105
|
### Constants
|
|
86
106
|
|
|
87
107
|
```js
|
|
@@ -238,11 +258,18 @@ onMount(() => {
|
|
|
238
258
|
console.log('Component connected to DOM')
|
|
239
259
|
})
|
|
240
260
|
|
|
261
|
+
onMount(async () => {
|
|
262
|
+
const data = await fetch('/api/items').then(r => r.json())
|
|
263
|
+
items.set(data)
|
|
264
|
+
})
|
|
265
|
+
|
|
241
266
|
onDestroy(() => {
|
|
242
267
|
console.log('Component removed from DOM')
|
|
243
268
|
})
|
|
244
269
|
```
|
|
245
270
|
|
|
271
|
+
Async callbacks are wrapped in an IIFE — `connectedCallback` itself stays synchronous.
|
|
272
|
+
|
|
246
273
|
## CSS Scoping
|
|
247
274
|
|
|
248
275
|
Styles are automatically scoped to the component using tag-name prefixing:
|
package/lib/codegen.js
CHANGED
|
@@ -95,6 +95,14 @@ export function transformExpr(expr, signalNames, computedNames, propsObjectName
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Transform bare prop names → this._s_x() (for template expressions like :style="{ color: myProp }")
|
|
99
|
+
for (const propName of propNames) {
|
|
100
|
+
if (propsObjectName && propName === propsObjectName) continue;
|
|
101
|
+
if (emitsObjectName && propName === emitsObjectName) continue;
|
|
102
|
+
const bareRe = new RegExp(`\\b(${propName})\\b(?!\\.set\\()(?!\\()`, 'g');
|
|
103
|
+
result = result.replace(bareRe, `this._s_${propName}()`);
|
|
104
|
+
}
|
|
105
|
+
|
|
98
106
|
// Transform computed names first (to avoid partial matches with signals)
|
|
99
107
|
for (const name of computedNames) {
|
|
100
108
|
// Skip propsObjectName and emitsObjectName
|
|
@@ -289,6 +297,103 @@ export function isStaticForExpr(expr, itemVar, indexVar, propsSet, rootVarNames,
|
|
|
289
297
|
return true;
|
|
290
298
|
}
|
|
291
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Generate per-item setup code for bindings, events, show, attr, model, and slots.
|
|
302
|
+
* Used by both keyed and non-keyed each effects.
|
|
303
|
+
*
|
|
304
|
+
* @param {string[]} lines — Output lines array
|
|
305
|
+
* @param {object} forBlock — ForBlock with bindings, events, etc.
|
|
306
|
+
* @param {string} itemVar
|
|
307
|
+
* @param {string|null} indexVar
|
|
308
|
+
* @param {Set<string>} propNames
|
|
309
|
+
* @param {Set<string>} signalNamesSet
|
|
310
|
+
* @param {Set<string>} computedNamesSet
|
|
311
|
+
*/
|
|
312
|
+
function generateItemSetup(lines, forBlock, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet) {
|
|
313
|
+
const indent = ' ';
|
|
314
|
+
|
|
315
|
+
// Bindings
|
|
316
|
+
for (const b of forBlock.bindings) {
|
|
317
|
+
const nodeRef = pathExpr(b.path, 'node');
|
|
318
|
+
if (isStaticForBinding(b.name, itemVar, indexVar)) {
|
|
319
|
+
lines.push(`${indent} ${nodeRef}.textContent = ${b.name} ?? '';`);
|
|
320
|
+
} else {
|
|
321
|
+
const expr = transformForExpr(b.name, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
322
|
+
lines.push(`${indent} __effect(() => { ${nodeRef}.textContent = ${expr} ?? ''; });`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Events
|
|
327
|
+
for (const e of forBlock.events) {
|
|
328
|
+
const nodeRef = pathExpr(e.path, 'node');
|
|
329
|
+
lines.push(`${indent} ${nodeRef}.addEventListener('${e.event}', this._${e.handler}.bind(this));`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Show
|
|
333
|
+
for (const sb of forBlock.showBindings) {
|
|
334
|
+
const nodeRef = pathExpr(sb.path, 'node');
|
|
335
|
+
if (isStaticForExpr(sb.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet)) {
|
|
336
|
+
lines.push(`${indent} ${nodeRef}.style.display = (${sb.expression}) ? '' : 'none';`);
|
|
337
|
+
} else {
|
|
338
|
+
const expr = transformForExpr(sb.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
339
|
+
lines.push(`${indent} __effect(() => { ${nodeRef}.style.display = (${expr}) ? '' : 'none'; });`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Attr bindings
|
|
344
|
+
for (const ab of forBlock.attrBindings) {
|
|
345
|
+
const nodeRef = pathExpr(ab.path, 'node');
|
|
346
|
+
if (isStaticForExpr(ab.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet)) {
|
|
347
|
+
lines.push(`${indent} const __val_${ab.varName} = ${ab.expression};`);
|
|
348
|
+
lines.push(`${indent} if (__val_${ab.varName} != null && __val_${ab.varName} !== false) { ${nodeRef}.setAttribute('${ab.attr}', __val_${ab.varName}); }`);
|
|
349
|
+
} else {
|
|
350
|
+
const expr = transformForExpr(ab.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
351
|
+
lines.push(`${indent} __effect(() => {`);
|
|
352
|
+
lines.push(`${indent} const __val = ${expr};`);
|
|
353
|
+
lines.push(`${indent} if (__val == null || __val === false) { ${nodeRef}.removeAttribute('${ab.attr}'); }`);
|
|
354
|
+
lines.push(`${indent} else { ${nodeRef}.setAttribute('${ab.attr}', __val); }`);
|
|
355
|
+
lines.push(`${indent} });`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Model bindings
|
|
360
|
+
for (const mb of (forBlock.modelBindings || [])) {
|
|
361
|
+
const nodeRef = pathExpr(mb.path, 'node');
|
|
362
|
+
lines.push(`${indent} __effect(() => {`);
|
|
363
|
+
if (mb.prop === 'checked' && mb.radioValue !== null) {
|
|
364
|
+
lines.push(`${indent} ${nodeRef}.checked = (this._${mb.signal}() === '${mb.radioValue}');`);
|
|
365
|
+
} else if (mb.prop === 'checked') {
|
|
366
|
+
lines.push(`${indent} ${nodeRef}.checked = !!this._${mb.signal}();`);
|
|
367
|
+
} else {
|
|
368
|
+
lines.push(`${indent} ${nodeRef}.value = this._${mb.signal}() ?? '';`);
|
|
369
|
+
}
|
|
370
|
+
lines.push(`${indent} });`);
|
|
371
|
+
if (mb.prop === 'checked' && mb.radioValue === null) {
|
|
372
|
+
lines.push(`${indent} ${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.checked); });`);
|
|
373
|
+
} else if (mb.coerce) {
|
|
374
|
+
lines.push(`${indent} ${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(Number(e.target.value)); });`);
|
|
375
|
+
} else {
|
|
376
|
+
lines.push(`${indent} ${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.value); });`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Scoped slots
|
|
381
|
+
for (const s of (forBlock.slots || [])) {
|
|
382
|
+
if (s.slotProps.length > 0) {
|
|
383
|
+
const slotNodeRef = pathExpr(s.path, 'node');
|
|
384
|
+
const propsEntries = s.slotProps.map(sp => `'${sp.prop}': ${sp.source}`).join(', ');
|
|
385
|
+
lines.push(`${indent} { const __slotEl = ${slotNodeRef};`);
|
|
386
|
+
lines.push(`${indent} const __sp = { ${propsEntries} };`);
|
|
387
|
+
lines.push(`${indent} let __h = __slotEl.innerHTML;`);
|
|
388
|
+
lines.push(`${indent} for (const [k, v] of Object.entries(__sp)) {`);
|
|
389
|
+
lines.push(`${indent} __h = __h.replace(new RegExp('\\\\{\\\\{\\\\s*' + k + '\\\\s*\\\\}\\\\}', 'g'), v ?? '');`);
|
|
390
|
+
lines.push(`${indent} }`);
|
|
391
|
+
lines.push(`${indent} __slotEl.innerHTML = __h;`);
|
|
392
|
+
lines.push(`${indent} }`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
292
397
|
/**
|
|
293
398
|
* Generate a fully self-contained JS component from a ParseResult.
|
|
294
399
|
*
|
|
@@ -320,8 +425,11 @@ export function generateComponent(parseResult) {
|
|
|
320
425
|
attrBindings = [],
|
|
321
426
|
slots = [],
|
|
322
427
|
constantVars = [],
|
|
428
|
+
watchers = [],
|
|
323
429
|
refs = [],
|
|
324
430
|
refBindings = [],
|
|
431
|
+
childComponents = [],
|
|
432
|
+
childImports = [],
|
|
325
433
|
} = parseResult;
|
|
326
434
|
|
|
327
435
|
const signalNames = signals.map(s => s.name);
|
|
@@ -336,6 +444,14 @@ export function generateComponent(parseResult) {
|
|
|
336
444
|
lines.push(reactiveRuntime.trim());
|
|
337
445
|
lines.push('');
|
|
338
446
|
|
|
447
|
+
// ── 1b. Child component imports ──
|
|
448
|
+
for (const ci of childImports) {
|
|
449
|
+
lines.push(`import '${ci.importPath}';`);
|
|
450
|
+
}
|
|
451
|
+
if (childImports.length > 0) {
|
|
452
|
+
lines.push('');
|
|
453
|
+
}
|
|
454
|
+
|
|
339
455
|
// ── 2. CSS injection (scoped CSS into document.head, always) ──
|
|
340
456
|
if (style) {
|
|
341
457
|
const scoped = scopeCSS(style, tagName);
|
|
@@ -410,6 +526,11 @@ export function generateComponent(parseResult) {
|
|
|
410
526
|
lines.push(` this.${s.varName} = ${pathExpr(s.path, '__root')};`);
|
|
411
527
|
}
|
|
412
528
|
|
|
529
|
+
// Assign DOM refs for child component instances
|
|
530
|
+
for (const cc of childComponents) {
|
|
531
|
+
lines.push(` this.${cc.varName} = ${pathExpr(cc.path, '__root')};`);
|
|
532
|
+
}
|
|
533
|
+
|
|
413
534
|
// Assign DOM refs for attr bindings (reuse ref when same path)
|
|
414
535
|
const attrPathMap = new Map();
|
|
415
536
|
for (const ab of attrBindings) {
|
|
@@ -443,6 +564,11 @@ export function generateComponent(parseResult) {
|
|
|
443
564
|
lines.push(` this._c_${c.name} = __computed(() => ${body});`);
|
|
444
565
|
}
|
|
445
566
|
|
|
567
|
+
// Watcher prev-value initialization
|
|
568
|
+
for (const w of watchers) {
|
|
569
|
+
lines.push(` this.__prev_${w.target} = undefined;`);
|
|
570
|
+
}
|
|
571
|
+
|
|
446
572
|
// ── if: template creation, anchor reference, state init ──
|
|
447
573
|
for (const ifBlock of ifBlocks) {
|
|
448
574
|
const vn = ifBlock.varName;
|
|
@@ -542,6 +668,27 @@ export function generateComponent(parseResult) {
|
|
|
542
668
|
}
|
|
543
669
|
}
|
|
544
670
|
|
|
671
|
+
// Child component reactive prop bindings
|
|
672
|
+
for (const cc of childComponents) {
|
|
673
|
+
for (const pb of cc.propBindings) {
|
|
674
|
+
let ref;
|
|
675
|
+
if (pb.type === 'prop') {
|
|
676
|
+
ref = `this._s_${pb.expr}()`;
|
|
677
|
+
} else if (pb.type === 'computed') {
|
|
678
|
+
ref = `this._c_${pb.expr}()`;
|
|
679
|
+
} else if (pb.type === 'signal') {
|
|
680
|
+
ref = `this._${pb.expr}()`;
|
|
681
|
+
} else if (pb.type === 'constant') {
|
|
682
|
+
ref = `this._const_${pb.expr}`;
|
|
683
|
+
} else {
|
|
684
|
+
ref = `this._${pb.expr}()`;
|
|
685
|
+
}
|
|
686
|
+
lines.push(' __effect(() => {');
|
|
687
|
+
lines.push(` this.${cc.varName}.setAttribute('${pb.attr}', ${ref} ?? '');`);
|
|
688
|
+
lines.push(' });');
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
545
692
|
// User effects
|
|
546
693
|
for (const eff of effects) {
|
|
547
694
|
const body = transformMethodBody(eff.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames);
|
|
@@ -554,6 +701,31 @@ export function generateComponent(parseResult) {
|
|
|
554
701
|
lines.push(' });');
|
|
555
702
|
}
|
|
556
703
|
|
|
704
|
+
// Watcher effects
|
|
705
|
+
for (const w of watchers) {
|
|
706
|
+
// Determine the signal reference for the watch target
|
|
707
|
+
let watchRef;
|
|
708
|
+
if (propNames.has(w.target)) {
|
|
709
|
+
watchRef = `this._s_${w.target}()`;
|
|
710
|
+
} else if (computedNames.includes(w.target)) {
|
|
711
|
+
watchRef = `this._c_${w.target}()`;
|
|
712
|
+
} else {
|
|
713
|
+
watchRef = `this._${w.target}()`;
|
|
714
|
+
}
|
|
715
|
+
const body = transformMethodBody(w.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames);
|
|
716
|
+
lines.push(' __effect(() => {');
|
|
717
|
+
lines.push(` const ${w.newParam} = ${watchRef};`);
|
|
718
|
+
lines.push(` if (this.__prev_${w.target} !== undefined) {`);
|
|
719
|
+
lines.push(` const ${w.oldParam} = this.__prev_${w.target};`);
|
|
720
|
+
const bodyLines = body.split('\n');
|
|
721
|
+
for (const line of bodyLines) {
|
|
722
|
+
lines.push(` ${line}`);
|
|
723
|
+
}
|
|
724
|
+
lines.push(' }');
|
|
725
|
+
lines.push(` this.__prev_${w.target} = ${w.newParam};`);
|
|
726
|
+
lines.push(' });');
|
|
727
|
+
}
|
|
728
|
+
|
|
557
729
|
// Event listeners
|
|
558
730
|
for (const e of events) {
|
|
559
731
|
lines.push(` this.${e.varName}.addEventListener('${e.event}', this._${e.handler}.bind(this));`);
|
|
@@ -695,7 +867,7 @@ export function generateComponent(parseResult) {
|
|
|
695
867
|
// ── each effects ──
|
|
696
868
|
for (const forBlock of forBlocks) {
|
|
697
869
|
const vn = forBlock.varName;
|
|
698
|
-
const { itemVar, indexVar, source } = forBlock;
|
|
870
|
+
const { itemVar, indexVar, source, keyExpr } = forBlock;
|
|
699
871
|
|
|
700
872
|
const signalNamesSet = new Set(signalNames);
|
|
701
873
|
const computedNamesSet = new Set(computedNames);
|
|
@@ -706,116 +878,79 @@ export function generateComponent(parseResult) {
|
|
|
706
878
|
lines.push(' __effect(() => {');
|
|
707
879
|
lines.push(` const __source = ${sourceExpr};`);
|
|
708
880
|
lines.push('');
|
|
709
|
-
lines.push(` for (const n of this.${vn}_nodes) n.remove();`);
|
|
710
|
-
lines.push(` this.${vn}_nodes = [];`);
|
|
711
|
-
lines.push('');
|
|
712
881
|
lines.push(" const __iter = typeof __source === 'number'");
|
|
713
882
|
lines.push(' ? Array.from({ length: __source }, (_, i) => i + 1)');
|
|
714
883
|
lines.push(' : (__source || []);');
|
|
715
884
|
lines.push('');
|
|
716
|
-
lines.push(` __iter.forEach((${itemVar}, ${indexVar || '__idx'}) => {`);
|
|
717
|
-
lines.push(` const clone = this.${vn}_tpl.content.cloneNode(true);`);
|
|
718
|
-
lines.push(' const node = clone.firstChild;');
|
|
719
|
-
|
|
720
|
-
// Setup bindings per item
|
|
721
|
-
for (const b of forBlock.bindings) {
|
|
722
|
-
const nodeRef = pathExpr(b.path, 'node');
|
|
723
|
-
if (isStaticForBinding(b.name, itemVar, indexVar)) {
|
|
724
|
-
// Static binding: reference only item/index, assign once
|
|
725
|
-
lines.push(` ${nodeRef}.textContent = ${b.name} ?? '';`);
|
|
726
|
-
} else {
|
|
727
|
-
// Reactive binding: references component variables, wrap in effect
|
|
728
|
-
const expr = transformForExpr(b.name, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
729
|
-
lines.push(` __effect(() => { ${nodeRef}.textContent = ${expr} ?? ''; });`);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// Setup events per item
|
|
734
|
-
for (const e of forBlock.events) {
|
|
735
|
-
const nodeRef = pathExpr(e.path, 'node');
|
|
736
|
-
lines.push(` ${nodeRef}.addEventListener('${e.event}', this._${e.handler}.bind(this));`);
|
|
737
|
-
}
|
|
738
885
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
886
|
+
if (keyExpr) {
|
|
887
|
+
// ── Keyed reconciliation ──
|
|
888
|
+
lines.push(` const __oldMap = this.${vn}_keyMap || new Map();`);
|
|
889
|
+
lines.push(' const __newMap = new Map();');
|
|
890
|
+
lines.push(' const __newNodes = [];');
|
|
891
|
+
lines.push('');
|
|
892
|
+
lines.push(` __iter.forEach((${itemVar}, ${indexVar || '__idx'}) => {`);
|
|
893
|
+
lines.push(` const __key = ${keyExpr};`);
|
|
894
|
+
lines.push(' if (__oldMap.has(__key)) {');
|
|
895
|
+
lines.push(' const node = __oldMap.get(__key);');
|
|
896
|
+
lines.push(' __newMap.set(__key, node);');
|
|
897
|
+
lines.push(' __newNodes.push(node);');
|
|
898
|
+
lines.push(' __oldMap.delete(__key);');
|
|
899
|
+
lines.push(' } else {');
|
|
900
|
+
lines.push(` const clone = this.${vn}_tpl.content.cloneNode(true);`);
|
|
901
|
+
lines.push(' const node = clone.firstChild;');
|
|
902
|
+
|
|
903
|
+
// Setup bindings/events/show/attr/model/slots for NEW nodes only
|
|
904
|
+
// (reused nodes keep their existing bindings)
|
|
905
|
+
generateItemSetup(lines, forBlock, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
906
|
+
|
|
907
|
+
lines.push(' __newMap.set(__key, node);');
|
|
908
|
+
lines.push(' __newNodes.push(node);');
|
|
909
|
+
lines.push(' }');
|
|
910
|
+
lines.push(' });');
|
|
911
|
+
lines.push('');
|
|
912
|
+
lines.push(' // Remove nodes no longer in the list');
|
|
913
|
+
lines.push(' for (const n of __oldMap.values()) n.remove();');
|
|
914
|
+
lines.push('');
|
|
915
|
+
lines.push(' // Reorder: insert all nodes in correct order before anchor');
|
|
916
|
+
lines.push(` for (const n of __newNodes) this.${vn}_anchor.parentNode.insertBefore(n, this.${vn}_anchor);`);
|
|
917
|
+
lines.push('');
|
|
918
|
+
lines.push(` this.${vn}_nodes = __newNodes;`);
|
|
919
|
+
lines.push(` this.${vn}_keyMap = __newMap;`);
|
|
920
|
+
lines.push(' });');
|
|
921
|
+
} else {
|
|
922
|
+
// ── Non-keyed: destroy all and recreate (original behavior) ──
|
|
923
|
+
lines.push(` for (const n of this.${vn}_nodes) n.remove();`);
|
|
924
|
+
lines.push(` this.${vn}_nodes = [];`);
|
|
925
|
+
lines.push('');
|
|
926
|
+
lines.push(` __iter.forEach((${itemVar}, ${indexVar || '__idx'}) => {`);
|
|
927
|
+
lines.push(` const clone = this.${vn}_tpl.content.cloneNode(true);`);
|
|
928
|
+
lines.push(' const node = clone.firstChild;');
|
|
750
929
|
|
|
751
|
-
|
|
752
|
-
for (const ab of forBlock.attrBindings) {
|
|
753
|
-
const nodeRef = pathExpr(ab.path, 'node');
|
|
754
|
-
if (isStaticForExpr(ab.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet)) {
|
|
755
|
-
const expr = ab.expression;
|
|
756
|
-
lines.push(` const __val_${ab.varName} = ${expr};`);
|
|
757
|
-
lines.push(` if (__val_${ab.varName} != null && __val_${ab.varName} !== false) { ${nodeRef}.setAttribute('${ab.attr}', __val_${ab.varName}); }`);
|
|
758
|
-
} else {
|
|
759
|
-
const expr = transformForExpr(ab.expression, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
760
|
-
lines.push(` __effect(() => {`);
|
|
761
|
-
lines.push(` const __val = ${expr};`);
|
|
762
|
-
lines.push(` if (__val == null || __val === false) { ${nodeRef}.removeAttribute('${ab.attr}'); }`);
|
|
763
|
-
lines.push(` else { ${nodeRef}.setAttribute('${ab.attr}', __val); }`);
|
|
764
|
-
lines.push(` });`);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
930
|
+
generateItemSetup(lines, forBlock, itemVar, indexVar, propNames, signalNamesSet, computedNamesSet);
|
|
767
931
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
lines.push(` __effect(() => {`);
|
|
773
|
-
if (mb.prop === 'checked' && mb.radioValue !== null) {
|
|
774
|
-
lines.push(` ${nodeRef}.checked = (this._${mb.signal}() === '${mb.radioValue}');`);
|
|
775
|
-
} else if (mb.prop === 'checked') {
|
|
776
|
-
lines.push(` ${nodeRef}.checked = !!this._${mb.signal}();`);
|
|
777
|
-
} else {
|
|
778
|
-
lines.push(` ${nodeRef}.value = this._${mb.signal}() ?? '';`);
|
|
779
|
-
}
|
|
780
|
-
lines.push(` });`);
|
|
781
|
-
// Listener (DOM → signal)
|
|
782
|
-
if (mb.prop === 'checked' && mb.radioValue === null) {
|
|
783
|
-
lines.push(` ${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.checked); });`);
|
|
784
|
-
} else if (mb.coerce) {
|
|
785
|
-
lines.push(` ${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(Number(e.target.value)); });`);
|
|
786
|
-
} else {
|
|
787
|
-
lines.push(` ${nodeRef}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.value); });`);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Setup scoped slot resolution per item
|
|
792
|
-
for (const s of (forBlock.slots || [])) {
|
|
793
|
-
if (s.slotProps.length > 0) {
|
|
794
|
-
const slotNodeRef = pathExpr(s.path, 'node');
|
|
795
|
-
const propsEntries = s.slotProps.map(sp => `'${sp.prop}': ${sp.source}`).join(', ');
|
|
796
|
-
lines.push(` { const __slotEl = ${slotNodeRef};`);
|
|
797
|
-
lines.push(` const __sp = { ${propsEntries} };`);
|
|
798
|
-
lines.push(` let __h = __slotEl.innerHTML;`);
|
|
799
|
-
lines.push(` for (const [k, v] of Object.entries(__sp)) {`);
|
|
800
|
-
lines.push(` __h = __h.replace(new RegExp('\\\\{\\\\{\\\\s*' + k + '\\\\s*\\\\}\\\\}', 'g'), v ?? '');`);
|
|
801
|
-
lines.push(` }`);
|
|
802
|
-
lines.push(` __slotEl.innerHTML = __h;`);
|
|
803
|
-
lines.push(` }`);
|
|
804
|
-
}
|
|
932
|
+
lines.push(` this.${vn}_anchor.parentNode.insertBefore(node, this.${vn}_anchor);`);
|
|
933
|
+
lines.push(` this.${vn}_nodes.push(node);`);
|
|
934
|
+
lines.push(' });');
|
|
935
|
+
lines.push(' });');
|
|
805
936
|
}
|
|
806
|
-
|
|
807
|
-
lines.push(` this.${vn}_anchor.parentNode.insertBefore(node, this.${vn}_anchor);`);
|
|
808
|
-
lines.push(` this.${vn}_nodes.push(node);`);
|
|
809
|
-
lines.push(' });');
|
|
810
|
-
lines.push(' });');
|
|
811
937
|
}
|
|
812
938
|
|
|
813
939
|
// Lifecycle: onMount hooks (at the very end of connectedCallback)
|
|
814
940
|
for (const hook of onMountHooks) {
|
|
815
941
|
const body = transformMethodBody(hook.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames);
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
942
|
+
if (hook.async) {
|
|
943
|
+
lines.push(' (async () => {');
|
|
944
|
+
const bodyLines = body.split('\n');
|
|
945
|
+
for (const line of bodyLines) {
|
|
946
|
+
lines.push(` ${line}`);
|
|
947
|
+
}
|
|
948
|
+
lines.push(' })();');
|
|
949
|
+
} else {
|
|
950
|
+
const bodyLines = body.split('\n');
|
|
951
|
+
for (const line of bodyLines) {
|
|
952
|
+
lines.push(` ${line}`);
|
|
953
|
+
}
|
|
819
954
|
}
|
|
820
955
|
}
|
|
821
956
|
|
|
@@ -827,9 +962,18 @@ export function generateComponent(parseResult) {
|
|
|
827
962
|
lines.push(' disconnectedCallback() {');
|
|
828
963
|
for (const hook of onDestroyHooks) {
|
|
829
964
|
const body = transformMethodBody(hook.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames);
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
965
|
+
if (hook.async) {
|
|
966
|
+
lines.push(' (async () => {');
|
|
967
|
+
const bodyLines = body.split('\n');
|
|
968
|
+
for (const line of bodyLines) {
|
|
969
|
+
lines.push(` ${line}`);
|
|
970
|
+
}
|
|
971
|
+
lines.push(' })();');
|
|
972
|
+
} else {
|
|
973
|
+
const bodyLines = body.split('\n');
|
|
974
|
+
for (const line of bodyLines) {
|
|
975
|
+
lines.push(` ${line}`);
|
|
976
|
+
}
|
|
833
977
|
}
|
|
834
978
|
}
|
|
835
979
|
lines.push(' }');
|