Focus Cleanup
Add Samples
Update Documentation
Remove internal references from public docs
Bug: 170154986
Bug: 190386715
Bug: 186567354
Bug: 168510304
Test: N/A
Relnote: "Removed deprecated experimental `FocusManager#moveFocusIn` and `FocusManager#moveFocusOut`"
Change-Id: I227d79e985d993d52d383d22439abaecbce9f593
diff --git a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta10.txt b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta10.txt
index e9f571b..409ec8a 100644
--- a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta10.txt
+++ b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta10.txt
@@ -343,8 +343,6 @@
public interface FocusManager {
method public void clearFocus(optional boolean force);
method public boolean moveFocus-3ESFkO8(int focusDirection);
- method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusIn();
- method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusOut();
}
public final class FocusModifierKt {
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index e9f571b..409ec8a 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -343,8 +343,6 @@
public interface FocusManager {
method public void clearFocus(optional boolean force);
method public boolean moveFocus-3ESFkO8(int focusDirection);
- method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusIn();
- method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public default boolean moveFocusOut();
}
public final class FocusModifierKt {
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
index 8d4b434..5e56360 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
@@ -20,6 +20,7 @@
import androidx.compose.integration.demos.common.ComposableDemo
import androidx.compose.integration.demos.common.DemoCategory
import androidx.compose.ui.demos.autofill.ExplicitAutofillTypesDemo
+import androidx.compose.ui.demos.focus.CaptureFocusDemo
import androidx.compose.ui.demos.focus.CustomFocusOrderDemo
import androidx.compose.ui.demos.focus.FocusInDialogDemo
import androidx.compose.ui.demos.focus.FocusInPopupDemo
@@ -118,7 +119,8 @@
ComposableDemo("Reuse Focus Requester") { ReuseFocusRequesterDemo() },
ComposableDemo("Focus Search") { FocusSearchDemo() },
ComposableDemo("Custom Focus Order") { CustomFocusOrderDemo() },
- ComposableDemo("FocusManager.moveFocus()") { FocusManagerMoveFocusDemo() }
+ ComposableDemo("FocusManager.moveFocus()") { FocusManagerMoveFocusDemo() },
+ ComposableDemo("Capture/Free Focus") { CaptureFocusDemo() }
)
)
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CaptureFocusDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CaptureFocusDemo.kt
new file mode 100644
index 0000000..fc4646b
--- /dev/null
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CaptureFocusDemo.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2021 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.ui.demos.focus
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color.Companion.Red
+import androidx.compose.ui.graphics.Color.Companion.Transparent
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun CaptureFocusDemo() {
+ Column {
+ Text(
+ "This demo demonstrates how a component can capture focus when it is in an " +
+ "invalidated state."
+ )
+
+ Spacer(Modifier.height(30.dp))
+
+ Text("Enter a word that is 5 characters or shorter")
+ val shortWord = remember { FocusRequester() }
+ var shortString by remember { mutableStateOf("apple") }
+ var shortStringBorder by remember { mutableStateOf(Transparent) }
+ TextField(
+ value = shortString,
+ onValueChange = {
+ shortString = it
+ if (shortString.length > 5) shortWord.captureFocus() else shortWord.freeFocus()
+ },
+ modifier = Modifier
+ .border(2.dp, shortStringBorder)
+ .focusRequester(shortWord)
+ .onFocusChanged { shortStringBorder = if (it.isCaptured) Red else Transparent }
+ )
+
+ Spacer(Modifier.height(30.dp))
+
+ Text("Enter a word that is longer than 5 characters")
+ val longWord = remember { FocusRequester() }
+ var longString by remember { mutableStateOf("pineapple") }
+ var longStringBorder by remember { mutableStateOf(Transparent) }
+
+ TextField(
+ value = longString,
+ onValueChange = {
+ longString = it
+ if (longString.length < 5) longWord.captureFocus() else longWord.freeFocus()
+ },
+ modifier = Modifier
+ .border(2.dp, longStringBorder)
+ .focusRequester(longWord)
+ .onFocusChanged { longStringBorder = if (it.isCaptured) Red else Transparent }
+ )
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
index f31814e..e0e0f56 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/CustomFocusOrderDemo.kt
@@ -56,7 +56,7 @@
)
}
Column(Modifier.fillMaxSize(), SpaceEvenly) {
- val (item1, item2, item3, item4) = FocusRequester.createRefs()
+ val (item1, item2, item3, item4) = remember { FocusRequester.createRefs() }
Row(Modifier.fillMaxWidth(), SpaceEvenly) {
FocusableText(
text = "1",
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
index f01d362..8f0da4a 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
@@ -17,16 +17,112 @@
package androidx.compose.ui.samples
import androidx.annotation.Sampled
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.Text
+import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusOrder
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.focusTarget
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color.Companion.Black
+import androidx.compose.ui.graphics.Color.Companion.Green
+import androidx.compose.ui.graphics.Color.Companion.Red
+import androidx.compose.ui.graphics.Color.Companion.Transparent
import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.unit.dp
+
+@Sampled
+@Composable
+fun FocusableSample() {
+ var color by remember { mutableStateOf(Black) }
+ Box(
+ Modifier
+ .border(2.dp, color)
+ // The onFocusChanged should be added BEFORE the focusable that is being observed.
+ .onFocusChanged { color = if (it.isFocused) Green else Black }
+ .focusable()
+ )
+}
+
+@Sampled
+@Composable
+fun FocusableSampleUsingLowerLevelFocusTarget() {
+ var color by remember { mutableStateOf(Black) }
+ Box(
+ Modifier
+ .border(2.dp, color)
+ // The onFocusChanged should be added BEFORE the focusTarget that is being observed.
+ .onFocusChanged { color = if (it.isFocused) Green else Black }
+ .focusTarget()
+ )
+}
+
+@Sampled
+@Composable
+fun CaptureFocusSample() {
+ val focusRequester = remember { FocusRequester() }
+ var value by remember { mutableStateOf("apple") }
+ var borderColor by remember { mutableStateOf(Transparent) }
+ TextField(
+ value = value,
+ onValueChange = {
+ value = it.apply {
+ if (length > 5) focusRequester.captureFocus() else focusRequester.freeFocus()
+ }
+ },
+ modifier = Modifier
+ .border(2.dp, borderColor)
+ .focusRequester(focusRequester)
+ .onFocusChanged { borderColor = if (it.isCaptured) Red else Transparent }
+ )
+}
+
+@Sampled
+@Composable
+fun RequestFocusSample() {
+ val focusRequester = remember { FocusRequester() }
+ var color by remember { mutableStateOf(Black) }
+ Box(
+ Modifier
+ .clickable { focusRequester.requestFocus() }
+ .border(2.dp, color)
+ // The focusRequester should be added BEFORE the focusable.
+ .focusRequester(focusRequester)
+ // The onFocusChanged should be added BEFORE the focusable that is being observed.
+ .onFocusChanged { color = if (it.isFocused) Green else Black }
+ .focusable()
+ )
+}
+
+@Sampled
+@Composable
+fun ClearFocusSample() {
+ val focusManager = LocalFocusManager.current
+ Column(Modifier.clickable { focusManager.clearFocus() }) {
+ Box(Modifier.focusable().size(100.dp))
+ Box(Modifier.focusable().size(100.dp))
+ Box(Modifier.focusable().size(100.dp))
+ }
+}
@Sampled
@Composable
@@ -46,4 +142,66 @@
Button(onClick = { focusManager.moveFocus(FocusDirection.Up) }) { Text("Up") }
Button(onClick = { focusManager.moveFocus(FocusDirection.Down) }) { Text("Down") }
}
-}
\ No newline at end of file
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun CreateFocusRequesterRefsSample() {
+ val (item1, item2, item3, item4) = remember { FocusRequester.createRefs() }
+ Column {
+ Box(Modifier.focusRequester(item1).focusable())
+ Box(Modifier.focusRequester(item2).focusable())
+ Box(Modifier.focusRequester(item3).focusable())
+ Box(Modifier.focusRequester(item4).focusable())
+ }
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun CustomFocusOrderSample() {
+ Column(Modifier.fillMaxSize(), Arrangement.SpaceEvenly) {
+ val (item1, item2, item3, item4) = remember { FocusRequester.createRefs() }
+ Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) {
+ Box(
+ Modifier
+ .focusOrder(item1) {
+ next = item2
+ right = item2
+ down = item3
+ previous = item4
+ }
+ .focusable()
+ )
+ Box(
+ Modifier
+ .focusOrder(item2) {
+ next = item3
+ right = item1
+ down = item4
+ previous = item1
+ }
+ .focusable()
+ )
+ }
+ Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) {
+ Box(
+ Modifier.focusOrder(item3) {
+ next = item4
+ right = item4
+ up = item1
+ previous = item2
+ }
+ )
+ Box(
+ Modifier.focusOrder(item4) {
+ next = item1
+ left = item3
+ up = item2
+ previous = item3
+ }
+ )
+ }
+ }
+}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/KeyInputSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/KeyInputSamples.kt
new file mode 100644
index 0000000..d8a498a
--- /dev/null
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/KeyInputSamples.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2021 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.ui.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
+import androidx.compose.ui.input.key.KeyEventType.Companion.Unknown
+import androidx.compose.ui.input.key.isAltPressed
+import androidx.compose.ui.input.key.isCtrlPressed
+import androidx.compose.ui.input.key.isMetaPressed
+import androidx.compose.ui.input.key.isShiftPressed
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.key.type
+
+@Suppress("UNUSED_ANONYMOUS_PARAMETER")
+@Sampled
+@Composable
+fun KeyEventSample() {
+ // When the inner Box is focused, and the user presses a key, the key goes down the hierarchy
+ // and then back up to the parent. At any stage you can stop the propagation by returning
+ // true to indicate that you consumed the event.
+ Box(
+ Modifier
+ .onPreviewKeyEvent { keyEvent1 -> false }
+ .onKeyEvent { keyEvent4 -> false }
+ ) {
+ Box(
+ Modifier
+ .onPreviewKeyEvent { keyEvent2 -> false }
+ .onKeyEvent { keyEvent3 -> false }
+ .focusable()
+ )
+ }
+}
+
+@Sampled
+@Composable
+fun KeyEventTypeSample() {
+ Box(
+ Modifier
+ .onKeyEvent {
+ when (it.type) {
+ KeyUp -> println(" KeyUp Pressed")
+ KeyDown -> println(" KeyUp Pressed")
+ Unknown -> println("Unknown key type")
+ else -> println("New KeyTpe (For Future Use)")
+ }
+ false
+ }
+ .focusable()
+ )
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun KeyEventIsAltPressedSample() {
+ Box(
+ Modifier
+ .onKeyEvent {
+ if (it.isAltPressed && it.key == Key.A) {
+ println("Alt + A is pressed")
+ true
+ } else {
+ false
+ }
+ }
+ .focusable()
+ )
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun KeyEventIsCtrlPressedSample() {
+ Box(
+ Modifier
+ .onKeyEvent {
+ if (it.isCtrlPressed && it.key == Key.A) {
+ println("Ctrl + A is pressed")
+ true
+ } else {
+ false
+ }
+ }
+ .focusable()
+ )
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun KeyEventIsMetaPressedSample() {
+ Box(
+ Modifier
+ .onKeyEvent {
+ if (it.isMetaPressed && it.key == Key.A) {
+ println("Meta + A is pressed")
+ true
+ } else {
+ false
+ }
+ }
+ .focusable()
+ )
+}
+
+@ExperimentalComposeUiApi
+@Sampled
+@Composable
+fun KeyEventIsShiftPressedSample() {
+ Box(
+ Modifier
+ .onKeyEvent {
+ if (it.isShiftPressed && it.key == Key.A) {
+ println("Shift + A is pressed")
+ true
+ } else {
+ false
+ }
+ }
+ .focusable()
+ )
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/Key.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/Key.android.kt
index 4650948..f18af37 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/Key.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/Key.android.kt
@@ -17,8 +17,8 @@
package androidx.compose.ui.input.key
import android.view.KeyEvent
-import android.view.KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP
import android.view.KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN
+import android.view.KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.input.key.Key.Companion.Number
import androidx.compose.ui.util.packInts
@@ -28,6 +28,8 @@
* Actual implementation of [Key] for Android.
*
* @param keyCode an integer code representing the key pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
*/
@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
actual inline class Key(val keyCode: Long) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
index e0a35a3..18e9190 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/key/KeyEvent.android.kt
@@ -29,6 +29,8 @@
/**
* The key that was pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
*/
actual val KeyEvent.key: Key
get() = Key(nativeKeyEvent.keyCode)
@@ -54,6 +56,8 @@
/**
* The [type][KeyEventType] of key event.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventTypeSample
*/
actual val KeyEvent.type: KeyEventType
get() = when (nativeKeyEvent.action) {
@@ -64,24 +68,32 @@
/**
* Indicates whether the Alt key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
*/
actual val KeyEvent.isAltPressed: Boolean
get() = nativeKeyEvent.isAltPressed
/**
* Indicates whether the Ctrl key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsCtrlPressedSample
*/
actual val KeyEvent.isCtrlPressed: Boolean
get() = nativeKeyEvent.isCtrlPressed
/**
* Indicates whether the Meta key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsMetaPressedSample
*/
actual val KeyEvent.isMetaPressed: Boolean
get() = nativeKeyEvent.isMetaPressed
/**
* Indicates whether the Shift key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsShiftPressedSample
*/
actual val KeyEvent.isShiftPressed: Boolean
get() = nativeKeyEvent.isShiftPressed
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt
index cc14f53..9440c6b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusChangedModifier.kt
@@ -25,9 +25,12 @@
/**
* Add this modifier to a component to observe focus state events. [onFocusChanged] is invoked
- * only when the focus state changes.
+ * when the focus state changes. The [onFocusChanged] modifier listens to the state of the first
+ * [focusTarget] following this modifier.
*
- * If you want to be notified every time the internal focus state is written to (even if it
+ * @sample androidx.compose.ui.samples.FocusableSample
+ *
+ * Note: If you want to be notified every time the internal focus state is written to (even if it
* hasn't changed), use [onFocusEvent] instead.
*/
fun Modifier.onFocusChanged(onFocusChanged: (FocusState) -> Unit): Modifier =
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
index a6cc7df..782bdb4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
@@ -16,7 +16,6 @@
package androidx.compose.ui.focus
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusStateImpl.Active
import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
@@ -31,6 +30,8 @@
*
* @param force: Whether we should forcefully clear focus regardless of whether we have
* any components that have Captured focus.
+ *
+ * @sample androidx.compose.ui.samples.ClearFocusSample
*/
fun clearFocus(force: Boolean = false)
@@ -41,42 +42,10 @@
* [Modifier.focusOrder()][focusOrder].
*
* @return true if focus was moved successfully. false if the focused item is unchanged.
+ *
+ * @sample androidx.compose.ui.samples.MoveFocusSample
*/
fun moveFocus(focusDirection: FocusDirection): Boolean
-
- /**
- * Moves focus to one of the children of the currently focused item.
- *
- * This function is deprecated. Use FocusManager.moveFocus(FocusDirection.In) instead.
- *
- * @return true if focus was moved successfully.
- */
- @ExperimentalComposeUiApi
- @Deprecated(
- message = "Use FocusManager.moveFocus(FocusDirection.In) instead",
- ReplaceWith(
- "moveFocus(In)",
- "androidx.compose.ui.focus.FocusDirection.Companion.In"
- )
- )
- fun moveFocusIn(): Boolean = false
-
- /**
- * Moves focus to the nearest focusable parent of the currently focused item.
- *
- * This function is deprecated. Use FocusManager.moveFocus(FocusDirection.Out) instead.
- *
- * @return true if focus was moved successfully.
- */
- @ExperimentalComposeUiApi
- @Deprecated(
- message = "Use FocusManager.moveFocus(FocusDirection.Out) instead",
- ReplaceWith(
- "moveFocus(Out)",
- "androidx.compose.ui.focus.FocusDirection.Companion.Out"
- )
- )
- fun moveFocusOut(): Boolean = false
}
/**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
index 5c53268..f2718af 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusModifier.kt
@@ -50,6 +50,16 @@
/**
* Add this modifier to a component to make it focusable.
+ *
+ * Focus state is stored within this modifier. The bounds of this modifier reflect the bounds of
+ * the focus box.
+ *
+ * Note: This is a low level modifier. Before using this consider using
+ * [Modifier.focusable()][androidx.compose.foundation.focusable]. It uses a [focusTarget] in
+ * its implementation. [Modifier.focusable()][androidx.compose.foundation.focusable] adds semantics
+ * that are needed for accessibility.
+ *
+ * @sample androidx.compose.ui.samples.FocusableSampleUsingLowerLevelFocusTarget
*/
fun Modifier.focusTarget(): Modifier = composed(debugInspectorInfo { name = "focusTarget" }) {
remember { FocusModifier(Inactive) }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt
index dbd306d..d258b41 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOrderModifier.kt
@@ -23,6 +23,8 @@
/**
* A [modifier][Modifier.Element] that can be used to set a custom focus traversal order.
+ *
+ * @see Modifier.focusOrder
*/
interface FocusOrderModifier : Modifier.Element {
@@ -36,48 +38,66 @@
/**
* Specifies custom focus destinations that are used instead of the default focus traversal order.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
class FocusOrder {
/**
- * A custom item to be used when the user moves focus to the "next" item.
+ * A custom item to be used when the user requests a focus moves to the "next" item.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
var next: FocusRequester = FocusRequester.Default
/**
- * A custom item to be used when the user moves focus to the "previous" item.
+ * A custom item to be used when the user requests a focus moves to the "previous" item.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
var previous: FocusRequester = FocusRequester.Default
/**
* A custom item to be used when the user moves focus "up".
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
var up: FocusRequester = FocusRequester.Default
/**
* A custom item to be used when the user moves focus "down".
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
var down: FocusRequester = FocusRequester.Default
/**
- * A custom item to be used when the user moves focus to the "left" item.
+ * A custom item to be used when the user requests a focus moves to the "left" item.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
var left: FocusRequester = FocusRequester.Default
/**
- * A custom item to be used when the user moves focus to the "right" item.
+ * A custom item to be used when the user requests a focus moves to the "right" item.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
var right: FocusRequester = FocusRequester.Default
/**
- * A custom item to be used when the user moves focus to the "left" in LTR mode and "right"
- * in RTL mode.
+ * A custom item to be used when the user requests a focus moves to the "left" in LTR mode and
+ * "right" in RTL mode.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
var start: FocusRequester = FocusRequester.Default
/**
- * A custom item to be used when the user moves focus to the "right" in LTR mode and "left"
- * in RTL mode.
+ * A custom item to be used when the user requests a focus moves to the "right" in LTR mode
+ * and "left" in RTL mode.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
var end: FocusRequester = FocusRequester.Default
}
@@ -98,6 +118,8 @@
* to move the current focus to the [next][FocusOrder.next] item, or wants to move
* focus [left][FocusOrder.left], [right][FocusOrder.right], [up][FocusOrder.up] or
* [down][FocusOrder.down].
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
fun Modifier.focusOrder(focusOrderReceiver: FocusOrder.() -> Unit): Modifier {
return this.then(
@@ -114,12 +136,16 @@
/**
* A modifier that lets you specify a [FocusRequester] for the current composable so that this
* [focusRequester] can be used by another composable to specify a custom focus order.
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
fun Modifier.focusOrder(focusRequester: FocusRequester): Modifier = focusRequester(focusRequester)
/**
* A modifier that lets you specify a [FocusRequester] for the current composable along with
* [focusOrder].
+ *
+ * @sample androidx.compose.ui.samples.CustomFocusOrderSample
*/
fun Modifier.focusOrder(
focusRequester: FocusRequester,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
index 0dded38..7f0d6f9 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
@@ -21,14 +21,21 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.node.ModifiedFocusRequesterNode
-private const val focusRequesterNotInitialized = "FocusRequester is not initialized. One reason " +
- "for this is that you requesting focus changes during composition. Focus requesters should " +
- "not be made during composition, but should be made in response to some event."
+private const val focusRequesterNotInitialized = """
+ FocusRequester is not initialized. Here are some possible fixes:
+
+ 1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
+ 2. Did you forget to add a Modifier.focusRequester() ?
+ 3. Are you attempting to request focus during composition? Focus requests should be made in
+ response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
+"""
/**
* The [FocusRequester] is used in conjunction with
- * [Modifier.focusRequester][androidx.compose.ui.focus.focusRequester] to send requests for focus
- * state change.
+ * [Modifier.focusRequester][androidx.compose.ui.focus.focusRequester] to send requests to
+ * change focus.
+ *
+ * @sample androidx.compose.ui.samples.RequestFocusSample
*
* @see androidx.compose.ui.focus.focusRequester
*/
@@ -38,8 +45,10 @@
/**
* Use this function to request focus. If the system grants focus to a component associated
- * with this [FocusRequester], its [state][FocusState] will be set to
- * [Active][FocusState.Active].
+ * with this [FocusRequester], its [onFocusChanged] modifiers will receive a [FocusState] object
+ * where [FocusState.isFocused] is true.
+ *
+ * @sample androidx.compose.ui.samples.RequestFocusSample
*/
fun requestFocus() {
check(focusRequesterNodes.isNotEmpty()) { focusRequesterNotInitialized }
@@ -49,15 +58,17 @@
/**
* Deny requests to clear focus.
*
- * Use this function to send a request to capture the focus. If a component is captured,
- * its [state][FocusState] will be set to [Captured][FocusState.Captured]. When a
- * component is in this state, it holds onto focus until [freeFocus] is called. When a
- * component is in the [Captured][FocusState.Captured] state, all focus requests from
- * other components are declined.
+ * Use this function to send a request to capture focus. If a component captures focus,
+ * it will send a [FocusState] object to its associated [onFocusChanged]
+ * modifiers where [FocusState.isCaptured]() == true.
+ *
+ * When a component is in a Captured state, all focus requests from other components are
+ * declined.
*
* @return true if the focus was successfully captured by one of the
- * [focus][androidx.compose.ui.focus] modifiers associated with this [FocusRequester].
- * false otherwise.
+ * [focus][focusTarget] modifiers associated with this [FocusRequester]. False otherwise.
+ *
+ * @sample androidx.compose.ui.samples.CaptureFocusSample
*/
fun captureFocus(): Boolean {
check(focusRequesterNodes.isNotEmpty()) { focusRequesterNotInitialized }
@@ -73,19 +84,18 @@
}
/**
- * Use this function to send a request to release focus when one of the components associated
- * with this [FocusRequester] is in a [Captured][FocusState.Captured] state.
+ * Use this function to send a request to free focus when one of the components associated
+ * with this [FocusRequester] is in a Captured state. If a component frees focus,
+ * it will send a [FocusState] object to its associated [onFocusChanged]
+ * modifiers where [FocusState.isCaptured]() == false.
*
- * When the node is in the [Captured][FocusState.Captured] state, it rejects all requests to
- * clear focus. Calling
- * [freeFocus] puts the node in the [Active][FocusState.Active] state, where it is no longer
- * preventing other
- * nodes from requesting focus.
+ * When a component is in a Captured state, all focus requests from other components are
+ * declined.
+ *.
+ * @return true if the captured focus was successfully released. i.e. At the end of this
+ * operation, one of the components associated with this [focusRequester] freed focus.
*
- * @return true if the focus was successfully released. i.e. At the end of this operation,
- * one of the components associated with this
- * [focusRequester][androidx.compose.ui.focus.focusRequester] is in the
- * [Active][FocusState.Active] state. false otherwise.
+ * @sample androidx.compose.ui.samples.CaptureFocusSample
*/
fun freeFocus(): Boolean {
check(focusRequesterNodes.isNotEmpty()) { focusRequesterNotInitialized }
@@ -103,13 +113,15 @@
companion object {
/**
* Default [focusRequester], which when used in [Modifier.focusOrder][focusOrder] implies
- * that we want to use the default system focus order, that is based on the location on
+ * that we want to use the default system focus order, that is based on the position of the
* items on the screen.
*/
val Default = FocusRequester()
/**
* Convenient way to create multiple [FocusRequester] instances.
+ *
+ * @sample androidx.compose.ui.samples.CreateFocusRequesterRefsSample
*/
@ExperimentalComposeUiApi
object FocusRequesterFactory {
@@ -134,6 +146,8 @@
/**
* Convenient way to create multiple [FocusRequester]s, which can to be used to request
* focus, or to specify a focus traversal order.
+ *
+ * @sample androidx.compose.ui.samples.CreateFocusRequesterRefsSample
*/
@ExperimentalComposeUiApi
fun createRefs() = FocusRequesterFactory
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt
index 739030d..8f193ed 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequesterModifier.kt
@@ -22,14 +22,19 @@
import androidx.compose.ui.platform.debugInspectorInfo
/**
- * A [modifier][Modifier.Element] that can be used to pass in a [FocusRequester] that can be used
- * to request focus state changes.
+ * A [modifier][Modifier.Element] that is used to pass in a [FocusRequester] that can be used to
+ * request focus state changes.
+ *
+ * @sample androidx.compose.ui.samples.RequestFocusSample
*
* @see FocusRequester
+ * @see Modifier.focusRequester
*/
interface FocusRequesterModifier : Modifier.Element {
/**
* An instance of [FocusRequester], that can be used to request focus state changes.
+ *
+ * @sample androidx.compose.ui.samples.RequestFocusSample
*/
val focusRequester: FocusRequester
}
@@ -40,7 +45,9 @@
) : FocusRequesterModifier, InspectorValueInfo(inspectorInfo)
/**
- * Add this modifier to a component to observe changes to focus state.
+ * Add this modifier to a component to request changes to focus.
+ *
+ * @sample androidx.compose.ui.samples.RequestFocusSample
*/
fun Modifier.focusRequester(focusRequester: FocusRequester): Modifier {
return this.then(
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
index 5f2f046..390eba2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusState.kt
@@ -19,14 +19,19 @@
/**
* The focus state of a [FocusModifier]. Use [onFocusChanged] or [onFocusEvent] modifiers to
* access [FocusState].
+ *
+ * @sample androidx.compose.ui.samples.FocusableSample
*/
interface FocusState {
/**
* Whether the component is focused or not.
*
+ * @sample androidx.compose.ui.samples.FocusableSample
+ *
* @return true if the component is focused, false otherwise.
*/
val isFocused: Boolean
+
/**
* Whether the focus modifier associated with this [FocusState] has a child that is focused.
*
@@ -36,13 +41,16 @@
/**
* Whether focus is captured or not. A focusable component is in a captured state when it
- * wants to hold onto focus. (Eg. when a text field has an invalid phone number]. When we are
- * in a captured state, clicking outside the focused item does not clear focus.
+ * wants to hold onto focus. (Eg. when a text field has an invalid phone number). When we are
+ * in a captured state, clicking on other focusable items does not clear focus from the
+ * currently focused item.
*
* You can capture focus by calling [focusRequester.captureFocus()][captureFocus] and free
* focus by calling [focusRequester.freeFocus()][freeFocus].
*
* @return true if focus is captured, false otherwise.
+ *
+ * @sample androidx.compose.ui.samples.CaptureFocusSample
*/
val isCaptured: Boolean
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
index fdd5444..94a823b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTraversal.kt
@@ -39,8 +39,8 @@
private const val invalidFocusDirection = "Invalid FocusDirection"
/**
- * The [FocusDirection] is used to specify direction when a focus change request is made via
- * [moveFocus].
+ * The [FocusDirection] is used to specify the direction for a [FocusManager.moveFocus]
+ * request.
*
* @sample androidx.compose.ui.samples.MoveFocusSample
*/
@@ -67,36 +67,48 @@
/**
* Direction used in [moveFocus] to indicate that you are searching for the next
* focusable item.
+ *
+ * @sample androidx.compose.ui.samples.MoveFocusSample
*/
val Next: FocusDirection = FocusDirection(1)
/**
* Direction used in [moveFocus] to indicate that you are searching for the previous
* focusable item.
+ *
+ * @sample androidx.compose.ui.samples.MoveFocusSample
*/
val Previous: FocusDirection = FocusDirection(2)
/**
* Direction used in [moveFocus] to indicate that you are searching for the next
* focusable item to the left of the currently focused item.
+ *
+ * @sample androidx.compose.ui.samples.MoveFocusSample
*/
val Left: FocusDirection = FocusDirection(3)
/**
* Direction used in [moveFocus] to indicate that you are searching for the next
* focusable item to the right of the currently focused item.
+ *
+ * @sample androidx.compose.ui.samples.MoveFocusSample
*/
val Right: FocusDirection = FocusDirection(4)
/**
* Direction used in [moveFocus] to indicate that you are searching for the next
* focusable item that is above the currently focused item.
+ *
+ * @sample androidx.compose.ui.samples.MoveFocusSample
*/
val Up: FocusDirection = FocusDirection(5)
/**
* Direction used in [moveFocus] to indicate that you are searching for the next
* focusable item that is below the currently focused item.
+ *
+ * @sample androidx.compose.ui.samples.MoveFocusSample
*/
val Down: FocusDirection = FocusDirection(6)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/Key.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/Key.kt
index c420f17..9612a0d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/Key.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/Key.kt
@@ -23,6 +23,8 @@
*
* @param keyCode a Long value representing the key pressed. Note: This keycode can be used to
* uniquely identify a hardware key. It is different from the native keycode.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
*/
@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
expect inline class Key(val keyCode: Long) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
index 48845cfb..efd3ce9 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyEvent.kt
@@ -22,14 +22,21 @@
expect class NativeKeyEvent
/**
- * When a user presses a key on a hardware keyboard, a [KeyEvent] is sent to the
- * [KeyInputModifier] that is currently active.
+ * When a user presses a key on a hardware keyboard, a [KeyEvent] is sent to the item that is
+ * currently focused. Any parent composable can intercept this [key event][KeyEvent] on its way to
+ * the focused item by using [Modifier.onPreviewKeyEvent()]][onPreviewKeyEvent]. If the item is
+ * not consumed, it returns back to each parent and can be intercepted by using
+ * [Modifier.onKeyEvent()]][onKeyEvent].
+ *
+ * @sample androidx.compose.ui.samples.KeyEventSample
*/
@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
inline class KeyEvent(val nativeKeyEvent: NativeKeyEvent)
/**
* The key that was pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
*/
expect val KeyEvent.key: Key
@@ -53,31 +60,43 @@
/**
* The [type][KeyEventType] of key event.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventTypeSample
*/
expect val KeyEvent.type: KeyEventType
/**
* Indicates whether the Alt key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsAltPressedSample
*/
expect val KeyEvent.isAltPressed: Boolean
/**
* Indicates whether the Ctrl key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsCtrlPressedSample
*/
expect val KeyEvent.isCtrlPressed: Boolean
/**
* Indicates whether the Meta key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsMetaPressedSample
*/
expect val KeyEvent.isMetaPressed: Boolean
/**
* Indicates whether the Shift key is pressed.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventIsShiftPressedSample
*/
expect val KeyEvent.isShiftPressed: Boolean
/**
* The type of Key Event.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventTypeSample
*/
@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
inline class KeyEventType internal constructor(@Suppress("unused") private val value: Int) {
@@ -94,16 +113,22 @@
companion object {
/**
* Unknown key event.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventTypeSample
*/
val Unknown: KeyEventType = KeyEventType(0)
/**
* Type of KeyEvent sent when the user lifts their finger off a key on the keyboard.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventTypeSample
*/
val KeyUp: KeyEventType = KeyEventType(1)
/**
* Type of KeyEvent sent when the user presses down their finger on a key on the keyboard.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventTypeSample
*/
val KeyDown: KeyEventType = KeyEventType(2)
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
index 4519562..7aeab72 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/KeyInputModifier.kt
@@ -24,11 +24,13 @@
/**
* Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
- * allow it to intercept hardware key events.
+ * allow it to intercept hardware key events when it (or one of its children) is focused.
*
* @param onKeyEvent This callback is invoked when the user interacts with the hardware keyboard.
* While implementing this callback, return true to stop propagation of this event. If you return
* false, the key event will be sent to this [onKeyEvent]'s parent.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventSample
*/
// TODO: b/191017532 remove Modifier.composed
@Suppress("UnnecessaryComposedModifier")
@@ -43,13 +45,15 @@
/**
* Adding this [modifier][Modifier] to the [modifier][Modifier] parameter of a component will
- * allow it to intercept hardware key events.
+ * allow it to intercept hardware key events when it (or one of its children) is focused.
*
* @param onPreviewKeyEvent This callback is invoked when the user interacts with the hardware
* keyboard. It gives ancestors of a focused component the chance to intercept a [KeyEvent].
* Return true to stop propagation of this event. If you return false, the key event will be sent
* to this [onPreviewKeyEvent]'s child. If none of the children consume the event, it will be
* sent back up to the root [KeyInputModifier] using the onKeyEvent callback.
+ *
+ * @sample androidx.compose.ui.samples.KeyEventSample
*/
// TODO: b/191017532 remove Modifier.composed
@Suppress("UnnecessaryComposedModifier")