This is an alternative site for discovering Elm packages. You may be looking for the official Elm package site instead.
1.0.1
README

Keyboard.Extra.Browser

Convenience helpers for working with keyboard inputs.

Browser

type Browser = Opera | InternetExplorer | FireFox | Safari | Chrome

This type represents what browser the user is in. Presumably you would figure this information out in JavaScript using navigator.userAgent, and then pass it into your app as flags.

Msg and Update

Using Keyboard.Extra this way, you get all the help it can provide. Use either this approach, or the plain subscriptions and handle the state yourself.

type Msg = Down Key | Up Key

Keyboard.Extra's internal message type.

subscriptions : Browser -> Sub Msg

The subscriptions needed for the "Msg and Update" way.

update : Msg -> List Key -> List Key

Use this (or updateWithKeyChange) to have the list of keys update.

If you need to know exactly what changed just now, have a look at updateWithKeyChange.

updateWithKeyChange : Msg -> List Key -> ( List Key, Maybe KeyChange )

This alternate update function answers the question: "Did the pressed down keys in fact change just now?"

You might be wondering why this is a Maybe KeyChange – it's because keydown events happen many times per second when you hold down a key. Thus, not all incoming messages actually cause a change in the model.

Note This is provided for convenience, and may not perform well in real programs. If you are experiencing slowness or jittering when using updateWithKeyChange, see if the regular update makes it go away.

type KeyChange = KeyDown Key | KeyUp Key

The second value updateWithKeyChange may return, representing the actual change that happened during the update.

Helpers

Note: To find out if a key is being pressed, simply use List.member key keyList.

type alias Arrows = { x : Int, y : Int }

Record type used for arrows and wasd. Both x and y can range from -1 to 1, and are 0 if no keys are pressed.

arrows : List Key -> Arrows

Gives the arrow keys' pressed down state as follows:

arrows []                      --> { x = 0, y = 0 }

arrows [ ArrowLeft ]           --> { x = -1, y = 0 }

arrows [ ArrowUp, ArrowRight ] --> { x = 1, y = 1 }

arrows [ ArrowDown, ArrowLeft, ArrowRight ]
                               --> { x = 0, y = -1 }
wasd : List Key -> Arrows

Similar to arrows, gives the W, A, S and D keys' pressed down state.

wasd []                       --> { x = 0, y = 0 }

wasd [ CharA ]                --> { x = -1, y = 0 }

wasd [ CharW, CharD ]         --> { x = 1, y = 1 }

wasd [ CharA, CharS, CharD ]  --> { x = 0, y = -1 }
type Direction = North | NorthEast | East | SouthEast | South | SouthWest | West | NorthWest | NoDirection

Type representation of the arrows.

arrowsDirection : List Key -> Direction

Gives the arrow keys' pressed down state as follows:

arrowsDirection []                      --> NoDirection

arrowsDirection [ ArrowLeft ]           --> West

arrowsDirection [ ArrowUp, ArrowRight ] --> NorthEast

arrowsDirection [ ArrowDown, ArrowLeft, ArrowRight ]
                                        --> South
wasdDirection : List Key -> Direction

Similar to arrows, gives the W, A, S and D keys' pressed down state.

wasdDirection []                      --> NoDirection

wasdDirection [ CharA ]               --> West

wasdDirection [ CharW, CharD ]        --> NorthEast

wasdDirection [ CharA, CharS, CharD ] --> South

Plain Subscriptions

If you prefer to only get "the facts" and do your own handling, use these subscriptions. Otherwise, you may be more comfortable with the Msg and Update.

downs : Browser -> (Key -> msg) -> Sub msg

Subscription for key down events.

Note When the user presses and holds a key, there may or may not be many of these messages before the corresponding key up message.

ups : Browser -> (Key -> msg) -> Sub msg

Subscription for key up events.

Decoder

targetKey : Browser -> Json.Decoder Key

A Json.Decoder for grabbing event.keyCode and turning it into a Key

import Json.Decode as Json

onKey : (Key -> msg) -> Attribute msg
onKey tagger =
    on "keydown" (Json.map tagger (targetKey Chrome))

Low level

fromCode : Browser -> KeyCode -> Key

Convert a key code into a Key.

fromCode Chrome 13  --> Enter
fromCode Chrome 173 --> VolumeMute
fromCode FireFox 173 --> HyphenMinus
toCode : Browser -> Key -> KeyCode

Convert a Key into a key code.

toCode Chrome Enter  --> 13
toCode Chrome Equals --> 187
toCode FireFox Equals --> 61

Keyboard keys

type Key = Cancel | Help | BackSpace | Tab | Clear | Enter | Shift | Control | Alt | Pause | CapsLock | Escape | Convert | NonConvert | Accept | ModeChange | Space | PageUp | PageDown | End | Home | ArrowLeft | ArrowUp | ArrowRight | ArrowDown | Select | Print | Execute | PrintScreen | Insert | Delete | Number0 | Number1 | Number2 | Number3 | Number4 | Number5 | Number6 | Number7 | Number8 | Number9 | Colon | Semicolon | LessThan | Equals | GreaterThan | QuestionMark | At | CharA | CharB | CharC | CharD | CharE | CharF | CharG | CharH | CharI | CharJ | CharK | CharL | CharM | CharN | CharO | CharP | CharQ | CharR | CharS | CharT | CharU | CharV | CharW | CharX | CharY | CharZ | Super | ContextMenu | Sleep | Numpad0 | Numpad1 | Numpad2 | Numpad3 | Numpad4 | Numpad5 | Numpad6 | Numpad7 | Numpad8 | Numpad9 | Multiply | Add | Separator | Subtract | Decimal | Divide | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15 | F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | NumLock | ScrollLock | Circumflex | Exclamation | DoubleQuote | Hash | Dollar | Percent | Ampersand | Underscore | OpenParen | CloseParen | Asterisk | Plus | Pipe | HyphenMinus | OpenCurlyBracket | CloseCurlyBracket | Tilde | VolumeMute | VolumeDown | VolumeUp | Comma | Minus | Period | Slash | BackQuote | OpenBracket | BackSlash | CloseBracket | Quote | Meta | Altgr | Other

These are all the keys that have names in Keyboard.Extra.

module Keyboard.Extra.Browser
    exposing
        ( Arrows
        , Browser(..)
        , Direction(..)
        , Key(..)
        , KeyChange(..)
        , Msg
        , arrows
        , arrowsDirection
        , downs
        , fromCode
        , subscriptions
        , targetKey
        , toCode
        , update
        , updateWithKeyChange
        , ups
        , wasd
        , wasdDirection
        )

{-| Convenience helpers for working with keyboard inputs.


# Browser

@docs Browser


# Msg and Update

Using Keyboard.Extra this way, you get all the help it can provide.
Use either this approach, or the plain subscriptions and handle the state yourself.

@docs Msg, subscriptions, update, updateWithKeyChange, KeyChange


## Helpers

> **Note:** To find out if a key is being pressed, simply use `List.member key keyList`.

@docs Arrows, arrows, wasd, Direction, arrowsDirection, wasdDirection


# Plain Subscriptions

If you prefer to only get "the facts" and do your own handling, use these
subscriptions. Otherwise, you may be more comfortable with the Msg and Update.

@docs downs, ups


# Decoder

@docs targetKey


# Low level

@docs fromCode, toCode


# Keyboard keys

@docs Key

-}

import Dict exposing (Dict)
import Json.Decode as Json
import Keyboard exposing (KeyCode)


{-| This type represents what browser the user is in. Presumably you would figure this information out in JavaScript using `navigator.userAgent`, and then pass it into your app as flags.
-}
type Browser
    = Opera
    | InternetExplorer
    | FireFox
    | Safari
    | Chrome


{-| Subscription for key down events.

**Note** When the user presses and holds a key, there may or may not be many of
these messages before the corresponding key up message.

-}
downs : Browser -> (Key -> msg) -> Sub msg
downs browser toMsg =
    Keyboard.downs (toMsg << fromCode browser)


{-| Subscription for key up events.
-}
ups : Browser -> (Key -> msg) -> Sub msg
ups browser toMsg =
    Keyboard.ups (toMsg << fromCode browser)


{-| `Keyboard.Extra`'s internal message type.
-}
type Msg
    = Down Key
    | Up Key


{-| Record type used for `arrows` and `wasd`.
Both `x` and `y` can range from `-1` to `1`, and are `0` if no keys are pressed.
-}
type alias Arrows =
    { x : Int, y : Int }


{-| The subscriptions needed for the "Msg and Update" way.
-}
subscriptions : Browser -> Sub Msg
subscriptions browser =
    Sub.batch
        [ Keyboard.downs (Down << fromCode browser)
        , Keyboard.ups (Up << fromCode browser)
        ]


insert : Key -> List Key -> List Key
insert code list =
    list
        |> remove code
        |> (::) code


remove : Key -> List Key -> List Key
remove code list =
    list
        |> List.filter ((/=) code)


{-| Use this (or `updateWithKeyChange`) to have the list of keys update.

_If you need to know exactly what changed just now, have a look
at `updateWithKeyChange`._

-}
update : Msg -> List Key -> List Key
update msg state =
    case msg of
        Down key ->
            insert key state

        Up key ->
            remove key state


{-| The second value `updateWithKeyChange` may return, representing the actual
change that happened during the update.
-}
type KeyChange
    = KeyDown Key
    | KeyUp Key


{-| This alternate update function answers the question: "Did the pressed down
keys in fact change just now?"

You might be wondering why this is a `Maybe KeyChange` &ndash; it's because
`keydown` events happen many times per second when you hold down a key. Thus,
not all incoming messages actually cause a change in the model.

**Note** This is provided for convenience, and may not perform well in real
programs. If you are experiencing slowness or jittering when using
`updateWithKeyChange`, see if the regular `update` makes it go away.

-}
updateWithKeyChange : Msg -> List Key -> ( List Key, Maybe KeyChange )
updateWithKeyChange msg state =
    case msg of
        Down key ->
            let
                nextState =
                    insert key state

                change =
                    if List.length nextState /= List.length state then
                        Just (KeyDown key)
                    else
                        Nothing
            in
            ( nextState, change )

        Up key ->
            let
                nextState =
                    remove key state

                change =
                    if List.length nextState /= List.length state then
                        Just (KeyUp key)
                    else
                        Nothing
            in
            ( nextState, change )


{-| Gives the arrow keys' pressed down state as follows:

    arrows []                      --> { x = 0, y = 0 }

    arrows [ ArrowLeft ]           --> { x = -1, y = 0 }

    arrows [ ArrowUp, ArrowRight ] --> { x = 1, y = 1 }

    arrows [ ArrowDown, ArrowLeft, ArrowRight ]
                                   --> { x = 0, y = -1 }

-}
arrows : List Key -> Arrows
arrows keys =
    let
        toInt key =
            keys
                |> List.member key
                |> boolToInt

        x =
            toInt ArrowRight - toInt ArrowLeft

        y =
            toInt ArrowUp - toInt ArrowDown
    in
    { x = x, y = y }


{-| Similar to `arrows`, gives the W, A, S and D keys' pressed down state.

    wasd []                       --> { x = 0, y = 0 }

    wasd [ CharA ]                --> { x = -1, y = 0 }

    wasd [ CharW, CharD ]         --> { x = 1, y = 1 }

    wasd [ CharA, CharS, CharD ]  --> { x = 0, y = -1 }

-}
wasd : List Key -> Arrows
wasd keys =
    let
        toInt key =
            keys
                |> List.member key
                |> boolToInt

        x =
            toInt CharD - toInt CharA

        y =
            toInt CharW - toInt CharS
    in
    { x = x, y = y }


{-| Type representation of the arrows.
-}
type Direction
    = North
    | NorthEast
    | East
    | SouthEast
    | South
    | SouthWest
    | West
    | NorthWest
    | NoDirection


{-| Gives the arrow keys' pressed down state as follows:

    arrowsDirection []                      --> NoDirection

    arrowsDirection [ ArrowLeft ]           --> West

    arrowsDirection [ ArrowUp, ArrowRight ] --> NorthEast

    arrowsDirection [ ArrowDown, ArrowLeft, ArrowRight ]
                                            --> South

-}
arrowsDirection : List Key -> Direction
arrowsDirection =
    arrowsToDir << arrows


{-| Similar to `arrows`, gives the W, A, S and D keys' pressed down state.

    wasdDirection []                      --> NoDirection

    wasdDirection [ CharA ]               --> West

    wasdDirection [ CharW, CharD ]        --> NorthEast

    wasdDirection [ CharA, CharS, CharD ] --> South

-}
wasdDirection : List Key -> Direction
wasdDirection =
    arrowsToDir << wasd


arrowsToDir : Arrows -> Direction
arrowsToDir { x, y } =
    case ( x, y ) of
        ( 0, 1 ) ->
            North

        ( 1, 1 ) ->
            NorthEast

        ( 1, 0 ) ->
            East

        ( 1, -1 ) ->
            SouthEast

        ( 0, -1 ) ->
            South

        ( -1, -1 ) ->
            SouthWest

        ( -1, 0 ) ->
            West

        ( -1, 1 ) ->
            NorthWest

        _ ->
            NoDirection


{-| Convert a key code into a `Key`.

    fromCode Chrome 13  --> Enter
    fromCode Chrome 173 --> VolumeMute
    fromCode FireFox 173 --> HyphenMinus

-}
fromCode : Browser -> KeyCode -> Key
fromCode browser =
    case browser of
        FireFox ->
            fromCodeBasic fireFoxCodeDict

        _ ->
            fromCodeBasic notFireFoxCodeDict


fromCodeBasic : Dict KeyCode Key -> KeyCode -> Key
fromCodeBasic dict keyCode =
    dict
        |> Dict.get keyCode
        |> Maybe.withDefault Other


{-| Convert a `Key` into a key code.

    toCode Chrome Enter  --> 13
    toCode Chrome Equals --> 187
    toCode FireFox Equals --> 61

-}
toCode : Browser -> Key -> KeyCode
toCode browser =
    case browser of
        FireFox ->
            toCodeBasic fireFoxCodeBook

        _ ->
            toCodeBasic notFireFoxCodeBook


toCodeBasic : List ( Int, Key ) -> Key -> KeyCode
toCodeBasic codeBook key =
    codeBook
        |> List.filter ((==) key << Tuple.second)
        |> List.map Tuple.first
        |> List.head
        |> Maybe.withDefault 0


{-| A `Json.Decoder` for grabbing `event.keyCode` and turning it into a `Key`

    import Json.Decode as Json

    onKey : (Key -> msg) -> Attribute msg
    onKey tagger =
        on "keydown" (Json.map tagger (targetKey Chrome))

-}
targetKey : Browser -> Json.Decoder Key
targetKey browser =
    Json.map (fromCode browser) (Json.field "keyCode" Json.int)


boolToInt : Bool -> Int
boolToInt bool =
    if bool then
        1
    else
        0


{-| These are all the keys that have names in `Keyboard.Extra`.
-}
type Key
    = Cancel
    | Help
    | BackSpace
    | Tab
    | Clear
    | Enter
    | Shift
    | Control
    | Alt
    | Pause
    | CapsLock
    | Escape
    | Convert
    | NonConvert
    | Accept
    | ModeChange
    | Space
    | PageUp
    | PageDown
    | End
    | Home
    | ArrowLeft
    | ArrowUp
    | ArrowRight
    | ArrowDown
    | Select
    | Print
    | Execute
    | PrintScreen
    | Insert
    | Delete
    | Number0
    | Number1
    | Number2
    | Number3
    | Number4
    | Number5
    | Number6
    | Number7
    | Number8
    | Number9
    | Colon
    | Semicolon
    | LessThan
    | Equals
    | GreaterThan
    | QuestionMark
    | At
    | CharA
    | CharB
    | CharC
    | CharD
    | CharE
    | CharF
    | CharG
    | CharH
    | CharI
    | CharJ
    | CharK
    | CharL
    | CharM
    | CharN
    | CharO
    | CharP
    | CharQ
    | CharR
    | CharS
    | CharT
    | CharU
    | CharV
    | CharW
    | CharX
    | CharY
    | CharZ
    | Super
    | ContextMenu
    | Sleep
    | Numpad0
    | Numpad1
    | Numpad2
    | Numpad3
    | Numpad4
    | Numpad5
    | Numpad6
    | Numpad7
    | Numpad8
    | Numpad9
    | Multiply
    | Add
    | Separator
    | Subtract
    | Decimal
    | Divide
    | F1
    | F2
    | F3
    | F4
    | F5
    | F6
    | F7
    | F8
    | F9
    | F10
    | F11
    | F12
    | F13
    | F14
    | F15
    | F16
    | F17
    | F18
    | F19
    | F20
    | F21
    | F22
    | F23
    | F24
    | NumLock
    | ScrollLock
    | Circumflex
    | Exclamation
    | DoubleQuote
    | Hash
    | Dollar
    | Percent
    | Ampersand
    | Underscore
    | OpenParen
    | CloseParen
    | Asterisk
    | Plus
    | Pipe
    | HyphenMinus
    | OpenCurlyBracket
    | CloseCurlyBracket
    | Tilde
    | VolumeMute
    | VolumeDown
    | VolumeUp
    | Comma
    | Minus
    | Period
    | Slash
    | BackQuote
    | OpenBracket
    | BackSlash
    | CloseBracket
    | Quote
    | Meta
    | Altgr
    | Other


fireFoxCodeDict : Dict KeyCode Key
fireFoxCodeDict =
    Dict.fromList fireFoxCodeBook


notFireFoxCodeDict : Dict KeyCode Key
notFireFoxCodeDict =
    Dict.fromList notFireFoxCodeBook


notFireFoxCodeBook : List ( Int, Key )
notFireFoxCodeBook =
    [ ( 173, VolumeMute )
    , ( 174, VolumeDown )
    , ( 175, VolumeUp )
    , ( 186, Semicolon )
    , ( 187, Equals )
    , ( 189, HyphenMinus )
    ]
        ++ basicCodeBook


fireFoxCodeBook : List ( Int, Key )
fireFoxCodeBook =
    [ ( 181, VolumeMute )
    , ( 182, VolumeDown )
    , ( 183, VolumeUp )
    , ( 59, Semicolon )
    , ( 61, Equals )
    , ( 173, HyphenMinus )
    ]
        ++ basicCodeBook


basicCodeBook : List ( KeyCode, Key )
basicCodeBook =
    [ ( 3, Cancel )
    , ( 6, Help )
    , ( 8, BackSpace )
    , ( 9, Tab )
    , ( 12, Clear )
    , ( 13, Enter )
    , ( 16, Shift )
    , ( 17, Control )
    , ( 18, Alt )
    , ( 19, Pause )
    , ( 20, CapsLock )
    , ( 27, Escape )
    , ( 28, Convert )
    , ( 29, NonConvert )
    , ( 30, Accept )
    , ( 31, ModeChange )
    , ( 32, Space )
    , ( 33, PageUp )
    , ( 34, PageDown )
    , ( 35, End )
    , ( 36, Home )
    , ( 37, ArrowLeft )
    , ( 38, ArrowUp )
    , ( 39, ArrowRight )
    , ( 40, ArrowDown )
    , ( 41, Select )
    , ( 42, Print )
    , ( 43, Execute )
    , ( 44, PrintScreen )
    , ( 45, Insert )
    , ( 46, Delete )
    , ( 48, Number0 )
    , ( 49, Number1 )
    , ( 50, Number2 )
    , ( 51, Number3 )
    , ( 52, Number4 )
    , ( 53, Number5 )
    , ( 54, Number6 )
    , ( 55, Number7 )
    , ( 56, Number8 )
    , ( 57, Number9 )
    , ( 58, Colon )
    , ( 60, LessThan )
    , ( 62, GreaterThan )
    , ( 63, QuestionMark )
    , ( 64, At )
    , ( 65, CharA )
    , ( 66, CharB )
    , ( 67, CharC )
    , ( 68, CharD )
    , ( 69, CharE )
    , ( 70, CharF )
    , ( 71, CharG )
    , ( 72, CharH )
    , ( 73, CharI )
    , ( 74, CharJ )
    , ( 75, CharK )
    , ( 76, CharL )
    , ( 77, CharM )
    , ( 78, CharN )
    , ( 79, CharO )
    , ( 80, CharP )
    , ( 81, CharQ )
    , ( 82, CharR )
    , ( 83, CharS )
    , ( 84, CharT )
    , ( 85, CharU )
    , ( 86, CharV )
    , ( 87, CharW )
    , ( 88, CharX )
    , ( 89, CharY )
    , ( 90, CharZ )
    , ( 91, Super )
    , ( 93, ContextMenu )
    , ( 95, Sleep )
    , ( 96, Numpad0 )
    , ( 97, Numpad1 )
    , ( 98, Numpad2 )
    , ( 99, Numpad3 )
    , ( 100, Numpad4 )
    , ( 101, Numpad5 )
    , ( 102, Numpad6 )
    , ( 103, Numpad7 )
    , ( 104, Numpad8 )
    , ( 105, Numpad9 )
    , ( 106, Multiply )
    , ( 107, Add )
    , ( 108, Separator )
    , ( 109, Subtract )
    , ( 110, Decimal )
    , ( 111, Divide )
    , ( 112, F1 )
    , ( 113, F2 )
    , ( 114, F3 )
    , ( 115, F4 )
    , ( 116, F5 )
    , ( 117, F6 )
    , ( 118, F7 )
    , ( 119, F8 )
    , ( 120, F9 )
    , ( 121, F10 )
    , ( 122, F11 )
    , ( 123, F12 )
    , ( 124, F13 )
    , ( 125, F14 )
    , ( 126, F15 )
    , ( 127, F16 )
    , ( 128, F17 )
    , ( 129, F18 )
    , ( 130, F19 )
    , ( 131, F20 )
    , ( 132, F21 )
    , ( 133, F22 )
    , ( 134, F23 )
    , ( 135, F24 )
    , ( 144, NumLock )
    , ( 145, ScrollLock )
    , ( 160, Circumflex )
    , ( 161, Exclamation )
    , ( 162, DoubleQuote )
    , ( 163, Hash )
    , ( 164, Dollar )
    , ( 165, Percent )
    , ( 166, Ampersand )
    , ( 167, Underscore )
    , ( 168, OpenParen )
    , ( 169, CloseParen )
    , ( 170, Asterisk )
    , ( 171, Plus )
    , ( 172, Pipe )
    , ( 174, OpenCurlyBracket )
    , ( 175, CloseCurlyBracket )
    , ( 176, Tilde )
    , ( 188, Comma )
    , ( 189, Minus )
    , ( 190, Period )
    , ( 191, Slash )
    , ( 192, BackQuote )
    , ( 219, OpenBracket )
    , ( 220, BackSlash )
    , ( 221, CloseBracket )
    , ( 222, Quote )
    , ( 224, Meta )
    , ( 225, Altgr )
    ]