@pyreon/runtime-dom 0.13.1 → 0.15.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/README.md +23 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/analysis/keep-alive-entry.js.html +5406 -0
- package/lib/analysis/transition-entry.js.html +5406 -0
- package/lib/index.js +156 -57
- package/lib/keep-alive-entry.js +1342 -0
- package/lib/transition-entry.js +168 -0
- package/lib/types/index.d.ts +54 -5
- package/lib/types/keep-alive-entry.d.ts +41 -0
- package/lib/types/transition-entry.d.ts +59 -0
- package/package.json +17 -6
- package/src/delegate.ts +16 -0
- package/src/hydrate.ts +23 -14
- package/src/hydration-debug.ts +99 -14
- package/src/index.ts +30 -6
- package/src/keep-alive-entry.ts +3 -0
- package/src/keep-alive.ts +5 -1
- package/src/mount.ts +160 -56
- package/src/nodes.ts +62 -13
- package/src/props.ts +1 -2
- package/src/template.ts +57 -2
- package/src/tests/coverage-gaps.test.ts +709 -0
- package/src/tests/dev-gate-pattern.test.ts +17 -11
- package/src/tests/dev-gate-treeshake.test.ts +20 -26
- package/src/tests/hydration-integration.test.tsx +166 -1
- package/src/tests/lis-prepend.browser.test.ts +99 -0
- package/src/tests/mount.test.ts +91 -0
- package/src/tests/native-markers.test.ts +19 -0
- package/src/tests/runtime-dom.browser.test.ts +121 -7
- package/src/tests/show-context.test.ts +93 -0
- package/src/tests/template.test.ts +135 -1
- package/src/transition-entry.ts +7 -0
- package/src/transition-group.ts +6 -1
- package/src/transition.ts +11 -3
- package/lib/index.js.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { batch, effect, effectScope, renderEffect, runUntracked, setCurrentScope, signal } from "@pyreon/reactivity";
|
|
2
|
-
import { EMPTY_PROPS, ForSymbol, Fragment, PortalSymbol, captureContextStack, createRef, cx, dispatchToErrorBoundary, h, makeReactiveProps, normalizeStyleValue, onMount, onUnmount, propagateError, reportError, restoreContextStack, runWithHooks, toKebabCase } from "@pyreon/core";
|
|
2
|
+
import { EMPTY_PROPS, ForSymbol, Fragment, PortalSymbol, captureContextStack, createRef, cx, dispatchToErrorBoundary, h, makeReactiveProps, nativeCompat, normalizeStyleValue, onMount, onUnmount, propagateError, reportError, restoreContextStack, runWithHooks, toKebabCase } from "@pyreon/core";
|
|
3
3
|
|
|
4
4
|
//#region src/delegate.ts
|
|
5
5
|
/**
|
|
@@ -66,6 +66,10 @@ function setupDelegation(container) {
|
|
|
66
66
|
while (el && el !== container) {
|
|
67
67
|
const handler = el[prop];
|
|
68
68
|
if (typeof handler === "function") {
|
|
69
|
+
Object.defineProperty(e, "currentTarget", {
|
|
70
|
+
value: el,
|
|
71
|
+
configurable: true
|
|
72
|
+
});
|
|
69
73
|
batch(() => handler(e));
|
|
70
74
|
if (e.cancelBubble) break;
|
|
71
75
|
}
|
|
@@ -77,13 +81,29 @@ function setupDelegation(container) {
|
|
|
77
81
|
|
|
78
82
|
//#endregion
|
|
79
83
|
//#region src/hydration-debug.ts
|
|
80
|
-
let _enabled =
|
|
84
|
+
let _enabled = process.env.NODE_ENV !== "production";
|
|
81
85
|
function enableHydrationWarnings() {
|
|
82
86
|
_enabled = true;
|
|
83
87
|
}
|
|
84
88
|
function disableHydrationWarnings() {
|
|
85
89
|
_enabled = false;
|
|
86
90
|
}
|
|
91
|
+
let _handlers = [];
|
|
92
|
+
/**
|
|
93
|
+
* Register a hydration mismatch handler. Called on every mismatch in BOTH
|
|
94
|
+
* development and production, independent of the dev-mode warn toggle.
|
|
95
|
+
*
|
|
96
|
+
* Mirrors `@pyreon/core`'s `registerErrorHandler` pattern — multiple
|
|
97
|
+
* handlers can be registered; each is called in registration order;
|
|
98
|
+
* handler errors are swallowed so they don't propagate into the
|
|
99
|
+
* framework. Returns an unregister function.
|
|
100
|
+
*/
|
|
101
|
+
function onHydrationMismatch(handler) {
|
|
102
|
+
_handlers.push(handler);
|
|
103
|
+
return () => {
|
|
104
|
+
_handlers = _handlers.filter((h) => h !== handler);
|
|
105
|
+
};
|
|
106
|
+
}
|
|
87
107
|
/**
|
|
88
108
|
* Emit a hydration mismatch warning.
|
|
89
109
|
* @param type - Kind of mismatch
|
|
@@ -91,9 +111,20 @@ function disableHydrationWarnings() {
|
|
|
91
111
|
* @param actual - What the DOM had
|
|
92
112
|
* @param path - Human-readable path in the tree, e.g. "root > div > span"
|
|
93
113
|
*/
|
|
94
|
-
function warnHydrationMismatch(
|
|
95
|
-
if (
|
|
96
|
-
|
|
114
|
+
function warnHydrationMismatch(type, expected, actual, path) {
|
|
115
|
+
if (_enabled) console.warn(`[Pyreon] Hydration mismatch (${type}): expected ${String(expected)}, got ${String(actual)} at ${path}`);
|
|
116
|
+
if (_handlers.length > 0) {
|
|
117
|
+
const ctx = {
|
|
118
|
+
type,
|
|
119
|
+
expected,
|
|
120
|
+
actual,
|
|
121
|
+
path,
|
|
122
|
+
timestamp: Date.now()
|
|
123
|
+
};
|
|
124
|
+
for (const h of _handlers) try {
|
|
125
|
+
h(ctx);
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
97
128
|
}
|
|
98
129
|
|
|
99
130
|
//#endregion
|
|
@@ -279,13 +310,18 @@ function installDevTools() {
|
|
|
279
310
|
});
|
|
280
311
|
const win = window;
|
|
281
312
|
win.$p = {
|
|
313
|
+
/** List all mounted components */
|
|
282
314
|
components: () => devtools.getAllComponents(),
|
|
315
|
+
/** Component tree (roots only) */
|
|
283
316
|
tree: () => devtools.getComponentTree(),
|
|
317
|
+
/** Highlight a component by id */
|
|
284
318
|
highlight: (id) => devtools.highlight(id),
|
|
319
|
+
/** Toggle component inspector overlay */
|
|
285
320
|
inspect: () => {
|
|
286
321
|
if (_overlayActive) disableOverlay();
|
|
287
322
|
else enableOverlay();
|
|
288
323
|
},
|
|
324
|
+
/** Print component count */
|
|
289
325
|
stats: () => {
|
|
290
326
|
const all = devtools.getAllComponents();
|
|
291
327
|
const roots = devtools.getComponentTree();
|
|
@@ -295,6 +331,7 @@ function installDevTools() {
|
|
|
295
331
|
roots: roots.length
|
|
296
332
|
};
|
|
297
333
|
},
|
|
334
|
+
/** Quick help */
|
|
298
335
|
help: () => {
|
|
299
336
|
console.log("[Pyreon] $p commands:\n $p.components() — list all mounted components\n $p.tree() — component tree (roots only)\n $p.highlight(id)— outline a component\n $p.inspect() — toggle component inspector\n $p.stats() — print component count\n $p.help() — this message");
|
|
300
337
|
}
|
|
@@ -303,7 +340,8 @@ function installDevTools() {
|
|
|
303
340
|
|
|
304
341
|
//#endregion
|
|
305
342
|
//#region src/nodes.ts
|
|
306
|
-
const __DEV__$
|
|
343
|
+
const __DEV__$5 = process.env.NODE_ENV !== "production";
|
|
344
|
+
const _countSink$3 = globalThis;
|
|
307
345
|
/**
|
|
308
346
|
* Move all nodes strictly between `start` and `end` into a throwaway
|
|
309
347
|
* DocumentFragment, detaching them from the live DOM in O(n) top-level moves.
|
|
@@ -367,6 +405,7 @@ function growLisArrays(lis, n) {
|
|
|
367
405
|
function computeKeyedLis(lis, n, newKeyOrder, curPos) {
|
|
368
406
|
const { tails, tailIdx, pred } = lis;
|
|
369
407
|
let lisLen = 0;
|
|
408
|
+
let ops = 0;
|
|
370
409
|
for (let i = 0; i < n; i++) {
|
|
371
410
|
const key = newKeyOrder[i];
|
|
372
411
|
if (key === void 0) continue;
|
|
@@ -376,6 +415,7 @@ function computeKeyedLis(lis, n, newKeyOrder, curPos) {
|
|
|
376
415
|
let hi = lisLen;
|
|
377
416
|
while (lo < hi) {
|
|
378
417
|
const mid = lo + hi >> 1;
|
|
418
|
+
ops++;
|
|
379
419
|
if (tails[mid] < v) lo = mid + 1;
|
|
380
420
|
else hi = mid;
|
|
381
421
|
}
|
|
@@ -384,6 +424,7 @@ function computeKeyedLis(lis, n, newKeyOrder, curPos) {
|
|
|
384
424
|
if (lo > 0) pred[i] = tailIdx[lo - 1];
|
|
385
425
|
if (lo === lisLen) lisLen++;
|
|
386
426
|
}
|
|
427
|
+
if (__DEV__$5 && ops > 0) _countSink$3.__pyreon_count__?.("runtime.mountFor.lisOps", ops);
|
|
387
428
|
return lisLen;
|
|
388
429
|
}
|
|
389
430
|
function markStayingEntries(lis, lisLen) {
|
|
@@ -521,21 +562,36 @@ function trySmallKReorder(n, newKeys, currentKeys, cache, liveParent, tailMarker
|
|
|
521
562
|
function computeForLis(lis, n, newKeys, cache) {
|
|
522
563
|
const { tails, tailIdx, pred } = lis;
|
|
523
564
|
let lisLen = 0;
|
|
565
|
+
let ops = 0;
|
|
566
|
+
let lastV = -1;
|
|
524
567
|
for (let i = 0; i < n; i++) {
|
|
525
568
|
const key = newKeys[i];
|
|
526
569
|
const v = cache.get(key)?.pos ?? 0;
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
570
|
+
if (v > lastV) {
|
|
571
|
+
tails[lisLen] = v;
|
|
572
|
+
tailIdx[lisLen] = i;
|
|
573
|
+
if (lisLen > 0) pred[i] = tailIdx[lisLen - 1];
|
|
574
|
+
lisLen++;
|
|
575
|
+
lastV = v;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
let lo;
|
|
579
|
+
if (v < lisLen && tails[v] === v) lo = v;
|
|
580
|
+
else {
|
|
581
|
+
lo = 0;
|
|
582
|
+
let hi = lisLen;
|
|
583
|
+
while (lo < hi) {
|
|
584
|
+
const mid = lo + hi >> 1;
|
|
585
|
+
ops++;
|
|
586
|
+
if (tails[mid] < v) lo = mid + 1;
|
|
587
|
+
else hi = mid;
|
|
588
|
+
}
|
|
533
589
|
}
|
|
534
590
|
tails[lo] = v;
|
|
535
591
|
tailIdx[lo] = i;
|
|
536
592
|
if (lo > 0) pred[i] = tailIdx[lo - 1];
|
|
537
|
-
if (lo === lisLen) lisLen++;
|
|
538
593
|
}
|
|
594
|
+
if (__DEV__$5 && ops > 0) _countSink$3.__pyreon_count__?.("runtime.mountFor.lisOps", ops);
|
|
539
595
|
return lisLen;
|
|
540
596
|
}
|
|
541
597
|
function applyForMoves(n, newKeys, stay, cache, liveParent, tailMarker) {
|
|
@@ -576,6 +632,7 @@ function mountFor(source, getKey, renderItem, parent, anchor, mountChild) {
|
|
|
576
632
|
parent.insertBefore(tailMarker, anchor);
|
|
577
633
|
let cache = /* @__PURE__ */ new Map();
|
|
578
634
|
let currentKeys = [];
|
|
635
|
+
const _reusableKeySet = /* @__PURE__ */ new Set();
|
|
579
636
|
let cleanupCount = 0;
|
|
580
637
|
let anchorsRegistered = false;
|
|
581
638
|
let lis = {
|
|
@@ -586,9 +643,9 @@ function mountFor(source, getKey, renderItem, parent, anchor, mountChild) {
|
|
|
586
643
|
};
|
|
587
644
|
const warnForKey = (seen, key) => {
|
|
588
645
|
if (!seen) return;
|
|
589
|
-
if (__DEV__$
|
|
646
|
+
if (__DEV__$5 && key == null) console.warn("[Pyreon] <For> `by` function returned null/undefined. Keys must be strings or numbers. Check your `by` prop.");
|
|
590
647
|
if (seen.has(key)) {
|
|
591
|
-
if (__DEV__$
|
|
648
|
+
if (__DEV__$5) console.warn(`[Pyreon] Duplicate key "${String(key)}" in <For> list. Keys must be unique.`);
|
|
592
649
|
return true;
|
|
593
650
|
}
|
|
594
651
|
seen.add(key);
|
|
@@ -714,7 +771,9 @@ function mountFor(source, getKey, renderItem, parent, anchor, mountChild) {
|
|
|
714
771
|
return false;
|
|
715
772
|
};
|
|
716
773
|
const handleIncrementalUpdate = (items, n, newKeys, liveParent) => {
|
|
717
|
-
|
|
774
|
+
_reusableKeySet.clear();
|
|
775
|
+
for (let i = 0; i < newKeys.length; i++) _reusableKeySet.add(newKeys[i]);
|
|
776
|
+
removeStaleForEntries(_reusableKeySet);
|
|
718
777
|
mountNewForEntries(items, n, newKeys, liveParent);
|
|
719
778
|
if (!anchorsRegistered) {
|
|
720
779
|
for (const entry of cache.values()) _forAnchors.add(entry.anchor);
|
|
@@ -813,7 +872,7 @@ function moveEntryBefore(parent, startNode, before) {
|
|
|
813
872
|
|
|
814
873
|
//#endregion
|
|
815
874
|
//#region src/props.ts
|
|
816
|
-
const __DEV__$
|
|
875
|
+
const __DEV__$4 = process.env.NODE_ENV !== "production";
|
|
817
876
|
let _customSanitizer = null;
|
|
818
877
|
/**
|
|
819
878
|
* Set a custom HTML sanitizer used by `innerHTML` and `sanitizeHtml()`.
|
|
@@ -978,7 +1037,7 @@ function applyProps(el, props) {
|
|
|
978
1037
|
*/
|
|
979
1038
|
function applyEventProp(el, key, value) {
|
|
980
1039
|
if (typeof value !== "function") {
|
|
981
|
-
if (__DEV__$
|
|
1040
|
+
if (__DEV__$4 && value != null) console.warn(`[Pyreon] Event handler "${key}" received a non-function value (${typeof value}). Expected a function. Did you mean ${key}={() => ...}?`);
|
|
982
1041
|
return null;
|
|
983
1042
|
}
|
|
984
1043
|
const eventName = (key[2]?.toLowerCase() + key.slice(3)).toLowerCase();
|
|
@@ -1009,7 +1068,7 @@ function applyEventProp(el, key, value) {
|
|
|
1009
1068
|
* dispatch) eliminates the entire bug class.
|
|
1010
1069
|
*/
|
|
1011
1070
|
function applyStaticProp(el, key, value) {
|
|
1012
|
-
if (__DEV__$
|
|
1071
|
+
if (__DEV__$4 && typeof value === "function") console.warn(`[Pyreon] applyStaticProp received a function for "${key}". This likely means a new special-cased prop sink in applyProp() bypassed the reactive-wrap path. The closure would be stringified and set as a literal value. Verify the dispatch in applyProp().`);
|
|
1013
1072
|
if (key === "innerHTML") {
|
|
1014
1073
|
const html = String(value ?? "");
|
|
1015
1074
|
if (typeof el.setHTML === "function") el.setHTML(html);
|
|
@@ -1076,7 +1135,7 @@ function applyClassProp(el, value) {
|
|
|
1076
1135
|
}
|
|
1077
1136
|
function setStaticProp(el, key, value) {
|
|
1078
1137
|
if (URL_ATTRS.has(key) && typeof value === "string" && UNSAFE_URL_RE.test(value)) {
|
|
1079
|
-
if (__DEV__$
|
|
1138
|
+
if (__DEV__$4) console.warn(`[Pyreon] Blocked unsafe URL in "${key}" attribute: ${value}`);
|
|
1080
1139
|
return;
|
|
1081
1140
|
}
|
|
1082
1141
|
if (key === "class" || key === "className") {
|
|
@@ -1113,10 +1172,12 @@ function setStaticProp(el, key, value) {
|
|
|
1113
1172
|
|
|
1114
1173
|
//#endregion
|
|
1115
1174
|
//#region src/mount.ts
|
|
1116
|
-
const __DEV__$
|
|
1175
|
+
const __DEV__$3 = process.env.NODE_ENV !== "production";
|
|
1176
|
+
const _countSink$2 = globalThis;
|
|
1117
1177
|
const noop$1 = () => {};
|
|
1118
1178
|
let _elementDepth = 0;
|
|
1119
|
-
|
|
1179
|
+
let _mountingStack;
|
|
1180
|
+
if (__DEV__$3) _mountingStack = [];
|
|
1120
1181
|
/**
|
|
1121
1182
|
* Mount a single child into `parent`, inserting before `anchor` (null = append).
|
|
1122
1183
|
* Returns a cleanup that removes the node(s) and disposes all reactive effects.
|
|
@@ -1125,6 +1186,7 @@ const _mountingStack = [];
|
|
|
1125
1186
|
* function call overhead in tight render loops (1000+ calls per list render).
|
|
1126
1187
|
*/
|
|
1127
1188
|
function mountChild(child, parent, anchor = null) {
|
|
1189
|
+
if (__DEV__$3) _countSink$2.__pyreon_count__?.("runtime.mountChild");
|
|
1128
1190
|
if (typeof child === "function") {
|
|
1129
1191
|
const sample = runUntracked(() => child());
|
|
1130
1192
|
if (isKeyedArray(sample)) {
|
|
@@ -1139,7 +1201,8 @@ function mountChild(child, parent, anchor = null) {
|
|
|
1139
1201
|
parent.insertBefore(text, anchor);
|
|
1140
1202
|
const dispose = renderEffect(() => {
|
|
1141
1203
|
const v = child();
|
|
1142
|
-
|
|
1204
|
+
const next = v == null || v === false ? "" : String(v);
|
|
1205
|
+
if (next !== text.data) text.data = next;
|
|
1143
1206
|
});
|
|
1144
1207
|
if (_elementDepth > 0) return dispose;
|
|
1145
1208
|
return () => {
|
|
@@ -1180,24 +1243,26 @@ function mountChild(child, parent, anchor = null) {
|
|
|
1180
1243
|
const vnode = child;
|
|
1181
1244
|
if (vnode.type === Fragment) return mountChildren(vnode.children ?? [], parent, anchor);
|
|
1182
1245
|
if (vnode.type === ForSymbol) {
|
|
1183
|
-
const
|
|
1246
|
+
const props = vnode.props;
|
|
1247
|
+
const initialEach = props.each;
|
|
1248
|
+
const source = typeof initialEach === "function" ? initialEach : (() => props.each);
|
|
1184
1249
|
const prevDepth = _elementDepth;
|
|
1185
1250
|
_elementDepth = 0;
|
|
1186
|
-
const cleanup = mountFor(
|
|
1251
|
+
const cleanup = mountFor(source, props.by, props.children, parent, anchor, mountChild);
|
|
1187
1252
|
_elementDepth = prevDepth;
|
|
1188
1253
|
return cleanup;
|
|
1189
1254
|
}
|
|
1190
1255
|
if (vnode.type === PortalSymbol) {
|
|
1191
1256
|
const { target, children } = vnode.props;
|
|
1192
|
-
if (__DEV__$
|
|
1257
|
+
if (__DEV__$3 && !target) {
|
|
1193
1258
|
console.warn("[Pyreon] <Portal> received a falsy `target`. Provide a valid DOM element.");
|
|
1194
1259
|
return noop$1;
|
|
1195
1260
|
}
|
|
1196
|
-
if (__DEV__$
|
|
1261
|
+
if (__DEV__$3 && !(target instanceof Node)) console.warn(`[Pyreon] <Portal> target must be a DOM node. Received ${typeof target}. Use document.getElementById() or a ref to get the target element.`);
|
|
1197
1262
|
return mountChild(children, target, null);
|
|
1198
1263
|
}
|
|
1199
1264
|
if (typeof vnode.type === "function") return mountComponent(vnode, parent, anchor);
|
|
1200
|
-
if (__DEV__$
|
|
1265
|
+
if (__DEV__$3 && typeof vnode.type !== "string") {
|
|
1201
1266
|
console.warn(`[Pyreon] Invalid VNode type: expected a string tag or component function, received ${typeof vnode.type} (${String(vnode.type)}). This usually means you passed an object or class instead of a component function.`);
|
|
1202
1267
|
return noop$1;
|
|
1203
1268
|
}
|
|
@@ -1312,7 +1377,7 @@ function mountElement(vnode, parent, anchor) {
|
|
|
1312
1377
|
const isMathml = tag === "math";
|
|
1313
1378
|
if (isSvg) _svgDepth++;
|
|
1314
1379
|
if (isMathml) _mathmlDepth++;
|
|
1315
|
-
if (__DEV__$
|
|
1380
|
+
if (__DEV__$3 && (vnode.children?.length ?? 0) > 0 && VOID_ELEMENTS.has(vnode.type)) console.warn(`[Pyreon] <${vnode.type}> is a void element and cannot have children. Children passed to void elements will be ignored by the browser.`);
|
|
1316
1381
|
const props = vnode.props;
|
|
1317
1382
|
const propCleanup = props !== EMPTY_PROPS ? applyProps(el, props) : null;
|
|
1318
1383
|
_elementDepth++;
|
|
@@ -1360,9 +1425,13 @@ function mountComponent(vnode, parent, anchor) {
|
|
|
1360
1425
|
let hooks;
|
|
1361
1426
|
let output;
|
|
1362
1427
|
const componentName = vnode.type.name || "Anonymous";
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1428
|
+
let compId;
|
|
1429
|
+
let devParentId;
|
|
1430
|
+
if (__DEV__$3) {
|
|
1431
|
+
compId = `${componentName}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1432
|
+
devParentId = _mountingStack[_mountingStack.length - 1] ?? null;
|
|
1433
|
+
_mountingStack.push(compId);
|
|
1434
|
+
}
|
|
1366
1435
|
const children = vnode.children ?? [];
|
|
1367
1436
|
const rawProps = children.length > 0 && vnode.props.children === void 0 ? {
|
|
1368
1437
|
...vnode.props,
|
|
@@ -1374,7 +1443,7 @@ function mountComponent(vnode, parent, anchor) {
|
|
|
1374
1443
|
hooks = result.hooks;
|
|
1375
1444
|
output = result.vnode;
|
|
1376
1445
|
} catch (err) {
|
|
1377
|
-
_mountingStack.pop();
|
|
1446
|
+
if (__DEV__$3) _mountingStack.pop();
|
|
1378
1447
|
setCurrentScope(null);
|
|
1379
1448
|
scope.stop();
|
|
1380
1449
|
reportError({
|
|
@@ -1386,7 +1455,7 @@ function mountComponent(vnode, parent, anchor) {
|
|
|
1386
1455
|
});
|
|
1387
1456
|
const handled = dispatchToErrorBoundary(err);
|
|
1388
1457
|
if (!handled) console.error(`[Pyreon] <${componentName}> threw during setup:`, err);
|
|
1389
|
-
if (__DEV__$
|
|
1458
|
+
if (__DEV__$3 && !handled) {
|
|
1390
1459
|
const overlay = document.createElement("pre");
|
|
1391
1460
|
overlay.style.cssText = "color:#e53e3e;background:#fff5f5;padding:12px;border:2px solid #e53e3e;border-radius:6px;font-size:12px;margin:4px;font-family:monospace;white-space:pre-wrap;word-break:break-word";
|
|
1392
1461
|
const e = err;
|
|
@@ -1398,16 +1467,16 @@ function mountComponent(vnode, parent, anchor) {
|
|
|
1398
1467
|
} finally {
|
|
1399
1468
|
setCurrentScope(null);
|
|
1400
1469
|
}
|
|
1401
|
-
if (__DEV__$
|
|
1470
|
+
if (__DEV__$3 && output != null && typeof output === "object") {
|
|
1402
1471
|
if (output instanceof Promise) console.warn(`[Pyreon] Component <${componentName}> returned a Promise. Components must be synchronous — use lazy() + Suspense for async loading, or fetch data in onMount and store it in a signal.`);
|
|
1403
1472
|
else if (!("type" in output) && !Array.isArray(output) && !output.__isNative) console.warn(`[Pyreon] Component <${componentName}> returned an invalid value. Components must return a VNode, string, null, function, or array.`);
|
|
1404
1473
|
}
|
|
1405
|
-
for (const fn of hooks.update) scope.addUpdateHook(fn);
|
|
1474
|
+
if (hooks.update) for (const fn of hooks.update) scope.addUpdateHook(fn);
|
|
1406
1475
|
let subtreeCleanup = noop$1;
|
|
1407
1476
|
try {
|
|
1408
1477
|
subtreeCleanup = output != null ? mountChild(output, parent, anchor) : noop$1;
|
|
1409
1478
|
} catch (err) {
|
|
1410
|
-
_mountingStack.pop();
|
|
1479
|
+
if (__DEV__$3) _mountingStack.pop();
|
|
1411
1480
|
scope.stop();
|
|
1412
1481
|
if (!(propagateError(err, hooks) || dispatchToErrorBoundary(err))) {
|
|
1413
1482
|
reportError({
|
|
@@ -1421,15 +1490,21 @@ function mountComponent(vnode, parent, anchor) {
|
|
|
1421
1490
|
}
|
|
1422
1491
|
return noop$1;
|
|
1423
1492
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1493
|
+
if (__DEV__$3) {
|
|
1494
|
+
_mountingStack.pop();
|
|
1495
|
+
const firstEl = parent instanceof Element ? parent.firstElementChild : null;
|
|
1496
|
+
registerComponent(compId, componentName, firstEl, devParentId);
|
|
1497
|
+
}
|
|
1498
|
+
let mountCleanups = null;
|
|
1499
|
+
if (hooks.mount) for (const fn of hooks.mount) try {
|
|
1428
1500
|
let cleanup;
|
|
1429
1501
|
scope.runInScope(() => {
|
|
1430
1502
|
cleanup = fn();
|
|
1431
1503
|
});
|
|
1432
|
-
if (cleanup)
|
|
1504
|
+
if (cleanup) {
|
|
1505
|
+
if (mountCleanups === null) mountCleanups = [];
|
|
1506
|
+
mountCleanups.push(cleanup);
|
|
1507
|
+
}
|
|
1433
1508
|
} catch (err) {
|
|
1434
1509
|
console.error(`[Pyreon] Error in onMount hook of <${componentName}>:`, err);
|
|
1435
1510
|
reportError({
|
|
@@ -1440,10 +1515,10 @@ function mountComponent(vnode, parent, anchor) {
|
|
|
1440
1515
|
});
|
|
1441
1516
|
}
|
|
1442
1517
|
return () => {
|
|
1443
|
-
unregisterComponent(compId);
|
|
1518
|
+
if (__DEV__$3) unregisterComponent(compId);
|
|
1444
1519
|
scope.stop();
|
|
1445
1520
|
subtreeCleanup();
|
|
1446
|
-
for (const fn of hooks.unmount) try {
|
|
1521
|
+
if (hooks.unmount) for (const fn of hooks.unmount) try {
|
|
1447
1522
|
fn();
|
|
1448
1523
|
} catch (err) {
|
|
1449
1524
|
console.error(`[Pyreon] Error in onUnmount hook of <${componentName}>:`, err);
|
|
@@ -1454,7 +1529,7 @@ function mountComponent(vnode, parent, anchor) {
|
|
|
1454
1529
|
timestamp: Date.now()
|
|
1455
1530
|
});
|
|
1456
1531
|
}
|
|
1457
|
-
for (const fn of mountCleanups) fn();
|
|
1532
|
+
if (mountCleanups) for (const fn of mountCleanups) fn();
|
|
1458
1533
|
};
|
|
1459
1534
|
}
|
|
1460
1535
|
function mountChildren(children, parent, anchor) {
|
|
@@ -1647,10 +1722,10 @@ function hydrateComponent(vnode, domNode, parent, anchor, path = "root") {
|
|
|
1647
1722
|
const mountCleanups = [];
|
|
1648
1723
|
let nextDom = domNode;
|
|
1649
1724
|
const componentName = vnode.type.name || "Anonymous";
|
|
1650
|
-
const mergedProps = (vnode.children ?? []).length > 0 && vnode.props.children === void 0 ? {
|
|
1725
|
+
const mergedProps = makeReactiveProps((vnode.children ?? []).length > 0 && vnode.props.children === void 0 ? {
|
|
1651
1726
|
...vnode.props,
|
|
1652
1727
|
children: (vnode.children ?? []).length === 1 ? (vnode.children ?? [])[0] : vnode.children ?? []
|
|
1653
|
-
} : vnode.props;
|
|
1728
|
+
} : vnode.props);
|
|
1654
1729
|
let result;
|
|
1655
1730
|
try {
|
|
1656
1731
|
result = runWithHooks(vnode.type, mergedProps);
|
|
@@ -1670,13 +1745,13 @@ function hydrateComponent(vnode, domNode, parent, anchor, path = "root") {
|
|
|
1670
1745
|
}
|
|
1671
1746
|
setCurrentScope(null);
|
|
1672
1747
|
const { vnode: output, hooks } = result;
|
|
1673
|
-
for (const fn of hooks.update) scope.addUpdateHook(fn);
|
|
1748
|
+
if (hooks.update) for (const fn of hooks.update) scope.addUpdateHook(fn);
|
|
1674
1749
|
if (output != null) {
|
|
1675
1750
|
const [childCleanup, next] = hydrateChild(output, domNode, parent, anchor, path);
|
|
1676
1751
|
subtreeCleanup = childCleanup;
|
|
1677
1752
|
nextDom = next;
|
|
1678
1753
|
}
|
|
1679
|
-
for (const fn of hooks.mount) try {
|
|
1754
|
+
if (hooks.mount) for (const fn of hooks.mount) try {
|
|
1680
1755
|
let c;
|
|
1681
1756
|
scope.runInScope(() => {
|
|
1682
1757
|
c = fn();
|
|
@@ -1693,7 +1768,7 @@ function hydrateComponent(vnode, domNode, parent, anchor, path = "root") {
|
|
|
1693
1768
|
const cleanup = () => {
|
|
1694
1769
|
scope.stop();
|
|
1695
1770
|
subtreeCleanup();
|
|
1696
|
-
for (const fn of hooks.unmount) fn();
|
|
1771
|
+
if (hooks.unmount) for (const fn of hooks.unmount) fn();
|
|
1697
1772
|
for (const fn of mountCleanups) fn();
|
|
1698
1773
|
};
|
|
1699
1774
|
return [cleanup, nextDom];
|
|
@@ -1769,9 +1844,12 @@ function KeepAlive(props) {
|
|
|
1769
1844
|
style: "display: contents"
|
|
1770
1845
|
});
|
|
1771
1846
|
}
|
|
1847
|
+
nativeCompat(KeepAlive);
|
|
1772
1848
|
|
|
1773
1849
|
//#endregion
|
|
1774
1850
|
//#region src/template.ts
|
|
1851
|
+
const __DEV__$2 = process.env.NODE_ENV !== "production";
|
|
1852
|
+
const _countSink$1 = globalThis;
|
|
1775
1853
|
/**
|
|
1776
1854
|
* Creates a row/item factory backed by HTML template cloning.
|
|
1777
1855
|
*
|
|
@@ -1830,7 +1908,8 @@ function _bindText(source, node) {
|
|
|
1830
1908
|
if (source.direct) {
|
|
1831
1909
|
const textUpdate = () => {
|
|
1832
1910
|
const v = source._v;
|
|
1833
|
-
|
|
1911
|
+
const next = v == null || v === false ? "" : String(v);
|
|
1912
|
+
if (next !== node.data) node.data = next;
|
|
1834
1913
|
};
|
|
1835
1914
|
textUpdate();
|
|
1836
1915
|
return source.direct(textUpdate);
|
|
@@ -1838,7 +1917,8 @@ function _bindText(source, node) {
|
|
|
1838
1917
|
const fn = source;
|
|
1839
1918
|
return renderEffect(() => {
|
|
1840
1919
|
const v = fn();
|
|
1841
|
-
|
|
1920
|
+
const next = v == null || v === false ? "" : String(v);
|
|
1921
|
+
if (next !== node.data) node.data = next;
|
|
1842
1922
|
});
|
|
1843
1923
|
}
|
|
1844
1924
|
/**
|
|
@@ -1865,6 +1945,7 @@ function _bindDirect(source, updater) {
|
|
|
1865
1945
|
const fn = source;
|
|
1866
1946
|
return renderEffect(() => updater(fn()));
|
|
1867
1947
|
}
|
|
1948
|
+
const TPL_CACHE_MAX = 1024;
|
|
1868
1949
|
const _tplCache = /* @__PURE__ */ new Map();
|
|
1869
1950
|
/**
|
|
1870
1951
|
* Compiler-emitted template instantiation.
|
|
@@ -1890,10 +1971,18 @@ const _tplCache = /* @__PURE__ */ new Map();
|
|
|
1890
1971
|
* })
|
|
1891
1972
|
*/
|
|
1892
1973
|
function _tpl(html, bind) {
|
|
1974
|
+
if (__DEV__$2) _countSink$1.__pyreon_count__?.("runtime.tpl");
|
|
1893
1975
|
let tpl = _tplCache.get(html);
|
|
1894
1976
|
if (!tpl) {
|
|
1895
1977
|
tpl = document.createElement("template");
|
|
1896
1978
|
tpl.innerHTML = html;
|
|
1979
|
+
if (_tplCache.size >= TPL_CACHE_MAX) {
|
|
1980
|
+
const oldest = _tplCache.keys().next().value;
|
|
1981
|
+
if (oldest !== void 0) _tplCache.delete(oldest);
|
|
1982
|
+
}
|
|
1983
|
+
_tplCache.set(html, tpl);
|
|
1984
|
+
} else {
|
|
1985
|
+
_tplCache.delete(html);
|
|
1897
1986
|
_tplCache.set(html, tpl);
|
|
1898
1987
|
}
|
|
1899
1988
|
const el = tpl.content.firstElementChild?.cloneNode(true);
|
|
@@ -1928,7 +2017,7 @@ function _mountSlot(children, parent, placeholder) {
|
|
|
1928
2017
|
|
|
1929
2018
|
//#endregion
|
|
1930
2019
|
//#region src/transition.ts
|
|
1931
|
-
const __DEV__$1 =
|
|
2020
|
+
const __DEV__$1 = process.env.NODE_ENV !== "production";
|
|
1932
2021
|
/**
|
|
1933
2022
|
* Transition — adds CSS enter/leave animation classes to a single child element,
|
|
1934
2023
|
* controlled by the reactive `show` prop.
|
|
@@ -2087,6 +2176,7 @@ function Transition(props) {
|
|
|
2087
2176
|
};
|
|
2088
2177
|
});
|
|
2089
2178
|
}
|
|
2179
|
+
nativeCompat(Transition);
|
|
2090
2180
|
|
|
2091
2181
|
//#endregion
|
|
2092
2182
|
//#region src/transition-group.ts
|
|
@@ -2340,10 +2430,12 @@ function TransitionGroup(props) {
|
|
|
2340
2430
|
});
|
|
2341
2431
|
return h(tag, { ref: containerRef });
|
|
2342
2432
|
}
|
|
2433
|
+
nativeCompat(TransitionGroup);
|
|
2343
2434
|
|
|
2344
2435
|
//#endregion
|
|
2345
2436
|
//#region src/index.ts
|
|
2346
|
-
const __DEV__ =
|
|
2437
|
+
const __DEV__ = process.env.NODE_ENV !== "production";
|
|
2438
|
+
const _countSink = globalThis;
|
|
2347
2439
|
/**
|
|
2348
2440
|
* Mount a VNode tree into a container element.
|
|
2349
2441
|
* Clears the container first, then mounts the given child.
|
|
@@ -2354,14 +2446,21 @@ const __DEV__ = import.meta.env?.DEV === true;
|
|
|
2354
2446
|
*/
|
|
2355
2447
|
function mount(root, container) {
|
|
2356
2448
|
if (__DEV__ && container == null) throw new Error("[pyreon] mount() called with a null/undefined container. Make sure the element exists in the DOM, e.g. document.getElementById(\"app\")");
|
|
2357
|
-
|
|
2449
|
+
if (__DEV__) {
|
|
2450
|
+
_countSink.__pyreon_count__?.("runtime.mount");
|
|
2451
|
+
installDevTools();
|
|
2452
|
+
}
|
|
2358
2453
|
setupDelegation(container);
|
|
2359
2454
|
container.innerHTML = "";
|
|
2360
|
-
|
|
2455
|
+
const unmount = mountChild(root, container, null);
|
|
2456
|
+
return () => {
|
|
2457
|
+
if (__DEV__) _countSink.__pyreon_count__?.("runtime.unmount");
|
|
2458
|
+
unmount();
|
|
2459
|
+
};
|
|
2361
2460
|
}
|
|
2362
2461
|
/** Alias for `mount` */
|
|
2363
2462
|
const render = mount;
|
|
2364
2463
|
|
|
2365
2464
|
//#endregion
|
|
2366
|
-
export { DELEGATED_EVENTS, KeepAlive, Transition, TransitionGroup, applyProps as _applyProps, applyProps, _bindDirect, _bindText, _mountSlot, _tpl, applyProp, createTemplate, delegatedPropName, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, render, sanitizeHtml, setSanitizer, setupDelegation };
|
|
2465
|
+
export { DELEGATED_EVENTS, KeepAlive, Transition, TransitionGroup, applyProps as _applyProps, applyProps, _bindDirect, _bindText, _mountSlot, _tpl, applyProp, createTemplate, delegatedPropName, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, onHydrationMismatch, render, sanitizeHtml, setSanitizer, setupDelegation };
|
|
2367
2466
|
//# sourceMappingURL=index.js.map
|