@jxsuite/runtime 6.0.2 → 6.2.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 +137 -31
- package/dist/runtime.js.map +3 -3
- package/package.json +2 -2
- package/src/runtime.ts +257 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/runtime",
|
|
3
|
-
"version": "6.0
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "JSON-native reactive web component runtime",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"upgrade": "bun update --latest && bun install"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@jxsuite/schema": "^5.
|
|
36
|
+
"@jxsuite/schema": "^5.3.0",
|
|
37
37
|
"@vue/reactivity": "3.5.44"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
package/src/runtime.ts
CHANGED
|
@@ -388,6 +388,19 @@ export async function resolve(source: string | JxDocument, ctx?: JxContext): Pro
|
|
|
388
388
|
`app shell (a single-page fallback), so this path is not served.`,
|
|
389
389
|
);
|
|
390
390
|
}
|
|
391
|
+
/* A site origin answers `.jxn` as JSON, so only an editing server or a bare file host gets here
|
|
392
|
+
with KDL, and the parser is the host's to supply: a lazy import here would be inlined by any
|
|
393
|
+
host that bundles the runtime into one file, and ship the parser to every reader. */
|
|
394
|
+
if (JXN_URL.test(source) && !contentType.includes("json")) {
|
|
395
|
+
if (!_documentParser) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`Jx: ${source} is a .jxn file and this page has no parser for it. Serve it from a Jx ` +
|
|
398
|
+
`site, which answers JSON, or call setDocumentParser(parseJxn) with parseJxn from ` +
|
|
399
|
+
`@jxsuite/schema/jxn.`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return _documentParser(await res.text(), source);
|
|
403
|
+
}
|
|
391
404
|
/* SAFETY: trust boundary. `resolve` fetches only URLs that name a Jx document (a `Jx()` source,
|
|
392
405
|
an `$elements` entry, a `$switch` case), and the host answered 2xx with a non-HTML body, so
|
|
393
406
|
the JSON is that document; the runtime does not schema-validate documents by design. */
|
|
@@ -399,6 +412,28 @@ export async function resolve(source: string | JxDocument, ctx?: JxContext): Pro
|
|
|
399
412
|
|
|
400
413
|
// ─── Step 2: Build scope ──────────────────────────────────────────────────────
|
|
401
414
|
|
|
415
|
+
/** The path part of a URL naming a `.jxn` file. */
|
|
416
|
+
const JXN_URL = /\.jxn(?:[?#]|$)/iu;
|
|
417
|
+
|
|
418
|
+
/** What reads a `.jxn` file's text. Absent, the runtime refuses one rather than guessing. */
|
|
419
|
+
let _documentParser: ((text: string, source: string) => JxDocument | Promise<JxDocument>) | null =
|
|
420
|
+
null;
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Supply what reads a `.jxn` file, for a host that serves one as its text.
|
|
424
|
+
*
|
|
425
|
+
* A compiled site never needs this, and a `$ref` from a Jx site origin does not: both get JSON.
|
|
426
|
+
* Studio's canvas does, because an editing server answers a file as it is.
|
|
427
|
+
*
|
|
428
|
+
* @param parser `parseJxn` from `@jxsuite/schema/jxn`, or a function that loads it first, or null
|
|
429
|
+
* to take it away again.
|
|
430
|
+
*/
|
|
431
|
+
export function setDocumentParser(
|
|
432
|
+
parser: ((text: string, source: string) => JxDocument | Promise<JxDocument>) | null,
|
|
433
|
+
): void {
|
|
434
|
+
_documentParser = parser;
|
|
435
|
+
}
|
|
436
|
+
|
|
402
437
|
/** JSON Schema keywords used to identify pure type definitions (Shape 2b). */
|
|
403
438
|
const SCHEMA_KEYWORDS = new Set([
|
|
404
439
|
"type",
|
|
@@ -1163,6 +1198,69 @@ export function setTemplateErrorHandler(
|
|
|
1163
1198
|
_templateErrorHandler = handler;
|
|
1164
1199
|
}
|
|
1165
1200
|
|
|
1201
|
+
/** Warn about a throwing template, once per source. */
|
|
1202
|
+
function warnTemplateError(source: string, error: Error): void {
|
|
1203
|
+
if (!reportedTemplates.has(source)) {
|
|
1204
|
+
reportedTemplates.add(source);
|
|
1205
|
+
console.warn(`jx: template ${JSON.stringify(source)} threw:`, error);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/** Lookups in flight whose answers can make a throwing template valid (§6.1). */
|
|
1210
|
+
let pendingLookups = 0;
|
|
1211
|
+
|
|
1212
|
+
/** Each binding still throwing while a lookup is in flight, by its `run`, with its latest throw. */
|
|
1213
|
+
const heldTemplateErrors = new Map<() => void, { error: Error; source: string }>();
|
|
1214
|
+
|
|
1215
|
+
/** Who {@link lookupsSettled} has yet to answer. */
|
|
1216
|
+
const lookupWaiters: (() => void)[] = [];
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Hold template warnings until `lookup` and every other lookup in flight have settled.
|
|
1220
|
+
*
|
|
1221
|
+
* `lookup` must write its answer to state before it settles: the write re-runs the bindings waiting
|
|
1222
|
+
* on it, so whatever is still held when the last one settles is a template the data did not fix,
|
|
1223
|
+
* and only that is reported. A lookup its own answer starts is counted before this one is
|
|
1224
|
+
* released.
|
|
1225
|
+
*
|
|
1226
|
+
* @param {Promise<void>} lookup - Never rejects: it reports its own failure
|
|
1227
|
+
*/
|
|
1228
|
+
function holdTemplateErrorsFor(lookup: Promise<void>): void {
|
|
1229
|
+
pendingLookups += 1;
|
|
1230
|
+
void lookup.finally(() => {
|
|
1231
|
+
pendingLookups -= 1;
|
|
1232
|
+
if (pendingLookups === 0) {
|
|
1233
|
+
/* The console, even if a handler was installed meanwhile: these throws happened outside that
|
|
1234
|
+
handler's render, and Studio's render critic would refuse its page for them. */
|
|
1235
|
+
for (const { error, source } of heldTemplateErrors.values()) {
|
|
1236
|
+
warnTemplateError(source, error);
|
|
1237
|
+
}
|
|
1238
|
+
heldTemplateErrors.clear();
|
|
1239
|
+
for (const wake of lookupWaiters.splice(0)) {
|
|
1240
|
+
wake();
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* Answers once no lookup the runtime started is in flight (embedding.md §5.2).
|
|
1248
|
+
*
|
|
1249
|
+
* A host whose question is whether a document renders awaits this between `buildScope` and
|
|
1250
|
+
* `renderNode`, so its templates read the data rather than the `null` before it. A lookup can hang,
|
|
1251
|
+
* and this waits for it, so bounding the wait is the host's job.
|
|
1252
|
+
*
|
|
1253
|
+
* @returns {Promise<void>}
|
|
1254
|
+
*/
|
|
1255
|
+
export function lookupsSettled(): Promise<void> {
|
|
1256
|
+
if (pendingLookups === 0) {
|
|
1257
|
+
return Promise.resolve();
|
|
1258
|
+
}
|
|
1259
|
+
return new Promise((wake) => {
|
|
1260
|
+
lookupWaiters.push(wake);
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1166
1264
|
/**
|
|
1167
1265
|
* Run a template binding as an effect that survives its expression throwing.
|
|
1168
1266
|
*
|
|
@@ -1172,7 +1270,8 @@ export function setTemplateErrorHandler(
|
|
|
1172
1270
|
* and the throw unwound the whole `renderNode` above it: one early read blanked the page. Caught
|
|
1173
1271
|
* INSIDE the effect, every dependency read before the throw stays tracked, so the binding re-runs
|
|
1174
1272
|
* on the change that makes it valid. Until then it shows `fallback`'s answer (nothing written, by
|
|
1175
|
-
* default), and the error is reported once per source
|
|
1273
|
+
* default), and the error is reported once per source, after any lookup in flight has settled: a
|
|
1274
|
+
* template that only needed that lookup's answer is not reported at all.
|
|
1176
1275
|
*
|
|
1177
1276
|
* @param {string} source - The template, for the report
|
|
1178
1277
|
* @param {() => void} run - Evaluates the template and writes it
|
|
@@ -1182,12 +1281,15 @@ function bindTemplate(source: string, run: () => void, fallback?: () => void): v
|
|
|
1182
1281
|
effect(() => {
|
|
1183
1282
|
try {
|
|
1184
1283
|
run();
|
|
1284
|
+
heldTemplateErrors.delete(run);
|
|
1185
1285
|
} catch (error) {
|
|
1286
|
+
const thrown = error instanceof Error ? error : new Error(String(error));
|
|
1186
1287
|
if (_templateErrorHandler) {
|
|
1187
|
-
_templateErrorHandler(
|
|
1188
|
-
} else if (
|
|
1189
|
-
|
|
1190
|
-
|
|
1288
|
+
_templateErrorHandler(thrown, source);
|
|
1289
|
+
} else if (pendingLookups > 0) {
|
|
1290
|
+
heldTemplateErrors.set(run, { error: thrown, source });
|
|
1291
|
+
} else {
|
|
1292
|
+
warnTemplateError(source, thrown);
|
|
1191
1293
|
}
|
|
1192
1294
|
fallback?.();
|
|
1193
1295
|
}
|
|
@@ -1619,7 +1721,15 @@ export const RESERVED_KEYS = new Set([
|
|
|
1619
1721
|
]);
|
|
1620
1722
|
|
|
1621
1723
|
/**
|
|
1622
|
-
* A child scope:
|
|
1724
|
+
* A child scope: a `$map` row, or a node carrying `$`-prefixed local bindings or `$props`. Reads
|
|
1725
|
+
* fall through to `parent`, and so does a write to any key the chain above already holds.
|
|
1726
|
+
*
|
|
1727
|
+
* The write is the half a bare `Object.create(parent)` gets wrong, and it gets it wrong silently.
|
|
1728
|
+
* An assignment that reaches the reactive proxy through a prototype chain arrives with the child as
|
|
1729
|
+
* its receiver, so the proxy defines the value on the child and triggers nothing: the handler's
|
|
1730
|
+
* `count += 1` shadows `count`, the root never changes, and the bindings reading it never re-run. A
|
|
1731
|
+
* compiled document assigns to the root directly, so the same document counted there and sat still
|
|
1732
|
+
* here. A value this scope owns is declared with {@link bindLocal}, which is what shadows.
|
|
1623
1733
|
*
|
|
1624
1734
|
* @param {JxScope} parent
|
|
1625
1735
|
* @returns {JxScope}
|
|
@@ -1627,7 +1737,35 @@ export const RESERVED_KEYS = new Set([
|
|
|
1627
1737
|
function inheritScope(parent: JxScope): JxScope {
|
|
1628
1738
|
/* SAFETY: `Object.create` is typed `any`, but what it returns is a plain object whose prototype
|
|
1629
1739
|
is `parent`, so it is keyed by string exactly as the scope it extends is. */
|
|
1630
|
-
|
|
1740
|
+
const local = Object.create(parent) as JxScope;
|
|
1741
|
+
return new Proxy(local, {
|
|
1742
|
+
set(target, key: string, value: JxScopeValue) {
|
|
1743
|
+
if (Object.hasOwn(target, key)) {
|
|
1744
|
+
return Reflect.set(target, key, value);
|
|
1745
|
+
}
|
|
1746
|
+
if (key in parent) {
|
|
1747
|
+
return Reflect.set(parent, key, value);
|
|
1748
|
+
}
|
|
1749
|
+
bindLocal(target, key, value);
|
|
1750
|
+
return true;
|
|
1751
|
+
},
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* Declare `key` on a child scope itself, shadowing whatever its parents hold under that name.
|
|
1757
|
+
*
|
|
1758
|
+
* @param {JxScope} scope
|
|
1759
|
+
* @param {string} key
|
|
1760
|
+
* @param {JxScopeValue} value
|
|
1761
|
+
*/
|
|
1762
|
+
function bindLocal(scope: JxScope, key: string, value: JxScopeValue): void {
|
|
1763
|
+
Object.defineProperty(scope, key, {
|
|
1764
|
+
configurable: true,
|
|
1765
|
+
enumerable: true,
|
|
1766
|
+
value,
|
|
1767
|
+
writable: true,
|
|
1768
|
+
});
|
|
1631
1769
|
}
|
|
1632
1770
|
|
|
1633
1771
|
/**
|
|
@@ -1752,7 +1890,7 @@ export function renderNode(
|
|
|
1752
1890
|
if (localState === state) {
|
|
1753
1891
|
localState = inheritScope(state);
|
|
1754
1892
|
}
|
|
1755
|
-
localState
|
|
1893
|
+
bindLocal(localState, key, isRefValue(val) ? resolveRef(val.$ref, state) : val);
|
|
1756
1894
|
}
|
|
1757
1895
|
}
|
|
1758
1896
|
|
|
@@ -1857,11 +1995,74 @@ export function renderNode(
|
|
|
1857
1995
|
el.append(renderNode(child, localState, childOpts));
|
|
1858
1996
|
}
|
|
1859
1997
|
}
|
|
1998
|
+
} else if (isTemplateString(kids)) {
|
|
1999
|
+
renderComputedChildren(el, kids, localState, {
|
|
2000
|
+
...options,
|
|
2001
|
+
_ns: childNs,
|
|
2002
|
+
_path: [...path, "children"],
|
|
2003
|
+
});
|
|
1860
2004
|
}
|
|
1861
2005
|
|
|
1862
2006
|
return el;
|
|
1863
2007
|
}
|
|
1864
2008
|
|
|
2009
|
+
/**
|
|
2010
|
+
* Draw a whole-`children` template, the way the site build's template pass expands one (spec.md
|
|
2011
|
+
* §8.4). This is what an item's or an entry's parsed body looks like to something that renders the
|
|
2012
|
+
* source document rather than the build's output: Studio's Preview, chiefly.
|
|
2013
|
+
*
|
|
2014
|
+
* A result that is not an array draws nothing, because a plain string is not a `children` value.
|
|
2015
|
+
* The children are rendered in a detached scope of their own, replaced when the template's value
|
|
2016
|
+
* changes (a collection that was still loading), and with tracking paused for the reason
|
|
2017
|
+
* {@link renderSwitch} gives: the bindings inside track for themselves.
|
|
2018
|
+
*
|
|
2019
|
+
* @param {Element} el - The element whose children these are
|
|
2020
|
+
* @param {string} template - The `children` value
|
|
2021
|
+
* @param {JxScope} state
|
|
2022
|
+
* @param {JxRenderOptions} options - `_path` is the `children` position of `el`
|
|
2023
|
+
*/
|
|
2024
|
+
function renderComputedChildren(
|
|
2025
|
+
el: Element,
|
|
2026
|
+
template: string,
|
|
2027
|
+
state: JxScope,
|
|
2028
|
+
options: JxRenderOptions,
|
|
2029
|
+
): void {
|
|
2030
|
+
const path = options._path ?? [];
|
|
2031
|
+
let live: EffectScope | null = null;
|
|
2032
|
+
const retire = (): void => {
|
|
2033
|
+
// Detached before it is stopped, for the reason `teardown` in `renderMappedArray` gives.
|
|
2034
|
+
el.replaceChildren();
|
|
2035
|
+
live?.stop();
|
|
2036
|
+
live = null;
|
|
2037
|
+
};
|
|
2038
|
+
bindTemplate(
|
|
2039
|
+
template,
|
|
2040
|
+
() => {
|
|
2041
|
+
const nodes = evaluateValueTemplate(template, state);
|
|
2042
|
+
retire();
|
|
2043
|
+
if (!Array.isArray(nodes)) {
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
const scope = effectScope(true);
|
|
2047
|
+
live = scope;
|
|
2048
|
+
pauseTracking();
|
|
2049
|
+
try {
|
|
2050
|
+
scope.run(() => {
|
|
2051
|
+
/* SAFETY: a children template that yields an array stands for the child list itself, the
|
|
2052
|
+
same reading the site build's template pass gives it. */
|
|
2053
|
+
for (const [i, child] of (nodes as (JxElement | string)[]).entries()) {
|
|
2054
|
+
el.append(renderNode(child, state, { ...options, _path: [...path, i] }));
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
} finally {
|
|
2058
|
+
resetTracking();
|
|
2059
|
+
}
|
|
2060
|
+
},
|
|
2061
|
+
retire,
|
|
2062
|
+
);
|
|
2063
|
+
onScopeDispose(() => live?.stop(), true);
|
|
2064
|
+
}
|
|
2065
|
+
|
|
1865
2066
|
// ─── Template string utilities ────────────────────────────────────────────────
|
|
1866
2067
|
|
|
1867
2068
|
/**
|
|
@@ -2930,7 +3131,7 @@ function renderMappedArrayInto(
|
|
|
2930
3131
|
): MappedRow => {
|
|
2931
3132
|
const map = shallowReactive({ index, item });
|
|
2932
3133
|
const child = inheritScope(state);
|
|
2933
|
-
child
|
|
3134
|
+
bindLocal(child, "$map", map);
|
|
2934
3135
|
// The flat aliases stay readable by legacy readers, and follow the reactive context.
|
|
2935
3136
|
Object.defineProperty(child, "$map/item", { enumerable: true, get: () => map.item });
|
|
2936
3137
|
Object.defineProperty(child, "$map/index", { enumerable: true, get: () => map.index });
|
|
@@ -3297,29 +3498,32 @@ export async function resolvePrototype(
|
|
|
3297
3498
|
}
|
|
3298
3499
|
});
|
|
3299
3500
|
|
|
3300
|
-
const doFetch = () =>
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
.then((r): Promise<JsonValue> => (r.ok ? r.json() : Promise.reject(r.statusText)))
|
|
3310
|
-
.then((d) => {
|
|
3311
|
-
s.value = d;
|
|
3501
|
+
const doFetch = () => {
|
|
3502
|
+
holdTemplateErrorsFor(
|
|
3503
|
+
fetch(url, {
|
|
3504
|
+
method: def.method ?? "GET",
|
|
3505
|
+
signal: controller.signal,
|
|
3506
|
+
...(def.headers && { headers: def.headers }),
|
|
3507
|
+
...(def.body && {
|
|
3508
|
+
body: isNonNullObject(def.body) ? JSON.stringify(def.body) : def.body,
|
|
3509
|
+
}),
|
|
3312
3510
|
})
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
s.value =
|
|
3316
|
-
}
|
|
3317
|
-
|
|
3511
|
+
.then((r): Promise<JsonValue> => (r.ok ? r.json() : Promise.reject(r.statusText)))
|
|
3512
|
+
.then((d) => {
|
|
3513
|
+
s.value = d;
|
|
3514
|
+
})
|
|
3515
|
+
.catch((error) => {
|
|
3516
|
+
if (!(error instanceof Error && error.name === "AbortError")) {
|
|
3517
|
+
s.value = { error: String(error) };
|
|
3518
|
+
}
|
|
3519
|
+
}),
|
|
3520
|
+
);
|
|
3521
|
+
};
|
|
3318
3522
|
|
|
3319
3523
|
if (debounceMs > 0) {
|
|
3320
3524
|
debounceTimer = setTimeout(doFetch, debounceMs);
|
|
3321
3525
|
} else {
|
|
3322
|
-
|
|
3526
|
+
doFetch();
|
|
3323
3527
|
}
|
|
3324
3528
|
});
|
|
3325
3529
|
}
|
|
@@ -3850,24 +4054,25 @@ async function resolveViaDevProxy(def: JxPrototypeDef, state: JxScope, key: stri
|
|
|
3850
4054
|
|
|
3851
4055
|
// Always wrap in ref for reactivity
|
|
3852
4056
|
const s: Ref<JxScopeValue> = ref(null);
|
|
4057
|
+
const resolveInto = (resolvedConfig: JxScope) => {
|
|
4058
|
+
holdTemplateErrorsFor(
|
|
4059
|
+
doResolve(resolvedConfig)
|
|
4060
|
+
.then((value) => {
|
|
4061
|
+
s.value = value;
|
|
4062
|
+
})
|
|
4063
|
+
.catch((error) => console.error("Jx dev proxy:", error)),
|
|
4064
|
+
);
|
|
4065
|
+
};
|
|
3853
4066
|
if (hasTemplates) {
|
|
3854
4067
|
effect(() => {
|
|
3855
4068
|
const resolvedConfig: JxScope = {};
|
|
3856
4069
|
for (const [k, v] of Object.entries(config)) {
|
|
3857
4070
|
resolvedConfig[k] = isTemplateString(v) ? evaluateTemplate(v, state) : v;
|
|
3858
4071
|
}
|
|
3859
|
-
|
|
3860
|
-
.then((value) => {
|
|
3861
|
-
s.value = value;
|
|
3862
|
-
})
|
|
3863
|
-
.catch((error) => console.error("Jx dev proxy:", error));
|
|
4072
|
+
resolveInto(resolvedConfig);
|
|
3864
4073
|
});
|
|
3865
4074
|
} else {
|
|
3866
|
-
|
|
3867
|
-
.then((value) => {
|
|
3868
|
-
s.value = value;
|
|
3869
|
-
})
|
|
3870
|
-
.catch((error) => console.error("Jx dev proxy:", error));
|
|
4075
|
+
resolveInto(config);
|
|
3871
4076
|
}
|
|
3872
4077
|
return s;
|
|
3873
4078
|
}
|
|
@@ -3900,19 +4105,21 @@ function latestServerCall(
|
|
|
3900
4105
|
const n = seq;
|
|
3901
4106
|
/* The executor runs before the constructor returns, so the call still starts synchronously,
|
|
3902
4107
|
and a throw inside it rejects this promise instead of escaping to the caller. */
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
.then((result) => {
|
|
3907
|
-
if (n === seq) {
|
|
3908
|
-
s.value = result;
|
|
3909
|
-
}
|
|
4108
|
+
holdTemplateErrorsFor(
|
|
4109
|
+
new Promise<JsonValue>((settle) => {
|
|
4110
|
+
settle(invoke(args));
|
|
3910
4111
|
})
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
4112
|
+
.then((result) => {
|
|
4113
|
+
if (n === seq) {
|
|
4114
|
+
s.value = result;
|
|
4115
|
+
}
|
|
4116
|
+
})
|
|
4117
|
+
.catch((error) => {
|
|
4118
|
+
if (n === seq) {
|
|
4119
|
+
console.error(label, error);
|
|
4120
|
+
}
|
|
4121
|
+
}),
|
|
4122
|
+
);
|
|
3916
4123
|
};
|
|
3917
4124
|
}
|
|
3918
4125
|
|
|
@@ -4281,7 +4488,7 @@ function mergeProps(def: JxElement, parentState: JxScope): JxScope {
|
|
|
4281
4488
|
if (refusePrivateProp(k, "$props")) {
|
|
4282
4489
|
continue;
|
|
4283
4490
|
}
|
|
4284
|
-
child
|
|
4491
|
+
bindLocal(child, k, isRefValue(v) ? resolveRef(v.$ref, parentState) : v);
|
|
4285
4492
|
}
|
|
4286
4493
|
return child;
|
|
4287
4494
|
}
|