@sola-air-ui/compiler 1.0.5 → 1.0.7

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +38 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sola-air-ui/compiler",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Compiler for Sola single-file components (.sola) — transforms template + script + style to ES modules",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -172,6 +172,10 @@ function extractExpressions(text) {
172
172
  const inner = text.slice(i + 1, closeIdx).trim();
173
173
  if (inner.startsWith('#') || inner.startsWith('/') || inner.startsWith(':')) {
174
174
  result += text.slice(i, closeIdx + 1); i = closeIdx + 1;
175
+ } else if (inner.startsWith('@html')) {
176
+ const htmlExpr = inner.slice('@html'.length).trim();
177
+ result += `<sola-html expr="${htmlExpr.replace(/"/g, '&quot;')}"></sola-html>`;
178
+ i = closeIdx + 1;
175
179
  } else {
176
180
  result += `<sola-expr expr="${inner.replace(/"/g, '&quot;')}"></sola-expr>`;
177
181
  i = closeIdx + 1;
@@ -245,7 +249,7 @@ export function compile(source, options = {}) {
245
249
  templateNodes.pop();
246
250
  }
247
251
  }
248
- }, { recognizeSelfClosing: true, lowerCaseTags: false });
252
+ }, { recognizeSelfClosing: true, lowerCaseTags: false, lowerCaseAttributeNames: false });
249
253
 
250
254
  parser.write(rawTemplate.trim() || '<div></div>');
251
255
  parser.end();
@@ -440,7 +444,25 @@ export function compile(source, options = {}) {
440
444
  const id = `e${uid++}`;
441
445
  domCode += ` const ${id} = document.createTextNode('');\n`;
442
446
  domCode += ` ${parentVar}.appendChild(${id});\n`;
443
- domCode += ` createEffect(() => { ${id}.textContent = String(${expr} ?? ''); });\n`;
447
+ // Parenthesize expr before `?? ''`: JS forbids mixing ?? with || or && at the
448
+ // same level without explicit grouping (e.g. `col.label || col ?? ''` is a
449
+ // SyntaxError), and any {a || b}-style fallback expression — an extremely
450
+ // common pattern — would otherwise produce invalid generated code.
451
+ domCode += ` createEffect(() => { ${id}.textContent = String((${expr}) ?? ''); });\n`;
452
+ return;
453
+ }
454
+
455
+ // {@html expr} — renders expr as raw HTML via innerHTML, unlike {expr} which always
456
+ // goes through textContent. Wrapped in its own element so setting innerHTML can't
457
+ // clobber unrelated sibling nodes. Callers are responsible for trusting/sanitizing
458
+ // whatever markup they pass — same contract as innerHTML anywhere else.
459
+ if (node.name === 'sola-html') {
460
+ const expr = (node.attribs.expr || '').replace(/&quot;/g, '"');
461
+ const id = `h${uid++}`;
462
+ domCode += ` const ${id} = document.createElement('div');\n`;
463
+ domCode += ` ${id}.style.display = 'contents';\n`;
464
+ domCode += ` ${parentVar}.appendChild(${id});\n`;
465
+ domCode += ` createEffect(() => { ${id}.innerHTML = String((${expr}) ?? ''); });\n`;
444
466
  return;
445
467
  }
446
468
 
@@ -454,10 +476,23 @@ export function compile(source, options = {}) {
454
476
  // Dynamically-bound props (rows={items}) arrive here as __soladyn_N__ placeholders —
455
477
  // resolve them back to the real expression from dynAttrs and pass it as a raw
456
478
  // (unquoted) property so the child receives live data, not the placeholder text.
479
+ //
480
+ // `onXxx`-named props (e.g. onChange={handler}) are a special case: preprocessTemplate
481
+ // strips their {expr} down to raw identifier text before this code ever sees them
482
+ // (the same exception that makes real onclick/on:click DOM handlers work), so they
483
+ // never arrive as a __soladyn_N__ placeholder at all. Since preprocessTemplate can't
484
+ // yet tell a component apart from a native element, that exception can't distinguish
485
+ // "this is a DOM event on <button>" from "this is a callback prop on <Toggle>" — so
486
+ // treat any on-prefixed key here as a raw expression too, same as a real dynAttr.
457
487
  const propEntries = [];
458
488
  for (const [key, val] of Object.entries(node.attribs || {})) {
459
489
  const dynMatch = /^__soladyn_(\d+)__$/.exec(val);
460
- const propValue = dynMatch ? `(${dynAttrs[parseInt(dynMatch[1], 10)]})` : JSON.stringify(val);
490
+ const isEventLikeProp = /^on:?[\w]+$/.test(key);
491
+ const propValue = dynMatch
492
+ ? `(${dynAttrs[parseInt(dynMatch[1], 10)]})`
493
+ : isEventLikeProp
494
+ ? `(${val})`
495
+ : JSON.stringify(val);
461
496
  propEntries.push(`${JSON.stringify(key)}: ${propValue}`);
462
497
  }
463
498