feat(portal): support multiple portal appending to same target
This commit is contained in:
		
							parent
							
								
									b8ffbffaf7
								
							
						
					
					
						commit
						aafb880a0a
					
				| @ -124,7 +124,7 @@ export const transformElement: NodeTransform = (node, context) => { | |||||||
| 
 | 
 | ||||||
|       const shouldBuildAsSlots = |       const shouldBuildAsSlots = | ||||||
|         isComponent && |         isComponent && | ||||||
|         // Portal is not a real component has dedicated handling in the renderer
 |         // Portal is not a real component and has dedicated runtime handling
 | ||||||
|         vnodeTag !== PORTAL && |         vnodeTag !== PORTAL && | ||||||
|         // explained above.
 |         // explained above.
 | ||||||
|         vnodeTag !== KEEP_ALIVE |         vnodeTag !== KEEP_ALIVE | ||||||
| @ -135,7 +135,7 @@ export const transformElement: NodeTransform = (node, context) => { | |||||||
|         if (hasDynamicSlots) { |         if (hasDynamicSlots) { | ||||||
|           patchFlag |= PatchFlags.DYNAMIC_SLOTS |           patchFlag |= PatchFlags.DYNAMIC_SLOTS | ||||||
|         } |         } | ||||||
|       } else if (node.children.length === 1) { |       } else if (node.children.length === 1 && vnodeTag !== PORTAL) { | ||||||
|         const child = node.children[0] |         const child = node.children[0] | ||||||
|         const type = child.type |         const type = child.type | ||||||
|         // check for dynamic text children
 |         // check for dynamic text children
 | ||||||
|  | |||||||
| @ -3,29 +3,32 @@ import { | |||||||
|   serializeInner, |   serializeInner, | ||||||
|   render, |   render, | ||||||
|   h, |   h, | ||||||
|   defineComponent, |  | ||||||
|   Portal, |   Portal, | ||||||
|   Text, |   Text, | ||||||
|   ref, |   ref, | ||||||
|   nextTick, |   nextTick | ||||||
|   TestElement, |  | ||||||
|   TestNode |  | ||||||
| } from '@vue/runtime-test' | } from '@vue/runtime-test' | ||||||
| import { VNodeArrayChildren, createVNode } from '../../src/vnode' | import { createVNode } from '../../src/vnode' | ||||||
| 
 | 
 | ||||||
| describe('renderer: portal', () => { | describe('renderer: portal', () => { | ||||||
|   test('should work', () => { |   test('should work', () => { | ||||||
|     const target = nodeOps.createElement('div') |     const target = nodeOps.createElement('div') | ||||||
|     const root = nodeOps.createElement('div') |     const root = nodeOps.createElement('div') | ||||||
| 
 | 
 | ||||||
|     const Comp = defineComponent(() => () => [ |     render( | ||||||
|  |       h(() => [ | ||||||
|         h(Portal, { target }, h('div', 'teleported')), |         h(Portal, { target }, h('div', 'teleported')), | ||||||
|         h('div', 'root') |         h('div', 'root') | ||||||
|     ]) |       ]), | ||||||
|     render(h(Comp), root) |       root | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
|     expect(serializeInner(root)).toMatchSnapshot() |     expect(serializeInner(root)).toMatchInlineSnapshot( | ||||||
|     expect(serializeInner(target)).toMatchSnapshot() |       `"<!--portal--><div>root</div>"` | ||||||
|  |     ) | ||||||
|  |     expect(serializeInner(target)).toMatchInlineSnapshot( | ||||||
|  |       `"<div>teleported</div>"` | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   test('should update target', async () => { |   test('should update target', async () => { | ||||||
| @ -34,58 +37,70 @@ describe('renderer: portal', () => { | |||||||
|     const target = ref(targetA) |     const target = ref(targetA) | ||||||
|     const root = nodeOps.createElement('div') |     const root = nodeOps.createElement('div') | ||||||
| 
 | 
 | ||||||
|     const Comp = defineComponent(() => () => [ |     render( | ||||||
|  |       h(() => [ | ||||||
|         h(Portal, { target: target.value }, h('div', 'teleported')), |         h(Portal, { target: target.value }, h('div', 'teleported')), | ||||||
|         h('div', 'root') |         h('div', 'root') | ||||||
|     ]) |       ]), | ||||||
|     render(h(Comp), root) |       root | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
|     expect(serializeInner(root)).toMatchSnapshot() |     expect(serializeInner(root)).toMatchInlineSnapshot( | ||||||
|     expect(serializeInner(targetA)).toMatchSnapshot() |       `"<!--portal--><div>root</div>"` | ||||||
|     expect(serializeInner(targetB)).toMatchSnapshot() |     ) | ||||||
|  |     expect(serializeInner(targetA)).toMatchInlineSnapshot( | ||||||
|  |       `"<div>teleported</div>"` | ||||||
|  |     ) | ||||||
|  |     expect(serializeInner(targetB)).toMatchInlineSnapshot(`""`) | ||||||
| 
 | 
 | ||||||
|     target.value = targetB |     target.value = targetB | ||||||
|     await nextTick() |     await nextTick() | ||||||
| 
 | 
 | ||||||
|     expect(serializeInner(root)).toMatchSnapshot() |     expect(serializeInner(root)).toMatchInlineSnapshot( | ||||||
|     expect(serializeInner(targetA)).toMatchSnapshot() |       `"<!--portal--><div>root</div>"` | ||||||
|     expect(serializeInner(targetB)).toMatchSnapshot() |     ) | ||||||
|  |     expect(serializeInner(targetA)).toMatchInlineSnapshot(`""`) | ||||||
|  |     expect(serializeInner(targetB)).toMatchInlineSnapshot( | ||||||
|  |       `"<div>teleported</div>"` | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   test('should update children', async () => { |   test('should update children', async () => { | ||||||
|     const target = nodeOps.createElement('div') |     const target = nodeOps.createElement('div') | ||||||
|     const root = nodeOps.createElement('div') |     const root = nodeOps.createElement('div') | ||||||
|     const children = ref<VNodeArrayChildren<TestNode, TestElement>>([ |     const children = ref([h('div', 'teleported')]) | ||||||
|       h('div', 'teleported') |  | ||||||
|     ]) |  | ||||||
| 
 | 
 | ||||||
|     const Comp = defineComponent(() => () => |     render(h(Portal, { target }, children.value), root) | ||||||
|       h(Portal, { target }, children.value) |     expect(serializeInner(target)).toMatchInlineSnapshot( | ||||||
|  |       `"<div>teleported</div>"` | ||||||
|     ) |     ) | ||||||
|     render(h(Comp), root) |  | ||||||
| 
 |  | ||||||
|     expect(serializeInner(target)).toMatchSnapshot() |  | ||||||
| 
 | 
 | ||||||
|     children.value = [] |     children.value = [] | ||||||
|     await nextTick() |     await nextTick() | ||||||
| 
 | 
 | ||||||
|     expect(serializeInner(target)).toMatchSnapshot() |     expect(serializeInner(target)).toMatchInlineSnapshot( | ||||||
|  |       `"<div>teleported</div>"` | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
|     children.value = [createVNode(Text, null, 'teleported')] |     children.value = [createVNode(Text, null, 'teleported')] | ||||||
|     await nextTick() |     await nextTick() | ||||||
| 
 | 
 | ||||||
|     expect(serializeInner(target)).toMatchSnapshot() |     expect(serializeInner(target)).toMatchInlineSnapshot( | ||||||
|  |       `"<div>teleported</div>"` | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   test('should remove children when unmounted', () => { |   test('should remove children when unmounted', () => { | ||||||
|     const target = nodeOps.createElement('div') |     const target = nodeOps.createElement('div') | ||||||
|     const root = nodeOps.createElement('div') |     const root = nodeOps.createElement('div') | ||||||
| 
 | 
 | ||||||
|     const Comp = defineComponent(() => () => [ |     render( | ||||||
|  |       h(() => [ | ||||||
|         h(Portal, { target }, h('div', 'teleported')), |         h(Portal, { target }, h('div', 'teleported')), | ||||||
|         h('div', 'root') |         h('div', 'root') | ||||||
|     ]) |       ]), | ||||||
|     render(h(Comp), root) |       root | ||||||
|  |     ) | ||||||
|     expect(serializeInner(target)).toMatchInlineSnapshot( |     expect(serializeInner(target)).toMatchInlineSnapshot( | ||||||
|       `"<div>teleported</div>"` |       `"<div>teleported</div>"` | ||||||
|     ) |     ) | ||||||
| @ -93,4 +108,72 @@ describe('renderer: portal', () => { | |||||||
|     render(null, root) |     render(null, root) | ||||||
|     expect(serializeInner(target)).toBe('') |     expect(serializeInner(target)).toBe('') | ||||||
|   }) |   }) | ||||||
|  | 
 | ||||||
|  |   test('multiple portal with same target', () => { | ||||||
|  |     const target = nodeOps.createElement('div') | ||||||
|  |     const root = nodeOps.createElement('div') | ||||||
|  | 
 | ||||||
|  |     render( | ||||||
|  |       h('div', [ | ||||||
|  |         h(Portal, { target }, h('div', 'one')), | ||||||
|  |         h(Portal, { target }, 'two') | ||||||
|  |       ]), | ||||||
|  |       root | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     expect(serializeInner(root)).toMatchInlineSnapshot( | ||||||
|  |       `"<div><!--portal--><!--portal--></div>"` | ||||||
|  |     ) | ||||||
|  |     expect(serializeInner(target)).toMatchInlineSnapshot(`"<div>one</div>two"`) | ||||||
|  | 
 | ||||||
|  |     // update existing content
 | ||||||
|  |     render( | ||||||
|  |       h('div', [ | ||||||
|  |         h(Portal, { target }, [h('div', 'one'), h('div', 'two')]), | ||||||
|  |         h(Portal, { target }, 'three') | ||||||
|  |       ]), | ||||||
|  |       root | ||||||
|  |     ) | ||||||
|  |     expect(serializeInner(target)).toMatchInlineSnapshot( | ||||||
|  |       `"<div>one</div><div>two</div>three"` | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     // toggling
 | ||||||
|  |     render(h('div', [null, h(Portal, { target }, 'three')]), root) | ||||||
|  |     expect(serializeInner(root)).toMatchInlineSnapshot( | ||||||
|  |       `"<div><!----><!--portal--></div>"` | ||||||
|  |     ) | ||||||
|  |     expect(serializeInner(target)).toMatchInlineSnapshot(`"three"`) | ||||||
|  | 
 | ||||||
|  |     // toggle back
 | ||||||
|  |     render( | ||||||
|  |       h('div', [ | ||||||
|  |         h(Portal, { target }, [h('div', 'one'), h('div', 'two')]), | ||||||
|  |         h(Portal, { target }, 'three') | ||||||
|  |       ]), | ||||||
|  |       root | ||||||
|  |     ) | ||||||
|  |     expect(serializeInner(root)).toMatchInlineSnapshot( | ||||||
|  |       `"<div><!--portal--><!--portal--></div>"` | ||||||
|  |     ) | ||||||
|  |     // should append
 | ||||||
|  |     expect(serializeInner(target)).toMatchInlineSnapshot( | ||||||
|  |       `"three<div>one</div><div>two</div>"` | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     // toggle the other portal
 | ||||||
|  |     render( | ||||||
|  |       h('div', [ | ||||||
|  |         h(Portal, { target }, [h('div', 'one'), h('div', 'two')]), | ||||||
|  |         null | ||||||
|  |       ]), | ||||||
|  |       root | ||||||
|  |     ) | ||||||
|  |     expect(serializeInner(root)).toMatchInlineSnapshot( | ||||||
|  |       `"<div><!--portal--><!----></div>"` | ||||||
|  |     ) | ||||||
|  |     expect(serializeInner(target)).toMatchInlineSnapshot( | ||||||
|  |       `"<div>one</div><div>two</div>"` | ||||||
|  |     ) | ||||||
|  |   }) | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -1,23 +0,0 @@ | |||||||
| // Jest Snapshot v1, https://goo.gl/fbAQLP |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should update children 1`] = `"<div>teleported</div>"`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should update children 2`] = `""`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should update children 3`] = `"teleported"`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should update target 1`] = `"<!--portal--><div>root</div>"`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should update target 2`] = `"<div>teleported</div>"`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should update target 3`] = `""`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should update target 4`] = `"<!--portal--><div>root</div>"`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should update target 5`] = `""`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should update target 6`] = `"<div>teleported</div>"`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should work 1`] = `"<!--portal--><div>root</div>"`; |  | ||||||
| 
 |  | ||||||
| exports[`renderer: portal should work 2`] = `"<div>teleported</div>"`; |  | ||||||
| @ -4,10 +4,11 @@ import { | |||||||
|   RendererInternals, |   RendererInternals, | ||||||
|   MoveType, |   MoveType, | ||||||
|   RendererElement, |   RendererElement, | ||||||
|   RendererNode |   RendererNode, | ||||||
|  |   RendererOptions | ||||||
| } from '../renderer' | } from '../renderer' | ||||||
| import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode' | import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode' | ||||||
| import { isString, ShapeFlags, PatchFlags } from '@vue/shared' | import { isString, ShapeFlags } from '@vue/shared' | ||||||
| import { warn } from '../warning' | import { warn } from '../warning' | ||||||
| 
 | 
 | ||||||
| export const isPortal = (type: any): boolean => type.__isPortal | export const isPortal = (type: any): boolean => type.__isPortal | ||||||
| @ -32,11 +33,11 @@ export const PortalImpl = { | |||||||
|       pc: patchChildren, |       pc: patchChildren, | ||||||
|       pbc: patchBlockChildren, |       pbc: patchBlockChildren, | ||||||
|       m: move, |       m: move, | ||||||
|       o: { insert, querySelector, setElementText, createComment } |       o: { insert, querySelector, createText, createComment } | ||||||
|     }: RendererInternals |     }: RendererInternals | ||||||
|   ) { |   ) { | ||||||
|     const targetSelector = n2.props && n2.props.target |     const targetSelector = n2.props && n2.props.target | ||||||
|     const { patchFlag, shapeFlag, children } = n2 |     const { shapeFlag, children } = n2 | ||||||
|     if (n1 == null) { |     if (n1 == null) { | ||||||
|       // insert an empty node as the placeholder for the portal
 |       // insert an empty node as the placeholder for the portal
 | ||||||
|       insert((n2.el = createComment(`portal`)), container, anchor) |       insert((n2.el = createComment(`portal`)), container, anchor) | ||||||
| @ -49,14 +50,18 @@ export const PortalImpl = { | |||||||
|       const target = (n2.target = isString(targetSelector) |       const target = (n2.target = isString(targetSelector) | ||||||
|         ? querySelector!(targetSelector) |         ? querySelector!(targetSelector) | ||||||
|         : targetSelector) |         : targetSelector) | ||||||
|  |       // portal content needs an anchor to support patching multiple portals
 | ||||||
|  |       // appending to the same target element.
 | ||||||
|  |       const portalAnchor = (n2.anchor = createText('')) | ||||||
|       if (target) { |       if (target) { | ||||||
|         if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { |         insert(portalAnchor, target) | ||||||
|           setElementText(target, children as string) |         // Portal *always* has Array children. This is enforced in both the
 | ||||||
|         } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { |         // compiler and vnode children normalization.
 | ||||||
|  |         if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { | ||||||
|           mountChildren( |           mountChildren( | ||||||
|             children as VNodeArrayChildren, |             children as VNodeArrayChildren, | ||||||
|             target, |             target, | ||||||
|             null, |             portalAnchor, | ||||||
|             parentComponent, |             parentComponent, | ||||||
|             parentSuspense, |             parentSuspense, | ||||||
|             isSVG, |             isSVG, | ||||||
| @ -67,12 +72,11 @@ export const PortalImpl = { | |||||||
|         warn('Invalid Portal target on mount:', target, `(${typeof target})`) |         warn('Invalid Portal target on mount:', target, `(${typeof target})`) | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       n2.el = n1.el |  | ||||||
|       // update content
 |       // update content
 | ||||||
|  |       n2.el = n1.el | ||||||
|       const target = (n2.target = n1.target)! |       const target = (n2.target = n1.target)! | ||||||
|       if (patchFlag === PatchFlags.TEXT) { |       const portalAnchor = (n2.anchor = n1.anchor)! | ||||||
|         setElementText(target, children as string) |       if (n2.dynamicChildren) { | ||||||
|       } else if (n2.dynamicChildren) { |  | ||||||
|         // fast path when the portal happens to be a block root
 |         // fast path when the portal happens to be a block root
 | ||||||
|         patchBlockChildren( |         patchBlockChildren( | ||||||
|           n1.dynamicChildren!, |           n1.dynamicChildren!, | ||||||
| @ -87,27 +91,20 @@ export const PortalImpl = { | |||||||
|           n1, |           n1, | ||||||
|           n2, |           n2, | ||||||
|           target, |           target, | ||||||
|           null, |           portalAnchor, | ||||||
|           parentComponent, |           parentComponent, | ||||||
|           parentSuspense, |           parentSuspense, | ||||||
|           isSVG |           isSVG | ||||||
|         ) |         ) | ||||||
|       } |       } | ||||||
|  | 
 | ||||||
|       // target changed
 |       // target changed
 | ||||||
|       if (targetSelector !== (n1.props && n1.props.target)) { |       if (targetSelector !== (n1.props && n1.props.target)) { | ||||||
|         const nextTarget = (n2.target = isString(targetSelector) |         const nextTarget = (n2.target = isString(targetSelector) | ||||||
|           ? querySelector!(targetSelector) |           ? querySelector!(targetSelector) | ||||||
|           : targetSelector) |           : targetSelector) | ||||||
|         if (nextTarget) { |         if (nextTarget) { | ||||||
|           // move content
 |           movePortal(n2, nextTarget, null, insert, move) | ||||||
|           if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { |  | ||||||
|             setElementText(target, '') |  | ||||||
|             setElementText(nextTarget, children as string) |  | ||||||
|           } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { |  | ||||||
|             for (let i = 0; i < (children as VNode[]).length; i++) { |  | ||||||
|               move((children as VNode[])[i], nextTarget, null, MoveType.REORDER) |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|         } else if (__DEV__) { |         } else if (__DEV__) { | ||||||
|           warn('Invalid Portal target on update:', target, `(${typeof target})`) |           warn('Invalid Portal target on update:', target, `(${typeof target})`) | ||||||
|         } |         } | ||||||
| @ -117,12 +114,11 @@ export const PortalImpl = { | |||||||
| 
 | 
 | ||||||
|   remove( |   remove( | ||||||
|     vnode: VNode, |     vnode: VNode, | ||||||
|     { r: remove, o: { setElementText } }: RendererInternals |     { r: remove, o: { remove: hostRemove } }: RendererInternals | ||||||
|   ) { |   ) { | ||||||
|     const { target, shapeFlag, children } = vnode |     const { shapeFlag, children, anchor } = vnode | ||||||
|     if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { |     hostRemove(anchor!) | ||||||
|       setElementText(target!, '') |     if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { | ||||||
|     } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { |  | ||||||
|       for (let i = 0; i < (children as VNode[]).length; i++) { |       for (let i = 0; i < (children as VNode[]).length; i++) { | ||||||
|         remove((children as VNode[])[i]) |         remove((children as VNode[])[i]) | ||||||
|       } |       } | ||||||
| @ -130,6 +126,24 @@ export const PortalImpl = { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const movePortal = ( | ||||||
|  |   vnode: VNode, | ||||||
|  |   nextTarget: RendererElement, | ||||||
|  |   anchor: RendererNode | null, | ||||||
|  |   insert: RendererOptions['insert'], | ||||||
|  |   move: RendererInternals['m'] | ||||||
|  | ) => { | ||||||
|  |   const { anchor: portalAnchor, shapeFlag, children } = vnode | ||||||
|  |   // move content.
 | ||||||
|  |   // Portal has either Array children or no children.
 | ||||||
|  |   insert(portalAnchor!, nextTarget, anchor) | ||||||
|  |   if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { | ||||||
|  |     for (let i = 0; i < (children as VNode[]).length; i++) { | ||||||
|  |       move((children as VNode[])[i], nextTarget, portalAnchor, MoveType.REORDER) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Force-casted public typing for h and TSX props inference
 | // Force-casted public typing for h and TSX props inference
 | ||||||
| export const Portal = (PortalImpl as any) as { | export const Portal = (PortalImpl as any) as { | ||||||
|   __isPortal: true |   __isPortal: true | ||||||
|  | |||||||
| @ -1839,7 +1839,7 @@ function baseCreateRenderer( | |||||||
|     if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) { |     if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) { | ||||||
|       return vnode.suspense!.next() |       return vnode.suspense!.next() | ||||||
|     } |     } | ||||||
|     return hostNextSibling((vnode.anchor || vnode.el)!) |     return hostNextSibling((vnode.type === Fragment ? vnode.anchor : vnode.el)!) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const setRef = ( |   const setRef = ( | ||||||
|  | |||||||
| @ -419,14 +419,17 @@ export function cloneIfMounted(child: VNode): VNode { | |||||||
| 
 | 
 | ||||||
| export function normalizeChildren(vnode: VNode, children: unknown) { | export function normalizeChildren(vnode: VNode, children: unknown) { | ||||||
|   let type = 0 |   let type = 0 | ||||||
|  |   const { shapeFlag } = vnode | ||||||
|   if (children == null) { |   if (children == null) { | ||||||
|     children = null |     children = null | ||||||
|   } else if (isArray(children)) { |   } else if (isArray(children)) { | ||||||
|     type = ShapeFlags.ARRAY_CHILDREN |     type = ShapeFlags.ARRAY_CHILDREN | ||||||
|   } else if (typeof children === 'object') { |   } else if (typeof children === 'object') { | ||||||
|     // in case <component :is="x"> resolves to native element, the vnode call
 |     // Normalize slot to plain children
 | ||||||
|     // will receive slots object.
 |     if ( | ||||||
|     if (vnode.shapeFlag & ShapeFlags.ELEMENT && (children as any).default) { |       (shapeFlag & ShapeFlags.ELEMENT || shapeFlag & ShapeFlags.PORTAL) && | ||||||
|  |       (children as any).default | ||||||
|  |     ) { | ||||||
|       normalizeChildren(vnode, (children as any).default()) |       normalizeChildren(vnode, (children as any).default()) | ||||||
|       return |       return | ||||||
|     } else { |     } else { | ||||||
| @ -440,8 +443,14 @@ export function normalizeChildren(vnode: VNode, children: unknown) { | |||||||
|     type = ShapeFlags.SLOTS_CHILDREN |     type = ShapeFlags.SLOTS_CHILDREN | ||||||
|   } else { |   } else { | ||||||
|     children = String(children) |     children = String(children) | ||||||
|  |     // force portal children to array so it can be moved around
 | ||||||
|  |     if (shapeFlag & ShapeFlags.PORTAL) { | ||||||
|  |       type = ShapeFlags.ARRAY_CHILDREN | ||||||
|  |       children = [createTextVNode(children as string)] | ||||||
|  |     } else { | ||||||
|       type = ShapeFlags.TEXT_CHILDREN |       type = ShapeFlags.TEXT_CHILDREN | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|   vnode.children = children as VNodeNormalizedChildren |   vnode.children = children as VNodeNormalizedChildren | ||||||
|   vnode.shapeFlag |= type |   vnode.shapeFlag |= type | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user