From 9f2a8f25f8ca5ff43bc3808e4656024e199eb05d Mon Sep 17 00:00:00 2001 From: Trax Bagley Date: Fri, 12 Dec 2025 13:01:28 -0700 Subject: [PATCH 1/2] initial attempt --- .../internal/PopoverTestingPage.kt | 52 +++-- .../internal/TestingGroundPage.kt | 33 +++ .../views/direct/openPopover.android.kt | 5 +- .../kiteui/views/direct/FloatingInfoHolder.kt | 4 +- .../kiteui/views/ViewContextExtensions.kt | 2 + .../kiteui/views/direct/openPopover.kt | 6 +- .../kiteui/views/direct/openPopover.ios.kt | 60 +++--- .../views/direct/FloatingInfoHolder.js.kt | 203 +++++++++++------- .../kiteui/views/direct/openPopover.js.kt | 5 +- .../views/direct/FloatingInfoHolder.jvm.kt | 13 +- .../kiteui/views/direct/openPopover.jvm.kt | 3 +- 11 files changed, 248 insertions(+), 138 deletions(-) diff --git a/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/PopoverTestingPage.kt b/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/PopoverTestingPage.kt index d7ad38241..bb90ada96 100644 --- a/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/PopoverTestingPage.kt +++ b/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/PopoverTestingPage.kt @@ -1,10 +1,10 @@ package com.lightningkite.mppexampleapp.internal -import com.lightningkite.kiteui.views.ViewWriter import com.lightningkite.kiteui.Routable import com.lightningkite.kiteui.models.* import com.lightningkite.kiteui.navigation.Page import com.lightningkite.kiteui.views.* +import com.lightningkite.kiteui.views.ViewWriter import com.lightningkite.kiteui.views.direct.* @Routable("popover-testing") @@ -17,22 +17,26 @@ object PopoverTestingPage : Page { row { for (align in Align.entries) { card.menuButton { - text(buildString { - if (horizontal) append("H") else append("V") - if (after) append(">") else append("<") - append(align.name.first()) - }) - preferredDirection = PopoverPreferredDirection( - horizontal = horizontal, - after = after, - align = align + text( + buildString { + if (horizontal) append("H") else append("V") + if (after) append(">") else append("<") + append(align.name.first()) + } ) + preferredDirection = + PopoverPreferredDirection( + horizontal = horizontal, + after = after, + align = align + ) requireClick = true opensMenu { sizeConstraints(width = 20.rem, height = 20.rem).frame { centered.col { text("Popover!") - if (horizontal) text("Horizontal") else text("Vertical") + if (horizontal) text("Horizontal") + else text("Vertical") if (after) text("After") else text("Before") text(align.name) } @@ -45,11 +49,12 @@ object PopoverTestingPage : Page { } card.menuButton { text("dumb") - preferredDirection = PopoverPreferredDirection( - horizontal = true, - after = true, - align = Align.Start - ) + preferredDirection = + PopoverPreferredDirection( + horizontal = true, + after = true, + align = Align.Start + ) requireClick = true opensMenu { sizeConstraints(width = 1000.rem, height = 1000.rem).frame { @@ -60,6 +65,19 @@ object PopoverTestingPage : Page { } } } + + row { + val anchorTarget = button { text("Anchor Target") } + space() + button { + text("Open at Target") + onClick { + openPopover(PopoverPreferredDirection.belowCenter, anchorTarget) { + text("Anchored to Target!") + } + } + } + } } atTopStart.testGrouping() atTopCenter.testGrouping() @@ -72,4 +90,4 @@ object PopoverTestingPage : Page { atBottomEnd.testGrouping() } } -} \ No newline at end of file +} diff --git a/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/TestingGroundPage.kt b/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/TestingGroundPage.kt index 4358a1e3e..1cb20e094 100644 --- a/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/TestingGroundPage.kt +++ b/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/TestingGroundPage.kt @@ -1,11 +1,24 @@ package com.lightningkite.mppexampleapp.internal import com.lightningkite.kiteui.Routable +import com.lightningkite.kiteui.models.Color +import com.lightningkite.kiteui.models.CornerRadii +import com.lightningkite.kiteui.models.DismissSemantic +import com.lightningkite.kiteui.models.PopoverPreferredDirection +import com.lightningkite.kiteui.models.PopoverSemantic +import com.lightningkite.kiteui.models.ScreenTransitions +import com.lightningkite.kiteui.models.SemanticOverrides +import com.lightningkite.kiteui.models.ThemeDerivation +import com.lightningkite.kiteui.models.ThemeDerivation.Companion.invoke +import com.lightningkite.kiteui.models.dp +import com.lightningkite.kiteui.models.override import com.lightningkite.kiteui.models.rem import com.lightningkite.kiteui.navigation.Page import com.lightningkite.kiteui.reactive.* import com.lightningkite.kiteui.views.* import com.lightningkite.kiteui.views.direct.* +import com.lightningkite.kiteui.views.l2.overlayFrame +import com.lightningkite.kiteui.views.l2.rawPopover import com.lightningkite.mppexampleapp.Resources import com.lightningkite.reactive.context.* import com.lightningkite.reactive.core.* @@ -54,6 +67,26 @@ object TestingGroundPage: Page { } } // } + + val input = Signal("") + + col { + val anchorTarget = this + fieldTheme.textArea { + content.bind(input) + + } + reactive { + if (input() == "test") { + var willRemove: RView? = null + openPopover(PopoverPreferredDirection.belowCenter, anchorTarget) { + text("Anchored to Target!") + } + } else closeThisPopover() + } + text("Is this behind it or what") + } + } } } \ No newline at end of file diff --git a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.android.kt b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.android.kt index b6cd16c96..e779e388b 100644 --- a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.android.kt +++ b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.android.kt @@ -18,6 +18,7 @@ import com.lightningkite.kiteui.views.popoverWriter actual fun RView.openPopover( preferredDirection: PopoverPreferredDirection, + anchor: RView?, createMenu: Frame.() -> Unit ) { var willRemove: RView? = null @@ -52,11 +53,11 @@ actual fun RView.openPopover( } this@dismissBackground.native.addOnLayoutChangeListener{ dismissBackground, _, _, _, _, _, _, _, _ -> val overlayContainer = this@frame.native - val anchor = this@openPopover.native + val anchorView = (anchor ?: this@openPopover).native val overlayBoundsInWindow = overlayContainer.getBoundariesInWindow() val offset = preferredDirection.calculatePopoverOffset( - anchor.getBoundariesInWindow(), + anchorView.getBoundariesInWindow(), overlayBoundsInWindow, dismissBackground.getBoundariesInWindow() ) diff --git a/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.kt b/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.kt index cfff2c3f4..e1b39e2e4 100644 --- a/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.kt +++ b/library/src/commonHtmlMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.kt @@ -3,10 +3,10 @@ package com.lightningkite.kiteui.views.direct import com.lightningkite.kiteui.models.PopoverPreferredDirection import com.lightningkite.kiteui.views.RView -expect class FloatingInfoHolder(source: RView) { +expect class FloatingInfoHolder(source: RView, anchor: RView? = null) { var preferredDirection: PopoverPreferredDirection var menuGenerator: Frame.() -> Unit fun open() fun block() fun close() -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lightningkite/kiteui/views/ViewContextExtensions.kt b/library/src/commonMain/kotlin/com/lightningkite/kiteui/views/ViewContextExtensions.kt index ef1eda5bf..64aba2f46 100644 --- a/library/src/commonMain/kotlin/com/lightningkite/kiteui/views/ViewContextExtensions.kt +++ b/library/src/commonMain/kotlin/com/lightningkite/kiteui/views/ViewContextExtensions.kt @@ -78,11 +78,13 @@ fun ViewWriter.keepPopoverOpen(lifecycle: CoroutineScope) { fun ViewWriter.popoverWriter(overlay: ViewWriter = this, popoverRoot: Boolean = false, close: ()->Unit): ViewWriter { popoverCloser?.invoke() popoverCloser = close + val writer = object : ViewWriter(), CalculationContext by this { override val representsView: RView? = overlay.representsView override val context: RContext = this@popoverWriter.context.split() override fun willAddChild(view: RView) = overlay.willAddChild(view) override fun addChild(view: RView) = overlay.addChild(view) + } writer.popoverParent = this@popoverWriter.takeIf { !popoverRoot } writer.popoverCloser = null diff --git a/library/src/commonMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.kt b/library/src/commonMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.kt index 73034a340..ac50dae16 100644 --- a/library/src/commonMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.kt +++ b/library/src/commonMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.kt @@ -4,4 +4,8 @@ import com.lightningkite.kiteui.models.PopoverPreferredDirection import com.lightningkite.kiteui.views.RView import com.lightningkite.kiteui.views.ViewWriter -expect fun RView.openPopover(preferredDirection: PopoverPreferredDirection, createMenu: Frame.() -> Unit) \ No newline at end of file +expect fun RView.openPopover( + preferredDirection: PopoverPreferredDirection, + anchor: RView? = null, + createMenu: Frame.() -> Unit +) \ No newline at end of file diff --git a/library/src/iosMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.ios.kt b/library/src/iosMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.ios.kt index 36cb6b4c8..2db151ac0 100644 --- a/library/src/iosMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.ios.kt +++ b/library/src/iosMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.ios.kt @@ -16,38 +16,42 @@ import com.lightningkite.kiteui.views.popoverWriter actual fun RView.openPopover( preferredDirection: PopoverPreferredDirection, + anchor: RView?, createMenu: Frame.() -> Unit ) { var willRemove: RView? = null - val f= overlayFrame!! - f.popoverWriter { - willRemove?.let { f.removeChild(it) } - willRemove = null - }.run { - willRemove = dismissBackground { - themeChoice += ThemeDerivation { - it.copy( - id = "mnubtndsm", - cascading = false, - semanticOverrides = SemanticOverrides( - DismissSemantic.override { - it.withBack( - background = Color.transparent, - outlineWidth = 0.dp, - cornerRadii = CornerRadii.Constant(0.dp), - cascading = false, + val f = overlayFrame!! + f + .popoverWriter { + willRemove?.let { f.removeChild(it) } + willRemove = null + } + .run { + willRemove = dismissBackground { + themeChoice += ThemeDerivation { + it.copy( + id = "mnubtndsm", + cascading = false, + semanticOverrides = + SemanticOverrides( + DismissSemantic.override { + it.withBack( + background = Color.transparent, + outlineWidth = 0.dp, + cornerRadii = + CornerRadii.Constant( + 0.dp + ), + cascading = false, + ) + } ) - } ) - ).withBack - } - native.anchor = preferredDirection to this@openPopover.native - onClick { - closePopovers() - } - PopoverSemantic.onNext.frame { - createMenu() + .withBack + } + native.anchor = preferredDirection to (anchor ?: this@openPopover).native + onClick { closePopovers() } + PopoverSemantic.onNext.frame { createMenu() } } } - } -} \ No newline at end of file +} diff --git a/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.js.kt b/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.js.kt index 32c75522e..6023cfce5 100644 --- a/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.js.kt +++ b/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.js.kt @@ -2,27 +2,25 @@ package com.lightningkite.kiteui.views.direct import com.lightningkite.kiteui.dom.DOMRect import com.lightningkite.kiteui.models.Align - import com.lightningkite.kiteui.models.Icon import com.lightningkite.kiteui.models.PopoverPreferredDirection import com.lightningkite.kiteui.models.PopoverSemantic -import com.lightningkite.kiteui.models.Rect import com.lightningkite.kiteui.views.* import com.lightningkite.kiteui.views.l2.icon import com.lightningkite.kiteui.views.l2.overlayFrame +import kotlin.math.min +import kotlin.random.Random +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds import kotlinx.browser.document import kotlinx.browser.window -import org.w3c.dom.DOMRectInit import org.w3c.dom.HTMLElement import org.w3c.dom.events.Event import org.w3c.dom.events.MouseEvent -import kotlin.math.min -import kotlin.random.Random -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds -actual class FloatingInfoHolder actual constructor(val source: RView) { - val theme get() = source.theme +actual class FloatingInfoHolder actual constructor(val source: RView, val anchor: RView?) { + val theme + get() = source.theme val maxDist = 32 var blockView: RView? = null var closeView: RView? = null @@ -39,9 +37,7 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { with(o) { beforeNextElementSetup { closeView = this }.atTopEnd.button { icon(Icon.close, "Close") - onClick { - close() - } + onClick { close() } } } } @@ -55,9 +51,7 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { object : RView(o.context) { init { native.tag = "div" - native.addEventListener("click") { - close() - } + native.addEventListener("click") { close() } native.style.position = "absolute" native.style.left = "0" native.style.right = "0" @@ -75,9 +69,8 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { actual fun open() { if (existingView != null) return var removeElementFromOverlay = {} - val popoverWriter = source.popoverWriter(source.overlayFrame!!) { - removeElementFromOverlay() - } + val popoverWriter = + source.popoverWriter(source.overlayFrame!!) { removeElementFromOverlay() } with(popoverWriter) { frame { source.keepPopoverOpen(this) @@ -98,7 +91,8 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { fun reposition() { native.onElement { e -> e as HTMLElement - val sourcePosition = source.native.element!!.getBoundingClientRect() + val sourcePosition = + (anchor ?: source).native.element!!.getBoundingClientRect() val screen = document.body!!.getBoundingClientRect() val size = e.getBoundingClientRect() e.style.removeProperty("top") @@ -109,49 +103,108 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { val gap = 0 fun PopoverPreferredDirection.bounds(): DOMRect { - return if(this.horizontal) { - val x = if(this.after) - sourcePosition.right + gap - else - sourcePosition.left - gap - size.width - when(this.align) { - Align.Start -> DOMRect(x, sourcePosition.bottom - size.height, size.width, size.height) - Align.End -> DOMRect(x, sourcePosition.top, size.width, size.height) - Align.Center -> DOMRect(x, sourcePosition.centerY - size.height / 2, size.width, size.height) + return if (this.horizontal) { + val x = + if (this.after) sourcePosition.right + gap + else sourcePosition.left - gap - size.width + when (this.align) { + Align.Start -> + DOMRect( + x, + sourcePosition.bottom - size.height, + size.width, + size.height + ) + + Align.End -> + DOMRect(x, sourcePosition.top, size.width, size.height) + + Align.Center -> + DOMRect( + x, + sourcePosition.centerY - size.height / 2, + size.width, + size.height + ) Align.Stretch -> DOMRect(x, 0.0, size.width, screen.height) } } else { - val y = if(this.after) - sourcePosition.bottom + gap - else - sourcePosition.top - gap - size.height - when(this.align) { - Align.Start -> DOMRect(sourcePosition.right - size.width, y, size.width, size.height) - Align.End -> DOMRect(sourcePosition.left, y, size.width, size.height) - Align.Center -> DOMRect(sourcePosition.centerX - size.width / 2, y, size.width, size.height) + val y = + if (this.after) sourcePosition.bottom + gap + else sourcePosition.top - gap - size.height + when (this.align) { + Align.Start -> + DOMRect( + sourcePosition.right - size.width, + y, + size.width, + size.height + ) + + Align.End -> + DOMRect(sourcePosition.left, y, size.width, size.height) + + Align.Center -> + DOMRect( + sourcePosition.centerX - size.width / 2, + y, + size.width, + size.height + ) Align.Stretch -> DOMRect(0.0, y, screen.width, size.height) } } } - val currentDirection = buildList { - add(preferredDirection) - for(otherAlign in Align.entries) - add(preferredDirection.copy(align = otherAlign)) - for(otherAlign in listOf(preferredDirection.align)+Align.entries) - add(preferredDirection.copy(after = !preferredDirection.after, align = otherAlign)) - val altAligns = if(preferredDirection.after) listOf(Align.End, Align.Center, Align.Start, Align.Stretch) else listOf(Align.Start, Align.Center, Align.End, Align.Stretch) - for(otherAlign in altAligns) - add(preferredDirection.copy(horizontal = !preferredDirection.horizontal, after = !preferredDirection.after, align = otherAlign)) - } - .firstOrNull { - val proposed = it.bounds() - val epsilon = - 0.01 // Fix precision mismatch i.e. screen.right is 1231 but proposed is 1231.0000457763672 - (proposed.left >= screen.left - epsilon) && (proposed.right <= screen.right + epsilon) && - (proposed.top >= screen.top - epsilon) && (proposed.bottom <= screen.bottom + epsilon) + + val currentDirection = + buildList { + add(preferredDirection) + for (otherAlign in Align.entries) add( + preferredDirection.copy(align = otherAlign) + ) + for (otherAlign in + listOf(preferredDirection.align) + Align.entries) add( + preferredDirection.copy( + after = !preferredDirection.after, + align = otherAlign + ) + ) + val altAligns = + if (preferredDirection.after) + listOf( + Align.End, + Align.Center, + Align.Start, + Align.Stretch + ) + else + listOf( + Align.Start, + Align.Center, + Align.End, + Align.Stretch + ) + for (otherAlign in altAligns) add( + preferredDirection.copy( + horizontal = !preferredDirection.horizontal, + after = !preferredDirection.after, + align = otherAlign + ) + ) } + .firstOrNull { + val proposed = it.bounds() + val epsilon = + 0.01 // Fix precision mismatch i.e. screen.right + // is 1231 but proposed is + // 1231.0000457763672 + (proposed.left >= screen.left - epsilon) && + (proposed.right <= screen.right + epsilon) && + (proposed.top >= screen.top - epsilon) && + (proposed.bottom <= screen.bottom + epsilon) + } - if(currentDirection == null) { + if (currentDirection == null) { closeButton() e.style.left = "0px" e.style.right = "0px" @@ -174,17 +227,14 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { ty = sourcePosition.bottom tym = -100 } - Align.End -> { ty = sourcePosition.top tym = 0 } - Align.Center -> { ty = (sourcePosition.top + sourcePosition.bottom) / 2 tym = -50 } - Align.Stretch -> { ty = 0.0 tym = 0 @@ -205,17 +255,14 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { tx = sourcePosition.right txm = -100 } - Align.End -> { tx = sourcePosition.left txm = 0 } - Align.Center -> { tx = (sourcePosition.left + sourcePosition.right) / 2 txm = -50 } - Align.Stretch -> { tx = 0.0 txm = 0 @@ -231,9 +278,7 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { // Corrective measures: force it back on-screen native.onElement { e -> e as HTMLElement - ResizeObserver { entry, observer -> - reposition() - }.observe(e) + ResizeObserver { entry, observer -> reposition() }.observe(e) } menuGenerator(this) @@ -246,7 +291,9 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { val mouseMove = { it: Event -> it as MouseEvent if (blockView == null && popoverKeepOpen <= 0) { - val clientRect = (source.native.element as HTMLElement).getBoundingClientRect() + val clientRect = + ((anchor ?: source).native.element as HTMLElement) + .getBoundingClientRect() val popUpRect = (native.element as HTMLElement).getBoundingClientRect() if (min( maxOf( @@ -254,38 +301,38 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { popUpRect.left - it.x, it.y - popUpRect.bottom, popUpRect.top - it.y, - ), maxOf( + ), + maxOf( it.x - clientRect.right, clientRect.left - it.x, it.y - clientRect.bottom, clientRect.top - it.y, ) ) > maxDist - ) close() + ) + close() } } window.addEventListener("mousemove", mouseMove) removeElementFromOverlay = { - blockView?.let { - source.overlayFrame!!.removeChild(it) - } + blockView?.let { source.overlayFrame!!.removeChild(it) } blockView = null - closeView?.let { - source.overlayFrame!!.removeChild(it) - } + closeView?.let { source.overlayFrame!!.removeChild(it) } closeView = null window.removeEventListener("scroll", repos, true) window.removeEventListener("mousemove", mouseMove) native.onElement { e -> this.shutdown() (e as HTMLElement) - window.getComputedStyle(e).getPropertyValue("transition-duration") + window.getComputedStyle(e) + .getPropertyValue("transition-duration") .let { Duration.parseOrNull(it) ?: 0.25.seconds } .let { - window.setTimeout({ - source.overlayFrame!!.removeChild(this) - }, it.inWholeMilliseconds.toInt()) + window.setTimeout( + { source.overlayFrame!!.removeChild(this) }, + it.inWholeMilliseconds.toInt() + ) } e.style.opacity = "0" e.style.setProperty("pointer-events", "none") @@ -301,5 +348,7 @@ actual class FloatingInfoHolder actual constructor(val source: RView) { } } -val DOMRect.centerY get() = (top + bottom) / 2 -val DOMRect.centerX get() = (left + right) / 2 \ No newline at end of file +val DOMRect.centerY + get() = (top + bottom) / 2 +val DOMRect.centerX + get() = (left + right) / 2 diff --git a/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.js.kt b/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.js.kt index 88bdabb4b..df1185521 100644 --- a/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.js.kt +++ b/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.js.kt @@ -5,10 +5,11 @@ import com.lightningkite.kiteui.views.RView actual fun RView.openPopover( preferredDirection: PopoverPreferredDirection, + anchor: RView?, createMenu: Frame.() -> Unit ) { - val floating = FloatingInfoHolder(this) + val floating = FloatingInfoHolder(this, anchor) floating.menuGenerator = createMenu floating.open() floating.block() -} \ No newline at end of file +} diff --git a/library/src/jvmSsrMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.jvm.kt b/library/src/jvmSsrMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.jvm.kt index b7452867e..69f6c8188 100644 --- a/library/src/jvmSsrMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.jvm.kt +++ b/library/src/jvmSsrMain/kotlin/com/lightningkite/kiteui/views/direct/FloatingInfoHolder.jvm.kt @@ -3,16 +3,13 @@ package com.lightningkite.kiteui.views.direct import com.lightningkite.kiteui.models.PopoverPreferredDirection import com.lightningkite.kiteui.views.RView -actual class FloatingInfoHolder actual constructor(source: RView) { +actual class FloatingInfoHolder actual constructor(source: RView, anchor: RView?) { actual var preferredDirection: PopoverPreferredDirection = PopoverPreferredDirection.belowCenter actual var menuGenerator: Frame.() -> Unit = {} - actual fun open() { - } + actual fun open() {} - actual fun block() { - } + actual fun block() {} - actual fun close() { - } -} \ No newline at end of file + actual fun close() {} +} diff --git a/library/src/jvmSsrMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.jvm.kt b/library/src/jvmSsrMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.jvm.kt index dae351024..8f8cb0e15 100644 --- a/library/src/jvmSsrMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.jvm.kt +++ b/library/src/jvmSsrMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.jvm.kt @@ -5,7 +5,8 @@ import com.lightningkite.kiteui.views.RView actual fun RView.openPopover( preferredDirection: PopoverPreferredDirection, + anchor: RView?, createMenu: Frame.() -> Unit ) { // Well... nothing to do here. -} \ No newline at end of file +} From a7b7c640a1176cadddde7dab501135d92bb65dba Mon Sep 17 00:00:00 2001 From: Trax Bagley Date: Fri, 12 Dec 2025 15:14:32 -0700 Subject: [PATCH 2/2] fix android taking up whole page issue --- .../internal/TestingGroundPage.kt | 87 ++++++++----------- .../kiteui/views/direct/MenuButton.android.kt | 23 ++--- .../views/direct/openPopover.android.kt | 58 +++++++++---- .../kiteui/views/direct/openPopover.js.kt | 1 + 4 files changed, 82 insertions(+), 87 deletions(-) diff --git a/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/TestingGroundPage.kt b/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/TestingGroundPage.kt index 1cb20e094..a8dfeec2b 100644 --- a/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/TestingGroundPage.kt +++ b/example-app/src/commonMain/kotlin/com/lightningkite/mppexampleapp/internal/TestingGroundPage.kt @@ -1,24 +1,13 @@ package com.lightningkite.mppexampleapp.internal import com.lightningkite.kiteui.Routable -import com.lightningkite.kiteui.models.Color -import com.lightningkite.kiteui.models.CornerRadii -import com.lightningkite.kiteui.models.DismissSemantic import com.lightningkite.kiteui.models.PopoverPreferredDirection -import com.lightningkite.kiteui.models.PopoverSemantic -import com.lightningkite.kiteui.models.ScreenTransitions -import com.lightningkite.kiteui.models.SemanticOverrides -import com.lightningkite.kiteui.models.ThemeDerivation import com.lightningkite.kiteui.models.ThemeDerivation.Companion.invoke -import com.lightningkite.kiteui.models.dp -import com.lightningkite.kiteui.models.override import com.lightningkite.kiteui.models.rem import com.lightningkite.kiteui.navigation.Page import com.lightningkite.kiteui.reactive.* import com.lightningkite.kiteui.views.* import com.lightningkite.kiteui.views.direct.* -import com.lightningkite.kiteui.views.l2.overlayFrame -import com.lightningkite.kiteui.views.l2.rawPopover import com.lightningkite.mppexampleapp.Resources import com.lightningkite.reactive.context.* import com.lightningkite.reactive.core.* @@ -29,21 +18,42 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Routable("testing") -object TestingGroundPage: Page { +object TestingGroundPage : Page { val progressRatio: Signal = Signal(0f) override fun ViewWriter.render(): Unit = run { -// val ratioShared = sharedSuspending { -// kotlinx.coroutines.delay(1000) -// ratio.set(ratio.value + 0.1f) -// println("ratio: $ratio.value") -// ratio() -// } - - + // val ratioShared = sharedSuspending { + // kotlinx.coroutines.delay(1000) + // ratio.set(ratio.value + 0.1f) + // println("ratio: $ratio.value") + // ratio() + // } scrolling.col { h1("Experiments test") - centered.sizeConstraints(maxWidth = 10.rem).image { source = Resources.imagesSnowyBackground } + centered.sizeConstraints(maxWidth = 10.rem).image { + source = Resources.imagesSnowyBackground + } + col { + val input = Signal("") + var anchorTarget: RView? = null + fieldTheme.beforeNextElementSetup { anchorTarget = this }.textArea { + hint = "Type test show popup" + content.bind(input) + } + reactive { + if (input() == "test") { + anchorTarget?.openPopover( + PopoverPreferredDirection.aboveRight, + anchorTarget + ) { + card.text("Trax Bagley") + } + } else closeThisPopover() + } + row { + expanding.text("Is this behind it or what. Lorem ipsum dolor sit amet. Test Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum") + } + } launch { while (true) { @@ -55,38 +65,15 @@ object TestingGroundPage: Page { } } - progressBar { - ::ratio { - progressRatio.invoke() - } + progressBar { ::ratio { progressRatio.invoke() } } + // frame { + sizeConstraints(width = 6.rem, height = 6.rem).circularProgress { + ::ratio { progressRatio.invoke() } } -// frame { - sizeConstraints(width = 6.rem, height = 6.rem).circularProgress { - ::ratio { - progressRatio.invoke() - } - } -// } + // } - val input = Signal("") - col { - val anchorTarget = this - fieldTheme.textArea { - content.bind(input) - - } - reactive { - if (input() == "test") { - var willRemove: RView? = null - openPopover(PopoverPreferredDirection.belowCenter, anchorTarget) { - text("Anchored to Target!") - } - } else closeThisPopover() - } - text("Is this behind it or what") - } } } -} \ No newline at end of file +} diff --git a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/MenuButton.android.kt b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/MenuButton.android.kt index a5cf769b1..f1974659f 100644 --- a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/MenuButton.android.kt +++ b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/MenuButton.android.kt @@ -38,24 +38,11 @@ actual class MenuButton actual constructor(context: RContext): RView(context) { closePopovers() } atTopStart.onNext(PopoverSemantic).frame { - this@dismissBackground.native.apply { - clipChildren = false - clipToPadding = false - } - this@dismissBackground.native.addOnLayoutChangeListener{ dismissBackground, _, _, _, _, _, _, _, _ -> - val overlayContainer = this@frame.native - val anchor = this@MenuButton.native - - val overlayBoundsInWindow = overlayContainer.getBoundariesInWindow() - val offset = preferredDirection.calculatePopoverOffset( - anchor.getBoundariesInWindow(), - overlayBoundsInWindow, - dismissBackground.getBoundariesInWindow() - ) - - overlayContainer.offsetLeftAndRight((offset.first - overlayBoundsInWindow.left).toInt()) - overlayContainer.offsetTopAndBottom((offset.second - overlayBoundsInWindow.top).toInt()) - } + configurePopoverLayout( + dismissBackground = this@dismissBackground, + anchorView = this@MenuButton.native, + preferredDirection = preferredDirection + ) createMenu() } } diff --git a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.android.kt b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.android.kt index e779e388b..48568488c 100644 --- a/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.android.kt +++ b/library/src/androidMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.android.kt @@ -1,5 +1,7 @@ package com.lightningkite.kiteui.views.direct + +import android.view.ViewGroup import com.lightningkite.kiteui.models.Color import com.lightningkite.kiteui.models.CornerRadii import com.lightningkite.kiteui.models.DismissSemantic @@ -46,27 +48,45 @@ actual fun RView.openPopover( closePopovers() } - atTopStart.onNext(PopoverSemantic).frame { - this@dismissBackground.native.apply { - clipChildren = false - clipToPadding = false - } - this@dismissBackground.native.addOnLayoutChangeListener{ dismissBackground, _, _, _, _, _, _, _, _ -> - val overlayContainer = this@frame.native - val anchorView = (anchor ?: this@openPopover).native - - val overlayBoundsInWindow = overlayContainer.getBoundariesInWindow() - val offset = preferredDirection.calculatePopoverOffset( - anchorView.getBoundariesInWindow(), - overlayBoundsInWindow, - dismissBackground.getBoundariesInWindow() - ) - - overlayContainer.offsetLeftAndRight((offset.first - overlayBoundsInWindow.left).toInt()) - overlayContainer.offsetTopAndBottom((offset.second - overlayBoundsInWindow.top).toInt()) - } + PopoverSemantic.onNext.frame { + configurePopoverLayout( + dismissBackground = this@dismissBackground, + anchorView = (anchor ?: this@openPopover).native, + preferredDirection = preferredDirection + ) createMenu() } } } +} + + +fun Frame.configurePopoverLayout( + dismissBackground: RView, + anchorView: android.view.View, + preferredDirection: PopoverPreferredDirection +) { + native.layoutParams = android.widget.FrameLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT + } + (dismissBackground.native as? ViewGroup)?.apply { + clipChildren = false + setClipToPadding(false) + } + dismissBackground.native.addOnLayoutChangeListener { dismissBackgroundView, _, _, _, _, _, _, _, _ -> + val overlayContainer = this.native + + val overlayBoundsInWindow = overlayContainer.getBoundariesInWindow() + val offset = preferredDirection.calculatePopoverOffset( + anchorView.getBoundariesInWindow(), + overlayBoundsInWindow, + dismissBackgroundView.getBoundariesInWindow() + ) + + overlayContainer.offsetLeftAndRight((offset.first - overlayBoundsInWindow.left).toInt()) + overlayContainer.offsetTopAndBottom((offset.second - overlayBoundsInWindow.top).toInt()) + } } \ No newline at end of file diff --git a/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.js.kt b/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.js.kt index df1185521..637d77b7c 100644 --- a/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.js.kt +++ b/library/src/jsMain/kotlin/com/lightningkite/kiteui/views/direct/openPopover.js.kt @@ -9,6 +9,7 @@ actual fun RView.openPopover( createMenu: Frame.() -> Unit ) { val floating = FloatingInfoHolder(this, anchor) + floating.preferredDirection = preferredDirection floating.menuGenerator = createMenu floating.open() floating.block()