@@ -0,0 +1,700 @@
1+module Plum.View
2+ ( View
3+ , Nerve(..)
4+ , Meat(..)
5+ , Font
6+ , BorderStyle(..)
7+ , Corner(..)
8+ , Shadow(..)
9+ , ShadowCfg
10+ , Side(..)
11+ , Bones(..)
12+ , grow
13+ , Direction(..)
14+ , Alignment(..)
15+ , Length(..)
16+ , Color(..)
17+ , rgb255
18+ , rgba255
19+ , rgb
20+ , rgba
21+ , UI
22+ , GenericUI(..)
23+ , GenericUIView
24+ , column
25+ , row
26+ , stack
27+ , text
28+ , link
29+ , newTabLink
30+ , download
31+ , downloadAs
32+ , image
33+ , textInput
34+ , spacing
35+ , explain
36+ , align
37+ , width
38+ , height
39+ , wrapped
40+ , bgColor
41+ , padding
42+ , spread
43+ , opacity
44+ , pointer
45+ , move
46+ , clip
47+ , borders
48+ , border
49+ , borderStyle
50+ , roundAll
51+ , round
52+ , shadow
53+ , innerShadow
54+ , onHover
55+ , onMouseDown
56+ , font
57+ , fontSize
58+ , fontWeight
59+ , onClick
60+ ) where
61+
62+import Prelude
63+
64+import Control.Monad.Writer (Writer, runWriter, tell)
65+import Control.Monad.Writer.Trans (mapWriterT)
66+import Data.Array (foldMap)
67+import Data.Array as Array
68+import Data.Array.NonEmpty (concatMap)
69+import Data.Int as Int
70+import Data.Maybe (Maybe(..))
71+import Data.Maybe as Maybe
72+import Data.String as String
73+import Data.Traversable (traverse)
74+import Data.TraversableWithIndex (traverseWithIndex)
75+import Data.Tuple.Nested (type (/\), (/\))
76+import Effect (Effect)
77+import Foreign.Object (Object)
78+import Foreign.Object as Object
79+import Literals.Undefined (undefined)
80+import Prim.TypeError (class Warn)
81+import Prim.TypeError as TypeError
82+import Snabbdom (VNode)
83+import Snabbdom as Snabbdom
84+import Type.Row.Homogeneous (class Homogeneous)
85+import Unsafe.Coerce (unsafeCoerce)
86+import Untagged.Union (asOneOf)
87+import Web.DOM.Element as Element
88+import Web.DOM.ParentNode (firstElementChild)
89+import Web.Event.Event (Event)
90+import Web.Event.Event as Event
91+import Web.UIEvent.InputEvent as InputEvent
92+import Web.HTML.HTMLInputElement as HTMLInputElement
93+
94+type GenericUIView children msg =
95+ { nerves :: Array (Nerve msg)
96+ , meat :: Array Meat
97+ , hover :: Array Meat
98+ , down :: Array Meat
99+ , children :: children
100+ }
101+
102+type UIView msg = GenericUIView (Array (View msg)) msg
103+
104+data GenericUI children msg a = UI (GenericUIView children msg) a
105+
106+instance (Semigroup children, Semigroup a) => Semigroup (GenericUI children msg a) where
107+ append (UI v a) (UI v' a') = UI (v <> v') (a <> a')
108+
109+instance (Monoid children, Monoid a) => Monoid (GenericUI children msg a) where
110+ mempty = UI mempty mempty
111+
112+type UI msg a = GenericUI (Array (View msg)) msg a
113+
114+instance Functor (GenericUI children msg) where
115+ map f (UI v a) = UI v $ f a
116+
117+instance Monoid children => Apply (GenericUI children msg) where
118+ apply (UI views f) (UI views' a) = UI (views <> views') (f a)
119+
120+instance Monoid children => Applicative (GenericUI children msg) where
121+ pure = UI mempty
122+
123+instance Monoid children => Bind (GenericUI children msg) where
124+ bind (UI views a) f =
125+ let
126+ UI views' b = f a
127+ in
128+ UI (views <> views') b
129+
130+instance Monoid children => Monad (GenericUI children msg)
131+
132+column :: forall msg. UI msg Unit -> UI msg Unit
133+column (UI { nerves, meat, hover, down, children } _) =
134+ UI (mempty :: UIView msg) { children = [ { meat, nerves, hover, down } /\ Column children ] } unit
135+
136+row :: forall msg a. UI msg a -> UI msg a
137+row (UI { nerves, meat, hover, down, children } a) =
138+ UI (mempty :: UIView msg) { children = [ { meat, nerves, hover, down } /\ Row children ] } a
139+
140+stack :: forall msg a. UI msg a -> UI msg a
141+stack (UI { nerves, meat, hover, down, children } a) =
142+ UI (mempty :: UIView msg) { children = [ { meat, nerves, hover, down } /\ Stack children ] } a
143+
144+text :: forall msg. String -> GenericUI Unit msg Unit -> UI msg Unit
145+text s (UI { nerves, meat, hover, down } a) = UI (mempty :: UIView msg) { children = [ { meat, nerves, hover, down } /\ Text s ] } a
146+
147+link :: forall msg a. String -> UI msg a -> UI msg a
148+link url (UI { nerves, meat, hover, down, children } a) = UI
149+ (mempty :: UIView msg)
150+ { children =
151+ [ { meat
152+ , nerves
153+ , hover
154+ , down
155+ } /\ Link url { newTab: false } (Maybe.fromMaybe none $ Array.head children)
156+ ]
157+ }
158+ a
159+
160+newTabLink :: forall msg a. String -> UI msg a -> UI msg a
161+newTabLink url (UI { nerves, meat, hover, down, children } a) = UI
162+ (mempty :: UIView msg)
163+ { children =
164+ [ { meat
165+ , nerves
166+ , hover
167+ , down
168+ } /\ Link url { newTab: true } (Maybe.fromMaybe none $ Array.head children)
169+ ]
170+ }
171+ a
172+
173+download :: forall msg a. String -> UI msg a -> UI msg a
174+download url (UI { nerves, meat, children, down, hover } a) = UI
175+ (mempty :: UIView msg)
176+ { children =
177+ [ { meat
178+ , nerves
179+ , down
180+ , hover
181+ } /\ Download url { filename: Nothing } (Maybe.fromMaybe none $ Array.head children)
182+ ]
183+ }
184+ a
185+
186+downloadAs :: forall msg a. String -> { filename :: String } -> UI msg a -> UI msg a
187+downloadAs url { filename } (UI { nerves, meat, hover, down, children } a) = UI
188+ (mempty :: UIView msg)
189+ { children =
190+ [ { meat
191+ , nerves
192+ , hover
193+ , down
194+ } /\ Download url { filename: Just filename } (Maybe.fromMaybe none $ Array.head children)
195+ ]
196+ }
197+ a
198+
199+image :: forall msg a. String -> { description :: String } -> GenericUI Unit msg a -> UI msg a
200+image url description (UI { nerves, meat, hover, down } a) =
201+ UI (mempty :: UIView msg) { children = [ { meat, nerves, hover, down } /\ Image url description ] } a
202+
203+textInput :: forall msg a. { value :: String, onChange :: String -> msg } -> GenericUI Unit msg a -> UI msg a
204+textInput props (UI { nerves, meat, hover, down } a) =
205+ UI (mempty :: UIView msg) { children = [ { meat, nerves, hover, down } /\ TextInput props ] } a
206+
207+string :: String -> Classes -> WiredNerves -> String -> VNode
208+string elem classes (WiredNerves on) s =
209+ Snabbdom.h elem
210+ { attrs: Object.singleton "class" (Array.intercalate " " classes)
211+ , on
212+ , style: Object.empty
213+ , props: Object.empty
214+ }
215+ []
216+ (asOneOf s)
217+
218+h :: String -> Classes -> WiredNerves -> Array VNode -> VNode
219+h elem classes (WiredNerves on) children =
220+ Snabbdom.h elem
221+ { attrs: Object.singleton "class" (Array.intercalate " " classes)
222+ , on
223+ , props: Object.empty
224+ , style: Object.empty
225+ }
226+ children
227+ (asOneOf undefined)
228+
229+hWith :: forall props. Homogeneous props String => String -> Record props -> Classes -> WiredNerves -> Array VNode -> VNode
230+hWith elem props classes (WiredNerves on) children =
231+ Snabbdom.h elem
232+ { attrs: Object.singleton "class" (Array.intercalate " " classes)
233+ , on
234+ , props: Object.fromHomogeneous props
235+ , style: Object.empty
236+ }
237+ children
238+ (asOneOf undefined)
239+
240+data Length = Px Int | Fill | Fit | Max Int Length | Min Int Length
241+
242+data Alignment = Start | Center | End
243+
244+data Direction = X | Y
245+
246+n :: forall ch msg. Monoid ch => Nerve msg -> GenericUI ch msg Unit
247+n nerve = UI (mempty :: GenericUIView ch msg) { nerves = [ nerve ] } mempty
248+
249+onClick :: forall ch msg. Monoid ch => msg -> GenericUI ch msg Unit
250+onClick msg = n $ OnClick msg
251+
252+m :: forall ch msg. Monoid ch => Meat -> GenericUI ch msg Unit
253+m meat = UI (mempty :: GenericUIView ch msg) { meat = [ meat ] } mempty
254+
255+spacing :: forall ch msg. Monoid ch => { x :: Int, y :: Int } -> GenericUI ch msg Unit
256+spacing { x, y } = m $ Spacing x y
257+
258+explain :: forall ch msg. Monoid ch => Warn (TypeError.Text "Debug explain present") => GenericUI ch msg Unit
259+explain = m Explain
260+
261+align :: forall ch msg. Monoid ch => Direction -> Alignment -> GenericUI ch msg Unit
262+align dir al = m $ Align dir al
263+
264+width :: forall ch msg. Monoid ch => Length -> GenericUI ch msg Unit
265+width l = m $ Width l
266+
267+height :: forall ch msg. Monoid ch => Length -> GenericUI ch msg Unit
268+height l = m $ Height l
269+
270+wrapped :: forall ch msg. Monoid ch => GenericUI ch msg Unit
271+wrapped = m Wrapped
272+
273+bgColor :: forall ch msg. Monoid ch => Color -> GenericUI ch msg Unit
274+bgColor c = m $ BackgroundColor c
275+
276+padding :: forall ch msg. Monoid ch => Side -> Int -> GenericUI ch msg Unit
277+padding s l = m $ Padding s l
278+
279+spread :: forall ch msg. Monoid ch => GenericUI ch msg Unit
280+spread = m Spread
281+
282+opacity :: forall ch msg. Monoid ch => Number -> GenericUI ch msg Unit
283+opacity o = m $ Opacity o
284+
285+pointer :: forall ch msg. Monoid ch => GenericUI ch msg Unit
286+pointer = m Pointer
287+
288+move :: forall ch msg. Monoid ch => { x :: Int, y :: Int } -> GenericUI ch msg Unit
289+move a = m $ Move a
290+
291+clip :: forall ch msg. Monoid ch => Direction -> GenericUI ch msg Unit
292+clip dir = m $ Clip dir
293+
294+onHover :: forall ch msg. Monoid ch => GenericUI Unit msg Unit -> GenericUI ch msg Unit
295+onHover (UI { meat } a) = UI (mempty :: GenericUIView ch msg) { hover = meat } a
296+
297+onMouseDown :: forall ch msg. Monoid ch => GenericUI Unit msg Unit -> GenericUI ch msg Unit
298+onMouseDown (UI { meat } a) = UI (mempty :: GenericUIView ch msg) { down = meat } a
299+
300+borders :: forall ch msg. Monoid ch => Int -> GenericUI ch msg Unit
301+borders l = Array.foldMap (\s -> m $ BorderWidth s l) [ Top, Right, Bottom, Left ]
302+
303+border :: forall ch msg. Monoid ch => Side -> Int -> GenericUI ch msg Unit
304+border s l = m $ BorderWidth s l
305+
306+borderStyle :: forall ch msg. Monoid ch => BorderStyle -> GenericUI ch msg Unit
307+borderStyle s = m $ BorderStyle s
308+
309+roundAll :: forall ch msg. Monoid ch => Int -> GenericUI ch msg Unit
310+roundAll l = Array.foldMap (\c -> m $ Round c l) [ TopLeft, TopRight, BottomRight, BottomLeft ]
311+
312+round :: forall ch msg. Monoid ch => Corner -> Int -> GenericUI ch msg Unit
313+round c l = m $ Round c l
314+
315+shadow :: forall ch msg. Monoid ch => ShadowCfg -> GenericUI ch msg Unit
316+shadow cfg = m $ Shadow $ ShadowCfg cfg
317+
318+innerShadow :: forall ch msg. Monoid ch => ShadowCfg -> GenericUI ch msg Unit
319+innerShadow cfg = m $ InnerShadow $ ShadowCfg cfg
320+
321+font :: forall ch msg. Monoid ch => String -> GenericUI ch msg Unit
322+font f = m $ Font f
323+
324+fontSize :: forall ch msg. Monoid ch => Int -> GenericUI ch msg Unit
325+fontSize f = m $ FontSize f
326+
327+fontWeight :: forall ch msg. Monoid ch => Int -> GenericUI ch msg Unit
328+fontWeight f = m $ FontWeight f
329+
330+data Meat
331+ = Spacing Int Int
332+ | Explain
333+ | Align Direction Alignment
334+ | Width Length
335+ | Height Length
336+ | Wrapped
337+ | BackgroundColor Color
338+ | Padding Side Int
339+ | Spread
340+ | Opacity Number
341+ | Pointer
342+ | Move { x :: Int, y :: Int }
343+ | Clip Direction
344+ | BorderWidth Side Int
345+ | BorderStyle BorderStyle
346+ | Round Corner Int
347+ | Shadow Shadow
348+ | InnerShadow Shadow
349+ | FontColor Color
350+ | FontWeight Int
351+ | FontSize Int
352+ | Font Font
353+
354+type Font = String
355+
356+type ShadowCfg = { offset :: { x :: Int, y :: Int }, size :: Number, blur :: Number, color :: Color }
357+
358+newtype Shadow = ShadowCfg ShadowCfg
359+
360+instance Renderable Shadow where
361+ render (ShadowCfg s) = show s.offset.x <> "px " <> show s.offset.y <> "px " <> show s.blur <> "px " <> show s.size <> "px " <> render s.color
362+ renderKey (ShadowCfg s) = show s.offset.x <> "-" <> show s.offset.y <> "-" <> show s.blur <> "-" <> show s.size <> "-" <> renderKey s.color
363+
364+data Corner = TopLeft | TopRight | BottomRight | BottomLeft
365+
366+instance Renderable Corner where
367+ render = case _ of
368+ TopLeft -> "top-left"
369+ TopRight -> "top-right"
370+ BottomRight -> "bottom-right"
371+ BottomLeft -> "bottom-left"
372+ renderKey = case _ of
373+ TopLeft -> "top-left"
374+ TopRight -> "top-right"
375+ BottomRight -> "bottom-right"
376+ BottomLeft -> "bottom-left"
377+
378+data BorderStyle = Solid | Dashed | Dotted
379+
380+instance Renderable BorderStyle where
381+ render = case _ of
382+ Solid -> "solid"
383+ Dashed -> "dashed"
384+ Dotted -> "dotted"
385+ renderKey = case _ of
386+ Solid -> "solid"
387+ Dashed -> "dashed"
388+ Dotted -> "dotted"
389+
390+data Side = Top | Right | Bottom | Left
391+
392+instance Renderable Side where
393+ render = case _ of
394+ Top -> "top"
395+ Right -> "right"
396+ Bottom -> "bottom"
397+ Left -> "left"
398+ renderKey = case _ of
399+ Top -> "top"
400+ Right -> "right"
401+ Bottom -> "bottom"
402+ Left -> "left"
403+
404+data Nerve msg = OnClick msg
405+
406+newtype WiredNerves = WiredNerves (Object (Event -> Effect Unit))
407+
408+instance Semigroup WiredNerves where
409+ append (WiredNerves x) (WiredNerves y) =
410+ WiredNerves $ Object.unionWith (\f g ev -> f ev *> g ev) x y
411+
412+instance Monoid WiredNerves where
413+ mempty = WiredNerves Object.empty
414+
415+wireNerve :: forall msg. (msg -> Effect Unit) -> Nerve msg -> WiredNerves
416+wireNerve fire nerve = WiredNerves $ case nerve of
417+ OnClick msg -> Object.singleton "click" $ \_ev -> fire msg
418+
419+type View msg =
420+ { nerves :: Array (Nerve msg)
421+ , meat :: Array Meat
422+ , hover :: Array Meat
423+ , down :: Array Meat
424+ } /\ Bones msg
425+
426+none :: forall msg. View msg
427+none = mempty /\ None
428+
429+newtype Skin = Skin (Object String)
430+
431+sk :: String -> String -> String -> Writer SkinGrowth Classes
432+sk cName key value = do
433+ tell $ SkinGrowth $ { meat: Object.singleton cName { key, value }, hover: Object.empty, down: Object.empty }
434+ pure [ cName ]
435+
436+instance Semigroup Skin where
437+ append (Skin x) (Skin y) = Skin $ Object.union x y
438+
439+instance Monoid Skin where
440+ mempty = Skin Object.empty
441+
442+data Context = Grid | Flexbox Direction | Generic | Span
443+
444+skin :: Context -> Meat -> Writer SkinGrowth Classes
445+skin ctx = case _ of
446+ Spacing x y -> sk ("spacing-" <> show x <> "-" <> show y) "gap" (show x <> "px " <> show y <> "px")
447+ Explain -> sk "explain" "border" "dashed magenta"
448+ Align dir alignment -> case ctx of
449+ Generic -> mempty
450+ Span -> mempty
451+ Grid ->
452+ let
453+ k =
454+ ( case dir of
455+ X -> "justify-items"
456+ Y -> "align-items"
457+ )
458+ v =
459+ ( case alignment of
460+ Start -> "start"
461+ Center -> "center"
462+ End -> "end"
463+ )
464+ in
465+ sk ("align-grid-" <> k <> "-" <> v) k v
466+
467+ Flexbox flexDir ->
468+ let
469+ k =
470+ ( case flexDir /\ dir of
471+ X /\ X -> "justify-content"
472+ X /\ Y -> "align-items"
473+ Y /\ X -> "align-items"
474+ Y /\ Y -> "justify-content"
475+ )
476+
477+ v =
478+ ( case alignment of
479+ Start -> "flex-start"
480+ Center -> "center"
481+ End -> "flex-end"
482+ )
483+ in
484+ sk ("align-flexbox-" <> k <> "-" <> v) k v
485+ Width l -> sk ("width-" <> renderKey l) "width" (render l)
486+ Height l -> sk ("height-" <> renderKey l) "height" (render l)
487+ Wrapped -> case ctx of
488+ Flexbox _ -> sk "wrapped" "flex-wrap" "wrap"
489+ _ -> pure []
490+ BackgroundColor color -> sk ("bg-color-" <> renderKey color) "background-color" (render color)
491+ Padding side l ->
492+ sk
493+ ("padding-" <> renderKey side <> "-" <> show l)
494+ ("padding-" <> render side)
495+ (show l <> "px")
496+ Spread -> case ctx of
497+ Flexbox _ -> sk "spread" "justify-content" "space-between"
498+ _ -> pure []
499+ Opacity o -> sk ("opacity-" <> show o) "opacity" (show o)
500+ Pointer -> sk "pointer" "cursor" "pointer"
501+ Move { x, y } -> sk ("move-" <> show x <> "-" <> show y) "transform" ("translate(" <> show x <> "px, " <> show y <> "px)")
502+ Clip X -> sk "clip-x" "overflow-x" "hidden"
503+ Clip Y -> sk "clip-y" "overflow-y" "hidden"
504+ BorderWidth side l -> sk ("border-width-" <> renderKey side <> "-" <> show l) ("border-" <> render side <> "-width") (show l <> "px")
505+ BorderStyle s -> sk ("border-style-" <> renderKey s) "border-style" (render s)
506+ Round corner l -> sk ("round-" <> renderKey corner <> "-" <> show l) ("border-" <> render corner <> "-radius") (show l <> "px")
507+ Shadow s -> sk ("shadow-" <> renderKey s) "box-shadow" (render s)
508+ InnerShadow s -> sk ("inner-shadow-" <> renderKey s) "box-shadow" (render s <> " inset")
509+ FontColor c -> sk ("font-color-" <> renderKey c) "color" (render c)
510+ FontSize l -> sk ("font-size-" <> show l) "font-size" (show l <> "px")
511+ Font f -> sk ("font-" <> removeSpaces f) "font-family" f
512+ FontWeight w -> sk ("font-weight-" <> show w) "font-weight" (show w)
513+
514+removeSpaces :: String -> String
515+removeSpaces = String.replaceAll (String.Pattern " ") (String.Replacement "-")
516+
517+class Renderable x where
518+ render :: x -> String
519+ renderKey :: x -> String
520+
521+instance Renderable Length where
522+ render = case _ of
523+ Px px -> show px <> "px"
524+ Fit -> "fit-content"
525+ Fill -> "100%"
526+ Max px l -> "max(" <> show px <> "px," <> render l <> ")"
527+ Min px l -> "min(" <> show px <> "px," <> render l <> ")"
528+ renderKey = case _ of
529+ Px px -> show px
530+ Fit -> "fit-content"
531+ Fill -> "fill"
532+ Max px l -> "max-" <> show px <> "-" <> renderKey l
533+ Min px l -> "min-" <> show px <> "-" <> renderKey l
534+
535+newtype Color = Color { r :: Int, g :: Int, b :: Int, a :: Int }
536+
537+rgb255 :: Int -> Int -> Int -> Color
538+rgb255 r g b = Color { r, g, b, a: 100 }
539+
540+rgba255 :: Int -> Int -> Int -> Int -> Color
541+rgba255 r g b a = Color { r, g, b, a }
542+
543+rgb :: Number -> Number -> Number -> Color
544+rgb r g b = Color { r: Int.round (r * 255.0), g: Int.round (g * 255.0), b: Int.round (b * 255.0), a: 100 }
545+
546+rgba :: Number -> Number -> Number -> Number -> Color
547+rgba r g b a = Color { r: Int.round (r * 255.0), g: Int.round (g * 255.0), b: Int.round (b * 255.0), a: Int.round (a * 100.0) }
548+
549+instance Renderable Color where
550+ render (Color { r, g, b, a }) = "rgb(" <> show r <> "," <> show g <> "," <> show b <> "," <> show a <> "%)"
551+ renderKey (Color { r, g, b, a }) = show r <> "-" <> show g <> "-" <> show b <> "-" <> show a
552+
553+data Bones :: Type -> Type
554+data Bones msg
555+ = Column (Array (View msg))
556+ | Row (Array (View msg))
557+ | Stack (Array (View msg))
558+ | Wrapper (View msg)
559+ | Text String
560+ | Link String { newTab :: Boolean } (View msg)
561+ | Download String { filename :: Maybe String } (View msg)
562+ | Image String { description :: String }
563+ | TextInput { value :: String, onChange :: String -> msg }
564+ | None
565+
566+type Classes = Array String
567+
568+newtype Mutation = Mutation { extraSkin :: Classes }
569+
570+instance Semigroup Mutation where
571+ append (Mutation x) (Mutation y) = Mutation { extraSkin: x.extraSkin <> y.extraSkin }
572+
573+instance Monoid Mutation where
574+ mempty = Mutation { extraSkin: mempty }
575+
576+newtype SkinGrowth = SkinGrowth
577+ { meat :: Object { key :: String, value :: String }
578+ , hover :: Object { key :: String, value :: String }
579+ , down :: Object { key :: String, value :: String }
580+ }
581+
582+styleSkinGrowth :: SkinGrowth -> String
583+styleSkinGrowth (SkinGrowth o) =
584+ Object.foldMap (\className { key, value } -> "." <> className <> "{" <> key <> ":" <> value <> ";}") o.meat
585+ <> Object.foldMap (\className { key, value } -> "." <> className <> "{" <> key <> ":" <> value <> ";}") o.hover
586+ <> Object.foldMap (\className { key, value } -> "." <> className <> "{" <> key <> ":" <> value <> ";}") o.down
587+
588+instance Semigroup SkinGrowth where
589+ append (SkinGrowth x) (SkinGrowth y) = SkinGrowth $
590+ { meat: Object.union x.meat y.meat
591+ , hover: Object.union x.hover y.hover
592+ , down: Object.union x.down y.down
593+ }
594+
595+instance Monoid SkinGrowth where
596+ mempty = SkinGrowth mempty
597+
598+growSkin :: forall msg. (msg -> Effect Unit) -> Mutation -> View msg -> Writer SkinGrowth VNode
599+growSkin fire (Mutation mutation) ({ meat, hover, down, nerves } /\ bones) = do
600+ let
601+ wiredNerves = Array.foldMap (wireNerve fire) nerves
602+ alignCenter = [ Align X Center, Align Y Center ]
603+ widthFill = [ Width Fill, Height Fill ]
604+ skn ctx = do
605+ meatClasses <- ((_ <> mutation.extraSkin) <<< Array.concat) <$> traverse (skin ctx)
606+ ( meat
607+ <> case ctx of
608+ Span -> mempty
609+ Grid -> widthFill
610+ Flexbox _ -> widthFill
611+ Generic -> widthFill
612+ <> case ctx of
613+ Grid -> alignCenter
614+ Flexbox _ -> alignCenter
615+ Span -> mempty
616+ Generic -> alignCenter
617+ )
618+ let hoverClasses /\ (SkinGrowth hover') = runWriter $ traverse (skin ctx) hover
619+ _ <- traverseWithIndex
620+ ( \key val -> tell $ SkinGrowth $
621+ { meat: Object.empty
622+ , hover: Object.singleton (key <> "-hover:hover") val
623+ , down: Object.empty
624+ }
625+ )
626+ hover'.meat
627+ let downClasses /\ (SkinGrowth down') = runWriter $ traverse (skin ctx) down
628+ _ <- traverseWithIndex
629+ ( \key val -> tell $ SkinGrowth $
630+ { meat: Object.empty
631+ , hover: Object.empty
632+ , down: Object.singleton (key <> "-down:active") val
633+ }
634+ )
635+ down'.meat
636+
637+ pure $ meatClasses <> map (_ <> "-hover") (Array.concat hoverClasses) <> map (_ <> "-down") (Array.concat downClasses)
638+
639+ case bones of
640+ Text t -> (\c -> string "span" c wiredNerves t) <$> (skn Span)
641+ Wrapper x -> do
642+ classes <- (skn (Flexbox X) <> sk "display-flex" "display" "flex" <> sk "flex-direction-row" "flex-direction" "row")
643+ x' <- growSkin fire mempty x
644+ pure $ h "div" classes wiredNerves [ x' ]
645+ Stack children -> do
646+ classes <- (skn Grid <> sk "display-grid" "display" "grid")
647+ extraSkin <- sk "grid-row-1" "grid-row" "1" <> sk "grid-column-1" "grid-column" "1"
648+ children' <- (traverse (growSkin fire $ Mutation { extraSkin }) children)
649+ pure $ h "div" classes wiredNerves children'
650+
651+ Row children -> do
652+ classes <- (skn (Flexbox X) <> sk "display-flex" "display" "flex" <> sk "flex-direction-row" "flex-direction" "row")
653+ children' <- traverse (growSkin fire mempty) children
654+ pure $ h "div" classes wiredNerves children'
655+ Column children -> do
656+ classes <- (skn (Flexbox Y) <> sk "display-flex" "display" "flex" <> sk "flex-direction-column" "flex-direction" "column")
657+ children' <- traverse (growSkin fire mempty) children
658+ pure $ h "div" classes wiredNerves children'
659+ Link href { newTab } child -> do
660+ classes <- (skn (Flexbox X) <> sk "display-flex" "display" "flex" <> sk "flex-direction-row" "flex-direction" "row")
661+ child' <- growSkin fire mempty child
662+ pure $ hWith "a"
663+ { href
664+ , rel: "noopener noreferrer"
665+ , target: if newTab then "_blank" else "_self"
666+ }
667+ classes
668+ wiredNerves
669+ [ child' ]
670+
671+ Download href { filename } child -> do
672+ classes <- (skn (Flexbox X) <> sk "display-flex" "display" "flex" <> sk "flex-direction-row" "flex-direction" "row")
673+ child' <- growSkin fire mempty child
674+ pure $ hWith "a"
675+ { href
676+ , download: Maybe.fromMaybe "" filename
677+ }
678+ classes
679+ wiredNerves
680+ [ child' ]
681+ Image src { description } -> (\c -> hWith "img" { src, alt: description } c wiredNerves []) <$> (skn Generic)
682+ TextInput { value, onChange } -> do
683+ classes <- skn Generic
684+ let
685+ onInputNerve = WiredNerves $ Object.singleton "input" $ \ev ->
686+ case Event.currentTarget ev >>= HTMLInputElement.fromEventTarget of
687+ Nothing -> pure unit
688+ Just inputEl -> do
689+ val <- HTMLInputElement.value inputEl
690+ fire $ onChange val
691+ pure $ hWith "input" { value } classes (wiredNerves <> onInputNerve) []
692+ None -> pure $ h "div" mempty wiredNerves []
693+
694+grow :: forall msg. (msg -> Effect Unit) -> View msg -> { node :: VNode, style :: String }
695+grow fire v =
696+ let
697+ node /\ skinGrowth = runWriter (growSkin fire mempty v)
698+ in
699+ { node, style: styleSkinGrowth skinGrowth }
700+