jq79 0.3.24 → 0.3.25

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/src/jq79.ts CHANGED
@@ -24,12 +24,18 @@ type TagBlock = {
24
24
  const elementAttrs = (el: Element): Record<string, string> =>
25
25
  Object.fromEntries(Array.from(el.attributes).map(attr => [attr.name, attr.value]))
26
26
 
27
+ // text is kept verbatim - not trimmed, not dropped when it's only whitespace.
28
+ // A template is HTML, so the space in `<span>a</span>\n<span>b</span>` is the
29
+ // same space the browser would collapse-and-render between them, and CSS gets
30
+ // to decide what it's worth (nothing in a block or flex container, one space
31
+ // between inline elements). Trimming it here, as this used to, silently glued
32
+ // siblings together and ate the spaces in `hola <b>mundo</b> adios`
27
33
  const elementToAST = (el: Element): TemplateNode => ({
28
34
  tag: el.tagName.toLowerCase(),
29
35
  attrs: elementAttrs(el),
30
36
  children: Array.from(el.childNodes).flatMap((node): (TemplateNode | string)[] => {
31
37
  if (node.nodeType === Node.TEXT_NODE) {
32
- const text = node.textContent?.trim() ?? ""
38
+ const text = node.textContent ?? ""
33
39
  return text ? [text] : []
34
40
  }
35
41
  if (node.nodeType === Node.ELEMENT_NODE) {
@@ -409,8 +415,10 @@ const renderNodes = (nodes: (TemplateNode | string)[], scope: Record<string, any
409
415
  const node = nodes[i]
410
416
 
411
417
  if (typeof node === "string") {
412
- const textNode = document.createTextNode("")
413
- fx.effect(() => { textNode.textContent = interpolate(node, scope) })
418
+ const textNode = document.createTextNode(node)
419
+ // static text is most of a template (all of its indentation, for a start):
420
+ // only text with a {{ expression }} in it needs an effect to stay in sync
421
+ if (node.includes("{{")) fx.effect(() => { textNode.textContent = interpolate(node, scope) })
414
422
  fragment.appendChild(textNode)
415
423
  i++
416
424
  continue
@@ -425,15 +433,27 @@ const renderNodes = (nodes: (TemplateNode | string)[], scope: Record<string, any
425
433
  if (":if" in node.attrs) {
426
434
  const branches: ConditionalBranch[] = [{ expr: node.attrs[":if"], node }]
427
435
  i++
428
- while (i < nodes.length && typeof nodes[i] !== "string" && ":elseif" in (nodes[i] as TemplateNode).attrs) {
429
- const elseifNode = nodes[i] as TemplateNode
430
- branches.push({ expr: elseifNode.attrs[":elseif"], node: elseifNode })
431
- i++
436
+
437
+ // the branches of a chain are siblings in the AST, but the template writes
438
+ // them on their own lines - so the whitespace between them is indentation
439
+ // and nothing else, and it's dropped rather than rendered: only one branch
440
+ // is ever in the DOM, so there is nothing for it to be a space *between*
441
+ const nextBranch = (attr: string): TemplateNode | undefined => {
442
+ let next = i
443
+ while (next < nodes.length && typeof nodes[next] === "string" && !(nodes[next] as string).trim()) next++
444
+ const candidate = nodes[next]
445
+ if (typeof candidate === "object" && attr in candidate.attrs) {
446
+ i = next + 1
447
+ return candidate
448
+ }
449
+ return undefined
432
450
  }
433
- if (i < nodes.length && typeof nodes[i] !== "string" && ":else" in (nodes[i] as TemplateNode).attrs) {
434
- branches.push({ node: nodes[i] as TemplateNode })
435
- i++
451
+
452
+ for (let elseif = nextBranch(":elseif"); elseif; elseif = nextBranch(":elseif")) {
453
+ branches.push({ expr: elseif.attrs[":elseif"], node: elseif })
436
454
  }
455
+ const elseNode = nextBranch(":else")
456
+ if (elseNode) branches.push({ node: elseNode })
437
457
 
438
458
  fragment.appendChild(renderConditional(branches, scope, fx))
439
459
  continue