diff --git a/examples/dev.html b/examples/dev.html index 03d77a1de..143fbbeb5 100644 --- a/examples/dev.html +++ b/examples/dev.html @@ -48,7 +48,10 @@ type: "bi.vertical", items: [{ type: "bi.button", - text: store.model.text + text: store.model.text, + handler: function () { + console.log("click"); + } }, { type: "bi.label", text: store.model.text @@ -62,7 +65,12 @@ var Widget = BI.inherit(BI.Widget, { props: { - updateMode: "auto" + vdom: true + }, + watch: { + text: function () { + this.reset(); + } }, setup: function () { var child; @@ -72,13 +80,13 @@ setInterval(function () { store.toggle(); }, 1000); - BI.watch("text", function () { - child.reset(); - }); + // BI.watch("text", function () { + // child.reset(); + // }); return function () { return { type: "bi.vertical", - vgap: 20, + vgap: store.model.expand ? 20 : 30, items: [{ type: "demo.child", ref: function (_ref) { diff --git a/src/core/4.widget.js b/src/core/4.widget.js index 8eb0d6a53..84ff65d99 100644 --- a/src/core/4.widget.js +++ b/src/core/4.widget.js @@ -38,7 +38,7 @@ extraCls: "", cls: "", css: null, - updateMode: "manual" // manual / auto + vdom: false }); }, @@ -165,6 +165,7 @@ this.element = BI.Widget._renderEngine.createElement(this); } this.element._isWidget = true; + (this.element[0]._Widget = this.element[0]._Widget || []).push(this); this._initCurrent(); }, @@ -216,20 +217,45 @@ els = [els]; } if (BI.isArray(els)) { + if (this.options.vdom) { + this.vnode = this._renderVNode(); + var div = document.createElement("div"); + this.element.append(div); + BI.patchVNode(div, this.vnode); + } else { + BI.each(els, function (i, el) { + if (el) { + BI._lazyCreateWidget(el, { + element: self + }); + } + }); + } + } + this._mount(); + if (this.__async === true && isMounted) { + callLifeHook(this, "mounted"); + this.fireEvent(BI.Events.MOUNT); + } + }, + + _renderVNode: function () { + var render = BI.isFunction(this.options.render) ? this.options.render : this.render; + var els = render && render.call(this); + if (BI.isPlainObject(els)) { + els = [els]; + } + if (BI.isArray(els)) { + var container = document.createElement("div"); BI.each(els, function (i, el) { if (el) { BI._lazyCreateWidget(el, { - element: self + element: container }); } }); } - this._mount(); - - if (this.__async === true && isMounted) { - callLifeHook(this, "mounted"); - this.fireEvent(BI.Events.MOUNT); - } + return BI.Element2Snabbdom(container); }, _setParent: function (parent) { @@ -578,6 +604,12 @@ if (this.__async === true || this.__asking === true) { return; } + if (this.options.vdom) { + var vnode = this._renderVNode(); + BI.patchVNode(this.vnode, vnode); + this.vnode = vnode; + return; + } // this._isMounted = false; // this.purgeListeners(); this._empty(); diff --git a/src/core/8.snabdom.js b/src/core/8.snabdom.js new file mode 100644 index 000000000..f16d8355d --- /dev/null +++ b/src/core/8.snabdom.js @@ -0,0 +1,1052 @@ +(function (global, factory) { + factory(BI.Snabbdom = BI.Snabbdom || {}); +})(this, function (exports) { + 'use strict'; + + function createElement(tagName) { + return document.createElement(tagName); + } + function createElementNS(namespaceURI, qualifiedName) { + return document.createElementNS(namespaceURI, qualifiedName); + } + function createTextNode(text) { + return document.createTextNode(text); + } + function createComment(text) { + return document.createComment(text); + } + function insertBefore(parentNode, newNode, referenceNode) { + parentNode.insertBefore(newNode, referenceNode); + } + function removeChild(node, child) { + node.removeChild(child); + } + function appendChild(node, child) { + node.appendChild(child); + } + function parentNode(node) { + return node.parentNode; + } + function nextSibling(node) { + return node.nextSibling; + } + function tagName(elm) { + return elm.tagName; + } + function setTextContent(node, text) { + node.textContent = text; + } + function getTextContent(node) { + return node.textContent; + } + function isElement(node) { + return node.nodeType === 1; + } + function isText(node) { + return node.nodeType === 3; + } + function isComment(node) { + return node.nodeType === 8; + } + var htmlDomApi = { + createElement: createElement, + createElementNS: createElementNS, + createTextNode: createTextNode, + createComment: createComment, + insertBefore: insertBefore, + removeChild: removeChild, + appendChild: appendChild, + parentNode: parentNode, + nextSibling: nextSibling, + tagName: tagName, + setTextContent: setTextContent, + getTextContent: getTextContent, + isElement: isElement, + isText: isText, + isComment: isComment + }; + + function vnode(sel, data, children, text, elm) { + var key = data === undefined ? undefined : data.key; + return { sel: sel, data: data, children: children, text: text, elm: elm, key: key }; + } + + var array = Array.isArray; + function primitive(s) { + return typeof s === "string" || typeof s === "number"; + } + + function isUndef(s) { + return s === undefined; + } + function isDef(s) { + return s !== undefined; + } + var emptyNode = vnode("", {}, [], undefined, undefined); + function sameVnode(vnode1, vnode2) { + return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel; + } + function isVnode(vnode) { + return vnode.sel !== undefined; + } + function createKeyToOldIdx(children, beginIdx, endIdx) { + var _a; + var map = {}; + for (var i = beginIdx; i <= endIdx; ++i) { + var key = (_a = children[i]) === null || _a === void 0 ? void 0 : _a.key; + if (key !== undefined) { + map[key] = i; + } + } + return map; + } + var hooks = ["create", "update", "remove", "destroy", "pre", "post"]; + function init(modules, domApi) { + var i = void 0; + var j = void 0; + var cbs = { + create: [], + update: [], + remove: [], + destroy: [], + pre: [], + post: [] + }; + var api = domApi !== undefined ? domApi : htmlDomApi; + for (i = 0; i < hooks.length; ++i) { + cbs[hooks[i]] = []; + for (j = 0; j < modules.length; ++j) { + var hook = modules[j][hooks[i]]; + if (hook !== undefined) { + cbs[hooks[i]].push(hook); + } + } + } + function emptyNodeAt(elm) { + var id = elm.id ? "#" + elm.id : ""; + var c = elm.className ? "." + elm.className.split(" ").join(".") : ""; + return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm); + } + function createRmCb(childElm, listeners) { + return function rmCb() { + if (--listeners === 0) { + var parent = api.parentNode(childElm); + api.removeChild(parent, childElm); + } + }; + } + function createElm(vnode, insertedVnodeQueue) { + var _a, _b; + var i = void 0; + var data = vnode.data; + if (data !== undefined) { + var _init = (_a = data.hook) === null || _a === void 0 ? void 0 : _a.init; + if (isDef(_init)) { + _init(vnode); + data = vnode.data; + } + } + var children = vnode.children; + var sel = vnode.sel; + if (sel === "!") { + if (isUndef(vnode.text)) { + vnode.text = ""; + } + vnode.elm = api.createComment(vnode.text); + } else if (sel !== undefined) { + // Parse selector + var hashIdx = sel.indexOf("#"); + var dotIdx = sel.indexOf(".", hashIdx); + var hash = hashIdx > 0 ? hashIdx : sel.length; + var dot = dotIdx > 0 ? dotIdx : sel.length; + var tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel; + var elm = vnode.elm = isDef(data) && isDef(i = data.ns) ? api.createElementNS(i, tag) : api.createElement(tag); + if (hash < dot) elm.setAttribute("id", sel.slice(hash + 1, dot)); + if (dotIdx > 0) elm.setAttribute("class", sel.slice(dot + 1).replace(/\./g, " ")); + for (i = 0; i < cbs.create.length; ++i) { + cbs.create[i](emptyNode, vnode); + }if (array(children)) { + for (i = 0; i < children.length; ++i) { + var ch = children[i]; + if (ch != null) { + api.appendChild(elm, createElm(ch, insertedVnodeQueue)); + } + } + } else if (primitive(vnode.text)) { + api.appendChild(elm, api.createTextNode(vnode.text)); + } + var _hook = vnode.data.hook; + if (isDef(_hook)) { + (_b = _hook.create) === null || _b === void 0 ? void 0 : _b.call(_hook, emptyNode, vnode); + if (_hook.insert) { + insertedVnodeQueue.push(vnode); + } + } + } else { + vnode.elm = api.createTextNode(vnode.text); + } + return vnode.elm; + } + function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) { + for (; startIdx <= endIdx; ++startIdx) { + var ch = vnodes[startIdx]; + if (ch != null) { + api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before); + } + } + } + function invokeDestroyHook(vnode) { + var _a, _b; + var data = vnode.data; + if (data !== undefined) { + (_b = (_a = data === null || data === void 0 ? void 0 : data.hook) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a, vnode); + for (var _i = 0; _i < cbs.destroy.length; ++_i) { + cbs.destroy[_i](vnode); + }if (vnode.children !== undefined) { + for (var _j = 0; _j < vnode.children.length; ++_j) { + var child = vnode.children[_j]; + if (child != null && typeof child !== "string") { + invokeDestroyHook(child); + } + } + } + } + } + function removeVnodes(parentElm, vnodes, startIdx, endIdx) { + var _a, _b; + for (; startIdx <= endIdx; ++startIdx) { + var listeners = void 0; + var rm = void 0; + var ch = vnodes[startIdx]; + if (ch != null) { + if (isDef(ch.sel)) { + invokeDestroyHook(ch); + listeners = cbs.remove.length + 1; + rm = createRmCb(ch.elm, listeners); + for (var _i2 = 0; _i2 < cbs.remove.length; ++_i2) { + cbs.remove[_i2](ch, rm); + }var removeHook = (_b = (_a = ch === null || ch === void 0 ? void 0 : ch.data) === null || _a === void 0 ? void 0 : _a.hook) === null || _b === void 0 ? void 0 : _b.remove; + if (isDef(removeHook)) { + removeHook(ch, rm); + } else { + rm(); + } + } else { + // Text node + api.removeChild(parentElm, ch.elm); + } + } + } + } + function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) { + var oldStartIdx = 0; + var newStartIdx = 0; + var oldEndIdx = oldCh.length - 1; + var oldStartVnode = oldCh[0]; + var oldEndVnode = oldCh[oldEndIdx]; + var newEndIdx = newCh.length - 1; + var newStartVnode = newCh[0]; + var newEndVnode = newCh[newEndIdx]; + var oldKeyToIdx = void 0; + var idxInOld = void 0; + var elmToMove = void 0; + var before = void 0; + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { + if (oldStartVnode == null) { + oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left + } else if (oldEndVnode == null) { + oldEndVnode = oldCh[--oldEndIdx]; + } else if (newStartVnode == null) { + newStartVnode = newCh[++newStartIdx]; + } else if (newEndVnode == null) { + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldStartVnode, newStartVnode)) { + patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); + oldStartVnode = oldCh[++oldStartIdx]; + newStartVnode = newCh[++newStartIdx]; + } else if (sameVnode(oldEndVnode, newEndVnode)) { + patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); + oldEndVnode = oldCh[--oldEndIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldStartVnode, newEndVnode)) { + // Vnode moved right + patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); + api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm)); + oldStartVnode = oldCh[++oldStartIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldEndVnode, newStartVnode)) { + // Vnode moved left + patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); + api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); + oldEndVnode = oldCh[--oldEndIdx]; + newStartVnode = newCh[++newStartIdx]; + } else { + if (oldKeyToIdx === undefined) { + oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); + } + idxInOld = oldKeyToIdx[newStartVnode.key]; + if (isUndef(idxInOld)) { + // New element + api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); + } else { + elmToMove = oldCh[idxInOld]; + if (elmToMove.sel !== newStartVnode.sel) { + api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); + } else { + patchVnode(elmToMove, newStartVnode, insertedVnodeQueue); + oldCh[idxInOld] = undefined; + api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm); + } + } + newStartVnode = newCh[++newStartIdx]; + } + } + if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) { + if (oldStartIdx > oldEndIdx) { + before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm; + addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); + } else { + removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); + } + } + } + function patchVnode(oldVnode, vnode, insertedVnodeQueue) { + var _a, _b, _c, _d, _e; + var hook = (_a = vnode.data) === null || _a === void 0 ? void 0 : _a.hook; + (_b = hook === null || hook === void 0 ? void 0 : hook.prepatch) === null || _b === void 0 ? void 0 : _b.call(hook, oldVnode, vnode); + var elm = vnode.elm = oldVnode.elm; + var oldCh = oldVnode.children; + var ch = vnode.children; + if (oldVnode === vnode) return; + if (vnode.data !== undefined) { + for (var _i3 = 0; _i3 < cbs.update.length; ++_i3) { + cbs.update[_i3](oldVnode, vnode); + }(_d = (_c = vnode.data.hook) === null || _c === void 0 ? void 0 : _c.update) === null || _d === void 0 ? void 0 : _d.call(_c, oldVnode, vnode); + } + if (isUndef(vnode.text)) { + if (isDef(oldCh) && isDef(ch)) { + if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue); + } else if (isDef(ch)) { + if (isDef(oldVnode.text)) api.setTextContent(elm, ""); + addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); + } else if (isDef(oldCh)) { + removeVnodes(elm, oldCh, 0, oldCh.length - 1); + } else if (isDef(oldVnode.text)) { + api.setTextContent(elm, ""); + } + } else if (oldVnode.text !== vnode.text) { + if (isDef(oldCh)) { + removeVnodes(elm, oldCh, 0, oldCh.length - 1); + } + api.setTextContent(elm, vnode.text); + } + (_e = hook === null || hook === void 0 ? void 0 : hook.postpatch) === null || _e === void 0 ? void 0 : _e.call(hook, oldVnode, vnode); + } + return function patch(oldVnode, vnode) { + var i = void 0, + elm = void 0, + parent = void 0; + var insertedVnodeQueue = []; + for (i = 0; i < cbs.pre.length; ++i) { + cbs.pre[i](); + }if (!isVnode(oldVnode)) { + oldVnode = emptyNodeAt(oldVnode); + } + if (sameVnode(oldVnode, vnode)) { + patchVnode(oldVnode, vnode, insertedVnodeQueue); + } else { + elm = oldVnode.elm; + parent = api.parentNode(elm); + createElm(vnode, insertedVnodeQueue); + if (parent !== null) { + api.insertBefore(parent, vnode.elm, api.nextSibling(elm)); + removeVnodes(parent, [oldVnode], 0, 0); + } + } + for (i = 0; i < insertedVnodeQueue.length; ++i) { + insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]); + } + for (i = 0; i < cbs.post.length; ++i) { + cbs.post[i](); + }return vnode; + }; + } + + function addNS(data, children, sel) { + data.ns = "http://www.w3.org/2000/svg"; + if (sel !== "foreignObject" && children !== undefined) { + for (var i = 0; i < children.length; ++i) { + var childData = children[i].data; + if (childData !== undefined) { + addNS(childData, children[i].children, children[i].sel); + } + } + } + } + function h(sel, b, c) { + var data = {}; + var children = void 0; + var text = void 0; + var i = void 0; + if (c !== undefined) { + if (b !== null) { + data = b; + } + if (array(c)) { + children = c; + } else if (primitive(c)) { + text = c; + } else if (c && c.sel) { + children = [c]; + } + } else if (b !== undefined && b !== null) { + if (array(b)) { + children = b; + } else if (primitive(b)) { + text = b; + } else if (b && b.sel) { + children = [b]; + } else { + data = b; + } + } + if (children !== undefined) { + for (i = 0; i < children.length; ++i) { + if (primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined); + } + } + if (sel[0] === "s" && sel[1] === "v" && sel[2] === "g" && (sel.length === 3 || sel[3] === "." || sel[3] === "#")) { + addNS(data, children, sel); + } + return vnode(sel, data, children, text, undefined); + } + + function copyToThunk(vnode, thunk) { + vnode.data.fn = thunk.data.fn; + vnode.data.args = thunk.data.args; + thunk.data = vnode.data; + thunk.children = vnode.children; + thunk.text = vnode.text; + thunk.elm = vnode.elm; + } + function init$1(thunk) { + var cur = thunk.data; + var vnode = cur.fn.apply(undefined, cur.args); + copyToThunk(vnode, thunk); + } + function prepatch(oldVnode, thunk) { + var i = void 0; + var old = oldVnode.data; + var cur = thunk.data; + var oldArgs = old.args; + var args = cur.args; + if (old.fn !== cur.fn || oldArgs.length !== args.length) { + copyToThunk(cur.fn.apply(undefined, args), thunk); + return; + } + for (i = 0; i < args.length; ++i) { + if (oldArgs[i] !== args[i]) { + copyToThunk(cur.fn.apply(undefined, args), thunk); + return; + } + } + copyToThunk(oldVnode, thunk); + } + var thunk = function thunk(sel, key, fn, args) { + if (args === undefined) { + args = fn; + fn = key; + key = undefined; + } + return h(sel, { + key: key, + hook: { init: init$1, prepatch: prepatch }, + fn: fn, + args: args + }); + }; + + function pre(vnode, newVnode) { + var attachData = vnode.data.attachData; + // Copy created placeholder and real element from old vnode + newVnode.data.attachData.placeholder = attachData.placeholder; + newVnode.data.attachData.real = attachData.real; + // Mount real element in vnode so the patch process operates on it + vnode.elm = vnode.data.attachData.real; + } + function post(_, vnode) { + // Mount dummy placeholder in vnode so potential reorders use it + vnode.elm = vnode.data.attachData.placeholder; + } + function destroy(vnode) { + // Remove placeholder + if (vnode.elm !== undefined) { + vnode.elm.parentNode.removeChild(vnode.elm); + } + // Remove real element from where it was inserted + vnode.elm = vnode.data.attachData.real; + } + function create(_, vnode) { + var real = vnode.elm; + var attachData = vnode.data.attachData; + var placeholder = document.createElement("span"); + // Replace actual element with dummy placeholder + // Snabbdom will then insert placeholder instead + vnode.elm = placeholder; + attachData.target.appendChild(real); + attachData.real = real; + attachData.placeholder = placeholder; + } + function attachTo(target, vnode) { + if (vnode.data === undefined) vnode.data = {}; + if (vnode.data.hook === undefined) vnode.data.hook = {}; + var data = vnode.data; + var hook = vnode.data.hook; + data.attachData = { target: target, placeholder: undefined, real: undefined }; + hook.create = create; + hook.prepatch = pre; + hook.postpatch = post; + hook.destroy = destroy; + return vnode; + } + + function toVNode(node, domApi) { + var api = domApi !== undefined ? domApi : htmlDomApi; + var text = void 0; + if (api.isElement(node)) { + var id = node.id ? "#" + node.id : ""; + var cn = node.getAttribute("class"); + var c = cn ? "." + cn.split(" ").join(".") : ""; + var sel = api.tagName(node).toLowerCase() + id + c; + var attrs = {}; + var children = []; + var name = void 0; + var i = void 0, + n = void 0; + var elmAttrs = node.attributes; + var elmChildren = node.childNodes; + for (i = 0, n = elmAttrs.length; i < n; i++) { + name = elmAttrs[i].nodeName; + if (name !== "id" && name !== "class") { + attrs[name] = elmAttrs[i].nodeValue; + } + } + for (i = 0, n = elmChildren.length; i < n; i++) { + children.push(toVNode(elmChildren[i], domApi)); + } + return vnode(sel, { attrs: attrs }, children, undefined, node); + } else if (api.isText(node)) { + text = api.getTextContent(node); + return vnode(undefined, undefined, undefined, text, node); + } else if (api.isComment(node)) { + text = api.getTextContent(node); + return vnode("!", {}, [], text, node); + } else { + return vnode("", {}, [], undefined, node); + } + } + + var xlinkNS = "http://www.w3.org/1999/xlink"; + var xmlNS = "http://www.w3.org/XML/1998/namespace"; + var colonChar = 58; + var xChar = 120; + function updateAttrs(oldVnode, vnode) { + var key = void 0; + var elm = vnode.elm; + var oldAttrs = oldVnode.data.attrs; + var attrs = vnode.data.attrs; + if (!oldAttrs && !attrs) return; + if (oldAttrs === attrs) return; + oldAttrs = oldAttrs || {}; + attrs = attrs || {}; + // update modified attributes, add new attributes + for (key in attrs) { + var cur = attrs[key]; + var old = oldAttrs[key]; + if (old !== cur) { + if (cur === true) { + elm.setAttribute(key, ""); + } else if (cur === false) { + elm.removeAttribute(key); + } else { + if (key.charCodeAt(0) !== xChar) { + elm.setAttribute(key, cur); + } else if (key.charCodeAt(3) === colonChar) { + // Assume xml namespace + elm.setAttributeNS(xmlNS, key, cur); + } else if (key.charCodeAt(5) === colonChar) { + // Assume xlink namespace + elm.setAttributeNS(xlinkNS, key, cur); + } else { + elm.setAttribute(key, cur); + } + } + } + } + // remove removed attributes + // use `in` operator since the previous `for` iteration uses it (.i.e. add even attributes with undefined value) + // the other option is to remove all attributes with value == undefined + for (key in oldAttrs) { + if (!(key in attrs)) { + elm.removeAttribute(key); + } + } + } + var attributesModule = { create: updateAttrs, update: updateAttrs }; + + function updateClass(oldVnode, vnode) { + var cur = void 0; + var name = void 0; + var elm = vnode.elm; + var oldClass = oldVnode.data["class"]; + var klass = vnode.data["class"]; + if (!oldClass && !klass) return; + if (oldClass === klass) return; + oldClass = oldClass || {}; + klass = klass || {}; + for (name in oldClass) { + if (oldClass[name] && !Object.prototype.hasOwnProperty.call(klass, name)) { + // was `true` and now not provided + elm.classList.remove(name); + } + } + for (name in klass) { + cur = klass[name]; + if (cur !== oldClass[name]) { + elm.classList[cur ? "add" : "remove"](name); + } + } + } + var classModule = { create: updateClass, update: updateClass }; + + var CAPS_REGEX = /[A-Z]/g; + function updateDataset(oldVnode, vnode) { + var elm = vnode.elm; + var oldDataset = oldVnode.data.dataset; + var dataset = vnode.data.dataset; + var key = void 0; + if (!oldDataset && !dataset) return; + if (oldDataset === dataset) return; + oldDataset = oldDataset || {}; + dataset = dataset || {}; + var d = elm.dataset; + for (key in oldDataset) { + if (!dataset[key]) { + if (d) { + if (key in d) { + delete d[key]; + } + } else { + elm.removeAttribute("data-" + key.replace(CAPS_REGEX, "-$&").toLowerCase()); + } + } + } + for (key in dataset) { + if (oldDataset[key] !== dataset[key]) { + if (d) { + d[key] = dataset[key]; + } else { + elm.setAttribute("data-" + key.replace(CAPS_REGEX, "-$&").toLowerCase(), dataset[key]); + } + } + } + } + var datasetModule = { create: updateDataset, update: updateDataset }; + + function invokeHandler(handler, vnode, event) { + if (typeof handler === "function") { + // call function handler + handler.call(vnode, event, vnode); + } else if (typeof handler === "object") { + // call multiple handlers + for (var i = 0; i < handler.length; i++) { + invokeHandler(handler[i], vnode, event); + } + } + } + function handleEvent(event, vnode) { + var name = event.type; + var on = vnode.data.on; + // call event handler(s) if exists + if (on && on[name]) { + invokeHandler(on[name], vnode, event); + } + } + function createListener() { + return function handler(event) { + handleEvent(event, handler.vnode); + }; + } + function updateEventListeners(oldVnode, vnode) { + var oldOn = oldVnode.data.on; + var oldListener = oldVnode.listener; + var oldElm = oldVnode.elm; + var on = vnode && vnode.data.on; + var elm = vnode && vnode.elm; + var name = void 0; + // optimization for reused immutable handlers + if (oldOn === on) { + return; + } + // remove existing listeners which no longer used + if (oldOn && oldListener) { + // if element changed or deleted we remove all existing listeners unconditionally + if (!on) { + for (name in oldOn) { + // remove listener if element was changed or existing listeners removed + oldElm.removeEventListener(name, oldListener, false); + } + } else { + for (name in oldOn) { + // remove listener if existing listener removed + if (!on[name]) { + oldElm.removeEventListener(name, oldListener, false); + } + } + } + } + // add new listeners which has not already attached + if (on) { + // reuse existing listener or create new + var listener = vnode.listener = oldVnode.listener || createListener(); + // update vnode for listener + listener.vnode = vnode; + // if element changed or added we add all needed listeners unconditionally + if (!oldOn) { + for (name in on) { + // add listener if element was changed or new listeners added + elm.addEventListener(name, listener, false); + } + } else { + for (name in on) { + // add listener if new listener added + if (!oldOn[name]) { + elm.addEventListener(name, listener, false); + } + } + } + } + } + var eventListenersModule = { + create: updateEventListeners, + update: updateEventListeners, + destroy: updateEventListeners + }; + + var raf = typeof window !== "undefined" && window.requestAnimationFrame || setTimeout; + var nextFrame = function nextFrame(fn) { + raf(function () { + raf(fn); + }); + }; + function setNextFrame(obj, prop, val) { + nextFrame(function () { + obj[prop] = val; + }); + } + function getTextNodeRect(textNode) { + var rect = void 0; + if (document.createRange) { + var range = document.createRange(); + range.selectNodeContents(textNode); + if (range.getBoundingClientRect) { + rect = range.getBoundingClientRect(); + } + } + return rect; + } + function calcTransformOrigin(isTextNode, textRect, boundingRect) { + if (isTextNode) { + if (textRect) { + // calculate pixels to center of text from left edge of bounding box + var relativeCenterX = textRect.left + textRect.width / 2 - boundingRect.left; + var relativeCenterY = textRect.top + textRect.height / 2 - boundingRect.top; + return relativeCenterX + "px " + relativeCenterY + "px"; + } + } + return "0 0"; // top left + } + function getTextDx(oldTextRect, newTextRect) { + if (oldTextRect && newTextRect) { + return oldTextRect.left + oldTextRect.width / 2 - (newTextRect.left + newTextRect.width / 2); + } + return 0; + } + function getTextDy(oldTextRect, newTextRect) { + if (oldTextRect && newTextRect) { + return oldTextRect.top + oldTextRect.height / 2 - (newTextRect.top + newTextRect.height / 2); + } + return 0; + } + function isTextElement(elm) { + return elm.childNodes.length === 1 && elm.childNodes[0].nodeType === 3; + } + var removed = void 0; + var created = void 0; + function pre$1() { + removed = {}; + created = []; + } + function create$1(oldVnode, vnode) { + var hero = vnode.data.hero; + if (hero && hero.id) { + created.push(hero.id); + created.push(vnode); + } + } + function destroy$1(vnode) { + var hero = vnode.data.hero; + if (hero && hero.id) { + var elm = vnode.elm; + vnode.isTextNode = isTextElement(elm); // is this a text node? + vnode.boundingRect = elm.getBoundingClientRect(); // save the bounding rectangle to a new property on the vnode + vnode.textRect = vnode.isTextNode ? getTextNodeRect(elm.childNodes[0]) : null; // save bounding rect of inner text node + var computedStyle = window.getComputedStyle(elm, undefined); // get current styles (includes inherited properties) + vnode.savedStyle = JSON.parse(JSON.stringify(computedStyle)); // save a copy of computed style values + removed[hero.id] = vnode; + } + } + function post$1() { + var i = void 0, + id = void 0, + newElm = void 0, + oldVnode = void 0, + oldElm = void 0, + hRatio = void 0, + wRatio = void 0, + oldRect = void 0, + newRect = void 0, + dx = void 0, + dy = void 0, + origTransform = void 0, + origTransition = void 0, + newStyle = void 0, + oldStyle = void 0, + newComputedStyle = void 0, + isTextNode = void 0, + newTextRect = void 0, + oldTextRect = void 0; + for (i = 0; i < created.length; i += 2) { + id = created[i]; + newElm = created[i + 1].elm; + oldVnode = removed[id]; + if (oldVnode) { + isTextNode = oldVnode.isTextNode && isTextElement(newElm); // Are old & new both text? + newStyle = newElm.style; + newComputedStyle = window.getComputedStyle(newElm, undefined); // get full computed style for new element + oldElm = oldVnode.elm; + oldStyle = oldElm.style; + // Overall element bounding boxes + newRect = newElm.getBoundingClientRect(); + oldRect = oldVnode.boundingRect; // previously saved bounding rect + // Text node bounding boxes & distances + if (isTextNode) { + newTextRect = getTextNodeRect(newElm.childNodes[0]); + oldTextRect = oldVnode.textRect; + dx = getTextDx(oldTextRect, newTextRect); + dy = getTextDy(oldTextRect, newTextRect); + } else { + // Calculate distances between old & new positions + dx = oldRect.left - newRect.left; + dy = oldRect.top - newRect.top; + } + hRatio = newRect.height / Math.max(oldRect.height, 1); + wRatio = isTextNode ? hRatio : newRect.width / Math.max(oldRect.width, 1); // text scales based on hRatio + // Animate new element + origTransform = newStyle.transform; + origTransition = newStyle.transition; + if (newComputedStyle.display === "inline") { + // inline elements cannot be transformed + newStyle.display = "inline-block"; // this does not appear to have any negative side effects + } + newStyle.transition = origTransition + "transform 0s"; + newStyle.transformOrigin = calcTransformOrigin(isTextNode, newTextRect, newRect); + newStyle.opacity = "0"; + newStyle.transform = origTransform + "translate(" + dx + "px, " + dy + "px) " + "scale(" + 1 / wRatio + ", " + 1 / hRatio + ")"; + setNextFrame(newStyle, "transition", origTransition); + setNextFrame(newStyle, "transform", origTransform); + setNextFrame(newStyle, "opacity", "1"); + // Animate old element + for (var key in oldVnode.savedStyle) { + // re-apply saved inherited properties + if (String(parseInt(key)) !== key) { + var ms = key.substring(0, 2) === "ms"; + var moz = key.substring(0, 3) === "moz"; + var webkit = key.substring(0, 6) === "webkit"; + if (!ms && !moz && !webkit) { + // ignore prefixed style properties + oldStyle[key] = oldVnode.savedStyle[key]; + } + } + } + oldStyle.position = "absolute"; + oldStyle.top = oldRect.top + "px"; // start at existing position + oldStyle.left = oldRect.left + "px"; + oldStyle.width = oldRect.width + "px"; // Needed for elements who were sized relative to their parents + oldStyle.height = oldRect.height + "px"; // Needed for elements who were sized relative to their parents + oldStyle.margin = "0"; // Margin on hero element leads to incorrect positioning + oldStyle.transformOrigin = calcTransformOrigin(isTextNode, oldTextRect, oldRect); + oldStyle.transform = ""; + oldStyle.opacity = "1"; + document.body.appendChild(oldElm); + setNextFrame(oldStyle, "transform", "translate(" + -dx + "px, " + -dy + "px) scale(" + wRatio + ", " + hRatio + ")"); // scale must be on far right for translate to be correct + setNextFrame(oldStyle, "opacity", "0"); + oldElm.addEventListener("transitionend", function (ev) { + if (ev.propertyName === "transform") { + document.body.removeChild(ev.target); + } + }); + } + } + removed = created = undefined; + } + var heroModule = { + pre: pre$1, + create: create$1, + destroy: destroy$1, + post: post$1 + }; + + function updateProps(oldVnode, vnode) { + var key = void 0; + var cur = void 0; + var old = void 0; + var elm = vnode.elm; + var oldProps = oldVnode.data.props; + var props = vnode.data.props; + if (!oldProps && !props) return; + if (oldProps === props) return; + oldProps = oldProps || {}; + props = props || {}; + for (key in props) { + cur = props[key]; + old = oldProps[key]; + if (old !== cur && (key !== "value" || elm[key] !== cur)) { + elm[key] = cur; + } + } + } + var propsModule = { create: updateProps, update: updateProps }; + + // Bindig `requestAnimationFrame` like this fixes a bug in IE/Edge. See #360 and #409. + var raf$1 = typeof window !== "undefined" && window.requestAnimationFrame.bind(window) || setTimeout; + var nextFrame$1 = function nextFrame$1(fn) { + raf$1(function () { + raf$1(fn); + }); + }; + var reflowForced = false; + function setNextFrame$1(obj, prop, val) { + nextFrame$1(function () { + obj[prop] = val; + }); + } + function updateStyle(oldVnode, vnode) { + var cur = void 0; + var name = void 0; + var elm = vnode.elm; + var oldStyle = oldVnode.data.style; + var style = vnode.data.style; + if (!oldStyle && !style) return; + if (oldStyle === style) return; + oldStyle = oldStyle || {}; + style = style || {}; + var oldHasDel = "delayed" in oldStyle; + for (name in oldStyle) { + if (!style[name]) { + if (name[0] === "-" && name[1] === "-") { + elm.style.removeProperty(name); + } else { + elm.style[name] = ""; + } + } + } + for (name in style) { + cur = style[name]; + if (name === "delayed" && style.delayed) { + for (var name2 in style.delayed) { + cur = style.delayed[name2]; + if (!oldHasDel || cur !== oldStyle.delayed[name2]) { + setNextFrame$1(elm.style, name2, cur); + } + } + } else if (name !== "remove" && cur !== oldStyle[name]) { + if (name[0] === "-" && name[1] === "-") { + elm.style.setProperty(name, cur); + } else { + elm.style[name] = cur; + } + } + } + } + function applyDestroyStyle(vnode) { + var style = void 0; + var name = void 0; + var elm = vnode.elm; + var s = vnode.data.style; + if (!s || !(style = s.destroy)) return; + for (name in style) { + elm.style[name] = style[name]; + } + } + function applyRemoveStyle(vnode, rm) { + var s = vnode.data.style; + if (!s || !s.remove) { + rm(); + return; + } + if (!reflowForced) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + vnode.elm.offsetLeft; + reflowForced = true; + } + var name = void 0; + var elm = vnode.elm; + var i = 0; + var style = s.remove; + var amount = 0; + var applied = []; + for (name in style) { + applied.push(name); + elm.style[name] = style[name]; + } + var compStyle = getComputedStyle(elm); + var props = compStyle["transition-property"].split(", "); + for (; i < props.length; ++i) { + if (applied.indexOf(props[i]) !== -1) amount++; + } + elm.addEventListener("transitionend", function (ev) { + if (ev.target === elm) --amount; + if (amount === 0) rm(); + }); + } + function forceReflow() { + reflowForced = false; + } + var styleModule = { + pre: forceReflow, + create: updateStyle, + update: updateStyle, + destroy: applyDestroyStyle, + remove: applyRemoveStyle + }; + + exports.array = array; + exports.attachTo = attachTo; + exports.attributesModule = attributesModule; + exports.classModule = classModule; + exports.datasetModule = datasetModule; + exports.eventListenersModule = eventListenersModule; + exports.h = h; + exports.heroModule = heroModule; + exports.htmlDomApi = htmlDomApi; + exports.init = init; + exports.primitive = primitive; + exports.propsModule = propsModule; + exports.styleModule = styleModule; + exports.thunk = thunk; + exports.toVNode = toVNode; + exports.vnode = vnode; + + exports.__esModule = true; +}); diff --git a/src/core/element2Snabbdom.js b/src/core/element2Snabbdom.js new file mode 100644 index 000000000..cb8d347bb --- /dev/null +++ b/src/core/element2Snabbdom.js @@ -0,0 +1,42 @@ +!function () { + var patch = BI.Snabbdom.init([BI.Snabbdom.attributesModule, BI.Snabbdom.classModule, BI.Snabbdom.datasetModule, BI.Snabbdom.propsModule, BI.Snabbdom.styleModule, BI.Snabbdom.eventListenersModule]); + BI.Element2Snabbdom = function (parentNode) { + if (parentNode.nodeType === 3) { + return parentNode.textContent; + } + var data = BI.jQuery._data(parentNode); + var on = {}; + BI.each(data && data.events, function (eventName, events) { + on[eventName] = function () { + var ob = this, args = arguments; + BI.each(events, function (i, ev) { + ev.handler.apply(ob, args); + }); + }; + }); + var style = parentNode.getAttribute("style"); + // var claz = parentNode.getAttribute("class"); + var vnode = BI.Snabbdom.h(parentNode.nodeName, { + class: BI.makeObject(parentNode.classList), + props: { + style: style + }, + on: on, + hook: { + create: function () { + BI.each(parentNode._Widget, function (i, w) { + w.element = $(vnode.elm); + }); + } + } + }, BI.map(parentNode.childNodes, function (i, childNode) { + return BI.Element2Snabbdom(childNode); + })); + return vnode; + }; + + BI.patchVNode = function (element, node) { + patch(element, node); + }; +}(); +