ghcjs-vdom (empty) → 0.2.0.0
raw patch · 30 files changed
+5281/−0 lines, 30 filesdep +basedep +containersdep +ghc-primsetup-changed
Dependencies added: base, containers, ghc-prim, ghcjs-base, ghcjs-ffiqq, ghcjs-prim, ghcjs-vdom, split, template-haskell
Files
- LICENSE +20/−0
- README.markdown +4/−0
- Setup.hs +2/−0
- data/example.css +28/−0
- examples/Components.hs +116/−0
- examples/Render.hs +52/−0
- examples/Table.hs +107/−0
- ghcjs-vdom.cabal +108/−0
- jsbits/vdom.js +23/−0
- src/GHCJS/VDOM.hs +106/−0
- src/GHCJS/VDOM/Attribute.hs +37/−0
- src/GHCJS/VDOM/Component.hs +60/−0
- src/GHCJS/VDOM/DOMComponent.hs +47/−0
- src/GHCJS/VDOM/Element.hs +16/−0
- src/GHCJS/VDOM/Element/Builtin.hs +30/−0
- src/GHCJS/VDOM/Event.hs +198/−0
- src/GHCJS/VDOM/Internal.hs +199/−0
- src/GHCJS/VDOM/Internal/TH.hs +73/−0
- src/GHCJS/VDOM/Internal/Thunk.hs +71/−0
- src/GHCJS/VDOM/Internal/Types.hs +83/−0
- src/GHCJS/VDOM/QQ.hs +88/−0
- src/GHCJS/VDOM/Render.hs +104/−0
- src/GHCJS/VDOM/Unsafe.hs +31/−0
- virtual-dom/LICENSE +19/−0
- virtual-dom/README.md +13/−0
- virtual-dom/diff.js +445/−0
- virtual-dom/handle-thunk.js +18/−0
- virtual-dom/lib.js +2710/−0
- virtual-dom/lib.require.js +457/−0
- virtual-dom/package.json +16/−0
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2014 Luite Stegeman++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.markdown view
@@ -0,0 +1,4 @@+# virtual-dom bindings++[virtual-dom](https://github.com/Matt-Esch/virtual-dom) is a library for fast incremental+DOM updates by comparing immutable virtual DOM trees.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ data/example.css view
@@ -0,0 +1,28 @@+body {+ background-color: #ccc;+}++div.row {+ padding: 0;+ margin: 0;+ height: 5px;+ line-height: 5px;+ text-size: 10%;+}++div.pixel-red, div.pixel-white {+ vertical-align: top;+ display: inline-block;+ width: 5px;+ height: 5px;+ margin: 0;+ padding: 0;+}++div.pixel-red {+ background-color: #f00;+}++div.pixel-white {+ background-color: #fff;+}
+ examples/Components.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ScopedTypeVariables #-}++{-+ ghcjs-vdom example, demonstrating components+ -}++module Main where++import GHCJS.Foreign.QQ+import GHCJS.Types++import qualified GHCJS.VDOM.Component as C+import qualified GHCJS.VDOM.DOMComponent as D+import qualified GHCJS.VDOM.Attribute as A+import qualified GHCJS.VDOM.Element as E+import qualified GHCJS.VDOM.Event as Ev+import GHCJS.VDOM++import qualified Data.JSString.Int as JSS++import Data.IORef+import Data.Monoid+import qualified Data.Map as M++import Control.Concurrent+import Control.Monad++import System.IO++{-+ example virtual-dom component:+ a simple counter component that increments on click+ -}+data Counter = Counter { counterComp :: VComp+ , counterKey :: Int+ , getCount :: IO Int+ }++mkCounter :: JSString -> Int -> IO Counter+mkCounter description startValue = do+ val <- newIORef startValue+ let description' = E.text (description <> ": ")+ c <- fixIO $ \c ->+ -- example only: diff and patch should really done through a Renderer+ let repaint = C.render c >>= C.diff c >>= C.patch c+ increment = modifyIORef' val (+1) >> repaint >> return ()+ in C.mkComponent $ do+ v <- readIORef val+ return $ E.div (A.class_ "counter", Ev.click (const increment))+ [description', E.text (JSS.decimal v)]+ return $ Counter c startValue (readIORef val)++-- example DOM component+data Scroller = Scroller { scrollerComp :: DComp+ , scrollerKey :: Int+ }++mkScroller :: JSString -> Int -> IO Scroller+mkScroller txt k = do+ mounts <- newIORef M.empty+ let mountScroller m = do+ (n::JSVal) <- [js| document.createElement('div') |]+ (t::JSVal) <- [js| document.createTextNode(`txt) |]+ [jsu_| `n.appendChild(`t); |]+ thr <- forkIO . forever $ do+ threadDelay 200000+ [jsu_| `t.data = `t.data.substr(1) + `t.data.substr(0,1); |]+ atomicModifyIORef mounts ((,()) . M.insert m thr)+ return n+ unmountScroller m _ = do+ Just thr <- M.lookup m <$> readIORef mounts+ killThread thr+ atomicModifyIORef mounts ((,()) . M.delete m)+ return ()+ c <- D.mkComponent mountScroller unmountScroller+ return (Scroller c k)++renderCounterList :: [Counter] -> VNode+renderCounterList counters =+ E.ul (A.class_ "counterList")+ (map (\c -> E.li (A.key (counterKey c)) (C.toNode (counterComp c)))+ counters)++renderScrollerList :: [Scroller] -> VNode+renderScrollerList scrollers =+ E.ul (A.class_ "scrollerList")+ (map (\s -> E.li (A.key (scrollerKey s)) (D.toNode (scrollerComp s)))+ scrollers)++render :: [Counter] -> [Scroller] -> VNode+render counters scrollers =+ E.div () [renderCounterList counters, renderScrollerList scrollers]++main :: IO ()+main = do+ Ev.initEventDelegation Ev.defaultEvents+ root <- [js| document.createElement('div') |]+ [js_| document.body.appendChild(`root); |]+ counters <- mapM (\i -> mkCounter ("counter " <> JSS.decimal i) i) [1..10]+ scrollers <- mapM (\i -> mkScroller ("scroller " <> JSS.decimal i) i) [1..10]+ m <- mount root (render counters scrollers)+ rotateComponents m 11 counters scrollers++rotateComponents :: VMount -> Int -> [Counter] -> [Scroller] -> IO ()+rotateComponents m n counters scrollers = do+ threadDelay 1000000+ newCounter <- mkCounter ("counter " <> JSS.decimal n) n+ newScroller <- mkScroller ("scroller " <> JSS.decimal n) n+ let scrollers' = tail scrollers ++ [newScroller]+ counters' = tail counters ++ [newCounter]+ void $ diff m (render counters' scrollers') >>= patch m+ rotateComponents m (n+1) counters' scrollers'
+ examples/Render.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE QuasiQuotes, OverloadedStrings, BangPatterns #-}++{-+ ghcjs-vdom example, demonstrating the render queue+ -}++module Main where++import Control.Concurrent+import Control.Monad++import Data.IORef+import Data.Monoid++import GHCJS.Foreign.QQ+import GHCJS.Types++import qualified GHCJS.VDOM.Component as C+import qualified GHCJS.VDOM.Attribute as A+import qualified GHCJS.VDOM.Element as E+import qualified GHCJS.VDOM.Render as R+import GHCJS.VDOM++import qualified Data.JSString.Int as JSS+++-- a component with a slowed down rendering function+mkSlow :: JSString -> Int -> IO VComp+mkSlow descr delay = do+ count <- newIORef (0::Int)+ let descr' = E.text (descr <> " " <> JSS.decimal delay <> ": ")+ C.mkComponent $ do+ threadDelay delay+ c <- atomicModifyIORef count (\x -> let x' = x+1 in (x',x'))+ return $ E.div (A.class_ "slow") [descr', E.text (JSS.decimal c)]++main :: IO ()+main = do+ root <- [js| document.createElement('div') |]+ [js_| document.body.appendChild(`root); |]+ slows1 <- mapM (mkSlow "slow1") [10000, 50000, 100000, 300000]+ slows2 <- mapM (mkSlow "slow2") [10000, 50000, 100000]+ void $ mount root (E.div () (map C.toNode (slows1++slows2)))+ r1 <- R.mkRenderer+ r2 <- R.mkRenderer+ forever $ do+ mapM_ (R.render r1) slows1+ mapM_ (R.render r2) slows2+ threadDelay 10000+++
+ examples/Table.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE QuasiQuotes, OverloadedStrings, BangPatterns #-}++{-+ virtual-dom bindings demo, rendering a large pixel grid with a bouncing red+ square. the step and patch are calculated asynchronously, the update is+ batched in an animation frame+ -}++module Main where++import Control.Monad++import Data.IntMap (IntMap)+import qualified Data.IntMap as IM+import qualified Data.JSString as JSS++import GHCJS.VDOM+import GHCJS.VDOM.QQ+import qualified GHCJS.VDOM.Element as E+import qualified GHCJS.VDOM.Attribute as A++import GHCJS.Foreign.Callback+import GHCJS.Foreign.QQ+import GHCJS.Types++import JavaScript.Web.AnimationFrame (inAnimationFrame)++red :: JSString+red = "pixel-red"++white :: JSString+white = "pixel-white"++type Pixels = IntMap (IntMap JSString)++setPixel :: Int -> Int -> JSString -> Pixels -> Pixels+setPixel x y c p =+ let r = p IM.! y+ r' = IM.insert x c r+ in r' `seq` IM.insert y r' p++data State = State { x :: !Int, y :: !Int+ , dx :: !Int, dy :: !Int+ , w :: !Int, h :: !Int+ , pixels :: !Pixels+ }++mkState :: Int -> Int -> Int -> Int -> State+mkState w h x y = State x y 1 1 w h pix+ where+ pix = IM.fromList $ map row [0..h-1]+ row n = (n, IM.fromList (map (col n) [0..w-1]))+ col n m = (m, if (m,n)==(x,y) then red else white)++step :: State -> State+step (State x y dx dy w h p) =+ let dx' = if x==0 then 1 else if x==(w-1) then -1 else dx+ dy' = if y==0 then 1 else if y==(h-1) then -1 else dy+ x' = x+dx'+ y' = y+dy'+ p' = setPixel x' y' red (setPixel x y white p)+ in State x' y' dx' dy' w h p'++cls :: JSString -> Attributes'+cls name = [att| className: name |]++render :: State -> VNode+render s = E.div (cls "state") [ch|pixelDiv,numDiv|]+ where+ xd = textDiv (y s)+ yd = textDiv (x s)+ numDiv = E.div (cls "numeric") [ch|xd,yd|]+ pixelDiv = E.div (cls "pixels")+ (map (renderRowM (w s) . (pixels s IM.!)) [0..h s-1])++textDiv :: Show a => a -> VNode+textDiv x = E.div () [ch|c|]+ where+ c = E.text . JSS.pack . show $ x++renderRowM !w !r = memo renderRow w r++renderRow :: Int -> IntMap JSString -> VNode+renderRow w r =+ E.div (A.class_ "row", A.lang "EN") (map (renderPixelM r) [0..w-1])++renderPixelM !r !c = memo renderPixel r c++renderPixel :: IntMap JSString -> Int -> VNode+renderPixel r c = E.div (cls (r IM.! c)) ()++animate :: VMount -> State -> IO ()+animate m s =+ let s' = step s+ r' = render s'+ in do p <- diff m r'+ void $ inAnimationFrame ContinueAsync (\_ -> patch m p >> animate m s')++main :: IO ()+main = do+ root <- [js| document.createElement('div') |]+ [js_| document.body.appendChild(`root); |]+ let s = mkState 167 101 10 20+ m <- mount root (E.div () ())+ animate m s++
+ ghcjs-vdom.cabal view
@@ -0,0 +1,108 @@+name: ghcjs-vdom+version: 0.2.0.0+synopsis: Virtual-dom bindings for GHCJS+description: Virtual-dom is a library for fast incremental DOM+ updates by comparing virtual immutable DOM trees to+ find a minimal number of changes to update the actual DOM.++ The bindings support memoized nodes which are only+ recomputed when the underlying data changes, using+ referential equality for the function and arguments.++ The diff procedure in the virtual-dom library has been+ modified slightly to support computing a diff in an+ asynchronous thread. Since computing a diff forces all data+ around the virtual-dom tree, the computation, the computation+ can be expensive.++ An asynchronous diff computation can be safely aborted+ with an async exception.++license: MIT+license-file: LICENSE+author: Luite Stegeman+maintainer: stegeman@gmail.com+category: Web+build-type: Simple+cabal-version: >=1.10+extra-source-files: virtual-dom/lib.require.js+ virtual-dom/diff.js+ virtual-dom/LICENSE+ virtual-dom/handle-thunk.js+ virtual-dom/README.md+ virtual-dom/package.json+ data/example.css+ README.markdown++flag build-examples+ description: build the example programs+ default: False+ manual: True++library+ js-sources: jsbits/vdom.js+ virtual-dom/lib.js+ ghcjs-options: -Wall+ exposed-modules: GHCJS.VDOM+ GHCJS.VDOM.Attribute+ GHCJS.VDOM.Component+ GHCJS.VDOM.DOMComponent+ GHCJS.VDOM.Element+ GHCJS.VDOM.Event+ GHCJS.VDOM.QQ+ GHCJS.VDOM.Render+ GHCJS.VDOM.Unsafe+ other-modules: GHCJS.VDOM.Internal+ GHCJS.VDOM.Internal.TH+ GHCJS.VDOM.Internal.Thunk+ GHCJS.VDOM.Internal.Types+ GHCJS.VDOM.Element.Builtin+ build-depends: base >=4.7 && < 5,+ ghc-prim,+ ghcjs-ffiqq,+ ghcjs-base >= 0.2.0.0,+ ghcjs-prim,+ containers,+ split,+ template-haskell+ hs-source-dirs: src+ default-language: Haskell2010++executable ghcjs-vdom-example-table+ if !flag(build-examples)+ buildable: False+ Main-Is: Table.hs+ Default-Language: Haskell2010+ hs-source-dirs: examples+ Build-Depends: base >= 4 && < 5,+ ghcjs-ffiqq,+ ghcjs-vdom,+ containers,+ ghcjs-base+ ghcjs-Options: -Wall++executable ghcjs-vdom-example-components+ if !flag(build-examples)+ buildable: False+ Main-Is: Components.hs+ Default-Language: Haskell2010+ hs-source-dirs: examples+ Build-Depends: base >= 4 && < 5,+ ghcjs-ffiqq,+ ghcjs-vdom,+ containers,+ ghcjs-base+ ghcjs-Options: -Wall++executable ghcjs-vdom-example-render+ if !flag(build-examples)+ buildable: False+ Main-Is: Render.hs+ Default-Language: Haskell2010+ hs-source-dirs: examples+ Build-Depends: base >= 4 && < 5,+ ghcjs-ffiqq,+ ghcjs-vdom,+ containers,+ ghcjs-base+ ghcjs-Options: -Wall
+ jsbits/vdom.js view
@@ -0,0 +1,23 @@+#include <ghcjs/rts.h>++/* + * global name for the things we need from the virtual-dom library+ */+var h$vdom;++function h$vdomEventCallback(async, action, ev) {+ var a = MK_AP1(action, MK_JSVAL(ev));+ if(async) {+ h$run(a);+ } else {+ h$runSync(a, true);+ }+}++function h$vdomMountComponentCallback(action, mnt, comp) {+ h$run(MK_AP2(action, MK_JSVAL(mnt), MK_JSVAL(comp)));+}++function h$vdomUnmountComponentCallback(action, mnt, node) {+ h$run(MK_AP2(action, MK_JSVAL(mnt), MK_JSVAL(node)));+}
+ src/GHCJS/VDOM.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE QuasiQuotes #-}++{-|+ Bindings for the virtual-dom library.++ The virtual-dom diff function has been changed slightly to allow it to work+ with full functionality in asynchronous threads.++ It's possible to implement the bindings without the modifications at the+ cost of tail-call optimization and preemptive threading in the diff, by+ recursively forcing the thunks in synchronous threads.+ -}++module GHCJS.VDOM ( Attributes, Children+ , Attributes', Children'+ , VMount, VNode, VComp, DComp, Patch, DOMNode+ , mount, unmount+ , diff, patch+ , memo, memoKey+ ) where++import GHCJS.Types+import GHCJS.Foreign.QQ+import GHCJS.Prim+import GHCJS.Marshal.Pure++import Control.Monad+import Data.Coerce++import System.IO.Unsafe++import GHCJS.VDOM.Internal.Types+import GHCJS.VDOM.Internal.Thunk+import GHCJS.VDOM.Internal (j,J)+import qualified GHCJS.VDOM.Internal as I++class MemoNode a where memoNode :: (J, [JSIdent], a) -> a++instance MemoNode VNode+ where+ memoNode (_,[],a) = a+ memoNode (k,xs,v) =+ let vd = I.unsafeExportValue v+ xs1 = unsafePerformIO (toJSArray $ coerce xs)+ in VNode [j| h$vdom.th(`vd, `xs1, `k, true) |]+ {-# INLINE memoNode #-}++instance MemoNode b => MemoNode (a -> b)+ where+ memoNode (k,xs,f) = \a -> memoNode (k, I.objectIdent a:xs, f a)+ {-# INLINE memoNode #-}++memoKey :: MemoNode a => JSString -> a -> a+memoKey k = memo' (pToJSVal k)+{-# NOINLINE memoKey #-}++memo :: MemoNode a => a -> a+memo = memo' [j| $r = null; |]+{-# NOINLINE memo #-}++memo' :: MemoNode a => J -> a -> a+memo' k f = memoNode (k,[I.objectIdent f],f)+{-# INLINE memo' #-}++{-|+ Mount a virtual-dom tree in the real DOM. The mount point can be updated+ with patch.+-}+mount :: DOMNode -> VNode -> IO VMount+mount n v = do+ m <- VMount <$> [js| h$vdom.mount(`n) |]+ void $ patch m =<< diff m v+ return m+{-# INLINE mount #-}++{-|+ Remove a virtual-dom tree from the document. It's important to use+ unmount rather than removing the mount point any other way since this+ releases all associated Haskell data structures.+ -}+unmount :: VMount -> IO ()+unmount (VMount m) = [jsu_| h$vdom.unmount(`m); |]+{-# INLINE unmount #-}++{-|+ Compute a patch to update the mounted tree to match the virtual-dom tree+ -}+diff :: VMount -> VNode -> IO Patch+diff (VMount m) (VNode v) = do+ thunks <- [jsu| [] |]+ patch <- [jsu| `m.diff(`v, `thunks) |]+ forceThunks thunks+ forcePatch [j| `patch.patch |]+ return (Patch patch)+{-# INLINE diff #-}++{-|+ Apply a patch to a mounted virtual-dom tree. Fails if the tree has already+ been patched after the diff was computed.+ -}+patch :: VMount -> Patch -> IO Bool+patch (VMount m) (Patch p) = [jsu| `m.patch(`p); |]+{-# INLINE patch #-}+++
+ src/GHCJS/VDOM/Attribute.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}++module GHCJS.VDOM.Attribute ( Attribute+ , Attributes+ -- * some predefined attributes+ , class_+ , id+ , href+ , alt+ , src+ , name+ , target+ , value+ , width+ , height+ , title+ , lang+ , type_+ , key -- virtual-dom identifiers+ ) where++import Prelude hiding (id)++import GHCJS.Types++import GHCJS.VDOM.Internal.Types+import GHCJS.VDOM.Internal++mkAttrs ''JSString [ "id", "href", "src", "alt", "title"+ , "lang", "name", "target", "value"+ ]++mkAttrs' ''JSString [ ("class_", "className")+ , ("type_", "type")+ ]++mkAttrs ''Int [ "key", "width", "height" ]
+ src/GHCJS/VDOM/Component.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE GHCForeignImportPrim #-}+{-# LANGUAGE UnliftedFFITypes #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE MagicHash #-}++module GHCJS.VDOM.Component ( VComp+ , toNode+ , mkComponent+ , render+ , diff+ , patch+ ) where++import Control.Monad++import GHCJS.Foreign.QQ++import GHCJS.VDOM.Internal.Types++import qualified GHCJS.VDOM.Internal as I+import GHCJS.VDOM.Internal (j)+import qualified GHCJS.VDOM.Internal.Thunk as I++import GHC.Exts+import GHC.Types (IO(..))+import Unsafe.Coerce++toNode :: VComp -> VNode+toNode (VComp v) = VNode v+{-# INLINE toNode #-}++mkComponent :: IO VNode -> IO VComp+mkComponent r = do+ let renderE = I.unsafeExportValue r+ c <- VComp <$> [jsu| h$vdom.c(`renderE, null, null, null) |]+ void $ patch c =<< diff c =<< render c+ return c++foreign import javascript unsafe "$r = $1.hsRender;"+ js_hsRender :: VComp -> State# RealWorld -> (# State# RealWorld, Any #)++render :: VComp -> IO VNode+render c = join $ IO (\s -> case js_hsRender c s of+ (# s', r #) -> (# s', unsafeCoerce r #))+{-# INLINE render #-}++diff :: VComp -> VNode -> IO Patch+diff (VComp c) (VNode v) = do+ thunks <- [jsu| [] |]+ patch <- [jsu| `c.diff(`v, `thunks) |]+ I.forceThunks thunks+ I.forcePatch [j| `patch.patch |]+ return (Patch patch)+{-# INLINE diff #-}++patch :: VComp -> Patch -> IO Bool+patch (VComp c) (Patch p) = [jsu| `c.patch(`p) |]+{-# INLINE patch #-}+
+ src/GHCJS/VDOM/DOMComponent.hs view
@@ -0,0 +1,47 @@+{- |+ DOM components manage a normal DOM subtree inside a virtual-dom tree.++ The component has callbacks for mounting and unmounting. The mount+ callback returns a DOM tree that stays in the document until the+ unmount callback is called.++ A single component can be mounted multiple times. The mount callback+ is called for each mount, and is expected to return a fresh DOM+ tree every time.+ -}++{-# LANGUAGE ForeignFunctionInterface, JavaScriptFFI, QuasiQuotes #-}+module GHCJS.VDOM.DOMComponent ( DComp+ , mkComponent+ , toNode+ ) where++import GHCJS.Foreign.QQ+import GHCJS.Marshal.Pure+import GHCJS.Types++import GHCJS.VDOM.Internal.Types++import qualified GHCJS.VDOM.Internal as I+++toNode :: DComp -> VNode+toNode (DComp v) = VNode v+{-# INLINE toNode #-}++mkComponent :: (Int -> IO JSVal) -- ^ mount action, return a DOM node+ -> (Int -> JSVal -> IO ()) -- ^ unmount action+ -> IO DComp+mkComponent mount unmount =+ let mountE = I.unsafeExportValue (mountComponent mount)+ unmountE = I.unsafeExportValue (unmountComponent unmount)+ in DComp <$> [jsu| h$vdom.c(null, `mountE, `unmountE, null) |]+++mountComponent :: (Int -> IO JSVal) -> JSVal -> JSVal -> IO ()+mountComponent f mnt c = do+ node <- f (pFromJSVal mnt)+ [jsu| `c.updateMount(`mnt, `node); |]++unmountComponent :: (Int -> JSVal -> IO ()) -> JSVal -> JSVal -> IO ()+unmountComponent f mnt node = f (pFromJSVal mnt) node
+ src/GHCJS/VDOM/Element.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}+module GHCJS.VDOM.Element ( custom+ , text+ , module GHCJS.VDOM.Element.Builtin+ ) where++import Data.JSString (JSString)++import qualified GHCJS.VDOM.Internal as I+import GHCJS.VDOM.Internal.Types++import GHCJS.VDOM.Element.Builtin++custom :: (Attributes a, Children c) => JSString -> a -> c -> VNode+custom tag a c = I.mkVNode tag a c+{-# INLINE custom #-}
+ src/GHCJS/VDOM/Element/Builtin.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE TemplateHaskell #-}++module GHCJS.VDOM.Element.Builtin where++import GHCJS.VDOM.Internal++mkElems [ "address", "article", "body", "footer", "header"+ , "h1", "h2", "h3", "h4", "h5", "h6"+ , "hgroup", "nav", "section"+ , "dd", "div", "dl", "dt", "figcaption", "figure", "hr", "li"+ , "main", "ol", "p", "pre", "ul"+ , "a", "abbr", "b", "bdi", "br", "cite", "code", "dfn"+ , "em", "i", "kbd", "mark", "q", "rp", "rt", "rtc", "ruby"+ , "s", "samp", "small", "span", "strong", "sub", "sup", "time"+ , "u", "var", "wbr"+ , "area", "audio", "img", "map", "track", "video"+ , "embed", "iframe", "object", "param", "source"+ , "canvas", "noscript", "script"+ , "del", "ins"+ , "caption", "col", "colgroup", "table", "tbody", "td", "tfoot"+ , "th", "thead", "tr"+ , "button", "datalist", "fieldset", "form", "input", "keygen"+ , "label", "legend", "meter", "optgroup", "option", "output"+ , "progress", "select", "textarea"+ , "details", "dialog", "menu", "menuitem", "summary"+ , "content", "element", "shadow", "template"+ ]++-- use 'data_' as the name for the data tag, data is a reserved word+mkElem "data_" "data"
+ src/GHCJS/VDOM/Event.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleContexts #-}++module GHCJS.VDOM.Event ( initEventDelegation+ , defaultEvents+-- , target+ , stopPropagation+ , stopImmediatePropagation+ , preventDefault+ + -- * mouse+ , MouseEvent+ , click+ , dblclick+ , mousedown+ , mouseenter+ , mouseleave+ , mousemove+ , mouseout+ , mouseover+ , mouseup+ --+ , button+ , buttons+ , clientX+ , clientY+ + -- * keyboard+ , KeyboardEvent+ , keydown+ , keypress+ , keyup+ --+ , key+ , ctrlKey+ , metaKey+ , shiftKey+ + -- * drag+ , DragEvent+ , drag+ , dragend+ , dragenter+ , dragleave+ , dragover+ , dragstart+ + -- * focus+ , FocusEvent+ , focus+ , blur++ -- * ui+ , UIEvent+ , resize+ , scroll+ , select+ , unload+ + -- * wheel+ , WheelEvent+ , wheel+ --+ , deltaX+ , deltaY+ , deltaZ+ , deltaMode+ + -- * generic+ , Event+ , submit+ , change+ ) where++import Data.Coerce++import Unsafe.Coerce++import GHCJS.Prim+import GHCJS.Types+import GHCJS.Foreign.QQ++import GHCJS.VDOM.Internal++-- | call this to initialize the virtual-dom event handling system+initEventDelegation :: [JSString] -> IO ()+initEventDelegation eventTypes = do+ a <- toJSArray (unsafeCoerce eventTypes)+ [jsu_| h$vdom.initDelegator(`a); |]++class Coercible a JSVal => Event_ a+class Event_ a => KeyModEvent_ a+class Event_ a => MouseEvent_ a+class Event_ a => FocusEvent_ a++mkEventTypes ''Event_ [ ("MouseEvent", [''MouseEvent_])+ , ("KeyboardEvent", [''KeyModEvent_])+ , ("FocusEvent", [''FocusEvent_])+ , ("DragEvent", [])+ , ("WheelEvent", [])+ , ("UIEvent", [])+ , ("Event", [])+ ]++mkEvents 'MouseEvent [ "click", "dblclick", "mousedown", "mouseenter"+ , "mouseleave", "mousemove", "mouseout"+ , "mouseover", "mouseup"+ ]++mkEvents 'KeyboardEvent [ "keydown", "keypress", "keyup" ]++mkEvents 'DragEvent [ "drag", "dragend", "dragenter", "dragleave"+ , "dragover", "dragstart" ]++mkEvents 'FocusEvent [ "focus", "blur" ]++mkEvents 'UIEvent [ "resize", "scroll", "select", "unload" ]++mkEvents 'WheelEvent [ "wheel" ]++mkEvents 'Event [ "submit", "change" ]++er :: Event_ a => (JSVal -> b) -> a -> b+er f x = f (coerce x)++-- -----------------------------------------------------------------------------++-- this contains all event types added with mkEvents+defaultEvents :: [JSString]+defaultEvents = $(mkDefaultEvents)+++-- target :: Event_ a => a -> VNode+-- target e = undefined++stopPropagation :: Event_ a => a -> IO ()+stopPropagation = er $ \e -> [jsu_| `e.stopPropagation(); |]+{-# INLINE stopPropagation #-}++stopImmediatePropagation :: Event_ a => a -> IO ()+stopImmediatePropagation = er $ \e -> [jsu_| `e.stopImmediatePropagation(); |]+{-# INLINE stopImmediatePropagation #-}++preventDefault :: Event_ a => a -> IO ()+preventDefault = er $ \e -> [jsu_| `e.preventDefault(); |]+{-# INLINE preventDefault #-}++ctrlKey :: KeyModEvent_ a => a -> Bool+ctrlKey = er $ \e -> [jsu'| `e.ctrlKey |]+{-# INLINE ctrlKey #-}++metaKey :: KeyModEvent_ a => a -> Bool+metaKey = er $ \e -> [jsu'| `e.ctrlKey |]+{-# INLINE metaKey #-}++shiftKey :: KeyModEvent_ a => a -> Bool+shiftKey = er $ \e -> [jsu'| `e.ctrlKey |]+{-# INLINE shiftKey #-}++key :: KeyboardEvent -> JSString+key = er $ \e -> [jsu'| `e.key |]+{-# INLINE key #-}++button :: MouseEvent_ a => a -> Int+button = er $ \e -> [jsu'| `e.button |]+{-# INLINE button #-}++buttons :: MouseEvent_ a => a -> Int+buttons = er $ \e -> [jsu'| `e.buttons |]+{-# INLINE buttons #-}++deltaX :: WheelEvent -> Double+deltaX = er $ \e -> [jsu'| `e.deltaX |]+{-# INLINE deltaX #-}++deltaY :: WheelEvent -> Double+deltaY = er $ \e -> [jsu'| `e.deltaY |]+{-# INLINE deltaY #-}++deltaZ :: WheelEvent -> Double+deltaZ = er $ \e -> [jsu'| `e.deltaZ |]+{-# INLINE deltaZ #-}++deltaMode :: WheelEvent -> Double+deltaMode = er $ \e -> [jsu'| `e.deltaMode |]+{-# INLINE deltaMode #-}++clientX :: MouseEvent -> Int+clientX = er $ \e -> [jsu'| `e.clientX|0 |]+{-# INLINE clientX #-}++clientY :: MouseEvent -> Int+clientY = er $ \e -> [jsu'| `e.clientY|0 |]+{-# INLINE clientY #-}
+ src/GHCJS/VDOM/Internal.hs view
@@ -0,0 +1,199 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnliftedFFITypes #-}+{-# LANGUAGE GHCForeignImportPrim #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE ScopedTypeVariables #-}++module GHCJS.VDOM.Internal where++import GHCJS.VDOM.Internal.Types++import Language.Haskell.TH+import Language.Haskell.TH.Quote+import Language.Haskell.TH.Syntax++import GHC.Prim (Any, State#, RealWorld)++import Control.Monad+import Unsafe.Coerce++import GHCJS.Foreign.QQ+import GHCJS.Types+import GHCJS.Marshal.Pure++import Data.List (foldl')+import Data.String (IsString(..))+import Data.Typeable++import GHC.IO ( IO(..) )+import GHC.Base ( StableName# )++type J = JSVal++j :: QuasiQuoter+j = jsu'++mkVNode :: (Attributes a, Children c) => JSString -> a -> c -> VNode+mkVNode tag atts children = js_vnode tag (mkAttributes atts) (mkChildren children)+{-# INLINE mkVNode #-}++mkElems :: [String] -> Q [Dec]+mkElems = fmap concat . mapM (join mkElem)++mkElem :: String -> String -> Q [Dec]+mkElem name tag = do+ let n = mkName name+ a <- newName "a"+ c <- newName "c"+ b <- [| mkVNode (fromString tag) |]+ typ <- [t|forall a c. (Attributes a, Children c) => a -> c -> VNode |]+ return [ SigD n typ+ , FunD n [Clause [VarP a, VarP c] (NormalB (AppE (AppE b (VarE a)) (VarE c))) []]+ , PragmaD (InlineP n Inline FunLike AllPhases)+ ]++mkAttrs :: Name -> [String] -> Q [Dec]+mkAttrs ty = fmap concat . mapM (join (mkAttr ty))++mkAttrs' :: Name -> [(String, String)] -> Q [Dec]+mkAttrs' ty = fmap concat . mapM (uncurry (mkAttr ty))++mkAttr :: Name -> String -> String -> Q [Dec]+mkAttr ty name attr = do+ let n = mkName name+ x <- newName "x"+ b <- [| \y -> Attribute attr (pToJSVal y) |]+ return [ SigD n (AppT (AppT ArrowT (ConT ty)) (ConT ''Attribute))+ , FunD n [Clause [VarP x] (NormalB (AppE b (VarE x))) []]+ , PragmaD (InlineP n Inline FunLike AllPhases)+ ]++mkEventTypes :: Name -> [(String, [Name])] -> Q [Dec]+mkEventTypes base = fmap concat . mapM mk+ where+ mk (n, cls) = do+ let nn = mkName n+#if MIN_VERSION_template_haskell(2,11,0)+ mkI cn = InstanceD Nothing [] (AppT (ConT cn) (ConT nn)) []+#else+ mkI cn = InstanceD [] (AppT (ConT cn) (ConT nn)) []+#endif+ insts = map mkI (base : cls)+ jsr <- [t| JSVal |]+ typ <- [t| Typeable |]+#if MIN_VERSION_template_haskell(2,11,0)+ return $ (NewtypeD [] nn [] Nothing (NormalC nn [(Bang NoSourceUnpackedness NoSourceStrictness, jsr)]) [ typ ]) : insts+#else+ return $ (NewtypeD [] nn [] (NormalC nn [(NotStrict, jsr)]) [''Typeable]) : insts+#endif++newtype CreatedEvents = CreatedEvents { unCreatedEvents :: [String] }+ deriving (Typeable)++addCreatedEvent :: String -> CreatedEvents -> CreatedEvents+addCreatedEvent ev (CreatedEvents es) = CreatedEvents (ev:es)++-- dcon must be a newtype constructor, not a data con+mkEvents :: Name -> [String] -> Q [Dec]+mkEvents dcon xs = fmap concat (mapM (\x -> mkEvent dcon x ("ev-"++x)) xs)++-- dcon must be a newtype constructor, not a data con+mkEvent :: Name -> String -> String -> Q [Dec]+mkEvent dcon name attr = do+ let n = mkName name+ emsg = "GHCJS.VDOM.Internal.mkEvent: expected newtype constructor"+ i <- reify dcon+ dctyp <- case i of+#if MIN_VERSION_template_haskell(2,11,0)+ DataConI _ _ pn -> do+ pni <- reify pn+ case pni of+ TyConI (NewtypeD _ ctn _ _ _ _) -> return (ConT ctn)+ _ -> error emsg+ _ -> error emsg+#else+ DataConI _ _ pn _ -> do+ pni <- reify pn+ case pni of+ TyConI (NewtypeD _ ctn _ _ _) -> return (ConT ctn)+ _ -> error emsg+ _ -> error emsg+#endif+ iou <- [t| IO () |]+ h <- newName "h"+ b <- [| mkEventAttr (fromString attr) |]+ let ht = AppT (AppT ArrowT dctyp) iou+ -- typ <- [t| (dctyp -> IO ()) -> Attribute |]+ qPutQ . maybe (CreatedEvents [name]) (addCreatedEvent name) =<< qGetQ+ return [ SigD n (AppT (AppT ArrowT ht) (ConT ''Attribute))+ , FunD n [Clause [VarP h] (NormalB (AppE (AppE b (ConE dcon)) (VarE h))) []]+ , PragmaD (InlineP n Inline FunLike AllPhases)+ ]++-- a must be a newtype of JSVal!+mkEventAttr :: JSString -> (JSVal -> a) -> (a -> IO ()) -> Attribute+mkEventAttr attr _wrap h =+ + let e = unsafeExportValue h+ h' = [js'| h$vdom.makeHandler(`e, false) |]+ in h' `seq` Attribute attr h'+{-# INLINE mkEventAttr #-}++{-+eventLogger :: JSVal ()+eventLogger = [js'| function(ev) { console.log("event caught"); } |]+-}++-- generate a list of all events stored in the persistent TH state, created with mkEvent+mkDefaultEvents :: Q Exp+mkDefaultEvents = do+ evs <- maybe [] unCreatedEvents <$> qGetQ+ nil <- [| [] |]+ cons <- [| (:) |]+ return $ foldl' (\xs e -> AppE (AppE cons (LitE . stringL $ e)) xs) nil evs+ +js_vnode :: JSString -> Attributes' -> Children' -> VNode+js_vnode tag (Attributes' props) (Children' children) =+ VNode [jsu'| h$vdom.v(`tag, `props, `children) |]+ --VNode [jsu'| new h$vdom.VNode(`tag, `props, `children) |]++getThunk :: J -> IO J+getThunk x = IO (js_getThunk x)++foreign import javascript unsafe "$r = $1.hst;"+ js_getThunk :: J -> State# RealWorld -> (# State# RealWorld, J #)++-- -----------------------------------------------------------------------------+{-|+ Export an arbitrary Haskell value to JS.++ be careful with these JSVal values, losing track of them will result in+ incorrect memory management. As long as we keep the values directly in+ a Property or VNode, the ghcjs-vdom extensible retention system will know+ where to find them.+ -}+unsafeExportValue :: a -> JSVal+unsafeExportValue x = js_export (unsafeCoerce x)+{-# INLINE unsafeExportValue #-}++{-|+ make a unique identifier that can be easily compared in JS+ if(objectIdent(o1) === objectIdent(o2) or both are NaN, then o1 and o2 are+ are the same Haskell value+ -}+objectIdent :: a -> JSIdent+objectIdent x = x `seq` js_makeObjectIdent (unsafeExportValue x)+{-+ unsafePerformIO . IO $ \s ->+ case makeStableName# x s of (# s', sn #) -> (# s', js_convertSn sn #)+-}+{-# INLINE objectIdent #-}+ +foreign import javascript unsafe "$r = $1;" js_export :: Any -> JSVal+foreign import javascript unsafe "$r = $1;" js_convertSn :: StableName# a -> JSIdent++foreign import javascript unsafe "h$makeStableName($1)" js_makeObjectIdent :: JSVal -> JSIdent
+ src/GHCJS/VDOM/Internal/TH.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE CPP, TemplateHaskell, QuasiQuotes #-}++module GHCJS.VDOM.Internal.TH where++import Data.List (foldl')++import Language.Haskell.TH++import Unsafe.Coerce++mkTupleChildrenInstances :: Name -> Name -> Name -> Name -> Name -> [Int] -> Q [Dec]+mkTupleChildrenInstances cls method ty con wrapper xs =+ concat <$> mapM (mkTupleChildrenInstance cls method ty con wrapper) xs++{-++instance cls (ty, ty, ...) where+ method (con x1, con x2, ...) = wrapper (buildArrayIN x1 x2 ...)+ {-# INLINE method #-}++-}+mkTupleChildrenInstance :: Name -> Name -> Name -> Name -> Name -> Int -> Q [Dec]+mkTupleChildrenInstance cls method ty con wrapper n = do+ let xs = map (mkName.('x':).show) [1..n]+ t = AppT (ConT cls) (iterate (`AppT` ConT ty) (TupleT n) !! n)+ build = mkName ("GHCJS.Prim.Internal.Build.buildArrayI" ++ show n)+ pat = [TupP (map (ConP con . (:[]) . VarP) xs)]+ body = NormalB (AppE (ConE wrapper)+ (foldl' (\e v -> AppE e (VarE v)) (VarE build) xs))+#if MIN_VERSION_template_haskell(2,11,0)+ return [InstanceD Nothing [] t [ FunD method [Clause pat body []]+ , PragmaD (InlineP method Inline FunLike AllPhases)+ ]+ ]+#else+ return [InstanceD [] t [ FunD method [Clause pat body []]+ , PragmaD (InlineP method Inline FunLike AllPhases)+ ]+ ]+#endif++mkTupleAttrInstances :: Name -> Name -> Name -> Name -> Name -> [Int] -> Q [Dec]+mkTupleAttrInstances cls method ty con wrapper xs =+ concat <$> mapM (mkTupleAttrInstance cls method ty con wrapper) xs++{-++instance cls (ty, ty, ...) where+ method (con k1 v1, con k2 v2, ...) =+ wrapper (buildObjectIN k1 k2 v1 v2 ...)+ {-# INLINE method #-}++ -}+mkTupleAttrInstance :: Name -> Name -> Name -> Name -> Name -> Int -> Q [Dec]+mkTupleAttrInstance cls method ty con wrapper n = do+ let xs = map (\i -> let si = show i in [mkName ('k':si), mkName ('v':si)]) [1..n]+ t = AppT (ConT cls) (iterate (`AppT` ConT ty) (TupleT n) !! n)+ build = mkName ("GHCJS.Prim.Internal.Build.buildObjectI" ++ show n)+ pat = [TupP (map (ConP con . map VarP) xs)]+ app e k v = AppE (AppE e (AppE (VarE 'unsafeCoerce) (VarE k))) (VarE v)+ body = NormalB (AppE (ConE wrapper)+ (foldl' (\e [k,v] -> app e k v) (VarE build) xs))+#if MIN_VERSION_template_haskell(2,11,0)+ return [InstanceD Nothing [] t [ FunD method [Clause pat body []]+ , PragmaD (InlineP method Inline FunLike AllPhases)+ ]+ ]+#else+ return [InstanceD [] t [ FunD method [Clause pat body []]+ , PragmaD (InlineP method Inline FunLike AllPhases)+ ]+ ]+#endif
+ src/GHCJS/VDOM/Internal/Thunk.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE TemplateHaskell, QuasiQuotes, LambdaCase, GHCForeignImportPrim #-}+{-|+ Code that deals with forcing thunks in virtual-dom trees. When+ computing a diff, the virtual-dom code returns a list of thunks+ found in the tree. The caller then forces the thunks and recurses+ into them to advance the diff computation, until all thunks have+ been evaluated.+ -}+module GHCJS.VDOM.Internal.Thunk where++import GHCJS.Foreign.QQ+import GHCJS.Prim++import Control.Exception+import Control.Monad++import GHC.Exts (Any)+import Unsafe.Coerce++import GHCJS.VDOM.Internal (j,J)+import qualified GHCJS.VDOM.Internal as I+import GHCJS.VDOM.Internal.Types++diff' :: J -> J -> IO J+diff' a b = do+ thunks <- [jsu| [] |]+ p <- [jsu| h$vdom.diff(`a, `b, `thunks) |]+ forceThunks thunks+ forcePatch p+ return p++forceThunks :: J -> IO ()+forceThunks thunks+ | [j| `thunks.length > 0 |] = fromJSArray thunks >>= mapM_ forceNode+ | otherwise = return ()+ where+ forceNode n = do+ forceThunkNode [j| `n.a |]+ forceThunkNode [j| `n.b |]+ patch <- diff' [j| `n.a.vnode |] [j| `n.b.vnode |]+ [jsu_| h$vdom.setThunkPatch(`n, `patch); |]++forceThunkNode :: J -> IO ()+forceThunkNode x =+ [jsu| `x && `x.hst && !`x.vnode |] >>= \case+ True -> do+ (VNode u) <- fmap unsafeCoerce . js_toHeapObject =<< I.getThunk x+ [jsu| `x.hst = null;+ `x.vnode = `u;+ |]+ _ -> return ()+++forcePatch :: J -> IO ()+forcePatch p = do+ thunks <- [jsu| h$vdom.forcePatch(`p) |]+ forceTree [thunks]++forceTree :: [J] -> IO ()+forceTree [] = return ()+forceTree (x:xs) = do+ x' <- fromJSArray x+ ys <- forM x' $ \t -> do+ forceThunkNode t+ newThunks <- [jsu| [] |]+ [jsu_| h$vdom.forceTree(`t.vnode, `newThunks) |]+ return newThunks+ forceTree (filter (\a -> [jsu'| `a.length !== 0 |]) ys ++ xs)++foreign import javascript unsafe+ "$r = $1;" js_toHeapObject :: JSVal -> IO Any
+ src/GHCJS/VDOM/Internal/Types.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE QuasiQuotes, TemplateHaskell, FlexibleInstances #-}++module GHCJS.VDOM.Internal.Types where++import qualified Data.JSString as JSS+import Data.String (IsString(..))++import GHCJS.Foreign.QQ+import GHCJS.Types+import qualified GHCJS.Prim.Internal.Build+import qualified GHCJS.Prim.Internal.Build as IB++import GHCJS.VDOM.Internal.TH++import Unsafe.Coerce++-- do not export the constructors for these, this ensures that the objects are opaque+-- and cannot be mutated+newtype VNode = VNode { unVNode :: JSVal }+newtype VComp = VComp { unVComp :: JSVal }+newtype DComp = DComp { unDComp :: JSVal }+newtype Patch = Patch { unPatch :: JSVal }+newtype VMount = VMount { unVMount :: JSVal }++-- fixme: make newtype?+-- newtype JSIdent = JSIdent JSVal+-- newtype DOMNode = DOMNode JSVal+type JSIdent = JSVal+type DOMNode = JSVal+++class Attributes a where+ mkAttributes :: a -> Attributes'+newtype Attributes' = Attributes' JSVal++data Attribute = Attribute JSString JSVal++class Children a where+ mkChildren :: a -> Children'+newtype Children' = Children' { unChildren :: JSVal }++instance Children Children' where+ mkChildren x = x+ {-# INLINE mkChildren #-}++instance Children () where+ mkChildren _ = Children' [jsu'| [] |]+ {-# INLINE mkChildren #-}++instance Children VNode where+ mkChildren (VNode v) =+ Children' [jsu'| [`v] |]+ {-# INLINE mkChildren #-}++mkTupleChildrenInstances ''Children 'mkChildren ''VNode 'VNode 'Children' [2..32]++instance Children [VNode] where+ mkChildren xs = Children' $ IB.buildArrayI (unsafeCoerce xs)+ {-# INLINE mkChildren #-}++instance Attributes Attributes' where+ mkAttributes x = x+ {-# INLINE mkAttributes #-}++instance Attributes () where+ mkAttributes _ = Attributes' [jsu'| {} |]+ {-# INLINE mkAttributes #-}++instance Attributes Attribute where+ mkAttributes (Attribute k v) =+ Attributes' (IB.buildObjectI1 (unsafeCoerce k) v)++instance Attributes [Attribute] where+ mkAttributes xs = Attributes' (IB.buildObjectI $+ map (\(Attribute k v) -> (unsafeCoerce k,v)) xs)++mkTupleAttrInstances ''Attributes 'mkAttributes ''Attribute 'Attribute 'Attributes' [2..32]++instance IsString VNode where fromString xs = text (JSS.pack xs)++text :: JSString -> VNode+text xs = VNode [jsu'| h$vdom.t(`xs) |]+{-# INLINE text #-}
+ src/GHCJS/VDOM/QQ.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE QuasiQuotes, DeriveDataTypeable, TemplateHaskell #-}+{-+ More efficient JavaScript literals with QuasiQuoters++ mostly experimental, might not stay+ -}+module GHCJS.VDOM.QQ (ch, children, att, attributes) where++import Language.Haskell.TH.Quote+import Language.Haskell.TH.Syntax++import GHCJS.VDOM.Internal.Types++import GHCJS.Types+import GHCJS.Marshal++import Control.Applicative++import Data.Char+import qualified Data.List as L+import Data.List.Split+import Data.Typeable++import System.IO.Unsafe++att :: QuasiQuoter+att = attributes++ch :: QuasiQuoter+ch = children++-- example: [props|a:1, b: null, c: x, d: y |]+-- every value is either a literal or a variable referring to a convertible Haskell name+-- fixme, this does not have a proper parser+attributes :: QuasiQuoter+attributes = QuasiQuoter { quoteExp = quoteProps }++quoteProps :: String -> Q Exp+quoteProps pat = jsExpQQ ('{':ffiPat++"}") (map mkName names)+ (\x -> AppE (VarE 'unsafePerformIO) (AppE (VarE 'toJSVal) x))+ (AppE (ConE 'Attributes'))+ where+ (names, ffiPat) = genpat 1 $ map (break (==':') . trim) (linesBy (==',') pat)+ isName [] = False+ isName (x:xs) = isAlpha x && all isAlphaNum xs+ genpat :: Int -> [(String,String)] -> ([String], String)+ genpat _ [] = ([], "")+ genpat k ((x,':':n):xs)+ | isName n' = (n':ns, x ++ ": $" ++ (show k) ++ p)+ | otherwise = (ns, x ++ ':' : n ++ sep ++ p)+ where+ n' = trim n+ ~(ns, p) = genpat (k+1) xs+ sep = if null xs then "" else ","+ genpat _ _ = error "invalid pattern"+++-- example: [children|x,y,z|] for haskell names x,y,z :: VNode+children :: QuasiQuoter+children = QuasiQuoter { quoteExp = quoteChildren }++quoteChildren :: String -> Q Exp+quoteChildren pat = jsExpQQ ffiPat names (AppE (VarE 'unVNode)) (AppE (ConE 'Children'))+ where+ names = map (mkName.trim) (linesBy (==',') pat)+ ffiPat = '[' : L.intercalate "," (map (('$':).show) (take (length names) [(1::Int)..])) ++ "]"++trim :: String -> String+trim = let f = reverse . dropWhile isSpace in f . f++newtype QQCounter = QQCounter { getCount :: Int } deriving (Typeable, Show)++jsExpQQ :: String -> [Name] -> (Exp -> Exp) -> (Exp -> Exp) -> Q Exp+jsExpQQ pat args unwrap wrap = do+ c <- maybe 0 getCount <$> qGetQ+ n <- newName ("__ghcjs_vdom_qq_spliced_" ++ show c)+ let ffiDecl = ForeignD (ImportF CCall Unsafe pat' n (ty $ length args))+ ty :: Int -> Type+ ty 0 = ref+ ty n = AppT (AppT ArrowT ref) (ty (n-1))+ ref = ConT ''JSVal+ ffiCall [] = (VarE n)+ ffiCall (y:ys) = AppE (ffiCall ys) (unwrap (VarE y))+ pat' = "__ghcjs_javascript_" ++ L.intercalate "_" (map (show . ord) pat)+ qAddTopDecls [ffiDecl]+ qPutQ (QQCounter (c+1))+ return $ wrap (ffiCall $ reverse args)+
+ src/GHCJS/VDOM/Render.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE TypeFamilies, QuasiQuotes, ScopedTypeVariables #-}+{-|+ Some utilities to manage a render queue for ghcjs-vdom components.++ Diffing is performed in an asynchronous background thread. When a+ patch is ready, an animationframe is requested to apply the update.++ Using the render queue is optional, directly calling diff and patch+ for the components is a lower level way to update the document.+ -}++module GHCJS.VDOM.Render ( Renderer+ , render+ , mkRenderer+ ) where++import GHCJS.Foreign.Callback+import GHCJS.Foreign.QQ+import GHCJS.Types++import GHCJS.VDOM.Internal.Types+import qualified GHCJS.VDOM.Component as C+import qualified GHCJS.VDOM as V++import JavaScript.Web.AnimationFrame++import Control.Concurrent+import qualified Control.Exception as E+import Control.Monad++import Data.IORef+import Data.Map (Map)+import qualified Data.Map as M+import Data.Typeable++class Renderable a where+ type Render a+ doRender :: Renderer -> a -> Render a++instance Renderable VComp where+ type Render VComp = IO ()+ doRender r c@(VComp vcj) =+ enqueueRender r vcj (C.render c >>= C.diff c >>= addPatch vcj) (flushPatch vcj)++instance Renderable VMount where+ type Render VMount = (VNode -> IO ())+ doRender r vm@(VMount vmj) vn =+ enqueueRender r vmj (V.diff vm vn >>= addPatch vmj) (flushPatch vmj)++render :: Renderable a => Renderer -> a -> Render a+render r x = doRender r x++mkRenderer :: IO Renderer+mkRenderer = do+ r <- Renderer <$> newIORef M.empty+ <*> newIORef M.empty+ <*> newChan+ void (forkIO (renderThread r))+ return r++data Renderer = Renderer+ { renderPending :: IORef (Map Double (IO (), IO ()))+ , renderFlushPending :: IORef (Map Double (IO ()))+ , renderQueue :: Chan Double+ } deriving (Typeable)++renderThread :: Renderer -> IO ()+renderThread r = forever $ do+ k <- readChan (renderQueue r)+ Just (compute, flush) <-+ atomicModifyIORef (renderPending r) (\m -> (M.delete k m, M.lookup k m))+ let actions = do+ compute+ m <- atomicModifyIORef (renderFlushPending r)+ (\m -> (M.insert k flush m, m))+ when (M.null m) (void $ inAnimationFrame ThrowWouldBlock+ (\_ -> flushPatches r))+ actions`E.catch` \(_::E.SomeException) -> return ()++flushPatches :: Renderer -> IO ()+flushPatches r =+ mapM_ (\m -> m `E.catch` \(_::E.SomeException) -> return ()) =<<+ atomicModifyIORef (renderFlushPending r)+ (\m -> (M.empty, M.elems m))++enqueueRender :: Renderer+ -> JSVal+ -> IO ()+ -> IO ()+ -> IO ()+enqueueRender r renderable compute flush = do+ k <- renderableKey renderable+ m <- atomicModifyIORef (renderPending r)+ (\m -> (M.insert k (compute, flush) m, m))+ when (M.notMember k m) (writeChan (renderQueue r) k)++renderableKey :: JSVal -> IO Double+renderableKey r = [jsu| `r._key |]++addPatch :: JSVal -> Patch -> IO ()+addPatch r (Patch p) = [jsu_| `r.addPatch(`p); |]++flushPatch :: JSVal -> IO ()+flushPatch r = [jsu_| `r.patch(null); |]
+ src/GHCJS/VDOM/Unsafe.hs view
@@ -0,0 +1,31 @@+{-|+ Unsafe API for constructing Children' and Attributes', useful for+ writing new Children and Attributes instances+ -}+module GHCJS.VDOM.Unsafe ( Attributes(..), Children(..)+ , Attributes', Children'+ , unsafeToAttributes, unsafeToChildren ) where++import GHCJS.Types++import GHCJS.VDOM.Internal.Types++{-|+ Convert a JSVal, which must be a JS object, to Attributes'. The object+ may not be mutated.++ FIXME what do we need to always be able to find Haskell callbacks?+ -}+unsafeToAttributes :: JSVal -> Attributes'+unsafeToAttributes = Attributes'++{-|+ Convert a JSVal, which must be an array of virtual-dom nodes+ to Children'. The array and the child nodes may not be mutated.++ note: All nodes must either be a virtual node or an instance of HSThunk,+ if you use other node types, the extensible retention (see scanTree in+ virtual-dom/lib.require.js) must be extended.+ -}+unsafeToChildren :: JSVal -> Children'+unsafeToChildren = Children'
+ virtual-dom/LICENSE view
@@ -0,0 +1,19 @@+Copyright (c) 2014 Matt-Esch.++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ virtual-dom/README.md view
@@ -0,0 +1,13 @@+to initiate:++ $ npm install++to build++ $ grunt++(install grunt with: npm install -g grunt-cli)++to watch for changes and rebuild++ $ grunt watch
+ virtual-dom/diff.js view
@@ -0,0 +1,445 @@+/*+ vtree/diff module modified to defer rendering thunks. this makes it possible to+ implement thunks that cannot be called directly as a function, but have+ an asynchronous callback or require evaluation in some specific runtime+ environment+ */++var isArray = require("x-is-array")++var VPatch = require("virtual-dom/vnode/vpatch")+var isVNode = require("virtual-dom/vnode/is-vnode")+var isVText = require("virtual-dom/vnode/is-vtext")+var isWidget = require("virtual-dom/vnode/is-widget")+var isThunk = require("virtual-dom/vnode/is-thunk")+var handleThunk = require("./handle-thunk")++var diffProps = require("virtual-dom/vtree/diff-props")++module.exports = diff++// unevaluated thunks are added to the thunks argument (array)+function diff(a, b, thunks) {+ if(!a) throw new Error ("diff a: " + a);+ if(!a) throw new Error ("diff b: " + b);+ var patch = { a: a }+ walk(a, b, patch, thunks, 0)+ return patch+}++function walk(a, b, patch, thunks, index) {+ if (a === b) {+ return+ }++ var apply = patch[index]+ var applyClear = false++ if (isThunk(a) || isThunk(b)) {+ doThunks(a, b, patch, thunks, index)+ } else if (b == null) {++ // If a is a widget we will add a remove patch for it+ // Otherwise any child widgets/hooks must be destroyed.+ // This prevents adding two remove patches for a widget.+ if (!isWidget(a)) {+ clearState(a, patch, index)+ apply = patch[index]+ }++ apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b))+ } else if (isVNode(b)) {+ if (isVNode(a)) {+ if (a.tagName === b.tagName &&+ a.namespace === b.namespace &&+ a.key === b.key) {+ var propsPatch = diffProps(a.properties, b.properties)+ if (propsPatch) {+ apply = appendPatch(apply,+ new VPatch(VPatch.PROPS, a, propsPatch))+ }+ apply = diffChildren(a, b, patch, apply, thunks, index)+ } else {+ apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))+ applyClear = true+ }+ } else {+ apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))+ applyClear = true+ }+ } else if (isVText(b)) {+ if (!isVText(a)) {+ apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))+ applyClear = true+ } else if (a.text !== b.text) {+ apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))+ }+ } else if (isWidget(b)) {+ if (!isWidget(a)) {+ applyClear = true+ }++ apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b))+ }++ if (apply) {+ patch[index] = apply+ }++ if (applyClear) {+ clearState(a, patch, index)+ }+}++function diffChildren(a, b, patch, apply, thunks, index) {+ var aChildren = a.children+ var orderedSet = reorder(aChildren, b.children)+ var bChildren = orderedSet.children++ var aLen = aChildren.length+ var bLen = bChildren.length+ var len = aLen > bLen ? aLen : bLen++ for (var i = 0; i < len; i++) {+ var leftNode = aChildren[i]+ var rightNode = bChildren[i]+ index += 1++ if (!leftNode) {+ if (rightNode) {+ // Excess nodes in b need to be added+ apply = appendPatch(apply,+ new VPatch(VPatch.INSERT, null, rightNode))+ }+ } else {+ walk(leftNode, rightNode, patch, thunks, index)+ }++ if (isVNode(leftNode) && leftNode.count) {+ index += leftNode.count+ }+ }++ if (orderedSet.moves) {+ // Reorder nodes last+ apply = appendPatch(apply, new VPatch(+ VPatch.ORDER,+ a,+ orderedSet.moves+ ))+ }++ return apply+}++function clearState(vNode, patch, index) {+ // TODO: Make this a single walk, not two+ unhook(vNode, patch, index)+ destroyWidgets(vNode, patch, index)+}++// Patch records for all destroyed widgets must be added because we need+// a DOM node reference for the destroy function+function destroyWidgets(vNode, patch, index) {+ if (isWidget(vNode)) {+ if (typeof vNode.destroy === "function") {+ patch[index] = appendPatch(+ patch[index],+ new VPatch(VPatch.REMOVE, vNode, null)+ )+ }+ } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) {+ var children = vNode.children+ var len = children.length+ for (var i = 0; i < len; i++) {+ var child = children[i]+ index += 1++ destroyWidgets(child, patch, index)++ if (isVNode(child) && child.count) {+ index += child.count+ }+ }+ } else if (isThunk(vNode)) {+ doThunks(vNode, null, patch, thunks, index)+ }+}++// Create a sub-patch for thunks+function doThunks(a, b, patch, thunks, index) {+ var ts = handleThunk(a, b);+ if(ts.a || ts.b) {+ // defer rendering, caller is responsible for:+ // - filling ts.a.vnode / ts.b.vnode with the result from the thunk+ // - ts.p[t.sp] = new VPatch(VPatch.THUNK, null, diff(ts.a.vnode, ts.b.vnode))+ // before using it with patch+ thunks.push({ i: index, p: patch, a: a, b: b});+ } else {+ var thunkPatch = diff(a.vnode, b.vnode, thunks)+ if (hasPatches(thunkPatch)) {+ patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch)+ }+ }+}++function hasPatches(patch) {+ for (var index in patch) {+ if (index !== "a") {+ return true+ }+ }++ return false+}++// Execute hooks when two nodes are identical+function unhook(vNode, patch, index) {+ if (isVNode(vNode)) {+ if (vNode.hooks) {+ patch[index] = appendPatch(+ patch[index],+ new VPatch(+ VPatch.PROPS,+ vNode,+ undefinedKeys(vNode.hooks)+ )+ )+ }++ if (vNode.descendantHooks || vNode.hasThunks) {+ var children = vNode.children+ var len = children.length+ for (var i = 0; i < len; i++) {+ var child = children[i]+ index += 1++ unhook(child, patch, index)++ if (isVNode(child) && child.count) {+ index += child.count+ }+ }+ }+ } else if (isThunk(vNode)) {+ doThunks(vNode, null, patch, thunks, index)+ }+}++function undefinedKeys(obj) {+ var result = {}++ for (var key in obj) {+ result[key] = undefined+ }++ return result+}++// List diff, naive left to right reordering+function reorder(aChildren, bChildren) {+ // O(M) time, O(M) memory+ var bChildIndex = keyIndex(bChildren)+ var bKeys = bChildIndex.keys+ var bFree = bChildIndex.free++ if (bFree.length === bChildren.length) {+ return {+ children: bChildren,+ moves: null+ }+ }++ // O(N) time, O(N) memory+ var aChildIndex = keyIndex(aChildren)+ var aKeys = aChildIndex.keys+ var aFree = aChildIndex.free++ if (aFree.length === aChildren.length) {+ return {+ children: bChildren,+ moves: null+ }+ }++ // O(MAX(N, M)) memory+ var newChildren = []++ var freeIndex = 0+ var freeCount = bFree.length+ var deletedItems = 0++ // Iterate through a and match a node in b+ // O(N) time,+ for (var i = 0 ; i < aChildren.length; i++) {+ var aItem = aChildren[i]+ var itemIndex++ if (aItem.key) {+ if (bKeys.hasOwnProperty(aItem.key)) {+ // Match up the old keys+ itemIndex = bKeys[aItem.key]+ newChildren.push(bChildren[itemIndex])++ } else {+ // Remove old keyed items+ itemIndex = i - deletedItems+++ newChildren.push(null)+ }+ } else {+ // Match the item in a with the next free item in b+ if (freeIndex < freeCount) {+ itemIndex = bFree[freeIndex++]+ newChildren.push(bChildren[itemIndex])+ } else {+ // There are no free items in b to match with+ // the free items in a, so the extra free nodes+ // are deleted.+ itemIndex = i - deletedItems+++ newChildren.push(null)+ }+ }+ }++ var lastFreeIndex = freeIndex >= bFree.length ?+ bChildren.length :+ bFree[freeIndex]++ // Iterate through b and append any new keys+ // O(M) time+ for (var j = 0; j < bChildren.length; j++) {+ var newItem = bChildren[j]++ if (newItem.key) {+ if (!aKeys.hasOwnProperty(newItem.key)) {+ // Add any new keyed items+ // We are adding new items to the end and then sorting them+ // in place. In future we should insert new items in place.+ newChildren.push(newItem)+ }+ } else if (j >= lastFreeIndex) {+ // Add any leftover non-keyed items+ newChildren.push(newItem)+ }+ }++ var simulate = newChildren.slice()+ var simulateIndex = 0+ var removes = []+ var inserts = []+ var simulateItem++ for (var k = 0; k < bChildren.length;) {+ var wantedItem = bChildren[k]+ simulateItem = simulate[simulateIndex]++ // remove items+ while (simulateItem === null && simulate.length) {+ removes.push(remove(simulate, simulateIndex, null))+ simulateItem = simulate[simulateIndex]+ }++ if (!simulateItem || simulateItem.key !== wantedItem.key) {+ // if we need a key in this position...+ if (wantedItem.key) {+ if (simulateItem && simulateItem.key) {+ // if an insert doesn't put this key in place, it needs to move+ if (bKeys[simulateItem.key] !== k + 1) {+ removes.push(remove(simulate, simulateIndex, simulateItem.key))+ simulateItem = simulate[simulateIndex]+ // if the remove didn't put the wanted item in place, we need to insert it+ if (!simulateItem || simulateItem.key !== wantedItem.key) {+ inserts.push({key: wantedItem.key, to: k})+ }+ // items are matching, so skip ahead+ else {+ simulateIndex+++ }+ }+ else {+ inserts.push({key: wantedItem.key, to: k})+ }+ }+ else {+ inserts.push({key: wantedItem.key, to: k})+ }+ k+++ }+ // a key in simulate has no matching wanted key, remove it+ else if (simulateItem && simulateItem.key) {+ removes.push(remove(simulate, simulateIndex, simulateItem.key))+ }+ }+ else {+ simulateIndex+++ k+++ }+ }++ // remove all the remaining nodes from simulate+ while(simulateIndex < simulate.length) {+ simulateItem = simulate[simulateIndex]+ removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key))+ }++ // If the only moves we have are deletes then we can just+ // let the delete patch remove these items.+ if (removes.length === deletedItems && !inserts.length) {+ return {+ children: newChildren,+ moves: null+ }+ }++ return {+ children: newChildren,+ moves: {+ removes: removes,+ inserts: inserts+ }+ }+}++function remove(arr, index, key) {+ arr.splice(index, 1)++ return {+ from: index,+ key: key+ }+}++function keyIndex(children) {+ var keys = {}+ var free = []+ var length = children.length++ for (var i = 0; i < length; i++) {+ var child = children[i]++ if (child.key) {+ keys[child.key] = i+ } else {+ free.push(i)+ }+ }++ return {+ keys: keys, // A hash of key name to index+ free: free // An array of unkeyed item indices+ }+}++function appendPatch(apply, patch) {+ if (apply) {+ if (isArray(apply)) {+ apply.push(patch)+ } else {+ apply = [apply, patch]+ }++ return apply+ } else {+ return patch+ }+}
+ virtual-dom/handle-thunk.js view
@@ -0,0 +1,18 @@+var isVNode = require("virtual-dom/vnode/is-vnode")+var isVText = require("virtual-dom/vnode/is-vtext")+var isWidget = require("virtual-dom/vnode/is-widget")+var isThunk = require("virtual-dom/vnode/is-thunk")++module.exports = handleThunk++function handleThunk(a, b) {+ return { a: isThunk(a) ? renderThunk(a, null) : null+ , b: isThunk(b) ? renderThunk(b, a) : null+ }+}++function renderThunk(thunk, previous) {+ if(thunk.vnode) return null;+ thunk.render(previous);+ return thunk.vnode ? null : thunk;+}
+ virtual-dom/lib.js view
@@ -0,0 +1,2710 @@+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){+/*+ vtree/diff module modified to defer rendering thunks. this makes it possible to+ implement thunks that cannot be called directly as a function, but have+ an asynchronous callback or require evaluation in some specific runtime+ environment+ */++var isArray = require("x-is-array")++var VPatch = require("virtual-dom/vnode/vpatch")+var isVNode = require("virtual-dom/vnode/is-vnode")+var isVText = require("virtual-dom/vnode/is-vtext")+var isWidget = require("virtual-dom/vnode/is-widget")+var isThunk = require("virtual-dom/vnode/is-thunk")+var handleThunk = require("./handle-thunk")++var diffProps = require("virtual-dom/vtree/diff-props")++module.exports = diff++// unevaluated thunks are added to the thunks argument (array)+function diff(a, b, thunks) {+ if(!a) throw new Error ("diff a: " + a);+ if(!a) throw new Error ("diff b: " + b);+ var patch = { a: a }+ walk(a, b, patch, thunks, 0)+ return patch+}++function walk(a, b, patch, thunks, index) {+ if (a === b) {+ return+ }++ var apply = patch[index]+ var applyClear = false++ if (isThunk(a) || isThunk(b)) {+ doThunks(a, b, patch, thunks, index)+ } else if (b == null) {++ // If a is a widget we will add a remove patch for it+ // Otherwise any child widgets/hooks must be destroyed.+ // This prevents adding two remove patches for a widget.+ if (!isWidget(a)) {+ clearState(a, patch, index)+ apply = patch[index]+ }++ apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b))+ } else if (isVNode(b)) {+ if (isVNode(a)) {+ if (a.tagName === b.tagName &&+ a.namespace === b.namespace &&+ a.key === b.key) {+ var propsPatch = diffProps(a.properties, b.properties)+ if (propsPatch) {+ apply = appendPatch(apply,+ new VPatch(VPatch.PROPS, a, propsPatch))+ }+ apply = diffChildren(a, b, patch, apply, thunks, index)+ } else {+ apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))+ applyClear = true+ }+ } else {+ apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))+ applyClear = true+ }+ } else if (isVText(b)) {+ if (!isVText(a)) {+ apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))+ applyClear = true+ } else if (a.text !== b.text) {+ apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))+ }+ } else if (isWidget(b)) {+ if (!isWidget(a)) {+ applyClear = true+ }++ apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b))+ }++ if (apply) {+ patch[index] = apply+ }++ if (applyClear) {+ clearState(a, patch, index)+ }+}++function diffChildren(a, b, patch, apply, thunks, index) {+ var aChildren = a.children+ var orderedSet = reorder(aChildren, b.children)+ var bChildren = orderedSet.children++ var aLen = aChildren.length+ var bLen = bChildren.length+ var len = aLen > bLen ? aLen : bLen++ for (var i = 0; i < len; i++) {+ var leftNode = aChildren[i]+ var rightNode = bChildren[i]+ index += 1++ if (!leftNode) {+ if (rightNode) {+ // Excess nodes in b need to be added+ apply = appendPatch(apply,+ new VPatch(VPatch.INSERT, null, rightNode))+ }+ } else {+ walk(leftNode, rightNode, patch, thunks, index)+ }++ if (isVNode(leftNode) && leftNode.count) {+ index += leftNode.count+ }+ }++ if (orderedSet.moves) {+ // Reorder nodes last+ apply = appendPatch(apply, new VPatch(+ VPatch.ORDER,+ a,+ orderedSet.moves+ ))+ }++ return apply+}++function clearState(vNode, patch, index) {+ // TODO: Make this a single walk, not two+ unhook(vNode, patch, index)+ destroyWidgets(vNode, patch, index)+}++// Patch records for all destroyed widgets must be added because we need+// a DOM node reference for the destroy function+function destroyWidgets(vNode, patch, index) {+ if (isWidget(vNode)) {+ if (typeof vNode.destroy === "function") {+ patch[index] = appendPatch(+ patch[index],+ new VPatch(VPatch.REMOVE, vNode, null)+ )+ }+ } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) {+ var children = vNode.children+ var len = children.length+ for (var i = 0; i < len; i++) {+ var child = children[i]+ index += 1++ destroyWidgets(child, patch, index)++ if (isVNode(child) && child.count) {+ index += child.count+ }+ }+ } else if (isThunk(vNode)) {+ doThunks(vNode, null, patch, thunks, index)+ }+}++// Create a sub-patch for thunks+function doThunks(a, b, patch, thunks, index) {+ var ts = handleThunk(a, b);+ if(ts.a || ts.b) {+ // defer rendering, caller is responsible for:+ // - filling ts.a.vnode / ts.b.vnode with the result from the thunk+ // - ts.p[t.sp] = new VPatch(VPatch.THUNK, null, diff(ts.a.vnode, ts.b.vnode))+ // before using it with patch+ thunks.push({ i: index, p: patch, a: a, b: b});+ } else {+ var thunkPatch = diff(a.vnode, b.vnode, thunks)+ if (hasPatches(thunkPatch)) {+ patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch)+ }+ }+}++function hasPatches(patch) {+ for (var index in patch) {+ if (index !== "a") {+ return true+ }+ }++ return false+}++// Execute hooks when two nodes are identical+function unhook(vNode, patch, index) {+ if (isVNode(vNode)) {+ if (vNode.hooks) {+ patch[index] = appendPatch(+ patch[index],+ new VPatch(+ VPatch.PROPS,+ vNode,+ undefinedKeys(vNode.hooks)+ )+ )+ }++ if (vNode.descendantHooks || vNode.hasThunks) {+ var children = vNode.children+ var len = children.length+ for (var i = 0; i < len; i++) {+ var child = children[i]+ index += 1++ unhook(child, patch, index)++ if (isVNode(child) && child.count) {+ index += child.count+ }+ }+ }+ } else if (isThunk(vNode)) {+ doThunks(vNode, null, patch, thunks, index)+ }+}++function undefinedKeys(obj) {+ var result = {}++ for (var key in obj) {+ result[key] = undefined+ }++ return result+}++// List diff, naive left to right reordering+function reorder(aChildren, bChildren) {+ // O(M) time, O(M) memory+ var bChildIndex = keyIndex(bChildren)+ var bKeys = bChildIndex.keys+ var bFree = bChildIndex.free++ if (bFree.length === bChildren.length) {+ return {+ children: bChildren,+ moves: null+ }+ }++ // O(N) time, O(N) memory+ var aChildIndex = keyIndex(aChildren)+ var aKeys = aChildIndex.keys+ var aFree = aChildIndex.free++ if (aFree.length === aChildren.length) {+ return {+ children: bChildren,+ moves: null+ }+ }++ // O(MAX(N, M)) memory+ var newChildren = []++ var freeIndex = 0+ var freeCount = bFree.length+ var deletedItems = 0++ // Iterate through a and match a node in b+ // O(N) time,+ for (var i = 0 ; i < aChildren.length; i++) {+ var aItem = aChildren[i]+ var itemIndex++ if (aItem.key) {+ if (bKeys.hasOwnProperty(aItem.key)) {+ // Match up the old keys+ itemIndex = bKeys[aItem.key]+ newChildren.push(bChildren[itemIndex])++ } else {+ // Remove old keyed items+ itemIndex = i - deletedItems+++ newChildren.push(null)+ }+ } else {+ // Match the item in a with the next free item in b+ if (freeIndex < freeCount) {+ itemIndex = bFree[freeIndex++]+ newChildren.push(bChildren[itemIndex])+ } else {+ // There are no free items in b to match with+ // the free items in a, so the extra free nodes+ // are deleted.+ itemIndex = i - deletedItems+++ newChildren.push(null)+ }+ }+ }++ var lastFreeIndex = freeIndex >= bFree.length ?+ bChildren.length :+ bFree[freeIndex]++ // Iterate through b and append any new keys+ // O(M) time+ for (var j = 0; j < bChildren.length; j++) {+ var newItem = bChildren[j]++ if (newItem.key) {+ if (!aKeys.hasOwnProperty(newItem.key)) {+ // Add any new keyed items+ // We are adding new items to the end and then sorting them+ // in place. In future we should insert new items in place.+ newChildren.push(newItem)+ }+ } else if (j >= lastFreeIndex) {+ // Add any leftover non-keyed items+ newChildren.push(newItem)+ }+ }++ var simulate = newChildren.slice()+ var simulateIndex = 0+ var removes = []+ var inserts = []+ var simulateItem++ for (var k = 0; k < bChildren.length;) {+ var wantedItem = bChildren[k]+ simulateItem = simulate[simulateIndex]++ // remove items+ while (simulateItem === null && simulate.length) {+ removes.push(remove(simulate, simulateIndex, null))+ simulateItem = simulate[simulateIndex]+ }++ if (!simulateItem || simulateItem.key !== wantedItem.key) {+ // if we need a key in this position...+ if (wantedItem.key) {+ if (simulateItem && simulateItem.key) {+ // if an insert doesn't put this key in place, it needs to move+ if (bKeys[simulateItem.key] !== k + 1) {+ removes.push(remove(simulate, simulateIndex, simulateItem.key))+ simulateItem = simulate[simulateIndex]+ // if the remove didn't put the wanted item in place, we need to insert it+ if (!simulateItem || simulateItem.key !== wantedItem.key) {+ inserts.push({key: wantedItem.key, to: k})+ }+ // items are matching, so skip ahead+ else {+ simulateIndex+++ }+ }+ else {+ inserts.push({key: wantedItem.key, to: k})+ }+ }+ else {+ inserts.push({key: wantedItem.key, to: k})+ }+ k+++ }+ // a key in simulate has no matching wanted key, remove it+ else if (simulateItem && simulateItem.key) {+ removes.push(remove(simulate, simulateIndex, simulateItem.key))+ }+ }+ else {+ simulateIndex+++ k+++ }+ }++ // remove all the remaining nodes from simulate+ while(simulateIndex < simulate.length) {+ simulateItem = simulate[simulateIndex]+ removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key))+ }++ // If the only moves we have are deletes then we can just+ // let the delete patch remove these items.+ if (removes.length === deletedItems && !inserts.length) {+ return {+ children: newChildren,+ moves: null+ }+ }++ return {+ children: newChildren,+ moves: {+ removes: removes,+ inserts: inserts+ }+ }+}++function remove(arr, index, key) {+ arr.splice(index, 1)++ return {+ from: index,+ key: key+ }+}++function keyIndex(children) {+ var keys = {}+ var free = []+ var length = children.length++ for (var i = 0; i < length; i++) {+ var child = children[i]++ if (child.key) {+ keys[child.key] = i+ } else {+ free.push(i)+ }+ }++ return {+ keys: keys, // A hash of key name to index+ free: free // An array of unkeyed item indices+ }+}++function appendPatch(apply, patch) {+ if (apply) {+ if (isArray(apply)) {+ apply.push(patch)+ } else {+ apply = [apply, patch]+ }++ return apply+ } else {+ return patch+ }+}++},{"./handle-thunk":2,"virtual-dom/vnode/is-thunk":37,"virtual-dom/vnode/is-vnode":39,"virtual-dom/vnode/is-vtext":40,"virtual-dom/vnode/is-widget":41,"virtual-dom/vnode/vpatch":44,"virtual-dom/vtree/diff-props":46,"x-is-array":47}],2:[function(require,module,exports){+var isVNode = require("virtual-dom/vnode/is-vnode")+var isVText = require("virtual-dom/vnode/is-vtext")+var isWidget = require("virtual-dom/vnode/is-widget")+var isThunk = require("virtual-dom/vnode/is-thunk")++module.exports = handleThunk++function handleThunk(a, b) {+ return { a: isThunk(a) ? renderThunk(a, null) : null+ , b: isThunk(b) ? renderThunk(b, a) : null+ }+}++function renderThunk(thunk, previous) {+ if(thunk.vnode) return null;+ thunk.render(previous);+ return thunk.vnode ? null : thunk;+}++},{"virtual-dom/vnode/is-thunk":37,"virtual-dom/vnode/is-vnode":39,"virtual-dom/vnode/is-vtext":40,"virtual-dom/vnode/is-widget":41}],3:[function(require,module,exports){+/*+ to generate lib.js, install virtual-dom and process file:++ $ npm install+ $ grunt+ the ./diff module is vtree/diff with a few changes to+ allow diff to run in an asynchronous thread in the presence of+ memoized nodes.+ */++/*+ Note on memory management:++ To ensure accurate heap tracing for finalization and profiling purposes,+ GHCJS needs to know reachable all Haskell values. ghcjs-vdom stores some+ Haskell values inside JavaScript references and uses extensible retention+ to collect these values. It's crucial that all data structures that may+ contain Haskell values are stored directly in a JSVal and not inside other+ JS data structures.++ The recognized types are:++ - HSPatch:+ The patch object contains the new (target) virtual-dom tree and+ the original tree is reachable trough the parent. Since all handlers,+ components and thunks are reachable through these trees, the patch itself+ does not need to be traversed.++ - HSMount:+ All mounted HSMount points are scanned as roots. The current virtual tree+ (mount.vtree) is traversed for this.++ - HSComponent:+ Traversed when reachable through Haskell heap or virtual-dom tree. Contains+ current tree (component.vtree) and rendering action (component.hsRender)++ - HSThunk:+ Traversed when reachable through Haskell heap or virtual-dom tree. Contains+ Haskell suspension (thunk.hst) or rendered tree (thunk.vnode)++ - virtual node ( isVirtualNode(x) )+ Some node in a virtual-dom tree. Haskell event handlers are stored in 'ev-*'+ properties, which virtual-dom adds to the node's hooks (vnode.hooks). If+ a virtual node contains thunks, widgets or any of its descendants have hooks,+ the children of the node have to be traversed.++ forceThunks and forcePatch fill an array of thunks, which is not directly+ scannable; however, these functions are only used as part of a `diff` operation.+ The initial diff creates an HSPatch object, through which the original and target+ virtual-dom tree are completely reachable.+ */++var isVirtualNode = require('virtual-dom/vnode/is-vnode');+var isThunk = require('virtual-dom/vnode/is-thunk');+var isWidget = require("virtual-dom/vnode/is-widget");+var h = require('virtual-dom/h');+var isArray = require('x-is-array');+var VPatch = require("virtual-dom/vnode/vpatch");+var VText = require('virtual-dom/vnode/vtext');+var vdomPatch = require('virtual-dom/vdom/patch');+var DomDelegator = require('dom-delegator');++var diff = require('./diff');++var VRenderableN = 0;++/** @constructor */+function HSPatch(patch, old, vnode, parent) {+ this.patch = patch;+ this.old = old;+ this.vnode = vnode;+ this.parent = parent;+}++/** @constructor */+function HSThunk(t, ids, key) {+ this.hst = t; // haskell thunk+ this.ids = ids; // array of haskell unique ids+ this.key = key;+ this.vnode = null;+ this._ghcjsMark = 0;+}++HSThunk.prototype.type = 'Thunk';++/* + render returns the deferred rendering object+ null if the thunk has already been rendered, in which case the value is in this.vnode+ */+HSThunk.prototype.render = function(previous) {+ if(previous && !this.vnode && eqIds(this.ids, previous.ids)) {+ if(previous.hst) {+ this.hst = previous.hst;+ } else {+ this.hst = null;+ this.vnode = previous.vnode;+ }+ }+ return this.vnode ? null : this;+}++/** @constructor */+function HSComponent(r, mnt, unmnt, key) {+ this._key = ++VRenderableN;+ this.hsRender = r; // IO action that produces a vdom tree+ this.hsMount = mnt;+ this.hsUnmount = unmnt;+ this.key = key || this._key;+ this.vnode = this.initialVnode = new VText("");+ this.pending = [];+ this.mounts = {};+ this.version = 0;+ this.latest = 0;+}++HSComponent.prototype.type = 'Widget';++HSComponent.prototype.init = function() {+ var n = document.createTextNode('');+ if(this.vnode !== this.initialVnode) {+ var thunks = [];+ var p = diff(this.initialVnode, this.vnode, thunks);+ if(thunks.length !== 0) {+ throw new Error("HSComponent vnode contains unevaluated thunks");+ }+ n = vdomPatch(n, p);+ }+ var m = new HSComponentMount(n);+ n._key = m._key;+ n._widget = this;+ this.mounts[m._key] = m;+ if(this.hsMount) {+ h$vdomMountComponentCallback(this.hsMount, m._key, this);+ }+ return n;+}++HSComponent.prototype.destroy = function(domNode) {+ delete this.mounts[domNode._key];+ if(this.hsUnmount) {+ h$vdomUnmountComponentCallback(this.hsUnmount, domNode._key, domNode);+ }+}++HSComponent.prototype.diff = function(v, thunks) {+ var vn = this.vnode;+ if(this.pending.length > 0) {+ vn = this.pending[this.pending.length-1].vnode;+ }+ return new HSPatch( diff(vn, v, thunks)+ , vn+ , v+ , this);+}++HSComponent.prototype.addPatch = function(p) {+ var cur = this.pending.length > 0 ? this.pending[this.pending.length-1]+ : this.vnode;+ if(p.old === cur) this.pending.push(p);+}++HSComponent.prototype.patch = function(p) {+ if(this.pending.length > 0) {+ var pnd = this.pending;+ this.pending = [];+ for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]);+ }+ if(!p) return;+ if(p.parent !== this || p.old !== this.vnode) {+ return false;+ }+ for(var k in this.mounts) {+ var m = this.mounts[k];+ m.node = vdomPatch(m.node, p.patch);+ }+ this.vnode = p.vnode;+ return true;+}++// only use this for manually updated components (i.e. no diff/patch)+HSComponent.prototype.updateMount = function(mnt, node) {+ var m = this.mounts[mnt];+ node._key = mnt;+ node._widget = this;+ m.node.parentNode.replaceChild(node, m.node);+ m.node = node;+}++HSComponent.prototype.update = function(leftVNode, node) {+ if(node._widget) {+ if(node._widget == this) return node;+ node._widget.destroy(node);+ }+ return this.init();+}++var HSComponentMountN = 0;+function HSComponentMount(domNode) {+ this._key = ++HSComponentMountN;+ this.node = domNode;+}++/** @constructor */+function HSMount(domNode) {+ this._key = ++VRenderableN;+ // this.version = 0;+ this.vnode = new VText(""); // currently rendered vdom tree+ this.pending = []; // pending patches, not yet applied+ this.node = document.createTextNode("");+ this.parentNode = domNode;+}++HSMount.prototype.diff = function(v, thunks) {+ var vn = this.vnode;+ if(this.pending.length > 0) {+ vn = this.pending[this.pending.length-1].vnode;+ }+ return new HSPatch( diff(vn, v, thunks)+ , vn+ , v+ , this);+}++HSMount.prototype.addPatch = function(p) {+ var cur = this.pending.length > 0 ? this.pending[this.pending.length-1]+ : this.vnode;+ if(p.old === cur) this.pending.push(p);+}++// HSMount.patch(null) to flush pending list+HSMount.prototype.patch = function(p) {+ if(this.pending.length > 0) {+ var pnd = this.pending;+ this.pending = [];+ for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]);+ }+ if(!p) return;+ if(p.parent !== this || p.old !== this.vnode) {+ return false;+ }+ this.node = vdomPatch(this.node, p.patch);+ this.vnode = p.vnode;+ return true;+}+ +/* mount a vdom tree, making it visible to extensible retention */+function mount(domNode) {+ while(domNode.firstChild) domNode.removeChild(domNode.firstChild);+ var m = new HSMount(domNode);+ domNode.appendChild(m.node);+ vdomMounts.add(m);+ return m;+}++/* unmount a tree, removing all child nodes. */+function unmount(vmount) {+ var n = vmount.parentNode;+ while(n.firstChild) n.removeChild(n.firstChild);+ vdomMounts.remove(vmount);+}++/*+ Compare lists of object identifiers associated with a thunk node. If the lists are equal,+ the subtree does not have to be recomputed.+*/+function eqIds(ids1, ids2) {+ if(!ids1 || !ids2 || ids1.length != ids2.length) return false;+ for(var i=ids1.length-1;i>=0;i--) {+ var id1 = ids1[i], id2 = ids2[i];+ if(typeof id1 === 'number') {+ if(typeof id2 !== 'number') return false;+ if(id1 !== id2 && !((id1!=id1) && (id2!=id2))) return false;+ } else {+ if(id1 !== id2) return false;+ }+ }+ return true;+}++function forcePatch(p) {+ var thunks = [], i, j, pi;+ for(i in p) {+ var pi = p[i];+ if(isArray(pi)) {+ for(j=pi.length-1;j>=0;j--) {+ forceTree(pi[j].patch, thunks);+ }+ }+ else if(pi.patch) forceTree(pi.patch, thunks);+ else forceTree(pi, thunks);+ }+ return thunks;+}++function forceTree(n, t) {+ if(isThunk(n)) {+ if(n.vnode) forceTree(n.vnode, t);+ else t.push(n);+ } else if(isVirtualNode(n) && n.hasThunks) {+ for(var i=n.children.length-1;i>=0;i--) {+ forceTree(n.children[i], t);+ }+ }+}++/*+ scan all mounted virtual-dom trees+ */+function scanMounts(currentMark) {+ var i = vdomMounts.iter(), m, res = [];+ while((m = i.next()) !== null) {+ scanTreeRec(m.vnode, res, currentMark);+ if(m.pending.length > 0) {+ scanTreeRec(m.pending[m.pending.length-1].vnode, res, currentMark);+ }+ }+ return res;+}++/*+ scan a tree (extensible retention callback).++ returns:+ - an array of haskell items if any+ - true if no haskell items have been found+ - false if the object is not a ghcjs-vdom tree+ (fallthrough to other extensible retention scanners)+ */+var scanTreeRes = [];+function scanTree(o, currentMark) {+ if(isVirtualNode(o) || isThunk(o) || isWidget(o) ||+ o instanceof HSPatch || o instanceof HSComponent || o instanceof HSMount) {+ var r = scanTreeRes;+ scanTreeRec(o, r, currentMark);+ if(r.length > 0) {+ scanTreeRes = [];+ return r;+ } else {+ return true;+ }+ } else { // not a ghcjs-vdom object, fall through+ return false; + }+}++function scanTreeRec(o, r, currentMark) {+ if(o instanceof HSPatch) {+ scanTreeRec(o.vnode, r, currentMark);+ scanTreeRec(o.parent);+ } else if(o instanceof HSThunk) {+ if(o._ghcjsMark !== currentMark) {+ o._ghcjsMark = currentMark;+ if(o.hst) r.push(o.hst);+ if(o.vnode) scanTreeRec(o.vnode, r, currentMark);+ }+ } else if(o instanceof HSComponent) {+ if(o._ghcjsMark !== currentMark) {+ o._ghcjsMark = currentMark;+ if(o.hsRender) r.push(o.hsRender);+ if(o.hsMount) r.push(o.hsMount);+ if(o.hsUnmount) r.push(o.hsUnmount);+ if(o.vnode) scanTreeRec(o.vnode, r, currentMark);+ if(o.pending.length > 0) {+ scanTreeRec(o.pending[o.pending.length-1].vnode, r, currentMark);+ }+ }+ } else if(isVirtualNode(o)) {+ if(o._ghcjsMark !== currentMark) {+ o._ghcjsMark = currentMark;+ // collect event handlers+ var hooks = o.hooks;+ for(var p in hooks) {+ if(p.indexOf('ev-') === 0) {+ var handler = hooks[p];+ if(handler.value && handler.value.hsAction) {+ r.push(handler.value.hsAction);+ }+ }+ }+ // recurse if any of the children may have thunks, components or handlers+ if(o.hasWidgets || o.hasThunks || o.descendantHooks) {+ for(var i=o.children.length-1;i>=0;i--) {+ scanTreeRec(o.children[i], r, currentMark);+ }+ }+ }+ }+}++function setThunkPatch(n, p) {+ if(hasPatches(p)) n.p[n.i] = new VPatch(VPatch.THUNK, null, p);+}++function hasPatches(patch) {+ for (var index in patch) {+ if (index !== "a") {+ return true;+ }+ }+ return false;+}++function initDelegator(evTypes) {+ var d = DomDelegator();+ var l = evTypes.length;+ for(var i = 0; i < l; i++) {+ d.listenTo(evTypes[i]);+ }+}++function v(tag, props, children) {+ return h(tag, props, children);+}++function t(text) {+ return new VText(text);+}++function th(t, ids, key) {+ return new HSThunk(t, ids, key);+}++function c(r, m, u, key) {+ return new HSComponent(r, m, u, key);+}++function makeHandler(action, async) {+ var f = function(ev) {+ return h$vdomEventCallback(async, action, ev);+ }+ f.hsAction = action;+ return f;+}++var vdomMounts = new h$Set();++module.exports = { setThunkPatch: setThunkPatch+ , forceTree: forceTree+ , forcePatch: forcePatch+ , diff: diff+ , mount: mount+ , unmount: unmount+ , initDelegator: initDelegator+ , v: v+ , th: th+ , t: t+ , c: c+ , makeHandler: makeHandler+ };++// the global variable we're using in the bindings+h$vdom = module.exports;++h$registerExtensibleRetention(scanTree);+h$registerExtensibleRetentionRoot(scanMounts);++++},{"./diff":1,"dom-delegator":6,"virtual-dom/h":19,"virtual-dom/vdom/patch":30,"virtual-dom/vnode/is-thunk":37,"virtual-dom/vnode/is-vnode":39,"virtual-dom/vnode/is-widget":41,"virtual-dom/vnode/vpatch":44,"virtual-dom/vnode/vtext":45,"x-is-array":47}],4:[function(require,module,exports){+var EvStore = require("ev-store")++module.exports = addEvent++function addEvent(target, type, handler) {+ var events = EvStore(target)+ var event = events[type]++ if (!event) {+ events[type] = handler+ } else if (Array.isArray(event)) {+ if (event.indexOf(handler) === -1) {+ event.push(handler)+ }+ } else if (event !== handler) {+ events[type] = [event, handler]+ }+}++},{"ev-store":8}],5:[function(require,module,exports){+var globalDocument = require("global/document")+var EvStore = require("ev-store")+var createStore = require("weakmap-shim/create-store")++var addEvent = require("./add-event.js")+var removeEvent = require("./remove-event.js")+var ProxyEvent = require("./proxy-event.js")++var HANDLER_STORE = createStore()++module.exports = DOMDelegator++function DOMDelegator(document) {+ if (!(this instanceof DOMDelegator)) {+ return new DOMDelegator(document);+ }++ document = document || globalDocument++ this.target = document.documentElement+ this.events = {}+ this.rawEventListeners = {}+ this.globalListeners = {}+}++DOMDelegator.prototype.addEventListener = addEvent+DOMDelegator.prototype.removeEventListener = removeEvent++DOMDelegator.allocateHandle =+ function allocateHandle(func) {+ var handle = new Handle()++ HANDLER_STORE(handle).func = func;++ return handle+ }++DOMDelegator.transformHandle =+ function transformHandle(handle, broadcast) {+ var func = HANDLER_STORE(handle).func++ return this.allocateHandle(function (ev) {+ broadcast(ev, func);+ })+ }++DOMDelegator.prototype.addGlobalEventListener =+ function addGlobalEventListener(eventName, fn) {+ var listeners = this.globalListeners[eventName] || [];+ if (listeners.indexOf(fn) === -1) {+ listeners.push(fn)+ }++ this.globalListeners[eventName] = listeners;+ }++DOMDelegator.prototype.removeGlobalEventListener =+ function removeGlobalEventListener(eventName, fn) {+ var listeners = this.globalListeners[eventName] || [];++ var index = listeners.indexOf(fn)+ if (index !== -1) {+ listeners.splice(index, 1)+ }+ }++DOMDelegator.prototype.listenTo = function listenTo(eventName) {+ if (!(eventName in this.events)) {+ this.events[eventName] = 0;+ }++ this.events[eventName]++;++ if (this.events[eventName] !== 1) {+ return+ }++ var listener = this.rawEventListeners[eventName]+ if (!listener) {+ listener = this.rawEventListeners[eventName] =+ createHandler(eventName, this)+ }++ this.target.addEventListener(eventName, listener, true)+}++DOMDelegator.prototype.unlistenTo = function unlistenTo(eventName) {+ if (!(eventName in this.events)) {+ this.events[eventName] = 0;+ }++ if (this.events[eventName] === 0) {+ throw new Error("already unlistened to event.");+ }++ this.events[eventName]--;++ if (this.events[eventName] !== 0) {+ return+ }++ var listener = this.rawEventListeners[eventName]++ if (!listener) {+ throw new Error("dom-delegator#unlistenTo: cannot " ++ "unlisten to " + eventName)+ }++ this.target.removeEventListener(eventName, listener, true)+}++function createHandler(eventName, delegator) {+ var globalListeners = delegator.globalListeners;+ var delegatorTarget = delegator.target;++ return handler++ function handler(ev) {+ var globalHandlers = globalListeners[eventName] || []++ if (globalHandlers.length > 0) {+ var globalEvent = new ProxyEvent(ev);+ globalEvent.currentTarget = delegatorTarget;+ callListeners(globalHandlers, globalEvent)+ }++ findAndInvokeListeners(ev.target, ev, eventName)+ }+}++function findAndInvokeListeners(elem, ev, eventName) {+ var listener = getListener(elem, eventName)++ if (listener && listener.handlers.length > 0) {+ var listenerEvent = new ProxyEvent(ev);+ listenerEvent.currentTarget = listener.currentTarget+ callListeners(listener.handlers, listenerEvent)++ if (listenerEvent._bubbles) {+ var nextTarget = listener.currentTarget.parentNode+ findAndInvokeListeners(nextTarget, ev, eventName)+ }+ }+}++function getListener(target, type) {+ // terminate recursion if parent is `null`+ if (target === null || typeof target === "undefined") {+ return null+ }++ var events = EvStore(target)+ // fetch list of handler fns for this event+ var handler = events[type]+ var allHandler = events.event++ if (!handler && !allHandler) {+ return getListener(target.parentNode, type)+ }++ var handlers = [].concat(handler || [], allHandler || [])+ return new Listener(target, handlers)+}++function callListeners(handlers, ev) {+ handlers.forEach(function (handler) {+ if (typeof handler === "function") {+ handler(ev)+ } else if (typeof handler.handleEvent === "function") {+ handler.handleEvent(ev)+ } else if (handler.type === "dom-delegator-handle") {+ HANDLER_STORE(handler).func(ev)+ } else {+ throw new Error("dom-delegator: unknown handler " ++ "found: " + JSON.stringify(handlers));+ }+ })+}++function Listener(target, handlers) {+ this.currentTarget = target+ this.handlers = handlers+}++function Handle() {+ this.type = "dom-delegator-handle"+}++},{"./add-event.js":4,"./proxy-event.js":16,"./remove-event.js":17,"ev-store":8,"global/document":11,"weakmap-shim/create-store":14}],6:[function(require,module,exports){+var Individual = require("individual")+var cuid = require("cuid")+var globalDocument = require("global/document")++var DOMDelegator = require("./dom-delegator.js")++var versionKey = "13"+var cacheKey = "__DOM_DELEGATOR_CACHE@" + versionKey+var cacheTokenKey = "__DOM_DELEGATOR_CACHE_TOKEN@" + versionKey+var delegatorCache = Individual(cacheKey, {+ delegators: {}+})+var commonEvents = [+ "blur", "change", "click", "contextmenu", "dblclick",+ "error","focus", "focusin", "focusout", "input", "keydown",+ "keypress", "keyup", "load", "mousedown", "mouseup",+ "resize", "select", "submit", "touchcancel",+ "touchend", "touchstart", "unload"+]++/* Delegator is a thin wrapper around a singleton `DOMDelegator`+ instance.++ Only one DOMDelegator should exist because we do not want+ duplicate event listeners bound to the DOM.++ `Delegator` will also `listenTo()` all events unless+ every caller opts out of it+*/+module.exports = Delegator++function Delegator(opts) {+ opts = opts || {}+ var document = opts.document || globalDocument++ var cacheKey = document[cacheTokenKey]++ if (!cacheKey) {+ cacheKey =+ document[cacheTokenKey] = cuid()+ }++ var delegator = delegatorCache.delegators[cacheKey]++ if (!delegator) {+ delegator = delegatorCache.delegators[cacheKey] =+ new DOMDelegator(document)+ }++ if (opts.defaultEvents !== false) {+ for (var i = 0; i < commonEvents.length; i++) {+ delegator.listenTo(commonEvents[i])+ }+ }++ return delegator+}++Delegator.allocateHandle = DOMDelegator.allocateHandle;+Delegator.transformHandle = DOMDelegator.transformHandle;++},{"./dom-delegator.js":5,"cuid":7,"global/document":11,"individual":12}],7:[function(require,module,exports){+/**+ * cuid.js+ * Collision-resistant UID generator for browsers and node.+ * Sequential for fast db lookups and recency sorting.+ * Safe for element IDs and server-side lookups.+ *+ * Extracted from CLCTR+ *+ * Copyright (c) Eric Elliott 2012+ * MIT License+ */++/*global window, navigator, document, require, process, module */+(function (app) {+ 'use strict';+ var namespace = 'cuid',+ c = 0,+ blockSize = 4,+ base = 36,+ discreteValues = Math.pow(base, blockSize),++ pad = function pad(num, size) {+ var s = "000000000" + num;+ return s.substr(s.length-size);+ },++ randomBlock = function randomBlock() {+ return pad((Math.random() *+ discreteValues << 0)+ .toString(base), blockSize);+ },++ safeCounter = function () {+ c = (c < discreteValues) ? c : 0;+ c++; // this is not subliminal+ return c - 1;+ },++ api = function cuid() {+ // Starting with a lowercase letter makes+ // it HTML element ID friendly.+ var letter = 'c', // hard-coded allows for sequential access++ // timestamp+ // warning: this exposes the exact date and time+ // that the uid was created.+ timestamp = (new Date().getTime()).toString(base),++ // Prevent same-machine collisions.+ counter,++ // A few chars to generate distinct ids for different+ // clients (so different computers are far less+ // likely to generate the same id)+ fingerprint = api.fingerprint(),++ // Grab some more chars from Math.random()+ random = randomBlock() + randomBlock();++ counter = pad(safeCounter().toString(base), blockSize);++ return (letter + timestamp + counter + fingerprint + random);+ };++ api.slug = function slug() {+ var date = new Date().getTime().toString(36),+ counter,+ print = api.fingerprint().slice(0,1) ++ api.fingerprint().slice(-1),+ random = randomBlock().slice(-2);++ counter = safeCounter().toString(36).slice(-4);++ return date.slice(-2) ++ counter + print + random;+ };++ api.globalCount = function globalCount() {+ // We want to cache the results of this+ var cache = (function calc() {+ var i,+ count = 0;++ for (i in window) {+ count++;+ }++ return count;+ }());++ api.globalCount = function () { return cache; };+ return cache;+ };++ api.fingerprint = function browserPrint() {+ return pad((navigator.mimeTypes.length ++ navigator.userAgent.length).toString(36) ++ api.globalCount().toString(36), 4);+ };++ // don't change anything from here down.+ if (app.register) {+ app.register(namespace, api);+ } else if (typeof module !== 'undefined') {+ module.exports = api;+ } else {+ app[namespace] = api;+ }++}(this.applitude || this));++},{}],8:[function(require,module,exports){+'use strict';++var OneVersionConstraint = require('individual/one-version');++var MY_VERSION = '7';+OneVersionConstraint('ev-store', MY_VERSION);++var hashKey = '__EV_STORE_KEY@' + MY_VERSION;++module.exports = EvStore;++function EvStore(elem) {+ var hash = elem[hashKey];++ if (!hash) {+ hash = elem[hashKey] = {};+ }++ return hash;+}++},{"individual/one-version":10}],9:[function(require,module,exports){+(function (global){+'use strict';++/*global window, global*/++var root = typeof window !== 'undefined' ?+ window : typeof global !== 'undefined' ?+ global : {};++module.exports = Individual;++function Individual(key, value) {+ if (key in root) {+ return root[key];+ }++ root[key] = value;++ return value;+}++}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})+},{}],10:[function(require,module,exports){+'use strict';++var Individual = require('./index.js');++module.exports = OneVersion;++function OneVersion(moduleName, version, defaultValue) {+ var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName;+ var enforceKey = key + '_ENFORCE_SINGLETON';++ var versionValue = Individual(enforceKey, version);++ if (versionValue !== version) {+ throw new Error('Can only have one copy of ' ++ moduleName + '.\n' ++ 'You already have version ' + versionValue ++ ' installed.\n' ++ 'This means you cannot install version ' + version);+ }++ return Individual(key, defaultValue);+}++},{"./index.js":9}],11:[function(require,module,exports){+(function (global){+var topLevel = typeof global !== 'undefined' ? global :+ typeof window !== 'undefined' ? window : {}+var minDoc = require('min-document');++if (typeof document !== 'undefined') {+ module.exports = document;+} else {+ var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];++ if (!doccy) {+ doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;+ }++ module.exports = doccy;+}++}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})+},{"min-document":18}],12:[function(require,module,exports){+(function (global){+var root = typeof window !== 'undefined' ?+ window : typeof global !== 'undefined' ?+ global : {};++module.exports = Individual++function Individual(key, value) {+ if (root[key]) {+ return root[key]+ }++ Object.defineProperty(root, key, {+ value: value+ , configurable: true+ })++ return value+}++}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})+},{}],13:[function(require,module,exports){+if (typeof Object.create === 'function') {+ // implementation from standard node.js 'util' module+ module.exports = function inherits(ctor, superCtor) {+ ctor.super_ = superCtor+ ctor.prototype = Object.create(superCtor.prototype, {+ constructor: {+ value: ctor,+ enumerable: false,+ writable: true,+ configurable: true+ }+ });+ };+} else {+ // old school shim for old browsers+ module.exports = function inherits(ctor, superCtor) {+ ctor.super_ = superCtor+ var TempCtor = function () {}+ TempCtor.prototype = superCtor.prototype+ ctor.prototype = new TempCtor()+ ctor.prototype.constructor = ctor+ }+}++},{}],14:[function(require,module,exports){+var hiddenStore = require('./hidden-store.js');++module.exports = createStore;++function createStore() {+ var key = {};++ return function (obj) {+ if ((typeof obj !== 'object' || obj === null) &&+ typeof obj !== 'function'+ ) {+ throw new Error('Weakmap-shim: Key must be object')+ }++ var store = obj.valueOf(key);+ return store && store.identity === key ?+ store : hiddenStore(obj, key);+ };+}++},{"./hidden-store.js":15}],15:[function(require,module,exports){+module.exports = hiddenStore;++function hiddenStore(obj, key) {+ var store = { identity: key };+ var valueOf = obj.valueOf;++ Object.defineProperty(obj, "valueOf", {+ value: function (value) {+ return value !== key ?+ valueOf.apply(this, arguments) : store;+ },+ writable: true+ });++ return store;+}++},{}],16:[function(require,module,exports){+var inherits = require("inherits")++var ALL_PROPS = [+ "altKey", "bubbles", "cancelable", "ctrlKey",+ "eventPhase", "metaKey", "relatedTarget", "shiftKey",+ "target", "timeStamp", "type", "view", "which"+]+var KEY_PROPS = ["char", "charCode", "key", "keyCode"]+var MOUSE_PROPS = [+ "button", "buttons", "clientX", "clientY", "layerX",+ "layerY", "offsetX", "offsetY", "pageX", "pageY",+ "screenX", "screenY", "toElement"+]++var rkeyEvent = /^key|input/+var rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/++module.exports = ProxyEvent++function ProxyEvent(ev) {+ if (!(this instanceof ProxyEvent)) {+ return new ProxyEvent(ev)+ }++ if (rkeyEvent.test(ev.type)) {+ return new KeyEvent(ev)+ } else if (rmouseEvent.test(ev.type)) {+ return new MouseEvent(ev)+ }++ for (var i = 0; i < ALL_PROPS.length; i++) {+ var propKey = ALL_PROPS[i]+ this[propKey] = ev[propKey]+ }++ this._rawEvent = ev+ this._bubbles = false;+}++ProxyEvent.prototype.preventDefault = function () {+ this._rawEvent.preventDefault()+}++ProxyEvent.prototype.startPropagation = function () {+ this._bubbles = true;+}++function MouseEvent(ev) {+ for (var i = 0; i < ALL_PROPS.length; i++) {+ var propKey = ALL_PROPS[i]+ this[propKey] = ev[propKey]+ }++ for (var j = 0; j < MOUSE_PROPS.length; j++) {+ var mousePropKey = MOUSE_PROPS[j]+ this[mousePropKey] = ev[mousePropKey]+ }++ this._rawEvent = ev+}++inherits(MouseEvent, ProxyEvent)++function KeyEvent(ev) {+ for (var i = 0; i < ALL_PROPS.length; i++) {+ var propKey = ALL_PROPS[i]+ this[propKey] = ev[propKey]+ }++ for (var j = 0; j < KEY_PROPS.length; j++) {+ var keyPropKey = KEY_PROPS[j]+ this[keyPropKey] = ev[keyPropKey]+ }++ this._rawEvent = ev+}++inherits(KeyEvent, ProxyEvent)++},{"inherits":13}],17:[function(require,module,exports){+var EvStore = require("ev-store")++module.exports = removeEvent++function removeEvent(target, type, handler) {+ var events = EvStore(target)+ var event = events[type]++ if (!event) {+ return+ } else if (Array.isArray(event)) {+ var index = event.indexOf(handler)+ if (index !== -1) {+ event.splice(index, 1)+ }+ } else if (event === handler) {+ events[type] = null+ }+}++},{"ev-store":8}],18:[function(require,module,exports){++},{}],19:[function(require,module,exports){+var h = require("./virtual-hyperscript/index.js")++module.exports = h++},{"./virtual-hyperscript/index.js":34}],20:[function(require,module,exports){+/*!+ * Cross-Browser Split 1.1.1+ * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>+ * Available under the MIT License+ * ECMAScript compliant, uniform cross-browser split method+ */++/**+ * Splits a string into an array of strings using a regex or string separator. Matches of the+ * separator are not included in the result array. However, if `separator` is a regex that contains+ * capturing groups, backreferences are spliced into the result each time `separator` is matched.+ * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably+ * cross-browser.+ * @param {String} str String to split.+ * @param {RegExp|String} separator Regex or string to use for separating the string.+ * @param {Number} [limit] Maximum number of items to include in the result array.+ * @returns {Array} Array of substrings.+ * @example+ *+ * // Basic use+ * split('a b c d', ' ');+ * // -> ['a', 'b', 'c', 'd']+ *+ * // With limit+ * split('a b c d', ' ', 2);+ * // -> ['a', 'b']+ *+ * // Backreferences in result array+ * split('..word1 word2..', /([a-z]+)(\d+)/i);+ * // -> ['..', 'word', '1', ' ', 'word', '2', '..']+ */+module.exports = (function split(undef) {++ var nativeSplit = String.prototype.split,+ compliantExecNpcg = /()??/.exec("")[1] === undef,+ // NPCG: nonparticipating capturing group+ self;++ self = function(str, separator, limit) {+ // If `separator` is not a regex, use `nativeSplit`+ if (Object.prototype.toString.call(separator) !== "[object RegExp]") {+ return nativeSplit.call(str, separator, limit);+ }+ var output = [],+ flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6+ (separator.sticky ? "y" : ""),+ // Firefox 3++ lastLastIndex = 0,+ // Make `global` and avoid `lastIndex` issues by working with a copy+ separator = new RegExp(separator.source, flags + "g"),+ separator2, match, lastIndex, lastLength;+ str += ""; // Type-convert+ if (!compliantExecNpcg) {+ // Doesn't need flags gy, but they don't hurt+ separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);+ }+ /* Values for `limit`, per the spec:+ * If undefined: 4294967295 // Math.pow(2, 32) - 1+ * If 0, Infinity, or NaN: 0+ * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;+ * If negative number: 4294967296 - Math.floor(Math.abs(limit))+ * If other: Type-convert, then use the above rules+ */+ limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1+ limit >>> 0; // ToUint32(limit)+ while (match = separator.exec(str)) {+ // `separator.lastIndex` is not reliable cross-browser+ lastIndex = match.index + match[0].length;+ if (lastIndex > lastLastIndex) {+ output.push(str.slice(lastLastIndex, match.index));+ // Fix browsers whose `exec` methods don't consistently return `undefined` for+ // nonparticipating capturing groups+ if (!compliantExecNpcg && match.length > 1) {+ match[0].replace(separator2, function() {+ for (var i = 1; i < arguments.length - 2; i++) {+ if (arguments[i] === undef) {+ match[i] = undef;+ }+ }+ });+ }+ if (match.length > 1 && match.index < str.length) {+ Array.prototype.push.apply(output, match.slice(1));+ }+ lastLength = match[0].length;+ lastLastIndex = lastIndex;+ if (output.length >= limit) {+ break;+ }+ }+ if (separator.lastIndex === match.index) {+ separator.lastIndex++; // Avoid an infinite loop+ }+ }+ if (lastLastIndex === str.length) {+ if (lastLength || !separator.test("")) {+ output.push("");+ }+ } else {+ output.push(str.slice(lastLastIndex));+ }+ return output.length > limit ? output.slice(0, limit) : output;+ };++ return self;+})();++},{}],21:[function(require,module,exports){+module.exports=require(8)+},{"individual/one-version":23}],22:[function(require,module,exports){+module.exports=require(9)+},{}],23:[function(require,module,exports){+module.exports=require(10)+},{"./index.js":22}],24:[function(require,module,exports){+module.exports=require(11)+},{"min-document":18}],25:[function(require,module,exports){+"use strict";++module.exports = function isObject(x) {+ return typeof x === "object" && x !== null;+};++},{}],26:[function(require,module,exports){+var isObject = require("is-object")+var isHook = require("../vnode/is-vhook.js")++module.exports = applyProperties++function applyProperties(node, props, previous) {+ for (var propName in props) {+ var propValue = props[propName]++ if (propValue === undefined) {+ removeProperty(node, propName, propValue, previous);+ } else if (isHook(propValue)) {+ removeProperty(node, propName, propValue, previous)+ if (propValue.hook) {+ propValue.hook(node,+ propName,+ previous ? previous[propName] : undefined)+ }+ } else {+ if (isObject(propValue)) {+ patchObject(node, props, previous, propName, propValue);+ } else {+ node[propName] = propValue+ }+ }+ }+}++function removeProperty(node, propName, propValue, previous) {+ if (previous) {+ var previousValue = previous[propName]++ if (!isHook(previousValue)) {+ if (propName === "attributes") {+ for (var attrName in previousValue) {+ node.removeAttribute(attrName)+ }+ } else if (propName === "style") {+ for (var i in previousValue) {+ node.style[i] = ""+ }+ } else if (typeof previousValue === "string") {+ node[propName] = ""+ } else {+ node[propName] = null+ }+ } else if (previousValue.unhook) {+ previousValue.unhook(node, propName, propValue)+ }+ }+}++function patchObject(node, props, previous, propName, propValue) {+ var previousValue = previous ? previous[propName] : undefined++ // Set attributes+ if (propName === "attributes") {+ for (var attrName in propValue) {+ var attrValue = propValue[attrName]++ if (attrValue === undefined) {+ node.removeAttribute(attrName)+ } else {+ node.setAttribute(attrName, attrValue)+ }+ }++ return+ }++ if(previousValue && isObject(previousValue) &&+ getPrototype(previousValue) !== getPrototype(propValue)) {+ node[propName] = propValue+ return+ }++ if (!isObject(node[propName])) {+ node[propName] = {}+ }++ var replacer = propName === "style" ? "" : undefined++ for (var k in propValue) {+ var value = propValue[k]+ node[propName][k] = (value === undefined) ? replacer : value+ }+}++function getPrototype(value) {+ if (Object.getPrototypeOf) {+ return Object.getPrototypeOf(value)+ } else if (value.__proto__) {+ return value.__proto__+ } else if (value.constructor) {+ return value.constructor.prototype+ }+}++},{"../vnode/is-vhook.js":38,"is-object":25}],27:[function(require,module,exports){+var document = require("global/document")++var applyProperties = require("./apply-properties")++var isVNode = require("../vnode/is-vnode.js")+var isVText = require("../vnode/is-vtext.js")+var isWidget = require("../vnode/is-widget.js")+var handleThunk = require("../vnode/handle-thunk.js")++module.exports = createElement++function createElement(vnode, opts) {+ var doc = opts ? opts.document || document : document+ var warn = opts ? opts.warn : null++ vnode = handleThunk(vnode).a++ if (isWidget(vnode)) {+ return vnode.init()+ } else if (isVText(vnode)) {+ return doc.createTextNode(vnode.text)+ } else if (!isVNode(vnode)) {+ if (warn) {+ warn("Item is not a valid virtual dom node", vnode)+ }+ return null+ }++ var node = (vnode.namespace === null) ?+ doc.createElement(vnode.tagName) :+ doc.createElementNS(vnode.namespace, vnode.tagName)++ var props = vnode.properties+ applyProperties(node, props)++ var children = vnode.children++ for (var i = 0; i < children.length; i++) {+ var childNode = createElement(children[i], opts)+ if (childNode) {+ node.appendChild(childNode)+ }+ }++ return node+}++},{"../vnode/handle-thunk.js":36,"../vnode/is-vnode.js":39,"../vnode/is-vtext.js":40,"../vnode/is-widget.js":41,"./apply-properties":26,"global/document":24}],28:[function(require,module,exports){+// Maps a virtual DOM tree onto a real DOM tree in an efficient manner.+// We don't want to read all of the DOM nodes in the tree so we use+// the in-order tree indexing to eliminate recursion down certain branches.+// We only recurse into a DOM node if we know that it contains a child of+// interest.++var noChild = {}++module.exports = domIndex++function domIndex(rootNode, tree, indices, nodes) {+ if (!indices || indices.length === 0) {+ return {}+ } else {+ indices.sort(ascending)+ return recurse(rootNode, tree, indices, nodes, 0)+ }+}++function recurse(rootNode, tree, indices, nodes, rootIndex) {+ nodes = nodes || {}+++ if (rootNode) {+ if (indexInRange(indices, rootIndex, rootIndex)) {+ nodes[rootIndex] = rootNode+ }++ var vChildren = tree.children++ if (vChildren) {++ var childNodes = rootNode.childNodes++ for (var i = 0; i < tree.children.length; i++) {+ rootIndex += 1++ var vChild = vChildren[i] || noChild+ var nextIndex = rootIndex + (vChild.count || 0)++ // skip recursion down the tree if there are no nodes down here+ if (indexInRange(indices, rootIndex, nextIndex)) {+ recurse(childNodes[i], vChild, indices, nodes, rootIndex)+ }++ rootIndex = nextIndex+ }+ }+ }++ return nodes+}++// Binary search for an index in the interval [left, right]+function indexInRange(indices, left, right) {+ if (indices.length === 0) {+ return false+ }++ var minIndex = 0+ var maxIndex = indices.length - 1+ var currentIndex+ var currentItem++ while (minIndex <= maxIndex) {+ currentIndex = ((maxIndex + minIndex) / 2) >> 0+ currentItem = indices[currentIndex]++ if (minIndex === maxIndex) {+ return currentItem >= left && currentItem <= right+ } else if (currentItem < left) {+ minIndex = currentIndex + 1+ } else if (currentItem > right) {+ maxIndex = currentIndex - 1+ } else {+ return true+ }+ }++ return false;+}++function ascending(a, b) {+ return a > b ? 1 : -1+}++},{}],29:[function(require,module,exports){+var applyProperties = require("./apply-properties")++var isWidget = require("../vnode/is-widget.js")+var VPatch = require("../vnode/vpatch.js")++var updateWidget = require("./update-widget")++module.exports = applyPatch++function applyPatch(vpatch, domNode, renderOptions) {+ var type = vpatch.type+ var vNode = vpatch.vNode+ var patch = vpatch.patch++ switch (type) {+ case VPatch.REMOVE:+ return removeNode(domNode, vNode)+ case VPatch.INSERT:+ return insertNode(domNode, patch, renderOptions)+ case VPatch.VTEXT:+ return stringPatch(domNode, vNode, patch, renderOptions)+ case VPatch.WIDGET:+ return widgetPatch(domNode, vNode, patch, renderOptions)+ case VPatch.VNODE:+ return vNodePatch(domNode, vNode, patch, renderOptions)+ case VPatch.ORDER:+ reorderChildren(domNode, patch)+ return domNode+ case VPatch.PROPS:+ applyProperties(domNode, patch, vNode.properties)+ return domNode+ case VPatch.THUNK:+ return replaceRoot(domNode,+ renderOptions.patch(domNode, patch, renderOptions))+ default:+ return domNode+ }+}++function removeNode(domNode, vNode) {+ var parentNode = domNode.parentNode++ if (parentNode) {+ parentNode.removeChild(domNode)+ }++ destroyWidget(domNode, vNode);++ return null+}++function insertNode(parentNode, vNode, renderOptions) {+ var newNode = renderOptions.render(vNode, renderOptions)++ if (parentNode) {+ parentNode.appendChild(newNode)+ }++ return parentNode+}++function stringPatch(domNode, leftVNode, vText, renderOptions) {+ var newNode++ if (domNode.nodeType === 3) {+ domNode.replaceData(0, domNode.length, vText.text)+ newNode = domNode+ } else {+ var parentNode = domNode.parentNode+ newNode = renderOptions.render(vText, renderOptions)++ if (parentNode && newNode !== domNode) {+ parentNode.replaceChild(newNode, domNode)+ }+ }++ return newNode+}++function widgetPatch(domNode, leftVNode, widget, renderOptions) {+ var updating = updateWidget(leftVNode, widget)+ var newNode++ if (updating) {+ newNode = widget.update(leftVNode, domNode) || domNode+ } else {+ newNode = renderOptions.render(widget, renderOptions)+ }++ var parentNode = domNode.parentNode++ if (parentNode && newNode !== domNode) {+ parentNode.replaceChild(newNode, domNode)+ }++ if (!updating) {+ destroyWidget(domNode, leftVNode)+ }++ return newNode+}++function vNodePatch(domNode, leftVNode, vNode, renderOptions) {+ var parentNode = domNode.parentNode+ var newNode = renderOptions.render(vNode, renderOptions)++ if (parentNode && newNode !== domNode) {+ parentNode.replaceChild(newNode, domNode)+ }++ return newNode+}++function destroyWidget(domNode, w) {+ if (typeof w.destroy === "function" && isWidget(w)) {+ w.destroy(domNode)+ }+}++function reorderChildren(domNode, moves) {+ var childNodes = domNode.childNodes+ var keyMap = {}+ var node+ var remove+ var insert++ for (var i = 0; i < moves.removes.length; i++) {+ remove = moves.removes[i]+ node = childNodes[remove.from]+ if (remove.key) {+ keyMap[remove.key] = node+ }+ domNode.removeChild(node)+ }++ var length = childNodes.length+ for (var j = 0; j < moves.inserts.length; j++) {+ insert = moves.inserts[j]+ node = keyMap[insert.key]+ // this is the weirdest bug i've ever seen in webkit+ domNode.insertBefore(node, insert.to >= length++ ? null : childNodes[insert.to])+ }+}++function replaceRoot(oldRoot, newRoot) {+ if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) {+ oldRoot.parentNode.replaceChild(newRoot, oldRoot)+ }++ return newRoot;+}++},{"../vnode/is-widget.js":41,"../vnode/vpatch.js":44,"./apply-properties":26,"./update-widget":31}],30:[function(require,module,exports){+var document = require("global/document")+var isArray = require("x-is-array")++var render = require("./create-element")+var domIndex = require("./dom-index")+var patchOp = require("./patch-op")+module.exports = patch++function patch(rootNode, patches, renderOptions) {+ renderOptions = renderOptions || {}+ renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch+ ? renderOptions.patch+ : patchRecursive+ renderOptions.render = renderOptions.render || render++ return renderOptions.patch(rootNode, patches, renderOptions)+}++function patchRecursive(rootNode, patches, renderOptions) {+ var indices = patchIndices(patches)++ if (indices.length === 0) {+ return rootNode+ }++ var index = domIndex(rootNode, patches.a, indices)+ var ownerDocument = rootNode.ownerDocument++ if (!renderOptions.document && ownerDocument !== document) {+ renderOptions.document = ownerDocument+ }++ for (var i = 0; i < indices.length; i++) {+ var nodeIndex = indices[i]+ rootNode = applyPatch(rootNode,+ index[nodeIndex],+ patches[nodeIndex],+ renderOptions)+ }++ return rootNode+}++function applyPatch(rootNode, domNode, patchList, renderOptions) {+ if (!domNode) {+ return rootNode+ }++ var newNode++ if (isArray(patchList)) {+ for (var i = 0; i < patchList.length; i++) {+ newNode = patchOp(patchList[i], domNode, renderOptions)++ if (domNode === rootNode) {+ rootNode = newNode+ }+ }+ } else {+ newNode = patchOp(patchList, domNode, renderOptions)++ if (domNode === rootNode) {+ rootNode = newNode+ }+ }++ return rootNode+}++function patchIndices(patches) {+ var indices = []++ for (var key in patches) {+ if (key !== "a") {+ indices.push(Number(key))+ }+ }++ return indices+}++},{"./create-element":27,"./dom-index":28,"./patch-op":29,"global/document":24,"x-is-array":47}],31:[function(require,module,exports){+var isWidget = require("../vnode/is-widget.js")++module.exports = updateWidget++function updateWidget(a, b) {+ if (isWidget(a) && isWidget(b)) {+ if ("name" in a && "name" in b) {+ return a.id === b.id+ } else {+ return a.init === b.init+ }+ }++ return false+}++},{"../vnode/is-widget.js":41}],32:[function(require,module,exports){+'use strict';++var EvStore = require('ev-store');++module.exports = EvHook;++function EvHook(value) {+ if (!(this instanceof EvHook)) {+ return new EvHook(value);+ }++ this.value = value;+}++EvHook.prototype.hook = function (node, propertyName) {+ var es = EvStore(node);+ var propName = propertyName.substr(3);++ es[propName] = this.value;+};++EvHook.prototype.unhook = function(node, propertyName) {+ var es = EvStore(node);+ var propName = propertyName.substr(3);++ es[propName] = undefined;+};++},{"ev-store":21}],33:[function(require,module,exports){+'use strict';++module.exports = SoftSetHook;++function SoftSetHook(value) {+ if (!(this instanceof SoftSetHook)) {+ return new SoftSetHook(value);+ }++ this.value = value;+}++SoftSetHook.prototype.hook = function (node, propertyName) {+ if (node[propertyName] !== this.value) {+ node[propertyName] = this.value;+ }+};++},{}],34:[function(require,module,exports){+'use strict';++var isArray = require('x-is-array');++var VNode = require('../vnode/vnode.js');+var VText = require('../vnode/vtext.js');+var isVNode = require('../vnode/is-vnode');+var isVText = require('../vnode/is-vtext');+var isWidget = require('../vnode/is-widget');+var isHook = require('../vnode/is-vhook');+var isVThunk = require('../vnode/is-thunk');++var parseTag = require('./parse-tag.js');+var softSetHook = require('./hooks/soft-set-hook.js');+var evHook = require('./hooks/ev-hook.js');++module.exports = h;++function h(tagName, properties, children) {+ var childNodes = [];+ var tag, props, key, namespace;++ if (!children && isChildren(properties)) {+ children = properties;+ props = {};+ }++ props = props || properties || {};+ tag = parseTag(tagName, props);++ // support keys+ if (props.hasOwnProperty('key')) {+ key = props.key;+ props.key = undefined;+ }++ // support namespace+ if (props.hasOwnProperty('namespace')) {+ namespace = props.namespace;+ props.namespace = undefined;+ }++ // fix cursor bug+ if (tag === 'INPUT' &&+ !namespace &&+ props.hasOwnProperty('value') &&+ props.value !== undefined &&+ !isHook(props.value)+ ) {+ props.value = softSetHook(props.value);+ }++ transformProperties(props);++ if (children !== undefined && children !== null) {+ addChild(children, childNodes, tag, props);+ }+++ return new VNode(tag, props, childNodes, key, namespace);+}++function addChild(c, childNodes, tag, props) {+ if (typeof c === 'string') {+ childNodes.push(new VText(c));+ } else if (typeof c === 'number') {+ childNodes.push(new VText(String(c)));+ } else if (isChild(c)) {+ childNodes.push(c);+ } else if (isArray(c)) {+ for (var i = 0; i < c.length; i++) {+ addChild(c[i], childNodes, tag, props);+ }+ } else if (c === null || c === undefined) {+ return;+ } else {+ throw UnexpectedVirtualElement({+ foreignObject: c,+ parentVnode: {+ tagName: tag,+ properties: props+ }+ });+ }+}++function transformProperties(props) {+ for (var propName in props) {+ if (props.hasOwnProperty(propName)) {+ var value = props[propName];++ if (isHook(value)) {+ continue;+ }++ if (propName.substr(0, 3) === 'ev-') {+ // add ev-foo support+ props[propName] = evHook(value);+ }+ }+ }+}++function isChild(x) {+ return isVNode(x) || isVText(x) || isWidget(x) || isVThunk(x);+}++function isChildren(x) {+ return typeof x === 'string' || isArray(x) || isChild(x);+}++function UnexpectedVirtualElement(data) {+ var err = new Error();++ err.type = 'virtual-hyperscript.unexpected.virtual-element';+ err.message = 'Unexpected virtual child passed to h().\n' ++ 'Expected a VNode / Vthunk / VWidget / string but:\n' ++ 'got:\n' ++ errorString(data.foreignObject) ++ '.\n' ++ 'The parent vnode is:\n' ++ errorString(data.parentVnode)+ '\n' ++ 'Suggested fix: change your `h(..., [ ... ])` callsite.';+ err.foreignObject = data.foreignObject;+ err.parentVnode = data.parentVnode;++ return err;+}++function errorString(obj) {+ try {+ return JSON.stringify(obj, null, ' ');+ } catch (e) {+ return String(obj);+ }+}++},{"../vnode/is-thunk":37,"../vnode/is-vhook":38,"../vnode/is-vnode":39,"../vnode/is-vtext":40,"../vnode/is-widget":41,"../vnode/vnode.js":43,"../vnode/vtext.js":45,"./hooks/ev-hook.js":32,"./hooks/soft-set-hook.js":33,"./parse-tag.js":35,"x-is-array":47}],35:[function(require,module,exports){+'use strict';++var split = require('browser-split');++var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/;+var notClassId = /^\.|#/;++module.exports = parseTag;++function parseTag(tag, props) {+ if (!tag) {+ return 'DIV';+ }++ var noId = !(props.hasOwnProperty('id'));++ var tagParts = split(tag, classIdSplit);+ var tagName = null;++ if (notClassId.test(tagParts[1])) {+ tagName = 'DIV';+ }++ var classes, part, type, i;++ for (i = 0; i < tagParts.length; i++) {+ part = tagParts[i];++ if (!part) {+ continue;+ }++ type = part.charAt(0);++ if (!tagName) {+ tagName = part;+ } else if (type === '.') {+ classes = classes || [];+ classes.push(part.substring(1, part.length));+ } else if (type === '#' && noId) {+ props.id = part.substring(1, part.length);+ }+ }++ if (classes) {+ if (props.className) {+ classes.push(props.className);+ }++ props.className = classes.join(' ');+ }++ return props.namespace ? tagName : tagName.toUpperCase();+}++},{"browser-split":20}],36:[function(require,module,exports){+var isVNode = require("./is-vnode")+var isVText = require("./is-vtext")+var isWidget = require("./is-widget")+var isThunk = require("./is-thunk")++module.exports = handleThunk++function handleThunk(a, b) {+ var renderedA = a+ var renderedB = b++ if (isThunk(b)) {+ renderedB = renderThunk(b, a)+ }++ if (isThunk(a)) {+ renderedA = renderThunk(a, null)+ }++ return {+ a: renderedA,+ b: renderedB+ }+}++function renderThunk(thunk, previous) {+ var renderedThunk = thunk.vnode++ if (!renderedThunk) {+ renderedThunk = thunk.vnode = thunk.render(previous)+ }++ if (!(isVNode(renderedThunk) ||+ isVText(renderedThunk) ||+ isWidget(renderedThunk))) {+ throw new Error("thunk did not return a valid node");+ }++ return renderedThunk+}++},{"./is-thunk":37,"./is-vnode":39,"./is-vtext":40,"./is-widget":41}],37:[function(require,module,exports){+module.exports = isThunk++function isThunk(t) {+ return t && t.type === "Thunk"+}++},{}],38:[function(require,module,exports){+module.exports = isHook++function isHook(hook) {+ return hook &&+ (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") ||+ typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook"))+}++},{}],39:[function(require,module,exports){+var version = require("./version")++module.exports = isVirtualNode++function isVirtualNode(x) {+ return x && x.type === "VirtualNode" && x.version === version+}++},{"./version":42}],40:[function(require,module,exports){+var version = require("./version")++module.exports = isVirtualText++function isVirtualText(x) {+ return x && x.type === "VirtualText" && x.version === version+}++},{"./version":42}],41:[function(require,module,exports){+module.exports = isWidget++function isWidget(w) {+ return w && w.type === "Widget"+}++},{}],42:[function(require,module,exports){+module.exports = "2"++},{}],43:[function(require,module,exports){+var version = require("./version")+var isVNode = require("./is-vnode")+var isWidget = require("./is-widget")+var isThunk = require("./is-thunk")+var isVHook = require("./is-vhook")++module.exports = VirtualNode++var noProperties = {}+var noChildren = []++function VirtualNode(tagName, properties, children, key, namespace) {+ this.tagName = tagName+ this.properties = properties || noProperties+ this.children = children || noChildren+ this.key = key != null ? String(key) : undefined+ this.namespace = (typeof namespace === "string") ? namespace : null++ var count = (children && children.length) || 0+ var descendants = 0+ var hasWidgets = false+ var hasThunks = false+ var descendantHooks = false+ var hooks++ for (var propName in properties) {+ if (properties.hasOwnProperty(propName)) {+ var property = properties[propName]+ if (isVHook(property) && property.unhook) {+ if (!hooks) {+ hooks = {}+ }++ hooks[propName] = property+ }+ }+ }++ for (var i = 0; i < count; i++) {+ var child = children[i]+ if (isVNode(child)) {+ descendants += child.count || 0++ if (!hasWidgets && child.hasWidgets) {+ hasWidgets = true+ }++ if (!hasThunks && child.hasThunks) {+ hasThunks = true+ }++ if (!descendantHooks && (child.hooks || child.descendantHooks)) {+ descendantHooks = true+ }+ } else if (!hasWidgets && isWidget(child)) {+ if (typeof child.destroy === "function") {+ hasWidgets = true+ }+ } else if (!hasThunks && isThunk(child)) {+ hasThunks = true;+ }+ }++ this.count = count + descendants+ this.hasWidgets = hasWidgets+ this.hasThunks = hasThunks+ this.hooks = hooks+ this.descendantHooks = descendantHooks+}++VirtualNode.prototype.version = version+VirtualNode.prototype.type = "VirtualNode"++},{"./is-thunk":37,"./is-vhook":38,"./is-vnode":39,"./is-widget":41,"./version":42}],44:[function(require,module,exports){+var version = require("./version")++VirtualPatch.NONE = 0+VirtualPatch.VTEXT = 1+VirtualPatch.VNODE = 2+VirtualPatch.WIDGET = 3+VirtualPatch.PROPS = 4+VirtualPatch.ORDER = 5+VirtualPatch.INSERT = 6+VirtualPatch.REMOVE = 7+VirtualPatch.THUNK = 8++module.exports = VirtualPatch++function VirtualPatch(type, vNode, patch) {+ this.type = Number(type)+ this.vNode = vNode+ this.patch = patch+}++VirtualPatch.prototype.version = version+VirtualPatch.prototype.type = "VirtualPatch"++},{"./version":42}],45:[function(require,module,exports){+var version = require("./version")++module.exports = VirtualText++function VirtualText(text) {+ this.text = String(text)+}++VirtualText.prototype.version = version+VirtualText.prototype.type = "VirtualText"++},{"./version":42}],46:[function(require,module,exports){+var isObject = require("is-object")+var isHook = require("../vnode/is-vhook")++module.exports = diffProps++function diffProps(a, b) {+ var diff++ for (var aKey in a) {+ if (!(aKey in b)) {+ diff = diff || {}+ diff[aKey] = undefined+ }++ var aValue = a[aKey]+ var bValue = b[aKey]++ if (aValue === bValue) {+ continue+ } else if (isObject(aValue) && isObject(bValue)) {+ if (getPrototype(bValue) !== getPrototype(aValue)) {+ diff = diff || {}+ diff[aKey] = bValue+ } else if (isHook(bValue)) {+ diff = diff || {}+ diff[aKey] = bValue+ } else {+ var objectDiff = diffProps(aValue, bValue)+ if (objectDiff) {+ diff = diff || {}+ diff[aKey] = objectDiff+ }+ }+ } else {+ diff = diff || {}+ diff[aKey] = bValue+ }+ }++ for (var bKey in b) {+ if (!(bKey in a)) {+ diff = diff || {}+ diff[bKey] = b[bKey]+ }+ }++ return diff+}++function getPrototype(value) {+ if (Object.getPrototypeOf) {+ return Object.getPrototypeOf(value)+ } else if (value.__proto__) {+ return value.__proto__+ } else if (value.constructor) {+ return value.constructor.prototype+ }+}++},{"../vnode/is-vhook":38,"is-object":25}],47:[function(require,module,exports){+var nativeIsArray = Array.isArray+var toString = Object.prototype.toString++module.exports = nativeIsArray || isArray++function isArray(obj) {+ return toString.call(obj) === "[object Array]"+}++},{}]},{},[3]);
+ virtual-dom/lib.require.js view
@@ -0,0 +1,457 @@+/*+ to generate lib.js, install virtual-dom and process file:++ $ npm install+ $ grunt+ the ./diff module is vtree/diff with a few changes to+ allow diff to run in an asynchronous thread in the presence of+ memoized nodes.+ */++/*+ Note on memory management:++ To ensure accurate heap tracing for finalization and profiling purposes,+ GHCJS needs to know reachable all Haskell values. ghcjs-vdom stores some+ Haskell values inside JavaScript references and uses extensible retention+ to collect these values. It's crucial that all data structures that may+ contain Haskell values are stored directly in a JSVal and not inside other+ JS data structures.++ The recognized types are:++ - HSPatch:+ The patch object contains the new (target) virtual-dom tree and+ the original tree is reachable trough the parent. Since all handlers,+ components and thunks are reachable through these trees, the patch itself+ does not need to be traversed.++ - HSMount:+ All mounted HSMount points are scanned as roots. The current virtual tree+ (mount.vtree) is traversed for this.++ - HSComponent:+ Traversed when reachable through Haskell heap or virtual-dom tree. Contains+ current tree (component.vtree) and rendering action (component.hsRender)++ - HSThunk:+ Traversed when reachable through Haskell heap or virtual-dom tree. Contains+ Haskell suspension (thunk.hst) or rendered tree (thunk.vnode)++ - virtual node ( isVirtualNode(x) )+ Some node in a virtual-dom tree. Haskell event handlers are stored in 'ev-*'+ properties, which virtual-dom adds to the node's hooks (vnode.hooks). If+ a virtual node contains thunks, widgets or any of its descendants have hooks,+ the children of the node have to be traversed.++ forceThunks and forcePatch fill an array of thunks, which is not directly+ scannable; however, these functions are only used as part of a `diff` operation.+ The initial diff creates an HSPatch object, through which the original and target+ virtual-dom tree are completely reachable.+ */++var isVirtualNode = require('virtual-dom/vnode/is-vnode');+var isThunk = require('virtual-dom/vnode/is-thunk');+var isWidget = require("virtual-dom/vnode/is-widget");+var h = require('virtual-dom/h');+var isArray = require('x-is-array');+var VPatch = require("virtual-dom/vnode/vpatch");+var VText = require('virtual-dom/vnode/vtext');+var vdomPatch = require('virtual-dom/vdom/patch');+var DomDelegator = require('dom-delegator');++var diff = require('./diff');++var VRenderableN = 0;++/** @constructor */+function HSPatch(patch, old, vnode, parent) {+ this.patch = patch;+ this.old = old;+ this.vnode = vnode;+ this.parent = parent;+}++/** @constructor */+function HSThunk(t, ids, key) {+ this.hst = t; // haskell thunk+ this.ids = ids; // array of haskell unique ids+ this.key = key;+ this.vnode = null;+ this._ghcjsMark = 0;+}++HSThunk.prototype.type = 'Thunk';++/* + render returns the deferred rendering object+ null if the thunk has already been rendered, in which case the value is in this.vnode+ */+HSThunk.prototype.render = function(previous) {+ if(previous && !this.vnode && eqIds(this.ids, previous.ids)) {+ if(previous.hst) {+ this.hst = previous.hst;+ } else {+ this.hst = null;+ this.vnode = previous.vnode;+ }+ }+ return this.vnode ? null : this;+}++/** @constructor */+function HSComponent(r, mnt, unmnt, key) {+ this._key = ++VRenderableN;+ this.hsRender = r; // IO action that produces a vdom tree+ this.hsMount = mnt;+ this.hsUnmount = unmnt;+ this.key = key || this._key;+ this.vnode = this.initialVnode = new VText("");+ this.pending = [];+ this.mounts = {};+ this.version = 0;+ this.latest = 0;+}++HSComponent.prototype.type = 'Widget';++HSComponent.prototype.init = function() {+ var n = document.createTextNode('');+ if(this.vnode !== this.initialVnode) {+ var thunks = [];+ var p = diff(this.initialVnode, this.vnode, thunks);+ if(thunks.length !== 0) {+ throw new Error("HSComponent vnode contains unevaluated thunks");+ }+ n = vdomPatch(n, p);+ }+ var m = new HSComponentMount(n);+ n._key = m._key;+ n._widget = this;+ this.mounts[m._key] = m;+ if(this.hsMount) {+ h$vdomMountComponentCallback(this.hsMount, m._key, this);+ }+ return n;+}++HSComponent.prototype.destroy = function(domNode) {+ delete this.mounts[domNode._key];+ if(this.hsUnmount) {+ h$vdomUnmountComponentCallback(this.hsUnmount, domNode._key, domNode);+ }+}++HSComponent.prototype.diff = function(v, thunks) {+ var vn = this.vnode;+ if(this.pending.length > 0) {+ vn = this.pending[this.pending.length-1].vnode;+ }+ return new HSPatch( diff(vn, v, thunks)+ , vn+ , v+ , this);+}++HSComponent.prototype.addPatch = function(p) {+ var cur = this.pending.length > 0 ? this.pending[this.pending.length-1]+ : this.vnode;+ if(p.old === cur) this.pending.push(p);+}++HSComponent.prototype.patch = function(p) {+ if(this.pending.length > 0) {+ var pnd = this.pending;+ this.pending = [];+ for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]);+ }+ if(!p) return;+ if(p.parent !== this || p.old !== this.vnode) {+ return false;+ }+ for(var k in this.mounts) {+ var m = this.mounts[k];+ m.node = vdomPatch(m.node, p.patch);+ }+ this.vnode = p.vnode;+ return true;+}++// only use this for manually updated components (i.e. no diff/patch)+HSComponent.prototype.updateMount = function(mnt, node) {+ var m = this.mounts[mnt];+ node._key = mnt;+ node._widget = this;+ m.node.parentNode.replaceChild(node, m.node);+ m.node = node;+}++HSComponent.prototype.update = function(leftVNode, node) {+ if(node._widget) {+ if(node._widget == this) return node;+ node._widget.destroy(node);+ }+ return this.init();+}++var HSComponentMountN = 0;+function HSComponentMount(domNode) {+ this._key = ++HSComponentMountN;+ this.node = domNode;+}++/** @constructor */+function HSMount(domNode) {+ this._key = ++VRenderableN;+ // this.version = 0;+ this.vnode = new VText(""); // currently rendered vdom tree+ this.pending = []; // pending patches, not yet applied+ this.node = document.createTextNode("");+ this.parentNode = domNode;+}++HSMount.prototype.diff = function(v, thunks) {+ var vn = this.vnode;+ if(this.pending.length > 0) {+ vn = this.pending[this.pending.length-1].vnode;+ }+ return new HSPatch( diff(vn, v, thunks)+ , vn+ , v+ , this);+}++HSMount.prototype.addPatch = function(p) {+ var cur = this.pending.length > 0 ? this.pending[this.pending.length-1]+ : this.vnode;+ if(p.old === cur) this.pending.push(p);+}++// HSMount.patch(null) to flush pending list+HSMount.prototype.patch = function(p) {+ if(this.pending.length > 0) {+ var pnd = this.pending;+ this.pending = [];+ for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]);+ }+ if(!p) return;+ if(p.parent !== this || p.old !== this.vnode) {+ return false;+ }+ this.node = vdomPatch(this.node, p.patch);+ this.vnode = p.vnode;+ return true;+}+ +/* mount a vdom tree, making it visible to extensible retention */+function mount(domNode) {+ while(domNode.firstChild) domNode.removeChild(domNode.firstChild);+ var m = new HSMount(domNode);+ domNode.appendChild(m.node);+ vdomMounts.add(m);+ return m;+}++/* unmount a tree, removing all child nodes. */+function unmount(vmount) {+ var n = vmount.parentNode;+ while(n.firstChild) n.removeChild(n.firstChild);+ vdomMounts.remove(vmount);+}++/*+ Compare lists of object identifiers associated with a thunk node. If the lists are equal,+ the subtree does not have to be recomputed.+*/+function eqIds(ids1, ids2) {+ if(!ids1 || !ids2 || ids1.length != ids2.length) return false;+ for(var i=ids1.length-1;i>=0;i--) {+ var id1 = ids1[i], id2 = ids2[i];+ if(typeof id1 === 'number') {+ if(typeof id2 !== 'number') return false;+ if(id1 !== id2 && !((id1!=id1) && (id2!=id2))) return false;+ } else {+ if(id1 !== id2) return false;+ }+ }+ return true;+}++function forcePatch(p) {+ var thunks = [], i, j, pi;+ for(i in p) {+ var pi = p[i];+ if(isArray(pi)) {+ for(j=pi.length-1;j>=0;j--) {+ forceTree(pi[j].patch, thunks);+ }+ }+ else if(pi.patch) forceTree(pi.patch, thunks);+ else forceTree(pi, thunks);+ }+ return thunks;+}++function forceTree(n, t) {+ if(isThunk(n)) {+ if(n.vnode) forceTree(n.vnode, t);+ else t.push(n);+ } else if(isVirtualNode(n) && n.hasThunks) {+ for(var i=n.children.length-1;i>=0;i--) {+ forceTree(n.children[i], t);+ }+ }+}++/*+ scan all mounted virtual-dom trees+ */+function scanMounts(currentMark) {+ var i = vdomMounts.iter(), m, res = [];+ while((m = i.next()) !== null) {+ scanTreeRec(m.vnode, res, currentMark);+ if(m.pending.length > 0) {+ scanTreeRec(m.pending[m.pending.length-1].vnode, res, currentMark);+ }+ }+ return res;+}++/*+ scan a tree (extensible retention callback).++ returns:+ - an array of haskell items if any+ - true if no haskell items have been found+ - false if the object is not a ghcjs-vdom tree+ (fallthrough to other extensible retention scanners)+ */+var scanTreeRes = [];+function scanTree(o, currentMark) {+ if(isVirtualNode(o) || isThunk(o) || isWidget(o) ||+ o instanceof HSPatch || o instanceof HSComponent || o instanceof HSMount) {+ var r = scanTreeRes;+ scanTreeRec(o, r, currentMark);+ if(r.length > 0) {+ scanTreeRes = [];+ return r;+ } else {+ return true;+ }+ } else { // not a ghcjs-vdom object, fall through+ return false; + }+}++function scanTreeRec(o, r, currentMark) {+ if(o instanceof HSPatch) {+ scanTreeRec(o.vnode, r, currentMark);+ scanTreeRec(o.parent);+ } else if(o instanceof HSThunk) {+ if(o._ghcjsMark !== currentMark) {+ o._ghcjsMark = currentMark;+ if(o.hst) r.push(o.hst);+ if(o.vnode) scanTreeRec(o.vnode, r, currentMark);+ }+ } else if(o instanceof HSComponent) {+ if(o._ghcjsMark !== currentMark) {+ o._ghcjsMark = currentMark;+ if(o.hsRender) r.push(o.hsRender);+ if(o.hsMount) r.push(o.hsMount);+ if(o.hsUnmount) r.push(o.hsUnmount);+ if(o.vnode) scanTreeRec(o.vnode, r, currentMark);+ if(o.pending.length > 0) {+ scanTreeRec(o.pending[o.pending.length-1].vnode, r, currentMark);+ }+ }+ } else if(isVirtualNode(o)) {+ if(o._ghcjsMark !== currentMark) {+ o._ghcjsMark = currentMark;+ // collect event handlers+ var hooks = o.hooks;+ for(var p in hooks) {+ if(p.indexOf('ev-') === 0) {+ var handler = hooks[p];+ if(handler.value && handler.value.hsAction) {+ r.push(handler.value.hsAction);+ }+ }+ }+ // recurse if any of the children may have thunks, components or handlers+ if(o.hasWidgets || o.hasThunks || o.descendantHooks) {+ for(var i=o.children.length-1;i>=0;i--) {+ scanTreeRec(o.children[i], r, currentMark);+ }+ }+ }+ }+}++function setThunkPatch(n, p) {+ if(hasPatches(p)) n.p[n.i] = new VPatch(VPatch.THUNK, null, p);+}++function hasPatches(patch) {+ for (var index in patch) {+ if (index !== "a") {+ return true;+ }+ }+ return false;+}++function initDelegator(evTypes) {+ var d = DomDelegator();+ var l = evTypes.length;+ for(var i = 0; i < l; i++) {+ d.listenTo(evTypes[i]);+ }+}++function v(tag, props, children) {+ return h(tag, props, children);+}++function t(text) {+ return new VText(text);+}++function th(t, ids, key) {+ return new HSThunk(t, ids, key);+}++function c(r, m, u, key) {+ return new HSComponent(r, m, u, key);+}++function makeHandler(action, async) {+ var f = function(ev) {+ return h$vdomEventCallback(async, action, ev);+ }+ f.hsAction = action;+ return f;+}++var vdomMounts = new h$Set();++module.exports = { setThunkPatch: setThunkPatch+ , forceTree: forceTree+ , forcePatch: forcePatch+ , diff: diff+ , mount: mount+ , unmount: unmount+ , initDelegator: initDelegator+ , v: v+ , th: th+ , t: t+ , c: c+ , makeHandler: makeHandler+ };++// the global variable we're using in the bindings+h$vdom = module.exports;++h$registerExtensibleRetention(scanTree);+h$registerExtensibleRetentionRoot(scanMounts);++
+ virtual-dom/package.json view
@@ -0,0 +1,16 @@+{+ "name": "ghcjs-vdom-support",+ "version": "0.0.0",+ "description": "Support lib for ghcjs-vdom",+ "devDependencies": {+ "grunt": "^0.4.5",+ "grunt-browserify": "^2.1.4",+ "grunt-contrib-watch": "^0.6.1"+ },+ "dependencies": {+ "is-object": "^0.1.2",+ "virtual-dom": "^2.0.1",+ "x-is-array": "^0.1.0",+ "dom-delegator": "^13.1.0"+ }+}