panel
Version:
Web Components with Virtual DOM: lightweight composable web apps
135 lines (106 loc) • 3.86 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.EMPTY_DIV = exports.DOMPatcher = void 0;
Object.defineProperty(exports, "h", {
enumerable: true,
get: function () {
return _snabbdom.h;
}
});
var _snabbdom = require("snabbdom");
var _snabbdomDelayedClass = _interopRequireDefault(require("snabbdom-delayed-class"));
var _perf = require("./component-utils/perf");
var _snabbdomParamsModule = require("./component-utils/snabbdom-params-module");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Manages Virtual DOM -> DOM rendering cycle
* @module dom-patcher
* @private
*/
const patch = (0, _snabbdom.init)([_snabbdom.datasetModule, _snabbdom.attributesModule, _snabbdom.classModule, _snabbdom.propsModule, _snabbdom.styleModule, _snabbdom.eventListenersModule, _snabbdomDelayedClass.default, _snabbdomParamsModule.paramsModule]);
const EMPTY_DIV = (0, _snabbdom.h)(`div`);
exports.EMPTY_DIV = EMPTY_DIV;
class DOMPatcher {
constructor(initialState, renderFunc) {
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
this.updateMode = options.updateMode || `async`;
this.state = Object.assign({}, initialState);
this.renderFunc = renderFunc;
this.vnode = this.renderFunc(this.state);
this.postRenderCallback = options.postRenderCallback; // prepare root element
const tagName = this.vnode.sel.split(/[#.]/)[0];
const classMatches = this.vnode.sel.match(/\.[^.#]+/g);
const idMatch = this.vnode.sel.match(/#[^.#]+/);
this.el = document.createElement(tagName);
if (classMatches) {
this.el.className = classMatches.map(c => c.slice(1)).join(` `); // this attribute setting ensures that svg elements behave as expected and will ensure
// compatibility with different snabbdom versions
this.el.setAttribute(`class`, this.el.className);
}
if (idMatch) {
this.el.id = idMatch[0].slice(1);
}
patch(this.el, this.vnode);
if (this.el === this.vnode.elm) {
const insertHook = this.vnode.data.hook && this.vnode.data.hook.insert;
if (insertHook) {
// since Snabbdom recycled our newly-created root element (this.el) rather
// than creating its own element, it doesn't fire the insert hook, so we're
// going to fake it out and call it ourselves
insertHook(this.vnode);
}
}
this.el = this.vnode.elm;
}
update(newState) {
if (this.rendering) {
console.error(`Applying new DOM update while render is already in progress!`);
}
this.pendingState = newState;
switch (this.updateMode) {
case `async`:
if (!this.pending) {
this.pending = true;
requestAnimationFrame(() => this.render());
}
break;
case `sync`:
this.render();
break;
}
}
render() {
// if disconnected, don't render
if (!this.renderFunc) {
return;
}
const startedAt = _perf.Perf.getNow();
this.rendering = true;
this.pending = false;
this.state = this.pendingState;
const newVnode = this.renderFunc(this.state);
this.rendering = false;
patch(this.vnode, newVnode);
this.vnode = newVnode;
this.el = this.vnode.elm;
if (this.postRenderCallback) {
this.postRenderCallback(_perf.Perf.getNow() - startedAt);
}
}
disconnect() {
const vnode = this.vnode;
this.renderFunc = null;
this.state = null;
this.vnode = null;
this.el = null;
this.postRenderCallback = null; // patch with empty vnode to clear out listeners in tree
// this ensures we don't leave dangling DetachedHTMLElements blocking GC
patch(vnode, {
sel: vnode.sel,
key: vnode.key
});
}
}
exports.DOMPatcher = DOMPatcher;