@jxsuite/compiler 5.1.3 → 6.0.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.
- package/dist/cli.js +194 -191
- package/dist/compiler.js +97 -94
- package/package.json +8 -7
- package/src/shared.ts +344 -34
- package/src/site/project-sections.ts +4 -0
- package/src/site/site-build.ts +43 -18
- package/src/targets/compile-client.ts +15 -5
- package/src/targets/compile-element.ts +72 -60
package/src/site/site-build.ts
CHANGED
|
@@ -69,7 +69,7 @@ import type { AssetMount } from "@jxsuite/schema/asset-paths";
|
|
|
69
69
|
import type { FormatEntry, FormatRegistry } from "@jxsuite/schema/format-registry";
|
|
70
70
|
import type { ExtensionRegistry } from "@jxsuite/schema/extension-registry";
|
|
71
71
|
import { resolveLayout } from "./layout-resolver.ts";
|
|
72
|
-
import { mergeHead, renderHead } from "@jxsuite/site/head-merger";
|
|
72
|
+
import { mergeHead, renderHead, withPageDescription } from "@jxsuite/site/head-merger";
|
|
73
73
|
import { unregisteredHeadRelations } from "./link-relations.ts";
|
|
74
74
|
import { injectContext } from "./context-injection.ts";
|
|
75
75
|
import { compile, compileServer, compileSiteServer } from "../compiler.ts";
|
|
@@ -84,18 +84,22 @@ import {
|
|
|
84
84
|
collectServerEntries,
|
|
85
85
|
collectStyles,
|
|
86
86
|
colorSchemePrePaintScript,
|
|
87
|
+
createSlotFill,
|
|
88
|
+
definitionHasSlot,
|
|
87
89
|
escapeStyleText,
|
|
88
90
|
evaluateStaticTemplate,
|
|
89
91
|
isComponentFullyStatic,
|
|
90
92
|
isSingleExpression,
|
|
91
93
|
liftPropsAttributes,
|
|
94
|
+
nextSlotId,
|
|
92
95
|
preRenderComponentHtml,
|
|
93
96
|
pureSchemeOf,
|
|
94
97
|
renderStaticNode,
|
|
95
98
|
resolveHostStyle,
|
|
96
99
|
resolveRefValue,
|
|
97
100
|
} from "../shared.ts";
|
|
98
|
-
import type { ComponentPrerenderContext } from "../shared.ts";
|
|
101
|
+
import type { ComponentPrerenderContext, StaticChild } from "../shared.ts";
|
|
102
|
+
import { SLOT_ATTR } from "@jxsuite/runtime";
|
|
99
103
|
import { resolvePrototypes } from "./prototype-resolver.ts";
|
|
100
104
|
import { transformImageNodes } from "./image-transform.ts";
|
|
101
105
|
import { collectCspSources, emptyCspSources } from "./csp.ts";
|
|
@@ -1343,7 +1347,10 @@ async function compilePage(
|
|
|
1343
1347
|
// Extract head arrays before they get lost in the merge
|
|
1344
1348
|
/* SAFETY: `_pageHead` is written only by `resolveLayout`, which copies the page's `$head` (a
|
|
1345
1349
|
JxHeadEntry[]) into it when the page has one. */
|
|
1346
|
-
const pageHead = (
|
|
1350
|
+
const pageHead = withPageDescription(
|
|
1351
|
+
(pageDoc.$head ?? layoutDoc._pageHead ?? []) as JxHeadEntry[],
|
|
1352
|
+
pageDoc.description,
|
|
1353
|
+
);
|
|
1347
1354
|
const layoutHead = layoutDoc.$head ?? [];
|
|
1348
1355
|
/* SAFETY: `_pageTitle` is written only by `resolveLayout`, which copies the page's `title` (a
|
|
1349
1356
|
string) into it when the page has one. */
|
|
@@ -1423,7 +1430,9 @@ async function compilePage(
|
|
|
1423
1430
|
media: { ...projectConfig.$media, ...layoutDoc.$media },
|
|
1424
1431
|
rules: [],
|
|
1425
1432
|
};
|
|
1426
|
-
|
|
1433
|
+
// One counter per page, so every `data-jx-slots` id on it is unique (spec.md §8.5).
|
|
1434
|
+
const slotIds = { n: 0 };
|
|
1435
|
+
expandComponents(layoutDoc, componentDefs, slotCss, projectConfig.defaults, slotIds);
|
|
1427
1436
|
|
|
1428
1437
|
// Strip resolved timing: "compiler" state entries — they're now baked into the tree
|
|
1429
1438
|
// And keeping them would cause isDynamic() to misclassify the page as dynamic.
|
|
@@ -2208,19 +2217,21 @@ interface SlotCssCollector {
|
|
|
2208
2217
|
* @param {Map<string, JxElement>} componentDefs
|
|
2209
2218
|
* @param {SlotCssCollector} [slotCss]
|
|
2210
2219
|
* @param {ProjectConfig["defaults"]} [defaults] - Read for `defaults.shadow` (spec.md §16.6)
|
|
2220
|
+
* @param {{ n: number }} [slotIds] - The page's `data-jx-slots` counter
|
|
2211
2221
|
*/
|
|
2212
2222
|
function expandComponents(
|
|
2213
2223
|
node: JxElement | string | (JxElement | string)[],
|
|
2214
2224
|
componentDefs: Map<string, JxElement>,
|
|
2215
2225
|
slotCss?: SlotCssCollector,
|
|
2216
2226
|
defaults?: ProjectConfig["defaults"],
|
|
2227
|
+
slotIds?: { n: number },
|
|
2217
2228
|
) {
|
|
2218
2229
|
if (!isNonNullObject(node)) {
|
|
2219
2230
|
return;
|
|
2220
2231
|
}
|
|
2221
2232
|
if (Array.isArray(node)) {
|
|
2222
2233
|
for (const n of node) {
|
|
2223
|
-
expandComponents(n, componentDefs, slotCss, defaults);
|
|
2234
|
+
expandComponents(n, componentDefs, slotCss, defaults, slotIds);
|
|
2224
2235
|
}
|
|
2225
2236
|
return;
|
|
2226
2237
|
}
|
|
@@ -2228,7 +2239,7 @@ function expandComponents(
|
|
|
2228
2239
|
// Recurse into children first (bottom-up expansion)
|
|
2229
2240
|
if (Array.isArray(node.children)) {
|
|
2230
2241
|
for (const child of node.children) {
|
|
2231
|
-
expandComponents(child, componentDefs, slotCss, defaults);
|
|
2242
|
+
expandComponents(child, componentDefs, slotCss, defaults, slotIds);
|
|
2232
2243
|
}
|
|
2233
2244
|
}
|
|
2234
2245
|
|
|
@@ -2256,17 +2267,12 @@ function expandComponents(
|
|
|
2256
2267
|
* was given. Instances written inside `def` itself are another matter — those are reached
|
|
2257
2268
|
* below, through `preRenderComponentHtml`, with `node` as the first frame of the path.
|
|
2258
2269
|
*/
|
|
2259
|
-
const
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
}
|
|
2266
|
-
return renderStaticNode(c, {}, null);
|
|
2267
|
-
})
|
|
2268
|
-
.join("\n")
|
|
2269
|
-
: null;
|
|
2270
|
+
const renderSlotted = (c: StaticChild) => {
|
|
2271
|
+
if (slotCss && isNonNullObject(c) && !Array.isArray(c)) {
|
|
2272
|
+
collectStyles(c, slotCss.rules, slotCss.media, "", slotCss.counter, "jxs");
|
|
2273
|
+
}
|
|
2274
|
+
return renderStaticNode(c, {}, null);
|
|
2275
|
+
};
|
|
2270
2276
|
|
|
2271
2277
|
/*
|
|
2272
2278
|
* In shadow mode the slotted children are NOT substituted into the prerendered markup: they
|
|
@@ -2282,9 +2288,28 @@ function expandComponents(
|
|
|
2282
2288
|
componentDefs,
|
|
2283
2289
|
defaults,
|
|
2284
2290
|
path: [{ key: JSON.stringify(props ?? {}), tag: tagName }],
|
|
2291
|
+
slotIds,
|
|
2285
2292
|
};
|
|
2286
|
-
const innerHTML = preRenderComponentHtml(def, props, shadow ? null : slotContent, context);
|
|
2287
2293
|
const isStatic = isComponentFullyStatic(def);
|
|
2294
|
+
const { children } = node;
|
|
2295
|
+
/* Light mode places the children by slot name (spec.md §8.5). A live instance whose definition
|
|
2296
|
+
has a slot is stamped and its groups bracketed, so its element recovers them on upgrade. */
|
|
2297
|
+
const slotId =
|
|
2298
|
+
!shadow && !isStatic && definitionHasSlot(def.children) ? nextSlotId(context) : null;
|
|
2299
|
+
const slotContent = shadow
|
|
2300
|
+
? (Array.isArray(children) ? children : [])
|
|
2301
|
+
.map((c: StaticChild) => renderSlotted(c))
|
|
2302
|
+
.join("\n")
|
|
2303
|
+
: null;
|
|
2304
|
+
const innerHTML = preRenderComponentHtml(
|
|
2305
|
+
def,
|
|
2306
|
+
props,
|
|
2307
|
+
shadow ? null : createSlotFill(children, {}, renderSlotted, slotId),
|
|
2308
|
+
context,
|
|
2309
|
+
);
|
|
2310
|
+
if (slotId !== null) {
|
|
2311
|
+
node.attributes = { ...node.attributes, [SLOT_ATTR]: String(slotId) };
|
|
2312
|
+
}
|
|
2288
2313
|
|
|
2289
2314
|
/*
|
|
2290
2315
|
* A declarative shadow root: markup the parser materializes before any script runs, which the
|
|
@@ -32,10 +32,12 @@ import {
|
|
|
32
32
|
isRefObject,
|
|
33
33
|
isSchemaOnly,
|
|
34
34
|
isTemplateString,
|
|
35
|
+
LIT_PART_GUARD,
|
|
35
36
|
PREFORMATTED_TAGS,
|
|
36
37
|
pureSchemeOf,
|
|
37
38
|
resolveStaticValue,
|
|
38
39
|
srcImportBinding,
|
|
40
|
+
templateValueSource,
|
|
39
41
|
} from "../shared.ts";
|
|
40
42
|
import {
|
|
41
43
|
bodyReturnsValue,
|
|
@@ -141,7 +143,7 @@ export function compileClient(
|
|
|
141
143
|
// Naked primitive or array → reactive state
|
|
142
144
|
if (isTemplateString(def)) {
|
|
143
145
|
// Template string → computed on state so other computeds can ref it
|
|
144
|
-
computedEntries.push([key, `() =>
|
|
146
|
+
computedEntries.push([key, `() => ${templateValueSource(def)}`]);
|
|
145
147
|
} else {
|
|
146
148
|
stateEntries.push([key, def]);
|
|
147
149
|
}
|
|
@@ -776,15 +778,19 @@ function emitLitMapTemplate(def: JxMutableNode | null | undefined, preformatted
|
|
|
776
778
|
attrs += ` contenteditable="${def.contentEditable}"`;
|
|
777
779
|
}
|
|
778
780
|
|
|
781
|
+
/* A row can hold a component instance, whose children its slots redistribute; a child part
|
|
782
|
+
there ends at a guard rather than at a sibling that may move (`LIT_PART_GUARD`). */
|
|
783
|
+
const guard = tag.includes("-") ? LIT_PART_GUARD : "";
|
|
784
|
+
|
|
779
785
|
// Inner content
|
|
780
786
|
let inner = "";
|
|
781
787
|
if (def.textContent !== undefined) {
|
|
782
788
|
const tc = String(def.textContent);
|
|
783
789
|
if (isTemplateString(tc)) {
|
|
784
|
-
inner = mapRefsToLit(tc)
|
|
790
|
+
inner = `${mapRefsToLit(tc)}${guard}`;
|
|
785
791
|
} else if (isRefObject(def.textContent)) {
|
|
786
792
|
const path = refToBindingKey(def.textContent.$ref);
|
|
787
|
-
inner = `\${state.${path}}`;
|
|
793
|
+
inner = `\${state.${path}}${guard}`;
|
|
788
794
|
} else {
|
|
789
795
|
inner = escapeHtml(tc);
|
|
790
796
|
}
|
|
@@ -792,9 +798,13 @@ function emitLitMapTemplate(def: JxMutableNode | null | undefined, preformatted
|
|
|
792
798
|
inner = mapRefsToLit(String(def.innerHTML));
|
|
793
799
|
} else if (isMappedArray(def.children)) {
|
|
794
800
|
// Legacy whole-children repeater nested inside a map template.
|
|
795
|
-
inner = `\n ${emitArrayHole(def.children)}\n `;
|
|
801
|
+
inner = `\n ${emitArrayHole(def.children)}${guard}\n `;
|
|
796
802
|
} else if (Array.isArray(def.children)) {
|
|
797
|
-
const rendered = def.children.map((c) =>
|
|
803
|
+
const rendered = def.children.map((c) => {
|
|
804
|
+
const text = emitChildLit(c, inPre);
|
|
805
|
+
const isPart = isMappedArray(c) || (isString(c) && isTemplateString(c));
|
|
806
|
+
return isPart ? `${text}${guard}` : text;
|
|
807
|
+
});
|
|
798
808
|
inner = inPre ? rendered.join("") : `\n ${rendered.join("\n ")}\n `;
|
|
799
809
|
}
|
|
800
810
|
|
|
@@ -10,7 +10,12 @@ import {
|
|
|
10
10
|
ATTR_HELPER,
|
|
11
11
|
attrHelperSource,
|
|
12
12
|
booleanAttrValue,
|
|
13
|
+
definitionHasSlot,
|
|
14
|
+
LIT_PART_GUARD,
|
|
13
15
|
PREFORMATTED_TAGS,
|
|
16
|
+
SLOT_CAPTURE_HELPER,
|
|
17
|
+
SLOT_DISTRIBUTE_HELPER,
|
|
18
|
+
slotHelperSource,
|
|
14
19
|
collectStyles,
|
|
15
20
|
compileExpression,
|
|
16
21
|
compileOperandSource,
|
|
@@ -23,6 +28,7 @@ import {
|
|
|
23
28
|
srcExportName,
|
|
24
29
|
srcImportBinding,
|
|
25
30
|
tagNameToClassName,
|
|
31
|
+
templateValueSource,
|
|
26
32
|
} from "../shared.ts";
|
|
27
33
|
import {
|
|
28
34
|
bodyReturnsValue,
|
|
@@ -436,6 +442,13 @@ export function emitElementModule(
|
|
|
436
442
|
`import { render, html, nothing } from 'lit-html';`,
|
|
437
443
|
attrHelperSource(),
|
|
438
444
|
);
|
|
445
|
+
/* Light-DOM slot distribution, by the rule the interpreter applies (spec.md §8.5): only a module
|
|
446
|
+
whose template draws a slot carries it, and a shadow component never does — its light children
|
|
447
|
+
ARE the slotted content, and a real `<slot>` distributes them. */
|
|
448
|
+
const hasSlot = shadow === null && definitionHasSlot(doc.children);
|
|
449
|
+
if (hasSlot) {
|
|
450
|
+
lines.push(slotHelperSource());
|
|
451
|
+
}
|
|
439
452
|
|
|
440
453
|
/*
|
|
441
454
|
* `$lazy` swaps the static import for a memoized dynamic one. A static import is a download, a
|
|
@@ -620,13 +633,14 @@ export function emitElementModule(
|
|
|
620
633
|
);
|
|
621
634
|
}
|
|
622
635
|
|
|
623
|
-
/* Emitted with the same `state.` → `this.state.` rewrite the inline-body path below uses
|
|
624
|
-
|
|
625
|
-
|
|
636
|
+
/* Emitted with the same `state.` → `this.state.` rewrite the inline-body path below uses. A
|
|
637
|
+
single expression keeps its own type and anything else is a template literal, exactly as the
|
|
638
|
+
runtime and the build-time scope evaluate it (spec.md §5.3), so a component reads the same
|
|
639
|
+
value hydrated as it did prerendered. */
|
|
626
640
|
for (const [key, template] of templateEntries) {
|
|
627
641
|
lines.push(
|
|
628
642
|
"",
|
|
629
|
-
` ${refAccessor("this.state", escapeToken(key))} = computed(() =>
|
|
643
|
+
` ${refAccessor("this.state", escapeToken(key))} = computed(() => ${templateValueSource(template.replaceAll("state.", "this.state."))});`,
|
|
630
644
|
);
|
|
631
645
|
}
|
|
632
646
|
|
|
@@ -795,19 +809,17 @@ export function emitElementModule(
|
|
|
795
809
|
lines.push(" }));");
|
|
796
810
|
}
|
|
797
811
|
}
|
|
798
|
-
const hasSlot = treeHasSlot(doc.children);
|
|
799
812
|
/*
|
|
800
|
-
* The slot emulation is a light-DOM construction:
|
|
801
|
-
* then
|
|
813
|
+
* The slot emulation is a light-DOM construction: capture the host's children, render over them,
|
|
814
|
+
* then replace each literal `<slot>` with its group (spec.md §8.5). Inside a shadow root that
|
|
802
815
|
* would be actively wrong — the light children ARE the slotted content and must stay in the
|
|
803
816
|
* light tree, where a real `<slot>` distributes them. This is the single largest behavioural
|
|
804
|
-
* difference between the two modes, and the reason shadow cannot be the default.
|
|
817
|
+
* difference between the two modes, and the reason shadow cannot be the default. Captured before
|
|
818
|
+
* `data-jx-prerendered` goes and before the clear: a prerendered instance's children are its
|
|
819
|
+
* markup, and capture recovers what the prerender placed from its brackets instead.
|
|
805
820
|
*/
|
|
806
|
-
if (hasSlot
|
|
807
|
-
|
|
808
|
-
lines.push(
|
|
809
|
-
" const _slotted = Array.from(this.childNodes).filter(n => n.nodeType === 1 || (n.nodeType === 3 && n.textContent.trim()));",
|
|
810
|
-
);
|
|
821
|
+
if (hasSlot) {
|
|
822
|
+
lines.push(` const _slotted = ${SLOT_CAPTURE_HELPER}(this);`);
|
|
811
823
|
}
|
|
812
824
|
lines.push(
|
|
813
825
|
" if (this.hasAttribute('data-jx-prerendered')) {",
|
|
@@ -848,15 +860,9 @@ export function emitElementModule(
|
|
|
848
860
|
" this.#effects.push(effect(() => render(this.template(), _root)));",
|
|
849
861
|
);
|
|
850
862
|
}
|
|
851
|
-
if (hasSlot
|
|
852
|
-
//
|
|
853
|
-
lines.push(
|
|
854
|
-
" const _slot = this.querySelector('slot');",
|
|
855
|
-
" if (_slot && _slotted.length > 0) {",
|
|
856
|
-
" for (const n of _slotted) _slot.before(n);",
|
|
857
|
-
" _slot.remove();",
|
|
858
|
-
" }",
|
|
859
|
-
);
|
|
863
|
+
if (hasSlot) {
|
|
864
|
+
// Once, after the first render: a slot a later render draws stays literal (spec.md §8.5).
|
|
865
|
+
lines.push(` ${SLOT_DISTRIBUTE_HELPER}(this, _slotted);`);
|
|
860
866
|
}
|
|
861
867
|
lines.push(
|
|
862
868
|
// Lifecycle: onMount (spec §16.4) — same microtask contract as the runtime
|
|
@@ -936,27 +942,39 @@ function emitLitChildren(
|
|
|
936
942
|
preformatted = false,
|
|
937
943
|
inMap = false,
|
|
938
944
|
scope: HoistScope | null = null,
|
|
945
|
+
/** The children are an instance's, so each child part ends at a guard rather than a sibling. */
|
|
946
|
+
guardParts = false,
|
|
939
947
|
) {
|
|
940
948
|
if (!children) {
|
|
941
949
|
return "";
|
|
942
950
|
}
|
|
943
951
|
|
|
952
|
+
const guard = guardParts ? LIT_PART_GUARD : "";
|
|
953
|
+
|
|
944
954
|
// Legacy whole-children repeater: the array IS the children slot.
|
|
945
955
|
if (isMappedArray(children)) {
|
|
946
|
-
return emitMappedArray(children, indent)
|
|
956
|
+
return `${emitMappedArray(children, indent)}${guard}`;
|
|
947
957
|
}
|
|
948
958
|
|
|
949
959
|
if (!Array.isArray(children)) {
|
|
950
960
|
return "";
|
|
951
961
|
}
|
|
952
962
|
|
|
953
|
-
|
|
963
|
+
/* Mixed children: elements/text plus array pseudo-elements expanded inline among siblings. Among
|
|
964
|
+
an instance's children, a child that emits a lit child part — a binding, a repeater, a chosen
|
|
965
|
+
tag — is followed by a guard: distribution moves its neighbours, and a part that ended at one
|
|
966
|
+
would clear its new parent on the next update (`LIT_PART_GUARD`). */
|
|
954
967
|
return children
|
|
955
|
-
.map((child) =>
|
|
956
|
-
isMappedArray(child)
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
968
|
+
.map((child) => {
|
|
969
|
+
if (isMappedArray(child)) {
|
|
970
|
+
return `${emitMappedArray(child, indent)}${guard}`;
|
|
971
|
+
}
|
|
972
|
+
const text = emitLitNode(child, preformatted ? "" : indent, preformatted, inMap, scope);
|
|
973
|
+
const isPart =
|
|
974
|
+
(isString(child) && child.includes("${")) ||
|
|
975
|
+
(isNonNullObject(child) && isTagExpression(child.tagName));
|
|
976
|
+
return isPart ? `${text}${guard}` : text;
|
|
977
|
+
})
|
|
960
978
|
.join(preformatted ? "" : "\n");
|
|
961
979
|
}
|
|
962
980
|
|
|
@@ -1020,7 +1038,14 @@ function emitLitNode(
|
|
|
1020
1038
|
const sharedPre = candidateTags.map((t) => preformatted || PREFORMATTED_TAGS.has(t));
|
|
1021
1039
|
let innerRef: string | null = null;
|
|
1022
1040
|
if (scope && sharedPre.every((p) => p === sharedPre[0])) {
|
|
1023
|
-
const sharedInner = emitNodeInner(
|
|
1041
|
+
const sharedInner = emitNodeInner(
|
|
1042
|
+
def,
|
|
1043
|
+
`${indent} `,
|
|
1044
|
+
sharedPre[0] === true,
|
|
1045
|
+
inMap,
|
|
1046
|
+
scope,
|
|
1047
|
+
candidateTags.some((t) => t.includes("-")),
|
|
1048
|
+
);
|
|
1024
1049
|
if (sharedInner.trim() !== "") {
|
|
1025
1050
|
innerRef = hoistSubtree(scope, sharedInner);
|
|
1026
1051
|
}
|
|
@@ -1180,12 +1205,18 @@ ${no}
|
|
|
1180
1205
|
return `${indent}<${tag}${attrsStr}\n${indent}>`;
|
|
1181
1206
|
}
|
|
1182
1207
|
|
|
1183
|
-
|
|
1208
|
+
// A custom element's children are distributed into its slots, so their parts need guards.
|
|
1209
|
+
const inner = innerOverride ?? emitNodeInner(def, indent, inPre, inMap, scope, tag.includes("-"));
|
|
1210
|
+
|
|
1211
|
+
/* A `<slot>` is replaced after the first render, so no part may end at it or at its last child:
|
|
1212
|
+
one guard before the slot (where an adjacent part ends) and one before `</slot>` (where a last
|
|
1213
|
+
fallback binding ends, and which travels with the fallback when it is shown). */
|
|
1214
|
+
const guard = tag === "slot" ? LIT_PART_GUARD : "";
|
|
1184
1215
|
|
|
1185
1216
|
// Inside a <pre>, the newline before `>` would land in the element's own text.
|
|
1186
1217
|
return inPre
|
|
1187
|
-
?
|
|
1188
|
-
: `${indent}<${tag}${attrsStr}\n${indent}>${inner}</${tag}>`;
|
|
1218
|
+
? `${guard}<${tag}${attrsStr}>${inner}${guard}</${tag}>`
|
|
1219
|
+
: `${indent}${guard}<${tag}${attrsStr}\n${indent}>${inner}${guard}</${tag}>`;
|
|
1189
1220
|
}
|
|
1190
1221
|
|
|
1191
1222
|
/**
|
|
@@ -1207,6 +1238,8 @@ function emitNodeInner(
|
|
|
1207
1238
|
inPre: boolean,
|
|
1208
1239
|
inMap: boolean,
|
|
1209
1240
|
scope: HoistScope | null,
|
|
1241
|
+
/** `def` is a custom element, whose children are distributed: see `emitLitChildren`. */
|
|
1242
|
+
guardParts = false,
|
|
1210
1243
|
): string {
|
|
1211
1244
|
if (def.$switch) {
|
|
1212
1245
|
const switchRef = isRef(def.$switch) ? def.$switch.$ref : def.$switch;
|
|
@@ -1232,15 +1265,18 @@ function emitNodeInner(
|
|
|
1232
1265
|
return `\${{\n${caseEntries.join(",\n")}\n}[${switchExpr}]}`;
|
|
1233
1266
|
}
|
|
1234
1267
|
if (def.textContent !== undefined) {
|
|
1235
|
-
|
|
1268
|
+
const text = toLitTextContent(def.textContent);
|
|
1269
|
+
const bound =
|
|
1270
|
+
isRef(def.textContent) || (isString(def.textContent) && def.textContent.includes("${"));
|
|
1271
|
+
return guardParts && bound ? `${text}${LIT_PART_GUARD}` : text;
|
|
1236
1272
|
}
|
|
1237
1273
|
if (def.innerHTML !== undefined) {
|
|
1238
1274
|
return def.innerHTML;
|
|
1239
1275
|
}
|
|
1240
1276
|
if (def.children) {
|
|
1241
1277
|
return inPre
|
|
1242
|
-
? emitLitChildren(def.children, def.style, "", true, inMap, scope)
|
|
1243
|
-
: `\n${emitLitChildren(def.children, def.style, `${indent} `, false, inMap, scope)}\n${indent}`;
|
|
1278
|
+
? emitLitChildren(def.children, def.style, "", true, inMap, scope, guardParts)
|
|
1279
|
+
: `\n${emitLitChildren(def.children, def.style, `${indent} `, false, inMap, scope, guardParts)}\n${indent}`;
|
|
1244
1280
|
}
|
|
1245
1281
|
return "";
|
|
1246
1282
|
}
|
|
@@ -1538,27 +1574,3 @@ function emitStyleString(styleDef: JxStyle | null | undefined) {
|
|
|
1538
1574
|
|
|
1539
1575
|
return parts.join("; ");
|
|
1540
1576
|
}
|
|
1541
|
-
|
|
1542
|
-
/**
|
|
1543
|
-
* Check if a children tree contains a `<slot>` element.
|
|
1544
|
-
*
|
|
1545
|
-
* @param {JxMutableNode["children"]} children
|
|
1546
|
-
* @returns {boolean}
|
|
1547
|
-
*/
|
|
1548
|
-
function treeHasSlot(children: JxMutableNode["children"]): boolean {
|
|
1549
|
-
if (!Array.isArray(children)) {
|
|
1550
|
-
return false;
|
|
1551
|
-
}
|
|
1552
|
-
for (const child of children) {
|
|
1553
|
-
if (!isNonNullObject(child)) {
|
|
1554
|
-
continue;
|
|
1555
|
-
}
|
|
1556
|
-
if (child.tagName === "slot") {
|
|
1557
|
-
return true;
|
|
1558
|
-
}
|
|
1559
|
-
if (treeHasSlot(child.children)) {
|
|
1560
|
-
return true;
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
return false;
|
|
1564
|
-
}
|