miso-1.13.0.0: src/Miso/Native/Element/List/Event.hs
-----------------------------------------------------------------------------
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module : Miso.Native.Element.List.Event
-- 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
--
-- @since 1.13.0.0
----------------------------------------------------------------------------
module Miso.Native.Element.List.Event
( -- *** Event
onScroll
, onScrollWith
, onScrollMain
, onScrollMainWith
, onScrollToUpper
, onScrollToUpperWith
, onScrollToUpperMain
, onScrollToUpperMainWith
, onScrollToLower
, onScrollToLowerWith
, onScrollToLowerMain
, onScrollToLowerMainWith
, onScrollStateChange
, onScrollStateChangeWith
, onScrollStateChangeMain
, onScrollStateChangeMainWith
, onLayoutComplete
, onLayoutCompleteWith
, onLayoutCompleteMain
, onLayoutCompleteMainWith
, onSnap
, onSnapWith
, onSnapMain
, onSnapMainWith
-- *** Types
, ScrollEvent (..)
, SnapEvent (..)
, LayoutCompleteEvent (..)
, DiffResult (..)
, ListEventSource (..)
, Cell (..)
, ScrollStateChange (..)
, ListItemInfo (..)
-- *** Decoder
, scrollDecoder
, snapDecoder
, layoutCompleteDecoder
-- *** Event Map
, listEvents
) where
-----------------------------------------------------------------------------
import qualified Data.Map as M
-----------------------------------------------------------------------------
import Miso.Event
import Miso.JSON
import Miso.Types (Attribute, EventHandler, DOMRef)
import Miso.String (MisoString)
-----------------------------------------------------------------------------
-- | The 'Events' map for the Lynx @<list>@ element.
--
-- Combine with other element maps using @<>@ and pass the result to
-- 'Miso.Native.native', so the delegator listens for these events.
--
-- @since 1.13.0.0
listEvents :: Events
listEvents
= M.fromList
[ ("scroll", BUBBLE)
, ("scrolltoupper", BUBBLE)
, ("scrolltolower", BUBBLE)
, ("scrollstatechange", BUBBLE)
, ("layoutcomplete", BUBBLE)
, ("snap", BUBBLE)
]
-----------------------------------------------------------------------------
-- | t'Decoder' producing a t'ScrollEvent' from the raw Lynx event payload.
--
-- Pass it to 'Miso.Event.on' \/ 'Miso.Event.onMain' when writing a handler by
-- hand; the @on*@ helpers in this module already use it.
--
-- @since 1.13.0.0
scrollDecoder :: Decoder ScrollEvent
scrollDecoder = ["detail"] `at` parseJSON
-----------------------------------------------------------------------------
instance FromJSON ScrollEvent where
parseJSON = withObject "ScrollEvent" $ \o ->
ScrollEvent
<$> o .:? "deltaX" .!= 0
<*> o .:? "deltaY" .!= 0
<*> o .:? "scrollLeft" .!= 0
<*> o .:? "scrollTop" .!= 0
<*> o .:? "scrollWidth" .!= 0
<*> o .:? "scrollHeight" .!= 0
<*> o .:? "listWidth" .!= 0
<*> o .:? "listHeight" .!= 0
-- `eventSource`/`attachedCells` are declared required in Lynx's
-- ListScrollInfo, but `attachedCells` is only populated when
-- `need-visible-item-info` is enabled (otherwise absent). Decode
-- defensively so a @scroll@ event without them still succeeds.
<*> o .:? "eventSource" .!= SCROLL
<*> o .:? "attachedCells" .!= []
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/list.html#scroll
data ScrollEvent
= ScrollEvent
{ deltaX, deltaY :: Double
-- ^ Horizontal / vertical scroll offset since the last scroll, in px
, scrollLeft, scrollTop :: Double
-- ^ Current horizontal / vertical scroll offset, in px
, scrollWidth, scrollHeight :: Double
-- ^ Current content area height / width, in px
, listWidth, listHeight :: Double
-- ^ List width / height in px
, listEventSource :: ListEventSource
-- ^ Scroll event source
, attachedCells :: [Cell]
-- ^ Attached cells
} deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | One cell currently attached to a @<list>@, as reported by
-- 'Miso.Native.Element.List.Method.getVisibleCells' and the layout events.
--
-- @since 1.13.0.0
data Cell
= Cell
{ cellId :: MisoString
-- ^ Node id (Lynx types this @ListAttachedCell.id@ as a string)
, cellItemKey :: MisoString
-- ^ Node item-key
, cellIndex, cellLeft, cellTop, cellRight, cellBottom :: Double
-- ^ Node left/top/right/bottom boundary position relative to list, in px
} deriving (Show, Eq)
-----------------------------------------------------------------------------
instance FromJSON Cell where
parseJSON = withObject "Cell" $ \cell -> Cell
<$> cell .: "id"
<*> cell .: "itemKey"
<*> cell .: "index"
<*> cell .: "left"
<*> cell .: "top"
<*> cell .: "right"
<*> cell .: "bottom"
-----------------------------------------------------------------------------
-- | What triggered a @<list>@ layout-complete event: a data @DIFF@, a
-- re-@LAYOUT@, or a @SCROLL@.
--
-- @since 1.13.0.0
data ListEventSource
= DIFF
| LAYOUT
| SCROLL
deriving (Show, Eq, Enum)
-----------------------------------------------------------------------------
instance FromJSON ListEventSource where
parseJSON = withNumber "ListEventSource" $ \case
0 -> pure DIFF
1 -> pure LAYOUT
2 -> pure SCROLL
x -> typeMismatch "ListEventSource" (toJSON x)
-----------------------------------------------------------------------------
-- | The scroll state a @<list>@ has just entered — at rest, under the
-- user's finger, coasting, or animating to a snap point.
--
-- @since 1.13.0.0
data ScrollStateChange
= Stationary
| Dragging
| InertialScrolling
| SmoothAnimationScrolling
deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | Numbering matches Lynx's @ScrollState@ enum (@kIdle = 1@ … @kScrollAnimation
-- = 4@), so 'toEnum'\/'fromEnum' agree with the wire and with 'FromJSON' below.
-- (A derived 'Enum' would be 0-based and disagree with the values Lynx sends.)
instance Enum ScrollStateChange where
fromEnum Stationary = 1
fromEnum Dragging = 2
fromEnum InertialScrolling = 3
fromEnum SmoothAnimationScrolling = 4
toEnum 1 = Stationary
toEnum 2 = Dragging
toEnum 3 = InertialScrolling
toEnum 4 = SmoothAnimationScrolling
toEnum n = error ("ScrollStateChange.toEnum: bad argument " <> show n)
-----------------------------------------------------------------------------
instance FromJSON ScrollStateChange where
parseJSON = withNumber "ScrollStateChange" $ \case
1 -> pure Stationary
2 -> pure Dragging
3 -> pure InertialScrolling
4 -> pure SmoothAnimationScrolling
x -> typeMismatch "ScrollStateChange" (toJSON x)
-----------------------------------------------------------------------------
scrollStateDecoder :: Decoder ScrollStateChange
scrollStateDecoder = ["detail"] `at` withObject "ScrollStateChange" (.: "state")
-----------------------------------------------------------------------------
-- | Payload of a @<list>@ pagination (snap) event: which cell will be
-- snapped to, and the scroll offsets at the moment of the snap.
--
-- @since 1.13.0.0
data SnapEvent
= SnapEvent
{ position :: Double
-- ^ The index of the node that will be paginated to
, currentScrollLeft :: Double
-- ^ Current horizontal scroll offset, in px
, currentScrollTop :: Double
-- ^ Current vertical scroll offset, in px
, targetScrollLeft :: Double
-- ^ Target horizontal scroll offset for pagination, in px
, targetScrollTop :: Double
-- ^ Target vertical scroll offset for pagination, in px
} deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | t'Decoder' producing a t'SnapEvent' from the raw Lynx event payload.
--
-- Pass it to 'Miso.Event.on' \/ 'Miso.Event.onMain' when writing a handler by
-- hand; the @on*@ helpers in this module already use it.
--
-- @since 1.13.0.0
snapDecoder :: Decoder SnapEvent
snapDecoder = ["detail"] `at` do
withObject "SnapEvent" $ \o ->
SnapEvent
<$> o .: "position"
<*> o .: "currentScrollLeft"
<*> o .: "currentScrollTop"
<*> o .: "targetScrollLeft"
<*> o .: "targetScrollTop"
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/list.html#layoutcomplete
--
-- Enable @needLayoutCompleteInfo@ to use.
--
data LayoutCompleteEvent
= LayoutCompleteEvent
{ layoutId :: Double
, scrollInfo :: ScrollEvent
-- ^ Current horizontal scroll offset, in px
, diffResult :: Maybe DiffResult
-- ^ Current vertical scroll offset, in px
, visibleCellsAfterUpdate :: [ListItemInfo]
-- ^ Target horizontal scroll offset for pagination, in px
, visibleCellsBeforeUpdate :: [ListItemInfo]
-- ^ Target vertical scroll offset for pagination, in px
} deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | The row-level changes a @<list>@ data update produced, as index lists:
-- insertions, moves and removals.
--
-- @since 1.13.0.0
data DiffResult
= DiffResult
{ insertions :: [Double]
, moveFrom :: [Double]
, moveTo :: [Double]
, removals :: [Double]
, updateFrom :: [Double]
, updateTo :: [Double]
} deriving (Show, Eq)
-----------------------------------------------------------------------------
instance FromJSON DiffResult where
parseJSON = withObject "DiffResult" $ \o ->
DiffResult
<$> o .: "insertions"
<*> o .: "move_from"
<*> o .: "move_to"
<*> o .: "removals"
<*> o .: "update_from"
<*> o .: "update_to"
-----------------------------------------------------------------------------
-- | Position and identity of a single @<list>@ item, as reported in the
-- layout-complete event's before\/after cell lists.
--
-- @since 1.13.0.0
data ListItemInfo
= ListItemInfo
{ listItemInfoHeight :: Double
-- ^ Child node height
, listItemInfoWidth :: Double
-- ^ Child node width
, listItemInfoItemKey :: MisoString
-- ^ Child node ItemKey
, listItemInfoIsBinding :: Bool
-- ^ Whether the child node is in rendering state
, listItemInfoOriginX :: Double
-- ^ X coordinate position of the child node relative to the entire scroll area
, listItemInfoOriginY :: Double
-- ^ Y coordinate position of the child node relative to the entire scroll area
, listItemInfoUpdated :: Bool
-- ^ Whether the child node has been updated
} deriving (Show, Eq)
-----------------------------------------------------------------------------
instance FromJSON ListItemInfo where
parseJSON = withObject "ListItemInfo" $ \o ->
ListItemInfo
<$> o .: "height"
<*> o .: "width"
<*> o .: "itemKey"
<*> o .: "isBinding"
<*> o .: "originX"
<*> o .: "originY"
<*> o .: "updated"
-----------------------------------------------------------------------------
-- | t'Decoder' producing a t'LayoutCompleteEvent' from the raw Lynx event payload.
--
-- Pass it to 'Miso.Event.on' \/ 'Miso.Event.onMain' when writing a handler by
-- hand; the @on*@ helpers in this module already use it.
--
-- @since 1.13.0.0
layoutCompleteDecoder :: Decoder LayoutCompleteEvent
layoutCompleteDecoder = ["detail"] `at` do
withObject "LayoutCompleteEvent" $ \o ->
LayoutCompleteEvent
<$> o .: "layout-id"
<*> o .: "scrollInfo"
<*> o .: "diffResult"
<*> o .: "visibleCellsAfterUpdate"
<*> o .: "visibleCellsBeforeUpdate"
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/list.html#scroll
--
-- \<list\> scroll event.
--
-- @
--
-- data Action = HandleScroll ScrollEvent
--
-- view :: context -> props -> Model -> View context Action
-- view _ _ model = list_ defaultListOptions [ onScroll HandleScroll ] [ ]
--
-- update :: Action -> Effect props Model Action
-- update (HandleScroll ScrollEvent {..}) =
-- io_ (consoleLog "handled scroll event")
--
-- @
--
onScroll :: (ScrollEvent -> action) -> Attribute model action
onScroll action = on "scroll" scrollDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onScroll', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = HandleScroll ScrollEvent
--
-- view_ [ event (static (onScrollMain HandleScroll)) ] [ "some view" ]
-- @
--
onScrollMain :: (ScrollEvent -> action) -> EventHandler model action
onScrollMain action = onMain "scroll" scrollDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onScrollMain', but the handler also receives read-only access to the
-- @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = HandleScroll ScrollEvent Model DOMRef
--
-- view_ [ event (static (onScrollMainWith HandleScroll)) ] [ "some view" ]
-- @
--
onScrollMainWith :: (ScrollEvent -> model -> DOMRef -> action) -> EventHandler model action
onScrollMainWith action = onMain "scroll" scrollDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/list.html#scrolltoupper
--
-- Callback triggered when scrolling to the top of \<list\>. The trigger
-- position of this callback can be controlled by @upperThresholdItemCount@.
--
-- @
--
-- data Action = HandleScroll ScrollEvent
--
-- view :: context -> props -> Model -> View context Action
-- view _ _ model = list_ defaultListOptions [ onScrollToUpper HandleScroll ] [ ]
--
-- update :: Action -> Effect props Model Action
-- update (HandleScroll ScrollEvent {..}) =
-- io_ (consoleLog "handled scroll event")
--
-- @
--
onScrollToUpper :: (ScrollEvent -> action) -> Attribute model action
onScrollToUpper action = on "scrolltoupper" scrollDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onScrollToUpper', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = HandleScroll ScrollEvent
--
-- view_ [ event (static (onScrollToUpperMain HandleScroll)) ] [ "some view" ]
-- @
--
onScrollToUpperMain :: (ScrollEvent -> action) -> EventHandler model action
onScrollToUpperMain action = onMain "scrolltoupper" scrollDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onScrollToUpperMain', but the handler also receives read-only access
-- to the @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = HandleScroll ScrollEvent Model DOMRef
--
-- view_ [ event (static (onScrollToUpperMainWith HandleScroll)) ] [ "some view" ]
-- @
--
onScrollToUpperMainWith :: (ScrollEvent -> model -> DOMRef -> action) -> EventHandler model action
onScrollToUpperMainWith action = onMain "scrolltoupper" scrollDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/list.html#scrolltolower
--
-- Callback triggered when scrolling to the bottom of \<list\>. The trigger
-- position of this callback can be controlled by 'Miso.Native.Element.List.Property.lowerThresholdItemCount_'
--
-- @
--
-- data Action = HandleScroll ScrollEvent
--
-- view :: context -> props -> Model -> View context Action
-- view _ _ model = list_ defaultListOptions [ onScrollToLower HandleScroll ] [ ]
--
-- update :: Action -> Effect props Model Action
-- update (HandleScroll ScrollEvent {..}) =
-- io_ (consoleLog "handled scroll event")
--
-- @
--
onScrollToLower :: (ScrollEvent -> action) -> Attribute model action
onScrollToLower action = on "scrolltolower" scrollDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onScrollToLower', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = HandleScroll ScrollEvent
--
-- view_ [ event (static (onScrollToLowerMain HandleScroll)) ] [ "some view" ]
-- @
--
onScrollToLowerMain :: (ScrollEvent -> action) -> EventHandler model action
onScrollToLowerMain action = onMain "scrolltolower" scrollDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onScrollToLowerMain', but the handler also receives read-only access
-- to the @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = HandleScroll ScrollEvent Model DOMRef
--
-- view_ [ event (static (onScrollToLowerMainWith HandleScroll)) ] [ "some view" ]
-- @
--
onScrollToLowerMainWith :: (ScrollEvent -> model -> DOMRef -> action) -> EventHandler model action
onScrollToLowerMainWith action = onMain "scrolltolower" scrollDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/list.html#scrollstatechange
--
-- Callback triggered when the scroll state of \<list\> changes. The state
-- field in the event parameter's detail indicates the scroll state:
-- * 1 for stationary
-- * 2 for dragging
-- * 3 for inertial scrolling
-- * 4 for smooth animation scrolling.
--
-- @
--
-- data Action = HandleScrollState ScrollStateChange
--
-- view :: context -> props -> Model -> View context Action
-- view _ _ model = list_ defaultListOptions [ onScrollStateChange HandleScrollState ] [ ]
--
-- update :: Action -> Effect props Model Action
-- update (HandleScroll Stationary) =
-- io_ (consoleLog "Received Stationary scroll state change")
-- update _ = pure ()
--
-- @
--
onScrollStateChange :: (ScrollStateChange -> action) -> Attribute model action
onScrollStateChange action = on "scrollstatechange" scrollStateDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onScrollStateChange', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = HandleScrollState ScrollStateChange
--
-- view_ [ event (static (onScrollStateChangeMain HandleScrollState)) ] [ "some view" ]
-- @
--
onScrollStateChangeMain :: (ScrollStateChange -> action) -> EventHandler model action
onScrollStateChangeMain action = onMain "scrollstatechange" scrollStateDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onScrollStateChangeMain', but the handler also receives read-only
-- access to the @model@ and the target element's 'DOMRef' (for imperative MTS
-- mutation).
--
-- @
-- data Action = HandleScrollState ScrollStateChange Model DOMRef
--
-- view_ [ event (static (onScrollStateChangeMainWith HandleScrollState)) ] [ "some view" ]
-- @
--
onScrollStateChangeMainWith :: (ScrollStateChange -> model -> DOMRef -> action) -> EventHandler model action
onScrollStateChangeMainWith action = onMain "scrollstatechange" scrollStateDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/list.html#layoutcomplete
--
-- Callback triggered after \<list\> layout is complete.
--
-- @
--
-- data Action = HandleLayout LayoutCompleteEvent
--
-- view :: context -> props -> Model -> View context Action
-- view _ _ model = list_ defaultListOptions [ onLayoutComplete HandleLayout ] [ ]
--
-- update :: Action -> Effect props Model Action
-- update (HandleLayout LayoutCompleteEvent {..}) =
-- io_ (consoleLog "Received LayoutCompleteEvent")
--
-- @
--
onLayoutComplete :: (LayoutCompleteEvent -> action) -> Attribute model action
onLayoutComplete action = on "layoutcomplete" layoutCompleteDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onLayoutComplete', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = HandleLayout LayoutCompleteEvent
--
-- view_ [ event (static (onLayoutCompleteMain HandleLayout)) ] [ "some view" ]
-- @
--
onLayoutCompleteMain :: (LayoutCompleteEvent -> action) -> EventHandler model action
onLayoutCompleteMain action = onMain "layoutcomplete" layoutCompleteDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onLayoutCompleteMain', but the handler also receives read-only access
-- to the @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = HandleLayout LayoutCompleteEvent Model DOMRef
--
-- view_ [ event (static (onLayoutCompleteMainWith HandleLayout)) ] [ "some view" ]
-- @
--
onLayoutCompleteMainWith :: (LayoutCompleteEvent -> model -> DOMRef -> action) -> EventHandler model action
onLayoutCompleteMainWith action = onMain "layoutcomplete" layoutCompleteDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/list.html#snap
--
-- Callback when pagination scrolling is about to occur.
--
-- @
--
-- data Action = HandleSnap SnapEvent
--
-- view :: context -> props -> Model -> View context Action
-- view _ _ model = list_ defaultListOptions [ onSnap HandleSnap ] [ ]
--
-- update :: Action -> Effect props Model Action
-- update (HandleSnap SnapEvent {..}) =
-- io_ (consoleLog "Received SnapEvent")
--
-- @
--
onSnap :: (SnapEvent -> action) -> Attribute model action
onSnap action = on "snap" snapDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onSnap', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = HandleSnap SnapEvent
--
-- view_ [ event (static (onSnapMain HandleSnap)) ] [ "some view" ]
-- @
--
onSnapMain :: (SnapEvent -> action) -> EventHandler model action
onSnapMain action = onMain "snap" snapDecoder (\x _ _ -> action x)
-----------------------------------------------------------------------------
-- | Like 'onSnapMain', but the handler also receives read-only access to the
-- @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = HandleSnap SnapEvent Model DOMRef
--
-- view_ [ event (static (onSnapMainWith HandleSnap)) ] [ "some view" ]
-- @
--
onSnapMainWith :: (SnapEvent -> model -> DOMRef -> action) -> EventHandler model action
onSnapMainWith action = onMain "snap" snapDecoder action
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- | Like 'onScroll', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onScrollWith :: (ScrollEvent -> DOMRef -> action) -> Attribute model action
onScrollWith action = on "scroll" scrollDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------
-- | Like 'onScrollToUpper', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onScrollToUpperWith :: (ScrollEvent -> DOMRef -> action) -> Attribute model action
onScrollToUpperWith action = on "scrolltoupper" scrollDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------
-- | Like 'onScrollToLower', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onScrollToLowerWith :: (ScrollEvent -> DOMRef -> action) -> Attribute model action
onScrollToLowerWith action = on "scrolltolower" scrollDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------
-- | Like 'onScrollStateChange', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onScrollStateChangeWith :: (ScrollStateChange -> DOMRef -> action) -> Attribute model action
onScrollStateChangeWith action = on "scrollstatechange" scrollStateDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------
-- | Like 'onLayoutComplete', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onLayoutCompleteWith :: (LayoutCompleteEvent -> DOMRef -> action) -> Attribute model action
onLayoutCompleteWith action = on "layoutcomplete" layoutCompleteDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------
-- | Like 'onSnap', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onSnapWith :: (SnapEvent -> DOMRef -> action) -> Attribute model action
onSnapWith action = on "snap" snapDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------