mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 17:47:03 +00:00
ui: Implement modal focus trap
This commit is contained in:
@@ -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
|
||||
|
||||
58
ui/src/Modal.elm
Normal file
58
ui/src/Modal.elm
Normal 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"
|
||||
@@ -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
|
||||
|
||||
@@ -223,6 +223,10 @@ h3 {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.modal:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/** BUTTONS */
|
||||
|
||||
.button-bar {
|
||||
|
||||
Reference in New Issue
Block a user