UNPKG

panel

Version:

Web Components with Virtual DOM: lightweight composable web apps

135 lines (106 loc) 3.86 kB
"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;