@srfnstack/fntags 1.2.6 → 1.2.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srfnstack/fntags",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "author": "Robert Kempton <r@snow87.com>",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/srfnstack/fntags",
@@ -1 +1 @@
1
- {"version":3,"file":"fntags.d.mts","sourceRoot":"","sources":["fntags.mjs"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,2DALW,MAAM,eACH,CAAC,IAAI,MAAO,CAAC,OA6C1B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,iEAPgB,GAAG,cAqIlB;AAqfD;;;;GAIG;AACH,iCAHW,GAAG,GACD,IAAI,CAuBhB;AAqFD;;;;GAIG;AACH,6BAHW,GAAG,GACD,OAAO,CAInB;AAED;;;;GAIG;AACH,mCAHW,GAAG,GACF,MAAM,CAIjB;AAED;;;;;;;;;;GAUG;AACH,kEALW,MAAM,GAAC,MAAM,OACb,MAAM,YACN,MAAM,EAAE,GAAC,IAAI,EAAE,KAezB;AAOD;;;;;;;;GAQG;AACH,wCAJW,MAAM,0BAiBhB;AAED;;;;;;;;;GASG;AACH,oCALW,MAAM,gBACN,GAAG,eACE,GAAG,KAAK,MAAM,OAc7B;AAED;;;;;;;;GAQG;AACH,mCAJW,WAAW,SACX,CAAC,MAAI,IAAI,CAAC,GAAC,IAAI,GACb,WAAW,CAWvB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qDAJkB,IAAI,iBACH,IAAI,CA+DtB;;;;;;;;;kCA18BmC,CAAC,YAAY,CAAC,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;;2BAE/C,CAAC,MAAI,CAAC,IAAI,GAAC,GAAG,CAAC,CAAC,GAAC,GAAG,GAAC,IAAI,gCAAkC,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;qBAG9E,MAAM,KAAG,IAAI;;;;sCAEI,CAAC,YAAY,CAAC,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;mCACvC,CAAC,YAAY,CAAC,KAAG,MAAM,KAAK,MAAM;;;;yCAC7B,GAAG,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;+CACrB,GAAG,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;;;kBAC7C,GAAG,KAAG,IAAI;;;;cAGhB,MAAK,GAAG;;;;;qBACC,CAAC,KAAG,IAAI;;;;;;oBAEV,MAAM,KAAG,GAAG;;;;oBAGZ,MAAM,SAAS,GAAG,mBAAmB,OAAO,KAAG,IAAI;;;;uCAClC,CAAC,YAAY,CAAC,KAAG,IAAI,KAAK,IAAI;;;;eACtD,OAAO;;;;;sDAKqB,CAAC,KAAG,CAAC"}
1
+ {"version":3,"file":"fntags.d.mts","sourceRoot":"","sources":["fntags.mjs"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,2DALW,MAAM,eACH,CAAC,IAAI,MAAO,CAAC,OA6C1B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,iEAPgB,GAAG,cAqIlB;AAgjBD;;;;GAIG;AACH,iCAHW,GAAG,GACD,IAAI,CAuBhB;AAqFD;;;;GAIG;AACH,6BAHW,GAAG,GACD,OAAO,CAInB;AAED;;;;GAIG;AACH,mCAHW,GAAG,GACF,MAAM,CAIjB;AAED;;;;;;;;;;GAUG;AACH,kEALW,MAAM,GAAC,MAAM,OACb,MAAM,YACN,MAAM,EAAE,GAAC,IAAI,EAAE,KAezB;AAOD;;;;;;;;GAQG;AACH,wCAJW,MAAM,0BAiBhB;AAED;;;;;;;;;GASG;AACH,oCALW,MAAM,gBACN,GAAG,eACE,GAAG,KAAK,MAAM,OAc7B;AAED;;;;;;;;GAQG;AACH,mCAJW,WAAW,SACX,CAAC,MAAI,IAAI,CAAC,GAAC,IAAI,GACb,WAAW,CAWvB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qDAJkB,IAAI,iBACH,IAAI,CA+DtB;;;;;;;;;kCArgCmC,CAAC,YAAY,CAAC,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;;2BAE/C,CAAC,MAAI,CAAC,IAAI,GAAC,GAAG,CAAC,CAAC,GAAC,GAAG,GAAC,IAAI,gCAAkC,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;qBAG9E,MAAM,KAAG,IAAI;;;;sCAEI,CAAC,YAAY,CAAC,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;mCACvC,CAAC,YAAY,CAAC,KAAG,MAAM,KAAK,MAAM;;;;yCAC7B,GAAG,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;+CACrB,GAAG,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;;;kBAC7C,GAAG,KAAG,IAAI;;;;cAGhB,MAAK,GAAG;;;;;qBACC,CAAC,KAAG,IAAI;;;;;;oBAEV,MAAM,KAAG,GAAG;;;;oBAGZ,MAAM,SAAS,GAAG,mBAAmB,OAAO,KAAG,IAAI;;;;uCAClC,CAAC,YAAY,CAAC,KAAG,IAAI,KAAK,IAAI;;;;eACtD,OAAO;;;;;sDAKqB,CAAC,KAAG,CAAC"}
package/src/fntags.mjs CHANGED
@@ -489,7 +489,17 @@ const doBind = function (ctx, element, handleReplace) {
489
489
  const prevRenderCleanups = activeRenderCleanups
490
490
  const thisRenderCleanups = []
491
491
  activeRenderCleanups = thisRenderCleanups
492
- const elCtx = { current: renderNode(evaluateElement(element, ctx.currentValue)), cleanups: thisRenderCleanups }
492
+ const evaluated = evaluateElement(element, ctx.currentValue)
493
+ const childElCtx = (typeof evaluated === 'function' && evaluated.isBindGetter)
494
+ ? evaluated.elCtx
495
+ : null
496
+ const elCtx = { current: renderNode(evaluated), cleanups: thisRenderCleanups }
497
+ // When the evaluated element is itself a bind getter (a nested bindAs/bindSelect),
498
+ // register this elCtx as a back-ref on the child so that later child-driven
499
+ // node swaps keep this elCtx.current in sync with the live DOM node.
500
+ if (childElCtx && childElCtx.current === elCtx.current) {
501
+ addParentRef(childElCtx, elCtx)
502
+ }
493
503
  // Suppress tracking during handleReplace — the driving subscription is
494
504
  // registered separately below as part of a cascading cleanup.
495
505
  activeRenderCleanups = null
@@ -504,10 +514,46 @@ const doBind = function (ctx, element, handleReplace) {
504
514
  if (elCtx.drivingUnsub) elCtx.drivingUnsub()
505
515
  if (elCtx.selectUnsub) elCtx.selectUnsub()
506
516
  runCleanups(elCtx)
517
+ // Mark dead so any child elCtx that still holds us in its parentRefs
518
+ // can prune us lazily on its next propagateReplace walk.
519
+ elCtx.dead = true
507
520
  })
508
521
  }
509
522
 
510
- return () => elCtx.current
523
+ const getter = () => elCtx.current
524
+ getter.isBindGetter = true
525
+ getter.elCtx = elCtx
526
+ return getter
527
+ }
528
+
529
+ // Register a back-ref from a child bind elCtx to a parent elCtx, avoiding
530
+ // duplicate entries when the same parent re-renders against the same child.
531
+ function addParentRef (childElCtx, parentElCtx) {
532
+ if (!childElCtx.parentRefs) {
533
+ childElCtx.parentRefs = [parentElCtx]
534
+ } else if (!childElCtx.parentRefs.includes(parentElCtx)) {
535
+ childElCtx.parentRefs.push(parentElCtx)
536
+ }
537
+ }
538
+
539
+ // Walk the back-ref chain and update any ancestor elCtx whose .current still
540
+ // points at the old DOM node. Used when a nested bindAs swaps its node so the
541
+ // outer bindAs keeps a live reference instead of a detached one. Prunes any
542
+ // dead ancestors encountered so parentRefs does not retain torn-down elCtxs.
543
+ function propagateReplace (elCtx, oldNode, newNode) {
544
+ const refs = elCtx.parentRefs
545
+ if (!refs) return
546
+ for (let i = refs.length - 1; i >= 0; i--) {
547
+ const parent = refs[i]
548
+ if (parent.dead) {
549
+ refs.splice(i, 1)
550
+ continue
551
+ }
552
+ if (parent.current === oldNode) {
553
+ parent.current = newNode
554
+ propagateReplace(parent, oldNode, newNode)
555
+ }
556
+ }
511
557
  }
512
558
 
513
559
  const updateReplacer = (ctx, element, elCtx) => (_, oldValue) => {
@@ -520,7 +566,11 @@ const updateReplacer = (ctx, element, elCtx) => (_, oldValue) => {
520
566
  const prevRenderCleanups = activeRenderCleanups
521
567
  const thisRenderCleanups = []
522
568
  activeRenderCleanups = thisRenderCleanups
523
- let rendered = renderNode(evaluateElement(element, ctx.currentValue, oldValue))
569
+ const evaluated = evaluateElement(element, ctx.currentValue, oldValue)
570
+ const childElCtx = (typeof evaluated === 'function' && evaluated.isBindGetter)
571
+ ? evaluated.elCtx
572
+ : null
573
+ let rendered = renderNode(evaluated)
524
574
  activeRenderCleanups = prevRenderCleanups
525
575
  elCtx.cleanups = thisRenderCleanups
526
576
 
@@ -533,14 +583,23 @@ const updateReplacer = (ctx, element, elCtx) => (_, oldValue) => {
533
583
  bindContext.boundElementByKey[elCtx.current.key] = rendered
534
584
  }
535
585
  }
586
+ // Re-establish the parent-ref link when a re-render resolves to another
587
+ // nested bind getter, so future child swaps propagate back to this elCtx.
588
+ if (childElCtx && childElCtx.current === rendered) {
589
+ addParentRef(childElCtx, elCtx)
590
+ }
536
591
  // Perform this action on the next event loop to give the parent a chance to render
537
592
  new Promise((resolve) => {
538
593
  // Guard: skip replaceWith when the same element is returned (e.g. when
539
594
  // toggling between two pre-built element references). el.replaceWith(el)
540
595
  // is a no-op in the DOM but wastes a microtask.
541
596
  if (rendered !== elCtx.current) {
597
+ const oldCurrent = elCtx.current
542
598
  elCtx.current.replaceWith(rendered)
543
599
  elCtx.current = rendered
600
+ // Keep any ancestor bindAs whose elCtx shared this node in sync with
601
+ // the new node, so their next replaceWith targets a live DOM node.
602
+ propagateReplace(elCtx, oldCurrent, rendered)
544
603
  }
545
604
  rendered = null
546
605
  resolve()