on develop help sign in
Merge branch 'develop' into paste-event
commit02ff86e060a0107fd8fbfea17ab8b322fa650458 authorRyan Trinkle <ryan@trinkle.org> dateSun 3 May 2020, 12:33 (6 years ago) parent086ec2ae2b8460
21 files changed+382−236
FAQ.md+31 cabal.project+1 default.nix−53 dep/reflex-platform/github.json+3−3 reflex-dom-core/ChangeLog.md+54 reflex-dom-core/reflex-dom-core.cabal+10−8 reflex-dom-core/src/Reflex/Dom/Builder/Class.hs+1−1 reflex-dom-core/src/Reflex/Dom/Builder/Class/Events.hs+11−1 reflex-dom-core/src/Reflex/Dom/Builder/Hydratable.hs+4−4 reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+75−39 reflex-dom-core/src/Reflex/Dom/Builder/Static.hs+12−9 reflex-dom-core/src/Reflex/Dom/Class.hs+4−7 reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs+5−25 reflex-dom-core/src/Reflex/Dom/Widget/Input.hs+5−39 reflex-dom-core/src/Reflex/Dom/Xhr.hs+8−6 reflex-dom-core/test/hydration.hs+94−26 reflex-dom/ChangeLog.md+15−1 reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java+6 reflex-dom/reflex-dom.cabal+11−6 reflex-dom/src/Reflex/Dom/Internal.hs+5 release.nix+27−8
FAQ.mdnew file+31
@@ -0,0 +1,31 @@
1+# Frequently Asked Questions
2+
3+### How do I configure DOM events with flags like `preventDefault` and `stopPropagation`?
4+
5+Using `element`, a low-level DOM builder function. Its second argument, `ElementConfig`, has a `_elementConfig_eventSpec` field that can be modified using `addEventSpecFlags`. It takes a `Proxy` that specifies the `DomSpace`, an event name, and a callback which takes the event result and returns an event flag, which can enable behavior like `preventDefault` and `stopPropagation`.
6+
7+The type signature on `def` is needed because the fields of the `ElementConfig` can change the type of the `ElementConfig`. GHC can probably figure out the type of the resulting `ElementConfig`, but it doesn't know what the type of `def` was before the modifications, since it never gets used with that configuration.
8+
9+For example:
10+
11+```haskell
12+{-# LANGUAGE OverloadedStrings #-}
13+{-# LANGUAGE ScopedTypeVariables #-}
14+
15+import Control.Lens ((&), (%~), (.~))
16+import Data.Proxy
17+import Reflex.Dom
18+
19+main :: IO ()
20+main = mainWidget $ do
21+ (click, ()) <- linkPreventDefault $ text "reflex-frp"
22+ display =<< count click
23+
24+linkPreventDefault :: forall t m a. DomBuilder t m => m a -> m (Event t (), a)
25+linkPreventDefault c = do
26+ let cfg = (def :: ElementConfig EventResult t (DomBuilderSpace m))
27+ & elementConfig_initialAttributes .~ ("href" =: "https://reflex-frp.org")
28+ & elementConfig_eventSpec %~ addEventSpecFlags (Proxy :: Proxy (DomBuilderSpace m)) Click (const preventDefault)
29+ (link, a) <- element "a" cfg c
30+ return (domEvent Click link, a)
31+```
cabal.projectnew file+1
@@ -0,0 +1 @@
1+packages: reflex-dom-core, reflex-dom
default.nixdeleted−53
@@ -1,53 +0,0 @@
1−haskellPackages: let
2− pkgs = haskellPackages.callPackage ({ pkgs }: pkgs) {};
3− inherit (pkgs) stdenv;
4− haskellLib = pkgs.haskell.lib;
5−in {
6− reflex-dom-core = let
7− inherit (haskellPackages) ghc;
8− noGcTest = stdenv.hostPlatform.system != "x86_64-linux"
9− || stdenv.hostPlatform != stdenv.buildPlatform
10− || (ghc.isGhcjs or false);
11− in haskellLib.overrideCabal
12− (haskellPackages.callCabal2nix "reflex-dom-core" ./reflex-dom-core { })
13− (drv: {
14− #TODO: Get hlint working for cross-compilation
15− doCheck = stdenv.hostPlatform == stdenv.buildPlatform && !(ghc.isGhcjs or false);
16−
17− # The headless browser run as part of the tests will exit without this
18− preBuild = ''
19− export HOME="$PWD"
20− '';
21−
22− # Show some output while running tests, so we might notice what's wrong
23− testTarget = "--show-details=streaming";
24−
25− testHaskellDepends = with haskellPackages; (drv.testHaskellDepends or []) ++ stdenv.lib.optionals (!noGcTest) [
26− temporary
27− jsaddle-warp
28− process
29− chrome-test-utils
30− ];
31−
32− testSystemDepends = with pkgs; (drv.testSystemDepends or []) ++ [
33− selenium-server-standalone which
34− ] ++ stdenv.lib.optionals (!noGcTest) [
35− chromium
36− pkgs.iproute
37− ];
38− } // stdenv.lib.optionalAttrs (!noGcTest) {
39− # The headless browser run as part of gc tests would hang/crash without this
40− preCheck = ''
41− export FONTCONFIG_PATH=${pkgs.fontconfig.out}/etc/fonts
42− '';
43− });
44− reflex-dom = haskellLib.overrideCabal
45− (haskellPackages.callCabal2nix "reflex-dom" ./reflex-dom { })
46− (drv: {
47− # Hack until https://github.com/NixOS/cabal2nix/pull/432 lands
48− libraryHaskellDepends = (drv.libraryHaskellDepends or []) ++ stdenv.lib.optionals (with stdenv.hostPlatform; isAndroid && is32bit) [
49− haskellPackages.android-activity
50− ];
51− });
52− chrome-test-utils = haskellPackages.callCabal2nix "chrome-test-utils" ./chrome-test-utils {};
53−}
dep/reflex-platform/github.json+3−3
@@ -1,7 +1,7 @@
11{
22 "owner": "reflex-frp",
33 "repo": "reflex-platform",
4− "branch": "develop",
5− "rev": "338fc907e37d70e6469c529456f33221fb05ca1f",
6− "sha256": "08gfhpp0pkvvi1wnlpcn6wmpzdjvy5z7dy1h66gp2w8b5cx8hx5h"
4+ "branch": "master",
5+ "rev": "510b990d0b11f0626afbec5fe8575b5b2395391b",
6+ "sha256": "09cmahsbxr0963wq171c7j139iyzz49hramr4v9nsf684wcwkngv"
77}
reflex-dom-core/ChangeLog.md+54
@@ -2,24 +2,78 @@
22
33## Unreleased
44
5+* ([#353](https://github.com/reflex-frp/reflex-dom/pull/353)) Support GHC 8.8.
6+* ([#358](https://github.com/reflex-frp/reflex-dom/pull/358)) Fix attribute support for explicitly namespaced elements.
7+* ([#363](https://github.com/reflex-frp/reflex-dom/pull/363)) Remove deprecation warnings for the following widgets in
8+ `Reflex.Dom.Widget.Basic`:
9+
10+ * `Link`
11+ * `button`
12+ * `dtdd`
13+ * `linkClass`
14+ * `link`
15+ * `tabDisplay`
16+ * `tableDynAttr`
17+* ([#361](https://github.com/reflex-frp/reflex-dom/pull/361)) Fix bug in hydration causing the JavaScript to crash when dealing with unexpected HTML.
18+* ([#364](https://github.com/reflex-frp/reflex-dom/pull/364)) Export attributes used for controlling hydration at the element level:
19+ * "data-ssr" is now available as `Reflex.Dom.Builder.Immediate.hydratableAttribute`.
20+ * "data-hydration-skip" is now available as `Reflex.Dom.Builder.Immediate.skipHydrationAttribute`.
21+* ([#348](https://github.com/reflex-frp/reflex-dom/pull/348)) **(Breaking change)** Make XHR response headers case insensitive by changing `_xhrResponse_headers :: Map Text Text` to `_xhrResponse_headers :: Map (CI Text) Text`.
22+* ([#225](https://github.com/reflex-frp/reflex-dom/pull/225)) **(Breaking change)** Add a functional dependency to `HasDomEvent`.
23+* ([#366](https://github.com/reflex-frp/reflex-dom/pull/366)) Bump bounds for `reflex` to include 0.7.
24+
25+
26+## 0.5.3
27+
28+* Deprecate a number of old inflexible widget helpers in `Reflex.Dom.Widget.Basic`:
29+
30+ * `Link`
31+ * `button`
32+ * `dtdd`
33+ * `linkClass`
34+ * `link`
35+ * `tabDisplay`
36+ * `tableDynAttr`
37+
38+ And in `Reflex.Dom.Widget.Input`:
39+
40+ * `TextInput`
41+ * `TextAreaConfig`
42+ * `CheckboxConfig`
43+ * `FileInput`
44+
45+ * Add `< 0.7` upper bound for reflex
46+
47+* *(Breaking change)* The mouse wheel event now is a `WheelEventResult` rather than `()`.
48+ This provides information about the wheel's motion beyond the fact that it merely moved.
49+
550## 0.5.2
651
752* Update to use new dependent-sum/map packages and drop dependency on `*Tag` classes (e.g., `ShowTag`).
53+
854* Update version bounds of base, containers, and stm
55+
956* Update to use the newly split `these`/`semialign` packages. To use the pre-split `these` package, set the `split-these` flag to false.
57+
1058* Reintroduce "data-ssr": elements without this attribute are skipped during
1159 hydration.
60+
1261* Fix an issue in the hydration tests that prevented the test from finding the chromium executable
62+
1363* Relax constraints on `dyn` and `widgetHold` to match the ones in `networkView` and `networkHold` respectively
64+
1465* Fix prerender for RequesterT so that it doesn't accidentally discard a request that is made at the same moment as getPostBuild's Event fires
1566
1667## 0.5.1
1768
1869* Added support for marking elements with a "data-skip-hydration" attribute, which will cause hydration to ignore and skip over them.
70+
1971* Removed "data-ssr" attributes from statically rendered output.
2072
2173## 0.5
2274
2375* Add HydrationDomBuilderT to support hydration of statically rendered DOM nodes. See the note at the top of Reflex.Dom.Builder.Immediate.
76+
2477* As a result of the hydration changes, the Prerender class has changed, the type of `prerender` has changed and it is now a class method.
78+
2579* Add the Reflex.Dom.Xhr.FormData module to make posting formdata over xhr more convenient.
reflex-dom-core/reflex-dom-core.cabal+10−8
@@ -1,5 +1,5 @@
11Name: reflex-dom-core
2−Version: 0.5.2.0
2+Version: 0.5.3
33Synopsis: Functional Reactive Web Apps with Reflex
44Description: Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine
55License: BSD3
@@ -48,11 +48,12 @@ library
4848 hs-source-dirs: src
4949 build-depends:
5050 aeson >= 0.8 && < 1.5,
51− base >= 4.7 && < 4.13,
51+ base >= 4.7 && < 4.14,
5252 bifunctors >= 4.2 && < 6,
5353 bimap >= 0.3 && < 0.4,
5454 blaze-builder,
5555 bytestring == 0.10.*,
56+ case-insensitive < 1.3,
5657 containers >= 0.6 && < 0.7,
5758 constraints >= 0.9 && < 0.12,
5859 contravariant >= 1.4 && < 1.6,
@@ -63,17 +64,17 @@ library
6364 directory >= 1.2 && < 1.4,
6465 exception-transformers == 0.4.*,
6566 ghcjs-dom >= 0.9.1.0 && < 0.10,
66− jsaddle >=0.9.0.0 && <0.10,
67+ jsaddle >= 0.9.0.0 && < 0.10,
6768 -- keycode-0.2 has a bug on firefox
6869 keycode >= 0.2.1 && < 0.3,
6970 lens >= 4.7 && < 5,
7071 monad-control >= 1.0.1 && < 1.1,
7172 mtl >= 2.1 && < 2.3,
72− primitive >= 0.5 && < 0.7,
73+ primitive >= 0.5 && < 0.8,
7374 random,
7475 ref-tf == 0.4.*,
75− reflex >= 0.6.2,
76− semigroups >= 0.16 && < 0.19,
76+ reflex >= 0.6.2 && < 0.8,
77+ semigroups >= 0.16 && < 0.20,
7778 stm >= 2.4 && < 2.6,
7879 text == 1.2.*,
7980 transformers >= 0.3 && < 0.6,
@@ -84,7 +85,7 @@ library
8485 hs-source-dirs: src-ghcjs
8586 build-depends:
8687 ghcjs-base,
87− hashable == 1.2.*
88+ hashable >= 1.2 && < 1.4
8889 else
8990 hs-source-dirs: src-ghc
9091 if !os(windows)
@@ -92,7 +93,7 @@ library
9293
9394 if flag(split-these)
9495 build-depends:
95− semialign >= 1 && < 1.1,
96+ semialign >= 1 && < 1.2,
9697 these >= 1 && < 1.1
9798 else
9899 build-depends:
@@ -166,6 +167,7 @@ test-suite hlint
166167test-suite hydration
167168 build-depends: base
168169 , aeson
170+ , async
169171 , bytestring
170172 , chrome-test-utils
171173 , constraints
reflex-dom-core/src/Reflex/Dom/Builder/Class.hs+1−1
@@ -638,7 +638,7 @@ instance (DomBuilder t m, MonadFix m, MonadHold t m, Group q, Query q, Additive
638638
639639-- * Convenience functions
640640
641−class HasDomEvent t target eventName where
641+class HasDomEvent t target eventName | target -> t where
642642 type DomEventType target eventName :: *
643643 domEvent :: EventName eventName -> target -> Event t (DomEventType target eventName)
644644
reflex-dom-core/src/Reflex/Dom/Builder/Class/Events.hs+11−1
@@ -160,7 +160,17 @@ type family EventResultType (en :: EventTag) :: * where
160160 EventResultType 'TouchmoveTag = TouchEventResult
161161 EventResultType 'TouchendTag = TouchEventResult
162162 EventResultType 'TouchcancelTag = TouchEventResult
163− EventResultType 'WheelTag = ()
163+ EventResultType 'WheelTag = WheelEventResult
164+
165+data DeltaMode = DeltaPixel | DeltaLine | DeltaPage
166+ deriving (Show, Read, Eq, Ord, Bounded, Enum)
167+
168+data WheelEventResult = WheelEventResult
169+ { _wheelEventResult_deltaX :: Double
170+ , _wheelEventResult_deltaY :: Double
171+ , _wheelEventResult_deltaZ :: Double
172+ , _wheelEventResult_deltaMode :: DeltaMode
173+ } deriving (Show, Read, Eq, Ord)
164174
165175data TouchEventResult = TouchEventResult
166176 { _touchEventResult_altKey :: Bool
reflex-dom-core/src/Reflex/Dom/Builder/Hydratable.hs+4−4
@@ -21,10 +21,10 @@ import GHCJS.DOM.Types (MonadJSM (..))
2121#endif
2222import Reflex
2323import Reflex.Dom.Builder.Class
24−import Reflex.Dom.Builder.Immediate (HasDocument (..))
24+import Reflex.Dom.Builder.Immediate (HasDocument (..), hydratableAttribute)
2525import Reflex.Host.Class
2626
27−-- | A DomBuilder transformer that adds "data-ssr" to all elements such that the
27+-- | A DomBuilder transformer that adds an attribute to all elements such that the
2828-- hydration builder knows which bits of DOM were added by us, and which were
2929-- added by external scripts.
3030newtype HydratableT m a = HydratableT { runHydratableT :: m a } deriving (Functor, Applicative, Monad, MonadAtomicRef, MonadFix, MonadIO)
@@ -62,8 +62,8 @@ instance PrimMonad m => PrimMonad (HydratableT m) where
6262
6363makeHydratable :: Reflex t => ElementConfig er t m -> ElementConfig er t m
6464makeHydratable cfg = cfg
65− { _elementConfig_initialAttributes = Map.insert "data-ssr" "" $ _elementConfig_initialAttributes cfg
66− , _elementConfig_modifyAttributes = fmap (Map.delete "data-ssr") <$> _elementConfig_modifyAttributes cfg
65+ { _elementConfig_initialAttributes = Map.insert hydratableAttribute "" $ _elementConfig_initialAttributes cfg
66+ , _elementConfig_modifyAttributes = fmap (Map.delete hydratableAttribute) <$> _elementConfig_modifyAttributes cfg
6767 }
6868
6969instance PostBuild t m => PostBuild t (HydratableT m) where
reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+75−39
@@ -107,6 +107,9 @@ module Reflex.Dom.Builder.Immediate
107107 , WindowConfig (..)
108108 , Window (..)
109109 , wrapWindow
110+ -- * Attributes for controlling hydration
111+ , hydratableAttribute
112+ , skipHydrationAttribute
110113 -- * Internal
111114 , traverseDMapWithKeyWithAdjust'
112115 , hoistTraverseWithKeyWithAdjust
@@ -137,12 +140,13 @@ import Data.IntMap.Strict (IntMap)
137140import Data.Maybe
138141import Data.Monoid ((<>))
139142import Data.Some (Some(..))
143+import Data.String (IsString)
140144import Data.Text (Text)
141145import Foreign.JavaScript.Internal.Utils
142146import Foreign.JavaScript.TH
143147import GHCJS.DOM.ClipboardEvent as ClipboardEvent
144148import 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)
146150import GHCJS.DOM.EventM (EventM, event, on)
147151import GHCJS.DOM.KeyboardEvent as KeyboardEvent
148152import GHCJS.DOM.MouseEvent
@@ -194,6 +198,7 @@ import qualified GHCJS.DOM.TouchEvent as TouchEvent
194198import qualified GHCJS.DOM.TouchList as TouchList
195199import qualified GHCJS.DOM.Types as DOM
196200import qualified GHCJS.DOM.Window as Window
201+import qualified GHCJS.DOM.WheelEvent as WheelEvent
197202import qualified Reflex.Patch.DMap as PatchDMap
198203import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
199204import qualified Reflex.Patch.MapWithMove as PatchMapWithMove
@@ -320,8 +325,7 @@ addHydrationStepWithSetup :: (Adjustable t m, MonadIO m) => m a -> (a -> Hydrati
320325addHydrationStepWithSetup setup f = getHydrationMode >>= \case
321326 HydrationMode_Immediate -> pure ()
322327 HydrationMode_Hydrating -> do
323− switchover <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover
324− (s, _) <- lift $ runWithReplace setup $ return () <$ switchover
328+ s <- lift setup
325329 addHydrationStep (f s)
326330
327331-- | Add a hydration step
@@ -745,6 +749,14 @@ elementInternal elementTag cfg child = getHydrationMode >>= \case
745749 -> HydrationDomBuilderT HydrationDomSpace DomTimeline HydrationM (Element er HydrationDomSpace DomTimeline, a)
746750 #-}
747751
752+-- | An attribute which causes hydration to skip over an element completely.
753+skipHydrationAttribute :: IsString s => s
754+skipHydrationAttribute = "data-hydration-skip"
755+
756+-- | An attribute which signals that an element should be hydrated.
757+hydratableAttribute :: IsString s => s
758+hydratableAttribute = "data-ssr"
759+
748760{-# INLINE hydrateElement #-}
749761hydrateElement
750762 :: forall er t m a. (MonadJSM m, Reflex t, MonadReflexCreateTrigger t m, MonadFix m)
@@ -766,18 +778,15 @@ hydrateElement elementTag cfg child = do
766778 }
767779 result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
768780 wrapResult <- liftIO newEmptyMVar
769− let skipAttr = "data-hydration-skip" :: DOM.JSString
770− ssrAttr = "data-ssr" :: DOM.JSString
781+ let -- Determine if we should skip an element. We currently skip elements for
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
771785 shouldSkip :: DOM.Element -> HydrationRunnerT t m Bool
772− shouldSkip e = case cfg ^. namespace of
773− Nothing -> do
774− skip <- hasAttribute e skipAttr
775− ssr <- hasAttribute e ssrAttr
776− pure $ skip || not ssr
777− Just ns -> do
778− skip <- hasAttributeNS e (Just ns) skipAttr
779− ssr <- hasAttributeNS e (Just ns) ssrAttr
780− pure $ skip || not ssr
786+ shouldSkip e = do
787+ skip <- hasAttribute e (skipHydrationAttribute :: DOM.JSString)
788+ hydratable <- hasAttribute e (hydratableAttribute :: DOM.JSString)
789+ pure $ skip || not hydratable
781790 childDom <- liftIO $ readIORef childDelayedRef
782791 let rawCfg = extractRawElementConfig cfg
783792 doc <- askDocument
@@ -793,7 +802,7 @@ hydrateElement elementTag cfg child = do
793802 Just node -> DOM.castTo DOM.Element node >>= \case
794803 Nothing -> go (Just node) -- this node is not an element, skip
795804 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
797806 False -> do
798807 t <- Element.getTagName e
799808 -- TODO: check attributes?
@@ -1279,8 +1288,8 @@ hydrateComment doc t mSetContents = do
12791288skipToAndReplaceComment
12801289 :: (MonadJSM m, Reflex t, MonadFix m, Adjustable t m, MonadHold t m, RawDocument (DomBuilderSpace (HydrationDomBuilderT s t m)) ~ Document)
12811290 => Text
1282− -> IORef Text
1283− -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef Text)
1291+ -> IORef (Maybe Text)
1292+ -> HydrationDomBuilderT s t m (HydrationRunnerT t m (), IORef DOM.Text, IORef (Maybe Text))
12841293skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
12851294 HydrationMode_Immediate -> do
12861295 -- If we're in immediate mode, we don't try to replace an existing comment,
@@ -1288,43 +1297,50 @@ skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
12881297 t <- textNodeImmediate $ TextNodeConfig ("" :: Text) Nothing
12891298 append $ toNode t
12901299 textNodeRef <- liftIO $ newIORef t
1291− keyRef <- liftIO $ newIORef ""
1300+ keyRef <- liftIO $ newIORef Nothing
12921301 pure (pure (), textNodeRef, keyRef)
12931302 HydrationMode_Hydrating -> do
12941303 doc <- askDocument
12951304 textNodeRef <- liftIO $ newIORef $ error "textNodeRef not yet initialized"
12961305 keyRef <- liftIO $ newIORef $ error "keyRef not yet initialized"
1297− let go key0 mLastNode = do
1298− parent <- askParent
1299− node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
1300− DOM.castTo DOM.Comment node >>= \case
1306+ let
1307+ go Nothing _ = do
1308+ tn <- createTextNode doc ("" :: Text)
1309+ insertAfterPreviousNode tn
1310+ HydrationRunnerT $ modify' $ \s -> s { _hydrationState_failed = True }
1311+ pure (tn, Nothing)
1312+ go (Just key0) mLastNode = do
1313+ parent <- askParent
1314+ maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
1315+ Nothing -> go Nothing Nothing
1316+ Just node -> DOM.castTo DOM.Comment node >>= \case
13011317 Just comment -> do
1302− commentText <- Node.getTextContentUnchecked comment
1303− case T.stripPrefix (prefix <> key0) commentText of
1318+ commentText <- fromMaybe (error "Cannot get text content of comment node") <$> Node.getTextContent comment
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.
13041320 Just key -> do
13051321 -- Replace the comment with an (invisible) text node
13061322 tn <- createTextNode doc ("" :: Text)
13071323 Node.replaceChild_ parent tn comment
1308− pure (tn, key)
1324+ pure (tn, Just key)
13091325 Nothing -> do
1310− go key0 (Just node)
1326+ go (Just key0) (Just node)
13111327 Nothing -> do
1312− go key0 (Just node)
1313− switchComment = do
1314− key0 <- liftIO $ readIORef key0Ref
1315− (tn, key) <- go key0 =<< getPreviousNode
1316− setPreviousNode $ Just $ toNode tn
1317− liftIO $ do
1318− writeIORef textNodeRef tn
1319− writeIORef keyRef key
1328+ go (Just key0) (Just node)
1329+ switchComment = do
1330+ key0 <- liftIO $ readIORef key0Ref
1331+ (tn, key) <- go key0 =<< getPreviousNode
1332+ setPreviousNode $ Just $ toNode tn
1333+ liftIO $ do
1334+ writeIORef textNodeRef tn
1335+ writeIORef keyRef key
13201336 pure (switchComment, textNodeRef, keyRef)
13211337
13221338{-# 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)
1324−skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef "")
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))
1340+skipToReplaceStart = skipToAndReplaceComment "replace-start" =<< liftIO (newIORef $ Just "") -- TODO: Don't rely on clever usage @""@ to make this work.
13251341
13261342{-# 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)
13281344skipToReplaceEnd key = fmap (\(m,e,_) -> (m,e)) $ skipToAndReplaceComment "replace-end" key
13291345
13301346instance SupportsHydrationDomBuilder t m => NotReady t (HydrationDomBuilderT s t m) where
@@ -2250,7 +2266,7 @@ defaultDomEventHandler e evt = fmap (Just . EventResult) $ case evt of
22502266 Touchend -> getTouchEvent
22512267 Touchcancel -> getTouchEvent
22522268 Mousewheel -> return ()
2253− Wheel -> return ()
2269+ Wheel -> getWheelEvent
22542270
22552271{-# INLINABLE defaultDomWindowEventHandler #-}
22562272defaultDomWindowEventHandler :: 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
23002316 Touchend -> getTouchEvent
23012317 Touchcancel -> getTouchEvent
23022318 Mousewheel -> return ()
2303− Wheel -> return ()
2319+ Wheel -> getWheelEvent
23042320
23052321{-# INLINABLE withIsEvent #-}
23062322withIsEvent :: EventName en -> (IsEvent (EventType en) => r) -> r
@@ -2618,6 +2634,26 @@ getTouchEvent = do
26182634 , _touchEventResult_touches = touches
26192635 }
26202636
2637+{-# INLINABLE getWheelEvent #-}
2638+getWheelEvent :: EventM e WheelEvent WheelEventResult
2639+getWheelEvent = do
2640+ e <- event
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
2650+ 0 -> DeltaPixel
2651+ 1 -> DeltaLine
2652+ 2 -> DeltaPage
2653+ -- See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
2654+ _ -> error "getWheelEvent: impossible encoding"
2655+ }
2656+
26212657instance MonadSample t m => MonadSample t (HydrationDomBuilderT s t m) where
26222658 {-# INLINABLE sample #-}
26232659 sample = lift . sample
reflex-dom-core/src/Reflex/Dom/Builder/Static.hs+12−9
@@ -55,7 +55,6 @@ import Reflex.PerformEvent.Class
5555import Reflex.PostBuild.Base
5656import Reflex.PostBuild.Class
5757import Reflex.TriggerEvent.Class
58−import System.Random (randomRIO)
5958
6059data StaticDomBuilderEnv t = StaticDomBuilderEnv
6160 { _staticDomBuilderEnv_shouldEscape :: Bool
@@ -64,6 +63,7 @@ data StaticDomBuilderEnv t = StaticDomBuilderEnv
6463 -- We use this to add a "selected" attribute to the appropriate "option" child element.
6564 -- This is not yet a perfect simulation of what the browser does, but it is much closer than doing nothing.
6665 -- TODO: Handle edge cases, e.g. setting to a value for which there is no option, then adding that option dynamically afterwards.
66+ , _staticDomBuilderEnv_nextRunWithReplaceKey :: IORef Int
6767 }
6868
6969newtype StaticDomBuilderT t m a = StaticDomBuilderT
@@ -164,7 +164,7 @@ instance (SupportsStaticDomBuilder t m, Monad m) => HasDocument (StaticDomBuilde
164164instance (Reflex t, Adjustable t m, MonadHold t m, SupportsStaticDomBuilder t m) => Adjustable t (StaticDomBuilderT t m) where
165165 runWithReplace a0 a' = do
166166 e <- StaticDomBuilderT ask
167− key <- replaceStart
167+ key <- replaceStart e
168168 (result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0 e) (flip runStaticDomBuilderT e <$> a')
169169 o <- hold (snd result0) $ fmapCheap snd result'
170170 StaticDomBuilderT $ modify $ (:) $ join o
@@ -174,10 +174,9 @@ instance (Reflex t, Adjustable t m, MonadHold t m, SupportsStaticDomBuilder t m)
174174 traverseDMapWithKeyWithAdjust = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap
175175 traverseDMapWithKeyWithAdjustWithMove = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove
176176
177−-- TODO remove this?
178−replaceStart :: (DomBuilder t m, MonadIO m) => m Text
179−replaceStart = do
180− str <- liftIO $ replicateM 8 $ randomRIO ('a', 'z')
177+replaceStart :: (DomBuilder t m, MonadIO m) => StaticDomBuilderEnv t -> m Text
178+replaceStart env = do
179+ str <- show <$> liftIO (atomicModifyRef (_staticDomBuilderEnv_nextRunWithReplaceKey env) $ \k -> (succ k, k))
181180 let key = "-" <> T.pack str
182181 _ <- commentNode $ def { _commentNodeConfig_initialContents = "replace-start" <> key }
183182 pure key
@@ -287,7 +286,8 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
287286 es <- newFanEventWithTrigger $ \_ _ -> return (return ())
288287 StaticDomBuilderT $ do
289288 let shouldEscape = elementTag `Set.notMember` noEscapeElements
290− (result, innerHtml) <- lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv shouldEscape Nothing
289+ nextRunWithReplaceKey <- asks _staticDomBuilderEnv_nextRunWithReplaceKey
290+ (result, innerHtml) <- lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv shouldEscape Nothing nextRunWithReplaceKey
291291 attrs0 <- foldDyn applyMap (cfg ^. initialAttributes) (cfg ^. modifyAttributes)
292292 selectValue <- asks _staticDomBuilderEnv_selectValue
293293 let addSelectedAttr attrs sel = case Map.lookup "value" attrs of
@@ -341,7 +341,9 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
341341 selectElement cfg child = do
342342 v <- holdDyn (cfg ^. selectElementConfig_initialValue) (cfg ^. selectElementConfig_setValue)
343343 (e, result) <- element "select" (_selectElementConfig_elementConfig cfg) $ do
344− (a, innerHtml) <- StaticDomBuilderT $ lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv False $ Just (current v)
344+ (a, innerHtml) <- StaticDomBuilderT $ do
345+ nextRunWithReplaceKey <- asks _staticDomBuilderEnv_nextRunWithReplaceKey
346+ lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv False (Just $ current v) nextRunWithReplaceKey
345347 StaticDomBuilderT $ lift $ modify $ (:) innerHtml
346348 return a
347349 let wrapped = SelectElement
@@ -363,7 +365,8 @@ renderStatic :: StaticWidget x a -> IO (a, ByteString)
363365renderStatic w = do
364366 runDomHost $ do
365367 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
366− let env0 = StaticDomBuilderEnv True Nothing
368+ nextRunWithReplaceKey <- newRef 0
369+ let env0 = StaticDomBuilderEnv True Nothing nextRunWithReplaceKey
367370 ((res, bs), FireCommand fire) <- hostPerformEventT $ runStaticDomBuilderT (runPostBuildT w postBuild) env0
368371 mPostBuildTrigger <- readRef postBuildTriggerRef
369372 forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
reflex-dom-core/src/Reflex/Dom/Class.hs+4−7
@@ -4,8 +4,7 @@ module Reflex.Dom.Class ( module Reflex.Dom.Class
44 , module Web.KeyCode
55 ) where
66
7−import Data.Map (Map)
8−import qualified Data.Map as Map
7+import Control.Lens
98import Reflex.Class
109import Web.KeyCode
1110
@@ -13,11 +12,9 @@ import Foreign.JavaScript.TH
1312import Reflex.PerformEvent.Class
1413import Reflex.PostBuild.Class
1514
16−import Prelude hiding (concat, mapM, mapM_, sequence)
17−
18−-- | Alias for Data.Map.singleton
19−(=:) :: k -> a -> Map k a
20−(=:) = Map.singleton
15+-- | Previously an alias for 'Data.Map.singleton', but now generalised to 'At'
16+(=:) :: (At m, Monoid m) => Index m -> IxValue m -> m
17+k =: a = at k ?~ a $ mempty
2118infixr 7 =: -- Ought to bind tighter than <>, which is infixr 6
2219
2320{-# DEPRECATED keycodeEnter "Instead of `x == keycodeEnter`, use `keyCodeLookup x == Enter`" #-}
reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs+5−25
@@ -145,7 +145,9 @@ button t = do
145145
146146--TODO: Should this be renamed to 'widgetView' for consistency with 'widgetHold'?
147147-- | Given a Dynamic of widget-creating actions, create a widget that is recreated whenever the Dynamic updates.
148−-- The returned Event of widget results occurs when the Dynamic does.
148+-- The returned Event occurs whenever the child widget is updated, which is
149+-- at post-build in addition to the times at which the input Dynamic is
150+-- updated, and its value is the result of running the widget.
149151-- Note: Often, the type @a@ is an 'Event', in which case the return value is an Event-of-Events that would typically be flattened (via 'switchHold').
150152dyn :: (Adjustable t m, NotReady t m, PostBuild t m) => Dynamic t (m a) -> m (Event t a)
151153dyn = networkView
@@ -269,34 +271,10 @@ dynamicAttributesToModifyAttributesWithInitial attrs0 d = do
269271 return $ if Map.null p then Nothing else Just p
270272 return modificationsNeeded
271273
272−--------------------------------------------------------------------------------
273−-- Copied and pasted from Reflex.Widget.Class
274−--------------------------------------------------------------------------------
275−
276−{-
277−schedulePostBuild x = performEvent_ . (x <$) =<< getPostBuild
278−
279−elDynHtml' :: DomBuilder t m => Text -> Dynamic t Text -> m (El t)
280−elDynHtml' elementTag html = do
281− e <- buildEmptyElement elementTag (Map.empty :: Map Text Text)
282− schedulePostBuild $ setInnerHTML e . Just =<< sample (current html)
283− performEvent_ $ fmap (setInnerHTML e . Just) $ updated html
284− wrapElement defaultDomEventHandler e
285−
286−elDynHtmlAttr' :: DomBuilder t m => Text -> Map Text Text -> Dynamic t Text -> m (El t)
287−elDynHtmlAttr' elementTag attrs html = do
288− e <- buildEmptyElement elementTag attrs
289− schedulePostBuild $ setInnerHTML e . Just =<< sample (current html)
290− performEvent_ $ fmap (setInnerHTML e . Just) $ updated html
291− wrapElement defaultDomEventHandler e
292−-}
293−
294274newtype Link t
295275 = Link { _link_clicked :: Event t ()
296276 }
297277
298−-- | >>> linkClass "Click here" "link-class"
299−-- > <a class="link-class">Click here</a>
300278linkClass :: DomBuilder t m => Text -> Text -> m (Link t)
301279linkClass s c = do
302280 (l,_) <- elAttr' "a" ("class" =: c) $ text s
@@ -316,6 +294,7 @@ dtdd h w = do
316294blank :: forall m. Monad m => m ()
317295blank = return ()
318296
297+-- TODO: Move to an example project.
319298-- | A widget to display a table with static columns and dynamic rows.
320299tableDynAttr :: forall t m r k v. (Ord k, DomBuilder t m, MonadHold t m, PostBuild t m, MonadFix m)
321300 => Text -- ^ Class applied to <table> element
@@ -332,6 +311,7 @@ tableDynAttr klass cols dRows rowAttrs = elAttr "div" (Map.singleton "style" "zo
332311 dAttrs <- rowAttrs k
333312 elDynAttr' "tr" dAttrs $ mapM (\x -> el "td" $ snd x k r) cols)
334313
314+-- TODO: Move to an example project.
335315-- | A widget to construct a tabbed view that shows only one of its child widgets at a time.
336316-- Creates a header bar containing a <ul> with one <li> per child; clicking a <li> displays
337317-- the corresponding child and hides all others.
reflex-dom-core/src/Reflex/Dom/Widget/Input.hs+5−39
@@ -54,6 +54,7 @@ import qualified Text.Read as T
5454import qualified GHCJS.DOM.Event as Event
5555import qualified GHCJS.DOM.HTMLInputElement as Input
5656
57+{-# DEPRECATED TextInput, _textInput_element, TextInputConfig, textInput "Use 'inputElement' directly" #-}
5758data TextInput t
5859 = TextInput { _textInput_value :: Dynamic t Text
5960 , _textInput_input :: Event t Text
@@ -161,6 +162,7 @@ rangeInput (RangeInputConfig initial eSetValue dAttrs) = do
161162 , _rangeInput_element = _inputElement_raw i
162163 }
163164
165+{-# DEPRECATED TextAreaConfig, TextArea, textArea "Use 'textAreaElement' directly" #-}
164166data TextAreaConfig t
165167 = TextAreaConfig { _textAreaConfig_initialValue :: Text
166168 , _textAreaConfig_setValue :: Event t Text
@@ -198,6 +200,7 @@ textArea (TextAreaConfig initial eSet attrs) = do
198200 , _textArea_element = _textAreaElement_raw i
199201 }
200202
203+{-# DEPRECATED CheckboxConfig, Checkbox, checkbox, checkboxView, CheckboxViewEventResultType, regularToCheckboxViewEventType, CheckboxViewEventResult "Use 'inputElement' directly" #-}
201204data CheckboxConfig t
202205 = CheckboxConfig { _checkboxConfig_setValue :: Event t Bool
203206 , _checkboxConfig_attributes :: Dynamic t (Map Text Text)
@@ -287,7 +290,7 @@ regularToCheckboxViewEventType en r = case en of
287290
288291newtype CheckboxViewEventResult en = CheckboxViewEventResult { unCheckboxViewEventResult :: CheckboxViewEventResultType en }
289292
290−--TODO
293+-- | Create a view only checkbox
291294{-# INLINABLE checkboxView #-}
292295checkboxView :: forall t m. (DomBuilder t m, DomBuilderSpace m ~ GhcjsDomSpace, PostBuild t m, MonadHold t m) => Dynamic t (Map Text Text) -> Dynamic t Bool -> m (Event t Bool)
293296checkboxView dAttrs dValue = do
@@ -321,6 +324,7 @@ checkboxView dAttrs dValue = do
321324 i <- inputElement inputElementConfig
322325 return $ unCheckboxViewEventResult <$> select (_element_events $ _inputElement_element i) (WrapArg Click)
323326
327+{-# DEPRECATED FileInput, FileInputConfig, fileInput "Use 'inputElement' directly" #-}
324328data FileInput d t
325329 = FileInput { _fileInput_value :: Dynamic t [File]
326330 , _fileInput_element :: RawInputElement d
@@ -681,41 +685,3 @@ instance HasValue (Dropdown t k) where
681685instance HasValue (Checkbox t) where
682686 type Value (Checkbox t) = Dynamic t Bool
683687 value = _checkbox_value
684−
685−{-
686−type family Controller sm t a where
687− Controller Edit t a = (a, Event t a) -- Initial value and setter
688− Controller View t a = Dynamic t a -- Value (always)
689−
690−type family Output sm t a where
691− Output Edit t a = Dynamic t a -- Value (always)
692− Output View t a = Event t a -- Requested changes
693−
694−data CheckboxConfig sm t
695− = CheckboxConfig { _checkbox_input :: Controller sm t Bool
696− , _checkbox_attributes :: Attributes
697− }
698−
699−instance Reflex t => Default (CheckboxConfig Edit t) where
700− def = CheckboxConfig (False, never) mempty
701−
702−data Checkbox sm t
703− = Checkbox { _checkbox_output :: Output sm t Bool
704− }
705−
706−data StateMode = Edit | View
707−
708−--TODO: There must be a more generic way to get this witness and allow us to case on the type-level StateMode
709−data StateModeWitness (sm :: StateMode) where
710− EditWitness :: StateModeWitness Edit
711− ViewWitness :: StateModeWitness View
712−
713−class HasStateModeWitness (sm :: StateMode) where
714− stateModeWitness :: StateModeWitness sm
715−
716−instance HasStateModeWitness Edit where
717− stateModeWitness = EditWitness
718−
719−instance HasStateModeWitness View where
720− stateModeWitness = ViewWitness
721−-}
reflex-dom-core/src/Reflex/Dom/Xhr.hs+8−6
@@ -159,6 +159,8 @@ import Data.Aeson.Text
159159import Data.Aeson.Encode
160160#endif
161161import qualified Data.ByteString.Lazy as BL
162+import Data.CaseInsensitive (CI)
163+import qualified Data.CaseInsensitive as CI
162164import Data.Default
163165import qualified Data.List as L
164166import Data.Map (Map)
@@ -198,12 +200,12 @@ data XhrResponse
198200 , _xhrResponse_statusText :: Text
199201 , _xhrResponse_response :: Maybe XhrResponseBody
200202 , _xhrResponse_responseText :: Maybe Text
201− , _xhrResponse_headers :: Map Text Text
203+ , _xhrResponse_headers :: Map (CI Text) Text
202204 }
203205 deriving (Typeable)
204206
205207data XhrResponseHeaders =
206− OnlyHeaders (Set.Set Text) -- ^ Parse a subset of headers from the XHR Response
208+ OnlyHeaders (Set.Set (CI Text)) -- ^ Parse a subset of headers from the XHR Response
207209 | AllHeaders -- ^ Parse all headers from the XHR Response
208210 deriving (Show, Read, Eq, Ord, Typeable)
209211
@@ -275,7 +277,7 @@ newXMLHttpRequestWithError req cb = do
275277 AllHeaders -> parseAllHeadersString <$>
276278 xmlHttpRequestGetAllResponseHeaders xhr
277279 OnlyHeaders xs -> traverse (xmlHttpRequestGetResponseHeader xhr)
278− (Map.fromSet id xs)
280+ (Map.fromSet CI.original xs)
279281 _ <- liftJSM $ cb $ Right
280282 XhrResponse { _xhrResponse_status = status
281283 , _xhrResponse_statusText = statusText
@@ -288,10 +290,10 @@ newXMLHttpRequestWithError req cb = do
288290 return ()
289291 return xhr
290292
291−parseAllHeadersString :: Text -> Map Text Text
293+parseAllHeadersString :: Text -> Map (CI Text) Text
292294parseAllHeadersString s = Map.fromList $ fmap (stripBoth . T.span (/=':')) $
293295 L.dropWhileEnd T.null $ T.splitOn (T.pack "\r\n") s
294− where stripBoth (txt1, txt2) = (T.strip txt1, T.strip $ T.drop 1 txt2)
296+ where stripBoth (txt1, txt2) = (CI.mk $ T.strip txt1, T.strip $ T.drop 1 txt2)
295297
296298newXMLHttpRequest :: (HasJSContext m, MonadJSM m, IsXhrPayload a) => XhrRequest a -> (XhrResponse -> JSM ()) -> m XMLHttpRequest
297299newXMLHttpRequest req cb = newXMLHttpRequestWithError req $ mapM_ cb
@@ -444,7 +446,7 @@ xhrResponse_responseText :: Lens' XhrResponse (Maybe Text)
444446xhrResponse_responseText f (XhrResponse x1 x2 x3 x4 x5) = (\y -> XhrResponse x1 x2 x3 y x5) <$> f x4
445447{-# INLINE xhrResponse_responseText #-}
446448
447−xhrResponse_headers :: Lens' XhrResponse (Map Text Text)
449+xhrResponse_headers :: Lens' XhrResponse (Map (CI Text) Text)
448450xhrResponse_headers f (XhrResponse x1 x2 x3 x4 x5) = (\y -> XhrResponse x1 x2 x3 x4 y) <$> f x5
449451{-# INLINE xhrResponse_headers #-}
450452
reflex-dom-core/test/hydration.hs+94−26
@@ -1,13 +1,9 @@
1−{-# OPTIONS_GHC -fno-warn-orphans #-}
21{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE StandaloneDeriving #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−{-# LANGUAGE UndecidableInstances #-}
62{-# LANGUAGE ConstraintKinds #-}
7−{-# LANGUAGE CPP #-}
83{-# LANGUAGE FlexibleContexts #-}
94{-# LANGUAGE FlexibleInstances #-}
105{-# LANGUAGE GADTs #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
117{-# LANGUAGE LambdaCase #-}
128{-# LANGUAGE MultiParamTypeClasses #-}
139{-# LANGUAGE OverloadedStrings #-}
@@ -15,13 +11,19 @@
1511{-# LANGUAGE RankNTypes #-}
1612{-# LANGUAGE RecursiveDo #-}
1713{-# LANGUAGE ScopedTypeVariables #-}
14+{-# LANGUAGE StandaloneDeriving #-}
1815{-# LANGUAGE TemplateHaskell #-}
1916{-# LANGUAGE TupleSections #-}
2017{-# LANGUAGE TypeApplications #-}
2118{-# LANGUAGE TypeFamilies #-}
19+{-# LANGUAGE UndecidableInstances #-}
20+
21+{-# OPTIONS_GHC -fno-warn-orphans #-}
2222
2323import Prelude hiding (fail)
2424import Control.Concurrent
25+import qualified Control.Concurrent.Async as Async
26+import Control.Lens.Operators
2527import Control.Monad hiding (fail)
2628import Control.Monad.Catch
2729import Control.Monad.Fail
@@ -31,7 +33,7 @@ import Control.Monad.Ref
3133import Data.Constraint.Extras
3234import Data.Constraint.Extras.TH
3335import Data.Dependent.Map (DMap)
34−import Data.Dependent.Sum (DSum(..), (==>), EqTag(..), ShowTag(..))
36+import Data.Dependent.Sum (DSum(..), (==>))
3537import Data.Functor.Identity
3638import Data.Functor.Misc
3739import Data.GADT.Compare.TH
@@ -62,7 +64,6 @@ import Test.Hspec (xit)
6264import Test.Hspec.WebDriver hiding (runWD, click, uploadFile, WD)
6365import qualified Test.Hspec.WebDriver as WD
6466import Test.WebDriver (WD(..))
65−import Test.WebDriver.Exceptions (ServerError(..))
6667
6768import qualified Data.ByteString.Lazy as LBS
6869import qualified Data.Dependent.Map as DMap
@@ -78,6 +79,9 @@ import qualified Test.WebDriver.Capabilities as WD
7879import Test.Util.ChromeFlags
7980import Test.Util.UnshareNetwork
8081
82+-- ORPHAN: https://github.com/kallisti-dev/hs-webdriver/pull/167
83+deriving instance MonadMask WD
84+
8185chromium :: FilePath
8286chromium = $(staticWhich "chromium")
8387
@@ -770,6 +774,15 @@ tests withDebugging wdConfig caps _selenium = do
770774 prerender_ (pure ()) (liftIO $ trigger "Client")
771775 textNode $ TextNodeConfig "Initial" $ Just e
772776
777+ describe "namespaces" $ session' $ do
778+ it "dyn can be nested in namespaced widget" $ runWD $ do
779+ testWidget (pure ()) (checkTextInTag "svg" "one") $ do
780+ let svgRootCfg = def
781+ & (elementConfig_namespace ?~ "http://www.w3.org/2000/svg")
782+ & (elementConfig_initialAttributes .~ ("width" =: "100%" <> "height" =: "100%" <> "viewBox" =: "0 0 1536 2048"))
783+ void $ element "svg" svgRootCfg $ do
784+ dyn_ $ text "one" <$ pure ()
785+
773786 describe "runWithReplace" $ session' $ do
774787 it "works" $ runWD $ do
775788 replaceChan :: Chan Text <- liftIO newChan
@@ -914,6 +927,58 @@ tests withDebugging wdConfig caps _selenium = do
914927 , el "span" . text <$> replace
915928 ]
916929
930+ let checkInnerHtml t x = findElemWithRetry (WD.ByTag t) >>= (`attr` "innerHTML") >>= (`shouldBe` Just x)
931+ it "removes bracketing comments" $ runWD $ do
932+ replaceChan :: Chan () <- liftIO newChan
933+ let
934+ preSwitchover = checkInnerHtml "div" "before|<!--replace-start-0-->inner1<!--replace-end-0-->|after"
935+ check () = do
936+ liftIO $ writeChan replaceChan () -- trigger creation of p tag
937+ _ <- findElemWithRetry $ WD.ByTag "p" -- wait till p tag is created
938+ checkInnerHtml "div" "before|<p>inner2</p>|after"
939+ testWidget' preSwitchover check $ do
940+ replace <- triggerEventWithChan replaceChan
941+ el "div" $ do
942+ text "before|"
943+ _ <- runWithReplace (text "inner1") $ el "p" (text "inner2") <$ replace
944+ text "|after"
945+ it "ignores extra ending bracketing comment" $ runWD $ do
946+ replaceChan :: Chan () <- liftIO newChan
947+ let
948+ preSwitchover = checkInnerHtml "div" "before|<!--replace-start-0-->inner1<!--replace-end-0--><!--replace-end-0-->|after"
949+ check () = do
950+ liftIO $ writeChan replaceChan () -- trigger creation of p tag
951+ _ <- findElemWithRetry $ WD.ByTag "p" -- wait till p tag is created
952+ checkInnerHtml "div" "before|inner2|after"
953+ testWidget' preSwitchover check $ do
954+ replace <- triggerEventWithChan replaceChan
955+ el "div" $ do
956+ text "before|"
957+ _ <- runWithReplace (text "inner1" *> comment "replace-end-0") $ text "inner2" <$ replace
958+ text "|after"
959+ void $ runWithReplace blank $ el "p" blank <$ replace -- Signal tag for end of test
960+ it "ignores missing ending bracketing comments" $ runWD $ do
961+ replaceChan :: Chan () <- liftIO newChan
962+ let
963+ preSwitchover = do
964+ checkInnerHtml "div" "before|<!--replace-start-0-->inner1<!--replace-end-0-->|after"
965+ divEl <- findElemWithRetry (WD.ByTag "div")
966+ let wrongHtml = "<!--replace-start-0-->inner1"
967+ actualHtml :: String <- WD.executeJS
968+ [WD.JSArg divEl, WD.JSArg wrongHtml]
969+ "arguments[0].innerHTML = arguments[1]; return arguments[0].innerHTML"
970+ actualHtml `shouldBe` wrongHtml
971+ check () = do
972+ liftIO $ writeChan replaceChan () -- trigger creation of p tag
973+ _ <- findElemWithRetry $ WD.ByTag "p" -- wait till p tag is created
974+ checkInnerHtml "div" "before|<p>inner2</p>|after"
975+ testWidget' preSwitchover check $ do
976+ replace <- triggerEventWithChan replaceChan
977+ el "div" $ do
978+ text "before|"
979+ _ <- runWithReplace (text "inner1") $ el "p" (text "inner2") <$ replace
980+ text "|after"
981+
917982 describe "traverseDMapWithKeyWithAdjust" $ session' $ do
918983 let widget :: DomBuilder t m => DKey a -> Identity a -> m (Identity a)
919984 widget k (Identity v) = elAttr "li" ("id" =: textKey k) $ do
@@ -1305,8 +1370,8 @@ withRetry a = wait 300
13051370 where wait :: Int -> WD a
13061371 wait 0 = a
13071372 wait n = try a >>= \case
1308− Left (_ :: SomeException) -> do
1309− liftIO $ threadDelay 100000
1373+ Left (e :: SomeException) -> do
1374+ liftIO $ putStrLn ("(retrying due to " <> show e <> ")") *> threadDelay 100000
13101375 wait $ n - 1
13111376 Right v -> return v
13121377
@@ -1388,23 +1453,26 @@ testWidgetDebug' withDebugging beforeJS afterSwitchover bodyWidget = do
13881453 ]
13891454 -- hSilence to get rid of ConnectionClosed logs
13901455 silenceIfDebug = if withDebugging then id else hSilence [stderr]
1391− jsaddleWarp = forkIO $ silenceIfDebug $ Warp.runSettings settings application
1392− jsaddleTid <- liftIO jsaddleWarp
1393− putStrLnDebug "taking waitJSaddle"
1394− liftIO $ takeMVar waitJSaddle
1395− putStrLnDebug "opening page"
1396− WD.openPage $ "http://localhost:" <> show jsaddlePort
1397− putStrLnDebug "running beforeJS"
1398− a <- beforeJS
1399− putStrLnDebug "putting waitBeforeJS"
1400− liftIO $ putMVar waitBeforeJS ()
1401− putStrLnDebug "taking waitUntilSwitchover"
1402− liftIO $ takeMVar waitUntilSwitchover
1403− putStrLnDebug "running afterSwitchover"
1404− b <- afterSwitchover a
1405− putStrLnDebug "killing jsaddle thread"
1406− liftIO $ killThread jsaddleTid
1407− return b
1456+ jsaddleWarp = silenceIfDebug $ Warp.runSettings settings application
1457+ withAsync' jsaddleWarp $ do
1458+ putStrLnDebug "taking waitJSaddle"
1459+ liftIO $ takeMVar waitJSaddle
1460+ putStrLnDebug "opening page"
1461+ WD.openPage $ "http://localhost:" <> show jsaddlePort
1462+ putStrLnDebug "running beforeJS"
1463+ a <- beforeJS
1464+ putStrLnDebug "putting waitBeforeJS"
1465+ liftIO $ putMVar waitBeforeJS ()
1466+ putStrLnDebug "taking waitUntilSwitchover"
1467+ liftIO $ takeMVar waitUntilSwitchover
1468+ putStrLnDebug "running afterSwitchover"
1469+ afterSwitchover a
1470+
1471+withAsync' :: (MonadIO m, MonadMask m) => IO a -> m b -> m b
1472+withAsync' f g = bracket
1473+ (liftIO $ Async.async f)
1474+ (liftIO . Async.uninterruptibleCancel)
1475+ (const g)
14081476
14091477data Key2 a where
14101478 Key2_Int :: Int -> Key2 Int
reflex-dom/ChangeLog.md+15−1
@@ -1,6 +1,19 @@
11# Revision history for reflex-dom
22
3−## 0.5.1
3+## Unreleased
4+
5+* ([#366](https://github.com/reflex-frp/reflex-dom/pull/366)) Bump bounds for `reflex` to include 0.7.
6+
7+## 0.5.3
8+
9+* On Android, enable prompting the user for geolocation
10+ permissions on demand by default. See
11+ https://developer.android.com/reference/android/webkit/WebChromeClient.html#onGeolocationPermissionsShowPrompt(java.lang.String,%20android.webkit.GeolocationPermissions.Callback)
12+ for details.
13+
14+ * Add `< 0.7` upper bound for reflex
15+
16+## 0.5.2
417
518* The default jsaddle backend on macOS when built from nix
619 is now jsaddle-wkwebview, matching the behaviour of cabal
@@ -9,4 +22,5 @@
922## 0.5
1023
1124* Re-export new hydration widget "mainWidget" functions.
25+
1226* The use-warp flag now properly takes precedence on macOS.
reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java+6
@@ -8,6 +8,7 @@ import android.util.Log;
88import android.view.ViewGroup.LayoutParams;
99import android.view.Window;
1010import android.webkit.CookieManager;
11+import android.webkit.GeolocationPermissions;
1112import android.webkit.JavascriptInterface;
1213import android.webkit.MimeTypeMap;
1314import android.webkit.PermissionRequest;
@@ -122,6 +123,11 @@ public class MainWidget {
122123 public Bitmap getDefaultVideoPoster() {
123124 return Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
124125 }
126+
127+ @Override
128+ public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
129+ callback.invoke(origin, true, false);
130+ }
125131 });
126132
127133 wv.addJavascriptInterface(new JSaddleCallbacks(jsaddleCallbacks), "jsaddle");
reflex-dom/reflex-dom.cabal+11−6
@@ -1,5 +1,5 @@
11Name: reflex-dom
2−Version: 0.5.2.0
2+Version: 0.5.3
33Synopsis: Functional Reactive Web Apps with Reflex
44Description:
55 Reflex-DOM is a Functional Reactive web framework based on the Reflex FRP engine.
@@ -59,8 +59,8 @@ library
5959 build-depends:
6060 base >= 4.7 && < 4.13,
6161 bytestring == 0.10.*,
62− reflex >= 0.6.2,
63− reflex-dom-core >=0.5.2 && <0.6,
62+ reflex >= 0.6.2 && < 0.8,
63+ reflex-dom-core >=0.5.2 && < 0.6,
6464 text == 1.2.*
6565 if !impl(ghcjs)
6666 if flag(use-warp) && (os(linux) || os(osx))
@@ -74,13 +74,18 @@ library
7474 jsaddle,
7575 jsaddle-wkwebview
7676 else
77− if flag(webkit2gtk)
77+ if arch(wasm32)
7878 build-depends:
79− jsaddle-webkit2gtk
79+ jsaddle,
80+ jsaddle-wasm
8081 else
81− if !os(android)
82+ if flag(webkit2gtk)
8283 build-depends:
8384 jsaddle-webkit2gtk
85+ else
86+ if !os(android)
87+ build-depends:
88+ jsaddle-webkit2gtk
8489
8590 exposed-modules:
8691 Reflex.Dom
reflex-dom/src/Reflex/Dom/Internal.hs+5
@@ -80,6 +80,11 @@ run jsm = do
8080 startMainWidget a startPage jsm
8181 }
8282 forever $ threadDelay 1000000000
83+#elif defined(wasm32_HOST_ARCH)
84+import qualified Language.Javascript.JSaddle.Wasm as Wasm (run)
85+import Language.Javascript.JSaddle (JSM)
86+run :: JSM () -> IO ()
87+run = Wasm.run 0
8388#else
8489import Language.Javascript.JSaddle.WebKitGTK (run)
8590#endif
release.nix+27−8
@@ -17,16 +17,35 @@ let
1717 ] ++ lib.optionals (reflex-platform.iosSupport) [
1818 "ghcIosAarch64"
1919 ];
20− hsPkgs = lib.genAttrs compilers (ghc: let
21− ghc' = reflex-platform.${ghc}.override {
22− overrides = self: super: import ./. self;
20+ compilerPkgs = lib.genAttrs compilers (ghc: let
21+ reflex-platform = reflex-platform-fun {
22+ inherit system;
23+ haskellOverlays = [
24+ # Use this package's source for reflex
25+ (self: super: {
26+ _dep = super._dep // {
27+ reflex-dom = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [
28+ "release.nix"
29+ ".git"
30+ "dist"
31+ ])) ./.;
32+ };
33+ })
34+ ];
2335 };
24− in {
25− inherit (ghc') reflex-dom reflex-dom-core;
36+ all = {
37+ inherit (reflex-platform.${ghc})
38+ reflex-dom-core
39+ reflex-dom
40+ ;
41+ };
42+ in all // {
43+ cache = reflex-platform.pinBuildInputs "reflex-${system}-${ghc}"
44+ (builtins.attrValues all);
2645 });
27− in hsPkgs // {
28− cache = reflex-platform.pinBuildInputs "reflex-dom-${system}"
29− (lib.concatLists (map builtins.attrValues (builtins.attrValues hsPkgs)));
46+ in compilerPkgs // {
47+ cache = reflex-platform.pinBuildInputs "reflex-${system}"
48+ (map (a: a.cache) (builtins.attrValues compilerPkgs));
3049 });
3150
3251 metaCache = native-reflex-platform.pinBuildInputs "reflex-dom-everywhere"