@abide/abide 0.31.1 → 0.32.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/AGENTS.md +1 -1
- package/CHANGELOG.md +10 -0
- package/package.json +1 -2
- package/src/checkAbide.ts +11 -6
- package/src/lib/ui/compile/AbideCompileError.ts +16 -0
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +0 -1
- package/src/lib/ui/compile/abideUiPlugin.ts +25 -2
- package/src/lib/ui/compile/bindListenEvent.ts +19 -0
- package/src/lib/ui/compile/compileShadow.ts +16 -4
- package/src/lib/ui/compile/generateBuild.ts +110 -213
- package/src/lib/ui/compile/generateSSR.ts +51 -86
- package/src/lib/ui/compile/lowerContext.ts +64 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +6 -1
- package/src/lib/ui/compile/offsetToLineColumn.ts +16 -0
- package/src/lib/ui/compile/scopeAttr.ts +9 -0
- package/src/lib/ui/compile/staticAttr.ts +11 -0
- package/src/lib/ui/compile/staticTextPart.ts +12 -0
- package/src/lib/ui/compile/unwrapParens.ts +10 -0
- package/src/lib/ui/dom/awaitBlock.ts +27 -21
- package/src/lib/ui/dom/clearBetween.ts +16 -0
- package/src/lib/ui/dom/each.ts +64 -38
- package/src/lib/ui/dom/eachAsync.ts +39 -52
- package/src/lib/ui/dom/fillBefore.ts +16 -0
- package/src/lib/ui/dom/moveRange.ts +19 -0
- package/src/lib/ui/dom/openMarker.ts +22 -0
- package/src/lib/ui/dom/removeRange.ts +18 -0
- package/src/lib/ui/dom/switchBlock.ts +32 -40
- package/src/lib/ui/dom/tryBlock.ts +31 -35
- package/src/lib/ui/dom/types/EachRow.ts +10 -3
- package/src/lib/ui/dom/types/SwitchCase.ts +3 -2
- package/src/lib/ui/dom/when.ts +34 -43
- package/src/lib/ui/installHotBridge.ts +0 -2
- package/src/lib/ui/state.ts +14 -5
- package/src/lib/ui/compile/branchElements.ts +0 -50
- package/src/lib/ui/dom/openRoot.ts +0 -20
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { OUTLET_TAG } from '../runtime/OUTLET_TAG.ts'
|
|
2
|
-
import {
|
|
3
|
-
import { escapeHtml } from './escapeHtml.ts'
|
|
2
|
+
import { bindListenEvent } from './bindListenEvent.ts'
|
|
4
3
|
import { groupBindParts } from './groupBindParts.ts'
|
|
5
|
-
import {
|
|
4
|
+
import { lowerContext } from './lowerContext.ts'
|
|
6
5
|
import { partitionSlots } from './partitionSlots.ts'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { scopeAttr } from './scopeAttr.ts'
|
|
7
|
+
import { staticAttr } from './staticAttr.ts'
|
|
9
8
|
import { staticAttrValue } from './staticAttrValue.ts'
|
|
9
|
+
import { staticTextPart } from './staticTextPart.ts'
|
|
10
10
|
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
11
11
|
import { VOID_TAGS } from './VOID_TAGS.ts'
|
|
12
12
|
|
|
@@ -29,24 +29,12 @@ export function generateBuild(
|
|
|
29
29
|
let counter = 0
|
|
30
30
|
const nextVar = (prefix: string): string => `${prefix}${counter++}`
|
|
31
31
|
|
|
32
|
-
/*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/* Rewrites signal refs, then lowers a single expression (no trailing `;`). */
|
|
40
|
-
function lowerExpression(code: string): string {
|
|
41
|
-
const renamed = renameSignalRefs(code, stateNames, derefScope())
|
|
42
|
-
return lowerDocAccess(renamed, 'model').trim().replace(/;$/, '')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/* As above but keeps the trailing `;` for a handler body. */
|
|
46
|
-
function lowerStatement(code: string): string {
|
|
47
|
-
const renamed = renameSignalRefs(code, stateNames, derefScope())
|
|
48
|
-
return lowerDocAccess(renamed, 'model').trim()
|
|
49
|
-
}
|
|
32
|
+
/* The shared signal→`model` lowering + branch-scoped nested-script deref scope. */
|
|
33
|
+
const {
|
|
34
|
+
expression: lowerExpression,
|
|
35
|
+
statement: lowerStatement,
|
|
36
|
+
withNestedScripts,
|
|
37
|
+
} = lowerContext(stateNames, derivedNames)
|
|
50
38
|
|
|
51
39
|
/* Builds an element and its children; returns the build code and its var.
|
|
52
40
|
`varExpr` is how the element is obtained — `openChild(parent, tag)` for a
|
|
@@ -90,39 +78,20 @@ export function generateBuild(
|
|
|
90
78
|
}
|
|
91
79
|
} else {
|
|
92
80
|
/* Two-way: drive the property from the path, and write the path
|
|
93
|
-
back on
|
|
94
|
-
|
|
81
|
+
back on the property's native event (`input` for most fields,
|
|
82
|
+
but `toggle` for `<details open>`, `change` for checked/select).
|
|
83
|
+
The path is an lvalue, so the write lowers to an assignment. */
|
|
84
|
+
const event = bindListenEvent(attr.property, node.tag)
|
|
95
85
|
code += `effect(() => { ${varName}.${attr.property} = ${lowerExpression(attr.code)}; });\n`
|
|
96
|
-
code += `on(${varName},
|
|
86
|
+
code += `on(${varName}, ${JSON.stringify(event)}, () => { ${lowerStatement(`${attr.code} = ${varName}.${attr.property}`)} });\n`
|
|
97
87
|
}
|
|
98
88
|
}
|
|
99
89
|
/* A `<script>` among the children scopes its bindings to this element's
|
|
100
90
|
subtree (its later siblings auto-deref them); pop after. */
|
|
101
|
-
|
|
102
|
-
code += generateChildren(node.children, varName)
|
|
103
|
-
for (const name of added) {
|
|
104
|
-
localDerived.delete(name)
|
|
105
|
-
}
|
|
91
|
+
code += withNestedScripts(node.children, () => generateChildren(node.children, varName))
|
|
106
92
|
return { code, varName }
|
|
107
93
|
}
|
|
108
94
|
|
|
109
|
-
/* Adds the binding names of any `<script>` children to the deref scope, returning
|
|
110
|
-
the names it added (for the caller to pop). */
|
|
111
|
-
function scopeNestedScripts(children: TemplateNode[]): string[] {
|
|
112
|
-
const added: string[] = []
|
|
113
|
-
for (const child of children) {
|
|
114
|
-
if (child.kind === 'script') {
|
|
115
|
-
for (const name of nestedBindingNames(child.code)) {
|
|
116
|
-
if (!localDerived.has(name)) {
|
|
117
|
-
localDerived.add(name)
|
|
118
|
-
added.push(name)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return added
|
|
124
|
-
}
|
|
125
|
-
|
|
126
95
|
/* Emits code appending `node` to `parentVar`. */
|
|
127
96
|
function generateChild(node: TemplateNode, parentVar: string): string {
|
|
128
97
|
if (node.kind === 'script') {
|
|
@@ -238,13 +207,11 @@ export function generateBuild(
|
|
|
238
207
|
(child): child is Extract<TemplateNode, { kind: 'case' }> => child.kind === 'case',
|
|
239
208
|
)
|
|
240
209
|
.map((branch) => {
|
|
241
|
-
const param = nextVar('p')
|
|
242
|
-
const roots = elementRoots(branch.children, '<template case>', param)
|
|
243
210
|
const match =
|
|
244
211
|
branch.match === undefined
|
|
245
212
|
? 'undefined'
|
|
246
213
|
: `() => (${lowerExpression(branch.match)})`
|
|
247
|
-
return `{ match: ${match}, render:
|
|
214
|
+
return `{ match: ${match}, render: ${branchThunk(branch.children)} }`
|
|
248
215
|
})
|
|
249
216
|
.join(', ')
|
|
250
217
|
return `switchBlock(${parentVar}, () => (${lowerExpression(node.subject)}), [${cases}]);\n`
|
|
@@ -275,17 +242,12 @@ export function generateBuild(
|
|
|
275
242
|
|
|
276
243
|
/* Mounts a child component into a wrapper element, passing each prop as a
|
|
277
244
|
reactive thunk so the child re-reads when the parent expression changes. */
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
): string {
|
|
282
|
-
const wrapper = nextVar('cmp')
|
|
245
|
+
/* The prop + slot thunks a child mount receives — its props as value thunks and
|
|
246
|
+
its slot content as host-taking builders (`$children` / `$slots[name]`). */
|
|
247
|
+
function componentParts(node: Extract<TemplateNode, { kind: 'component' }>): string[] {
|
|
283
248
|
const parts = node.props.map(
|
|
284
249
|
(prop) => `${JSON.stringify(prop.name)}: () => (${lowerExpression(prop.code)})`,
|
|
285
250
|
)
|
|
286
|
-
/* Slot content compiles to builders the child mounts into the host it passes
|
|
287
|
-
from each <slot> position: the default markup as `$children`, and each
|
|
288
|
-
`slot="name"` group as `$slots[name]`. */
|
|
289
251
|
const groups = partitionSlots(node.children)
|
|
290
252
|
const slotCode = groups.default.map((child) => generateChild(child, '$slot')).join('')
|
|
291
253
|
if (slotCode.trim() !== '') {
|
|
@@ -300,13 +262,31 @@ export function generateBuild(
|
|
|
300
262
|
.join(', ')
|
|
301
263
|
parts.push(`"$slots": { ${entries} }`)
|
|
302
264
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
265
|
+
return parts
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* Mounts a child into a wrapper obtained via `varExpr` (openChild — appends on
|
|
269
|
+
create / claims on hydrate). Hydration stays active, so the child adopts its
|
|
270
|
+
server markup inside the wrapper. Returns the wrapper var. */
|
|
271
|
+
function mountComponent(
|
|
272
|
+
node: Extract<TemplateNode, { kind: 'component' }>,
|
|
273
|
+
varExpr: string,
|
|
274
|
+
): { code: string; varName: string } {
|
|
275
|
+
const wrapper = nextVar('cmp')
|
|
276
|
+
const code =
|
|
277
|
+
`const ${wrapper} = ${varExpr};\n` +
|
|
278
|
+
`mountChild(${wrapper}, ${node.name}, { ${componentParts(node).join(', ')} });\n`
|
|
279
|
+
return { code, varName: wrapper }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function generateComponent(
|
|
283
|
+
node: Extract<TemplateNode, { kind: 'component' }>,
|
|
284
|
+
parentVar: string,
|
|
285
|
+
): string {
|
|
286
|
+
return mountComponent(
|
|
287
|
+
node,
|
|
288
|
+
`openChild(${parentVar}, ${JSON.stringify(node.name.toLowerCase())})`,
|
|
289
|
+
).code
|
|
310
290
|
}
|
|
311
291
|
|
|
312
292
|
/* An await block: pending → resolved(value) / error branches. Each branch is a
|
|
@@ -324,17 +304,16 @@ export function generateBuild(
|
|
|
324
304
|
const pending = node.blocking
|
|
325
305
|
? []
|
|
326
306
|
: node.children.filter((child) => child.kind !== 'branch')
|
|
307
|
+
const thenBranch = node.children.find(isBranch('then'))
|
|
327
308
|
const thenThunk = node.blocking
|
|
328
|
-
?
|
|
309
|
+
? branchThunk(
|
|
329
310
|
node.children.filter((child) => child.kind !== 'branch'),
|
|
330
311
|
node.as ?? '_value',
|
|
331
|
-
'<template await then>',
|
|
332
312
|
finallyChildren,
|
|
333
313
|
)
|
|
334
|
-
:
|
|
335
|
-
|
|
336
|
-
'_value',
|
|
337
|
-
'<template then>',
|
|
314
|
+
: branchThunk(
|
|
315
|
+
branchChildren(thenBranch),
|
|
316
|
+
branchVar(thenBranch) ?? '_value',
|
|
338
317
|
finallyChildren,
|
|
339
318
|
)
|
|
340
319
|
/* Neither catch nor finally → pass `undefined` so awaitBlock re-throws the
|
|
@@ -343,10 +322,15 @@ export function generateBuild(
|
|
|
343
322
|
const catchThunk =
|
|
344
323
|
catchBranch === undefined && finallyChildren.length === 0
|
|
345
324
|
? 'undefined'
|
|
346
|
-
:
|
|
325
|
+
: branchThunk(
|
|
326
|
+
branchChildren(catchBranch),
|
|
327
|
+
branchVar(catchBranch) ?? '_error',
|
|
328
|
+
finallyChildren,
|
|
329
|
+
)
|
|
330
|
+
const pendingThunk = hasRenderableContent(pending) ? branchThunk(pending) : 'undefined'
|
|
347
331
|
return (
|
|
348
332
|
`awaitBlock(${parentVar}, nextBlockId(), () => (${lowerExpression(node.promise)}), ` +
|
|
349
|
-
`${
|
|
333
|
+
`${pendingThunk}, ` +
|
|
350
334
|
`${thenThunk}, ` +
|
|
351
335
|
`${catchThunk});\n`
|
|
352
336
|
)
|
|
@@ -362,98 +346,45 @@ export function generateBuild(
|
|
|
362
346
|
return branch !== undefined && branch.kind === 'branch' ? branch.as : undefined
|
|
363
347
|
}
|
|
364
348
|
|
|
365
|
-
/*
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
349
|
+
/* A branch's content as a void render thunk `(parent[, value]) => void` that
|
|
350
|
+
builds its children — and an optional trailing `finally` branch — into
|
|
351
|
+
`parent`. The full-range model tracks the built content between markers, so a
|
|
352
|
+
branch holds ANY content (components, text, nested control-flow, snippets) and
|
|
353
|
+
is generated exactly like a normal child list. `valueParam` binds a resolved /
|
|
354
|
+
error / item value into scope. Nested `<script>`s are emitted in document order
|
|
355
|
+
by `generateChildren`; `withNestedScripts` puts their bindings in deref scope. */
|
|
356
|
+
function branchThunk(
|
|
369
357
|
children: TemplateNode[],
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
allowEmpty = false,
|
|
373
|
-
): { code: string; expr: string } {
|
|
374
|
-
/* Nested `<script>`s: add their bindings to the deref scope (so the script
|
|
375
|
-
body + this branch's markup auto-deref them), emit the lowered script
|
|
376
|
-
bodies first, then build the element roots — all within the scope, which
|
|
377
|
-
we pop afterward. The scripts run when the branch mounts, owned by its
|
|
378
|
-
scope. */
|
|
379
|
-
const added: string[] = []
|
|
380
|
-
for (const child of children) {
|
|
381
|
-
if (child.kind === 'script') {
|
|
382
|
-
for (const name of nestedBindingNames(child.code)) {
|
|
383
|
-
if (!localDerived.has(name)) {
|
|
384
|
-
localDerived.add(name)
|
|
385
|
-
added.push(name)
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
const scriptCode = children
|
|
391
|
-
.filter(
|
|
392
|
-
(child): child is Extract<TemplateNode, { kind: 'script' }> =>
|
|
393
|
-
child.kind === 'script',
|
|
394
|
-
)
|
|
395
|
-
.map((child) => `${lowerStatement(child.code)}\n`)
|
|
396
|
-
.join('')
|
|
397
|
-
const built = branchElements(children, context, allowEmpty).map((element) =>
|
|
398
|
-
generateElement(element, `openRoot(${parentVar}, ${JSON.stringify(element.tag)})`),
|
|
399
|
-
)
|
|
400
|
-
for (const name of added) {
|
|
401
|
-
localDerived.delete(name)
|
|
402
|
-
}
|
|
403
|
-
return {
|
|
404
|
-
code: scriptCode + built.map((part) => part.code).join(''),
|
|
405
|
-
expr: `[${built.map((part) => part.varName).join(', ')}]`,
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/* A `(parent[, value]) => Node[]` thunk over a branch's element roots, or
|
|
410
|
-
`undefined` when empty (a `<template await>` with no pending branch).
|
|
411
|
-
`paramName`/`fallback` name the resolved/error value the branch binds. */
|
|
412
|
-
function renderThunk(
|
|
413
|
-
children: TemplateNode[],
|
|
414
|
-
paramName: string | undefined,
|
|
415
|
-
context: string,
|
|
416
|
-
fallback?: string,
|
|
358
|
+
valueParam?: string,
|
|
359
|
+
finallyChildren: TemplateNode[] = [],
|
|
417
360
|
): string {
|
|
418
|
-
const hasElement = children.some((child) => child.kind === 'element')
|
|
419
361
|
const parentParam = nextVar('p')
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
roots, both possibly empty. `param` names a bound value (the resolved/error
|
|
431
|
-
value, or the caught error); undefined for a value-less branch (try/pending). */
|
|
432
|
-
function renderRangeThunk(
|
|
433
|
-
children: TemplateNode[],
|
|
434
|
-
param: string | undefined,
|
|
435
|
-
context: string,
|
|
436
|
-
finallyChildren: TemplateNode[],
|
|
437
|
-
): string {
|
|
438
|
-
const parentParam = nextVar('p')
|
|
439
|
-
const head = param === undefined ? `(${parentParam})` : `(${parentParam}, ${param})`
|
|
440
|
-
const roots = elementRoots(children, context, parentParam, true)
|
|
441
|
-
const finallyRoots = elementRoots(finallyChildren, '<template finally>', parentParam, true)
|
|
442
|
-
return `${head} => {\n${roots.code}${finallyRoots.code}return [...${roots.expr}, ...${finallyRoots.expr}];\n}`
|
|
362
|
+
const head =
|
|
363
|
+
valueParam === undefined ? `(${parentParam})` : `(${parentParam}, ${valueParam})`
|
|
364
|
+
const body = withNestedScripts(children, () => generateChildren(children, parentParam))
|
|
365
|
+
const finallyBody =
|
|
366
|
+
finallyChildren.length > 0
|
|
367
|
+
? withNestedScripts(finallyChildren, () =>
|
|
368
|
+
generateChildren(finallyChildren, parentParam),
|
|
369
|
+
)
|
|
370
|
+
: ''
|
|
371
|
+
return `${head} => {\n${body}${finallyBody}}`
|
|
443
372
|
}
|
|
444
373
|
|
|
445
|
-
/*
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
374
|
+
/* True when a branch has content worth a render thunk — vs an absent/empty branch
|
|
375
|
+
a block represents with `undefined` (an `await` with no pending markup). */
|
|
376
|
+
function hasRenderableContent(children: TemplateNode[]): boolean {
|
|
377
|
+
return children.some(
|
|
378
|
+
(child) =>
|
|
379
|
+
child.kind === 'element' ||
|
|
380
|
+
child.kind === 'component' ||
|
|
381
|
+
child.kind === 'if' ||
|
|
382
|
+
child.kind === 'each' ||
|
|
383
|
+
child.kind === 'await' ||
|
|
384
|
+
child.kind === 'try' ||
|
|
385
|
+
child.kind === 'switch' ||
|
|
386
|
+
child.kind === 'snippet' ||
|
|
387
|
+
(child.kind === 'text' && !isWhitespaceText(child)),
|
|
457
388
|
)
|
|
458
389
|
}
|
|
459
390
|
|
|
@@ -475,62 +406,46 @@ export function generateBuild(
|
|
|
475
406
|
const catchBranch = findBranch(node.children, 'catch')
|
|
476
407
|
const finallyChildren = branchChildren(findBranch(node.children, 'finally'))
|
|
477
408
|
const guarded = node.children.filter((child) => child.kind !== 'branch')
|
|
478
|
-
const tryThunk =
|
|
409
|
+
const tryThunk = branchThunk(guarded, undefined, finallyChildren)
|
|
479
410
|
const catchThunk =
|
|
480
411
|
catchBranch === undefined
|
|
481
412
|
? 'undefined'
|
|
482
|
-
:
|
|
413
|
+
: branchThunk(
|
|
483
414
|
branchChildren(catchBranch),
|
|
484
415
|
branchVar(catchBranch) ?? '_error',
|
|
485
|
-
'<template catch>',
|
|
486
416
|
finallyChildren,
|
|
487
417
|
)
|
|
488
418
|
return `tryBlock(${parentVar}, nextBlockId(), ${tryThunk}, ${catchThunk});\n`
|
|
489
419
|
}
|
|
490
420
|
|
|
491
|
-
/* A conditional with an optional nested `<template else>` (a `case` child).
|
|
492
|
-
|
|
421
|
+
/* A conditional with an optional nested `<template else>` (a `case` child). Each
|
|
422
|
+
branch is a content range the runtime tracks between markers. */
|
|
493
423
|
function generateIf(node: Extract<TemplateNode, { kind: 'if' }>, parentVar: string): string {
|
|
494
424
|
const elseBranch = node.children.find(
|
|
495
425
|
(child): child is Extract<TemplateNode, { kind: 'case' }> => child.kind === 'case',
|
|
496
426
|
)
|
|
497
427
|
const thenChildren = node.children.filter((child) => child.kind !== 'case')
|
|
498
|
-
const
|
|
499
|
-
const thenRoots = elementRoots(thenChildren, '<template if>', thenParam)
|
|
500
|
-
const thenThunk = `(${thenParam}) => {\n${thenRoots.code}return ${thenRoots.expr};\n}`
|
|
428
|
+
const thenThunk = branchThunk(thenChildren)
|
|
501
429
|
if (elseBranch === undefined) {
|
|
502
430
|
return `when(${parentVar}, () => (${lowerExpression(node.condition)}), ${thenThunk});\n`
|
|
503
431
|
}
|
|
504
|
-
const
|
|
505
|
-
const elseRoots = elementRoots(elseBranch.children, '<template else>', elseParam)
|
|
506
|
-
const elseThunk = `(${elseParam}) => {\n${elseRoots.code}return ${elseRoots.expr};\n}`
|
|
432
|
+
const elseThunk = branchThunk(elseBranch.children)
|
|
507
433
|
return `when(${parentVar}, () => (${lowerExpression(node.condition)}), ${thenThunk}, ${elseThunk});\n`
|
|
508
434
|
}
|
|
509
435
|
|
|
510
|
-
/* A keyed each.
|
|
436
|
+
/* A keyed each. Each row is a content RANGE (any content, tracked between the
|
|
437
|
+
row's markers), built by a `(rowParent, item) => void` thunk. */
|
|
511
438
|
function generateEach(
|
|
512
439
|
node: Extract<TemplateNode, { kind: 'each' }>,
|
|
513
440
|
parentVar: string,
|
|
514
441
|
): string {
|
|
515
442
|
const rowParam = nextVar('p')
|
|
516
|
-
/*
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const
|
|
520
|
-
.
|
|
521
|
-
(child): child is Extract<TemplateNode, { kind: 'script' }> =>
|
|
522
|
-
child.kind === 'script',
|
|
523
|
-
)
|
|
524
|
-
.map((child) => `${lowerStatement(child.code)}\n`)
|
|
525
|
-
.join('')
|
|
526
|
-
const row = singleElementRoot(
|
|
527
|
-
node.children,
|
|
528
|
-
'<template each> must contain a single element row',
|
|
529
|
-
rowParam,
|
|
443
|
+
/* The row body builds its children (a `<script>` declares per-row local signals,
|
|
444
|
+
emitted in document order) into the row parent. A `<template catch>` child is
|
|
445
|
+
consumed by the async-each, not the row — `generateChildren` skips it. */
|
|
446
|
+
const rowBody = withNestedScripts(node.children, () =>
|
|
447
|
+
generateChildren(node.children, rowParam),
|
|
530
448
|
)
|
|
531
|
-
for (const name of added) {
|
|
532
|
-
localDerived.delete(name)
|
|
533
|
-
}
|
|
534
449
|
const keyExpression = node.key === undefined ? node.as : lowerExpression(node.key)
|
|
535
450
|
/* `await` → the AsyncIterable runtime, drained row-by-row on the client, with an
|
|
536
451
|
optional `<template catch>` branch rendered (after the streamed rows) when the
|
|
@@ -540,30 +455,14 @@ export function generateBuild(
|
|
|
540
455
|
(child) => child.kind === 'branch' && child.branch === 'catch',
|
|
541
456
|
)
|
|
542
457
|
const catchArg = node.async
|
|
543
|
-
? `, ${catchBranch === undefined ? 'undefined' :
|
|
458
|
+
? `, ${catchBranch === undefined ? 'undefined' : branchThunk(branchChildren(catchBranch), branchVar(catchBranch) ?? '_error')}`
|
|
544
459
|
: ''
|
|
545
460
|
return (
|
|
546
461
|
`${fn}(${parentVar}, () => (${lowerExpression(node.items)}), ` +
|
|
547
|
-
`(${node.as}) => (${keyExpression}), (${rowParam}, ${node.as}) => {\n${
|
|
462
|
+
`(${node.as}) => (${keyExpression}), (${rowParam}, ${node.as}) => {\n${rowBody}}${catchArg});\n`
|
|
548
463
|
)
|
|
549
464
|
}
|
|
550
465
|
|
|
551
|
-
/* Builds the lone element child of a control-flow block (each/if return one
|
|
552
|
-
node), erroring if the block isn't a single element. The root is opened
|
|
553
|
-
with `openRoot(parentVar, tag)` so it's detached on create and claimed on
|
|
554
|
-
hydrate — `parentVar` is the render thunk's parent parameter. */
|
|
555
|
-
function singleElementRoot(
|
|
556
|
-
children: TemplateNode[],
|
|
557
|
-
message: string,
|
|
558
|
-
parentVar: string,
|
|
559
|
-
): { code: string; varName: string } {
|
|
560
|
-
const root = children.find((child) => child.kind === 'element')
|
|
561
|
-
if (root === undefined || root.kind !== 'element') {
|
|
562
|
-
throw new Error(`[abide] ${message}`)
|
|
563
|
-
}
|
|
564
|
-
return generateElement(root, `openRoot(${parentVar}, ${JSON.stringify(root.tag)})`)
|
|
565
|
-
}
|
|
566
|
-
|
|
567
466
|
return generateChildren(nodes, hostVar)
|
|
568
467
|
}
|
|
569
468
|
|
|
@@ -613,9 +512,7 @@ template and the server markup parse to the same DOM. Only handles the shapes
|
|
|
613
512
|
function staticHtml(node: TemplateNode): string {
|
|
614
513
|
if (node.kind === 'text') {
|
|
615
514
|
return node.parts
|
|
616
|
-
.map((part) =>
|
|
617
|
-
part.kind === 'static' && part.value.trim() !== '' ? escapeHtml(part.value) : '',
|
|
618
|
-
)
|
|
515
|
+
.map((part) => (part.kind === 'static' ? staticTextPart(part.value) : ''))
|
|
619
516
|
.join('')
|
|
620
517
|
}
|
|
621
518
|
if (node.kind !== 'element') {
|
|
@@ -623,11 +520,11 @@ function staticHtml(node: TemplateNode): string {
|
|
|
623
520
|
}
|
|
624
521
|
let html = `<${node.tag}`
|
|
625
522
|
for (const scope of node.scopes ?? []) {
|
|
626
|
-
html +=
|
|
523
|
+
html += scopeAttr(scope)
|
|
627
524
|
}
|
|
628
525
|
for (const attr of node.attrs) {
|
|
629
526
|
if (attr.kind === 'static') {
|
|
630
|
-
html +=
|
|
527
|
+
html += staticAttr(attr.name, attr.value)
|
|
631
528
|
}
|
|
632
529
|
}
|
|
633
530
|
html += '>'
|