فهرست منبع

fix(runtime-vapor): re-sync whole slot fallback carrier block

daiwei 3 روز پیش
والد
کامیت
9c7505cb39

+ 32 - 0
packages/runtime-vapor/__tests__/componentSlots.spec.ts

@@ -418,6 +418,38 @@ describe('component: slots', () => {
       expect(fallback.onUpdated).toHaveLength(1)
     })
 
+    test('slot fallback controller re-syncs the whole carrier block order', async () => {
+      const container = document.createElement('div')
+      const carrierA = document.createTextNode('x')
+      const carrierB = document.createTextNode('y')
+      const marker = document.createTextNode('!')
+      const slotAnchor = document.createComment('slot')
+      const fallback = new VaporFragment([
+        document.createTextNode('a'),
+        document.createTextNode('b'),
+      ])
+      const controller = new SlotFallbackController({
+        getParentBoundary: () => null,
+        getLocalFallback: () => () => fallback,
+        getContent: () => [carrierA, carrierB],
+        getParentNode: () => container,
+        getAnchor: () => slotAnchor,
+        runWithRenderCtx: fn => fn(),
+        isContentValid: () => false,
+        onValidityChange: vi.fn(),
+      })
+
+      container.append(carrierA, marker, carrierB, slotAnchor)
+      controller.recheck()
+
+      expect(container.innerHTML).toBe('abx!y<!--slot-->')
+
+      controller.syncActiveFallback()
+      await nextTick()
+
+      expect(container.innerHTML).toBe('abxy!<!--slot-->')
+    })
+
     test('slot fallback controller defaults to idle when isBusy is omitted', () => {
       const fallback = document.createTextNode('fallback')
       const controller = new SlotFallbackController({

+ 4 - 3
packages/runtime-vapor/src/apiCreateFor.ts

@@ -176,9 +176,10 @@ export const createFor = (
               ) {
                 setCurrentHydrationNode(hydrationStart)
               }
-              queuePostFlushCb(() =>
-                anchor.parentNode!.insertBefore(parentAnchor, anchor),
-              )
+              queuePostFlushCb(() => {
+                const parentNode = anchor.parentNode
+                if (parentNode) parentNode.insertBefore(parentAnchor, anchor)
+              })
             } else {
               const close = locateHydrationBoundaryClose(currentHydrationNode!)
               parentAnchor = markHydrationAnchor(close)

+ 33 - 0
packages/runtime-vapor/src/fragment.ts

@@ -698,6 +698,39 @@ function collectBlockNodes(
   return nodes
 }
 
+function collectSlotFallbackCarrierNodes(
+  block: Block,
+  nodes: Node[] = [],
+  includeComments: boolean = false,
+): Node[] {
+  if (block instanceof Node) {
+    if (includeComments || !(block instanceof Comment)) {
+      nodes.push(block)
+    }
+    return nodes
+  }
+
+  if (isVaporComponent(block)) {
+    if (block.block) {
+      collectSlotFallbackCarrierNodes(block.block, nodes, includeComments)
+    }
+    return nodes
+  }
+
+  if (isArray(block)) {
+    for (const child of block) {
+      collectSlotFallbackCarrierNodes(child, nodes, includeComments)
+    }
+    return nodes
+  }
+
+  collectSlotFallbackCarrierNodes(block.nodes, nodes, true)
+  if (block.anchor) {
+    nodes.push(block.anchor)
+  }
+  return nodes
+}
+
 export function insertSlotFallbackCarrier(
   block: Block,
   parent: ParentNode,