@@ -107,6 +107,9 @@ module Reflex.Dom.Builder.Immediate
107 , WindowConfig (..)
107 , WindowConfig (..)
108 , Window (..)
108 , Window (..)
109 , wrapWindow
109 , wrapWindow
110+ -- * Attributes for controlling hydration
111+ , hydratableAttribute
112+ , skipHydrationAttribute
110 -- * Internal
113 -- * Internal
111 , traverseDMapWithKeyWithAdjust'
114 , traverseDMapWithKeyWithAdjust'
112 , hoistTraverseWithKeyWithAdjust
115 , hoistTraverseWithKeyWithAdjust
@@ -137,12 +140,13 @@ import Data.IntMap.Strict (IntMap)
137import Data.Maybe
140import Data.Maybe
138import Data.Monoid ((<>))
141import Data.Monoid ((<>))
139import Data.Some (Some(..))
142import Data.Some (Some(..))
143+import Data.String (IsString)
140import Data.Text (Text)
144import Data.Text (Text)
141import Foreign.JavaScript.Internal.Utils
145import Foreign.JavaScript.Internal.Utils
142import Foreign.JavaScript.TH
146import Foreign.JavaScript.TH
143import GHCJS.DOM.ClipboardEvent as ClipboardEvent
147import GHCJS.DOM.ClipboardEvent as ClipboardEvent
144import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
148import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
145−import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS, hasAttribute, hasAttributeNS)
149+import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS, hasAttribute)
146import GHCJS.DOM.EventM (EventM, event, on)
150import GHCJS.DOM.EventM (EventM, event, on)
147import GHCJS.DOM.KeyboardEvent as KeyboardEvent
151import GHCJS.DOM.KeyboardEvent as KeyboardEvent
148import GHCJS.DOM.MouseEvent
152import GHCJS.DOM.MouseEvent
@@ -194,6 +198,7 @@ import qualified GHCJS.DOM.TouchEvent as TouchEvent
194import qualified GHCJS.DOM.TouchList as TouchList
198import qualified GHCJS.DOM.TouchList as TouchList
195import qualified GHCJS.DOM.Types as DOM
199import qualified GHCJS.DOM.Types as DOM
196import qualified GHCJS.DOM.Window as Window
200import qualified GHCJS.DOM.Window as Window
201+import qualified GHCJS.DOM.WheelEvent as WheelEvent
197import qualified Reflex.Patch.DMap as PatchDMap
202import qualified Reflex.Patch.DMap as PatchDMap
198import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
203import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
199import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
204import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
@@ -320,8 +325,7 @@ addHydrationStepWithSetup :: (Adjustable t m, MonadIO m) => m a -> (a -> Hydrati
320addHydrationStepWithSetup setup f = getHydrationMode >>= \case
325addHydrationStepWithSetup setup f = getHydrationMode >>= \case
321 HydrationMode_Immediate -> pure ()
326 HydrationMode_Immediate -> pure ()
322 HydrationMode_Hydrating -> do
327 HydrationMode_Hydrating -> do
323− switchover <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover
328+ s <- lift setup
324− (s, _) <- lift $ runWithReplace setup $ return () <$ switchover
325 addHydrationStep (f s)
329 addHydrationStep (f s)
327-- | Add a hydration step
331-- | Add a hydration step
@@ -745,6 +749,14 @@ elementInternal elementTag cfg child = getHydrationMode >>= \case
745 -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (Element er HydrationDomSpace DomTimeline, a)
749 -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (Element er HydrationDomSpace DomTimeline, a)
752+-- | An attribute which causes hydration to skip over an element completely.
753+skipHydrationAttribute :: IsString s => s
754+skipHydrationAttribute = "data-hydration-skip"
756+-- | An attribute which signals that an element should be hydrated.
757+hydratableAttribute :: IsString s => s
758+hydratableAttribute = "data-ssr"
748{-# INLINE hydrateElement #-}
760{-# INLINE hydrateElement #-}
749hydrateElement
761hydrateElement
750 :: forall er t m a. (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
762 :: forall er t m a. (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
@@ -766,18 +778,15 @@ hydrateElement elementTag cfg child = do
767 result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
779 result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
768 wrapResult <- liftIO newEmptyMVar
780 wrapResult <- liftIO newEmptyMVar
769− let skipAttr = "data-hydration-skip" :: DOM.JSString
781+ let -- Determine if we should skip an element. We currently skip elements for
770− ssrAttr = "data-ssr" :: DOM.JSString
782+ -- two reasons:
783+ -- 1) it was not produced by a static builder which supports hydration
784+ -- 2) it is explicitly marked to be skipped
771 shouldSkip :: DOM.Element -> HydrationRunnerT t m Bool
785 shouldSkip :: DOM.Element -> HydrationRunnerT t m Bool
772− shouldSkip e = case cfg ^. namespace of
786+ shouldSkip e = do
774− skip <- hasAttribute e skipAttr
787+ skip <- hasAttribute e (skipHydrationAttribute :: DOM.JSString)
775− ssr <- hasAttribute e ssrAttr
788+ hydratable <- hasAttribute e (hydratableAttribute :: DOM.JSString)
776− pure $ skip || not ssr
778− skip <- hasAttributeNS e (Just ns) skipAttr
779− ssr <- hasAttributeNS e (Just ns) ssrAttr
780− pure $ skip || not ssr
789+ pure $ skip || not hydratable
781 childDom <- liftIO $ readIORef childDelayedRef
790 childDom <- liftIO $ readIORef childDelayedRef
782 let rawCfg = extractRawElementConfig cfg
791 let rawCfg = extractRawElementConfig cfg
783 doc <- askDocument
792 doc <- askDocument
@@ -793,7 +802,7 @@ hydrateElement elementTag cfg child = do
793 Just node -> DOM.castTo DOM.Element node >>= \case
802 Just node -> DOM.castTo DOM.Element node >>= \case
794 Nothing -> go (Just node) -- this node is not an element, skip
803 Nothing -> go (Just node) -- this node is not an element, skip
795 Just e -> shouldSkip e >>= \case
804 Just e -> shouldSkip e >>= \case
796− True -> go (Just node) -- this element is explicitly marked for being skipped by hydration
805+ True -> go (Just node) -- this element should be skipped by hydration
797 False -> do
806 False -> do
798 t <- Element.getTagName e
807 t <- Element.getTagName e
799 -- TODO: check attributes?
808 -- TODO: check attributes?
@@ -1279,8 +1288,8 @@ hydrateComment doc t mSetContents = do
1279skipToAndReplaceComment
1288skipToAndReplaceComment
1280 :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1289 :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1282− -> IORef Text
1291+ -> IORef (Maybe Text)
1283− -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1292+ -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef (Maybe Text))
1284skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
1293skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
1285 HydrationMode_Immediate -> do
1294 HydrationMode_Immediate -> do
1286 -- If we're in immediate mode, we don't try to replace an existing comment,
1295 -- If we're in immediate mode, we don't try to replace an existing comment,
@@ -1288,43 +1297,50 @@ skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
1288 t <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
1297 t <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
1289 append $ toNode t
1298 append $ toNode t
1290 textNodeRef <- liftIO $ newIORef t
1299 textNodeRef <- liftIO $ newIORef t
1291− keyRef <- liftIO $ newIORef ""
1300+ keyRef <- liftIO $ newIORef Nothing
1292 pure (pure (), textNodeRef, keyRef)
1301 pure (pure (), textNodeRef, keyRef)
1293 HydrationMode_Hydrating -> do
1302 HydrationMode_Hydrating -> do
1294 doc <- askDocument
1303 doc <- askDocument
1295 textNodeRef <- liftIO $ newIORef $ error "textNodeRef not yet initialized"
1304 textNodeRef <- liftIO $ newIORef $ error "textNodeRef not yet initialized"
1296 keyRef <- liftIO $ newIORef $ error "keyRef not yet initialized"
1305 keyRef <- liftIO $ newIORef $ error "keyRef not yet initialized"
1308+ tn <- createTextNode doc ("" :: Text)
1309+ insertAfterPreviousNode tn
1310+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
1297− let go key0 mLastNode = do
1312+ go (Just key0) mLastNode = do
1298− parent <- askParent
1313+ parent <- askParent
1299− node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
1314+ maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
1315+ Nothing -> go Nothing Nothing
1300− DOM.castTo DOM.Comment node >>= \case
1316+ Just node -> DOM.castTo DOM.Comment node >>= \case
1301 Just comment -> do
1317 Just comment -> do
1302− commentText <- Node.getTextContentUnchecked comment
1318+ commentText <- fromMaybe (error "Cannot get text content of comment node") <$> Node.getTextContent comment
1303− case T.stripPrefix (prefix <> key0) commentText of
1319+ case T.stripPrefix (prefix <> key0) commentText of -- 'key0' may be @""@ in which case we're just finding the actual key; TODO: Don't be clever.
1304 Just key -> do
1320 Just key -> do
1305 -- Replace the comment with an (invisible) text node
1321 -- Replace the comment with an (invisible) text node
1306 tn <- createTextNode doc ("" :: Text)
1322 tn <- createTextNode doc ("" :: Text)
1307 Node.replaceChild_ parent tn comment
1323 Node.replaceChild_ parent tn comment
1308− pure (tn, key)
1324+ pure (tn, Just key)
1309 Nothing -> do
1325 Nothing -> do
1310− go key0 (Just node)
1326+ go (Just key0) (Just node)
1311 Nothing -> do
1327 Nothing -> do
1312− go key0 (Just node)
1328+ go (Just key0) (Just node)
1313− switchComment = do
1329+ switchComment = do
1314− key0 <- liftIO $ readIORef key0Ref
1330+ key0 <- liftIO $ readIORef key0Ref
1315− (tn, key) <- go key0 =<< getPreviousNode
1331+ (tn, key) <- go key0 =<< getPreviousNode
1316− setPreviousNode $ Just $ toNode tn
1332+ setPreviousNode $ Just $ toNode tn
1317− liftIO $ do
1333+ liftIO $ do
1318− writeIORef textNodeRef tn
1334+ writeIORef textNodeRef tn
1319− writeIORef keyRef key
1335+ writeIORef keyRef key
1320 pure (switchComment, textNodeRef, keyRef)
1336 pure (switchComment, textNodeRef, keyRef)
1322{-# INLINABLE skipToReplaceStart #-}
1338{-# INLINABLE skipToReplaceStart #-}
1323−skipToReplaceStart :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1339+skipToReplaceStart :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef (Maybe Text))
1324−skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef "")
1340+skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef $ Just "") -- TODO: Don't rely on clever usage @""@ to make this work.
1326{-# INLINABLE skipToReplaceEnd #-}
1342{-# INLINABLE skipToReplaceEnd #-}
1327−skipToReplaceEnd :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => IORef Text -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text)
1343+skipToReplaceEnd :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document) => IORef (Maybe Text) -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text)
1328skipToReplaceEnd key = fmap (\(m,e,_) -> (m,e)) $ skipToAndReplaceComment "replace-end" key
1344skipToReplaceEnd key = fmap (\(m,e,_) -> (m,e)) $ skipToAndReplaceComment "replace-end" key
1330instance SupportsHydrationDomBuilder t m => NotReady t (HydrationDomBuilderT s t m) where
1346instance SupportsHydrationDomBuilder t m => NotReady t (HydrationDomBuilderT s t m) where
@@ -2250,7 +2266,7 @@ defaultDomEventHandler e evt = fmap (Just . EventResult) $ case evt of
2250 Touchend -> getTouchEvent
2266 Touchend -> getTouchEvent
2251 Touchcancel -> getTouchEvent
2267 Touchcancel -> getTouchEvent
2252 Mousewheel -> return ()
2268 Mousewheel -> return ()
2253− Wheel -> return ()
2269+ Wheel -> getWheelEvent
2255{-# INLINABLE defaultDomWindowEventHandler #-}
2271{-# INLINABLE defaultDomWindowEventHandler #-}
2256defaultDomWindowEventHandler :: DOM.Window -> EventName en -> EventM DOM.Window (EventType en) (Maybe (EventResult en))
2272defaultDomWindowEventHandler :: DOM.Window -> EventName en -> EventM DOM.Window (EventType en) (Maybe (EventResult en))
@@ -2300,7 +2316,7 @@ defaultDomWindowEventHandler w evt = fmap (Just . EventResult) $ case evt of
2300 Touchend -> getTouchEvent
2316 Touchend -> getTouchEvent
2301 Touchcancel -> getTouchEvent
2317 Touchcancel -> getTouchEvent
2302 Mousewheel -> return ()
2318 Mousewheel -> return ()
2303− Wheel -> return ()
2319+ Wheel -> getWheelEvent
2305{-# INLINABLE withIsEvent #-}
2321{-# INLINABLE withIsEvent #-}
2306withIsEvent :: EventName en -> (IsEvent (EventType en) => r) -> r
2322withIsEvent :: EventName en -> (IsEvent (EventType en) => r) -> r
@@ -2618,6 +2634,26 @@ getTouchEvent = do
2618 , _touchEventResult_touches = touches
2634 , _touchEventResult_touches = touches
2637+{-# INLINABLE getWheelEvent #-}
2638+getWheelEvent :: EventM e WheelEvent WheelEventResult
2641+ dx :: Double <- WheelEvent.getDeltaX e
2642+ dy :: Double <- WheelEvent.getDeltaY e
2643+ dz :: Double <- WheelEvent.getDeltaZ e
2644+ deltaMode :: Word <- WheelEvent.getDeltaMode e
2645+ return $ WheelEventResult
2646+ { _wheelEventResult_deltaX = dx
2647+ , _wheelEventResult_deltaY = dy
2648+ , _wheelEventResult_deltaZ = dz
2649+ , _wheelEventResult_deltaMode = case deltaMode of
2653+ -- See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
2654+ _ -> error "getWheelEvent: impossible encoding"
2621instance MonadSample t m => MonadSample t (HydrationDomBuilderT s t m) where
2657instance MonadSample t m => MonadSample t (HydrationDomBuilderT s t m) where
2622 {-# INLINABLE sample #-}
2658 {-# INLINABLE sample #-}
2623 sample = lift . sample
2659 sample = lift . sample