From 3befdffb67e528075b668eb2ee921f5fed625dc8 Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Sat, 18 Jan 2025 16:23:27 +0100 Subject: [PATCH] Handle entering FocusWrap from outside When FocusWrap was used, but the previous focus was outside of the wrapped element, the browser would try to focus the boundary element, causing us to focus the last element instead. This is fixed by checking the relatedTarget of the event, and specially handling focus coming from outside. Fixes #3636. --- assets/js/phoenix_live_view/hooks.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/assets/js/phoenix_live_view/hooks.js b/assets/js/phoenix_live_view/hooks.js index bda38cd62d..f69765794f 100644 --- a/assets/js/phoenix_live_view/hooks.js +++ b/assets/js/phoenix_live_view/hooks.js @@ -47,8 +47,26 @@ let Hooks = { mounted(){ this.focusStart = this.el.firstElementChild this.focusEnd = this.el.lastElementChild - this.focusStart.addEventListener("focus", () => ARIA.focusLast(this.el)) - this.focusEnd.addEventListener("focus", () => ARIA.focusFirst(this.el)) + this.focusStart.addEventListener("focus", (e) => { + if(!e.relatedTarget || !this.el.contains(e.relatedTarget)){ + // Handle focus entering from outside (e.g. Tab when body is focused) + // https://github.com/phoenixframework/phoenix_live_view/issues/3636 + const nextFocus = e.target.nextElementSibling + ARIA.attemptFocus(nextFocus) || ARIA.focusFirst(nextFocus) + } else { + ARIA.focusLast(this.el) + } + }) + this.focusEnd.addEventListener("focus", (e) => { + if(!e.relatedTarget || !this.el.contains(e.relatedTarget)){ + // Handle focus entering from outside (e.g. Shift+Tab when body is focused) + // https://github.com/phoenixframework/phoenix_live_view/issues/3636 + const nextFocus = e.target.previousElementSibling + ARIA.attemptFocus(nextFocus) || ARIA.focusLast(nextFocus) + } else { + ARIA.focusFirst(this.el) + } + }) this.el.addEventListener("phx:show-end", () => this.el.focus()) if(window.getComputedStyle(this.el).display !== "none"){ ARIA.focusFirst(this.el)