miso-1.12.0.0: src/Miso/Event/Types.hs
-----------------------------------------------------------------------------
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-----------------------------------------------------------------------------
-- |
-- Module : Miso.Event.Types
-- Copyright : (C) 2016-2026 David M. Johnson
-- License : BSD3-style (see the file LICENSE)
-- Maintainer : David M. Johnson <code@dmj.io>
-- Stability : experimental
-- Portability : non-portable
--
-- = Overview
--
-- "Miso.Event.Types" defines the payload types for browser DOM events and
-- the 'Events' map that controls which events are delegated to the miso
-- runtime and at which 'Phase' of the event lifecycle.
--
-- = Event delegation
--
-- Miso uses
-- <https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Event_bubbling event delegation>:
-- a single listener is attached at the root of the component's DOM subtree
-- and catches all matching events as they bubble or capture past it.
-- The 'Events' map (@'Data.Map.Strict.Map' 'Miso.String.MisoString' 'Phase'@)
-- declares which event names participate and their phase.
--
-- The default set ('defaultEvents') covers the most common interactions.
-- Additional event groups can be merged in when constructing a component:
--
-- @
-- myComponent = ('Miso.component' model update view)
-- { 'Miso.Types.events' = 'defaultEvents'
-- \<\> 'keyboardEvents'
-- \<\> 'pointerEvents'
-- }
-- @
--
-- = Event groups
--
-- ['defaultEvents'] blur, change, click, contextmenu, dblclick, focus, input, select, submit
-- ['keyboardEvents'] keydown, keypress, keyup
-- ['mouseEvents'] mouseup, mousedown, mouseenter, mouseleave, mouseover, mouseout, contextmenu
-- ['dragEvents'] drag, dragstart, dragend, dragenter, dragleave, dragover, drop
-- ['pointerEvents'] pointerup, pointerdown, pointerenter, pointerleave, pointerover, pointerout, pointercancel, contextmenu
-- ['mediaEvents'] play, pause, ended, timeupdate, volumechange, …
-- ['clipboardEvents'] cut, copy, paste
-- ['touchEvents'] touchstart, touchend, touchmove, touchcancel
--
-- = Payload types
--
-- * 'KeyCode' \/ 'KeyInfo' — keyboard event key code and modifier state.
-- * 'Checked' — checkbox @checked@ boolean.
-- * 'PointerEvent' \/ 'PointerType' — pointer position, pressure, tilt, and device type.
-- * 'Options' — per-handler flags: 'preventDefault', 'stopPropagation'.
--
-- = See also
--
-- * "Miso.Event.Decoder" — 'Miso.Event.Decoder.Decoder' and pre-built decoders for these types
-- * "Miso.Html.Event" — @onClick@, @onInput@, @onKeyDown@, … combinators
-----------------------------------------------------------------------------
module Miso.Event.Types
( -- ** Types
Events
, Phase (..)
-- *** KeyboardEvent
, KeyInfo (..)
, KeyCode (..)
-- *** CheckedEvent
, Checked (..)
-- *** PointerEvent
, PointerEvent (..)
, PointerType (..)
-- *** Options
, Options (..)
, defaultOptions
, preventDefault
, stopPropagation
-- *** Events
, defaultEvents
, keyboardEvents
, mouseEvents
, dragEvents
, pointerEvents
, mediaEvents
, clipboardEvents
, touchEvents
) where
-----------------------------------------------------------------------------
import Miso.JSON (FromJSON(..), withText)
import qualified Data.Map.Strict as M
-----------------------------------------------------------------------------
import Miso.DSL
import Miso.String (MisoString, ms)
-----------------------------------------------------------------------------
-- | Type useful for both KeyCode and additional key press information.
data KeyInfo
= KeyInfo
{ keyCode :: !KeyCode
-- ^ Numeric key code of the pressed key (see 'KeyCode')
, shiftKey :: !Bool
-- ^ 'True' if the Shift key was held when the event fired
, metaKey :: !Bool
-- ^ 'True' if the Meta (Command on macOS, Windows key on PC) key was held
, ctrlKey :: !Bool
-- ^ 'True' if the Control key was held
, altKey :: !Bool
-- ^ 'True' if the Alt (Option on macOS) key was held
} deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | Type used for Keyboard events.
--
-- See <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Browser_compatibility>
newtype KeyCode = KeyCode Int
deriving (Show, Eq, Ord, FromJSON, Num)
-----------------------------------------------------------------------------
-- | Type used for Checkbox events.
newtype Checked = Checked Bool
deriving (Show, Eq, Ord, FromJSON)
-----------------------------------------------------------------------------
-- | Type used for Pointer events.
-- <https://w3c.github.io/pointerevents>
data PointerEvent
= PointerEvent
{ pointerType :: PointerType
-- ^ Device kind: 'MousePointerType', 'PenPointerType', 'TouchPointerType', or 'UnknownPointerType'
, pointerId :: Int
-- ^ Unique identifier for this pointer, stable across move\/up\/cancel events
, isPrimary :: Bool
-- ^ 'True' for the primary pointer in a multi-touch sequence
, client :: (Double, Double)
-- ^ @(clientX, clientY)@ — position relative to the viewport
, screen :: (Double, Double)
-- ^ @(screenX, screenY)@ — position relative to the screen
, offset :: (Double, Double)
-- ^ @(offsetX, offsetY)@ — position relative to the target element
, page :: (Double,Double)
-- ^ @(pageX, pageY)@ — position relative to the full document
, tilt :: (Double,Double)
-- ^ @(tiltX, tiltY)@ — pen tilt angle in degrees from the surface plane
, pressure :: Double
-- ^ Normalised pressure in @[0, 1]@; @0.5@ for mouse buttons that lack pressure sensitivity
, button :: Int
-- ^ Which button changed state; @-1@ during move with no button change.
-- See <https://w3c.github.io/pointerevents/#the-button-property PointerEvent.button>
} deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | Pointer type
-- <https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType>
data PointerType
= MousePointerType
| PenPointerType
| TouchPointerType
| UnknownPointerType MisoString
deriving (Show, Eq)
-----------------------------------------------------------------------------
instance FromJSON PointerType where
parseJSON = withText "PointerType" $ \case
"mouse" -> pure MousePointerType
"touch" -> pure TouchPointerType
"pen" -> pure PenPointerType
x -> pure (UnknownPointerType (ms x))
-----------------------------------------------------------------------------
-- | t'Options' for handling event propagation.
data Options
= Options
{ _preventDefault :: Bool
-- ^ If 'True', calls @event.preventDefault()@ to suppress the browser's default behaviour
, _stopPropagation :: Bool
-- ^ If 'True', calls @event.stopPropagation()@ to halt event bubbling\/capturing
} deriving (Show, Eq)
-----------------------------------------------------------------------------
instance Monoid Options where
mempty = defaultOptions
-----------------------------------------------------------------------------
instance Semigroup Options where
Options p1 s1 <> Options p2 s2 = Options (p1 || p2) (s1 || s2)
-----------------------------------------------------------------------------
-- | Smart constructor for specifying 'preventDefault'
--
-- @since 1.9.0.0
preventDefault :: Options
preventDefault = defaultOptions { _preventDefault = True }
-----------------------------------------------------------------------------
-- | Smart constructor for specifying 'stopPropagation'
--
-- @since 1.9.0.0
stopPropagation :: Options
stopPropagation = defaultOptions { _stopPropagation = True }
-----------------------------------------------------------------------------
instance ToJSVal Options where
toJSVal Options {..} = do
o <- create
flip (setProp "preventDefault") o =<< toJSVal _preventDefault
flip (setProp "stopPropagation") o =<< toJSVal _stopPropagation
toJSVal o
-----------------------------------------------------------------------------
-- | Default value for @Options@.
--
-- > defaultOptions = Options { preventDefault = False, stopPropagation = False }
defaultOptions :: Options
defaultOptions
= Options
{ _preventDefault = False
, _stopPropagation = False
}
-----------------------------------------------------------------------------
-- | Convenience type for Events
type Events = M.Map MisoString Phase
-----------------------------------------------------------------------------
-- | Default delegated events
defaultEvents :: Events
defaultEvents = M.fromList
[ ("blur", CAPTURE)
, ("change", BUBBLE)
, ("click", BUBBLE)
, ("contextmenu", BUBBLE)
, ("dblclick", BUBBLE)
, ("focus", CAPTURE)
, ("input", BUBBLE)
, ("select", BUBBLE)
, ("submit", BUBBLE)
]
-----------------------------------------------------------------------------
-- | Keyboard events
keyboardEvents :: Events
keyboardEvents = M.fromList
[ ("keydown", BUBBLE)
, ("keypress", BUBBLE)
, ("keyup", BUBBLE)
]
-----------------------------------------------------------------------------
-- | Mouse events
mouseEvents :: Events
mouseEvents = M.fromList
[ ("mouseup", BUBBLE)
, ("mousedown", BUBBLE)
, ("mouseenter", CAPTURE)
, ("mouseleave", BUBBLE)
, ("mouseover", BUBBLE)
, ("mouseout", BUBBLE)
, ("contextmenu", BUBBLE)
]
-----------------------------------------------------------------------------
-- | Drag events
dragEvents :: Events
dragEvents = M.fromList
[ ("dragstart", BUBBLE)
, ("dragover", BUBBLE)
, ("dragend", BUBBLE)
, ("dragenter", BUBBLE)
, ("dragleave", BUBBLE)
, ("drag", BUBBLE)
, ("drop", BUBBLE)
]
-----------------------------------------------------------------------------
-- | Pointer events
pointerEvents :: Events
pointerEvents = M.fromList
[ ("pointerup", BUBBLE)
, ("pointerdown", BUBBLE)
, ("pointerenter", CAPTURE)
, ("pointercancel", BUBBLE)
, ("pointerleave", BUBBLE)
, ("pointerover", BUBBLE)
, ("pointerout", BUBBLE)
, ("contextmenu", BUBBLE)
]
-----------------------------------------------------------------------------
-- | Audio video events
-- For use with the @<audio>@ and @<video>@ tags.
--
-- @
-- myApp :: 'Miso.Types.App' Model Action
-- myApp = ('Miso.Types.component' model update view){ events = 'defaultEvents' <> 'mediaEvents' }
-- @
mediaEvents :: Events
mediaEvents = M.fromList
[ ("abort", CAPTURE)
, ("canplay", CAPTURE)
, ("canplaythrough", CAPTURE)
, ("durationchange", BUBBLE)
, ("emptied", CAPTURE)
, ("ended", CAPTURE)
, ("error", CAPTURE)
, ("loadeddata", BUBBLE)
, ("loadedmetadata", BUBBLE)
, ("loadstart", BUBBLE)
, ("pause", CAPTURE)
, ("play", CAPTURE)
, ("playing", CAPTURE)
, ("progress", CAPTURE)
, ("ratechange", CAPTURE)
, ("seeked", CAPTURE)
, ("seeking", CAPTURE)
, ("stalled", CAPTURE)
, ("suspend", CAPTURE)
, ("timeupdate", CAPTURE)
, ("volumechange", CAPTURE)
, ("waiting", CAPTURE)
]
-----------------------------------------------------------------------------
-- | Clipboard events
clipboardEvents :: Events
clipboardEvents = M.fromList
[ ("cut", BUBBLE)
, ("copy", BUBBLE)
, ("paste", BUBBLE)
]
-----------------------------------------------------------------------------
-- | Touch events
touchEvents :: Events
touchEvents = M.fromList
[ ("touchstart", BUBBLE)
, ("touchcancel", BUBBLE)
, ("touchmove", BUBBLE)
, ("touchend", BUBBLE)
]
-----------------------------------------------------------------------------
-- | Phase during which event listener is invoked.
--
-- @since 1.9.0.0
data Phase = CAPTURE | BUBBLE deriving (Eq, Show)
-----------------------------------------------------------------------------
instance ToJSVal Phase where
toJSVal = \case
CAPTURE -> toJSVal True
BUBBLE -> toJSVal False
-----------------------------------------------------------------------------