@b9g/crank 0.7.1 → 0.7.2

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/crank.cjs CHANGED
@@ -168,6 +168,7 @@ const IsInForOfLoop = 1 << 13;
168
168
  const IsInForAwaitOfLoop = 1 << 14;
169
169
  const NeedsToYield = 1 << 15;
170
170
  const PropsAvailable = 1 << 16;
171
+ const IsSchedulingRefresh = 1 << 17;
171
172
  function getFlag(ret, flag) {
172
173
  return !!(ret.f & flag);
173
174
  }
@@ -521,10 +522,10 @@ function diffChildren(adapter, root, host, ctx, scope, parent, newChildren) {
521
522
  }
522
523
  }
523
524
  else if (ret) {
525
+ let candidateFound = false;
524
526
  // we do not need to add the retainer to the graveyard if it is the
525
527
  // fallback of another retainer
526
528
  // search for the tag in fallback chain
527
- let candidateFound = false;
528
529
  for (let predecessor = ret, candidate = ret.fallback; candidate; predecessor = candidate, candidate = candidate.fallback) {
529
530
  if (candidate.el.tag === child.tag) {
530
531
  // If we find a retainer in the fallback chain with the same tag,
@@ -1359,6 +1360,9 @@ class Context extends eventTarget.CustomEventTarget {
1359
1360
  });
1360
1361
  }
1361
1362
  }
1363
+ if (getFlag(ctx.ret, IsScheduling)) {
1364
+ setFlag(ctx.ret, IsSchedulingRefresh);
1365
+ }
1362
1366
  let diff;
1363
1367
  const schedulePromises = [];
1364
1368
  try {
@@ -1704,10 +1708,11 @@ function runComponent(ctx) {
1704
1708
  if (getFlag(ctx.ret, IsInForOfLoop) &&
1705
1709
  !getFlag(ctx.ret, NeedsToYield) &&
1706
1710
  !getFlag(ctx.ret, IsUnmounted) &&
1707
- !getFlag(ctx.ret, IsScheduling)) {
1711
+ !getFlag(ctx.ret, IsSchedulingRefresh)) {
1708
1712
  console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
1709
1713
  }
1710
1714
  setFlag(ctx.ret, NeedsToYield, false);
1715
+ setFlag(ctx.ret, IsSchedulingRefresh, false);
1711
1716
  if (iteration.done) {
1712
1717
  setFlag(ctx.ret, IsSyncGen, false);
1713
1718
  ctx.iterator = undefined;
@@ -1753,11 +1758,12 @@ function runComponent(ctx) {
1753
1758
  if (getFlag(ctx.ret, IsInForOfLoop) &&
1754
1759
  !getFlag(ctx.ret, NeedsToYield) &&
1755
1760
  !getFlag(ctx.ret, IsUnmounted) &&
1756
- !getFlag(ctx.ret, IsScheduling)) {
1761
+ !getFlag(ctx.ret, IsSchedulingRefresh)) {
1757
1762
  console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
1758
1763
  }
1759
1764
  }
1760
1765
  setFlag(ctx.ret, NeedsToYield, false);
1766
+ setFlag(ctx.ret, IsSchedulingRefresh, false);
1761
1767
  if (iteration.done) {
1762
1768
  setFlag(ctx.ret, IsAsyncGen, false);
1763
1769
  ctx.iterator = undefined;
@@ -1989,18 +1995,17 @@ function commitComponent(ctx, schedulePromises, hydrationNodes) {
1989
1995
  });
1990
1996
  return getValue(ctx.ret);
1991
1997
  }
1992
- const wasScheduling = getFlag(ctx.ret, IsScheduling);
1993
1998
  const values = commitChildren(ctx.adapter, ctx.host, ctx, ctx.scope, ctx.ret, ctx.index, schedulePromises, hydrationNodes);
1994
1999
  if (getFlag(ctx.ret, IsUnmounted)) {
1995
2000
  return;
1996
2001
  }
1997
2002
  eventTarget.addEventTargetDelegates(ctx.ctx, values);
1998
2003
  // Execute schedule callbacks early to check for async deferral
1999
- const callbacks = scheduleMap.get(ctx);
2004
+ const wasScheduling = getFlag(ctx.ret, IsScheduling);
2000
2005
  let schedulePromises1;
2006
+ const callbacks = scheduleMap.get(ctx);
2001
2007
  if (callbacks) {
2002
2008
  scheduleMap.delete(ctx);
2003
- // TODO: think about error handling for schedule callbacks
2004
2009
  setFlag(ctx.ret, IsScheduling);
2005
2010
  const result = ctx.adapter.read(_utils.unwrap(values));
2006
2011
  for (const callback of callbacks) {
@@ -2011,7 +2016,7 @@ function commitComponent(ctx, schedulePromises, hydrationNodes) {
2011
2016
  }
2012
2017
  if (schedulePromises1 && !getFlag(ctx.ret, DidCommit)) {
2013
2018
  const scheduleCallbacksP = Promise.all(schedulePromises1).then(() => {
2014
- setFlag(ctx.ret, IsScheduling, false);
2019
+ setFlag(ctx.ret, IsScheduling, wasScheduling);
2015
2020
  propagateComponent(ctx);
2016
2021
  if (ctx.ret.fallback) {
2017
2022
  unmount(ctx.adapter, ctx.host, ctx.parent, ctx.ret.fallback, false);
@@ -2051,6 +2056,37 @@ function commitComponent(ctx, schedulePromises, hydrationNodes) {
2051
2056
  // if schedule callbacks call refresh() or async mounting is happening.
2052
2057
  return getValue(ctx.ret, true);
2053
2058
  }
2059
+ /**
2060
+ * Checks if a target retainer is active (contributing) in the host's retainer tree.
2061
+ * Performs a downward traversal from host to find if target is in the active path.
2062
+ */
2063
+ function isRetainerActive(target, host) {
2064
+ const stack = [host];
2065
+ while (stack.length > 0) {
2066
+ const current = stack.pop();
2067
+ if (current === target) {
2068
+ return true;
2069
+ }
2070
+ // Add direct children to stack (skip if this is a host boundary)
2071
+ // Host boundaries are: DOM elements (string tags) or Portal, but NOT Fragment
2072
+ const isHostBoundary = current !== host &&
2073
+ ((typeof current.el.tag === "string" && current.el.tag !== Fragment) ||
2074
+ current.el.tag === Portal);
2075
+ if (current.children && !isHostBoundary) {
2076
+ const children = _utils.wrap(current.children);
2077
+ for (const child of children) {
2078
+ if (child) {
2079
+ stack.push(child);
2080
+ }
2081
+ }
2082
+ }
2083
+ // Add fallback chains (only if current retainer is using fallback)
2084
+ if (current.fallback && !getFlag(current, DidDiff)) {
2085
+ stack.push(current.fallback);
2086
+ }
2087
+ }
2088
+ return false;
2089
+ }
2054
2090
  /**
2055
2091
  * Propagates component changes up to ancestors when rendering starts from a
2056
2092
  * component via refresh() or multiple for await...of renders. This handles
@@ -2061,14 +2097,20 @@ function propagateComponent(ctx) {
2061
2097
  const values = getChildValues(ctx.ret, ctx.index);
2062
2098
  eventTarget.addEventTargetDelegates(ctx.ctx, values, (ctx1) => ctx1[_ContextState].host === ctx.host);
2063
2099
  const host = ctx.host;
2100
+ const initiator = ctx.ret;
2101
+ // Check if initiator is active in the host's tree
2102
+ if (!isRetainerActive(initiator, host)) {
2103
+ return;
2104
+ }
2064
2105
  const props = stripSpecialProps(host.el.props);
2106
+ const hostChildren = getChildValues(host, 0);
2065
2107
  ctx.adapter.arrange({
2066
2108
  tag: host.el.tag,
2067
2109
  tagName: getTagName(host.el.tag),
2068
2110
  node: host.value,
2069
2111
  props,
2070
2112
  oldProps: props,
2071
- children: getChildValues(host, 0),
2113
+ children: hostChildren,
2072
2114
  });
2073
2115
  flush(ctx.adapter, ctx.root, ctx);
2074
2116
  }