@qwik.dev/core 2.0.0-beta.25 → 2.0.0-beta.26

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/dist/core.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @license
3
- * @qwik.dev/core 2.0.0-beta.25-dev+2677279
3
+ * @qwik.dev/core 2.0.0-beta.26-dev+c693cf5
4
4
  * Copyright QwikDev. All Rights Reserved.
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://github.com/QwikDev/qwik/blob/main/LICENSE
@@ -1007,7 +1007,7 @@ const COMMA = ',';
1007
1007
  *
1008
1008
  * @public
1009
1009
  */
1010
- const version = "2.0.0-beta.25-dev+2677279";
1010
+ const version = "2.0.0-beta.26-dev+c693cf5";
1011
1011
 
1012
1012
  // keep this import from core/build so the cjs build works
1013
1013
  const createPlatform = () => {
@@ -1020,6 +1020,8 @@ const createPlatform = () => {
1020
1020
  if (regSym) {
1021
1021
  return regSym;
1022
1022
  }
1023
+ // we never lazy import on the server
1024
+ throw qError(6 /* QError.dynamicImportFailed */, [symbolName]);
1023
1025
  }
1024
1026
  if (!url) {
1025
1027
  throw qError(14 /* QError.qrlMissingChunk */, [symbolName]);
@@ -3705,7 +3707,14 @@ const cleanupDestroyable = (destroyable) => {
3705
3707
  * This safely calls an event handler, handling errors and retrying on thrown Promises, and
3706
3708
  * providing extra parameters defined on the elements as arguments (used for loop optimization)
3707
3709
  */
3708
- function runEventHandlerQRL(handler, event, element, ctx = newInvokeContextFromDOM(event, element)) {
3710
+ function runEventHandlerQRL(handler, event, element, ctx) {
3711
+ if (!element.isConnected) {
3712
+ // ignore events on disconnected elements, this can happen when the event is triggered while the element is being removed
3713
+ return;
3714
+ }
3715
+ if (!ctx) {
3716
+ ctx = newInvokeContextFromDOM(event, element);
3717
+ }
3709
3718
  const container = ctx.$container$;
3710
3719
  const hostElement = ctx.$hostElement$;
3711
3720
  vnode_ensureElementInflated(container, hostElement);
@@ -3770,8 +3779,8 @@ function setAttribute(journal, vnode, key, value, scopedStyleIdPrefix, originalV
3770
3779
  vnode_setProp(vnode, key, originalValue);
3771
3780
  addVNodeOperation(journal, createSetAttributeOperation(vnode.node, key, value, scopedStyleIdPrefix, (vnode.flags & 512 /* VNodeFlags.NS_svg */) !== 0));
3772
3781
  }
3773
- const vnode_diff = (container, journal, jsxNode, vStartNode, cursor, scopedStyleIdPrefix) => {
3774
- const diffContext = {
3782
+ function createDiffContext(container, journal, cursor, scopedStyleIdPrefix) {
3783
+ return {
3775
3784
  container,
3776
3785
  journal,
3777
3786
  cursor,
@@ -3802,6 +3811,9 @@ const vnode_diff = (container, journal, jsxNode, vStartNode, cursor, scopedStyle
3802
3811
  }),
3803
3812
  },
3804
3813
  };
3814
+ }
3815
+ const vnode_diff = (container, journal, jsxNode, vStartNode, cursor, scopedStyleIdPrefix) => {
3816
+ const diffContext = createDiffContext(container, journal, cursor, scopedStyleIdPrefix);
3805
3817
  ////////////////////////////////
3806
3818
  diff(diffContext, jsxNode, vStartNode);
3807
3819
  const result = drainAsyncQueue(diffContext);
@@ -4156,6 +4168,7 @@ function expectSlot(diffContext) {
4156
4168
  vHost && vnode_setProp(vHost, slotNameKey, diffContext.vNewNode);
4157
4169
  isDev &&
4158
4170
  vnode_setProp(diffContext.vNewNode, DEBUG_TYPE, "P" /* VirtualType.Projection */);
4171
+ vnode_inflateProjectionTrailingText(diffContext.journal, diffContext.vNewNode);
4159
4172
  vnode_insertBefore(diffContext.journal, diffContext.vParent, diffContext.vNewNode, diffContext.vCurrent && getInsertBefore(diffContext));
4160
4173
  // If we moved from a q:template and it's now empty, remove it
4161
4174
  if (oldParent &&
@@ -4706,26 +4719,34 @@ function expectComponent(diffContext, component) {
4706
4719
  const vNodeLookupKey = getKey(host) || vNodeComponentHash;
4707
4720
  const lookupKeysAreEqual = lookupKey === vNodeLookupKey;
4708
4721
  const hashesAreEqual = componentHash === vNodeComponentHash;
4709
- if (!lookupKeysAreEqual) {
4710
- if (moveOrCreateKeyedNode(diffContext, null, lookupKey, lookupKey, diffContext.vParent)) {
4722
+ if (lookupKeysAreEqual) {
4723
+ if (hashesAreEqual) {
4724
+ deleteFromSideBuffer(diffContext, null, lookupKey);
4725
+ }
4726
+ else {
4711
4727
  insertNewComponent(diffContext, host, componentQRL, jsxProps);
4728
+ host = diffContext.vNewNode;
4712
4729
  shouldRender = true;
4713
4730
  }
4714
- host = (diffContext.vNewNode || diffContext.vCurrent);
4715
- }
4716
- else if (!hashesAreEqual || !jsxNode.key) {
4717
- insertNewComponent(diffContext, host, componentQRL, jsxProps);
4718
- host = diffContext.vNewNode;
4719
- shouldRender = true;
4720
4731
  }
4721
4732
  else {
4722
- // delete the key from the side buffer if it is the same component
4723
- deleteFromSideBuffer(diffContext, null, lookupKey);
4733
+ if (moveOrCreateKeyedNode(diffContext, null, lookupKey, lookupKey, diffContext.vParent)) {
4734
+ insertNewComponent(diffContext, host, componentQRL, jsxProps);
4735
+ shouldRender = true;
4736
+ }
4737
+ host = (diffContext.vNewNode || diffContext.vCurrent);
4724
4738
  }
4725
4739
  if (host) {
4726
4740
  const vNodeProps = vnode_getProp(host, ELEMENT_PROPS, diffContext.container.$getObjectById$);
4727
4741
  if (!shouldRender) {
4728
- shouldRender ||= handleProps(host, jsxProps, vNodeProps, diffContext.container);
4742
+ const propsChanged = handleProps(host, jsxProps, vNodeProps, diffContext.container);
4743
+ // if props changed but key is null we need to insert a new component, because we need to execute hooks etc
4744
+ if (propsChanged && jsxNode.key == null) {
4745
+ insertNewComponent(diffContext, host, componentQRL, jsxProps);
4746
+ host = diffContext.vNewNode;
4747
+ shouldRender = true;
4748
+ }
4749
+ shouldRender ||= propsChanged;
4729
4750
  }
4730
4751
  if (shouldRender) {
4731
4752
  // Assign the new QRL instance to the host.
@@ -4747,7 +4768,7 @@ function expectComponent(diffContext, component) {
4747
4768
  const lookupKeysAreEqual = lookupKey === vNodeLookupKey;
4748
4769
  const vNodeComponentHash = getComponentHash(host, diffContext.container.$getObjectById$);
4749
4770
  const isInlineComponent = vNodeComponentHash == null;
4750
- if ((host && !isInlineComponent) || lookupKey == null) {
4771
+ if ((host && !isInlineComponent) || !host) {
4751
4772
  insertNewInlineComponent(diffContext);
4752
4773
  host = diffContext.vNewNode;
4753
4774
  }
@@ -5739,7 +5760,7 @@ function walkCursor(cursor, options) {
5739
5760
  return;
5740
5761
  }
5741
5762
  // Check time budget (only for DOM, not SSR)
5742
- if (!isRunningOnServer && !import.meta.env.TEST) {
5763
+ if (isBrowser) {
5743
5764
  const elapsed = performance.now() - startTime;
5744
5765
  if (elapsed >= timeBudget) {
5745
5766
  // Schedule continuation as macrotask to actually yield to browser
@@ -5781,10 +5802,27 @@ function tryDescendDirtyChildren(container, cursorData, currentVNode, cursor) {
5781
5802
  return null;
5782
5803
  }
5783
5804
  partitionDirtyChildren(dirtyChildren, currentVNode);
5805
+ // Scan dirtyChildren directly instead of going through getNextVNode.
5806
+ // getNextVNode follows the child's parent/slotParent pointer, which for Projection nodes
5807
+ // points to the DOM insertion location rather than currentVNode — that would scan the
5808
+ // wrong dirtyChildren array and potentially cause infinite loops.
5809
+ // const len = dirtyChildren.length;
5810
+ // for (let i = 0; i < len; i++) {
5811
+ // const child = dirtyChildren[i];
5812
+ // if (child.dirty & ChoreBits.DIRTY_MASK) {
5813
+ // currentVNode.nextDirtyChildIndex = (i + 1) % len;
5814
+ // setCursorPosition(container, cursorData, child);
5815
+ // return child;
5816
+ // }
5817
+ // }
5818
+ // // No dirty child found — clean up
5819
+ // currentVNode.dirty &= ~ChoreBits.CHILDREN;
5820
+ // currentVNode.dirtyChildren = null;
5784
5821
  currentVNode.nextDirtyChildIndex = 0;
5785
5822
  const next = getNextVNode(dirtyChildren[0], cursor);
5786
5823
  setCursorPosition(container, cursorData, next);
5787
5824
  return next;
5825
+ // return null;
5788
5826
  }
5789
5827
  /**
5790
5828
  * Partitions dirtyChildren array so non-projections come first, projections last. Uses in-place
@@ -5813,14 +5851,14 @@ function getNextVNode(vNode, cursor) {
5813
5851
  }
5814
5852
  return null;
5815
5853
  }
5816
- // Prefer parent if it's dirty, otherwise try slotParent
5854
+ // Prefer slotParent (logical owner) for Projections, fall back to parent
5817
5855
  let parent = null;
5818
- if (vNode.parent && vNode.parent.dirty & 32 /* ChoreBits.CHILDREN */) {
5819
- parent = vNode.parent;
5820
- }
5821
- else if (vNode.slotParent && vNode.slotParent.dirty & 32 /* ChoreBits.CHILDREN */) {
5856
+ if (vNode.slotParent && vNode.slotParent.dirty & 32 /* ChoreBits.CHILDREN */) {
5822
5857
  parent = vNode.slotParent;
5823
5858
  }
5859
+ else if (vNode.parent && vNode.parent.dirty & 32 /* ChoreBits.CHILDREN */) {
5860
+ parent = vNode.parent;
5861
+ }
5824
5862
  if (!parent) {
5825
5863
  if (cursor.dirty & 127 /* ChoreBits.DIRTY_MASK */) {
5826
5864
  return cursor;
@@ -5992,7 +6030,9 @@ function propagatePath(target) {
5992
6030
  const parent = reusablePath[i + 1] || target;
5993
6031
  parent.dirty |= 32 /* ChoreBits.CHILDREN */;
5994
6032
  parent.dirtyChildren ||= [];
5995
- parent.dirtyChildren.push(child);
6033
+ if (!parent.dirtyChildren.includes(child)) {
6034
+ parent.dirtyChildren.push(child);
6035
+ }
5996
6036
  }
5997
6037
  }
5998
6038
  /**
@@ -6001,7 +6041,7 @@ function propagatePath(target) {
6001
6041
  */
6002
6042
  function propagateToCursorRoot(vNode, cursorRoot) {
6003
6043
  reusablePath.push(vNode);
6004
- let current = vNode.parent || vNode.slotParent;
6044
+ let current = vNode.slotParent || vNode.parent;
6005
6045
  while (current) {
6006
6046
  const isDirty = current.dirty & 127 /* ChoreBits.DIRTY_MASK */;
6007
6047
  const currentIsCursor = isCursor(current);
@@ -6026,7 +6066,7 @@ function propagateToCursorRoot(vNode, cursorRoot) {
6026
6066
  }
6027
6067
  }
6028
6068
  reusablePath.push(current);
6029
- current = current.parent || current.slotParent;
6069
+ current = current.slotParent || current.parent;
6030
6070
  }
6031
6071
  reusablePath.length = 0;
6032
6072
  throwErrorAndStop('Cursor root not found in current path!');
@@ -6037,7 +6077,7 @@ function propagateToCursorRoot(vNode, cursorRoot) {
6037
6077
  */
6038
6078
  function findAndPropagateToBlockingCursor(vNode) {
6039
6079
  reusablePath.push(vNode);
6040
- let current = vNode.parent || vNode.slotParent;
6080
+ let current = vNode.slotParent || vNode.parent;
6041
6081
  while (current) {
6042
6082
  const currentIsCursor = isCursor(current);
6043
6083
  if (currentIsCursor) {
@@ -6046,7 +6086,7 @@ function findAndPropagateToBlockingCursor(vNode) {
6046
6086
  return true;
6047
6087
  }
6048
6088
  reusablePath.push(current);
6049
- current = current.parent || current.slotParent;
6089
+ current = current.slotParent || current.parent;
6050
6090
  }
6051
6091
  reusablePath.length = 0;
6052
6092
  return false;
@@ -6080,7 +6120,7 @@ function markVNodeDirty(container, vNode, bits, cursorRoot = null) {
6080
6120
  if ((isRealDirty ? prevDirty & 127 /* ChoreBits.DIRTY_MASK */ : prevDirty) || vNode === cursorRoot) {
6081
6121
  return;
6082
6122
  }
6083
- const parent = vNode.parent || vNode.slotParent;
6123
+ const parent = vNode.slotParent || vNode.parent;
6084
6124
  // If cursorRoot is provided, propagate up to it
6085
6125
  if (cursorRoot && isRealDirty && parent && !parent.dirty) {
6086
6126
  propagateToCursorRoot(vNode, cursorRoot);
@@ -6092,7 +6132,9 @@ function markVNodeDirty(container, vNode, bits, cursorRoot = null) {
6092
6132
  parent.dirty |= 32 /* ChoreBits.CHILDREN */;
6093
6133
  }
6094
6134
  parent.dirtyChildren ||= [];
6095
- parent.dirtyChildren.push(vNode);
6135
+ if (!parent.dirtyChildren.includes(vNode)) {
6136
+ parent.dirtyChildren.push(vNode);
6137
+ }
6096
6138
  if (isRealDirty && vNode.dirtyChildren) {
6097
6139
  // this node is maybe an ancestor of the current cursor position
6098
6140
  // if so we must restart from here
@@ -6103,7 +6145,7 @@ function markVNodeDirty(container, vNode, bits, cursorRoot = null) {
6103
6145
  if (cursorPosition) {
6104
6146
  // find the ancestor of the cursor position that is current vNode
6105
6147
  while (cursorPosition !== cursor) {
6106
- cursorPosition = cursorPosition.parent || cursorPosition.slotParent;
6148
+ cursorPosition = cursorPosition.slotParent || cursorPosition.parent;
6107
6149
  if (cursorPosition === vNode) {
6108
6150
  // set cursor position to this node
6109
6151
  cursorData.position = vNode;
@@ -6487,7 +6529,7 @@ function vnode_walkVNode(vNode, callback) {
6487
6529
  let vCursor = vNode;
6488
6530
  // Depth first traversal
6489
6531
  if (vnode_isTextVNode(vNode)) {
6490
- // Text nodes don't have subscriptions or children;
6532
+ callback?.(vNode, null);
6491
6533
  return;
6492
6534
  }
6493
6535
  let vParent = null;
@@ -6635,11 +6677,6 @@ const vnode_getDomSibling = (vNode, nextDirection, descend) => {
6635
6677
  }
6636
6678
  return null;
6637
6679
  };
6638
- const vnode_ensureInflatedIfText = (journal, vNode) => {
6639
- if (vnode_isTextVNode(vNode)) {
6640
- vnode_ensureTextInflated(journal, vNode);
6641
- }
6642
- };
6643
6680
  const vnode_ensureTextInflated = (journal, vnode) => {
6644
6681
  const textVNode = ensureTextVNode(vnode);
6645
6682
  const flags = textVNode.flags;
@@ -6965,7 +7002,9 @@ const vnode_findInsertBefore = (journal, parent, insertBefore) => {
6965
7002
  else {
6966
7003
  adjustedInsertBefore = insertBefore;
6967
7004
  }
6968
- adjustedInsertBefore && vnode_ensureInflatedIfText(journal, adjustedInsertBefore);
7005
+ adjustedInsertBefore &&
7006
+ vnode_isTextVNode(adjustedInsertBefore) &&
7007
+ vnode_ensureTextInflated(journal, adjustedInsertBefore);
6969
7008
  return adjustedInsertBefore;
6970
7009
  };
6971
7010
  const vnode_connectSiblings = (parent, vNode, vNext) => {
@@ -7029,6 +7068,45 @@ const vnode_unlinkFromOldParent = (journal, currentParent, newParent, newChild)
7029
7068
  vnode_remove(journal, currentParent, newChild, false);
7030
7069
  }
7031
7070
  };
7071
+ /**
7072
+ * When a projection vnode is about to be repositioned (moved in the vnode tree), its trailing text
7073
+ * node must be inflated before the projection is unlinked from its current sibling chain.
7074
+ * `vnode_ensureTextInflated` relies on `vnode_getDomSibling` to locate adjacent text nodes and
7075
+ * decide which one is "last" (i.e. the one that gets to reuse the shared SSR DOM `Text` node). Once
7076
+ * the projection is unlinked, its `nextSibling` becomes `null`, so `getDomSibling` can no longer
7077
+ * cross the boundary to find a trailing sibling such as an empty-string text node — causing
7078
+ * `isLastNode` to be `true` prematurely and corrupting the shared DOM text node. Inflating the
7079
+ * trailing text node while the siblings are still connected gives it its own fresh DOM node and
7080
+ * avoids the corruption.
7081
+ *
7082
+ * Example:
7083
+ *
7084
+ * ```
7085
+ * <Component>
7086
+ * <button>
7087
+ * <InlineComponent>
7088
+ * <span>
7089
+ * "*"
7090
+ * </span>
7091
+ * </InlineComponent>
7092
+ * <Projection> // <-- this projection when unlinked from the siblings will cause the "test" text node to be considered the last node without inflating it
7093
+ * "test" // <-- this text node is sharing the same DOM node with the ""
7094
+ * </Projection>
7095
+ * "" <-- this text node is sharing the same DOM node with the "test"
7096
+ * </button>
7097
+ * </Component>
7098
+ * ```
7099
+ */
7100
+ const vnode_inflateProjectionTrailingText = (journal, projection) => {
7101
+ // Follow lastChild through any inner virtual wrappers to reach the actual trailing text node.
7102
+ let last = projection;
7103
+ while (last && vnode_isVirtualVNode(last)) {
7104
+ last = last.lastChild;
7105
+ }
7106
+ if (last && vnode_isTextVNode(last) && (last.flags & 8 /* VNodeFlags.Inflated */) === 0) {
7107
+ vnode_ensureTextInflated(journal, last);
7108
+ }
7109
+ };
7032
7110
  const vnode_insertBefore = (journal, parent, newChild, insertBefore) => {
7033
7111
  if (vnode_isElementOrTextVNode(newChild)) {
7034
7112
  vnode_insertElementBefore(journal, parent, newChild, insertBefore);