packages feed

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

-----------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Miso.Native.X.Element.Input.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.Input.Event
  ( -- *** Events
    onBlur
  , onBlurWith
  , onBlurMain
  , onBlurMainWith
  , onConfirm
  , onConfirmWith
  , onConfirmMain
  , onConfirmMainWith
  , onFocus
  , onFocusWith
  , onFocusMain
  , onFocusMainWith
  , onInput
  , onInputWith
  , onInputMain
  , onInputMainWith
  , onSelection
  , onSelectionWith
  , onSelectionMain
  , onSelectionMainWith
    -- *** Types
  , InputEvent (..)
  , SelectionEvent (..)
    -- *** Decoders
  , inputValueDecoder
  , inputDecoder
  , selectionDecoder
    -- *** Event Map
  , inputEvents
  ) where
-----------------------------------------------------------------------------
import qualified Data.Map as M
-----------------------------------------------------------------------------
import           Miso.Event
import           Miso.JSON
import           Miso.String (MisoString)
import           Miso.Types (Attribute, EventHandler, DOMRef)
-----------------------------------------------------------------------------
-- | The 'Events' map for the Lynx @<input>@ 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
inputEvents :: Events
inputEvents
  = M.fromList
  [ ("blur", BUBBLE)
  , ("confirm", BUBBLE)
  , ("focus", BUBBLE)
  , ("input", BUBBLE)
  , ("selection", BUBBLE)
  ]
-----------------------------------------------------------------------------
-- | Payload of the @bindinput@ event.
data InputEvent
  = InputEvent
  { inputValue :: MisoString
    -- ^ The current input content
  , inputSelectionStart :: Int
    -- ^ Start position of the selection
  , inputSelectionEnd :: Int
    -- ^ End position of the selection
  , inputIsComposing :: Bool
    -- ^ Whether the input is mid-composition (IME)
  } deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | Payload of the @bindselection@ event.
data SelectionEvent
  = SelectionEvent
  { selStart :: Int
    -- ^ Start position of the selection
  , selEnd :: Int
    -- ^ End position of the selection
  } deriving (Show, Eq)
-----------------------------------------------------------------------------
-- | Decodes the @value@ field shared by @bindblur@, @bindconfirm@ and @bindfocus@.
inputValueDecoder :: Decoder MisoString
inputValueDecoder = ["detail"] `at` details
  where
    details = withObject "detail" $ \o -> o .: "value"
-----------------------------------------------------------------------------
-- | t'Decoder' producing a t'InputEvent' 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
inputDecoder :: Decoder InputEvent
inputDecoder = ["detail"] `at` details
  where
    details = withObject "detail" $ \o ->
      InputEvent
        <$> o .: "value"
        <*> o .:? "selectionStart" .!= 0
        <*> o .:? "selectionEnd" .!= 0
        -- Lynx's native input sends @isComposing@ as a number (0/1), bridged
        -- from an ObjC @BOOL@ — not a JSON boolean — so decode it as an 'Int'
        -- and coerce. Absent (e.g. on the simulator's non-composing path) is
        -- @False@. Decoding it as 'Bool' fails the whole decoder on device.
        <*> (maybe False (/= (0 :: Int)) <$> o .:? "isComposing")
-----------------------------------------------------------------------------
-- Note: the JS keys stay @selectionStart@/@selectionEnd@; the record fields are
-- 'selStart'/'selEnd' to avoid clashing with 'InputValue' when the hub module
-- re-exports Event and Method together.
-- | t'Decoder' producing a t'SelectionEvent' 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
selectionDecoder :: Decoder SelectionEvent
selectionDecoder = ["detail"] `at` details
  where
    details = withObject "detail" $ \o ->
      SelectionEvent
        <$> o .: "selectionStart"
        <*> o .: "selectionEnd"
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/input.html#bindblur
--
-- Triggered when the input is blurred, outputting the current value.
--
onBlur :: (MisoString -> action) -> Attribute model action
onBlur action = on "blur" inputValueDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onBlur', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = Blurred MisoString
--
-- view_ [ event (static (onBlurMain Blurred)) ] [ "some view" ]
-- @
--
onBlurMain :: (MisoString -> action) -> EventHandler model action
onBlurMain action = onMain "blur" inputValueDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onBlurMain', but the handler also receives read-only access to the
-- @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = Blurred MisoString Model DOMRef
--
-- view_ [ event (static (onBlurMainWith Blurred)) ] [ "some view" ]
-- @
--
onBlurMainWith :: (MisoString -> model -> DOMRef -> action) -> EventHandler model action
onBlurMainWith action = onMain "blur" inputValueDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/input.html#bindconfirm
--
-- Triggered when the confirm button is clicked, outputting the current value.
--
onConfirm :: (MisoString -> action) -> Attribute model action
onConfirm action = on "confirm" inputValueDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onConfirm', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = Confirmed MisoString
--
-- view_ [ event (static (onConfirmMain Confirmed)) ] [ "some view" ]
-- @
--
onConfirmMain :: (MisoString -> action) -> EventHandler model action
onConfirmMain action = onMain "confirm" inputValueDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onConfirmMain', but the handler also receives read-only access to the
-- @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = Confirmed MisoString Model DOMRef
--
-- view_ [ event (static (onConfirmMainWith Confirmed)) ] [ "some view" ]
-- @
--
onConfirmMainWith :: (MisoString -> model -> DOMRef -> action) -> EventHandler model action
onConfirmMainWith action = onMain "confirm" inputValueDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/input.html#bindfocus
--
-- Triggered when the input is focused, outputting the current value.
--
onFocus :: (MisoString -> action) -> Attribute model action
onFocus action = on "focus" inputValueDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onFocus', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = Focused MisoString
--
-- view_ [ event (static (onFocusMain Focused)) ] [ "some view" ]
-- @
--
onFocusMain :: (MisoString -> action) -> EventHandler model action
onFocusMain action = onMain "focus" inputValueDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onFocusMain', but the handler also receives read-only access to the
-- @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = Focused MisoString Model DOMRef
--
-- view_ [ event (static (onFocusMainWith Focused)) ] [ "some view" ]
-- @
--
onFocusMainWith :: (MisoString -> model -> DOMRef -> action) -> EventHandler model action
onFocusMainWith action = onMain "focus" inputValueDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/input.html#bindinput
--
-- Triggered when the input content changes.
--
onInput :: (InputEvent -> action) -> Attribute model action
onInput action = on "input" inputDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onInput', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = Changed InputEvent
--
-- view_ [ event (static (onInputMain Changed)) ] [ "some view" ]
-- @
--
onInputMain :: (InputEvent -> action) -> EventHandler model action
onInputMain action = onMain "input" inputDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onInputMain', but the handler also receives read-only access to the
-- @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = Changed InputEvent Model DOMRef
--
-- view_ [ event (static (onInputMainWith Changed)) ] [ "some view" ]
-- @
--
onInputMainWith :: (InputEvent -> model -> DOMRef -> action) -> EventHandler model action
onInputMainWith action = onMain "input" inputDecoder action
-----------------------------------------------------------------------------
-- | https://lynxjs.org/api/elements/built-in/input.html#bindselection
--
-- Triggered when the input selection changes.
--
onSelection :: (SelectionEvent -> action) -> Attribute model action
onSelection action = on "selection" selectionDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onSelection', but dispatched on the Lynx __main thread__ (@MTS@).
--
-- Runs imperatively on the MTS (no VDOM diff). Meant to be used with
-- @-XStaticPointers@.
--
-- @
-- data Action = Selected SelectionEvent
--
-- view_ [ event (static (onSelectionMain Selected)) ] [ "some view" ]
-- @
--
onSelectionMain :: (SelectionEvent -> action) -> EventHandler model action
onSelectionMain action = onMain "selection" selectionDecoder (\e _ _ -> action e)
-----------------------------------------------------------------------------
-- | Like 'onSelectionMain', but the handler also receives read-only access to
-- the @model@ and the target element's 'DOMRef' (for imperative MTS mutation).
--
-- @
-- data Action = Selected SelectionEvent Model DOMRef
--
-- view_ [ event (static (onSelectionMainWith Selected)) ] [ "some view" ]
-- @
--
onSelectionMainWith :: (SelectionEvent -> model -> DOMRef -> action) -> EventHandler model action
onSelectionMainWith action = onMain "selection" selectionDecoder action
-----------------------------------------------------------------------------
-- | Like 'onBlur', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onBlurWith :: (MisoString -> DOMRef -> action) -> Attribute model action
onBlurWith action = on "blur" inputValueDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------
-- | Like 'onConfirm', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onConfirmWith :: (MisoString -> DOMRef -> action) -> Attribute model action
onConfirmWith action = on "confirm" inputValueDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------
-- | Like 'onFocus', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onFocusWith :: (MisoString -> DOMRef -> action) -> Attribute model action
onFocusWith action = on "focus" inputValueDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------
-- | Like 'onInput', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onInputWith :: (InputEvent -> DOMRef -> action) -> Attribute model action
onInputWith action = on "input" inputDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------
-- | Like 'onSelection', but the handler also receives the target element's 'DOMRef'.
-- Use for main-thread (@MTS@) handlers that imperatively mutate the element.
onSelectionWith :: (SelectionEvent -> DOMRef -> action) -> Attribute model action
onSelectionWith action = on "selection" selectionDecoder $ \v _ domRef -> action v domRef
-----------------------------------------------------------------------------