Portal.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { ComponentInternalInstance } from '../component'
  2. import { SuspenseBoundary } from './Suspense'
  3. import {
  4. RendererInternals,
  5. MoveType,
  6. RendererElement,
  7. RendererNode,
  8. RendererOptions
  9. } from '../renderer'
  10. import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode'
  11. import { isString, ShapeFlags } from '@vue/shared'
  12. import { warn } from '../warning'
  13. export const isPortal = (type: any): boolean => type.__isPortal
  14. export interface PortalProps {
  15. target: string | object
  16. }
  17. export const PortalImpl = {
  18. __isPortal: true,
  19. process(
  20. n1: VNode | null,
  21. n2: VNode,
  22. container: RendererElement,
  23. anchor: RendererNode | null,
  24. parentComponent: ComponentInternalInstance | null,
  25. parentSuspense: SuspenseBoundary | null,
  26. isSVG: boolean,
  27. optimized: boolean,
  28. {
  29. mc: mountChildren,
  30. pc: patchChildren,
  31. pbc: patchBlockChildren,
  32. m: move,
  33. o: { insert, querySelector, createText, createComment }
  34. }: RendererInternals
  35. ) {
  36. const targetSelector = n2.props && n2.props.target
  37. const { shapeFlag, children } = n2
  38. if (n1 == null) {
  39. // insert an empty node as the placeholder for the portal
  40. insert((n2.el = createComment(`portal`)), container, anchor)
  41. if (__DEV__ && isString(targetSelector) && !querySelector) {
  42. warn(
  43. `Current renderer does not support string target for Portals. ` +
  44. `(missing querySelector renderer option)`
  45. )
  46. }
  47. const target = (n2.target = isString(targetSelector)
  48. ? querySelector!(targetSelector)
  49. : targetSelector)
  50. // portal content needs an anchor to support patching multiple portals
  51. // appending to the same target element.
  52. const portalAnchor = (n2.anchor = createText(''))
  53. if (target) {
  54. insert(portalAnchor, target)
  55. // Portal *always* has Array children. This is enforced in both the
  56. // compiler and vnode children normalization.
  57. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  58. mountChildren(
  59. children as VNodeArrayChildren,
  60. target,
  61. portalAnchor,
  62. parentComponent,
  63. parentSuspense,
  64. isSVG,
  65. optimized
  66. )
  67. }
  68. } else if (__DEV__) {
  69. warn('Invalid Portal target on mount:', target, `(${typeof target})`)
  70. }
  71. } else {
  72. // update content
  73. n2.el = n1.el
  74. const target = (n2.target = n1.target)!
  75. const portalAnchor = (n2.anchor = n1.anchor)!
  76. if (n2.dynamicChildren) {
  77. // fast path when the portal happens to be a block root
  78. patchBlockChildren(
  79. n1.dynamicChildren!,
  80. n2.dynamicChildren,
  81. container,
  82. parentComponent,
  83. parentSuspense,
  84. isSVG
  85. )
  86. } else if (!optimized) {
  87. patchChildren(
  88. n1,
  89. n2,
  90. target,
  91. portalAnchor,
  92. parentComponent,
  93. parentSuspense,
  94. isSVG
  95. )
  96. }
  97. // target changed
  98. if (targetSelector !== (n1.props && n1.props.target)) {
  99. const nextTarget = (n2.target = isString(targetSelector)
  100. ? querySelector!(targetSelector)
  101. : targetSelector)
  102. if (nextTarget) {
  103. movePortal(n2, nextTarget, null, insert, move)
  104. } else if (__DEV__) {
  105. warn('Invalid Portal target on update:', target, `(${typeof target})`)
  106. }
  107. }
  108. }
  109. },
  110. remove(
  111. vnode: VNode,
  112. { r: remove, o: { remove: hostRemove } }: RendererInternals
  113. ) {
  114. const { shapeFlag, children, anchor } = vnode
  115. hostRemove(anchor!)
  116. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  117. for (let i = 0; i < (children as VNode[]).length; i++) {
  118. remove((children as VNode[])[i])
  119. }
  120. }
  121. }
  122. }
  123. const movePortal = (
  124. vnode: VNode,
  125. nextTarget: RendererElement,
  126. anchor: RendererNode | null,
  127. insert: RendererOptions['insert'],
  128. move: RendererInternals['m']
  129. ) => {
  130. const { anchor: portalAnchor, shapeFlag, children } = vnode
  131. // move content.
  132. // Portal has either Array children or no children.
  133. insert(portalAnchor!, nextTarget, anchor)
  134. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  135. for (let i = 0; i < (children as VNode[]).length; i++) {
  136. move((children as VNode[])[i], nextTarget, portalAnchor, MoveType.REORDER)
  137. }
  138. }
  139. }
  140. // Force-casted public typing for h and TSX props inference
  141. export const Portal = (PortalImpl as any) as {
  142. __isPortal: true
  143. new (): { $props: VNodeProps & PortalProps }
  144. }