├── .gitignore ├── LICENSE ├── README.markdown ├── Setup.hs ├── data └── example.css ├── examples ├── Components.hs ├── Render.hs └── Table.hs ├── ghcjs-vdom.cabal ├── jsbits └── vdom.js ├── src └── GHCJS │ ├── VDOM.hs │ └── VDOM │ ├── Attribute.hs │ ├── Component.hs │ ├── DOMComponent.hs │ ├── Element.hs │ ├── Element │ └── Builtin.hs │ ├── Event.hs │ ├── Internal.hs │ ├── Internal │ ├── TH.hs │ ├── Thunk.hs │ └── Types.hs │ ├── QQ.hs │ ├── Render.hs │ └── Unsafe.hs └── virtual-dom ├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── diff.js ├── handle-thunk.js ├── lib.js ├── lib.require.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /vendor/ 3 | /GNUmakefile 4 | *.js_hi 5 | *.hi 6 | *.o 7 | *.js_o 8 | *.jsexe 9 | *_stub.h 10 | .fuse_hidden* 11 | *~ 12 | \#* 13 | .#* 14 | /lib/cache/build 15 | .cabal-sandbox 16 | cabal.config 17 | cabal.sandbox.config 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Luite Stegeman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # virtual-dom bindings 2 | 3 | [virtual-dom](https://github.com/Matt-Esch/virtual-dom) is a library for fast incremental 4 | DOM updates by comparing immutable virtual DOM trees. 5 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /data/example.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #ccc; 3 | } 4 | 5 | div.row { 6 | padding: 0; 7 | margin: 0; 8 | height: 5px; 9 | line-height: 5px; 10 | text-size: 10%; 11 | } 12 | 13 | div.pixel-red, div.pixel-white { 14 | vertical-align: top; 15 | display: inline-block; 16 | width: 5px; 17 | height: 5px; 18 | margin: 0; 19 | padding: 0; 20 | } 21 | 22 | div.pixel-red { 23 | background-color: #f00; 24 | } 25 | 26 | div.pixel-white { 27 | background-color: #fff; 28 | } -------------------------------------------------------------------------------- /examples/Components.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE BangPatterns #-} 4 | {-# LANGUAGE TupleSections #-} 5 | {-# LANGUAGE ScopedTypeVariables #-} 6 | 7 | {- 8 | ghcjs-vdom example, demonstrating components 9 | -} 10 | 11 | module Main where 12 | 13 | import GHCJS.Foreign.QQ 14 | import GHCJS.Types 15 | 16 | import qualified GHCJS.VDOM.Component as C 17 | import qualified GHCJS.VDOM.DOMComponent as D 18 | import qualified GHCJS.VDOM.Attribute as A 19 | import qualified GHCJS.VDOM.Element as E 20 | import qualified GHCJS.VDOM.Event as Ev 21 | import GHCJS.VDOM 22 | 23 | import qualified Data.JSString.Int as JSS 24 | 25 | import Data.IORef 26 | import Data.Monoid 27 | import qualified Data.Map as M 28 | 29 | import Control.Concurrent 30 | import Control.Monad 31 | 32 | import System.IO 33 | 34 | {- 35 | example virtual-dom component: 36 | a simple counter component that increments on click 37 | -} 38 | data Counter = Counter { counterComp :: VComp 39 | , counterKey :: Int 40 | , getCount :: IO Int 41 | } 42 | 43 | mkCounter :: JSString -> Int -> IO Counter 44 | mkCounter description startValue = do 45 | val <- newIORef startValue 46 | let description' = E.text (description <> ": ") 47 | c <- fixIO $ \c -> 48 | -- example only: diff and patch should really done through a Renderer 49 | let repaint = C.render c >>= C.diff c >>= C.patch c 50 | increment = modifyIORef' val (+1) >> repaint >> return () 51 | in C.mkComponent $ do 52 | v <- readIORef val 53 | return $ E.div (A.class_ "counter", Ev.click (const increment)) 54 | [description', E.text (JSS.decimal v)] 55 | return $ Counter c startValue (readIORef val) 56 | 57 | -- example DOM component 58 | data Scroller = Scroller { scrollerComp :: DComp 59 | , scrollerKey :: Int 60 | } 61 | 62 | mkScroller :: JSString -> Int -> IO Scroller 63 | mkScroller txt k = do 64 | mounts <- newIORef M.empty 65 | let mountScroller m = do 66 | (n::JSVal) <- [js| document.createElement('div') |] 67 | (t::JSVal) <- [js| document.createTextNode(`txt) |] 68 | [jsu_| `n.appendChild(`t); |] 69 | thr <- forkIO . forever $ do 70 | threadDelay 200000 71 | [jsu_| `t.data = `t.data.substr(1) + `t.data.substr(0,1); |] 72 | atomicModifyIORef mounts ((,()) . M.insert m thr) 73 | return n 74 | unmountScroller m _ = do 75 | Just thr <- M.lookup m <$> readIORef mounts 76 | killThread thr 77 | atomicModifyIORef mounts ((,()) . M.delete m) 78 | return () 79 | c <- D.mkComponent mountScroller unmountScroller 80 | return (Scroller c k) 81 | 82 | renderCounterList :: [Counter] -> VNode 83 | renderCounterList counters = 84 | E.ul (A.class_ "counterList") 85 | (map (\c -> E.li (A.key (counterKey c)) (C.toNode (counterComp c))) 86 | counters) 87 | 88 | renderScrollerList :: [Scroller] -> VNode 89 | renderScrollerList scrollers = 90 | E.ul (A.class_ "scrollerList") 91 | (map (\s -> E.li (A.key (scrollerKey s)) (D.toNode (scrollerComp s))) 92 | scrollers) 93 | 94 | render :: [Counter] -> [Scroller] -> VNode 95 | render counters scrollers = 96 | E.div () [renderCounterList counters, renderScrollerList scrollers] 97 | 98 | main :: IO () 99 | main = do 100 | Ev.initEventDelegation Ev.defaultEvents 101 | root <- [js| document.createElement('div') |] 102 | [js_| document.body.appendChild(`root); |] 103 | counters <- mapM (\i -> mkCounter ("counter " <> JSS.decimal i) i) [1..10] 104 | scrollers <- mapM (\i -> mkScroller ("scroller " <> JSS.decimal i) i) [1..10] 105 | m <- mount root (render counters scrollers) 106 | rotateComponents m 11 counters scrollers 107 | 108 | rotateComponents :: VMount -> Int -> [Counter] -> [Scroller] -> IO () 109 | rotateComponents m n counters scrollers = do 110 | threadDelay 1000000 111 | newCounter <- mkCounter ("counter " <> JSS.decimal n) n 112 | newScroller <- mkScroller ("scroller " <> JSS.decimal n) n 113 | let scrollers' = tail scrollers ++ [newScroller] 114 | counters' = tail counters ++ [newCounter] 115 | void $ diff m (render counters' scrollers') >>= patch m 116 | rotateComponents m (n+1) counters' scrollers' 117 | -------------------------------------------------------------------------------- /examples/Render.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes, OverloadedStrings, BangPatterns #-} 2 | 3 | {- 4 | ghcjs-vdom example, demonstrating the render queue 5 | -} 6 | 7 | module Main where 8 | 9 | import Control.Concurrent 10 | import Control.Monad 11 | 12 | import Data.IORef 13 | import Data.Monoid 14 | 15 | import GHCJS.Foreign.QQ 16 | import GHCJS.Types 17 | 18 | import qualified GHCJS.VDOM.Component as C 19 | import qualified GHCJS.VDOM.Attribute as A 20 | import qualified GHCJS.VDOM.Element as E 21 | import qualified GHCJS.VDOM.Render as R 22 | import GHCJS.VDOM 23 | 24 | import qualified Data.JSString.Int as JSS 25 | 26 | 27 | -- a component with a slowed down rendering function 28 | mkSlow :: JSString -> Int -> IO VComp 29 | mkSlow descr delay = do 30 | count <- newIORef (0::Int) 31 | let descr' = E.text (descr <> " " <> JSS.decimal delay <> ": ") 32 | C.mkComponent $ do 33 | threadDelay delay 34 | c <- atomicModifyIORef count (\x -> let x' = x+1 in (x',x')) 35 | return $ E.div (A.class_ "slow") [descr', E.text (JSS.decimal c)] 36 | 37 | main :: IO () 38 | main = do 39 | root <- [js| document.createElement('div') |] 40 | [js_| document.body.appendChild(`root); |] 41 | slows1 <- mapM (mkSlow "slow1") [10000, 50000, 100000, 300000] 42 | slows2 <- mapM (mkSlow "slow2") [10000, 50000, 100000] 43 | void $ mount root (E.div () (map C.toNode (slows1++slows2))) 44 | r1 <- R.mkRenderer 45 | r2 <- R.mkRenderer 46 | forever $ do 47 | mapM_ (R.render r1) slows1 48 | mapM_ (R.render r2) slows2 49 | threadDelay 10000 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /examples/Table.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes, OverloadedStrings, BangPatterns #-} 2 | 3 | {- 4 | virtual-dom bindings demo, rendering a large pixel grid with a bouncing red 5 | square. the step and patch are calculated asynchronously, the update is 6 | batched in an animation frame 7 | -} 8 | 9 | module Main where 10 | 11 | import Control.Monad 12 | 13 | import Data.IntMap (IntMap) 14 | import qualified Data.IntMap as IM 15 | import qualified Data.JSString as JSS 16 | 17 | import GHCJS.VDOM 18 | import GHCJS.VDOM.QQ 19 | import qualified GHCJS.VDOM.Element as E 20 | import qualified GHCJS.VDOM.Attribute as A 21 | 22 | import GHCJS.Foreign.Callback 23 | import GHCJS.Foreign.QQ 24 | import GHCJS.Types 25 | 26 | import JavaScript.Web.AnimationFrame (inAnimationFrame) 27 | 28 | red :: JSString 29 | red = "pixel-red" 30 | 31 | white :: JSString 32 | white = "pixel-white" 33 | 34 | type Pixels = IntMap (IntMap JSString) 35 | 36 | setPixel :: Int -> Int -> JSString -> Pixels -> Pixels 37 | setPixel x y c p = 38 | let r = p IM.! y 39 | r' = IM.insert x c r 40 | in r' `seq` IM.insert y r' p 41 | 42 | data State = State { x :: !Int, y :: !Int 43 | , dx :: !Int, dy :: !Int 44 | , w :: !Int, h :: !Int 45 | , pixels :: !Pixels 46 | } 47 | 48 | mkState :: Int -> Int -> Int -> Int -> State 49 | mkState w h x y = State x y 1 1 w h pix 50 | where 51 | pix = IM.fromList $ map row [0..h-1] 52 | row n = (n, IM.fromList (map (col n) [0..w-1])) 53 | col n m = (m, if (m,n)==(x,y) then red else white) 54 | 55 | step :: State -> State 56 | step (State x y dx dy w h p) = 57 | let dx' = if x==0 then 1 else if x==(w-1) then -1 else dx 58 | dy' = if y==0 then 1 else if y==(h-1) then -1 else dy 59 | x' = x+dx' 60 | y' = y+dy' 61 | p' = setPixel x' y' red (setPixel x y white p) 62 | in State x' y' dx' dy' w h p' 63 | 64 | cls :: JSString -> Attributes' 65 | cls name = [att| className: name |] 66 | 67 | render :: State -> VNode 68 | render s = E.div (cls "state") [ch|pixelDiv,numDiv|] 69 | where 70 | xd = textDiv (y s) 71 | yd = textDiv (x s) 72 | numDiv = E.div (cls "numeric") [ch|xd,yd|] 73 | pixelDiv = E.div (cls "pixels") 74 | (map (renderRowM (w s) . (pixels s IM.!)) [0..h s-1]) 75 | 76 | textDiv :: Show a => a -> VNode 77 | textDiv x = E.div () [ch|c|] 78 | where 79 | c = E.text . JSS.pack . show $ x 80 | 81 | renderRowM !w !r = memo renderRow w r 82 | 83 | renderRow :: Int -> IntMap JSString -> VNode 84 | renderRow w r = 85 | E.div (A.class_ "row", A.lang "EN") (map (renderPixelM r) [0..w-1]) 86 | 87 | renderPixelM !r !c = memo renderPixel r c 88 | 89 | renderPixel :: IntMap JSString -> Int -> VNode 90 | renderPixel r c = E.div (cls (r IM.! c)) () 91 | 92 | animate :: VMount -> State -> IO () 93 | animate m s = 94 | let s' = step s 95 | r' = render s' 96 | in do p <- diff m r' 97 | void $ inAnimationFrame ContinueAsync (\_ -> patch m p >> animate m s') 98 | 99 | main :: IO () 100 | main = do 101 | root <- [js| document.createElement('div') |] 102 | [js_| document.body.appendChild(`root); |] 103 | let s = mkState 167 101 10 20 104 | m <- mount root (E.div () ()) 105 | animate m s 106 | 107 | 108 | -------------------------------------------------------------------------------- /ghcjs-vdom.cabal: -------------------------------------------------------------------------------- 1 | name: ghcjs-vdom 2 | version: 0.2.0.0 3 | synopsis: Virtual-dom bindings for GHCJS 4 | description: Virtual-dom is a library for fast incremental DOM 5 | updates by comparing virtual immutable DOM trees to 6 | find a minimal number of changes to update the actual DOM. 7 | 8 | The bindings support memoized nodes which are only 9 | recomputed when the underlying data changes, using 10 | referential equality for the function and arguments. 11 | 12 | The diff procedure in the virtual-dom library has been 13 | modified slightly to support computing a diff in an 14 | asynchronous thread. Since computing a diff forces all data 15 | around the virtual-dom tree, the computation, the computation 16 | can be expensive. 17 | 18 | An asynchronous diff computation can be safely aborted 19 | with an async exception. 20 | 21 | license: MIT 22 | license-file: LICENSE 23 | author: Luite Stegeman 24 | maintainer: stegeman@gmail.com 25 | category: Web 26 | build-type: Simple 27 | cabal-version: >=1.10 28 | extra-source-files: virtual-dom/lib.require.js 29 | virtual-dom/diff.js 30 | virtual-dom/LICENSE 31 | virtual-dom/handle-thunk.js 32 | virtual-dom/README.md 33 | virtual-dom/package.json 34 | data/example.css 35 | README.markdown 36 | 37 | flag build-examples 38 | description: build the example programs 39 | default: False 40 | manual: True 41 | 42 | library 43 | js-sources: jsbits/vdom.js 44 | virtual-dom/lib.js 45 | ghcjs-options: -Wall 46 | exposed-modules: GHCJS.VDOM 47 | GHCJS.VDOM.Attribute 48 | GHCJS.VDOM.Component 49 | GHCJS.VDOM.DOMComponent 50 | GHCJS.VDOM.Element 51 | GHCJS.VDOM.Event 52 | GHCJS.VDOM.QQ 53 | GHCJS.VDOM.Render 54 | GHCJS.VDOM.Unsafe 55 | other-modules: GHCJS.VDOM.Internal 56 | GHCJS.VDOM.Internal.TH 57 | GHCJS.VDOM.Internal.Thunk 58 | GHCJS.VDOM.Internal.Types 59 | GHCJS.VDOM.Element.Builtin 60 | build-depends: base >=4.7 && < 5, 61 | ghc-prim, 62 | ghcjs-ffiqq, 63 | ghcjs-base >= 0.2.0.0, 64 | ghcjs-prim, 65 | containers, 66 | split, 67 | template-haskell 68 | hs-source-dirs: src 69 | default-language: Haskell2010 70 | 71 | executable ghcjs-vdom-example-table 72 | if !flag(build-examples) 73 | buildable: False 74 | Main-Is: Table.hs 75 | Default-Language: Haskell2010 76 | hs-source-dirs: examples 77 | Build-Depends: base >= 4 && < 5, 78 | ghcjs-ffiqq, 79 | ghcjs-vdom, 80 | containers, 81 | ghcjs-base 82 | ghcjs-Options: -Wall 83 | 84 | executable ghcjs-vdom-example-components 85 | if !flag(build-examples) 86 | buildable: False 87 | Main-Is: Components.hs 88 | Default-Language: Haskell2010 89 | hs-source-dirs: examples 90 | Build-Depends: base >= 4 && < 5, 91 | ghcjs-ffiqq, 92 | ghcjs-vdom, 93 | containers, 94 | ghcjs-base 95 | ghcjs-Options: -Wall 96 | 97 | executable ghcjs-vdom-example-render 98 | if !flag(build-examples) 99 | buildable: False 100 | Main-Is: Render.hs 101 | Default-Language: Haskell2010 102 | hs-source-dirs: examples 103 | Build-Depends: base >= 4 && < 5, 104 | ghcjs-ffiqq, 105 | ghcjs-vdom, 106 | containers, 107 | ghcjs-base 108 | ghcjs-Options: -Wall -------------------------------------------------------------------------------- /jsbits/vdom.js: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * global name for the things we need from the virtual-dom library 5 | */ 6 | var h$vdom; 7 | 8 | function h$vdomEventCallback(async, action, ev) { 9 | var a = MK_AP1(action, MK_JSVAL(ev)); 10 | if(async) { 11 | h$run(a); 12 | } else { 13 | h$runSync(a, true); 14 | } 15 | } 16 | 17 | function h$vdomMountComponentCallback(action, mnt, comp) { 18 | h$run(MK_AP2(action, MK_JSVAL(mnt), MK_JSVAL(comp))); 19 | } 20 | 21 | function h$vdomUnmountComponentCallback(action, mnt, node) { 22 | h$run(MK_AP2(action, MK_JSVAL(mnt), MK_JSVAL(node))); 23 | } 24 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes #-} 2 | 3 | {-| 4 | Bindings for the virtual-dom library. 5 | 6 | The virtual-dom diff function has been changed slightly to allow it to work 7 | with full functionality in asynchronous threads. 8 | 9 | It's possible to implement the bindings without the modifications at the 10 | cost of tail-call optimization and preemptive threading in the diff, by 11 | recursively forcing the thunks in synchronous threads. 12 | -} 13 | 14 | module GHCJS.VDOM ( Attributes, Children 15 | , Attributes', Children' 16 | , VMount, VNode, VComp, DComp, Patch, DOMNode 17 | , mount, unmount 18 | , diff, patch 19 | , memo, memoKey 20 | ) where 21 | 22 | import GHCJS.Types 23 | import GHCJS.Foreign.QQ 24 | import GHCJS.Prim 25 | import GHCJS.Marshal.Pure 26 | 27 | import Control.Monad 28 | import Data.Coerce 29 | 30 | import System.IO.Unsafe 31 | 32 | import GHCJS.VDOM.Internal.Types 33 | import GHCJS.VDOM.Internal.Thunk 34 | import GHCJS.VDOM.Internal (j,J) 35 | import qualified GHCJS.VDOM.Internal as I 36 | 37 | class MemoNode a where memoNode :: (J, [JSIdent], a) -> a 38 | 39 | instance MemoNode VNode 40 | where 41 | memoNode (_,[],a) = a 42 | memoNode (k,xs,v) = 43 | let vd = I.unsafeExportValue v 44 | xs1 = unsafePerformIO (toJSArray $ coerce xs) 45 | in VNode [j| h$vdom.th(`vd, `xs1, `k, true) |] 46 | {-# INLINE memoNode #-} 47 | 48 | instance MemoNode b => MemoNode (a -> b) 49 | where 50 | memoNode (k,xs,f) = \a -> memoNode (k, I.objectIdent a:xs, f a) 51 | {-# INLINE memoNode #-} 52 | 53 | memoKey :: MemoNode a => JSString -> a -> a 54 | memoKey k = memo' (pToJSVal k) 55 | {-# NOINLINE memoKey #-} 56 | 57 | memo :: MemoNode a => a -> a 58 | memo = memo' [j| $r = null; |] 59 | {-# NOINLINE memo #-} 60 | 61 | memo' :: MemoNode a => J -> a -> a 62 | memo' k f = memoNode (k,[I.objectIdent f],f) 63 | {-# INLINE memo' #-} 64 | 65 | {-| 66 | Mount a virtual-dom tree in the real DOM. The mount point can be updated 67 | with patch. 68 | -} 69 | mount :: DOMNode -> VNode -> IO VMount 70 | mount n v = do 71 | m <- VMount <$> [js| h$vdom.mount(`n) |] 72 | void $ patch m =<< diff m v 73 | return m 74 | {-# INLINE mount #-} 75 | 76 | {-| 77 | Remove a virtual-dom tree from the document. It's important to use 78 | unmount rather than removing the mount point any other way since this 79 | releases all associated Haskell data structures. 80 | -} 81 | unmount :: VMount -> IO () 82 | unmount (VMount m) = [jsu_| h$vdom.unmount(`m); |] 83 | {-# INLINE unmount #-} 84 | 85 | {-| 86 | Compute a patch to update the mounted tree to match the virtual-dom tree 87 | -} 88 | diff :: VMount -> VNode -> IO Patch 89 | diff (VMount m) (VNode v) = do 90 | thunks <- [jsu| [] |] 91 | patch <- [jsu| `m.diff(`v, `thunks) |] 92 | forceThunks thunks 93 | forcePatch [j| `patch.patch |] 94 | return (Patch patch) 95 | {-# INLINE diff #-} 96 | 97 | {-| 98 | Apply a patch to a mounted virtual-dom tree. Fails if the tree has already 99 | been patched after the diff was computed. 100 | -} 101 | patch :: VMount -> Patch -> IO Bool 102 | patch (VMount m) (Patch p) = [jsu| `m.patch(`p); |] 103 | {-# INLINE patch #-} 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Attribute.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings, TemplateHaskell #-} 2 | 3 | module GHCJS.VDOM.Attribute ( Attribute 4 | , Attributes 5 | -- * some predefined attributes 6 | , class_ 7 | , id 8 | , href 9 | , alt 10 | , src 11 | , name 12 | , target 13 | , value 14 | , width 15 | , height 16 | , title 17 | , lang 18 | , type_ 19 | , key -- virtual-dom identifiers 20 | ) where 21 | 22 | import Prelude hiding (id) 23 | 24 | import GHCJS.Types 25 | 26 | import GHCJS.VDOM.Internal.Types 27 | import GHCJS.VDOM.Internal 28 | 29 | mkAttrs ''JSString [ "id", "href", "src", "alt", "title" 30 | , "lang", "name", "target", "value" 31 | ] 32 | 33 | mkAttrs' ''JSString [ ("class_", "className") 34 | , ("type_", "type") 35 | ] 36 | 37 | mkAttrs ''Int [ "key", "width", "height" ] 38 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Component.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes #-} 2 | {-# LANGUAGE GHCForeignImportPrim #-} 3 | {-# LANGUAGE UnliftedFFITypes #-} 4 | {-# LANGUAGE UnboxedTuples #-} 5 | {-# LANGUAGE MagicHash #-} 6 | 7 | module GHCJS.VDOM.Component ( VComp 8 | , toNode 9 | , mkComponent 10 | , render 11 | , diff 12 | , patch 13 | ) where 14 | 15 | import Control.Monad 16 | 17 | import GHCJS.Foreign.QQ 18 | 19 | import GHCJS.VDOM.Internal.Types 20 | 21 | import qualified GHCJS.VDOM.Internal as I 22 | import GHCJS.VDOM.Internal (j) 23 | import qualified GHCJS.VDOM.Internal.Thunk as I 24 | 25 | import GHC.Exts 26 | import GHC.Types (IO(..)) 27 | import Unsafe.Coerce 28 | 29 | toNode :: VComp -> VNode 30 | toNode (VComp v) = VNode v 31 | {-# INLINE toNode #-} 32 | 33 | mkComponent :: IO VNode -> IO VComp 34 | mkComponent r = do 35 | let renderE = I.unsafeExportValue r 36 | c <- VComp <$> [jsu| h$vdom.c(`renderE, null, null, null) |] 37 | void $ patch c =<< diff c =<< render c 38 | return c 39 | 40 | foreign import javascript unsafe "$r = $1.hsRender;" 41 | js_hsRender :: VComp -> State# RealWorld -> (# State# RealWorld, Any #) 42 | 43 | render :: VComp -> IO VNode 44 | render c = join $ IO (\s -> case js_hsRender c s of 45 | (# s', r #) -> (# s', unsafeCoerce r #)) 46 | {-# INLINE render #-} 47 | 48 | diff :: VComp -> VNode -> IO Patch 49 | diff (VComp c) (VNode v) = do 50 | thunks <- [jsu| [] |] 51 | patch <- [jsu| `c.diff(`v, `thunks) |] 52 | I.forceThunks thunks 53 | I.forcePatch [j| `patch.patch |] 54 | return (Patch patch) 55 | {-# INLINE diff #-} 56 | 57 | patch :: VComp -> Patch -> IO Bool 58 | patch (VComp c) (Patch p) = [jsu| `c.patch(`p) |] 59 | {-# INLINE patch #-} 60 | 61 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/DOMComponent.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | DOM components manage a normal DOM subtree inside a virtual-dom tree. 3 | 4 | The component has callbacks for mounting and unmounting. The mount 5 | callback returns a DOM tree that stays in the document until the 6 | unmount callback is called. 7 | 8 | A single component can be mounted multiple times. The mount callback 9 | is called for each mount, and is expected to return a fresh DOM 10 | tree every time. 11 | -} 12 | 13 | {-# LANGUAGE ForeignFunctionInterface, JavaScriptFFI, QuasiQuotes #-} 14 | module GHCJS.VDOM.DOMComponent ( DComp 15 | , mkComponent 16 | , toNode 17 | ) where 18 | 19 | import GHCJS.Foreign.QQ 20 | import GHCJS.Marshal.Pure 21 | import GHCJS.Types 22 | 23 | import GHCJS.VDOM.Internal.Types 24 | 25 | import qualified GHCJS.VDOM.Internal as I 26 | 27 | 28 | toNode :: DComp -> VNode 29 | toNode (DComp v) = VNode v 30 | {-# INLINE toNode #-} 31 | 32 | mkComponent :: (Int -> IO JSVal) -- ^ mount action, return a DOM node 33 | -> (Int -> JSVal -> IO ()) -- ^ unmount action 34 | -> IO DComp 35 | mkComponent mount unmount = 36 | let mountE = I.unsafeExportValue (mountComponent mount) 37 | unmountE = I.unsafeExportValue (unmountComponent unmount) 38 | in DComp <$> [jsu| h$vdom.c(null, `mountE, `unmountE, null) |] 39 | 40 | 41 | mountComponent :: (Int -> IO JSVal) -> JSVal -> JSVal -> IO () 42 | mountComponent f mnt c = do 43 | node <- f (pFromJSVal mnt) 44 | [jsu| `c.updateMount(`mnt, `node); |] 45 | 46 | unmountComponent :: (Int -> JSVal -> IO ()) -> JSVal -> JSVal -> IO () 47 | unmountComponent f mnt node = f (pFromJSVal mnt) node 48 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Element.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings, TemplateHaskell #-} 2 | module GHCJS.VDOM.Element ( custom 3 | , text 4 | , module GHCJS.VDOM.Element.Builtin 5 | ) where 6 | 7 | import Data.JSString (JSString) 8 | 9 | import qualified GHCJS.VDOM.Internal as I 10 | import GHCJS.VDOM.Internal.Types 11 | 12 | import GHCJS.VDOM.Element.Builtin 13 | 14 | custom :: (Attributes a, Children c) => JSString -> a -> c -> VNode 15 | custom tag a c = I.mkVNode tag a c 16 | {-# INLINE custom #-} 17 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Element/Builtin.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | 3 | module GHCJS.VDOM.Element.Builtin where 4 | 5 | import GHCJS.VDOM.Internal 6 | 7 | mkElems [ "address", "article", "body", "footer", "header" 8 | , "h1", "h2", "h3", "h4", "h5", "h6" 9 | , "hgroup", "nav", "section" 10 | , "dd", "div", "dl", "dt", "figcaption", "figure", "hr", "li" 11 | , "main", "ol", "p", "pre", "ul" 12 | , "a", "abbr", "b", "bdi", "br", "cite", "code", "dfn" 13 | , "em", "i", "kbd", "mark", "q", "rp", "rt", "rtc", "ruby" 14 | , "s", "samp", "small", "span", "strong", "sub", "sup", "time" 15 | , "u", "var", "wbr" 16 | , "area", "audio", "img", "map", "track", "video" 17 | , "embed", "iframe", "object", "param", "source" 18 | , "canvas", "noscript", "script" 19 | , "del", "ins" 20 | , "caption", "col", "colgroup", "table", "tbody", "td", "tfoot" 21 | , "th", "thead", "tr" 22 | , "button", "datalist", "fieldset", "form", "input", "keygen" 23 | , "label", "legend", "meter", "optgroup", "option", "output" 24 | , "progress", "select", "textarea" 25 | , "details", "dialog", "menu", "menuitem", "summary" 26 | , "content", "element", "shadow", "template" 27 | ] 28 | 29 | -- use 'data_' as the name for the data tag, data is a reserved word 30 | mkElem "data_" "data" 31 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Event.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE QuasiQuotes #-} 3 | {-# LANGUAGE TemplateHaskell #-} 4 | {-# LANGUAGE DeriveDataTypeable #-} 5 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 6 | {-# LANGUAGE FlexibleContexts #-} 7 | 8 | module GHCJS.VDOM.Event ( initEventDelegation 9 | , defaultEvents 10 | -- , target 11 | , stopPropagation 12 | , stopImmediatePropagation 13 | , preventDefault 14 | 15 | -- * mouse 16 | , MouseEvent 17 | , click 18 | , dblclick 19 | , mousedown 20 | , mouseenter 21 | , mouseleave 22 | , mousemove 23 | , mouseout 24 | , mouseover 25 | , mouseup 26 | -- 27 | , button 28 | , buttons 29 | , clientX 30 | , clientY 31 | 32 | -- * keyboard 33 | , KeyboardEvent 34 | , keydown 35 | , keypress 36 | , keyup 37 | -- 38 | , key 39 | , ctrlKey 40 | , metaKey 41 | , shiftKey 42 | 43 | -- * drag 44 | , DragEvent 45 | , drag 46 | , dragend 47 | , dragenter 48 | , dragleave 49 | , dragover 50 | , dragstart 51 | 52 | -- * focus 53 | , FocusEvent 54 | , focus 55 | , blur 56 | 57 | -- * ui 58 | , UIEvent 59 | , resize 60 | , scroll 61 | , select 62 | , unload 63 | 64 | -- * wheel 65 | , WheelEvent 66 | , wheel 67 | -- 68 | , deltaX 69 | , deltaY 70 | , deltaZ 71 | , deltaMode 72 | 73 | -- * generic 74 | , Event 75 | , submit 76 | , change 77 | ) where 78 | 79 | import Data.Coerce 80 | 81 | import Unsafe.Coerce 82 | 83 | import GHCJS.Prim 84 | import GHCJS.Types 85 | import GHCJS.Foreign.QQ 86 | 87 | import GHCJS.VDOM.Internal 88 | 89 | -- | call this to initialize the virtual-dom event handling system 90 | initEventDelegation :: [JSString] -> IO () 91 | initEventDelegation eventTypes = do 92 | a <- toJSArray (unsafeCoerce eventTypes) 93 | [jsu_| h$vdom.initDelegator(`a); |] 94 | 95 | class Coercible a JSVal => Event_ a 96 | class Event_ a => KeyModEvent_ a 97 | class Event_ a => MouseEvent_ a 98 | class Event_ a => FocusEvent_ a 99 | 100 | mkEventTypes ''Event_ [ ("MouseEvent", [''MouseEvent_]) 101 | , ("KeyboardEvent", [''KeyModEvent_]) 102 | , ("FocusEvent", [''FocusEvent_]) 103 | , ("DragEvent", []) 104 | , ("WheelEvent", []) 105 | , ("UIEvent", []) 106 | , ("Event", []) 107 | ] 108 | 109 | mkEvents 'MouseEvent [ "click", "dblclick", "mousedown", "mouseenter" 110 | , "mouseleave", "mousemove", "mouseout" 111 | , "mouseover", "mouseup" 112 | ] 113 | 114 | mkEvents 'KeyboardEvent [ "keydown", "keypress", "keyup" ] 115 | 116 | mkEvents 'DragEvent [ "drag", "dragend", "dragenter", "dragleave" 117 | , "dragover", "dragstart" ] 118 | 119 | mkEvents 'FocusEvent [ "focus", "blur" ] 120 | 121 | mkEvents 'UIEvent [ "resize", "scroll", "select", "unload" ] 122 | 123 | mkEvents 'WheelEvent [ "wheel" ] 124 | 125 | mkEvents 'Event [ "submit", "change" ] 126 | 127 | er :: Event_ a => (JSVal -> b) -> a -> b 128 | er f x = f (coerce x) 129 | 130 | -- ----------------------------------------------------------------------------- 131 | 132 | -- this contains all event types added with mkEvents 133 | defaultEvents :: [JSString] 134 | defaultEvents = $(mkDefaultEvents) 135 | 136 | 137 | -- target :: Event_ a => a -> VNode 138 | -- target e = undefined 139 | 140 | stopPropagation :: Event_ a => a -> IO () 141 | stopPropagation = er $ \e -> [jsu_| `e.stopPropagation(); |] 142 | {-# INLINE stopPropagation #-} 143 | 144 | stopImmediatePropagation :: Event_ a => a -> IO () 145 | stopImmediatePropagation = er $ \e -> [jsu_| `e.stopImmediatePropagation(); |] 146 | {-# INLINE stopImmediatePropagation #-} 147 | 148 | preventDefault :: Event_ a => a -> IO () 149 | preventDefault = er $ \e -> [jsu_| `e.preventDefault(); |] 150 | {-# INLINE preventDefault #-} 151 | 152 | ctrlKey :: KeyModEvent_ a => a -> Bool 153 | ctrlKey = er $ \e -> [jsu'| `e.ctrlKey |] 154 | {-# INLINE ctrlKey #-} 155 | 156 | metaKey :: KeyModEvent_ a => a -> Bool 157 | metaKey = er $ \e -> [jsu'| `e.ctrlKey |] 158 | {-# INLINE metaKey #-} 159 | 160 | shiftKey :: KeyModEvent_ a => a -> Bool 161 | shiftKey = er $ \e -> [jsu'| `e.ctrlKey |] 162 | {-# INLINE shiftKey #-} 163 | 164 | key :: KeyboardEvent -> JSString 165 | key = er $ \e -> [jsu'| `e.key |] 166 | {-# INLINE key #-} 167 | 168 | button :: MouseEvent_ a => a -> Int 169 | button = er $ \e -> [jsu'| `e.button |] 170 | {-# INLINE button #-} 171 | 172 | buttons :: MouseEvent_ a => a -> Int 173 | buttons = er $ \e -> [jsu'| `e.buttons |] 174 | {-# INLINE buttons #-} 175 | 176 | deltaX :: WheelEvent -> Double 177 | deltaX = er $ \e -> [jsu'| `e.deltaX |] 178 | {-# INLINE deltaX #-} 179 | 180 | deltaY :: WheelEvent -> Double 181 | deltaY = er $ \e -> [jsu'| `e.deltaY |] 182 | {-# INLINE deltaY #-} 183 | 184 | deltaZ :: WheelEvent -> Double 185 | deltaZ = er $ \e -> [jsu'| `e.deltaZ |] 186 | {-# INLINE deltaZ #-} 187 | 188 | deltaMode :: WheelEvent -> Double 189 | deltaMode = er $ \e -> [jsu'| `e.deltaMode |] 190 | {-# INLINE deltaMode #-} 191 | 192 | clientX :: MouseEvent -> Int 193 | clientX = er $ \e -> [jsu'| `e.clientX|0 |] 194 | {-# INLINE clientX #-} 195 | 196 | clientY :: MouseEvent -> Int 197 | clientY = er $ \e -> [jsu'| `e.clientY|0 |] 198 | {-# INLINE clientY #-} 199 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Internal.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE QuasiQuotes #-} 3 | {-# LANGUAGE TemplateHaskell #-} 4 | {-# LANGUAGE OverloadedStrings #-} 5 | {-# LANGUAGE UnliftedFFITypes #-} 6 | {-# LANGUAGE GHCForeignImportPrim #-} 7 | {-# LANGUAGE MagicHash #-} 8 | {-# LANGUAGE UnboxedTuples #-} 9 | {-# LANGUAGE ScopedTypeVariables #-} 10 | 11 | module GHCJS.VDOM.Internal where 12 | 13 | import GHCJS.VDOM.Internal.Types 14 | 15 | import Language.Haskell.TH 16 | import Language.Haskell.TH.Quote 17 | import Language.Haskell.TH.Syntax 18 | 19 | import GHC.Prim (Any, State#, RealWorld) 20 | 21 | import Control.Monad 22 | import Unsafe.Coerce 23 | 24 | import GHCJS.Foreign.QQ 25 | import GHCJS.Types 26 | import GHCJS.Marshal.Pure 27 | 28 | import Data.List (foldl') 29 | import Data.String (IsString(..)) 30 | import Data.Typeable 31 | 32 | import GHC.IO ( IO(..) ) 33 | import GHC.Base ( StableName# ) 34 | 35 | type J = JSVal 36 | 37 | j :: QuasiQuoter 38 | j = jsu' 39 | 40 | mkVNode :: (Attributes a, Children c) => JSString -> a -> c -> VNode 41 | mkVNode tag atts children = js_vnode tag (mkAttributes atts) (mkChildren children) 42 | {-# INLINE mkVNode #-} 43 | 44 | mkElems :: [String] -> Q [Dec] 45 | mkElems = fmap concat . mapM (join mkElem) 46 | 47 | mkElem :: String -> String -> Q [Dec] 48 | mkElem name tag = do 49 | let n = mkName name 50 | a <- newName "a" 51 | c <- newName "c" 52 | b <- [| mkVNode (fromString tag) |] 53 | typ <- [t|forall a c. (Attributes a, Children c) => a -> c -> VNode |] 54 | return [ SigD n typ 55 | , FunD n [Clause [VarP a, VarP c] (NormalB (AppE (AppE b (VarE a)) (VarE c))) []] 56 | , PragmaD (InlineP n Inline FunLike AllPhases) 57 | ] 58 | 59 | mkAttrs :: Name -> [String] -> Q [Dec] 60 | mkAttrs ty = fmap concat . mapM (join (mkAttr ty)) 61 | 62 | mkAttrs' :: Name -> [(String, String)] -> Q [Dec] 63 | mkAttrs' ty = fmap concat . mapM (uncurry (mkAttr ty)) 64 | 65 | mkAttr :: Name -> String -> String -> Q [Dec] 66 | mkAttr ty name attr = do 67 | let n = mkName name 68 | x <- newName "x" 69 | b <- [| \y -> Attribute attr (pToJSVal y) |] 70 | return [ SigD n (AppT (AppT ArrowT (ConT ty)) (ConT ''Attribute)) 71 | , FunD n [Clause [VarP x] (NormalB (AppE b (VarE x))) []] 72 | , PragmaD (InlineP n Inline FunLike AllPhases) 73 | ] 74 | 75 | mkEventTypes :: Name -> [(String, [Name])] -> Q [Dec] 76 | mkEventTypes base = fmap concat . mapM mk 77 | where 78 | mk (n, cls) = do 79 | let nn = mkName n 80 | #if MIN_VERSION_template_haskell(2,11,0) 81 | mkI cn = InstanceD Nothing [] (AppT (ConT cn) (ConT nn)) [] 82 | #else 83 | mkI cn = InstanceD [] (AppT (ConT cn) (ConT nn)) [] 84 | #endif 85 | insts = map mkI (base : cls) 86 | jsr <- [t| JSVal |] 87 | typ <- [t| Typeable |] 88 | #if MIN_VERSION_template_haskell(2,11,0) 89 | return $ (NewtypeD [] nn [] Nothing (NormalC nn [(Bang NoSourceUnpackedness NoSourceStrictness, jsr)]) [ typ ]) : insts 90 | #else 91 | return $ (NewtypeD [] nn [] (NormalC nn [(NotStrict, jsr)]) [''Typeable]) : insts 92 | #endif 93 | 94 | newtype CreatedEvents = CreatedEvents { unCreatedEvents :: [String] } 95 | deriving (Typeable) 96 | 97 | addCreatedEvent :: String -> CreatedEvents -> CreatedEvents 98 | addCreatedEvent ev (CreatedEvents es) = CreatedEvents (ev:es) 99 | 100 | -- dcon must be a newtype constructor, not a data con 101 | mkEvents :: Name -> [String] -> Q [Dec] 102 | mkEvents dcon xs = fmap concat (mapM (\x -> mkEvent dcon x ("ev-"++x)) xs) 103 | 104 | -- dcon must be a newtype constructor, not a data con 105 | mkEvent :: Name -> String -> String -> Q [Dec] 106 | mkEvent dcon name attr = do 107 | let n = mkName name 108 | emsg = "GHCJS.VDOM.Internal.mkEvent: expected newtype constructor" 109 | i <- reify dcon 110 | dctyp <- case i of 111 | #if MIN_VERSION_template_haskell(2,11,0) 112 | DataConI _ _ pn -> do 113 | pni <- reify pn 114 | case pni of 115 | TyConI (NewtypeD _ ctn _ _ _ _) -> return (ConT ctn) 116 | _ -> error emsg 117 | _ -> error emsg 118 | #else 119 | DataConI _ _ pn _ -> do 120 | pni <- reify pn 121 | case pni of 122 | TyConI (NewtypeD _ ctn _ _ _) -> return (ConT ctn) 123 | _ -> error emsg 124 | _ -> error emsg 125 | #endif 126 | iou <- [t| IO () |] 127 | h <- newName "h" 128 | b <- [| mkEventAttr (fromString attr) |] 129 | let ht = AppT (AppT ArrowT dctyp) iou 130 | -- typ <- [t| (dctyp -> IO ()) -> Attribute |] 131 | qPutQ . maybe (CreatedEvents [name]) (addCreatedEvent name) =<< qGetQ 132 | return [ SigD n (AppT (AppT ArrowT ht) (ConT ''Attribute)) 133 | , FunD n [Clause [VarP h] (NormalB (AppE (AppE b (ConE dcon)) (VarE h))) []] 134 | , PragmaD (InlineP n Inline FunLike AllPhases) 135 | ] 136 | 137 | -- a must be a newtype of JSVal! 138 | mkEventAttr :: JSString -> (JSVal -> a) -> (a -> IO ()) -> Attribute 139 | mkEventAttr attr _wrap h = 140 | 141 | let e = unsafeExportValue h 142 | h' = [js'| h$vdom.makeHandler(`e, false) |] 143 | in h' `seq` Attribute attr h' 144 | {-# INLINE mkEventAttr #-} 145 | 146 | {- 147 | eventLogger :: JSVal () 148 | eventLogger = [js'| function(ev) { console.log("event caught"); } |] 149 | -} 150 | 151 | -- generate a list of all events stored in the persistent TH state, created with mkEvent 152 | mkDefaultEvents :: Q Exp 153 | mkDefaultEvents = do 154 | evs <- maybe [] unCreatedEvents <$> qGetQ 155 | nil <- [| [] |] 156 | cons <- [| (:) |] 157 | return $ foldl' (\xs e -> AppE (AppE cons (LitE . stringL $ e)) xs) nil evs 158 | 159 | js_vnode :: JSString -> Attributes' -> Children' -> VNode 160 | js_vnode tag (Attributes' props) (Children' children) = 161 | VNode [jsu'| h$vdom.v(`tag, `props, `children) |] 162 | --VNode [jsu'| new h$vdom.VNode(`tag, `props, `children) |] 163 | 164 | getThunk :: J -> IO J 165 | getThunk x = IO (js_getThunk x) 166 | 167 | foreign import javascript unsafe "$r = $1.hst;" 168 | js_getThunk :: J -> State# RealWorld -> (# State# RealWorld, J #) 169 | 170 | -- ----------------------------------------------------------------------------- 171 | {-| 172 | Export an arbitrary Haskell value to JS. 173 | 174 | be careful with these JSVal values, losing track of them will result in 175 | incorrect memory management. As long as we keep the values directly in 176 | a Property or VNode, the ghcjs-vdom extensible retention system will know 177 | where to find them. 178 | -} 179 | unsafeExportValue :: a -> JSVal 180 | unsafeExportValue x = js_export (unsafeCoerce x) 181 | {-# INLINE unsafeExportValue #-} 182 | 183 | {-| 184 | make a unique identifier that can be easily compared in JS 185 | if(objectIdent(o1) === objectIdent(o2) or both are NaN, then o1 and o2 are 186 | are the same Haskell value 187 | -} 188 | objectIdent :: a -> JSIdent 189 | objectIdent x = x `seq` js_makeObjectIdent (unsafeExportValue x) 190 | {- 191 | unsafePerformIO . IO $ \s -> 192 | case makeStableName# x s of (# s', sn #) -> (# s', js_convertSn sn #) 193 | -} 194 | {-# INLINE objectIdent #-} 195 | 196 | foreign import javascript unsafe "$r = $1;" js_export :: Any -> JSVal 197 | foreign import javascript unsafe "$r = $1;" js_convertSn :: StableName# a -> JSIdent 198 | 199 | foreign import javascript unsafe "h$makeStableName($1)" js_makeObjectIdent :: JSVal -> JSIdent 200 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Internal/TH.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP, TemplateHaskell, QuasiQuotes #-} 2 | 3 | module GHCJS.VDOM.Internal.TH where 4 | 5 | import Data.List (foldl') 6 | 7 | import Language.Haskell.TH 8 | 9 | import Unsafe.Coerce 10 | 11 | mkTupleChildrenInstances :: Name -> Name -> Name -> Name -> Name -> [Int] -> Q [Dec] 12 | mkTupleChildrenInstances cls method ty con wrapper xs = 13 | concat <$> mapM (mkTupleChildrenInstance cls method ty con wrapper) xs 14 | 15 | {- 16 | 17 | instance cls (ty, ty, ...) where 18 | method (con x1, con x2, ...) = wrapper (buildArrayIN x1 x2 ...) 19 | {-# INLINE method #-} 20 | 21 | -} 22 | mkTupleChildrenInstance :: Name -> Name -> Name -> Name -> Name -> Int -> Q [Dec] 23 | mkTupleChildrenInstance cls method ty con wrapper n = do 24 | let xs = map (mkName.('x':).show) [1..n] 25 | t = AppT (ConT cls) (iterate (`AppT` ConT ty) (TupleT n) !! n) 26 | build = mkName ("GHCJS.Prim.Internal.Build.buildArrayI" ++ show n) 27 | pat = [TupP (map (ConP con . (:[]) . VarP) xs)] 28 | body = NormalB (AppE (ConE wrapper) 29 | (foldl' (\e v -> AppE e (VarE v)) (VarE build) xs)) 30 | #if MIN_VERSION_template_haskell(2,11,0) 31 | return [InstanceD Nothing [] t [ FunD method [Clause pat body []] 32 | , PragmaD (InlineP method Inline FunLike AllPhases) 33 | ] 34 | ] 35 | #else 36 | return [InstanceD [] t [ FunD method [Clause pat body []] 37 | , PragmaD (InlineP method Inline FunLike AllPhases) 38 | ] 39 | ] 40 | #endif 41 | 42 | mkTupleAttrInstances :: Name -> Name -> Name -> Name -> Name -> [Int] -> Q [Dec] 43 | mkTupleAttrInstances cls method ty con wrapper xs = 44 | concat <$> mapM (mkTupleAttrInstance cls method ty con wrapper) xs 45 | 46 | {- 47 | 48 | instance cls (ty, ty, ...) where 49 | method (con k1 v1, con k2 v2, ...) = 50 | wrapper (buildObjectIN k1 k2 v1 v2 ...) 51 | {-# INLINE method #-} 52 | 53 | -} 54 | mkTupleAttrInstance :: Name -> Name -> Name -> Name -> Name -> Int -> Q [Dec] 55 | mkTupleAttrInstance cls method ty con wrapper n = do 56 | let xs = map (\i -> let si = show i in [mkName ('k':si), mkName ('v':si)]) [1..n] 57 | t = AppT (ConT cls) (iterate (`AppT` ConT ty) (TupleT n) !! n) 58 | build = mkName ("GHCJS.Prim.Internal.Build.buildObjectI" ++ show n) 59 | pat = [TupP (map (ConP con . map VarP) xs)] 60 | app e k v = AppE (AppE e (AppE (VarE 'unsafeCoerce) (VarE k))) (VarE v) 61 | body = NormalB (AppE (ConE wrapper) 62 | (foldl' (\e [k,v] -> app e k v) (VarE build) xs)) 63 | #if MIN_VERSION_template_haskell(2,11,0) 64 | return [InstanceD Nothing [] t [ FunD method [Clause pat body []] 65 | , PragmaD (InlineP method Inline FunLike AllPhases) 66 | ] 67 | ] 68 | #else 69 | return [InstanceD [] t [ FunD method [Clause pat body []] 70 | , PragmaD (InlineP method Inline FunLike AllPhases) 71 | ] 72 | ] 73 | #endif 74 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Internal/Thunk.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell, QuasiQuotes, LambdaCase, GHCForeignImportPrim #-} 2 | {-| 3 | Code that deals with forcing thunks in virtual-dom trees. When 4 | computing a diff, the virtual-dom code returns a list of thunks 5 | found in the tree. The caller then forces the thunks and recurses 6 | into them to advance the diff computation, until all thunks have 7 | been evaluated. 8 | -} 9 | module GHCJS.VDOM.Internal.Thunk where 10 | 11 | import GHCJS.Foreign.QQ 12 | import GHCJS.Prim 13 | 14 | import Control.Exception 15 | import Control.Monad 16 | 17 | import GHC.Exts (Any) 18 | import Unsafe.Coerce 19 | 20 | import GHCJS.VDOM.Internal (j,J) 21 | import qualified GHCJS.VDOM.Internal as I 22 | import GHCJS.VDOM.Internal.Types 23 | 24 | diff' :: J -> J -> IO J 25 | diff' a b = do 26 | thunks <- [jsu| [] |] 27 | p <- [jsu| h$vdom.diff(`a, `b, `thunks) |] 28 | forceThunks thunks 29 | forcePatch p 30 | return p 31 | 32 | forceThunks :: J -> IO () 33 | forceThunks thunks 34 | | [j| `thunks.length > 0 |] = fromJSArray thunks >>= mapM_ forceNode 35 | | otherwise = return () 36 | where 37 | forceNode n = do 38 | forceThunkNode [j| `n.a |] 39 | forceThunkNode [j| `n.b |] 40 | patch <- diff' [j| `n.a.vnode |] [j| `n.b.vnode |] 41 | [jsu_| h$vdom.setThunkPatch(`n, `patch); |] 42 | 43 | forceThunkNode :: J -> IO () 44 | forceThunkNode x = 45 | [jsu| `x && `x.hst && !`x.vnode |] >>= \case 46 | True -> do 47 | (VNode u) <- fmap unsafeCoerce . js_toHeapObject =<< I.getThunk x 48 | [jsu| `x.hst = null; 49 | `x.vnode = `u; 50 | |] 51 | _ -> return () 52 | 53 | 54 | forcePatch :: J -> IO () 55 | forcePatch p = do 56 | thunks <- [jsu| h$vdom.forcePatch(`p) |] 57 | forceTree [thunks] 58 | 59 | forceTree :: [J] -> IO () 60 | forceTree [] = return () 61 | forceTree (x:xs) = do 62 | x' <- fromJSArray x 63 | ys <- forM x' $ \t -> do 64 | forceThunkNode t 65 | newThunks <- [jsu| [] |] 66 | [jsu_| h$vdom.forceTree(`t.vnode, `newThunks) |] 67 | return newThunks 68 | forceTree (filter (\a -> [jsu'| `a.length !== 0 |]) ys ++ xs) 69 | 70 | foreign import javascript unsafe 71 | "$r = $1;" js_toHeapObject :: JSVal -> IO Any 72 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Internal/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes, TemplateHaskell, FlexibleInstances #-} 2 | 3 | module GHCJS.VDOM.Internal.Types where 4 | 5 | import qualified Data.JSString as JSS 6 | import Data.String (IsString(..)) 7 | 8 | import GHCJS.Foreign.QQ 9 | import GHCJS.Types 10 | import qualified GHCJS.Prim.Internal.Build 11 | import qualified GHCJS.Prim.Internal.Build as IB 12 | 13 | import GHCJS.VDOM.Internal.TH 14 | 15 | import Unsafe.Coerce 16 | 17 | -- do not export the constructors for these, this ensures that the objects are opaque 18 | -- and cannot be mutated 19 | newtype VNode = VNode { unVNode :: JSVal } 20 | newtype VComp = VComp { unVComp :: JSVal } 21 | newtype DComp = DComp { unDComp :: JSVal } 22 | newtype Patch = Patch { unPatch :: JSVal } 23 | newtype VMount = VMount { unVMount :: JSVal } 24 | 25 | -- fixme: make newtype? 26 | -- newtype JSIdent = JSIdent JSVal 27 | -- newtype DOMNode = DOMNode JSVal 28 | type JSIdent = JSVal 29 | type DOMNode = JSVal 30 | 31 | 32 | class Attributes a where 33 | mkAttributes :: a -> Attributes' 34 | newtype Attributes' = Attributes' JSVal 35 | 36 | data Attribute = Attribute JSString JSVal 37 | 38 | class Children a where 39 | mkChildren :: a -> Children' 40 | newtype Children' = Children' { unChildren :: JSVal } 41 | 42 | instance Children Children' where 43 | mkChildren x = x 44 | {-# INLINE mkChildren #-} 45 | 46 | instance Children () where 47 | mkChildren _ = Children' [jsu'| [] |] 48 | {-# INLINE mkChildren #-} 49 | 50 | instance Children VNode where 51 | mkChildren (VNode v) = 52 | Children' [jsu'| [`v] |] 53 | {-# INLINE mkChildren #-} 54 | 55 | mkTupleChildrenInstances ''Children 'mkChildren ''VNode 'VNode 'Children' [2..32] 56 | 57 | instance Children [VNode] where 58 | mkChildren xs = Children' $ IB.buildArrayI (unsafeCoerce xs) 59 | {-# INLINE mkChildren #-} 60 | 61 | instance Attributes Attributes' where 62 | mkAttributes x = x 63 | {-# INLINE mkAttributes #-} 64 | 65 | instance Attributes () where 66 | mkAttributes _ = Attributes' [jsu'| {} |] 67 | {-# INLINE mkAttributes #-} 68 | 69 | instance Attributes Attribute where 70 | mkAttributes (Attribute k v) = 71 | Attributes' (IB.buildObjectI1 (unsafeCoerce k) v) 72 | 73 | instance Attributes [Attribute] where 74 | mkAttributes xs = Attributes' (IB.buildObjectI $ 75 | map (\(Attribute k v) -> (unsafeCoerce k,v)) xs) 76 | 77 | mkTupleAttrInstances ''Attributes 'mkAttributes ''Attribute 'Attribute 'Attributes' [2..32] 78 | 79 | instance IsString VNode where fromString xs = text (JSS.pack xs) 80 | 81 | text :: JSString -> VNode 82 | text xs = VNode [jsu'| h$vdom.t(`xs) |] 83 | {-# INLINE text #-} 84 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/QQ.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes, DeriveDataTypeable, TemplateHaskell #-} 2 | {- 3 | More efficient JavaScript literals with QuasiQuoters 4 | 5 | mostly experimental, might not stay 6 | -} 7 | module GHCJS.VDOM.QQ (ch, children, att, attributes) where 8 | 9 | import Language.Haskell.TH.Quote 10 | import Language.Haskell.TH.Syntax 11 | 12 | import GHCJS.VDOM.Internal.Types 13 | 14 | import GHCJS.Types 15 | import GHCJS.Marshal 16 | 17 | import Control.Applicative 18 | 19 | import Data.Char 20 | import qualified Data.List as L 21 | import Data.List.Split 22 | import Data.Typeable 23 | 24 | import System.IO.Unsafe 25 | 26 | att :: QuasiQuoter 27 | att = attributes 28 | 29 | ch :: QuasiQuoter 30 | ch = children 31 | 32 | -- example: [props|a:1, b: null, c: x, d: y |] 33 | -- every value is either a literal or a variable referring to a convertible Haskell name 34 | -- fixme, this does not have a proper parser 35 | attributes :: QuasiQuoter 36 | attributes = QuasiQuoter { quoteExp = quoteProps } 37 | 38 | quoteProps :: String -> Q Exp 39 | quoteProps pat = jsExpQQ ('{':ffiPat++"}") (map mkName names) 40 | (\x -> AppE (VarE 'unsafePerformIO) (AppE (VarE 'toJSVal) x)) 41 | (AppE (ConE 'Attributes')) 42 | where 43 | (names, ffiPat) = genpat 1 $ map (break (==':') . trim) (linesBy (==',') pat) 44 | isName [] = False 45 | isName (x:xs) = isAlpha x && all isAlphaNum xs 46 | genpat :: Int -> [(String,String)] -> ([String], String) 47 | genpat _ [] = ([], "") 48 | genpat k ((x,':':n):xs) 49 | | isName n' = (n':ns, x ++ ": $" ++ (show k) ++ p) 50 | | otherwise = (ns, x ++ ':' : n ++ sep ++ p) 51 | where 52 | n' = trim n 53 | ~(ns, p) = genpat (k+1) xs 54 | sep = if null xs then "" else "," 55 | genpat _ _ = error "invalid pattern" 56 | 57 | 58 | -- example: [children|x,y,z|] for haskell names x,y,z :: VNode 59 | children :: QuasiQuoter 60 | children = QuasiQuoter { quoteExp = quoteChildren } 61 | 62 | quoteChildren :: String -> Q Exp 63 | quoteChildren pat = jsExpQQ ffiPat names (AppE (VarE 'unVNode)) (AppE (ConE 'Children')) 64 | where 65 | names = map (mkName.trim) (linesBy (==',') pat) 66 | ffiPat = '[' : L.intercalate "," (map (('$':).show) (take (length names) [(1::Int)..])) ++ "]" 67 | 68 | trim :: String -> String 69 | trim = let f = reverse . dropWhile isSpace in f . f 70 | 71 | newtype QQCounter = QQCounter { getCount :: Int } deriving (Typeable, Show) 72 | 73 | jsExpQQ :: String -> [Name] -> (Exp -> Exp) -> (Exp -> Exp) -> Q Exp 74 | jsExpQQ pat args unwrap wrap = do 75 | c <- maybe 0 getCount <$> qGetQ 76 | n <- newName ("__ghcjs_vdom_qq_spliced_" ++ show c) 77 | let ffiDecl = ForeignD (ImportF CCall Unsafe pat' n (ty $ length args)) 78 | ty :: Int -> Type 79 | ty 0 = ref 80 | ty n = AppT (AppT ArrowT ref) (ty (n-1)) 81 | ref = ConT ''JSVal 82 | ffiCall [] = (VarE n) 83 | ffiCall (y:ys) = AppE (ffiCall ys) (unwrap (VarE y)) 84 | pat' = "__ghcjs_javascript_" ++ L.intercalate "_" (map (show . ord) pat) 85 | qAddTopDecls [ffiDecl] 86 | qPutQ (QQCounter (c+1)) 87 | return $ wrap (ffiCall $ reverse args) 88 | 89 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Render.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TypeFamilies, QuasiQuotes, ScopedTypeVariables #-} 2 | {-| 3 | Some utilities to manage a render queue for ghcjs-vdom components. 4 | 5 | Diffing is performed in an asynchronous background thread. When a 6 | patch is ready, an animationframe is requested to apply the update. 7 | 8 | Using the render queue is optional, directly calling diff and patch 9 | for the components is a lower level way to update the document. 10 | -} 11 | 12 | module GHCJS.VDOM.Render ( Renderer 13 | , render 14 | , mkRenderer 15 | ) where 16 | 17 | import GHCJS.Foreign.Callback 18 | import GHCJS.Foreign.QQ 19 | import GHCJS.Types 20 | 21 | import GHCJS.VDOM.Internal.Types 22 | import qualified GHCJS.VDOM.Component as C 23 | import qualified GHCJS.VDOM as V 24 | 25 | import JavaScript.Web.AnimationFrame 26 | 27 | import Control.Concurrent 28 | import qualified Control.Exception as E 29 | import Control.Monad 30 | 31 | import Data.IORef 32 | import Data.Map (Map) 33 | import qualified Data.Map as M 34 | import Data.Typeable 35 | 36 | class Renderable a where 37 | type Render a 38 | doRender :: Renderer -> a -> Render a 39 | 40 | instance Renderable VComp where 41 | type Render VComp = IO () 42 | doRender r c@(VComp vcj) = 43 | enqueueRender r vcj (C.render c >>= C.diff c >>= addPatch vcj) (flushPatch vcj) 44 | 45 | instance Renderable VMount where 46 | type Render VMount = (VNode -> IO ()) 47 | doRender r vm@(VMount vmj) vn = 48 | enqueueRender r vmj (V.diff vm vn >>= addPatch vmj) (flushPatch vmj) 49 | 50 | render :: Renderable a => Renderer -> a -> Render a 51 | render r x = doRender r x 52 | 53 | mkRenderer :: IO Renderer 54 | mkRenderer = do 55 | r <- Renderer <$> newIORef M.empty 56 | <*> newIORef M.empty 57 | <*> newChan 58 | void (forkIO (renderThread r)) 59 | return r 60 | 61 | data Renderer = Renderer 62 | { renderPending :: IORef (Map Double (IO (), IO ())) 63 | , renderFlushPending :: IORef (Map Double (IO ())) 64 | , renderQueue :: Chan Double 65 | } deriving (Typeable) 66 | 67 | renderThread :: Renderer -> IO () 68 | renderThread r = forever $ do 69 | k <- readChan (renderQueue r) 70 | Just (compute, flush) <- 71 | atomicModifyIORef (renderPending r) (\m -> (M.delete k m, M.lookup k m)) 72 | let actions = do 73 | compute 74 | m <- atomicModifyIORef (renderFlushPending r) 75 | (\m -> (M.insert k flush m, m)) 76 | when (M.null m) (void $ inAnimationFrame ThrowWouldBlock 77 | (\_ -> flushPatches r)) 78 | actions`E.catch` \(_::E.SomeException) -> return () 79 | 80 | flushPatches :: Renderer -> IO () 81 | flushPatches r = 82 | mapM_ (\m -> m `E.catch` \(_::E.SomeException) -> return ()) =<< 83 | atomicModifyIORef (renderFlushPending r) 84 | (\m -> (M.empty, M.elems m)) 85 | 86 | enqueueRender :: Renderer 87 | -> JSVal 88 | -> IO () 89 | -> IO () 90 | -> IO () 91 | enqueueRender r renderable compute flush = do 92 | k <- renderableKey renderable 93 | m <- atomicModifyIORef (renderPending r) 94 | (\m -> (M.insert k (compute, flush) m, m)) 95 | when (M.notMember k m) (writeChan (renderQueue r) k) 96 | 97 | renderableKey :: JSVal -> IO Double 98 | renderableKey r = [jsu| `r._key |] 99 | 100 | addPatch :: JSVal -> Patch -> IO () 101 | addPatch r (Patch p) = [jsu_| `r.addPatch(`p); |] 102 | 103 | flushPatch :: JSVal -> IO () 104 | flushPatch r = [jsu_| `r.patch(null); |] 105 | -------------------------------------------------------------------------------- /src/GHCJS/VDOM/Unsafe.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Unsafe API for constructing Children' and Attributes', useful for 3 | writing new Children and Attributes instances 4 | -} 5 | module GHCJS.VDOM.Unsafe ( Attributes(..), Children(..) 6 | , Attributes', Children' 7 | , unsafeToAttributes, unsafeToChildren ) where 8 | 9 | import GHCJS.Types 10 | 11 | import GHCJS.VDOM.Internal.Types 12 | 13 | {-| 14 | Convert a JSVal, which must be a JS object, to Attributes'. The object 15 | may not be mutated. 16 | 17 | FIXME what do we need to always be able to find Haskell callbacks? 18 | -} 19 | unsafeToAttributes :: JSVal -> Attributes' 20 | unsafeToAttributes = Attributes' 21 | 22 | {-| 23 | Convert a JSVal, which must be an array of virtual-dom nodes 24 | to Children'. The array and the child nodes may not be mutated. 25 | 26 | note: All nodes must either be a virtual node or an instance of HSThunk, 27 | if you use other node types, the extensible retention (see scanTree in 28 | virtual-dom/lib.require.js) must be extended. 29 | -} 30 | unsafeToChildren :: JSVal -> Children' 31 | unsafeToChildren = Children' 32 | -------------------------------------------------------------------------------- /virtual-dom/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /virtual-dom/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | browserify: { 4 | lib: { 5 | files: { 6 | 'lib.js': ['lib.require.js'] 7 | } 8 | } 9 | }, 10 | watch: { 11 | lib: { 12 | files: 'lib.require.js', 13 | tasks: ['browserify:lib'] 14 | } 15 | } 16 | }); 17 | grunt.loadNpmTasks('grunt-contrib-watch'); 18 | grunt.loadNpmTasks('grunt-browserify'); 19 | 20 | grunt.registerTask('default', ['browserify']); 21 | }; 22 | -------------------------------------------------------------------------------- /virtual-dom/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Matt-Esch. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /virtual-dom/README.md: -------------------------------------------------------------------------------- 1 | to initiate: 2 | 3 | $ npm install 4 | 5 | to build 6 | 7 | $ grunt 8 | 9 | (install grunt with: npm install -g grunt-cli) 10 | 11 | to watch for changes and rebuild 12 | 13 | $ grunt watch 14 | -------------------------------------------------------------------------------- /virtual-dom/diff.js: -------------------------------------------------------------------------------- 1 | /* 2 | vtree/diff module modified to defer rendering thunks. this makes it possible to 3 | implement thunks that cannot be called directly as a function, but have 4 | an asynchronous callback or require evaluation in some specific runtime 5 | environment 6 | */ 7 | 8 | var isArray = require("x-is-array") 9 | 10 | var VPatch = require("virtual-dom/vnode/vpatch") 11 | var isVNode = require("virtual-dom/vnode/is-vnode") 12 | var isVText = require("virtual-dom/vnode/is-vtext") 13 | var isWidget = require("virtual-dom/vnode/is-widget") 14 | var isThunk = require("virtual-dom/vnode/is-thunk") 15 | var handleThunk = require("./handle-thunk") 16 | 17 | var diffProps = require("virtual-dom/vtree/diff-props") 18 | 19 | module.exports = diff 20 | 21 | // unevaluated thunks are added to the thunks argument (array) 22 | function diff(a, b, thunks) { 23 | if(!a) throw new Error ("diff a: " + a); 24 | if(!a) throw new Error ("diff b: " + b); 25 | var patch = { a: a } 26 | walk(a, b, patch, thunks, 0) 27 | return patch 28 | } 29 | 30 | function walk(a, b, patch, thunks, index) { 31 | if (a === b) { 32 | return 33 | } 34 | 35 | var apply = patch[index] 36 | var applyClear = false 37 | 38 | if (isThunk(a) || isThunk(b)) { 39 | doThunks(a, b, patch, thunks, index) 40 | } else if (b == null) { 41 | 42 | // If a is a widget we will add a remove patch for it 43 | // Otherwise any child widgets/hooks must be destroyed. 44 | // This prevents adding two remove patches for a widget. 45 | if (!isWidget(a)) { 46 | clearState(a, patch, index) 47 | apply = patch[index] 48 | } 49 | 50 | apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b)) 51 | } else if (isVNode(b)) { 52 | if (isVNode(a)) { 53 | if (a.tagName === b.tagName && 54 | a.namespace === b.namespace && 55 | a.key === b.key) { 56 | var propsPatch = diffProps(a.properties, b.properties) 57 | if (propsPatch) { 58 | apply = appendPatch(apply, 59 | new VPatch(VPatch.PROPS, a, propsPatch)) 60 | } 61 | apply = diffChildren(a, b, patch, apply, thunks, index) 62 | } else { 63 | apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) 64 | applyClear = true 65 | } 66 | } else { 67 | apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) 68 | applyClear = true 69 | } 70 | } else if (isVText(b)) { 71 | if (!isVText(a)) { 72 | apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) 73 | applyClear = true 74 | } else if (a.text !== b.text) { 75 | apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) 76 | } 77 | } else if (isWidget(b)) { 78 | if (!isWidget(a)) { 79 | applyClear = true 80 | } 81 | 82 | apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b)) 83 | } 84 | 85 | if (apply) { 86 | patch[index] = apply 87 | } 88 | 89 | if (applyClear) { 90 | clearState(a, patch, index) 91 | } 92 | } 93 | 94 | function diffChildren(a, b, patch, apply, thunks, index) { 95 | var aChildren = a.children 96 | var orderedSet = reorder(aChildren, b.children) 97 | var bChildren = orderedSet.children 98 | 99 | var aLen = aChildren.length 100 | var bLen = bChildren.length 101 | var len = aLen > bLen ? aLen : bLen 102 | 103 | for (var i = 0; i < len; i++) { 104 | var leftNode = aChildren[i] 105 | var rightNode = bChildren[i] 106 | index += 1 107 | 108 | if (!leftNode) { 109 | if (rightNode) { 110 | // Excess nodes in b need to be added 111 | apply = appendPatch(apply, 112 | new VPatch(VPatch.INSERT, null, rightNode)) 113 | } 114 | } else { 115 | walk(leftNode, rightNode, patch, thunks, index) 116 | } 117 | 118 | if (isVNode(leftNode) && leftNode.count) { 119 | index += leftNode.count 120 | } 121 | } 122 | 123 | if (orderedSet.moves) { 124 | // Reorder nodes last 125 | apply = appendPatch(apply, new VPatch( 126 | VPatch.ORDER, 127 | a, 128 | orderedSet.moves 129 | )) 130 | } 131 | 132 | return apply 133 | } 134 | 135 | function clearState(vNode, patch, index) { 136 | // TODO: Make this a single walk, not two 137 | unhook(vNode, patch, index) 138 | destroyWidgets(vNode, patch, index) 139 | } 140 | 141 | // Patch records for all destroyed widgets must be added because we need 142 | // a DOM node reference for the destroy function 143 | function destroyWidgets(vNode, patch, index) { 144 | if (isWidget(vNode)) { 145 | if (typeof vNode.destroy === "function") { 146 | patch[index] = appendPatch( 147 | patch[index], 148 | new VPatch(VPatch.REMOVE, vNode, null) 149 | ) 150 | } 151 | } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) { 152 | var children = vNode.children 153 | var len = children.length 154 | for (var i = 0; i < len; i++) { 155 | var child = children[i] 156 | index += 1 157 | 158 | destroyWidgets(child, patch, index) 159 | 160 | if (isVNode(child) && child.count) { 161 | index += child.count 162 | } 163 | } 164 | } else if (isThunk(vNode)) { 165 | doThunks(vNode, null, patch, thunks, index) 166 | } 167 | } 168 | 169 | // Create a sub-patch for thunks 170 | function doThunks(a, b, patch, thunks, index) { 171 | var ts = handleThunk(a, b); 172 | if(ts.a || ts.b) { 173 | // defer rendering, caller is responsible for: 174 | // - filling ts.a.vnode / ts.b.vnode with the result from the thunk 175 | // - ts.p[t.sp] = new VPatch(VPatch.THUNK, null, diff(ts.a.vnode, ts.b.vnode)) 176 | // before using it with patch 177 | thunks.push({ i: index, p: patch, a: a, b: b}); 178 | } else { 179 | var thunkPatch = diff(a.vnode, b.vnode, thunks) 180 | if (hasPatches(thunkPatch)) { 181 | patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch) 182 | } 183 | } 184 | } 185 | 186 | function hasPatches(patch) { 187 | for (var index in patch) { 188 | if (index !== "a") { 189 | return true 190 | } 191 | } 192 | 193 | return false 194 | } 195 | 196 | // Execute hooks when two nodes are identical 197 | function unhook(vNode, patch, index) { 198 | if (isVNode(vNode)) { 199 | if (vNode.hooks) { 200 | patch[index] = appendPatch( 201 | patch[index], 202 | new VPatch( 203 | VPatch.PROPS, 204 | vNode, 205 | undefinedKeys(vNode.hooks) 206 | ) 207 | ) 208 | } 209 | 210 | if (vNode.descendantHooks || vNode.hasThunks) { 211 | var children = vNode.children 212 | var len = children.length 213 | for (var i = 0; i < len; i++) { 214 | var child = children[i] 215 | index += 1 216 | 217 | unhook(child, patch, index) 218 | 219 | if (isVNode(child) && child.count) { 220 | index += child.count 221 | } 222 | } 223 | } 224 | } else if (isThunk(vNode)) { 225 | doThunks(vNode, null, patch, thunks, index) 226 | } 227 | } 228 | 229 | function undefinedKeys(obj) { 230 | var result = {} 231 | 232 | for (var key in obj) { 233 | result[key] = undefined 234 | } 235 | 236 | return result 237 | } 238 | 239 | // List diff, naive left to right reordering 240 | function reorder(aChildren, bChildren) { 241 | // O(M) time, O(M) memory 242 | var bChildIndex = keyIndex(bChildren) 243 | var bKeys = bChildIndex.keys 244 | var bFree = bChildIndex.free 245 | 246 | if (bFree.length === bChildren.length) { 247 | return { 248 | children: bChildren, 249 | moves: null 250 | } 251 | } 252 | 253 | // O(N) time, O(N) memory 254 | var aChildIndex = keyIndex(aChildren) 255 | var aKeys = aChildIndex.keys 256 | var aFree = aChildIndex.free 257 | 258 | if (aFree.length === aChildren.length) { 259 | return { 260 | children: bChildren, 261 | moves: null 262 | } 263 | } 264 | 265 | // O(MAX(N, M)) memory 266 | var newChildren = [] 267 | 268 | var freeIndex = 0 269 | var freeCount = bFree.length 270 | var deletedItems = 0 271 | 272 | // Iterate through a and match a node in b 273 | // O(N) time, 274 | for (var i = 0 ; i < aChildren.length; i++) { 275 | var aItem = aChildren[i] 276 | var itemIndex 277 | 278 | if (aItem.key) { 279 | if (bKeys.hasOwnProperty(aItem.key)) { 280 | // Match up the old keys 281 | itemIndex = bKeys[aItem.key] 282 | newChildren.push(bChildren[itemIndex]) 283 | 284 | } else { 285 | // Remove old keyed items 286 | itemIndex = i - deletedItems++ 287 | newChildren.push(null) 288 | } 289 | } else { 290 | // Match the item in a with the next free item in b 291 | if (freeIndex < freeCount) { 292 | itemIndex = bFree[freeIndex++] 293 | newChildren.push(bChildren[itemIndex]) 294 | } else { 295 | // There are no free items in b to match with 296 | // the free items in a, so the extra free nodes 297 | // are deleted. 298 | itemIndex = i - deletedItems++ 299 | newChildren.push(null) 300 | } 301 | } 302 | } 303 | 304 | var lastFreeIndex = freeIndex >= bFree.length ? 305 | bChildren.length : 306 | bFree[freeIndex] 307 | 308 | // Iterate through b and append any new keys 309 | // O(M) time 310 | for (var j = 0; j < bChildren.length; j++) { 311 | var newItem = bChildren[j] 312 | 313 | if (newItem.key) { 314 | if (!aKeys.hasOwnProperty(newItem.key)) { 315 | // Add any new keyed items 316 | // We are adding new items to the end and then sorting them 317 | // in place. In future we should insert new items in place. 318 | newChildren.push(newItem) 319 | } 320 | } else if (j >= lastFreeIndex) { 321 | // Add any leftover non-keyed items 322 | newChildren.push(newItem) 323 | } 324 | } 325 | 326 | var simulate = newChildren.slice() 327 | var simulateIndex = 0 328 | var removes = [] 329 | var inserts = [] 330 | var simulateItem 331 | 332 | for (var k = 0; k < bChildren.length;) { 333 | var wantedItem = bChildren[k] 334 | simulateItem = simulate[simulateIndex] 335 | 336 | // remove items 337 | while (simulateItem === null && simulate.length) { 338 | removes.push(remove(simulate, simulateIndex, null)) 339 | simulateItem = simulate[simulateIndex] 340 | } 341 | 342 | if (!simulateItem || simulateItem.key !== wantedItem.key) { 343 | // if we need a key in this position... 344 | if (wantedItem.key) { 345 | if (simulateItem && simulateItem.key) { 346 | // if an insert doesn't put this key in place, it needs to move 347 | if (bKeys[simulateItem.key] !== k + 1) { 348 | removes.push(remove(simulate, simulateIndex, simulateItem.key)) 349 | simulateItem = simulate[simulateIndex] 350 | // if the remove didn't put the wanted item in place, we need to insert it 351 | if (!simulateItem || simulateItem.key !== wantedItem.key) { 352 | inserts.push({key: wantedItem.key, to: k}) 353 | } 354 | // items are matching, so skip ahead 355 | else { 356 | simulateIndex++ 357 | } 358 | } 359 | else { 360 | inserts.push({key: wantedItem.key, to: k}) 361 | } 362 | } 363 | else { 364 | inserts.push({key: wantedItem.key, to: k}) 365 | } 366 | k++ 367 | } 368 | // a key in simulate has no matching wanted key, remove it 369 | else if (simulateItem && simulateItem.key) { 370 | removes.push(remove(simulate, simulateIndex, simulateItem.key)) 371 | } 372 | } 373 | else { 374 | simulateIndex++ 375 | k++ 376 | } 377 | } 378 | 379 | // remove all the remaining nodes from simulate 380 | while(simulateIndex < simulate.length) { 381 | simulateItem = simulate[simulateIndex] 382 | removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key)) 383 | } 384 | 385 | // If the only moves we have are deletes then we can just 386 | // let the delete patch remove these items. 387 | if (removes.length === deletedItems && !inserts.length) { 388 | return { 389 | children: newChildren, 390 | moves: null 391 | } 392 | } 393 | 394 | return { 395 | children: newChildren, 396 | moves: { 397 | removes: removes, 398 | inserts: inserts 399 | } 400 | } 401 | } 402 | 403 | function remove(arr, index, key) { 404 | arr.splice(index, 1) 405 | 406 | return { 407 | from: index, 408 | key: key 409 | } 410 | } 411 | 412 | function keyIndex(children) { 413 | var keys = {} 414 | var free = [] 415 | var length = children.length 416 | 417 | for (var i = 0; i < length; i++) { 418 | var child = children[i] 419 | 420 | if (child.key) { 421 | keys[child.key] = i 422 | } else { 423 | free.push(i) 424 | } 425 | } 426 | 427 | return { 428 | keys: keys, // A hash of key name to index 429 | free: free // An array of unkeyed item indices 430 | } 431 | } 432 | 433 | function appendPatch(apply, patch) { 434 | if (apply) { 435 | if (isArray(apply)) { 436 | apply.push(patch) 437 | } else { 438 | apply = [apply, patch] 439 | } 440 | 441 | return apply 442 | } else { 443 | return patch 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /virtual-dom/handle-thunk.js: -------------------------------------------------------------------------------- 1 | var isVNode = require("virtual-dom/vnode/is-vnode") 2 | var isVText = require("virtual-dom/vnode/is-vtext") 3 | var isWidget = require("virtual-dom/vnode/is-widget") 4 | var isThunk = require("virtual-dom/vnode/is-thunk") 5 | 6 | module.exports = handleThunk 7 | 8 | function handleThunk(a, b) { 9 | return { a: isThunk(a) ? renderThunk(a, null) : null 10 | , b: isThunk(b) ? renderThunk(b, a) : null 11 | } 12 | } 13 | 14 | function renderThunk(thunk, previous) { 15 | if(thunk.vnode) return null; 16 | thunk.render(previous); 17 | return thunk.vnode ? null : thunk; 18 | } 19 | -------------------------------------------------------------------------------- /virtual-dom/lib.js: -------------------------------------------------------------------------------- 1 | (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 bLen ? aLen : bLen 103 | 104 | for (var i = 0; i < len; i++) { 105 | var leftNode = aChildren[i] 106 | var rightNode = bChildren[i] 107 | index += 1 108 | 109 | if (!leftNode) { 110 | if (rightNode) { 111 | // Excess nodes in b need to be added 112 | apply = appendPatch(apply, 113 | new VPatch(VPatch.INSERT, null, rightNode)) 114 | } 115 | } else { 116 | walk(leftNode, rightNode, patch, thunks, index) 117 | } 118 | 119 | if (isVNode(leftNode) && leftNode.count) { 120 | index += leftNode.count 121 | } 122 | } 123 | 124 | if (orderedSet.moves) { 125 | // Reorder nodes last 126 | apply = appendPatch(apply, new VPatch( 127 | VPatch.ORDER, 128 | a, 129 | orderedSet.moves 130 | )) 131 | } 132 | 133 | return apply 134 | } 135 | 136 | function clearState(vNode, patch, index) { 137 | // TODO: Make this a single walk, not two 138 | unhook(vNode, patch, index) 139 | destroyWidgets(vNode, patch, index) 140 | } 141 | 142 | // Patch records for all destroyed widgets must be added because we need 143 | // a DOM node reference for the destroy function 144 | function destroyWidgets(vNode, patch, index) { 145 | if (isWidget(vNode)) { 146 | if (typeof vNode.destroy === "function") { 147 | patch[index] = appendPatch( 148 | patch[index], 149 | new VPatch(VPatch.REMOVE, vNode, null) 150 | ) 151 | } 152 | } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) { 153 | var children = vNode.children 154 | var len = children.length 155 | for (var i = 0; i < len; i++) { 156 | var child = children[i] 157 | index += 1 158 | 159 | destroyWidgets(child, patch, index) 160 | 161 | if (isVNode(child) && child.count) { 162 | index += child.count 163 | } 164 | } 165 | } else if (isThunk(vNode)) { 166 | doThunks(vNode, null, patch, thunks, index) 167 | } 168 | } 169 | 170 | // Create a sub-patch for thunks 171 | function doThunks(a, b, patch, thunks, index) { 172 | var ts = handleThunk(a, b); 173 | if(ts.a || ts.b) { 174 | // defer rendering, caller is responsible for: 175 | // - filling ts.a.vnode / ts.b.vnode with the result from the thunk 176 | // - ts.p[t.sp] = new VPatch(VPatch.THUNK, null, diff(ts.a.vnode, ts.b.vnode)) 177 | // before using it with patch 178 | thunks.push({ i: index, p: patch, a: a, b: b}); 179 | } else { 180 | var thunkPatch = diff(a.vnode, b.vnode, thunks) 181 | if (hasPatches(thunkPatch)) { 182 | patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch) 183 | } 184 | } 185 | } 186 | 187 | function hasPatches(patch) { 188 | for (var index in patch) { 189 | if (index !== "a") { 190 | return true 191 | } 192 | } 193 | 194 | return false 195 | } 196 | 197 | // Execute hooks when two nodes are identical 198 | function unhook(vNode, patch, index) { 199 | if (isVNode(vNode)) { 200 | if (vNode.hooks) { 201 | patch[index] = appendPatch( 202 | patch[index], 203 | new VPatch( 204 | VPatch.PROPS, 205 | vNode, 206 | undefinedKeys(vNode.hooks) 207 | ) 208 | ) 209 | } 210 | 211 | if (vNode.descendantHooks || vNode.hasThunks) { 212 | var children = vNode.children 213 | var len = children.length 214 | for (var i = 0; i < len; i++) { 215 | var child = children[i] 216 | index += 1 217 | 218 | unhook(child, patch, index) 219 | 220 | if (isVNode(child) && child.count) { 221 | index += child.count 222 | } 223 | } 224 | } 225 | } else if (isThunk(vNode)) { 226 | doThunks(vNode, null, patch, thunks, index) 227 | } 228 | } 229 | 230 | function undefinedKeys(obj) { 231 | var result = {} 232 | 233 | for (var key in obj) { 234 | result[key] = undefined 235 | } 236 | 237 | return result 238 | } 239 | 240 | // List diff, naive left to right reordering 241 | function reorder(aChildren, bChildren) { 242 | // O(M) time, O(M) memory 243 | var bChildIndex = keyIndex(bChildren) 244 | var bKeys = bChildIndex.keys 245 | var bFree = bChildIndex.free 246 | 247 | if (bFree.length === bChildren.length) { 248 | return { 249 | children: bChildren, 250 | moves: null 251 | } 252 | } 253 | 254 | // O(N) time, O(N) memory 255 | var aChildIndex = keyIndex(aChildren) 256 | var aKeys = aChildIndex.keys 257 | var aFree = aChildIndex.free 258 | 259 | if (aFree.length === aChildren.length) { 260 | return { 261 | children: bChildren, 262 | moves: null 263 | } 264 | } 265 | 266 | // O(MAX(N, M)) memory 267 | var newChildren = [] 268 | 269 | var freeIndex = 0 270 | var freeCount = bFree.length 271 | var deletedItems = 0 272 | 273 | // Iterate through a and match a node in b 274 | // O(N) time, 275 | for (var i = 0 ; i < aChildren.length; i++) { 276 | var aItem = aChildren[i] 277 | var itemIndex 278 | 279 | if (aItem.key) { 280 | if (bKeys.hasOwnProperty(aItem.key)) { 281 | // Match up the old keys 282 | itemIndex = bKeys[aItem.key] 283 | newChildren.push(bChildren[itemIndex]) 284 | 285 | } else { 286 | // Remove old keyed items 287 | itemIndex = i - deletedItems++ 288 | newChildren.push(null) 289 | } 290 | } else { 291 | // Match the item in a with the next free item in b 292 | if (freeIndex < freeCount) { 293 | itemIndex = bFree[freeIndex++] 294 | newChildren.push(bChildren[itemIndex]) 295 | } else { 296 | // There are no free items in b to match with 297 | // the free items in a, so the extra free nodes 298 | // are deleted. 299 | itemIndex = i - deletedItems++ 300 | newChildren.push(null) 301 | } 302 | } 303 | } 304 | 305 | var lastFreeIndex = freeIndex >= bFree.length ? 306 | bChildren.length : 307 | bFree[freeIndex] 308 | 309 | // Iterate through b and append any new keys 310 | // O(M) time 311 | for (var j = 0; j < bChildren.length; j++) { 312 | var newItem = bChildren[j] 313 | 314 | if (newItem.key) { 315 | if (!aKeys.hasOwnProperty(newItem.key)) { 316 | // Add any new keyed items 317 | // We are adding new items to the end and then sorting them 318 | // in place. In future we should insert new items in place. 319 | newChildren.push(newItem) 320 | } 321 | } else if (j >= lastFreeIndex) { 322 | // Add any leftover non-keyed items 323 | newChildren.push(newItem) 324 | } 325 | } 326 | 327 | var simulate = newChildren.slice() 328 | var simulateIndex = 0 329 | var removes = [] 330 | var inserts = [] 331 | var simulateItem 332 | 333 | for (var k = 0; k < bChildren.length;) { 334 | var wantedItem = bChildren[k] 335 | simulateItem = simulate[simulateIndex] 336 | 337 | // remove items 338 | while (simulateItem === null && simulate.length) { 339 | removes.push(remove(simulate, simulateIndex, null)) 340 | simulateItem = simulate[simulateIndex] 341 | } 342 | 343 | if (!simulateItem || simulateItem.key !== wantedItem.key) { 344 | // if we need a key in this position... 345 | if (wantedItem.key) { 346 | if (simulateItem && simulateItem.key) { 347 | // if an insert doesn't put this key in place, it needs to move 348 | if (bKeys[simulateItem.key] !== k + 1) { 349 | removes.push(remove(simulate, simulateIndex, simulateItem.key)) 350 | simulateItem = simulate[simulateIndex] 351 | // if the remove didn't put the wanted item in place, we need to insert it 352 | if (!simulateItem || simulateItem.key !== wantedItem.key) { 353 | inserts.push({key: wantedItem.key, to: k}) 354 | } 355 | // items are matching, so skip ahead 356 | else { 357 | simulateIndex++ 358 | } 359 | } 360 | else { 361 | inserts.push({key: wantedItem.key, to: k}) 362 | } 363 | } 364 | else { 365 | inserts.push({key: wantedItem.key, to: k}) 366 | } 367 | k++ 368 | } 369 | // a key in simulate has no matching wanted key, remove it 370 | else if (simulateItem && simulateItem.key) { 371 | removes.push(remove(simulate, simulateIndex, simulateItem.key)) 372 | } 373 | } 374 | else { 375 | simulateIndex++ 376 | k++ 377 | } 378 | } 379 | 380 | // remove all the remaining nodes from simulate 381 | while(simulateIndex < simulate.length) { 382 | simulateItem = simulate[simulateIndex] 383 | removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key)) 384 | } 385 | 386 | // If the only moves we have are deletes then we can just 387 | // let the delete patch remove these items. 388 | if (removes.length === deletedItems && !inserts.length) { 389 | return { 390 | children: newChildren, 391 | moves: null 392 | } 393 | } 394 | 395 | return { 396 | children: newChildren, 397 | moves: { 398 | removes: removes, 399 | inserts: inserts 400 | } 401 | } 402 | } 403 | 404 | function remove(arr, index, key) { 405 | arr.splice(index, 1) 406 | 407 | return { 408 | from: index, 409 | key: key 410 | } 411 | } 412 | 413 | function keyIndex(children) { 414 | var keys = {} 415 | var free = [] 416 | var length = children.length 417 | 418 | for (var i = 0; i < length; i++) { 419 | var child = children[i] 420 | 421 | if (child.key) { 422 | keys[child.key] = i 423 | } else { 424 | free.push(i) 425 | } 426 | } 427 | 428 | return { 429 | keys: keys, // A hash of key name to index 430 | free: free // An array of unkeyed item indices 431 | } 432 | } 433 | 434 | function appendPatch(apply, patch) { 435 | if (apply) { 436 | if (isArray(apply)) { 437 | apply.push(patch) 438 | } else { 439 | apply = [apply, patch] 440 | } 441 | 442 | return apply 443 | } else { 444 | return patch 445 | } 446 | } 447 | 448 | },{"./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){ 449 | var isVNode = require("virtual-dom/vnode/is-vnode") 450 | var isVText = require("virtual-dom/vnode/is-vtext") 451 | var isWidget = require("virtual-dom/vnode/is-widget") 452 | var isThunk = require("virtual-dom/vnode/is-thunk") 453 | 454 | module.exports = handleThunk 455 | 456 | function handleThunk(a, b) { 457 | return { a: isThunk(a) ? renderThunk(a, null) : null 458 | , b: isThunk(b) ? renderThunk(b, a) : null 459 | } 460 | } 461 | 462 | function renderThunk(thunk, previous) { 463 | if(thunk.vnode) return null; 464 | thunk.render(previous); 465 | return thunk.vnode ? null : thunk; 466 | } 467 | 468 | },{"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){ 469 | /* 470 | to generate lib.js, install virtual-dom and process file: 471 | 472 | $ npm install 473 | $ grunt 474 | the ./diff module is vtree/diff with a few changes to 475 | allow diff to run in an asynchronous thread in the presence of 476 | memoized nodes. 477 | */ 478 | 479 | /* 480 | Note on memory management: 481 | 482 | To ensure accurate heap tracing for finalization and profiling purposes, 483 | GHCJS needs to know reachable all Haskell values. ghcjs-vdom stores some 484 | Haskell values inside JavaScript references and uses extensible retention 485 | to collect these values. It's crucial that all data structures that may 486 | contain Haskell values are stored directly in a JSVal and not inside other 487 | JS data structures. 488 | 489 | The recognized types are: 490 | 491 | - HSPatch: 492 | The patch object contains the new (target) virtual-dom tree and 493 | the original tree is reachable trough the parent. Since all handlers, 494 | components and thunks are reachable through these trees, the patch itself 495 | does not need to be traversed. 496 | 497 | - HSMount: 498 | All mounted HSMount points are scanned as roots. The current virtual tree 499 | (mount.vtree) is traversed for this. 500 | 501 | - HSComponent: 502 | Traversed when reachable through Haskell heap or virtual-dom tree. Contains 503 | current tree (component.vtree) and rendering action (component.hsRender) 504 | 505 | - HSThunk: 506 | Traversed when reachable through Haskell heap or virtual-dom tree. Contains 507 | Haskell suspension (thunk.hst) or rendered tree (thunk.vnode) 508 | 509 | - virtual node ( isVirtualNode(x) ) 510 | Some node in a virtual-dom tree. Haskell event handlers are stored in 'ev-*' 511 | properties, which virtual-dom adds to the node's hooks (vnode.hooks). If 512 | a virtual node contains thunks, widgets or any of its descendants have hooks, 513 | the children of the node have to be traversed. 514 | 515 | forceThunks and forcePatch fill an array of thunks, which is not directly 516 | scannable; however, these functions are only used as part of a `diff` operation. 517 | The initial diff creates an HSPatch object, through which the original and target 518 | virtual-dom tree are completely reachable. 519 | */ 520 | 521 | var isVirtualNode = require('virtual-dom/vnode/is-vnode'); 522 | var isThunk = require('virtual-dom/vnode/is-thunk'); 523 | var isWidget = require("virtual-dom/vnode/is-widget"); 524 | var h = require('virtual-dom/h'); 525 | var isArray = require('x-is-array'); 526 | var VPatch = require("virtual-dom/vnode/vpatch"); 527 | var VText = require('virtual-dom/vnode/vtext'); 528 | var vdomPatch = require('virtual-dom/vdom/patch'); 529 | var DomDelegator = require('dom-delegator'); 530 | 531 | var diff = require('./diff'); 532 | 533 | var VRenderableN = 0; 534 | 535 | /** @constructor */ 536 | function HSPatch(patch, old, vnode, parent) { 537 | this.patch = patch; 538 | this.old = old; 539 | this.vnode = vnode; 540 | this.parent = parent; 541 | } 542 | 543 | /** @constructor */ 544 | function HSThunk(t, ids, key) { 545 | this.hst = t; // haskell thunk 546 | this.ids = ids; // array of haskell unique ids 547 | this.key = key; 548 | this.vnode = null; 549 | this._ghcjsMark = 0; 550 | } 551 | 552 | HSThunk.prototype.type = 'Thunk'; 553 | 554 | /* 555 | render returns the deferred rendering object 556 | null if the thunk has already been rendered, in which case the value is in this.vnode 557 | */ 558 | HSThunk.prototype.render = function(previous) { 559 | if(previous && !this.vnode && eqIds(this.ids, previous.ids)) { 560 | if(previous.hst) { 561 | this.hst = previous.hst; 562 | } else { 563 | this.hst = null; 564 | this.vnode = previous.vnode; 565 | } 566 | } 567 | return this.vnode ? null : this; 568 | } 569 | 570 | /** @constructor */ 571 | function HSComponent(r, mnt, unmnt, key) { 572 | this._key = ++VRenderableN; 573 | this.hsRender = r; // IO action that produces a vdom tree 574 | this.hsMount = mnt; 575 | this.hsUnmount = unmnt; 576 | this.key = key || this._key; 577 | this.vnode = this.initialVnode = new VText(""); 578 | this.pending = []; 579 | this.mounts = {}; 580 | this.version = 0; 581 | this.latest = 0; 582 | } 583 | 584 | HSComponent.prototype.type = 'Widget'; 585 | 586 | HSComponent.prototype.init = function() { 587 | var n = document.createTextNode(''); 588 | if(this.vnode !== this.initialVnode) { 589 | var thunks = []; 590 | var p = diff(this.initialVnode, this.vnode, thunks); 591 | if(thunks.length !== 0) { 592 | throw new Error("HSComponent vnode contains unevaluated thunks"); 593 | } 594 | n = vdomPatch(n, p); 595 | } 596 | var m = new HSComponentMount(n); 597 | n._key = m._key; 598 | n._widget = this; 599 | this.mounts[m._key] = m; 600 | if(this.hsMount) { 601 | h$vdomMountComponentCallback(this.hsMount, m._key, this); 602 | } 603 | return n; 604 | } 605 | 606 | HSComponent.prototype.destroy = function(domNode) { 607 | delete this.mounts[domNode._key]; 608 | if(this.hsUnmount) { 609 | h$vdomUnmountComponentCallback(this.hsUnmount, domNode._key, domNode); 610 | } 611 | } 612 | 613 | HSComponent.prototype.diff = function(v, thunks) { 614 | var vn = this.vnode; 615 | if(this.pending.length > 0) { 616 | vn = this.pending[this.pending.length-1].vnode; 617 | } 618 | return new HSPatch( diff(vn, v, thunks) 619 | , vn 620 | , v 621 | , this); 622 | } 623 | 624 | HSComponent.prototype.addPatch = function(p) { 625 | var cur = this.pending.length > 0 ? this.pending[this.pending.length-1] 626 | : this.vnode; 627 | if(p.old === cur) this.pending.push(p); 628 | } 629 | 630 | HSComponent.prototype.patch = function(p) { 631 | if(this.pending.length > 0) { 632 | var pnd = this.pending; 633 | this.pending = []; 634 | for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]); 635 | } 636 | if(!p) return; 637 | if(p.parent !== this || p.old !== this.vnode) { 638 | return false; 639 | } 640 | for(var k in this.mounts) { 641 | var m = this.mounts[k]; 642 | m.node = vdomPatch(m.node, p.patch); 643 | } 644 | this.vnode = p.vnode; 645 | return true; 646 | } 647 | 648 | // only use this for manually updated components (i.e. no diff/patch) 649 | HSComponent.prototype.updateMount = function(mnt, node) { 650 | var m = this.mounts[mnt]; 651 | node._key = mnt; 652 | node._widget = this; 653 | m.node.parentNode.replaceChild(node, m.node); 654 | m.node = node; 655 | } 656 | 657 | HSComponent.prototype.update = function(leftVNode, node) { 658 | if(node._widget) { 659 | if(node._widget == this) return node; 660 | node._widget.destroy(node); 661 | } 662 | return this.init(); 663 | } 664 | 665 | var HSComponentMountN = 0; 666 | function HSComponentMount(domNode) { 667 | this._key = ++HSComponentMountN; 668 | this.node = domNode; 669 | } 670 | 671 | /** @constructor */ 672 | function HSMount(domNode) { 673 | this._key = ++VRenderableN; 674 | // this.version = 0; 675 | this.vnode = new VText(""); // currently rendered vdom tree 676 | this.pending = []; // pending patches, not yet applied 677 | this.node = document.createTextNode(""); 678 | this.parentNode = domNode; 679 | } 680 | 681 | HSMount.prototype.diff = function(v, thunks) { 682 | var vn = this.vnode; 683 | if(this.pending.length > 0) { 684 | vn = this.pending[this.pending.length-1].vnode; 685 | } 686 | return new HSPatch( diff(vn, v, thunks) 687 | , vn 688 | , v 689 | , this); 690 | } 691 | 692 | HSMount.prototype.addPatch = function(p) { 693 | var cur = this.pending.length > 0 ? this.pending[this.pending.length-1] 694 | : this.vnode; 695 | if(p.old === cur) this.pending.push(p); 696 | } 697 | 698 | // HSMount.patch(null) to flush pending list 699 | HSMount.prototype.patch = function(p) { 700 | if(this.pending.length > 0) { 701 | var pnd = this.pending; 702 | this.pending = []; 703 | for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]); 704 | } 705 | if(!p) return; 706 | if(p.parent !== this || p.old !== this.vnode) { 707 | return false; 708 | } 709 | this.node = vdomPatch(this.node, p.patch); 710 | this.vnode = p.vnode; 711 | return true; 712 | } 713 | 714 | /* mount a vdom tree, making it visible to extensible retention */ 715 | function mount(domNode) { 716 | while(domNode.firstChild) domNode.removeChild(domNode.firstChild); 717 | var m = new HSMount(domNode); 718 | domNode.appendChild(m.node); 719 | vdomMounts.add(m); 720 | return m; 721 | } 722 | 723 | /* unmount a tree, removing all child nodes. */ 724 | function unmount(vmount) { 725 | var n = vmount.parentNode; 726 | while(n.firstChild) n.removeChild(n.firstChild); 727 | vdomMounts.remove(vmount); 728 | } 729 | 730 | /* 731 | Compare lists of object identifiers associated with a thunk node. If the lists are equal, 732 | the subtree does not have to be recomputed. 733 | */ 734 | function eqIds(ids1, ids2) { 735 | if(!ids1 || !ids2 || ids1.length != ids2.length) return false; 736 | for(var i=ids1.length-1;i>=0;i--) { 737 | var id1 = ids1[i], id2 = ids2[i]; 738 | if(typeof id1 === 'number') { 739 | if(typeof id2 !== 'number') return false; 740 | if(id1 !== id2 && !((id1!=id1) && (id2!=id2))) return false; 741 | } else { 742 | if(id1 !== id2) return false; 743 | } 744 | } 745 | return true; 746 | } 747 | 748 | function forcePatch(p) { 749 | var thunks = [], i, j, pi; 750 | for(i in p) { 751 | var pi = p[i]; 752 | if(isArray(pi)) { 753 | for(j=pi.length-1;j>=0;j--) { 754 | forceTree(pi[j].patch, thunks); 755 | } 756 | } 757 | else if(pi.patch) forceTree(pi.patch, thunks); 758 | else forceTree(pi, thunks); 759 | } 760 | return thunks; 761 | } 762 | 763 | function forceTree(n, t) { 764 | if(isThunk(n)) { 765 | if(n.vnode) forceTree(n.vnode, t); 766 | else t.push(n); 767 | } else if(isVirtualNode(n) && n.hasThunks) { 768 | for(var i=n.children.length-1;i>=0;i--) { 769 | forceTree(n.children[i], t); 770 | } 771 | } 772 | } 773 | 774 | /* 775 | scan all mounted virtual-dom trees 776 | */ 777 | function scanMounts(currentMark) { 778 | var i = vdomMounts.iter(), m, res = []; 779 | while((m = i.next()) !== null) { 780 | scanTreeRec(m.vnode, res, currentMark); 781 | if(m.pending.length > 0) { 782 | scanTreeRec(m.pending[m.pending.length-1].vnode, res, currentMark); 783 | } 784 | } 785 | return res; 786 | } 787 | 788 | /* 789 | scan a tree (extensible retention callback). 790 | 791 | returns: 792 | - an array of haskell items if any 793 | - true if no haskell items have been found 794 | - false if the object is not a ghcjs-vdom tree 795 | (fallthrough to other extensible retention scanners) 796 | */ 797 | var scanTreeRes = []; 798 | function scanTree(o, currentMark) { 799 | if(isVirtualNode(o) || isThunk(o) || isWidget(o) || 800 | o instanceof HSPatch || o instanceof HSComponent || o instanceof HSMount) { 801 | var r = scanTreeRes; 802 | scanTreeRec(o, r, currentMark); 803 | if(r.length > 0) { 804 | scanTreeRes = []; 805 | return r; 806 | } else { 807 | return true; 808 | } 809 | } else { // not a ghcjs-vdom object, fall through 810 | return false; 811 | } 812 | } 813 | 814 | function scanTreeRec(o, r, currentMark) { 815 | if(o instanceof HSPatch) { 816 | scanTreeRec(o.vnode, r, currentMark); 817 | scanTreeRec(o.parent); 818 | } else if(o instanceof HSThunk) { 819 | if(o._ghcjsMark !== currentMark) { 820 | o._ghcjsMark = currentMark; 821 | if(o.hst) r.push(o.hst); 822 | if(o.vnode) scanTreeRec(o.vnode, r, currentMark); 823 | } 824 | } else if(o instanceof HSComponent) { 825 | if(o._ghcjsMark !== currentMark) { 826 | o._ghcjsMark = currentMark; 827 | if(o.hsRender) r.push(o.hsRender); 828 | if(o.hsMount) r.push(o.hsMount); 829 | if(o.hsUnmount) r.push(o.hsUnmount); 830 | if(o.vnode) scanTreeRec(o.vnode, r, currentMark); 831 | if(o.pending.length > 0) { 832 | scanTreeRec(o.pending[o.pending.length-1].vnode, r, currentMark); 833 | } 834 | } 835 | } else if(isVirtualNode(o)) { 836 | if(o._ghcjsMark !== currentMark) { 837 | o._ghcjsMark = currentMark; 838 | // collect event handlers 839 | var hooks = o.hooks; 840 | for(var p in hooks) { 841 | if(p.indexOf('ev-') === 0) { 842 | var handler = hooks[p]; 843 | if(handler.value && handler.value.hsAction) { 844 | r.push(handler.value.hsAction); 845 | } 846 | } 847 | } 848 | // recurse if any of the children may have thunks, components or handlers 849 | if(o.hasWidgets || o.hasThunks || o.descendantHooks) { 850 | for(var i=o.children.length-1;i>=0;i--) { 851 | scanTreeRec(o.children[i], r, currentMark); 852 | } 853 | } 854 | } 855 | } 856 | } 857 | 858 | function setThunkPatch(n, p) { 859 | if(hasPatches(p)) n.p[n.i] = new VPatch(VPatch.THUNK, null, p); 860 | } 861 | 862 | function hasPatches(patch) { 863 | for (var index in patch) { 864 | if (index !== "a") { 865 | return true; 866 | } 867 | } 868 | return false; 869 | } 870 | 871 | function initDelegator(evTypes) { 872 | var d = DomDelegator(); 873 | var l = evTypes.length; 874 | for(var i = 0; i < l; i++) { 875 | d.listenTo(evTypes[i]); 876 | } 877 | } 878 | 879 | function v(tag, props, children) { 880 | return h(tag, props, children); 881 | } 882 | 883 | function t(text) { 884 | return new VText(text); 885 | } 886 | 887 | function th(t, ids, key) { 888 | return new HSThunk(t, ids, key); 889 | } 890 | 891 | function c(r, m, u, key) { 892 | return new HSComponent(r, m, u, key); 893 | } 894 | 895 | function makeHandler(action, async) { 896 | var f = function(ev) { 897 | return h$vdomEventCallback(async, action, ev); 898 | } 899 | f.hsAction = action; 900 | return f; 901 | } 902 | 903 | var vdomMounts = new h$Set(); 904 | 905 | module.exports = { setThunkPatch: setThunkPatch 906 | , forceTree: forceTree 907 | , forcePatch: forcePatch 908 | , diff: diff 909 | , mount: mount 910 | , unmount: unmount 911 | , initDelegator: initDelegator 912 | , v: v 913 | , th: th 914 | , t: t 915 | , c: c 916 | , makeHandler: makeHandler 917 | }; 918 | 919 | // the global variable we're using in the bindings 920 | h$vdom = module.exports; 921 | 922 | h$registerExtensibleRetention(scanTree); 923 | h$registerExtensibleRetentionRoot(scanMounts); 924 | 925 | 926 | 927 | },{"./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){ 928 | var EvStore = require("ev-store") 929 | 930 | module.exports = addEvent 931 | 932 | function addEvent(target, type, handler) { 933 | var events = EvStore(target) 934 | var event = events[type] 935 | 936 | if (!event) { 937 | events[type] = handler 938 | } else if (Array.isArray(event)) { 939 | if (event.indexOf(handler) === -1) { 940 | event.push(handler) 941 | } 942 | } else if (event !== handler) { 943 | events[type] = [event, handler] 944 | } 945 | } 946 | 947 | },{"ev-store":8}],5:[function(require,module,exports){ 948 | var globalDocument = require("global/document") 949 | var EvStore = require("ev-store") 950 | var createStore = require("weakmap-shim/create-store") 951 | 952 | var addEvent = require("./add-event.js") 953 | var removeEvent = require("./remove-event.js") 954 | var ProxyEvent = require("./proxy-event.js") 955 | 956 | var HANDLER_STORE = createStore() 957 | 958 | module.exports = DOMDelegator 959 | 960 | function DOMDelegator(document) { 961 | if (!(this instanceof DOMDelegator)) { 962 | return new DOMDelegator(document); 963 | } 964 | 965 | document = document || globalDocument 966 | 967 | this.target = document.documentElement 968 | this.events = {} 969 | this.rawEventListeners = {} 970 | this.globalListeners = {} 971 | } 972 | 973 | DOMDelegator.prototype.addEventListener = addEvent 974 | DOMDelegator.prototype.removeEventListener = removeEvent 975 | 976 | DOMDelegator.allocateHandle = 977 | function allocateHandle(func) { 978 | var handle = new Handle() 979 | 980 | HANDLER_STORE(handle).func = func; 981 | 982 | return handle 983 | } 984 | 985 | DOMDelegator.transformHandle = 986 | function transformHandle(handle, broadcast) { 987 | var func = HANDLER_STORE(handle).func 988 | 989 | return this.allocateHandle(function (ev) { 990 | broadcast(ev, func); 991 | }) 992 | } 993 | 994 | DOMDelegator.prototype.addGlobalEventListener = 995 | function addGlobalEventListener(eventName, fn) { 996 | var listeners = this.globalListeners[eventName] || []; 997 | if (listeners.indexOf(fn) === -1) { 998 | listeners.push(fn) 999 | } 1000 | 1001 | this.globalListeners[eventName] = listeners; 1002 | } 1003 | 1004 | DOMDelegator.prototype.removeGlobalEventListener = 1005 | function removeGlobalEventListener(eventName, fn) { 1006 | var listeners = this.globalListeners[eventName] || []; 1007 | 1008 | var index = listeners.indexOf(fn) 1009 | if (index !== -1) { 1010 | listeners.splice(index, 1) 1011 | } 1012 | } 1013 | 1014 | DOMDelegator.prototype.listenTo = function listenTo(eventName) { 1015 | if (!(eventName in this.events)) { 1016 | this.events[eventName] = 0; 1017 | } 1018 | 1019 | this.events[eventName]++; 1020 | 1021 | if (this.events[eventName] !== 1) { 1022 | return 1023 | } 1024 | 1025 | var listener = this.rawEventListeners[eventName] 1026 | if (!listener) { 1027 | listener = this.rawEventListeners[eventName] = 1028 | createHandler(eventName, this) 1029 | } 1030 | 1031 | this.target.addEventListener(eventName, listener, true) 1032 | } 1033 | 1034 | DOMDelegator.prototype.unlistenTo = function unlistenTo(eventName) { 1035 | if (!(eventName in this.events)) { 1036 | this.events[eventName] = 0; 1037 | } 1038 | 1039 | if (this.events[eventName] === 0) { 1040 | throw new Error("already unlistened to event."); 1041 | } 1042 | 1043 | this.events[eventName]--; 1044 | 1045 | if (this.events[eventName] !== 0) { 1046 | return 1047 | } 1048 | 1049 | var listener = this.rawEventListeners[eventName] 1050 | 1051 | if (!listener) { 1052 | throw new Error("dom-delegator#unlistenTo: cannot " + 1053 | "unlisten to " + eventName) 1054 | } 1055 | 1056 | this.target.removeEventListener(eventName, listener, true) 1057 | } 1058 | 1059 | function createHandler(eventName, delegator) { 1060 | var globalListeners = delegator.globalListeners; 1061 | var delegatorTarget = delegator.target; 1062 | 1063 | return handler 1064 | 1065 | function handler(ev) { 1066 | var globalHandlers = globalListeners[eventName] || [] 1067 | 1068 | if (globalHandlers.length > 0) { 1069 | var globalEvent = new ProxyEvent(ev); 1070 | globalEvent.currentTarget = delegatorTarget; 1071 | callListeners(globalHandlers, globalEvent) 1072 | } 1073 | 1074 | findAndInvokeListeners(ev.target, ev, eventName) 1075 | } 1076 | } 1077 | 1078 | function findAndInvokeListeners(elem, ev, eventName) { 1079 | var listener = getListener(elem, eventName) 1080 | 1081 | if (listener && listener.handlers.length > 0) { 1082 | var listenerEvent = new ProxyEvent(ev); 1083 | listenerEvent.currentTarget = listener.currentTarget 1084 | callListeners(listener.handlers, listenerEvent) 1085 | 1086 | if (listenerEvent._bubbles) { 1087 | var nextTarget = listener.currentTarget.parentNode 1088 | findAndInvokeListeners(nextTarget, ev, eventName) 1089 | } 1090 | } 1091 | } 1092 | 1093 | function getListener(target, type) { 1094 | // terminate recursion if parent is `null` 1095 | if (target === null || typeof target === "undefined") { 1096 | return null 1097 | } 1098 | 1099 | var events = EvStore(target) 1100 | // fetch list of handler fns for this event 1101 | var handler = events[type] 1102 | var allHandler = events.event 1103 | 1104 | if (!handler && !allHandler) { 1105 | return getListener(target.parentNode, type) 1106 | } 1107 | 1108 | var handlers = [].concat(handler || [], allHandler || []) 1109 | return new Listener(target, handlers) 1110 | } 1111 | 1112 | function callListeners(handlers, ev) { 1113 | handlers.forEach(function (handler) { 1114 | if (typeof handler === "function") { 1115 | handler(ev) 1116 | } else if (typeof handler.handleEvent === "function") { 1117 | handler.handleEvent(ev) 1118 | } else if (handler.type === "dom-delegator-handle") { 1119 | HANDLER_STORE(handler).func(ev) 1120 | } else { 1121 | throw new Error("dom-delegator: unknown handler " + 1122 | "found: " + JSON.stringify(handlers)); 1123 | } 1124 | }) 1125 | } 1126 | 1127 | function Listener(target, handlers) { 1128 | this.currentTarget = target 1129 | this.handlers = handlers 1130 | } 1131 | 1132 | function Handle() { 1133 | this.type = "dom-delegator-handle" 1134 | } 1135 | 1136 | },{"./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){ 1137 | var Individual = require("individual") 1138 | var cuid = require("cuid") 1139 | var globalDocument = require("global/document") 1140 | 1141 | var DOMDelegator = require("./dom-delegator.js") 1142 | 1143 | var versionKey = "13" 1144 | var cacheKey = "__DOM_DELEGATOR_CACHE@" + versionKey 1145 | var cacheTokenKey = "__DOM_DELEGATOR_CACHE_TOKEN@" + versionKey 1146 | var delegatorCache = Individual(cacheKey, { 1147 | delegators: {} 1148 | }) 1149 | var commonEvents = [ 1150 | "blur", "change", "click", "contextmenu", "dblclick", 1151 | "error","focus", "focusin", "focusout", "input", "keydown", 1152 | "keypress", "keyup", "load", "mousedown", "mouseup", 1153 | "resize", "select", "submit", "touchcancel", 1154 | "touchend", "touchstart", "unload" 1155 | ] 1156 | 1157 | /* Delegator is a thin wrapper around a singleton `DOMDelegator` 1158 | instance. 1159 | 1160 | Only one DOMDelegator should exist because we do not want 1161 | duplicate event listeners bound to the DOM. 1162 | 1163 | `Delegator` will also `listenTo()` all events unless 1164 | every caller opts out of it 1165 | */ 1166 | module.exports = Delegator 1167 | 1168 | function Delegator(opts) { 1169 | opts = opts || {} 1170 | var document = opts.document || globalDocument 1171 | 1172 | var cacheKey = document[cacheTokenKey] 1173 | 1174 | if (!cacheKey) { 1175 | cacheKey = 1176 | document[cacheTokenKey] = cuid() 1177 | } 1178 | 1179 | var delegator = delegatorCache.delegators[cacheKey] 1180 | 1181 | if (!delegator) { 1182 | delegator = delegatorCache.delegators[cacheKey] = 1183 | new DOMDelegator(document) 1184 | } 1185 | 1186 | if (opts.defaultEvents !== false) { 1187 | for (var i = 0; i < commonEvents.length; i++) { 1188 | delegator.listenTo(commonEvents[i]) 1189 | } 1190 | } 1191 | 1192 | return delegator 1193 | } 1194 | 1195 | Delegator.allocateHandle = DOMDelegator.allocateHandle; 1196 | Delegator.transformHandle = DOMDelegator.transformHandle; 1197 | 1198 | },{"./dom-delegator.js":5,"cuid":7,"global/document":11,"individual":12}],7:[function(require,module,exports){ 1199 | /** 1200 | * cuid.js 1201 | * Collision-resistant UID generator for browsers and node. 1202 | * Sequential for fast db lookups and recency sorting. 1203 | * Safe for element IDs and server-side lookups. 1204 | * 1205 | * Extracted from CLCTR 1206 | * 1207 | * Copyright (c) Eric Elliott 2012 1208 | * MIT License 1209 | */ 1210 | 1211 | /*global window, navigator, document, require, process, module */ 1212 | (function (app) { 1213 | 'use strict'; 1214 | var namespace = 'cuid', 1215 | c = 0, 1216 | blockSize = 4, 1217 | base = 36, 1218 | discreteValues = Math.pow(base, blockSize), 1219 | 1220 | pad = function pad(num, size) { 1221 | var s = "000000000" + num; 1222 | return s.substr(s.length-size); 1223 | }, 1224 | 1225 | randomBlock = function randomBlock() { 1226 | return pad((Math.random() * 1227 | discreteValues << 0) 1228 | .toString(base), blockSize); 1229 | }, 1230 | 1231 | safeCounter = function () { 1232 | c = (c < discreteValues) ? c : 0; 1233 | c++; // this is not subliminal 1234 | return c - 1; 1235 | }, 1236 | 1237 | api = function cuid() { 1238 | // Starting with a lowercase letter makes 1239 | // it HTML element ID friendly. 1240 | var letter = 'c', // hard-coded allows for sequential access 1241 | 1242 | // timestamp 1243 | // warning: this exposes the exact date and time 1244 | // that the uid was created. 1245 | timestamp = (new Date().getTime()).toString(base), 1246 | 1247 | // Prevent same-machine collisions. 1248 | counter, 1249 | 1250 | // A few chars to generate distinct ids for different 1251 | // clients (so different computers are far less 1252 | // likely to generate the same id) 1253 | fingerprint = api.fingerprint(), 1254 | 1255 | // Grab some more chars from Math.random() 1256 | random = randomBlock() + randomBlock(); 1257 | 1258 | counter = pad(safeCounter().toString(base), blockSize); 1259 | 1260 | return (letter + timestamp + counter + fingerprint + random); 1261 | }; 1262 | 1263 | api.slug = function slug() { 1264 | var date = new Date().getTime().toString(36), 1265 | counter, 1266 | print = api.fingerprint().slice(0,1) + 1267 | api.fingerprint().slice(-1), 1268 | random = randomBlock().slice(-2); 1269 | 1270 | counter = safeCounter().toString(36).slice(-4); 1271 | 1272 | return date.slice(-2) + 1273 | counter + print + random; 1274 | }; 1275 | 1276 | api.globalCount = function globalCount() { 1277 | // We want to cache the results of this 1278 | var cache = (function calc() { 1279 | var i, 1280 | count = 0; 1281 | 1282 | for (i in window) { 1283 | count++; 1284 | } 1285 | 1286 | return count; 1287 | }()); 1288 | 1289 | api.globalCount = function () { return cache; }; 1290 | return cache; 1291 | }; 1292 | 1293 | api.fingerprint = function browserPrint() { 1294 | return pad((navigator.mimeTypes.length + 1295 | navigator.userAgent.length).toString(36) + 1296 | api.globalCount().toString(36), 4); 1297 | }; 1298 | 1299 | // don't change anything from here down. 1300 | if (app.register) { 1301 | app.register(namespace, api); 1302 | } else if (typeof module !== 'undefined') { 1303 | module.exports = api; 1304 | } else { 1305 | app[namespace] = api; 1306 | } 1307 | 1308 | }(this.applitude || this)); 1309 | 1310 | },{}],8:[function(require,module,exports){ 1311 | 'use strict'; 1312 | 1313 | var OneVersionConstraint = require('individual/one-version'); 1314 | 1315 | var MY_VERSION = '7'; 1316 | OneVersionConstraint('ev-store', MY_VERSION); 1317 | 1318 | var hashKey = '__EV_STORE_KEY@' + MY_VERSION; 1319 | 1320 | module.exports = EvStore; 1321 | 1322 | function EvStore(elem) { 1323 | var hash = elem[hashKey]; 1324 | 1325 | if (!hash) { 1326 | hash = elem[hashKey] = {}; 1327 | } 1328 | 1329 | return hash; 1330 | } 1331 | 1332 | },{"individual/one-version":10}],9:[function(require,module,exports){ 1333 | (function (global){ 1334 | 'use strict'; 1335 | 1336 | /*global window, global*/ 1337 | 1338 | var root = typeof window !== 'undefined' ? 1339 | window : typeof global !== 'undefined' ? 1340 | global : {}; 1341 | 1342 | module.exports = Individual; 1343 | 1344 | function Individual(key, value) { 1345 | if (key in root) { 1346 | return root[key]; 1347 | } 1348 | 1349 | root[key] = value; 1350 | 1351 | return value; 1352 | } 1353 | 1354 | }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 1355 | },{}],10:[function(require,module,exports){ 1356 | 'use strict'; 1357 | 1358 | var Individual = require('./index.js'); 1359 | 1360 | module.exports = OneVersion; 1361 | 1362 | function OneVersion(moduleName, version, defaultValue) { 1363 | var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName; 1364 | var enforceKey = key + '_ENFORCE_SINGLETON'; 1365 | 1366 | var versionValue = Individual(enforceKey, version); 1367 | 1368 | if (versionValue !== version) { 1369 | throw new Error('Can only have one copy of ' + 1370 | moduleName + '.\n' + 1371 | 'You already have version ' + versionValue + 1372 | ' installed.\n' + 1373 | 'This means you cannot install version ' + version); 1374 | } 1375 | 1376 | return Individual(key, defaultValue); 1377 | } 1378 | 1379 | },{"./index.js":9}],11:[function(require,module,exports){ 1380 | (function (global){ 1381 | var topLevel = typeof global !== 'undefined' ? global : 1382 | typeof window !== 'undefined' ? window : {} 1383 | var minDoc = require('min-document'); 1384 | 1385 | if (typeof document !== 'undefined') { 1386 | module.exports = document; 1387 | } else { 1388 | var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; 1389 | 1390 | if (!doccy) { 1391 | doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; 1392 | } 1393 | 1394 | module.exports = doccy; 1395 | } 1396 | 1397 | }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 1398 | },{"min-document":18}],12:[function(require,module,exports){ 1399 | (function (global){ 1400 | var root = typeof window !== 'undefined' ? 1401 | window : typeof global !== 'undefined' ? 1402 | global : {}; 1403 | 1404 | module.exports = Individual 1405 | 1406 | function Individual(key, value) { 1407 | if (root[key]) { 1408 | return root[key] 1409 | } 1410 | 1411 | Object.defineProperty(root, key, { 1412 | value: value 1413 | , configurable: true 1414 | }) 1415 | 1416 | return value 1417 | } 1418 | 1419 | }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 1420 | },{}],13:[function(require,module,exports){ 1421 | if (typeof Object.create === 'function') { 1422 | // implementation from standard node.js 'util' module 1423 | module.exports = function inherits(ctor, superCtor) { 1424 | ctor.super_ = superCtor 1425 | ctor.prototype = Object.create(superCtor.prototype, { 1426 | constructor: { 1427 | value: ctor, 1428 | enumerable: false, 1429 | writable: true, 1430 | configurable: true 1431 | } 1432 | }); 1433 | }; 1434 | } else { 1435 | // old school shim for old browsers 1436 | module.exports = function inherits(ctor, superCtor) { 1437 | ctor.super_ = superCtor 1438 | var TempCtor = function () {} 1439 | TempCtor.prototype = superCtor.prototype 1440 | ctor.prototype = new TempCtor() 1441 | ctor.prototype.constructor = ctor 1442 | } 1443 | } 1444 | 1445 | },{}],14:[function(require,module,exports){ 1446 | var hiddenStore = require('./hidden-store.js'); 1447 | 1448 | module.exports = createStore; 1449 | 1450 | function createStore() { 1451 | var key = {}; 1452 | 1453 | return function (obj) { 1454 | if ((typeof obj !== 'object' || obj === null) && 1455 | typeof obj !== 'function' 1456 | ) { 1457 | throw new Error('Weakmap-shim: Key must be object') 1458 | } 1459 | 1460 | var store = obj.valueOf(key); 1461 | return store && store.identity === key ? 1462 | store : hiddenStore(obj, key); 1463 | }; 1464 | } 1465 | 1466 | },{"./hidden-store.js":15}],15:[function(require,module,exports){ 1467 | module.exports = hiddenStore; 1468 | 1469 | function hiddenStore(obj, key) { 1470 | var store = { identity: key }; 1471 | var valueOf = obj.valueOf; 1472 | 1473 | Object.defineProperty(obj, "valueOf", { 1474 | value: function (value) { 1475 | return value !== key ? 1476 | valueOf.apply(this, arguments) : store; 1477 | }, 1478 | writable: true 1479 | }); 1480 | 1481 | return store; 1482 | } 1483 | 1484 | },{}],16:[function(require,module,exports){ 1485 | var inherits = require("inherits") 1486 | 1487 | var ALL_PROPS = [ 1488 | "altKey", "bubbles", "cancelable", "ctrlKey", 1489 | "eventPhase", "metaKey", "relatedTarget", "shiftKey", 1490 | "target", "timeStamp", "type", "view", "which" 1491 | ] 1492 | var KEY_PROPS = ["char", "charCode", "key", "keyCode"] 1493 | var MOUSE_PROPS = [ 1494 | "button", "buttons", "clientX", "clientY", "layerX", 1495 | "layerY", "offsetX", "offsetY", "pageX", "pageY", 1496 | "screenX", "screenY", "toElement" 1497 | ] 1498 | 1499 | var rkeyEvent = /^key|input/ 1500 | var rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/ 1501 | 1502 | module.exports = ProxyEvent 1503 | 1504 | function ProxyEvent(ev) { 1505 | if (!(this instanceof ProxyEvent)) { 1506 | return new ProxyEvent(ev) 1507 | } 1508 | 1509 | if (rkeyEvent.test(ev.type)) { 1510 | return new KeyEvent(ev) 1511 | } else if (rmouseEvent.test(ev.type)) { 1512 | return new MouseEvent(ev) 1513 | } 1514 | 1515 | for (var i = 0; i < ALL_PROPS.length; i++) { 1516 | var propKey = ALL_PROPS[i] 1517 | this[propKey] = ev[propKey] 1518 | } 1519 | 1520 | this._rawEvent = ev 1521 | this._bubbles = false; 1522 | } 1523 | 1524 | ProxyEvent.prototype.preventDefault = function () { 1525 | this._rawEvent.preventDefault() 1526 | } 1527 | 1528 | ProxyEvent.prototype.startPropagation = function () { 1529 | this._bubbles = true; 1530 | } 1531 | 1532 | function MouseEvent(ev) { 1533 | for (var i = 0; i < ALL_PROPS.length; i++) { 1534 | var propKey = ALL_PROPS[i] 1535 | this[propKey] = ev[propKey] 1536 | } 1537 | 1538 | for (var j = 0; j < MOUSE_PROPS.length; j++) { 1539 | var mousePropKey = MOUSE_PROPS[j] 1540 | this[mousePropKey] = ev[mousePropKey] 1541 | } 1542 | 1543 | this._rawEvent = ev 1544 | } 1545 | 1546 | inherits(MouseEvent, ProxyEvent) 1547 | 1548 | function KeyEvent(ev) { 1549 | for (var i = 0; i < ALL_PROPS.length; i++) { 1550 | var propKey = ALL_PROPS[i] 1551 | this[propKey] = ev[propKey] 1552 | } 1553 | 1554 | for (var j = 0; j < KEY_PROPS.length; j++) { 1555 | var keyPropKey = KEY_PROPS[j] 1556 | this[keyPropKey] = ev[keyPropKey] 1557 | } 1558 | 1559 | this._rawEvent = ev 1560 | } 1561 | 1562 | inherits(KeyEvent, ProxyEvent) 1563 | 1564 | },{"inherits":13}],17:[function(require,module,exports){ 1565 | var EvStore = require("ev-store") 1566 | 1567 | module.exports = removeEvent 1568 | 1569 | function removeEvent(target, type, handler) { 1570 | var events = EvStore(target) 1571 | var event = events[type] 1572 | 1573 | if (!event) { 1574 | return 1575 | } else if (Array.isArray(event)) { 1576 | var index = event.indexOf(handler) 1577 | if (index !== -1) { 1578 | event.splice(index, 1) 1579 | } 1580 | } else if (event === handler) { 1581 | events[type] = null 1582 | } 1583 | } 1584 | 1585 | },{"ev-store":8}],18:[function(require,module,exports){ 1586 | 1587 | },{}],19:[function(require,module,exports){ 1588 | var h = require("./virtual-hyperscript/index.js") 1589 | 1590 | module.exports = h 1591 | 1592 | },{"./virtual-hyperscript/index.js":34}],20:[function(require,module,exports){ 1593 | /*! 1594 | * Cross-Browser Split 1.1.1 1595 | * Copyright 2007-2012 Steven Levithan 1596 | * Available under the MIT License 1597 | * ECMAScript compliant, uniform cross-browser split method 1598 | */ 1599 | 1600 | /** 1601 | * Splits a string into an array of strings using a regex or string separator. Matches of the 1602 | * separator are not included in the result array. However, if `separator` is a regex that contains 1603 | * capturing groups, backreferences are spliced into the result each time `separator` is matched. 1604 | * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably 1605 | * cross-browser. 1606 | * @param {String} str String to split. 1607 | * @param {RegExp|String} separator Regex or string to use for separating the string. 1608 | * @param {Number} [limit] Maximum number of items to include in the result array. 1609 | * @returns {Array} Array of substrings. 1610 | * @example 1611 | * 1612 | * // Basic use 1613 | * split('a b c d', ' '); 1614 | * // -> ['a', 'b', 'c', 'd'] 1615 | * 1616 | * // With limit 1617 | * split('a b c d', ' ', 2); 1618 | * // -> ['a', 'b'] 1619 | * 1620 | * // Backreferences in result array 1621 | * split('..word1 word2..', /([a-z]+)(\d+)/i); 1622 | * // -> ['..', 'word', '1', ' ', 'word', '2', '..'] 1623 | */ 1624 | module.exports = (function split(undef) { 1625 | 1626 | var nativeSplit = String.prototype.split, 1627 | compliantExecNpcg = /()??/.exec("")[1] === undef, 1628 | // NPCG: nonparticipating capturing group 1629 | self; 1630 | 1631 | self = function(str, separator, limit) { 1632 | // If `separator` is not a regex, use `nativeSplit` 1633 | if (Object.prototype.toString.call(separator) !== "[object RegExp]") { 1634 | return nativeSplit.call(str, separator, limit); 1635 | } 1636 | var output = [], 1637 | flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6 1638 | (separator.sticky ? "y" : ""), 1639 | // Firefox 3+ 1640 | lastLastIndex = 0, 1641 | // Make `global` and avoid `lastIndex` issues by working with a copy 1642 | separator = new RegExp(separator.source, flags + "g"), 1643 | separator2, match, lastIndex, lastLength; 1644 | str += ""; // Type-convert 1645 | if (!compliantExecNpcg) { 1646 | // Doesn't need flags gy, but they don't hurt 1647 | separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags); 1648 | } 1649 | /* Values for `limit`, per the spec: 1650 | * If undefined: 4294967295 // Math.pow(2, 32) - 1 1651 | * If 0, Infinity, or NaN: 0 1652 | * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296; 1653 | * If negative number: 4294967296 - Math.floor(Math.abs(limit)) 1654 | * If other: Type-convert, then use the above rules 1655 | */ 1656 | limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1 1657 | limit >>> 0; // ToUint32(limit) 1658 | while (match = separator.exec(str)) { 1659 | // `separator.lastIndex` is not reliable cross-browser 1660 | lastIndex = match.index + match[0].length; 1661 | if (lastIndex > lastLastIndex) { 1662 | output.push(str.slice(lastLastIndex, match.index)); 1663 | // Fix browsers whose `exec` methods don't consistently return `undefined` for 1664 | // nonparticipating capturing groups 1665 | if (!compliantExecNpcg && match.length > 1) { 1666 | match[0].replace(separator2, function() { 1667 | for (var i = 1; i < arguments.length - 2; i++) { 1668 | if (arguments[i] === undef) { 1669 | match[i] = undef; 1670 | } 1671 | } 1672 | }); 1673 | } 1674 | if (match.length > 1 && match.index < str.length) { 1675 | Array.prototype.push.apply(output, match.slice(1)); 1676 | } 1677 | lastLength = match[0].length; 1678 | lastLastIndex = lastIndex; 1679 | if (output.length >= limit) { 1680 | break; 1681 | } 1682 | } 1683 | if (separator.lastIndex === match.index) { 1684 | separator.lastIndex++; // Avoid an infinite loop 1685 | } 1686 | } 1687 | if (lastLastIndex === str.length) { 1688 | if (lastLength || !separator.test("")) { 1689 | output.push(""); 1690 | } 1691 | } else { 1692 | output.push(str.slice(lastLastIndex)); 1693 | } 1694 | return output.length > limit ? output.slice(0, limit) : output; 1695 | }; 1696 | 1697 | return self; 1698 | })(); 1699 | 1700 | },{}],21:[function(require,module,exports){ 1701 | module.exports=require(8) 1702 | },{"individual/one-version":23}],22:[function(require,module,exports){ 1703 | module.exports=require(9) 1704 | },{}],23:[function(require,module,exports){ 1705 | module.exports=require(10) 1706 | },{"./index.js":22}],24:[function(require,module,exports){ 1707 | module.exports=require(11) 1708 | },{"min-document":18}],25:[function(require,module,exports){ 1709 | "use strict"; 1710 | 1711 | module.exports = function isObject(x) { 1712 | return typeof x === "object" && x !== null; 1713 | }; 1714 | 1715 | },{}],26:[function(require,module,exports){ 1716 | var isObject = require("is-object") 1717 | var isHook = require("../vnode/is-vhook.js") 1718 | 1719 | module.exports = applyProperties 1720 | 1721 | function applyProperties(node, props, previous) { 1722 | for (var propName in props) { 1723 | var propValue = props[propName] 1724 | 1725 | if (propValue === undefined) { 1726 | removeProperty(node, propName, propValue, previous); 1727 | } else if (isHook(propValue)) { 1728 | removeProperty(node, propName, propValue, previous) 1729 | if (propValue.hook) { 1730 | propValue.hook(node, 1731 | propName, 1732 | previous ? previous[propName] : undefined) 1733 | } 1734 | } else { 1735 | if (isObject(propValue)) { 1736 | patchObject(node, props, previous, propName, propValue); 1737 | } else { 1738 | node[propName] = propValue 1739 | } 1740 | } 1741 | } 1742 | } 1743 | 1744 | function removeProperty(node, propName, propValue, previous) { 1745 | if (previous) { 1746 | var previousValue = previous[propName] 1747 | 1748 | if (!isHook(previousValue)) { 1749 | if (propName === "attributes") { 1750 | for (var attrName in previousValue) { 1751 | node.removeAttribute(attrName) 1752 | } 1753 | } else if (propName === "style") { 1754 | for (var i in previousValue) { 1755 | node.style[i] = "" 1756 | } 1757 | } else if (typeof previousValue === "string") { 1758 | node[propName] = "" 1759 | } else { 1760 | node[propName] = null 1761 | } 1762 | } else if (previousValue.unhook) { 1763 | previousValue.unhook(node, propName, propValue) 1764 | } 1765 | } 1766 | } 1767 | 1768 | function patchObject(node, props, previous, propName, propValue) { 1769 | var previousValue = previous ? previous[propName] : undefined 1770 | 1771 | // Set attributes 1772 | if (propName === "attributes") { 1773 | for (var attrName in propValue) { 1774 | var attrValue = propValue[attrName] 1775 | 1776 | if (attrValue === undefined) { 1777 | node.removeAttribute(attrName) 1778 | } else { 1779 | node.setAttribute(attrName, attrValue) 1780 | } 1781 | } 1782 | 1783 | return 1784 | } 1785 | 1786 | if(previousValue && isObject(previousValue) && 1787 | getPrototype(previousValue) !== getPrototype(propValue)) { 1788 | node[propName] = propValue 1789 | return 1790 | } 1791 | 1792 | if (!isObject(node[propName])) { 1793 | node[propName] = {} 1794 | } 1795 | 1796 | var replacer = propName === "style" ? "" : undefined 1797 | 1798 | for (var k in propValue) { 1799 | var value = propValue[k] 1800 | node[propName][k] = (value === undefined) ? replacer : value 1801 | } 1802 | } 1803 | 1804 | function getPrototype(value) { 1805 | if (Object.getPrototypeOf) { 1806 | return Object.getPrototypeOf(value) 1807 | } else if (value.__proto__) { 1808 | return value.__proto__ 1809 | } else if (value.constructor) { 1810 | return value.constructor.prototype 1811 | } 1812 | } 1813 | 1814 | },{"../vnode/is-vhook.js":38,"is-object":25}],27:[function(require,module,exports){ 1815 | var document = require("global/document") 1816 | 1817 | var applyProperties = require("./apply-properties") 1818 | 1819 | var isVNode = require("../vnode/is-vnode.js") 1820 | var isVText = require("../vnode/is-vtext.js") 1821 | var isWidget = require("../vnode/is-widget.js") 1822 | var handleThunk = require("../vnode/handle-thunk.js") 1823 | 1824 | module.exports = createElement 1825 | 1826 | function createElement(vnode, opts) { 1827 | var doc = opts ? opts.document || document : document 1828 | var warn = opts ? opts.warn : null 1829 | 1830 | vnode = handleThunk(vnode).a 1831 | 1832 | if (isWidget(vnode)) { 1833 | return vnode.init() 1834 | } else if (isVText(vnode)) { 1835 | return doc.createTextNode(vnode.text) 1836 | } else if (!isVNode(vnode)) { 1837 | if (warn) { 1838 | warn("Item is not a valid virtual dom node", vnode) 1839 | } 1840 | return null 1841 | } 1842 | 1843 | var node = (vnode.namespace === null) ? 1844 | doc.createElement(vnode.tagName) : 1845 | doc.createElementNS(vnode.namespace, vnode.tagName) 1846 | 1847 | var props = vnode.properties 1848 | applyProperties(node, props) 1849 | 1850 | var children = vnode.children 1851 | 1852 | for (var i = 0; i < children.length; i++) { 1853 | var childNode = createElement(children[i], opts) 1854 | if (childNode) { 1855 | node.appendChild(childNode) 1856 | } 1857 | } 1858 | 1859 | return node 1860 | } 1861 | 1862 | },{"../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){ 1863 | // Maps a virtual DOM tree onto a real DOM tree in an efficient manner. 1864 | // We don't want to read all of the DOM nodes in the tree so we use 1865 | // the in-order tree indexing to eliminate recursion down certain branches. 1866 | // We only recurse into a DOM node if we know that it contains a child of 1867 | // interest. 1868 | 1869 | var noChild = {} 1870 | 1871 | module.exports = domIndex 1872 | 1873 | function domIndex(rootNode, tree, indices, nodes) { 1874 | if (!indices || indices.length === 0) { 1875 | return {} 1876 | } else { 1877 | indices.sort(ascending) 1878 | return recurse(rootNode, tree, indices, nodes, 0) 1879 | } 1880 | } 1881 | 1882 | function recurse(rootNode, tree, indices, nodes, rootIndex) { 1883 | nodes = nodes || {} 1884 | 1885 | 1886 | if (rootNode) { 1887 | if (indexInRange(indices, rootIndex, rootIndex)) { 1888 | nodes[rootIndex] = rootNode 1889 | } 1890 | 1891 | var vChildren = tree.children 1892 | 1893 | if (vChildren) { 1894 | 1895 | var childNodes = rootNode.childNodes 1896 | 1897 | for (var i = 0; i < tree.children.length; i++) { 1898 | rootIndex += 1 1899 | 1900 | var vChild = vChildren[i] || noChild 1901 | var nextIndex = rootIndex + (vChild.count || 0) 1902 | 1903 | // skip recursion down the tree if there are no nodes down here 1904 | if (indexInRange(indices, rootIndex, nextIndex)) { 1905 | recurse(childNodes[i], vChild, indices, nodes, rootIndex) 1906 | } 1907 | 1908 | rootIndex = nextIndex 1909 | } 1910 | } 1911 | } 1912 | 1913 | return nodes 1914 | } 1915 | 1916 | // Binary search for an index in the interval [left, right] 1917 | function indexInRange(indices, left, right) { 1918 | if (indices.length === 0) { 1919 | return false 1920 | } 1921 | 1922 | var minIndex = 0 1923 | var maxIndex = indices.length - 1 1924 | var currentIndex 1925 | var currentItem 1926 | 1927 | while (minIndex <= maxIndex) { 1928 | currentIndex = ((maxIndex + minIndex) / 2) >> 0 1929 | currentItem = indices[currentIndex] 1930 | 1931 | if (minIndex === maxIndex) { 1932 | return currentItem >= left && currentItem <= right 1933 | } else if (currentItem < left) { 1934 | minIndex = currentIndex + 1 1935 | } else if (currentItem > right) { 1936 | maxIndex = currentIndex - 1 1937 | } else { 1938 | return true 1939 | } 1940 | } 1941 | 1942 | return false; 1943 | } 1944 | 1945 | function ascending(a, b) { 1946 | return a > b ? 1 : -1 1947 | } 1948 | 1949 | },{}],29:[function(require,module,exports){ 1950 | var applyProperties = require("./apply-properties") 1951 | 1952 | var isWidget = require("../vnode/is-widget.js") 1953 | var VPatch = require("../vnode/vpatch.js") 1954 | 1955 | var updateWidget = require("./update-widget") 1956 | 1957 | module.exports = applyPatch 1958 | 1959 | function applyPatch(vpatch, domNode, renderOptions) { 1960 | var type = vpatch.type 1961 | var vNode = vpatch.vNode 1962 | var patch = vpatch.patch 1963 | 1964 | switch (type) { 1965 | case VPatch.REMOVE: 1966 | return removeNode(domNode, vNode) 1967 | case VPatch.INSERT: 1968 | return insertNode(domNode, patch, renderOptions) 1969 | case VPatch.VTEXT: 1970 | return stringPatch(domNode, vNode, patch, renderOptions) 1971 | case VPatch.WIDGET: 1972 | return widgetPatch(domNode, vNode, patch, renderOptions) 1973 | case VPatch.VNODE: 1974 | return vNodePatch(domNode, vNode, patch, renderOptions) 1975 | case VPatch.ORDER: 1976 | reorderChildren(domNode, patch) 1977 | return domNode 1978 | case VPatch.PROPS: 1979 | applyProperties(domNode, patch, vNode.properties) 1980 | return domNode 1981 | case VPatch.THUNK: 1982 | return replaceRoot(domNode, 1983 | renderOptions.patch(domNode, patch, renderOptions)) 1984 | default: 1985 | return domNode 1986 | } 1987 | } 1988 | 1989 | function removeNode(domNode, vNode) { 1990 | var parentNode = domNode.parentNode 1991 | 1992 | if (parentNode) { 1993 | parentNode.removeChild(domNode) 1994 | } 1995 | 1996 | destroyWidget(domNode, vNode); 1997 | 1998 | return null 1999 | } 2000 | 2001 | function insertNode(parentNode, vNode, renderOptions) { 2002 | var newNode = renderOptions.render(vNode, renderOptions) 2003 | 2004 | if (parentNode) { 2005 | parentNode.appendChild(newNode) 2006 | } 2007 | 2008 | return parentNode 2009 | } 2010 | 2011 | function stringPatch(domNode, leftVNode, vText, renderOptions) { 2012 | var newNode 2013 | 2014 | if (domNode.nodeType === 3) { 2015 | domNode.replaceData(0, domNode.length, vText.text) 2016 | newNode = domNode 2017 | } else { 2018 | var parentNode = domNode.parentNode 2019 | newNode = renderOptions.render(vText, renderOptions) 2020 | 2021 | if (parentNode && newNode !== domNode) { 2022 | parentNode.replaceChild(newNode, domNode) 2023 | } 2024 | } 2025 | 2026 | return newNode 2027 | } 2028 | 2029 | function widgetPatch(domNode, leftVNode, widget, renderOptions) { 2030 | var updating = updateWidget(leftVNode, widget) 2031 | var newNode 2032 | 2033 | if (updating) { 2034 | newNode = widget.update(leftVNode, domNode) || domNode 2035 | } else { 2036 | newNode = renderOptions.render(widget, renderOptions) 2037 | } 2038 | 2039 | var parentNode = domNode.parentNode 2040 | 2041 | if (parentNode && newNode !== domNode) { 2042 | parentNode.replaceChild(newNode, domNode) 2043 | } 2044 | 2045 | if (!updating) { 2046 | destroyWidget(domNode, leftVNode) 2047 | } 2048 | 2049 | return newNode 2050 | } 2051 | 2052 | function vNodePatch(domNode, leftVNode, vNode, renderOptions) { 2053 | var parentNode = domNode.parentNode 2054 | var newNode = renderOptions.render(vNode, renderOptions) 2055 | 2056 | if (parentNode && newNode !== domNode) { 2057 | parentNode.replaceChild(newNode, domNode) 2058 | } 2059 | 2060 | return newNode 2061 | } 2062 | 2063 | function destroyWidget(domNode, w) { 2064 | if (typeof w.destroy === "function" && isWidget(w)) { 2065 | w.destroy(domNode) 2066 | } 2067 | } 2068 | 2069 | function reorderChildren(domNode, moves) { 2070 | var childNodes = domNode.childNodes 2071 | var keyMap = {} 2072 | var node 2073 | var remove 2074 | var insert 2075 | 2076 | for (var i = 0; i < moves.removes.length; i++) { 2077 | remove = moves.removes[i] 2078 | node = childNodes[remove.from] 2079 | if (remove.key) { 2080 | keyMap[remove.key] = node 2081 | } 2082 | domNode.removeChild(node) 2083 | } 2084 | 2085 | var length = childNodes.length 2086 | for (var j = 0; j < moves.inserts.length; j++) { 2087 | insert = moves.inserts[j] 2088 | node = keyMap[insert.key] 2089 | // this is the weirdest bug i've ever seen in webkit 2090 | domNode.insertBefore(node, insert.to >= length++ ? null : childNodes[insert.to]) 2091 | } 2092 | } 2093 | 2094 | function replaceRoot(oldRoot, newRoot) { 2095 | if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) { 2096 | oldRoot.parentNode.replaceChild(newRoot, oldRoot) 2097 | } 2098 | 2099 | return newRoot; 2100 | } 2101 | 2102 | },{"../vnode/is-widget.js":41,"../vnode/vpatch.js":44,"./apply-properties":26,"./update-widget":31}],30:[function(require,module,exports){ 2103 | var document = require("global/document") 2104 | var isArray = require("x-is-array") 2105 | 2106 | var render = require("./create-element") 2107 | var domIndex = require("./dom-index") 2108 | var patchOp = require("./patch-op") 2109 | module.exports = patch 2110 | 2111 | function patch(rootNode, patches, renderOptions) { 2112 | renderOptions = renderOptions || {} 2113 | renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch 2114 | ? renderOptions.patch 2115 | : patchRecursive 2116 | renderOptions.render = renderOptions.render || render 2117 | 2118 | return renderOptions.patch(rootNode, patches, renderOptions) 2119 | } 2120 | 2121 | function patchRecursive(rootNode, patches, renderOptions) { 2122 | var indices = patchIndices(patches) 2123 | 2124 | if (indices.length === 0) { 2125 | return rootNode 2126 | } 2127 | 2128 | var index = domIndex(rootNode, patches.a, indices) 2129 | var ownerDocument = rootNode.ownerDocument 2130 | 2131 | if (!renderOptions.document && ownerDocument !== document) { 2132 | renderOptions.document = ownerDocument 2133 | } 2134 | 2135 | for (var i = 0; i < indices.length; i++) { 2136 | var nodeIndex = indices[i] 2137 | rootNode = applyPatch(rootNode, 2138 | index[nodeIndex], 2139 | patches[nodeIndex], 2140 | renderOptions) 2141 | } 2142 | 2143 | return rootNode 2144 | } 2145 | 2146 | function applyPatch(rootNode, domNode, patchList, renderOptions) { 2147 | if (!domNode) { 2148 | return rootNode 2149 | } 2150 | 2151 | var newNode 2152 | 2153 | if (isArray(patchList)) { 2154 | for (var i = 0; i < patchList.length; i++) { 2155 | newNode = patchOp(patchList[i], domNode, renderOptions) 2156 | 2157 | if (domNode === rootNode) { 2158 | rootNode = newNode 2159 | } 2160 | } 2161 | } else { 2162 | newNode = patchOp(patchList, domNode, renderOptions) 2163 | 2164 | if (domNode === rootNode) { 2165 | rootNode = newNode 2166 | } 2167 | } 2168 | 2169 | return rootNode 2170 | } 2171 | 2172 | function patchIndices(patches) { 2173 | var indices = [] 2174 | 2175 | for (var key in patches) { 2176 | if (key !== "a") { 2177 | indices.push(Number(key)) 2178 | } 2179 | } 2180 | 2181 | return indices 2182 | } 2183 | 2184 | },{"./create-element":27,"./dom-index":28,"./patch-op":29,"global/document":24,"x-is-array":47}],31:[function(require,module,exports){ 2185 | var isWidget = require("../vnode/is-widget.js") 2186 | 2187 | module.exports = updateWidget 2188 | 2189 | function updateWidget(a, b) { 2190 | if (isWidget(a) && isWidget(b)) { 2191 | if ("name" in a && "name" in b) { 2192 | return a.id === b.id 2193 | } else { 2194 | return a.init === b.init 2195 | } 2196 | } 2197 | 2198 | return false 2199 | } 2200 | 2201 | },{"../vnode/is-widget.js":41}],32:[function(require,module,exports){ 2202 | 'use strict'; 2203 | 2204 | var EvStore = require('ev-store'); 2205 | 2206 | module.exports = EvHook; 2207 | 2208 | function EvHook(value) { 2209 | if (!(this instanceof EvHook)) { 2210 | return new EvHook(value); 2211 | } 2212 | 2213 | this.value = value; 2214 | } 2215 | 2216 | EvHook.prototype.hook = function (node, propertyName) { 2217 | var es = EvStore(node); 2218 | var propName = propertyName.substr(3); 2219 | 2220 | es[propName] = this.value; 2221 | }; 2222 | 2223 | EvHook.prototype.unhook = function(node, propertyName) { 2224 | var es = EvStore(node); 2225 | var propName = propertyName.substr(3); 2226 | 2227 | es[propName] = undefined; 2228 | }; 2229 | 2230 | },{"ev-store":21}],33:[function(require,module,exports){ 2231 | 'use strict'; 2232 | 2233 | module.exports = SoftSetHook; 2234 | 2235 | function SoftSetHook(value) { 2236 | if (!(this instanceof SoftSetHook)) { 2237 | return new SoftSetHook(value); 2238 | } 2239 | 2240 | this.value = value; 2241 | } 2242 | 2243 | SoftSetHook.prototype.hook = function (node, propertyName) { 2244 | if (node[propertyName] !== this.value) { 2245 | node[propertyName] = this.value; 2246 | } 2247 | }; 2248 | 2249 | },{}],34:[function(require,module,exports){ 2250 | 'use strict'; 2251 | 2252 | var isArray = require('x-is-array'); 2253 | 2254 | var VNode = require('../vnode/vnode.js'); 2255 | var VText = require('../vnode/vtext.js'); 2256 | var isVNode = require('../vnode/is-vnode'); 2257 | var isVText = require('../vnode/is-vtext'); 2258 | var isWidget = require('../vnode/is-widget'); 2259 | var isHook = require('../vnode/is-vhook'); 2260 | var isVThunk = require('../vnode/is-thunk'); 2261 | 2262 | var parseTag = require('./parse-tag.js'); 2263 | var softSetHook = require('./hooks/soft-set-hook.js'); 2264 | var evHook = require('./hooks/ev-hook.js'); 2265 | 2266 | module.exports = h; 2267 | 2268 | function h(tagName, properties, children) { 2269 | var childNodes = []; 2270 | var tag, props, key, namespace; 2271 | 2272 | if (!children && isChildren(properties)) { 2273 | children = properties; 2274 | props = {}; 2275 | } 2276 | 2277 | props = props || properties || {}; 2278 | tag = parseTag(tagName, props); 2279 | 2280 | // support keys 2281 | if (props.hasOwnProperty('key')) { 2282 | key = props.key; 2283 | props.key = undefined; 2284 | } 2285 | 2286 | // support namespace 2287 | if (props.hasOwnProperty('namespace')) { 2288 | namespace = props.namespace; 2289 | props.namespace = undefined; 2290 | } 2291 | 2292 | // fix cursor bug 2293 | if (tag === 'INPUT' && 2294 | !namespace && 2295 | props.hasOwnProperty('value') && 2296 | props.value !== undefined && 2297 | !isHook(props.value) 2298 | ) { 2299 | props.value = softSetHook(props.value); 2300 | } 2301 | 2302 | transformProperties(props); 2303 | 2304 | if (children !== undefined && children !== null) { 2305 | addChild(children, childNodes, tag, props); 2306 | } 2307 | 2308 | 2309 | return new VNode(tag, props, childNodes, key, namespace); 2310 | } 2311 | 2312 | function addChild(c, childNodes, tag, props) { 2313 | if (typeof c === 'string') { 2314 | childNodes.push(new VText(c)); 2315 | } else if (typeof c === 'number') { 2316 | childNodes.push(new VText(String(c))); 2317 | } else if (isChild(c)) { 2318 | childNodes.push(c); 2319 | } else if (isArray(c)) { 2320 | for (var i = 0; i < c.length; i++) { 2321 | addChild(c[i], childNodes, tag, props); 2322 | } 2323 | } else if (c === null || c === undefined) { 2324 | return; 2325 | } else { 2326 | throw UnexpectedVirtualElement({ 2327 | foreignObject: c, 2328 | parentVnode: { 2329 | tagName: tag, 2330 | properties: props 2331 | } 2332 | }); 2333 | } 2334 | } 2335 | 2336 | function transformProperties(props) { 2337 | for (var propName in props) { 2338 | if (props.hasOwnProperty(propName)) { 2339 | var value = props[propName]; 2340 | 2341 | if (isHook(value)) { 2342 | continue; 2343 | } 2344 | 2345 | if (propName.substr(0, 3) === 'ev-') { 2346 | // add ev-foo support 2347 | props[propName] = evHook(value); 2348 | } 2349 | } 2350 | } 2351 | } 2352 | 2353 | function isChild(x) { 2354 | return isVNode(x) || isVText(x) || isWidget(x) || isVThunk(x); 2355 | } 2356 | 2357 | function isChildren(x) { 2358 | return typeof x === 'string' || isArray(x) || isChild(x); 2359 | } 2360 | 2361 | function UnexpectedVirtualElement(data) { 2362 | var err = new Error(); 2363 | 2364 | err.type = 'virtual-hyperscript.unexpected.virtual-element'; 2365 | err.message = 'Unexpected virtual child passed to h().\n' + 2366 | 'Expected a VNode / Vthunk / VWidget / string but:\n' + 2367 | 'got:\n' + 2368 | errorString(data.foreignObject) + 2369 | '.\n' + 2370 | 'The parent vnode is:\n' + 2371 | errorString(data.parentVnode) 2372 | '\n' + 2373 | 'Suggested fix: change your `h(..., [ ... ])` callsite.'; 2374 | err.foreignObject = data.foreignObject; 2375 | err.parentVnode = data.parentVnode; 2376 | 2377 | return err; 2378 | } 2379 | 2380 | function errorString(obj) { 2381 | try { 2382 | return JSON.stringify(obj, null, ' '); 2383 | } catch (e) { 2384 | return String(obj); 2385 | } 2386 | } 2387 | 2388 | },{"../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){ 2389 | 'use strict'; 2390 | 2391 | var split = require('browser-split'); 2392 | 2393 | var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/; 2394 | var notClassId = /^\.|#/; 2395 | 2396 | module.exports = parseTag; 2397 | 2398 | function parseTag(tag, props) { 2399 | if (!tag) { 2400 | return 'DIV'; 2401 | } 2402 | 2403 | var noId = !(props.hasOwnProperty('id')); 2404 | 2405 | var tagParts = split(tag, classIdSplit); 2406 | var tagName = null; 2407 | 2408 | if (notClassId.test(tagParts[1])) { 2409 | tagName = 'DIV'; 2410 | } 2411 | 2412 | var classes, part, type, i; 2413 | 2414 | for (i = 0; i < tagParts.length; i++) { 2415 | part = tagParts[i]; 2416 | 2417 | if (!part) { 2418 | continue; 2419 | } 2420 | 2421 | type = part.charAt(0); 2422 | 2423 | if (!tagName) { 2424 | tagName = part; 2425 | } else if (type === '.') { 2426 | classes = classes || []; 2427 | classes.push(part.substring(1, part.length)); 2428 | } else if (type === '#' && noId) { 2429 | props.id = part.substring(1, part.length); 2430 | } 2431 | } 2432 | 2433 | if (classes) { 2434 | if (props.className) { 2435 | classes.push(props.className); 2436 | } 2437 | 2438 | props.className = classes.join(' '); 2439 | } 2440 | 2441 | return props.namespace ? tagName : tagName.toUpperCase(); 2442 | } 2443 | 2444 | },{"browser-split":20}],36:[function(require,module,exports){ 2445 | var isVNode = require("./is-vnode") 2446 | var isVText = require("./is-vtext") 2447 | var isWidget = require("./is-widget") 2448 | var isThunk = require("./is-thunk") 2449 | 2450 | module.exports = handleThunk 2451 | 2452 | function handleThunk(a, b) { 2453 | var renderedA = a 2454 | var renderedB = b 2455 | 2456 | if (isThunk(b)) { 2457 | renderedB = renderThunk(b, a) 2458 | } 2459 | 2460 | if (isThunk(a)) { 2461 | renderedA = renderThunk(a, null) 2462 | } 2463 | 2464 | return { 2465 | a: renderedA, 2466 | b: renderedB 2467 | } 2468 | } 2469 | 2470 | function renderThunk(thunk, previous) { 2471 | var renderedThunk = thunk.vnode 2472 | 2473 | if (!renderedThunk) { 2474 | renderedThunk = thunk.vnode = thunk.render(previous) 2475 | } 2476 | 2477 | if (!(isVNode(renderedThunk) || 2478 | isVText(renderedThunk) || 2479 | isWidget(renderedThunk))) { 2480 | throw new Error("thunk did not return a valid node"); 2481 | } 2482 | 2483 | return renderedThunk 2484 | } 2485 | 2486 | },{"./is-thunk":37,"./is-vnode":39,"./is-vtext":40,"./is-widget":41}],37:[function(require,module,exports){ 2487 | module.exports = isThunk 2488 | 2489 | function isThunk(t) { 2490 | return t && t.type === "Thunk" 2491 | } 2492 | 2493 | },{}],38:[function(require,module,exports){ 2494 | module.exports = isHook 2495 | 2496 | function isHook(hook) { 2497 | return hook && 2498 | (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") || 2499 | typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook")) 2500 | } 2501 | 2502 | },{}],39:[function(require,module,exports){ 2503 | var version = require("./version") 2504 | 2505 | module.exports = isVirtualNode 2506 | 2507 | function isVirtualNode(x) { 2508 | return x && x.type === "VirtualNode" && x.version === version 2509 | } 2510 | 2511 | },{"./version":42}],40:[function(require,module,exports){ 2512 | var version = require("./version") 2513 | 2514 | module.exports = isVirtualText 2515 | 2516 | function isVirtualText(x) { 2517 | return x && x.type === "VirtualText" && x.version === version 2518 | } 2519 | 2520 | },{"./version":42}],41:[function(require,module,exports){ 2521 | module.exports = isWidget 2522 | 2523 | function isWidget(w) { 2524 | return w && w.type === "Widget" 2525 | } 2526 | 2527 | },{}],42:[function(require,module,exports){ 2528 | module.exports = "2" 2529 | 2530 | },{}],43:[function(require,module,exports){ 2531 | var version = require("./version") 2532 | var isVNode = require("./is-vnode") 2533 | var isWidget = require("./is-widget") 2534 | var isThunk = require("./is-thunk") 2535 | var isVHook = require("./is-vhook") 2536 | 2537 | module.exports = VirtualNode 2538 | 2539 | var noProperties = {} 2540 | var noChildren = [] 2541 | 2542 | function VirtualNode(tagName, properties, children, key, namespace) { 2543 | this.tagName = tagName 2544 | this.properties = properties || noProperties 2545 | this.children = children || noChildren 2546 | this.key = key != null ? String(key) : undefined 2547 | this.namespace = (typeof namespace === "string") ? namespace : null 2548 | 2549 | var count = (children && children.length) || 0 2550 | var descendants = 0 2551 | var hasWidgets = false 2552 | var hasThunks = false 2553 | var descendantHooks = false 2554 | var hooks 2555 | 2556 | for (var propName in properties) { 2557 | if (properties.hasOwnProperty(propName)) { 2558 | var property = properties[propName] 2559 | if (isVHook(property) && property.unhook) { 2560 | if (!hooks) { 2561 | hooks = {} 2562 | } 2563 | 2564 | hooks[propName] = property 2565 | } 2566 | } 2567 | } 2568 | 2569 | for (var i = 0; i < count; i++) { 2570 | var child = children[i] 2571 | if (isVNode(child)) { 2572 | descendants += child.count || 0 2573 | 2574 | if (!hasWidgets && child.hasWidgets) { 2575 | hasWidgets = true 2576 | } 2577 | 2578 | if (!hasThunks && child.hasThunks) { 2579 | hasThunks = true 2580 | } 2581 | 2582 | if (!descendantHooks && (child.hooks || child.descendantHooks)) { 2583 | descendantHooks = true 2584 | } 2585 | } else if (!hasWidgets && isWidget(child)) { 2586 | if (typeof child.destroy === "function") { 2587 | hasWidgets = true 2588 | } 2589 | } else if (!hasThunks && isThunk(child)) { 2590 | hasThunks = true; 2591 | } 2592 | } 2593 | 2594 | this.count = count + descendants 2595 | this.hasWidgets = hasWidgets 2596 | this.hasThunks = hasThunks 2597 | this.hooks = hooks 2598 | this.descendantHooks = descendantHooks 2599 | } 2600 | 2601 | VirtualNode.prototype.version = version 2602 | VirtualNode.prototype.type = "VirtualNode" 2603 | 2604 | },{"./is-thunk":37,"./is-vhook":38,"./is-vnode":39,"./is-widget":41,"./version":42}],44:[function(require,module,exports){ 2605 | var version = require("./version") 2606 | 2607 | VirtualPatch.NONE = 0 2608 | VirtualPatch.VTEXT = 1 2609 | VirtualPatch.VNODE = 2 2610 | VirtualPatch.WIDGET = 3 2611 | VirtualPatch.PROPS = 4 2612 | VirtualPatch.ORDER = 5 2613 | VirtualPatch.INSERT = 6 2614 | VirtualPatch.REMOVE = 7 2615 | VirtualPatch.THUNK = 8 2616 | 2617 | module.exports = VirtualPatch 2618 | 2619 | function VirtualPatch(type, vNode, patch) { 2620 | this.type = Number(type) 2621 | this.vNode = vNode 2622 | this.patch = patch 2623 | } 2624 | 2625 | VirtualPatch.prototype.version = version 2626 | VirtualPatch.prototype.type = "VirtualPatch" 2627 | 2628 | },{"./version":42}],45:[function(require,module,exports){ 2629 | var version = require("./version") 2630 | 2631 | module.exports = VirtualText 2632 | 2633 | function VirtualText(text) { 2634 | this.text = String(text) 2635 | } 2636 | 2637 | VirtualText.prototype.version = version 2638 | VirtualText.prototype.type = "VirtualText" 2639 | 2640 | },{"./version":42}],46:[function(require,module,exports){ 2641 | var isObject = require("is-object") 2642 | var isHook = require("../vnode/is-vhook") 2643 | 2644 | module.exports = diffProps 2645 | 2646 | function diffProps(a, b) { 2647 | var diff 2648 | 2649 | for (var aKey in a) { 2650 | if (!(aKey in b)) { 2651 | diff = diff || {} 2652 | diff[aKey] = undefined 2653 | } 2654 | 2655 | var aValue = a[aKey] 2656 | var bValue = b[aKey] 2657 | 2658 | if (aValue === bValue) { 2659 | continue 2660 | } else if (isObject(aValue) && isObject(bValue)) { 2661 | if (getPrototype(bValue) !== getPrototype(aValue)) { 2662 | diff = diff || {} 2663 | diff[aKey] = bValue 2664 | } else if (isHook(bValue)) { 2665 | diff = diff || {} 2666 | diff[aKey] = bValue 2667 | } else { 2668 | var objectDiff = diffProps(aValue, bValue) 2669 | if (objectDiff) { 2670 | diff = diff || {} 2671 | diff[aKey] = objectDiff 2672 | } 2673 | } 2674 | } else { 2675 | diff = diff || {} 2676 | diff[aKey] = bValue 2677 | } 2678 | } 2679 | 2680 | for (var bKey in b) { 2681 | if (!(bKey in a)) { 2682 | diff = diff || {} 2683 | diff[bKey] = b[bKey] 2684 | } 2685 | } 2686 | 2687 | return diff 2688 | } 2689 | 2690 | function getPrototype(value) { 2691 | if (Object.getPrototypeOf) { 2692 | return Object.getPrototypeOf(value) 2693 | } else if (value.__proto__) { 2694 | return value.__proto__ 2695 | } else if (value.constructor) { 2696 | return value.constructor.prototype 2697 | } 2698 | } 2699 | 2700 | },{"../vnode/is-vhook":38,"is-object":25}],47:[function(require,module,exports){ 2701 | var nativeIsArray = Array.isArray 2702 | var toString = Object.prototype.toString 2703 | 2704 | module.exports = nativeIsArray || isArray 2705 | 2706 | function isArray(obj) { 2707 | return toString.call(obj) === "[object Array]" 2708 | } 2709 | 2710 | },{}]},{},[3]); -------------------------------------------------------------------------------- /virtual-dom/lib.require.js: -------------------------------------------------------------------------------- 1 | /* 2 | to generate lib.js, install virtual-dom and process file: 3 | 4 | $ npm install 5 | $ grunt 6 | the ./diff module is vtree/diff with a few changes to 7 | allow diff to run in an asynchronous thread in the presence of 8 | memoized nodes. 9 | */ 10 | 11 | /* 12 | Note on memory management: 13 | 14 | To ensure accurate heap tracing for finalization and profiling purposes, 15 | GHCJS needs to know reachable all Haskell values. ghcjs-vdom stores some 16 | Haskell values inside JavaScript references and uses extensible retention 17 | to collect these values. It's crucial that all data structures that may 18 | contain Haskell values are stored directly in a JSVal and not inside other 19 | JS data structures. 20 | 21 | The recognized types are: 22 | 23 | - HSPatch: 24 | The patch object contains the new (target) virtual-dom tree and 25 | the original tree is reachable trough the parent. Since all handlers, 26 | components and thunks are reachable through these trees, the patch itself 27 | does not need to be traversed. 28 | 29 | - HSMount: 30 | All mounted HSMount points are scanned as roots. The current virtual tree 31 | (mount.vtree) is traversed for this. 32 | 33 | - HSComponent: 34 | Traversed when reachable through Haskell heap or virtual-dom tree. Contains 35 | current tree (component.vtree) and rendering action (component.hsRender) 36 | 37 | - HSThunk: 38 | Traversed when reachable through Haskell heap or virtual-dom tree. Contains 39 | Haskell suspension (thunk.hst) or rendered tree (thunk.vnode) 40 | 41 | - virtual node ( isVirtualNode(x) ) 42 | Some node in a virtual-dom tree. Haskell event handlers are stored in 'ev-*' 43 | properties, which virtual-dom adds to the node's hooks (vnode.hooks). If 44 | a virtual node contains thunks, widgets or any of its descendants have hooks, 45 | the children of the node have to be traversed. 46 | 47 | forceThunks and forcePatch fill an array of thunks, which is not directly 48 | scannable; however, these functions are only used as part of a `diff` operation. 49 | The initial diff creates an HSPatch object, through which the original and target 50 | virtual-dom tree are completely reachable. 51 | */ 52 | 53 | var isVirtualNode = require('virtual-dom/vnode/is-vnode'); 54 | var isThunk = require('virtual-dom/vnode/is-thunk'); 55 | var isWidget = require("virtual-dom/vnode/is-widget"); 56 | var h = require('virtual-dom/h'); 57 | var isArray = require('x-is-array'); 58 | var VPatch = require("virtual-dom/vnode/vpatch"); 59 | var VText = require('virtual-dom/vnode/vtext'); 60 | var vdomPatch = require('virtual-dom/vdom/patch'); 61 | var DomDelegator = require('dom-delegator'); 62 | 63 | var diff = require('./diff'); 64 | 65 | var VRenderableN = 0; 66 | 67 | /** @constructor */ 68 | function HSPatch(patch, old, vnode, parent) { 69 | this.patch = patch; 70 | this.old = old; 71 | this.vnode = vnode; 72 | this.parent = parent; 73 | } 74 | 75 | /** @constructor */ 76 | function HSThunk(t, ids, key) { 77 | this.hst = t; // haskell thunk 78 | this.ids = ids; // array of haskell unique ids 79 | this.key = key; 80 | this.vnode = null; 81 | this._ghcjsMark = 0; 82 | } 83 | 84 | HSThunk.prototype.type = 'Thunk'; 85 | 86 | /* 87 | render returns the deferred rendering object 88 | null if the thunk has already been rendered, in which case the value is in this.vnode 89 | */ 90 | HSThunk.prototype.render = function(previous) { 91 | if(previous && !this.vnode && eqIds(this.ids, previous.ids)) { 92 | if(previous.hst) { 93 | this.hst = previous.hst; 94 | } else { 95 | this.hst = null; 96 | this.vnode = previous.vnode; 97 | } 98 | } 99 | return this.vnode ? null : this; 100 | } 101 | 102 | /** @constructor */ 103 | function HSComponent(r, mnt, unmnt, key) { 104 | this._key = ++VRenderableN; 105 | this.hsRender = r; // IO action that produces a vdom tree 106 | this.hsMount = mnt; 107 | this.hsUnmount = unmnt; 108 | this.key = key || this._key; 109 | this.vnode = this.initialVnode = new VText(""); 110 | this.pending = []; 111 | this.mounts = {}; 112 | this.version = 0; 113 | this.latest = 0; 114 | } 115 | 116 | HSComponent.prototype.type = 'Widget'; 117 | 118 | HSComponent.prototype.init = function() { 119 | var n = document.createTextNode(''); 120 | if(this.vnode !== this.initialVnode) { 121 | var thunks = []; 122 | var p = diff(this.initialVnode, this.vnode, thunks); 123 | if(thunks.length !== 0) { 124 | throw new Error("HSComponent vnode contains unevaluated thunks"); 125 | } 126 | n = vdomPatch(n, p); 127 | } 128 | var m = new HSComponentMount(n); 129 | n._key = m._key; 130 | n._widget = this; 131 | this.mounts[m._key] = m; 132 | if(this.hsMount) { 133 | h$vdomMountComponentCallback(this.hsMount, m._key, this); 134 | } 135 | return n; 136 | } 137 | 138 | HSComponent.prototype.destroy = function(domNode) { 139 | delete this.mounts[domNode._key]; 140 | if(this.hsUnmount) { 141 | h$vdomUnmountComponentCallback(this.hsUnmount, domNode._key, domNode); 142 | } 143 | } 144 | 145 | HSComponent.prototype.diff = function(v, thunks) { 146 | var vn = this.vnode; 147 | if(this.pending.length > 0) { 148 | vn = this.pending[this.pending.length-1].vnode; 149 | } 150 | return new HSPatch( diff(vn, v, thunks) 151 | , vn 152 | , v 153 | , this); 154 | } 155 | 156 | HSComponent.prototype.addPatch = function(p) { 157 | var cur = this.pending.length > 0 ? this.pending[this.pending.length-1] 158 | : this.vnode; 159 | if(p.old === cur) this.pending.push(p); 160 | } 161 | 162 | HSComponent.prototype.patch = function(p) { 163 | if(this.pending.length > 0) { 164 | var pnd = this.pending; 165 | this.pending = []; 166 | for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]); 167 | } 168 | if(!p) return; 169 | if(p.parent !== this || p.old !== this.vnode) { 170 | return false; 171 | } 172 | for(var k in this.mounts) { 173 | var m = this.mounts[k]; 174 | m.node = vdomPatch(m.node, p.patch); 175 | } 176 | this.vnode = p.vnode; 177 | return true; 178 | } 179 | 180 | // only use this for manually updated components (i.e. no diff/patch) 181 | HSComponent.prototype.updateMount = function(mnt, node) { 182 | var m = this.mounts[mnt]; 183 | node._key = mnt; 184 | node._widget = this; 185 | m.node.parentNode.replaceChild(node, m.node); 186 | m.node = node; 187 | } 188 | 189 | HSComponent.prototype.update = function(leftVNode, node) { 190 | if(node._widget) { 191 | if(node._widget == this) return node; 192 | node._widget.destroy(node); 193 | } 194 | return this.init(); 195 | } 196 | 197 | var HSComponentMountN = 0; 198 | function HSComponentMount(domNode) { 199 | this._key = ++HSComponentMountN; 200 | this.node = domNode; 201 | } 202 | 203 | /** @constructor */ 204 | function HSMount(domNode) { 205 | this._key = ++VRenderableN; 206 | // this.version = 0; 207 | this.vnode = new VText(""); // currently rendered vdom tree 208 | this.pending = []; // pending patches, not yet applied 209 | this.node = document.createTextNode(""); 210 | this.parentNode = domNode; 211 | } 212 | 213 | HSMount.prototype.diff = function(v, thunks) { 214 | var vn = this.vnode; 215 | if(this.pending.length > 0) { 216 | vn = this.pending[this.pending.length-1].vnode; 217 | } 218 | return new HSPatch( diff(vn, v, thunks) 219 | , vn 220 | , v 221 | , this); 222 | } 223 | 224 | HSMount.prototype.addPatch = function(p) { 225 | var cur = this.pending.length > 0 ? this.pending[this.pending.length-1] 226 | : this.vnode; 227 | if(p.old === cur) this.pending.push(p); 228 | } 229 | 230 | // HSMount.patch(null) to flush pending list 231 | HSMount.prototype.patch = function(p) { 232 | if(this.pending.length > 0) { 233 | var pnd = this.pending; 234 | this.pending = []; 235 | for(var i = 0; i < pnd.length; i++) this.patch(pnd[i]); 236 | } 237 | if(!p) return; 238 | if(p.parent !== this || p.old !== this.vnode) { 239 | return false; 240 | } 241 | this.node = vdomPatch(this.node, p.patch); 242 | this.vnode = p.vnode; 243 | return true; 244 | } 245 | 246 | /* mount a vdom tree, making it visible to extensible retention */ 247 | function mount(domNode) { 248 | while(domNode.firstChild) domNode.removeChild(domNode.firstChild); 249 | var m = new HSMount(domNode); 250 | domNode.appendChild(m.node); 251 | vdomMounts.add(m); 252 | return m; 253 | } 254 | 255 | /* unmount a tree, removing all child nodes. */ 256 | function unmount(vmount) { 257 | var n = vmount.parentNode; 258 | while(n.firstChild) n.removeChild(n.firstChild); 259 | vdomMounts.remove(vmount); 260 | } 261 | 262 | /* 263 | Compare lists of object identifiers associated with a thunk node. If the lists are equal, 264 | the subtree does not have to be recomputed. 265 | */ 266 | function eqIds(ids1, ids2) { 267 | if(!ids1 || !ids2 || ids1.length != ids2.length) return false; 268 | for(var i=ids1.length-1;i>=0;i--) { 269 | var id1 = ids1[i], id2 = ids2[i]; 270 | if(typeof id1 === 'number') { 271 | if(typeof id2 !== 'number') return false; 272 | if(id1 !== id2 && !((id1!=id1) && (id2!=id2))) return false; 273 | } else { 274 | if(id1 !== id2) return false; 275 | } 276 | } 277 | return true; 278 | } 279 | 280 | function forcePatch(p) { 281 | var thunks = [], i, j, pi; 282 | for(i in p) { 283 | var pi = p[i]; 284 | if(isArray(pi)) { 285 | for(j=pi.length-1;j>=0;j--) { 286 | forceTree(pi[j].patch, thunks); 287 | } 288 | } 289 | else if(pi.patch) forceTree(pi.patch, thunks); 290 | else forceTree(pi, thunks); 291 | } 292 | return thunks; 293 | } 294 | 295 | function forceTree(n, t) { 296 | if(isThunk(n)) { 297 | if(n.vnode) forceTree(n.vnode, t); 298 | else t.push(n); 299 | } else if(isVirtualNode(n) && n.hasThunks) { 300 | for(var i=n.children.length-1;i>=0;i--) { 301 | forceTree(n.children[i], t); 302 | } 303 | } 304 | } 305 | 306 | /* 307 | scan all mounted virtual-dom trees 308 | */ 309 | function scanMounts(currentMark) { 310 | var i = vdomMounts.iter(), m, res = []; 311 | while((m = i.next()) !== null) { 312 | scanTreeRec(m.vnode, res, currentMark); 313 | if(m.pending.length > 0) { 314 | scanTreeRec(m.pending[m.pending.length-1].vnode, res, currentMark); 315 | } 316 | } 317 | return res; 318 | } 319 | 320 | /* 321 | scan a tree (extensible retention callback). 322 | 323 | returns: 324 | - an array of haskell items if any 325 | - true if no haskell items have been found 326 | - false if the object is not a ghcjs-vdom tree 327 | (fallthrough to other extensible retention scanners) 328 | */ 329 | var scanTreeRes = []; 330 | function scanTree(o, currentMark) { 331 | if(isVirtualNode(o) || isThunk(o) || isWidget(o) || 332 | o instanceof HSPatch || o instanceof HSComponent || o instanceof HSMount) { 333 | var r = scanTreeRes; 334 | scanTreeRec(o, r, currentMark); 335 | if(r.length > 0) { 336 | scanTreeRes = []; 337 | return r; 338 | } else { 339 | return true; 340 | } 341 | } else { // not a ghcjs-vdom object, fall through 342 | return false; 343 | } 344 | } 345 | 346 | function scanTreeRec(o, r, currentMark) { 347 | if(o instanceof HSPatch) { 348 | scanTreeRec(o.vnode, r, currentMark); 349 | scanTreeRec(o.parent); 350 | } else if(o instanceof HSThunk) { 351 | if(o._ghcjsMark !== currentMark) { 352 | o._ghcjsMark = currentMark; 353 | if(o.hst) r.push(o.hst); 354 | if(o.vnode) scanTreeRec(o.vnode, r, currentMark); 355 | } 356 | } else if(o instanceof HSComponent) { 357 | if(o._ghcjsMark !== currentMark) { 358 | o._ghcjsMark = currentMark; 359 | if(o.hsRender) r.push(o.hsRender); 360 | if(o.hsMount) r.push(o.hsMount); 361 | if(o.hsUnmount) r.push(o.hsUnmount); 362 | if(o.vnode) scanTreeRec(o.vnode, r, currentMark); 363 | if(o.pending.length > 0) { 364 | scanTreeRec(o.pending[o.pending.length-1].vnode, r, currentMark); 365 | } 366 | } 367 | } else if(isVirtualNode(o)) { 368 | if(o._ghcjsMark !== currentMark) { 369 | o._ghcjsMark = currentMark; 370 | // collect event handlers 371 | var hooks = o.hooks; 372 | for(var p in hooks) { 373 | if(p.indexOf('ev-') === 0) { 374 | var handler = hooks[p]; 375 | if(handler.value && handler.value.hsAction) { 376 | r.push(handler.value.hsAction); 377 | } 378 | } 379 | } 380 | // recurse if any of the children may have thunks, components or handlers 381 | if(o.hasWidgets || o.hasThunks || o.descendantHooks) { 382 | for(var i=o.children.length-1;i>=0;i--) { 383 | scanTreeRec(o.children[i], r, currentMark); 384 | } 385 | } 386 | } 387 | } 388 | } 389 | 390 | function setThunkPatch(n, p) { 391 | if(hasPatches(p)) n.p[n.i] = new VPatch(VPatch.THUNK, null, p); 392 | } 393 | 394 | function hasPatches(patch) { 395 | for (var index in patch) { 396 | if (index !== "a") { 397 | return true; 398 | } 399 | } 400 | return false; 401 | } 402 | 403 | function initDelegator(evTypes) { 404 | var d = DomDelegator(); 405 | var l = evTypes.length; 406 | for(var i = 0; i < l; i++) { 407 | d.listenTo(evTypes[i]); 408 | } 409 | } 410 | 411 | function v(tag, props, children) { 412 | return h(tag, props, children); 413 | } 414 | 415 | function t(text) { 416 | return new VText(text); 417 | } 418 | 419 | function th(t, ids, key) { 420 | return new HSThunk(t, ids, key); 421 | } 422 | 423 | function c(r, m, u, key) { 424 | return new HSComponent(r, m, u, key); 425 | } 426 | 427 | function makeHandler(action, async) { 428 | var f = function(ev) { 429 | return h$vdomEventCallback(async, action, ev); 430 | } 431 | f.hsAction = action; 432 | return f; 433 | } 434 | 435 | var vdomMounts = new h$Set(); 436 | 437 | module.exports = { setThunkPatch: setThunkPatch 438 | , forceTree: forceTree 439 | , forcePatch: forcePatch 440 | , diff: diff 441 | , mount: mount 442 | , unmount: unmount 443 | , initDelegator: initDelegator 444 | , v: v 445 | , th: th 446 | , t: t 447 | , c: c 448 | , makeHandler: makeHandler 449 | }; 450 | 451 | // the global variable we're using in the bindings 452 | h$vdom = module.exports; 453 | 454 | h$registerExtensibleRetention(scanTree); 455 | h$registerExtensibleRetentionRoot(scanMounts); 456 | 457 | 458 | -------------------------------------------------------------------------------- /virtual-dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ghcjs-vdom-support", 3 | "version": "0.0.0", 4 | "description": "Support lib for ghcjs-vdom", 5 | "devDependencies": { 6 | "grunt": "^0.4.5", 7 | "grunt-browserify": "^2.1.4", 8 | "grunt-contrib-watch": "^0.6.1" 9 | }, 10 | "dependencies": { 11 | "is-object": "^0.1.2", 12 | "virtual-dom": "^2.0.1", 13 | "x-is-array": "^0.1.0", 14 | "dom-delegator": "^13.1.0" 15 | } 16 | } 17 | --------------------------------------------------------------------------------