-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added portalling to PopupPanel, Toast and Modal
- Loading branch information
1 parent
fbbc410
commit f446629
Showing
6 changed files
with
132 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/portalling.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package dev.fritz2.headless.foundation | ||
|
||
import dev.fritz2.core.* | ||
import kotlinx.browser.document | ||
import kotlinx.coroutines.* | ||
import org.w3c.dom.Element | ||
import org.w3c.dom.HTMLDivElement | ||
import org.w3c.dom.Node | ||
|
||
|
||
const val PORTALLING_MODAL_ZINDEX = 10 | ||
const val PORTALLING_POPUP_ZINDEX = 30 | ||
const val PORTALLING_TOAST_ZINDEX = 50 | ||
|
||
|
||
private val portalRootId by lazy { "portal-root".also { addGlobalStyle("#$it { display: contents; }") } } | ||
private val portalCompanionClass by lazy { "portal-companion".also { addGlobalStyle(".$it { display: contents; display: none; }") } } | ||
private val portalContainerClass by lazy { | ||
"portal-container".also { | ||
addGlobalStyles( | ||
listOf( | ||
".$it { display: contents; }", | ||
".$it > div { z-index: inherit; }" | ||
) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* Ein PortalRoot wird benötigt, um alle Overlays darin zu rendern. Sollte als letztes Element `document.body` stehen | ||
* | ||
* @see portalContainer | ||
*/ | ||
fun RenderContext.portalRoot(): RenderContext { | ||
addComponentStructureInfo(portalRootId, this.scope, this) | ||
register(PortalRenderContext) {} | ||
return PortalRenderContext | ||
} | ||
|
||
private object PortalRenderContext : RenderContext, WithDomNode<Element> { | ||
override val job: Job = Job() | ||
override val scope: Scope = Scope() | ||
override val domNode: Element by lazy { | ||
document.createElement("div").apply { | ||
id = portalRootId | ||
setAttribute(Aria.live, "polite") | ||
} | ||
} | ||
|
||
override fun <N : Node, W : WithDomNode<N>> register(element: W, content: (W) -> Unit): W { | ||
content(element) | ||
domNode.appendChild(element.domNode) | ||
return element | ||
} | ||
|
||
init { | ||
(MainScope() + job).launch { | ||
delay(500) | ||
if (domNode.parentNode == null) { | ||
console.error("you have to create a portalRoot to use portalled components (e.g. popup, modal and toast)") | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
/** | ||
* With Portalling a rendered overlay will be rendered outside of the clipping ancestors to avoid clipping. | ||
* Therefore a [portalRoot] is needed as last element in the document.body. | ||
* | ||
* See https://floating-ui.com/docs/misc#clipping for more information. | ||
* | ||
* A Portal-Container always comes with a Companion-Element, which is rendered directly into the [RenderContext]. | ||
* The Companion-Element is used to cleanup the decoupled PortalContainer when the companion-Element gets removed. | ||
*/ | ||
fun <C : Element> RenderContext.portalContainer( | ||
classes: String? = null, | ||
id: String? = null, | ||
scope: (ScopeContext.() -> Unit) = {}, | ||
tag: TagFactory<Tag<C>>, | ||
zIndex: Int, | ||
content: Tag<C>.() -> Unit = {} | ||
): Tag<C> { | ||
val companion = div(id = "$id-companion", baseClass = portalCompanionClass) {} | ||
return export { | ||
PortalRenderContext.run { | ||
div(portalContainerClass, "$id-portal", scope) { | ||
domNode.style.zIndex = zIndex.toString() | ||
export(tag(this, classes, id, scope) { content() }) | ||
companion.beforeUnmount { _, _ -> | ||
PortalRenderContext.domNode.removeChild(domNode) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @see portalContainer | ||
*/ | ||
fun RenderContext.portalContainer( | ||
classes: String? = null, | ||
id: String? = null, | ||
scope: (ScopeContext.() -> Unit) = {}, | ||
zIndex: Int, | ||
content: Tag<HTMLDivElement>.() -> Unit | ||
): Tag<HTMLDivElement> = portalContainer(classes, id, scope, RenderContext::div, zIndex, content) | ||
|
||
|
||
/** | ||
* A convenience function to wrap a [TagFactory] with a [portalContainer] | ||
* @see portalContainer | ||
*/ | ||
fun <E : Element> TagFactory<Tag<E>>.portalled(zIndex: Int): TagFactory<Tag<E>> = { ctx, classes, id, scope, content -> | ||
ctx.portalContainer(classes, id, scope, this, zIndex, content) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters