all repos — h3 @ 0c0e83cd257e58656aaabc4e95840e55a2e1ffe9

A tiny, extremely minimalist JavaScript microframework.

Merge branch 'dev'
h3rald h3rald@h3rald.com
Sat, 31 Oct 2020 18:58:42 +0100
commit

0c0e83cd257e58656aaabc4e95840e55a2e1ffe9

parent

a8f5b97212c140f64dbae263b0deeb48c4c7c302

A .prettierignore

@@ -0,0 +1,1 @@

+*.md
M README.mdREADME.md

@@ -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.10.0/h3.js" target="_blank" class="button primary">Download v0.10.0 (Jittery Jem'Hadar)</a> +<a href="https://raw.githubusercontent.com/h3rald/h3/v0.11.0/h3.js" target="_blank" class="button primary">Download v0.11.0 (Keen Klingon)</a> <small>Or get the minified version [here](https://raw.githubusercontent.com/h3rald/h3/v0.9.0/h3.min.js).</small>
M __tests__/router.js__tests__/router.js

@@ -1,4 +1,4 @@

-const mod = require("../h3.js"); +const mod = require('../h3.js'); const h3 = mod.h3; const h = mod.h;

@@ -8,129 +8,135 @@ let count = 0;

let result = 0; const setCount = () => { - count = count + 2; - h3.dispatch("count/set", count); + count = count + 2; + h3.dispatch('count/set', count); }; -let hash = "#/c2"; +let hash = '#/c2'; const mockLocation = { - get hash() { - return hash; - }, - set hash(value) { - const event = new CustomEvent("hashchange"); - event.oldURL = hash; - event.newURL = value; - hash = value; - window.dispatchEvent(event); - }, + get hash() { + return hash; + }, + set hash(value) { + const event = new CustomEvent('hashchange'); + event.oldURL = hash; + event.newURL = value; + hash = value; + window.dispatchEvent(event); + }, }; const C1 = () => { - const parts = h3.route.parts; - const content = Object.keys(parts).map((key) => - h("li", `${key}: ${parts[key]}`) - ); - return h("ul.c1", content); + const parts = h3.route.parts; + const content = Object.keys(parts).map((key) => h('li', `${key}: ${parts[key]}`)); + return h('ul.c1', content); }; const C2 = () => { - const params = h3.route.params; - const content = Object.keys(params).map((key) => - h("li", `${key}: ${params[key]}`) - ); - return h("ul.c2", content); + const params = h3.route.params; + const content = Object.keys(params).map((key) => h('li', `${key}: ${params[key]}`)); + return h('ul.c2', content); }; -describe("h3 (Router)", () => { - beforeEach(async () => { - const preStart = () => (preStartCalled = true); - const postStart = () => (postStartCalled = true); - await h3.init({ - routes: { - "/c1/:a/:b/:c": C1, - "/c2": C2, - }, - location: mockLocation, - preStart: preStart, - postStart: postStart, - }); +describe('h3 (Router)', () => { + beforeEach(async () => { + const preStart = () => (preStartCalled = true); + const postStart = () => (postStartCalled = true); + await h3.init({ + routes: { + '/c1/:a/:b/:c': C1, + '/c2': C2, + }, + location: mockLocation, + preStart: preStart, + postStart: postStart, }); + }); - it("should support routing configuration at startup", () => { - expect(h3.route.def).toEqual("/c2"); - }); + it('should support routing configuration at startup', () => { + expect(h3.route.def).toEqual('/c2'); + }); - it("should support pre/post start hooks", () => { - expect(preStartCalled).toEqual(true); - expect(postStartCalled).toEqual(true); - }); + it('should support pre/post start hooks', () => { + expect(preStartCalled).toEqual(true); + expect(postStartCalled).toEqual(true); + }); - it("should support the capturing of parts within the current route", (done) => { - const sub = h3.on("$redraw", () => { - expect( - document.body.childNodes[0].childNodes[1].textContent - ).toEqual("b: 2"); - sub(); - done(); - }); - mockLocation.hash = "#/c1/1/2/3"; + it('should support the capturing of parts within the current route', (done) => { + const sub = h3.on('$redraw', () => { + expect(document.body.childNodes[0].childNodes[1].textContent).toEqual('b: 2'); + sub(); + done(); }); + mockLocation.hash = '#/c1/1/2/3'; + }); - it("should expose a navigateTo method to navigate to another path", (done) => { - const sub = h3.on("$redraw", () => { - expect( - document.body.childNodes[0].childNodes[1].textContent - ).toEqual("test2: 2"); - sub(); - done(); - }); - h3.navigateTo("/c2", { test1: 1, test2: 2 }); + it('should expose a navigateTo method to navigate to another path', (done) => { + const sub = h3.on('$redraw', () => { + expect(document.body.childNodes[0].childNodes[1].textContent).toEqual('test2: 2'); + sub(); + done(); }); + h3.navigateTo('/c2', { test1: 1, test2: 2 }); + }); - it("should throw an error if no route matches", async () => { - try { - await h3.init({ - element: document.body, - routes: { - "/c1/:a/:b/:c": () => h("div"), - "/c2": () => h("div"), - }, - }); - } catch (e) { - expect(e.message).toMatch(/No route matches/); - } + it('should throw an error if no route matches', async () => { + try { + await h3.init({ + element: document.body, + routes: { + '/c1/:a/:b/:c': () => h('div'), + '/c2': () => h('div'), + }, + }); + } catch (e) { + expect(e.message).toMatch(/No route matches/); + } + }); + + it('should execute setup and teardown methods', (done) => { + let redraws = 0; + C1.setup = (cstate) => { + cstate.result = cstate.result || 0; + cstate.sub = h3.on('count/set', (state, count) => { + cstate.result = count * count; + }); + }; + C1.teardown = (cstate) => { + cstate.sub(); + result = cstate.result; + return { result: cstate.result }; + }; + const sub = h3.on('$redraw', () => { + redraws++; + setCount(); + setCount(); + if (redraws === 1) { + expect(count).toEqual(4); + expect(result).toEqual(0); + h3.navigateTo('/c2'); + } + if (redraws === 2) { + expect(count).toEqual(8); + expect(result).toEqual(16); + delete C1.setup; + delete C1.teardown; + sub(); + done(); + } }); + h3.navigateTo('/c1/a/b/c'); + }); - it("should execute setup and teardown methods", (done) => { - let redraws = 0; - C1.setup = (cstate) => { - cstate.result = cstate.result || 0; - cstate.sub = h3.on("count/set", (state, count) => { - cstate.result = count * count; - }); - }; - C1.teardown = (cstate) => { - cstate.sub(); - result = cstate.result; - return { result: cstate.result }; - }; - const sub = h3.on("$redraw", () => { - redraws++; - setCount(); - setCount(); - if (redraws === 1) { - expect(count).toEqual(4); - expect(result).toEqual(0); - h3.navigateTo("/c2"); - } - if (redraws === 2) { - expect(count).toEqual(8); - expect(result).toEqual(16); - delete C1.setup; - delete C1.teardown; - sub(); - done(); - } - }); - h3.navigateTo("/c1/a/b/c"); + it('should not navigate if setup method returns false', (done) => { + let redraws = 0; + const oldroute = h3.route; + C1.setup = () => { + return false; + }; + h3.on('$navigation', (state, data) => { + expect(data).toEqual(null); + expect(h3.route).toEqual(oldroute); + done(); }); + h3.navigateTo('/c1/a/b/c'); + }); });
M __tests__/vnode.js__tests__/vnode.js

@@ -1,400 +1,417 @@

-const mod = require("../h3.js"); +const mod = require('../h3.js'); const h3 = mod.h3; const h = mod.h; -describe("VNode", () => { - it("should provide a from method to initialize itself from an object", () => { - const fn = () => true; - const obj = { - id: "test", - type: "input", - value: "AAA", - $html: "", - data: { a: "1", b: "2" }, - eventListeners: { click: fn }, - children: [], - props: { title: "test" }, - classList: ["a1", "a2"], - style: "padding: 2px", - }; - const vnode1 = h("br"); - vnode1.from(obj); - const vnode2 = h("input#test.a1.a2", { - value: "AAA", - $html: "", - data: { a: "1", b: "2" }, - onclick: fn, - title: "test", - style: "padding: 2px", - }); - expect(vnode1).toEqual(vnode2); +describe('VNode', () => { + it('should provide a from method to initialize itself from an object', () => { + const fn = () => true; + const obj = { + id: 'test', + type: 'input', + value: 'AAA', + $html: '', + data: { a: '1', b: '2' }, + eventListeners: { click: fn }, + children: [], + props: { title: 'test' }, + classList: ['a1', 'a2'], + style: 'padding: 2px', + }; + const vnode1 = h('br'); + vnode1.from(obj); + const vnode2 = h('input#test.a1.a2', { + value: 'AAA', + $html: '', + data: { a: '1', b: '2' }, + onclick: fn, + title: 'test', + style: 'padding: 2px', }); + expect(vnode1).toEqual(vnode2); + }); - it("should provide a render method able to render textual nodes", () => { - const createTextNode = jest.spyOn(document, "createTextNode"); - const vnode = h({ type: "#text", value: "test" }); - const node = vnode.render(); - expect(createTextNode).toHaveBeenCalledWith("test"); - expect(node.constructor).toEqual(Text); - }); + it('should provide a render method able to render textual nodes', () => { + const createTextNode = jest.spyOn(document, 'createTextNode'); + const vnode = h({ type: '#text', value: 'test' }); + const node = vnode.render(); + expect(createTextNode).toHaveBeenCalledWith('test'); + expect(node.constructor).toEqual(Text); + }); - it("should provide a render method able to render simple element nodes", () => { - const createElement = jest.spyOn(document, "createElement"); - const vnode = h("br"); - const node = vnode.render(); - expect(createElement).toHaveBeenCalledWith("br"); - expect(node.constructor).toEqual(HTMLBRElement); - }); + it('should provide a render method able to render simple element nodes', () => { + const createElement = jest.spyOn(document, 'createElement'); + const vnode = h('br'); + const node = vnode.render(); + expect(createElement).toHaveBeenCalledWith('br'); + expect(node.constructor).toEqual(HTMLBRElement); + }); - it("should provide a render method able to render element nodes with props and classes", () => { - const createElement = jest.spyOn(document, "createElement"); - const vnode = h("span.test1.test2", { title: "test", falsy: false }); - const node = vnode.render(); - expect(createElement).toHaveBeenCalledWith("span"); - expect(node.constructor).toEqual(HTMLSpanElement); - expect(node.getAttribute("title")).toEqual("test"); - expect(node.classList.value).toEqual("test1 test2"); + it('should provide a render method able to render element nodes with props and classes', () => { + const createElement = jest.spyOn(document, 'createElement'); + const vnode = h('span.test1.test2', { title: 'test', falsy: false }); + const node = vnode.render(); + expect(createElement).toHaveBeenCalledWith('span'); + expect(node.constructor).toEqual(HTMLSpanElement); + expect(node.getAttribute('title')).toEqual('test'); + expect(node.classList.value).toEqual('test1 test2'); + }); + + it('should provide a render method able to render element nodes with children', () => { + const vnode = h('ul', [h('li', 'test1'), h('li', 'test2')]); + const createElement = jest.spyOn(document, 'createElement'); + const node = vnode.render(); + expect(createElement).toHaveBeenCalledWith('ul'); + expect(createElement).toHaveBeenCalledWith('li'); + expect(node.constructor).toEqual(HTMLUListElement); + expect(node.childNodes.length).toEqual(2); + expect(node.childNodes[1].constructor).toEqual(HTMLLIElement); + expect(node.childNodes[0].childNodes[0].data).toEqual('test1'); + }); + + it('should handle boolean props when redrawing', () => { + const vnode1 = h('input', { type: 'checkbox', checked: true }); + const node = vnode1.render(); + expect(node.checked).toEqual(true); + const vnode = h('input', { type: 'checkbox', checked: false }); + vnode1.redraw({ node, vnode }); + expect(node.checked).toEqual(false); + }); + + it('should handle falsy props when redrawing', () => { + const vnode1 = h('test-element', { q: 1 }); + const node = vnode1.render(); + expect(node.q).toEqual(1); + const vnode = h('test-element', { q: 0 }); + vnode1.redraw({ node, vnode }); + expect(node.q).toEqual(0); + expect(vnode1.props.q).toEqual(0); + }); + + it('should handle non-string props as properties and not create attributes', () => { + const v = h('div', { + test: true, + obj: { a: 1, b: 2 }, + arr: [1, 2, 3], + num: 2.3, + str: 'test', + s: '', + title: 'testing!', + value: false, }); + const v2 = h('div', { + test: true, + obj: { a: 1, b: 2 }, + arr: [1, 2, 3], + s: '', + title: 'testing!', + value: 'true', + }); + const n = v.render(); + expect(n.test).toEqual(true); + expect(n.obj).toEqual({ a: 1, b: 2 }); + expect(n.arr).toEqual([1, 2, 3]); + expect(n.num).toEqual(2.3); + expect(n.str).toEqual('test'); + expect(n.getAttribute('str')).toEqual('test'); + expect(n.s).toEqual(''); + expect(n.getAttribute('s')).toEqual(''); + expect(n.title).toEqual('testing!'); + expect(n.getAttribute('title')).toEqual('testing!'); + expect(n.value).toEqual(undefined); + expect(n.getAttribute('value')).toEqual(null); + v.redraw({ node: n, vnode: v2 }); + expect(n.getAttribute('value')).toEqual('true'); + v2.value = null; + v.redraw({ node: n, vnode: v2 }); + expect(n.getAttribute('value')).toEqual(''); + }); - it("should provide a render method able to render element nodes with children", () => { - const vnode = h("ul", [h("li", "test1"), h("li", "test2")]); - const createElement = jest.spyOn(document, "createElement"); - const node = vnode.render(); - expect(createElement).toHaveBeenCalledWith("ul"); - expect(createElement).toHaveBeenCalledWith("li"); - expect(node.constructor).toEqual(HTMLUListElement); - expect(node.childNodes.length).toEqual(2); - expect(node.childNodes[1].constructor).toEqual(HTMLLIElement); - expect(node.childNodes[0].childNodes[0].data).toEqual("test1"); - }); + it('should provide a render method able to render element nodes with a value', () => { + let vnode = h('input', { value: 'test' }); + const createElement = jest.spyOn(document, 'createElement'); + let node = vnode.render(); + expect(createElement).toHaveBeenCalledWith('input'); + expect(node.constructor).toEqual(HTMLInputElement); + expect(node.value).toEqual('test'); + vnode = h('input', { value: null }); + node = vnode.render(); + expect(node.value).toEqual(''); + vnode = h('test', { value: 123 }); + node = vnode.render(); + expect(node.getAttribute('value')).toEqual('123'); + expect(node.value).toEqual(undefined); + }); - it("should handle boolean props when redrawing", () => { - const vnode1 = h("input", { type: "checkbox", checked: true }); - const node = vnode1.render(); - expect(node.checked).toEqual(true); - const vnode = h("input", { type: "checkbox", checked: false }); - vnode1.redraw({ node, vnode }); - expect(node.checked).toEqual(false); + it('should provide a render method able to render element nodes with event handlers', () => { + const handler = () => { + console.log('test'); + }; + const vnode = h('button', { onclick: handler }); + const button = document.createElement('button'); + const createElement = jest.spyOn(document, 'createElement').mockImplementationOnce(() => { + return button; }); + const addEventListener = jest.spyOn(button, 'addEventListener'); + const node = vnode.render(); + expect(createElement).toHaveBeenCalledWith('button'); + expect(node.constructor).toEqual(HTMLButtonElement); + expect(addEventListener).toHaveBeenCalledWith('click', handler); + }); - it("should handle non-string props as properties and not create attributes", () => { - const v = h("div", { - test: true, - obj: { a: 1, b: 2 }, - arr: [1, 2, 3], - num: 2.3, - str: "test", - s: "", - title: "testing!", - value: false, - }); - const v2 = h("div", { - test: true, - obj: { a: 1, b: 2 }, - arr: [1, 2, 3], - s: "", - title: "testing!", - value: "true", - }); - const n = v.render(); - expect(n.test).toEqual(true); - expect(n.obj).toEqual({ a: 1, b: 2 }); - expect(n.arr).toEqual([1, 2, 3]); - expect(n.num).toEqual(2.3); - expect(n.str).toEqual("test"); - expect(n.getAttribute("str")).toEqual("test"); - expect(n.s).toEqual(""); - expect(n.getAttribute("s")).toEqual(""); - expect(n.title).toEqual("testing!"); - expect(n.getAttribute("title")).toEqual("testing!"); - expect(n.value).toEqual(undefined); - expect(n.getAttribute("value")).toEqual(null); - v.redraw({ node: n, vnode: v2 }); - expect(n.getAttribute("value")).toEqual("true"); - v2.value = null; - v.redraw({ node: n, vnode: v2 }); - expect(n.getAttribute("value")).toEqual(""); + it('it should provide a render method able to render elements with special props', () => { + const vnode = h('div', { + id: 'test', + style: 'margin: auto;', + data: { test: 'aaa' }, + $html: '<p>Hello!</p>', }); + const createElement = jest.spyOn(document, 'createElement'); + const node = vnode.render(); + expect(createElement).toHaveBeenCalledWith('div'); + expect(node.constructor).toEqual(HTMLDivElement); + expect(node.style.cssText).toEqual('margin: auto;'); + expect(node.id).toEqual('test'); + expect(node.dataset['test']).toEqual('aaa'); + expect(node.childNodes[0].textContent).toEqual('Hello!'); + }); - it("should provide a render method able to render element nodes with a value", () => { - let vnode = h("input", { value: "test" }); - const createElement = jest.spyOn(document, "createElement"); - let node = vnode.render(); - expect(createElement).toHaveBeenCalledWith("input"); - expect(node.constructor).toEqual(HTMLInputElement); - expect(node.value).toEqual("test"); - vnode = h("input", { value: null }); - node = vnode.render(); - expect(node.value).toEqual(""); - vnode = h("test", { value: 123 }); - node = vnode.render(); - expect(node.getAttribute("value")).toEqual("123"); - expect(node.value).toEqual(undefined); - }); + it('should provide a redraw method that is able to add new DOM nodes', () => { + const oldvnode = h('div#test', h('span')); + const newvnodeNoChildren = h('div'); + const newvnode = h('div', [h('span#a'), h('span')]); + const node = oldvnode.render(); + const span = node.childNodes[0]; + oldvnode.redraw({ node: node, vnode: newvnodeNoChildren }); + expect(oldvnode.children.length).toEqual(0); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(oldvnode).toEqual(newvnode); + expect(oldvnode.children.length).toEqual(2); + expect(node.childNodes.length).toEqual(2); + expect(node.childNodes[0].id).toEqual('a'); + expect(span).toEqual(node.childNodes[1]); + }); - it("should provide a render method able to render element nodes with event handlers", () => { - const handler = () => { - console.log("test"); - }; - const vnode = h("button", { onclick: handler }); - const button = document.createElement("button"); - const createElement = jest - .spyOn(document, "createElement") - .mockImplementationOnce(() => { - return button; - }); - const addEventListener = jest.spyOn(button, "addEventListener"); - const node = vnode.render(); - expect(createElement).toHaveBeenCalledWith("button"); - expect(node.constructor).toEqual(HTMLButtonElement); - expect(addEventListener).toHaveBeenCalledWith("click", handler); - }); + it('should provide a redraw method that is able to remove existing DOM nodes', () => { + let oldvnode = h('div', [h('span#a'), h('span')]); + let newvnode = h('div', [h('span')]); + let node = oldvnode.render(); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(oldvnode).toEqual(newvnode); + expect(oldvnode.children.length).toEqual(1); + expect(node.childNodes.length).toEqual(1); + oldvnode = h('div.test-children', [h('span.a'), h('span.b')]); + node = oldvnode.render(); + newvnode = h('div.test-children', [h('div.c')]); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(oldvnode).toEqual(newvnode); + expect(oldvnode.children.length).toEqual(1); + expect(node.childNodes.length).toEqual(1); + expect(oldvnode.children[0].classList[0]).toEqual('c'); + }); - it("it should provide a render method able to render elements with special props", () => { - const vnode = h("div", { - id: "test", - style: "margin: auto;", - data: { test: "aaa" }, - $html: "<p>Hello!</p>", - }); - const createElement = jest.spyOn(document, "createElement"); - const node = vnode.render(); - expect(createElement).toHaveBeenCalledWith("div"); - expect(node.constructor).toEqual(HTMLDivElement); - expect(node.style.cssText).toEqual("margin: auto;"); - expect(node.id).toEqual("test"); - expect(node.dataset["test"]).toEqual("aaa"); - expect(node.childNodes[0].textContent).toEqual("Hello!"); - }); + it('should provide a redraw method that is able to figure out differences in children', () => { + const oldvnode = h('div', [h('span', 'a'), h('span'), h('span', 'b')]); + const newvnode = h('div', [h('span', 'a'), h('span', 'c'), h('span', 'b')]); + const node = oldvnode.render(); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(node.childNodes[1].textContent).toEqual('c'); + }); - it("should provide a redraw method that is able to add new DOM nodes", () => { - const oldvnode = h("div#test", h("span")); - const newvnodeNoChildren = h("div"); - const newvnode = h("div", [h("span#a"), h("span")]); - const node = oldvnode.render(); - const span = node.childNodes[0]; - oldvnode.redraw({ node: node, vnode: newvnodeNoChildren }); - expect(oldvnode.children.length).toEqual(0); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(oldvnode).toEqual(newvnode); - expect(oldvnode.children.length).toEqual(2); - expect(node.childNodes.length).toEqual(2); - expect(node.childNodes[0].id).toEqual("a"); - expect(span).toEqual(node.childNodes[1]); - }); + it('should provide a redraw method that is able to figure out differences in existing children', () => { + const oldvnode = h('div', [h('span.test', 'a'), h('span.test', 'b'), h('span.test', 'c')]); + const newvnode = h('div', [h('span.test', 'a'), h('span.test1', 'b'), h('span.test', 'c')]); + const node = oldvnode.render(); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(node.childNodes[0].classList[0]).toEqual('test'); + expect(node.childNodes[1].classList[0]).toEqual('test1'); + expect(node.childNodes[2].classList[0]).toEqual('test'); + }); - it("should provide a redraw method that is able to remove existing DOM nodes", () => { - let oldvnode = h("div", [h("span#a"), h("span")]); - let newvnode = h("div", [h("span")]); - let node = oldvnode.render(); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(oldvnode).toEqual(newvnode); - expect(oldvnode.children.length).toEqual(1); - expect(node.childNodes.length).toEqual(1); - oldvnode = h("div.test-children", [h("span.a"), h("span.b")]); - node = oldvnode.render(); - newvnode = h("div.test-children", [h("div.c")]); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(oldvnode).toEqual(newvnode); - expect(oldvnode.children.length).toEqual(1); - expect(node.childNodes.length).toEqual(1); - expect(oldvnode.children[0].classList[0]).toEqual("c"); - }); + it('should provide a redraw method that is able to update different props', () => { + const oldvnode = h('span', { title: 'a', something: 'b' }); + const newvnode = h('span', { title: 'b', id: 'bbb' }); + const node = oldvnode.render(); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(oldvnode).toEqual(newvnode); + expect(node.getAttribute('title')).toEqual('b'); + expect(node.getAttribute('id')).toEqual('bbb'); + expect(node.hasAttribute('something')).toEqual(false); + }); - it("should provide a redraw method that is able to figure out differences in children", () => { - const oldvnode = h("div", [h("span", "a"), h("span"), h("span", "b")]); - const newvnode = h("div", [ - h("span", "a"), - h("span", "c"), - h("span", "b"), - ]); - const node = oldvnode.render(); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(node.childNodes[1].textContent).toEqual("c"); - }); + it('should provide a redraw method that is able to update different classes', () => { + const oldvnode = h('span.a.b', { title: 'b' }); + const newvnode = h('span.a.c', { title: 'b' }); + const node = oldvnode.render(); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(oldvnode).toEqual(newvnode); + expect(node.classList.value).toEqual('a c'); + }); - it("should provide a redraw method that is able to figure out differences in existing children", () => { - const oldvnode = h("div", [ - h("span.test", "a"), - h("span.test", "b"), - h("span.test", "c"), - ]); - const newvnode = h("div", [ - h("span.test", "a"), - h("span.test1", "b"), - h("span.test", "c"), - ]); - const node = oldvnode.render(); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(node.childNodes[0].classList[0]).toEqual("test"); - expect(node.childNodes[1].classList[0]).toEqual("test1"); - expect(node.childNodes[2].classList[0]).toEqual("test"); - }); + it('should provide redraw method to detect changed nodes if they have different elements', () => { + const oldvnode = h('span.c', { title: 'b' }); + const newvnode = h('div.c', { title: 'b' }); + const container = document.createElement('div'); + const node = oldvnode.render(); + container.appendChild(node); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(node).not.toEqual(container.childNodes[0]); + expect(node.constructor).toEqual(HTMLSpanElement); + expect(container.childNodes[0].constructor).toEqual(HTMLDivElement); + }); - it("should provide a redraw method that is able to update different props", () => { - const oldvnode = h("span", { title: "a", something: "b" }); - const newvnode = h("span", { title: "b", id: "bbb" }); - const node = oldvnode.render(); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(oldvnode).toEqual(newvnode); - expect(node.getAttribute("title")).toEqual("b"); - expect(node.getAttribute("id")).toEqual("bbb"); - expect(node.hasAttribute("something")).toEqual(false); - }); + it('should provide redraw method to detect position changes in child nodes', () => { + const v1 = h('ul', [h('li.a'), h('li.b'), h('li.c'), h('li.d')]); + const v2 = h('ul', [h('li.c'), h('li.b'), h('li.a'), h('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 a redraw method that is able to update different classes", () => { - const oldvnode = h("span.a.b", { title: "b" }); - const newvnode = h("span.a.c", { title: "b" }); - const node = oldvnode.render(); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(oldvnode).toEqual(newvnode); - expect(node.classList.value).toEqual("a c"); - }); + it('should optimize insertion and deletions when redrawing if all old/new children exist', () => { + const v = h('div', h('a'), h('d')); + const vnode = h('div', h('a'), h('b'), h('c'), h('d')); + const node = v.render(); + v.redraw({ node, vnode }); + expect(v.children.length).toEqual(4); + }); - it("should provide redraw method to detect changed nodes if they have different elements", () => { - const oldvnode = h("span.c", { title: "b" }); - const newvnode = h("div.c", { title: "b" }); - const container = document.createElement("div"); - const node = oldvnode.render(); - container.appendChild(node); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(node).not.toEqual(container.childNodes[0]); - expect(node.constructor).toEqual(HTMLSpanElement); - expect(container.childNodes[0].constructor).toEqual(HTMLDivElement); - }); + it('should provide redraw method to detect changed nodes if they have different node types', () => { + const oldvnode = h('span.c', { title: 'b' }); + const newvnode = h({ type: '#text', value: 'test' }); + const container = document.createElement('div'); + const node = oldvnode.render(); + container.appendChild(node); + expect(node.constructor).toEqual(HTMLSpanElement); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(node).not.toEqual(container.childNodes[0]); + expect(container.childNodes[0].data).toEqual('test'); + }); - it("should provide redraw method to detect position changes in child nodes", () => { - const v1 = h("ul", [h("li.a"), h("li.b"), h("li.c"), h("li.d")]); - const v2 = h("ul", [h("li.c"), h("li.b"), h("li.a"), h("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 text', () => { + const oldvnode = h({ type: '#text', value: 'test1' }); + const newvnode = h({ type: '#text', value: 'test2' }); + const container = document.createElement('div'); + const node = oldvnode.render(); + container.appendChild(node); + expect(node.data).toEqual('test1'); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(container.childNodes[0].data).toEqual('test2'); + }); - it("should optimize insertion and deletions when redrawing if all old/new children exist", () => { - const v = h("div", h("a"), h("d")); - const vnode = h("div", h("a"), h("b"), h("c"), h("d")); - const node = v.render(); - v.redraw({ node, vnode }); - expect(v.children.length).toEqual(4); - }); + it('should provide redraw method to detect changed nodes and recurse', () => { + const oldvnode = h('ul.c', { title: 'b' }, [h('li#aaa'), h('li#bbb'), h('li#ccc')]); + const newvnode = h('ul.c', { title: 'b' }, [h('li#aaa'), h('li#ccc')]); + const node = oldvnode.render(); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(oldvnode).toEqual(newvnode); + expect(node.childNodes.length).toEqual(2); + expect(node.childNodes[0].getAttribute('id')).toEqual('aaa'); + expect(node.childNodes[1].getAttribute('id')).toEqual('ccc'); + }); - it("should provide redraw method to detect changed nodes if they have different node types", () => { - const oldvnode = h("span.c", { title: "b" }); - const newvnode = h({ type: "#text", value: "test" }); - const container = document.createElement("div"); - const node = oldvnode.render(); - container.appendChild(node); - expect(node.constructor).toEqual(HTMLSpanElement); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(node).not.toEqual(container.childNodes[0]); - expect(container.childNodes[0].data).toEqual("test"); + it('should provide a redraw method able to detect specific changes to style, data, value, props, $onrender and eventListeners', () => { + const fn = () => false; + const oldvnode = h('input', { + style: 'margin: auto;', + data: { a: 111, b: 222, d: 444 }, + value: null, + title: 'test', + label: 'test', + onkeydown: () => true, + onclick: () => true, + onkeypress: () => true, }); - - it("should provide redraw method to detect changed nodes if they have different text", () => { - const oldvnode = h({ type: "#text", value: "test1" }); - const newvnode = h({ type: "#text", value: "test2" }); - const container = document.createElement("div"); - const node = oldvnode.render(); - container.appendChild(node); - expect(node.data).toEqual("test1"); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(container.childNodes[0].data).toEqual("test2"); + const newvnode = h('input', { + style: false, + data: { a: 111, b: 223, c: 333 }, + title: 'test #2', + label: 'test', + something: false, + somethingElse: { test: 1 }, + value: 0, + placeholder: 'test', + onkeydown: () => true, + onkeypress: () => false, + $onrender: () => true, + onhover: () => true, }); - - it("should provide redraw method to detect changed nodes and recurse", () => { - const oldvnode = h("ul.c", { title: "b" }, [ - h("li#aaa"), - h("li#bbb"), - h("li#ccc"), - ]); - const newvnode = h("ul.c", { title: "b" }, [h("li#aaa"), h("li#ccc")]); - const node = oldvnode.render(); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(oldvnode).toEqual(newvnode); - expect(node.childNodes.length).toEqual(2); - expect(node.childNodes[0].getAttribute("id")).toEqual("aaa"); - expect(node.childNodes[1].getAttribute("id")).toEqual("ccc"); + const newvnode2 = h('input', { + style: false, + data: { a: 111, b: 223, c: 333 }, + title: 'test #2', + label: 'test', + something: false, + somethingElse: { test: 1 }, + placeholder: 'test', + onkeydown: () => true, + onkeypress: () => false, + $onrender: () => true, + onhover: () => true, }); + const container = document.createElement('div'); + const node = oldvnode.render(); + expect(node.value).toEqual(''); + container.appendChild(node); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(oldvnode).toEqual(newvnode); + expect(node.style.cssText).toEqual(''); + expect(node.dataset['a']).toEqual('111'); + expect(node.dataset['c']).toEqual('333'); + expect(node.dataset['b']).toEqual('223'); + expect(node.dataset['d']).toEqual(undefined); + expect(node.something).toEqual(false); + expect(node.getAttribute('title')).toEqual('test #2'); + expect(node.getAttribute('placeholder')).toEqual('test'); + expect(node.value).toEqual('0'); + oldvnode.redraw({ node, vnode: newvnode2 }); + expect(node.value).toEqual(''); + }); - it("should provide a redraw method able to detect specific changes to style, data, value, props, $onrender and eventListeners", () => { - const fn = () => false; - const oldvnode = h("input", { - style: "margin: auto;", - data: { a: 111, b: 222, d: 444 }, - value: "Test...", - title: "test", - label: "test", - onkeydown: () => true, - onclick: () => true, - onkeypress: () => true, - }); - const newvnode = h("input", { - style: false, - data: { a: 111, b: 223, c: 333 }, - title: "test #2", - label: "test", - placeholder: "test", - onkeydown: () => true, - onkeypress: () => false, - $onrender: () => true, - onhover: () => true, - }); - const container = document.createElement("div"); - const node = oldvnode.render(); - container.appendChild(node); - oldvnode.redraw({ node: node, vnode: newvnode }); - expect(oldvnode).toEqual(newvnode); - expect(node.style.cssText).toEqual(""); - expect(node.dataset["a"]).toEqual("111"); - expect(node.dataset["c"]).toEqual("333"); - expect(node.dataset["b"]).toEqual("223"); - expect(node.dataset["d"]).toEqual(undefined); - expect(node.getAttribute("title")).toEqual("test #2"); - expect(node.getAttribute("placeholder")).toEqual("test"); - expect(node.value).toEqual(""); - }); + it('should handle value property/attribute for non-input fields', () => { + const v = h('test', { value: null }); + const n = v.render(); + expect(n.value).toEqual(undefined); + expect(n.getAttribute('value')).toEqual(null); + }); - it("should provide a redraw method able to detect changes in child content", () => { - const v1 = h("ul", [h("li", "a"), h("li", "b")]); - const n1 = v1.render(); - const v2 = h("ul", { - $html: "<li>a</li><li>b</li>", - $onrender: (node) => node.classList.add("test"), - }); - const v3 = h("ul", [h("li", "a")]); - const v4 = h("ul", [h("li", "b")]); - const n2 = v2.render(); - const n3 = v3.render(); - 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); + it('should provide a redraw method able to detect changes in child content', () => { + const v1 = h('ul', [h('li', 'a'), h('li', 'b')]); + const n1 = v1.render(); + const v2 = h('ul', { + $html: '<li>a</li><li>b</li>', + $onrender: (node) => node.classList.add('test'), }); + const v3 = h('ul', [h('li', 'a')]); + const v4 = h('ul', [h('li', 'b')]); + const n2 = v2.render(); + const n3 = v3.render(); + 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); + }); - it("should execute $onrender callbacks whenever a child node is added to the DOM", async () => { - let n; - const $onrender = (node) => { - n = node; - }; - const vn1 = h("ul", [h("li")]); - const vn2 = h("ul", [h("li"), h("li.vn2", { $onrender })]); - const n1 = vn1.render(); - vn1.redraw({ node: n1, vnode: vn2 }); - expect(n.classList.value).toEqual("vn2"); - const vn3 = h("ul", [h("span.vn3", { $onrender })]); - vn1.redraw({ node: n1, vnode: vn3 }); - expect(n.classList.value).toEqual("vn3"); - const rc = () => h("div.rc", { $onrender }); - await h3.init(rc); - expect(n.classList.value).toEqual("rc"); - const rc2 = () => vn2; - await h3.init(rc2); - expect(n.classList.value).toEqual("vn2"); - }); + it('should execute $onrender callbacks whenever a child node is added to the DOM', async () => { + let n; + const $onrender = (node) => { + n = node; + }; + const vn1 = h('ul', [h('li')]); + const vn2 = h('ul', [h('li'), h('li.vn2', { $onrender })]); + const n1 = vn1.render(); + vn1.redraw({ node: n1, vnode: vn2 }); + expect(n.classList.value).toEqual('vn2'); + const vn3 = h('ul', [h('span.vn3', { $onrender })]); + vn1.redraw({ node: n1, vnode: vn3 }); + expect(n.classList.value).toEqual('vn3'); + const rc = () => h('div.rc', { $onrender }); + await h3.init(rc); + expect(n.classList.value).toEqual('rc'); + const rc2 = () => vn2; + await h3.init(rc2); + expect(n.classList.value).toEqual('vn2'); + }); });
M docs/H3_DeveloperGuide.htmdocs/H3_DeveloperGuide.htm

@@ -7252,7 +7252,7 @@ </li>

<li><a href="#Quick-Start">Quick Start</a> <ul> <li><a href="#Create-a-basic-HTML-file">Create a basic HTML file</a></li> - <li><a href="#Import-h3.js">Import h3.js</a></li> + <li><a href="#Import-h3-and-h-from-h3.js">Import h3 and h from h3.js</a></li> <li><a href="#Create-your-SPA">Create your SPA</a></li> </ul> </li>

@@ -7335,7 +7335,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.10.0/h3.js" target="_blank" class="button primary">Download v0.10.0 (Jittery Jem'Hadar)</a></p> +<p><a href="https://raw.githubusercontent.com/h3rald/h3/v0.11.0/h3.js" target="_blank" class="button primary">Download v0.11.0 (Keen Klingon)</a></p> <p><small>Or get the minified version <a href="https://raw.githubusercontent.com/h3rald/h3/v0.9.0/h3.min.js">here</a>.</small></p>

@@ -7402,22 +7402,22 @@ </code></pre>

<p>Note that the script must be marked as an ES6 module (<code>type="module"</code>), otherwise your imports won&rsquo;t work.</p> -<a name="Import-h3.js"></a> -<h3>Import h3.js<a href="#document-top" title="Go to top"></a></h3> +<a name="Import-h3-and-h-from-h3.js"></a> +<h3>Import h3 and h from h3.js<a href="#document-top" title="Go to top"></a></h3> -<p>Then, inside your <code>app.js</code> file, import <code>h3.js</code>, which should be accessible somewhere in your app:</p> +<p>Then, inside your <code>app.js</code> file, import <code>h</code> and <code>h3</code> from <code>h3.js</code>, which should be accessible somewhere in your app:</p> -<pre><code class="js">import h3 from "./h3.js"; +<pre><code class="js">import { h3, h } from "./h3.js"; </code></pre> -<p>This will work in <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules">every modern browser except Internet Explorer</a>. You don&rsquo;t need a transpiler, you don&rsquo;t need something to convert your beautiful ES6 code back to cluncky ES5.</p> +<p>This will work in <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules">every modern browser except Internet Explorer</a>. You don&rsquo;t need a transpiler, you don&rsquo;t need something to convert your beautiful ES6 code back to clunky ES5.</p> <p>Unless your company tells you to, do yourself a favor and don&rsquo;t support IE. It&rsquo;s 2020, even <a href="https://www.theverge.com/2020/1/15/21066767/microsoft-edge-chromium-new-browser-windows-mac-download-os">Microsoft moved on</a>, and now ES6 modules work in all major browsers.</p> <a name="Create-your-SPA"></a> <h3>Create your SPA<a href="#document-top" title="Go to top"></a></h3> -<p>After importing the <code>h3</code> object, you can start developing your SPA. A bare minimum SPA is comprised by a single component passed to the <code>h3.init()</code> method:</p> +<p>After importing the <code>h3</code> object and the <code>h</code> function, you can start developing your SPA. A bare minimum SPA is comprised by a single component passed to the <code>h3.init()</code> method:</p> <pre><code class="js">// A simple component printing the current date and time // Pressig the Refresh button causes the application to redraw

@@ -7508,6 +7508,7 @@ <p>Screens are typically created using the <strong>h3.screen</strong> shorthand method, but they can stll created using an 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 subsequent teardowns. +* If the <strong>setup</strong> method returns <strong>false</strong>, the <strong>display</strong> method of the screen (or the main screen function if you created it manually) will not be executed (and a <strong>$navigation</strong> event will be dispatched with <strong>null</strong> as data parameter). This can be useful in certain situations to interrupt navigation or perform redirects. * 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>

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

h("img", { alt: "H3", src: "images/h3.svg" }), ]), h("div.version.col-sm.col-md", [ - h("div.version-number", "v0.10.0"), - h("div.version-label", "“Jittery Jem'Hadar“"), + h("div.version-number", "v0.11.0"), + h("div.version-label", "“Keen Klingon“"), ]), h("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);

@@ -7977,6 +7978,8 @@ </code></pre>

<p>This example shows how to implement a simple component that renders an employee profile in the <code>display</code> function, fetches data (if necessary) in the <code>setup</code> function, and preserves data in the <code>teardown</code> function.</p> +<p><strong>Tip</strong> To interrupt navigation or perform redirects, return <strong>false</strong> in the <strong>setup</strong> method.</p> + <a name="h3.dispatch(event:-string,-data:-any)"></a> <h3>h3.dispatch(event: string, data: any)<a href="#document-top" title="Go to top"></a></h3>

@@ -8125,7 +8128,7 @@ </ul>

</div> <div id="footer"> - <p><span class="copy"></span> Fabio Cevasco &ndash; August 2, 2020</p> + <p><span class="copy"></span> Fabio Cevasco &ndash; October 31, 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,11 +1,14 @@

/** - * H3 v0.10.0 "Jittery Jem'Hadar" + * H3 v0.11.0 "Keen Klingon" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * @license MIT * For the full license, see: https://github.com/h3rald/h3/blob/master/LICENSE */ const checkProperties = (obj1, obj2) => { + if (Object.keys(obj1).length !== Object.keys(obj2).length) { + return false; + } for (const key in obj1) { if (!(key in obj2)) { return false;

@@ -17,25 +20,19 @@ }

return true; }; +const blank = (v) => [undefined, null].includes(v); + const equal = (obj1, obj2) => { - if ( - (obj1 === null && obj2 === null) || - (obj1 === undefined && obj2 === undefined) - ) { + if ((obj1 === null && obj2 === null) || (obj1 === undefined && obj2 === undefined)) { return true; } - if ( - (obj1 === undefined && obj2 !== undefined) || - (obj1 !== undefined && obj2 === undefined) || - (obj1 === null && obj2 !== null) || - (obj1 !== null && obj2 === null) - ) { + if ((obj1 === undefined && obj2 !== undefined) || (obj1 !== undefined && obj2 === undefined) || (obj1 === null && obj2 !== null) || (obj1 !== null && obj2 === null)) { return false; } if (obj1.constructor !== obj2.constructor) { return false; } - if (typeof obj1 === "function") { + if (typeof obj1 === 'function') { if (obj1.toString() !== obj2.toString()) { return false; }

@@ -54,7 +51,7 @@ }

} return true; } - return checkProperties(obj1, obj2); + return checkProperties(obj1, obj2); }; const selectorRegex = /^([a-z][a-z0-9:_=-]*)?(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i;

@@ -76,49 +73,37 @@ this.children = [];

this.classList = []; this.eventListeners = {}; if (args.length === 0) { - throw new Error("[VNode] No arguments passed to VNode constructor."); + throw new Error('[VNode] No arguments passed to VNode constructor.'); } if (args.length === 1) { let vnode = args[0]; - if (typeof vnode === "string") { + if (typeof vnode === 'string') { // Assume empty element this.processSelector(vnode); - } else if ( - typeof vnode === "function" || - (typeof vnode === "object" && vnode !== null) - ) { + } else if (typeof vnode === 'function' || (typeof vnode === 'object' && vnode !== null)) { // Text node - if (vnode.type === "#text") { - this.type = "#text"; + if (vnode.type === '#text') { + this.type = '#text'; this.value = vnode.value; } else { this.from(this.processVNodeObject(vnode)); } } else { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); + throw new Error('[VNode] Invalid first argument passed to VNode constructor.'); } } else if (args.length === 2) { let [selector, data] = args; - if (typeof selector !== "string") { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); + if (typeof selector !== 'string') { + throw new Error('[VNode] Invalid first argument passed to VNode constructor.'); } this.processSelector(selector); - if (typeof data === "string") { + if (typeof data === 'string') { // Assume single child text node - this.children = [new VNode({ type: "#text", value: data })]; + this.children = [new VNode({ type: '#text', value: data })]; return; } - if ( - typeof data !== "function" && - (typeof data !== "object" || data === null) - ) { - throw new Error( - "[VNode] The second argument of a VNode constructor must be an object, an array or a string." - ); + if (typeof data !== 'function' && (typeof data !== 'object' || data === null)) { + throw new Error('[VNode] The second argument of a VNode constructor must be an object, an array or a string.'); } if (Array.isArray(data)) { // Assume 2nd argument as children

@@ -137,24 +122,16 @@ if (args.length > 3) {

children = args.slice(2); } children = Array.isArray(children) ? children : [children]; - if (typeof selector !== "string") { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); + if (typeof selector !== 'string') { + throw new Error('[VNode] Invalid first argument passed to VNode constructor.'); } this.processSelector(selector); - if ( - props instanceof Function || - props instanceof VNode || - typeof props === "string" - ) { + if (props instanceof Function || props instanceof VNode || typeof props === 'string') { // 2nd argument is a child children = [props].concat(children); } else { - if (typeof props !== "object" || props === null) { - throw new Error( - "[VNode] Invalid second argument passed to VNode constructor." - ); + if (typeof props !== 'object' || props === null) { + throw new Error('[VNode] Invalid second argument passed to VNode constructor.'); } this.processProperties(props); }

@@ -188,18 +165,13 @@ this.$onrender = attrs.$onrender;

this.style = attrs.style; this.value = attrs.value; this.data = attrs.data || {}; - this.classList = - attrs.classList && attrs.classList.length > 0 - ? attrs.classList - : this.classList; + this.classList = attrs.classList && attrs.classList.length > 0 ? attrs.classList : this.classList; this.props = attrs; Object.keys(attrs) - .filter((a) => a.startsWith("on") && attrs[a]) + .filter((a) => a.startsWith('on') && attrs[a]) .forEach((key) => { - if (typeof attrs[key] !== "function") { - throw new Error( - `[VNode] Event handler specified for ${key} event is not a function.` - ); + if (typeof attrs[key] !== 'function') { + throw new Error(`[VNode] Event handler specified for ${key} event is not a function.`); } this.eventListeners[key.slice(2)] = attrs[key]; delete this.props[key];

@@ -222,7 +194,7 @@ this.type = type;

if (id) { this.id = id.slice(1); } - this.classList = (classes && classes.split(".").slice(1)) || []; + this.classList = (classes && classes.split('.').slice(1)) || []; } processVNodeObject(arg) {

@@ -231,27 +203,25 @@ return arg;

} if (arg instanceof Function) { let vnode = arg(); - if (typeof vnode === "string") { - vnode = new VNode({ type: "#text", value: vnode }); + if (typeof vnode === 'string') { + vnode = new VNode({ type: '#text', value: vnode }); } if (!(vnode instanceof VNode)) { - throw new Error("[VNode] Function argument does not return a VNode"); + throw new Error('[VNode] Function argument does not return a VNode'); } return vnode; } - throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." - ); + throw new Error('[VNode] Invalid first argument provided to VNode constructor.'); } processChildren(arg) { const children = Array.isArray(arg) ? arg : [arg]; this.children = children .map((c) => { - if (typeof c === "string") { - return new VNode({ type: "#text", value: c }); + if (typeof c === 'string') { + return new VNode({ type: '#text', value: c }); } - if (typeof c === "function" || (typeof c === "object" && c !== null)) { + if (typeof c === 'function' || (typeof c === 'object' && c !== null)) { return this.processVNodeObject(c); } if (c) {

@@ -263,19 +233,19 @@ }

// Renders the actual DOM Node corresponding to the current Virtual Node render() { - if (this.type === "#text") { + if (this.type === '#text') { return document.createTextNode(this.value); } const node = document.createElement(this.type); - if (this.id) { + if (!blank(this.id)) { node.id = this.id; } Object.keys(this.props).forEach((p) => { // Set attributes - if (typeof this.props[p] === "boolean") { - this.props[p] ? node.setAttribute(p, "") : node.removeAttribute(p); + if (typeof this.props[p] === 'boolean') { + this.props[p] ? node.setAttribute(p, '') : node.removeAttribute(p); } - if (["string", "number"].includes(typeof this.props[p])) { + if (['string', 'number'].includes(typeof this.props[p])) { node.setAttribute(p, this.props[p]); } // Set properties

@@ -287,10 +257,10 @@ node.addEventListener(event, this.eventListeners[event]);

}); // Value if (this.value) { - if (["textarea", "input"].includes(this.type)) { + if (['textarea', 'input', 'option', 'button'].includes(this.type)) { node.value = this.value; } else { - node.setAttribute("value", this.value); + node.setAttribute('value', this.value.toString()); } } // Style

@@ -299,7 +269,7 @@ node.style.cssText = this.style;

} // Classes this.classList.forEach((c) => { - node.classList.add(c); + c && node.classList.add(c); }); // Data Object.keys(this.data).forEach((key) => {

@@ -325,9 +295,7 @@ const oldvnode = this;

if ( oldvnode.constructor !== newvnode.constructor || oldvnode.type !== newvnode.type || - (oldvnode.type === newvnode.type && - oldvnode.type === "#text" && - oldvnode !== newvnode) + (oldvnode.type === newvnode.type && oldvnode.type === '#text' && oldvnode !== newvnode) ) { const renderedNode = newvnode.render(); node.parentNode.replaceChild(renderedNode, node);

@@ -337,16 +305,16 @@ return;

} // ID if (oldvnode.id !== newvnode.id) { - node.id = newvnode.id || ""; + node.id = newvnode.id; oldvnode.id = newvnode.id; } // Value if (oldvnode.value !== newvnode.value) { oldvnode.value = newvnode.value; - if (["textarea", "input"].includes(oldvnode.type)) { - node.value = newvnode.value || ""; + if (['textarea', 'input', 'option', 'button'].includes(oldvnode.type)) { + node.value = blank(newvnode.value) ? '' : newvnode.value.toString(); } else { - node.setAttribute("value", newvnode.value || ""); + node.setAttribute('value', blank(newvnode.value) ? '' : newvnode.value.toString()); } } // Classes

@@ -357,7 +325,7 @@ node.classList.remove(c);

} }); newvnode.classList.forEach((c) => { - if (!oldvnode.classList.includes(c)) { + if (c && !oldvnode.classList.includes(c)) { node.classList.add(c); } });

@@ -365,7 +333,7 @@ oldvnode.classList = newvnode.classList;

} // Style if (oldvnode.style !== newvnode.style) { - node.style.cssText = newvnode.style || ""; + node.style.cssText = newvnode.style || ''; oldvnode.style = newvnode.style; } // Data

@@ -388,28 +356,28 @@ // Properties & Attributes

if (!equal(oldvnode.props, newvnode.props)) { Object.keys(oldvnode.props).forEach((a) => { node[a] = newvnode.props[a]; - if (typeof newvnode.props[a] === "boolean") { + if (typeof newvnode.props[a] === 'boolean') { oldvnode.props[a] = newvnode.props[a]; - newvnode.props[a] - ? node.setAttribute(a, "") - : node.removeAttribute(a); - } else if (!newvnode.props[a]) { + newvnode.props[a] ? node.setAttribute(a, '') : node.removeAttribute(a); + } else if ([null, undefined].includes(newvnode.props[a])) { delete oldvnode.props[a]; node.removeAttribute(a); - } else if ( - newvnode.props[a] && - newvnode.props[a] !== oldvnode.props[a] - ) { + } else if (newvnode.props[a] !== oldvnode.props[a]) { oldvnode.props[a] = newvnode.props[a]; - if (["string", "number"].includes(typeof newvnode.props[a])) { + if (['string', 'number'].includes(typeof newvnode.props[a])) { node.setAttribute(a, newvnode.props[a]); } } }); Object.keys(newvnode.props).forEach((a) => { - if (!oldvnode.props[a] && newvnode.props[a]) { + if (blank(oldvnode.props[a]) && !blank(newvnode.props[a])) { oldvnode.props[a] = newvnode.props[a]; - node.setAttribute(a, newvnode.props[a]); + node[a] = newvnode.props[a]; + if (typeof newvnode.props[a] === 'boolean') { + node.setAttribute(a, ''); + } else if (['string', 'number'].includes(typeof newvnode.props[a])) { + node.setAttribute(a, newvnode.props[a]); + } } }); }

@@ -418,9 +386,7 @@ if (!equal(oldvnode.eventListeners, newvnode.eventListeners)) {

Object.keys(oldvnode.eventListeners).forEach((a) => { if (!newvnode.eventListeners[a]) { node.removeEventListener(a, oldvnode.eventListeners[a]); - } else if ( - !equal(newvnode.eventListeners[a], oldvnode.eventListeners[a]) - ) { + } else if (!equal(newvnode.eventListeners[a], oldvnode.eventListeners[a])) { node.removeEventListener(a, oldvnode.eventListeners[a]); node.addEventListener(a, newvnode.eventListeners[a]); }

@@ -455,8 +421,7 @@ case INSERT: {

oldvnode.children.splice(count, 0, newvnode.children[count]); const renderedNode = newvnode.children[count].render(); node.insertBefore(renderedNode, node.childNodes[count]); - newvnode.children[count].$onrender && - newvnode.children[count].$onrender(renderedNode); + newvnode.children[count].$onrender && newvnode.children[count].$onrender(renderedNode); break checkmap; } case DELETE: {

@@ -501,11 +466,7 @@ op = oIdx; // Same node found

break; } } - if ( - op < 0 && - newList.length >= oldList.length && - map.length >= oldList.length - ) { + if (op < 0 && newList.length >= oldList.length && map.length >= oldList.length) { op = INSERT; } map.push(op);

@@ -513,9 +474,7 @@ }

const oldNodesFound = map.filter((c) => c >= 0); if (oldList.length > newList.length) { // Remove remaining nodes - [...Array(oldList.length - newList.length).keys()].forEach(() => - map.push(DELETE) - ); + [...Array(oldList.length - newList.length).keys()].forEach(() => map.push(DELETE)); } else if (oldNodesFound.length === oldList.length) { // All nodes not found are insertions map = map.map((c) => (c < 0 ? INSERT : c));

@@ -535,7 +494,7 @@ this.events = {};

this.state = {}; } dispatch(event, data) { - if (event !== "$log") this.dispatch("$log", { event, data }); + if (event !== '$log') this.dispatch('$log', { event, data }); if (this.events[event]) { let changes = {}; let changed;

@@ -562,9 +521,9 @@ this.query = query;

this.parts = parts; this.params = {}; if (this.query) { - const rawParams = this.query.split("&"); + const rawParams = this.query.split('&'); rawParams.forEach((p) => { - const [name, value] = p.split("="); + const [name, value] = p.split('='); this.params[decodeURIComponent(name)] = decodeURIComponent(value); }); }

@@ -578,7 +537,7 @@ this.redraw = null;

this.store = store; this.location = location || window.location; if (!routes || Object.keys(routes).length === 0) { - throw new Error("[Router] No routes defined."); + throw new Error('[Router] No routes defined.'); } const defs = Object.keys(routes); this.routes = routes;

@@ -590,34 +549,30 @@ vnode.redraw({

node: this.element.childNodes[0], vnode: this.routes[this.route.def](state), }); - this.store.dispatch("$redraw"); + this.store.dispatch('$redraw'); }; } async start() { const processPath = async (data) => { const oldRoute = this.route; - const fragment = - (data && - data.newURL && - data.newURL.match(/(#.+)$/) && - data.newURL.match(/(#.+)$/)[1]) || - this.location.hash; - const path = fragment.replace(/\?.+$/, "").slice(1); + const fragment = (data && data.newURL && data.newURL.match(/(#.+)$/) && data.newURL.match(/(#.+)$/)[1]) || this.location.hash; + const path = fragment.replace(/\?.+$/, '').slice(1); const rawQuery = fragment.match(/\?(.+)$/); - const query = rawQuery && rawQuery[1] ? rawQuery[1] : ""; - const pathParts = path.split("/").slice(1); + const query = rawQuery && rawQuery[1] ? rawQuery[1] : ''; + const pathParts = path.split('/').slice(1); let parts = {}; + let newRoute; for (let def of Object.keys(this.routes)) { - let routeParts = def.split("/").slice(1); + let routeParts = def.split('/').slice(1); let match = true; let index = 0; parts = {}; while (match && routeParts[index]) { const rP = routeParts[index]; const pP = pathParts[index]; - if (rP.startsWith(":") && pP) { + if (rP.startsWith(':') && pP) { parts[rP.slice(1)] = pP; } else { match = rP === pP;

@@ -625,30 +580,35 @@ }

index++; } if (match) { - this.route = new Route({ query, path, def, parts }); + newRoute = new Route({ query, path, def, parts }); break; } } - if (!this.route) { + if (!newRoute) { throw new Error(`[Router] No route matches '${fragment}'`); } // Old route component teardown let state = {}; if (oldRoute) { const oldRouteComponent = this.routes[oldRoute.def]; - state = - (oldRouteComponent.teardown && - (await oldRouteComponent.teardown(oldRouteComponent.state))) || - state; + state = (oldRouteComponent.teardown && (await oldRouteComponent.teardown(oldRouteComponent.state))) || state; } // New route component setup - const newRouteComponent = this.routes[this.route.def]; + const newRouteComponent = this.routes[newRoute.def]; newRouteComponent.state = state; - newRouteComponent.setup && - (await newRouteComponent.setup(newRouteComponent.state)); + if (newRouteComponent.setup) { + this.route = newRoute; + if ((await newRouteComponent.setup(newRouteComponent.state)) === false) { + // Abort navigation + this.route = oldRoute; + this.store.dispatch('$navigation', null); + return; + } + } + this.route = newRoute; // Redrawing... redrawing = true; - this.store.dispatch("$navigation", this.route); + this.store.dispatch('$navigation', this.route); while (this.element.firstChild) { this.element.removeChild(this.element.firstChild); }

@@ -661,17 +621,17 @@ vnode.$onrender && vnode.$onrender(node);

$onrenderCallbacks.forEach((cbk) => cbk()); $onrenderCallbacks = []; window.scrollTo(0, 0); - this.store.dispatch("$redraw"); + this.store.dispatch('$redraw'); }; - window.addEventListener("hashchange", processPath); + window.addEventListener('hashchange', processPath); await processPath(); } navigateTo(path, params) { let query = Object.keys(params || {}) .map((p) => `${encodeURIComponent(p)}=${encodeURIComponent(params[p])}`) - .join("&"); - query = query ? `?${query}` : ""; + .join('&'); + query = query ? `?${query}` : ''; this.location.hash = `#${path}${query}`; } }

@@ -692,23 +652,21 @@ h3.init = (config) => {

let { element, routes, modules, preStart, postStart, location } = config; if (!routes) { // Assume config is a component object, define default route - if (typeof config !== "function") { - throw new Error( - "[h3.init] The specified argument is not a valid configuration object or component function" - ); + if (typeof config !== 'function') { + throw new Error('[h3.init] The specified argument is not a valid configuration object or component function'); } - routes = { "/": config }; + routes = { '/': config }; } element = element || document.body; if (!(element && element instanceof Element)) { - throw new Error("[h3.init] Invalid element specified."); + throw new Error('[h3.init] Invalid element specified.'); } // Initialize store store = new Store(); (modules || []).forEach((i) => { i(store); }); - store.dispatch("$init"); + store.dispatch('$init'); // Initialize router router = new Router({ element, routes, store, location }); return Promise.resolve(preStart && preStart())

@@ -718,30 +676,24 @@ };

h3.navigateTo = (path, params) => { if (!router) { - throw new Error( - "[h3.navigateTo] No application initialized, unable to navigate." - ); + throw new Error('[h3.navigateTo] No application initialized, unable to navigate.'); } return router.navigateTo(path, params); }; -Object.defineProperty(h3, "route", { +Object.defineProperty(h3, 'route', { get: () => { if (!router) { - throw new Error( - "[h3.route] No application initialized, unable to retrieve current route." - ); + throw new Error('[h3.route] No application initialized, unable to retrieve current route.'); } return router.route; }, }); -Object.defineProperty(h3, "state", { +Object.defineProperty(h3, 'state', { get: () => { if (!store) { - throw new Error( - "[h3.state] No application initialized, unable to retrieve current state." - ); + throw new Error('[h3.state] No application initialized, unable to retrieve current state.'); } return store.state; },

@@ -749,45 +701,41 @@ });

h3.on = (event, cb) => { if (!store) { - throw new Error( - "[h3.on] No application initialized, unable to listen to events." - ); + throw new Error('[h3.on] No application initialized, unable to listen to events.'); } return store.on(event, cb); }; h3.dispatch = (event, data) => { if (!store) { - throw new Error( - "[h3.dispatch] No application initialized, unable to dispatch events." - ); + throw new Error('[h3.dispatch] No application initialized, unable to dispatch events.'); } return store.dispatch(event, data); }; h3.redraw = (setRedrawing) => { if (!router || !router.redraw) { - throw new Error( - "[h3.redraw] No application initialized, unable to redraw." - ); + throw new Error('[h3.redraw] No application initialized, unable to redraw.'); } if (redrawing) { return; } redrawing = true; - router.redraw(); - redrawing = setRedrawing || false; + requestAnimationFrame(() => { + router.redraw(); + redrawing = setRedrawing || false; + }); }; h3.screen = ({ setup, display, teardown }) => { - if (!display || typeof display !== "function") { - throw new Error("[h3.screen] No display property specified."); + if (!display || typeof display !== 'function') { + throw new Error('[h3.screen] No display property specified.'); } - if (setup && typeof setup !== "function") { - throw new Error("[h3.screen] setup property is not a function."); + if (setup && typeof setup !== 'function') { + throw new Error('[h3.screen] setup property is not a function.'); } - if (teardown && typeof teardown !== "function") { - throw new Error("[h3.screen] teardown property is not a function."); + if (teardown && typeof teardown !== 'function') { + throw new Error('[h3.screen] teardown property is not a function.'); } const fn = display; if (setup) {
M docs/js/app.jsdocs/js/app.js

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

h("img", { alt: "H3", src: "images/h3.svg" }), ]), h("div.version.col-sm.col-md", [ - h("div.version-number", "v0.10.0"), - h("div.version-label", "“Jittery Jem'Hadar“"), + h("div.version-number", "v0.11.0"), + h("div.version-label", "“Keen Klingon“"), ]), h("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);
M docs/js/h3.jsdocs/js/h3.js

@@ -1,11 +1,14 @@

/** - * H3 v0.10.0 "Jittery Jem'Hadar" + * H3 v0.11.0 "Keen Klingon" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * @license MIT * For the full license, see: https://github.com/h3rald/h3/blob/master/LICENSE */ const checkProperties = (obj1, obj2) => { + if (Object.keys(obj1).length !== Object.keys(obj2).length) { + return false; + } for (const key in obj1) { if (!(key in obj2)) { return false;

@@ -17,25 +20,19 @@ }

return true; }; +const blank = (v) => [undefined, null].includes(v); + const equal = (obj1, obj2) => { - if ( - (obj1 === null && obj2 === null) || - (obj1 === undefined && obj2 === undefined) - ) { + if ((obj1 === null && obj2 === null) || (obj1 === undefined && obj2 === undefined)) { return true; } - if ( - (obj1 === undefined && obj2 !== undefined) || - (obj1 !== undefined && obj2 === undefined) || - (obj1 === null && obj2 !== null) || - (obj1 !== null && obj2 === null) - ) { + if ((obj1 === undefined && obj2 !== undefined) || (obj1 !== undefined && obj2 === undefined) || (obj1 === null && obj2 !== null) || (obj1 !== null && obj2 === null)) { return false; } if (obj1.constructor !== obj2.constructor) { return false; } - if (typeof obj1 === "function") { + if (typeof obj1 === 'function') { if (obj1.toString() !== obj2.toString()) { return false; }

@@ -54,7 +51,7 @@ }

} return true; } - return checkProperties(obj1, obj2); + return checkProperties(obj1, obj2); }; const selectorRegex = /^([a-z][a-z0-9:_=-]*)?(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i;

@@ -76,49 +73,37 @@ this.children = [];

this.classList = []; this.eventListeners = {}; if (args.length === 0) { - throw new Error("[VNode] No arguments passed to VNode constructor."); + throw new Error('[VNode] No arguments passed to VNode constructor.'); } if (args.length === 1) { let vnode = args[0]; - if (typeof vnode === "string") { + if (typeof vnode === 'string') { // Assume empty element this.processSelector(vnode); - } else if ( - typeof vnode === "function" || - (typeof vnode === "object" && vnode !== null) - ) { + } else if (typeof vnode === 'function' || (typeof vnode === 'object' && vnode !== null)) { // Text node - if (vnode.type === "#text") { - this.type = "#text"; + if (vnode.type === '#text') { + this.type = '#text'; this.value = vnode.value; } else { this.from(this.processVNodeObject(vnode)); } } else { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); + throw new Error('[VNode] Invalid first argument passed to VNode constructor.'); } } else if (args.length === 2) { let [selector, data] = args; - if (typeof selector !== "string") { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); + if (typeof selector !== 'string') { + throw new Error('[VNode] Invalid first argument passed to VNode constructor.'); } this.processSelector(selector); - if (typeof data === "string") { + if (typeof data === 'string') { // Assume single child text node - this.children = [new VNode({ type: "#text", value: data })]; + this.children = [new VNode({ type: '#text', value: data })]; return; } - if ( - typeof data !== "function" && - (typeof data !== "object" || data === null) - ) { - throw new Error( - "[VNode] The second argument of a VNode constructor must be an object, an array or a string." - ); + if (typeof data !== 'function' && (typeof data !== 'object' || data === null)) { + throw new Error('[VNode] The second argument of a VNode constructor must be an object, an array or a string.'); } if (Array.isArray(data)) { // Assume 2nd argument as children

@@ -137,24 +122,16 @@ if (args.length > 3) {

children = args.slice(2); } children = Array.isArray(children) ? children : [children]; - if (typeof selector !== "string") { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); + if (typeof selector !== 'string') { + throw new Error('[VNode] Invalid first argument passed to VNode constructor.'); } this.processSelector(selector); - if ( - props instanceof Function || - props instanceof VNode || - typeof props === "string" - ) { + if (props instanceof Function || props instanceof VNode || typeof props === 'string') { // 2nd argument is a child children = [props].concat(children); } else { - if (typeof props !== "object" || props === null) { - throw new Error( - "[VNode] Invalid second argument passed to VNode constructor." - ); + if (typeof props !== 'object' || props === null) { + throw new Error('[VNode] Invalid second argument passed to VNode constructor.'); } this.processProperties(props); }

@@ -188,18 +165,13 @@ this.$onrender = attrs.$onrender;

this.style = attrs.style; this.value = attrs.value; this.data = attrs.data || {}; - this.classList = - attrs.classList && attrs.classList.length > 0 - ? attrs.classList - : this.classList; + this.classList = attrs.classList && attrs.classList.length > 0 ? attrs.classList : this.classList; this.props = attrs; Object.keys(attrs) - .filter((a) => a.startsWith("on") && attrs[a]) + .filter((a) => a.startsWith('on') && attrs[a]) .forEach((key) => { - if (typeof attrs[key] !== "function") { - throw new Error( - `[VNode] Event handler specified for ${key} event is not a function.` - ); + if (typeof attrs[key] !== 'function') { + throw new Error(`[VNode] Event handler specified for ${key} event is not a function.`); } this.eventListeners[key.slice(2)] = attrs[key]; delete this.props[key];

@@ -222,7 +194,7 @@ this.type = type;

if (id) { this.id = id.slice(1); } - this.classList = (classes && classes.split(".").slice(1)) || []; + this.classList = (classes && classes.split('.').slice(1)) || []; } processVNodeObject(arg) {

@@ -231,27 +203,25 @@ return arg;

} if (arg instanceof Function) { let vnode = arg(); - if (typeof vnode === "string") { - vnode = new VNode({ type: "#text", value: vnode }); + if (typeof vnode === 'string') { + vnode = new VNode({ type: '#text', value: vnode }); } if (!(vnode instanceof VNode)) { - throw new Error("[VNode] Function argument does not return a VNode"); + throw new Error('[VNode] Function argument does not return a VNode'); } return vnode; } - throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." - ); + throw new Error('[VNode] Invalid first argument provided to VNode constructor.'); } processChildren(arg) { const children = Array.isArray(arg) ? arg : [arg]; this.children = children .map((c) => { - if (typeof c === "string") { - return new VNode({ type: "#text", value: c }); + if (typeof c === 'string') { + return new VNode({ type: '#text', value: c }); } - if (typeof c === "function" || (typeof c === "object" && c !== null)) { + if (typeof c === 'function' || (typeof c === 'object' && c !== null)) { return this.processVNodeObject(c); } if (c) {

@@ -263,19 +233,19 @@ }

// Renders the actual DOM Node corresponding to the current Virtual Node render() { - if (this.type === "#text") { + if (this.type === '#text') { return document.createTextNode(this.value); } const node = document.createElement(this.type); - if (this.id) { + if (!blank(this.id)) { node.id = this.id; } Object.keys(this.props).forEach((p) => { // Set attributes - if (typeof this.props[p] === "boolean") { - this.props[p] ? node.setAttribute(p, "") : node.removeAttribute(p); + if (typeof this.props[p] === 'boolean') { + this.props[p] ? node.setAttribute(p, '') : node.removeAttribute(p); } - if (["string", "number"].includes(typeof this.props[p])) { + if (['string', 'number'].includes(typeof this.props[p])) { node.setAttribute(p, this.props[p]); } // Set properties

@@ -287,10 +257,10 @@ node.addEventListener(event, this.eventListeners[event]);

}); // Value if (this.value) { - if (["textarea", "input"].includes(this.type)) { + if (['textarea', 'input', 'option', 'button'].includes(this.type)) { node.value = this.value; } else { - node.setAttribute("value", this.value); + node.setAttribute('value', this.value.toString()); } } // Style

@@ -299,7 +269,7 @@ node.style.cssText = this.style;

} // Classes this.classList.forEach((c) => { - node.classList.add(c); + c && node.classList.add(c); }); // Data Object.keys(this.data).forEach((key) => {

@@ -325,9 +295,7 @@ const oldvnode = this;

if ( oldvnode.constructor !== newvnode.constructor || oldvnode.type !== newvnode.type || - (oldvnode.type === newvnode.type && - oldvnode.type === "#text" && - oldvnode !== newvnode) + (oldvnode.type === newvnode.type && oldvnode.type === '#text' && oldvnode !== newvnode) ) { const renderedNode = newvnode.render(); node.parentNode.replaceChild(renderedNode, node);

@@ -337,16 +305,16 @@ return;

} // ID if (oldvnode.id !== newvnode.id) { - node.id = newvnode.id || ""; + node.id = newvnode.id; oldvnode.id = newvnode.id; } // Value if (oldvnode.value !== newvnode.value) { oldvnode.value = newvnode.value; - if (["textarea", "input"].includes(oldvnode.type)) { - node.value = newvnode.value || ""; + if (['textarea', 'input', 'option', 'button'].includes(oldvnode.type)) { + node.value = blank(newvnode.value) ? '' : newvnode.value.toString(); } else { - node.setAttribute("value", newvnode.value || ""); + node.setAttribute('value', blank(newvnode.value) ? '' : newvnode.value.toString()); } } // Classes

@@ -357,7 +325,7 @@ node.classList.remove(c);

} }); newvnode.classList.forEach((c) => { - if (!oldvnode.classList.includes(c)) { + if (c && !oldvnode.classList.includes(c)) { node.classList.add(c); } });

@@ -365,7 +333,7 @@ oldvnode.classList = newvnode.classList;

} // Style if (oldvnode.style !== newvnode.style) { - node.style.cssText = newvnode.style || ""; + node.style.cssText = newvnode.style || ''; oldvnode.style = newvnode.style; } // Data

@@ -388,28 +356,28 @@ // Properties & Attributes

if (!equal(oldvnode.props, newvnode.props)) { Object.keys(oldvnode.props).forEach((a) => { node[a] = newvnode.props[a]; - if (typeof newvnode.props[a] === "boolean") { + if (typeof newvnode.props[a] === 'boolean') { oldvnode.props[a] = newvnode.props[a]; - newvnode.props[a] - ? node.setAttribute(a, "") - : node.removeAttribute(a); - } else if (!newvnode.props[a]) { + newvnode.props[a] ? node.setAttribute(a, '') : node.removeAttribute(a); + } else if ([null, undefined].includes(newvnode.props[a])) { delete oldvnode.props[a]; node.removeAttribute(a); - } else if ( - newvnode.props[a] && - newvnode.props[a] !== oldvnode.props[a] - ) { + } else if (newvnode.props[a] !== oldvnode.props[a]) { oldvnode.props[a] = newvnode.props[a]; - if (["string", "number"].includes(typeof newvnode.props[a])) { + if (['string', 'number'].includes(typeof newvnode.props[a])) { node.setAttribute(a, newvnode.props[a]); } } }); Object.keys(newvnode.props).forEach((a) => { - if (!oldvnode.props[a] && newvnode.props[a]) { + if (blank(oldvnode.props[a]) && !blank(newvnode.props[a])) { oldvnode.props[a] = newvnode.props[a]; - node.setAttribute(a, newvnode.props[a]); + node[a] = newvnode.props[a]; + if (typeof newvnode.props[a] === 'boolean') { + node.setAttribute(a, ''); + } else if (['string', 'number'].includes(typeof newvnode.props[a])) { + node.setAttribute(a, newvnode.props[a]); + } } }); }

@@ -418,9 +386,7 @@ if (!equal(oldvnode.eventListeners, newvnode.eventListeners)) {

Object.keys(oldvnode.eventListeners).forEach((a) => { if (!newvnode.eventListeners[a]) { node.removeEventListener(a, oldvnode.eventListeners[a]); - } else if ( - !equal(newvnode.eventListeners[a], oldvnode.eventListeners[a]) - ) { + } else if (!equal(newvnode.eventListeners[a], oldvnode.eventListeners[a])) { node.removeEventListener(a, oldvnode.eventListeners[a]); node.addEventListener(a, newvnode.eventListeners[a]); }

@@ -455,8 +421,7 @@ case INSERT: {

oldvnode.children.splice(count, 0, newvnode.children[count]); const renderedNode = newvnode.children[count].render(); node.insertBefore(renderedNode, node.childNodes[count]); - newvnode.children[count].$onrender && - newvnode.children[count].$onrender(renderedNode); + newvnode.children[count].$onrender && newvnode.children[count].$onrender(renderedNode); break checkmap; } case DELETE: {

@@ -501,11 +466,7 @@ op = oIdx; // Same node found

break; } } - if ( - op < 0 && - newList.length >= oldList.length && - map.length >= oldList.length - ) { + if (op < 0 && newList.length >= oldList.length && map.length >= oldList.length) { op = INSERT; } map.push(op);

@@ -513,9 +474,7 @@ }

const oldNodesFound = map.filter((c) => c >= 0); if (oldList.length > newList.length) { // Remove remaining nodes - [...Array(oldList.length - newList.length).keys()].forEach(() => - map.push(DELETE) - ); + [...Array(oldList.length - newList.length).keys()].forEach(() => map.push(DELETE)); } else if (oldNodesFound.length === oldList.length) { // All nodes not found are insertions map = map.map((c) => (c < 0 ? INSERT : c));

@@ -535,7 +494,7 @@ this.events = {};

this.state = {}; } dispatch(event, data) { - if (event !== "$log") this.dispatch("$log", { event, data }); + if (event !== '$log') this.dispatch('$log', { event, data }); if (this.events[event]) { let changes = {}; let changed;

@@ -562,9 +521,9 @@ this.query = query;

this.parts = parts; this.params = {}; if (this.query) { - const rawParams = this.query.split("&"); + const rawParams = this.query.split('&'); rawParams.forEach((p) => { - const [name, value] = p.split("="); + const [name, value] = p.split('='); this.params[decodeURIComponent(name)] = decodeURIComponent(value); }); }

@@ -578,7 +537,7 @@ this.redraw = null;

this.store = store; this.location = location || window.location; if (!routes || Object.keys(routes).length === 0) { - throw new Error("[Router] No routes defined."); + throw new Error('[Router] No routes defined.'); } const defs = Object.keys(routes); this.routes = routes;

@@ -590,34 +549,30 @@ vnode.redraw({

node: this.element.childNodes[0], vnode: this.routes[this.route.def](state), }); - this.store.dispatch("$redraw"); + this.store.dispatch('$redraw'); }; } async start() { const processPath = async (data) => { const oldRoute = this.route; - const fragment = - (data && - data.newURL && - data.newURL.match(/(#.+)$/) && - data.newURL.match(/(#.+)$/)[1]) || - this.location.hash; - const path = fragment.replace(/\?.+$/, "").slice(1); + const fragment = (data && data.newURL && data.newURL.match(/(#.+)$/) && data.newURL.match(/(#.+)$/)[1]) || this.location.hash; + const path = fragment.replace(/\?.+$/, '').slice(1); const rawQuery = fragment.match(/\?(.+)$/); - const query = rawQuery && rawQuery[1] ? rawQuery[1] : ""; - const pathParts = path.split("/").slice(1); + const query = rawQuery && rawQuery[1] ? rawQuery[1] : ''; + const pathParts = path.split('/').slice(1); let parts = {}; + let newRoute; for (let def of Object.keys(this.routes)) { - let routeParts = def.split("/").slice(1); + let routeParts = def.split('/').slice(1); let match = true; let index = 0; parts = {}; while (match && routeParts[index]) { const rP = routeParts[index]; const pP = pathParts[index]; - if (rP.startsWith(":") && pP) { + if (rP.startsWith(':') && pP) { parts[rP.slice(1)] = pP; } else { match = rP === pP;

@@ -625,30 +580,35 @@ }

index++; } if (match) { - this.route = new Route({ query, path, def, parts }); + newRoute = new Route({ query, path, def, parts }); break; } } - if (!this.route) { + if (!newRoute) { throw new Error(`[Router] No route matches '${fragment}'`); } // Old route component teardown let state = {}; if (oldRoute) { const oldRouteComponent = this.routes[oldRoute.def]; - state = - (oldRouteComponent.teardown && - (await oldRouteComponent.teardown(oldRouteComponent.state))) || - state; + state = (oldRouteComponent.teardown && (await oldRouteComponent.teardown(oldRouteComponent.state))) || state; } // New route component setup - const newRouteComponent = this.routes[this.route.def]; + const newRouteComponent = this.routes[newRoute.def]; newRouteComponent.state = state; - newRouteComponent.setup && - (await newRouteComponent.setup(newRouteComponent.state)); + if (newRouteComponent.setup) { + this.route = newRoute; + if ((await newRouteComponent.setup(newRouteComponent.state)) === false) { + // Abort navigation + this.route = oldRoute; + this.store.dispatch('$navigation', null); + return; + } + } + this.route = newRoute; // Redrawing... redrawing = true; - this.store.dispatch("$navigation", this.route); + this.store.dispatch('$navigation', this.route); while (this.element.firstChild) { this.element.removeChild(this.element.firstChild); }

@@ -661,17 +621,17 @@ vnode.$onrender && vnode.$onrender(node);

$onrenderCallbacks.forEach((cbk) => cbk()); $onrenderCallbacks = []; window.scrollTo(0, 0); - this.store.dispatch("$redraw"); + this.store.dispatch('$redraw'); }; - window.addEventListener("hashchange", processPath); + window.addEventListener('hashchange', processPath); await processPath(); } navigateTo(path, params) { let query = Object.keys(params || {}) .map((p) => `${encodeURIComponent(p)}=${encodeURIComponent(params[p])}`) - .join("&"); - query = query ? `?${query}` : ""; + .join('&'); + query = query ? `?${query}` : ''; this.location.hash = `#${path}${query}`; } }

@@ -692,23 +652,21 @@ h3.init = (config) => {

let { element, routes, modules, preStart, postStart, location } = config; if (!routes) { // Assume config is a component object, define default route - if (typeof config !== "function") { - throw new Error( - "[h3.init] The specified argument is not a valid configuration object or component function" - ); + if (typeof config !== 'function') { + throw new Error('[h3.init] The specified argument is not a valid configuration object or component function'); } - routes = { "/": config }; + routes = { '/': config }; } element = element || document.body; if (!(element && element instanceof Element)) { - throw new Error("[h3.init] Invalid element specified."); + throw new Error('[h3.init] Invalid element specified.'); } // Initialize store store = new Store(); (modules || []).forEach((i) => { i(store); }); - store.dispatch("$init"); + store.dispatch('$init'); // Initialize router router = new Router({ element, routes, store, location }); return Promise.resolve(preStart && preStart())

@@ -718,30 +676,24 @@ };

h3.navigateTo = (path, params) => { if (!router) { - throw new Error( - "[h3.navigateTo] No application initialized, unable to navigate." - ); + throw new Error('[h3.navigateTo] No application initialized, unable to navigate.'); } return router.navigateTo(path, params); }; -Object.defineProperty(h3, "route", { +Object.defineProperty(h3, 'route', { get: () => { if (!router) { - throw new Error( - "[h3.route] No application initialized, unable to retrieve current route." - ); + throw new Error('[h3.route] No application initialized, unable to retrieve current route.'); } return router.route; }, }); -Object.defineProperty(h3, "state", { +Object.defineProperty(h3, 'state', { get: () => { if (!store) { - throw new Error( - "[h3.state] No application initialized, unable to retrieve current state." - ); + throw new Error('[h3.state] No application initialized, unable to retrieve current state.'); } return store.state; },

@@ -749,45 +701,41 @@ });

h3.on = (event, cb) => { if (!store) { - throw new Error( - "[h3.on] No application initialized, unable to listen to events." - ); + throw new Error('[h3.on] No application initialized, unable to listen to events.'); } return store.on(event, cb); }; h3.dispatch = (event, data) => { if (!store) { - throw new Error( - "[h3.dispatch] No application initialized, unable to dispatch events." - ); + throw new Error('[h3.dispatch] No application initialized, unable to dispatch events.'); } return store.dispatch(event, data); }; h3.redraw = (setRedrawing) => { if (!router || !router.redraw) { - throw new Error( - "[h3.redraw] No application initialized, unable to redraw." - ); + throw new Error('[h3.redraw] No application initialized, unable to redraw.'); } if (redrawing) { return; } redrawing = true; - router.redraw(); - redrawing = setRedrawing || false; + requestAnimationFrame(() => { + router.redraw(); + redrawing = setRedrawing || false; + }); }; h3.screen = ({ setup, display, teardown }) => { - if (!display || typeof display !== "function") { - throw new Error("[h3.screen] No display property specified."); + if (!display || typeof display !== 'function') { + throw new Error('[h3.screen] No display property specified.'); } - if (setup && typeof setup !== "function") { - throw new Error("[h3.screen] setup property is not a function."); + if (setup && typeof setup !== 'function') { + throw new Error('[h3.screen] setup property is not a function.'); } - if (teardown && typeof teardown !== "function") { - throw new Error("[h3.screen] teardown property is not a function."); + if (teardown && typeof teardown !== 'function') { + throw new Error('[h3.screen] teardown property is not a function.'); } const fn = display; if (setup) {
M docs/md/api.mddocs/md/api.md

@@ -186,6 +186,8 @@ ```

This example shows how to implement a simple component that renders an employee profile in the `display` function, fetches data (if necessary) in the `setup` function, and preserves data in the `teardown` function. +**Tip** To interrupt navigation or perform redirects, return **false** in the **setup** method. + ### h3.dispatch(event: string, data: any) Dispatches a event and optionally some data. Messages are typically handled centrally by modules.
M docs/md/best-practices.mddocs/md/best-practices.md

@@ -8,7 +8,7 @@ No, that's not a mistake. Although you should understand [why immutability is important](https://stackoverflow.com/questions/34385243/why-is-immutability-so-important-or-needed-in-javascript), you shouldn't force yourself to use it in all situations. Instead, you should go through [this article](https://desalasworks.com/article/immutability-in-javascript-a-contrarian-view/) and try to understand also a contrarian view of immutability.

In H3, changes only occur when needed. Most notably, when re-rendering the Virtual DOM tree of the application will be *mutated in place*, but only where necessary. Functions as well are considered equal if their source code (i.e. string representation) is equal. While this can cause problems in some situations if you are not aware of it, it can be beneficial and actually simplify things most of the time. -When managing state, if something is different you should typically _just change it_, unless it's shared across the whole application through the Store, in which case (but only in that case) you should try to manage change without side effects and following basic immutability rules. As a rule of thumb, Modules should manage shared application state in an immutable way. +When managing state, if something is different you should typically *just change it*, unless it's shared across the whole application through the Store, in which case (but only in that case) you should try to manage change without side effects and following basic immutability rules. As a rule of thumb, Modules should manage shared application state in an immutable way. ### Components
M docs/md/key-concepts.mddocs/md/key-concepts.md

@@ -67,6 +67,7 @@ Screens are typically created using the **h3.screen** shorthand method, but they can stll created using an 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 subsequent teardowns. +* If the **setup** method returns **false**, the **display** method of the screen (or the main screen function if you created it manually) will not be executed (and a **$navigation** event will be dispatched with **null** as data parameter). This can be useful in certain situations to interrupt navigation or perform redirects. * 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.
M docs/md/overview.mddocs/md/overview.md

@@ -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.10.0/h3.js" target="_blank" class="button primary">Download v0.10.0 (Jittery Jem'Hadar)</a> +<a href="https://raw.githubusercontent.com/h3rald/h3/v0.11.0/h3.js" target="_blank" class="button primary">Download v0.11.0 (Keen Klingon)</a> <small>Or get the minified version [here](https://raw.githubusercontent.com/h3rald/h3/v0.9.0/h3.min.js).</small>
M docs/md/quick-start.mddocs/md/quick-start.md

@@ -22,21 +22,21 @@ ```

Note that the script must be marked as an ES6 module (`type="module"`), otherwise your imports won't work. -### Import h3.js +### Import h3 and h from h3.js -Then, inside your `app.js` file, import `h3.js`, which should be accessible somewhere in your app: +Then, inside your `app.js` file, import `h` and `h3` from `h3.js`, which should be accessible somewhere in your app: ```js -import h3 from "./h3.js"; +import { h3, h } from "./h3.js"; ``` -This will work in [every modern browser except Internet Explorer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). You don't need a transpiler, you don't need something to convert your beautiful ES6 code back to cluncky ES5. +This will work in [every modern browser except Internet Explorer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). You don't need a transpiler, you don't need something to convert your beautiful ES6 code back to clunky ES5. Unless your company tells you to, do yourself a favor and don't support IE. It's 2020, even [Microsoft moved on](https://www.theverge.com/2020/1/15/21066767/microsoft-edge-chromium-new-browser-windows-mac-download-os), and now ES6 modules work in all major browsers. ### Create your SPA -After importing the `h3` object, you can start developing your SPA. A bare minimum SPA is comprised by a single component passed to the `h3.init()` method: +After importing the `h3` object and the `h` function, you can start developing your SPA. A bare minimum SPA is comprised by a single component passed to the `h3.init()` method: ```js // A simple component printing the current date and time
M docs/md/tutorial.mddocs/md/tutorial.md

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

h("img", { alt: "H3", src: "images/h3.svg" }), ]), h("div.version.col-sm.col-md", [ - h("div.version-number", "v0.10.0"), - h("div.version-label", "“Jittery Jem'Hadar“"), + h("div.version-number", "v0.11.0"), + h("div.version-label", "“Keen Klingon“"), ]), h("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);
M h3.jsh3.js

@@ -1,11 +1,14 @@

/** - * H3 v0.10.0 "Jittery Jem'Hadar" + * H3 v0.11.0 "Keen Klingon" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * @license MIT * For the full license, see: https://github.com/h3rald/h3/blob/master/LICENSE */ const checkProperties = (obj1, obj2) => { + if (Object.keys(obj1).length !== Object.keys(obj2).length) { + return false; + } for (const key in obj1) { if (!(key in obj2)) { return false;

@@ -17,25 +20,19 @@ }

return true; }; +const blank = (v) => [undefined, null].includes(v); + const equal = (obj1, obj2) => { - if ( - (obj1 === null && obj2 === null) || - (obj1 === undefined && obj2 === undefined) - ) { + if ((obj1 === null && obj2 === null) || (obj1 === undefined && obj2 === undefined)) { return true; } - if ( - (obj1 === undefined && obj2 !== undefined) || - (obj1 !== undefined && obj2 === undefined) || - (obj1 === null && obj2 !== null) || - (obj1 !== null && obj2 === null) - ) { + if ((obj1 === undefined && obj2 !== undefined) || (obj1 !== undefined && obj2 === undefined) || (obj1 === null && obj2 !== null) || (obj1 !== null && obj2 === null)) { return false; } if (obj1.constructor !== obj2.constructor) { return false; } - if (typeof obj1 === "function") { + if (typeof obj1 === 'function') { if (obj1.toString() !== obj2.toString()) { return false; }

@@ -54,7 +51,7 @@ }

} return true; } - return checkProperties(obj1, obj2); + return checkProperties(obj1, obj2); }; const selectorRegex = /^([a-z][a-z0-9:_=-]*)?(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i;

@@ -76,49 +73,37 @@ this.children = [];

this.classList = []; this.eventListeners = {}; if (args.length === 0) { - throw new Error("[VNode] No arguments passed to VNode constructor."); + throw new Error('[VNode] No arguments passed to VNode constructor.'); } if (args.length === 1) { let vnode = args[0]; - if (typeof vnode === "string") { + if (typeof vnode === 'string') { // Assume empty element this.processSelector(vnode); - } else if ( - typeof vnode === "function" || - (typeof vnode === "object" && vnode !== null) - ) { + } else if (typeof vnode === 'function' || (typeof vnode === 'object' && vnode !== null)) { // Text node - if (vnode.type === "#text") { - this.type = "#text"; + if (vnode.type === '#text') { + this.type = '#text'; this.value = vnode.value; } else { this.from(this.processVNodeObject(vnode)); } } else { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); + throw new Error('[VNode] Invalid first argument passed to VNode constructor.'); } } else if (args.length === 2) { let [selector, data] = args; - if (typeof selector !== "string") { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); + if (typeof selector !== 'string') { + throw new Error('[VNode] Invalid first argument passed to VNode constructor.'); } this.processSelector(selector); - if (typeof data === "string") { + if (typeof data === 'string') { // Assume single child text node - this.children = [new VNode({ type: "#text", value: data })]; + this.children = [new VNode({ type: '#text', value: data })]; return; } - if ( - typeof data !== "function" && - (typeof data !== "object" || data === null) - ) { - throw new Error( - "[VNode] The second argument of a VNode constructor must be an object, an array or a string." - ); + if (typeof data !== 'function' && (typeof data !== 'object' || data === null)) { + throw new Error('[VNode] The second argument of a VNode constructor must be an object, an array or a string.'); } if (Array.isArray(data)) { // Assume 2nd argument as children

@@ -137,24 +122,16 @@ if (args.length > 3) {

children = args.slice(2); } children = Array.isArray(children) ? children : [children]; - if (typeof selector !== "string") { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); + if (typeof selector !== 'string') { + throw new Error('[VNode] Invalid first argument passed to VNode constructor.'); } this.processSelector(selector); - if ( - props instanceof Function || - props instanceof VNode || - typeof props === "string" - ) { + if (props instanceof Function || props instanceof VNode || typeof props === 'string') { // 2nd argument is a child children = [props].concat(children); } else { - if (typeof props !== "object" || props === null) { - throw new Error( - "[VNode] Invalid second argument passed to VNode constructor." - ); + if (typeof props !== 'object' || props === null) { + throw new Error('[VNode] Invalid second argument passed to VNode constructor.'); } this.processProperties(props); }

@@ -188,18 +165,13 @@ this.$onrender = attrs.$onrender;

this.style = attrs.style; this.value = attrs.value; this.data = attrs.data || {}; - this.classList = - attrs.classList && attrs.classList.length > 0 - ? attrs.classList - : this.classList; + this.classList = attrs.classList && attrs.classList.length > 0 ? attrs.classList : this.classList; this.props = attrs; Object.keys(attrs) - .filter((a) => a.startsWith("on") && attrs[a]) + .filter((a) => a.startsWith('on') && attrs[a]) .forEach((key) => { - if (typeof attrs[key] !== "function") { - throw new Error( - `[VNode] Event handler specified for ${key} event is not a function.` - ); + if (typeof attrs[key] !== 'function') { + throw new Error(`[VNode] Event handler specified for ${key} event is not a function.`); } this.eventListeners[key.slice(2)] = attrs[key]; delete this.props[key];

@@ -222,7 +194,7 @@ this.type = type;

if (id) { this.id = id.slice(1); } - this.classList = (classes && classes.split(".").slice(1)) || []; + this.classList = (classes && classes.split('.').slice(1)) || []; } processVNodeObject(arg) {

@@ -231,27 +203,25 @@ return arg;

} if (arg instanceof Function) { let vnode = arg(); - if (typeof vnode === "string") { - vnode = new VNode({ type: "#text", value: vnode }); + if (typeof vnode === 'string') { + vnode = new VNode({ type: '#text', value: vnode }); } if (!(vnode instanceof VNode)) { - throw new Error("[VNode] Function argument does not return a VNode"); + throw new Error('[VNode] Function argument does not return a VNode'); } return vnode; } - throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." - ); + throw new Error('[VNode] Invalid first argument provided to VNode constructor.'); } processChildren(arg) { const children = Array.isArray(arg) ? arg : [arg]; this.children = children .map((c) => { - if (typeof c === "string") { - return new VNode({ type: "#text", value: c }); + if (typeof c === 'string') { + return new VNode({ type: '#text', value: c }); } - if (typeof c === "function" || (typeof c === "object" && c !== null)) { + if (typeof c === 'function' || (typeof c === 'object' && c !== null)) { return this.processVNodeObject(c); } if (c) {

@@ -263,19 +233,19 @@ }

// Renders the actual DOM Node corresponding to the current Virtual Node render() { - if (this.type === "#text") { + if (this.type === '#text') { return document.createTextNode(this.value); } const node = document.createElement(this.type); - if (this.id) { + if (!blank(this.id)) { node.id = this.id; } Object.keys(this.props).forEach((p) => { // Set attributes - if (typeof this.props[p] === "boolean") { - this.props[p] ? node.setAttribute(p, "") : node.removeAttribute(p); + if (typeof this.props[p] === 'boolean') { + this.props[p] ? node.setAttribute(p, '') : node.removeAttribute(p); } - if (["string", "number"].includes(typeof this.props[p])) { + if (['string', 'number'].includes(typeof this.props[p])) { node.setAttribute(p, this.props[p]); } // Set properties

@@ -287,10 +257,10 @@ node.addEventListener(event, this.eventListeners[event]);

}); // Value if (this.value) { - if (["textarea", "input"].includes(this.type)) { + if (['textarea', 'input', 'option', 'button'].includes(this.type)) { node.value = this.value; } else { - node.setAttribute("value", this.value); + node.setAttribute('value', this.value.toString()); } } // Style

@@ -299,7 +269,7 @@ node.style.cssText = this.style;

} // Classes this.classList.forEach((c) => { - node.classList.add(c); + c && node.classList.add(c); }); // Data Object.keys(this.data).forEach((key) => {

@@ -325,9 +295,7 @@ const oldvnode = this;

if ( oldvnode.constructor !== newvnode.constructor || oldvnode.type !== newvnode.type || - (oldvnode.type === newvnode.type && - oldvnode.type === "#text" && - oldvnode !== newvnode) + (oldvnode.type === newvnode.type && oldvnode.type === '#text' && oldvnode !== newvnode) ) { const renderedNode = newvnode.render(); node.parentNode.replaceChild(renderedNode, node);

@@ -337,16 +305,16 @@ return;

} // ID if (oldvnode.id !== newvnode.id) { - node.id = newvnode.id || ""; + node.id = newvnode.id; oldvnode.id = newvnode.id; } // Value if (oldvnode.value !== newvnode.value) { oldvnode.value = newvnode.value; - if (["textarea", "input"].includes(oldvnode.type)) { - node.value = newvnode.value || ""; + if (['textarea', 'input', 'option', 'button'].includes(oldvnode.type)) { + node.value = blank(newvnode.value) ? '' : newvnode.value.toString(); } else { - node.setAttribute("value", newvnode.value || ""); + node.setAttribute('value', blank(newvnode.value) ? '' : newvnode.value.toString()); } } // Classes

@@ -357,7 +325,7 @@ node.classList.remove(c);

} }); newvnode.classList.forEach((c) => { - if (!oldvnode.classList.includes(c)) { + if (c && !oldvnode.classList.includes(c)) { node.classList.add(c); } });

@@ -365,7 +333,7 @@ oldvnode.classList = newvnode.classList;

} // Style if (oldvnode.style !== newvnode.style) { - node.style.cssText = newvnode.style || ""; + node.style.cssText = newvnode.style || ''; oldvnode.style = newvnode.style; } // Data

@@ -388,28 +356,28 @@ // Properties & Attributes

if (!equal(oldvnode.props, newvnode.props)) { Object.keys(oldvnode.props).forEach((a) => { node[a] = newvnode.props[a]; - if (typeof newvnode.props[a] === "boolean") { + if (typeof newvnode.props[a] === 'boolean') { oldvnode.props[a] = newvnode.props[a]; - newvnode.props[a] - ? node.setAttribute(a, "") - : node.removeAttribute(a); - } else if (!newvnode.props[a]) { + newvnode.props[a] ? node.setAttribute(a, '') : node.removeAttribute(a); + } else if ([null, undefined].includes(newvnode.props[a])) { delete oldvnode.props[a]; node.removeAttribute(a); - } else if ( - newvnode.props[a] && - newvnode.props[a] !== oldvnode.props[a] - ) { + } else if (newvnode.props[a] !== oldvnode.props[a]) { oldvnode.props[a] = newvnode.props[a]; - if (["string", "number"].includes(typeof newvnode.props[a])) { + if (['string', 'number'].includes(typeof newvnode.props[a])) { node.setAttribute(a, newvnode.props[a]); } } }); Object.keys(newvnode.props).forEach((a) => { - if (!oldvnode.props[a] && newvnode.props[a]) { + if (blank(oldvnode.props[a]) && !blank(newvnode.props[a])) { oldvnode.props[a] = newvnode.props[a]; - node.setAttribute(a, newvnode.props[a]); + node[a] = newvnode.props[a]; + if (typeof newvnode.props[a] === 'boolean') { + node.setAttribute(a, ''); + } else if (['string', 'number'].includes(typeof newvnode.props[a])) { + node.setAttribute(a, newvnode.props[a]); + } } }); }

@@ -418,9 +386,7 @@ if (!equal(oldvnode.eventListeners, newvnode.eventListeners)) {

Object.keys(oldvnode.eventListeners).forEach((a) => { if (!newvnode.eventListeners[a]) { node.removeEventListener(a, oldvnode.eventListeners[a]); - } else if ( - !equal(newvnode.eventListeners[a], oldvnode.eventListeners[a]) - ) { + } else if (!equal(newvnode.eventListeners[a], oldvnode.eventListeners[a])) { node.removeEventListener(a, oldvnode.eventListeners[a]); node.addEventListener(a, newvnode.eventListeners[a]); }

@@ -455,8 +421,7 @@ case INSERT: {

oldvnode.children.splice(count, 0, newvnode.children[count]); const renderedNode = newvnode.children[count].render(); node.insertBefore(renderedNode, node.childNodes[count]); - newvnode.children[count].$onrender && - newvnode.children[count].$onrender(renderedNode); + newvnode.children[count].$onrender && newvnode.children[count].$onrender(renderedNode); break checkmap; } case DELETE: {

@@ -501,11 +466,7 @@ op = oIdx; // Same node found

break; } } - if ( - op < 0 && - newList.length >= oldList.length && - map.length >= oldList.length - ) { + if (op < 0 && newList.length >= oldList.length && map.length >= oldList.length) { op = INSERT; } map.push(op);

@@ -513,9 +474,7 @@ }

const oldNodesFound = map.filter((c) => c >= 0); if (oldList.length > newList.length) { // Remove remaining nodes - [...Array(oldList.length - newList.length).keys()].forEach(() => - map.push(DELETE) - ); + [...Array(oldList.length - newList.length).keys()].forEach(() => map.push(DELETE)); } else if (oldNodesFound.length === oldList.length) { // All nodes not found are insertions map = map.map((c) => (c < 0 ? INSERT : c));

@@ -535,7 +494,7 @@ this.events = {};

this.state = {}; } dispatch(event, data) { - if (event !== "$log") this.dispatch("$log", { event, data }); + if (event !== '$log') this.dispatch('$log', { event, data }); if (this.events[event]) { let changes = {}; let changed;

@@ -562,9 +521,9 @@ this.query = query;

this.parts = parts; this.params = {}; if (this.query) { - const rawParams = this.query.split("&"); + const rawParams = this.query.split('&'); rawParams.forEach((p) => { - const [name, value] = p.split("="); + const [name, value] = p.split('='); this.params[decodeURIComponent(name)] = decodeURIComponent(value); }); }

@@ -578,7 +537,7 @@ this.redraw = null;

this.store = store; this.location = location || window.location; if (!routes || Object.keys(routes).length === 0) { - throw new Error("[Router] No routes defined."); + throw new Error('[Router] No routes defined.'); } const defs = Object.keys(routes); this.routes = routes;

@@ -590,34 +549,30 @@ vnode.redraw({

node: this.element.childNodes[0], vnode: this.routes[this.route.def](state), }); - this.store.dispatch("$redraw"); + this.store.dispatch('$redraw'); }; } async start() { const processPath = async (data) => { const oldRoute = this.route; - const fragment = - (data && - data.newURL && - data.newURL.match(/(#.+)$/) && - data.newURL.match(/(#.+)$/)[1]) || - this.location.hash; - const path = fragment.replace(/\?.+$/, "").slice(1); + const fragment = (data && data.newURL && data.newURL.match(/(#.+)$/) && data.newURL.match(/(#.+)$/)[1]) || this.location.hash; + const path = fragment.replace(/\?.+$/, '').slice(1); const rawQuery = fragment.match(/\?(.+)$/); - const query = rawQuery && rawQuery[1] ? rawQuery[1] : ""; - const pathParts = path.split("/").slice(1); + const query = rawQuery && rawQuery[1] ? rawQuery[1] : ''; + const pathParts = path.split('/').slice(1); let parts = {}; + let newRoute; for (let def of Object.keys(this.routes)) { - let routeParts = def.split("/").slice(1); + let routeParts = def.split('/').slice(1); let match = true; let index = 0; parts = {}; while (match && routeParts[index]) { const rP = routeParts[index]; const pP = pathParts[index]; - if (rP.startsWith(":") && pP) { + if (rP.startsWith(':') && pP) { parts[rP.slice(1)] = pP; } else { match = rP === pP;

@@ -625,30 +580,35 @@ }

index++; } if (match) { - this.route = new Route({ query, path, def, parts }); + newRoute = new Route({ query, path, def, parts }); break; } } - if (!this.route) { + if (!newRoute) { throw new Error(`[Router] No route matches '${fragment}'`); } // Old route component teardown let state = {}; if (oldRoute) { const oldRouteComponent = this.routes[oldRoute.def]; - state = - (oldRouteComponent.teardown && - (await oldRouteComponent.teardown(oldRouteComponent.state))) || - state; + state = (oldRouteComponent.teardown && (await oldRouteComponent.teardown(oldRouteComponent.state))) || state; } // New route component setup - const newRouteComponent = this.routes[this.route.def]; + const newRouteComponent = this.routes[newRoute.def]; newRouteComponent.state = state; - newRouteComponent.setup && - (await newRouteComponent.setup(newRouteComponent.state)); + if (newRouteComponent.setup) { + this.route = newRoute; + if ((await newRouteComponent.setup(newRouteComponent.state)) === false) { + // Abort navigation + this.route = oldRoute; + this.store.dispatch('$navigation', null); + return; + } + } + this.route = newRoute; // Redrawing... redrawing = true; - this.store.dispatch("$navigation", this.route); + this.store.dispatch('$navigation', this.route); while (this.element.firstChild) { this.element.removeChild(this.element.firstChild); }

@@ -661,17 +621,17 @@ vnode.$onrender && vnode.$onrender(node);

$onrenderCallbacks.forEach((cbk) => cbk()); $onrenderCallbacks = []; window.scrollTo(0, 0); - this.store.dispatch("$redraw"); + this.store.dispatch('$redraw'); }; - window.addEventListener("hashchange", processPath); + window.addEventListener('hashchange', processPath); await processPath(); } navigateTo(path, params) { let query = Object.keys(params || {}) .map((p) => `${encodeURIComponent(p)}=${encodeURIComponent(params[p])}`) - .join("&"); - query = query ? `?${query}` : ""; + .join('&'); + query = query ? `?${query}` : ''; this.location.hash = `#${path}${query}`; } }

@@ -692,23 +652,21 @@ h3.init = (config) => {

let { element, routes, modules, preStart, postStart, location } = config; if (!routes) { // Assume config is a component object, define default route - if (typeof config !== "function") { - throw new Error( - "[h3.init] The specified argument is not a valid configuration object or component function" - ); + if (typeof config !== 'function') { + throw new Error('[h3.init] The specified argument is not a valid configuration object or component function'); } - routes = { "/": config }; + routes = { '/': config }; } element = element || document.body; if (!(element && element instanceof Element)) { - throw new Error("[h3.init] Invalid element specified."); + throw new Error('[h3.init] Invalid element specified.'); } // Initialize store store = new Store(); (modules || []).forEach((i) => { i(store); }); - store.dispatch("$init"); + store.dispatch('$init'); // Initialize router router = new Router({ element, routes, store, location }); return Promise.resolve(preStart && preStart())

@@ -718,30 +676,24 @@ };

h3.navigateTo = (path, params) => { if (!router) { - throw new Error( - "[h3.navigateTo] No application initialized, unable to navigate." - ); + throw new Error('[h3.navigateTo] No application initialized, unable to navigate.'); } return router.navigateTo(path, params); }; -Object.defineProperty(h3, "route", { +Object.defineProperty(h3, 'route', { get: () => { if (!router) { - throw new Error( - "[h3.route] No application initialized, unable to retrieve current route." - ); + throw new Error('[h3.route] No application initialized, unable to retrieve current route.'); } return router.route; }, }); -Object.defineProperty(h3, "state", { +Object.defineProperty(h3, 'state', { get: () => { if (!store) { - throw new Error( - "[h3.state] No application initialized, unable to retrieve current state." - ); + throw new Error('[h3.state] No application initialized, unable to retrieve current state.'); } return store.state; },

@@ -749,45 +701,41 @@ });

h3.on = (event, cb) => { if (!store) { - throw new Error( - "[h3.on] No application initialized, unable to listen to events." - ); + throw new Error('[h3.on] No application initialized, unable to listen to events.'); } return store.on(event, cb); }; h3.dispatch = (event, data) => { if (!store) { - throw new Error( - "[h3.dispatch] No application initialized, unable to dispatch events." - ); + throw new Error('[h3.dispatch] No application initialized, unable to dispatch events.'); } return store.dispatch(event, data); }; h3.redraw = (setRedrawing) => { if (!router || !router.redraw) { - throw new Error( - "[h3.redraw] No application initialized, unable to redraw." - ); + throw new Error('[h3.redraw] No application initialized, unable to redraw.'); } if (redrawing) { return; } redrawing = true; - router.redraw(); - redrawing = setRedrawing || false; + requestAnimationFrame(() => { + router.redraw(); + redrawing = setRedrawing || false; + }); }; h3.screen = ({ setup, display, teardown }) => { - if (!display || typeof display !== "function") { - throw new Error("[h3.screen] No display property specified."); + if (!display || typeof display !== 'function') { + throw new Error('[h3.screen] No display property specified.'); } - if (setup && typeof setup !== "function") { - throw new Error("[h3.screen] setup property is not a function."); + if (setup && typeof setup !== 'function') { + throw new Error('[h3.screen] setup property is not a function.'); } - if (teardown && typeof teardown !== "function") { - throw new Error("[h3.screen] teardown property is not a function."); + if (teardown && typeof teardown !== 'function') { + throw new Error('[h3.screen] teardown property is not a function.'); } const fn = display; if (setup) {
M h3.js.maph3.js.map

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

-{"version":3,"sources":["0"],"names":["checkProperties","obj1","obj2","key","equal","undefined","constructor","toString","String","Number","Boolean","includes","Array","length","i","selectorRegex","PATCH","INSERT","DELETE","$onrenderCallbacks","VNode","[object Object]","args","this","type","props","data","id","$html","$onrender","style","value","children","classList","eventListeners","Error","vnode","processSelector","from","processVNodeObject","selector","isArray","Function","processChildren","processProperties","slice","concat","a","b","attrs","Object","keys","filter","startsWith","forEach","match","classes","split","arg","map","c","document","createTextNode","node","createElement","p","setAttribute","removeAttribute","event","addEventListener","cssText","add","dataset","cnode","render","appendChild","push","innerHTML","newvnode","oldvnode","renderedNode","parentNode","replaceChild","remove","removeEventListener","childMap","mapChildren","resultMap","count","checkmap","redraw","childNodes","splice","insertBefore","removeChild","vtarget","target","newList","oldList","nIdx","op","oIdx","oldNodesFound","Store","events","state","dispatch","cb","Route","path","def","query","parts","params","name","decodeURIComponent","Router","element","routes","store","location","window","route","processPath","async","oldRoute","fragment","newURL","hash","replace","rawQuery","pathParts","routeParts","index","rP","pP","oldRouteComponent","teardown","newRouteComponent","setup","redrawing","firstChild","setRedraw","cbk","scrollTo","encodeURIComponent","join","h","h3","router","init","config","modules","preStart","postStart","/","body","Element","Promise","resolve","then","start","navigateTo","defineProperty","get","on","setRedrawing","screen","display","fn"],"mappings":"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","file":"h3.js"}+{"version":3,"sources":["0"],"names":["checkProperties","obj1","obj2","Object","keys","length","key","equal","blank","v","undefined","includes","constructor","toString","String","Number","Boolean","Array","i","selectorRegex","PATCH","INSERT","DELETE","$onrenderCallbacks","VNode","[object Object]","args","this","type","props","data","id","$html","$onrender","style","value","children","classList","eventListeners","Error","vnode","processSelector","from","processVNodeObject","selector","isArray","Function","processChildren","processProperties","slice","concat","a","b","attrs","filter","startsWith","forEach","match","classes","split","arg","map","c","document","createTextNode","node","createElement","p","setAttribute","removeAttribute","event","addEventListener","cssText","add","dataset","cnode","render","appendChild","push","innerHTML","newvnode","oldvnode","renderedNode","parentNode","replaceChild","remove","removeEventListener","childMap","mapChildren","resultMap","count","checkmap","redraw","childNodes","splice","insertBefore","removeChild","vtarget","target","newList","oldList","nIdx","op","oIdx","oldNodesFound","Store","events","state","dispatch","cb","Route","path","def","query","parts","params","name","decodeURIComponent","Router","element","routes","store","location","window","route","processPath","async","oldRoute","fragment","newURL","hash","replace","rawQuery","pathParts","newRoute","routeParts","index","rP","pP","oldRouteComponent","teardown","newRouteComponent","setup","redrawing","firstChild","setRedraw","cbk","scrollTo","encodeURIComponent","join","h","h3","router","init","config","modules","preStart","postStart","/","body","Element","Promise","resolve","then","start","navigateTo","defineProperty","get","on","setRedrawing","requestAnimationFrame","screen","display","fn"],"mappings":"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","file":"h3.js"}
M h3.min.jsh3.min.js

@@ -1,9 +1,9 @@

/** - * H3 v0.10.0 "Jittery Jem'Hadar" + * H3 v0.11.0 "Keen Klingon" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * @license MIT * For the full license, see: https://github.com/h3rald/h3/blob/master/LICENSE */ -const checkProperties=(e,t)=>{for(const r in e){if(!(r in t))return!1;if(!equal(e[r],t[r]))return!1}return!0},equal=(e,t)=>{if(null===e&&null===t||void 0===e&&void 0===t)return!0;if(void 0===e&&void 0!==t||void 0!==e&&void 0===t||null===e&&null!==t||null!==e&&null===t)return!1;if(e.constructor!==t.constructor)return!1;if("function"==typeof e&&e.toString()!==t.toString())return!1;if([String,Number,Boolean].includes(e.constructor))return e===t;if(e.constructor===Array){if(e.length!==t.length)return!1;for(let r=0;r<e.length;r++)if(!equal(e[r],t[r]))return!1;return!0}return checkProperties(e,t)},selectorRegex=/^([a-z][a-z0-9:_=-]*)?(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i,[PATCH,INSERT,DELETE]=[-1,-2,-3];let $onrenderCallbacks=[];class VNode{constructor(...e){if(this.type=void 0,this.props={},this.data={},this.id=void 0,this.$html=void 0,this.$onrender=void 0,this.style=void 0,this.value=void 0,this.children=[],this.classList=[],this.eventListeners={},0===e.length)throw new Error("[VNode] No arguments passed to VNode constructor.");if(1===e.length){let t=e[0];if("string"==typeof t)this.processSelector(t);else{if("function"!=typeof t&&("object"!=typeof t||null===t))throw new Error("[VNode] Invalid first argument passed to VNode constructor.");"#text"===t.type?(this.type="#text",this.value=t.value):this.from(this.processVNodeObject(t))}}else if(2===e.length){let[t,r]=e;if("string"!=typeof t)throw new Error("[VNode] Invalid first argument passed to VNode constructor.");if(this.processSelector(t),"string"==typeof r)return void(this.children=[new VNode({type:"#text",value:r})]);if("function"!=typeof r&&("object"!=typeof r||null===r))throw new Error("[VNode] The second argument of a VNode constructor must be an object, an array or a string.");Array.isArray(r)||r instanceof Function||r instanceof VNode?this.processChildren(r):this.processProperties(r)}else{let[t,r,s]=e;if(e.length>3&&(s=e.slice(2)),s=Array.isArray(s)?s:[s],"string"!=typeof t)throw new Error("[VNode] Invalid first argument passed to VNode constructor.");if(this.processSelector(t),r instanceof Function||r instanceof VNode||"string"==typeof r)s=[r].concat(s);else{if("object"!=typeof r||null===r)throw new Error("[VNode] Invalid second argument passed to VNode constructor.");this.processProperties(r)}this.processChildren(s)}}from(e){this.value=e.value,this.type=e.type,this.id=e.id,this.$html=e.$html,this.$onrender=e.$onrender,this.style=e.style,this.data=e.data,this.value=e.value,this.eventListeners=e.eventListeners,this.children=e.children,this.props=e.props,this.classList=e.classList}equal(e,t){return equal(e,void 0===t?this:t)}processProperties(e){this.id=this.id||e.id,this.$html=e.$html,this.$onrender=e.$onrender,this.style=e.style,this.value=e.value,this.data=e.data||{},this.classList=e.classList&&e.classList.length>0?e.classList:this.classList,this.props=e,Object.keys(e).filter(t=>t.startsWith("on")&&e[t]).forEach(t=>{if("function"!=typeof e[t])throw new Error(`[VNode] Event handler specified for ${t} event is not a function.`);this.eventListeners[t.slice(2)]=e[t],delete this.props[t]}),delete this.props.value,delete this.props.$html,delete this.props.$onrender,delete this.props.id,delete this.props.data,delete this.props.style,delete this.props.classList}processSelector(e){if(!e.match(selectorRegex)||0===e.length)throw new Error("[VNode] Invalid selector: "+e);const[,t,r,s]=e.match(selectorRegex);this.type=t,r&&(this.id=r.slice(1)),this.classList=s&&s.split(".").slice(1)||[]}processVNodeObject(e){if(e instanceof VNode)return e;if(e instanceof Function){let t=e();if("string"==typeof t&&(t=new VNode({type:"#text",value:t})),!(t instanceof VNode))throw new Error("[VNode] Function argument does not return a VNode");return t}throw new Error("[VNode] Invalid first argument provided to VNode constructor.")}processChildren(e){const t=Array.isArray(e)?e:[e];this.children=t.map(e=>{if("string"==typeof e)return new VNode({type:"#text",value:e});if("function"==typeof e||"object"==typeof e&&null!==e)return this.processVNodeObject(e);if(e)throw new Error("[VNode] Specified child is not a VNode: "+e)}).filter(e=>e)}render(){if("#text"===this.type)return document.createTextNode(this.value);const e=document.createElement(this.type);return this.id&&(e.id=this.id),Object.keys(this.props).forEach(t=>{"boolean"==typeof this.props[t]&&(this.props[t]?e.setAttribute(t,""):e.removeAttribute(t)),["string","number"].includes(typeof this.props[t])&&e.setAttribute(t,this.props[t]),e[t]=this.props[t]}),Object.keys(this.eventListeners).forEach(t=>{e.addEventListener(t,this.eventListeners[t])}),this.value&&(["textarea","input"].includes(this.type)?e.value=this.value:e.setAttribute("value",this.value)),this.style&&(e.style.cssText=this.style),this.classList.forEach(t=>{e.classList.add(t)}),Object.keys(this.data).forEach(t=>{e.dataset[t]=this.data[t]}),this.children.forEach(t=>{const r=t.render();e.appendChild(r),t.$onrender&&$onrenderCallbacks.push(()=>t.$onrender(r))}),this.$html&&(e.innerHTML=this.$html),e}redraw(e){let{node:t,vnode:r}=e;const s=r,o=this;if(o.constructor!==s.constructor||o.type!==s.type||o.type===s.type&&"#text"===o.type&&o!==s){const e=s.render();return t.parentNode.replaceChild(e,t),s.$onrender&&s.$onrender(e),void o.from(s)}o.id!==s.id&&(t.id=s.id||"",o.id=s.id),o.value!==s.value&&(o.value=s.value,["textarea","input"].includes(o.type)?t.value=s.value||"":t.setAttribute("value",s.value||"")),equal(o.classList,s.classList)||(o.classList.forEach(e=>{s.classList.includes(e)||t.classList.remove(e)}),s.classList.forEach(e=>{o.classList.includes(e)||t.classList.add(e)}),o.classList=s.classList),o.style!==s.style&&(t.style.cssText=s.style||"",o.style=s.style),equal(o.data,s.data)||(Object.keys(o.data).forEach(e=>{s.data[e]?s.data[e]!==o.data[e]&&(t.dataset[e]=s.data[e]):delete t.dataset[e]}),Object.keys(s.data).forEach(e=>{o.data[e]||(t.dataset[e]=s.data[e])}),o.data=s.data),equal(o.props,s.props)||(Object.keys(o.props).forEach(e=>{t[e]=s.props[e],"boolean"==typeof s.props[e]?(o.props[e]=s.props[e],s.props[e]?t.setAttribute(e,""):t.removeAttribute(e)):s.props[e]?s.props[e]&&s.props[e]!==o.props[e]&&(o.props[e]=s.props[e],["string","number"].includes(typeof s.props[e])&&t.setAttribute(e,s.props[e])):(delete o.props[e],t.removeAttribute(e))}),Object.keys(s.props).forEach(e=>{!o.props[e]&&s.props[e]&&(o.props[e]=s.props[e],t.setAttribute(e,s.props[e]))})),equal(o.eventListeners,s.eventListeners)||(Object.keys(o.eventListeners).forEach(e=>{s.eventListeners[e]?equal(s.eventListeners[e],o.eventListeners[e])||(t.removeEventListener(e,o.eventListeners[e]),t.addEventListener(e,s.eventListeners[e])):t.removeEventListener(e,o.eventListeners[e])}),Object.keys(s.eventListeners).forEach(e=>{o.eventListeners[e]||t.addEventListener(e,s.eventListeners[e])}),o.eventListeners=s.eventListeners);let i=mapChildren(o,s),n=[...Array(s.children.length).keys()];for(;!equal(i,n);){let e=-1;e:for(const r of i)if(e++,r!==e)switch(r){case PATCH:o.children[e].redraw({node:t.childNodes[e],vnode:s.children[e]});break e;case INSERT:{o.children.splice(e,0,s.children[e]);const r=s.children[e].render();t.insertBefore(r,t.childNodes[e]),s.children[e].$onrender&&s.children[e].$onrender(r);break e}case DELETE:o.children.splice(e,1),t.removeChild(t.childNodes[e]);break e;default:{const s=o.children.splice(r,1)[0];o.children.splice(e,0,s);const i=t.removeChild(t.childNodes[r]);t.insertBefore(i,t.childNodes[e]);break e}}i=mapChildren(o,s),n=[...Array(s.children.length).keys()]}equal(o.$onrender,s.$onrender)||(o.$onrender=s.$onrender),o.$html!==s.$html&&(t.innerHTML=s.$html,o.$html=s.$html,o.$onrender&&o.$onrender(t))}}const mapChildren=(e,t)=>{const r=t.children,s=e.children;let o=[];for(let e=0;e<r.length;e++){let t=PATCH;for(let i=0;i<s.length;i++)if(equal(r[e],s[i])&&!o.includes(i)){t=i;break}t<0&&r.length>=s.length&&o.length>=s.length&&(t=INSERT),o.push(t)}const i=o.filter(e=>e>=0);return s.length>r.length?[...Array(s.length-r.length).keys()].forEach(()=>o.push(DELETE)):i.length===s.length&&(o=o.map(e=>e<0?INSERT:e)),o};class Store{constructor(){this.events={},this.state={}}dispatch(e,t){if("$log"!==e&&this.dispatch("$log",{event:e,data:t}),this.events[e]){this.events[e].forEach(e=>{this.state={...this.state,...e(this.state,t)}})}}on(e,t){return(this.events[e]||(this.events[e]=[])).push(t),()=>{this.events[e]=this.events[e].filter(e=>e!==t)}}}class Route{constructor({path:e,def:t,query:r,parts:s}){if(this.path=e,this.def=t,this.query=r,this.parts=s,this.params={},this.query){this.query.split("&").forEach(e=>{const[t,r]=e.split("=");this.params[decodeURIComponent(t)]=decodeURIComponent(r)})}}}class Router{constructor({element:e,routes:t,store:r,location:s}){if(this.element=e,this.redraw=null,this.store=r,this.location=s||window.location,!t||0===Object.keys(t).length)throw new Error("[Router] No routes defined.");Object.keys(t);this.routes=t}setRedraw(e,t){this.redraw=()=>{e.redraw({node:this.element.childNodes[0],vnode:this.routes[this.route.def](t)}),this.store.dispatch("$redraw")}}async start(){const e=async e=>{const t=this.route,r=e&&e.newURL&&e.newURL.match(/(#.+)$/)&&e.newURL.match(/(#.+)$/)[1]||this.location.hash,s=r.replace(/\?.+$/,"").slice(1),o=r.match(/\?(.+)$/),i=o&&o[1]?o[1]:"",n=s.split("/").slice(1);let a={};for(let e of Object.keys(this.routes)){let t=e.split("/").slice(1),r=!0,o=0;for(a={};r&&t[o];){const e=t[o],s=n[o];e.startsWith(":")&&s?a[e.slice(1)]=s:r=e===s,o++}if(r){this.route=new Route({query:i,path:s,def:e,parts:a});break}}if(!this.route)throw new Error(`[Router] No route matches '${r}'`);let l={};if(t){const e=this.routes[t.def];l=e.teardown&&await e.teardown(e.state)||l}const h=this.routes[this.route.def];for(h.state=l,h.setup&&await h.setup(h.state),redrawing=!0,this.store.dispatch("$navigation",this.route);this.element.firstChild;)this.element.removeChild(this.element.firstChild);const d=h(h.state),c=d.render();this.element.appendChild(c),this.setRedraw(d,h.state),redrawing=!1,d.$onrender&&d.$onrender(c),$onrenderCallbacks.forEach(e=>e()),$onrenderCallbacks=[],window.scrollTo(0,0),this.store.dispatch("$redraw")};window.addEventListener("hashchange",e),await e()}navigateTo(e,t){let r=Object.keys(t||{}).map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(t[e])}`).join("&");r=r?"?"+r:"",this.location.hash=`#${e}${r}`}}export const h=(...e)=>new VNode(...e);export const h3={};let store=null,router=null,redrawing=!1;h3.init=e=>{let{element:t,routes:r,modules:s,preStart:o,postStart:i,location:n}=e;if(!r){if("function"!=typeof e)throw new Error("[h3.init] The specified argument is not a valid configuration object or component function");r={"/":e}}if(t=t||document.body,!(t&&t instanceof Element))throw new Error("[h3.init] Invalid element specified.");return store=new Store,(s||[]).forEach(e=>{e(store)}),store.dispatch("$init"),router=new Router({element:t,routes:r,store:store,location:n}),Promise.resolve(o&&o()).then(()=>router.start()).then(()=>i&&i())},h3.navigateTo=(e,t)=>{if(!router)throw new Error("[h3.navigateTo] No application initialized, unable to navigate.");return router.navigateTo(e,t)},Object.defineProperty(h3,"route",{get:()=>{if(!router)throw new Error("[h3.route] No application initialized, unable to retrieve current route.");return router.route}}),Object.defineProperty(h3,"state",{get:()=>{if(!store)throw new Error("[h3.state] No application initialized, unable to retrieve current state.");return store.state}}),h3.on=(e,t)=>{if(!store)throw new Error("[h3.on] No application initialized, unable to listen to events.");return store.on(e,t)},h3.dispatch=(e,t)=>{if(!store)throw new Error("[h3.dispatch] No application initialized, unable to dispatch events.");return store.dispatch(e,t)},h3.redraw=e=>{if(!router||!router.redraw)throw new Error("[h3.redraw] No application initialized, unable to redraw.");redrawing||(redrawing=!0,router.redraw(),redrawing=e||!1)},h3.screen=({setup:e,display:t,teardown:r})=>{if(!t||"function"!=typeof t)throw new Error("[h3.screen] No display property specified.");if(e&&"function"!=typeof e)throw new Error("[h3.screen] setup property is not a function.");if(r&&"function"!=typeof r)throw new Error("[h3.screen] teardown property is not a function.");const s=t;return e&&(s.setup=e),r&&(s.teardown=r),s};export default h3; +const checkProperties=(e,t)=>{if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const r in e){if(!(r in t))return!1;if(!equal(e[r],t[r]))return!1}return!0},blank=e=>[void 0,null].includes(e),equal=(e,t)=>{if(null===e&&null===t||void 0===e&&void 0===t)return!0;if(void 0===e&&void 0!==t||void 0!==e&&void 0===t||null===e&&null!==t||null!==e&&null===t)return!1;if(e.constructor!==t.constructor)return!1;if("function"==typeof e&&e.toString()!==t.toString())return!1;if([String,Number,Boolean].includes(e.constructor))return e===t;if(e.constructor===Array){if(e.length!==t.length)return!1;for(let r=0;r<e.length;r++)if(!equal(e[r],t[r]))return!1;return!0}return checkProperties(e,t)},selectorRegex=/^([a-z][a-z0-9:_=-]*)?(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i,[PATCH,INSERT,DELETE]=[-1,-2,-3];let $onrenderCallbacks=[];class VNode{constructor(...e){if(this.type=void 0,this.props={},this.data={},this.id=void 0,this.$html=void 0,this.$onrender=void 0,this.style=void 0,this.value=void 0,this.children=[],this.classList=[],this.eventListeners={},0===e.length)throw new Error("[VNode] No arguments passed to VNode constructor.");if(1===e.length){let t=e[0];if("string"==typeof t)this.processSelector(t);else{if("function"!=typeof t&&("object"!=typeof t||null===t))throw new Error("[VNode] Invalid first argument passed to VNode constructor.");"#text"===t.type?(this.type="#text",this.value=t.value):this.from(this.processVNodeObject(t))}}else if(2===e.length){let[t,r]=e;if("string"!=typeof t)throw new Error("[VNode] Invalid first argument passed to VNode constructor.");if(this.processSelector(t),"string"==typeof r)return void(this.children=[new VNode({type:"#text",value:r})]);if("function"!=typeof r&&("object"!=typeof r||null===r))throw new Error("[VNode] The second argument of a VNode constructor must be an object, an array or a string.");Array.isArray(r)||r instanceof Function||r instanceof VNode?this.processChildren(r):this.processProperties(r)}else{let[t,r,s]=e;if(e.length>3&&(s=e.slice(2)),s=Array.isArray(s)?s:[s],"string"!=typeof t)throw new Error("[VNode] Invalid first argument passed to VNode constructor.");if(this.processSelector(t),r instanceof Function||r instanceof VNode||"string"==typeof r)s=[r].concat(s);else{if("object"!=typeof r||null===r)throw new Error("[VNode] Invalid second argument passed to VNode constructor.");this.processProperties(r)}this.processChildren(s)}}from(e){this.value=e.value,this.type=e.type,this.id=e.id,this.$html=e.$html,this.$onrender=e.$onrender,this.style=e.style,this.data=e.data,this.value=e.value,this.eventListeners=e.eventListeners,this.children=e.children,this.props=e.props,this.classList=e.classList}equal(e,t){return equal(e,void 0===t?this:t)}processProperties(e){this.id=this.id||e.id,this.$html=e.$html,this.$onrender=e.$onrender,this.style=e.style,this.value=e.value,this.data=e.data||{},this.classList=e.classList&&e.classList.length>0?e.classList:this.classList,this.props=e,Object.keys(e).filter(t=>t.startsWith("on")&&e[t]).forEach(t=>{if("function"!=typeof e[t])throw new Error(`[VNode] Event handler specified for ${t} event is not a function.`);this.eventListeners[t.slice(2)]=e[t],delete this.props[t]}),delete this.props.value,delete this.props.$html,delete this.props.$onrender,delete this.props.id,delete this.props.data,delete this.props.style,delete this.props.classList}processSelector(e){if(!e.match(selectorRegex)||0===e.length)throw new Error("[VNode] Invalid selector: "+e);const[,t,r,s]=e.match(selectorRegex);this.type=t,r&&(this.id=r.slice(1)),this.classList=s&&s.split(".").slice(1)||[]}processVNodeObject(e){if(e instanceof VNode)return e;if(e instanceof Function){let t=e();if("string"==typeof t&&(t=new VNode({type:"#text",value:t})),!(t instanceof VNode))throw new Error("[VNode] Function argument does not return a VNode");return t}throw new Error("[VNode] Invalid first argument provided to VNode constructor.")}processChildren(e){const t=Array.isArray(e)?e:[e];this.children=t.map(e=>{if("string"==typeof e)return new VNode({type:"#text",value:e});if("function"==typeof e||"object"==typeof e&&null!==e)return this.processVNodeObject(e);if(e)throw new Error("[VNode] Specified child is not a VNode: "+e)}).filter(e=>e)}render(){if("#text"===this.type)return document.createTextNode(this.value);const e=document.createElement(this.type);return blank(this.id)||(e.id=this.id),Object.keys(this.props).forEach(t=>{"boolean"==typeof this.props[t]&&(this.props[t]?e.setAttribute(t,""):e.removeAttribute(t)),["string","number"].includes(typeof this.props[t])&&e.setAttribute(t,this.props[t]),e[t]=this.props[t]}),Object.keys(this.eventListeners).forEach(t=>{e.addEventListener(t,this.eventListeners[t])}),this.value&&(["textarea","input","option","button"].includes(this.type)?e.value=this.value:e.setAttribute("value",this.value.toString())),this.style&&(e.style.cssText=this.style),this.classList.forEach(t=>{t&&e.classList.add(t)}),Object.keys(this.data).forEach(t=>{e.dataset[t]=this.data[t]}),this.children.forEach(t=>{const r=t.render();e.appendChild(r),t.$onrender&&$onrenderCallbacks.push(()=>t.$onrender(r))}),this.$html&&(e.innerHTML=this.$html),e}redraw(e){let{node:t,vnode:r}=e;const s=r,o=this;if(o.constructor!==s.constructor||o.type!==s.type||o.type===s.type&&"#text"===o.type&&o!==s){const e=s.render();return t.parentNode.replaceChild(e,t),s.$onrender&&s.$onrender(e),void o.from(s)}o.id!==s.id&&(t.id=s.id,o.id=s.id),o.value!==s.value&&(o.value=s.value,["textarea","input","option","button"].includes(o.type)?t.value=blank(s.value)?"":s.value.toString():t.setAttribute("value",blank(s.value)?"":s.value.toString())),equal(o.classList,s.classList)||(o.classList.forEach(e=>{s.classList.includes(e)||t.classList.remove(e)}),s.classList.forEach(e=>{e&&!o.classList.includes(e)&&t.classList.add(e)}),o.classList=s.classList),o.style!==s.style&&(t.style.cssText=s.style||"",o.style=s.style),equal(o.data,s.data)||(Object.keys(o.data).forEach(e=>{s.data[e]?s.data[e]!==o.data[e]&&(t.dataset[e]=s.data[e]):delete t.dataset[e]}),Object.keys(s.data).forEach(e=>{o.data[e]||(t.dataset[e]=s.data[e])}),o.data=s.data),equal(o.props,s.props)||(Object.keys(o.props).forEach(e=>{t[e]=s.props[e],"boolean"==typeof s.props[e]?(o.props[e]=s.props[e],s.props[e]?t.setAttribute(e,""):t.removeAttribute(e)):[null,void 0].includes(s.props[e])?(delete o.props[e],t.removeAttribute(e)):s.props[e]!==o.props[e]&&(o.props[e]=s.props[e],["string","number"].includes(typeof s.props[e])&&t.setAttribute(e,s.props[e]))}),Object.keys(s.props).forEach(e=>{blank(o.props[e])&&!blank(s.props[e])&&(o.props[e]=s.props[e],t[e]=s.props[e],"boolean"==typeof s.props[e]?t.setAttribute(e,""):["string","number"].includes(typeof s.props[e])&&t.setAttribute(e,s.props[e]))})),equal(o.eventListeners,s.eventListeners)||(Object.keys(o.eventListeners).forEach(e=>{s.eventListeners[e]?equal(s.eventListeners[e],o.eventListeners[e])||(t.removeEventListener(e,o.eventListeners[e]),t.addEventListener(e,s.eventListeners[e])):t.removeEventListener(e,o.eventListeners[e])}),Object.keys(s.eventListeners).forEach(e=>{o.eventListeners[e]||t.addEventListener(e,s.eventListeners[e])}),o.eventListeners=s.eventListeners);let i=mapChildren(o,s),n=[...Array(s.children.length).keys()];for(;!equal(i,n);){let e=-1;e:for(const r of i)if(e++,r!==e)switch(r){case PATCH:o.children[e].redraw({node:t.childNodes[e],vnode:s.children[e]});break e;case INSERT:{o.children.splice(e,0,s.children[e]);const r=s.children[e].render();t.insertBefore(r,t.childNodes[e]),s.children[e].$onrender&&s.children[e].$onrender(r);break e}case DELETE:o.children.splice(e,1),t.removeChild(t.childNodes[e]);break e;default:{const s=o.children.splice(r,1)[0];o.children.splice(e,0,s);const i=t.removeChild(t.childNodes[r]);t.insertBefore(i,t.childNodes[e]);break e}}i=mapChildren(o,s),n=[...Array(s.children.length).keys()]}equal(o.$onrender,s.$onrender)||(o.$onrender=s.$onrender),o.$html!==s.$html&&(t.innerHTML=s.$html,o.$html=s.$html,o.$onrender&&o.$onrender(t))}}const mapChildren=(e,t)=>{const r=t.children,s=e.children;let o=[];for(let e=0;e<r.length;e++){let t=PATCH;for(let i=0;i<s.length;i++)if(equal(r[e],s[i])&&!o.includes(i)){t=i;break}t<0&&r.length>=s.length&&o.length>=s.length&&(t=INSERT),o.push(t)}const i=o.filter(e=>e>=0);return s.length>r.length?[...Array(s.length-r.length).keys()].forEach(()=>o.push(DELETE)):i.length===s.length&&(o=o.map(e=>e<0?INSERT:e)),o};class Store{constructor(){this.events={},this.state={}}dispatch(e,t){if("$log"!==e&&this.dispatch("$log",{event:e,data:t}),this.events[e]){this.events[e].forEach(e=>{this.state={...this.state,...e(this.state,t)}})}}on(e,t){return(this.events[e]||(this.events[e]=[])).push(t),()=>{this.events[e]=this.events[e].filter(e=>e!==t)}}}class Route{constructor({path:e,def:t,query:r,parts:s}){if(this.path=e,this.def=t,this.query=r,this.parts=s,this.params={},this.query){this.query.split("&").forEach(e=>{const[t,r]=e.split("=");this.params[decodeURIComponent(t)]=decodeURIComponent(r)})}}}class Router{constructor({element:e,routes:t,store:r,location:s}){if(this.element=e,this.redraw=null,this.store=r,this.location=s||window.location,!t||0===Object.keys(t).length)throw new Error("[Router] No routes defined.");Object.keys(t);this.routes=t}setRedraw(e,t){this.redraw=()=>{e.redraw({node:this.element.childNodes[0],vnode:this.routes[this.route.def](t)}),this.store.dispatch("$redraw")}}async start(){const e=async e=>{const t=this.route,r=e&&e.newURL&&e.newURL.match(/(#.+)$/)&&e.newURL.match(/(#.+)$/)[1]||this.location.hash,s=r.replace(/\?.+$/,"").slice(1),o=r.match(/\?(.+)$/),i=o&&o[1]?o[1]:"",n=s.split("/").slice(1);let a,l={};for(let e of Object.keys(this.routes)){let t=e.split("/").slice(1),r=!0,o=0;for(l={};r&&t[o];){const e=t[o],s=n[o];e.startsWith(":")&&s?l[e.slice(1)]=s:r=e===s,o++}if(r){a=new Route({query:i,path:s,def:e,parts:l});break}}if(!a)throw new Error(`[Router] No route matches '${r}'`);let h={};if(t){const e=this.routes[t.def];h=e.teardown&&await e.teardown(e.state)||h}const d=this.routes[a.def];if(d.state=h,d.setup&&(this.route=a,!1===await d.setup(d.state)))return this.route=t,void this.store.dispatch("$navigation",null);for(this.route=a,redrawing=!0,this.store.dispatch("$navigation",this.route);this.element.firstChild;)this.element.removeChild(this.element.firstChild);const c=d(d.state),p=c.render();this.element.appendChild(p),this.setRedraw(c,d.state),redrawing=!1,c.$onrender&&c.$onrender(p),$onrenderCallbacks.forEach(e=>e()),$onrenderCallbacks=[],window.scrollTo(0,0),this.store.dispatch("$redraw")};window.addEventListener("hashchange",e),await e()}navigateTo(e,t){let r=Object.keys(t||{}).map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(t[e])}`).join("&");r=r?"?"+r:"",this.location.hash=`#${e}${r}`}}export const h=(...e)=>new VNode(...e);export const h3={};let store=null,router=null,redrawing=!1;h3.init=e=>{let{element:t,routes:r,modules:s,preStart:o,postStart:i,location:n}=e;if(!r){if("function"!=typeof e)throw new Error("[h3.init] The specified argument is not a valid configuration object or component function");r={"/":e}}if(t=t||document.body,!(t&&t instanceof Element))throw new Error("[h3.init] Invalid element specified.");return store=new Store,(s||[]).forEach(e=>{e(store)}),store.dispatch("$init"),router=new Router({element:t,routes:r,store:store,location:n}),Promise.resolve(o&&o()).then(()=>router.start()).then(()=>i&&i())},h3.navigateTo=(e,t)=>{if(!router)throw new Error("[h3.navigateTo] No application initialized, unable to navigate.");return router.navigateTo(e,t)},Object.defineProperty(h3,"route",{get:()=>{if(!router)throw new Error("[h3.route] No application initialized, unable to retrieve current route.");return router.route}}),Object.defineProperty(h3,"state",{get:()=>{if(!store)throw new Error("[h3.state] No application initialized, unable to retrieve current state.");return store.state}}),h3.on=(e,t)=>{if(!store)throw new Error("[h3.on] No application initialized, unable to listen to events.");return store.on(e,t)},h3.dispatch=(e,t)=>{if(!store)throw new Error("[h3.dispatch] No application initialized, unable to dispatch events.");return store.dispatch(e,t)},h3.redraw=e=>{if(!router||!router.redraw)throw new Error("[h3.redraw] No application initialized, unable to redraw.");redrawing||(redrawing=!0,requestAnimationFrame(()=>{router.redraw(),redrawing=e||!1}))},h3.screen=({setup:e,display:t,teardown:r})=>{if(!t||"function"!=typeof t)throw new Error("[h3.screen] No display property specified.");if(e&&"function"!=typeof e)throw new Error("[h3.screen] setup property is not a function.");if(r&&"function"!=typeof r)throw new Error("[h3.screen] teardown property is not a function.");const s=t;return e&&(s.setup=e),r&&(s.teardown=r),s};export default h3; //# sourceMappingURL=h3.js.map
M package-lock.jsonpackage-lock.json

@@ -1,6 +1,6 @@

{ "name": "@h3rald/h3", - "version": "0.10.0", + "version": "0.11.0", "lockfileVersion": 1, "requires": true, "dependencies": {
M package.jsonpackage.json

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

{ "name": "@h3rald/h3", - "version": "0.10.0", - "versionName": "Jittery Jem'Hadar", + "version": "0.11.0", + "versionName": "Keen Klingon", "description": "A tiny, extremely minimalist JavaScript microframework.", "main": "h3.js", "scripts": {