Vue Teleport Implementation Challenge
You're tasked with building a feature in a Vue.js application that allows certain UI elements, like modals or tooltips, to be rendered outside their DOM parent. This is crucial for preventing CSS stacking context issues and ensuring elements are visible regardless of their parent's positioning.
Problem Description
The goal is to implement a reusable Vue component that allows its content to be "teleported" to a designated target element in the DOM. This component should provide a clear API for specifying the target and managing the teleportation process.
Key Requirements:
TeleportTargetComponent: Create a Vue component namedTeleportTarget. This component will act as a placeholder for where the teleported content should be rendered. It should accept anidprop to uniquely identify it in the DOM.TeleportContentComponent: Create a Vue component namedTeleportContent. This component will wrap the content that needs to be teleported. It should accept atargetIdprop, which corresponds to theidof aTeleportTargetcomponent.- Dynamic Rendering: When
TeleportContentis mounted, its slotted content should be moved from its current DOM position to the DOM element identified by thetargetId. - Reactivity: If the
targetIdchanges, the content should be moved to the new target. - Cleanup: When
TeleportContentis unmounted, its content should be removed from the DOM. - Multiple Teleports: The system should support multiple
TeleportContentcomponents teleporting to different or the sameTeleportTargets.
Expected Behavior:
- A
TeleportTargetcomponent with a specificidshould create a corresponding DOM element with thatidin the application's main DOM (e.g., withinapp.vueor directly inindex.html). - A
TeleportContentcomponent, when rendered, should have its children nodes programmatically moved to the DOM element matching itstargetId. - The original location of the
TeleportContent's children should effectively become empty. - When the
TeleportContentcomponent is removed from the Vue component tree, its content should be removed from the DOM target.
Edge Cases:
- What happens if
targetIddoes not match any existingTeleportTarget? - What happens if the
TeleportTargetis rendered after theTeleportContent? - What happens if the
TeleportTargetis removed from the DOM?
Examples
Example 1: Basic Teleport
Imagine a simple modal.
App.vue
<template>
<div>
<h1>My App</h1>
<button @click="showModal = true">Open Modal</button>
<TeleportContent targetId="modal-container">
<MyModal v-if="showModal" @close="showModal = false" />
</TeleportContent>
<TeleportTarget id="modal-container" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import TeleportContent from './components/TeleportContent.vue';
import TeleportTarget from './components/TeleportTarget.vue';
import MyModal from './components/MyModal.vue'; // Assume this is a modal component
const showModal = ref(false);
</script>
components/MyModal.vue
<template>
<div class="modal-backdrop">
<div class="modal-content">
<h2>Modal Title</h2>
<p>This content is teleported!</p>
<button @click="$emit('close')">Close</button>
</div>
</div>
</template>
<style scoped>
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
}
</style>
Expected DOM Output (when modal is open):
The div with class modal-backdrop should appear directly within the DOM element corresponding to TeleportTarget with id="modal-container", NOT as a child of the div in App.vue that contains the TeleportContent component.
<body>
<div id="app">
<div>
<h1>My App</h1>
<button>Open Modal</button>
<!-- TeleportContent's original slot is now empty -->
</div>
<div id="modal-container">
<!-- The modal content appears here -->
<div class="modal-backdrop">
<div class="modal-content">
<h2>Modal Title</h2>
<p>This content is teleported!</p>
<button>Close</button>
</div>
</div>
</div>
</div>
</body>
Example 2: Tooltip Scenario
<!-- Parent Component -->
<template>
<div class="container">
<p>Hover over me for a tooltip.</p>
<TooltipTrigger>
<span class="hoverable">Hover Me</span>
<TooltipContent targetId="tooltip-layer">
<div class="tooltip">This is a dynamic tooltip!</div>
</TooltipContent>
</TooltipTrigger>
</div>
<TeleportTarget id="tooltip-layer" />
</template>
<script setup lang="ts">
import TooltipTrigger from './components/TooltipTrigger.vue';
import TooltipContent from './components/TooltipContent.vue';
import TeleportTarget from './components/TeleportTarget.vue';
</script>
<style>
.container {
position: relative; /* To demonstrate potential stacking context issues */
border: 1px solid blue;
padding: 50px;
margin: 50px;
}
.hoverable {
cursor: pointer;
background-color: yellow;
}
.tooltip {
background-color: black;
color: white;
padding: 10px;
border-radius: 4px;
position: absolute; /* Often used for tooltips */
/* ... positioning logic would be in TooltipTrigger or TooltipContent */
}
</style>
Expected Behavior:
The .tooltip div should be rendered directly inside the DOM element with id="tooltip-layer", preventing it from being clipped or affected by the position: relative of the .container if it were a child.
Constraints
- The solution must be implemented using Vue.js (Composition API or Options API is acceptable, but Composition API with
setupis preferred for modern Vue). - The solution must use TypeScript.
- Avoid using external libraries that provide teleportation functionality (like Vue's built-in
Teleportcomponent, as the goal is to implement the logic). - The
TeleportTargetcomponent should ideally be placed in a way that it can render its content at the application's root, or at least in a predictable location within the main DOM structure. - Performance is important; DOM manipulation should be efficient.
Notes
- Consider how to find the actual DOM element for a given
targetId. You might need to usedocument.getElementById. - Think about the lifecycle hooks in Vue (e.g.,
mounted,unmounted,updated) to manage the DOM manipulation. - How will you handle the content of the
TeleportContentcomponent? It will likely involve using slots. - When a
TeleportContentis unmounted, ensure its content is properly removed from the DOM target to avoid memory leaks. - Consider how to handle the scenario where the
TeleportTargetmight not exist whenTeleportContenttries to mount. A reasonable approach might be to log a warning or simply not teleport the content until the target is available.