All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s
207 lines
6.2 KiB
JavaScript
207 lines
6.2 KiB
JavaScript
var CAN_USE_DOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
|
|
|
|
// Adapted from Modernizr
|
|
// https://github.com/Modernizr/Modernizr/blob/acb3f0d9/feature-detects/dom/passiveeventlisteners.js#L26-L37
|
|
function testPassiveEventListeners() {
|
|
if (!CAN_USE_DOM) {
|
|
return false;
|
|
}
|
|
|
|
if (!window.addEventListener || !window.removeEventListener || !Object.defineProperty) {
|
|
return false;
|
|
}
|
|
|
|
var supportsPassiveOption = false;
|
|
try {
|
|
var opts = Object.defineProperty({}, 'passive', {
|
|
// eslint-disable-next-line getter-return
|
|
get: function () {
|
|
function get() {
|
|
supportsPassiveOption = true;
|
|
}
|
|
|
|
return get;
|
|
}()
|
|
});
|
|
var noop = function noop() {};
|
|
window.addEventListener('testPassiveEventSupport', noop, opts);
|
|
window.removeEventListener('testPassiveEventSupport', noop, opts);
|
|
} catch (e) {
|
|
// do nothing
|
|
}
|
|
|
|
return supportsPassiveOption;
|
|
}
|
|
|
|
var memoized = void 0;
|
|
|
|
function canUsePassiveEventListeners() {
|
|
if (memoized === undefined) {
|
|
memoized = testPassiveEventListeners();
|
|
}
|
|
return memoized;
|
|
}
|
|
|
|
function normalizeEventOptions(eventOptions) {
|
|
if (!eventOptions) {
|
|
return undefined;
|
|
}
|
|
|
|
if (!canUsePassiveEventListeners()) {
|
|
// If the browser does not support the passive option, then it is expecting
|
|
// a boolean for the options argument to specify whether it should use
|
|
// capture or not. In more modern browsers, this is passed via the `capture`
|
|
// option, so let's just hoist that value up.
|
|
return !!eventOptions.capture;
|
|
}
|
|
|
|
return eventOptions;
|
|
}
|
|
|
|
/* eslint-disable no-bitwise */
|
|
|
|
/**
|
|
* Generate a unique key for any set of event options
|
|
*/
|
|
function eventOptionsKey(normalizedEventOptions) {
|
|
if (!normalizedEventOptions) {
|
|
return 0;
|
|
}
|
|
|
|
// If the browser does not support passive event listeners, the normalized
|
|
// event options will be a boolean.
|
|
if (normalizedEventOptions === true) {
|
|
return 100;
|
|
}
|
|
|
|
// At this point, the browser supports passive event listeners, so we expect
|
|
// the event options to be an object with possible properties of capture,
|
|
// passive, and once.
|
|
//
|
|
// We want to consistently return the same value, regardless of the order of
|
|
// these properties, so let's use binary maths to assign each property to a
|
|
// bit, and then add those together (with an offset to account for the
|
|
// booleans at the beginning of this function).
|
|
var capture = normalizedEventOptions.capture << 0;
|
|
var passive = normalizedEventOptions.passive << 1;
|
|
var once = normalizedEventOptions.once << 2;
|
|
return capture + passive + once;
|
|
}
|
|
|
|
function ensureCanMutateNextEventHandlers(eventHandlers) {
|
|
if (eventHandlers.handlers === eventHandlers.nextHandlers) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
eventHandlers.nextHandlers = eventHandlers.handlers.slice();
|
|
}
|
|
}
|
|
|
|
function TargetEventHandlers(target) {
|
|
this.target = target;
|
|
this.events = {};
|
|
}
|
|
|
|
TargetEventHandlers.prototype.getEventHandlers = function () {
|
|
function getEventHandlers(eventName, options) {
|
|
var key = String(eventName) + ' ' + String(eventOptionsKey(options));
|
|
|
|
if (!this.events[key]) {
|
|
this.events[key] = {
|
|
handlers: [],
|
|
handleEvent: undefined
|
|
};
|
|
this.events[key].nextHandlers = this.events[key].handlers;
|
|
}
|
|
|
|
return this.events[key];
|
|
}
|
|
|
|
return getEventHandlers;
|
|
}();
|
|
|
|
TargetEventHandlers.prototype.handleEvent = function () {
|
|
function handleEvent(eventName, options, event) {
|
|
var eventHandlers = this.getEventHandlers(eventName, options);
|
|
eventHandlers.handlers = eventHandlers.nextHandlers;
|
|
eventHandlers.handlers.forEach(function (handler) {
|
|
if (handler) {
|
|
// We need to check for presence here because a handler function may
|
|
// cause later handlers to get removed. This can happen if you for
|
|
// instance have a waypoint that unmounts another waypoint as part of an
|
|
// onEnter/onLeave handler.
|
|
handler(event);
|
|
}
|
|
});
|
|
}
|
|
|
|
return handleEvent;
|
|
}();
|
|
|
|
TargetEventHandlers.prototype.add = function () {
|
|
function add(eventName, listener, options) {
|
|
var _this = this;
|
|
|
|
// options has already been normalized at this point.
|
|
var eventHandlers = this.getEventHandlers(eventName, options);
|
|
|
|
ensureCanMutateNextEventHandlers(eventHandlers);
|
|
|
|
if (eventHandlers.nextHandlers.length === 0) {
|
|
eventHandlers.handleEvent = this.handleEvent.bind(this, eventName, options);
|
|
|
|
this.target.addEventListener(eventName, eventHandlers.handleEvent, options);
|
|
}
|
|
|
|
eventHandlers.nextHandlers.push(listener);
|
|
|
|
var isSubscribed = true;
|
|
var unsubscribe = function () {
|
|
function unsubscribe() {
|
|
if (!isSubscribed) {
|
|
return;
|
|
}
|
|
|
|
isSubscribed = false;
|
|
|
|
ensureCanMutateNextEventHandlers(eventHandlers);
|
|
var index = eventHandlers.nextHandlers.indexOf(listener);
|
|
eventHandlers.nextHandlers.splice(index, 1);
|
|
|
|
if (eventHandlers.nextHandlers.length === 0) {
|
|
// All event handlers have been removed, so we want to remove the event
|
|
// listener from the target node.
|
|
|
|
if (_this.target) {
|
|
// There can be a race condition where the target may no longer exist
|
|
// when this function is called, e.g. when a React component is
|
|
// unmounting. Guarding against this prevents the following error:
|
|
//
|
|
// Cannot read property 'removeEventListener' of undefined
|
|
_this.target.removeEventListener(eventName, eventHandlers.handleEvent, options);
|
|
}
|
|
|
|
eventHandlers.handleEvent = undefined;
|
|
}
|
|
}
|
|
|
|
return unsubscribe;
|
|
}();
|
|
return unsubscribe;
|
|
}
|
|
|
|
return add;
|
|
}();
|
|
|
|
var EVENT_HANDLERS_KEY = '__consolidated_events_handlers__';
|
|
|
|
// eslint-disable-next-line import/prefer-default-export
|
|
function addEventListener(target, eventName, listener, options) {
|
|
if (!target[EVENT_HANDLERS_KEY]) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
target[EVENT_HANDLERS_KEY] = new TargetEventHandlers(target);
|
|
}
|
|
var normalizedEventOptions = normalizeEventOptions(options);
|
|
return target[EVENT_HANDLERS_KEY].add(eventName, listener, normalizedEventOptions);
|
|
}
|
|
|
|
export { addEventListener };
|