|
| 1 | +module Halogen.VDom.DOM.Prop |
| 2 | + ( Prop(..) |
| 3 | + , Namespace(..) |
| 4 | + , ElemRef(..) |
| 5 | + , PropValue |
| 6 | + , propFromString |
| 7 | + , propFromBoolean |
| 8 | + , propFromInt |
| 9 | + , propFromNumber |
| 10 | + , buildProp |
| 11 | + ) where |
| 12 | + |
| 13 | +import Prelude |
| 14 | +import Control.Monad.Eff (Eff) |
| 15 | +import Control.Monad.Eff.Ref (REF) |
| 16 | +import Control.Monad.Eff.Ref as Ref |
| 17 | +import Data.Maybe (Maybe(..), maybe) |
| 18 | +import Data.StrMap (StrMap) |
| 19 | +import Data.StrMap as StrMap |
| 20 | +import Data.StrMap.ST (STStrMap) |
| 21 | +import Data.StrMap.ST as STStrMap |
| 22 | +import Data.Nullable (Nullable, toNullable) |
| 23 | +import Data.Function.Uncurried as Fn |
| 24 | +import Data.Newtype (class Newtype) |
| 25 | +import Data.Tuple (Tuple(..), fst, snd) |
| 26 | +import DOM (DOM) |
| 27 | +import DOM.Event.EventTarget (EventListener, eventListener) as DOM |
| 28 | +import DOM.Event.Types (EventType(..), Event) as DOM |
| 29 | +import DOM.HTML.Types (HTMLElement) as DOM |
| 30 | +import DOM.Node.Types (Element) as DOM |
| 31 | +import Halogen.VDom as V |
| 32 | +import Halogen.VDom.Util (refEq, diffWithKeyAndIxE, strMapWithIxE) |
| 33 | +import Unsafe.Coerce (unsafeCoerce) |
| 34 | + |
| 35 | +data Prop a |
| 36 | + = Attribute (Maybe Namespace) String String |
| 37 | + | Property String PropValue |
| 38 | + | Handler DOM.EventType (DOM.Event → Maybe a) |
| 39 | + | Ref (ElemRef DOM.HTMLElement → Maybe a) |
| 40 | + |
| 41 | +instance functorProp ∷ Functor Prop where |
| 42 | + map f (Handler ty g) = Handler ty (map f <$> g) |
| 43 | + map f (Ref g) = Ref (map f <$> g) |
| 44 | + map f p = unsafeCoerce p |
| 45 | + |
| 46 | +newtype Namespace = Namespace String |
| 47 | + |
| 48 | +derive instance newtypeNamespace ∷ Newtype Namespace _ |
| 49 | + |
| 50 | +data ElemRef a |
| 51 | + = Created a |
| 52 | + | Removed a |
| 53 | + |
| 54 | +instance functorElemRef ∷ Functor ElemRef where |
| 55 | + map f (Created a) = Created (f a) |
| 56 | + map f (Removed a) = Removed (f a) |
| 57 | + |
| 58 | +foreign import data PropValue ∷ * |
| 59 | + |
| 60 | +propFromString ∷ String → PropValue |
| 61 | +propFromString = unsafeCoerce |
| 62 | + |
| 63 | +propFromBoolean ∷ Boolean → PropValue |
| 64 | +propFromBoolean = unsafeCoerce |
| 65 | + |
| 66 | +propFromInt ∷ Int → PropValue |
| 67 | +propFromInt = unsafeCoerce |
| 68 | + |
| 69 | +propFromNumber ∷ Number → PropValue |
| 70 | +propFromNumber = unsafeCoerce |
| 71 | + |
| 72 | +type PropEff eff a = |
| 73 | + Eff (dom ∷ DOM, ref ∷ REF | eff) a |
| 74 | + |
| 75 | +buildProp |
| 76 | + ∷ ∀ eff a |
| 77 | + . (a → Eff (ref ∷ REF, dom ∷ DOM | eff) Unit) |
| 78 | + → DOM.Element |
| 79 | + → V.VDomMachine (ref ∷ REF, dom ∷ DOM | eff) (Array (Prop a)) Unit |
| 80 | +buildProp emit el = render |
| 81 | + where |
| 82 | + render ps1 = do |
| 83 | + events ← newMutMap |
| 84 | + ps1' ← Fn.runFn3 strMapWithIxE ps1 propToStrKey (applyProp events) |
| 85 | + pure |
| 86 | + (V.Step unit |
| 87 | + (Fn.runFn2 patch (unsafeFreeze events) ps1') |
| 88 | + (done ps1')) |
| 89 | + |
| 90 | + patch = Fn.mkFn2 \prevEvents ps1 → \ps2 → do |
| 91 | + events ← newMutMap |
| 92 | + let |
| 93 | + onThese = Fn.runFn2 diffProp prevEvents events |
| 94 | + onThis = removeProp prevEvents |
| 95 | + onThat = applyProp events |
| 96 | + ps2' ← Fn.runFn6 diffWithKeyAndIxE ps1 ps2 propToStrKey onThese onThis onThat |
| 97 | + pure |
| 98 | + (V.Step unit |
| 99 | + (Fn.runFn2 patch (unsafeFreeze events) ps2') |
| 100 | + (done ps2')) |
| 101 | + |
| 102 | + done ps = do |
| 103 | + case StrMap.lookup "ref" ps of |
| 104 | + Just (Ref f) → do |
| 105 | + mbEmit (f (Removed (unsafeElementToHTMLElement el))) |
| 106 | + _ → do |
| 107 | + effUnit |
| 108 | + |
| 109 | + mbEmit = |
| 110 | + maybe effUnit emit |
| 111 | + |
| 112 | + applyProp events = Fn.mkFn3 \_ _ v → |
| 113 | + case v of |
| 114 | + Attribute ns attr val → do |
| 115 | + Fn.runFn4 setAttribute (toNullable ns) attr val el |
| 116 | + pure v |
| 117 | + Property prop val → do |
| 118 | + Fn.runFn3 setProperty prop val el |
| 119 | + pure v |
| 120 | + Handler (DOM.EventType ty) f → do |
| 121 | + ref ← Ref.newRef f |
| 122 | + let |
| 123 | + listener = DOM.eventListener \ev → do |
| 124 | + f' ← Ref.readRef ref |
| 125 | + mbEmit (f' ev) |
| 126 | + Fn.runFn3 pokeMutMap ty (Tuple listener ref) events |
| 127 | + Fn.runFn3 addEventListener ty listener el |
| 128 | + pure v |
| 129 | + Ref f → do |
| 130 | + mbEmit (f (Created (unsafeElementToHTMLElement el))) |
| 131 | + pure v |
| 132 | + |
| 133 | + diffProp = Fn.mkFn2 \prevEvents events → Fn.mkFn4 \_ _ v1 v2 → |
| 134 | + case v1, v2 of |
| 135 | + Attribute _ _ val1, Attribute ns2 attr2 val2 → |
| 136 | + case val1 /= val2 of |
| 137 | + true → do |
| 138 | + Fn.runFn4 setAttribute (toNullable ns2) attr2 val2 el |
| 139 | + pure v2 |
| 140 | + _ → |
| 141 | + effPure v2 |
| 142 | + Property _ val1, Property prop2 val2 → |
| 143 | + case Fn.runFn2 refEq val1 val2, prop2 == "value" of |
| 144 | + true, _ → |
| 145 | + effPure v2 |
| 146 | + _, false → do |
| 147 | + Fn.runFn3 setProperty prop2 val2 el |
| 148 | + pure v2 |
| 149 | + _, _ → do |
| 150 | + elVal ← Fn.runFn2 getProperty "value" el |
| 151 | + case not (Fn.runFn2 refEq elVal val2) of |
| 152 | + true → do |
| 153 | + Fn.runFn3 setProperty prop2 val2 el |
| 154 | + pure v2 |
| 155 | + _ → |
| 156 | + pure v2 |
| 157 | + Handler _ _, Handler (DOM.EventType ty) f → do |
| 158 | + let |
| 159 | + handler = Fn.runFn2 unsafeLookup ty prevEvents |
| 160 | + Ref.writeRef (snd handler) f |
| 161 | + Fn.runFn3 pokeMutMap ty handler events |
| 162 | + pure v2 |
| 163 | + _, _ → |
| 164 | + effPure v2 |
| 165 | + |
| 166 | + removeProp prevEvents = Fn.mkFn2 \_ v → |
| 167 | + case v of |
| 168 | + Attribute ns attr _ → |
| 169 | + Fn.runFn3 removeAttribute (toNullable ns) attr el |
| 170 | + Property prop _ → |
| 171 | + Fn.runFn2 removeProperty prop el |
| 172 | + Handler (DOM.EventType ty) _ → do |
| 173 | + let |
| 174 | + handler = Fn.runFn2 unsafeLookup ty prevEvents |
| 175 | + Fn.runFn3 removeEventListener ty (fst handler) el |
| 176 | + Ref _ → |
| 177 | + effUnit |
| 178 | + |
| 179 | +propToStrKey ∷ ∀ i. Prop i → String |
| 180 | +propToStrKey = case _ of |
| 181 | + Attribute (Just (Namespace ns)) attr _ → "attr/" <> ns <> ":" <> attr |
| 182 | + Attribute _ attr _ → "attr/:" <> attr |
| 183 | + Property prop _ → "prop/" <> prop |
| 184 | + Handler (DOM.EventType ty) _ → "handler/" <> ty |
| 185 | + Ref _ → "ref" |
| 186 | + |
| 187 | +unsafeElementToHTMLElement ∷ DOM.Element → DOM.HTMLElement |
| 188 | +unsafeElementToHTMLElement = unsafeCoerce |
| 189 | + |
| 190 | +type MutStrMap = STStrMap Void |
| 191 | + |
| 192 | +newMutMap ∷ ∀ eff a. Eff (ref ∷ REF | eff) (MutStrMap a) |
| 193 | +newMutMap = unsafeCoerce STStrMap.new |
| 194 | + |
| 195 | +unsafeFreeze ∷ ∀ a. MutStrMap a → StrMap a |
| 196 | +unsafeFreeze = unsafeCoerce |
| 197 | + |
| 198 | +-- To avoid dictionary passing in some cases |
| 199 | +effPure ∷ ∀ eff a. a → Eff eff a |
| 200 | +effPure = pure |
| 201 | + |
| 202 | +effUnit ∷ ∀ eff. Eff eff Unit |
| 203 | +effUnit = pure unit |
| 204 | + |
| 205 | +foreign import setAttribute |
| 206 | + ∷ ∀ eff |
| 207 | + . Fn.Fn4 (Nullable Namespace) String String DOM.Element (Eff (dom ∷ DOM | eff) Unit) |
| 208 | + |
| 209 | +foreign import removeAttribute |
| 210 | + ∷ ∀ eff |
| 211 | + . Fn.Fn3 (Nullable Namespace) String DOM.Element (Eff (dom ∷ DOM | eff) Unit) |
| 212 | + |
| 213 | +foreign import addEventListener |
| 214 | + ∷ ∀ eff |
| 215 | + . Fn.Fn3 String (DOM.EventListener (dom ∷ DOM | eff)) DOM.Element (Eff (dom ∷ DOM | eff) Unit) |
| 216 | + |
| 217 | +foreign import removeEventListener |
| 218 | + ∷ ∀ eff |
| 219 | + . Fn.Fn3 String (DOM.EventListener (dom ∷ DOM | eff)) DOM.Element (Eff (dom ∷ DOM | eff) Unit) |
| 220 | + |
| 221 | +foreign import setProperty |
| 222 | + ∷ ∀ eff |
| 223 | + . Fn.Fn3 String PropValue DOM.Element (Eff (dom ∷ DOM | eff) Unit) |
| 224 | + |
| 225 | +foreign import getProperty |
| 226 | + ∷ ∀ eff |
| 227 | + . Fn.Fn2 String DOM.Element (Eff (dom ∷ DOM | eff) PropValue) |
| 228 | + |
| 229 | +foreign import removeProperty |
| 230 | + ∷ ∀ eff |
| 231 | + . Fn.Fn2 String DOM.Element (Eff (dom ∷ DOM | eff) Unit) |
| 232 | + |
| 233 | +foreign import unsafeLookup |
| 234 | + ∷ ∀ a. Fn.Fn2 String (StrMap a) a |
| 235 | + |
| 236 | +foreign import pokeMutMap |
| 237 | + ∷ ∀ eff a |
| 238 | + . Fn.Fn3 String a (MutStrMap a) (Eff (ref ∷ REF | eff) Unit) |
0 commit comments