Skip to content

Commit

Permalink
Add copy event handling in SelectionContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
eymar committed Mar 20, 2024
1 parent cd17460 commit 79d3fa0
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.text

import androidx.compose.runtime.Composable

@Composable
internal expect inline fun rememberClipboardEventsHandler(
crossinline onPaste: (String) -> Unit = {},
crossinline onCopy: () -> String? = { null },
crossinline onCut: () -> String? = { null },
isEnabled: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ import androidx.compose.foundation.text.selection.SimpleLayout
import androidx.compose.foundation.text.selection.TextFieldSelectionHandle
import androidx.compose.foundation.text.selection.TextFieldSelectionManager
import androidx.compose.foundation.text.selection.isSelectionHandleInVisibleBound
import androidx.compose.foundation.text.selection.selectionGestureInput
import androidx.compose.foundation.text.selection.textFieldMagnifier
import androidx.compose.foundation.text.selection.updateSelectionTouchMode
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
Expand Down Expand Up @@ -63,7 +61,6 @@ import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.IntrinsicMeasurable
Expand Down Expand Up @@ -304,7 +301,12 @@ internal fun CoreTextField(
val coroutineScope = rememberCoroutineScope()
val bringIntoViewRequester = remember { BringIntoViewRequester() }

rememberClipboardEventsHandler(manager, state.hasFocus)
rememberClipboardEventsHandler(
isEnabled = state.hasFocus,
onCopy = { manager.onCopyWithResult() },
onCut = { manager.onCutWithResult() },
onPaste = { manager.paste(AnnotatedString(it)) }
)

// Focus
val focusModifier = Modifier.textFieldFocusModifier(
Expand Down Expand Up @@ -1193,9 +1195,3 @@ private fun notifyFocusedRect(
internal const val USE_WINDOW_FOCUS_ENABLED = false
internal fun isWindowFocusedBehindFlag(windowInfo: WindowInfo) =
if (USE_WINDOW_FOCUS_ENABLED) windowInfo.isWindowFocused else true

@Composable
internal expect inline fun rememberClipboardEventsHandler(
textFieldSelectionManager: TextFieldSelectionManager,
isFocused: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package androidx.compose.foundation.text.selection

import androidx.compose.foundation.text.ContextMenuArea
import androidx.compose.foundation.text.detectDownAndDragGesturesWithObserver
import androidx.compose.foundation.text.rememberClipboardEventsHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
Expand Down Expand Up @@ -95,6 +96,11 @@ internal fun SelectionContainer(
manager.onSelectionChange = onSelectionChange
manager.selection = selection

rememberClipboardEventsHandler(
onCopy = { manager.getSelectedText()?.text },
isEnabled = manager.isNonEmptySelection()
)

ContextMenuArea(manager) {
CompositionLocalProvider(LocalSelectionRegistrar provides registrarImpl) {
// Get the layout coordinates of the selection container. This is for hit test of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ internal class SelectionManager(private val selectionRegistrar: SelectionRegistr
.focusable()
.updateSelectionTouchMode { isInTouchMode = it }
.onKeyEvent {
if (isCopyKeyEvent(it)) {
if (!skipCopyKeyEvent && isCopyKeyEvent(it)) {
copy()
true
} else {
Expand Down Expand Up @@ -1028,3 +1028,7 @@ private suspend fun AwaitPointerEventScope.awaitPointerEventWhereAllChanges(
pass: PointerEventPass = PointerEventPass.Main,
predicate: (PointerInputChange) -> Boolean,
) = awaitPointerEvent(pass).takeIf { it.changes.fastAll(predicate) }


// We skip `isCopyKeyEvent(it)` on web, because should handle browser 'copy' event
internal expect val SelectionManager.skipCopyKeyEvent: Boolean
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,56 @@

package androidx.compose.foundation.text

import androidx.compose.foundation.text.selection.TextFieldSelectionManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.ui.text.AnnotatedString
import kotlinx.browser.document
import org.w3c.dom.clipboard.ClipboardEvent
import org.w3c.dom.events.Event
import org.w3c.dom.events.EventListener

@Composable
@NonRestartableComposable
internal actual inline fun rememberClipboardEventsHandler(
textFieldSelectionManager: TextFieldSelectionManager,
isFocused: Boolean
crossinline onPaste: (String) -> Unit,
crossinline onCopy: () -> String?,
crossinline onCut: () -> String?,
isEnabled: Boolean
) {
if (isFocused) {
DisposableEffect(textFieldSelectionManager) {
if (isEnabled) {
DisposableEffect(Unit) {

val onCopy = EventListener { event ->
val textToCopy = textFieldSelectionManager.onCopyWithResult()
val copyListener = EventListener { event ->
val textToCopy = onCopy()
if (textToCopy != null && event is ClipboardEvent) {
event.clipboardData?.setData("text/plain", textToCopy)
event.preventDefault()
}
}

val onPaste = EventListener { event ->
val pasteListener = EventListener { event ->
if (event is ClipboardEvent) {
val textToPaste = event.clipboardData?.getData("text/plain") ?: ""
onPaste(textToPaste)
event.preventDefault()
textFieldSelectionManager.paste(AnnotatedString(textToPaste))
}
}

val onCut = EventListener { event ->
if (event is ClipboardEvent) {
val cutText = textFieldSelectionManager.onCutWithResult()
event.clipboardData?.setData("text/plain", cutText ?: "")
val cutListener = EventListener { event ->
val cutText = onCut()
if (cutText != null && event is ClipboardEvent) {
event.clipboardData?.setData("text/plain", cutText)
event.preventDefault()
}
}

document.addEventListener("copy", onCopy)
document.addEventListener("paste", onPaste)
document.addEventListener("cut", onCut)
document.addEventListener("copy", copyListener)
document.addEventListener("paste", pasteListener)
document.addEventListener("cut", cutListener)

onDispose {
document.removeEventListener("copy", onCopy)
document.removeEventListener("paste", onPaste)
document.removeEventListener("cut", onCut)
document.removeEventListener("copy", copyListener)
document.removeEventListener("paste", pasteListener)
document.removeEventListener("cut", cutListener)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ internal actual fun isCopyKeyEvent(keyEvent: KeyEvent): Boolean {
*/
internal actual fun Modifier.selectionMagnifier(manager: SelectionManager): Modifier =
TODO("implement js selectionMagnifier")

internal actual val SelectionManager.skipCopyKeyEvent: Boolean
get() = true
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

package androidx.compose.foundation.text

import androidx.compose.foundation.text.selection.TextFieldSelectionManager
import androidx.compose.runtime.Composable

@Composable
internal actual inline fun rememberClipboardEventsHandler(
textFieldSelectionManager: TextFieldSelectionManager,
isFocused: Boolean
crossinline onPaste: (String) -> Unit,
crossinline onCopy: () -> String?,
crossinline onCut: () -> String?,
isEnabled: Boolean
) {
// nothing to do
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.text.selection

internal actual val SelectionManager.skipCopyKeyEvent: Boolean
get() = false
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

package androidx.compose.foundation.text

import androidx.compose.foundation.text.selection.TextFieldSelectionManager
import androidx.compose.runtime.Composable

@Composable
internal actual inline fun rememberClipboardEventsHandler(
textFieldSelectionManager: TextFieldSelectionManager,
isFocused: Boolean
crossinline onPaste: (String) -> Unit,
crossinline onCopy: () -> String?,
crossinline onCut: () -> String?,
isEnabled: Boolean
) {
// nothing to do
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.text.selection

internal actual val SelectionManager.skipCopyKeyEvent: Boolean
get() = false
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@

package androidx.compose.foundation.text

import androidx.compose.foundation.text.selection.TextFieldSelectionManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.ui.text.AnnotatedString
import kotlinx.browser.document
import org.w3c.dom.clipboard.ClipboardEvent
import org.w3c.dom.events.Event
Expand All @@ -29,44 +27,45 @@ import org.w3c.dom.events.EventListener as EventListenerInterface
@Composable
@NonRestartableComposable
internal actual inline fun rememberClipboardEventsHandler(
textFieldSelectionManager: TextFieldSelectionManager,
isFocused: Boolean
crossinline onPaste: (String) -> Unit,
crossinline onCopy: () -> String?,
crossinline onCut: () -> String?,
isEnabled: Boolean
) {
if (isFocused) {
DisposableEffect(textFieldSelectionManager) {

val onCopy = EventListener { event ->
val textToCopy = textFieldSelectionManager.onCopyWithResult()
if (isEnabled) {
DisposableEffect(Unit) {
val copyListener = EventListener { event ->
val textToCopy = onCopy()
if (textToCopy != null && event is ClipboardEvent) {
event.clipboardData?.setData("text/plain", textToCopy)
event.preventDefault()
}
}

val onPaste = EventListener { event ->
val pasteListener = EventListener { event ->
if (event is ClipboardEvent) {
val textToPaste = event.clipboardData?.getData("text/plain") ?: ""
onPaste(textToPaste)
event.preventDefault()
textFieldSelectionManager.paste(AnnotatedString(textToPaste))
}
}

val onCut = EventListener { event ->
if (event is ClipboardEvent) {
val cutText = textFieldSelectionManager.onCutWithResult()
event.clipboardData?.setData("text/plain", cutText ?: "")
val cutListener = EventListener { event ->
val cutText = onCut()
if (cutText != null && event is ClipboardEvent) {
event.clipboardData?.setData("text/plain", cutText)
event.preventDefault()
}
}

document.addEventListener("copy", onCopy)
document.addEventListener("paste", onPaste)
document.addEventListener("cut", onCut)
document.addEventListener("copy", copyListener)
document.addEventListener("paste", pasteListener)
document.addEventListener("cut", cutListener)

onDispose {
document.removeEventListener("copy", onCopy)
document.removeEventListener("paste", onPaste)
document.removeEventListener("cut", onCut)
document.removeEventListener("copy", copyListener)
document.removeEventListener("paste", pasteListener)
document.removeEventListener("cut", cutListener)
}
}
}
Expand Down

0 comments on commit 79d3fa0

Please sign in to comment.