{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
module Shpadoinkle.Html.Event
( module Shpadoinkle.Html.Event
, module Shpadoinkle.Html.Event.Basic
) where
import Control.Concurrent.STM (retry)
import Control.Lens ((^.))
import Control.Monad (unless, void)
import Control.Monad.IO.Class (liftIO)
import Data.Text
import GHCJS.DOM.Types hiding (Text)
import Language.Javascript.JSaddle hiding (JSM, liftJSM, toJSString)
import UnliftIO.Concurrent (forkIO)
import UnliftIO.STM
import Shpadoinkle
import Shpadoinkle.Html.Event.Basic
import Shpadoinkle.Html.TH
import Shpadoinkle.Keyboard
mkWithFormVal :: (JSVal -> JSM v) -> Text -> JSString -> (v -> Continuation m a) -> (Text, Prop m a)
mkWithFormVal valTo evt from f = listenRaw evt $ \(RawNode n) _ ->
f <$> liftJSM (valTo =<< unsafeGetProp from =<< valToObject n)
onInputC :: (Text -> Continuation m a) -> (Text, Prop m a)
onInputC = mkWithFormVal valToText "input" "value"
$(mkEventVariantsAfforded "input" ''Text)
onBeforeinputC :: (Text -> Continuation m a) -> (Text, Prop m a)
onBeforeinputC = mkWithFormVal valToText "beforeinput" "value"
$(mkEventVariantsAfforded "beforeinput" ''Text)
onOptionC :: (Text -> Continuation m a) -> (Text, Prop m a)
onOptionC = mkWithFormVal valToText "change" "value"
$(mkEventVariantsAfforded "option" ''Text)
mkOnKey :: Text -> (KeyCode -> Continuation m a) -> (Text, Prop m a)
mkOnKey t f = listenRaw t $ \_ (RawEvent e) ->
f <$> liftJSM (fmap round $ valToNumber =<< unsafeGetProp "keyCode" =<< valToObject e)
onKeyupC, onKeydownC, onKeypressC :: (KeyCode -> Continuation m a) -> (Text, Prop m a)
onKeyupC = mkOnKey "keyup"
onKeydownC = mkOnKey "keydown"
onKeypressC = mkOnKey "keypress"
$(mkEventVariantsAfforded "keyup" ''KeyCode)
$(mkEventVariantsAfforded "keydown" ''KeyCode)
$(mkEventVariantsAfforded "keypress" ''KeyCode)
onCheckC :: (Bool -> Continuation m a) -> (Text, Prop m a)
onCheckC = mkWithFormVal valToBool "change" "checked"
$(mkEventVariantsAfforded "check" ''Bool)
preventDefault :: RawEvent -> JSM ()
preventDefault e = void $ valToObject e # ("preventDefault" :: String) $ ([] :: [()])
stopPropagation :: RawEvent -> JSM ()
stopPropagation e = void $ valToObject e # ("stopPropagation" :: String) $ ([] :: [()])
onSubmitC :: Continuation m a -> (Text, Prop m a)
onSubmitC m = listenRaw "submit" $ \_ e -> preventDefault e >> return m
$(mkEventVariants "submit")
mkGlobalMailbox :: Continuation m a -> JSM (JSM (), STM (Continuation m a))
mkGlobalMailbox c = do
(notify, stream) <- mkGlobalMailboxAfforded (const c)
return (notify (), stream)
mkGlobalMailboxAfforded :: (b -> Continuation m a) -> JSM (b -> JSM (), STM (Continuation m a))
mkGlobalMailboxAfforded bc = do
(notify, twas) <- liftIO $ (,) <$> newTVarIO (0, Nothing) <*> newTVarIO (0 :: Int)
return (\b -> atomically $ modifyTVar notify (\(i, _) -> (i + 1, Just b)), do
(new', b) <- readTVar notify
old <- readTVar twas
case b of
Just b' | new' /= old -> bc b' <$ writeTVar twas new'
_ -> retry)
onClickAwayC :: Continuation m a -> (Text, Prop m a)
onClickAwayC c =
( "onclickaway"
, PPotato $ \(RawNode elm) -> liftJSM $ do
(notify, stream) <- mkGlobalMailbox c
void $ jsg ("document" :: Text) ^. js2 ("addEventListener" :: Text) ("click" :: Text)
(fun $ \_ _ -> \case
evt:_ -> void . forkIO $ do
target <- evt ^. js ("target" :: Text)
onTarget <- fromJSVal =<< elm ^. js1 ("contains" :: Text) target
case onTarget of
Just False -> notify
_ -> return ()
[] -> pure ())
return stream
)
$(mkEventVariants "clickAway")
mkGlobalKey :: Text -> (KeyCode -> Continuation m a) -> (Text, Prop m a)
mkGlobalKey evtName c =
( "global" <> evtName
, PPotato $ \_ -> liftJSM $ do
(notify, stream) <- mkGlobalMailboxAfforded c
void $ jsg ("window" :: Text) ^. js2 ("addEventListener" :: Text) evtName
(fun $ \_ _ -> \case
e:_ -> notify . round =<< valToNumber =<< unsafeGetProp "keyCode" =<< valToObject e
[] -> return ())
return stream
)
mkGlobalKeyNoRepeat :: Text -> (KeyCode -> Continuation m a) -> (Text, Prop m a)
mkGlobalKeyNoRepeat evtName c =
( "global" <> evtName
, PPotato $ \_ -> liftJSM $ do
(notify, stream) <- mkGlobalMailboxAfforded c
void $ jsg ("window" :: Text) ^. js2 ("addEventListener" :: Text) evtName
(fun $ \_ _ -> \case
e:_ -> do
eObj <- valToObject e
isRepeat <- valToBool =<< unsafeGetProp "repeat" eObj
unless isRepeat $
notify . round =<< valToNumber =<< unsafeGetProp "keyCode" eObj
[] -> return ())
return stream
)
onGlobalKeyPressC, onGlobalKeyDownC, onGlobalKeyUpC :: (KeyCode -> Continuation m a) -> (Text, Prop m a)
onGlobalKeyPressC = mkGlobalKey "keypress"
onGlobalKeyDownC = mkGlobalKey "keydown"
onGlobalKeyUpC = mkGlobalKey "keyup"
$(mkEventVariantsAfforded "globalKeyPress" ''KeyCode)
$(mkEventVariantsAfforded "globalKeyDown" ''KeyCode)
$(mkEventVariantsAfforded "globalKeyUp" ''KeyCode)
onGlobalKeyDownNoRepeatC :: (KeyCode -> Continuation m a) -> (Text, Prop m a)
onGlobalKeyDownNoRepeatC = mkGlobalKeyNoRepeat "keydown"
$(mkEventVariantsAfforded "globalKeyDownNoRepeat" ''KeyCode)
onEscapeC :: Continuation m a -> (Text, Prop m a)
onEscapeC c = onKeyupC $ \case 27 -> c; _ -> done
$(mkEventVariants "escape")
onEnterC :: (Text -> Continuation m a) -> (Text, Prop m a)
onEnterC f = listenRaw "keyup" $ \(RawNode n) _ -> liftJSM $
f <$> (valToText =<< unsafeGetProp "value"
=<< valToObject n)
$(mkEventVariantsAfforded "enter" ''Text)