@@ -10,6 +10,7 @@
10{-# LANGUAGE OverloadedStrings #-}
10{-# LANGUAGE OverloadedStrings #-}
11{-# LANGUAGE PolyKinds #-}
11{-# LANGUAGE PolyKinds #-}
12{-# LANGUAGE RankNTypes #-}
12{-# LANGUAGE RankNTypes #-}
13+{-# LANGUAGE RecursiveDo #-}
13{-# LANGUAGE ScopedTypeVariables #-}
14{-# LANGUAGE ScopedTypeVariables #-}
14{-# LANGUAGE TemplateHaskell #-}
15{-# LANGUAGE TemplateHaskell #-}
15{-# LANGUAGE TypeFamilies #-}
16{-# LANGUAGE TypeFamilies #-}
@@ -17,23 +18,32 @@
17module Reflex.Dom.Builder.Class
18module Reflex.Dom.Builder.Class
18 ( module Reflex.Dom.Builder.Class
19 ( module Reflex.Dom.Builder.Class
19 , module Reflex.Dom.Builder.Class.Events
20 , module Reflex.Dom.Builder.Class.Events
20− , module Reflex.Dom.Deletable.Class
21+ , module Reflex.Deletable.Class
23−import Reflex
24+import Reflex.Class as Reflex
24import Reflex.Dom.Builder.Class.Events
25import Reflex.Dom.Builder.Class.Events
25−import Reflex.Dom.Deletable.Class
26+import Reflex.Deletable.Class
27+import Reflex.DynamicWriter
28+import Reflex.PerformEvent.Class
29+import Reflex.PostBuild.Class
27import qualified Control.Category
31import qualified Control.Category
28import Control.Lens hiding (element)
32import Control.Lens hiding (element)
29import Control.Monad.Reader
33import Control.Monad.Reader
34+import Control.Monad.State.Strict
30import Control.Monad.Trans.Control
35import Control.Monad.Trans.Control
31import Data.Default
36import Data.Default
32import Data.Functor.Misc
38import Data.Functor.Misc
39+import Data.List.NonEmpty (NonEmpty, nonEmpty)
33import Data.Map (Map)
40import Data.Map (Map)
41+import qualified Data.Map as Map
34import Data.Proxy
42import Data.Proxy
35import Data.Semigroup
43import Data.Semigroup
36import Data.Text (Text)
45import Data.Text (Text)
46+import Data.Traversable
37import Data.Type.Coercion
47import Data.Type.Coercion
38import GHC.Exts (Constraint)
48import GHC.Exts (Constraint)
@@ -43,19 +53,97 @@ class Default (EventSpec d EventResult) => DomSpace d where
43 type RawElement d :: *
53 type RawElement d :: *
44 type RawInputElement d :: *
54 type RawInputElement d :: *
45 type RawTextAreaElement d :: *
55 type RawTextAreaElement d :: *
56+ type RawSelectElement d :: *
57+ addEventSpecFlags :: proxy d -> EventName en -> (Maybe (er en) -> EventFlags) -> EventSpec d er -> EventSpec d er
59+{-# INLINABLE liftElementConfig #-}
60+liftElementConfig :: (DomBuilderSpace (f m) ~ DomBuilderSpace m) => ElementConfig er t (f m) -> ElementConfig er t m
61+liftElementConfig cfg = cfg
62+ { _elementConfig_eventSpec = _elementConfig_eventSpec cfg
47−-- | @'DomBuilder' t m@ indicates that @m@ is a 'Monad' capable of building dynamic DOM in the 'Reflex' timeline @t@
65+-- | @'DomBuilder' t m@ indicates that @m@ is a 'Monad' capable of building
66+-- dynamic DOM in the 'Reflex' timeline @t@
48class (Monad m, Reflex t, Deletable t m, DomSpace (DomBuilderSpace m)) => DomBuilder t m | m -> t where
67class (Monad m, Reflex t, Deletable t m, DomSpace (DomBuilderSpace m)) => DomBuilder t m | m -> t where
49 type DomBuilderSpace m :: *
68 type DomBuilderSpace m :: *
50 textNode :: TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
69 textNode :: TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
70+ default textNode :: ( MonadTrans f
72+ , DomBuilderSpace m' ~ DomBuilderSpace m
75+ => TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
76+ textNode = lift . textNode
77+ {-# INLINABLE textNode #-}
51 element :: Text -> ElementConfig er t m -> m a -> m (Element er (DomBuilderSpace m) t, a)
78 element :: Text -> ElementConfig er t m -> m a -> m (Element er (DomBuilderSpace m) t, a)
79+ default element :: ( MonadTransControl f
82+ , DomBuilderSpace m' ~ DomBuilderSpace m
85+ => Text -> ElementConfig er t m -> m a -> m (Element er (DomBuilderSpace m) t, a)
86+ element t cfg child = liftWith $ \run -> element t (liftElementConfig cfg) $ run child
87+ {-# INLINABLE element #-}
52 -- | Create a placeholder in the DOM, with the ability to insert new DOM before it
88 -- | Create a placeholder in the DOM, with the ability to insert new DOM before it
53 -- The provided DOM will be executed after the current frame, so it will not be affected by any occurrences that are concurrent with the occurrence that created it
89 -- The provided DOM will be executed after the current frame, so it will not be affected by any occurrences that are concurrent with the occurrence that created it
54 placeholder :: PlaceholderConfig above t m -> m (Placeholder above t)
90 placeholder :: PlaceholderConfig above t m -> m (Placeholder above t)
55 inputElement :: InputElementConfig er t m -> m (InputElement er (DomBuilderSpace m) t)
91 inputElement :: InputElementConfig er t m -> m (InputElement er (DomBuilderSpace m) t)
92+ default inputElement :: ( MonadTransControl f
94+ , DomBuilderSpace m' ~ DomBuilderSpace m
97+ => InputElementConfig er t m -> m (InputElement er (DomBuilderSpace m) t)
98+ inputElement cfg = lift $ inputElement $ cfg
99+ { _inputElementConfig_elementConfig = liftElementConfig $ _inputElementConfig_elementConfig cfg
101+ {-# INLINABLE inputElement #-}
56 textAreaElement :: TextAreaElementConfig er t m -> m (TextAreaElement er (DomBuilderSpace m) t)
102 textAreaElement :: TextAreaElementConfig er t m -> m (TextAreaElement er (DomBuilderSpace m) t)
103+ default textAreaElement :: ( MonadTransControl f
105+ , DomBuilderSpace m' ~ DomBuilderSpace m
108+ => TextAreaElementConfig er t m -> m (TextAreaElement er (DomBuilderSpace m) t)
109+ textAreaElement cfg = lift $ textAreaElement $ cfg
110+ { _textAreaElementConfig_elementConfig = liftElementConfig $ _textAreaElementConfig_elementConfig cfg
112+ {-# INLINABLE textAreaElement #-}
113+ selectElement :: SelectElementConfig er t m -> m a -> m (SelectElement er (DomBuilderSpace m) t, a)
114+ default selectElement :: ( MonadTransControl f
117+ , DomBuilderSpace m' ~ DomBuilderSpace m
120+ => SelectElementConfig er t m -> m a -> m (SelectElement er (DomBuilderSpace m) t, a)
121+ selectElement cfg child = do
123+ { _selectElementConfig_elementConfig = liftElementConfig $ _selectElementConfig_elementConfig cfg
125+ liftWith $ \run -> selectElement cfg' $ run child
126+ {-# INLINABLE selectElement #-}
57 placeRawElement :: RawElement (DomBuilderSpace m) -> m ()
127 placeRawElement :: RawElement (DomBuilderSpace m) -> m ()
128+ default placeRawElement :: ( MonadTrans f
130+ , DomBuilderSpace m' ~ DomBuilderSpace m
133+ => RawElement (DomBuilderSpace m) -> m ()
134+ placeRawElement = lift . placeRawElement
135+ {-# INLINABLE placeRawElement #-}
58 wrapRawElement :: RawElement (DomBuilderSpace m) -> RawElementConfig er t m -> m (Element er (DomBuilderSpace m) t)
136 wrapRawElement :: RawElement (DomBuilderSpace m) -> RawElementConfig er t m -> m (Element er (DomBuilderSpace m) t)
137+ default wrapRawElement :: ( MonadTrans f
139+ , DomBuilderSpace m' ~ DomBuilderSpace m
142+ => RawElement (DomBuilderSpace m) -> RawElementConfig er t m -> m (Element er (DomBuilderSpace m) t)
143+ wrapRawElement e cfg = lift $ wrapRawElement e $ cfg
144+ { _rawElementConfig_eventSpec = _rawElementConfig_eventSpec cfg
146+ {-# INLINABLE wrapRawElement #-}
60type Namespace = Text
148type Namespace = Text
@@ -75,7 +163,11 @@ data TextNode d t = TextNode
75 { _textNode_raw :: RawTextNode d
163 { _textNode_raw :: RawTextNode d
78−type AttributeName = (Maybe Namespace, Text)
166+data AttributeName = AttributeName !(Maybe Namespace) !Text deriving (Show, Read, Eq, Ord)
168+-- | By default, AttributeNames are unnamespaced
169+instance IsString AttributeName where
170+ fromString = AttributeName Nothing . fromString
80data Propagation
172data Propagation
81 = Propagation_Continue
173 = Propagation_Continue
@@ -121,15 +213,6 @@ data ElementConfig er t m
121 , _elementConfig_eventSpec :: EventSpec (DomBuilderSpace m) er
213 , _elementConfig_eventSpec :: EventSpec (DomBuilderSpace m) er
124−instance (Reflex t, er ~ EventResult, DomBuilder t m) => Default (ElementConfig er t m) where
125− {-# INLINABLE def #-}
127− { _elementConfig_namespace = Nothing
128− , _elementConfig_initialAttributes = mempty
129− , _elementConfig_modifyAttributes = never
130− , _elementConfig_eventSpec = def
133data Element er d t
216data Element er d t
134 = Element { _element_events :: EventSelector t (WrapArg er EventName) --TODO: EventSelector should have two arguments
217 = Element { _element_events :: EventSelector t (WrapArg er EventName) --TODO: EventSelector should have two arguments
135 , _element_raw :: RawElement d
218 , _element_raw :: RawElement d
@@ -219,12 +302,36 @@ instance (Reflex t, DomSpace (DomBuilderSpace m)) => Default (RawElementConfig E
219 , _rawElementConfig_eventSpec = def
302 , _rawElementConfig_eventSpec = def
305+data SelectElementConfig er t m = SelectElementConfig
306+ { _selectElementConfig_initialValue :: Text
307+ , _selectElementConfig_setValue :: Event t Text
308+ , _selectElementConfig_elementConfig :: ElementConfig er t m
311+instance (Reflex t, er ~ EventResult, DomBuilder t m) => Default (SelectElementConfig er t m) where
312+ def = SelectElementConfig
313+ { _selectElementConfig_initialValue = ""
314+ , _selectElementConfig_setValue = never
315+ , _selectElementConfig_elementConfig = def
318+data SelectElement er d t = SelectElement
319+ { _selectElement_element :: Element er d t
320+ , _selectElement_value :: Dynamic t Text
321+ , _selectElement_change :: Event t Text -- ^ Fires when the value is changed by the user, but not when it is set by setValue
322+ , _selectElement_hasFocus :: Dynamic t Bool
323+ , _selectElement_raw :: RawSelectElement d
326+concat <$> mapM makeLenses
222−makeLenses ''TextNodeConfig
327+ [ ''TextNodeConfig
223−makeLenses ''ElementConfig
328+ , ''ElementConfig
224−makeLenses ''PlaceholderConfig
329+ , ''PlaceholderConfig
225−makeLenses ''InputElementConfig
330+ , ''InputElementConfig
226−makeLenses ''TextAreaElementConfig
331+ , ''TextAreaElementConfig
332+ , ''SelectElementConfig
227−makeLenses ''RawElementConfig
333+ , ''RawElementConfig
229class CanDeleteSelf t a | a -> t where
336class CanDeleteSelf t a | a -> t where
230 deleteSelf :: Lens' a (Event t ())
337 deleteSelf :: Lens' a (Event t ())
@@ -246,6 +353,14 @@ instance InitialAttributes (ElementConfig er t m) where
246 {-# INLINABLE initialAttributes #-}
353 {-# INLINABLE initialAttributes #-}
247 initialAttributes = elementConfig_initialAttributes
354 initialAttributes = elementConfig_initialAttributes
356+instance InitialAttributes (InputElementConfig er t m) where
357+ {-# INLINABLE initialAttributes #-}
358+ initialAttributes = inputElementConfig_elementConfig . elementConfig_initialAttributes
360+instance InitialAttributes (TextAreaElementConfig er t m) where
361+ {-# INLINABLE initialAttributes #-}
362+ initialAttributes = textAreaElementConfig_elementConfig . elementConfig_initialAttributes
249class ModifyAttributes t a | a -> t where
364class ModifyAttributes t a | a -> t where
250 modifyAttributes :: Lens' a (Event t (Map AttributeName (Maybe Text)))
365 modifyAttributes :: Lens' a (Event t (Map AttributeName (Maybe Text)))
@@ -253,6 +368,14 @@ instance ModifyAttributes t (ElementConfig er t m) where
253 {-# INLINABLE modifyAttributes #-}
368 {-# INLINABLE modifyAttributes #-}
254 modifyAttributes = elementConfig_modifyAttributes
369 modifyAttributes = elementConfig_modifyAttributes
371+instance ModifyAttributes t (InputElementConfig er t m) where
372+ {-# INLINABLE modifyAttributes #-}
373+ modifyAttributes = inputElementConfig_elementConfig . elementConfig_modifyAttributes
375+instance ModifyAttributes t (TextAreaElementConfig er t m) where
376+ {-# INLINABLE modifyAttributes #-}
377+ modifyAttributes = textAreaElementConfig_elementConfig . elementConfig_modifyAttributes
256instance ModifyAttributes t (RawElementConfig er t m) where
379instance ModifyAttributes t (RawElementConfig er t m) where
257 {-# INLINABLE modifyAttributes #-}
380 {-# INLINABLE modifyAttributes #-}
258 modifyAttributes = rawElementConfig_modifyAttributes
381 modifyAttributes = rawElementConfig_modifyAttributes
@@ -264,6 +387,74 @@ instance HasNamespace (ElementConfig er t m) where
264 {-# INLINABLE namespace #-}
387 {-# INLINABLE namespace #-}
265 namespace = elementConfig_namespace
388 namespace = elementConfig_namespace
390+instance (Reflex t, er ~ EventResult, DomBuilder t m) => Default (ElementConfig er t m) where
391+ {-# INLINABLE def #-}
393+ { _elementConfig_namespace = Nothing
394+ , _elementConfig_initialAttributes = mempty
395+ , _elementConfig_modifyAttributes = never
396+ , _elementConfig_eventSpec = def
399+instance (DomBuilder t m, PerformEvent t m, MonadFix m, MonadHold t m) => DomBuilder t (PostBuildT t m) where
400+ type DomBuilderSpace (PostBuildT t m) = DomBuilderSpace m
401+ {-# INLINABLE placeholder #-}
402+ placeholder cfg = lift $ do
403+ rec childPostBuild <- deletable (_placeholder_deletedSelf p) $ performEvent $ return () <$ _placeholder_insertedAbove p
404+ p <- placeholder $ cfg
405+ { _placeholderConfig_insertAbove = ffor (_placeholderConfig_insertAbove cfg) $ \a -> runPostBuildT a =<< headE childPostBuild
408+ wrapRawElement e cfg = liftWith $ \run -> wrapRawElement e $ fmap1 run cfg
410+instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (DynamicWriterT t w m) where
411+ type DomBuilderSpace (DynamicWriterT t w m) = DomBuilderSpace m
412+ textNode = liftTextNode
413+ element elementTag cfg (DynamicWriterT child) = DynamicWriterT $ do
415+ let cfg' = liftElementConfig cfg
416+ (el, (a, newS)) <- lift $ element elementTag cfg' $ runStateT child s
419+ placeholder cfg = do
421+ { _placeholderConfig_insertAbove = runDynamicWriterTInternal <$> _placeholderConfig_insertAbove cfg
423+ let manageChildren :: Event t (NonEmpty (Replaceable t (Dynamic t w))) -- ^ Add nodes on the right; these are in reverse order
424+ -> Event t () -- ^ No more nodes will be added after this event fires
425+ -> m (Replaceable t (Dynamic t w))
426+ manageChildren newChildren additionsCeased = do
427+ rec nextId <- hold (0 :: Int) newNextId -- We assume this will never wrap around
428+ let numberedNewChildren :: Event t (Int, PatchMap (Map Int (Replaceable t (Dynamic t w))))
429+ numberedNewChildren = flip pushAlways newChildren $ \rcs -> do
430+ let cs = reverse $ toList rcs
431+ myFirstId <- sample nextId
432+ let (myNextId, numbered) = mapAccumL (\n v -> (succ n, (n, Just v))) myFirstId cs
433+ return (myNextId, PatchMap $ Map.fromList numbered)
434+ newNextId = fst <$> numberedNewChildren
435+ mconcatIncrementalReplaceableDynMap Map.empty (snd <$> numberedNewChildren) additionsCeased
436+ rec children <- lift $ manageChildren childOutputs $ cfg ^. deleteSelf
437+ p <- DynamicWriterT $ do
439+ lift $ placeholder cfg'
440+ let result = fst <$> _placeholder_insertedAbove p
441+ childOutputs = fmapMaybe (nonEmpty . snd) $ _placeholder_insertedAbove p
443+ { _placeholder_insertedAbove = result
445+ inputElement cfg = lift $ inputElement $ cfg & inputElementConfig_elementConfig %~ liftElementConfig
446+ textAreaElement cfg = lift $ textAreaElement $ cfg & textAreaElementConfig_elementConfig %~ liftElementConfig
447+ selectElement cfg (DynamicWriterT child) = DynamicWriterT $ do
449+ let cfg' = cfg & selectElementConfig_elementConfig %~ liftElementConfig
450+ (el, (a, newS)) <- lift $ selectElement cfg' $ runStateT child s
453+ placeRawElement = lift . placeRawElement
454+ wrapRawElement e cfg = lift $ wrapRawElement e $ cfg
455+ { _rawElementConfig_eventSpec = _rawElementConfig_eventSpec cfg
267-- * Convenience functions
458-- * Convenience functions
269--TODO: Move/replace
460--TODO: Move/replace
@@ -293,6 +484,10 @@ instance Functor1 (TextAreaElementConfig er t) where
293 type Functor1Constraint (TextAreaElementConfig er t) a b = Functor1Constraint (ElementConfig er t) a b
484 type Functor1Constraint (TextAreaElementConfig er t) a b = Functor1Constraint (ElementConfig er t) a b
294 fmap1 f cfg = cfg & textAreaElementConfig_elementConfig %~ fmap1 f
485 fmap1 f cfg = cfg & textAreaElementConfig_elementConfig %~ fmap1 f
487+instance Functor1 (SelectElementConfig er t) where
488+ type Functor1Constraint (SelectElementConfig er t) a b = Functor1Constraint (ElementConfig er t) a b
489+ fmap1 f cfg = cfg & selectElementConfig_elementConfig %~ fmap1 f
296instance Functor1 (RawElementConfig er t) where
491instance Functor1 (RawElementConfig er t) where
297 type Functor1Constraint (RawElementConfig er t) a b = DomBuilderSpace a ~ DomBuilderSpace b
492 type Functor1Constraint (RawElementConfig er t) a b = DomBuilderSpace a ~ DomBuilderSpace b
298 {-# INLINABLE fmap1 #-}
493 {-# INLINABLE fmap1 #-}
@@ -321,14 +516,7 @@ instance Reflex t => HasDomEvent t (TextAreaElement EventResult d t) en where
322instance DomBuilder t m => DomBuilder t (ReaderT r m) where
517instance DomBuilder t m => DomBuilder t (ReaderT r m) where
323 type DomBuilderSpace (ReaderT r m) = DomBuilderSpace m
518 type DomBuilderSpace (ReaderT r m) = DomBuilderSpace m
324− {-# INLINABLE textNode #-}
325− textNode = liftTextNode
326− element = liftElement
327 placeholder = liftPlaceholder
519 placeholder = liftPlaceholder
328− inputElement = liftInputElement
329− textAreaElement = liftTextAreaElement
330− placeRawElement = lift . placeRawElement
331− wrapRawElement = liftWrapRawElement
333type LiftDomBuilder t f m =
521type LiftDomBuilder t f m =
334 ( Reflex t
522 ( Reflex t