@cycle/dom
Version:
The standard DOM Driver for Cycle.js, based on Snabbdom
372 lines • 15.4 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
var xstream_1 = require("xstream");
var ScopeChecker_1 = require("./ScopeChecker");
var utils_1 = require("./utils");
var ElementFinder_1 = require("./ElementFinder");
var SymbolTree_1 = require("./SymbolTree");
var PriorityQueue_1 = require("./PriorityQueue");
var fromEvent_1 = require("./fromEvent");
exports.eventTypesThatDontBubble = [
"blur",
"canplay",
"canplaythrough",
"durationchange",
"emptied",
"ended",
"focus",
"load",
"loadeddata",
"loadedmetadata",
"mouseenter",
"mouseleave",
"pause",
"play",
"playing",
"ratechange",
"reset",
"scroll",
"seeked",
"seeking",
"stalled",
"submit",
"suspend",
"timeupdate",
"unload",
"volumechange",
"waiting",
];
/**
* Manages "Event delegation", by connecting an origin with multiple
* destinations.
*
* Attaches a DOM event listener to the DOM element called the "origin",
* and delegates events to "destinations", which are subjects as outputs
* for the DOMSource. Simulates bubbling or capturing, with regards to
* isolation boundaries too.
*/
var EventDelegator = /** @class */ (function () {
function EventDelegator(rootElement$, isolateModule) {
var _this = this;
this.rootElement$ = rootElement$;
this.isolateModule = isolateModule;
this.virtualListeners = new SymbolTree_1.default(function (x) { return x.scope; });
this.nonBubblingListenersToAdd = new Set();
this.virtualNonBubblingListener = [];
this.isolateModule.setEventDelegator(this);
this.domListeners = new Map();
this.domListenersToAdd = new Map();
this.nonBubblingListeners = new Map();
rootElement$.addListener({
next: function (el) {
if (_this.origin !== el) {
_this.origin = el;
_this.resetEventListeners();
_this.domListenersToAdd.forEach(function (passive, type) {
return _this.setupDOMListener(type, passive);
});
_this.domListenersToAdd.clear();
}
_this.nonBubblingListenersToAdd.forEach(function (arr) {
_this.setupNonBubblingListener(arr);
});
},
});
}
EventDelegator.prototype.addEventListener = function (eventType, namespace, options, bubbles) {
var subject = xstream_1.default.never();
var dest;
var scopeChecker = new ScopeChecker_1.ScopeChecker(namespace, this.isolateModule);
var shouldBubble = bubbles === undefined
? exports.eventTypesThatDontBubble.indexOf(eventType) === -1
: bubbles;
if (shouldBubble) {
if (!this.domListeners.has(eventType)) {
this.setupDOMListener(eventType, !!options.passive);
}
dest = this.insertListener(subject, scopeChecker, eventType, options);
return subject;
}
else {
var setArray_1 = [];
this.nonBubblingListenersToAdd.forEach(function (v) { return setArray_1.push(v); });
var found = undefined, index = 0;
var length_1 = setArray_1.length;
var tester = function (x) {
var _sub = x[0], et = x[1], ef = x[2], _ = x[3];
return eventType === et && utils_1.isEqualNamespace(ef.namespace, namespace);
};
while (!found && index < length_1) {
var item = setArray_1[index];
found = tester(item) ? item : found;
index++;
}
var input_1 = found;
var nonBubbleSubject_1;
if (!input_1) {
var finder = new ElementFinder_1.ElementFinder(namespace, this.isolateModule);
dest = this.insertListener(subject, scopeChecker, eventType, options);
input_1 = [subject, eventType, finder, dest];
nonBubbleSubject_1 = subject;
this.nonBubblingListenersToAdd.add(input_1);
this.setupNonBubblingListener(input_1);
}
else {
var sub = input_1[0];
nonBubbleSubject_1 = sub;
}
var self_1 = this;
var subscription_1 = null;
return xstream_1.default.create({
start: function (listener) {
subscription_1 = nonBubbleSubject_1.subscribe(listener);
},
stop: function () {
var _s = input_1[0], et = input_1[1], ef = input_1[2], _d = input_1[3];
var elements = ef.call();
elements.forEach(function (element) {
var subs = element.subs;
if (subs && subs[et]) {
subs[et].unsubscribe();
delete subs[et];
}
});
self_1.nonBubblingListenersToAdd.delete(input_1);
subscription_1.unsubscribe();
}
});
}
};
EventDelegator.prototype.removeElement = function (element, namespace) {
if (namespace !== undefined) {
this.virtualListeners.delete(namespace);
}
var toRemove = [];
this.nonBubblingListeners.forEach(function (map, type) {
if (map.has(element)) {
toRemove.push([type, element]);
var subs_1 = element.subs;
if (subs_1) {
Object.keys(subs_1).forEach(function (key) {
subs_1[key].unsubscribe();
});
}
}
});
for (var i = 0; i < toRemove.length; i++) {
var map = this.nonBubblingListeners.get(toRemove[i][0]);
if (!map) {
continue;
}
map.delete(toRemove[i][1]);
if (map.size === 0) {
this.nonBubblingListeners.delete(toRemove[i][0]);
}
else {
this.nonBubblingListeners.set(toRemove[i][0], map);
}
}
};
EventDelegator.prototype.insertListener = function (subject, scopeChecker, eventType, options) {
var relevantSets = [];
var n = scopeChecker._namespace;
var max = n.length;
do {
relevantSets.push(this.getVirtualListeners(eventType, n, true, max));
max--;
} while (max >= 0 && n[max].type !== 'total');
var destination = __assign({}, options, { scopeChecker: scopeChecker,
subject: subject, bubbles: !!options.bubbles, useCapture: !!options.useCapture, passive: !!options.passive });
for (var i = 0; i < relevantSets.length; i++) {
relevantSets[i].add(destination, n.length);
}
return destination;
};
/**
* Returns a set of all virtual listeners in the scope of the namespace
* Set `exact` to true to treat sibiling isolated scopes as total scopes
*/
EventDelegator.prototype.getVirtualListeners = function (eventType, namespace, exact, max) {
if (exact === void 0) { exact = false; }
var _max = max !== undefined ? max : namespace.length;
if (!exact) {
for (var i = _max - 1; i >= 0; i--) {
if (namespace[i].type === 'total') {
_max = i + 1;
break;
}
_max = i;
}
}
var map = this.virtualListeners.getDefault(namespace, function () { return new Map(); }, _max);
if (!map.has(eventType)) {
map.set(eventType, new PriorityQueue_1.default());
}
return map.get(eventType);
};
EventDelegator.prototype.setupDOMListener = function (eventType, passive) {
var _this = this;
if (this.origin) {
var sub = fromEvent_1.fromEvent(this.origin, eventType, false, false, passive).subscribe({
next: function (event) { return _this.onEvent(eventType, event, passive); },
error: function () { },
complete: function () { },
});
this.domListeners.set(eventType, { sub: sub, passive: passive });
}
else {
this.domListenersToAdd.set(eventType, passive);
}
};
EventDelegator.prototype.setupNonBubblingListener = function (input) {
var _ = input[0], eventType = input[1], elementFinder = input[2], destination = input[3];
if (!this.origin) {
return;
}
var elements = elementFinder.call();
if (elements.length) {
var self_2 = this;
elements.forEach(function (element) {
var _a;
var subs = element.subs;
if (!subs || !subs[eventType]) {
var sub = fromEvent_1.fromEvent(element, eventType, false, false, destination.passive).subscribe({
next: function (ev) {
return self_2.onEvent(eventType, ev, !!destination.passive, false);
},
error: function () { },
complete: function () { },
});
if (!self_2.nonBubblingListeners.has(eventType)) {
self_2.nonBubblingListeners.set(eventType, new Map());
}
var map = self_2.nonBubblingListeners.get(eventType);
if (!map) {
return;
}
map.set(element, { sub: sub, destination: destination });
element.subs = __assign({}, subs, (_a = {}, _a[eventType] = sub, _a));
}
});
}
};
EventDelegator.prototype.resetEventListeners = function () {
var iter = this.domListeners.entries();
var curr = iter.next();
while (!curr.done) {
var _a = curr.value, type = _a[0], _b = _a[1], sub = _b.sub, passive = _b.passive;
sub.unsubscribe();
this.setupDOMListener(type, passive);
curr = iter.next();
}
};
EventDelegator.prototype.putNonBubblingListener = function (eventType, elm, useCapture, passive) {
var map = this.nonBubblingListeners.get(eventType);
if (!map) {
return;
}
var listener = map.get(elm);
if (listener &&
listener.destination.passive === passive &&
listener.destination.useCapture === useCapture) {
this.virtualNonBubblingListener[0] = listener.destination;
}
};
EventDelegator.prototype.onEvent = function (eventType, event, passive, bubbles) {
if (bubbles === void 0) { bubbles = true; }
var cycleEvent = this.patchEvent(event);
var rootElement = this.isolateModule.getRootElement(event.target);
if (bubbles) {
var namespace = this.isolateModule.getNamespace(event.target);
if (!namespace) {
return;
}
var listeners = this.getVirtualListeners(eventType, namespace);
this.bubble(eventType, event.target, rootElement, cycleEvent, listeners, namespace, namespace.length - 1, true, passive);
this.bubble(eventType, event.target, rootElement, cycleEvent, listeners, namespace, namespace.length - 1, false, passive);
}
else {
this.putNonBubblingListener(eventType, event.target, true, passive);
this.doBubbleStep(eventType, event.target, rootElement, cycleEvent, this.virtualNonBubblingListener, true, passive);
this.putNonBubblingListener(eventType, event.target, false, passive);
this.doBubbleStep(eventType, event.target, rootElement, cycleEvent, this.virtualNonBubblingListener, false, passive);
event.stopPropagation(); //fix reset event (spec'ed as non-bubbling, but bubbles in reality
}
};
EventDelegator.prototype.bubble = function (eventType, elm, rootElement, event, listeners, namespace, index, useCapture, passive) {
if (!useCapture && !event.propagationHasBeenStopped) {
this.doBubbleStep(eventType, elm, rootElement, event, listeners, useCapture, passive);
}
var newRoot = rootElement;
var newIndex = index;
if (elm === rootElement) {
if (index >= 0 && namespace[index].type === 'sibling') {
newRoot = this.isolateModule.getElement(namespace, index);
newIndex--;
}
else {
return;
}
}
if (elm.parentNode && newRoot) {
this.bubble(eventType, elm.parentNode, newRoot, event, listeners, namespace, newIndex, useCapture, passive);
}
if (useCapture && !event.propagationHasBeenStopped) {
this.doBubbleStep(eventType, elm, rootElement, event, listeners, useCapture, passive);
}
};
EventDelegator.prototype.doBubbleStep = function (eventType, elm, rootElement, event, listeners, useCapture, passive) {
if (!rootElement) {
return;
}
this.mutateEventCurrentTarget(event, elm);
listeners.forEach(function (dest) {
if (dest.passive === passive && dest.useCapture === useCapture) {
var sel = utils_1.getSelectors(dest.scopeChecker.namespace);
if (!event.propagationHasBeenStopped &&
dest.scopeChecker.isDirectlyInScope(elm) &&
((sel !== '' && elm.matches(sel)) ||
(sel === '' && elm === rootElement))) {
fromEvent_1.preventDefaultConditional(event, dest.preventDefault);
dest.subject.shamefullySendNext(event);
}
}
});
};
EventDelegator.prototype.patchEvent = function (event) {
var pEvent = event;
pEvent.propagationHasBeenStopped = false;
var oldStopPropagation = pEvent.stopPropagation;
pEvent.stopPropagation = function stopPropagation() {
oldStopPropagation.call(this);
this.propagationHasBeenStopped = true;
};
return pEvent;
};
EventDelegator.prototype.mutateEventCurrentTarget = function (event, currentTargetElement) {
try {
Object.defineProperty(event, "currentTarget", {
value: currentTargetElement,
configurable: true,
});
}
catch (err) {
console.log("please use event.ownerTarget");
}
event.ownerTarget = currentTargetElement;
};
return EventDelegator;
}());
exports.EventDelegator = EventDelegator;
//# sourceMappingURL=EventDelegator.js.map