UNPKG

@cycle/dom

Version:

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

136 lines 5.35 kB
import { init, toVNode } from 'snabbdom'; import xs from 'xstream'; import concat from 'xstream/extra/concat'; import sampleCombine from 'xstream/extra/sampleCombine'; import { MainDOMSource } from './MainDOMSource'; import { VNodeWrapper } from './VNodeWrapper'; import { getValidNode, checkValidContainer } from './utils'; import defaultModules from './modules'; import { IsolateModule } from './IsolateModule'; import { EventDelegator } from './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 xs.merge(input, xs.never()); } function unwrapElementFromVNode(vnode) { return vnode.elm; } function defaultReportSnabbdomError(err) { (console.error || console.log)(err); } function makeDOMReady$() { return xs.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 = {}; } checkValidContainer(container); var modules = options.modules || defaultModules; makeDOMDriverInputGuard(modules); var isolateModule = new IsolateModule(); var snabbdomOptions = options && options.snabbdomOptions || undefined; var patch = init([isolateModule.createModule()].concat(modules), undefined, snabbdomOptions); var domReady$ = makeDOMReady$(); var vnodeWrapper; var mutationObserver; var mutationConfirmed$ = xs.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$ = xs.create(); var firstRoot$ = domReady$.map(function () { var firstRoot = getValidNode(container) || document.body; vnodeWrapper = new 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 xs .merge(rememberedVNode$.endWhen(sanitation$), sanitation$) .map(function (vnode) { return vnodeWrapper.call(vnode); }) .startWith(addRootScope(toVNode(firstRoot))) .fold(patch, 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(domReady$, mutationConfirmed$) .endWhen(sanitation$) .compose(sampleCombine(elementAfterPatch$)) .map(function (arr) { return arr[1]; }) .remember(); // Start the snabbdom patching, over time rootElement$.addListener({ error: options.reportSnabbdomError || defaultReportSnabbdomError, }); var delegator = new EventDelegator(rootElement$, isolateModule); return new MainDOMSource(rootElement$, sanitation$, [], isolateModule, delegator, name); } return DOMDriver; } export { makeDOMDriver }; //# sourceMappingURL=makeDOMDriver.js.map