Skip to content

Commit 869ceb9

Browse files
authored
Pass shadowRoot ot WebDragAndDropManager and use it for creating uncorrupted ghostImage (#2520)
The goals of this PR are: * introduce important minimal styling for shadowRoot that improved UX for mobile Safari * Fix the rendering of ghostImage in Drag-and-Drop scenarios ## Testing `gradlew testWeb` and manually checking drag and drop ## Release Notes N/A
1 parent ad1ebb2 commit 869ceb9

File tree

5 files changed

+26
-11
lines changed

5 files changed

+26
-11
lines changed

compose/mpp/demo/src/webMain/kotlin/androidx/compose/mpp/demo/Utils.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ internal fun setupBackingTextAreaDebugHints() {
3030
shadowRootStyle.textContent = """
3131
:host {
3232
--input-mode-indicator: transparent;
33-
34-
-webkit-touch-callout: none;
35-
-webkit-user-select: none;
36-
user-select: none;
3733
}
3834
3935
:host:has(input, textarea) {

compose/ui/ui/src/webCommonW3C/kotlin/androidx/compose/ui/draganddrop/WebDragAndDropManager.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ import org.w3c.dom.DragEvent
2121
import org.w3c.dom.HTMLCanvasElement
2222
import org.w3c.dom.ImageData
2323
import org.w3c.dom.HTMLElement
24+
import org.w3c.dom.Node
2425

25-
internal abstract class WebDragAndDropManager(eventListener: EventTargetListener, globalEventsListener: EventTargetListener, private val density: Density) :
26+
internal abstract class WebDragAndDropManager(private val rootNode: Node, eventListener: EventTargetListener, globalEventsListener: EventTargetListener, private val density: Density) :
2627
PlatformDragAndDropManager {
2728
override val isRequestDragAndDropTransferRequired: Boolean
2829
get() = false
@@ -42,12 +43,14 @@ internal abstract class WebDragAndDropManager(eventListener: EventTargetListener
4243
top = "0"
4344
left = "0"
4445

46+
4547
setProperty("pointer-events", "none")
48+
setProperty("z-index", "-1")
4649
}
4750

48-
// non-image elements passed to setDragImage should be present on document
51+
// non-image elements passed to setDragImage should be present in the document
4952
// the only browser the only browser not burdened with this limitation is Firefox
50-
document.body?.appendChild(ghostImage)
53+
rootNode.appendChild(ghostImage)
5154

5255
dataTransfer?.setDragImage(ghostImage, 0, 0)
5356

@@ -218,7 +221,7 @@ private class InternalStartTransferScope(
218221
return ImageData(uint8ClampedArray, imageBitmap.width, imageBitmap.height)
219222
}
220223

221-
fun ImageData.asHtmlCanvas(): HTMLCanvasElement {
224+
private fun ImageData.asHtmlCanvas(): HTMLCanvasElement {
222225
val canvasConverter = document.createElement("canvas") as HTMLCanvasElement
223226

224227
canvasConverter.width = width

compose/ui/ui/src/webCommonW3C/kotlin/androidx/compose/ui/window/ComposeWindow.w3c.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ import org.w3c.dom.HTMLElement
9797
import org.w3c.dom.HTMLStyleElement
9898
import org.w3c.dom.HTMLTitleElement
9999
import org.w3c.dom.MediaQueryListEvent
100+
import org.w3c.dom.Node
100101
import org.w3c.dom.OPEN
101102
import org.w3c.dom.ShadowRootInit
102103
import org.w3c.dom.ShadowRootMode
@@ -184,6 +185,7 @@ internal class DefaultWindowState(private val viewportContainer: Element) : Comp
184185
@OptIn(InternalComposeApi::class)
185186
internal class ComposeWindow(
186187
private val canvas: HTMLCanvasElement,
188+
private val rootNode: Node,
187189
private val interopContainerElement: HTMLDivElement,
188190
private val a11yContainerElement: HTMLDivElement?,
189191
private val configuration: ComposeViewportConfiguration,
@@ -217,7 +219,7 @@ internal class ComposeWindow(
217219
override val architectureComponentsOwner get() = archComponentsOwner
218220
override val inputModeManager: InputModeManager = DefaultInputModeManager()
219221

220-
override val dragAndDropManager: PlatformDragAndDropManager = object : WebDragAndDropManager(canvasEvents, state.globalEvents, density) {
222+
override val dragAndDropManager: PlatformDragAndDropManager = object : WebDragAndDropManager(rootNode, canvasEvents, state.globalEvents, density) {
221223
override val rootDragAndDropNode: ComposeSceneDragAndDropNode
222224
get() = scene.rootDragAndDropNode
223225
}
@@ -676,6 +678,7 @@ fun CanvasBasedWindow(
676678

677679
ComposeWindow(
678680
canvas = canvas,
681+
rootNode = canvas.getRootNode(),
679682
// a detached container
680683
interopContainerElement = document.createElement("div") as HTMLDivElement,
681684
a11yContainerElement = document.createElement("div") as HTMLDivElement,
@@ -736,7 +739,18 @@ fun ComposeViewport(
736739
}
737740

738741
val shadowRoot = viewportContainer.attachShadow(ShadowRootInit(ShadowRootMode.OPEN))
739-
shadowRoot.appendChild(layerRoot)
742+
val shadowRootStyle = document.createElement("style")
743+
shadowRootStyle.textContent = """
744+
:host {
745+
-webkit-touch-callout: none;
746+
-webkit-user-select: none;
747+
user-select: none;
748+
749+
position: relative;
750+
}
751+
""".trimIndent()
752+
753+
shadowRoot.append(shadowRootStyle, layerRoot)
740754
layerRoot.appendChild(canvas)
741755

742756
val interopContainerElement = document.createElement("div") as HTMLDivElement
@@ -765,6 +779,7 @@ fun ComposeViewport(
765779

766780
ComposeWindow(
767781
canvas = canvas,
782+
rootNode = shadowRoot,
768783
interopContainerElement = interopContainerElement,
769784
a11yContainerElement = a11yContainerElement,
770785
content = content,

compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/OnCanvasTests.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ internal interface OnCanvasTests {
7676

7777
private fun getContainer() = document.getElementById(containerId) ?: error("failed to get canvas with id ${containerId}")
7878

79-
private fun getAppRoot() = getShadowRoot().children[0] as HTMLElement
79+
private fun getAppRoot() = getShadowRoot().children[1] as HTMLElement
8080

8181
fun getA11YContainer(): HTMLElement? {
8282
return if (getAppRoot().children.length < 3) {

compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/window/ComposeWindowLifecycleTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class ComposeWindowLifecycleTest : OnCanvasTests {
4040

4141
val composeWindow = ComposeWindow(
4242
canvas = canvas,
43+
rootNode = getShadowRoot(),
4344
interopContainerElement = document.createElement("div") as HTMLDivElement,
4445
a11yContainerElement = null,
4546
content = {},

0 commit comments

Comments
 (0)