add spruce.ts (also inlined into Spruce.newt)
This commit is contained in:
@@ -29,8 +29,7 @@ pfunc runApp : ∀ msg model. Element → model → (update : msg → model →
|
|||||||
if (node) {
|
if (node) {
|
||||||
parent.insertBefore(child, node);
|
parent.insertBefore(child, node);
|
||||||
parent.removeChild(node);
|
parent.removeChild(node);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
parent.appendChild(child);
|
parent.appendChild(child);
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
@@ -47,8 +46,7 @@ pfunc runApp : ∀ msg model. Element → model → (update : msg → model →
|
|||||||
let el;
|
let el;
|
||||||
if (node instanceof Element && node.tagName.toLowerCase() == vnode.h1) {
|
if (node instanceof Element && node.tagName.toLowerCase() == vnode.h1) {
|
||||||
el = node;
|
el = node;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
el = document.createElement(vnode.h1);
|
el = document.createElement(vnode.h1);
|
||||||
}
|
}
|
||||||
// update node here
|
// update node here
|
||||||
|
|||||||
119
src/Web/spruce.ts
Normal file
119
src/Web/spruce.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// We're going to do the VDom thing, directly off of newt data
|
||||||
|
|
||||||
|
type Attrib<Msg> =
|
||||||
|
| { tag: 0; h1: string; h2: string }
|
||||||
|
| { tag: 1; h1: string; h2: Msg }
|
||||||
|
| { tag: 2; h1: string; h2: (_: string) => Msg };
|
||||||
|
|
||||||
|
type List<A> = { tag: 0 } | { tag: 1; h1: A; h2: List<A> };
|
||||||
|
|
||||||
|
type VNode<Msg> =
|
||||||
|
| { tag: 0; h1: string } // TNode
|
||||||
|
| { tag: 1; h1: string; h2: List<Attrib<Msg>>; h3: List<VNode<Msg>> };
|
||||||
|
|
||||||
|
type FancyElement<Msg> = Element & { events?: Record<string, Attrib<Msg> > };
|
||||||
|
|
||||||
|
// the pfunc will have two dummy arguments in front, and I dunno no node?
|
||||||
|
export function runapp<Model, Msg>(
|
||||||
|
node: Node,
|
||||||
|
init: Model,
|
||||||
|
update: (_: Msg) => (_: Model) => Model,
|
||||||
|
view: (_: Model) => VNode<Msg>,
|
||||||
|
) {
|
||||||
|
function replace(parent: Node, node: Node | undefined, child: Node) {
|
||||||
|
if (node) {
|
||||||
|
parent.insertBefore(child, node);
|
||||||
|
parent.removeChild(node);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(child);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
// patch node, possibly returning a new node
|
||||||
|
function patch<Msg>(
|
||||||
|
parentNode: Node,
|
||||||
|
node: Node | undefined,
|
||||||
|
vnode: VNode<Msg>,
|
||||||
|
): Node {
|
||||||
|
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: FancyElement<Msg>;
|
||||||
|
if (node instanceof Element && node.tagName.toLowerCase() == vnode.h1) {
|
||||||
|
el = node;
|
||||||
|
} else {
|
||||||
|
el = document.createElement(vnode.h1);
|
||||||
|
}
|
||||||
|
// update node here
|
||||||
|
let has: Record<string, boolean> = {};
|
||||||
|
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 as any)[key] = attr.h2
|
||||||
|
else
|
||||||
|
el.setAttribute(key, attr.h2);
|
||||||
|
} else {
|
||||||
|
// onBlah
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove attrs, not efficient..
|
||||||
|
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: Event) => {
|
||||||
|
console.log('listener', ev, ev.target)
|
||||||
|
let target = ev.target as FancyElement<Msg>;
|
||||||
|
for (;;) {
|
||||||
|
if (!target) return
|
||||||
|
if (target.events?.[ev.type]) break
|
||||||
|
target = target.parentElement as FancyElement<Msg>;
|
||||||
|
}
|
||||||
|
if (!target.events) return;
|
||||||
|
const attr = target.events[ev.type]
|
||||||
|
if (attr.tag === 2) {
|
||||||
|
// probably need to pass back the event
|
||||||
|
// we'll want to deal with keypress, change, ...
|
||||||
|
let action = attr.h2(ev.target!.value)
|
||||||
|
model = update(action)(model);
|
||||||
|
console.log('UPDATE', action, '->', model);
|
||||||
|
} else if (attr.tag === 1) {
|
||||||
|
model = update(attr.h2)(model);
|
||||||
|
console.log('UPDATE', attr.h2, '->', model);
|
||||||
|
}
|
||||||
|
vdom = view(model);
|
||||||
|
node = patch(node.parentNode!, node, vdom);
|
||||||
|
};
|
||||||
|
node = patch(node.parentNode!, node, vdom);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user