Vue Teleport Implementation Challenge
The "teleport" pattern allows you to render a component's content into a different part of the DOM tree than where it's defined in your component hierarchy. This is incredibly useful for modals, tooltips, and other elements that need to break out of their parent's CSS or DOM stacking context. Your task is to implement a reusable Vue component that mimics this behavior.
Problem Description
You need to create a Vue component, let's call it TeleportWrapper, that accepts a target prop. This target prop will be a CSS selector string (e.g., '#modal-container', '.body'). The TeleportWrapper component should render its default slot's content into the DOM element that matches the provided target selector.
Key Requirements:
- Dynamic Targeting: The
targetprop should be reactive. If thetargetchanges, the teleported content should move to the new target. - Default Slot Rendering: All content within the
TeleportWrapper's default slot must be rendered in the target DOM element. - Clean Up: When the
TeleportWrappercomponent is unmounted, its content should be removed from the DOM. - Error Handling: If the
targetselector does not match any element in the DOM, the content should remain in its original location (i.e., within theTeleportWrapper's parent).
Expected Behavior:
When a TeleportWrapper is mounted, its children should be moved to the DOM element specified by the target prop. If the target element is not found, the children should stay within the TeleportWrapper's own DOM. If the target prop changes to a new valid selector, the children should move again to the new location.
Edge Cases:
- Invalid Target Selector: What happens if the
targetselector doesn't exist in the DOM? - Empty Slot: What if the
TeleportWrapperhas no content in its default slot? - Changing Target: How does the component behave when the
targetprop is updated dynamically?
Examples
Example 1:
App.vue:
<template>
<div>
<h1>Main Content</h1>
<TeleportWrapper target="#modal-root">
<div class="modal-content">
This is a modal!
</div>
</TeleportWrapper>
<div id="modal-root">
<!-- Modal content will be teleported here -->
</div>
</div>
</template>
Expected DOM Structure (after mount):
<div>
<h1>Main Content</h1>
<!-- TeleportWrapper itself might not render anything in its original spot -->
<div id="modal-root">
<div class="modal-content">
This is a modal!
</div>
</div>
</div>
Explanation: The div with class modal-content is rendered inside the div with id="modal-root".
Example 2:
App.vue:
<template>
<div>
<button @click="showTooltip = !showTooltip">Toggle Tooltip</button>
<div ref="targetElement">Hover over me for tooltip</div>
<TeleportWrapper v-if="showTooltip" :target="tooltipTarget">
<div class="tooltip">This is a tooltip!</div>
</TeleportWrapper>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import TeleportWrapper from './TeleportWrapper.vue';
const showTooltip = ref(false);
const tooltipTarget = ref('#tooltip-area'); // Assume there's a #tooltip-area somewhere
// In a real scenario, tooltipTarget might be dynamic
</script>
<style>
#tooltip-area {
position: relative;
border: 1px solid black;
padding: 20px;
}
.tooltip {
position: absolute;
background-color: yellow;
padding: 5px;
border: 1px solid gray;
top: 100%;
left: 0;
}
</style>
(Assume a #tooltip-area div exists in the main index.html or another component)
Expected Behavior: When the button is clicked and showTooltip is true, the "This is a tooltip!" div will be rendered inside the element matching #tooltip-area. If showTooltip becomes false, the tooltip will disappear.
Explanation: The tooltip content is conditionally rendered and teleported to a designated area.
Example 3 (Edge Case: Invalid Target):
App.vue:
<template>
<div>
<TeleportWrapper target=".non-existent-element">
<p>This content should not move.</p>
</TeleportWrapper>
</div>
</template>
Expected DOM Structure (after mount):
<div>
<p>This content should not move.</p>
</div>
Explanation: Since .non-existent-element is not found, the <p> tag remains rendered within the TeleportWrapper's original DOM location.
Constraints
- The
targetprop must be astring. - The
targetprop can be an ID selector (e.g.,"#my-id") or a class selector (e.g.,".my-class"). Support for more complex CSS selectors is optional but appreciated. - The component should be performant and not cause unnecessary DOM manipulations.
- The implementation should be done in TypeScript.
Notes
- Consider using Vue's lifecycle hooks (
onMounted,onUpdated,onBeforeUnmount) to manage DOM manipulation. document.querySelectorwill be your friend for finding the target element.- Think about how to efficiently move and re-append DOM nodes.
- The
TeleportWrappercomponent itself should ideally not render any DOM elements in its original position if the teleport is successful.