Generalize the FocusTrap module

This commit is contained in:
Tessa Kelly 2022-04-25 17:44:39 -07:00
parent 90bdd4c703
commit 7beb363b6e
2 changed files with 89 additions and 52 deletions

View File

@ -7,8 +7,7 @@ module Nri.Ui.FocusTrap.V1 exposing (FocusTrap, toAttribute)
-}
import Accessibility.Styled as Html
import Html.Styled.Events as Events
import Json.Decode as Decode exposing (Decoder)
import Nri.Ui.WhenFocusLeaves.V1 as WhenFocusLeaves
{-| Defines how focus will wrap in reponse to tab keypresses in a part of the UI.
@ -28,53 +27,13 @@ type alias FocusTrap msg =
-}
toAttribute : FocusTrap msg -> Html.Attribute msg
toAttribute { firstId, lastId, focus } =
onTab <|
\elementId shiftKey ->
-- if the user tabs back while on the first id,
-- we want to wrap around to the last id.
if elementId == firstId && shiftKey then
Decode.succeed
{ message = focus lastId
, preventDefault = True
, stopPropagation = False
}
else if elementId == lastId && not shiftKey then
-- if the user tabs forward while on the last id,
-- we want to wrap around to the first id.
Decode.succeed
{ message = focus firstId
, preventDefault = True
, stopPropagation = False
}
else
Decode.fail "No need to intercept the key press"
onTab :
(String
-> Bool
-> Decoder { message : msg, preventDefault : Bool, stopPropagation : Bool }
)
-> Html.Attribute msg
onTab do =
Events.custom "keydown"
(Decode.andThen
(\( id, keyCode, shiftKey ) ->
if keyCode == 9 then
do id shiftKey
else
Decode.fail "No need to intercept the key press"
)
decodeKeydown
)
decodeKeydown : Decoder ( String, Int, Bool )
decodeKeydown =
Decode.map3 (\id keyCode shiftKey -> ( id, keyCode, shiftKey ))
(Decode.at [ "target", "id" ] Decode.string)
(Decode.field "keyCode" Decode.int)
(Decode.field "shiftKey" Decode.bool)
WhenFocusLeaves.toAttribute
{ firstId = firstId
, lastId = lastId
, -- if the user tabs back while on the first id,
-- we want to wrap around to the last id.
tabBackAction = focus lastId
, -- if the user tabs forward while on the last id,
-- we want to wrap around to the first id.
tabForwardAction = focus firstId
}

View File

@ -0,0 +1,78 @@
module Nri.Ui.WhenFocusLeaves.V1 exposing (toAttribute)
{-| Listen for when the focus leaves the area, and then do an action.
@docs toAttribute
-}
import Accessibility.Styled as Html
import Html.Styled.Events as Events
import Json.Decode as Decode exposing (Decoder)
{-| Attach this attribute to add a focus watcher to an HTML element and define
what to do in reponse to tab keypresses in a part of the UI.
The ids referenced here are expected to correspond to elements in the container
we are adding the attribute to.
-}
toAttribute :
{ firstId : String
, lastId : String
, tabBackAction : msg
, tabForwardAction : msg
}
-> Html.Attribute msg
toAttribute { firstId, lastId, tabBackAction, tabForwardAction } =
onTab <|
\elementId shiftKey ->
-- if the user tabs back while on the first id,
-- we execute the action
if elementId == firstId && shiftKey then
Decode.succeed
{ message = tabBackAction
, preventDefault = False
, stopPropagation = False
}
else if elementId == lastId && not shiftKey then
-- if the user tabs forward while on the last id,
-- we want to wrap around to the first id.
Decode.succeed
{ message = tabForwardAction
, preventDefault = False
, stopPropagation = False
}
else
Decode.fail "No need to intercept the key press"
onTab :
(String
-> Bool
-> Decoder { message : msg, preventDefault : Bool, stopPropagation : Bool }
)
-> Html.Attribute msg
onTab do =
Events.custom "keydown"
(Decode.andThen
(\( id, keyCode, shiftKey ) ->
if keyCode == 9 then
do id shiftKey
else
Decode.fail "No need to intercept the key press"
)
decodeKeydown
)
decodeKeydown : Decoder ( String, Int, Bool )
decodeKeydown =
Decode.map3 (\id keyCode shiftKey -> ( id, keyCode, shiftKey ))
(Decode.at [ "target", "id" ] Decode.string)
(Decode.field "keyCode" Decode.int)
(Decode.field "shiftKey" Decode.bool)