UNPKG

ng-event-options

Version:

Enable event options (capture, passive, ...) inside angular templates, based on browser support

334 lines (319 loc) 13.5 kB
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