@jxsuite/compiler 0.28.5 → 0.30.1

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.
@@ -268,16 +268,17 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
268
268
  lines.push(`import { ${names.join(", ")} } from '${srcPath}';`);
269
269
  }
270
270
 
271
- lines.push(`import { reactive, computed, effect } from '@vue/reactivity';`);
272
- lines.push(`import { render, html } from 'lit-html';`);
273
- lines.push("");
274
- lines.push(`class ${className} extends HTMLElement {`);
275
- lines.push(" #dispose = null;");
276
- lines.push("");
277
-
278
- // Constructor: build reactive state
279
- lines.push(" constructor() {");
280
- lines.push(" super();");
271
+ lines.push(
272
+ `import { reactive, computed, effect } from '@vue/reactivity';`,
273
+ `import { render, html } from 'lit-html';`,
274
+ "",
275
+ `class ${className} extends HTMLElement {`,
276
+ " #dispose = null;",
277
+ "",
278
+ // Constructor: build reactive state
279
+ " constructor() {",
280
+ " super();",
281
+ );
281
282
 
282
283
  const stateEntries: [string, string][] = [];
283
284
  const computedEntries: [string, JxExpressionDef | JxFunctionDef][] = [];
@@ -330,9 +331,11 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
330
331
  // $src function — wrap imported function so it receives state
331
332
  lines.push(` this.state.${key} = (${paramList}) => ${key}(${paramList});`);
332
333
  } else {
333
- lines.push(` this.state.${key} = (${paramList}) => {`);
334
- lines.push(` ${def.body ?? ""}`);
335
- lines.push(" };");
334
+ lines.push(
335
+ ` this.state.${key} = (${paramList}) => {`,
336
+ ` ${def.body ?? ""}`,
337
+ " };",
338
+ );
336
339
  }
337
340
  }
338
341
  }
@@ -351,13 +354,11 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
351
354
  } else {
352
355
  lines.push(` this.state.${key} = computed(() => {`);
353
356
  const body = (def.body ?? "").replaceAll("state.", "this.state.");
354
- lines.push(` ${body}`);
355
- lines.push(" });");
357
+ lines.push(` ${body}`, " });");
356
358
  }
357
359
  }
358
360
 
359
- lines.push(" }"); // End constructor
360
- lines.push("");
361
+ lines.push(" }", ""); // End constructor
361
362
 
362
363
  // Collect CSS rules from children tree (assigns .jx-N classes to defs)
363
364
  const cssRules: string[] = [];
@@ -368,35 +369,36 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
368
369
  }
369
370
  }
370
371
 
371
- // Template method
372
- lines.push(" template() {");
373
- lines.push(" const s = this.state;");
374
- lines.push(" return html`");
375
- lines.push(emitLitChildren(doc.children, doc.style, " "));
376
- lines.push(" `;");
377
- lines.push(" }");
378
- lines.push("");
379
-
380
- // ConnectedCallback
381
- lines.push(" connectedCallback() {");
382
- // Read $props from data-jx-props attribute (set by compiler for pre-rendered instances)
383
- lines.push(" const _pa = this.getAttribute('data-jx-props');");
384
- lines.push(" if (_pa) {");
385
- lines.push(" try {");
386
- lines.push(" const _p = JSON.parse(_pa);");
387
- lines.push(" for (const [k, v] of Object.entries(_p)) {");
388
- lines.push(" if (k in this.state) this.state[k] = v;");
389
- lines.push(" }");
390
- lines.push(" } catch {}");
391
- lines.push(" this.removeAttribute('data-jx-props');");
392
- lines.push(" }");
393
- // Merge JS properties set before connection (by parent runtime).
394
- // Only check own properties to avoid inherited DOM properties like `title`.
395
- lines.push(" for (const key of Object.keys(this.state)) {");
396
- lines.push(" if (this.hasOwnProperty(key) && this[key] !== undefined) {");
397
- lines.push(" this.state[key] = this[key];");
398
- lines.push(" }");
399
- lines.push(" }");
372
+ lines.push(
373
+ // Template method
374
+ " template() {",
375
+ " const s = this.state;",
376
+ " return html`",
377
+ emitLitChildren(doc.children, doc.style, " "),
378
+ " `;",
379
+ " }",
380
+ "",
381
+ // ConnectedCallback
382
+ " connectedCallback() {",
383
+ // Read $props from data-jx-props attribute (set by compiler for pre-rendered instances)
384
+ " const _pa = this.getAttribute('data-jx-props');",
385
+ " if (_pa) {",
386
+ " try {",
387
+ " const _p = JSON.parse(_pa);",
388
+ " for (const [k, v] of Object.entries(_p)) {",
389
+ " if (k in this.state) this.state[k] = v;",
390
+ " }",
391
+ " } catch {}",
392
+ " this.removeAttribute('data-jx-props');",
393
+ " }",
394
+ // Merge JS properties set before connection (by parent runtime).
395
+ // Only check own properties to avoid inherited DOM properties like `title`.
396
+ " for (const key of Object.keys(this.state)) {",
397
+ " if (this.hasOwnProperty(key) && this[key] !== undefined) {",
398
+ " this.state[key] = this[key];",
399
+ " }",
400
+ " }",
401
+ );
400
402
  if (doc.style && typeof doc.style === "object") {
401
403
  const dynamicStyles: [string, string][] = [];
402
404
  for (const [prop, value] of Object.entries(doc.style)) {
@@ -436,31 +438,35 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
436
438
  " const _slotted = Array.from(this.childNodes).filter(n => n.nodeType === 1 || (n.nodeType === 3 && n.textContent.trim()));",
437
439
  );
438
440
  }
439
- lines.push(" if (this.hasAttribute('data-jx-prerendered')) {");
440
- lines.push(" this.removeAttribute('data-jx-prerendered');");
441
- lines.push(" }");
442
- lines.push(" this.innerHTML = '';");
443
- lines.push(" this.#dispose = effect(() => render(this.template(), this));");
441
+ lines.push(
442
+ " if (this.hasAttribute('data-jx-prerendered')) {",
443
+ " this.removeAttribute('data-jx-prerendered');",
444
+ " }",
445
+ " this.innerHTML = '';",
446
+ " this.#dispose = effect(() => render(this.template(), this));",
447
+ );
444
448
  if (hasSlot) {
445
449
  // Replace <slot> placeholder with saved slotted content
446
- lines.push(" const _slot = this.querySelector('slot');");
447
- lines.push(" if (_slot && _slotted.length > 0) {");
448
- lines.push(" for (const n of _slotted) _slot.before(n);");
449
- lines.push(" _slot.remove();");
450
- lines.push(" }");
450
+ lines.push(
451
+ " const _slot = this.querySelector('slot');",
452
+ " if (_slot && _slotted.length > 0) {",
453
+ " for (const n of _slotted) _slot.before(n);",
454
+ " _slot.remove();",
455
+ " }",
456
+ );
451
457
  }
452
- lines.push(" }");
453
- lines.push("");
454
-
455
- // DisconnectedCallback
456
- lines.push(" disconnectedCallback() {");
457
- lines.push(" if (this.#dispose) { this.#dispose(); this.#dispose = null; }");
458
- lines.push(" }");
459
-
460
- lines.push("}");
461
- lines.push("");
462
- lines.push(`customElements.define('${doc.tagName}', ${className});`);
463
- lines.push("");
458
+ lines.push(
459
+ " }",
460
+ "",
461
+ // DisconnectedCallback
462
+ " disconnectedCallback() {",
463
+ " if (this.#dispose) { this.#dispose(); this.#dispose = null; }",
464
+ " }",
465
+ "}",
466
+ "",
467
+ `customElements.define('${doc.tagName}', ${className});`,
468
+ "",
469
+ );
464
470
 
465
471
  return lines.join("\n");
466
472
  }
@@ -475,13 +481,14 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
475
481
  */
476
482
  function emitLitChildren(
477
483
  children: JxMutableNode["children"],
478
- parentStyle: JxStyle | null | undefined,
484
+ _parentStyle: JxStyle | null | undefined,
479
485
  indent: string,
480
486
  ) {
481
487
  if (!children) {
482
488
  return "";
483
489
  }
484
490
 
491
+ // Legacy whole-children repeater: the array IS the children slot.
485
492
  if (isMappedArray(children)) {
486
493
  return emitMappedArray(children, indent);
487
494
  }
@@ -490,7 +497,12 @@ function emitLitChildren(
490
497
  return "";
491
498
  }
492
499
 
493
- return children.map((child: JxMutableNode | string) => emitLitNode(child, indent)).join("\n");
500
+ // Mixed children: elements/text plus array pseudo-elements expanded inline among siblings.
501
+ return children
502
+ .map((child: JxMutableNode | string) =>
503
+ isMappedArray(child) ? emitMappedArray(child, indent) : emitLitNode(child, indent),
504
+ )
505
+ .join("\n");
494
506
  }
495
507
 
496
508
  /**
@@ -12,6 +12,7 @@ import {
12
12
  escapeHtml,
13
13
  isNodeDynamic,
14
14
  resolveStaticValue,
15
+ SELF_CLOSING,
15
16
  } from "../shared.ts";
16
17
  import { emitElementModule } from "./compile-element.ts";
17
18
  import type { JxDocument, JxMutableNode, JxStyle } from "@jxsuite/schema/types";
@@ -143,6 +144,13 @@ function compileNode(
143
144
 
144
145
  const tag = def.tagName ?? "div";
145
146
  const attrs = buildAttrs(def, nextContext.scope);
147
+
148
+ // Void elements (br, hr, img, …) have no closing tag.
149
+ // Emitting `<br></br>` makes HTML parsers read `</br>` as a second `<br>`.
150
+ if (SELF_CLOSING.has(tag)) {
151
+ return `<${tag}${attrs}>`;
152
+ }
153
+
146
154
  const inner = buildInnerWithIslands(def, raw, nextContext, islands);
147
155
 
148
156
  return `<${tag}${attrs}>${inner}</${tag}>`;