1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 09:37:02 +00:00

ui: Implement modal focus trap

This commit is contained in:
James Hillyerd
2020-03-29 16:06:30 -07:00
parent cabbdacb89
commit 4a90b37815
4 changed files with 89 additions and 17 deletions

View File

@@ -37,6 +37,7 @@ import Html.Attributes
, value , value
) )
import Html.Events as Events import Html.Events as Events
import Modal
import Route exposing (Route) import Route exposing (Route)
@@ -79,6 +80,8 @@ reset model =
type Msg type Msg
= ClearFlash = ClearFlash
| ModalFocused Modal.Msg
| ModalUnfocused
| OnMailboxNameInput String | OnMailboxNameInput String
| OpenMailbox | OpenMailbox
| ShowRecent Bool | ShowRecent Bool
@@ -94,6 +97,15 @@ update msg model session =
, Cmd.none , Cmd.none
) )
ModalFocused message ->
( model
, Modal.updateSession message session
, Cmd.none
)
ModalUnfocused ->
( model, session, Modal.resetFocusCmd (ModalFocused >> model.mapMsg) )
OnMailboxNameInput name -> OnMailboxNameInput name ->
( { model | mailboxName = name } ( { model | mailboxName = name }
, session , session
@@ -165,7 +177,7 @@ frame { model, session, activePage, activeMailbox, modal, content } =
] ]
] ]
, div [ class "navbar-bg" ] [ text "" ] , div [ class "navbar-bg" ] [ text "" ]
, frameModal modal , Modal.view (ModalUnfocused |> model.mapMsg) modal
, div [ class "page" ] (errorFlash model session.flash :: content) , div [ class "page" ] (errorFlash model session.flash :: content)
, footer [] , footer []
[ div [ class "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 msg -> Maybe Session.Flash -> Html msg
errorFlash model maybeFlash = errorFlash model maybeFlash =
let let

58
ui/src/Modal.elm Normal file
View File

@@ -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"

View File

@@ -1,6 +1,7 @@
module Page.Mailbox exposing (Model, Msg, init, load, subscriptions, update, view) module Page.Mailbox exposing (Model, Msg, init, load, subscriptions, update, view)
import Api import Api
import Browser.Dom as Dom
import Data.Message as Message exposing (Message) import Data.Message as Message exposing (Message)
import Data.MessageHeader exposing (MessageHeader) import Data.MessageHeader exposing (MessageHeader)
import Data.Session as Session exposing (Session) import Data.Session as Session exposing (Session)
@@ -51,6 +52,7 @@ import Html.Events as Events
import HttpUtil import HttpUtil
import Json.Decode as D import Json.Decode as D
import Json.Encode as E import Json.Encode as E
import Modal
import Route import Route
import Task import Task
import Time exposing (Posix) import Time exposing (Posix)
@@ -174,6 +176,7 @@ type Msg
| PurgedMailbox (Result HttpUtil.Error ()) | PurgedMailbox (Result HttpUtil.Error ())
| OnSearchInput String | OnSearchInput String
| Tick Posix | Tick Posix
| ModalFocused Modal.Msg
update : Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )
@@ -262,6 +265,11 @@ update msg model =
MessageBody bodyMode -> MessageBody bodyMode ->
( { model | bodyMode = bodyMode }, Cmd.none ) ( { model | bodyMode = bodyMode }, Cmd.none )
ModalFocused message ->
( { model | session = Modal.updateSession message model.session }
, Cmd.none
)
OnSearchInput searchInput -> OnSearchInput searchInput ->
updateSearchInput model searchInput updateSearchInput model searchInput
@@ -293,13 +301,13 @@ update msg model =
( model, Cmd.none ) ( model, Cmd.none )
PurgeMailboxPrompt -> PurgeMailboxPrompt ->
( { model | promptPurge = True }, Cmd.none ) ( { model | promptPurge = True }, Modal.resetFocusCmd ModalFocused )
PurgeMailboxCanceled -> PurgeMailboxCanceled ->
( { model | promptPurge = False }, Cmd.none ) ( { model | promptPurge = False }, Cmd.none )
PurgeMailboxConfirmed -> PurgeMailboxConfirmed ->
updatePurge model updateTriggerPurge model
PurgedMailbox (Ok _) -> PurgedMailbox (Ok _) ->
( model, Cmd.none ) ( model, Cmd.none )
@@ -358,8 +366,10 @@ updateMessageResult model message =
) )
updatePurge : Model -> ( Model, Cmd Msg ) {-| Updates model and triggers commands to purge this mailbox.
updatePurge model = -}
updateTriggerPurge : Model -> ( Model, Cmd Msg )
updateTriggerPurge model =
let let
cmd = cmd =
Cmd.batch Cmd.batch

View File

@@ -223,6 +223,10 @@ h3 {
padding: 10px !important; padding: 10px !important;
} }
.modal:focus {
outline: none;
}
/** BUTTONS */ /** BUTTONS */
.button-bar { .button-bar {