diff --git a/ui/src/Layout.elm b/ui/src/Layout.elm index cb36952..3b0196f 100644 --- a/ui/src/Layout.elm +++ b/ui/src/Layout.elm @@ -37,6 +37,7 @@ import Html.Attributes , value ) import Html.Events as Events +import Modal import Route exposing (Route) @@ -79,6 +80,8 @@ reset model = type Msg = ClearFlash + | ModalFocused Modal.Msg + | ModalUnfocused | OnMailboxNameInput String | OpenMailbox | ShowRecent Bool @@ -94,6 +97,15 @@ update msg model session = , Cmd.none ) + ModalFocused message -> + ( model + , Modal.updateSession message session + , Cmd.none + ) + + ModalUnfocused -> + ( model, session, Modal.resetFocusCmd (ModalFocused >> model.mapMsg) ) + OnMailboxNameInput name -> ( { model | mailboxName = name } , session @@ -165,7 +177,7 @@ frame { model, session, activePage, activeMailbox, modal, content } = ] ] , div [ class "navbar-bg" ] [ text "" ] - , frameModal modal + , Modal.view (ModalUnfocused |> model.mapMsg) modal , div [ class "page" ] (errorFlash model session.flash :: content) , footer [] [ div [ class "footer" ] @@ -178,18 +190,6 @@ frame { model, session, activePage, activeMailbox, modal, content } = ] -frameModal : Maybe (Html msg) -> Html msg -frameModal maybeModal = - case maybeModal of - Just modal -> - div [ class "modal-mask" ] - [ div [ class "modal well" ] [ modal ] - ] - - Nothing -> - text "" - - errorFlash : Model msg -> Maybe Session.Flash -> Html msg errorFlash model maybeFlash = let diff --git a/ui/src/Modal.elm b/ui/src/Modal.elm new file mode 100644 index 0000000..7ebbcec --- /dev/null +++ b/ui/src/Modal.elm @@ -0,0 +1,58 @@ +module Modal exposing (Msg, resetFocusCmd, updateSession, view) + +import Browser.Dom as Dom +import Data.Session as Session exposing (Session) +import Html exposing (Html, div, span, text) +import Html.Attributes exposing (class, id, tabindex) +import Html.Events exposing (onFocus) +import Task + + +type alias Msg = + Result Dom.Error () + + +{-| Creates a command to focus the modal dialog. +-} +resetFocusCmd : (Msg -> msg) -> Cmd msg +resetFocusCmd resultMsg = + Task.attempt resultMsg (Dom.focus domId) + + +{-| Updates a Session with an error Flash if the resetFocusCmd failed. +-} +updateSession : Msg -> Session -> Session +updateSession result session = + case result of + Ok () -> + session + + Err (Dom.NotFound missingDomId) -> + let + flash = + { title = "DOM element not found" + , table = [ ( "Element ID", missingDomId ) ] + } + in + Session.showFlash flash session + + +view : msg -> Maybe (Html msg) -> Html msg +view unfocusedMsg maybeModal = + case maybeModal of + Just modal -> + div [ class "modal-mask" ] + [ span [ onFocus unfocusedMsg, tabindex 0 ] [] + , div [ id domId, class "modal well", tabindex -1 ] [ modal ] + , span [ onFocus unfocusedMsg, tabindex 0 ] [] + ] + + Nothing -> + text "" + + +{-| DOM ID of the modal dialog. +-} +domId : String +domId = + "modal-dialog" diff --git a/ui/src/Page/Mailbox.elm b/ui/src/Page/Mailbox.elm index 045cb87..038f327 100644 --- a/ui/src/Page/Mailbox.elm +++ b/ui/src/Page/Mailbox.elm @@ -1,6 +1,7 @@ module Page.Mailbox exposing (Model, Msg, init, load, subscriptions, update, view) import Api +import Browser.Dom as Dom import Data.Message as Message exposing (Message) import Data.MessageHeader exposing (MessageHeader) import Data.Session as Session exposing (Session) @@ -51,6 +52,7 @@ import Html.Events as Events import HttpUtil import Json.Decode as D import Json.Encode as E +import Modal import Route import Task import Time exposing (Posix) @@ -174,6 +176,7 @@ type Msg | PurgedMailbox (Result HttpUtil.Error ()) | OnSearchInput String | Tick Posix + | ModalFocused Modal.Msg update : Msg -> Model -> ( Model, Cmd Msg ) @@ -262,6 +265,11 @@ update msg model = MessageBody bodyMode -> ( { model | bodyMode = bodyMode }, Cmd.none ) + ModalFocused message -> + ( { model | session = Modal.updateSession message model.session } + , Cmd.none + ) + OnSearchInput searchInput -> updateSearchInput model searchInput @@ -293,13 +301,13 @@ update msg model = ( model, Cmd.none ) PurgeMailboxPrompt -> - ( { model | promptPurge = True }, Cmd.none ) + ( { model | promptPurge = True }, Modal.resetFocusCmd ModalFocused ) PurgeMailboxCanceled -> ( { model | promptPurge = False }, Cmd.none ) PurgeMailboxConfirmed -> - updatePurge model + updateTriggerPurge model PurgedMailbox (Ok _) -> ( model, Cmd.none ) @@ -358,8 +366,10 @@ updateMessageResult model message = ) -updatePurge : Model -> ( Model, Cmd Msg ) -updatePurge model = +{-| Updates model and triggers commands to purge this mailbox. +-} +updateTriggerPurge : Model -> ( Model, Cmd Msg ) +updateTriggerPurge model = let cmd = Cmd.batch diff --git a/ui/src/main.css b/ui/src/main.css index 649770d..3e2d826 100644 --- a/ui/src/main.css +++ b/ui/src/main.css @@ -223,6 +223,10 @@ h3 { padding: 10px !important; } +.modal:focus { + outline: none; +} + /** BUTTONS */ .button-bar {