all repos — h3 @ 4d04306829cad6cc2589180b9447c0e602e99cbe

A tiny, extremely minimalist JavaScript microframework.

Merge branch 'dev'
h3rald h3rald@h3rald.com
Sat, 30 May 2020 10:02:58 +0200
commit

4d04306829cad6cc2589180b9447c0e602e99cbe

parent

0b56e1cc8b2b5fcf268679becd21cf0fc4f77964

M README.mdREADME.md

@@ -11,7 +11,7 @@ **H3** is a microframework to build client-side single-page applications (SPAs) in modern JavaScript.

H3 is also: -- **tiny**, under [750 sloc](https://github.com/h3rald/h3/blob/master/h3.js). +- **tiny**, under [800 sloc](https://github.com/h3rald/h3/blob/master/h3.js). - **modern**, in the sense that it runs only in modern browsers (latest versions of Chrome, Firefox, Edge & similar). - **easy** to learn, its API is comprised of only six methods and two properties.

@@ -19,7 +19,7 @@ ### I'm sold! Where can I get it?

Here, look, it's just one file: -<a href="https://raw.githubusercontent.com/h3rald/h3/v0.6.0/h3.js" target="_blank" class="button primary">Download v0.6.0 (Furtive Ferengi)</a> +<a href="https://raw.githubusercontent.com/h3rald/h3/v0.7.0/h3.js" target="_blank" class="button primary">Download v0.7.0 (Gory Gorn)</a> Yes there is also a [NPM package](https://www.npmjs.com/package/@h3rald/h3) if you want to use it with WebPack and similar, but let me repeat: _it's just one file_.

@@ -46,3 +46,6 @@ ### What if something is broken?

Go fix it! Or at least open an issue on the [Github repo](https://github.com/h3rald/h3), pleasy. +### Can I download a copy of all the documentation as a standalone HTML file? + +What a weird thing to ask... sure you can: [here](https://h3.js.org/H3_DeveloperGuide.htm)!
M __tests__/vnode.js__tests__/vnode.js

@@ -217,6 +217,15 @@ expect(node.constructor).toEqual(HTMLSpanElement);

expect(container.childNodes[0].constructor).toEqual(HTMLDivElement); }); + it("should provide redraw method to detect position changes in child nodes", () => { + const v1 = h3("ul", [h3("li.a"), h3("li.b"), h3("li.c"), h3("li.d")]); + const v2 = h3("ul", [h3("li.c"), h3("li.b"), h3("li.a"), h3("li.d")]); + const n = v1.render(); + expect(n.childNodes[0].classList[0]).toEqual("a"); + v1.redraw({ node: n, vnode: v2 }); + expect(n.childNodes[0].classList[0]).toEqual("c"); + }); + it("should provide redraw method to detect changed nodes if they have different node types", () => { const oldvnode = h3("span.c", { title: "b" }); const newvnode = h3({ type: "#text", value: "test" });

@@ -255,7 +264,7 @@ expect(node.childNodes[0].getAttribute("id")).toEqual("aaa");

expect(node.childNodes[1].getAttribute("id")).toEqual("ccc"); }); - it("should provide a redraw method able to detect specific changes to style, data, value, attributes and eventListeners", () => { + it("should provide a redraw method able to detect specific changes to style, data, value, attributes, $onrender and eventListeners", () => { const fn = () => false; const oldvnode = h3("input", { style: "margin: auto;",

@@ -275,6 +284,7 @@ label: "test",

placeholder: "test", onkeydown: () => true, onkeypress: () => false, + $onrender: () => true, onhover: () => true, }); const container = document.createElement("div");

@@ -295,7 +305,10 @@

it("should provide a redraw method able to detect changes in child content", () => { const v1 = h3("ul", [h3("li", "a"), h3("li", "b")]); const n1 = v1.render(); - const v2 = h3("ul", { $html: "<li>a</li><li>b</li>" }); + const v2 = h3("ul", { + $html: "<li>a</li><li>b</li>", + $onrender: (node) => node.classList.add("test"), + }); const v3 = h3("ul", [h3("li", "a")]); const v4 = h3("ul", [h3("li", "b")]); const n2 = v2.render();

@@ -304,6 +317,7 @@ expect(n2.childNodes[0].childNodes[0].data).toEqual(

n1.childNodes[0].childNodes[0].data ); v1.redraw({ node: n1, vnode: v2 }); + expect(n1.classList[0]).toEqual("test"); expect(v1).toEqual(v2); v3.redraw({ node: n3, vnode: v4 }); expect(v3).toEqual(v4);
M docs/H3_DeveloperGuide.htmdocs/H3_DeveloperGuide.htm

@@ -7245,6 +7245,7 @@ <li><a href="#Hello,-World?">Hello, World?</a></li>

<li><a href="#Something-more-complex?">Something more complex?</a></li> <li><a href="#Can-I-use-it-then,-no-strings-attached?">Can I use it then, no strings attached?</a></li> <li><a href="#What-if-something-is-broken?">What if something is broken?</a></li> + <li><a href="#Can-I-download-a-copy-of-all-the-documentation-as-a-standalone-HTML-file?">Can I download a copy of all the documentation as a standalone HTML file?</a></li> </ul> </li> <li><a href="#Quick-Start">Quick Start</a>

@@ -7324,7 +7325,7 @@

<p>H3 is also:</p> <ul> -<li><strong>tiny</strong>, under <a href="https://github.com/h3rald/h3/blob/master/h3.js">750 sloc</a>.</li> +<li><strong>tiny</strong>, under <a href="https://github.com/h3rald/h3/blob/master/h3.js">800 sloc</a>.</li> <li><strong>modern</strong>, in the sense that it runs only in modern browsers (latest versions of Chrome, Firefox, Edge &amp; similar).</li> <li><strong>easy</strong> to learn, its API is comprised of only six methods and two properties.</li> </ul>

@@ -7335,7 +7336,7 @@ <h3>I&rsquo;m sold! Where can I get it?<a href="#document-top" title="Go to top"></a></h3>

<p>Here, look, it&rsquo;s just one file:</p> -<p><a href="https://raw.githubusercontent.com/h3rald/h3/v0.6.0/h3.js" target="_blank" class="button primary">Download v0.6.0 (Furtive Ferengi)</a></p> +<p><a href="https://raw.githubusercontent.com/h3rald/h3/v0.7.0/h3.js" target="_blank" class="button primary">Download v0.7.0 (Gory Gorn)</a></p> <p>Yes there is also a <a href="https://www.npmjs.com/package/@h3rald/h3">NPM package</a> if you want to use it with WebPack and similar, but let me repeat: <em>it&rsquo;s just one file</em>.</p>

@@ -7364,6 +7365,11 @@ <a name="What-if-something-is-broken?"></a>

<h3>What if something is broken?<a href="#document-top" title="Go to top"></a></h3> <p>Go fix it! Or at least open an issue on the <a href="https://github.com/h3rald/h3">Github repo</a>, pleasy.</p> + +<a name="Can-I-download-a-copy-of-all-the-documentation-as-a-standalone-HTML-file?"></a> +<h3>Can I download a copy of all the documentation as a standalone HTML file?<a href="#document-top" title="Go to top"></a></h3> + +<p>What a weird thing to ask&hellip; sure you can: <a href="https://h3.js.org/H3_DeveloperGuide.htm">here</a>!</p> <a name="Quick-Start"></a> <h2>Quick Start<a href="#document-top" title="Go to top"></a></h2>

@@ -7507,19 +7513,19 @@

<a name="Route-Components"></a> <h4>Route Components<a href="#document-top" title="Go to top"></a></h4> -<p>A route components is a top-level component specified to handle a specific route. Unlike ordinary components, route components:</p> +<p>A route component is a top-level component that handles a route. Unlike ordinary components, route components:</p> <ul> -<li>may have a dedicated <em>setup</em> (after the route component is added to the DOM) and <em>teardown</em> phase (after the route component is removed from the DOM and the new route component is loaded).</li> +<li>may have a dedicated <em>setup</em> (after the route component is added to the DOM) and <em>teardown</em> phase (after the route component is removed from the DOM and before the new route component is loaded).</li> <li>may have built-in local state, initialized during setup and (typically) destroyed during teardown.</li> </ul> -<p>Route components are stll created using ordinary function returning a VNode, but you can optionally define a <strong>setup</strong> and a <strong>teardown</strong> async methods on them (Functions are Objects in JavaScript after all&hellip;) to be executed during the corresponding phase.</p> +<p>Route components are stll created using ordinary function returning a VNode, but you can optionally define a <strong>setup</strong> and a <strong>teardown</strong> async methods on them (functions are objects in JavaScript after all&hellip;) to be executed during each corresponding phase.</p> <p>Note that: -* Both the <strong>setup</strong> method take an object as a parameter, representing the component state. Such object will be empty the first time the <strong>setup</strong> method is called for a given component, but it may contain properties not removed during teardowns. -* The <strong>teardown</strong> method can return an object, which will be retained as component state. If however nothing is returned, the component state is deleted. +* Both the <strong>setup</strong> method take an object as a parameter, representing the component state. Such object will be empty the first time the <strong>setup</strong> method is called for a given component, but it may contain properties not removed during subsequent teardowns. +* The <strong>teardown</strong> method can return an object, which will be retained as component state. If however nothing is returned, the component state object is emptied. * Both methods can be asynchronous, in which case H3 will wait for their completion before proceeding.</p> <a name="How-everything-works..."></a>

@@ -7693,8 +7699,8 @@ h3("a.logo.col-sm-1", { href: "#/" }, [

h3("img", { alt: "H3", src: "images/h3.svg" }), ]), h3("div.version.col-sm.col-md", [ - h3("div.version-number", "v0.6.0"), - h3("div.version-label", "“Furtive Ferengi“"), + h3("div.version-number", "v0.7.0"), + h3("div.version-label", "“Gory Gorn“"), ]), h3("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);

@@ -8044,7 +8050,7 @@ </ul>

</div> <div id="footer"> - <p><span class="copy"></span> Fabio Cevasco &ndash; May 23, 2020</p> + <p><span class="copy"></span> Fabio Cevasco &ndash; May 30, 2020</p> <p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe"><span class="hastyscribe"></span></a></p> </div> </div>
M docs/example/assets/js/h3.jsdocs/example/assets/js/h3.js

@@ -1,5 +1,5 @@

/** - * H3 v0.6.0 "Furtive Ferengi" + * H3 v0.7.0 "Gory Gorn" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * This source code is licensed under the MIT license found in the

@@ -60,7 +60,7 @@ return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1);

}; let $onrenderCallbacks = []; -const selectorRegex = /^([a-z0-9:_=-]+)(#[a-z0-9:_=-]+)?(\..+)?$/i; +const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; // Virtual Node Implementation with HyperScript-like syntax class VNode {

@@ -436,20 +436,45 @@ }

} return map; } - // Map positions of newvnode children in relation to oldvnode children - let newmap = mapChildren(newvnode, oldvnode); - // Map positions of oldvnode children in relation to newvnode children - let oldmap = mapChildren(oldvnode, newvnode); - let notFoundInOld = newmap.indexOf(-1); - let notFoundInNew = oldmap.indexOf(-1); - if (newmap.length === oldmap.length && notFoundInNew >= 0) { - // Something changed - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1 || oldmap[i] === -1) { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], - }); + let newmap, oldmap, notFoundInNew, notFoundInOld; + const remap = () => { + // Map positions of newvnode children in relation to oldvnode children + newmap = mapChildren(newvnode, oldvnode); + // Map positions of oldvnode children in relation to newvnode children + oldmap = mapChildren(oldvnode, newvnode); + notFoundInOld = newmap.indexOf(-1); + notFoundInNew = oldmap.indexOf(-1); + }; + remap(); + if (newmap.length === oldmap.length) { + if (equal(newmap, oldmap) && notFoundInNew >= 0) { + // Something changed (some nodes are different at the same position) + for (let i = 0; i < newmap.length; i++) { + if (newmap[i] === -1 || oldmap[i] === -1) { + oldvnode.children[i].redraw({ + node: node.childNodes[i], + vnode: newvnode.children[i], + }); + } + } + } else { + // Nodes in different position (maps have same nodes) + let index = 0; + while (!equal(oldmap, [...Array(oldmap.length).keys()])) { + if (newmap[index] !== index) { + const child = node.childNodes[newmap[index]]; + node.removeChild(child); + node.insertBefore(child, node.childNodes[index]); + const cnode = oldvnode.children[newmap[index]]; + oldvnode.children = oldvnode.children.filter( + (c) => !equal(c, cnode) + ); + oldvnode.children.splice(index, 0, cnode); + remap(); + index = 0; + } else { + index++; + } } } } else {

@@ -459,9 +484,15 @@ if (notFoundInNew >= 0) {

const childOfNew = newvnode.children.length > notFoundInNew && newvnode.children[notFoundInNew]; - const childofOld = oldvnode.children[notFoundInNew]; - if (childOfNew && childofOld && childofOld.type === childOfNew.type) { - // Optimization to avoid removing nodes of the same type + const childOfOld = oldvnode.children[notFoundInNew]; + if ( + childOfNew && + childOfOld && + childOfOld.type === childOfNew.type && + childOfNew.children.length === 0 && + childOfNew.children.length === 0 + ) { + // Optimization to avoid removing simple nodes of the same type oldvnode.children[notFoundInNew].redraw({ node: node.childNodes[notFoundInNew], vnode: newvnode.children[notFoundInNew],

@@ -481,16 +512,18 @@ 0,

newvnode.children[notFoundInOld] ); } - newmap = mapChildren(newvnode, oldvnode); - oldmap = mapChildren(oldvnode, newvnode); - notFoundInNew = oldmap.indexOf(-1); - notFoundInOld = newmap.indexOf(-1); + remap(); } } + // $onrender + if (!equal(oldvnode.$onrender, newvnode.$onrender)) { + oldvnode.$onrender = newvnode.$onrender; + } // innerHTML - if (newvnode.$html) { + if (oldvnode.$html !== newvnode.$html) { node.innerHTML = newvnode.$html; oldvnode.$html = newvnode.$html; + oldvnode.$onrender && oldvnode.$onrender(node); } } }

@@ -568,6 +601,7 @@ }

async start() { const processPath = async (data) => { + $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&
M docs/js/app.jsdocs/js/app.js

@@ -44,8 +44,8 @@ h3("a.logo.col-sm-1", { href: "#/" }, [

h3("img", { alt: "H3", src: "images/h3.svg" }), ]), h3("div.version.col-sm.col-md", [ - h3("div.version-number", "v0.6.0"), - h3("div.version-label", "“Furtive Ferengi“"), + h3("div.version-number", "v0.7.0"), + h3("div.version-label", "“Gory Gorn“"), ]), h3("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);
M docs/js/h3.jsdocs/js/h3.js

@@ -1,5 +1,5 @@

/** - * H3 v0.6.0 "Furtive Ferengi" + * H3 v0.7.0 "Gory Gorn" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * This source code is licensed under the MIT license found in the

@@ -60,7 +60,7 @@ return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1);

}; let $onrenderCallbacks = []; -const selectorRegex = /^([a-z0-9:_=-]+)(#[a-z0-9:_=-]+)?(\..+)?$/i; +const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; // Virtual Node Implementation with HyperScript-like syntax class VNode {

@@ -436,20 +436,45 @@ }

} return map; } - // Map positions of newvnode children in relation to oldvnode children - let newmap = mapChildren(newvnode, oldvnode); - // Map positions of oldvnode children in relation to newvnode children - let oldmap = mapChildren(oldvnode, newvnode); - let notFoundInOld = newmap.indexOf(-1); - let notFoundInNew = oldmap.indexOf(-1); - if (newmap.length === oldmap.length && notFoundInNew >= 0) { - // Something changed - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1 || oldmap[i] === -1) { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], - }); + let newmap, oldmap, notFoundInNew, notFoundInOld; + const remap = () => { + // Map positions of newvnode children in relation to oldvnode children + newmap = mapChildren(newvnode, oldvnode); + // Map positions of oldvnode children in relation to newvnode children + oldmap = mapChildren(oldvnode, newvnode); + notFoundInOld = newmap.indexOf(-1); + notFoundInNew = oldmap.indexOf(-1); + }; + remap(); + if (newmap.length === oldmap.length) { + if (equal(newmap, oldmap) && notFoundInNew >= 0) { + // Something changed (some nodes are different at the same position) + for (let i = 0; i < newmap.length; i++) { + if (newmap[i] === -1 || oldmap[i] === -1) { + oldvnode.children[i].redraw({ + node: node.childNodes[i], + vnode: newvnode.children[i], + }); + } + } + } else { + // Nodes in different position (maps have same nodes) + let index = 0; + while (!equal(oldmap, [...Array(oldmap.length).keys()])) { + if (newmap[index] !== index) { + const child = node.childNodes[newmap[index]]; + node.removeChild(child); + node.insertBefore(child, node.childNodes[index]); + const cnode = oldvnode.children[newmap[index]]; + oldvnode.children = oldvnode.children.filter( + (c) => !equal(c, cnode) + ); + oldvnode.children.splice(index, 0, cnode); + remap(); + index = 0; + } else { + index++; + } } } } else {

@@ -459,9 +484,15 @@ if (notFoundInNew >= 0) {

const childOfNew = newvnode.children.length > notFoundInNew && newvnode.children[notFoundInNew]; - const childofOld = oldvnode.children[notFoundInNew]; - if (childOfNew && childofOld && childofOld.type === childOfNew.type) { - // Optimization to avoid removing nodes of the same type + const childOfOld = oldvnode.children[notFoundInNew]; + if ( + childOfNew && + childOfOld && + childOfOld.type === childOfNew.type && + childOfNew.children.length === 0 && + childOfNew.children.length === 0 + ) { + // Optimization to avoid removing simple nodes of the same type oldvnode.children[notFoundInNew].redraw({ node: node.childNodes[notFoundInNew], vnode: newvnode.children[notFoundInNew],

@@ -481,16 +512,18 @@ 0,

newvnode.children[notFoundInOld] ); } - newmap = mapChildren(newvnode, oldvnode); - oldmap = mapChildren(oldvnode, newvnode); - notFoundInNew = oldmap.indexOf(-1); - notFoundInOld = newmap.indexOf(-1); + remap(); } } + // $onrender + if (!equal(oldvnode.$onrender, newvnode.$onrender)) { + oldvnode.$onrender = newvnode.$onrender; + } // innerHTML - if (newvnode.$html) { + if (oldvnode.$html !== newvnode.$html) { node.innerHTML = newvnode.$html; oldvnode.$html = newvnode.$html; + oldvnode.$onrender && oldvnode.$onrender(node); } } }

@@ -568,6 +601,7 @@ }

async start() { const processPath = async (data) => { + $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&
M docs/md/key-concepts.mddocs/md/key-concepts.md

@@ -80,16 +80,16 @@

#### Route Components -A route components is a top-level component specified to handle a specific route. Unlike ordinary components, route components: +A route component is a top-level component that handles a route. Unlike ordinary components, route components: -* may have a dedicated *setup* (after the route component is added to the DOM) and *teardown* phase (after the route component is removed from the DOM and the new route component is loaded). +* may have a dedicated *setup* (after the route component is added to the DOM) and *teardown* phase (after the route component is removed from the DOM and before the new route component is loaded). * may have built-in local state, initialized during setup and (typically) destroyed during teardown. -Route components are stll created using ordinary function returning a VNode, but you can optionally define a **setup** and a **teardown** async methods on them (Functions are Objects in JavaScript after all...) to be executed during the corresponding phase. +Route components are stll created using ordinary function returning a VNode, but you can optionally define a **setup** and a **teardown** async methods on them (functions are objects in JavaScript after all...) to be executed during each corresponding phase. Note that: -* Both the **setup** method take an object as a parameter, representing the component state. Such object will be empty the first time the **setup** method is called for a given component, but it may contain properties not removed during teardowns. -* The **teardown** method can return an object, which will be retained as component state. If however nothing is returned, the component state is deleted. +* Both the **setup** method take an object as a parameter, representing the component state. Such object will be empty the first time the **setup** method is called for a given component, but it may contain properties not removed during subsequent teardowns. +* The **teardown** method can return an object, which will be retained as component state. If however nothing is returned, the component state object is emptied. * Both methods can be asynchronous, in which case H3 will wait for their completion before proceeding. ### How everything works...
M docs/md/overview.mddocs/md/overview.md

@@ -4,7 +4,7 @@ **H3** is a microframework to build client-side single-page applications (SPAs) in modern JavaScript.

H3 is also: -- **tiny**, under [750 sloc](https://github.com/h3rald/h3/blob/master/h3.js). +- **tiny**, under [800 sloc](https://github.com/h3rald/h3/blob/master/h3.js). - **modern**, in the sense that it runs only in modern browsers (latest versions of Chrome, Firefox, Edge & similar). - **easy** to learn, its API is comprised of only six methods and two properties.

@@ -12,7 +12,7 @@ ### I'm sold! Where can I get it?

Here, look, it's just one file: -<a href="https://raw.githubusercontent.com/h3rald/h3/v0.6.0/h3.js" target="_blank" class="button primary">Download v0.6.0 (Furtive Ferengi)</a> +<a href="https://raw.githubusercontent.com/h3rald/h3/v0.7.0/h3.js" target="_blank" class="button primary">Download v0.7.0 (Gory Gorn)</a> Yes there is also a [NPM package](https://www.npmjs.com/package/@h3rald/h3) if you want to use it with WebPack and similar, but let me repeat: _it's just one file_.

@@ -39,3 +39,6 @@ ### What if something is broken?

Go fix it! Or at least open an issue on the [Github repo](https://github.com/h3rald/h3), pleasy. +### Can I download a copy of all the documentation as a standalone HTML file? + +What a weird thing to ask... sure you can: [here](https://h3.js.org/H3_DeveloperGuide.htm)!
M docs/md/tutorial.mddocs/md/tutorial.md

@@ -118,8 +118,8 @@ h3("a.logo.col-sm-1", { href: "#/" }, [

h3("img", { alt: "H3", src: "images/h3.svg" }), ]), h3("div.version.col-sm.col-md", [ - h3("div.version-number", "v0.6.0"), - h3("div.version-label", "“Furtive Ferengi“"), + h3("div.version-number", "v0.7.0"), + h3("div.version-label", "“Gory Gorn“"), ]), h3("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);
M h3.jsh3.js

@@ -1,5 +1,5 @@

/** - * H3 v0.6.0 "Furtive Ferengi" + * H3 v0.7.0 "Gory Gorn" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * This source code is licensed under the MIT license found in the

@@ -60,7 +60,7 @@ return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1);

}; let $onrenderCallbacks = []; -const selectorRegex = /^([a-z0-9:_=-]+)(#[a-z0-9:_=-]+)?(\..+)?$/i; +const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; // Virtual Node Implementation with HyperScript-like syntax class VNode {

@@ -436,20 +436,45 @@ }

} return map; } - // Map positions of newvnode children in relation to oldvnode children - let newmap = mapChildren(newvnode, oldvnode); - // Map positions of oldvnode children in relation to newvnode children - let oldmap = mapChildren(oldvnode, newvnode); - let notFoundInOld = newmap.indexOf(-1); - let notFoundInNew = oldmap.indexOf(-1); - if (newmap.length === oldmap.length && notFoundInNew >= 0) { - // Something changed - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1 || oldmap[i] === -1) { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], - }); + let newmap, oldmap, notFoundInNew, notFoundInOld; + const remap = () => { + // Map positions of newvnode children in relation to oldvnode children + newmap = mapChildren(newvnode, oldvnode); + // Map positions of oldvnode children in relation to newvnode children + oldmap = mapChildren(oldvnode, newvnode); + notFoundInOld = newmap.indexOf(-1); + notFoundInNew = oldmap.indexOf(-1); + }; + remap(); + if (newmap.length === oldmap.length) { + if (equal(newmap, oldmap) && notFoundInNew >= 0) { + // Something changed (some nodes are different at the same position) + for (let i = 0; i < newmap.length; i++) { + if (newmap[i] === -1 || oldmap[i] === -1) { + oldvnode.children[i].redraw({ + node: node.childNodes[i], + vnode: newvnode.children[i], + }); + } + } + } else { + // Nodes in different position (maps have same nodes) + let index = 0; + while (!equal(oldmap, [...Array(oldmap.length).keys()])) { + if (newmap[index] !== index) { + const child = node.childNodes[newmap[index]]; + node.removeChild(child); + node.insertBefore(child, node.childNodes[index]); + const cnode = oldvnode.children[newmap[index]]; + oldvnode.children = oldvnode.children.filter( + (c) => !equal(c, cnode) + ); + oldvnode.children.splice(index, 0, cnode); + remap(); + index = 0; + } else { + index++; + } } } } else {

@@ -459,9 +484,15 @@ if (notFoundInNew >= 0) {

const childOfNew = newvnode.children.length > notFoundInNew && newvnode.children[notFoundInNew]; - const childofOld = oldvnode.children[notFoundInNew]; - if (childOfNew && childofOld && childofOld.type === childOfNew.type) { - // Optimization to avoid removing nodes of the same type + const childOfOld = oldvnode.children[notFoundInNew]; + if ( + childOfNew && + childOfOld && + childOfOld.type === childOfNew.type && + childOfNew.children.length === 0 && + childOfNew.children.length === 0 + ) { + // Optimization to avoid removing simple nodes of the same type oldvnode.children[notFoundInNew].redraw({ node: node.childNodes[notFoundInNew], vnode: newvnode.children[notFoundInNew],

@@ -481,16 +512,18 @@ 0,

newvnode.children[notFoundInOld] ); } - newmap = mapChildren(newvnode, oldvnode); - oldmap = mapChildren(oldvnode, newvnode); - notFoundInNew = oldmap.indexOf(-1); - notFoundInOld = newmap.indexOf(-1); + remap(); } } + // $onrender + if (!equal(oldvnode.$onrender, newvnode.$onrender)) { + oldvnode.$onrender = newvnode.$onrender; + } // innerHTML - if (newvnode.$html) { + if (oldvnode.$html !== newvnode.$html) { node.innerHTML = newvnode.$html; oldvnode.$html = newvnode.$html; + oldvnode.$onrender && oldvnode.$onrender(node); } } }

@@ -568,6 +601,7 @@ }

async start() { const processPath = async (data) => { + $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&
M package.jsonpackage.json

@@ -1,7 +1,7 @@

{ "name": "@h3rald/h3", - "version": "0.6.0", - "versionName": "Furtive Ferengi", + "version": "0.7.0", + "versionName": "Gory Gorn", "description": "A tiny, extremely minimalist JavaScript microframework.", "main": "h3.js", "scripts": {
M scripts/release.jsscripts/release.js

@@ -28,7 +28,6 @@ readmeData = readmeData.replace(

/Download v\d+\.\d+\.\d+ \([^)]+\)/, `Download v${pkg.version} (${pkg.versionName})` ); -readmeData = readmeData.replace(/### Can I download(\n|.)+/gm, ""); fs.writeFileSync(readme, readmeData); // Remove badges and copy to overview.md