@jxsuite/compiler 0.19.0 → 0.20.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.
@@ -28,8 +28,8 @@ import {
28
28
  /**
29
29
  * Compile a Jx document to pre-rendered HTML + reactive JS module.
30
30
  *
31
- * @param {any} raw
32
- * @param {any} opts
31
+ * @param {JxMutableNode | Record<string, any>} raw
32
+ * @param {Record<string, unknown>} opts
33
33
  * @returns {{ html: string; files: { path: string; content: string }[] }}
34
34
  */
35
35
  export function compileClient(raw, opts) {
@@ -38,10 +38,14 @@ export function compileClient(raw, opts) {
38
38
  reactivitySrc = DEFAULT_REACTIVITY_SRC,
39
39
  litHtmlSrc = DEFAULT_LIT_HTML_SRC,
40
40
  modulePath = "app.js",
41
- } = opts;
41
+ } = /** @type {JxMutableNode} */ (opts);
42
42
 
43
43
  const context = createCompileContext(raw, null, raw.state ?? {}, raw.$media ?? {});
44
- const styleBlock = compileStyles(raw, raw.$media ?? {}, opts.projectStyle ?? null);
44
+ const styleBlock = compileStyles(
45
+ raw,
46
+ raw.$media ?? {},
47
+ /** @type {JxStyle | null} */ (opts.projectStyle ?? null),
48
+ );
45
49
 
46
50
  // Collectors for bindings and handlers
47
51
  const counter = { t: 0, s: 0, h: 0, m: 0, sw: 0, l: 0, needsLit: false };
@@ -79,7 +83,7 @@ export function compileClient(raw, opts) {
79
83
  continue;
80
84
  }
81
85
 
82
- const d = /** @type {any} */ (def);
86
+ const d = /** @type {JxMutableNode} */ (def);
83
87
 
84
88
  // $prototype: "Function"
85
89
  if (d.$prototype === "Function") {
@@ -197,12 +201,20 @@ ${importmapEntries.join(",\n")}
197
201
  // ─── HTML tree walker ─────────────────────────────────────────────────────────
198
202
 
199
203
  /**
200
- * @param {any} def
201
- * @param {any} raw
204
+ * @param {JxMutableNode} def
205
+ * @param {JxMutableNode} raw
202
206
  * @param {any} context
203
207
  * @param {Map<string, string>} bindings
204
208
  * @param {Map<string, any>} handlers
205
- * @param {any} counter
209
+ * @param {{
210
+ * t: number;
211
+ * s: number;
212
+ * h: number;
213
+ * m: number;
214
+ * sw: number;
215
+ * l: number;
216
+ * needsLit: boolean;
217
+ * }} counter
206
218
  * @returns {string}
207
219
  */
208
220
  function buildClientNode(def, raw, context, bindings, handlers, counter) {
@@ -228,11 +240,13 @@ function buildClientNode(def, raw, context, bindings, handlers, counter) {
228
240
 
229
241
  // textContent bindings
230
242
  if (def.textContent !== undefined) {
231
- const tc = raw?.textContent ?? def.textContent;
243
+ const tc = /** @type {JxMutableNode} */ (
244
+ /** @type {unknown} */ (raw?.textContent ?? def.textContent)
245
+ );
232
246
  if (isRefObject(tc)) {
233
- const key = refToBindingKey(tc.$ref);
247
+ const key = refToBindingKey(/** @type {string} */ (tc.$ref));
234
248
  bindAttrs.push(`:text-content="${key}"`);
235
- addRefBinding(bindings, key, tc.$ref);
249
+ addRefBinding(bindings, key, /** @type {string} */ (tc.$ref));
236
250
  needsBind = true;
237
251
  } else if (isTemplateString(tc)) {
238
252
  const key = `_t${counter.t++}`;
@@ -247,15 +261,15 @@ function buildClientNode(def, raw, context, bindings, handlers, counter) {
247
261
  if (!prop.startsWith("on") || prop === "observedAttributes") continue;
248
262
  const eventName = prop.slice(2).toLowerCase();
249
263
  if (isRefObject(val)) {
250
- const key = refToBindingKey(/** @type {any} */ (val).$ref);
264
+ const key = refToBindingKey(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref));
251
265
  bindAttrs.push(`@${eventName}="${key}"`);
252
266
  needsBind = true;
253
267
  } else if (
254
268
  val &&
255
269
  typeof val === "object" &&
256
- /** @type {any} */ (val).$prototype === "Function"
270
+ /** @type {JxMutableNode} */ (val).$prototype === "Function"
257
271
  ) {
258
- const v = /** @type {any} */ (val);
272
+ const v = /** @type {JxMutableNode} */ (val);
259
273
  const key = `_h${counter.h++}`;
260
274
  bindAttrs.push(`@${eventName}="${key}"`);
261
275
  handlers.set(key, { args: v.parameters ?? v.arguments ?? ["state", "event"], body: v.body });
@@ -288,9 +302,15 @@ function buildClientNode(def, raw, context, bindings, handlers, counter) {
288
302
  if (def.attributes && typeof def.attributes === "object") {
289
303
  for (const [attr, val] of Object.entries(def.attributes)) {
290
304
  if (isRefObject(val)) {
291
- const key = refToBindingKey(/** @type {any} */ (val).$ref);
305
+ const key = refToBindingKey(
306
+ /** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref),
307
+ );
292
308
  bindAttrs.push(`:attr.${attr}="${key}"`);
293
- addRefBinding(bindings, key, /** @type {any} */ (val).$ref);
309
+ addRefBinding(
310
+ bindings,
311
+ key,
312
+ /** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref),
313
+ );
294
314
  needsBind = true;
295
315
  } else if (isTemplateString(val)) {
296
316
  const key = `_t${counter.t++}`;
@@ -318,9 +338,9 @@ function buildClientNode(def, raw, context, bindings, handlers, counter) {
318
338
  )
319
339
  continue;
320
340
  if (isRefObject(val)) {
321
- const key = refToBindingKey(/** @type {any} */ (val).$ref);
341
+ const key = refToBindingKey(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref));
322
342
  bindAttrs.push(`:${camelToKebab(prop)}="${key}"`);
323
- addRefBinding(bindings, key, /** @type {any} */ (val).$ref);
343
+ addRefBinding(bindings, key, /** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref));
324
344
  needsBind = true;
325
345
  } else if (isTemplateString(val)) {
326
346
  const key = `_t${counter.t++}`;
@@ -347,17 +367,18 @@ function buildClientNode(def, raw, context, bindings, handlers, counter) {
347
367
  } else if (source.innerHTML) {
348
368
  // resolveStaticValue may return null if innerHTML contains `${` from rendered content
349
369
  // (e.g., code examples) that isn't an actual template expression. Fall back to raw value.
350
- inner = resolveStaticValue(source.innerHTML, nextContext.scope) ?? source.innerHTML;
370
+ inner = /** @type {string} */ (resolveStaticValue(source.innerHTML, nextContext.scope)) ??
371
+ source.innerHTML;
351
372
  } else if (
352
373
  source.children &&
353
374
  typeof source.children === "object" &&
354
375
  !Array.isArray(source.children) &&
355
- source.children.$prototype === "Array"
376
+ /** @type {JxMutableNode} */ (source.children).$prototype === "Array"
356
377
  ) {
357
378
  // ─── Mapped array → lit-html render binding ───
358
379
  counter.needsLit = true;
359
380
  const listKey = `_list${counter.l++}`;
360
- const arrayDef = source.children;
381
+ const arrayDef = /** @type {JxMutableNode} */ (source.children);
361
382
 
362
383
  // Resolve items source expression
363
384
  let itemsExpr;
@@ -388,9 +409,16 @@ function buildClientNode(def, raw, context, bindings, handlers, counter) {
388
409
  } else if (Array.isArray(source.children)) {
389
410
  const rawChildren = raw?.children;
390
411
  inner = source.children
391
- .map((/** @type {any} */ c, /** @type {number} */ i) => {
412
+ .map((c, i) => {
392
413
  const childRaw = rawChildren?.[i] ?? c;
393
- return buildClientNode(c, childRaw, nextContext, bindings, handlers, counter);
414
+ return buildClientNode(
415
+ /** @type {JxMutableNode} */ (/** @type {unknown} */ (c)),
416
+ /** @type {JxMutableNode} */ (/** @type {unknown} */ (childRaw)),
417
+ nextContext,
418
+ bindings,
419
+ handlers,
420
+ counter,
421
+ );
394
422
  })
395
423
  .join("\n ");
396
424
  }
@@ -410,7 +438,7 @@ function buildClientNode(def, raw, context, bindings, handlers, counter) {
410
438
  * Compile a map definition to a lit-html template string. Converts $map.item → item, $map.index →
411
439
  * index.
412
440
  *
413
- * @param {any} def
441
+ * @param {JxMutableNode} def
414
442
  * @returns {string}
415
443
  */
416
444
  function emitLitMapTemplate(def) {
@@ -463,14 +491,14 @@ function emitLitMapTemplate(def) {
463
491
  if (!prop.startsWith("on") || prop === "observedAttributes") continue;
464
492
  const eventName = prop.slice(2).toLowerCase();
465
493
  if (isRefObject(val)) {
466
- const key = refToBindingKey(/** @type {any} */ (val).$ref);
494
+ const key = refToBindingKey(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref));
467
495
  attrs += " @" + eventName + "=${(e) => { state.$map = { item, index }; on." + key + "(e); }}";
468
496
  } else if (
469
497
  val &&
470
498
  typeof val === "object" &&
471
- /** @type {any} */ (val).$prototype === "Function"
499
+ /** @type {JxMutableNode} */ (val).$prototype === "Function"
472
500
  ) {
473
- const body = mapRefsToLit(/** @type {any} */ (val).body);
501
+ const body = mapRefsToLit(/** @type {string} */ (/** @type {JxMutableNode} */ (val).body));
474
502
  attrs += " @" + eventName + "=${(e) => { " + body + " }}";
475
503
  }
476
504
  }
@@ -487,7 +515,11 @@ function emitLitMapTemplate(def) {
487
515
  if (isTemplateString(tc)) {
488
516
  inner = mapRefsToLit(tc);
489
517
  } else if (isRefObject(def.textContent)) {
490
- const path = refToBindingKey(def.textContent.$ref);
518
+ const path = refToBindingKey(
519
+ /** @type {string} */ (
520
+ /** @type {JxMutableNode} */ (/** @type {unknown} */ (def.textContent)).$ref
521
+ ),
522
+ );
491
523
  inner = "${state." + path + "}";
492
524
  } else {
493
525
  inner = escapeHtml(tc);
@@ -497,7 +529,9 @@ function emitLitMapTemplate(def) {
497
529
  } else if (Array.isArray(def.children)) {
498
530
  inner =
499
531
  "\n " +
500
- def.children.map((/** @type {any} */ c) => emitLitMapTemplate(c)).join("\n ") +
532
+ def.children
533
+ .map((c) => emitLitMapTemplate(/** @type {JxMutableNode} */ (c)))
534
+ .join("\n ") +
501
535
  "\n ";
502
536
  }
503
537
 
@@ -525,7 +559,15 @@ function mapRefsToLit(str) {
525
559
  * @param {[string, any][]} onEntries
526
560
  * @param {string[]} initBlocks
527
561
  * @param {Map<string, Set<string>>} srcImportMap
528
- * @param {any} counter
562
+ * @param {{
563
+ * t: number;
564
+ * s: number;
565
+ * h: number;
566
+ * m: number;
567
+ * sw: number;
568
+ * l: number;
569
+ * needsLit: boolean;
570
+ * }} counter
529
571
  * @param {string} _reactivitySrc
530
572
  * @returns {string}
531
573
  */
@@ -663,7 +705,7 @@ function emitClientModule(
663
705
 
664
706
  /**
665
707
  * @param {string} key
666
- * @param {any} def
708
+ * @param {JxMutableNode} def
667
709
  * @returns {string}
668
710
  */
669
711
  function emitRequestInit(key, def) {
@@ -713,7 +755,7 @@ function emitRequestInit(key, def) {
713
755
  * @param {string} key
714
756
  * @param {string} storeName
715
757
  * @param {string} storageKey
716
- * @param {any} defaultVal
758
+ * @param {unknown} defaultVal
717
759
  * @returns {string}
718
760
  */
719
761
  function emitStorageInit(key, storeName, storageKey, defaultVal) {
@@ -743,7 +785,7 @@ function emitStorageInit(key, storeName, storageKey, defaultVal) {
743
785
  /**
744
786
  * @param {string} key
745
787
  * @param {string} cookieName
746
- * @param {any} defaultVal
788
+ * @param {unknown} defaultVal
747
789
  * @returns {string}
748
790
  */
749
791
  function emitCookieInit(key, cookieName, defaultVal) {
@@ -12,22 +12,22 @@ import { escapeHtml, tagNameToClassName, isSchemaOnly, collectStyles } from "../
12
12
  * Compile a Jx custom element document to a JS module string.
13
13
  *
14
14
  * @param {string | any} sourcePath - Path to .json file or raw object
15
- * @param {any} [opts]
15
+ * @param {Record<string, unknown>} [opts]
16
16
  * @returns {Promise<{ files: { path: string; content: string; tagName: string }[] }>}
17
17
  */
18
18
  export async function compileElement(sourcePath, opts = {}) {
19
- const { resolveElementPath } = opts;
19
+ const { resolveElementPath } = /** @type {JxMutableNode} */ (opts);
20
20
  /** @type {{ path: string; content: string; tagName: string }[]} */
21
21
  const files = [];
22
22
  /** @type {Set<string>} */
23
23
  const visited = new Set();
24
24
 
25
25
  /**
26
- * @param {any} srcPath
26
+ * @param {string | any} srcPath
27
27
  * @param {string | null} parentDir
28
28
  */
29
29
  async function processElement(srcPath, parentDir) {
30
- /** @type {any} */
30
+ /** @type {JxMutableNode} */
31
31
  let doc;
32
32
  /** @type {string | null} */
33
33
  let filePath;
@@ -46,8 +46,8 @@ export async function compileElement(sourcePath, opts = {}) {
46
46
  } else {
47
47
  doc = srcPath;
48
48
  filePath = null;
49
- if (visited.has(doc.tagName)) return;
50
- visited.add(doc.tagName);
49
+ if (visited.has(/** @type {string} */ (doc.tagName))) return;
50
+ visited.add(/** @type {string} */ (doc.tagName));
51
51
  }
52
52
 
53
53
  const tagName = doc.tagName;
@@ -63,7 +63,7 @@ export async function compileElement(sourcePath, opts = {}) {
63
63
  const elementImports = [];
64
64
  if (Array.isArray(doc.$elements)) {
65
65
  for (const elRef of doc.$elements) {
66
- const refPath = elRef.$ref ?? elRef;
66
+ const refPath = /** @type {JxMutableNode} */ (elRef).$ref ?? elRef;
67
67
  if (typeof refPath !== "string") continue;
68
68
 
69
69
  if (currentDir) {
@@ -87,7 +87,7 @@ export async function compileElement(sourcePath, opts = {}) {
87
87
  files.push({ path: outputPath, content: jsContent, tagName });
88
88
  }
89
89
 
90
- await processElement(sourcePath, opts.basePath ?? null);
90
+ await processElement(sourcePath, /** @type {string | null} */ (opts.basePath ?? null));
91
91
  return { files };
92
92
  }
93
93
 
@@ -96,7 +96,7 @@ export async function compileElement(sourcePath, opts = {}) {
96
96
  * dependencies.
97
97
  *
98
98
  * @param {string | any} sourcePath
99
- * @param {any} [opts]
99
+ * @param {Record<string, unknown>} [opts]
100
100
  * @returns {Promise<{
101
101
  * html: string;
102
102
  * files: { path: string; content: string; tagName: string }[];
@@ -107,7 +107,7 @@ export async function compileElementPage(sourcePath, opts = {}) {
107
107
  title = "Jx App",
108
108
  reactivitySrc = "https://esm.sh/@vue/reactivity@3.5.13",
109
109
  litHtmlSrc = "https://esm.sh/lit-html@3.3.0",
110
- } = opts;
110
+ } = /** @type {JxMutableNode} */ (opts);
111
111
 
112
112
  const result = await compileElement(sourcePath, opts);
113
113
  const root = result.files[result.files.length - 1];
@@ -146,7 +146,7 @@ export async function compileElementPage(sourcePath, opts = {}) {
146
146
  * like { type, default, description } now correctly extract the `default` value instead of dumping
147
147
  * the whole object.
148
148
  *
149
- * @param {any} def
149
+ * @param {JxMutableNode} def
150
150
  * @returns {string | undefined}
151
151
  */
152
152
  function extractInitialValue(def) {
@@ -175,7 +175,7 @@ function extractInitialValue(def) {
175
175
  /**
176
176
  * Generate a complete ES module string for a custom element.
177
177
  *
178
- * @param {any} doc
178
+ * @param {JxMutableNode | Record<string, any>} doc
179
179
  * @param {string} className
180
180
  * @param {string[]} elementImports
181
181
  * @returns {string}
@@ -196,7 +196,7 @@ export function emitElementModule(doc, className, elementImports) {
196
196
  const srcImportMap = new Map();
197
197
  const defs = doc.state ?? {};
198
198
  for (const [key, def] of Object.entries(defs)) {
199
- const d = /** @type {any} */ (def);
199
+ const d = /** @type {JxMutableNode} */ (def);
200
200
  if (d && typeof d === "object" && !Array.isArray(d) && d.$prototype === "Function" && d.$src) {
201
201
  const srcPath = d.$src;
202
202
  if (!srcImportMap.has(srcPath)) srcImportMap.set(srcPath, []);
@@ -226,7 +226,7 @@ export function emitElementModule(doc, className, elementImports) {
226
226
  const functionEntries = [];
227
227
 
228
228
  for (const [key, def] of Object.entries(defs)) {
229
- const d = /** @type {any} */ (def);
229
+ const d = /** @type {JxMutableNode} */ (def);
230
230
  if (d && typeof d === "object" && !Array.isArray(d) && d.$prototype === "Function") {
231
231
  if (typeof d.body === "string" && d.body.includes("return")) {
232
232
  computedEntries.push([key, d]);
@@ -391,25 +391,27 @@ export function emitElementModule(doc, className, elementImports) {
391
391
  /**
392
392
  * Convert Jx children to lit-html template content.
393
393
  *
394
- * @param {any} children
395
- * @param {any} parentStyle
394
+ * @param {(JxMutableNode | string)[] | JxMutableNode | undefined} children
395
+ * @param {JxStyle | Record<string, any> | null | undefined} parentStyle
396
396
  * @param {string} indent
397
397
  * @returns {string}
398
398
  */
399
399
  function emitLitChildren(children, parentStyle, indent) {
400
400
  if (!children) return "";
401
401
 
402
- if (children.$prototype === "Array") {
403
- return emitMappedArray(children, indent);
402
+ if (/** @type {JxMutableNode} */ (children).$prototype === "Array") {
403
+ return emitMappedArray(/** @type {JxMutableNode} */ (children), indent);
404
404
  }
405
405
 
406
406
  if (!Array.isArray(children)) return "";
407
407
 
408
- return children.map((/** @type {any} */ child) => emitLitNode(child, indent)).join("\n");
408
+ return children
409
+ .map((/** @type {JxMutableNode | string} */ child) => emitLitNode(child, indent))
410
+ .join("\n");
409
411
  }
410
412
 
411
413
  /**
412
- * @param {any} def
414
+ * @param {JxMutableNode | string} def
413
415
  * @param {string} indent
414
416
  * @returns {string}
415
417
  */
@@ -458,8 +460,10 @@ function emitLitNode(def, indent) {
458
460
  )
459
461
  continue;
460
462
 
461
- if (val && typeof val === "object" && /** @type {any} */ (val).$ref) {
462
- parts.push(`.${key}="\${${refToExpr(/** @type {any} */ (val).$ref)}}"`);
463
+ if (val && typeof val === "object" && /** @type {JxMutableNode} */ (val).$ref) {
464
+ parts.push(
465
+ `.${key}="\${${refToExpr(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref))}}"`,
466
+ );
463
467
  } else if (typeof val === "string" && val.includes("${")) {
464
468
  parts.push(`.${key}="${toLitExpr(val)}"`);
465
469
  }
@@ -467,8 +471,10 @@ function emitLitNode(def, indent) {
467
471
 
468
472
  if (def.$props) {
469
473
  for (const [key, val] of Object.entries(def.$props)) {
470
- if (val && typeof val === "object" && /** @type {any} */ (val).$ref) {
471
- parts.push(`.${key}="\${${refToExpr(/** @type {any} */ (val).$ref)}}"`);
474
+ if (val && typeof val === "object" && /** @type {JxMutableNode} */ (val).$ref) {
475
+ parts.push(
476
+ `.${key}="\${${refToExpr(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref))}}"`,
477
+ );
472
478
  } else {
473
479
  parts.push(`.${key}="\${${JSON.stringify(val)}}"`);
474
480
  }
@@ -478,14 +484,18 @@ function emitLitNode(def, indent) {
478
484
  for (const [key, val] of Object.entries(def)) {
479
485
  if (!key.startsWith("on") || key === "observedAttributes") continue;
480
486
  const eventName = key.slice(2).toLowerCase();
481
- if (val && typeof val === "object" && /** @type {any} */ (val).$ref) {
482
- parts.push(`@${eventName}="\${(e) => ${refToExpr(/** @type {any} */ (val).$ref)}(s, e)}"`);
487
+ if (val && typeof val === "object" && /** @type {JxMutableNode} */ (val).$ref) {
488
+ parts.push(
489
+ `@${eventName}="\${(e) => ${refToExpr(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref))}(s, e)}"`,
490
+ );
483
491
  } else if (
484
492
  val &&
485
493
  typeof val === "object" &&
486
- /** @type {any} */ (val).$prototype === "Function"
494
+ /** @type {JxMutableNode} */ (val).$prototype === "Function"
487
495
  ) {
488
- parts.push(`@${eventName}="\${(e) => { ${inlineHandlerBody(/** @type {any} */ (val))} }}"`);
496
+ parts.push(
497
+ `@${eventName}="\${(e) => { ${inlineHandlerBody(/** @type {JxMutableNode} */ (val))} }}"`,
498
+ );
489
499
  }
490
500
  }
491
501
 
@@ -512,7 +522,7 @@ function emitLitNode(def, indent) {
512
522
  }
513
523
 
514
524
  /**
515
- * @param {any} arrayDef
525
+ * @param {JxMutableNode} arrayDef
516
526
  * @param {string} indent
517
527
  * @returns {string}
518
528
  */
@@ -528,8 +538,10 @@ function emitMappedArray(arrayDef, indent) {
528
538
 
529
539
  if (mapDef.$props) {
530
540
  for (const [key, val] of Object.entries(mapDef.$props)) {
531
- if (val && typeof val === "object" && /** @type {any} */ (val).$ref) {
532
- parts.push(`.${key}="\${${mapRefToExpr(/** @type {any} */ (val).$ref)}}"`);
541
+ if (val && typeof val === "object" && /** @type {JxMutableNode} */ (val).$ref) {
542
+ parts.push(
543
+ `.${key}="\${${mapRefToExpr(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref))}}"`,
544
+ );
533
545
  } else {
534
546
  parts.push(`.${key}="\${${JSON.stringify(val)}}"`);
535
547
  }
@@ -542,8 +554,10 @@ function emitMappedArray(arrayDef, indent) {
542
554
  for (const [key, val] of Object.entries(mapDef)) {
543
555
  if (!key.startsWith("on")) continue;
544
556
  const eventName = key.slice(2).toLowerCase();
545
- if (val && typeof val === "object" && /** @type {any} */ (val).$ref) {
546
- parts.push(`@${eventName}="\${(e) => ${refToExpr(/** @type {any} */ (val).$ref)}(s, e)}"`);
557
+ if (val && typeof val === "object" && /** @type {JxMutableNode} */ (val).$ref) {
558
+ parts.push(
559
+ `@${eventName}="\${(e) => ${refToExpr(/** @type {string} */ (/** @type {JxMutableNode} */ (val).$ref))}(s, e)}"`,
560
+ );
547
561
  }
548
562
  }
549
563
 
@@ -602,13 +616,17 @@ function toLitExpr(str) {
602
616
  * Convert textContent value to lit-html text content. Bug fix: handles $ref objects, which
603
617
  * previously produced [object Object].
604
618
  *
605
- * @param {any} value
619
+ * @param {unknown} value
606
620
  * @returns {string}
607
621
  */
608
622
  function toLitTextContent(value) {
609
623
  // Handle $ref objects → emit as lit expression
610
- if (value !== null && typeof value === "object" && typeof value.$ref === "string") {
611
- return `\${${refToExpr(value.$ref)}}`;
624
+ if (
625
+ value !== null &&
626
+ typeof value === "object" &&
627
+ typeof (/** @type {JxMutableNode} */ (value).$ref) === "string"
628
+ ) {
629
+ return `\${${refToExpr(/** @type {string} */ (/** @type {JxMutableNode} */ (value).$ref))}}`;
612
630
  }
613
631
  if (typeof value === "string" && value.includes("${")) {
614
632
  return toLitExpr(value);
@@ -617,7 +635,7 @@ function toLitTextContent(value) {
617
635
  }
618
636
 
619
637
  /**
620
- * @param {any} def
638
+ * @param {JxMutableNode} def
621
639
  * @returns {string}
622
640
  */
623
641
  function inlineHandlerBody(def) {
@@ -626,7 +644,7 @@ function inlineHandlerBody(def) {
626
644
  }
627
645
 
628
646
  /**
629
- * @param {any} styleDef
647
+ * @param {JxStyle | Record<string, any> | null | undefined} styleDef
630
648
  * @returns {string}
631
649
  */
632
650
  function emitStyleString(styleDef) {
@@ -658,7 +676,7 @@ function emitStyleString(styleDef) {
658
676
  /**
659
677
  * Check if a children tree contains a `<slot>` element.
660
678
  *
661
- * @param {any} children
679
+ * @param {(JxMutableNode | string)[] | undefined} children
662
680
  * @returns {boolean}
663
681
  */
664
682
  function treeHasSlot(children) {