UNPKG

@cycle/dom

Version:

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

372 lines 15.4 kB
"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