Inspired by the docs and HParker's answer, I wrote up a more complete solution using Elm 0.19 and subscriptions.
It adds a Subscription for Browser.Events.onKeyUp/Down
, and handles them in a top-level Msg with a subMsg for easier processing. It includes Alt, Ctrl, Shift, and Meta (Windows Key/Option).
Remember that onKeyUp/Down
and onKeyPressed
handle characters differently (where up/down use the appropriate case, where pressed does not).
import Browser.Events
import Json.Decode as Decode
type KeyEventMsg
= KeyEventControl
| KeyEventAlt
| KeyEventShift
| KeyEventMeta
| KeyEventLetter Char
| KeyEventUnknown String
type Msg
= KeyPressedMsg KeyEventMsg
| KeyReleasedMsg KeyEventMsg
update : Msg -> ( Model, Cmd Msg )
update msg =
case msg of
KeyPressedMsg keyEventMsg ->
case keyEventMsg of
KeyEventShift ->
( { model | shiftIsPressed = True }, Cmd.none )
_ ->
( model, Cmd.none )
KeyReleasedMsg keyEventMsg ->
case keyEventMsg of
KeyEventShift ->
( { model | shiftIsPressed = False }, Cmd.none )
_ ->
( model, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ Browser.Events.onKeyDown keyPressedDecoder
, Browser.Events.onKeyUp keyReleasedDecoder
]
keyPressedDecoder : Decode.Decoder Msg
keyPressedDecoder =
Decode.map (toKeyEventMsg >> KeyPressedMsg) (Decode.field "key" Decode.string)
keyReleasedDecoder : Decode.Decoder Msg
keyReleasedDecoder =
Decode.map (toKeyEventMsg >> KeyReleasedMsg) (Decode.field "key" Decode.string)
toKeyEventMsg : String -> KeyEventMsg
toKeyEventMsg eventKeyString =
case eventKeyString of
"Control" ->
KeyEventControl
"Shift" ->
KeyEventShift
"Alt" ->
KeyEventAlt
"Meta" ->
KeyEventMeta
string_ ->
case String.uncons string_ of
Just ( char, "" ) ->
KeyEventLetter char
_ ->
KeyEventUnknown eventKeyString