Hone logo
Hone
Problems

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:

  1. TeleportTarget Component: Create a Vue component named TeleportTarget. This component will act as a placeholder for where the teleported content should be rendered. It should accept an id prop to uniquely identify it in the DOM.
  2. TeleportContent Component: Create a Vue component named TeleportContent. This component will wrap the content that needs to be teleported. It should accept a targetId prop, which corresponds to the id of a TeleportTarget component.
  3. Dynamic Rendering: When TeleportContent is mounted, its slotted content should be moved from its current DOM position to the DOM element identified by the targetId.
  4. Reactivity: If the targetId changes, the content should be moved to the new target.
  5. Cleanup: When TeleportContent is unmounted, its content should be removed from the DOM.
  6. Multiple Teleports: The system should support multiple TeleportContent components teleporting to different or the same TeleportTargets.

Expected Behavior:

  • A TeleportTarget component with a specific id should create a corresponding DOM element with that id in the application's main DOM (e.g., within app.vue or directly in index.html).
  • A TeleportContent component, when rendered, should have its children nodes programmatically moved to the DOM element matching its targetId.
  • The original location of the TeleportContent's children should effectively become empty.
  • When the TeleportContent component is removed from the Vue component tree, its content should be removed from the DOM target.

Edge Cases:

  • What happens if targetId does not match any existing TeleportTarget?
  • What happens if the TeleportTarget is rendered after the TeleportContent?
  • What happens if the TeleportTarget is 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 setup is preferred for modern Vue).
  • The solution must use TypeScript.
  • Avoid using external libraries that provide teleportation functionality (like Vue's built-in Teleport component, as the goal is to implement the logic).
  • The TeleportTarget component 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 use document.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 TeleportContent component? It will likely involve using slots.
  • When a TeleportContent is unmounted, ensure its content is properly removed from the DOM target to avoid memory leaks.
  • Consider how to handle the scenario where the TeleportTarget might not exist when TeleportContent tries to mount. A reasonable approach might be to log a warning or simply not teleport the content until the target is available.
Loading editor...
typescript