@b9g/crank 0.7.1 → 0.7.3

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