├── .gitignore ├── screenshot.png ├── tests ├── Main.elm ├── elm-package.json └── Tests.elm ├── elm-package.json ├── example ├── elm-package.json ├── style.css ├── Minimal.elm └── Main.elm ├── src ├── Types.elm ├── Internal.elm └── Toast.elm ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | index.html 3 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iosphere/elm-toast/HEAD/screenshot.png -------------------------------------------------------------------------------- /tests/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Tests 4 | import Test.Runner.Html exposing (run) 5 | 6 | 7 | main : Test.Runner.Html.TestProgram 8 | main = 9 | run Tests.all 10 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Display toasts and other temporary UI notifications", 4 | "repository": "https://github.com/iosphere/elm-toast.git", 5 | "license": "BSD 3-Clause", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "Toast" 11 | ], 12 | "dependencies": { 13 | "elm-lang/core": "5.0.0 <= v < 6.0.0" 14 | }, 15 | "elm-version": "0.18.0 <= v < 0.19.0" 16 | } 17 | -------------------------------------------------------------------------------- /example/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Example for the elm-toast package", 4 | "repository": "https://github.com/iosphere/elm-toast.git", 5 | "license": "BSD 3-Clause", 6 | "source-directories": [ 7 | "", 8 | "../src" 9 | ], 10 | "native-modules": true, 11 | "exposed-modules": [], 12 | "dependencies": { 13 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 14 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 15 | }, 16 | "elm-version": "0.18.0 <= v < 0.19.0" 17 | } 18 | -------------------------------------------------------------------------------- /src/Types.elm: -------------------------------------------------------------------------------- 1 | module Types exposing (..) 2 | 3 | import Time exposing (Time) 4 | 5 | 6 | type Toast a 7 | = Toast (InternalToast a) 8 | 9 | 10 | type Notification a 11 | = Notification (InternalNotification a) 12 | 13 | 14 | type alias InternalConfig = 15 | { hideTransitionDelay : Time 16 | } 17 | 18 | 19 | type alias InternalToast a = 20 | { config : InternalConfig 21 | , notifications : List (Notification a) 22 | } 23 | 24 | 25 | type alias InternalNotification a = 26 | { startTime : Time 27 | , expirationTime : Time 28 | , message : a 29 | } 30 | -------------------------------------------------------------------------------- /tests/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Tests for iosphere/elm-toast", 4 | "repository": "https://github.com/iosphere/elm-toast.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "../src" 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "elm-community/elm-test": "3.1.0 <= v < 4.0.0", 13 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 14 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 15 | "rtfeldman/html-test-runner": "2.0.0 <= v < 3.0.0" 16 | }, 17 | "elm-version": "0.18.0 <= v < 0.19.0" 18 | } 19 | -------------------------------------------------------------------------------- /example/style.css: -------------------------------------------------------------------------------- 1 | .notifications-list { 2 | list-style-type: none; 3 | margin-left: 0; 4 | padding-left: 0; 5 | max-width: 500px; 6 | } 7 | 8 | .notification { 9 | position: relative; 10 | overflow: hidden; 11 | height: 40px; 12 | 13 | background-color: green; 14 | color: white; 15 | 16 | transition: height 0.75s ease, opacity 1s ease; 17 | margin-top: 1px; 18 | } 19 | 20 | .notification--important { 21 | font-weight: bold; 22 | background-color: red; 23 | } 24 | 25 | .notification--hidden { 26 | opacity: 0; 27 | height: 0; 28 | } 29 | 30 | .notification__content { 31 | padding: 10px; 32 | } 33 | -------------------------------------------------------------------------------- /src/Internal.elm: -------------------------------------------------------------------------------- 1 | module Internal exposing (..) 2 | 3 | import Types exposing (..) 4 | 5 | 6 | updateNotifications : Toast a -> List (Notification a) -> Toast a 7 | updateNotifications (Toast toast) list = 8 | Toast { toast | notifications = list } 9 | 10 | 11 | config : Toast a -> InternalConfig 12 | config (Toast toast) = 13 | toast.config 14 | 15 | 16 | listAllNotifications : Toast a -> List (Notification a) 17 | listAllNotifications (Toast toast) = 18 | toast.notifications 19 | 20 | 21 | member : Notification a -> Toast a -> Bool 22 | member notification (Toast internalToast) = 23 | List.member notification internalToast.notifications 24 | 25 | 26 | sanityCheck : Notification a -> Bool 27 | sanityCheck (Notification notification) = 28 | notification.startTime < notification.expirationTime 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, iosphere GmbH 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /tests/Tests.elm: -------------------------------------------------------------------------------- 1 | module Tests exposing (all) 2 | 3 | import Expect 4 | import Test exposing (describe, test) 5 | import Toast 6 | 7 | 8 | all : Test.Test 9 | all = 10 | describe "Toast" 11 | [ testActiveNotification, testViews ] 12 | 13 | 14 | notificationValid : Toast.Notification String 15 | notificationValid = 16 | Toast.createNotification "Valid" 110 17 | 18 | 19 | notificationValidNow : Toast.Notification String 20 | notificationValidNow = 21 | Toast.createFutureNotification 75 "Now" 100 22 | 23 | 24 | notificationFuture : Toast.Notification String 25 | notificationFuture = 26 | Toast.createFutureNotification 200 "Future" 300 27 | 28 | 29 | notificationPast : Toast.Notification String 30 | notificationPast = 31 | Toast.createNotification "Past" 50 32 | 33 | 34 | toast : Float -> Toast.Toast String 35 | toast delay = 36 | Toast.initWithTransitionDelay delay 37 | |> Toast.addNotification notificationValid 38 | |> Toast.addNotification notificationValidNow 39 | |> Toast.addNotification notificationFuture 40 | |> Toast.addNotification notificationPast 41 | 42 | 43 | testActiveNotification : Test.Test 44 | testActiveNotification = 45 | test "Test validNotifications" <| 46 | \() -> 47 | Toast.listActiveNotifications (toast 0) 75 48 | |> Expect.equal [ notificationValid, notificationValidNow ] 49 | 50 | 51 | viewFunc : Toast.NotificationState -> String -> String 52 | viewFunc state string = 53 | string ++ "|" ++ toString state 54 | 55 | 56 | testViews : Test.Test 57 | testViews = 58 | test "Test view" <| 59 | \() -> 60 | Toast.views (toast 5) 100 viewFunc 61 | |> Expect.equal [ "Valid|Visible", "Now|Hiding" ] 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elm-toast 2 | 3 | A view agnostic way to handle toasts and other temporary notifications. 4 | 5 | A typical application would be to display temporary notifications as Html views 6 | in your elm app. This module takes care of updating the notifications and let's 7 | you decide what content should be included in your notifications and how these 8 | should be represented in your view. 9 | 10 | A [demo](https://ellie-app.com/3rkbwjFF6KBa1/0) can be found here: https://ellie-app.com/3rkbwjFF6KBa1/0 11 | 12 | # Screenshot 13 | 14 | This is a screenshot of the [example app](example/Main.elm). The design is part 15 | of your app's responsibility. The concept of this package is that it does not 16 | impose any view type or design on your app (it does not even require views 17 | to be Html). 18 | 19 | ![Screenshot of elm-toast example](https://github.com/iosphere/elm-toast/raw/master/screenshot.png) 20 | 21 | # Minimal implementation 22 | 23 | Your app should contain the current time and the Toast somewhere in its model. 24 | 25 | type alias Model = 26 | { time : Time 27 | , toast : Toast String 28 | } 29 | 30 | You will then need to subscribe to time updates. The frequency of the updates 31 | will determine the update frequency of the toast and how accurately start times, 32 | expiration times and transition delays are respected: 33 | 34 | type Msg 35 | = Tick Time 36 | | PostNotification String 37 | 38 | 39 | subscriptions : Model -> Sub Msg 40 | subscriptions model = 41 | Time.every (Time.second * 1) Tick 42 | 43 | In your update method you will need to update the time and update the toast in 44 | your model on every `Tick` message. In addition you will probably want to add 45 | any posted notification to your model's `Toast`: 46 | 47 | pureUpdate : Msg -> Model -> Model 48 | pureUpdate msg model = 49 | case msg of 50 | Tick newTime -> 51 | let 52 | newToast = 53 | Toast.updateTimestamp newTime model.toast 54 | in 55 | updateTime newTime model 56 | |> updateToast newToast 57 | 58 | PostNotification notification -> 59 | let 60 | newToastNotification = 61 | Toast.createNotification notification (model.time + Time.second * 3) 62 | 63 | newToast = 64 | Toast.addNotification newToastNotification model.toast 65 | in 66 | updateToast newToast model 67 | 68 | 69 | updateTime : Time -> Model -> Model 70 | updateTime time model = 71 | { model | time = time } 72 | 73 | 74 | updateToast : Toast String -> Model -> Model 75 | updateToast toast model = 76 | { model | toast = toast } 77 | -------------------------------------------------------------------------------- /example/Minimal.elm: -------------------------------------------------------------------------------- 1 | module Minimal exposing (..) 2 | 3 | import Html exposing (Html) 4 | import Html.Attributes exposing (class, classList) 5 | import Html.Events exposing (onClick) 6 | import Time exposing (Time) 7 | import Toast exposing (Toast) 8 | 9 | 10 | -- MODEL 11 | 12 | 13 | type alias Model = 14 | { time : Time 15 | , toast : Toast String 16 | } 17 | 18 | 19 | init : ( Model, Cmd Msg ) 20 | init = 21 | ( initModel, Cmd.none ) 22 | 23 | 24 | initModel : Model 25 | initModel = 26 | { -- Ideally your implementation should start with the correct time. 27 | -- To avoid flags we start with 0 and wait for the first update. 28 | time = 0 29 | , -- initialize toast 30 | toast = Toast.init 31 | } 32 | 33 | 34 | 35 | -- UPDATE 36 | 37 | 38 | type Msg 39 | = Tick Time 40 | | Postnotification String 41 | 42 | 43 | update : Msg -> Model -> ( Model, Cmd Msg ) 44 | update msg model = 45 | pureUpdate msg model ! [] 46 | 47 | 48 | pureUpdate : Msg -> Model -> Model 49 | pureUpdate msg model = 50 | case msg of 51 | Tick newTime -> 52 | let 53 | newToast = 54 | Toast.updateTimestamp newTime model.toast 55 | in 56 | updateTime newTime model 57 | |> updateToast newToast 58 | 59 | Postnotification notification -> 60 | let 61 | newToastNotification = 62 | Toast.createNotification notification (model.time + Time.second * 3) 63 | 64 | newToast = 65 | Toast.addNotification newToastNotification model.toast 66 | in 67 | updateToast newToast model 68 | 69 | 70 | updateToast : Toast String -> Model -> Model 71 | updateToast toast model = 72 | { model | toast = toast } 73 | 74 | 75 | updateTime : Time -> Model -> Model 76 | updateTime time model = 77 | { model | time = time } 78 | 79 | 80 | 81 | -- SUBSCRIPTIONS 82 | 83 | 84 | subscriptions : Model -> Sub Msg 85 | subscriptions model = 86 | Time.every (Time.second * 1) Tick 87 | 88 | 89 | 90 | -- VIEW 91 | 92 | 93 | view : Model -> Html Msg 94 | view model = 95 | Html.div [] 96 | (boilerPlateViews 97 | ++ [ notificationsView model ] 98 | ) 99 | 100 | 101 | notificationsView : Model -> Html Msg 102 | notificationsView model = 103 | Html.ul [ class "notifications-list" ] (Toast.views model.toast model.time notificationView) 104 | 105 | 106 | notificationView : Toast.NotificationState -> String -> Html Msg 107 | notificationView state notification = 108 | Html.li [] [ Html.text notification ] 109 | 110 | 111 | 112 | -- BOILERPLATE 113 | 114 | 115 | main : Program Never Model Msg 116 | main = 117 | Html.program 118 | { init = init 119 | , view = view 120 | , update = update 121 | , subscriptions = subscriptions 122 | } 123 | 124 | 125 | boilerPlateViews : List (Html Msg) 126 | boilerPlateViews = 127 | [ Html.button [ onClick (Postnotification "My Message") ] [ Html.text "Post simple notification" ] 128 | , Html.h1 [] [ Html.text "Notifications" ] 129 | ] 130 | -------------------------------------------------------------------------------- /example/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Html exposing (Html) 4 | import Html.Attributes exposing (class, classList) 5 | import Html.Events exposing (onClick) 6 | import Html.Keyed 7 | import Time exposing (Time) 8 | import Toast exposing (Toast) 9 | 10 | 11 | -- MODEL 12 | 13 | 14 | type alias Model = 15 | { time : Time 16 | , toast : Toast AppNotification 17 | } 18 | 19 | 20 | init : ( Model, Cmd Msg ) 21 | init = 22 | ( initModel, Cmd.none ) 23 | 24 | 25 | initModel : Model 26 | initModel = 27 | { -- Ideally your implementation should start with the correct time. 28 | -- To avoid flags we start with 0 and wait for the first update. 29 | time = 0 30 | , -- initialize toast with a delay of 1.5 seconds. This is the duration 31 | -- for which notifications will remain "active" and in the DOM after their 32 | -- expiration. Their state will be `Toast.Hiding` and will give the css 33 | -- transition enough time to complete before removing the DOM element. 34 | toast = Toast.initWithTransitionDelay (Time.second * 1.5) 35 | } 36 | 37 | 38 | type alias AppNotification = 39 | { message : String 40 | , important : Bool 41 | } 42 | 43 | 44 | 45 | -- UPDATE 46 | 47 | 48 | type Msg 49 | = Tick Time 50 | | Postnotification AppNotification 51 | 52 | 53 | update : Msg -> Model -> ( Model, Cmd Msg ) 54 | update msg model = 55 | pureUpdate msg model ! [] 56 | 57 | 58 | pureUpdate : Msg -> Model -> Model 59 | pureUpdate msg model = 60 | case msg of 61 | Tick newTime -> 62 | let 63 | newToast = 64 | Toast.updateTimestamp newTime model.toast 65 | in 66 | updateTime newTime model 67 | |> updateToast newToast 68 | 69 | Postnotification notification -> 70 | let 71 | startTime = 72 | -- Show immediately 73 | 0 74 | 75 | expirationTime = 76 | (model.time + Time.second * 3) 77 | 78 | newToastNotification = 79 | Toast.createFutureNotification startTime notification expirationTime 80 | 81 | newToast = 82 | Toast.addNotification newToastNotification model.toast 83 | in 84 | updateToast newToast model 85 | 86 | 87 | updateToast : Toast AppNotification -> Model -> Model 88 | updateToast toast model = 89 | { model | toast = toast } 90 | 91 | 92 | updateTime : Time -> Model -> Model 93 | updateTime time model = 94 | { model | time = time } 95 | 96 | 97 | 98 | -- SUBSCRIPTIONS 99 | 100 | 101 | subscriptions : Model -> Sub Msg 102 | subscriptions model = 103 | Time.every (Time.millisecond * 500) Tick 104 | 105 | 106 | 107 | -- VIEW 108 | 109 | 110 | view : Model -> Html Msg 111 | view model = 112 | let 113 | modelText = 114 | toString model 115 | |> Html.text 116 | in 117 | Html.div [] 118 | (boilerPlateView 119 | ++ [ notificationsView model ] 120 | ) 121 | 122 | 123 | notificationsView : Model -> Html Msg 124 | notificationsView model = 125 | Html.Keyed.ul [ class "notifications-list" ] (Toast.keyedViews model.toast model.time notificationView) 126 | 127 | 128 | notificationView : Toast.NotificationState -> AppNotification -> Html Msg 129 | notificationView state notification = 130 | Html.li 131 | [ classList 132 | [ ( "notification", True ) 133 | , ( "notification--important", notification.important ) 134 | , ( "notification--hidden", (state == Toast.Hiding) ) 135 | ] 136 | ] 137 | [ Html.div [ class "notification__content" ] [ Html.text (notification.message ++ " | " ++ toString state) ] ] 138 | 139 | 140 | 141 | -- BOILERPLATE 142 | 143 | 144 | main : Program Never Model Msg 145 | main = 146 | Html.program 147 | { init = init 148 | , view = view 149 | , update = update 150 | , subscriptions = subscriptions 151 | } 152 | 153 | 154 | boilerPlateView : List (Html Msg) 155 | boilerPlateView = 156 | [ Html.node "link" [ Html.Attributes.rel "stylesheet", Html.Attributes.href "style.css" ] [] 157 | , Html.button [ onClick (Postnotification (AppNotification "This is important" True)) ] [ Html.text "Post import notification" ] 158 | , Html.button [ onClick (Postnotification (AppNotification "Not so important" False)) ] [ Html.text "Post normal notification" ] 159 | , Html.h1 [] [ Html.text "Notifications" ] 160 | ] 161 | -------------------------------------------------------------------------------- /src/Toast.elm: -------------------------------------------------------------------------------- 1 | module Toast 2 | exposing 3 | ( Duration 4 | , Notification 5 | , NotificationState(..) 6 | , Timestamp 7 | , Toast 8 | , ViewFunction 9 | , addNotification 10 | , createFutureNotification 11 | , createNotification 12 | , init 13 | , initWithTransitionDelay 14 | , keyedViews 15 | , listActiveNotifications 16 | , updateTimestamp 17 | , views 18 | ) 19 | 20 | {-| A view agnostic way to handle toasts and other temporary notifications. 21 | 22 | A typical application would be to display temporary notifications as Html views 23 | in your elm app. This module takes care of updating the notifications and let's 24 | you decide what content should be included in your notifications and how these 25 | should be represented in your view. 26 | 27 | * [Minimal implementation](#minimal-implementation) 28 | * [Reference](#reference) 29 | * [Setup](#setup) 30 | * [Create, add and list notifications](#create-add-and-list-notifications) 31 | * [Update](#update) 32 | * [Create views](#create-views-from-the-current-toast) 33 | * [Types](#types) 34 | * [Opaque types](#opaque-types) 35 | 36 | # Minimal implementation 37 | 38 | Your app should contain the current time and the Toast somewhere in its model. 39 | 40 | type alias Model = 41 | { time : Time 42 | , toast : Toast String 43 | } 44 | 45 | You will then need to subscribe to time updates. The frequency of the updates 46 | will determine the update frequency of the toast and how accurately start times, 47 | expiration times and transition delays are respected: 48 | 49 | type Msg 50 | = Tick Time 51 | | PostNotification String 52 | 53 | 54 | subscriptions : Model -> Sub Msg 55 | subscriptions model = 56 | Time.every (Time.second * 1) Tick 57 | 58 | In your update method you will need to update the time and update the toast in 59 | your model on every `Tick` message. In addition you will probably want to add 60 | any posted notification to your model's `Toast`: 61 | 62 | pureUpdate : Msg -> Model -> Model 63 | pureUpdate msg model = 64 | case msg of 65 | Tick newTime -> 66 | let 67 | newToast = 68 | Toast.updateTimestamp newTime model.toast 69 | in 70 | updateTime newTime model 71 | |> updateToast newToast 72 | 73 | PostNotification notification -> 74 | let 75 | newToastNotification = 76 | Toast.createNotification notification (model.time + Time.second * 3) 77 | 78 | newToast = 79 | Toast.addNotification newToastNotification model.toast 80 | in 81 | updateToast newToast model 82 | 83 | 84 | updateTime : Time -> Model -> Model 85 | updateTime time model = 86 | { model | time = time } 87 | 88 | 89 | updateToast : Toast String -> Model -> Model 90 | updateToast toast model = 91 | { model | toast = toast } 92 | 93 | 94 | # Reference 95 | 96 | ## Setup 97 | 98 | You are responsible to keep the `Toast` somewhere in your app model. 99 | You will also need to wire up a `Time` subscription to regularly update it 100 | (see [Update](#update) and[Minimal implementation](#minimal-implementation)). 101 | 102 | @docs init, initWithTransitionDelay 103 | 104 | ## Create, add and list notifications 105 | 106 | @docs addNotification, createNotification, createFutureNotification, listActiveNotifications 107 | 108 | ## Update 109 | 110 | @docs updateTimestamp 111 | 112 | ## Create views from the current Toast 113 | 114 | @docs ViewFunction, views, keyedViews 115 | 116 | ## Types 117 | 118 | @docs NotificationState, Timestamp, Duration 119 | 120 | ## Opaque types 121 | 122 | @docs Toast, Notification 123 | 124 | -} 125 | 126 | import Time exposing (Time) 127 | import Types 128 | import Internal 129 | 130 | 131 | {-| Opaque type encapsulating all active notifications. Use `init` or 132 | `initWithTransitionDelay` to create a Toast. 133 | -} 134 | type alias Toast a = 135 | Types.Toast a 136 | 137 | 138 | {-| Type alias to identify `Time` parameters used as timestamps. 139 | -} 140 | type alias Timestamp = 141 | Time 142 | 143 | 144 | {-| Type alias to identify `Time` parameters used as duration. 145 | -} 146 | type alias Duration = 147 | Time 148 | 149 | 150 | {-| -} 151 | type alias Notification a = 152 | Types.Notification a 153 | 154 | 155 | {-| When using a Toast without delay the state of a notification handed to you 156 | will always be visible. Hidden notifications will never be made available to 157 | you. 158 | 159 | If you use a Toast with a transition delay, the state will be `Hiding` once the 160 | expiration time has been reached until the transition delay has passed. 161 | 162 | You can use this information in your views to set the view to hidden and apply 163 | a transition. 164 | -} 165 | type NotificationState 166 | = Visible 167 | | Hiding 168 | 169 | 170 | {-| Initialize a Toast without transition delay. 171 | 172 | Any notification will be removed from the list of active notifications as soon 173 | as the expiration time is reached. 174 | 175 | The `notificationType` will determine what values are made available to your 176 | view function. The rest of the documentation will simply refer to this type as 177 | `a`. 178 | -} 179 | init : Toast notificationType 180 | init = 181 | initWithTransitionDelay 0 182 | 183 | 184 | {-| Initialize a Toast with a transition delay. 185 | 186 | This initializer is useful if you want to remove notifications from your view 187 | with a transition. For the sake of simplicity we assume that you use Html 188 | as the view type. 189 | 190 | The transition delay is the duration for which notifications will remain 191 | "active" and in the DOM after their expiration. Their state will be 192 | `Hiding` (see `NotificationState`) and will give the css transition enough time to complete 193 | before removing the DOM element. 194 | 195 | Make sure to use `Time.second` (or other constructors) 196 | to provide a meaningful parameter. 197 | -} 198 | initWithTransitionDelay : Duration -> Toast a 199 | initWithTransitionDelay delay = 200 | Types.Toast (Types.InternalToast (Types.InternalConfig delay) []) 201 | 202 | 203 | {-| Create a new Notification with type a. The type of the notification will 204 | need to match the type of the Toast in your app model when adding it via 205 | `addNotification`. 206 | 207 | The time parameter is the expiration time after which the notification should 208 | no longer remain in the `Visible` state. The exact time at which the 209 | notification changes its state will depend on the frequency with which 210 | you call `update` (e.g. if you subscribe to update every second the expiration 211 | time will be respected up to a one second precision). 212 | -} 213 | createNotification : a -> Timestamp -> Notification a 214 | createNotification notificationValue expiration = 215 | createFutureNotification 0 notificationValue expiration 216 | 217 | 218 | {-| Create a notification that will be shown at some point in the future. 219 | 220 | The first parameter is the start time at which the notification should start to 221 | be Visible. See [`createNotification`](#createNotification) for the remaining 222 | parameters. 223 | 224 | If you create a notification with a start time after its expiration time it will 225 | be discarded when adding it to the Toast. 226 | -} 227 | createFutureNotification : Timestamp -> a -> Timestamp -> Notification a 228 | createFutureNotification start notificationValue expiration = 229 | Types.Notification 230 | { startTime = start 231 | , expirationTime = expiration 232 | , message = notificationValue 233 | } 234 | 235 | 236 | {-| Add a previously created notification to the Toast. 237 | Duplicate notifications will be discarded. 238 | -} 239 | addNotification : Notification a -> Toast a -> Toast a 240 | addNotification notification toast = 241 | if 242 | Internal.sanityCheck notification 243 | && not (Internal.member notification toast) 244 | then 245 | (Internal.listAllNotifications toast) 246 | ++ [ notification ] 247 | |> Internal.updateNotifications toast 248 | else 249 | toast 250 | 251 | 252 | {-| Get a list of all active notifications in the toast. This excludes any 253 | hidden notifications. You will need to pass in your model's current timestamp. 254 | -} 255 | listActiveNotifications : Toast a -> Timestamp -> List (Notification a) 256 | listActiveNotifications (Types.Toast toast) time = 257 | filterActiveNotifications time toast.config toast.notifications 258 | 259 | 260 | 261 | -- Views 262 | 263 | 264 | {-| A function that creates some view representation (e.g. Html) from a 265 | notification type `a` as used in your Toast. 266 | -} 267 | type alias ViewFunction a viewType = 268 | NotificationState -> a -> viewType 269 | 270 | 271 | {-| Create a list of views from the current list of active notifications in the 272 | given Toast at the provided timestamp. 273 | 274 | The view function you provided will be called for each active notification value 275 | (of type `a`) and its state to provide some view representation of type 276 | `viewType` (e.g. Html). 277 | -} 278 | views : Toast a -> Timestamp -> ViewFunction a viewType -> List viewType 279 | views toast time viewFunc = 280 | let 281 | config = 282 | Internal.config toast 283 | in 284 | Internal.listAllNotifications toast 285 | |> List.filterMap (view config time viewFunc) 286 | 287 | 288 | {-| Similar to the `views` function, but providing the string 289 | representation of the notification along with the view in a Tuple, 290 | which makes it easy to use with Html.Keyed. 291 | -} 292 | keyedViews : Toast a -> Timestamp -> ViewFunction a viewType -> List ( String, viewType ) 293 | keyedViews toast time viewFunc = 294 | let 295 | config = 296 | Internal.config toast 297 | in 298 | Internal.listAllNotifications toast 299 | |> List.filterMap 300 | (\notification -> 301 | view config time viewFunc notification 302 | |> Maybe.map (\view -> ( toString notification, view )) 303 | ) 304 | 305 | 306 | 307 | -- Update 308 | 309 | 310 | {-| With every time update message you will need to update the Toast by calling 311 | this funtion with the current time and then update the Toast in your model 312 | with the returned value: 313 | 314 | pureUpdate : Msg -> Model -> Model 315 | pureUpdate msg model = 316 | case msg of 317 | Tick newTime -> 318 | let 319 | newToast = 320 | Toast.updateTimestamp newTime model.toast 321 | in 322 | { model | toast = newToast, time = newTime } 323 | 324 | -} 325 | updateTimestamp : Timestamp -> Toast a -> Toast a 326 | updateTimestamp time ((Types.Toast internalToast) as toast) = 327 | Internal.listAllNotifications toast 328 | |> filterValidNotifications time internalToast.config 329 | |> Internal.updateNotifications toast 330 | 331 | 332 | 333 | -- Internal methods using external union types 334 | {- 335 | The following method rely on the external union type NotificationState are 336 | included in this module to avoid circular imports. 337 | -} 338 | 339 | 340 | type InternalNotificationState 341 | = Active NotificationState 342 | | Inactive 343 | | Past 344 | 345 | 346 | notificationState : Timestamp -> Types.InternalConfig -> Notification a -> InternalNotificationState 347 | notificationState time config notification = 348 | case notification of 349 | Types.Notification internalNotification -> 350 | if internalNotification.startTime > time then 351 | -- Future notification 352 | Inactive 353 | else if internalNotification.expirationTime > time then 354 | -- Visible notification expiration in the future 355 | Active Visible 356 | else if internalNotification.expirationTime + config.hideTransitionDelay >= time then 357 | -- Currently dismissing 358 | Active Hiding 359 | else 360 | -- Past 361 | Past 362 | 363 | 364 | filterValidNotifications : Timestamp -> Types.InternalConfig -> List (Notification a) -> List (Notification a) 365 | filterValidNotifications time config = 366 | List.filter (\note -> (notificationState time config note) /= Past) 367 | 368 | 369 | filterActiveNotifications : Timestamp -> Types.InternalConfig -> List (Notification a) -> List (Notification a) 370 | filterActiveNotifications time config = 371 | List.filter 372 | (\note -> 373 | case notificationState time config note of 374 | Active _ -> 375 | True 376 | 377 | _ -> 378 | False 379 | ) 380 | 381 | 382 | view : Types.InternalConfig -> Timestamp -> ViewFunction a viewType -> Types.Notification a -> Maybe viewType 383 | view config time viewFunc ((Types.Notification internalNotification) as notification) = 384 | case notificationState time config notification of 385 | Active externalState -> 386 | viewFunc externalState internalNotification.message 387 | |> Just 388 | 389 | Inactive -> 390 | Nothing 391 | 392 | Past -> 393 | Nothing 394 | --------------------------------------------------------------------------------