@@ -84,7 +84,6 @@ import androidx.compose.ui.viewinterop.TrackInteropPlacementContainer
8484import androidx.compose.ui.viewinterop.WebInteropContainer
8585import androidx.lifecycle.Lifecycle
8686import androidx.lifecycle.enableSavedStateHandles
87- import kotlin.coroutines.coroutineContext
8887import kotlin.js.ExperimentalWasmJsInterop
8988import kotlin.js.JsArray
9089import kotlin.js.js
@@ -99,9 +98,7 @@ import kotlinx.coroutines.channels.Channel
9998import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
10099import kotlinx.coroutines.coroutineScope
101100import kotlinx.coroutines.flow.Flow
102- import kotlinx.coroutines.flow.flow
103101import kotlinx.coroutines.flow.receiveAsFlow
104- import kotlinx.coroutines.isActive
105102import org.jetbrains.skiko.SkiaLayer
106103import org.jetbrains.skiko.SkikoRenderDelegate
107104import org.jetbrains.skiko.hostOs
@@ -115,6 +112,7 @@ import org.w3c.dom.HTMLTextAreaElement
115112import org.w3c.dom.LOADING
116113import org.w3c.dom.MediaQueryListEvent
117114import org.w3c.dom.Node
115+ import org.w3c.dom.TouchEvent
118116import org.w3c.dom.events.Event
119117import org.w3c.dom.events.EventTarget
120118import org.w3c.dom.events.FocusEvent
@@ -135,19 +133,6 @@ internal interface ComposeWindowState {
135133 fun dispose () {
136134 globalEvents.dispose()
137135 }
138-
139- companion object {
140- fun createFromLambda (lambda : suspend () -> IntSize ): ComposeWindowState {
141- return object : ComposeWindowState {
142- override val globalEvents = EventTargetListener (window)
143- override fun sizeFlow (): Flow <IntSize > = flow {
144- while (coroutineContext.isActive) {
145- emit(lambda())
146- }
147- }
148- }
149- }
150- }
151136}
152137
153138private sealed interface KeyboardModeState {
@@ -402,6 +387,17 @@ internal class ComposeWindow(
402387 addTypedEvent<PointerEvent >(name, passive = false ) { onPointerEvent(it) }
403388 }
404389
390+ addTypedEvent<TouchEvent >(" touchstart" ) { evt ->
391+ // in most cases we don't care about touches since in Compose we do not process them at all
392+ // there's one case however when we need to cancel them - it's when we are focussed in a DOM backing field
393+ // see https://youtrack.jetbrains.com/issue/CMP-10079
394+
395+ val backingInput = (platformContext.textInputService as WebTextInputService ).getBackingInput()
396+ if (backingInput?.isFocused() == true ) {
397+ evt.preventDefault()
398+ }
399+ }
400+
405401 addTypedEvent<WheelEvent >(" wheel" , passive = false ) { event ->
406402 onWheelEvent(event)
407403 }
@@ -564,9 +560,43 @@ internal class ComposeWindow(
564560 private fun onPointerEvent (event : PointerEvent ) {
565561 val eventType = event.getPointerEventType()
566562 var result: PointerEventResult ? = null
567- val isTouchEvent = isTouchEvent(event)
568563
569- if (isTouchEvent) {
564+ if (isMouseEvent(event)) {
565+ keyboardModeState = KeyboardModeState .Hardware
566+
567+ // validate event before sending it further - see
568+ // https://youtrack.jetbrains.com/issue/CMP-8430/Sequence-of-Move-PointerInputEvents-cancel-out-press-PointerInputEvent-under-certain-conditions
569+
570+ var isValidEvent = true
571+ when (eventType) {
572+ PointerEventType .Press -> {
573+ actualActivePointerButtons = event.composeButtons
574+ }
575+ PointerEventType .Release -> {
576+ actualActivePointerButtons = null
577+ }
578+ PointerEventType .Move -> {
579+ isValidEvent = actualActivePointerButtons == null || actualActivePointerButtons == event.composeButtons
580+ }
581+ }
582+
583+ if (! isValidEvent) return
584+
585+ scene.sendPointerEvent(
586+ eventType = eventType,
587+ position = event.offset,
588+ timeMillis = event.timeStamp.toInt().toLong(),
589+ buttons = event.composeButtons,
590+ keyboardModifiers = PointerKeyboardModifiers (
591+ isCtrlPressed = event.ctrlKey,
592+ isMetaPressed = event.metaKey,
593+ isAltPressed = event.altKey,
594+ isShiftPressed = event.shiftKey,
595+ ),
596+ nativeEvent = event,
597+ button = event.composeButton,
598+ )
599+ } else {
570600 if (eventType == PointerEventType .Enter || eventType == PointerEventType .Exit ) {
571601 // Enter and Exit events have no sense for touches (Firefox and Safari send them)
572602 return
@@ -652,48 +682,7 @@ internal class ComposeWindow(
652682
653683 if (result != null && result.anyChangeConsumed && event.cancelable) {
654684 event.preventDefault()
655-
656- // Since we call preventDefault, the browser will not focus the canvas automatically,
657- // but it should be focused to receive key events.
658- if (! canvasFocused && ! isTouchEvent && eventType == PointerEventType .Press ) {
659- canvas.focus()
660- }
661- }
662- } else {
663- keyboardModeState = KeyboardModeState .Hardware
664-
665- // validate event before sending it further - see
666- // https://youtrack.jetbrains.com/issue/CMP-8430/Sequence-of-Move-PointerInputEvents-cancel-out-press-PointerInputEvent-under-certain-conditions
667-
668- var isValidEvent = true
669- when (eventType) {
670- PointerEventType .Press -> {
671- actualActivePointerButtons = event.composeButtons
672- }
673- PointerEventType .Release -> {
674- actualActivePointerButtons = null
675- }
676- PointerEventType .Move -> {
677- isValidEvent = actualActivePointerButtons == null || actualActivePointerButtons == event.composeButtons
678- }
679685 }
680-
681- if (! isValidEvent) return
682-
683- result = scene.sendPointerEvent(
684- eventType = eventType,
685- position = event.offset,
686- timeMillis = event.timeStamp.toInt().toLong(),
687- buttons = event.composeButtons,
688- keyboardModifiers = PointerKeyboardModifiers (
689- isCtrlPressed = event.ctrlKey,
690- isMetaPressed = event.metaKey,
691- isAltPressed = event.altKey,
692- isShiftPressed = event.shiftKey,
693- ),
694- nativeEvent = event,
695- button = event.composeButton,
696- )
697686 }
698687 }
699688
@@ -826,7 +815,7 @@ private fun clipTargetElement(canvas: HTMLCanvasElement): HTMLTextAreaElement {
826815
827816// strings checks are faster on a JS side
828817// language=js
829- private fun isTouchEvent (event : PointerEvent ): Boolean = js(" event.pointerType === 'touch '" )
818+ private fun isMouseEvent (event : PointerEvent ): Boolean = js(" event.pointerType === 'mouse '" )
830819
831820// strings checks are faster on a JS side
832821// language=js
@@ -858,4 +847,21 @@ private fun PointerEvent.getPointerEventType(): PointerEventType =
858847 PointerEventType .Enter .value -> PointerEventType .Enter
859848 PointerEventType .Exit .value -> PointerEventType .Exit
860849 else -> PointerEventType .Unknown
861- }
850+ }
851+
852+ private fun Element.isFocused (): Boolean {
853+ val activeElement = when {
854+ document.activeElement?.shadowRoot != null -> (document.activeElement?.shadowRoot as ? ShadowRootExt )?.activeElement
855+ else -> document.activeElement
856+ }
857+
858+ if (activeElement == null ) {
859+ return false
860+ }
861+
862+ return activeElement == this
863+ }
864+
865+ private external interface ShadowRootExt {
866+ val activeElement: Element ?
867+ }
0 commit comments