Does anyone have a strategy for working around thi...
# compose-desktop
r
Does anyone have a strategy for working around this issue: https://issuetracker.google.com/issues/319412360 (impossible to prevent focus of content within a
LazyColumn
via
Modifier.focusProperties { canFocus = false }
). This is a "wontfix: by design" in compose, and arguably not a big deal on mobile, but it's a real problem on desktop for keyboard navigation and accessibility. People can tab into UI that should not be accessible based on the current state. Is this something that compose desktop could handle differently? Or is there another way of preventing focus for a whole tree of composables?
a
I would submit this as a feature request on Google’s issue tracker.
👍 1
Modifier.focusProperties { canFocus = false }
, by design, applies only to the element itself, not its children.
r
Submitted: https://issuetracker.google.com/issues/347031398 I ended up being able to work around this, though in quite an ugly way:
Copy code
/** Focusable prevents focus within its content by redirecting focus elsewhere immediately on focus */
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun Focusable(modifier: Modifier = Modifier, focusable: Boolean, content: @Composable () -> Unit) {
    val focusManager = LocalFocusManager.current
    var focusEntryDirection by remember { mutableStateOf<FocusDirection?>(null) }

    Box(modifier = modifier
        .onFocusEvent { event ->
            if (focusable) return@onFocusEvent
            if (!event.isFocused && !event.hasFocus) return@onFocusEvent

            // The box is designed to have a single child, so moving focus in onFocusEvent *before* any children
            // gain focus means navigation occurs relative to the outer box. Therefore, no focus anchors are needed.
            when (focusEntryDirection) {
                FocusDirection.Previous -> {
                    focusManager.moveFocus(FocusDirection.Previous)
                }
                FocusDirection.Next -> {
                    focusManager.moveFocus(FocusDirection.Next)
                }
                else -> {
                    // So far in testing, focus always comes from Previous/Next, unless it was manually requested
                    // via a focus requester on a child composable.
                    // May need to handle Up/Down/Left/Right too.
                    focusManager.clearFocus()
                }
            }
        }
        .focusProperties {
            // This box (itself) shouldn't be focusable, but by default its children can be
            canFocus = false

            // To support disabling focus, accept the focus then immediately push it elsewhere in onFocusEvent
            // - `enter = { Cancel }` would consume the focus attempt rather than skipping over this item
            // - onFocusEvent API provides no way to know the entry direction, hence this hack. An alternative would
            //   be to detect the shift key, but that feels even hackier.
            enter = {
                focusEntryDirection = it
                Default
            }
        }
        .focusable()
    ) {
        CompositionLocalProvider(LocalFocusability provides focusable) {
            content()
        }
    }
}

val LocalFocusability = compositionLocalOf { true }