module Web.Spruce -- Spruce is not Elm -- TODO better story for import.. -- also, we'll probably want to expose something with subscriptions / dispatch / ... import Prelude ptype Element pfunc getElementById : String → Element := `(id) => document.getElementById(id)` -- Make this align with spruce.ts data Attr msg = SAttr String String | MAttr String msg | VAttr String (String → msg) data VNode msg = TNode String | ENode String (List $ Attr msg) (List $ VNode msg) text : ∀ msg. String → VNode msg text = TNode tag : ∀ msg. String → List (Attr msg)→ List (VNode msg) → VNode msg tag = ENode tag_ : ∀ msg. String → List (VNode msg)→ VNode msg tag_ tag es = ENode tag Nil es pfunc runApp : ∀ msg model. Element → model → (update : msg → model → model) → (view : model → VNode msg) → Unit := ` (_msg, _model, node, init, update, view) => { function replace(parent, node, child) { if (node) { parent.insertBefore(child, node); parent.removeChild(node); } else { parent.appendChild(child); } return child; } // patch node, possibly returning a new node function patch(parentNode, node, vnode) { if (vnode.tag == 0) { if (node && node.nodeType == 3) { node.nodeValue = vnode.h1; return node; } return replace(parentNode, node, document.createTextNode(vnode.h1)); } let el; if (node instanceof Element && node.tagName.toLowerCase() == vnode.h1) { el = node; } else { el = document.createElement(vnode.h1); } // update node here let has = {}; for (let attrs = vnode.h2; attrs.tag == 1; attrs = attrs.h2) { let attr = attrs.h1; if (attr.tag == 0) { let key = attr.h1; has[key] = true; if (key in el) el[key] = attr.h2; else el.setAttribute(key, attr.h2); } else { let key = attr.h1.slice(2).toLowerCase(); has[key] = true; let events = el.events || (el.events = {}); if (!events[key]) el.addEventListener(key, listener); events[key] = attr; } } for (let i = 0; i < el.attributes.length;) { let attr = el.attributes[i]; if (!has[attr.name]) { el.removeAttribute(attr.name); } else { i++; } } if (el.events) { for (let key of Object.keys(el.events)) { if (!has[key]) delete el.events[key]; } } let i = 0; for (let kids = vnode.h3; kids.tag == 1; kids = kids.h2) { patch(el, el.childNodes[i++], kids.h1); } while (el.childNodes[i]) el.removeChild(el.childNodes[i]); return node == el ? node : replace(parentNode, node, el); } let model = init; let vdom = view(model); let listener = (ev) => { console.log('listener', ev, ev.target); let target = ev.target; for (;;) { if (!target) return; if (target.events?.[ev.type]) break; target = target.parentElement; } const attr = target.events[ev.type]; let action = attr.tag == 2 ? attr.h2(ev.target.value) : attr.h2; model = update(action)(model); vdom = view(model); node = patch(node.parentNode, node, vdom); }; node = patch(node.parentNode, node, vdom); return 0 } `