Added h3.screen method.
h3rald h3rald@h3rald.com
Wed, 29 Jul 2020 21:36:28 +0200
7 files changed,
2440 insertions(+),
2442 deletions(-)
M
__tests__/h3.js
→
__tests__/h3.js
@@ -3,426 +3,439 @@ const h3 = mod.h3;
const h = mod.h; describe("h3", () => { - beforeEach(() => { - jest.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => - cb() - ); - }); + beforeEach(() => { + jest + .spyOn(window, "requestAnimationFrame") + .mockImplementation((cb) => cb()); + }); - afterEach(() => { - window.requestAnimationFrame.mockRestore(); - }); + afterEach(() => { + window.requestAnimationFrame.mockRestore(); + }); - it("should support a way to discriminate functions and objects", () => { - const v1 = h("div", { onclick: () => true }); - const v2 = h("div", { onclick: () => true }); - const v3 = h("div", { onclick: () => false }); - const v4 = h("div"); - expect(v1.equal(v2)).toEqual(true); - expect(v1.equal(v3)).toEqual(false); - expect(v4.equal({ type: "div" })).toEqual(false); - expect(v1.equal(null, null)).toEqual(true); - expect(v1.equal(null, undefined)).toEqual(false); - }); + it("should support a way to discriminate functions and objects", () => { + const v1 = h("div", { onclick: () => true }); + const v2 = h("div", { onclick: () => true }); + const v3 = h("div", { onclick: () => false }); + const v4 = h("div"); + expect(v1.equal(v2)).toEqual(true); + expect(v1.equal(v3)).toEqual(false); + expect(v4.equal({ type: "div" })).toEqual(false); + expect(v1.equal(null, null)).toEqual(true); + expect(v1.equal(null, undefined)).toEqual(false); + }); - it("should support the creation of empty virtual node elements", () => { - expect(h("div")).toEqual({ - type: "div", - children: [], - props: {}, - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - value: undefined, - }); + it("should support the creation of empty virtual node elements", () => { + expect(h("div")).toEqual({ + type: "div", + children: [], + props: {}, + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + value: undefined, }); + }); - it("should throw an error when invalid arguments are supplied", () => { - const empty = () => h(); - const invalid1st = () => h(1); - const invalid1st2 = () => h(1, {}); - const invalid1st3 = () => h(1, {}, []); - const invalid1st1 = () => h(() => ({ type: "#text", value: "test" })); - const invalid1st1b = () => h({ a: 2 }); - const invalid2nd = () => h("div", 1); - const invalid2nd2 = () => h("div", true, []); - const invalid2nd3 = () => h("div", null, []); - const invalidChildren = () => h("div", ["test", 1, 2]); - const emptySelector = () => h(""); - expect(empty).toThrowError(/No arguments passed/); - expect(invalid1st).toThrowError(/Invalid first argument/); - expect(invalid1st2).toThrowError(/Invalid first argument/); - expect(invalid1st3).toThrowError(/Invalid first argument/); - expect(invalid1st1).toThrowError(/does not return a VNode/); - expect(invalid1st1b).toThrowError(/Invalid first argument/); - expect(invalid2nd).toThrowError( - /second argument of a VNode constructor/ - ); - expect(invalid2nd2).toThrowError(/Invalid second argument/); - expect(invalid2nd3).toThrowError(/Invalid second argument/); - expect(invalidChildren).toThrowError(/not a VNode: 1/); - expect(emptySelector).toThrowError(/Invalid selector/); - }); + it("should throw an error when invalid arguments are supplied", () => { + const empty = () => h(); + const invalid1st = () => h(1); + const invalid1st2 = () => h(1, {}); + const invalid1st3 = () => h(1, {}, []); + const invalid1st1 = () => h(() => ({ type: "#text", value: "test" })); + const invalid1st1b = () => h({ a: 2 }); + const invalid2nd = () => h("div", 1); + const invalid2nd2 = () => h("div", true, []); + const invalid2nd3 = () => h("div", null, []); + const invalidChildren = () => h("div", ["test", 1, 2]); + const emptySelector = () => h(""); + expect(empty).toThrowError(/No arguments passed/); + expect(invalid1st).toThrowError(/Invalid first argument/); + expect(invalid1st2).toThrowError(/Invalid first argument/); + expect(invalid1st3).toThrowError(/Invalid first argument/); + expect(invalid1st1).toThrowError(/does not return a VNode/); + expect(invalid1st1b).toThrowError(/Invalid first argument/); + expect(invalid2nd).toThrowError(/second argument of a VNode constructor/); + expect(invalid2nd2).toThrowError(/Invalid second argument/); + expect(invalid2nd3).toThrowError(/Invalid second argument/); + expect(invalidChildren).toThrowError(/not a VNode: 1/); + expect(emptySelector).toThrowError(/Invalid selector/); + }); - it("should support several child arguments", () => { - let vnode = h("div", { test: "a" }, "a", "b", "c"); - expect(vnode.children.length).toEqual(3); - vnode = h("div", "a", "b", "c"); - expect(vnode.children.length).toEqual(3); - vnode = h("div", "a", "b"); - expect(vnode.children.length).toEqual(2); + it("should support several child arguments", () => { + let vnode = h("div", { test: "a" }, "a", "b", "c"); + expect(vnode.children.length).toEqual(3); + vnode = h("div", "a", "b", "c"); + expect(vnode.children.length).toEqual(3); + vnode = h("div", "a", "b"); + expect(vnode.children.length).toEqual(2); + }); + + it("should support the creation of elements with a single, non-array child", () => { + const vnode1 = h("div", () => "test"); + const vnode2 = h("div", () => h("span")); + expect(vnode1.children[0].value).toEqual("test"); + expect(vnode2.children[0].type).toEqual("span"); + }); + + it("should remove null/false/undefined children", () => { + const v1 = h("div", [false, "test", undefined, null, ""]); + expect(v1.children).toEqual([ + h({ type: "#text", value: "test" }), + h({ type: "#text", value: "" }), + ]); + }); + + it("should support the creation of nodes with a single child node", () => { + const result = { + type: "div", + children: [ + { + type: "#text", + children: [], + props: {}, + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + value: "test", + }, + ], + props: {}, + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + value: undefined, + }; + expect(h("div", "test")).toEqual(result); + const failing = () => h("***"); + expect(failing).toThrowError(/Invalid selector/); + }); + + it("should support the creation of virtual node elements with classes", () => { + const a = h("div.a.b.c"); + const b = h("div", { classList: ["a", "b", "c"] }); + expect(a).toEqual({ + type: "div", + children: [], + props: {}, + classList: ["a", "b", "c"], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + type: "div", + value: undefined, }); + expect(a).toEqual(b); + }); - it("should support the creation of elements with a single, non-array child", () => { - const vnode1 = h("div", () => "test"); - const vnode2 = h("div", () => h("span")); - expect(vnode1.children[0].value).toEqual("test"); - expect(vnode2.children[0].type).toEqual("span"); + it("should support the creation of virtual node elements with props and classes", () => { + expect(h("div.test1.test2", { id: "test" })).toEqual({ + type: "div", + children: [], + classList: ["test1", "test2"], + data: {}, + props: {}, + eventListeners: {}, + id: "test", + $html: undefined, + style: undefined, + type: "div", + value: undefined, }); + }); - it("should remove null/false/undefined children", () => { - const v1 = h("div", [false, "test", undefined, null, ""]); - expect(v1.children).toEqual([ - h({ type: "#text", value: "test" }), - h({ type: "#text", value: "" }), - ]); + it("should support the creation of virtual node elements with text children and classes", () => { + expect(h("div.test", ["a", "b"])).toEqual({ + type: "div", + children: [ + { + props: {}, + children: [], + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + type: "#text", + value: "a", + }, + { + props: {}, + children: [], + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + type: "#text", + value: "b", + }, + ], + props: {}, + classList: ["test"], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + value: undefined, }); + }); - it("should support the creation of nodes with a single child node", () => { - const result = { - type: "div", - children: [ - { - type: "#text", - children: [], - props: {}, - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - value: "test", - }, - ], + it("should support the creation of virtual node elements with text children, props, and classes", () => { + expect(h("div.test", { title: "Test...", id: "test" }, ["a", "b"])).toEqual( + { + type: "div", + children: [ + { props: {}, + children: [], classList: [], data: {}, eventListeners: {}, id: undefined, $html: undefined, style: undefined, - value: undefined, - }; - expect(h("div", "test")).toEqual(result); - const failing = () => h("***"); - expect(failing).toThrowError(/Invalid selector/); - }); - - it("should support the creation of virtual node elements with classes", () => { - const a = h("div.a.b.c"); - const b = h("div", { classList: ["a", "b", "c"] }); - expect(a).toEqual({ - type: "div", - children: [], + type: "#text", + value: "a", + }, + { props: {}, - classList: ["a", "b", "c"], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - type: "div", - value: undefined, - }); - expect(a).toEqual(b); - }); - - it("should support the creation of virtual node elements with props and classes", () => { - expect(h("div.test1.test2", { id: "test" })).toEqual({ - type: "div", children: [], - classList: ["test1", "test2"], - data: {}, - props: {}, - eventListeners: {}, - id: "test", - $html: undefined, - style: undefined, - type: "div", - value: undefined, - }); - }); - - it("should support the creation of virtual node elements with text children and classes", () => { - expect(h("div.test", ["a", "b"])).toEqual({ - type: "div", - children: [ - { - props: {}, - children: [], - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - type: "#text", - value: "a", - }, - { - props: {}, - children: [], - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - type: "#text", - value: "b", - }, - ], - props: {}, - classList: ["test"], + classList: [], data: {}, eventListeners: {}, id: undefined, $html: undefined, style: undefined, - value: undefined, - }); - }); + type: "#text", + value: "b", + }, + ], + data: {}, + eventListeners: {}, + id: "test", + $html: undefined, + style: undefined, + value: undefined, + props: { title: "Test..." }, + classList: ["test"], + } + ); + }); - it("should support the creation of virtual node elements with text children, props, and classes", () => { - expect( - h("div.test", { title: "Test...", id: "test" }, ["a", "b"]) - ).toEqual({ - type: "div", - children: [ - { - props: {}, - children: [], - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - type: "#text", - value: "a", - }, - { - props: {}, - children: [], - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - type: "#text", - value: "b", - }, - ], - data: {}, - eventListeners: {}, - id: "test", - $html: undefined, - style: undefined, - value: undefined, - props: { title: "Test..." }, - classList: ["test"], - }); + it("should support the creation of virtual node elements with props", () => { + expect(h("input", { type: "text", value: "AAA" })).toEqual({ + type: "input", + children: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + value: "AAA", + props: { type: "text" }, + classList: [], }); + }); - it("should support the creation of virtual node elements with props", () => { - expect(h("input", { type: "text", value: "AAA" })).toEqual({ - type: "input", - children: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - value: "AAA", - props: { type: "text" }, - classList: [], - }); + it("should support the creation of virtual node elements with event handlers", () => { + const fn = () => true; + expect(h("button", { onclick: fn })).toEqual({ + type: "button", + children: [], + data: {}, + eventListeners: { + click: fn, + }, + id: undefined, + $html: undefined, + style: undefined, + value: undefined, + props: {}, + classList: [], }); + expect(() => h("span", { onclick: "something" })).toThrowError( + /onclick event is not a function/ + ); + }); - it("should support the creation of virtual node elements with event handlers", () => { - const fn = () => true; - expect(h("button", { onclick: fn })).toEqual({ - type: "button", - children: [], - data: {}, - eventListeners: { - click: fn, + it("should support the creation of virtual node elements with element children and classes", () => { + expect( + h("div.test", ["a", h("span", ["test1"]), () => h("span", ["test2"])]) + ).toEqual({ + props: {}, + type: "div", + children: [ + { + props: {}, + children: [], + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + type: "#text", + value: "a", + }, + { + type: "span", + children: [ + { + props: {}, + children: [], + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + type: "#text", + value: "test1", }, - id: undefined, - $html: undefined, - style: undefined, - value: undefined, - props: {}, - classList: [], - }); - expect(() => h("span", { onclick: "something" })).toThrowError( - /onclick event is not a function/ - ); + ], + props: {}, + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + value: undefined, + }, + { + type: "span", + children: [ + { + props: {}, + children: [], + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + type: "#text", + value: "test2", + }, + ], + props: {}, + classList: [], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + value: undefined, + }, + ], + classList: ["test"], + data: {}, + eventListeners: {}, + id: undefined, + $html: undefined, + style: undefined, + value: undefined, }); + }); - it("should support the creation of virtual node elements with element children and classes", () => { - expect( - h("div.test", [ - "a", - h("span", ["test1"]), - () => h("span", ["test2"]), - ]) - ).toEqual({ - props: {}, - type: "div", - children: [ - { - props: {}, - children: [], - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - type: "#text", - value: "a", - }, - { - type: "span", - children: [ - { - props: {}, - children: [], - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - type: "#text", - value: "test1", - }, - ], - props: {}, - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - value: undefined, - }, - { - type: "span", - children: [ - { - props: {}, - children: [], - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - type: "#text", - value: "test2", - }, - ], - props: {}, - classList: [], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - value: undefined, - }, - ], - classList: ["test"], - data: {}, - eventListeners: {}, - id: undefined, - $html: undefined, - style: undefined, - value: undefined, - }); - }); + it("should not allow certain methods and properties to be called/accessed before initialization", () => { + const route = () => h3.route; + const state = () => h3.state; + const redraw = () => h3.redraw(); + const dispatch = () => h3.dispatch(); + const on = () => h3.on(); + const navigateTo = () => h3.navigateTo(); + expect(route).toThrowError(/No application initialized/); + expect(state).toThrowError(/No application initialized/); + expect(redraw).toThrowError(/No application initialized/); + expect(dispatch).toThrowError(/No application initialized/); + expect(on).toThrowError(/No application initialized/); + expect(navigateTo).toThrowError(/No application initialized/); + }); - it("should not allow certain methods and properties to be called/accessed before initialization", () => { - const route = () => h3.route; - const state = () => h3.state; - const redraw = () => h3.redraw(); - const dispatch = () => h3.dispatch(); - const on = () => h3.on(); - const navigateTo = () => h3.navigateTo(); - expect(route).toThrowError(/No application initialized/); - expect(state).toThrowError(/No application initialized/); - expect(redraw).toThrowError(/No application initialized/); - expect(dispatch).toThrowError(/No application initialized/); - expect(on).toThrowError(/No application initialized/); - expect(navigateTo).toThrowError(/No application initialized/); - }); + it("should provide an init method to initialize a SPA with a single component", async () => { + const c = () => h("div", "Hello, World!"); + const body = document.body; + const appendChild = jest.spyOn(body, "appendChild"); + await h3.init(c); + expect(appendChild).toHaveBeenCalled(); + expect(body.childNodes[0].childNodes[0].data).toEqual("Hello, World!"); + }); - it("should provide an init method to initialize a SPA with a single component", async () => { - const c = () => h("div", "Hello, World!"); - const body = document.body; - const appendChild = jest.spyOn(body, "appendChild"); - await h3.init(c); - expect(appendChild).toHaveBeenCalled(); - expect(body.childNodes[0].childNodes[0].data).toEqual("Hello, World!"); - }); + it("should provide some validation at initialization time", async () => { + try { + await h3.init({ element: "INVALID", routes: {} }); + } catch (e) { + expect(e.message).toMatch(/Invalid element/); + } + try { + await h3.init({ element: document.body }); + } catch (e) { + expect(e.message).toMatch(/not a valid configuration object/); + } + try { + await h3.init({ element: document.body, routes: {} }); + } catch (e) { + expect(e.message).toMatch(/No routes/); + } + }); - it("should provide some validation at initialization time", async () => { - try { - await h3.init({ element: "INVALID", routes: {} }); - } catch (e) { - expect(e.message).toMatch(/Invalid element/); - } - try { - await h3.init({ element: document.body }); - } catch (e) { - expect(e.message).toMatch(/not a valid configuration object/); - } - try { - await h3.init({ element: document.body, routes: {} }); - } catch (e) { - expect(e.message).toMatch(/No routes/); - } - }); + it("should expose a redraw method", async () => { + const vnode = h("div"); + await h3.init(() => vnode); + jest.spyOn(vnode, "redraw"); + h3.redraw(); + expect(vnode.redraw).toHaveBeenCalled(); + h3.redraw(true); + h3.redraw(); + h3.redraw(); + expect(vnode.redraw).toHaveBeenCalledTimes(2); + }); - it("should expose a redraw method", async () => { - const vnode = h("div"); - await h3.init(() => vnode); - jest.spyOn(vnode, "redraw"); - h3.redraw(); - expect(vnode.redraw).toHaveBeenCalled(); - h3.redraw(true); - h3.redraw(); - h3.redraw(); - expect(vnode.redraw).toHaveBeenCalledTimes(2); + it("should not redraw while a other redraw is in progress", async () => { + const vnode = h("div"); + await h3.init({ + routes: { + "/": () => vnode, + }, }); + jest.spyOn(vnode, "redraw"); + h3.redraw(true); + h3.redraw(); + expect(vnode.redraw).toHaveBeenCalledTimes(1); + }); - it("should not redraw while a other redraw is in progress", async () => { - const vnode = h("div"); - await h3.init({ - routes: { - "/": () => vnode, - }, - }); - jest.spyOn(vnode, "redraw"); - h3.redraw(true); - h3.redraw(); - expect(vnode.redraw).toHaveBeenCalledTimes(1); + it("should expose a screen method to define screen-level components with (optional) setup and teardown", async () => { + expect(() => h3.screen({})).toThrowError(/No display property specified/); + expect(() => h3.screen({ setup: 1, display: () => "" })).toThrowError( + /setup property is not a function/ + ); + expect(() => h3.screen({ teardown: 1, display: () => "" })).toThrowError( + /teardown property is not a function/ + ); + let s = h3.screen({ display: () => "test" }); + expect(typeof s).toEqual("function"); + s = h3.screen({ + display: () => "test", + setup: (state) => state, + teardown: (state) => state, }); + expect(typeof s.setup).toEqual("function"); + expect(typeof s.teardown).toEqual("function"); + }); });
M
docs/example/assets/js/h3.js
→
docs/example/assets/js/h3.js
@@ -6,55 +6,55 @@ * @license MIT
* For the full license, see: https://github.com/h3rald/h3/blob/master/LICENSE */ const checkProperties = (obj1, obj2) => { - for (const key in obj1) { - if (!(key in obj2)) { - return false; - } - if (!equal(obj1[key], obj2[key])) { - return false; - } + for (const key in obj1) { + if (!(key in obj2)) { + return false; } - return true; + if (!equal(obj1[key], obj2[key])) { + return false; + } + } + return true; }; const equal = (obj1, obj2) => { - if ( - (obj1 === null && obj2 === null) || - (obj1 === undefined && obj2 === undefined) - ) { - return true; + 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) + ) { + return false; + } + if (obj1.constructor !== obj2.constructor) { + return false; + } + if (typeof obj1 === "function") { + if (obj1.toString() !== obj2.toString()) { + return false; } - if ( - (obj1 === undefined && obj2 !== undefined) || - (obj1 !== undefined && obj2 === undefined) || - (obj1 === null && obj2 !== null) || - (obj1 !== null && obj2 === null) - ) { - return false; + } + if ([String, Number, Boolean].includes(obj1.constructor)) { + return obj1 === obj2; + } + if (obj1.constructor === Array) { + if (obj1.length !== obj2.length) { + return false; } - if (obj1.constructor !== obj2.constructor) { + for (let i = 0; i < obj1.length; i++) { + if (!equal(obj1[i], obj2[i])) { return false; + } } - if (typeof obj1 === "function") { - if (obj1.toString() !== obj2.toString()) { - return false; - } - } - if ([String, Number, Boolean].includes(obj1.constructor)) { - return obj1 === obj2; - } - if (obj1.constructor === Array) { - if (obj1.length !== obj2.length) { - return false; - } - for (let i = 0; i < obj1.length; i++) { - if (!equal(obj1[i], obj2[i])) { - return false; - } - } - return true; - } - return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); + return true; + } + return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); }; const selectorRegex = /^([a-z][a-z0-9:_=-]*)?(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i;@@ -63,484 +63,464 @@ let $onrenderCallbacks = [];
// Virtual Node Implementation with HyperScript-like syntax class VNode { - constructor(...args) { - this.type = undefined; - this.props = {}; - this.data = {}; - this.id = undefined; - this.$html = undefined; - this.$onrender = undefined; - this.style = undefined; - this.value = undefined; - this.children = []; - this.classList = []; - this.eventListeners = {}; - if (args.length === 0) { - throw new Error( - "[VNode] No arguments passed to VNode constructor." - ); + constructor(...args) { + this.type = undefined; + this.props = {}; + this.data = {}; + this.id = undefined; + this.$html = undefined; + this.$onrender = undefined; + this.style = undefined; + this.value = undefined; + this.children = []; + this.classList = []; + this.eventListeners = {}; + if (args.length === 0) { + throw new Error("[VNode] No arguments passed to VNode constructor."); + } + if (args.length === 1) { + let vnode = args[0]; + if (typeof vnode === "string") { + // Assume empty element + this.processSelector(vnode); + } else if ( + typeof vnode === "function" || + (typeof vnode === "object" && vnode !== null) + ) { + // Text node + if (vnode.type === "#text") { + this.type = "#text"; + this.value = vnode.value; + } else { + this.from(this.processVNodeObject(vnode)); } - if (args.length === 1) { - let vnode = args[0]; - if (typeof vnode === "string") { - // Assume empty element - this.processSelector(vnode); - } else if ( - typeof vnode === "function" || - (typeof vnode === "object" && vnode !== null) - ) { - // Text node - 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." - ); - } - } else if (args.length === 2) { - let [selector, data] = args; - if (typeof selector !== "string") { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); - } - this.processSelector(selector); - if (typeof data === "string") { - // Assume single child text node - 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 (Array.isArray(data)) { - // Assume 2nd argument as children - this.processChildren(data); - } else { - if (data instanceof Function || data instanceof VNode) { - this.processChildren(data); - } else { - // Not a VNode, assume props object - this.processProperties(data); - } - } + } else { + 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." + ); + } + this.processSelector(selector); + if (typeof data === "string") { + // Assume single child text node + 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 (Array.isArray(data)) { + // Assume 2nd argument as children + this.processChildren(data); + } else { + if (data instanceof Function || data instanceof VNode) { + this.processChildren(data); } else { - let [selector, props, children] = args; - 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." - ); - } - this.processSelector(selector); - 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." - ); - } - this.processProperties(props); - } - this.processChildren(children); + // Not a VNode, assume props object + this.processProperties(data); + } + } + } else { + let [selector, props, children] = args; + 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." + ); + } + this.processSelector(selector); + 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." + ); } + this.processProperties(props); + } + this.processChildren(children); } + } - from(data) { - this.value = data.value; - this.type = data.type; - this.id = data.id; - this.$html = data.$html; - this.$onrender = data.$onrender; - this.style = data.style; - this.data = data.data; - this.value = data.value; - this.eventListeners = data.eventListeners; - this.children = data.children; - this.props = data.props; - this.classList = data.classList; - } + from(data) { + this.value = data.value; + this.type = data.type; + this.id = data.id; + this.$html = data.$html; + this.$onrender = data.$onrender; + this.style = data.style; + this.data = data.data; + this.value = data.value; + this.eventListeners = data.eventListeners; + this.children = data.children; + this.props = data.props; + this.classList = data.classList; + } + + equal(a, b) { + return equal(a, b === undefined ? this : b); + } + + processProperties(attrs) { + this.id = this.id || attrs.id; + this.$html = attrs.$html; + 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.props = attrs; + Object.keys(attrs) + .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.` + ); + } + this.eventListeners[key.slice(2)] = attrs[key]; + delete this.props[key]; + }); + 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; + } - equal(a, b) { - return equal(a, b === undefined ? this : b); + processSelector(selector) { + if (!selector.match(selectorRegex) || selector.length === 0) { + throw new Error(`[VNode] Invalid selector: ${selector}`); } + const [, type, id, classes] = selector.match(selectorRegex); + this.type = type; + if (id) { + this.id = id.slice(1); + } + this.classList = (classes && classes.split(".").slice(1)) || []; + } - processProperties(attrs) { - this.id = this.id || attrs.id; - this.$html = attrs.$html; - 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.props = attrs; - Object.keys(attrs) - .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.` - ); - } - this.eventListeners[key.slice(2)] = attrs[key]; - delete this.props[key]; - }); - 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; + processVNodeObject(arg) { + if (arg instanceof VNode) { + return arg; } + if (arg instanceof Function) { + let vnode = arg(); + 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"); + } + return vnode; + } + throw new Error( + "[VNode] Invalid first argument provided to VNode constructor." + ); + } - processSelector(selector) { - if (!selector.match(selectorRegex) || selector.length === 0) { - throw new Error(`[VNode] Invalid selector: ${selector}`); + 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 === "function" || (typeof c === "object" && c !== null)) { + return this.processVNodeObject(c); } - const [, type, id, classes] = selector.match(selectorRegex); - this.type = type; - if (id) { - this.id = id.slice(1); + if (c) { + throw new Error(`[VNode] Specified child is not a VNode: ${c}`); } - this.classList = (classes && classes.split(".").slice(1)) || []; + }) + .filter((c) => c); + } + + // Renders the actual DOM Node corresponding to the current Virtual Node + render() { + if (this.type === "#text") { + return document.createTextNode(this.value); + } + const node = document.createElement(this.type); + if (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 (["string", "number"].includes(typeof this.props[p])) { + node.setAttribute(p, this.props[p]); + } + // Set properties + node[p] = this.props[p]; + }); + // Event Listeners + Object.keys(this.eventListeners).forEach((event) => { + node.addEventListener(event, this.eventListeners[event]); + }); + // Value + if (this.value) { + if (["textarea", "input"].includes(this.type)) { + node.value = this.value; + } else { + node.setAttribute("value", this.value); + } + } + // Style + if (this.style) { + node.style.cssText = this.style; + } + // Classes + this.classList.forEach((c) => { + node.classList.add(c); + }); + // Data + Object.keys(this.data).forEach((key) => { + node.dataset[key] = this.data[key]; + }); + // Children + this.children.forEach((c) => { + const cnode = c.render(); + node.appendChild(cnode); + c.$onrender && $onrenderCallbacks.push(() => c.$onrender(cnode)); + }); + if (this.$html) { + node.innerHTML = this.$html; } + return node; + } - processVNodeObject(arg) { - if (arg instanceof VNode) { - return arg; + // Updates the current Virtual Node with a new Virtual Node (and syncs the existing DOM Node) + redraw(data) { + let { node, vnode } = data; + const newvnode = vnode; + const oldvnode = this; + if ( + oldvnode.constructor !== newvnode.constructor || + oldvnode.type !== newvnode.type || + (oldvnode.type === newvnode.type && + oldvnode.type === "#text" && + oldvnode !== newvnode) + ) { + const renderedNode = newvnode.render(); + node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); + oldvnode.from(newvnode); + return; + } + // ID + if (oldvnode.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 || ""; + } else { + node.setAttribute("value", newvnode.value || ""); + } + } + // Classes + if (!equal(oldvnode.classList, newvnode.classList)) { + oldvnode.classList.forEach((c) => { + if (!newvnode.classList.includes(c)) { + node.classList.remove(c); } - if (arg instanceof Function) { - let vnode = arg(); - 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" - ); - } - return vnode; + }); + newvnode.classList.forEach((c) => { + if (!oldvnode.classList.includes(c)) { + node.classList.add(c); } - throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." - ); + }); + oldvnode.classList = newvnode.classList; } - - 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 === "function" || - (typeof c === "object" && c !== null) - ) { - return this.processVNodeObject(c); - } - if (c) { - throw new Error( - `[VNode] Specified child is not a VNode: ${c}` - ); - } - }) - .filter((c) => c); + // Style + if (oldvnode.style !== newvnode.style) { + node.style.cssText = newvnode.style || ""; + oldvnode.style = newvnode.style; } - - // Renders the actual DOM Node corresponding to the current Virtual Node - render() { - if (this.type === "#text") { - return document.createTextNode(this.value); + // Data + if (!equal(oldvnode.data, newvnode.data)) { + Object.keys(oldvnode.data).forEach((a) => { + if (!newvnode.data[a]) { + delete node.dataset[a]; + } else if (newvnode.data[a] !== oldvnode.data[a]) { + node.dataset[a] = newvnode.data[a]; } - const node = document.createElement(this.type); - if (this.id) { - node.id = this.id; + }); + Object.keys(newvnode.data).forEach((a) => { + if (!oldvnode.data[a]) { + node.dataset[a] = newvnode.data[a]; } - Object.keys(this.props).forEach((p) => { - // Set attributes - if (typeof this.props[p] === "boolean") { - this.props[p] - ? node.setAttribute(p, "") - : node.removeAttribute(p); - } - if (["string", "number"].includes(typeof this.props[p])) { - node.setAttribute(p, this.props[p]); - } - // Set properties - node[p] = this.props[p]; - }); - // Event Listeners - Object.keys(this.eventListeners).forEach((event) => { - node.addEventListener(event, this.eventListeners[event]); - }); - // Value - if (this.value) { - if (["textarea", "input"].includes(this.type)) { - node.value = this.value; - } else { - node.setAttribute("value", this.value); - } + }); + oldvnode.data = newvnode.data; + } + // 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") { + oldvnode.props[a] = newvnode.props[a]; + newvnode.props[a] + ? node.setAttribute(a, "") + : node.removeAttribute(a); + } else if (!newvnode.props[a]) { + delete oldvnode.props[a]; + node.removeAttribute(a); + } else if ( + newvnode.props[a] && + newvnode.props[a] !== oldvnode.props[a] + ) { + oldvnode.props[a] = newvnode.props[a]; + if (["string", "number"].includes(typeof newvnode.props[a])) { + node.setAttribute(a, newvnode.props[a]); + } } - // Style - if (this.style) { - node.style.cssText = this.style; + }); + Object.keys(newvnode.props).forEach((a) => { + if (!oldvnode.props[a] && newvnode.props[a]) { + oldvnode.props[a] = newvnode.props[a]; + node.setAttribute(a, newvnode.props[a]); } - // Classes - this.classList.forEach((c) => { - node.classList.add(c); - }); - // Data - Object.keys(this.data).forEach((key) => { - node.dataset[key] = this.data[key]; - }); - // Children - this.children.forEach((c) => { - const cnode = c.render(); - node.appendChild(cnode); - c.$onrender && $onrenderCallbacks.push(() => c.$onrender(cnode)); - }); - if (this.$html) { - node.innerHTML = this.$html; - } - return node; + }); } - - // Updates the current Virtual Node with a new Virtual Node (and syncs the existing DOM Node) - redraw(data) { - let { node, vnode } = data; - const newvnode = vnode; - const oldvnode = this; - if ( - oldvnode.constructor !== newvnode.constructor || - oldvnode.type !== newvnode.type || - (oldvnode.type === newvnode.type && - oldvnode.type === "#text" && - oldvnode !== newvnode) + // Event listeners + 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]) ) { - const renderedNode = newvnode.render(); - node.parentNode.replaceChild(renderedNode, node); - newvnode.$onrender && newvnode.$onrender(renderedNode); - oldvnode.from(newvnode); - return; + node.removeEventListener(a, oldvnode.eventListeners[a]); + node.addEventListener(a, newvnode.eventListeners[a]); } - // ID - if (oldvnode.id !== newvnode.id) { - node.id = newvnode.id || ""; - oldvnode.id = newvnode.id; + }); + Object.keys(newvnode.eventListeners).forEach((a) => { + if (!oldvnode.eventListeners[a]) { + node.addEventListener(a, newvnode.eventListeners[a]); } - // Value - if (oldvnode.value !== newvnode.value) { - oldvnode.value = newvnode.value; - if (["textarea", "input"].includes(oldvnode.type)) { - node.value = newvnode.value || ""; - } else { - node.setAttribute("value", newvnode.value || ""); - } - } - // Classes - if (!equal(oldvnode.classList, newvnode.classList)) { - oldvnode.classList.forEach((c) => { - if (!newvnode.classList.includes(c)) { - node.classList.remove(c); - } - }); - newvnode.classList.forEach((c) => { - if (!oldvnode.classList.includes(c)) { - node.classList.add(c); - } - }); - oldvnode.classList = newvnode.classList; - } - // Style - if (oldvnode.style !== newvnode.style) { - node.style.cssText = newvnode.style || ""; - oldvnode.style = newvnode.style; - } - // Data - if (!equal(oldvnode.data, newvnode.data)) { - Object.keys(oldvnode.data).forEach((a) => { - if (!newvnode.data[a]) { - delete node.dataset[a]; - } else if (newvnode.data[a] !== oldvnode.data[a]) { - node.dataset[a] = newvnode.data[a]; - } - }); - Object.keys(newvnode.data).forEach((a) => { - if (!oldvnode.data[a]) { - node.dataset[a] = newvnode.data[a]; - } - }); - oldvnode.data = newvnode.data; + }); + oldvnode.eventListeners = newvnode.eventListeners; + } + // Children + let childMap = mapChildren(oldvnode, newvnode); + let resultMap = [...Array(newvnode.children.length).keys()]; + while (!equal(childMap, resultMap)) { + let count = -1; + checkmap: for (const i of childMap) { + count++; + if (i === count) { + // Matching nodes; + continue; } - // 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") { - oldvnode.props[a] = newvnode.props[a]; - newvnode.props[a] - ? node.setAttribute(a, "") - : node.removeAttribute(a); - } else if (!newvnode.props[a]) { - delete oldvnode.props[a]; - node.removeAttribute(a); - } else if ( - newvnode.props[a] && - newvnode.props[a] !== oldvnode.props[a] - ) { - oldvnode.props[a] = newvnode.props[a]; - if ( - ["string", "number"].includes(typeof newvnode.props[a]) - ) { - node.setAttribute(a, newvnode.props[a]); - } - } + switch (i) { + case PATCH: { + oldvnode.children[count].redraw({ + node: node.childNodes[count], + vnode: newvnode.children[count], }); - Object.keys(newvnode.props).forEach((a) => { - if (!oldvnode.props[a] && newvnode.props[a]) { - oldvnode.props[a] = newvnode.props[a]; - node.setAttribute(a, newvnode.props[a]); - } - }); - } - // Event listeners - 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] - ) - ) { - node.removeEventListener(a, oldvnode.eventListeners[a]); - node.addEventListener(a, newvnode.eventListeners[a]); - } - }); - Object.keys(newvnode.eventListeners).forEach((a) => { - if (!oldvnode.eventListeners[a]) { - node.addEventListener(a, newvnode.eventListeners[a]); - } - }); - oldvnode.eventListeners = newvnode.eventListeners; - } - // Children - let childMap = mapChildren(oldvnode, newvnode); - let resultMap = [...Array(newvnode.children.length).keys()]; - while (!equal(childMap, resultMap)) { - let count = -1; - checkmap: for (const i of childMap) { - count++; - if (i === count) { - // Matching nodes; - continue; - } - switch (i) { - case PATCH: { - oldvnode.children[count].redraw({ - node: node.childNodes[count], - vnode: newvnode.children[count], - }); - break checkmap; - } - 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); - break checkmap; - } - case DELETE: { - oldvnode.children.splice(count, 1); - node.removeChild(node.childNodes[count]); - break checkmap; - } - default: { - const vtarget = oldvnode.children.splice(i, 1)[0]; - oldvnode.children.splice(count, 0, vtarget); - const target = node.removeChild(node.childNodes[i]); - node.insertBefore(target, node.childNodes[count]); - break checkmap; - } - } - } - childMap = mapChildren(oldvnode, newvnode); - resultMap = [...Array(newvnode.children.length).keys()]; - } - // $onrender - if (!equal(oldvnode.$onrender, newvnode.$onrender)) { - oldvnode.$onrender = newvnode.$onrender; - } - // innerHTML - if (oldvnode.$html !== newvnode.$html) { - node.innerHTML = newvnode.$html; - oldvnode.$html = newvnode.$html; - oldvnode.$onrender && oldvnode.$onrender(node); + break checkmap; + } + 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); + break checkmap; + } + case DELETE: { + oldvnode.children.splice(count, 1); + node.removeChild(node.childNodes[count]); + break checkmap; + } + default: { + const vtarget = oldvnode.children.splice(i, 1)[0]; + oldvnode.children.splice(count, 0, vtarget); + const target = node.removeChild(node.childNodes[i]); + node.insertBefore(target, node.childNodes[count]); + break checkmap; + } } + } + childMap = mapChildren(oldvnode, newvnode); + resultMap = [...Array(newvnode.children.length).keys()]; } + // $onrender + if (!equal(oldvnode.$onrender, newvnode.$onrender)) { + oldvnode.$onrender = newvnode.$onrender; + } + // innerHTML + if (oldvnode.$html !== newvnode.$html) { + node.innerHTML = newvnode.$html; + oldvnode.$html = newvnode.$html; + oldvnode.$onrender && oldvnode.$onrender(node); + } + } } const mapChildren = (oldvnode, newvnode) => { - const newList = newvnode.children; - const oldList = oldvnode.children; - let map = []; - for (let nIdx = 0; nIdx < newList.length; nIdx++) { - let op = PATCH; - for (let oIdx = 0; oIdx < oldList.length; oIdx++) { - if (equal(newList[nIdx], oldList[oIdx]) && !map.includes(oIdx)) { - op = oIdx; // Same node found - break; - } - } - if ( - op < 0 && - newList.length >= oldList.length && - map.length >= oldList.length - ) { - op = INSERT; - } - map.push(op); + const newList = newvnode.children; + const oldList = oldvnode.children; + let map = []; + for (let nIdx = 0; nIdx < newList.length; nIdx++) { + let op = PATCH; + for (let oIdx = 0; oIdx < oldList.length; oIdx++) { + if (equal(newList[nIdx], oldList[oIdx]) && !map.includes(oIdx)) { + op = oIdx; // Same node found + break; + } } - 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) - ); - } else if (oldNodesFound.length === oldList.length) { - // All nodes not found are insertions - map = map.map((c) => (c < 0 ? INSERT : c)); + if ( + op < 0 && + newList.length >= oldList.length && + map.length >= oldList.length + ) { + op = INSERT; } - return map; + map.push(op); + } + 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) + ); + } else if (oldNodesFound.length === oldList.length) { + // All nodes not found are insertions + map = map.map((c) => (c < 0 ? INSERT : c)); + } + return map; }; /**@@ -550,159 +530,154 @@ * <https://github.com/storeon/storeon/blob/master/LICENSE>
* Copyright 2019 Andrey Sitnik <andrey@sitnik.ru> */ class Store { - constructor() { - this.events = {}; - this.state = {}; + constructor() { + this.events = {}; + this.state = {}; + } + dispatch(event, data) { + if (event !== "$log") this.dispatch("$log", { event, data }); + if (this.events[event]) { + let changes = {}; + let changed; + this.events[event].forEach((i) => { + this.state = { ...this.state, ...i(this.state, data) }; + }); } - dispatch(event, data) { - if (event !== "$log") this.dispatch("$log", { event, data }); - if (this.events[event]) { - let changes = {}; - let changed; - this.events[event].forEach((i) => { - this.state = { ...this.state, ...i(this.state, data) }; - }); - } - } + } - on(event, cb) { - (this.events[event] || (this.events[event] = [])).push(cb); + on(event, cb) { + (this.events[event] || (this.events[event] = [])).push(cb); - return () => { - this.events[event] = this.events[event].filter((i) => i !== cb); - }; - } + return () => { + this.events[event] = this.events[event].filter((i) => i !== cb); + }; + } } class Route { - constructor({ path, def, query, parts }) { - this.path = path; - this.def = def; - this.query = query; - this.parts = parts; - this.params = {}; - if (this.query) { - const rawParams = this.query.split("&"); - rawParams.forEach((p) => { - const [name, value] = p.split("="); - this.params[decodeURIComponent(name)] = decodeURIComponent( - value - ); - }); - } + constructor({ path, def, query, parts }) { + this.path = path; + this.def = def; + this.query = query; + this.parts = parts; + this.params = {}; + if (this.query) { + const rawParams = this.query.split("&"); + rawParams.forEach((p) => { + const [name, value] = p.split("="); + this.params[decodeURIComponent(name)] = decodeURIComponent(value); + }); } + } } class Router { - constructor({ element, routes, store, location }) { - this.element = element; - 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."); - } - const defs = Object.keys(routes); - this.routes = routes; + constructor({ element, routes, store, location }) { + this.element = element; + 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."); } + const defs = Object.keys(routes); + this.routes = routes; + } - setRedraw(vnode, state) { - this.redraw = () => { - vnode.redraw({ - node: this.element.childNodes[0], - vnode: this.routes[this.route.def](state), - }); - this.store.dispatch("$redraw"); - }; - } + setRedraw(vnode, state) { + this.redraw = () => { + vnode.redraw({ + node: this.element.childNodes[0], + vnode: this.routes[this.route.def](state), + }); + 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 rawQuery = fragment.match(/\?(.+)$/); - const query = rawQuery && rawQuery[1] ? rawQuery[1] : ""; - const pathParts = path.split("/").slice(1); + 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 rawQuery = fragment.match(/\?(.+)$/); + const query = rawQuery && rawQuery[1] ? rawQuery[1] : ""; + const pathParts = path.split("/").slice(1); - let parts = {}; - for (let def of Object.keys(this.routes)) { - 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) { - parts[rP.slice(1)] = pP; - } else { - match = rP === pP; - } - index++; - } - if (match) { - this.route = new Route({ query, path, def, parts }); - break; - } - } - if (!this.route) { - throw new Error(`[Router] No route matches '${fragment}'`); - } - // Old route component teardown - if (oldRoute) { - const oldRouteComponent = this.routes[oldRoute.def]; - oldRouteComponent.state = - oldRouteComponent.teardown && - (await oldRouteComponent.teardown(oldRouteComponent.state)); - } - // New route component setup - const newRouteComponent = this.routes[this.route.def]; - newRouteComponent.state = {}; - newRouteComponent.setup && - (await newRouteComponent.setup(newRouteComponent.state)); - // Redrawing... - redrawing = true; - this.store.dispatch("$navigation", this.route); - while (this.element.firstChild) { - this.element.removeChild(this.element.firstChild); - } - const vnode = newRouteComponent(newRouteComponent.state); - const node = vnode.render(); - this.element.appendChild(node); - this.setRedraw(vnode, newRouteComponent.state); - redrawing = false; - vnode.$onrender && vnode.$onrender(node); - $onrenderCallbacks.forEach((cbk) => cbk()); - $onrenderCallbacks = []; - window.scrollTo(0, 0); - this.store.dispatch("$redraw"); - }; - window.addEventListener("hashchange", processPath); - await processPath(); - } + let parts = {}; + for (let def of Object.keys(this.routes)) { + 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) { + parts[rP.slice(1)] = pP; + } else { + match = rP === pP; + } + index++; + } + if (match) { + this.route = new Route({ query, path, def, parts }); + break; + } + } + if (!this.route) { + throw new Error(`[Router] No route matches '${fragment}'`); + } + // Old route component teardown + if (oldRoute) { + const oldRouteComponent = this.routes[oldRoute.def]; + oldRouteComponent.state = + oldRouteComponent.teardown && + (await oldRouteComponent.teardown(oldRouteComponent.state)); + } + // New route component setup + const newRouteComponent = this.routes[this.route.def]; + newRouteComponent.state = {}; + newRouteComponent.setup && + (await newRouteComponent.setup(newRouteComponent.state)); + // Redrawing... + redrawing = true; + this.store.dispatch("$navigation", this.route); + while (this.element.firstChild) { + this.element.removeChild(this.element.firstChild); + } + const vnode = newRouteComponent(newRouteComponent.state); + const node = vnode.render(); + this.element.appendChild(node); + this.setRedraw(vnode, newRouteComponent.state); + redrawing = false; + vnode.$onrender && vnode.$onrender(node); + $onrenderCallbacks.forEach((cbk) => cbk()); + $onrenderCallbacks = []; + window.scrollTo(0, 0); + this.store.dispatch("$redraw"); + }; + 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}` : ""; - this.location.hash = `#${path}${query}`; - } + navigateTo(path, params) { + let query = Object.keys(params || {}) + .map((p) => `${encodeURIComponent(p)}=${encodeURIComponent(params[p])}`) + .join("&"); + query = query ? `?${query}` : ""; + this.location.hash = `#${path}${query}`; + } } // High Level API export const h = (...args) => { - return new VNode(...args); + return new VNode(...args); }; export const h3 = {};@@ -712,94 +687,114 @@ let router = null;
let redrawing = false; 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" - ); - } - routes = { "/": 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" + ); } - element = element || document.body; - if (!(element && element instanceof Element)) { - throw new Error("[h3.init] Invalid element specified."); - } - // Initialize store - store = new Store(); - (modules || []).forEach((i) => { - i(store); - }); - store.dispatch("$init"); - // Initialize router - router = new Router({ element, routes, store, location }); - return Promise.resolve(preStart && preStart()) - .then(() => router.start()) - .then(() => postStart && postStart()); + routes = { "/": config }; + } + element = element || document.body; + if (!(element && element instanceof Element)) { + throw new Error("[h3.init] Invalid element specified."); + } + // Initialize store + store = new Store(); + (modules || []).forEach((i) => { + i(store); + }); + store.dispatch("$init"); + // Initialize router + router = new Router({ element, routes, store, location }); + return Promise.resolve(preStart && preStart()) + .then(() => router.start()) + .then(() => postStart && postStart()); }; h3.navigateTo = (path, params) => { - if (!router) { - throw new Error( - "[h3.navigateTo] No application initialized, unable to navigate." - ); - } - return router.navigateTo(path, params); + if (!router) { + throw new Error( + "[h3.navigateTo] No application initialized, unable to navigate." + ); + } + return router.navigateTo(path, params); }; Object.defineProperty(h3, "route", { - get: () => { - if (!router) { - throw new Error( - "[h3.route] No application initialized, unable to retrieve current route." - ); - } - return router.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; - }, + get: () => { + if (!store) { + throw new Error( + "[h3.state] No application initialized, unable to retrieve current state." + ); + } + return store.state; + }, }); h3.on = (event, cb) => { - if (!store) { - throw new Error( - "[h3.on] No application initialized, unable to listen to events." - ); - } - return store.on(event, cb); + if (!store) { + 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." - ); - } - return store.dispatch(event, data); + if (!store) { + 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." - ); - } - if (redrawing) { - return; - } - redrawing = true; - router.redraw(); - redrawing = setRedrawing || false; + if (!router || !router.redraw) { + throw new Error( + "[h3.redraw] No application initialized, unable to redraw." + ); + } + if (redrawing) { + return; + } + redrawing = true; + 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 (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."); + } + const fn = display; + if (setup) { + fn.setup = setup; + } + if (teardown) { + fn.teardown = teardown; + } + return fn; }; export default h3;
M
docs/js/h3.js
→
docs/js/h3.js
@@ -6,55 +6,55 @@ * @license MIT
* For the full license, see: https://github.com/h3rald/h3/blob/master/LICENSE */ const checkProperties = (obj1, obj2) => { - for (const key in obj1) { - if (!(key in obj2)) { - return false; - } - if (!equal(obj1[key], obj2[key])) { - return false; - } + for (const key in obj1) { + if (!(key in obj2)) { + return false; } - return true; + if (!equal(obj1[key], obj2[key])) { + return false; + } + } + return true; }; const equal = (obj1, obj2) => { - if ( - (obj1 === null && obj2 === null) || - (obj1 === undefined && obj2 === undefined) - ) { - return true; + 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) + ) { + return false; + } + if (obj1.constructor !== obj2.constructor) { + return false; + } + if (typeof obj1 === "function") { + if (obj1.toString() !== obj2.toString()) { + return false; } - if ( - (obj1 === undefined && obj2 !== undefined) || - (obj1 !== undefined && obj2 === undefined) || - (obj1 === null && obj2 !== null) || - (obj1 !== null && obj2 === null) - ) { - return false; + } + if ([String, Number, Boolean].includes(obj1.constructor)) { + return obj1 === obj2; + } + if (obj1.constructor === Array) { + if (obj1.length !== obj2.length) { + return false; } - if (obj1.constructor !== obj2.constructor) { + for (let i = 0; i < obj1.length; i++) { + if (!equal(obj1[i], obj2[i])) { return false; + } } - if (typeof obj1 === "function") { - if (obj1.toString() !== obj2.toString()) { - return false; - } - } - if ([String, Number, Boolean].includes(obj1.constructor)) { - return obj1 === obj2; - } - if (obj1.constructor === Array) { - if (obj1.length !== obj2.length) { - return false; - } - for (let i = 0; i < obj1.length; i++) { - if (!equal(obj1[i], obj2[i])) { - return false; - } - } - return true; - } - return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); + return true; + } + return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); }; const selectorRegex = /^([a-z][a-z0-9:_=-]*)?(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i;@@ -63,484 +63,464 @@ let $onrenderCallbacks = [];
// Virtual Node Implementation with HyperScript-like syntax class VNode { - constructor(...args) { - this.type = undefined; - this.props = {}; - this.data = {}; - this.id = undefined; - this.$html = undefined; - this.$onrender = undefined; - this.style = undefined; - this.value = undefined; - this.children = []; - this.classList = []; - this.eventListeners = {}; - if (args.length === 0) { - throw new Error( - "[VNode] No arguments passed to VNode constructor." - ); + constructor(...args) { + this.type = undefined; + this.props = {}; + this.data = {}; + this.id = undefined; + this.$html = undefined; + this.$onrender = undefined; + this.style = undefined; + this.value = undefined; + this.children = []; + this.classList = []; + this.eventListeners = {}; + if (args.length === 0) { + throw new Error("[VNode] No arguments passed to VNode constructor."); + } + if (args.length === 1) { + let vnode = args[0]; + if (typeof vnode === "string") { + // Assume empty element + this.processSelector(vnode); + } else if ( + typeof vnode === "function" || + (typeof vnode === "object" && vnode !== null) + ) { + // Text node + if (vnode.type === "#text") { + this.type = "#text"; + this.value = vnode.value; + } else { + this.from(this.processVNodeObject(vnode)); } - if (args.length === 1) { - let vnode = args[0]; - if (typeof vnode === "string") { - // Assume empty element - this.processSelector(vnode); - } else if ( - typeof vnode === "function" || - (typeof vnode === "object" && vnode !== null) - ) { - // Text node - 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." - ); - } - } else if (args.length === 2) { - let [selector, data] = args; - if (typeof selector !== "string") { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); - } - this.processSelector(selector); - if (typeof data === "string") { - // Assume single child text node - 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 (Array.isArray(data)) { - // Assume 2nd argument as children - this.processChildren(data); - } else { - if (data instanceof Function || data instanceof VNode) { - this.processChildren(data); - } else { - // Not a VNode, assume props object - this.processProperties(data); - } - } + } else { + 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." + ); + } + this.processSelector(selector); + if (typeof data === "string") { + // Assume single child text node + 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 (Array.isArray(data)) { + // Assume 2nd argument as children + this.processChildren(data); + } else { + if (data instanceof Function || data instanceof VNode) { + this.processChildren(data); } else { - let [selector, props, children] = args; - 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." - ); - } - this.processSelector(selector); - 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." - ); - } - this.processProperties(props); - } - this.processChildren(children); + // Not a VNode, assume props object + this.processProperties(data); + } + } + } else { + let [selector, props, children] = args; + 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." + ); + } + this.processSelector(selector); + 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." + ); } + this.processProperties(props); + } + this.processChildren(children); } + } - from(data) { - this.value = data.value; - this.type = data.type; - this.id = data.id; - this.$html = data.$html; - this.$onrender = data.$onrender; - this.style = data.style; - this.data = data.data; - this.value = data.value; - this.eventListeners = data.eventListeners; - this.children = data.children; - this.props = data.props; - this.classList = data.classList; - } + from(data) { + this.value = data.value; + this.type = data.type; + this.id = data.id; + this.$html = data.$html; + this.$onrender = data.$onrender; + this.style = data.style; + this.data = data.data; + this.value = data.value; + this.eventListeners = data.eventListeners; + this.children = data.children; + this.props = data.props; + this.classList = data.classList; + } + + equal(a, b) { + return equal(a, b === undefined ? this : b); + } + + processProperties(attrs) { + this.id = this.id || attrs.id; + this.$html = attrs.$html; + 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.props = attrs; + Object.keys(attrs) + .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.` + ); + } + this.eventListeners[key.slice(2)] = attrs[key]; + delete this.props[key]; + }); + 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; + } - equal(a, b) { - return equal(a, b === undefined ? this : b); + processSelector(selector) { + if (!selector.match(selectorRegex) || selector.length === 0) { + throw new Error(`[VNode] Invalid selector: ${selector}`); } + const [, type, id, classes] = selector.match(selectorRegex); + this.type = type; + if (id) { + this.id = id.slice(1); + } + this.classList = (classes && classes.split(".").slice(1)) || []; + } - processProperties(attrs) { - this.id = this.id || attrs.id; - this.$html = attrs.$html; - 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.props = attrs; - Object.keys(attrs) - .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.` - ); - } - this.eventListeners[key.slice(2)] = attrs[key]; - delete this.props[key]; - }); - 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; + processVNodeObject(arg) { + if (arg instanceof VNode) { + return arg; } + if (arg instanceof Function) { + let vnode = arg(); + 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"); + } + return vnode; + } + throw new Error( + "[VNode] Invalid first argument provided to VNode constructor." + ); + } - processSelector(selector) { - if (!selector.match(selectorRegex) || selector.length === 0) { - throw new Error(`[VNode] Invalid selector: ${selector}`); + 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 === "function" || (typeof c === "object" && c !== null)) { + return this.processVNodeObject(c); } - const [, type, id, classes] = selector.match(selectorRegex); - this.type = type; - if (id) { - this.id = id.slice(1); + if (c) { + throw new Error(`[VNode] Specified child is not a VNode: ${c}`); } - this.classList = (classes && classes.split(".").slice(1)) || []; + }) + .filter((c) => c); + } + + // Renders the actual DOM Node corresponding to the current Virtual Node + render() { + if (this.type === "#text") { + return document.createTextNode(this.value); + } + const node = document.createElement(this.type); + if (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 (["string", "number"].includes(typeof this.props[p])) { + node.setAttribute(p, this.props[p]); + } + // Set properties + node[p] = this.props[p]; + }); + // Event Listeners + Object.keys(this.eventListeners).forEach((event) => { + node.addEventListener(event, this.eventListeners[event]); + }); + // Value + if (this.value) { + if (["textarea", "input"].includes(this.type)) { + node.value = this.value; + } else { + node.setAttribute("value", this.value); + } + } + // Style + if (this.style) { + node.style.cssText = this.style; + } + // Classes + this.classList.forEach((c) => { + node.classList.add(c); + }); + // Data + Object.keys(this.data).forEach((key) => { + node.dataset[key] = this.data[key]; + }); + // Children + this.children.forEach((c) => { + const cnode = c.render(); + node.appendChild(cnode); + c.$onrender && $onrenderCallbacks.push(() => c.$onrender(cnode)); + }); + if (this.$html) { + node.innerHTML = this.$html; } + return node; + } - processVNodeObject(arg) { - if (arg instanceof VNode) { - return arg; + // Updates the current Virtual Node with a new Virtual Node (and syncs the existing DOM Node) + redraw(data) { + let { node, vnode } = data; + const newvnode = vnode; + const oldvnode = this; + if ( + oldvnode.constructor !== newvnode.constructor || + oldvnode.type !== newvnode.type || + (oldvnode.type === newvnode.type && + oldvnode.type === "#text" && + oldvnode !== newvnode) + ) { + const renderedNode = newvnode.render(); + node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); + oldvnode.from(newvnode); + return; + } + // ID + if (oldvnode.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 || ""; + } else { + node.setAttribute("value", newvnode.value || ""); + } + } + // Classes + if (!equal(oldvnode.classList, newvnode.classList)) { + oldvnode.classList.forEach((c) => { + if (!newvnode.classList.includes(c)) { + node.classList.remove(c); } - if (arg instanceof Function) { - let vnode = arg(); - 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" - ); - } - return vnode; + }); + newvnode.classList.forEach((c) => { + if (!oldvnode.classList.includes(c)) { + node.classList.add(c); } - throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." - ); + }); + oldvnode.classList = newvnode.classList; } - - 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 === "function" || - (typeof c === "object" && c !== null) - ) { - return this.processVNodeObject(c); - } - if (c) { - throw new Error( - `[VNode] Specified child is not a VNode: ${c}` - ); - } - }) - .filter((c) => c); + // Style + if (oldvnode.style !== newvnode.style) { + node.style.cssText = newvnode.style || ""; + oldvnode.style = newvnode.style; } - - // Renders the actual DOM Node corresponding to the current Virtual Node - render() { - if (this.type === "#text") { - return document.createTextNode(this.value); + // Data + if (!equal(oldvnode.data, newvnode.data)) { + Object.keys(oldvnode.data).forEach((a) => { + if (!newvnode.data[a]) { + delete node.dataset[a]; + } else if (newvnode.data[a] !== oldvnode.data[a]) { + node.dataset[a] = newvnode.data[a]; } - const node = document.createElement(this.type); - if (this.id) { - node.id = this.id; + }); + Object.keys(newvnode.data).forEach((a) => { + if (!oldvnode.data[a]) { + node.dataset[a] = newvnode.data[a]; } - Object.keys(this.props).forEach((p) => { - // Set attributes - if (typeof this.props[p] === "boolean") { - this.props[p] - ? node.setAttribute(p, "") - : node.removeAttribute(p); - } - if (["string", "number"].includes(typeof this.props[p])) { - node.setAttribute(p, this.props[p]); - } - // Set properties - node[p] = this.props[p]; - }); - // Event Listeners - Object.keys(this.eventListeners).forEach((event) => { - node.addEventListener(event, this.eventListeners[event]); - }); - // Value - if (this.value) { - if (["textarea", "input"].includes(this.type)) { - node.value = this.value; - } else { - node.setAttribute("value", this.value); - } + }); + oldvnode.data = newvnode.data; + } + // 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") { + oldvnode.props[a] = newvnode.props[a]; + newvnode.props[a] + ? node.setAttribute(a, "") + : node.removeAttribute(a); + } else if (!newvnode.props[a]) { + delete oldvnode.props[a]; + node.removeAttribute(a); + } else if ( + newvnode.props[a] && + newvnode.props[a] !== oldvnode.props[a] + ) { + oldvnode.props[a] = newvnode.props[a]; + if (["string", "number"].includes(typeof newvnode.props[a])) { + node.setAttribute(a, newvnode.props[a]); + } } - // Style - if (this.style) { - node.style.cssText = this.style; + }); + Object.keys(newvnode.props).forEach((a) => { + if (!oldvnode.props[a] && newvnode.props[a]) { + oldvnode.props[a] = newvnode.props[a]; + node.setAttribute(a, newvnode.props[a]); } - // Classes - this.classList.forEach((c) => { - node.classList.add(c); - }); - // Data - Object.keys(this.data).forEach((key) => { - node.dataset[key] = this.data[key]; - }); - // Children - this.children.forEach((c) => { - const cnode = c.render(); - node.appendChild(cnode); - c.$onrender && $onrenderCallbacks.push(() => c.$onrender(cnode)); - }); - if (this.$html) { - node.innerHTML = this.$html; - } - return node; + }); } - - // Updates the current Virtual Node with a new Virtual Node (and syncs the existing DOM Node) - redraw(data) { - let { node, vnode } = data; - const newvnode = vnode; - const oldvnode = this; - if ( - oldvnode.constructor !== newvnode.constructor || - oldvnode.type !== newvnode.type || - (oldvnode.type === newvnode.type && - oldvnode.type === "#text" && - oldvnode !== newvnode) + // Event listeners + 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]) ) { - const renderedNode = newvnode.render(); - node.parentNode.replaceChild(renderedNode, node); - newvnode.$onrender && newvnode.$onrender(renderedNode); - oldvnode.from(newvnode); - return; + node.removeEventListener(a, oldvnode.eventListeners[a]); + node.addEventListener(a, newvnode.eventListeners[a]); } - // ID - if (oldvnode.id !== newvnode.id) { - node.id = newvnode.id || ""; - oldvnode.id = newvnode.id; + }); + Object.keys(newvnode.eventListeners).forEach((a) => { + if (!oldvnode.eventListeners[a]) { + node.addEventListener(a, newvnode.eventListeners[a]); } - // Value - if (oldvnode.value !== newvnode.value) { - oldvnode.value = newvnode.value; - if (["textarea", "input"].includes(oldvnode.type)) { - node.value = newvnode.value || ""; - } else { - node.setAttribute("value", newvnode.value || ""); - } - } - // Classes - if (!equal(oldvnode.classList, newvnode.classList)) { - oldvnode.classList.forEach((c) => { - if (!newvnode.classList.includes(c)) { - node.classList.remove(c); - } - }); - newvnode.classList.forEach((c) => { - if (!oldvnode.classList.includes(c)) { - node.classList.add(c); - } - }); - oldvnode.classList = newvnode.classList; - } - // Style - if (oldvnode.style !== newvnode.style) { - node.style.cssText = newvnode.style || ""; - oldvnode.style = newvnode.style; - } - // Data - if (!equal(oldvnode.data, newvnode.data)) { - Object.keys(oldvnode.data).forEach((a) => { - if (!newvnode.data[a]) { - delete node.dataset[a]; - } else if (newvnode.data[a] !== oldvnode.data[a]) { - node.dataset[a] = newvnode.data[a]; - } - }); - Object.keys(newvnode.data).forEach((a) => { - if (!oldvnode.data[a]) { - node.dataset[a] = newvnode.data[a]; - } - }); - oldvnode.data = newvnode.data; + }); + oldvnode.eventListeners = newvnode.eventListeners; + } + // Children + let childMap = mapChildren(oldvnode, newvnode); + let resultMap = [...Array(newvnode.children.length).keys()]; + while (!equal(childMap, resultMap)) { + let count = -1; + checkmap: for (const i of childMap) { + count++; + if (i === count) { + // Matching nodes; + continue; } - // 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") { - oldvnode.props[a] = newvnode.props[a]; - newvnode.props[a] - ? node.setAttribute(a, "") - : node.removeAttribute(a); - } else if (!newvnode.props[a]) { - delete oldvnode.props[a]; - node.removeAttribute(a); - } else if ( - newvnode.props[a] && - newvnode.props[a] !== oldvnode.props[a] - ) { - oldvnode.props[a] = newvnode.props[a]; - if ( - ["string", "number"].includes(typeof newvnode.props[a]) - ) { - node.setAttribute(a, newvnode.props[a]); - } - } + switch (i) { + case PATCH: { + oldvnode.children[count].redraw({ + node: node.childNodes[count], + vnode: newvnode.children[count], }); - Object.keys(newvnode.props).forEach((a) => { - if (!oldvnode.props[a] && newvnode.props[a]) { - oldvnode.props[a] = newvnode.props[a]; - node.setAttribute(a, newvnode.props[a]); - } - }); - } - // Event listeners - 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] - ) - ) { - node.removeEventListener(a, oldvnode.eventListeners[a]); - node.addEventListener(a, newvnode.eventListeners[a]); - } - }); - Object.keys(newvnode.eventListeners).forEach((a) => { - if (!oldvnode.eventListeners[a]) { - node.addEventListener(a, newvnode.eventListeners[a]); - } - }); - oldvnode.eventListeners = newvnode.eventListeners; - } - // Children - let childMap = mapChildren(oldvnode, newvnode); - let resultMap = [...Array(newvnode.children.length).keys()]; - while (!equal(childMap, resultMap)) { - let count = -1; - checkmap: for (const i of childMap) { - count++; - if (i === count) { - // Matching nodes; - continue; - } - switch (i) { - case PATCH: { - oldvnode.children[count].redraw({ - node: node.childNodes[count], - vnode: newvnode.children[count], - }); - break checkmap; - } - 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); - break checkmap; - } - case DELETE: { - oldvnode.children.splice(count, 1); - node.removeChild(node.childNodes[count]); - break checkmap; - } - default: { - const vtarget = oldvnode.children.splice(i, 1)[0]; - oldvnode.children.splice(count, 0, vtarget); - const target = node.removeChild(node.childNodes[i]); - node.insertBefore(target, node.childNodes[count]); - break checkmap; - } - } - } - childMap = mapChildren(oldvnode, newvnode); - resultMap = [...Array(newvnode.children.length).keys()]; - } - // $onrender - if (!equal(oldvnode.$onrender, newvnode.$onrender)) { - oldvnode.$onrender = newvnode.$onrender; - } - // innerHTML - if (oldvnode.$html !== newvnode.$html) { - node.innerHTML = newvnode.$html; - oldvnode.$html = newvnode.$html; - oldvnode.$onrender && oldvnode.$onrender(node); + break checkmap; + } + 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); + break checkmap; + } + case DELETE: { + oldvnode.children.splice(count, 1); + node.removeChild(node.childNodes[count]); + break checkmap; + } + default: { + const vtarget = oldvnode.children.splice(i, 1)[0]; + oldvnode.children.splice(count, 0, vtarget); + const target = node.removeChild(node.childNodes[i]); + node.insertBefore(target, node.childNodes[count]); + break checkmap; + } } + } + childMap = mapChildren(oldvnode, newvnode); + resultMap = [...Array(newvnode.children.length).keys()]; } + // $onrender + if (!equal(oldvnode.$onrender, newvnode.$onrender)) { + oldvnode.$onrender = newvnode.$onrender; + } + // innerHTML + if (oldvnode.$html !== newvnode.$html) { + node.innerHTML = newvnode.$html; + oldvnode.$html = newvnode.$html; + oldvnode.$onrender && oldvnode.$onrender(node); + } + } } const mapChildren = (oldvnode, newvnode) => { - const newList = newvnode.children; - const oldList = oldvnode.children; - let map = []; - for (let nIdx = 0; nIdx < newList.length; nIdx++) { - let op = PATCH; - for (let oIdx = 0; oIdx < oldList.length; oIdx++) { - if (equal(newList[nIdx], oldList[oIdx]) && !map.includes(oIdx)) { - op = oIdx; // Same node found - break; - } - } - if ( - op < 0 && - newList.length >= oldList.length && - map.length >= oldList.length - ) { - op = INSERT; - } - map.push(op); + const newList = newvnode.children; + const oldList = oldvnode.children; + let map = []; + for (let nIdx = 0; nIdx < newList.length; nIdx++) { + let op = PATCH; + for (let oIdx = 0; oIdx < oldList.length; oIdx++) { + if (equal(newList[nIdx], oldList[oIdx]) && !map.includes(oIdx)) { + op = oIdx; // Same node found + break; + } } - 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) - ); - } else if (oldNodesFound.length === oldList.length) { - // All nodes not found are insertions - map = map.map((c) => (c < 0 ? INSERT : c)); + if ( + op < 0 && + newList.length >= oldList.length && + map.length >= oldList.length + ) { + op = INSERT; } - return map; + map.push(op); + } + 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) + ); + } else if (oldNodesFound.length === oldList.length) { + // All nodes not found are insertions + map = map.map((c) => (c < 0 ? INSERT : c)); + } + return map; }; /**@@ -550,159 +530,154 @@ * <https://github.com/storeon/storeon/blob/master/LICENSE>
* Copyright 2019 Andrey Sitnik <andrey@sitnik.ru> */ class Store { - constructor() { - this.events = {}; - this.state = {}; + constructor() { + this.events = {}; + this.state = {}; + } + dispatch(event, data) { + if (event !== "$log") this.dispatch("$log", { event, data }); + if (this.events[event]) { + let changes = {}; + let changed; + this.events[event].forEach((i) => { + this.state = { ...this.state, ...i(this.state, data) }; + }); } - dispatch(event, data) { - if (event !== "$log") this.dispatch("$log", { event, data }); - if (this.events[event]) { - let changes = {}; - let changed; - this.events[event].forEach((i) => { - this.state = { ...this.state, ...i(this.state, data) }; - }); - } - } + } - on(event, cb) { - (this.events[event] || (this.events[event] = [])).push(cb); + on(event, cb) { + (this.events[event] || (this.events[event] = [])).push(cb); - return () => { - this.events[event] = this.events[event].filter((i) => i !== cb); - }; - } + return () => { + this.events[event] = this.events[event].filter((i) => i !== cb); + }; + } } class Route { - constructor({ path, def, query, parts }) { - this.path = path; - this.def = def; - this.query = query; - this.parts = parts; - this.params = {}; - if (this.query) { - const rawParams = this.query.split("&"); - rawParams.forEach((p) => { - const [name, value] = p.split("="); - this.params[decodeURIComponent(name)] = decodeURIComponent( - value - ); - }); - } + constructor({ path, def, query, parts }) { + this.path = path; + this.def = def; + this.query = query; + this.parts = parts; + this.params = {}; + if (this.query) { + const rawParams = this.query.split("&"); + rawParams.forEach((p) => { + const [name, value] = p.split("="); + this.params[decodeURIComponent(name)] = decodeURIComponent(value); + }); } + } } class Router { - constructor({ element, routes, store, location }) { - this.element = element; - 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."); - } - const defs = Object.keys(routes); - this.routes = routes; + constructor({ element, routes, store, location }) { + this.element = element; + 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."); } + const defs = Object.keys(routes); + this.routes = routes; + } - setRedraw(vnode, state) { - this.redraw = () => { - vnode.redraw({ - node: this.element.childNodes[0], - vnode: this.routes[this.route.def](state), - }); - this.store.dispatch("$redraw"); - }; - } + setRedraw(vnode, state) { + this.redraw = () => { + vnode.redraw({ + node: this.element.childNodes[0], + vnode: this.routes[this.route.def](state), + }); + 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 rawQuery = fragment.match(/\?(.+)$/); - const query = rawQuery && rawQuery[1] ? rawQuery[1] : ""; - const pathParts = path.split("/").slice(1); + 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 rawQuery = fragment.match(/\?(.+)$/); + const query = rawQuery && rawQuery[1] ? rawQuery[1] : ""; + const pathParts = path.split("/").slice(1); - let parts = {}; - for (let def of Object.keys(this.routes)) { - 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) { - parts[rP.slice(1)] = pP; - } else { - match = rP === pP; - } - index++; - } - if (match) { - this.route = new Route({ query, path, def, parts }); - break; - } - } - if (!this.route) { - throw new Error(`[Router] No route matches '${fragment}'`); - } - // Old route component teardown - if (oldRoute) { - const oldRouteComponent = this.routes[oldRoute.def]; - oldRouteComponent.state = - oldRouteComponent.teardown && - (await oldRouteComponent.teardown(oldRouteComponent.state)); - } - // New route component setup - const newRouteComponent = this.routes[this.route.def]; - newRouteComponent.state = {}; - newRouteComponent.setup && - (await newRouteComponent.setup(newRouteComponent.state)); - // Redrawing... - redrawing = true; - this.store.dispatch("$navigation", this.route); - while (this.element.firstChild) { - this.element.removeChild(this.element.firstChild); - } - const vnode = newRouteComponent(newRouteComponent.state); - const node = vnode.render(); - this.element.appendChild(node); - this.setRedraw(vnode, newRouteComponent.state); - redrawing = false; - vnode.$onrender && vnode.$onrender(node); - $onrenderCallbacks.forEach((cbk) => cbk()); - $onrenderCallbacks = []; - window.scrollTo(0, 0); - this.store.dispatch("$redraw"); - }; - window.addEventListener("hashchange", processPath); - await processPath(); - } + let parts = {}; + for (let def of Object.keys(this.routes)) { + 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) { + parts[rP.slice(1)] = pP; + } else { + match = rP === pP; + } + index++; + } + if (match) { + this.route = new Route({ query, path, def, parts }); + break; + } + } + if (!this.route) { + throw new Error(`[Router] No route matches '${fragment}'`); + } + // Old route component teardown + if (oldRoute) { + const oldRouteComponent = this.routes[oldRoute.def]; + oldRouteComponent.state = + oldRouteComponent.teardown && + (await oldRouteComponent.teardown(oldRouteComponent.state)); + } + // New route component setup + const newRouteComponent = this.routes[this.route.def]; + newRouteComponent.state = {}; + newRouteComponent.setup && + (await newRouteComponent.setup(newRouteComponent.state)); + // Redrawing... + redrawing = true; + this.store.dispatch("$navigation", this.route); + while (this.element.firstChild) { + this.element.removeChild(this.element.firstChild); + } + const vnode = newRouteComponent(newRouteComponent.state); + const node = vnode.render(); + this.element.appendChild(node); + this.setRedraw(vnode, newRouteComponent.state); + redrawing = false; + vnode.$onrender && vnode.$onrender(node); + $onrenderCallbacks.forEach((cbk) => cbk()); + $onrenderCallbacks = []; + window.scrollTo(0, 0); + this.store.dispatch("$redraw"); + }; + 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}` : ""; - this.location.hash = `#${path}${query}`; - } + navigateTo(path, params) { + let query = Object.keys(params || {}) + .map((p) => `${encodeURIComponent(p)}=${encodeURIComponent(params[p])}`) + .join("&"); + query = query ? `?${query}` : ""; + this.location.hash = `#${path}${query}`; + } } // High Level API export const h = (...args) => { - return new VNode(...args); + return new VNode(...args); }; export const h3 = {};@@ -712,94 +687,114 @@ let router = null;
let redrawing = false; 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" - ); - } - routes = { "/": 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" + ); } - element = element || document.body; - if (!(element && element instanceof Element)) { - throw new Error("[h3.init] Invalid element specified."); - } - // Initialize store - store = new Store(); - (modules || []).forEach((i) => { - i(store); - }); - store.dispatch("$init"); - // Initialize router - router = new Router({ element, routes, store, location }); - return Promise.resolve(preStart && preStart()) - .then(() => router.start()) - .then(() => postStart && postStart()); + routes = { "/": config }; + } + element = element || document.body; + if (!(element && element instanceof Element)) { + throw new Error("[h3.init] Invalid element specified."); + } + // Initialize store + store = new Store(); + (modules || []).forEach((i) => { + i(store); + }); + store.dispatch("$init"); + // Initialize router + router = new Router({ element, routes, store, location }); + return Promise.resolve(preStart && preStart()) + .then(() => router.start()) + .then(() => postStart && postStart()); }; h3.navigateTo = (path, params) => { - if (!router) { - throw new Error( - "[h3.navigateTo] No application initialized, unable to navigate." - ); - } - return router.navigateTo(path, params); + if (!router) { + throw new Error( + "[h3.navigateTo] No application initialized, unable to navigate." + ); + } + return router.navigateTo(path, params); }; Object.defineProperty(h3, "route", { - get: () => { - if (!router) { - throw new Error( - "[h3.route] No application initialized, unable to retrieve current route." - ); - } - return router.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; - }, + get: () => { + if (!store) { + throw new Error( + "[h3.state] No application initialized, unable to retrieve current state." + ); + } + return store.state; + }, }); h3.on = (event, cb) => { - if (!store) { - throw new Error( - "[h3.on] No application initialized, unable to listen to events." - ); - } - return store.on(event, cb); + if (!store) { + 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." - ); - } - return store.dispatch(event, data); + if (!store) { + 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." - ); - } - if (redrawing) { - return; - } - redrawing = true; - router.redraw(); - redrawing = setRedrawing || false; + if (!router || !router.redraw) { + throw new Error( + "[h3.redraw] No application initialized, unable to redraw." + ); + } + if (redrawing) { + return; + } + redrawing = true; + 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 (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."); + } + const fn = display; + if (setup) { + fn.setup = setup; + } + if (teardown) { + fn.teardown = teardown; + } + return fn; }; export default h3;
M
h3.js
→
h3.js
@@ -6,55 +6,55 @@ * @license MIT
* For the full license, see: https://github.com/h3rald/h3/blob/master/LICENSE */ const checkProperties = (obj1, obj2) => { - for (const key in obj1) { - if (!(key in obj2)) { - return false; - } - if (!equal(obj1[key], obj2[key])) { - return false; - } + for (const key in obj1) { + if (!(key in obj2)) { + return false; } - return true; + if (!equal(obj1[key], obj2[key])) { + return false; + } + } + return true; }; const equal = (obj1, obj2) => { - if ( - (obj1 === null && obj2 === null) || - (obj1 === undefined && obj2 === undefined) - ) { - return true; + 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) + ) { + return false; + } + if (obj1.constructor !== obj2.constructor) { + return false; + } + if (typeof obj1 === "function") { + if (obj1.toString() !== obj2.toString()) { + return false; } - if ( - (obj1 === undefined && obj2 !== undefined) || - (obj1 !== undefined && obj2 === undefined) || - (obj1 === null && obj2 !== null) || - (obj1 !== null && obj2 === null) - ) { - return false; + } + if ([String, Number, Boolean].includes(obj1.constructor)) { + return obj1 === obj2; + } + if (obj1.constructor === Array) { + if (obj1.length !== obj2.length) { + return false; } - if (obj1.constructor !== obj2.constructor) { + for (let i = 0; i < obj1.length; i++) { + if (!equal(obj1[i], obj2[i])) { return false; + } } - if (typeof obj1 === "function") { - if (obj1.toString() !== obj2.toString()) { - return false; - } - } - if ([String, Number, Boolean].includes(obj1.constructor)) { - return obj1 === obj2; - } - if (obj1.constructor === Array) { - if (obj1.length !== obj2.length) { - return false; - } - for (let i = 0; i < obj1.length; i++) { - if (!equal(obj1[i], obj2[i])) { - return false; - } - } - return true; - } - return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); + return true; + } + return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); }; const selectorRegex = /^([a-z][a-z0-9:_=-]*)?(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i;@@ -63,484 +63,464 @@ let $onrenderCallbacks = [];
// Virtual Node Implementation with HyperScript-like syntax class VNode { - constructor(...args) { - this.type = undefined; - this.props = {}; - this.data = {}; - this.id = undefined; - this.$html = undefined; - this.$onrender = undefined; - this.style = undefined; - this.value = undefined; - this.children = []; - this.classList = []; - this.eventListeners = {}; - if (args.length === 0) { - throw new Error( - "[VNode] No arguments passed to VNode constructor." - ); + constructor(...args) { + this.type = undefined; + this.props = {}; + this.data = {}; + this.id = undefined; + this.$html = undefined; + this.$onrender = undefined; + this.style = undefined; + this.value = undefined; + this.children = []; + this.classList = []; + this.eventListeners = {}; + if (args.length === 0) { + throw new Error("[VNode] No arguments passed to VNode constructor."); + } + if (args.length === 1) { + let vnode = args[0]; + if (typeof vnode === "string") { + // Assume empty element + this.processSelector(vnode); + } else if ( + typeof vnode === "function" || + (typeof vnode === "object" && vnode !== null) + ) { + // Text node + if (vnode.type === "#text") { + this.type = "#text"; + this.value = vnode.value; + } else { + this.from(this.processVNodeObject(vnode)); } - if (args.length === 1) { - let vnode = args[0]; - if (typeof vnode === "string") { - // Assume empty element - this.processSelector(vnode); - } else if ( - typeof vnode === "function" || - (typeof vnode === "object" && vnode !== null) - ) { - // Text node - 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." - ); - } - } else if (args.length === 2) { - let [selector, data] = args; - if (typeof selector !== "string") { - throw new Error( - "[VNode] Invalid first argument passed to VNode constructor." - ); - } - this.processSelector(selector); - if (typeof data === "string") { - // Assume single child text node - 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 (Array.isArray(data)) { - // Assume 2nd argument as children - this.processChildren(data); - } else { - if (data instanceof Function || data instanceof VNode) { - this.processChildren(data); - } else { - // Not a VNode, assume props object - this.processProperties(data); - } - } + } else { + 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." + ); + } + this.processSelector(selector); + if (typeof data === "string") { + // Assume single child text node + 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 (Array.isArray(data)) { + // Assume 2nd argument as children + this.processChildren(data); + } else { + if (data instanceof Function || data instanceof VNode) { + this.processChildren(data); } else { - let [selector, props, children] = args; - 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." - ); - } - this.processSelector(selector); - 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." - ); - } - this.processProperties(props); - } - this.processChildren(children); + // Not a VNode, assume props object + this.processProperties(data); + } + } + } else { + let [selector, props, children] = args; + 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." + ); + } + this.processSelector(selector); + 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." + ); } + this.processProperties(props); + } + this.processChildren(children); } + } - from(data) { - this.value = data.value; - this.type = data.type; - this.id = data.id; - this.$html = data.$html; - this.$onrender = data.$onrender; - this.style = data.style; - this.data = data.data; - this.value = data.value; - this.eventListeners = data.eventListeners; - this.children = data.children; - this.props = data.props; - this.classList = data.classList; - } + from(data) { + this.value = data.value; + this.type = data.type; + this.id = data.id; + this.$html = data.$html; + this.$onrender = data.$onrender; + this.style = data.style; + this.data = data.data; + this.value = data.value; + this.eventListeners = data.eventListeners; + this.children = data.children; + this.props = data.props; + this.classList = data.classList; + } + + equal(a, b) { + return equal(a, b === undefined ? this : b); + } + + processProperties(attrs) { + this.id = this.id || attrs.id; + this.$html = attrs.$html; + 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.props = attrs; + Object.keys(attrs) + .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.` + ); + } + this.eventListeners[key.slice(2)] = attrs[key]; + delete this.props[key]; + }); + 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; + } - equal(a, b) { - return equal(a, b === undefined ? this : b); + processSelector(selector) { + if (!selector.match(selectorRegex) || selector.length === 0) { + throw new Error(`[VNode] Invalid selector: ${selector}`); } + const [, type, id, classes] = selector.match(selectorRegex); + this.type = type; + if (id) { + this.id = id.slice(1); + } + this.classList = (classes && classes.split(".").slice(1)) || []; + } - processProperties(attrs) { - this.id = this.id || attrs.id; - this.$html = attrs.$html; - 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.props = attrs; - Object.keys(attrs) - .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.` - ); - } - this.eventListeners[key.slice(2)] = attrs[key]; - delete this.props[key]; - }); - 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; + processVNodeObject(arg) { + if (arg instanceof VNode) { + return arg; } + if (arg instanceof Function) { + let vnode = arg(); + 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"); + } + return vnode; + } + throw new Error( + "[VNode] Invalid first argument provided to VNode constructor." + ); + } - processSelector(selector) { - if (!selector.match(selectorRegex) || selector.length === 0) { - throw new Error(`[VNode] Invalid selector: ${selector}`); + 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 === "function" || (typeof c === "object" && c !== null)) { + return this.processVNodeObject(c); } - const [, type, id, classes] = selector.match(selectorRegex); - this.type = type; - if (id) { - this.id = id.slice(1); + if (c) { + throw new Error(`[VNode] Specified child is not a VNode: ${c}`); } - this.classList = (classes && classes.split(".").slice(1)) || []; + }) + .filter((c) => c); + } + + // Renders the actual DOM Node corresponding to the current Virtual Node + render() { + if (this.type === "#text") { + return document.createTextNode(this.value); + } + const node = document.createElement(this.type); + if (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 (["string", "number"].includes(typeof this.props[p])) { + node.setAttribute(p, this.props[p]); + } + // Set properties + node[p] = this.props[p]; + }); + // Event Listeners + Object.keys(this.eventListeners).forEach((event) => { + node.addEventListener(event, this.eventListeners[event]); + }); + // Value + if (this.value) { + if (["textarea", "input"].includes(this.type)) { + node.value = this.value; + } else { + node.setAttribute("value", this.value); + } + } + // Style + if (this.style) { + node.style.cssText = this.style; + } + // Classes + this.classList.forEach((c) => { + node.classList.add(c); + }); + // Data + Object.keys(this.data).forEach((key) => { + node.dataset[key] = this.data[key]; + }); + // Children + this.children.forEach((c) => { + const cnode = c.render(); + node.appendChild(cnode); + c.$onrender && $onrenderCallbacks.push(() => c.$onrender(cnode)); + }); + if (this.$html) { + node.innerHTML = this.$html; } + return node; + } - processVNodeObject(arg) { - if (arg instanceof VNode) { - return arg; + // Updates the current Virtual Node with a new Virtual Node (and syncs the existing DOM Node) + redraw(data) { + let { node, vnode } = data; + const newvnode = vnode; + const oldvnode = this; + if ( + oldvnode.constructor !== newvnode.constructor || + oldvnode.type !== newvnode.type || + (oldvnode.type === newvnode.type && + oldvnode.type === "#text" && + oldvnode !== newvnode) + ) { + const renderedNode = newvnode.render(); + node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); + oldvnode.from(newvnode); + return; + } + // ID + if (oldvnode.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 || ""; + } else { + node.setAttribute("value", newvnode.value || ""); + } + } + // Classes + if (!equal(oldvnode.classList, newvnode.classList)) { + oldvnode.classList.forEach((c) => { + if (!newvnode.classList.includes(c)) { + node.classList.remove(c); } - if (arg instanceof Function) { - let vnode = arg(); - 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" - ); - } - return vnode; + }); + newvnode.classList.forEach((c) => { + if (!oldvnode.classList.includes(c)) { + node.classList.add(c); } - throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." - ); + }); + oldvnode.classList = newvnode.classList; } - - 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 === "function" || - (typeof c === "object" && c !== null) - ) { - return this.processVNodeObject(c); - } - if (c) { - throw new Error( - `[VNode] Specified child is not a VNode: ${c}` - ); - } - }) - .filter((c) => c); + // Style + if (oldvnode.style !== newvnode.style) { + node.style.cssText = newvnode.style || ""; + oldvnode.style = newvnode.style; } - - // Renders the actual DOM Node corresponding to the current Virtual Node - render() { - if (this.type === "#text") { - return document.createTextNode(this.value); + // Data + if (!equal(oldvnode.data, newvnode.data)) { + Object.keys(oldvnode.data).forEach((a) => { + if (!newvnode.data[a]) { + delete node.dataset[a]; + } else if (newvnode.data[a] !== oldvnode.data[a]) { + node.dataset[a] = newvnode.data[a]; } - const node = document.createElement(this.type); - if (this.id) { - node.id = this.id; + }); + Object.keys(newvnode.data).forEach((a) => { + if (!oldvnode.data[a]) { + node.dataset[a] = newvnode.data[a]; } - Object.keys(this.props).forEach((p) => { - // Set attributes - if (typeof this.props[p] === "boolean") { - this.props[p] - ? node.setAttribute(p, "") - : node.removeAttribute(p); - } - if (["string", "number"].includes(typeof this.props[p])) { - node.setAttribute(p, this.props[p]); - } - // Set properties - node[p] = this.props[p]; - }); - // Event Listeners - Object.keys(this.eventListeners).forEach((event) => { - node.addEventListener(event, this.eventListeners[event]); - }); - // Value - if (this.value) { - if (["textarea", "input"].includes(this.type)) { - node.value = this.value; - } else { - node.setAttribute("value", this.value); - } + }); + oldvnode.data = newvnode.data; + } + // 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") { + oldvnode.props[a] = newvnode.props[a]; + newvnode.props[a] + ? node.setAttribute(a, "") + : node.removeAttribute(a); + } else if (!newvnode.props[a]) { + delete oldvnode.props[a]; + node.removeAttribute(a); + } else if ( + newvnode.props[a] && + newvnode.props[a] !== oldvnode.props[a] + ) { + oldvnode.props[a] = newvnode.props[a]; + if (["string", "number"].includes(typeof newvnode.props[a])) { + node.setAttribute(a, newvnode.props[a]); + } } - // Style - if (this.style) { - node.style.cssText = this.style; + }); + Object.keys(newvnode.props).forEach((a) => { + if (!oldvnode.props[a] && newvnode.props[a]) { + oldvnode.props[a] = newvnode.props[a]; + node.setAttribute(a, newvnode.props[a]); } - // Classes - this.classList.forEach((c) => { - node.classList.add(c); - }); - // Data - Object.keys(this.data).forEach((key) => { - node.dataset[key] = this.data[key]; - }); - // Children - this.children.forEach((c) => { - const cnode = c.render(); - node.appendChild(cnode); - c.$onrender && $onrenderCallbacks.push(() => c.$onrender(cnode)); - }); - if (this.$html) { - node.innerHTML = this.$html; - } - return node; + }); } - - // Updates the current Virtual Node with a new Virtual Node (and syncs the existing DOM Node) - redraw(data) { - let { node, vnode } = data; - const newvnode = vnode; - const oldvnode = this; - if ( - oldvnode.constructor !== newvnode.constructor || - oldvnode.type !== newvnode.type || - (oldvnode.type === newvnode.type && - oldvnode.type === "#text" && - oldvnode !== newvnode) + // Event listeners + 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]) ) { - const renderedNode = newvnode.render(); - node.parentNode.replaceChild(renderedNode, node); - newvnode.$onrender && newvnode.$onrender(renderedNode); - oldvnode.from(newvnode); - return; + node.removeEventListener(a, oldvnode.eventListeners[a]); + node.addEventListener(a, newvnode.eventListeners[a]); } - // ID - if (oldvnode.id !== newvnode.id) { - node.id = newvnode.id || ""; - oldvnode.id = newvnode.id; + }); + Object.keys(newvnode.eventListeners).forEach((a) => { + if (!oldvnode.eventListeners[a]) { + node.addEventListener(a, newvnode.eventListeners[a]); } - // Value - if (oldvnode.value !== newvnode.value) { - oldvnode.value = newvnode.value; - if (["textarea", "input"].includes(oldvnode.type)) { - node.value = newvnode.value || ""; - } else { - node.setAttribute("value", newvnode.value || ""); - } - } - // Classes - if (!equal(oldvnode.classList, newvnode.classList)) { - oldvnode.classList.forEach((c) => { - if (!newvnode.classList.includes(c)) { - node.classList.remove(c); - } - }); - newvnode.classList.forEach((c) => { - if (!oldvnode.classList.includes(c)) { - node.classList.add(c); - } - }); - oldvnode.classList = newvnode.classList; - } - // Style - if (oldvnode.style !== newvnode.style) { - node.style.cssText = newvnode.style || ""; - oldvnode.style = newvnode.style; - } - // Data - if (!equal(oldvnode.data, newvnode.data)) { - Object.keys(oldvnode.data).forEach((a) => { - if (!newvnode.data[a]) { - delete node.dataset[a]; - } else if (newvnode.data[a] !== oldvnode.data[a]) { - node.dataset[a] = newvnode.data[a]; - } - }); - Object.keys(newvnode.data).forEach((a) => { - if (!oldvnode.data[a]) { - node.dataset[a] = newvnode.data[a]; - } - }); - oldvnode.data = newvnode.data; + }); + oldvnode.eventListeners = newvnode.eventListeners; + } + // Children + let childMap = mapChildren(oldvnode, newvnode); + let resultMap = [...Array(newvnode.children.length).keys()]; + while (!equal(childMap, resultMap)) { + let count = -1; + checkmap: for (const i of childMap) { + count++; + if (i === count) { + // Matching nodes; + continue; } - // 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") { - oldvnode.props[a] = newvnode.props[a]; - newvnode.props[a] - ? node.setAttribute(a, "") - : node.removeAttribute(a); - } else if (!newvnode.props[a]) { - delete oldvnode.props[a]; - node.removeAttribute(a); - } else if ( - newvnode.props[a] && - newvnode.props[a] !== oldvnode.props[a] - ) { - oldvnode.props[a] = newvnode.props[a]; - if ( - ["string", "number"].includes(typeof newvnode.props[a]) - ) { - node.setAttribute(a, newvnode.props[a]); - } - } + switch (i) { + case PATCH: { + oldvnode.children[count].redraw({ + node: node.childNodes[count], + vnode: newvnode.children[count], }); - Object.keys(newvnode.props).forEach((a) => { - if (!oldvnode.props[a] && newvnode.props[a]) { - oldvnode.props[a] = newvnode.props[a]; - node.setAttribute(a, newvnode.props[a]); - } - }); - } - // Event listeners - 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] - ) - ) { - node.removeEventListener(a, oldvnode.eventListeners[a]); - node.addEventListener(a, newvnode.eventListeners[a]); - } - }); - Object.keys(newvnode.eventListeners).forEach((a) => { - if (!oldvnode.eventListeners[a]) { - node.addEventListener(a, newvnode.eventListeners[a]); - } - }); - oldvnode.eventListeners = newvnode.eventListeners; - } - // Children - let childMap = mapChildren(oldvnode, newvnode); - let resultMap = [...Array(newvnode.children.length).keys()]; - while (!equal(childMap, resultMap)) { - let count = -1; - checkmap: for (const i of childMap) { - count++; - if (i === count) { - // Matching nodes; - continue; - } - switch (i) { - case PATCH: { - oldvnode.children[count].redraw({ - node: node.childNodes[count], - vnode: newvnode.children[count], - }); - break checkmap; - } - 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); - break checkmap; - } - case DELETE: { - oldvnode.children.splice(count, 1); - node.removeChild(node.childNodes[count]); - break checkmap; - } - default: { - const vtarget = oldvnode.children.splice(i, 1)[0]; - oldvnode.children.splice(count, 0, vtarget); - const target = node.removeChild(node.childNodes[i]); - node.insertBefore(target, node.childNodes[count]); - break checkmap; - } - } - } - childMap = mapChildren(oldvnode, newvnode); - resultMap = [...Array(newvnode.children.length).keys()]; - } - // $onrender - if (!equal(oldvnode.$onrender, newvnode.$onrender)) { - oldvnode.$onrender = newvnode.$onrender; - } - // innerHTML - if (oldvnode.$html !== newvnode.$html) { - node.innerHTML = newvnode.$html; - oldvnode.$html = newvnode.$html; - oldvnode.$onrender && oldvnode.$onrender(node); + break checkmap; + } + 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); + break checkmap; + } + case DELETE: { + oldvnode.children.splice(count, 1); + node.removeChild(node.childNodes[count]); + break checkmap; + } + default: { + const vtarget = oldvnode.children.splice(i, 1)[0]; + oldvnode.children.splice(count, 0, vtarget); + const target = node.removeChild(node.childNodes[i]); + node.insertBefore(target, node.childNodes[count]); + break checkmap; + } } + } + childMap = mapChildren(oldvnode, newvnode); + resultMap = [...Array(newvnode.children.length).keys()]; } + // $onrender + if (!equal(oldvnode.$onrender, newvnode.$onrender)) { + oldvnode.$onrender = newvnode.$onrender; + } + // innerHTML + if (oldvnode.$html !== newvnode.$html) { + node.innerHTML = newvnode.$html; + oldvnode.$html = newvnode.$html; + oldvnode.$onrender && oldvnode.$onrender(node); + } + } } const mapChildren = (oldvnode, newvnode) => { - const newList = newvnode.children; - const oldList = oldvnode.children; - let map = []; - for (let nIdx = 0; nIdx < newList.length; nIdx++) { - let op = PATCH; - for (let oIdx = 0; oIdx < oldList.length; oIdx++) { - if (equal(newList[nIdx], oldList[oIdx]) && !map.includes(oIdx)) { - op = oIdx; // Same node found - break; - } - } - if ( - op < 0 && - newList.length >= oldList.length && - map.length >= oldList.length - ) { - op = INSERT; - } - map.push(op); + const newList = newvnode.children; + const oldList = oldvnode.children; + let map = []; + for (let nIdx = 0; nIdx < newList.length; nIdx++) { + let op = PATCH; + for (let oIdx = 0; oIdx < oldList.length; oIdx++) { + if (equal(newList[nIdx], oldList[oIdx]) && !map.includes(oIdx)) { + op = oIdx; // Same node found + break; + } } - 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) - ); - } else if (oldNodesFound.length === oldList.length) { - // All nodes not found are insertions - map = map.map((c) => (c < 0 ? INSERT : c)); + if ( + op < 0 && + newList.length >= oldList.length && + map.length >= oldList.length + ) { + op = INSERT; } - return map; + map.push(op); + } + 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) + ); + } else if (oldNodesFound.length === oldList.length) { + // All nodes not found are insertions + map = map.map((c) => (c < 0 ? INSERT : c)); + } + return map; }; /**@@ -550,159 +530,154 @@ * <https://github.com/storeon/storeon/blob/master/LICENSE>
* Copyright 2019 Andrey Sitnik <andrey@sitnik.ru> */ class Store { - constructor() { - this.events = {}; - this.state = {}; + constructor() { + this.events = {}; + this.state = {}; + } + dispatch(event, data) { + if (event !== "$log") this.dispatch("$log", { event, data }); + if (this.events[event]) { + let changes = {}; + let changed; + this.events[event].forEach((i) => { + this.state = { ...this.state, ...i(this.state, data) }; + }); } - dispatch(event, data) { - if (event !== "$log") this.dispatch("$log", { event, data }); - if (this.events[event]) { - let changes = {}; - let changed; - this.events[event].forEach((i) => { - this.state = { ...this.state, ...i(this.state, data) }; - }); - } - } + } - on(event, cb) { - (this.events[event] || (this.events[event] = [])).push(cb); + on(event, cb) { + (this.events[event] || (this.events[event] = [])).push(cb); - return () => { - this.events[event] = this.events[event].filter((i) => i !== cb); - }; - } + return () => { + this.events[event] = this.events[event].filter((i) => i !== cb); + }; + } } class Route { - constructor({ path, def, query, parts }) { - this.path = path; - this.def = def; - this.query = query; - this.parts = parts; - this.params = {}; - if (this.query) { - const rawParams = this.query.split("&"); - rawParams.forEach((p) => { - const [name, value] = p.split("="); - this.params[decodeURIComponent(name)] = decodeURIComponent( - value - ); - }); - } + constructor({ path, def, query, parts }) { + this.path = path; + this.def = def; + this.query = query; + this.parts = parts; + this.params = {}; + if (this.query) { + const rawParams = this.query.split("&"); + rawParams.forEach((p) => { + const [name, value] = p.split("="); + this.params[decodeURIComponent(name)] = decodeURIComponent(value); + }); } + } } class Router { - constructor({ element, routes, store, location }) { - this.element = element; - 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."); - } - const defs = Object.keys(routes); - this.routes = routes; + constructor({ element, routes, store, location }) { + this.element = element; + 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."); } + const defs = Object.keys(routes); + this.routes = routes; + } - setRedraw(vnode, state) { - this.redraw = () => { - vnode.redraw({ - node: this.element.childNodes[0], - vnode: this.routes[this.route.def](state), - }); - this.store.dispatch("$redraw"); - }; - } + setRedraw(vnode, state) { + this.redraw = () => { + vnode.redraw({ + node: this.element.childNodes[0], + vnode: this.routes[this.route.def](state), + }); + 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 rawQuery = fragment.match(/\?(.+)$/); - const query = rawQuery && rawQuery[1] ? rawQuery[1] : ""; - const pathParts = path.split("/").slice(1); + 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 rawQuery = fragment.match(/\?(.+)$/); + const query = rawQuery && rawQuery[1] ? rawQuery[1] : ""; + const pathParts = path.split("/").slice(1); - let parts = {}; - for (let def of Object.keys(this.routes)) { - 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) { - parts[rP.slice(1)] = pP; - } else { - match = rP === pP; - } - index++; - } - if (match) { - this.route = new Route({ query, path, def, parts }); - break; - } - } - if (!this.route) { - throw new Error(`[Router] No route matches '${fragment}'`); - } - // Old route component teardown - if (oldRoute) { - const oldRouteComponent = this.routes[oldRoute.def]; - oldRouteComponent.state = - oldRouteComponent.teardown && - (await oldRouteComponent.teardown(oldRouteComponent.state)); - } - // New route component setup - const newRouteComponent = this.routes[this.route.def]; - newRouteComponent.state = {}; - newRouteComponent.setup && - (await newRouteComponent.setup(newRouteComponent.state)); - // Redrawing... - redrawing = true; - this.store.dispatch("$navigation", this.route); - while (this.element.firstChild) { - this.element.removeChild(this.element.firstChild); - } - const vnode = newRouteComponent(newRouteComponent.state); - const node = vnode.render(); - this.element.appendChild(node); - this.setRedraw(vnode, newRouteComponent.state); - redrawing = false; - vnode.$onrender && vnode.$onrender(node); - $onrenderCallbacks.forEach((cbk) => cbk()); - $onrenderCallbacks = []; - window.scrollTo(0, 0); - this.store.dispatch("$redraw"); - }; - window.addEventListener("hashchange", processPath); - await processPath(); - } + let parts = {}; + for (let def of Object.keys(this.routes)) { + 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) { + parts[rP.slice(1)] = pP; + } else { + match = rP === pP; + } + index++; + } + if (match) { + this.route = new Route({ query, path, def, parts }); + break; + } + } + if (!this.route) { + throw new Error(`[Router] No route matches '${fragment}'`); + } + // Old route component teardown + if (oldRoute) { + const oldRouteComponent = this.routes[oldRoute.def]; + oldRouteComponent.state = + oldRouteComponent.teardown && + (await oldRouteComponent.teardown(oldRouteComponent.state)); + } + // New route component setup + const newRouteComponent = this.routes[this.route.def]; + newRouteComponent.state = {}; + newRouteComponent.setup && + (await newRouteComponent.setup(newRouteComponent.state)); + // Redrawing... + redrawing = true; + this.store.dispatch("$navigation", this.route); + while (this.element.firstChild) { + this.element.removeChild(this.element.firstChild); + } + const vnode = newRouteComponent(newRouteComponent.state); + const node = vnode.render(); + this.element.appendChild(node); + this.setRedraw(vnode, newRouteComponent.state); + redrawing = false; + vnode.$onrender && vnode.$onrender(node); + $onrenderCallbacks.forEach((cbk) => cbk()); + $onrenderCallbacks = []; + window.scrollTo(0, 0); + this.store.dispatch("$redraw"); + }; + 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}` : ""; - this.location.hash = `#${path}${query}`; - } + navigateTo(path, params) { + let query = Object.keys(params || {}) + .map((p) => `${encodeURIComponent(p)}=${encodeURIComponent(params[p])}`) + .join("&"); + query = query ? `?${query}` : ""; + this.location.hash = `#${path}${query}`; + } } // High Level API export const h = (...args) => { - return new VNode(...args); + return new VNode(...args); }; export const h3 = {};@@ -712,94 +687,114 @@ let router = null;
let redrawing = false; 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" - ); - } - routes = { "/": 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" + ); } - element = element || document.body; - if (!(element && element instanceof Element)) { - throw new Error("[h3.init] Invalid element specified."); - } - // Initialize store - store = new Store(); - (modules || []).forEach((i) => { - i(store); - }); - store.dispatch("$init"); - // Initialize router - router = new Router({ element, routes, store, location }); - return Promise.resolve(preStart && preStart()) - .then(() => router.start()) - .then(() => postStart && postStart()); + routes = { "/": config }; + } + element = element || document.body; + if (!(element && element instanceof Element)) { + throw new Error("[h3.init] Invalid element specified."); + } + // Initialize store + store = new Store(); + (modules || []).forEach((i) => { + i(store); + }); + store.dispatch("$init"); + // Initialize router + router = new Router({ element, routes, store, location }); + return Promise.resolve(preStart && preStart()) + .then(() => router.start()) + .then(() => postStart && postStart()); }; h3.navigateTo = (path, params) => { - if (!router) { - throw new Error( - "[h3.navigateTo] No application initialized, unable to navigate." - ); - } - return router.navigateTo(path, params); + if (!router) { + throw new Error( + "[h3.navigateTo] No application initialized, unable to navigate." + ); + } + return router.navigateTo(path, params); }; Object.defineProperty(h3, "route", { - get: () => { - if (!router) { - throw new Error( - "[h3.route] No application initialized, unable to retrieve current route." - ); - } - return router.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; - }, + get: () => { + if (!store) { + throw new Error( + "[h3.state] No application initialized, unable to retrieve current state." + ); + } + return store.state; + }, }); h3.on = (event, cb) => { - if (!store) { - throw new Error( - "[h3.on] No application initialized, unable to listen to events." - ); - } - return store.on(event, cb); + if (!store) { + 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." - ); - } - return store.dispatch(event, data); + if (!store) { + 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." - ); - } - if (redrawing) { - return; - } - redrawing = true; - router.redraw(); - redrawing = setRedrawing || false; + if (!router || !router.redraw) { + throw new Error( + "[h3.redraw] No application initialized, unable to redraw." + ); + } + if (redrawing) { + return; + } + redrawing = true; + 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 (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."); + } + const fn = display; + if (setup) { + fn.setup = setup; + } + if (teardown) { + fn.teardown = teardown; + } + return fn; }; export default h3;
M
h3.js.map
→
h3.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"],"mappings":"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","file":"h3.js"}+{"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"}
M
h3.min.js
→
h3.min.js
@@ -5,5 +5,5 @@ *
* @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,i=this;if(i.constructor!==s.constructor||i.type!==s.type||i.type===s.type&&"#text"===i.type&&i!==s){const e=s.render();return t.parentNode.replaceChild(e,t),s.$onrender&&s.$onrender(e),void i.from(s)}i.id!==s.id&&(t.id=s.id||"",i.id=s.id),i.value!==s.value&&(i.value=s.value,["textarea","input"].includes(i.type)?t.value=s.value||"":t.setAttribute("value",s.value||"")),equal(i.classList,s.classList)||(i.classList.forEach(e=>{s.classList.includes(e)||t.classList.remove(e)}),s.classList.forEach(e=>{i.classList.includes(e)||t.classList.add(e)}),i.classList=s.classList),i.style!==s.style&&(t.style.cssText=s.style||"",i.style=s.style),equal(i.data,s.data)||(Object.keys(i.data).forEach(e=>{s.data[e]?s.data[e]!==i.data[e]&&(t.dataset[e]=s.data[e]):delete t.dataset[e]}),Object.keys(s.data).forEach(e=>{i.data[e]||(t.dataset[e]=s.data[e])}),i.data=s.data),equal(i.props,s.props)||(Object.keys(i.props).forEach(e=>{t[e]=s.props[e],"boolean"==typeof s.props[e]?(i.props[e]=s.props[e],s.props[e]?t.setAttribute(e,""):t.removeAttribute(e)):s.props[e]?s.props[e]&&s.props[e]!==i.props[e]&&(i.props[e]=s.props[e],["string","number"].includes(typeof s.props[e])&&t.setAttribute(e,s.props[e])):(delete i.props[e],t.removeAttribute(e))}),Object.keys(s.props).forEach(e=>{!i.props[e]&&s.props[e]&&(i.props[e]=s.props[e],t.setAttribute(e,s.props[e]))})),equal(i.eventListeners,s.eventListeners)||(Object.keys(i.eventListeners).forEach(e=>{s.eventListeners[e]?equal(s.eventListeners[e],i.eventListeners[e])||(t.removeEventListener(e,i.eventListeners[e]),t.addEventListener(e,s.eventListeners[e])):t.removeEventListener(e,i.eventListeners[e])}),Object.keys(s.eventListeners).forEach(e=>{i.eventListeners[e]||t.addEventListener(e,s.eventListeners[e])}),i.eventListeners=s.eventListeners);let o=mapChildren(i,s),n=[...Array(s.children.length).keys()];for(;!equal(o,n);){let e=-1;e:for(const r of o)if(e++,r!==e)switch(r){case PATCH:i.children[e].redraw({node:t.childNodes[e],vnode:s.children[e]});break e;case INSERT:{i.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:i.children.splice(e,1),t.removeChild(t.childNodes[e]);break e;default:{const s=i.children.splice(r,1)[0];i.children.splice(e,0,s);const o=t.removeChild(t.childNodes[r]);t.insertBefore(o,t.childNodes[e]);break e}}o=mapChildren(i,s),n=[...Array(s.children.length).keys()]}equal(i.$onrender,s.$onrender)||(i.$onrender=s.$onrender),i.$html!==s.$html&&(t.innerHTML=s.$html,i.$html=s.$html,i.$onrender&&i.$onrender(t))}}const mapChildren=(e,t)=>{const r=t.children,s=e.children;let i=[];for(let e=0;e<r.length;e++){let t=PATCH;for(let o=0;o<s.length;o++)if(equal(r[e],s[o])&&!i.includes(o)){t=o;break}t<0&&r.length>=s.length&&i.length>=s.length&&(t=INSERT),i.push(t)}const o=i.filter(e=>e>=0);return s.length>r.length?[...Array(s.length-r.length).keys()].forEach(()=>i.push(DELETE)):o.length===s.length&&(i=i.map(e=>e<0?INSERT:e)),i};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),i=r.match(/\?(.+)$/),o=i&&i[1]?i[1]:"",n=s.split("/").slice(1);let a={};for(let e of Object.keys(this.routes)){let t=e.split("/").slice(1),r=!0,i=0;for(a={};r&&t[i];){const e=t[i],s=n[i];e.startsWith(":")&&s?a[e.slice(1)]=s:r=e===s,i++}if(r){this.route=new Route({query:o,path:s,def:e,parts:a});break}}if(!this.route)throw new Error(`[Router] No route matches '${r}'`);if(t){const e=this.routes[t.def];e.state=e.teardown&&await e.teardown(e.state)}const l=this.routes[this.route.def];for(l.state={},l.setup&&await l.setup(l.state),redrawing=!0,this.store.dispatch("$navigation",this.route);this.element.firstChild;)this.element.removeChild(this.element.firstChild);const h=l(l.state),d=h.render();this.element.appendChild(d),this.setRedraw(h,l.state),redrawing=!1,h.$onrender&&h.$onrender(d),$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:i,postStart:o,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(i&&i()).then(()=>router.start()).then(()=>o&&o())},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)};export default h3; +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}'`);if(t){const e=this.routes[t.def];e.state=e.teardown&&await e.teardown(e.state)}const l=this.routes[this.route.def];for(l.state={},l.setup&&await l.setup(l.state),redrawing=!0,this.store.dispatch("$navigation",this.route);this.element.firstChild;)this.element.removeChild(this.element.firstChild);const h=l(l.state),d=h.render();this.element.appendChild(d),this.setRedraw(h,l.state),redrawing=!1,h.$onrender&&h.$onrender(d),$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; //# sourceMappingURL=h3.js.map
M
package-lock.json
→
package-lock.json
@@ -1,6 +1,6 @@
{ "name": "@h3rald/h3", - "version": "0.9.0", + "version": "0.10.0", "lockfileVersion": 1, "requires": true, "dependencies": {