ng-event-options
Version:
Enable event options (capture, passive, ...) inside angular templates, based on browser support
334 lines (319 loc) • 13.5 kB
JavaScript
import * as i0 from '@angular/core';
import { NgZone, PLATFORM_ID, Injectable, Inject, NgModule } from '@angular/core';
import { EVENT_MANAGER_PLUGINS } from '@angular/platform-browser';
import { isPlatformBrowser, DOCUMENT } from '@angular/common';
var ErrorMsg;
(function (ErrorMsg) {
ErrorMsg["PassivePreventDefault"] = "EventOptions: You cannot use 'passive (p)' and 'preventDefault (d)' simultaneously";
ErrorMsg["UnsupportedEventTarget"] = "Unsupported event target |~ for event |~";
ErrorMsg["UnsupportedOperator"] = "Unsupported operator |~";
})(ErrorMsg || (ErrorMsg = {}));
var EventOption;
(function (EventOption) {
EventOption[EventOption["Capture"] = 1] = "Capture";
EventOption[EventOption["Passive"] = 2] = "Passive";
EventOption[EventOption["Once"] = 4] = "Once";
EventOption[EventOption["NoZone"] = 8] = "NoZone";
EventOption[EventOption["Stop"] = 16] = "Stop";
EventOption[EventOption["PreventDefault"] = 32] = "PreventDefault";
EventOption[EventOption["InBrowser"] = 64] = "InBrowser";
})(EventOption || (EventOption = {}));
var GlobalEventTarget;
(function (GlobalEventTarget) {
GlobalEventTarget["Window"] = "window";
GlobalEventTarget["Document"] = "document";
GlobalEventTarget["Body"] = "body";
})(GlobalEventTarget || (GlobalEventTarget = {}));
var NativeEventOption;
(function (NativeEventOption) {
NativeEventOption["Capture"] = "capture";
NativeEventOption["Passive"] = "passive";
NativeEventOption["Once"] = "once";
})(NativeEventOption || (NativeEventOption = {}));
var OptionSymbol;
(function (OptionSymbol) {
OptionSymbol["Capture"] = "c";
OptionSymbol["NoZone"] = "n";
OptionSymbol["Passive"] = "p";
OptionSymbol["Stop"] = "s";
OptionSymbol["Once"] = "o";
OptionSymbol["PreventDefault"] = "d";
OptionSymbol["InBrowser"] = "b";
OptionSymbol["ForceSymbol"] = "*";
})(OptionSymbol || (OptionSymbol = {}));
const getBitValue = (...values) => {
const len = values.length;
let val = 0;
for (let i = 0; i < len; i++) {
val = val | values[i];
}
return val;
};
var OperatorSymbol;
(function (OperatorSymbol) {
OperatorSymbol["Debounce"] = "db";
OperatorSymbol["Throttle"] = "th";
})(OperatorSymbol || (OperatorSymbol = {}));
const throttleEvent = (callback, time = 50, immediate = 0) => {
let timeout;
return (event) => {
if (!timeout) {
if (immediate) {
callback(event);
}
timeout = window.setTimeout(() => {
timeout = 0;
return !immediate ? callback(event) : void 0;
}, time);
}
};
};
const debounceEvent = (callback, time = 50, immediate = 0) => {
let timeout;
let wait;
return (event) => {
window.clearTimeout(timeout);
timeout = window.setTimeout(() => (immediate ? (wait = false) : callback(event)), time);
if (immediate && !wait) {
wait = true;
callback(event);
}
};
};
// EventManagerPlugin is not yet part of the public API of Angular, once it is I can remove the `addGlobalEventListener`
class DomEventOptionsPlugin /*extends EventManagerPlugin*/ {
constructor(ngZone, doc, platformId) {
this.ngZone = ngZone;
this.doc = doc;
this.platformId = platformId;
this.nativeOptionsObjects = {};
this.nativeOptionsSupported = {
capture: false,
once: false,
passive: false,
};
this.keyEvents = ['keydown', 'keypress', 'keyup'];
this.blockSeparator = '|';
this.operatorSeparator = ',';
this.optionSeparator = '.';
this.optionSymbols = [];
this.operatorSymbols = [];
this.setSymbols();
this.checkSupport();
}
addEventListener(element, eventName, listener) {
const { type, options, operators } = this.getTypeOptions(eventName);
const inBrowser = options.indexOf(OptionSymbol.InBrowser) > -1 ? EventOption.InBrowser : 0;
if (inBrowser && !isPlatformBrowser(this.platformId)) {
return () => void 0;
}
if (typeof listener !== 'function') {
listener = () => void 0;
}
const passive = options.indexOf(OptionSymbol.Passive) > -1 ? EventOption.Passive : 0;
const preventDefault = options.indexOf(OptionSymbol.PreventDefault) > -1 ? EventOption.PreventDefault : 0;
if (passive && preventDefault) {
throw new Error(ErrorMsg.PassivePreventDefault);
}
const stop = options.indexOf(OptionSymbol.Stop) > -1 ? EventOption.Stop : 0;
const once = options.indexOf(OptionSymbol.Once) > -1 ? EventOption.Once : 0;
const noZone = options.indexOf(OptionSymbol.NoZone) > -1 ? EventOption.NoZone : 0;
const capture = options.indexOf(OptionSymbol.Capture) > -1 ? EventOption.Capture : 0;
const operatorSettings = this.parseOperators(operators);
const debounceParams = operatorSettings[OperatorSymbol.Debounce];
const throttleParams = operatorSettings[OperatorSymbol.Throttle];
const bitVal = getBitValue(capture, once, passive);
const eventOptionsObj = this.getEventOptionsObject(bitVal);
const inZone = NgZone.isInAngularZone();
const callback = (event) => {
if (noZone || !inZone) {
listener(event);
}
else {
this.ngZone.run(() => listener(event));
}
};
let debounceCallback;
let throttleCallback;
if (debounceParams) {
debounceCallback = debounceEvent(callback, ...debounceParams.map((p) => parseInt(p, 10)));
}
if (throttleParams) {
throttleCallback = throttleEvent(callback, ...throttleParams.map((p) => parseInt(p, 10)));
}
const intermediateListener = (event) => {
if (stop) {
event.stopPropagation();
event.stopImmediatePropagation();
}
if (preventDefault) {
event.preventDefault();
}
if (once && !this.nativeOptionsSupported[NativeEventOption.Once]) {
element.removeEventListener(type, intermediateListener, eventOptionsObj);
}
if (debounceCallback) {
debounceCallback(event);
}
else if (throttleCallback) {
throttleCallback(event);
}
else {
callback(event);
}
};
if (inZone) {
this.ngZone.runOutsideAngular(() => element.addEventListener(type, intermediateListener, eventOptionsObj));
}
else {
element.addEventListener(type, intermediateListener, eventOptionsObj);
}
return () => this.ngZone.runOutsideAngular(() => element.removeEventListener(type, intermediateListener, eventOptionsObj));
}
addGlobalEventListener(element, eventName, listener) {
if (!isPlatformBrowser(this.platformId)) {
return () => void 0;
}
let target;
if (element === GlobalEventTarget.Window) {
target = window;
}
else if (element === GlobalEventTarget.Document) {
target = this.doc;
}
else if (element === GlobalEventTarget.Body && this.doc) {
target = this.doc.body;
}
else {
const replace = [element, eventName];
throw new Error(ErrorMsg.UnsupportedEventTarget.replace(/\|~/g, () => replace.shift()));
}
return this.addEventListener(target, eventName, listener);
}
supports(eventName) {
const { type, options } = this.getTypeOptions(eventName);
if (!type) {
return false;
}
if (options.length === 1 && this.keyEvents.indexOf(type) > -1) {
return false;
}
const chosenOptions = options.split('');
return chosenOptions.every((option, index) => this.optionSymbols.indexOf(option) !== -1 && index === chosenOptions.lastIndexOf(option));
}
checkSupport() {
const supportObj = new Object(null);
Object.keys(NativeEventOption)
.map((optionKey) => NativeEventOption[optionKey])
.forEach((nativeOption) => Object.defineProperty(supportObj, nativeOption, {
get: () => {
this.nativeOptionsSupported[nativeOption] = true;
},
}));
try {
window.addEventListener('test', new Function(), supportObj);
}
catch {
// empty
}
this.nativeEventObjectSupported = this.nativeOptionsSupported[NativeEventOption.Capture];
}
parseOperators(operatorsStr) {
const operators = {};
if (operatorsStr) {
operatorsStr.split(/},?/).forEach((operatorStr) => {
const parts = operatorStr.split('{');
if (parts.length === 2) {
const operator = parts[0];
if (operator && this.operatorSymbols.indexOf(operator) > -1) {
operators[operator] = parts[1].split(this.operatorSeparator).filter((p) => p);
}
else {
throw new Error(ErrorMsg.UnsupportedOperator.replace(/\|~/g, operator));
}
}
});
}
return operators;
}
getEventOptionsObject(options) {
if (!this.nativeEventObjectSupported) {
return (options & EventOption.Capture) === EventOption.Capture;
}
const eventOptions = (options & EventOption.Capture) +
(options & EventOption.Passive) +
(options & EventOption.Once);
if (eventOptions in this.nativeOptionsObjects) {
return this.nativeOptionsObjects[eventOptions];
}
const optionsObj = {
capture: !!(eventOptions & EventOption.Capture),
passive: !!(eventOptions & EventOption.Passive),
once: !!(eventOptions & EventOption.Once),
};
this.nativeOptionsObjects[eventOptions] = optionsObj;
return optionsObj;
}
getTypeOptions(eventName) {
let [type, options, operators] = eventName.split(this.optionSeparator);
if (!options || !type) {
return { type: '', options: '', operators: '' };
}
[options, operators] = options.split(this.blockSeparator);
if (!operators) {
operators = '';
}
type = type.trim();
options = options.trim();
operators = operators.trim();
return { type, options, operators };
}
setSymbols() {
this.optionSymbols.length = 0;
Object.keys(OptionSymbol).forEach((optionKey) => this.optionSymbols.push(OptionSymbol[optionKey]));
this.operatorSymbols.length = 0;
Object.keys(OperatorSymbol).forEach((operatorSymbol) => this.operatorSymbols.push(OperatorSymbol[operatorSymbol]));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: DomEventOptionsPlugin /*extends EventManagerPlugin*/, deps: [{ token: i0.NgZone }, { token: DOCUMENT }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: DomEventOptionsPlugin /*extends EventManagerPlugin*/ }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: DomEventOptionsPlugin /*extends EventManagerPlugin*/, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i0.NgZone }, { type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [PLATFORM_ID]
}] }] });
class NgEventOptionsModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: NgEventOptionsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.0.1", ngImport: i0, type: NgEventOptionsModule }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: NgEventOptionsModule, providers: [
{
provide: EVENT_MANAGER_PLUGINS,
useClass: DomEventOptionsPlugin,
multi: true,
},
] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: NgEventOptionsModule, decorators: [{
type: NgModule,
args: [{
providers: [
{
provide: EVENT_MANAGER_PLUGINS,
useClass: DomEventOptionsPlugin,
multi: true,
},
],
}]
}] });
/*
* Public API Surface of ng-event-options
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgEventOptionsModule };
//# sourceMappingURL=ng-event-options.mjs.map