UNPKG

@cycle/dom

Version:

The standard DOM Driver for Cycle.js, based on Snabbdom

138 lines 5.66 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var snabbdom_1 = require("snabbdom"); var xstream_1 = require("xstream"); var concat_1 = require("xstream/extra/concat"); var sampleCombine_1 = require("xstream/extra/sampleCombine"); var MainDOMSource_1 = require("./MainDOMSource"); var VNodeWrapper_1 = require("./VNodeWrapper"); var utils_1 = require("./utils"); var modules_1 = require("./modules"); var IsolateModule_1 = require("./IsolateModule"); var EventDelegator_1 = require("./EventDelegator"); function makeDOMDriverInputGuard(modules) { if (!Array.isArray(modules)) { throw new Error("Optional modules option must be an array for snabbdom modules"); } } function domDriverInputGuard(view$) { if (!view$ || typeof view$.addListener !== "function" || typeof view$.fold !== "function") { throw new Error("The DOM driver function expects as input a Stream of " + "virtual DOM elements"); } } function dropCompletion(input) { return xstream_1.default.merge(input, xstream_1.default.never()); } function unwrapElementFromVNode(vnode) { return vnode.elm; } function defaultReportSnabbdomError(err) { (console.error || console.log)(err); } function makeDOMReady$() { return xstream_1.default.create({ start: function (lis) { if (document.readyState === 'loading') { document.addEventListener('readystatechange', function () { var state = document.readyState; if (state === 'interactive' || state === 'complete') { lis.next(null); lis.complete(); } }); } else { lis.next(null); lis.complete(); } }, stop: function () { }, }); } function addRootScope(vnode) { vnode.data = vnode.data || {}; vnode.data.isolate = []; return vnode; } function makeDOMDriver(container, options) { if (options === void 0) { options = {}; } utils_1.checkValidContainer(container); var modules = options.modules || modules_1.default; makeDOMDriverInputGuard(modules); var isolateModule = new IsolateModule_1.IsolateModule(); var snabbdomOptions = options && options.snabbdomOptions || undefined; var patch = snabbdom_1.init([isolateModule.createModule()].concat(modules), undefined, snabbdomOptions); var domReady$ = makeDOMReady$(); var vnodeWrapper; var mutationObserver; var mutationConfirmed$ = xstream_1.default.create({ start: function (listener) { mutationObserver = new MutationObserver(function () { return listener.next(null); }); }, stop: function () { mutationObserver.disconnect(); }, }); function DOMDriver(vnode$, name) { if (name === void 0) { name = 'DOM'; } domDriverInputGuard(vnode$); var sanitation$ = xstream_1.default.create(); var firstRoot$ = domReady$.map(function () { var firstRoot = utils_1.getValidNode(container) || document.body; vnodeWrapper = new VNodeWrapper_1.VNodeWrapper(firstRoot); return firstRoot; }); // We need to subscribe to the sink (i.e. vnode$) synchronously inside this // driver, and not later in the map().flatten() because this sink is in // reality a SinkProxy from @cycle/run, and we don't want to miss the first // emission when the main() is connected to the drivers. // Read more in issue #739. var rememberedVNode$ = vnode$.remember(); rememberedVNode$.addListener({}); // The mutation observer internal to mutationConfirmed$ should // exist before elementAfterPatch$ calls mutationObserver.observe() mutationConfirmed$.addListener({}); var elementAfterPatch$ = firstRoot$ .map(function (firstRoot) { return xstream_1.default .merge(rememberedVNode$.endWhen(sanitation$), sanitation$) .map(function (vnode) { return vnodeWrapper.call(vnode); }) .startWith(addRootScope(snabbdom_1.toVNode(firstRoot))) .fold(patch, snabbdom_1.toVNode(firstRoot)) .drop(1) .map(unwrapElementFromVNode) .startWith(firstRoot) .map(function (el) { mutationObserver.observe(el, { childList: true, attributes: true, characterData: true, subtree: true, attributeOldValue: true, characterDataOldValue: true, }); return el; }) .compose(dropCompletion); } // don't complete this stream ) .flatten(); var rootElement$ = concat_1.default(domReady$, mutationConfirmed$) .endWhen(sanitation$) .compose(sampleCombine_1.default(elementAfterPatch$)) .map(function (arr) { return arr[1]; }) .remember(); // Start the snabbdom patching, over time rootElement$.addListener({ error: options.reportSnabbdomError || defaultReportSnabbdomError, }); var delegator = new EventDelegator_1.EventDelegator(rootElement$, isolateModule); return new MainDOMSource_1.MainDOMSource(rootElement$, sanitation$, [], isolateModule, delegator, name); } return DOMDriver; } exports.makeDOMDriver = makeDOMDriver; //# sourceMappingURL=makeDOMDriver.js.map