Skip to content

Commit 9be697b

Browse files
committed
wip: save
1 parent 2696f14 commit 9be697b

File tree

6 files changed

+196
-4
lines changed

6 files changed

+196
-4
lines changed

packages/runtime-core/src/components/Teleport.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ export const TeleportEndKey: unique symbol = Symbol('_vte')
2727

2828
export const isTeleport = (type: any): boolean => type.__isTeleport
2929

30-
const isTeleportDisabled = (props: VNode['props']): boolean =>
30+
export const isTeleportDisabled = (props: VNode['props']): boolean =>
3131
props && (props.disabled || props.disabled === '')
3232

33-
const isTeleportDeferred = (props: VNode['props']): boolean =>
33+
export const isTeleportDeferred = (props: VNode['props']): boolean =>
3434
props && (props.defer || props.defer === '')
3535

3636
const isTargetSVG = (target: RendererElement): boolean =>
@@ -39,7 +39,7 @@ const isTargetSVG = (target: RendererElement): boolean =>
3939
const isTargetMathML = (target: RendererElement): boolean =>
4040
typeof MathMLElement === 'function' && target instanceof MathMLElement
4141

42-
const resolveTarget = <T = RendererElement>(
42+
export const resolveTarget = <T = RendererElement>(
4343
props: TeleportProps | null,
4444
select: RendererOptions['querySelector'],
4545
): T | null => {

packages/runtime-core/src/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -557,3 +557,12 @@ export { startMeasure, endMeasure } from './profiling'
557557
* @internal
558558
*/
559559
export { initFeatureFlags } from './featureFlags'
560+
/**
561+
* @internal
562+
*/
563+
export {
564+
resolveTarget,
565+
isTeleportDisabled,
566+
isTeleportDeferred,
567+
TeleportEndKey,
568+
} from './components/Teleport'

packages/runtime-vapor/src/block.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export type BlockFn = (...args: any[]) => Block
2020

2121
export class VaporFragment {
2222
nodes: Block
23+
target?: ParentNode | null
24+
targetAnchor?: Node | null
2325
anchor?: Node
2426
insert?: (parent: ParentNode, anchor: Node | null) => void
2527
remove?: (parent?: ParentNode) => void
@@ -129,7 +131,7 @@ export function insert(
129131
// TODO handle hydration for vdom interop
130132
block.insert(parent, anchor)
131133
} else {
132-
insert(block.nodes, parent, anchor)
134+
insert(block.nodes, block.target || parent, block.targetAnchor || anchor)
133135
}
134136
if (block.anchor) insert(block.anchor, parent, anchor)
135137
}

packages/runtime-vapor/src/component.ts

+18
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
import { hmrReload, hmrRerender } from './hmr'
6161
import { isHydrating, locateHydrationNode } from './dom/hydration'
6262
import { insertionAnchor, insertionParent } from './insertionState'
63+
import type { VaporTeleportImpl } from './components/Teleport'
6364

6465
export { currentInstance } from '@vue/runtime-dom'
6566

@@ -92,6 +93,8 @@ export interface ObjectVaporComponent
9293

9394
name?: string
9495
vapor?: boolean
96+
97+
__isTeleport?: boolean
9598
}
9699

97100
interface SharedInternalOptions {
@@ -157,6 +160,21 @@ export function createComponent(
157160
return frag
158161
}
159162

163+
// teleport
164+
if (component.__isTeleport) {
165+
const frag = (component as typeof VaporTeleportImpl).process(
166+
rawProps!,
167+
rawSlots!,
168+
)
169+
if (!isHydrating && _insertionParent) {
170+
insert(frag, _insertionParent, _insertionAnchor)
171+
} else {
172+
frag.hydrate()
173+
}
174+
175+
return frag as any
176+
}
177+
160178
if (
161179
isSingleRoot &&
162180
component.inheritAttrs !== false &&
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import {
2+
TeleportEndKey,
3+
type TeleportProps,
4+
isTeleportDeferred,
5+
isTeleportDisabled,
6+
queuePostFlushCb,
7+
resolveTarget,
8+
warn,
9+
} from '@vue/runtime-dom'
10+
import {
11+
type Block,
12+
type BlockFn,
13+
VaporFragment,
14+
insert,
15+
remove,
16+
} from '../block'
17+
import { createComment, createTextNode, querySelector } from '../dom/node'
18+
import type { LooseRawProps, LooseRawSlots } from '../component'
19+
import { rawPropsProxyHandlers } from '../componentProps'
20+
import { renderEffect } from '../renderEffect'
21+
22+
export const VaporTeleportImpl = {
23+
name: 'VaporTeleport',
24+
__isTeleport: true,
25+
__vapor: true,
26+
27+
process(props: LooseRawProps, slots: LooseRawSlots): TeleportFragment {
28+
const children = slots.default && (slots.default as BlockFn)()
29+
const frag = __DEV__
30+
? new TeleportFragment('teleport')
31+
: new TeleportFragment()
32+
33+
const resolvedProps = new Proxy(
34+
props,
35+
rawPropsProxyHandlers,
36+
) as any as TeleportProps
37+
38+
renderEffect(() => frag.update(resolvedProps, children))
39+
40+
frag.remove = parent => {
41+
const {
42+
nodes,
43+
target,
44+
cachedTargetAnchor,
45+
targetStart,
46+
placeholder,
47+
mainAnchor,
48+
} = frag
49+
50+
remove(nodes, target || parent)
51+
52+
// remove anchors
53+
if (targetStart) {
54+
let parentNode = targetStart.parentNode!
55+
remove(targetStart!, parentNode)
56+
remove(cachedTargetAnchor!, parentNode)
57+
}
58+
if (placeholder && placeholder.isConnected) {
59+
remove(placeholder!, parent)
60+
remove(mainAnchor!, parent)
61+
}
62+
}
63+
64+
return frag
65+
},
66+
}
67+
68+
export class TeleportFragment extends VaporFragment {
69+
anchor: Node
70+
target?: ParentNode | null
71+
targetStart?: Node | null
72+
targetAnchor?: Node | null
73+
cachedTargetAnchor?: Node
74+
mainAnchor?: Node
75+
placeholder?: Node
76+
77+
constructor(anchorLabel?: string) {
78+
super([])
79+
this.anchor =
80+
__DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
81+
}
82+
83+
update(props: TeleportProps, children: Block): void {
84+
this.nodes = children
85+
const parent = this.anchor.parentNode
86+
87+
if (!this.mainAnchor) {
88+
this.mainAnchor = __DEV__
89+
? createComment('teleport end')
90+
: createTextNode()
91+
}
92+
if (!this.placeholder) {
93+
this.placeholder = __DEV__
94+
? createComment('teleport start')
95+
: createTextNode()
96+
}
97+
if (parent) {
98+
insert(this.placeholder, parent, this.anchor)
99+
insert(this.mainAnchor, parent, this.anchor)
100+
}
101+
102+
const disabled = isTeleportDisabled(props)
103+
if (disabled) {
104+
this.target = this.anchor.parentNode
105+
this.targetAnchor = parent ? this.mainAnchor : null
106+
} else {
107+
const target = (this.target = resolveTarget(
108+
props,
109+
querySelector,
110+
) as ParentNode)
111+
if (target) {
112+
if (
113+
// initial mount
114+
!this.targetStart ||
115+
// target changed
116+
this.targetStart.parentNode !== target
117+
) {
118+
;[this.targetAnchor, this.targetStart] = prepareAnchor(target)
119+
this.cachedTargetAnchor = this.targetAnchor
120+
} else {
121+
// re-mount or target not changed, use cached target anchor
122+
this.targetAnchor = this.cachedTargetAnchor
123+
}
124+
} else if (__DEV__) {
125+
warn('Invalid Teleport target on mount:', target, `(${typeof target})`)
126+
}
127+
}
128+
129+
const mountToTarget = () => {
130+
insert(this.nodes, this.target!, this.targetAnchor)
131+
}
132+
133+
if (parent) {
134+
if (isTeleportDeferred(props)) {
135+
queuePostFlushCb(mountToTarget)
136+
} else {
137+
mountToTarget()
138+
}
139+
}
140+
}
141+
142+
hydrate(): void {
143+
// TODO
144+
}
145+
}
146+
147+
function prepareAnchor(target: ParentNode | null) {
148+
const targetStart = createTextNode('targetStart')
149+
const targetAnchor = createTextNode('targetAnchor')
150+
151+
// attach a special property, so we can skip teleported content in
152+
// renderer's nextSibling search
153+
// @ts-expect-error
154+
targetStart[TeleportEndKey] = targetAnchor
155+
156+
if (target) {
157+
insert(targetStart, target)
158+
insert(targetAnchor, target)
159+
}
160+
161+
return [targetAnchor, targetStart]
162+
}

packages/runtime-vapor/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { createVaporApp, createVaporSSRApp } from './apiCreateApp'
33
export { defineVaporComponent } from './apiDefineComponent'
44
export { vaporInteropPlugin } from './vdomInterop'
55
export type { VaporDirective } from './directives/custom'
6+
export { VaporTeleportImpl as VaporTeleport } from './components/Teleport'
67

78
// compiler-use only
89
export { insert, prepend, remove, isFragment, VaporFragment } from './block'

0 commit comments

Comments
 (0)