33 type GenericComponentInstance ,
44 MismatchTypes ,
55 MoveType ,
6+ type SchedulerJob ,
7+ SchedulerJobFlags ,
68 type TeleportProps ,
79 type TeleportTargetElement ,
810 isMismatchAllowed ,
@@ -82,6 +84,8 @@ export class TeleportFragment extends VaporFragment {
8284 mountContainer ?: ParentNode | null
8385 mountAnchor ?: Node | null
8486
87+ private mountToTargetJob ?: SchedulerJob
88+
8589 constructor ( props : LooseRawProps , slots ?: LooseRawSlots | null ) {
8690 super ( [ ] )
8791 this . rawProps = props
@@ -273,7 +277,21 @@ export class TeleportFragment extends VaporFragment {
273277 // typically due to an early insertion caused by setInsertionState.
274278 ! this . parent ! . isConnected
275279 ) {
276- queuePostFlushCb ( this . mountToTarget . bind ( this ) )
280+ // Reuse one queued mount job per Teleport instance so repeated
281+ // updates in the same flush don't enqueue duplicate target mounts.
282+ // If the previous job was disposed during unmount, recreate it.
283+ if (
284+ ! this . mountToTargetJob ||
285+ this . mountToTargetJob . flags ! & SchedulerJobFlags . DISPOSED
286+ ) {
287+ this . mountToTargetJob = ( ) => {
288+ this . mountToTargetJob = undefined
289+ // State may have changed before the post-flush job runs.
290+ if ( this . isDisabled || ! this . anchor ) return
291+ this . mountToTarget ( )
292+ }
293+ }
294+ queuePostFlushCb ( this . mountToTargetJob )
277295 } else {
278296 this . mountToTarget ( )
279297 }
@@ -297,6 +315,11 @@ export class TeleportFragment extends VaporFragment {
297315 }
298316
299317 remove = ( parent : ParentNode | undefined = this . parent ! ) : void => {
318+ if ( this . mountToTargetJob ) {
319+ this . mountToTargetJob . flags ! |= SchedulerJobFlags . DISPOSED
320+ this . mountToTargetJob = undefined
321+ }
322+
300323 // remove nodes
301324 if ( this . nodes && this . mountContainer ) {
302325 remove ( this . nodes , this . mountContainer )
0 commit comments