@cycle/dom
Version:
The standard DOM Driver for Cycle.js, based on Snabbdom
136 lines • 5.35 kB
JavaScript
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