@jxsuite/runtime 1.3.1 → 2.0.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/dist/runtime.js +235 -68
- package/dist/runtime.js.map +11 -8
- package/package.json +5 -4
- package/src/cookie.ts +104 -0
- package/src/expression.ts +146 -43
- package/src/pointer.ts +130 -0
- package/src/runtime.ts +160 -47
package/src/runtime.ts
CHANGED
|
@@ -23,7 +23,8 @@ import {
|
|
|
23
23
|
ref,
|
|
24
24
|
toRaw,
|
|
25
25
|
} from "@vue/reactivity";
|
|
26
|
-
import { evaluateExpression, isMutating } from "./expression.ts";
|
|
26
|
+
import { evaluateExpression, evaluateOperand, isMutating } from "./expression.ts";
|
|
27
|
+
import { readPath } from "./pointer.ts";
|
|
27
28
|
import type { DynamicClass, JxEventHandler, JxPath, JxRenderOptions, JxScope } from "./types.ts";
|
|
28
29
|
import {
|
|
29
30
|
bodyReturnsValue,
|
|
@@ -33,13 +34,16 @@ import {
|
|
|
33
34
|
isJsonObject,
|
|
34
35
|
isMappedArray,
|
|
35
36
|
isNamedFormulaDef,
|
|
37
|
+
isPrivateStateKey,
|
|
36
38
|
isPrototypeDef,
|
|
37
39
|
isRef as isRefValue,
|
|
38
40
|
isServerFnDef,
|
|
41
|
+
isTagExpression,
|
|
39
42
|
isTemplateString,
|
|
40
43
|
paramNames,
|
|
41
44
|
} from "@jxsuite/schema/guards";
|
|
42
45
|
import { runStatements } from "./statements.ts";
|
|
46
|
+
import { readCookie, serializeCookie } from "./cookie.ts";
|
|
43
47
|
import type {
|
|
44
48
|
JxAttributeValue,
|
|
45
49
|
JxClassDef,
|
|
@@ -170,6 +174,25 @@ export function setSkipServerFunctions(v: boolean) {
|
|
|
170
174
|
_serverFnConfig.skip = v;
|
|
171
175
|
}
|
|
172
176
|
|
|
177
|
+
/** @type {{ skip: boolean }} */
|
|
178
|
+
const _autoRequestConfig = { skip: false };
|
|
179
|
+
/**
|
|
180
|
+
* Set to true to suppress AUTOMATIC `$prototype: "Request"` fetches — the sibling of
|
|
181
|
+
* {@link setSkipServerFunctions}, and used by Studio for the same reason.
|
|
182
|
+
*
|
|
183
|
+
* `buildScope` re-resolves every `state` entry on each full canvas render, so an auto-request
|
|
184
|
+
* re-issued its HTTP call every time the canvas rebuilt. In edit/design mode that meant authoring
|
|
185
|
+
* actions — a signal edit, or pressing Enter inside component-wrapped content, both of which force
|
|
186
|
+
* a full render — fired network requests. Live data belongs to preview and to the deployed site.
|
|
187
|
+
*
|
|
188
|
+
* Only non-`manual` requests are gated: a `manual` one fires from an event handler, and edit mode
|
|
189
|
+
* strips handlers before rendering. The returned ref is unchanged either way — it simply stays at
|
|
190
|
+
* its initial `null`, which is the same state bindings observe before any fetch resolves.
|
|
191
|
+
*/
|
|
192
|
+
export function setSkipAutoRequests(v: boolean) {
|
|
193
|
+
_autoRequestConfig.skip = v;
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
// ─── Dev-proxy auth token (Studio cross-origin canvas) ────────────────────────
|
|
174
197
|
// The Studio canvas iframe is served from a token-gated loopback origin (createProjectServer): its
|
|
175
198
|
// POST /__jx_resolve__ + /__jx_server__ routes 403 without ?token=<rpcToken>. The iframe boot reads
|
|
@@ -764,8 +787,18 @@ export function renderNode(
|
|
|
764
787
|
}
|
|
765
788
|
}
|
|
766
789
|
|
|
767
|
-
|
|
768
|
-
|
|
790
|
+
/* RESOLVED ONCE, HERE, before anything branches on it.
|
|
791
|
+
A tag may be a name or a choice between names (`ElementTagName`). Resolving at the top means
|
|
792
|
+
every test below — the hyphen check that routes to the custom-element path, `customElements.get`,
|
|
793
|
+
`createElement` itself — sees a literal, exactly as it did when a tag could only be written out.
|
|
794
|
+
|
|
795
|
+
It is not tracked. `tagName` is in RESERVED_KEYS so no binding sweep reaches it, and a tag that
|
|
796
|
+
changed after mount would mean replacing the node: the subtree's listeners, its focus, its typed
|
|
797
|
+
input values and its component instances all go with it, and this runtime has no dispose walk to
|
|
798
|
+
pay that bill (renderSwitch, two hundred lines up, still leaks its previous case's effects).
|
|
799
|
+
`jx validate` warns when a tag discriminant is also an assignment target, so the case where this
|
|
800
|
+
rule bites is caught before it ships rather than found as a `<div>` that never became an `<a>`. */
|
|
801
|
+
const tagName = resolveTagName(def.tagName, localState);
|
|
769
802
|
const isCustomEl = tagName.includes("-") && customElements.get(tagName);
|
|
770
803
|
|
|
771
804
|
if (def.$props && isCustomEl) {
|
|
@@ -1230,6 +1263,34 @@ function renderMappedArrayInto(
|
|
|
1230
1263
|
});
|
|
1231
1264
|
}
|
|
1232
1265
|
|
|
1266
|
+
/**
|
|
1267
|
+
* An element's tag: a name, or the branch of a {@link JxTagExpression} the state selects.
|
|
1268
|
+
*
|
|
1269
|
+
* Every branch of the expression is itself a `TagName`, so this can only ever return something the
|
|
1270
|
+
* schema already held to the tag-name pattern — the property that lets the compiler enumerate the
|
|
1271
|
+
* candidates and lets `createElement` be called without a second thought here.
|
|
1272
|
+
*
|
|
1273
|
+
* @param {unknown} tagName The element's declared tag.
|
|
1274
|
+
* @param {JxScope} scope The state the discriminant reads.
|
|
1275
|
+
* @returns {string} A literal tag name; `"div"` when nothing is declared.
|
|
1276
|
+
*/
|
|
1277
|
+
export function resolveTagName(tagName: unknown, scope: JxScope): string {
|
|
1278
|
+
if (typeof tagName === "string") {
|
|
1279
|
+
return tagName;
|
|
1280
|
+
}
|
|
1281
|
+
if (!isTagExpression(tagName)) {
|
|
1282
|
+
return "div";
|
|
1283
|
+
}
|
|
1284
|
+
const expression = tagName.$expression;
|
|
1285
|
+
if (expression.operator === "?:") {
|
|
1286
|
+
// `evaluateOperand`, not `evaluateExpression`: the discriminant is an OPERAND — a pointer, a
|
|
1287
|
+
// Literal or a nested node — exactly as it is for a `$switch`, whose docstring names this.
|
|
1288
|
+
return evaluateOperand(expression.target, scope, null) ? expression.value : expression.initial;
|
|
1289
|
+
}
|
|
1290
|
+
const key = evaluateOperand(expression.target, scope, null);
|
|
1291
|
+
return expression.cases[String(key)] ?? expression.default;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1233
1294
|
// ─── $switch ──────────────────────────────────────────────────────────────────
|
|
1234
1295
|
|
|
1235
1296
|
/**
|
|
@@ -1240,7 +1301,7 @@ function renderMappedArrayInto(
|
|
|
1240
1301
|
*/
|
|
1241
1302
|
function renderSwitch(def: JxElement, state: JxScope, options?: JxRenderOptions) {
|
|
1242
1303
|
const path = options?._path ?? [];
|
|
1243
|
-
const container = document.createElement(def.tagName
|
|
1304
|
+
const container = document.createElement(resolveTagName(def.tagName, state));
|
|
1244
1305
|
|
|
1245
1306
|
if (options?.onNodeCreated) {
|
|
1246
1307
|
options.onNodeCreated(container, path, def, state);
|
|
@@ -1252,7 +1313,11 @@ function renderSwitch(def: JxElement, state: JxScope, options?: JxRenderOptions)
|
|
|
1252
1313
|
let generation = 0;
|
|
1253
1314
|
|
|
1254
1315
|
effect(() => {
|
|
1255
|
-
|
|
1316
|
+
/* `replaceChildren()` rather than `innerHTML = ""`: identical semantics, and it is not a
|
|
1317
|
+
Trusted Types injection sink — under `require-trusted-types-for 'script'` an innerHTML write
|
|
1318
|
+
needs a policy even when the string is empty. Four sinks that were never injecting anything
|
|
1319
|
+
is four fewer things a policy has to be permissive about. */
|
|
1320
|
+
container.replaceChildren();
|
|
1256
1321
|
if (!isRefObj(def.$switch)) {
|
|
1257
1322
|
return;
|
|
1258
1323
|
}
|
|
@@ -1264,7 +1329,8 @@ function renderSwitch(def: JxElement, state: JxScope, options?: JxRenderOptions)
|
|
|
1264
1329
|
|
|
1265
1330
|
if (isRefObj(caseDef)) {
|
|
1266
1331
|
// External $ref — fetch and render asynchronously
|
|
1267
|
-
|
|
1332
|
+
generation += 1;
|
|
1333
|
+
const gen = generation;
|
|
1268
1334
|
const { href } = new URL(caseDef.$ref, location.href);
|
|
1269
1335
|
resolve(href)
|
|
1270
1336
|
.then(async (doc) => {
|
|
@@ -1275,7 +1341,7 @@ function renderSwitch(def: JxElement, state: JxScope, options?: JxRenderOptions)
|
|
|
1275
1341
|
if (gen !== generation) {
|
|
1276
1342
|
return;
|
|
1277
1343
|
}
|
|
1278
|
-
container.
|
|
1344
|
+
container.replaceChildren();
|
|
1279
1345
|
const childOpts = options ? { ...options, _path: [...path, "cases", key] } : undefined;
|
|
1280
1346
|
container.append(renderNode(doc, childScope, childOpts));
|
|
1281
1347
|
})
|
|
@@ -1323,7 +1389,7 @@ export async function resolvePrototype(
|
|
|
1323
1389
|
const debounceMs = def.debounce ?? 0;
|
|
1324
1390
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1325
1391
|
|
|
1326
|
-
if (!def.manual) {
|
|
1392
|
+
if (!def.manual && !_autoRequestConfig.skip) {
|
|
1327
1393
|
effect(() => {
|
|
1328
1394
|
let url: string | undefined;
|
|
1329
1395
|
if (isTemplateString(def.url)) {
|
|
@@ -1422,38 +1488,21 @@ export async function resolvePrototype(
|
|
|
1422
1488
|
case "Cookie": {
|
|
1423
1489
|
const name = def.name ?? key;
|
|
1424
1490
|
const read = () => {
|
|
1425
|
-
const
|
|
1426
|
-
if (
|
|
1491
|
+
const raw = readCookie(document.cookie, name);
|
|
1492
|
+
if (raw === null) {
|
|
1427
1493
|
return null;
|
|
1428
1494
|
}
|
|
1429
1495
|
try {
|
|
1430
|
-
return JSON.parse(decodeURIComponent(
|
|
1496
|
+
return JSON.parse(decodeURIComponent(raw));
|
|
1431
1497
|
} catch {
|
|
1432
|
-
return
|
|
1498
|
+
return raw;
|
|
1433
1499
|
}
|
|
1434
1500
|
};
|
|
1435
1501
|
const cookieState: Ref<unknown> = ref(read() ?? def.default ?? null);
|
|
1436
1502
|
// Persist on change
|
|
1437
1503
|
effect(() => {
|
|
1438
|
-
const v = cookieState.value;
|
|
1439
|
-
let s = `${name}=${encodeURIComponent(JSON.stringify(v))}`;
|
|
1440
|
-
if (def.maxAge !== undefined) {
|
|
1441
|
-
s += `; Max-Age=${def.maxAge}`;
|
|
1442
|
-
}
|
|
1443
|
-
if (def.path) {
|
|
1444
|
-
s += `; Path=${def.path}`;
|
|
1445
|
-
}
|
|
1446
|
-
if (def.domain) {
|
|
1447
|
-
s += `; Domain=${def.domain}`;
|
|
1448
|
-
}
|
|
1449
|
-
if (def.secure) {
|
|
1450
|
-
s += `; Secure`;
|
|
1451
|
-
}
|
|
1452
|
-
if (def.sameSite) {
|
|
1453
|
-
s += `; SameSite=${def.sameSite}`;
|
|
1454
|
-
}
|
|
1455
1504
|
// oxlint-disable-next-line unicorn/no-document-cookie -- the Cookie $prototype IS the cookie store binding
|
|
1456
|
-
document.cookie =
|
|
1505
|
+
document.cookie = serializeCookie(name, cookieState.value, def);
|
|
1457
1506
|
});
|
|
1458
1507
|
return cookieState;
|
|
1459
1508
|
}
|
|
@@ -2050,15 +2099,16 @@ export function resolveRef(refPath: string, state: JxScope) {
|
|
|
2050
2099
|
return parts.length > 2 ? getPath(base, parts.slice(2).join("/")) : base;
|
|
2051
2100
|
}
|
|
2052
2101
|
if (refPath.startsWith("#/state/")) {
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
return state[sub];
|
|
2057
|
-
}
|
|
2058
|
-
return getPath(state[sub.slice(0, slash)], sub.slice(slash + 1));
|
|
2102
|
+
// One call, not a hand-split leading token: slicing at the first `/` skipped unescaping it, so
|
|
2103
|
+
// `#/state/a~1b/c` looked for a member called `a~1b` rather than `a/b`.
|
|
2104
|
+
return readPath(state, refPath.slice("#/state/".length));
|
|
2059
2105
|
}
|
|
2060
2106
|
if (refPath.startsWith("parent#/")) {
|
|
2061
|
-
|
|
2107
|
+
/*
|
|
2108
|
+
* A prop name may be a path into the prop. This read the whole path as one key and returned
|
|
2109
|
+
* undefined for `parent#/user/name`, while the compiler lowered it to a walk.
|
|
2110
|
+
*/
|
|
2111
|
+
return readPath(state, refPath.slice("parent#/".length));
|
|
2062
2112
|
}
|
|
2063
2113
|
if (refPath.startsWith("window#/")) {
|
|
2064
2114
|
return getPath(globalThis.window, refPath.slice("window#/".length));
|
|
@@ -2066,7 +2116,7 @@ export function resolveRef(refPath: string, state: JxScope) {
|
|
|
2066
2116
|
if (refPath.startsWith("document#/")) {
|
|
2067
2117
|
return getPath(globalThis.document, refPath.slice("document#/".length));
|
|
2068
2118
|
}
|
|
2069
|
-
return state
|
|
2119
|
+
return readPath(state, refPath) ?? null;
|
|
2070
2120
|
}
|
|
2071
2121
|
|
|
2072
2122
|
// ─── Utilities ────────────────────────────────────────────────────────────────
|
|
@@ -2098,16 +2148,51 @@ function isNestedSelector(k: string) {
|
|
|
2098
2148
|
}
|
|
2099
2149
|
|
|
2100
2150
|
/**
|
|
2151
|
+
* Walk a path off a value. Delegates to the one tokenizer (`pointer.ts`) — this used to split on
|
|
2152
|
+
* `/[./]/`, which no other path in the codebase agreed with.
|
|
2153
|
+
*
|
|
2101
2154
|
* @param {unknown} obj
|
|
2102
2155
|
* @param {string} path
|
|
2103
2156
|
* @returns {unknown}
|
|
2104
2157
|
*/
|
|
2105
2158
|
function getPath(obj: unknown, path: string) {
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2159
|
+
return readPath(obj, path);
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
/** Keys already reported, so a component rendered in a loop warns once rather than per instance. */
|
|
2163
|
+
const _privatePropWarned = new Set<string>();
|
|
2164
|
+
|
|
2165
|
+
/**
|
|
2166
|
+
* Refuse a `$props` write against a private (`#`) state key — `spec.md` §5.6.
|
|
2167
|
+
*
|
|
2168
|
+
* Ignored rather than thrown. A `$props` block naming a private entry is an authoring mistake, not
|
|
2169
|
+
* a broken document: the rest of the instance is well-formed and refusing to render it would turn a
|
|
2170
|
+
* typo into a blank page. But silence was the previous behavior and it is the wrong kind — the
|
|
2171
|
+
* author sees a prop that looks accepted and does nothing — so the write is dropped and named.
|
|
2172
|
+
*
|
|
2173
|
+
* @param {string} key
|
|
2174
|
+
* @param {string} [where] - The site refusing it, for a message that points somewhere
|
|
2175
|
+
* @returns {boolean} True when the key is private and the caller must skip it
|
|
2176
|
+
*/
|
|
2177
|
+
function refusePrivateProp(key: string, where?: string): boolean {
|
|
2178
|
+
if (!isPrivateStateKey(key)) {
|
|
2179
|
+
return false;
|
|
2109
2180
|
}
|
|
2110
|
-
|
|
2181
|
+
const seen = where === undefined ? key : `${where}:${key}`;
|
|
2182
|
+
if (!_privatePropWarned.has(seen)) {
|
|
2183
|
+
_privatePropWarned.add(seen);
|
|
2184
|
+
console.warn(
|
|
2185
|
+
`Jx: "${key}" is private state (spec.md §5.6) and cannot be set through $props` +
|
|
2186
|
+
`${where === undefined ? "" : ` (${where})`}. The write was ignored; rename the entry ` +
|
|
2187
|
+
"without the leading # to make it part of the component's interface.",
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
return true;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
/** Tests only — the warn-once set outlives a single render by design. */
|
|
2194
|
+
export function _resetPrivatePropWarnings(): void {
|
|
2195
|
+
_privatePropWarned.clear();
|
|
2111
2196
|
}
|
|
2112
2197
|
|
|
2113
2198
|
/**
|
|
@@ -2118,6 +2203,9 @@ function getPath(obj: unknown, path: string) {
|
|
|
2118
2203
|
function mergeProps(def: JxElement, parentState: JxScope): JxScope {
|
|
2119
2204
|
const child = Object.create(parentState) as JxScope;
|
|
2120
2205
|
for (const [k, v] of Object.entries(def.$props ?? {})) {
|
|
2206
|
+
if (refusePrivateProp(k, "$props")) {
|
|
2207
|
+
continue;
|
|
2208
|
+
}
|
|
2121
2209
|
child[k] = isRefObj(v) ? resolveRef(v.$ref, parentState) : v;
|
|
2122
2210
|
}
|
|
2123
2211
|
return child;
|
|
@@ -2306,7 +2394,12 @@ function injectHead(entries: JxHeadEntry[], _base: string) {
|
|
|
2306
2394
|
el.setAttribute(k, String(v));
|
|
2307
2395
|
}
|
|
2308
2396
|
if (entry.textContent) {
|
|
2309
|
-
|
|
2397
|
+
// An object is a structured data block (JSON-LD, site-architecture.md §8.5). The compiler
|
|
2398
|
+
// Serializes it the same way, so the interpreted page and the built page agree.
|
|
2399
|
+
el.textContent =
|
|
2400
|
+
typeof entry.textContent === "object"
|
|
2401
|
+
? JSON.stringify(entry.textContent, null, 2)
|
|
2402
|
+
: entry.textContent;
|
|
2310
2403
|
}
|
|
2311
2404
|
document.head.append(el);
|
|
2312
2405
|
}
|
|
@@ -2383,6 +2476,9 @@ export async function defineElement(source: string | JxDocument, baseUrl?: strin
|
|
|
2383
2476
|
try {
|
|
2384
2477
|
const props = JSON.parse(propsAttr) as Record<string, unknown>;
|
|
2385
2478
|
for (const [key, val] of Object.entries(props)) {
|
|
2479
|
+
if (refusePrivateProp(key, "data-jx-props")) {
|
|
2480
|
+
continue;
|
|
2481
|
+
}
|
|
2386
2482
|
if (key in (def.state ?? {})) {
|
|
2387
2483
|
state[key] = val;
|
|
2388
2484
|
}
|
|
@@ -2400,6 +2496,10 @@ export async function defineElement(source: string | JxDocument, baseUrl?: strin
|
|
|
2400
2496
|
);
|
|
2401
2497
|
for (const name of propAttrNames) {
|
|
2402
2498
|
const key = name.slice("props.".length);
|
|
2499
|
+
if (refusePrivateProp(key, "props.* attribute")) {
|
|
2500
|
+
this.removeAttribute(name);
|
|
2501
|
+
continue;
|
|
2502
|
+
}
|
|
2403
2503
|
if (key in (def.state ?? {})) {
|
|
2404
2504
|
state[key] = this.getAttribute(name);
|
|
2405
2505
|
this.removeAttribute(name);
|
|
@@ -2408,12 +2508,22 @@ export async function defineElement(source: string | JxDocument, baseUrl?: strin
|
|
|
2408
2508
|
|
|
2409
2509
|
// Merge $props set as JS properties by parent before connection
|
|
2410
2510
|
for (const key of Object.keys(def.state ?? {})) {
|
|
2511
|
+
if (isPrivateStateKey(key)) {
|
|
2512
|
+
continue;
|
|
2513
|
+
}
|
|
2411
2514
|
if (key in this && (this as Record<string, unknown>)[key] !== undefined) {
|
|
2412
2515
|
state[key] = (this as Record<string, unknown>)[key];
|
|
2413
2516
|
}
|
|
2414
2517
|
}
|
|
2415
|
-
|
|
2518
|
+
/*
|
|
2519
|
+
* Set up property getters/setters that forward into reactive state. Private entries get NO
|
|
2520
|
+
* accessor: the property-first interface IS the props mechanism, so defining one would leave
|
|
2521
|
+
* `el["#cache"] = x` writing straight through everything above.
|
|
2522
|
+
*/
|
|
2416
2523
|
for (const key of Object.keys(def.state ?? {})) {
|
|
2524
|
+
if (isPrivateStateKey(key)) {
|
|
2525
|
+
continue;
|
|
2526
|
+
}
|
|
2417
2527
|
if (!(key in HTMLElement.prototype)) {
|
|
2418
2528
|
Object.defineProperty(this, key, {
|
|
2419
2529
|
configurable: true,
|
|
@@ -2429,7 +2539,7 @@ export async function defineElement(source: string | JxDocument, baseUrl?: strin
|
|
|
2429
2539
|
|
|
2430
2540
|
// Capture light DOM children (for slot distribution) before rendering
|
|
2431
2541
|
const slottedChildren = [...this.childNodes];
|
|
2432
|
-
this.
|
|
2542
|
+
this.replaceChildren();
|
|
2433
2543
|
|
|
2434
2544
|
// Custom elements default to display:inline — use block so they behave as
|
|
2435
2545
|
// Containers (matching <div> semantics). The component's own style can
|
|
@@ -2503,7 +2613,7 @@ function renderCustomElementWithProps(
|
|
|
2503
2613
|
options?: JxRenderOptions,
|
|
2504
2614
|
path?: JxPath,
|
|
2505
2615
|
) {
|
|
2506
|
-
const el = document.createElement(def.tagName
|
|
2616
|
+
const el = document.createElement(resolveTagName(def.tagName, state));
|
|
2507
2617
|
|
|
2508
2618
|
if (options?.onNodeCreated) {
|
|
2509
2619
|
options.onNodeCreated(el, path ?? [], def, state);
|
|
@@ -2511,6 +2621,9 @@ function renderCustomElementWithProps(
|
|
|
2511
2621
|
|
|
2512
2622
|
// Set JS properties from $props (before connection)
|
|
2513
2623
|
for (const [key, val] of Object.entries(def.$props ?? {})) {
|
|
2624
|
+
if (refusePrivateProp(key, "$props")) {
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2514
2627
|
if (isRefObj(val)) {
|
|
2515
2628
|
const refVal = val;
|
|
2516
2629
|
const resolved = resolveRef(refVal.$ref, state);
|
|
@@ -2577,7 +2690,7 @@ function distributeSlots(host: HTMLElement, slottedChildren: ChildNode[]) {
|
|
|
2577
2690
|
const name = slot.getAttribute("name");
|
|
2578
2691
|
const matches = name ? (named.get(name) ?? []) : unnamed;
|
|
2579
2692
|
if (matches.length > 0) {
|
|
2580
|
-
slot.
|
|
2693
|
+
slot.replaceChildren();
|
|
2581
2694
|
for (const child of matches) {
|
|
2582
2695
|
slot.append(child);
|
|
2583
2696
|
}
|