Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix focus-trap for firefox tests #852

Merged
merged 1 commit into from
Feb 1, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 27 additions & 4 deletions headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/focus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,35 @@ import dev.fritz2.headless.foundation.InitialFocus.*
import kotlinx.browser.document
import kotlinx.browser.window
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import org.w3c.dom.events.Event
import org.w3c.dom.events.KeyboardEvent
import kotlin.math.max


/**
* Using fritz2 Event-Flows the calls for [preventDefault] and [stopImmediatePropagation] will be delayed slightly, so that
* the Browser might already perform the Tab Operation. To prevent this behaviour we have to use a plain JS Listener,
* which directly calls [preventDefault] and [stopImmediatePropagation]
*/
private val Tag<HTMLElement>.tabPress
get() = callbackFlow {
val listener = { event: Event ->
if (event is KeyboardEvent) {
if (setOf(Keys.Tab, Keys.Shift + Keys.Tab).contains(shortcutOf(event))) {
event.preventDefault()
event.stopImmediatePropagation()
trySend(event)
}
}
}
domNode.addEventListener("keydown", listener)
awaitClose { domNode.removeEventListener("keydown", listener) }
}

/*
* The implementation of the focus management (especially the "trap") is heavily inspired and based upon the
* fantastic [headless-ui project](https://github.com/tailwindlabs/headlessui)
Expand Down Expand Up @@ -202,7 +225,7 @@ enum class InitialFocus(val focus: Boolean) {
*/
fun Tag<HTMLElement>.trapFocusInMountpoint(restoreFocus: Boolean = true, setInitialFocus: InitialFocus = TryToSet) {
setInitialFocusOnDemandFromMountpoint(setInitialFocus)
trapFocusOn(keydowns.filter { setOf(Keys.Tab, Keys.Shift + Keys.Tab).contains(shortcutOf(it)) })
trapFocusOn(tabPress)
restoreFocusOnDemandFromMountpoint(restoreFocus)
}

Expand Down Expand Up @@ -250,9 +273,9 @@ fun Tag<HTMLElement>.trapFocusWhenever(
setInitialFocusOnDemandFromWhenever(setInitialFocus)
}
}
trapFocusOn(
keydowns.filter { sharedCondition.first() && setOf(Keys.Tab, Keys.Shift + Keys.Tab).contains(shortcutOf(it)) }
)


trapFocusOn(sharedCondition.transform { if (it) emitAll(tabPress) })
restoreFocusOnDemandFromWhenever(
sharedCondition.filter { it }.map { focusedElementBeforeTrap }
.combine(sharedCondition.map { !it && restoreFocus }, ::Pair)
Expand Down