{- |
Module      : Hypermedia.Datastar.PatchElements
Description : Send HTML fragments to patch the browser DOM

\"Patch elements\" is Datastar's primary mechanism for updating the page: the
server sends an HTML fragment and the browser morphs it into the DOM. The
morphing algorithm preserves focus, scroll position, and CSS transitions,
so partial page updates feel seamless.

The simplest case — send HTML and let Datastar match elements by @id@ —
needs only 'patchElements':

@
sendPatchElements gen (patchElements "\<div id=\\\"count\\\"\>42\<\/div\>")
@

To customise the patching behaviour, use record update syntax on the result
of 'patchElements':

@
sendPatchElements gen
  (patchElements "\<li\>new item\<\/li\>")
    { peSelector = Just \"#todo-list\"
    , peMode = Append
    }
@

To remove elements from the DOM, use 'removeElements' with a CSS selector:

@
sendPatchElements gen (removeElements \"#flash-message\")
@
-}
module Hypermedia.Datastar.PatchElements where

import Data.Text (Text)
import Data.Text qualified as T
import Hypermedia.Datastar.Types

{- | Configuration for a @datastar-patch-elements@ SSE event.

Construct values with 'patchElements' or 'removeElements', then customise
with record updates.
-}
data PatchElements = PatchElements
  { PatchElements -> Maybe Text
peElements :: Maybe Text
  {- ^ The HTML fragment to patch into the DOM. 'Nothing' when removing
  elements (see 'removeElements').
  -}
  , PatchElements -> Maybe Text
peSelector :: Maybe Text
  {- ^ CSS selector for the target element. When 'Nothing' (the default),
  Datastar matches by the @id@ attribute of the root element in
  'peElements'.
  -}
  , PatchElements -> ElementPatchMode
peMode :: ElementPatchMode
  {- ^ How to apply the patch. Default: 'Outer' (replace the matched
  element and its contents via morphing).
  -}
  , PatchElements -> Bool
peUseViewTransition :: Bool
  {- ^ Whether to wrap the DOM update in a
  <https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API View Transition>.
  Default: 'False'.
  -}
  , PatchElements -> ElementNamespace
peNamespace :: ElementNamespace
  {- ^ XML namespace for the patched elements. Default: 'HtmlNs'. Use
  'SvgNs' or 'MathmlNs' when patching inline SVG or MathML.
  -}
  , PatchElements -> Maybe Text
peEventId :: Maybe Text
  {- ^ Optional SSE event ID. The browser uses this for reconnection —
  after a dropped connection it sends @Last-Event-ID@ so the server can
  resume from the right point.
  -}
  , PatchElements -> Int
peRetryDuration :: Int
  -- ^ SSE retry interval in milliseconds. Default: @1000@.
  }
  deriving (PatchElements -> PatchElements -> Bool
(PatchElements -> PatchElements -> Bool)
-> (PatchElements -> PatchElements -> Bool) -> Eq PatchElements
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: PatchElements -> PatchElements -> Bool
== :: PatchElements -> PatchElements -> Bool
$c/= :: PatchElements -> PatchElements -> Bool
/= :: PatchElements -> PatchElements -> Bool
Eq, Int -> PatchElements -> ShowS
[PatchElements] -> ShowS
PatchElements -> String
(Int -> PatchElements -> ShowS)
-> (PatchElements -> String)
-> ([PatchElements] -> ShowS)
-> Show PatchElements
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PatchElements -> ShowS
showsPrec :: Int -> PatchElements -> ShowS
$cshow :: PatchElements -> String
show :: PatchElements -> String
$cshowList :: [PatchElements] -> ShowS
showList :: [PatchElements] -> ShowS
Show)

{- | Build a 'PatchElements' event with sensible defaults.

The HTML is sent as-is and Datastar matches target elements by their @id@
attribute.

@
patchElements "\<div id=\\\"greeting\\\"\>Hello!\<\/div\>"
@
-}
patchElements :: Text -> PatchElements
patchElements :: Text -> PatchElements
patchElements Text
html =
  PatchElements
    { peElements :: Maybe Text
peElements = if Text -> Bool
T.null Text
html then Maybe Text
forall a. Maybe a
Nothing else Text -> Maybe Text
forall a. a -> Maybe a
Just Text
html
    , peSelector :: Maybe Text
peSelector = Maybe Text
forall a. Maybe a
Nothing
    , peMode :: ElementPatchMode
peMode = ElementPatchMode
defaultPatchMode
    , peUseViewTransition :: Bool
peUseViewTransition = Bool
defaultUseViewTransition
    , peNamespace :: ElementNamespace
peNamespace = ElementNamespace
defaultNamespace
    , peEventId :: Maybe Text
peEventId = Maybe Text
forall a. Maybe a
Nothing
    , peRetryDuration :: Int
peRetryDuration = Int
defaultRetryDuration
    }

{- | Remove elements from the DOM matching a CSS selector.

@
removeElements \"#notification\"
removeElements \".stale-row\"
@
-}
removeElements :: Text -> PatchElements
removeElements :: Text -> PatchElements
removeElements Text
sel =
  PatchElements
    { peElements :: Maybe Text
peElements = Maybe Text
forall a. Maybe a
Nothing
    , peSelector :: Maybe Text
peSelector = Text -> Maybe Text
forall a. a -> Maybe a
Just Text
sel
    , peMode :: ElementPatchMode
peMode = ElementPatchMode
Remove
    , peUseViewTransition :: Bool
peUseViewTransition = Bool
defaultUseViewTransition
    , peNamespace :: ElementNamespace
peNamespace = ElementNamespace
defaultNamespace
    , peEventId :: Maybe Text
peEventId = Maybe Text
forall a. Maybe a
Nothing
    , peRetryDuration :: Int
peRetryDuration = Int
defaultRetryDuration
    }

toDatastarEvent :: PatchElements -> DatastarEvent
toDatastarEvent :: PatchElements -> DatastarEvent
toDatastarEvent PatchElements
pe =
  DatastarEvent
    { eventType :: EventType
eventType = EventType
EventPatchElements
    , eventId :: Maybe Text
eventId = PatchElements -> Maybe Text
peEventId PatchElements
pe
    , retry :: Int
retry = PatchElements -> Int
peRetryDuration PatchElements
pe
    , dataLines :: [Text]
dataLines =
        [[Text]] -> [Text]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
          [ [Text] -> (Text -> [Text]) -> Maybe Text -> [Text]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (\Text
s -> [Text
"selector " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
s]) (PatchElements -> Maybe Text
peSelector PatchElements
pe)
          , [ Text
"mode " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ElementPatchMode -> Text
patchModeToText (PatchElements -> ElementPatchMode
peMode PatchElements
pe)
            | PatchElements -> ElementPatchMode
peMode PatchElements
pe ElementPatchMode -> ElementPatchMode -> Bool
forall a. Eq a => a -> a -> Bool
/= ElementPatchMode
defaultPatchMode
            ]
          , [ Text
"useViewTransition true"
            | PatchElements -> Bool
peUseViewTransition PatchElements
pe
            ]
          , [ Text
"namespace " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ElementNamespace -> Text
namespaceToText (PatchElements -> ElementNamespace
peNamespace PatchElements
pe)
            | PatchElements -> ElementNamespace
peNamespace PatchElements
pe ElementNamespace -> ElementNamespace -> Bool
forall a. Eq a => a -> a -> Bool
/= ElementNamespace
defaultNamespace
            ]
          , [Text] -> (Text -> [Text]) -> Maybe Text -> [Text]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] ((Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Text
"elements " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>) ([Text] -> [Text]) -> (Text -> [Text]) -> Text -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Text]
T.lines) (PatchElements -> Maybe Text
peElements PatchElements
pe)
          ]
    }