@pyreon/runtime-dom 0.23.0 → 0.24.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/lib/_chunks/{keep-alive-BM7bn3W9.js → keep-alive-DznjF_h1.js} +9 -7
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +138 -3
- package/lib/keep-alive-entry.js +1 -1
- package/lib/types/index.d.ts +107 -1
- package/package.json +6 -6
- package/src/index.ts +2 -0
- package/src/nodes.ts +27 -6
- package/src/template.ts +186 -1
- package/src/tests/keyed-array-in-for-batched-toggle.browser.test.ts +140 -0
- package/src/tests/rs-collapse-dyn-h.browser.test.ts +303 -0
- package/src/tests/rs-collapse-dyn.browser.test.ts +316 -0
- package/src/tests/show-of-for-batched-toggle.browser.test.ts +122 -0
|
@@ -364,7 +364,8 @@ function mountReactive(accessor, parent, anchor, mount) {
|
|
|
364
364
|
hasCleanup = false;
|
|
365
365
|
const value = accessor();
|
|
366
366
|
if (value != null && value !== false) {
|
|
367
|
-
const
|
|
367
|
+
const liveParent = marker.parentNode ?? parent;
|
|
368
|
+
const cleanup = runUntracked(() => restoreContextStack(contextSnapshot, () => mount(value, liveParent, marker)));
|
|
368
369
|
if (myGen === generation) {
|
|
369
370
|
currentCleanup = cleanup;
|
|
370
371
|
hasCleanup = true;
|
|
@@ -483,15 +484,15 @@ function mountKeyedList(accessor, parent, listAnchor, mountVNode) {
|
|
|
483
484
|
curPos.delete(key);
|
|
484
485
|
}
|
|
485
486
|
};
|
|
486
|
-
const mountNewEntries = (newList) => {
|
|
487
|
+
const mountNewEntries = (newList, liveParent) => {
|
|
487
488
|
for (const vnode of newList) {
|
|
488
489
|
const key = vnode.key;
|
|
489
490
|
if (key === null || key === void 0) continue;
|
|
490
491
|
if (cache.has(key)) continue;
|
|
491
492
|
const anchor = document.createComment("");
|
|
492
493
|
_keyedAnchors.add(anchor);
|
|
493
|
-
|
|
494
|
-
const cleanup = mountVNode(vnode,
|
|
494
|
+
liveParent.insertBefore(anchor, tailMarker);
|
|
495
|
+
const cleanup = mountVNode(vnode, liveParent, tailMarker);
|
|
495
496
|
cache.set(key, {
|
|
496
497
|
anchor,
|
|
497
498
|
cleanup
|
|
@@ -502,6 +503,7 @@ function mountKeyedList(accessor, parent, listAnchor, mountVNode) {
|
|
|
502
503
|
const newList = accessor();
|
|
503
504
|
const n = newList.length;
|
|
504
505
|
runUntracked(() => {
|
|
506
|
+
const liveParent = tailMarker.parentNode ?? parent;
|
|
505
507
|
if (n === 0 && cache.size > 0) {
|
|
506
508
|
for (const entry of cache.values()) {
|
|
507
509
|
_emitCleanup();
|
|
@@ -515,8 +517,8 @@ function mountKeyedList(accessor, parent, listAnchor, mountVNode) {
|
|
|
515
517
|
}
|
|
516
518
|
const { newKeyOrder, newKeySet } = collectKeyOrder(newList);
|
|
517
519
|
removeStaleEntries(newKeySet);
|
|
518
|
-
mountNewEntries(newList);
|
|
519
|
-
if (currentKeyOrder.length > 0 && n > 0) lis = keyedListReorder(lis, n, newKeyOrder, curPos, cache,
|
|
520
|
+
mountNewEntries(newList, liveParent);
|
|
521
|
+
if (currentKeyOrder.length > 0 && n > 0) lis = keyedListReorder(lis, n, newKeyOrder, curPos, cache, liveParent, tailMarker);
|
|
520
522
|
curPos.clear();
|
|
521
523
|
for (let i = 0; i < newKeyOrder.length; i++) {
|
|
522
524
|
const k = newKeyOrder[i];
|
|
@@ -1654,4 +1656,4 @@ nativeCompat(KeepAlive);
|
|
|
1654
1656
|
|
|
1655
1657
|
//#endregion
|
|
1656
1658
|
export { applyProps as a, mountReactive as c, delegatedPropName as d, setupDelegation as f, applyProp as i, installDevTools as l, mountChild as n, sanitizeHtml as o, _bindEvent as r, setSanitizer as s, KeepAlive as t, DELEGATED_EVENTS as u };
|
|
1657
|
-
//# sourceMappingURL=keep-alive-
|
|
1659
|
+
//# sourceMappingURL=keep-alive-DznjF_h1.js.map
|
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"baaa0631-1","name":"hydration-debug.ts"},{"uid":"baaa0631-3","name":"hydrate.ts"},{"uid":"baaa0631-5","name":"template.ts"},{"uid":"baaa0631-7","name":"transition-group.ts"},{"uid":"baaa0631-9","name":"index.ts"}]}]},{"name":"keep-alive-entry.js","uid":"baaa0631-11"},{"name":"transition-entry.js","children":[{"name":"src/transition.ts","uid":"baaa0631-13"}]},{"name":"_chunks/keep-alive-DznjF_h1.js","children":[{"name":"src","children":[{"uid":"baaa0631-15","name":"delegate.ts"},{"uid":"baaa0631-17","name":"devtools.ts"},{"uid":"baaa0631-19","name":"nodes.ts"},{"uid":"baaa0631-21","name":"props.ts"},{"uid":"baaa0631-23","name":"mount.ts"},{"uid":"baaa0631-24","name":"keep-alive.ts"}]}]}],"isRoot":true},"nodeParts":{"baaa0631-1":{"renderedLength":1395,"gzipLength":718,"brotliLength":0,"metaUid":"baaa0631-0"},"baaa0631-3":{"renderedLength":8312,"gzipLength":2472,"brotliLength":0,"metaUid":"baaa0631-2"},"baaa0631-5":{"renderedLength":15703,"gzipLength":5438,"brotliLength":0,"metaUid":"baaa0631-4"},"baaa0631-7":{"renderedLength":8002,"gzipLength":2092,"brotliLength":0,"metaUid":"baaa0631-6"},"baaa0631-9":{"renderedLength":985,"gzipLength":549,"brotliLength":0,"metaUid":"baaa0631-8"},"baaa0631-11":{"id":"keep-alive-entry.js","gzipLength":93,"brotliLength":0,"renderedLength":89,"metaUid":"baaa0631-10"},"baaa0631-13":{"renderedLength":4925,"gzipLength":1407,"brotliLength":0,"metaUid":"baaa0631-12"},"baaa0631-15":{"renderedLength":2090,"gzipLength":1029,"brotliLength":0,"metaUid":"baaa0631-14"},"baaa0631-17":{"renderedLength":8163,"gzipLength":2538,"brotliLength":0,"metaUid":"baaa0631-16"},"baaa0631-19":{"renderedLength":17702,"gzipLength":4689,"brotliLength":0,"metaUid":"baaa0631-18"},"baaa0631-21":{"renderedLength":9054,"gzipLength":3495,"brotliLength":0,"metaUid":"baaa0631-20"},"baaa0631-23":{"renderedLength":12162,"gzipLength":3873,"brotliLength":0,"metaUid":"baaa0631-22"},"baaa0631-24":{"renderedLength":1518,"gzipLength":724,"brotliLength":0,"metaUid":"baaa0631-10"}},"nodeMetas":{"baaa0631-0":{"id":"/src/hydration-debug.ts","moduleParts":{"index.js":"baaa0631-1"},"imported":[],"importedBy":[{"uid":"baaa0631-8"},{"uid":"baaa0631-2"}]},"baaa0631-2":{"id":"/src/hydrate.ts","moduleParts":{"index.js":"baaa0631-3"},"imported":[{"uid":"baaa0631-26"},{"uid":"baaa0631-25"},{"uid":"baaa0631-14"},{"uid":"baaa0631-0"},{"uid":"baaa0631-22"},{"uid":"baaa0631-18"},{"uid":"baaa0631-20"}],"importedBy":[{"uid":"baaa0631-8"}]},"baaa0631-4":{"id":"/src/template.ts","moduleParts":{"index.js":"baaa0631-5"},"imported":[{"uid":"baaa0631-25"},{"uid":"baaa0631-22"},{"uid":"baaa0631-20"}],"importedBy":[{"uid":"baaa0631-8"}]},"baaa0631-6":{"id":"/src/transition-group.ts","moduleParts":{"index.js":"baaa0631-7"},"imported":[{"uid":"baaa0631-26"},{"uid":"baaa0631-25"},{"uid":"baaa0631-22"}],"importedBy":[{"uid":"baaa0631-8"}]},"baaa0631-8":{"id":"/src/index.ts","moduleParts":{"index.js":"baaa0631-9"},"imported":[{"uid":"baaa0631-14"},{"uid":"baaa0631-2"},{"uid":"baaa0631-0"},{"uid":"baaa0631-10"},{"uid":"baaa0631-22"},{"uid":"baaa0631-20"},{"uid":"baaa0631-4"},{"uid":"baaa0631-12"},{"uid":"baaa0631-6"},{"uid":"baaa0631-16"}],"importedBy":[],"isEntry":true},"baaa0631-10":{"id":"/src/keep-alive.ts","moduleParts":{"keep-alive-entry.js":"baaa0631-11","_chunks/keep-alive-DznjF_h1.js":"baaa0631-24"},"imported":[{"uid":"baaa0631-26"},{"uid":"baaa0631-25"},{"uid":"baaa0631-22"}],"importedBy":[{"uid":"baaa0631-8"}],"isEntry":true},"baaa0631-12":{"id":"/src/transition.ts","moduleParts":{"transition-entry.js":"baaa0631-13"},"imported":[{"uid":"baaa0631-26"},{"uid":"baaa0631-25"}],"importedBy":[{"uid":"baaa0631-8"}],"isEntry":true},"baaa0631-14":{"id":"/src/delegate.ts","moduleParts":{"_chunks/keep-alive-DznjF_h1.js":"baaa0631-15"},"imported":[{"uid":"baaa0631-25"}],"importedBy":[{"uid":"baaa0631-8"},{"uid":"baaa0631-2"},{"uid":"baaa0631-20"}]},"baaa0631-16":{"id":"/src/devtools.ts","moduleParts":{"_chunks/keep-alive-DznjF_h1.js":"baaa0631-17"},"imported":[{"uid":"baaa0631-25"}],"importedBy":[{"uid":"baaa0631-8"},{"uid":"baaa0631-22"}]},"baaa0631-18":{"id":"/src/nodes.ts","moduleParts":{"_chunks/keep-alive-DznjF_h1.js":"baaa0631-19"},"imported":[{"uid":"baaa0631-26"},{"uid":"baaa0631-25"}],"importedBy":[{"uid":"baaa0631-2"},{"uid":"baaa0631-22"}]},"baaa0631-20":{"id":"/src/props.ts","moduleParts":{"_chunks/keep-alive-DznjF_h1.js":"baaa0631-21"},"imported":[{"uid":"baaa0631-26"},{"uid":"baaa0631-25"},{"uid":"baaa0631-14"}],"importedBy":[{"uid":"baaa0631-8"},{"uid":"baaa0631-2"},{"uid":"baaa0631-22"},{"uid":"baaa0631-4"}]},"baaa0631-22":{"id":"/src/mount.ts","moduleParts":{"_chunks/keep-alive-DznjF_h1.js":"baaa0631-23"},"imported":[{"uid":"baaa0631-26"},{"uid":"baaa0631-25"},{"uid":"baaa0631-16"},{"uid":"baaa0631-18"},{"uid":"baaa0631-20"}],"importedBy":[{"uid":"baaa0631-8"},{"uid":"baaa0631-2"},{"uid":"baaa0631-10"},{"uid":"baaa0631-4"},{"uid":"baaa0631-6"}]},"baaa0631-25":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"baaa0631-14"},{"uid":"baaa0631-2"},{"uid":"baaa0631-10"},{"uid":"baaa0631-22"},{"uid":"baaa0631-20"},{"uid":"baaa0631-4"},{"uid":"baaa0631-12"},{"uid":"baaa0631-6"},{"uid":"baaa0631-16"},{"uid":"baaa0631-18"}]},"baaa0631-26":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"baaa0631-2"},{"uid":"baaa0631-10"},{"uid":"baaa0631-22"},{"uid":"baaa0631-20"},{"uid":"baaa0631-12"},{"uid":"baaa0631-6"},{"uid":"baaa0631-18"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as applyProps, c as mountReactive, d as delegatedPropName, f as setupDelegation, i as applyProp, l as installDevTools, n as mountChild, o as sanitizeHtml, r as _bindEvent, s as setSanitizer, t as KeepAlive, u as DELEGATED_EVENTS } from "./_chunks/keep-alive-
|
|
1
|
+
import { a as applyProps, c as mountReactive, d as delegatedPropName, f as setupDelegation, i as applyProp, l as installDevTools, n as mountChild, o as sanitizeHtml, r as _bindEvent, s as setSanitizer, t as KeepAlive, u as DELEGATED_EVENTS } from "./_chunks/keep-alive-DznjF_h1.js";
|
|
2
2
|
import { Transition } from "./transition-entry.js";
|
|
3
3
|
import { effect, effectScope, renderEffect, runUntracked, setCurrentScope, signal } from "@pyreon/reactivity";
|
|
4
4
|
import { ForSymbol, Fragment, PortalSymbol, createRef, dispatchToErrorBoundary, h, makeReactiveProps, nativeCompat, onMount, onUnmount, reportError, runWithHooks } from "@pyreon/core";
|
|
@@ -490,7 +490,142 @@ function _rsCollapseH(html, lightClass, darkClass, isDark, handlers, bind) {
|
|
|
490
490
|
el.className = v ? darkClass : lightClass;
|
|
491
491
|
});
|
|
492
492
|
const handlerDisposers = [];
|
|
493
|
-
for (const key
|
|
493
|
+
for (const key of Object.keys(handlers)) {
|
|
494
|
+
const d = _bindEvent(el, key, handlers[key]);
|
|
495
|
+
if (d) handlerDisposers.push(d);
|
|
496
|
+
}
|
|
497
|
+
const disposeChildren = bind ? bind(el) : null;
|
|
498
|
+
return () => {
|
|
499
|
+
disposeClass();
|
|
500
|
+
for (const d of handlerDisposers) d();
|
|
501
|
+
if (disposeChildren) disposeChildren();
|
|
502
|
+
};
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Compiler-emitted DYNAMIC-prop collapsed rocketstyle call site — PR 1
|
|
507
|
+
* of the dynamic-prop partial-collapse build (next bite after the
|
|
508
|
+
* `on*`-handler partial-collapse `_rsCollapseH`, `.claude/plans/open-work-2026-q3.md`
|
|
509
|
+
* → #1 dynamic-prop bucket = 15.3% of all real-corpus sites).
|
|
510
|
+
*
|
|
511
|
+
* Generalises {@link _rsCollapse}'s 2-class (light/dark) dispatch to an
|
|
512
|
+
* N-class dispatch for sites where one dimension prop is an enumerable
|
|
513
|
+
* dynamic expression (e.g. `<Button state={cond ? 'primary' : 'secondary'}>`).
|
|
514
|
+
* The compiler resolves EVERY value of that prop through the existing
|
|
515
|
+
* SSR-render resolver (so each value gets its own light + dark class
|
|
516
|
+
* baked in, byte-identical to a `_rsCollapse` site for that value), and
|
|
517
|
+
* the runtime picks the right `(value × mode)` class via the user's
|
|
518
|
+
* expression.
|
|
519
|
+
*
|
|
520
|
+
* Class layout in `classes` is **stride-2, value-major**: index
|
|
521
|
+
* `2 * valueIndex + (isDark ? 1 : 0)`. For the canonical ternary case:
|
|
522
|
+
*
|
|
523
|
+
* ```
|
|
524
|
+
* <Button state={cond ? 'primary' : 'secondary'}>Save</Button>
|
|
525
|
+
* →
|
|
526
|
+
* __rsCollapseDyn(
|
|
527
|
+
* "<button>Save</button>",
|
|
528
|
+
* ["btn-primary-light", "btn-primary-dark", "btn-secondary-light", "btn-secondary-dark"],
|
|
529
|
+
* () => cond ? 0 : 1,
|
|
530
|
+
* () => __pyrMode() === "dark"
|
|
531
|
+
* )
|
|
532
|
+
* ```
|
|
533
|
+
*
|
|
534
|
+
* Both the value expression AND the mode accessor are reactive: a change
|
|
535
|
+
* to either re-runs ONLY this className assignment, no remount (same
|
|
536
|
+
* contract as `_rsCollapse`'s mode flip). Both dispatches share a single
|
|
537
|
+
* `_bindDirect` so reading both inside one effect subscribes once per
|
|
538
|
+
* source — Pyreon's effect dedupe handles the rest.
|
|
539
|
+
*
|
|
540
|
+
* The structural HTML template is shared across every value (asserted
|
|
541
|
+
* by the resolver — divergent markup between values bails the collapse).
|
|
542
|
+
* Mirrors `_rsCollapse`'s mode-divergence-bails invariant.
|
|
543
|
+
*
|
|
544
|
+
* `bind` follows the same contract as `_rsCollapse` — standard `_tpl`
|
|
545
|
+
* child/event binder, runs after class binding, disposers chained.
|
|
546
|
+
*
|
|
547
|
+
* @param html static element HTML WITHOUT the root `class=` attr
|
|
548
|
+
* @param classes flat array of `2 × valueCount` class strings,
|
|
549
|
+
* indexed `[v0_light, v0_dark, v1_light, v1_dark, ...]`. The runtime
|
|
550
|
+
* does no validation — the compiler is the source of truth (an
|
|
551
|
+
* out-of-range `valueIndex()` would coerce to `undefined` className,
|
|
552
|
+
* which is correct-for-zero-style — never crashes)
|
|
553
|
+
* @param valueIndex user expression returning 0..valueCount-1 — reactive
|
|
554
|
+
* @param isDark app mode accessor — reactive
|
|
555
|
+
* @param bind standard _tpl binder for children/events (or null)
|
|
556
|
+
*/
|
|
557
|
+
function _rsCollapseDyn(html, classes, valueIndex, isDark, bind) {
|
|
558
|
+
return _tpl(html, (el) => {
|
|
559
|
+
const disposeClass = renderEffect(() => {
|
|
560
|
+
el.className = classes[valueIndex() << 1 | (isDark() ? 1 : 0)] ?? "";
|
|
561
|
+
});
|
|
562
|
+
const disposeChildren = bind ? bind(el) : null;
|
|
563
|
+
if (!disposeChildren) return disposeClass;
|
|
564
|
+
return () => {
|
|
565
|
+
disposeClass();
|
|
566
|
+
disposeChildren();
|
|
567
|
+
};
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Compiler-emitted DYNAMIC-prop + HANDLER collapsed rocketstyle call
|
|
572
|
+
* site — closes the largest remaining real-corpus dynamic-collapse
|
|
573
|
+
* gap (`.claude/plans/open-work-2026-q3.md` → #1 dynamic-prop bucket
|
|
574
|
+
* = 15.4% of all real-corpus sites; the strict no-handler subset was
|
|
575
|
+
* only 0.2% measured; this helper unlocks the handler-combined slice
|
|
576
|
+
* that was bailed by `tryDynamicCollapse` in PR #767 by design).
|
|
577
|
+
*
|
|
578
|
+
* Combines {@link _rsCollapseDyn}'s value-major class dispatch with
|
|
579
|
+
* {@link _rsCollapseH}'s handler re-attachment. Handlers are orthogonal
|
|
580
|
+
* to both the SSR-resolved styler class AND the value dispatcher (a
|
|
581
|
+
* `state={cond ? 'a' : 'b'} onClick={h}` site's onClick is identical
|
|
582
|
+
* for both `state="a"` and `state="b"` resolutions — the styler class
|
|
583
|
+
* varies, the handler does not). So this helper is structurally the
|
|
584
|
+
* union of the two, no new behavior:
|
|
585
|
+
*
|
|
586
|
+
* ```
|
|
587
|
+
* <Button state={cond ? 'primary' : 'secondary'} onClick={go}>Save</Button>
|
|
588
|
+
* →
|
|
589
|
+
* __rsCollapseDynH(
|
|
590
|
+
* "<button>Save</button>",
|
|
591
|
+
* ["pri-L", "pri-D", "sec-L", "sec-D"],
|
|
592
|
+
* () => cond ? 0 : 1,
|
|
593
|
+
* () => __pyrMode() === "dark",
|
|
594
|
+
* { onClick: go }
|
|
595
|
+
* )
|
|
596
|
+
* ```
|
|
597
|
+
*
|
|
598
|
+
* Class layout matches `_rsCollapseDyn` (stride-2 value-major):
|
|
599
|
+
* `index = 2 * valueIndex + (isDark ? 1 : 0)`. Handler attachment
|
|
600
|
+
* matches `_rsCollapseH` — routed through the canonical `_bindEvent`
|
|
601
|
+
* → `applyEventProp` path (delegation + batching + name
|
|
602
|
+
* normalization). All three reactives (valueIndex, isDark, handlers
|
|
603
|
+
* — though handler identity is captured at the call site) compose
|
|
604
|
+
* cleanly: a value flip OR a mode flip patches className IN PLACE
|
|
605
|
+
* on the SAME node, handlers stay attached across both.
|
|
606
|
+
*
|
|
607
|
+
* Layer-pure: no styler / ui-core imports (the styler injection is
|
|
608
|
+
* the emitted code's job via `__rsSheet.injectRules`).
|
|
609
|
+
*
|
|
610
|
+
* @param html static element HTML WITHOUT the root `class=` attr
|
|
611
|
+
* @param classes flat array of `2 × valueCount` class strings, indexed
|
|
612
|
+
* `[v0_L, v0_D, v1_L, v1_D, …]`
|
|
613
|
+
* @param valueIndex user expression returning 0..valueCount-1 — reactive
|
|
614
|
+
* @param isDark app mode accessor — reactive
|
|
615
|
+
* @param handlers `{ onClick: fn, onPointerEnter: fn, … }` — the
|
|
616
|
+
* residual handlers peeled off the call site by the
|
|
617
|
+
* compiler's emit (sliced source spans re-emitted
|
|
618
|
+
* verbatim, paren-wrapped to keep arrow / sequence
|
|
619
|
+
* expressions a single value)
|
|
620
|
+
* @param bind standard _tpl binder for children/events (or null)
|
|
621
|
+
*/
|
|
622
|
+
function _rsCollapseDynH(html, classes, valueIndex, isDark, handlers, bind) {
|
|
623
|
+
return _tpl(html, (el) => {
|
|
624
|
+
const disposeClass = renderEffect(() => {
|
|
625
|
+
el.className = classes[valueIndex() << 1 | (isDark() ? 1 : 0)] ?? "";
|
|
626
|
+
});
|
|
627
|
+
const handlerDisposers = [];
|
|
628
|
+
for (const key of Object.keys(handlers)) {
|
|
494
629
|
const d = _bindEvent(el, key, handlers[key]);
|
|
495
630
|
if (d) handlerDisposers.push(d);
|
|
496
631
|
}
|
|
@@ -811,5 +946,5 @@ function mount(root, container) {
|
|
|
811
946
|
const render = mount;
|
|
812
947
|
|
|
813
948
|
//#endregion
|
|
814
|
-
export { DELEGATED_EVENTS, KeepAlive, Transition, TransitionGroup, applyProps as _applyProps, applyProps, _bindDirect, _bindText, _mountSlot, _rsCollapse, _rsCollapseH, _tpl, applyProp, createTemplate, delegatedPropName, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, onHydrationMismatch, render, sanitizeHtml, setSanitizer, setupDelegation };
|
|
949
|
+
export { DELEGATED_EVENTS, KeepAlive, Transition, TransitionGroup, applyProps as _applyProps, applyProps, _bindDirect, _bindText, _mountSlot, _rsCollapse, _rsCollapseDyn, _rsCollapseDynH, _rsCollapseH, _tpl, applyProp, createTemplate, delegatedPropName, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, onHydrationMismatch, render, sanitizeHtml, setSanitizer, setupDelegation };
|
|
815
950
|
//# sourceMappingURL=index.js.map
|
package/lib/keep-alive-entry.js
CHANGED
package/lib/types/index.d.ts
CHANGED
|
@@ -372,6 +372,112 @@ declare function _rsCollapse(html: string, lightClass: string, darkClass: string
|
|
|
372
372
|
* sliced source spans `detectPartialCollapsibleShape` returned.
|
|
373
373
|
*/
|
|
374
374
|
declare function _rsCollapseH(html: string, lightClass: string, darkClass: string, isDark: () => boolean, handlers: Record<string, unknown>, bind?: ((el: HTMLElement) => (() => void) | null) | null): NativeItem;
|
|
375
|
+
/**
|
|
376
|
+
* Compiler-emitted DYNAMIC-prop collapsed rocketstyle call site — PR 1
|
|
377
|
+
* of the dynamic-prop partial-collapse build (next bite after the
|
|
378
|
+
* `on*`-handler partial-collapse `_rsCollapseH`, `.claude/plans/open-work-2026-q3.md`
|
|
379
|
+
* → #1 dynamic-prop bucket = 15.3% of all real-corpus sites).
|
|
380
|
+
*
|
|
381
|
+
* Generalises {@link _rsCollapse}'s 2-class (light/dark) dispatch to an
|
|
382
|
+
* N-class dispatch for sites where one dimension prop is an enumerable
|
|
383
|
+
* dynamic expression (e.g. `<Button state={cond ? 'primary' : 'secondary'}>`).
|
|
384
|
+
* The compiler resolves EVERY value of that prop through the existing
|
|
385
|
+
* SSR-render resolver (so each value gets its own light + dark class
|
|
386
|
+
* baked in, byte-identical to a `_rsCollapse` site for that value), and
|
|
387
|
+
* the runtime picks the right `(value × mode)` class via the user's
|
|
388
|
+
* expression.
|
|
389
|
+
*
|
|
390
|
+
* Class layout in `classes` is **stride-2, value-major**: index
|
|
391
|
+
* `2 * valueIndex + (isDark ? 1 : 0)`. For the canonical ternary case:
|
|
392
|
+
*
|
|
393
|
+
* ```
|
|
394
|
+
* <Button state={cond ? 'primary' : 'secondary'}>Save</Button>
|
|
395
|
+
* →
|
|
396
|
+
* __rsCollapseDyn(
|
|
397
|
+
* "<button>Save</button>",
|
|
398
|
+
* ["btn-primary-light", "btn-primary-dark", "btn-secondary-light", "btn-secondary-dark"],
|
|
399
|
+
* () => cond ? 0 : 1,
|
|
400
|
+
* () => __pyrMode() === "dark"
|
|
401
|
+
* )
|
|
402
|
+
* ```
|
|
403
|
+
*
|
|
404
|
+
* Both the value expression AND the mode accessor are reactive: a change
|
|
405
|
+
* to either re-runs ONLY this className assignment, no remount (same
|
|
406
|
+
* contract as `_rsCollapse`'s mode flip). Both dispatches share a single
|
|
407
|
+
* `_bindDirect` so reading both inside one effect subscribes once per
|
|
408
|
+
* source — Pyreon's effect dedupe handles the rest.
|
|
409
|
+
*
|
|
410
|
+
* The structural HTML template is shared across every value (asserted
|
|
411
|
+
* by the resolver — divergent markup between values bails the collapse).
|
|
412
|
+
* Mirrors `_rsCollapse`'s mode-divergence-bails invariant.
|
|
413
|
+
*
|
|
414
|
+
* `bind` follows the same contract as `_rsCollapse` — standard `_tpl`
|
|
415
|
+
* child/event binder, runs after class binding, disposers chained.
|
|
416
|
+
*
|
|
417
|
+
* @param html static element HTML WITHOUT the root `class=` attr
|
|
418
|
+
* @param classes flat array of `2 × valueCount` class strings,
|
|
419
|
+
* indexed `[v0_light, v0_dark, v1_light, v1_dark, ...]`. The runtime
|
|
420
|
+
* does no validation — the compiler is the source of truth (an
|
|
421
|
+
* out-of-range `valueIndex()` would coerce to `undefined` className,
|
|
422
|
+
* which is correct-for-zero-style — never crashes)
|
|
423
|
+
* @param valueIndex user expression returning 0..valueCount-1 — reactive
|
|
424
|
+
* @param isDark app mode accessor — reactive
|
|
425
|
+
* @param bind standard _tpl binder for children/events (or null)
|
|
426
|
+
*/
|
|
427
|
+
declare function _rsCollapseDyn(html: string, classes: readonly string[], valueIndex: () => number, isDark: () => boolean, bind?: ((el: HTMLElement) => (() => void) | null) | null): NativeItem;
|
|
428
|
+
/**
|
|
429
|
+
* Compiler-emitted DYNAMIC-prop + HANDLER collapsed rocketstyle call
|
|
430
|
+
* site — closes the largest remaining real-corpus dynamic-collapse
|
|
431
|
+
* gap (`.claude/plans/open-work-2026-q3.md` → #1 dynamic-prop bucket
|
|
432
|
+
* = 15.4% of all real-corpus sites; the strict no-handler subset was
|
|
433
|
+
* only 0.2% measured; this helper unlocks the handler-combined slice
|
|
434
|
+
* that was bailed by `tryDynamicCollapse` in PR #767 by design).
|
|
435
|
+
*
|
|
436
|
+
* Combines {@link _rsCollapseDyn}'s value-major class dispatch with
|
|
437
|
+
* {@link _rsCollapseH}'s handler re-attachment. Handlers are orthogonal
|
|
438
|
+
* to both the SSR-resolved styler class AND the value dispatcher (a
|
|
439
|
+
* `state={cond ? 'a' : 'b'} onClick={h}` site's onClick is identical
|
|
440
|
+
* for both `state="a"` and `state="b"` resolutions — the styler class
|
|
441
|
+
* varies, the handler does not). So this helper is structurally the
|
|
442
|
+
* union of the two, no new behavior:
|
|
443
|
+
*
|
|
444
|
+
* ```
|
|
445
|
+
* <Button state={cond ? 'primary' : 'secondary'} onClick={go}>Save</Button>
|
|
446
|
+
* →
|
|
447
|
+
* __rsCollapseDynH(
|
|
448
|
+
* "<button>Save</button>",
|
|
449
|
+
* ["pri-L", "pri-D", "sec-L", "sec-D"],
|
|
450
|
+
* () => cond ? 0 : 1,
|
|
451
|
+
* () => __pyrMode() === "dark",
|
|
452
|
+
* { onClick: go }
|
|
453
|
+
* )
|
|
454
|
+
* ```
|
|
455
|
+
*
|
|
456
|
+
* Class layout matches `_rsCollapseDyn` (stride-2 value-major):
|
|
457
|
+
* `index = 2 * valueIndex + (isDark ? 1 : 0)`. Handler attachment
|
|
458
|
+
* matches `_rsCollapseH` — routed through the canonical `_bindEvent`
|
|
459
|
+
* → `applyEventProp` path (delegation + batching + name
|
|
460
|
+
* normalization). All three reactives (valueIndex, isDark, handlers
|
|
461
|
+
* — though handler identity is captured at the call site) compose
|
|
462
|
+
* cleanly: a value flip OR a mode flip patches className IN PLACE
|
|
463
|
+
* on the SAME node, handlers stay attached across both.
|
|
464
|
+
*
|
|
465
|
+
* Layer-pure: no styler / ui-core imports (the styler injection is
|
|
466
|
+
* the emitted code's job via `__rsSheet.injectRules`).
|
|
467
|
+
*
|
|
468
|
+
* @param html static element HTML WITHOUT the root `class=` attr
|
|
469
|
+
* @param classes flat array of `2 × valueCount` class strings, indexed
|
|
470
|
+
* `[v0_L, v0_D, v1_L, v1_D, …]`
|
|
471
|
+
* @param valueIndex user expression returning 0..valueCount-1 — reactive
|
|
472
|
+
* @param isDark app mode accessor — reactive
|
|
473
|
+
* @param handlers `{ onClick: fn, onPointerEnter: fn, … }` — the
|
|
474
|
+
* residual handlers peeled off the call site by the
|
|
475
|
+
* compiler's emit (sliced source spans re-emitted
|
|
476
|
+
* verbatim, paren-wrapped to keep arrow / sequence
|
|
477
|
+
* expressions a single value)
|
|
478
|
+
* @param bind standard _tpl binder for children/events (or null)
|
|
479
|
+
*/
|
|
480
|
+
declare function _rsCollapseDynH(html: string, classes: readonly string[], valueIndex: () => number, isDark: () => boolean, handlers: Record<string, unknown>, bind?: ((el: HTMLElement) => (() => void) | null) | null): NativeItem;
|
|
375
481
|
/**
|
|
376
482
|
* Mount a children slot inside a template.
|
|
377
483
|
*
|
|
@@ -513,5 +619,5 @@ declare function mount(root: VNodeChild, container: Element): () => void;
|
|
|
513
619
|
/** Alias for `mount` */
|
|
514
620
|
declare const render: typeof mount;
|
|
515
621
|
//#endregion
|
|
516
|
-
export { DELEGATED_EVENTS, type DevtoolsComponentEntry, type HydrationMismatchContext, type HydrationMismatchHandler, type HydrationMismatchType, KeepAlive, type KeepAliveProps, type PyreonDevtools, type SanitizeFn, Transition, TransitionGroup, type TransitionGroupProps, type TransitionProps, applyProps as _applyProps, applyProps, _bindDirect, _bindText, _mountSlot, _rsCollapse, _rsCollapseH, _tpl, applyProp, createTemplate, delegatedPropName, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, onHydrationMismatch, render, sanitizeHtml, setSanitizer, setupDelegation };
|
|
622
|
+
export { DELEGATED_EVENTS, type DevtoolsComponentEntry, type HydrationMismatchContext, type HydrationMismatchHandler, type HydrationMismatchType, KeepAlive, type KeepAliveProps, type PyreonDevtools, type SanitizeFn, Transition, TransitionGroup, type TransitionGroupProps, type TransitionProps, applyProps as _applyProps, applyProps, _bindDirect, _bindText, _mountSlot, _rsCollapse, _rsCollapseDyn, _rsCollapseDynH, _rsCollapseH, _tpl, applyProp, createTemplate, delegatedPropName, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, onHydrationMismatch, render, sanitizeHtml, setSanitizer, setupDelegation };
|
|
517
623
|
//# sourceMappingURL=index2.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/runtime-dom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "DOM renderer for Pyreon",
|
|
5
5
|
"homepage": "https://github.com/pyreon/pyreon/tree/main/packages/runtime-dom#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -54,15 +54,15 @@
|
|
|
54
54
|
"prepublishOnly": "bun run build"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@pyreon/core": "^0.
|
|
58
|
-
"@pyreon/reactivity": "^0.
|
|
57
|
+
"@pyreon/core": "^0.24.0",
|
|
58
|
+
"@pyreon/reactivity": "^0.24.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@happy-dom/global-registrator": "^20.8.9",
|
|
62
|
-
"@pyreon/compiler": "^0.
|
|
62
|
+
"@pyreon/compiler": "^0.24.0",
|
|
63
63
|
"@pyreon/manifest": "0.13.1",
|
|
64
|
-
"@pyreon/runtime-server": "^0.
|
|
65
|
-
"@pyreon/test-utils": "^0.13.
|
|
64
|
+
"@pyreon/runtime-server": "^0.24.0",
|
|
65
|
+
"@pyreon/test-utils": "^0.13.11",
|
|
66
66
|
"@vitest/browser-playwright": "^4.1.4",
|
|
67
67
|
"esbuild": "^0.28.0",
|
|
68
68
|
"happy-dom": "^20.8.3",
|
package/src/index.ts
CHANGED
package/src/nodes.ts
CHANGED
|
@@ -100,8 +100,18 @@ export function mountReactive(
|
|
|
100
100
|
// Child components set up their OWN effects for reactivity
|
|
101
101
|
// (e.g. DynamicStyled's class swap effect). Those effects track
|
|
102
102
|
// their own dependencies independently.
|
|
103
|
+
//
|
|
104
|
+
// Use the marker's LIVE parent (not the closure-captured `parent`):
|
|
105
|
+
// when this mountReactive was created inside a DocumentFragment that
|
|
106
|
+
// mountFor later moved into the live tree via `insertBefore(frag, ...)`,
|
|
107
|
+
// the captured `parent` becomes a stale reference to the now-empty
|
|
108
|
+
// fragment. The marker, in contrast, was moved with the fragment's
|
|
109
|
+
// contents and `marker.parentNode` reflects the current live parent.
|
|
110
|
+
// Falling back to the captured `parent` only when the marker is
|
|
111
|
+
// detached (cleanup edge case) preserves prior behavior.
|
|
112
|
+
const liveParent = marker.parentNode ?? parent
|
|
103
113
|
const cleanup = runUntracked(() =>
|
|
104
|
-
restoreContextStack(contextSnapshot, () => mount(value,
|
|
114
|
+
restoreContextStack(contextSnapshot, () => mount(value, liveParent, marker)),
|
|
105
115
|
)
|
|
106
116
|
// Guard: a re-entrant signal update (e.g. ErrorBoundary catching a child
|
|
107
117
|
// throw) may have already re-run this effect and updated currentCleanup.
|
|
@@ -292,15 +302,15 @@ export function mountKeyedList(
|
|
|
292
302
|
}
|
|
293
303
|
}
|
|
294
304
|
|
|
295
|
-
const mountNewEntries = (newList: VNode[]) => {
|
|
305
|
+
const mountNewEntries = (newList: VNode[], liveParent: Node) => {
|
|
296
306
|
for (const vnode of newList) {
|
|
297
307
|
const key = vnode.key
|
|
298
308
|
if (key === null || key === undefined) continue
|
|
299
309
|
if (cache.has(key)) continue
|
|
300
310
|
const anchor = document.createComment('')
|
|
301
311
|
_keyedAnchors.add(anchor)
|
|
302
|
-
|
|
303
|
-
const cleanup = mountVNode(vnode,
|
|
312
|
+
liveParent.insertBefore(anchor, tailMarker)
|
|
313
|
+
const cleanup = mountVNode(vnode, liveParent, tailMarker)
|
|
304
314
|
cache.set(key, { anchor, cleanup })
|
|
305
315
|
}
|
|
306
316
|
}
|
|
@@ -311,6 +321,17 @@ export function mountKeyedList(
|
|
|
311
321
|
// Same untracking rationale as mountFor — see comment there. Child
|
|
312
322
|
// mounts via mountVNode must not re-track on this effect's run.
|
|
313
323
|
runUntracked(() => {
|
|
324
|
+
// Use the marker's LIVE parent (not the closure-captured `parent`).
|
|
325
|
+
// Same bug class fixed in #776 for mountReactive: when this
|
|
326
|
+
// mountKeyedList was created inside a DocumentFragment that mountFor
|
|
327
|
+
// later moved via `liveParent.insertBefore(frag, tailMarker)`, the
|
|
328
|
+
// captured `parent` becomes a stale reference to the now-empty
|
|
329
|
+
// fragment. The markers were moved with the fragment's contents
|
|
330
|
+
// and their `parentNode` reflects the current live parent.
|
|
331
|
+
// Fallback to the captured `parent` only when the marker is
|
|
332
|
+
// detached (cleanup edge case) preserves prior behavior.
|
|
333
|
+
const liveParent = tailMarker.parentNode ?? parent
|
|
334
|
+
|
|
314
335
|
if (n === 0 && cache.size > 0) {
|
|
315
336
|
for (const entry of cache.values()) {
|
|
316
337
|
_emitCleanup()
|
|
@@ -325,10 +346,10 @@ export function mountKeyedList(
|
|
|
325
346
|
|
|
326
347
|
const { newKeyOrder, newKeySet } = collectKeyOrder(newList)
|
|
327
348
|
removeStaleEntries(newKeySet)
|
|
328
|
-
mountNewEntries(newList)
|
|
349
|
+
mountNewEntries(newList, liveParent)
|
|
329
350
|
|
|
330
351
|
if (currentKeyOrder.length > 0 && n > 0) {
|
|
331
|
-
lis = keyedListReorder(lis, n, newKeyOrder, curPos, cache,
|
|
352
|
+
lis = keyedListReorder(lis, n, newKeyOrder, curPos, cache, liveParent, tailMarker)
|
|
332
353
|
}
|
|
333
354
|
|
|
334
355
|
curPos.clear()
|