packages feed

miso-1.13.0.0: src/Miso/Native/X/Element/Refresh/Event.hs

-----------------------------------------------------------------------------
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Miso.Native.X.Element.Refresh.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.X.Element.Refresh.Event
  ( -- *** Events
    onHeaderOffset
  , onHeaderOffsetWith
  , onHeaderOffsetMain
  , onHeaderOffsetMainWith
  , onRefreshStateChange
  , onRefreshStateChangeWith
  , onRefreshStateChangeMain
  , onRefreshStateChangeMainWith
  , onStartRefresh
  , onStartRefreshWith
  , onStartRefreshMain
  , onStartRefreshMainWith
    -- *** Types
  , HeaderOffsetEvent (..)
  , RefreshStateChangeEvent (..)
  , RefreshState (..)
  , StartRefreshEvent (..)
    -- *** Decoders
  , headerOffsetDecoder
  , refreshStateChangeDecoder
  , startRefreshDecoder
    -- *** Event Map
  , refreshEvents
  ) where
-----------------------------------------------------------------------------
import qualified Data.Map as M
-----------------------------------------------------------------------------
import           Miso.Event
import           Miso.JSON
import           Miso.Types (Attribute, EventHandler, DOMRef)
-----------------------------------------------------------------------------
-- | The 'Events' map for the Lynx @<refresh>@ 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
refreshEvents :: Events
refreshEvents
  = M.fromList
  [ ("headeroffset", BUBBLE)
  , ("refreshstatechange", BUBBLE)
  , ("startrefresh", BUBBLE)
  ]
-----------------------------------------------------------------------------
-- | Payload of the @bindheaderoffset@ event.
data HeaderOffsetEvent
  = HeaderOffsetEvent
  { isDragging :: Bool
    -- ^ Whether the \<refresh-header\> is being dragged
  , offsetPercent :: Double
    -- ^ Ratio of the pull-down distance to the header's own height
  } deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | The state of a \<refresh-header\>, mirrored from Lynx's @RefreshState@ enum.
--
-- @since 1.13.0.0
data RefreshState
  = Idle
  | OverDragRelease
  | Refreshing
  deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | Numbering matches Lynx's @RefreshState@ enum (@IDLE = 0@ … @REFRESHING
-- = 2@), the shape the wire actually sends.
instance FromJSON RefreshState where
  parseJSON = withNumber "RefreshState" $ \case
    0 -> pure Idle
    1 -> pure OverDragRelease
    2 -> pure Refreshing
    x -> typeMismatch "RefreshState" (toJSON x)
-----------------------------------------------------------------------------
-- | Payload of the @bindrefreshstatechange@ event.
newtype RefreshStateChangeEvent
  = RefreshStateChangeEvent
  { state :: RefreshState
    -- ^ The @RefreshState@ of the \<refresh-header\>
  } deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | Payload of the @bindstartrefresh@ event.
newtype StartRefreshEvent
  = StartRefreshEvent
  { isManual :: Bool
    -- ^ Whether the @startrefresh@ event was triggered by a manual drag
  } deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | t'Decoder' producing a t'HeaderOffsetEvent' 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
headerOffsetDecoder :: Decoder HeaderOffsetEvent
headerOffsetDecoder = ["detail"] `at` details
  where
    details = withObject "detail" $ \o ->
      HeaderOffsetEvent
        <$> o .: "isDragging"
        <*> o .: "offsetPercent"
-----------------------------------------------------------------------------
-- | t'Decoder' producing a t'RefreshStateChangeEvent' 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
refreshStateChangeDecoder :: Decoder RefreshStateChangeEvent
refreshStateChangeDecoder = ["detail"] `at` details
  where
    details = withObject "detail" $ \o ->
      RefreshStateChangeEvent <$> o .: "state"
-----------------------------------------------------------------------------
-- | t'Decoder' producing a t'StartRefreshEvent' 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
startRefreshDecoder :: Decoder StartRefreshEvent
startRefreshDecoder = ["detail"] `at` details
  where
    details = withObject "detail" $ \o ->
      StartRefreshEvent <$> o .: "isManual"
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/refresh.html#bindheaderoffset
--
-- Triggered during movement while the \<refresh-header\> is exposed.
--
onHeaderOffset :: (HeaderOffsetEvent -> action) -> Attribute model action
onHeaderOffset action = on "headeroffset" headerOffsetDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onHeaderOffset', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = Offset HeaderOffsetEvent
--
-- view_ [ event (static (onHeaderOffsetMain Offset)) ] [ "some view" ]
-- @
--
onHeaderOffsetMain :: (HeaderOffsetEvent -> action) -> EventHandler model action
onHeaderOffsetMain action = onMain "headeroffset" headerOffsetDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onHeaderOffsetMain', but the handler also receives read-only access to
-- the @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = Offset HeaderOffsetEvent Model DOMRef
--
-- view_ [ event (static (onHeaderOffsetMainWith Offset)) ] [ "some view" ]
-- @
--
onHeaderOffsetMainWith :: (HeaderOffsetEvent -> model -> DOMRef -> action) -> EventHandler model action
onHeaderOffsetMainWith action = onMain "headeroffset" headerOffsetDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/refresh.html#bindrefreshstatechange
--
-- Triggered when the \<refresh-header\> state changes.
--
onRefreshStateChange :: (RefreshStateChangeEvent -> action) -> Attribute model action
onRefreshStateChange action = on "refreshstatechange" refreshStateChangeDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onRefreshStateChange', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = StateChanged RefreshStateChangeEvent
--
-- view_ [ event (static (onRefreshStateChangeMain StateChanged)) ] [ "some view" ]
-- @
--
onRefreshStateChangeMain :: (RefreshStateChangeEvent -> action) -> EventHandler model action
onRefreshStateChangeMain action = onMain "refreshstatechange" refreshStateChangeDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onRefreshStateChangeMain', but the handler also receives read-only
-- access to the @model@ and the target element's 'DOMRef' (for imperative MTS
-- mutation).
--
-- @
-- data Action = StateChanged RefreshStateChangeEvent Model DOMRef
--
-- view_ [ event (static (onRefreshStateChangeMainWith StateChanged)) ] [ "some view" ]
-- @
--
onRefreshStateChangeMainWith :: (RefreshStateChangeEvent -> model -> DOMRef -> action) -> EventHandler model action
onRefreshStateChangeMainWith action = onMain "refreshstatechange" refreshStateChangeDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/refresh.html#bindstartrefresh
--
-- Triggered when the pull threshold is reached or @autoStartRefresh@ is called.
--
onStartRefresh :: (StartRefreshEvent -> action) -> Attribute model action
onStartRefresh action = on "startrefresh" startRefreshDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onStartRefresh', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = Started StartRefreshEvent
--
-- view_ [ event (static (onStartRefreshMain Started)) ] [ "some view" ]
-- @
--
onStartRefreshMain :: (StartRefreshEvent -> action) -> EventHandler model action
onStartRefreshMain action = onMain "startrefresh" startRefreshDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onStartRefreshMain', but the handler also receives read-only access to
-- the @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = Started StartRefreshEvent Model DOMRef
--
-- view_ [ event (static (onStartRefreshMainWith Started)) ] [ "some view" ]
-- @
--
onStartRefreshMainWith :: (StartRefreshEvent -> model -> DOMRef -> action) -> EventHandler model action
onStartRefreshMainWith action = onMain "startrefresh" startRefreshDecoder action
-----------------------------------------------------------------------------
-- | Like 'onHeaderOffset', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onHeaderOffsetWith :: (HeaderOffsetEvent -> DOMRef -> action) -> Attribute model action
onHeaderOffsetWith action = on "headeroffset" headerOffsetDecoder $ \h _ domRef -> action h domRef
-----------------------------------------------------------------------------
-- | Like 'onRefreshStateChange', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onRefreshStateChangeWith :: (RefreshStateChangeEvent -> DOMRef -> action) -> Attribute model action
onRefreshStateChangeWith action = on "refreshstatechange" refreshStateChangeDecoder $ \h _ domRef -> action h domRef
-----------------------------------------------------------------------------
-- | Like 'onStartRefresh', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onStartRefreshWith :: (StartRefreshEvent -> DOMRef -> action) -> Attribute model action
onStartRefreshWith action = on "startrefresh" startRefreshDecoder $ \h _ domRef -> action h domRef
-----------------------------------------------------------------------------