diff --git a/js/miso.js b/js/miso.js index 9ec4920f1..e1b2ea160 100644 --- a/js/miso.js +++ b/js/miso.js @@ -9,12 +9,11 @@ function diff(c, n, parent, context) { else if (c.type === 2 /* VText */ && n.type === 2 /* VText */) { diffVText(c, n, context); } else if (c.type === 0 /* VComp */ && n.type === 0 /* VComp */) { - if (n.tag === c.tag && n.key === c.key) { - n.domRef = c.domRef; - diffAttrs(c, n, context); - } else { - replace(c, n, parent, context); + if (n.key === c.key) { + n.child = c.child; + return; } + replace(c, n, parent, context); } else if (c.type === 1 /* VNode */ && n.type === 1 /* VNode */) { if (n.tag === c.tag && n.key === c.key) { n.domRef = c.domRef; @@ -31,6 +30,24 @@ function diffVText(c, n, context) { n.domRef = c.domRef; return; } +function drill(c) { + if (!c.child) + throw new Error("'drill' called on an unmounted Component. This should never happen, please make an issue."); + switch (c.child.type) { + case 0 /* VComp */: + return drill(c.child); + default: + return c.child.domRef; + } +} +function getDOMRef(tree) { + switch (tree.type) { + case 0 /* VComp */: + return drill(tree); + default: + return tree.domRef; + } +} function replace(c, n, parent, context) { switch (c.type) { case 2 /* VText */: @@ -41,15 +58,14 @@ function replace(c, n, parent, context) { } switch (n.type) { case 2 /* VText */: - switch (c.type) { - default: - n.domRef = context.createTextNode(n.text); - context.replaceChild(parent, n.domRef, c.domRef); - break; - } + n.domRef = context.createTextNode(n.text); + context.replaceChild(parent, n.domRef, getDOMRef(c)); break; - default: - context.replaceChild(parent, createElement(n, context), c.domRef); + case 0 /* VComp */: + createElement(parent, 1 /* REPLACE */, getDOMRef(c), n, context); + break; + case 1 /* VNode */: + createElement(parent, 1 /* REPLACE */, getDOMRef(c), n, context); break; } switch (c.type) { @@ -68,7 +84,7 @@ function destroy(c, parent, context) { callBeforeDestroyedRecursive(c); break; } - context.removeChild(parent, c.domRef); + context.removeChild(parent, getDOMRef(c)); switch (c.type) { case 2 /* VText */: break; @@ -79,10 +95,20 @@ function destroy(c, parent, context) { } function callDestroyedRecursive(c) { callDestroyed(c); - for (const child of c.children) { - if (child.type === 1 /* VNode */ || child.type === 0 /* VComp */) { - callDestroyedRecursive(child); - } + switch (c.type) { + case 1 /* VNode */: + for (const child of c.children) { + if (child.type === 1 /* VNode */ || child.type === 0 /* VComp */) { + callDestroyedRecursive(child); + } + } + break; + case 0 /* VComp */: + if (c.child) { + if (c.child.type === 1 /* VNode */ || c.child.type === 0 /* VComp */) + callDestroyedRecursive(c.child); + } + break; } } function callDestroyed(c) { @@ -107,18 +133,28 @@ function callBeforeDestroyed(c) { } function callBeforeDestroyedRecursive(c) { callBeforeDestroyed(c); - for (const child of c.children) - if (child.type === 1 /* VNode */ || child.type === 0 /* VComp */) - callBeforeDestroyedRecursive(child); + switch (c.type) { + case 1 /* VNode */: + for (const child of c.children) { + if (child.type === 2 /* VText */) + continue; + callBeforeDestroyedRecursive(child); + } + break; + case 0 /* VComp */: + if (c.child) { + if (c.child.type === 1 /* VNode */ || c.child.type === 0 /* VComp */) + callBeforeDestroyedRecursive(c.child); + } + break; + } } function diffAttrs(c, n, context) { diffProps(c ? c.props : {}, n.props, n.domRef, n.ns === "svg", context); diffClass(c ? c.classList : null, n.classList, n.domRef, context); diffCss(c ? c.css : {}, n.css, n.domRef, context); - if (n.type === 1 /* VNode */) { - diffChildren(c ? c.children : [], n.children, n.domRef, context); - drawCanvas(n); - } + diffChildren(c ? c.children : [], n.children, n.domRef, context); + drawCanvas(n); } function diffClass(c, n, domRef, context) { if (!c && !n) { @@ -226,27 +262,35 @@ function populateDomRef(c, context) { c.domRef = context.createElement(c.tag); } } -function callCreated(n, context) { +function callCreated(parent, n, context) { switch (n.type) { case 0 /* VComp */: - if (n.onBeforeMounted) - n.onBeforeMounted(); - mountComponent(n, context); + mountComponent(parent, 0 /* APPEND */, null, n, context); break; case 1 /* VNode */: if (n.onCreated) n.onCreated(n.domRef); break; } - return n.domRef; } -function createElement(n, context) { +function createElement(parent, op, replacing, n, context) { switch (n.type) { + case 2 /* VText */: + n.domRef = context.createTextNode(n.text); + switch (op) { + case 2 /* INSERT_BEFORE */: + context.insertBefore(parent, n.domRef, replacing); + break; + case 0 /* APPEND */: + context.appendChild(parent, n.domRef); + break; + case 1 /* REPLACE */: + context.replaceChild(parent, n.domRef, replacing); + break; + } + break; case 0 /* VComp */: - if (n.onBeforeMounted) - n.onBeforeMounted(); - populateDomRef(n, context); - mountComponent(n, context); + mountComponent(parent, op, replacing, n, context); break; case 1 /* VNode */: if (n.onBeforeCreated) @@ -254,10 +298,20 @@ function createElement(n, context) { populateDomRef(n, context); if (n.onCreated) n.onCreated(n.domRef); + diffAttrs(null, n, context); + switch (op) { + case 2 /* INSERT_BEFORE */: + context.insertBefore(parent, n.domRef, replacing); + break; + case 0 /* APPEND */: + context.appendChild(parent, n.domRef); + break; + case 1 /* REPLACE */: + context.replaceChild(parent, n.domRef, replacing); + break; + } break; } - diffAttrs(null, n, context); - return n.domRef; } function drawCanvas(c) { if (c.tag === "canvas" && c.draw) @@ -265,25 +319,45 @@ function drawCanvas(c) { } function unmountComponent(c) { if (c.onUnmounted) - c.onUnmounted(c.domRef); - c.unmount(c.domRef); + c.onUnmounted(drill(c)); + c.unmount(c.componentId); } -function mountComponent(obj, context) { - obj.mount(obj, (componentId, componentTree) => { - obj.children.push(componentTree); - context.appendChild(obj.domRef, componentTree.domRef); - if (obj.onMounted) - obj.onMounted(obj.domRef); +function mountComponent(parent, op, replacing, n, context) { + if (n.onBeforeMounted) + n.onBeforeMounted(); + n.mount(parent, (componentId, componentTree) => { + n.componentId = componentId; + n.child = componentTree; + componentTree.parent = n; + if (componentTree.type !== 0 /* VComp */) { + const childDomRef = getDOMRef(componentTree); + if (op === 1 /* REPLACE */ && replacing) { + context.replaceChild(parent, childDomRef, replacing); + } else if (op === 2 /* INSERT_BEFORE */) { + context.insertBefore(parent, childDomRef, replacing); + } + } }); + if (n.onMounted) + n.onMounted(drill(n)); } -function create(obj, parent, context) { - if (obj.type === 2 /* VText */) { - obj.domRef = context.createTextNode(obj.text); - context.appendChild(parent, obj.domRef); +function create(n, parent, context) { + if (n.type === 2 /* VText */) { + n.domRef = context.createTextNode(n.text); + context.appendChild(parent, n.domRef); } else { - context.appendChild(parent, createElement(obj, context)); + createElement(parent, 0 /* APPEND */, null, n, context); } } +function insertBefore(parent, n, o, context) { + context.insertBefore(parent, getDOMRef(n), o ? getDOMRef(o) : null); +} +function removeChild(parent, n, context) { + context.removeChild(parent, getDOMRef(n)); +} +function swapDOMRef(oLast, oFirst, parent, context) { + context.swapDOMRefs(getDOMRef(oLast), getDOMRef(oFirst), parent); +} function syncChildren(os, ns, parent, context) { var oldFirstIndex = 0, newFirstIndex = 0, oldLastIndex = os.length - 1, newLastIndex = ns.length - 1, tmp, nFirst, nLast, oLast, oFirst, found, node; for (;; ) { @@ -296,13 +370,13 @@ function syncChildren(os, ns, parent, context) { oLast = os[oldLastIndex]; if (oldFirstIndex > oldLastIndex) { diff(null, nFirst, parent, context); - context.insertBefore(parent, nFirst.domRef, oFirst ? oFirst.domRef : null); + insertBefore(parent, nFirst, oFirst, context); os.splice(newFirstIndex, 0, nFirst); newFirstIndex++; } else if (newFirstIndex > newLastIndex) { tmp = oldLastIndex; while (oldLastIndex >= oldFirstIndex) { - context.removeChild(parent, os[oldLastIndex--].domRef); + removeChild(parent, os[oldLastIndex--], context); } os.splice(oldFirstIndex, tmp - oldFirstIndex + 1); break; @@ -311,16 +385,16 @@ function syncChildren(os, ns, parent, context) { } else if (oLast.key === nLast.key) { diff(os[oldLastIndex--], ns[newLastIndex--], parent, context); } else if (oFirst.key === nLast.key && nFirst.key === oLast.key) { - context.swapDOMRefs(oLast.domRef, oFirst.domRef, parent); + swapDOMRef(oLast, oFirst, parent, context); swap(os, oldFirstIndex, oldLastIndex); diff(os[oldFirstIndex++], ns[newFirstIndex++], parent, context); diff(os[oldLastIndex--], ns[newLastIndex--], parent, context); } else if (oFirst.key === nLast.key) { - context.insertBefore(parent, oFirst.domRef, context.nextSibling(oLast)); + insertBefore(parent, oFirst, oLast.nextSibling, context); os.splice(oldLastIndex, 0, os.splice(oldFirstIndex, 1)[0]); diff(os[oldLastIndex--], ns[newLastIndex--], parent, context); } else if (oLast.key === nFirst.key) { - context.insertBefore(parent, oLast.domRef, oFirst.domRef); + insertBefore(parent, oLast, oFirst, context); os.splice(oldFirstIndex, 0, os.splice(oldLastIndex, 1)[0]); diff(os[oldFirstIndex++], nFirst, parent, context); newFirstIndex++; @@ -338,18 +412,10 @@ function syncChildren(os, ns, parent, context) { if (found) { os.splice(oldFirstIndex, 0, os.splice(tmp, 1)[0]); diff(os[oldFirstIndex++], nFirst, parent, context); - context.insertBefore(parent, node.domRef, os[oldFirstIndex].domRef); + insertBefore(parent, node, os[oldFirstIndex], context); newFirstIndex++; } else { - switch (nFirst.type) { - case 2 /* VText */: - nFirst.domRef = context.createTextNode(nFirst.text); - context.insertBefore(parent, nFirst.domRef, oFirst.domRef); - break; - default: - context.insertBefore(parent, createElement(nFirst, context), oFirst.domRef); - break; - } + createElement(parent, 2 /* INSERT_BEFORE */, getDOMRef(oFirst), nFirst, context); os.splice(oldFirstIndex++, 0, nFirst); newFirstIndex++; oldLastIndex++; @@ -415,7 +481,19 @@ function delegateEvent(event, obj, stack, debug, context) { } return; } else if (stack.length > 1) { - if (obj.type === 0 /* VComp */ || obj.type === 1 /* VNode */) { + if (obj.type === 2 /* VText */) { + return; + } else if (obj.type === 0 /* VComp */) { + if (!obj.child) { + if (debug) { + console.error("VComp has no child property set during event delegation", obj); + console.error("This means the Component has not been fully mounted, this should never happen"); + throw new Error("VComp has no .child property set during event delegation"); + } + return; + } + return delegateEvent(event, obj.child, stack, debug, context); + } else if (obj.type === 1 /* VNode */) { if (context.isEqual(obj.domRef, stack[0])) { const eventObj = obj.events.captures[event.type]; if (eventObj) { @@ -432,15 +510,15 @@ function delegateEvent(event, obj, stack, debug, context) { stack.splice(0, 1); } for (const child of obj.children) { - if (child.type === 0 /* VComp */ || child.type === 1 /* VNode */) { - if (context.isEqual(child.domRef, stack[0])) { - delegateEvent(event, child, stack, debug, context); - } - } + delegateEvent(event, child, stack, debug, context); } } } else { - if (obj.type === 1 /* VNode */) { + if (obj.type === 0 /* VComp */) { + if (obj.child) { + delegateEvent(event, obj.child, stack, debug, context); + } + } else if (obj.type === 1 /* VNode */) { const eventCaptureObj = obj.events.captures[event.type]; if (eventCaptureObj && !event["captureStopped"]) { const options = eventCaptureObj.options; @@ -549,45 +627,16 @@ function collapseSiblingTextNodes(vs) { } return adjusted; } -function preamble(mountPoint, context) { - var mountChildIdx = 0, node; - var root = context.getRoot(); - if (!mountPoint) { - if (root.childNodes.length > 0) { - node = root.firstChild; - } else { - node = root.appendChild(context.createElement("div")); - } - } else if (mountPoint.childNodes.length === 0) { - node = mountPoint.appendChild(context.createElement("div")); - } else { - while (mountPoint.childNodes[mountChildIdx] && (mountPoint.childNodes[mountChildIdx].nodeType === 3 || mountPoint.childNodes[mountChildIdx].localName === "script")) { - mountChildIdx++; - } - if (!mountPoint.childNodes[mountChildIdx]) { - node = root.appendChild(context.createElement("div")); - } else { - node = mountPoint.childNodes[mountChildIdx]; - } - } - return node; -} function hydrate(logLevel, mountPoint, vtree, context, drawingContext) { - const node = preamble(mountPoint, drawingContext); + if (!vtree || !mountPoint) + return false; + let node = drawingContext.getRoot() === mountPoint ? mountPoint.firstChild : mountPoint; if (!walk(logLevel, vtree, node, context, drawingContext)) { if (logLevel) { console.warn("[DEBUG_HYDRATE] Could not copy DOM into virtual DOM, falling back to diff"); } while (context.firstChild(node)) drawingContext.removeChild(node, context.lastChild(node)); - vtree.domRef = node; - switch (vtree.type) { - case 2 /* VText */: - break; - default: - diffAttrs(null, vtree, drawingContext); - break; - } return false; } else { if (logLevel) { @@ -626,7 +675,7 @@ function check(result, vtree, context, drawingContext) { console.warn("VText node content differs", vtree); result = false; } - } else { + } else if (vtree.type === 1 /* VNode */) { if (vtree.tag.toUpperCase() !== context.getTag(vtree.domRef).toUpperCase()) { console.warn("Integrity check failed, tags differ", vtree.tag.toUpperCase(), context.getTag(vtree.domRef)); result = false; @@ -678,16 +727,25 @@ function check(result, vtree, context, drawingContext) { function walk(logLevel, vtree, node, context, drawingContext) { switch (vtree.type) { case 0 /* VComp */: - vtree.domRef = node; - callCreated(vtree, drawingContext); + if (!walk(logLevel, vtree.child, node, context, drawingContext)) { + return false; + } break; case 2 /* VText */: + if (node.nodeType !== 3 || vtree.text.trim() !== node.textContent.trim()) { + diagnoseError(logLevel, vtree, node); + return false; + } vtree.domRef = node; break; case 1 /* VNode */: + if (node.nodeType !== 1) { + diagnoseError(logLevel, vtree, node); + return false; + } vtree.domRef = node; vtree.children = collapseSiblingTextNodes(vtree.children); - callCreated(vtree, drawingContext); + callCreated(node, vtree, drawingContext); for (var i = 0;i < vtree.children.length; i++) { const vdomChild = vtree.children[i]; const domChild = node.childNodes[i]; @@ -695,27 +753,11 @@ function walk(logLevel, vtree, node, context, drawingContext) { diagnoseError(logLevel, vdomChild, domChild); return false; } - switch (vdomChild.type) { - case 2 /* VText */: - if (domChild.nodeType !== 3) { - diagnoseError(logLevel, vdomChild, domChild); - return false; - } - if (vdomChild.text === domChild.textContent) { - vdomChild.domRef = context.children(node)[i]; - } else { - diagnoseError(logLevel, vdomChild, domChild); - return false; - } - break; - default: - if (domChild.nodeType !== 1) - return false; - vdomChild.domRef = domChild; - walk(logLevel, vdomChild, domChild, context, drawingContext); - break; + if (!walk(logLevel, vdomChild, domChild, context, drawingContext)) { + return false; } } + break; } return true; } @@ -790,20 +832,6 @@ function fetchCore(url, method, body, requestHeaders, successful, errorful, resp errorful({ body: null, error: err.message, headers, status }); } } -function getParentComponentId(vcompNode) { - var climb = function(node) { - let parentComponentId = null; - while (node && node.parentNode) { - if ("componentId" in node.parentNode) { - parentComponentId = node.parentNode["componentId"]; - break; - } - node = node.parentNode; - } - return parentComponentId; - }; - return climb(vcompNode); -} function websocketConnect(url, onOpen, onClose, onMessageText, onMessageJSON, onMessageBLOB, onMessageArrayBuffer, onError, textOnly) { try { let socket = new WebSocket(url); @@ -970,7 +998,15 @@ var componentContext = { }; var drawingContext = { nextSibling: (node) => { - return node.domRef.nextSibling; + if (node.nextSibling) { + switch (node.nextSibling.type) { + case 0 /* VComp */: + return drill(node.nextSibling); + default: + return node.nextSibling.domRef; + } + } + return null; }, createTextNode: (s) => { return document.createTextNode(s); @@ -1001,10 +1037,10 @@ var drawingContext = { insertBefore: (parent, child, node) => { return parent.insertBefore(child, node); }, - swapDOMRefs: (a, b, p) => { - const tmp = a.nextSibling; - p.insertBefore(a, b); - p.insertBefore(b, tmp); + swapDOMRefs: (oLast, oFirst, p) => { + const tmp = oLast.nextSibling; + p.insertBefore(oLast, oFirst); + p.insertBefore(oFirst, tmp); return; }, setInlineStyle: (cCss, nCss, node) => { @@ -1080,7 +1116,6 @@ globalThis["miso"] = { websocketSend, undelegate, populateClass, - getParentComponentId, integrityCheck, setDrawingContext: function(name) { const drawing = globalThis[name]["drawingContext"]; diff --git a/js/miso.prod.js b/js/miso.prod.js index 8508c8121..d55dc543d 100644 --- a/js/miso.prod.js +++ b/js/miso.prod.js @@ -1 +1 @@ -function J(G,K,Q,X){if(!G&&!K)return;else if(!G)WG(K,Q,X);else if(!K)QG(G,Q,X);else if(G.type===2&&K.type===2)KG(G,K,X);else if(G.type===0&&K.type===0)if(K.tag===G.tag&&K.key===G.key)K.domRef=G.domRef,A(G,K,X);else E(G,K,Q,X);else if(G.type===1&&K.type===1)if(K.tag===G.tag&&K.key===G.key)K.domRef=G.domRef,A(G,K,X);else E(G,K,Q,X);else E(G,K,Q,X)}function KG(G,K,Q){if(G.text!==K.text)Q.setTextContent(G.domRef,K.text);K.domRef=G.domRef;return}function E(G,K,Q,X){switch(G.type){case 2:break;default:S(G);break}switch(K.type){case 2:switch(G.type){default:K.domRef=X.createTextNode(K.text),X.replaceChild(Q,K.domRef,G.domRef);break}break;default:X.replaceChild(Q,O(K,X),G.domRef);break}switch(G.type){case 2:break;default:N(G);break}}function QG(G,K,Q){switch(G.type){case 2:break;default:S(G);break}switch(Q.removeChild(K,G.domRef),G.type){case 2:break;default:N(G);break}}function N(G){UG(G);for(let K of G.children)if(K.type===1||K.type===0)N(K)}function UG(G){if(G.type===1&&G.onDestroyed)G.onDestroyed();if(G.type===0)HG(G)}function XG(G){switch(G.type){case 0:if(G.onBeforeUnmounted)G.onBeforeUnmounted();break;case 1:if(G.onBeforeDestroyed)G.onBeforeDestroyed();break;default:break}}function S(G){XG(G);for(let K of G.children)if(K.type===1||K.type===0)S(K)}function A(G,K,Q){if(ZG(G?G.props:{},K.props,K.domRef,K.ns==="svg",Q),YG(G?G.classList:null,K.classList,K.domRef,Q),_G(G?G.css:{},K.css,K.domRef,Q),K.type===1)qG(G?G.children:[],K.children,K.domRef,Q),zG(K)}function YG(G,K,Q,X){if(!G&&!K)return;if(!G){for(let U of K)X.addClass(U,Q);return}if(!K){for(let U of G)X.removeClass(U,Q);return}for(let U of G)if(!K.has(U))X.removeClass(U,Q);for(let U of K)if(!G.has(U))X.addClass(U,Q);return}function ZG(G,K,Q,X,U){var Z;for(let Y in G)if(Z=K[Y],Z===void 0)if(X||!(Y in Q)||Y==="disabled")U.removeAttribute(Q,Y);else U.setAttribute(Q,Y,"");else{if(Z===G[Y]&&Y!=="checked"&&Y!=="value")continue;if(X)if(Y==="href")U.setAttributeNS(Q,"http://www.w3.org/1999/xlink","href",Z);else U.setAttribute(Q,Y,Z);else if(Y in Q&&!(Y==="list"||Y==="form"))Q[Y]=Z;else U.setAttribute(Q,Y,Z)}for(let Y in K){if(G&&G[Y])continue;if(Z=K[Y],X)if(Y==="href")U.setAttributeNS(Q,"http://www.w3.org/1999/xlink","href",Z);else U.setAttribute(Q,Y,Z);else if(Y in Q&&!(Y==="list"||Y==="form"))Q[Y]=K[Y];else U.setAttribute(Q,Y,Z)}}function _G(G,K,Q,X){X.setInlineStyle(G,K,Q)}function $G(G,K){if(G.length===0||K.length===0)return!1;for(var Q=0;Q{if(G.children.push(X),K.appendChild(G.domRef,X.domRef),G.onMounted)G.onMounted(G.domRef)})}function WG(G,K,Q){if(G.type===2)G.domRef=Q.createTextNode(G.text),Q.appendChild(K,G.domRef);else Q.appendChild(K,O(G,Q))}function JG(G,K,Q,X){var U=0,Z=0,Y=G.length-1,_=K.length-1,z,q,$,H,W,B,i;for(;;){if(Z>_&&U>Y)break;if(q=K[Z],$=K[_],W=G[U],H=G[Y],U>Y)J(null,q,Q,X),X.insertBefore(Q,q.domRef,W?W.domRef:null),G.splice(Z,0,q),Z++;else if(Z>_){z=Y;while(Y>=U)X.removeChild(Q,G[Y--].domRef);G.splice(U,z-U+1);break}else if(W.key===q.key)J(G[U++],K[Z++],Q,X);else if(H.key===$.key)J(G[Y--],K[_--],Q,X);else if(W.key===$.key&&q.key===H.key)X.swapDOMRefs(H.domRef,W.domRef,Q),AG(G,U,Y),J(G[U++],K[Z++],Q,X),J(G[Y--],K[_--],Q,X);else if(W.key===$.key)X.insertBefore(Q,W.domRef,X.nextSibling(H)),G.splice(Y,0,G.splice(U,1)[0]),J(G[Y--],K[_--],Q,X);else if(H.key===q.key)X.insertBefore(Q,H.domRef,W.domRef),G.splice(U,0,G.splice(Y,1)[0]),J(G[U++],q,Q,X),Z++;else{B=!1,z=U;while(z<=Y){if(G[z].key===q.key){B=!0,i=G[z];break}z++}if(B)G.splice(U,0,G.splice(z,1)[0]),J(G[U++],q,Q,X),X.insertBefore(Q,i.domRef,G[U].domRef),Z++;else{switch(q.type){case 2:q.domRef=X.createTextNode(q.text),X.insertBefore(Q,q.domRef,W.domRef);break;default:X.insertBefore(Q,O(q,X),W.domRef);break}G.splice(U++,0,q),Z++,Y++}}}}function AG(G,K,Q){let X=G[K];G[K]=G[Q],G[Q]=X}function P(G,K,Q,X,U){for(let Z of K)U.addEventListener(G,Z.name,function(Y){a(Y,G,Q,X,U)},Z.capture)}function L(G,K,Q,X,U){for(let Z of K)U.removeEventListener(G,Z.name,function(Y){a(Y,G,Q,X,U)},Z.capture)}function a(G,K,Q,X,U){Q(function(Z){if(Array.isArray(G))for(let Y of G)d(Y,Z,K,X,U);else d(G,Z,K,X,U)})}function d(G,K,Q,X,U){var Z=U.getTarget(G);if(Z){let Y=DG(Q,Z,U);r(G,K,Y,X,U)}}function DG(G,K,Q){var X=[];while(!Q.isEqual(G,K))if(X.unshift(K),K&&Q.parentNode(K))K=Q.parentNode(K);else return X;return X}function r(G,K,Q,X,U){if(!Q.length){if(X)console.warn('Event "'+G.type+'" did not find an event handler to dispatch on',K,G);return}else if(Q.length>1){if(K.type===0||K.type===1){if(U.isEqual(K.domRef,Q[0])){let Z=K.events.captures[G.type];if(Z){let Y=Z.options;if(Y.preventDefault)G.preventDefault();if(!G.captureStopped)Z.runEvent(G,K.domRef);if(Y.stopPropagation)G.captureStopped=!0}Q.splice(0,1)}for(let Z of K.children)if(Z.type===0||Z.type===1){if(U.isEqual(Z.domRef,Q[0]))r(G,Z,Q,X,U)}}}else if(K.type===1){let Z=K.events.captures[G.type];if(Z&&!G.captureStopped){let _=Z.options;if(U.isEqual(Q[0],K.domRef)){if(_.preventDefault)G.preventDefault();if(Z.runEvent(G,Q[0]),_.stopPropagation)G.captureStopped=!0}}let Y=K.events.bubbles[G.type];if(Y&&!G.captureStopped){let _=Y.options;if(U.isEqual(Q[0],K.domRef)){if(_.preventDefault)G.preventDefault();if(Y.runEvent(G,Q[0]),!_.stopPropagation)p(K.parent,G)}}else if(!G.captureStopped)p(K.parent,G)}}function p(G,K){while(G)switch(G.type){case 2:break;case 1:let Q=G.events.bubbles[K.type];if(Q){let X=Q.options;if(X.preventDefault)K.preventDefault();if(Q.runEvent(K,G.domRef),X.stopPropagation)return}G=G.parent;break;case 0:if(!G.eventPropagation)return;G=G.parent;break}}function D(G,K){if(typeof G[0]==="object"){var Q=[];for(var X=0;X0?[G[0]]:[];for(var X=1;X0)X=U.firstChild;else X=U.appendChild(K.createElement("div"));else if(G.childNodes.length===0)X=G.appendChild(K.createElement("div"));else{while(G.childNodes[Q]&&(G.childNodes[Q].nodeType===3||G.childNodes[Q].localName==="script"))Q++;if(!G.childNodes[Q])X=U.appendChild(K.createElement("div"));else X=G.childNodes[Q]}return X}function V(G,K,Q,X,U){let Z=RG(K,U);if(!o(G,Q,Z,X,U)){if(G)console.warn("[DEBUG_HYDRATE] Could not copy DOM into virtual DOM, falling back to diff");while(X.firstChild(Z))U.removeChild(Z,X.lastChild(Z));switch(Q.domRef=Z,Q.type){case 2:break;default:A(null,Q,U);break}return!1}else if(G)console.info("[DEBUG_HYDRATE] Successfully prerendered page");return!0}function F(G,K,Q){if(G)console.warn("[DEBUG_HYDRATE] VTree differed from node",K,Q)}function s(G){if(G.substr(0,1)=="#"){let K=(G.length-1)/3,Q=[17,1,0.062272][K-1];return[Math.round(parseInt(G.substr(1,K),16)*Q),Math.round(parseInt(G.substr(1+K,K),16)*Q),Math.round(parseInt(G.substr(1+2*K,K),16)*Q)]}else return G.split("(")[1].split(")")[0].split(",").map((K)=>{return+K})}function I(G,K,Q){return c(!0,G,K,Q)}function c(G,K,Q,X){if(K.type==2){if(Q.getTag(K.domRef)!=="#text")console.warn("VText domRef not a TEXT_NODE",K),G=!1;else if(K.text!==Q.getTextContent(K.domRef))console.warn("VText node content differs",K),G=!1}else{if(K.tag.toUpperCase()!==Q.getTag(K.domRef).toUpperCase())console.warn("Integrity check failed, tags differ",K.tag.toUpperCase(),Q.getTag(K.domRef)),G=!1;if("children"in K&&K.children.length!==Q.children(K.domRef).length)console.warn("Integrity check failed, children lengths differ",K,K.children,Q.children(K.domRef)),G=!1;for(let U in K.props)if(U==="href"||U==="src"){let Z=window.location.origin+"/"+K.props[U],Y=Q.getAttribute(K.domRef,U),_=K.props[U];if(Z!==Y&&_!==Y&&_+"/"!==Y&&Z+"/"!==Y)console.warn("Property "+U+" differs",K.props[U],Q.getAttribute(K.domRef,U)),G=!1}else if(U==="height"||U==="width"){if(parseFloat(K.props[U])!==parseFloat(Q.getAttribute(K.domRef,U)))console.warn("Property "+U+" differs",K.props[U],Q.getAttribute(K.domRef,U)),G=!1}else if(U==="class"||U==="className"){if(K.props[U]!==Q.getAttribute(K.domRef,"class"))console.warn("Property class differs",K.props[U],Q.getAttribute(K.domRef,"class")),G=!1}else if(K.props[U]!==Q.getAttribute(K.domRef,U))console.warn("Property "+U+" differs",K.props[U],Q.getAttribute(K.domRef,U)),G=!1;for(let U in K.css)if(U==="color"){if(s(Q.getInlineStyle(K.domRef,U)).toString()!==s(K.css[U]).toString())console.warn("Style "+U+" differs",K.css[U],Q.getInlineStyle(K.domRef,U)),G=!1}else if(K.css[U]!==Q.getInlineStyle(K.domRef,U))console.warn("Style "+U+" differs",K.css[U],Q.getInlineStyle(K.domRef,U)),G=!1;for(let U of K.children){let Z=c(G,U,Q,X);G=G&&Z}}return G}function o(G,K,Q,X,U){switch(K.type){case 0:K.domRef=Q,M(K,U);break;case 2:K.domRef=Q;break;case 1:K.domRef=Q,K.children=EG(K.children),M(K,U);for(var Z=0;Z0?setTimeout(Q,K):Q()}function C(G,K){var Q=function(){var X=document.getElementById(G);if(X&&X.blur)X.blur()};K>0?setTimeout(Q,K):Q()}function j(G,K){var Q=function(){var X=document.getElementById(G);if(X&&typeof X.select==="function")X.select()};K>0?setTimeout(Q,K):Q()}function k(G,K,Q,X){var U=function(){var Z=document.getElementById(G);if(Z&&typeof Z.setSelectionRange==="function")Z.setSelectionRange(K,Q,"none")};X>0?setTimeout(U,X):U()}function T(G,K,Q,X,U,Z,Y){var _={method:K,headers:X};if(Q)_.body=Q;let z={},q=null;try{fetch(G,_).then(($)=>{q=$.status;for(let[H,W]of $.headers)z[H]=W;if(!$.ok)throw new Error($.statusText);if(Y=="json")return $.json();else if(Y=="text")return $.text();else if(Y==="arrayBuffer")return $.arrayBuffer();else if(Y==="blob")return $.blob();else if(Y==="bytes")return $.bytes();else if(Y==="formData")return $.formData();else if(Y==="none")return U({error:null,body:null,headers:z,status:q})}).then(($)=>U({error:null,body:$,headers:z,status:q})).catch(($)=>Z({error:null,body:$,headers:z,status:q}))}catch($){Z({body:null,error:$.message,headers:z,status:q})}}function b(G){var K=function(Q){let X=null;while(Q&&Q.parentNode){if("componentId"in Q.parentNode){X=Q.parentNode.componentId;break}Q=Q.parentNode}return X};return K(G)}function g(G,K,Q,X,U,Z,Y,_,z){try{let q=new WebSocket(G);return q.onopen=function(){K()},q.onclose=function($){Q($)},q.onerror=function($){console.error($),_("WebSocket error received")},q.onmessage=function($){if(typeof $.data==="string")try{if(z){if(X)X($.data);return}let H=JSON.parse($.data);if(U)U(H)}catch(H){if(z&&X)X($.data);else _(H.message)}else if($.data instanceof Blob){if(Z)Z($.data)}else if($.data instanceof ArrayBuffer){if(Y)Y($.data)}else console.error("Received unknown message type from WebSocket",$),_("Unknown message received from WebSocket")},q}catch(q){_(q.message)}}function h(G){if(G)G.close(),G=null}function u(G,K){if(K&&G&&G.readyState===WebSocket.OPEN)G.send(K)}function y(G,K,Q,X,U,Z){try{let Y=new EventSource(G);return Y.onopen=function(){K()},Y.onerror=function(){U("EventSource error received")},Y.onmessage=function(_){try{if(Z){if(Q)Q(_.data);return}let z=JSON.parse(_.data);if(X)X(z)}catch(z){if(Z&&Q)Q(_.data);else U(z.message)}},Y}catch(Y){U(Y.message)}}function m(G){if(G)G.close(),G=null}function v(G,K){if(!G.classList)G.classList=new Set;for(let Q of K)for(let X of Q.trim().split(" "))if(X)G.classList.add(X)}var n={addEventListener:(G,K,Q,X)=>{G.addEventListener(K,Q,X)},removeEventListener:(G,K,Q,X)=>{G.removeEventListener(K,Q,X)},isEqual:(G,K)=>{return G===K},getTarget:(G)=>{return G.target},parentNode:(G)=>{return G.parentNode}},t={getInlineStyle:(G,K)=>{return G.style[K]},firstChild:(G)=>{return G.firstChild},lastChild:(G)=>{return G.lastChild},getAttribute:(G,K)=>{if(K==="class")return G.className;if(K in G)return G[K];return G.getAttribute(K)},getTag:(G)=>{return G.nodeName},getTextContent:(G)=>{return G.textContent},children:(G)=>{return G.childNodes}},e={mountComponent:function(G,K,Q){return},unmountComponent:function(G){return},modelHydration:function(G){return}},GG={nextSibling:(G)=>{return G.domRef.nextSibling},createTextNode:(G)=>{return document.createTextNode(G)},createElementNS:(G,K)=>{return document.createElementNS(G,K)},appendChild:(G,K)=>{return G.appendChild(K)},replaceChild:(G,K,Q)=>{return G.replaceChild(K,Q)},removeChild:(G,K)=>{return G.removeChild(K)},createElement:(G)=>{return document.createElement(G)},addClass:(G,K)=>{if(G)K.classList.add(G)},removeClass:(G,K)=>{if(G)K.classList.remove(G)},insertBefore:(G,K,Q)=>{return G.insertBefore(K,Q)},swapDOMRefs:(G,K,Q)=>{let X=G.nextSibling;Q.insertBefore(G,K),Q.insertBefore(K,X);return},setInlineStyle:(G,K,Q)=>{var X;for(let U in G)if(X=K[U],!X)if(U in Q.style)Q.style[U]="";else Q.style.setProperty(U,"");else if(X!==G[U])if(U in Q.style)Q.style[U]=X;else Q.style.setProperty(U,X);for(let U in K){if(G&&G[U])continue;if(U in Q.style)Q.style[U]=K[U];else Q.style.setProperty(U,K[U])}return},setAttribute:(G,K,Q)=>{return G.setAttribute(K,Q)},setAttributeNS:(G,K,Q,X)=>{return G.setAttributeNS(K,Q,X)},removeAttribute:(G,K)=>{return G.removeAttribute(K)},setTextContent:(G,K)=>{G.textContent=K;return},flush:()=>{return},getRoot:function(){return document.body}};globalThis.miso={hydrationContext:t,eventContext:n,drawingContext:GG,componentContext:e,diff:J,hydrate:V,version:w,delegate:P,callBlur:C,callFocus:f,callSelect:j,callSetSelectionRange:k,eventJSON:D,fetchCore:T,eventSourceConnect:y,eventSourceClose:m,websocketConnect:g,websocketClose:h,websocketSend:u,undelegate:L,populateClass:v,getParentComponentId:b,integrityCheck:I,setDrawingContext:function(G){let K=globalThis[G].drawingContext,Q=globalThis[G].eventContext;if(!K)console.error('Custom rendering engine ("drawingContext") is not defined at globalThis[name].drawingContext',G);if(!Q)console.error('Custom event delegation ("eventContext") is not defined at globalThis[name].eventContext',G);globalThis.miso.drawingContext=K,globalThis.miso.eventContext=Q}}; +function W(G,K,Q,U){if(!G&&!K)return;else if(!G)JG(K,Q,U);else if(!K)UG(G,Q,U);else if(G.type===2&&K.type===2)QG(G,K,U);else if(G.type===0&&K.type===0){if(K.key===G.key){K.child=G.child;return}P(G,K,Q,U)}else if(G.type===1&&K.type===1)if(K.tag===G.tag&&K.key===G.key)K.domRef=G.domRef,d(G,K,U);else P(G,K,Q,U);else P(G,K,Q,U)}function QG(G,K,Q){if(G.text!==K.text)Q.setTextContent(G.domRef,K.text);K.domRef=G.domRef;return}function E(G){if(!G.child)throw new Error("'drill' called on an unmounted Component. This should never happen, please make an issue.");switch(G.child.type){case 0:return E(G.child);default:return G.child.domRef}}function J(G){switch(G.type){case 0:return E(G);default:return G.domRef}}function P(G,K,Q,U){switch(G.type){case 2:break;default:S(G);break}switch(K.type){case 2:K.domRef=U.createTextNode(K.text),U.replaceChild(Q,K.domRef,J(G));break;case 0:R(Q,1,J(G),K,U);break;case 1:R(Q,1,J(G),K,U);break}switch(G.type){case 2:break;default:D(G);break}}function UG(G,K,Q){switch(G.type){case 2:break;default:S(G);break}switch(Q.removeChild(K,J(G)),G.type){case 2:break;default:D(G);break}}function D(G){switch(XG(G),G.type){case 1:for(let K of G.children)if(K.type===1||K.type===0)D(K);break;case 0:if(G.child){if(G.child.type===1||G.child.type===0)D(G.child)}break}}function XG(G){if(G.type===1&&G.onDestroyed)G.onDestroyed();if(G.type===0)AG(G)}function YG(G){switch(G.type){case 0:if(G.onBeforeUnmounted)G.onBeforeUnmounted();break;case 1:if(G.onBeforeDestroyed)G.onBeforeDestroyed();break;default:break}}function S(G){switch(YG(G),G.type){case 1:for(let K of G.children){if(K.type===2)continue;S(K)}break;case 0:if(G.child){if(G.child.type===1||G.child.type===0)S(G.child)}break}}function d(G,K,Q){_G(G?G.props:{},K.props,K.domRef,K.ns==="svg",Q),ZG(G?G.classList:null,K.classList,K.domRef,Q),$G(G?G.css:{},K.css,K.domRef,Q),zG(G?G.children:[],K.children,K.domRef,Q),WG(K)}function ZG(G,K,Q,U){if(!G&&!K)return;if(!G){for(let X of K)U.addClass(X,Q);return}if(!K){for(let X of G)U.removeClass(X,Q);return}for(let X of G)if(!K.has(X))U.removeClass(X,Q);for(let X of K)if(!G.has(X))U.addClass(X,Q);return}function _G(G,K,Q,U,X){var Z;for(let Y in G)if(Z=K[Y],Z===void 0)if(U||!(Y in Q)||Y==="disabled")X.removeAttribute(Q,Y);else X.setAttribute(Q,Y,"");else{if(Z===G[Y]&&Y!=="checked"&&Y!=="value")continue;if(U)if(Y==="href")X.setAttributeNS(Q,"http://www.w3.org/1999/xlink","href",Z);else X.setAttribute(Q,Y,Z);else if(Y in Q&&!(Y==="list"||Y==="form"))Q[Y]=Z;else X.setAttribute(Q,Y,Z)}for(let Y in K){if(G&&G[Y])continue;if(Z=K[Y],U)if(Y==="href")X.setAttributeNS(Q,"http://www.w3.org/1999/xlink","href",Z);else X.setAttribute(Q,Y,Z);else if(Y in Q&&!(Y==="list"||Y==="form"))Q[Y]=K[Y];else X.setAttribute(Q,Y,Z)}}function $G(G,K,Q,U){U.setInlineStyle(G,K,Q)}function qG(G,K){if(G.length===0||K.length===0)return!1;for(var Q=0;Q{if(U.componentId=Z,U.child=Y,Y.parent=U,Y.type!==0){let _=J(Y);if(K===1&&Q)X.replaceChild(G,_,Q);else if(K===2)X.insertBefore(G,_,Q)}}),U.onMounted)U.onMounted(E(U))}function JG(G,K,Q){if(G.type===2)G.domRef=Q.createTextNode(G.text),Q.appendChild(K,G.domRef);else R(K,0,null,G,Q)}function N(G,K,Q,U){U.insertBefore(G,J(K),Q?J(Q):null)}function EG(G,K,Q){Q.removeChild(G,J(K))}function BG(G,K,Q,U){U.swapDOMRefs(J(G),J(K),Q)}function NG(G,K,Q,U){var X=0,Z=0,Y=G.length-1,_=K.length-1,z,q,$,H,A,F,x;for(;;){if(Z>_&&X>Y)break;if(q=K[Z],$=K[_],A=G[X],H=G[Y],X>Y)W(null,q,Q,U),N(Q,q,A,U),G.splice(Z,0,q),Z++;else if(Z>_){z=Y;while(Y>=X)EG(Q,G[Y--],U);G.splice(X,z-X+1);break}else if(A.key===q.key)W(G[X++],K[Z++],Q,U);else if(H.key===$.key)W(G[Y--],K[_--],Q,U);else if(A.key===$.key&&q.key===H.key)BG(H,A,Q,U),DG(G,X,Y),W(G[X++],K[Z++],Q,U),W(G[Y--],K[_--],Q,U);else if(A.key===$.key)N(Q,A,H.nextSibling,U),G.splice(Y,0,G.splice(X,1)[0]),W(G[Y--],K[_--],Q,U);else if(H.key===q.key)N(Q,H,A,U),G.splice(X,0,G.splice(Y,1)[0]),W(G[X++],q,Q,U),Z++;else{F=!1,z=X;while(z<=Y){if(G[z].key===q.key){F=!0,x=G[z];break}z++}if(F)G.splice(X,0,G.splice(z,1)[0]),W(G[X++],q,Q,U),N(Q,x,G[X],U),Z++;else R(Q,2,J(A),q,U),G.splice(X++,0,q),Z++,Y++}}}function DG(G,K,Q){let U=G[K];G[K]=G[Q],G[Q]=U}function L(G,K,Q,U,X){for(let Z of K)X.addEventListener(G,Z.name,function(Y){c(Y,G,Q,U,X)},Z.capture)}function C(G,K,Q,U,X){for(let Z of K)X.removeEventListener(G,Z.name,function(Y){c(Y,G,Q,U,X)},Z.capture)}function c(G,K,Q,U,X){Q(function(Z){if(Array.isArray(G))for(let Y of G)s(Y,Z,K,U,X);else s(G,Z,K,U,X)})}function s(G,K,Q,U,X){var Z=X.getTarget(G);if(Z){let Y=SG(Q,Z,X);I(G,K,Y,U,X)}}function SG(G,K,Q){var U=[];while(!Q.isEqual(G,K))if(U.unshift(K),K&&Q.parentNode(K))K=Q.parentNode(K);else return U;return U}function I(G,K,Q,U,X){if(!Q.length){if(U)console.warn('Event "'+G.type+'" did not find an event handler to dispatch on',K,G);return}else if(Q.length>1){if(K.type===2)return;else if(K.type===0){if(!K.child){if(U)throw console.error("VComp has no child property set during event delegation",K),console.error("This means the Component has not been fully mounted, this should never happen"),new Error("VComp has no .child property set during event delegation");return}return I(G,K.child,Q,U,X)}else if(K.type===1){if(X.isEqual(K.domRef,Q[0])){let Z=K.events.captures[G.type];if(Z){let Y=Z.options;if(Y.preventDefault)G.preventDefault();if(!G.captureStopped)Z.runEvent(G,K.domRef);if(Y.stopPropagation)G.captureStopped=!0}Q.splice(0,1)}for(let Z of K.children)I(G,Z,Q,U,X)}}else if(K.type===0){if(K.child)I(G,K.child,Q,U,X)}else if(K.type===1){let Z=K.events.captures[G.type];if(Z&&!G.captureStopped){let _=Z.options;if(X.isEqual(Q[0],K.domRef)){if(_.preventDefault)G.preventDefault();if(Z.runEvent(G,Q[0]),_.stopPropagation)G.captureStopped=!0}}let Y=K.events.bubbles[G.type];if(Y&&!G.captureStopped){let _=Y.options;if(X.isEqual(Q[0],K.domRef)){if(_.preventDefault)G.preventDefault();if(Y.runEvent(G,Q[0]),!_.stopPropagation)r(K.parent,G)}}else if(!G.captureStopped)r(K.parent,G)}}function r(G,K){while(G)switch(G.type){case 2:break;case 1:let Q=G.events.bubbles[K.type];if(Q){let U=Q.options;if(U.preventDefault)K.preventDefault();if(Q.runEvent(K,G.domRef),U.stopPropagation)return}G=G.parent;break;case 0:if(!G.eventPropagation)return;G=G.parent;break}}function B(G,K){if(typeof G[0]==="object"){var Q=[];for(var U=0;U0?[G[0]]:[];for(var U=1;U{return+K})}function f(G,K,Q){return n(!0,G,K,Q)}function n(G,K,Q,U){if(K.type==2){if(Q.getTag(K.domRef)!=="#text")console.warn("VText domRef not a TEXT_NODE",K),G=!1;else if(K.text!==Q.getTextContent(K.domRef))console.warn("VText node content differs",K),G=!1}else if(K.type===1){if(K.tag.toUpperCase()!==Q.getTag(K.domRef).toUpperCase())console.warn("Integrity check failed, tags differ",K.tag.toUpperCase(),Q.getTag(K.domRef)),G=!1;if("children"in K&&K.children.length!==Q.children(K.domRef).length)console.warn("Integrity check failed, children lengths differ",K,K.children,Q.children(K.domRef)),G=!1;for(let X in K.props)if(X==="href"||X==="src"){let Z=window.location.origin+"/"+K.props[X],Y=Q.getAttribute(K.domRef,X),_=K.props[X];if(Z!==Y&&_!==Y&&_+"/"!==Y&&Z+"/"!==Y)console.warn("Property "+X+" differs",K.props[X],Q.getAttribute(K.domRef,X)),G=!1}else if(X==="height"||X==="width"){if(parseFloat(K.props[X])!==parseFloat(Q.getAttribute(K.domRef,X)))console.warn("Property "+X+" differs",K.props[X],Q.getAttribute(K.domRef,X)),G=!1}else if(X==="class"||X==="className"){if(K.props[X]!==Q.getAttribute(K.domRef,"class"))console.warn("Property class differs",K.props[X],Q.getAttribute(K.domRef,"class")),G=!1}else if(K.props[X]!==Q.getAttribute(K.domRef,X))console.warn("Property "+X+" differs",K.props[X],Q.getAttribute(K.domRef,X)),G=!1;for(let X in K.css)if(X==="color"){if(o(Q.getInlineStyle(K.domRef,X)).toString()!==o(K.css[X]).toString())console.warn("Style "+X+" differs",K.css[X],Q.getInlineStyle(K.domRef,X)),G=!1}else if(K.css[X]!==Q.getInlineStyle(K.domRef,X))console.warn("Style "+X+" differs",K.css[X],Q.getInlineStyle(K.domRef,X)),G=!1;for(let X of K.children){let Z=n(G,X,Q,U);G=G&&Z}}return G}function w(G,K,Q,U,X){switch(K.type){case 0:if(!w(G,K.child,Q,U,X))return!1;break;case 2:if(Q.nodeType!==3||K.text.trim()!==Q.textContent.trim())return V(G,K,Q),!1;K.domRef=Q;break;case 1:if(Q.nodeType!==1)return V(G,K,Q),!1;K.domRef=Q,K.children=MG(K.children),p(Q,K,X);for(var Z=0;Z0?setTimeout(Q,K):Q()}function b(G,K){var Q=function(){var U=document.getElementById(G);if(U&&U.blur)U.blur()};K>0?setTimeout(Q,K):Q()}function h(G,K){var Q=function(){var U=document.getElementById(G);if(U&&typeof U.select==="function")U.select()};K>0?setTimeout(Q,K):Q()}function T(G,K,Q,U){var X=function(){var Z=document.getElementById(G);if(Z&&typeof Z.setSelectionRange==="function")Z.setSelectionRange(K,Q,"none")};U>0?setTimeout(X,U):X()}function u(G,K,Q,U,X,Z,Y){var _={method:K,headers:U};if(Q)_.body=Q;let z={},q=null;try{fetch(G,_).then(($)=>{q=$.status;for(let[H,A]of $.headers)z[H]=A;if(!$.ok)throw new Error($.statusText);if(Y=="json")return $.json();else if(Y=="text")return $.text();else if(Y==="arrayBuffer")return $.arrayBuffer();else if(Y==="blob")return $.blob();else if(Y==="bytes")return $.bytes();else if(Y==="formData")return $.formData();else if(Y==="none")return X({error:null,body:null,headers:z,status:q})}).then(($)=>X({error:null,body:$,headers:z,status:q})).catch(($)=>Z({error:null,body:$,headers:z,status:q}))}catch($){Z({body:null,error:$.message,headers:z,status:q})}}function g(G,K,Q,U,X,Z,Y,_,z){try{let q=new WebSocket(G);return q.onopen=function(){K()},q.onclose=function($){Q($)},q.onerror=function($){console.error($),_("WebSocket error received")},q.onmessage=function($){if(typeof $.data==="string")try{if(z){if(U)U($.data);return}let H=JSON.parse($.data);if(X)X(H)}catch(H){if(z&&U)U($.data);else _(H.message)}else if($.data instanceof Blob){if(Z)Z($.data)}else if($.data instanceof ArrayBuffer){if(Y)Y($.data)}else console.error("Received unknown message type from WebSocket",$),_("Unknown message received from WebSocket")},q}catch(q){_(q.message)}}function m(G){if(G)G.close(),G=null}function y(G,K){if(K&&G&&G.readyState===WebSocket.OPEN)G.send(K)}function i(G,K,Q,U,X,Z){try{let Y=new EventSource(G);return Y.onopen=function(){K()},Y.onerror=function(){X("EventSource error received")},Y.onmessage=function(_){try{if(Z){if(Q)Q(_.data);return}let z=JSON.parse(_.data);if(U)U(z)}catch(z){if(Z&&Q)Q(_.data);else X(z.message)}},Y}catch(Y){X(Y.message)}}function v(G){if(G)G.close(),G=null}function l(G,K){if(!G.classList)G.classList=new Set;for(let Q of K)for(let U of Q.trim().split(" "))if(U)G.classList.add(U)}var t={addEventListener:(G,K,Q,U)=>{G.addEventListener(K,Q,U)},removeEventListener:(G,K,Q,U)=>{G.removeEventListener(K,Q,U)},isEqual:(G,K)=>{return G===K},getTarget:(G)=>{return G.target},parentNode:(G)=>{return G.parentNode}},e={getInlineStyle:(G,K)=>{return G.style[K]},firstChild:(G)=>{return G.firstChild},lastChild:(G)=>{return G.lastChild},getAttribute:(G,K)=>{if(K==="class")return G.className;if(K in G)return G[K];return G.getAttribute(K)},getTag:(G)=>{return G.nodeName},getTextContent:(G)=>{return G.textContent},children:(G)=>{return G.childNodes}},GG={mountComponent:function(G,K,Q){return},unmountComponent:function(G){return},modelHydration:function(G){return}},KG={nextSibling:(G)=>{if(G.nextSibling)switch(G.nextSibling.type){case 0:return E(G.nextSibling);default:return G.nextSibling.domRef}return null},createTextNode:(G)=>{return document.createTextNode(G)},createElementNS:(G,K)=>{return document.createElementNS(G,K)},appendChild:(G,K)=>{return G.appendChild(K)},replaceChild:(G,K,Q)=>{return G.replaceChild(K,Q)},removeChild:(G,K)=>{return G.removeChild(K)},createElement:(G)=>{return document.createElement(G)},addClass:(G,K)=>{if(G)K.classList.add(G)},removeClass:(G,K)=>{if(G)K.classList.remove(G)},insertBefore:(G,K,Q)=>{return G.insertBefore(K,Q)},swapDOMRefs:(G,K,Q)=>{let U=G.nextSibling;Q.insertBefore(G,K),Q.insertBefore(K,U);return},setInlineStyle:(G,K,Q)=>{var U;for(let X in G)if(U=K[X],!U)if(X in Q.style)Q.style[X]="";else Q.style.setProperty(X,"");else if(U!==G[X])if(X in Q.style)Q.style[X]=U;else Q.style.setProperty(X,U);for(let X in K){if(G&&G[X])continue;if(X in Q.style)Q.style[X]=K[X];else Q.style.setProperty(X,K[X])}return},setAttribute:(G,K,Q)=>{return G.setAttribute(K,Q)},setAttributeNS:(G,K,Q,U)=>{return G.setAttributeNS(K,Q,U)},removeAttribute:(G,K)=>{return G.removeAttribute(K)},setTextContent:(G,K)=>{G.textContent=K;return},flush:()=>{return},getRoot:function(){return document.body}};globalThis.miso={hydrationContext:e,eventContext:t,drawingContext:KG,componentContext:GG,diff:W,hydrate:O,version:j,delegate:L,callBlur:b,callFocus:k,callSelect:h,callSetSelectionRange:T,eventJSON:B,fetchCore:u,eventSourceConnect:i,eventSourceClose:v,websocketConnect:g,websocketClose:m,websocketSend:y,undelegate:C,populateClass:l,integrityCheck:f,setDrawingContext:function(G){let K=globalThis[G].drawingContext,Q=globalThis[G].eventContext;if(!K)console.error('Custom rendering engine ("drawingContext") is not defined at globalThis[name].drawingContext',G);if(!Q)console.error('Custom event delegation ("eventContext") is not defined at globalThis[name].eventContext',G);globalThis.miso.drawingContext=K,globalThis.miso.eventContext=Q}}; diff --git a/src/Miso.hs b/src/Miso.hs index 3668b241e..5aebe5bf6 100644 --- a/src/Miso.hs +++ b/src/Miso.hs @@ -28,6 +28,8 @@ module Miso , startComponent , component , (+>) + , mount + , mount_ -- ** Sink , withSink , Sink @@ -132,7 +134,7 @@ miso :: Eq model => (URI -> App model action) -> JSM () miso f = withJS $ do vcomp <- f <$> getURI body <- FFI.getBody - initialize Hydrate isRoot vcomp (pure body) + initialize rootComponentId Hydrate isRoot vcomp (pure body) ----------------------------------------------------------------------------- -- | Synonym for 'startComponent'. -- @@ -181,8 +183,8 @@ initComponent => Component parent model action -> JSM (ComponentState model action) initComponent vcomp@Component {..} = do - mount <- mountElement (getMountPoint mountPoint) - initialize Draw isRoot vcomp (pure mount) + root <- mountElement (getMountPoint mountPoint) + initialize rootComponentId Draw isRoot vcomp (pure root) ---------------------------------------------------------------------------- isRoot :: Bool isRoot = True diff --git a/src/Miso/Effect.hs b/src/Miso/Effect.hs index c5b7f7ac3..b3836dafd 100644 --- a/src/Miso/Effect.hs +++ b/src/Miso/Effect.hs @@ -171,9 +171,9 @@ type Effect parent model action = RWS (ComponentInfo parent) [Schedule action] m -- -- All t'IO' is by default asynchronous, use the 'sync' function for synchronous -- execution. Beware 'sync' can block the render thread for a specific --- 'Component'. +-- t'Miso.Types.Component'. -- --- N.B. During 'Miso.Types.Component' unmounting, all effects are evaluated +-- N.B. During t'Miso.Types.Component' unmounting, all effects are evaluated -- synchronously. -- -- @since 1.9.0.0 diff --git a/src/Miso/FFI.hs b/src/Miso/FFI.hs index df8b8a546..b8424fad2 100644 --- a/src/Miso/FFI.hs +++ b/src/Miso/FFI.hs @@ -58,9 +58,6 @@ module Miso.FFI , toLocaleString , getSeconds , getMilliseconds - -- ** 'Miso.Types.Component' - , getParentComponentId - , getComponentId -- ** DOM Traversal , nextSibling , previousSibling diff --git a/src/Miso/FFI/Internal.hs b/src/Miso/FFI/Internal.hs index 8f53fa064..e24b7238b 100644 --- a/src/Miso/FFI/Internal.hs +++ b/src/Miso/FFI/Internal.hs @@ -112,9 +112,6 @@ module Miso.FFI.Internal -- * Utils , getMilliseconds , getSeconds - -- * 'Miso.Types.Component' - , getParentComponentId - , getComponentId -- * Element , files , click @@ -393,7 +390,7 @@ eventJSON x y = do moduleMiso <- jsg "miso" moduleMiso # "eventJSON" $ [x,y] ----------------------------------------------------------------------------- --- | Populate the `classList` Set on the virtual DOM. +-- | Populate the 'Miso.Html.Property.classList' Set on the virtual DOM. populateClass :: JSVal -- ^ Node @@ -516,13 +513,13 @@ undelegate mountPoint events debug callback ctx = do -- -- See [hydration](https://en.wikipedia.org/wiki/Hydration_(web_development)) -- -hydrate :: Bool -> JSVal -> JSVal -> JSM () -hydrate logLevel mountPoint vtree = void $ do +hydrate :: Bool -> JSVal -> JSVal -> JSM JSVal +hydrate logLevel mountPoint vtree = do ll <- toJSVal logLevel drawingContext <- getDrawingContext hydrationContext <- getHydrationContext moduleMiso <- jsg "miso" - void $ moduleMiso # "hydrate" $ [ll, mountPoint, vtree, hydrationContext, drawingContext] + moduleMiso # "hydrate" $ (ll, mountPoint, vtree, hydrationContext, drawingContext) ----------------------------------------------------------------------------- -- | Fails silently if the element is not found. -- @@ -747,18 +744,6 @@ getSeconds date = fromJSValUnchecked =<< do date # "getSeconds" $ ([] :: [MisoString]) ----------------------------------------------------------------------------- --- | Climb the tree, get the parent. -getParentComponentId :: JSVal -> JSM (Maybe Int) -getParentComponentId domRef = - fromJSVal =<< do - jsg "miso" # "getParentComponentId" $ [domRef] ------------------------------------------------------------------------------ --- | Get access to the 'Miso.Effect.ComponentId' --- N.B. you * must * call this on the DOMRef, otherwise, problems. --- For use in 'Miso.Event.onMounted', etc. -getComponentId :: JSVal -> JSM Int -getComponentId vtree = fromJSValUnchecked =<< vtree ! "componentId" ------------------------------------------------------------------------------ -- | Fetch next sibling DOM node -- -- @since 1.9.0.0 diff --git a/src/Miso/Html/Render.hs b/src/Miso/Html/Render.hs index 52dc0f282..6d7f7ae9b 100644 --- a/src/Miso/Html/Render.hs +++ b/src/Miso/Html/Render.hs @@ -96,8 +96,8 @@ renderBuilder (VNode ns tag attrs children) = mconcat , ns == MATHML ] -renderBuilder (VComp ns tag attrs (SomeComponent vcomp)) = - renderBuilder (VNode ns tag attrs vkids) +renderBuilder (VComp _ (SomeComponent vcomp)) = + foldMap renderBuilder vkids where #ifdef SSR vkids = [ unsafeCoerce $ (view vcomp) $ getInitialComponentModel vcomp ] @@ -113,6 +113,7 @@ renderAttrs (ClassList classes) = , fromMisoString (MS.unwords classes) , stringUtf8 "\"" ] +renderAttrs (Property "key" _) = mempty renderAttrs (Property key value) = mconcat [ fromMisoString key diff --git a/src/Miso/Hydrate.hs b/src/Miso/Hydrate.hs index cbcce369f..62eb55334 100644 --- a/src/Miso/Hydrate.hs +++ b/src/Miso/Hydrate.hs @@ -20,8 +20,9 @@ import qualified Miso.FFI.Internal as FFI import Miso.Types ----------------------------------------------------------------------------- -- | Hydration of a t'VTree' -hydrate :: LogLevel -> DOMRef -> VTree -> JSM () +hydrate :: LogLevel -> DOMRef -> VTree -> JSM Bool hydrate loggingLevel domRef vtree = do jval <- toJSVal vtree - FFI.hydrate (loggingLevel `elem` [DebugHydrate, DebugAll]) domRef jval + fromJSValUnchecked =<< + FFI.hydrate (loggingLevel `elem` [DebugHydrate, DebugAll]) domRef jval ----------------------------------------------------------------------------- diff --git a/src/Miso/Runtime.hs b/src/Miso/Runtime.hs index 89e7542db..3db818bdd 100644 --- a/src/Miso/Runtime.hs +++ b/src/Miso/Runtime.hs @@ -120,14 +120,15 @@ import Miso.Effect ( ComponentInfo(..), Sub, Sink, Effect, Schedule(.. -- | Helper function to abstract out initialization of t'Miso.Types.Component' between top-level API functions. initialize :: (Eq parent, Eq model) - => Hydrate + => ComponentId + -> Hydrate -> Bool -- ^ Is the root node being rendered? -> Component parent model action -> JSM DOMRef -- ^ Callback function is used for obtaining the t'Miso.Types.Component' 'DOMRef'. -> JSM (ComponentState model action) -initialize hydrate isRoot Component {..} getComponentMountPoint = do +initialize componentParentId hydrate isRoot Component {..} getComponentMountPoint = do Waiter {..} <- liftIO waiter componentActions <- liftIO (newIORef S.empty) let @@ -152,22 +153,25 @@ initialize hydrate isRoot Component {..} getComponentMountPoint = do #ifdef BENCH start <- if isRoot then FFI.now else pure 0 #endif - vtree <- buildVTree hydrate componentSink logLevel events (view initializedModel) + vtree <- buildVTree componentId hydrate componentSink logLevel events (view initializedModel) #ifdef BENCH end <- if isRoot then FFI.now else pure 0 when isRoot $ FFI.consoleLog $ ms (printf "buildVTree: %.3f ms" (end - start) :: String) #endif + ref <- liftIO (newIORef vtree) case hydrate of Draw -> do Diff.diff Nothing (Just vtree) componentDOMRef + pure ref Hydrate -> do - Hydrate.hydrate logLevel componentDOMRef vtree - liftIO (newIORef vtree) + when isRoot $ do + hydrated <- Hydrate.hydrate logLevel componentDOMRef vtree + when (not hydrated) $ do + newTree <- buildVTree componentId Draw componentSink logLevel events (view initializedModel) + liftIO (atomicWriteIORef ref newTree) + Diff.diff Nothing (Just newTree) componentDOMRef + pure ref componentDOMRef <# ("componentId" :: MisoString) $ componentId - componentParentId <- do - FFI.getParentComponentId componentDOMRef >>= \case - Nothing -> pure rootComponentId - Just parentId -> pure parentId componentSubThreads <- liftIO (newIORef M.empty) forM_ subs $ \sub -> do threadId <- FFI.forkJSM (sub componentSink) @@ -185,7 +189,7 @@ initialize hydrate isRoot Component {..} getComponentMountPoint = do updatedName <- liftIO $ updatedModel `seq` makeStableName updatedModel isDirty <- liftIO (readTVarIO componentIsDirty) when ((currentName /= updatedName && currentModel /= updatedModel) || isDirty) $ do - newVTree <- buildVTree Draw componentSink logLevel events (view updatedModel) + newVTree <- buildVTree componentId Draw componentSink logLevel events (view updatedModel) oldVTree <- liftIO (readIORef componentVTree) void waitForAnimationFrame Diff.diff (Just oldVTree) (Just newVTree) componentDOMRef @@ -233,14 +237,14 @@ initialize hydrate isRoot Component {..} getComponentMountPoint = do let vcomp = ComponentState { componentNotify = notify , componentEvents = events + , componentParentId = componentParentId , .. } registerComponent vcomp if isRoot then - delegator componentDOMRef componentVTree - events (logLevel `elem` [DebugEvents, DebugAll]) + delegator componentDOMRef componentVTree events (logLevel `elem` [DebugEvents, DebugAll]) else addToDelegatedEvents logLevel events forM_ initialAction componentSink @@ -820,33 +824,47 @@ killSubscribers componentId = -- process we go between the Haskell heap and the JS heap. buildVTree :: Eq model - => Hydrate + => ComponentId + -> Hydrate -> Sink action -> LogLevel -> Events -> View model action -> JSM VTree -buildVTree hydrate snk logLevel_ events_ = \case - VComp ns tag attrs (SomeComponent app) -> do +buildVTree parentId hydrate snk logLevel_ events_ = \case + VComp attrs (SomeComponent app) -> do + vcomp <- create + mountCallback <- do - FFI.syncCallback2 $ \vcomp continuation -> do - domRef <- vcomp ! ("domRef" :: MisoString) - ComponentState {..} <- initialize hydrate False app (pure domRef) + FFI.syncCallback2 $ \parent_ continuation -> do + ComponentState {..} <- initialize parentId Draw False app (pure parent_) vtree <- toJSVal =<< liftIO (readIORef componentVTree) FFI.set "parent" vcomp (Object vtree) vcompId <- toJSVal componentId - FFI.set "componentId" vcompId (Object domRef) void $ call continuation global [vcompId, vtree] + unmountCallback <- toJSVal =<< do - FFI.syncCallback1 $ \domRef -> do - componentId <- liftJSM (FFI.getComponentId domRef) + FFI.syncCallback1 $ \vcompId -> do + componentId <- fromJSValUnchecked vcompId IM.lookup componentId <$> liftIO (readIORef components) >>= \case Nothing -> pure () Just componentState -> unmount app componentState - vcomp <- createNode "vcomp" ns tag + + case hydrate of + Hydrate -> do + -- Mock .domRef for use during hydration + domRef <- toJSVal =<< create + ComponentState {..} <- initialize parentId hydrate False app (pure domRef) + vtree <- toJSVal =<< liftIO (readIORef componentVTree) + FFI.set "parent" vcomp (Object vtree) + vcompId <- toJSVal componentId + FFI.set "componentId" vcompId vcomp + FFI.set "child" vtree vcomp + Draw -> do + FFI.set "child" jsNull vcomp + setAttrs vcomp attrs snk (logLevel app) (events app) - flip (FFI.set "children") vcomp =<< toJSVal ([] :: [MisoString]) flip (FFI.set "mount") vcomp =<< toJSVal mountCallback FFI.set "unmount" unmountCallback vcomp FFI.set "eventPropagation" (eventPropagation app) vcomp @@ -858,11 +876,11 @@ buildVTree hydrate snk logLevel_ events_ = \case vchildren <- toJSVal =<< procreate vnode flip (FFI.set "children") vnode vchildren flip (FFI.set "type") vnode =<< toJSVal VNodeType - pure $ VTree vnode + pure (VTree vnode) where procreate parentVTree = do kidsViews <- forM kids $ \kid -> do - VTree child <- buildVTree hydrate snk logLevel_ events_ kid + VTree child <- buildVTree parentId hydrate snk logLevel_ events_ kid FFI.set "parent" parentVTree child pure child setNextSibling kidsViews @@ -876,7 +894,7 @@ buildVTree hydrate snk logLevel_ events_ = \case forM_ key $ \k -> FFI.set "key" (ms k) vtree FFI.set "ns" ("text" :: JSString) vtree FFI.set "text" t vtree - pure $ VTree vtree + pure (VTree vtree) ----------------------------------------------------------------------------- -- | @createNode@ -- A helper function for constructing a vtree (used for @vcomp@ and @vnode@) diff --git a/src/Miso/Types.hs b/src/Miso/Types.hs index bc361a1c7..84365f73f 100644 --- a/src/Miso/Types.hs +++ b/src/Miso/Types.hs @@ -62,6 +62,8 @@ module Miso.Types , (<---) -- ** Component mounting , (+>) + , mount + , mount_ -- ** Utils , getMountPoint , optionalAttrs @@ -83,13 +85,13 @@ module Miso.Types , ms ) where ----------------------------------------------------------------------------- -import Data.Aeson (Value, ToJSON) +import Data.Aeson (Value, ToJSON(..)) import Data.JSString (JSString) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe, isJust) import Data.String (IsString, fromString) import qualified Data.Text as T -import Language.Javascript.JSaddle (ToJSVal(toJSVal), Object(..), JSM) +import Language.Javascript.JSaddle (ToJSVal(toJSVal), Object(..), JSM, MakeObject) import Prelude ----------------------------------------------------------------------------- import Miso.Binding ((<--), (-->), (<-->), (<---), (--->), (<--->), Binding(..)) @@ -264,7 +266,7 @@ data LogLevel data View model action = VNode NS MisoString [Attribute action] [View model action] | VText (Maybe Key) MisoString - | VComp NS MisoString [Attribute action] (SomeComponent model) + | VComp [Attribute action] (SomeComponent model) deriving Functor ----------------------------------------------------------------------------- -- | Existential wrapper allowing nesting of t'Miso.Types.Component' in t'Miso.Types.Component' @@ -277,26 +279,51 @@ data SomeComponent parent -- Used in the @view@ function to mount a t'Miso.Types.Component' on any 'VNode'. -- -- @ --- div_ [ key_ "component-id" ] +> component model noop $ \\m -> +-- "component-id" +> component model noop $ \\m -> -- div_ [ id_ "foo" ] [ text (ms m) ] -- @ -- -- @since 1.9.0.0 (+>) :: forall child model action a . Eq child - => ([View model a] -> View model a) + => MisoString -> Component model child action -> View model a infixr 0 +> -(+>) mkNode vcomp = - case mkNode [] of - VNode ns tag attrs _ -> - VComp ns tag attrs - (SomeComponent vcomp) - VComp ns tag attrs vcomp_ -> - VComp ns tag attrs vcomp_ - _ -> - error "Impossible: cannot mount on a Text node" +key +> vcomp = VComp [ Property "key" (toJSON key) ] (SomeComponent vcomp) +----------------------------------------------------------------------------- +-- | t'Miso.Types.Component' mounting combinator. Takes '[Attribute a]' as arguments. +-- +-- @ +-- mount_ [ key_ "foo", onMounted Mounted ] $ component model noop $ \\m -> +-- div_ [ id_ "foo" ] [ text (ms m) ] +-- @ +-- +-- @since 1.9.0.0 +mount_ + :: Eq m + => [Attribute action] + -> Component p m a + -> View p action +mount_ attrs vcomp = VComp attrs (SomeComponent vcomp) +----------------------------------------------------------------------------- +-- | t'Miso.Types.Component' mounting combinator. +-- +-- Note: only use this if you're certain you won't be diffing two t'Miso.Types.Component' +-- against each other. Otherwise, you will need a key to distinguish between +-- the two t'Miso.Types.Component', to ensure unmounting and mounting occurs. +-- +-- @ +-- mount $ component model noop $ \\m -> +-- div_ [ id_ "foo" ] [ text (ms m) ] +-- @ +-- +-- @since 1.9.0.0 +mount + :: Eq m + => Component p m a + -> View p action +mount = mount_ [] ----------------------------------------------------------------------------- -- | DOM element namespace. data NS @@ -381,6 +408,7 @@ instance IsString (View model action) where -- Used for diffing, patching and event delegation. -- Not meant to be constructed directly, see t'Miso.Types.View' instead. newtype VTree = VTree { getTree :: Object } + deriving (MakeObject) ----------------------------------------------------------------------------- instance ToJSVal VTree where toJSVal (VTree (Object vtree)) = pure vtree diff --git a/tests/app/Main.hs b/tests/app/Main.hs index 5be6836eb..af2ad989d 100644 --- a/tests/app/Main.hs +++ b/tests/app/Main.hs @@ -91,7 +91,7 @@ main = do _ <- jsm $ do startApp $ component (0 :: Int) noop $ \_ -> - div_ [] (replicate 9999 (div_ [] +> testComponent)) + div_ [] (replicate 9999 (mount testComponent)) mountedComponents >>= (`shouldBe` 10000) #endif ----------------------------------------------------------------------------- diff --git a/ts/index.ts b/ts/index.ts index 9fe13cb91..00fe95e36 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -10,7 +10,6 @@ import { eventJSON, fetchCore, undelegate, - getParentComponentId, integrityCheck, eventSourceConnect, eventSourceClose, @@ -50,7 +49,6 @@ globalThis['miso'] = { websocketSend, undelegate, populateClass, - getParentComponentId, integrityCheck, setDrawingContext : function (name) { // dmj: this looks for a custom globally defined rendering / event context diff --git a/ts/miso.ts b/ts/miso.ts index 2a5dfb5b4..1d7d36ca6 100644 --- a/ts/miso.ts +++ b/ts/miso.ts @@ -9,7 +9,6 @@ import { callSelect, callSetSelectionRange, fetchCore, - getParentComponentId, websocketConnect, websocketClose, websocketSend, @@ -52,7 +51,6 @@ export { fetchCore, undelegate, integrityCheck, - getParentComponentId, websocketConnect, websocketClose, websocketSend, diff --git a/ts/miso/context/dom.ts b/ts/miso/context/dom.ts index 21cc7f794..c45318fe6 100644 --- a/ts/miso/context/dom.ts +++ b/ts/miso/context/dom.ts @@ -8,8 +8,11 @@ import , DOMRef , ComponentContext , VTree + , VTreeType } from '../types'; +import { drill } from '../dom'; + export const eventContext : EventContext = { addEventListener : (mount: DOMRef, event: string, listener, capture: boolean) => { mount.addEventListener(event, listener, capture); @@ -39,9 +42,9 @@ export const hydrationContext : HydrationContext = { return node.lastChild as DOMRef; }, getAttribute: (node: DOMRef, key: string) => { - if (key === 'class') return node.className; - if (key in node) return node[key]; - return node.getAttribute(key); + if (key === 'class') return node.className; + if (key in node) return node[key]; + return node.getAttribute(key); }, getTag: (node: DOMRef) => { return node.nodeName; @@ -68,7 +71,15 @@ export const componentContext : ComponentContext = { export const drawingContext : DrawingContext = { nextSibling : (node: VTree) => { - return node.domRef.nextSibling as DOMRef; + if (node.nextSibling) { + switch (node.nextSibling.type) { + case VTreeType.VComp: + return drill(node.nextSibling) as DOMRef; + default: + return node.nextSibling.domRef as DOMRef; + } + } + return null; }, createTextNode : (s: string) => { return document.createTextNode(s) as any; // dmj: hrm @@ -97,10 +108,10 @@ export const drawingContext : DrawingContext = { insertBefore : (parent: DOMRef, child: DOMRef, node: DOMRef) => { return parent.insertBefore(child, node); }, - swapDOMRefs : (a: DOMRef, b: DOMRef, p: DOMRef) => { - const tmp = a.nextSibling; - p.insertBefore(a, b); - p.insertBefore(b, tmp); + swapDOMRefs : (oLast: DOMRef, oFirst: DOMRef, p: DOMRef) => { + const tmp = oLast.nextSibling; + p.insertBefore(oLast, oFirst); + p.insertBefore(oFirst, tmp); return; }, setInlineStyle: (cCss: CSS, nCss: CSS, node: DOMRef) => { diff --git a/ts/miso/context/patch.ts b/ts/miso/context/patch.ts index 1662b5bd2..43b4d6a25 100644 --- a/ts/miso/context/patch.ts +++ b/ts/miso/context/patch.ts @@ -2,12 +2,15 @@ import { ComponentId, EventCapture, DrawingContext, - VNode, NodeId, CSS, - ComponentContext + ComponentContext, + VTree, + VTreeType, } from '../types'; +import { drill } from '../dom'; + /* This file is used to provide testing for miso-lynx, or other patch-based architectures @@ -93,8 +96,16 @@ export const componentContext : ComponentContext = { }; export const patchDrawingContext : DrawingContext = { - nextSibling : (node: VNode) => { - return node.nextSibling.domRef; + nextSibling : (node: VTree) => { + if (node.nextSibling) { + switch (node.nextSibling.type) { + case VTreeType.VComp: + const drilled = drill (node.nextSibling); + return drilled ? drilled : null; + default: + return node.nextSibling.domRef as NodeId; + } + } }, createTextNode : (value : string) => { const nodeId: number = nextNodeId (); diff --git a/ts/miso/dom.ts b/ts/miso/dom.ts index ced4bf0bd..009fb1494 100644 --- a/ts/miso/dom.ts +++ b/ts/miso/dom.ts @@ -1,23 +1,19 @@ -import { Class, DrawingContext, CSS, VNode, VText, VComp, ComponentId, VTree, Props, VTreeType } from './types'; +import { Class, DrawingContext, CSS, VNode, VText, VComp, ComponentId, VTree, Props, VTreeType, OP } from './types'; /* virtual-dom diffing algorithm, applies patches as detected */ export function diff(c: VTree, n: VTree, parent: T, context: DrawingContext): void { - if (!c && !n) - return; - else if (!c) - create(n, parent, context); - else if (!n) - destroy(c, parent, context); + if (!c && !n) return; + else if (!c) create(n, parent, context); + else if (!n) destroy(c, parent, context); else if (c.type === VTreeType.VText && n.type === VTreeType.VText) { diffVText(c, n, context); } else if (c.type === VTreeType.VComp && n.type === VTreeType.VComp) { - if (n.tag === c.tag && n.key === c.key) { - n.domRef = c.domRef; - diffAttrs(c, n, context); - } else { - replace(c, n, parent, context); + if (n.key === c.key) { + n.child = c.child; + return; } + replace(c, n, parent, context); } else if (c.type === VTreeType.VNode && n.type === VTreeType.VNode) { if (n.tag === c.tag && n.key === c.key) { @@ -28,7 +24,7 @@ export function diff(c: VTree, n: VTree, parent: T, context: DrawingCon } } else - replace(c, n, parent, context); + replace(c, n, parent, context); } function diffVText(c: VText, n: VText, context : DrawingContext): void { @@ -37,6 +33,27 @@ function diffVText(c: VText, n: VText, context : DrawingContext): vo return; } +//c.child should never be null +export function drill(c: VComp): T { + if (!c.child) throw new Error ("'drill' called on an unmounted Component. This should never happen, please make an issue."); + switch (c.child.type) { + case VTreeType.VComp: + return drill (c.child) + default: + return c.child.domRef; + } +} + +// Extract DOM reference from any VTree (handles VComp drilling) +function getDOMRef(tree: VTree): T { + switch (tree.type) { + case VTreeType.VComp: + return drill(tree); + default: + return tree.domRef; + } +} + // replace everything function function replace(c: VTree, n: VTree, parent: T, context : DrawingContext): void { @@ -50,16 +67,15 @@ function replace(c: VTree, n: VTree, parent: T, context : DrawingContex } switch (n.type) { case VTreeType.VText: - switch (c.type) { - default: - n.domRef = context.createTextNode(n.text); - context.replaceChild(parent, n.domRef, c.domRef); - break; - } + n.domRef = context.createTextNode(n.text); + context.replaceChild(parent, n.domRef, getDOMRef(c)); break; - default: - context.replaceChild(parent, createElement(n, context), c.domRef as T); - break; + case VTreeType.VComp: + createElement(parent, OP.REPLACE, getDOMRef(c), n, context); + break; + case VTreeType.VNode: + createElement(parent, OP.REPLACE, getDOMRef(c), n, context); + break; } // step 3: call destroyed hooks, call created hooks switch (c.type) { @@ -82,7 +98,7 @@ function destroy(c: VTree, parent: T, context: DrawingContext): void { break; } // step 2: destroy - context.removeChild(parent, c.domRef); + context.removeChild(parent, getDOMRef(c)); // step 3: invoke post-hooks for vnode and vcomp switch (c.type) { case VTreeType.VText: @@ -96,10 +112,20 @@ function destroy(c: VTree, parent: T, context: DrawingContext): void { // ** recursive calls to hooks function callDestroyedRecursive(c: VNode | VComp): void { callDestroyed(c); - for (const child of c.children) { - if (child.type === VTreeType.VNode || child.type === VTreeType.VComp) { - callDestroyedRecursive(child); - } + switch (c.type) { + case VTreeType.VNode: + for (const child of c.children) { + if (child.type === VTreeType.VNode || child.type === VTreeType.VComp) { + callDestroyedRecursive(child); + } + } + break; + case VTreeType.VComp: + if (c.child) { + if (c.child.type === VTreeType.VNode || c.child.type === VTreeType.VComp) + callDestroyedRecursive(c.child); + } + break; } } @@ -123,19 +149,28 @@ function callBeforeDestroyed(c: VNode | VComp): void { function callBeforeDestroyedRecursive(c: VNode | VComp): void { callBeforeDestroyed(c); - for (const child of c.children) - if (child.type === VTreeType.VNode || child.type === VTreeType.VComp) - callBeforeDestroyedRecursive(child); + switch (c.type) { + case VTreeType.VNode: + for (const child of c.children) { + if (child.type === VTreeType.VText) continue; + callBeforeDestroyedRecursive(child); + } + break; + case VTreeType.VComp: + if (c.child) { + if (c.child.type === VTreeType.VNode || c.child.type === VTreeType.VComp) + callBeforeDestroyedRecursive(c.child); + } + break; + } } -export function diffAttrs(c: VNode | VComp | null, n: VNode | VComp, context: DrawingContext): void { +export function diffAttrs(c: VNode | null, n: VNode, context: DrawingContext): void { diffProps(c ? c.props : {}, n.props, n.domRef, n.ns === 'svg', context); diffClass(c ? c.classList : null, n.classList, n.domRef, context); diffCss(c ? c.css : {}, n.css, n.domRef, context); - if (n.type === VTreeType.VNode) { - diffChildren(c ? c.children : [], n.children, n.domRef, context); - drawCanvas(n); - } + diffChildren(c ? c.children : [], n.children, n.domRef, context); + drawCanvas(n); } export function diffClass (c: Class, n: Class, domRef: T, context: DrawingContext): void { @@ -252,7 +287,7 @@ function diffChildren(cs: Array>, ns: Array>, parent: T, co } } -function populateDomRef(c: VComp | VNode, context: DrawingContext): void { +function populateDomRef(c: VNode, context: DrawingContext): void { if (c.ns === 'svg') { c.domRef = context.createElementNS('http://www.w3.org/2000/svg', c.tag); } else if (c.ns === 'mathml') { @@ -263,34 +298,54 @@ function populateDomRef(c: VComp | VNode, context: DrawingContext): } /* used in hydrate.ts */ -export function callCreated(n: VComp | VNode, context: DrawingContext): T { +export function callCreated(parent: T, n: VComp | VNode, context: DrawingContext): void { switch (n.type) { case VTreeType.VComp: - if (n.onBeforeMounted) n.onBeforeMounted(); - mountComponent(n, context); + mountComponent(parent, OP.APPEND, null, n, context); break; case VTreeType.VNode: if (n.onCreated) n.onCreated(n.domRef); break; } - return n.domRef; } -function createElement(n: VComp | VNode, context: DrawingContext): T { +function createElement(parent : T, op: OP, replacing : T | null, n: VTree, context: DrawingContext): void { switch (n.type) { + case VTreeType.VText: + n.domRef = context.createTextNode(n.text); + switch (op) { + case OP.INSERT_BEFORE: + context.insertBefore(parent, n.domRef, replacing); + break; + case OP.APPEND: + context.appendChild(parent, n.domRef); + break; + case OP.REPLACE: + context.replaceChild(parent, n.domRef, replacing); + break; + } + break; case VTreeType.VComp: - if (n.onBeforeMounted) n.onBeforeMounted(); - populateDomRef(n, context); - mountComponent(n, context); + mountComponent(parent, op, replacing, n as VComp, context); break; case VTreeType.VNode: if (n.onBeforeCreated) n.onBeforeCreated(); populateDomRef(n, context); if (n.onCreated) n.onCreated(n.domRef); + diffAttrs(null, n, context); + switch (op) { + case OP.INSERT_BEFORE: + context.insertBefore(parent, n.domRef, replacing); + break; + case OP.APPEND: + context.appendChild(parent, n.domRef); + break; + case OP.REPLACE: + context.replaceChild(parent, n.domRef, replacing); + break; + } break; } - diffAttrs(null, n, context); - return n.domRef; } /* draw the canvas if you need to */ @@ -301,30 +356,58 @@ function drawCanvas (c: VNode) { // unmount components function unmountComponent(c: VComp): void { - if (c.onUnmounted) c.onUnmounted(c.domRef); - c.unmount(c.domRef); + if (c.onUnmounted) c.onUnmounted((drill(c))); + c.unmount(c.componentId); } // mounts vcomp by calling into Haskell side. // unmount is handled with pre-destroy recursive hooks -function mountComponent(obj: VComp, context: DrawingContext): void { - obj.mount(obj, (componentId: ComponentId, componentTree: VNode) => { - // mount() gives us the VTree from the Haskell side, so we just attach it here - // to tie the knot (attach to both vdom and real dom). - obj.children.push(componentTree); - context.appendChild(obj.domRef, componentTree.domRef); - if (obj.onMounted) obj.onMounted(obj.domRef); +function mountComponent(parent: T, op : OP, replacing: T | null, n: VComp, context: DrawingContext): void { + if (n.onBeforeMounted) n.onBeforeMounted(); + + // 'mount()' should be executed synchronously, including its callback function argument. + n.mount(parent, (componentId: ComponentId, componentTree: VTree) => { + // mount() gives us the VTree from the Haskell side + n.componentId = componentId; + n.child = componentTree; + componentTree.parent = n; + if (componentTree.type !== VTreeType.VComp) { + // Handle DOM placement for non-VComp child nodes + const childDomRef = getDOMRef(componentTree); + if (op === OP.REPLACE && replacing) { + context.replaceChild(parent, childDomRef, replacing); + } else if (op === OP.INSERT_BEFORE) { + context.insertBefore(parent, childDomRef, replacing); + } + // For OP.APPEND, this happens naturally in mount() + } }); + + if (n.onMounted) n.onMounted(drill(n)); } -// creates nodes on virtual and dom (vtext, vcomp, vnode) -function create(obj: VTree, parent: T, context: DrawingContext): void { - if (obj.type === VTreeType.VText) { - obj.domRef = context.createTextNode(obj.text); - context.appendChild(parent, obj.domRef); + +// Creates nodes on virtual dom (vtext, vcomp, vnode) +function create(n: VTree, parent: T, context: DrawingContext): void { + if (n.type === VTreeType.VText) { + n.domRef = context.createTextNode(n.text); + context.appendChild(parent, n.domRef); } else { - context.appendChild(parent, createElement(obj, context)); + createElement(parent, OP.APPEND, null, n, context); } } + +function insertBefore(parent: T, n: VTree, o: VTree | null, context: DrawingContext): void { + context.insertBefore(parent, getDOMRef(n), o ? getDOMRef(o) : null); +} + +function removeChild(parent: T, n: VTree, context: DrawingContext): void { + context.removeChild(parent, getDOMRef(n)); +} + +function swapDOMRef(oLast: VTree, oFirst: VTree, parent: T, context: DrawingContext): void { + context.swapDOMRefs(getDOMRef(oLast), getDOMRef(oFirst), parent); +} + /* Child reconciliation algorithm, inspired by kivi and Bobril */ function syncChildren(os: Array>, ns: Array>, parent: T, context: DrawingContext): void { var oldFirstIndex: number = 0, @@ -360,7 +443,7 @@ function syncChildren(os: Array>, ns: Array>, parent: T, co /* insertBefore's semantics will append a node if the second argument provided is `null` or `undefined`. Otherwise, it will insert node.domRef before oLast.domRef. */ - context.insertBefore(parent, nFirst.domRef, oFirst ? oFirst.domRef : null); + insertBefore(parent, nFirst, oFirst, context); os.splice(newFirstIndex, 0, nFirst); newFirstIndex++; } /* No more new nodes, delete all remaining nodes in old list @@ -370,7 +453,7 @@ function syncChildren(os: Array>, ns: Array>, parent: T, co else if (newFirstIndex > newLastIndex) { tmp = oldLastIndex; while (oldLastIndex >= oldFirstIndex) { - context.removeChild(parent, os[oldLastIndex--].domRef); + removeChild(parent, os[oldLastIndex--], context); } os.splice(oldFirstIndex, tmp - oldFirstIndex + 1); break; @@ -392,7 +475,7 @@ function syncChildren(os: Array>, ns: Array>, parent: T, co -> [ c b a ] <- new children */ else if (oFirst.key === nLast.key && nFirst.key === oLast.key) { - context.swapDOMRefs(oLast.domRef, oFirst.domRef, parent); + swapDOMRef(oLast, oFirst, parent, context); swap>(os, oldFirstIndex, oldLastIndex); diff(os[oldFirstIndex++], ns[newFirstIndex++], parent, context); diff(os[oldLastIndex--], ns[newLastIndex--], parent, context); @@ -408,7 +491,7 @@ function syncChildren(os: Array>, ns: Array>, parent: T, co and now we happy path */ else if (oFirst.key === nLast.key) { /* insertAfter */ - context.insertBefore(parent, oFirst.domRef, context.nextSibling(oLast)); + insertBefore(parent, oFirst, oLast.nextSibling, context); /* swap positions in old vdom */ os.splice(oldLastIndex, 0, os.splice(oldFirstIndex, 1)[0]); diff(os[oldLastIndex--], ns[newLastIndex--], parent, context); @@ -422,7 +505,7 @@ function syncChildren(os: Array>, ns: Array>, parent: T, co and now we happy path */ else if (oLast.key === nFirst.key) { /* insertAfter */ - context.insertBefore(parent, oLast.domRef, oFirst.domRef); + insertBefore(parent, oLast, oFirst, context); /* swap positions in old vdom */ os.splice(oldFirstIndex, 0, os.splice(oldLastIndex, 1)[0]); diff(os[oldFirstIndex++], nFirst, parent, context); @@ -463,7 +546,7 @@ function syncChildren(os: Array>, ns: Array>, parent: T, co /* optionally perform `diff` here */ diff(os[oldFirstIndex++], nFirst, parent, context); /* Swap DOM references */ - context.insertBefore(parent, node.domRef, os[oldFirstIndex].domRef); + insertBefore(parent, node, os[oldFirstIndex], context); /* increment counters */ newFirstIndex++; } /* If new key was *not* found in the old map this means it must now be created, example below @@ -477,18 +560,10 @@ function syncChildren(os: Array>, ns: Array>, parent: T, co -> [ b e a j ] <- new children ^ */ else { - switch (nFirst.type) { - case VTreeType.VText: - nFirst.domRef = context.createTextNode(nFirst.text); - context.insertBefore(parent, nFirst.domRef, oFirst.domRef); - break; - default: - context.insertBefore(parent, createElement(nFirst, context), oFirst.domRef); - break; - } - os.splice(oldFirstIndex++, 0, nFirst); - newFirstIndex++; - oldLastIndex++; + createElement(parent, OP.INSERT_BEFORE, getDOMRef(oFirst), nFirst, context); + os.splice(oldFirstIndex++, 0, nFirst); + newFirstIndex++; + oldLastIndex++; } } } diff --git a/ts/miso/event.ts b/ts/miso/event.ts index bbdfc7081..2e81a871f 100644 --- a/ts/miso/event.ts +++ b/ts/miso/event.ts @@ -95,7 +95,21 @@ function delegateEvent ( return; } /* stack not length 1, recurse */ else if (stack.length > 1) { - if (obj.type === VTreeType.VComp || obj.type === VTreeType.VNode) { + if (obj.type === VTreeType.VText) { + return; + } + else if (obj.type === VTreeType.VComp) { + if (!obj.child) { + if (debug) { + console.error('VComp has no child property set during event delegation', obj); + console.error('This means the Component has not been fully mounted, this should never happen'); + throw new Error('VComp has no .child property set during event delegation'); + } + return; + } + return delegateEvent(event, obj.child, stack, debug, context); + } + else if (obj.type === VTreeType.VNode) { if (context.isEqual(obj.domRef, stack[0])) { const eventObj: EventObject = obj.events.captures[event.type]; if (eventObj) { @@ -111,17 +125,19 @@ function delegateEvent ( } stack.splice(0,1); } - for (const child of obj.children) { - if (child.type === VTreeType.VComp || child.type === VTreeType.VNode) { - if (context.isEqual(child.domRef, stack[0])) { - delegateEvent(event, child, stack, debug, context); - } - } + for (const child of obj.children) { + delegateEvent(event, child, stack, debug, context); } } } else { + /* stack.length === 1, we're at the target */ + if (obj.type === VTreeType.VComp) { + /* VComp doesn't have events directly, delegate to its child */ + if (obj.child) { + delegateEvent(event, obj.child, stack, debug, context); + } + } else if (obj.type === VTreeType.VNode) { /* captures run first */ - if (obj.type === VTreeType.VNode) { const eventCaptureObj: EventObject = obj.events.captures[event.type]; if (eventCaptureObj && !event['captureStopped']) { const options: Options = eventCaptureObj.options; diff --git a/ts/miso/hydrate.ts b/ts/miso/hydrate.ts index ac81111e9..5e790650c 100644 --- a/ts/miso/hydrate.ts +++ b/ts/miso/hydrate.ts @@ -1,5 +1,5 @@ -import { callCreated, diffAttrs } from './dom'; -import { DrawingContext, HydrationContext, VTree, VComp, VText, DOMRef, VTreeType } from './types'; +import { callCreated } from './dom'; +import { DrawingContext, HydrationContext, VTree, VText, DOMRef, VTreeType } from './types'; /* prerendering / hydration / isomorphic support */ function collapseSiblingTextNodes(vs: Array>): Array> { @@ -14,64 +14,21 @@ function collapseSiblingTextNodes(vs: Array>): Array return adjusted; } -/* function to determine if