Fixed redraw algorithm for lists with common nodes in different positions; other fixes.
@@ -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 this site as a standalone HTML file? + +What a weird thing to ask... sure you can: [here](/H3_DeveloperGuide.htm)!
@@ -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);
@@ -8044,7 +8044,7 @@ </ul>
</div> <div id="footer"> - <p><span class="copy"></span> Fabio Cevasco – May 25, 2020</p> + <p><span class="copy"></span> Fabio Cevasco – May 29, 2020</p> <p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe"><span class="hastyscribe"></span></a></p> </div> </div>
@@ -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 (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], - }); + 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 {@@ -487,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); } } }@@ -574,6 +601,7 @@ }
async start() { const processPath = async (data) => { + $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&
@@ -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 (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], - }); + 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 {@@ -487,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); } } }@@ -574,6 +601,7 @@ }
async start() { const processPath = async (data) => { + $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&
@@ -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 (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], - }); + 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 {@@ -487,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); } } }@@ -574,6 +601,7 @@ }
async start() { const processPath = async (data) => { + $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&
@@ -28,8 +28,10 @@ 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 link to download guide in overview.md +readmeData = readmeData.replace(/### Can I download(\n|\r|.)+/gm, ""); // Remove badges and copy to overview.md const overviewData = readmeData.replace(/[^\*]+\*\*\*\s+/m, "");