-- https://twitter.com/unsoundscapes/status/1132405048338780161 -- https://ellie-app.com/5DBFLkbVVCwa1 module Main exposing (main) import Browser import Html exposing (Html, button, div, text) import Html.Attributes exposing (class) import Html.Events exposing (on, onClick, onFocus) import Json.Decode as Decode exposing (Decoder, Value) type alias Model = { expand : Bool, justFocused : Bool } initialModel : Model initialModel = { expand = False, justFocused = False } type Msg = Toggle | Collapse | Focus update : Msg -> Model -> Model update msg model = case msg of Collapse -> { expand = False, justFocused = False } Focus -> if not model.expand then { justFocused = True, expand = True } else model Toggle -> if not model.justFocused then { model | expand = not model.expand } else { model | justFocused = False } view : Model -> Html Msg view { expand } = div [ class "dropdown", on "focusout" onFocusout ] [ button [ onClick Toggle , onFocus Focus ] [ text "toggle" ] , div [] (if expand then [ div [] [ button [] [ text "item1" ] ] , div [] [ button [] [ text "item2" ] ] , div [] [ button [] [ text "item3" ] ] , div [] [ button [] [ text "item4" ] ] ] else [] ) ] main : Program () Model Msg main = Browser.sandbox { init = initialModel , view = view , update = update } onFocusout : Decoder Msg onFocusout = Decode.field "relatedTarget" outsideDropdown |> Decode.andThen (\outside -> if outside then Decode.succeed Collapse else Decode.fail "inside the dropdown" ) outsideDropdown : Decoder Bool outsideDropdown = Decode.oneOf [ Decode.andThen (\id -> if id == "dropdown" then Decode.succeed False else Decode.fail "continue" ) (Decode.field "className" Decode.string) , Decode.lazy (\_ -> Decode.field "parentNode" outsideDropdown) , Decode.succeed True ]