The PortalManager enables DOM teleportation, allowing elements to be rendered in different parts of the DOM tree while maintaining their component context. This is useful for modals, tooltips, and overlays that need to escape layout constraints.
Platform: JS only Package: code.yousef.summon.runtime Since: 0.4.8.4
object PortalManager {
fun portal(element: Element, targetSelector: String)
fun unportal(element: Element)
fun isPortaled(element: Element): Boolean
fun getPortalTarget(element: Element): String?
}
Moves a DOM element to a different container in the document.
Signature:
fun portal(element: Element, targetSelector: String)
Parameters:
element: Element - The DOM element to movetargetSelector: String - CSS selector for the target container (e.g., "body", "#modal-root")Behavior:
Example:
val modalElement = document.getElementById("myModal")
PortalManager.portal(modalElement, "body")
// Element is now a child of <body>
Returns a portaled element to its original parent.
Signature:
fun unportal(element: Element)
Parameters:
element: Element - The portaled element to restoreBehavior:
Example:
val modalElement = document.getElementById("myModal")
PortalManager.unportal(modalElement)
// Element is back in its original location
Checks if an element is currently portaled.
Signature:
fun isPortaled(element: Element): Boolean
Parameters:
element: Element - The element to checkReturns: true if the element is portaled, false otherwise
Example:
if (PortalManager.isPortaled(modalElement)) {
println("Element is currently portaled")
}
Gets the target selector for a portaled element.
Signature:
fun getPortalTarget(element: Element): String?
Parameters:
element: Element - The portaled elementReturns: The target selector string, or null if not portaled
Example:
val target = PortalManager.getPortalTarget(modalElement)
// Returns "body" if element was portaled to body
The Portal component automatically uses PortalManager:
@Composable
fun MyModal(isOpen: Boolean, onClose: () -> Unit) {
if (isOpen) {
Portal(target = "body") {
Box(modifier = Modifier()
.position(Position.Fixed)
.top("50%")
.left("50%")
.transform("translate(-50%, -50%)")
.zIndex(9999)
) {
Text("Modal Content")
Button(onClick = onClose, label = "Close")
}
}
}
}
Element ID: "#modal-root"
Class Name: ".portal-container"
Document Body: "body"
Custom Selector: Any valid CSS selector
document.querySelector()If a target doesn't exist, PortalManager automatically creates it:
// For #modal-root
<div id="modal-root"></div>
// For .portal-container
<div class="portal-container"></div>
// For custom selectors
<div data-portal-container="custom-selector"></div>
@Composable
fun FullScreenModal(content: @Composable () -> Unit) {
Portal(target = "body") {
// Overlay
Box(modifier = Modifier()
.position(Position.Fixed)
.top("0")
.left("0")
.right("0")
.bottom("0")
.backgroundColor("rgba(0,0,0,0.5)")
.zIndex(9999)
) {
// Modal content
Box(modifier = Modifier()
.backgroundColor("white")
.borderRadius("8px")
.padding("32px")
) {
content()
}
}
}
}
@Composable
fun Tooltip(text: String, anchorId: String) {
Portal(target = "body") {
Box(modifier = Modifier()
.position(Position.Absolute)
.zIndex(10000)
.backgroundColor("#333")
.color("white")
.padding("8px 12px")
.borderRadius("4px")
.fontSize("14px")
) {
Text(text)
}
}
}
@Composable
fun NotificationContainer() {
Portal(target = "body") {
Box(modifier = Modifier()
.position(Position.Fixed)
.top("20px")
.right("20px")
.zIndex(10000)
.display(Display.Flex)
.flexDirection(FlexDirection.Column)
.gap("10px")
) {
// Notifications render here
}
}
}
private data class PortalInfo(
val originalParent: Element,
val targetSelector: String,
val targetContainer: HTMLElement
)
private val portaledElements = mutableMapOf<Element, PortalInfo>()
private val portalContainers = mutableMapOf<String, HTMLElement>()
Portal Creation:
appendChild()portaledElementsPortal Removal:
The PortalManager is automatically used by:
Portal component via data-portal-target attributePlatformRenderer during element creationYou can also use PortalManager directly:
// Manual portal
val element = document.createElement("div")
document.body?.appendChild(element)
PortalManager.portal(element, "#custom-container")
// Later...
PortalManager.unportal(element)
All operations are safe and handle edge cases:
// Portaling already portaled element: no-op or re-portal
PortalManager.portal(element, "body")
PortalManager.portal(element, "#other-target") // Moves to new target
// Unportaling non-portaled element: no-op
PortalManager.unportal(element) // Safe, does nothing
// Getting target of non-portaled element: returns null
val target = PortalManager.getPortalTarget(element) // null
// Log portal information
if (PortalManager.isPortaled(element)) {
val target = PortalManager.getPortalTarget(element)
console.log("Element is portaled to: $target")
} else {
console.log("Element is not portaled")
}
Portaled elements have no special attributes but can be identified by:
PortalManager tracking maps