@@ -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,11 +140,12 @@ 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.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
147import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
144−import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS, hasAttribute, hasAttributeNS)
148+import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS, hasAttribute)
145import GHCJS.DOM.EventM (EventM, event, on)
149import GHCJS.DOM.EventM (EventM, event, on)
146import GHCJS.DOM.KeyboardEvent as KeyboardEvent
150import GHCJS.DOM.KeyboardEvent as KeyboardEvent
147import GHCJS.DOM.MouseEvent
151import GHCJS.DOM.MouseEvent
@@ -192,6 +196,7 @@ import qualified GHCJS.DOM.TouchEvent as TouchEvent
192import qualified GHCJS.DOM.TouchList as TouchList
196import qualified GHCJS.DOM.TouchList as TouchList
193import qualified GHCJS.DOM.Types as DOM
197import qualified GHCJS.DOM.Types as DOM
194import qualified GHCJS.DOM.Window as Window
198import qualified GHCJS.DOM.Window as Window
199+import qualified GHCJS.DOM.WheelEvent as WheelEvent
195import qualified Reflex.Patch.DMap as PatchDMap
200import qualified Reflex.Patch.DMap as PatchDMap
196import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
201import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
197import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
202import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
@@ -318,8 +323,7 @@ addHydrationStepWithSetup :: (Adjustable t m, MonadIO m) => m a -> (a -> Hydrati
318addHydrationStepWithSetup setup f = getHydrationMode >>= \case
323addHydrationStepWithSetup setup f = getHydrationMode >>= \case
319 HydrationMode_Immediate -> pure ()
324 HydrationMode_Immediate -> pure ()
320 HydrationMode_Hydrating -> do
325 HydrationMode_Hydrating -> do
321− switchover <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover
326+ s <- lift setup
322− (s, _) <- lift $ runWithReplace setup $ return () <$ switchover
323 addHydrationStep (f s)
327 addHydrationStep (f s)
325-- | Add a hydration step
329-- | Add a hydration step
@@ -743,6 +747,14 @@ elementInternal elementTag cfg child = getHydrationMode >>= \case
743 -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (Element er HydrationDomSpace DomTimeline, a)
747 -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (Element er HydrationDomSpace DomTimeline, a)
750+-- | An attribute which causes hydration to skip over an element completely.
751+skipHydrationAttribute :: IsString s => s
752+skipHydrationAttribute = "data-hydration-skip"
754+-- | An attribute which signals that an element should be hydrated.
755+hydratableAttribute :: IsString s => s
756+hydratableAttribute = "data-ssr"
746{-# INLINE hydrateElement #-}
758{-# INLINE hydrateElement #-}
747hydrateElement
759hydrateElement
748 :: forall er t m a. (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
760 :: forall er t m a. (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
@@ -764,18 +776,15 @@ hydrateElement elementTag cfg child = do
765 result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
777 result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
766 wrapResult <- liftIO newEmptyMVar
778 wrapResult <- liftIO newEmptyMVar
767− let skipAttr = "data-hydration-skip" :: DOM.JSString
779+ let -- Determine if we should skip an element. We currently skip elements for
768− ssrAttr = "data-ssr" :: DOM.JSString
780+ -- two reasons:
781+ -- 1) it was not produced by a static builder which supports hydration
782+ -- 2) it is explicitly marked to be skipped
769 shouldSkip :: DOM.Element -> HydrationRunnerT t m Bool
783 shouldSkip :: DOM.Element -> HydrationRunnerT t m Bool
770− shouldSkip e = case cfg ^. namespace of
784+ shouldSkip e = do
772− skip <- hasAttribute e skipAttr
785+ skip <- hasAttribute e (skipHydrationAttribute :: DOM.JSString)
773− ssr <- hasAttribute e ssrAttr
786+ hydratable <- hasAttribute e (hydratableAttribute :: DOM.JSString)
774− pure $ skip || not ssr
776− skip <- hasAttributeNS e (Just ns) skipAttr
777− ssr <- hasAttributeNS e (Just ns) ssrAttr
778− pure $ skip || not ssr
787+ pure $ skip || not hydratable
779 childDom <- liftIO $ readIORef childDelayedRef
788 childDom <- liftIO $ readIORef childDelayedRef
780 let rawCfg = extractRawElementConfig cfg
789 let rawCfg = extractRawElementConfig cfg
781 doc <- askDocument
790 doc <- askDocument
@@ -791,7 +800,7 @@ hydrateElement elementTag cfg child = do
791 Just node -> DOM.castTo DOM.Element node >>= \case
800 Just node -> DOM.castTo DOM.Element node >>= \case
792 Nothing -> go (Just node) -- this node is not an element, skip
801 Nothing -> go (Just node) -- this node is not an element, skip
793 Just e -> shouldSkip e >>= \case
802 Just e -> shouldSkip e >>= \case
794− True -> go (Just node) -- this element is explicitly marked for being skipped by hydration
803+ True -> go (Just node) -- this element should be skipped by hydration
795 False -> do
804 False -> do
796 t <- Element.getTagName e
805 t <- Element.getTagName e
797 -- TODO: check attributes?
806 -- TODO: check attributes?
@@ -1277,8 +1286,8 @@ hydrateComment doc t mSetContents = do
1277skipToAndReplaceComment
1286skipToAndReplaceComment
1278 :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1287 :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
1280− -> IORef Text
1289+ -> IORef (Maybe Text)
1281− -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1290+ -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef (Maybe Text))
1282skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
1291skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
1283 HydrationMode_Immediate -> do
1292 HydrationMode_Immediate -> do
1284 -- If we're in immediate mode, we don't try to replace an existing comment,
1293 -- If we're in immediate mode, we don't try to replace an existing comment,
@@ -1286,43 +1295,50 @@ skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
1286 t <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
1295 t <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
1287 append $ toNode t
1296 append $ toNode t
1288 textNodeRef <- liftIO $ newIORef t
1297 textNodeRef <- liftIO $ newIORef t
1289− keyRef <- liftIO $ newIORef ""
1298+ keyRef <- liftIO $ newIORef Nothing
1290 pure (pure (), textNodeRef, keyRef)
1299 pure (pure (), textNodeRef, keyRef)
1291 HydrationMode_Hydrating -> do
1300 HydrationMode_Hydrating -> do
1292 doc <- askDocument
1301 doc <- askDocument
1293 textNodeRef <- liftIO $ newIORef $ error "textNodeRef not yet initialized"
1302 textNodeRef <- liftIO $ newIORef $ error "textNodeRef not yet initialized"
1294 keyRef <- liftIO $ newIORef $ error "keyRef not yet initialized"
1303 keyRef <- liftIO $ newIORef $ error "keyRef not yet initialized"
1306+ tn <- createTextNode doc ("" :: Text)
1307+ insertAfterPreviousNode tn
1308+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
1295− let go key0 mLastNode = do
1310+ go (Just key0) mLastNode = do
1296− parent <- askParent
1311+ parent <- askParent
1297− node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
1312+ maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
1313+ Nothing -> go Nothing Nothing
1298− DOM.castTo DOM.Comment node >>= \case
1314+ Just node -> DOM.castTo DOM.Comment node >>= \case
1299 Just comment -> do
1315 Just comment -> do
1300− commentText <- Node.getTextContentUnchecked comment
1316+ commentText <- fromMaybe (error "Cannot get text content of comment node") <$> Node.getTextContent comment
1301− case T.stripPrefix (prefix <> key0) commentText of
1317+ 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.
1302 Just key -> do
1318 Just key -> do
1303 -- Replace the comment with an (invisible) text node
1319 -- Replace the comment with an (invisible) text node
1304 tn <- createTextNode doc ("" :: Text)
1320 tn <- createTextNode doc ("" :: Text)
1305 Node.replaceChild_ parent tn comment
1321 Node.replaceChild_ parent tn comment
1306− pure (tn, key)
1322+ pure (tn, Just key)
1307 Nothing -> do
1323 Nothing -> do
1308− go key0 (Just node)
1324+ go (Just key0) (Just node)
1309 Nothing -> do
1325 Nothing -> do
1310− go key0 (Just node)
1326+ go (Just key0) (Just node)
1311− switchComment = do
1327+ switchComment = do
1312− key0 <- liftIO $ readIORef key0Ref
1328+ key0 <- liftIO $ readIORef key0Ref
1313− (tn, key) <- go key0 =<< getPreviousNode
1329+ (tn, key) <- go key0 =<< getPreviousNode
1314− setPreviousNode $ Just $ toNode tn
1330+ setPreviousNode $ Just $ toNode tn
1315− liftIO $ do
1331+ liftIO $ do
1316− writeIORef textNodeRef tn
1332+ writeIORef textNodeRef tn
1317− writeIORef keyRef key
1333+ writeIORef keyRef key
1318 pure (switchComment, textNodeRef, keyRef)
1334 pure (switchComment, textNodeRef, keyRef)
1320{-# INLINABLE skipToReplaceStart #-}
1336{-# INLINABLE skipToReplaceStart #-}
1321−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)
1337+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))
1322−skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef "")
1338+skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef $ Just "") -- TODO: Don't rely on clever usage @""@ to make this work.
1324{-# INLINABLE skipToReplaceEnd #-}
1340{-# INLINABLE skipToReplaceEnd #-}
1325−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)
1341+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)
1326skipToReplaceEnd key = fmap (\(m,e,_) -> (m,e)) $ skipToAndReplaceComment "replace-end" key
1342skipToReplaceEnd key = fmap (\(m,e,_) -> (m,e)) $ skipToAndReplaceComment "replace-end" key
1328instance SupportsHydrationDomBuilder t m => NotReady t (HydrationDomBuilderT s t m) where
1344instance SupportsHydrationDomBuilder t m => NotReady t (HydrationDomBuilderT s t m) where
@@ -2248,7 +2264,7 @@ defaultDomEventHandler e evt = fmap (Just . EventResult) $ case evt of
2248 Touchend -> getTouchEvent
2264 Touchend -> getTouchEvent
2249 Touchcancel -> getTouchEvent
2265 Touchcancel -> getTouchEvent
2250 Mousewheel -> return ()
2266 Mousewheel -> return ()
2251− Wheel -> return ()
2267+ Wheel -> getWheelEvent
2253{-# INLINABLE defaultDomWindowEventHandler #-}
2269{-# INLINABLE defaultDomWindowEventHandler #-}
2254defaultDomWindowEventHandler :: DOM.Window -> EventName en -> EventM DOM.Window (EventType en) (Maybe (EventResult en))
2270defaultDomWindowEventHandler :: DOM.Window -> EventName en -> EventM DOM.Window (EventType en) (Maybe (EventResult en))
@@ -2298,7 +2314,7 @@ defaultDomWindowEventHandler w evt = fmap (Just . EventResult) $ case evt of
2298 Touchend -> getTouchEvent
2314 Touchend -> getTouchEvent
2299 Touchcancel -> getTouchEvent
2315 Touchcancel -> getTouchEvent
2300 Mousewheel -> return ()
2316 Mousewheel -> return ()
2301− Wheel -> return ()
2317+ Wheel -> getWheelEvent
2303{-# INLINABLE withIsEvent #-}
2319{-# INLINABLE withIsEvent #-}
2304withIsEvent :: EventName en -> (IsEvent (EventType en) => r) -> r
2320withIsEvent :: EventName en -> (IsEvent (EventType en) => r) -> r
@@ -2607,6 +2623,26 @@ getTouchEvent = do
2607 , _touchEventResult_touches = touches
2623 , _touchEventResult_touches = touches
2626+{-# INLINABLE getWheelEvent #-}
2627+getWheelEvent :: EventM e WheelEvent WheelEventResult
2630+ dx :: Double <- WheelEvent.getDeltaX e
2631+ dy :: Double <- WheelEvent.getDeltaY e
2632+ dz :: Double <- WheelEvent.getDeltaZ e
2633+ deltaMode :: Word <- WheelEvent.getDeltaMode e
2634+ return $ WheelEventResult
2635+ { _wheelEventResult_deltaX = dx
2636+ , _wheelEventResult_deltaY = dy
2637+ , _wheelEventResult_deltaZ = dz
2638+ , _wheelEventResult_deltaMode = case deltaMode of
2642+ -- See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
2643+ _ -> error "getWheelEvent: impossible encoding"
2610instance MonadSample t m => MonadSample t (HydrationDomBuilderT s t m) where
2646instance MonadSample t m => MonadSample t (HydrationDomBuilderT s t m) where
2611 {-# INLINABLE sample #-}
2647 {-# INLINABLE sample #-}
2612 sample = lift . sample
2648 sample = lift . sample