{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RankNTypes #-} module Shpadoinkle.Html.Event.Debounce ( debounce , debounceRaw , Debounce , runDebounce ) where import Control.Monad.IO.Class import Data.Maybe import Data.Text import Data.Time.Clock import GHCJS.DOM.Types (JSM, MonadJSM, liftJSM) import Shpadoinkle import UnliftIO import UnliftIO.Concurrent newtype Debounce m a b = Debounce { runDebounce :: (a -> (Text, Prop m b)) -> a -> (Text, Prop m b) } debounceRaw :: MonadJSM m => MonadIO n => NominalDiffTime -> n ( (RawNode -> RawEvent -> JSM (Continuation m a)) -> RawNode -> RawEvent -> JSM (Continuation m a) ) debounceRaw duration = do lastTriggered <- newTVarIO Nothing return $ \handler rn re -> do t0 <- liftIO getCurrentTime liftIO . atomically $ do t <- fromMaybe t0 <$> readTVar lastTriggered writeTVar lastTriggered (Just (max t t0)) return . kleisli $ \_ -> do liftIO . threadDelay . truncate $ duration * 1000000 continue <- liftIO . atomically $ do t1 <- readTVar lastTriggered return $ t1 == Just t0 if continue then liftJSM $ handler rn re else return done debounce :: MonadJSM m => MonadIO n => NominalDiffTime -> n (Debounce m a b) debounce duration = do db <- debounceRaw duration return . Debounce $ \g x -> let (attr, p) = g x in (attr, cataProp textProp (listenerProp . db) flagProp p)