| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- import { ComponentInternalInstance } from '../component'
- import { SuspenseBoundary } from './Suspense'
- import {
- RendererInternals,
- MoveType,
- RendererElement,
- RendererNode,
- RendererOptions,
- traverseStaticChildren
- } from '../renderer'
- import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode'
- import { isString, ShapeFlags } from '@vue/shared'
- import { warn } from '../warning'
- import { isHmrUpdating } from '../hmr'
- export type TeleportVNode = VNode<RendererNode, RendererElement, TeleportProps>
- export interface TeleportProps {
- to: string | RendererElement | null | undefined
- disabled?: boolean
- }
- export const isTeleport = (type: any): boolean => type.__isTeleport
- const isTeleportDisabled = (props: VNode['props']): boolean =>
- props && (props.disabled || props.disabled === '')
- const isTargetSVG = (target: RendererElement): boolean =>
- typeof SVGElement !== 'undefined' && target instanceof SVGElement
- const resolveTarget = <T = RendererElement>(
- props: TeleportProps | null,
- select: RendererOptions['querySelector']
- ): T | null => {
- const targetSelector = props && props.to
- if (isString(targetSelector)) {
- if (!select) {
- __DEV__ &&
- warn(
- `Current renderer does not support string target for Teleports. ` +
- `(missing querySelector renderer option)`
- )
- return null
- } else {
- const target = select(targetSelector)
- if (!target) {
- __DEV__ &&
- warn(
- `Failed to locate Teleport target with selector "${targetSelector}". ` +
- `Note the target element must exist before the component is mounted - ` +
- `i.e. the target cannot be rendered by the component itself, and ` +
- `ideally should be outside of the entire Vue component tree.`
- )
- }
- return target as T
- }
- } else {
- if (__DEV__ && !targetSelector && !isTeleportDisabled(props)) {
- warn(`Invalid Teleport target: ${targetSelector}`)
- }
- return targetSelector as T
- }
- }
- export const TeleportImpl = {
- __isTeleport: true,
- process(
- n1: TeleportVNode | null,
- n2: TeleportVNode,
- container: RendererElement,
- anchor: RendererNode | null,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: SuspenseBoundary | null,
- isSVG: boolean,
- slotScopeIds: string[] | null,
- optimized: boolean,
- internals: RendererInternals
- ) {
- const {
- mc: mountChildren,
- pc: patchChildren,
- pbc: patchBlockChildren,
- o: { insert, querySelector, createText, createComment }
- } = internals
- const disabled = isTeleportDisabled(n2.props)
- let { shapeFlag, children, dynamicChildren } = n2
- // #3302
- // HMR updated, force full diff
- if (__DEV__ && isHmrUpdating) {
- optimized = false
- dynamicChildren = null
- }
- if (n1 == null) {
- // insert anchors in the main view
- const placeholder = (n2.el = __DEV__
- ? createComment('teleport start')
- : createText(''))
- const mainAnchor = (n2.anchor = __DEV__
- ? createComment('teleport end')
- : createText(''))
- insert(placeholder, container, anchor)
- insert(mainAnchor, container, anchor)
- const target = (n2.target = resolveTarget(n2.props, querySelector))
- const targetAnchor = (n2.targetAnchor = createText(''))
- if (target) {
- insert(targetAnchor, target)
- // #2652 we could be teleporting from a non-SVG tree into an SVG tree
- isSVG = isSVG || isTargetSVG(target)
- } else if (__DEV__ && !disabled) {
- warn('Invalid Teleport target on mount:', target, `(${typeof target})`)
- }
- const mount = (container: RendererElement, anchor: RendererNode) => {
- // Teleport *always* has Array children. This is enforced in both the
- // compiler and vnode children normalization.
- if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
- mountChildren(
- children as VNodeArrayChildren,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- optimized
- )
- }
- }
- if (disabled) {
- mount(container, mainAnchor)
- } else if (target) {
- mount(target, targetAnchor)
- }
- } else {
- // update content
- n2.el = n1.el
- const mainAnchor = (n2.anchor = n1.anchor)!
- const target = (n2.target = n1.target)!
- const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
- const wasDisabled = isTeleportDisabled(n1.props)
- const currentContainer = wasDisabled ? container : target
- const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
- isSVG = isSVG || isTargetSVG(target)
- if (dynamicChildren) {
- // fast path when the teleport happens to be a block root
- patchBlockChildren(
- n1.dynamicChildren!,
- dynamicChildren,
- currentContainer,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds
- )
- // even in block tree mode we need to make sure all root-level nodes
- // in the teleport inherit previous DOM references so that they can
- // be moved in future patches.
- traverseStaticChildren(n1, n2, true)
- } else if (!optimized) {
- patchChildren(
- n1,
- n2,
- currentContainer,
- currentAnchor,
- parentComponent,
- parentSuspense,
- isSVG,
- slotScopeIds,
- false
- )
- }
- if (disabled) {
- if (!wasDisabled) {
- // enabled -> disabled
- // move into main container
- moveTeleport(
- n2,
- container,
- mainAnchor,
- internals,
- TeleportMoveTypes.TOGGLE
- )
- }
- } else {
- // target changed
- if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
- const nextTarget = (n2.target = resolveTarget(
- n2.props,
- querySelector
- ))
- if (nextTarget) {
- moveTeleport(
- n2,
- nextTarget,
- null,
- internals,
- TeleportMoveTypes.TARGET_CHANGE
- )
- } else if (__DEV__) {
- warn(
- 'Invalid Teleport target on update:',
- target,
- `(${typeof target})`
- )
- }
- } else if (wasDisabled) {
- // disabled -> enabled
- // move into teleport target
- moveTeleport(
- n2,
- target,
- targetAnchor,
- internals,
- TeleportMoveTypes.TOGGLE
- )
- }
- }
- }
- updateCssVars(n2)
- },
- remove(
- vnode: VNode,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: SuspenseBoundary | null,
- optimized: boolean,
- { um: unmount, o: { remove: hostRemove } }: RendererInternals,
- doRemove: Boolean
- ) {
- const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode
- if (target) {
- hostRemove(targetAnchor!)
- }
- // an unmounted teleport should always remove its children if not disabled
- if (doRemove || !isTeleportDisabled(props)) {
- hostRemove(anchor!)
- if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
- for (let i = 0; i < (children as VNode[]).length; i++) {
- const child = (children as VNode[])[i]
- unmount(
- child,
- parentComponent,
- parentSuspense,
- true,
- !!child.dynamicChildren
- )
- }
- }
- }
- },
- move: moveTeleport,
- hydrate: hydrateTeleport
- }
- export const enum TeleportMoveTypes {
- TARGET_CHANGE,
- TOGGLE, // enable / disable
- REORDER // moved in the main view
- }
- function moveTeleport(
- vnode: VNode,
- container: RendererElement,
- parentAnchor: RendererNode | null,
- { o: { insert }, m: move }: RendererInternals,
- moveType: TeleportMoveTypes = TeleportMoveTypes.REORDER
- ) {
- // move target anchor if this is a target change.
- if (moveType === TeleportMoveTypes.TARGET_CHANGE) {
- insert(vnode.targetAnchor!, container, parentAnchor)
- }
- const { el, anchor, shapeFlag, children, props } = vnode
- const isReorder = moveType === TeleportMoveTypes.REORDER
- // move main view anchor if this is a re-order.
- if (isReorder) {
- insert(el!, container, parentAnchor)
- }
- // if this is a re-order and teleport is enabled (content is in target)
- // do not move children. So the opposite is: only move children if this
- // is not a reorder, or the teleport is disabled
- if (!isReorder || isTeleportDisabled(props)) {
- // Teleport has either Array children or no children.
- if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
- for (let i = 0; i < (children as VNode[]).length; i++) {
- move(
- (children as VNode[])[i],
- container,
- parentAnchor,
- MoveType.REORDER
- )
- }
- }
- }
- // move main view anchor if this is a re-order.
- if (isReorder) {
- insert(anchor!, container, parentAnchor)
- }
- }
- interface TeleportTargetElement extends Element {
- // last teleport target
- _lpa?: Node | null
- }
- function hydrateTeleport(
- node: Node,
- vnode: TeleportVNode,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: SuspenseBoundary | null,
- slotScopeIds: string[] | null,
- optimized: boolean,
- {
- o: { nextSibling, parentNode, querySelector }
- }: RendererInternals<Node, Element>,
- hydrateChildren: (
- node: Node | null,
- vnode: VNode,
- container: Element,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: SuspenseBoundary | null,
- slotScopeIds: string[] | null,
- optimized: boolean
- ) => Node | null
- ): Node | null {
- const target = (vnode.target = resolveTarget<Element>(
- vnode.props,
- querySelector
- ))
- if (target) {
- // if multiple teleports rendered to the same target element, we need to
- // pick up from where the last teleport finished instead of the first node
- const targetNode =
- (target as TeleportTargetElement)._lpa || target.firstChild
- if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
- if (isTeleportDisabled(vnode.props)) {
- vnode.anchor = hydrateChildren(
- nextSibling(node),
- vnode,
- parentNode(node)!,
- parentComponent,
- parentSuspense,
- slotScopeIds,
- optimized
- )
- vnode.targetAnchor = targetNode
- } else {
- vnode.anchor = nextSibling(node)
- // lookahead until we find the target anchor
- // we cannot rely on return value of hydrateChildren() because there
- // could be nested teleports
- let targetAnchor = targetNode
- while (targetAnchor) {
- targetAnchor = nextSibling(targetAnchor)
- if (
- targetAnchor &&
- targetAnchor.nodeType === 8 &&
- (targetAnchor as Comment).data === 'teleport anchor'
- ) {
- vnode.targetAnchor = targetAnchor
- ;(target as TeleportTargetElement)._lpa =
- vnode.targetAnchor && nextSibling(vnode.targetAnchor as Node)
- break
- }
- }
- hydrateChildren(
- targetNode,
- vnode,
- target,
- parentComponent,
- parentSuspense,
- slotScopeIds,
- optimized
- )
- }
- }
- updateCssVars(vnode)
- }
- return vnode.anchor && nextSibling(vnode.anchor as Node)
- }
- // Force-casted public typing for h and TSX props inference
- export const Teleport = TeleportImpl as unknown as {
- __isTeleport: true
- new (): { $props: VNodeProps & TeleportProps }
- }
- function updateCssVars(vnode: VNode) {
- // presence of .ut method indicates owner component uses css vars.
- // code path here can assume browser environment.
- const ctx = vnode.ctx
- if (ctx && ctx.ut) {
- let node = (vnode.children as VNode[])[0].el!
- while (node !== vnode.targetAnchor) {
- if (node.nodeType === 1) node.setAttribute('data-v-owner', ctx.uid)
- node = node.nextSibling
- }
- ctx.ut()
- }
- }
|