UNPKG

@taiga-ui/event-plugins

Version:

This is a library for optimizing performance sensitive events and declarative preventDefault and stopPropagation

379 lines (359 loc) 14.9 kB
import { EventManagerPlugin, EVENT_MANAGER_PLUGINS } from '@angular/platform-browser'; import { DOCUMENT } from '@angular/common'; import * as i0 from '@angular/core'; import { inject, Injectable, InjectionToken } from '@angular/core'; class LongtapEvent extends CustomEvent { constructor(type, { clientX, clientY, ...eventInitDict }) { super(type, { ...eventInitDict, detail: { clientX, clientY } }); } } const isIos = ({ userAgent, maxTouchPoints }) => /ipad|iphone|ipod/i.test(userAgent) || (/^((?!chrome|android).)*safari/i.test(userAgent) && maxTouchPoints > 1); const TAP_DELAY = 700; const SAFE_NAVIGATOR = typeof navigator === 'undefined' ? null : navigator; const MOVE_THRESHOLD = 15; class LongtapEventPlugin extends EventManagerPlugin { constructor() { super(...arguments); this.isIOS = !!SAFE_NAVIGATOR && isIos(SAFE_NAVIGATOR); } addEventListener(element, _event, handler) { const removeLongtapEventPolyfill = this.isIOS ? this.listenTouchEvents(element) : this.listenContextmenuEvent(element); element.addEventListener('longtap', handler); return () => { removeLongtapEventPolyfill(); element.removeEventListener('longtap', handler); }; } supports(event) { return event === 'longtap'; } listenContextmenuEvent(element) { return this.manager.addEventListener(element, 'contextmenu.prevent.stop', ({ clientX, clientY }) => { this.dispatchLongtapEvent(element, clientX, clientY); }); } listenTouchEvents(element) { let longTapTimeout = null; let touchStartCoords = null; const reset = () => { clearTimeout(longTapTimeout); touchStartCoords = null; longTapTimeout = null; }; const removeTouchstartListener = this.manager.addEventListener(element, 'touchstart.zoneless.passive', ({ touches }) => { const touch = touches[0]; if (!touch) { return; } const { clientX, clientY } = touch; touchStartCoords = { clientX, clientY }; longTapTimeout = setTimeout(() => { this.dispatchLongtapEvent(element, clientX, clientY); reset(); }, TAP_DELAY); }); const removeTouchmoveListener = this.manager.addEventListener(element, 'touchmove.zoneless.passive', ({ touches }) => { const touch = touches[0]; if (!touch || !touchStartCoords) { return; } const { clientX, clientY } = touch; if (Math.hypot(clientX - touchStartCoords.clientX, clientY - touchStartCoords.clientY) <= MOVE_THRESHOLD) { return; } reset(); }); const removeTouchcancelListener = this.manager.addEventListener(element, 'touchcancel.zoneless.passive', reset); const removeTouchendListener = this.manager.addEventListener(element, 'touchend.zoneless.passive', reset); return () => { removeTouchstartListener(); removeTouchmoveListener(); removeTouchcancelListener(); removeTouchendListener(); }; } dispatchLongtapEvent(element, clientX, clientY) { element.dispatchEvent(new LongtapEvent('longtap', { clientX, clientY, bubbles: false, cancelable: false, composed: false, })); } } class TimedEventPlugin extends EventManagerPlugin { supports(event) { return this.regExp.test(event); } getDelay(event) { const match = this.regExp.exec(event); if (!match?.groups) { throw new Error(`Invalid event: ${event}`); } const { time, units } = match.groups; switch (units) { case 'ms': return Number(time); case 's': return Number(time) * 1000; default: throw new Error(`Invalid event: ${event}`); } } unwrap(event) { return event.replace(this.regExp, ''); } } class DebounceEventPlugin extends TimedEventPlugin { constructor() { super(...arguments); this.regExp = /\.debounce~(?<time>\d+)(?<units>ms|s)/; } addEventListener(element, eventName, handler) { let timeout; const unsubscribe = this.manager.addEventListener(element, this.unwrap(eventName), (event) => { clearTimeout(timeout); timeout = setTimeout(() => { handler(event); }, this.getDelay(eventName)); }); return () => { clearTimeout(timeout); unsubscribe(); }; } } class AbstractEventPlugin extends EventManagerPlugin { constructor() { super(inject(DOCUMENT)); } supports(event) { return event.includes(this.modifier); } unwrap(event) { return event .split('.') .filter((v) => !this.modifier.includes(v)) .join('.'); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AbstractEventPlugin, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AbstractEventPlugin }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: AbstractEventPlugin, decorators: [{ type: Injectable }], ctorParameters: () => [] }); /// <reference types="@taiga-ui/tsconfig/ng-dev-mode" /> const GLOBAL_HANDLER = new InjectionToken(ngDevMode ? '[GLOBAL_HANDLER]: Global event target handler' : '', { factory: () => { const document = inject(DOCUMENT); return (name) => name .split('.') .reduce((obj, prop) => obj?.[prop], document.defaultView); }, }); class GlobalEventPlugin extends AbstractEventPlugin { constructor() { super(...arguments); this.handler = inject(GLOBAL_HANDLER); this.modifier = '>'; } addEventListener(_, event, handler) { return this.manager.addEventListener(this.handler(event.split('>')[0]), event.split('>')?.[1] ?? '', handler); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: GlobalEventPlugin, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: GlobalEventPlugin }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: GlobalEventPlugin, decorators: [{ type: Injectable }] }); class OptionsEventPlugin extends AbstractEventPlugin { constructor() { super(...arguments); this.modifier = 'capture.once.passive'; } supports(event) { return event.includes('.') && !this.unwrap(event).includes('.'); } addEventListener(element, event, handler) { const unwrap = this.unwrap(event); const capture = event.includes('.capture'); element.addEventListener(unwrap, handler, { capture, once: event.includes('.once'), passive: event.includes('.passive'), }); return () => element.removeEventListener(unwrap, handler, { capture }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: OptionsEventPlugin, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: OptionsEventPlugin }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: OptionsEventPlugin, decorators: [{ type: Injectable }] }); class PreventEventPlugin extends AbstractEventPlugin { constructor() { super(...arguments); this.modifier = '.prevent'; } addEventListener(element, event, handler) { return this.manager.addEventListener(element, this.unwrap(event), (event) => { event.preventDefault(); handler(event); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: PreventEventPlugin, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: PreventEventPlugin }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: PreventEventPlugin, decorators: [{ type: Injectable }] }); class ResizePlugin extends AbstractEventPlugin { constructor() { super(...arguments); this.modifier = 'resize'; } supports(event) { return event === 'resize'; } addEventListener(element, event, handler) { if (typeof ResizeObserver === 'undefined' || !(element instanceof Element)) { element.addEventListener(event, handler); return () => element.removeEventListener(event, handler); } const observer = new ResizeObserver(handler); observer.observe(element); return () => observer.disconnect(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ResizePlugin, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ResizePlugin }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ResizePlugin, decorators: [{ type: Injectable }] }); class SelfEventPlugin extends AbstractEventPlugin { constructor() { super(...arguments); this.modifier = '.self'; } addEventListener(element, event, handler) { return this.manager.addEventListener(element, this.unwrap(event), (event) => { if (event.target === event.currentTarget) { handler(event); } }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SelfEventPlugin, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SelfEventPlugin }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SelfEventPlugin, decorators: [{ type: Injectable }] }); class StopEventPlugin extends AbstractEventPlugin { constructor() { super(...arguments); this.modifier = '.stop'; } addEventListener(element, event, handler) { return this.manager.addEventListener(element, this.unwrap(event), (event) => { event.stopPropagation(); handler(event); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: StopEventPlugin, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: StopEventPlugin }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: StopEventPlugin, decorators: [{ type: Injectable }] }); class ThrottleEventPlugin extends TimedEventPlugin { constructor() { super(...arguments); this.regExp = /\.throttle~(?<time>\d+)(?<units>ms|s)/; } addEventListener(element, eventName, handler) { let timeout; const unsubscribe = this.manager.addEventListener(element, this.unwrap(eventName), (event) => { if (timeout !== undefined) { return; } handler(event); timeout = setTimeout(() => { timeout = undefined; }, this.getDelay(eventName)); }); return () => { clearTimeout(timeout); unsubscribe(); }; } } class ZonelessPlugin extends AbstractEventPlugin { constructor() { super(...arguments); this.modifier = '.zoneless'; } addEventListener(element, event, handler) { ZonelessPlugin.ngZone = this.manager.getZone(); return ZonelessPlugin.ngZone?.runOutsideAngular(() => this.manager.addEventListener(element, this.unwrap(event), handler)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ZonelessPlugin, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ZonelessPlugin }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: ZonelessPlugin, decorators: [{ type: Injectable }] }); const PLUGINS = [ ZonelessPlugin, SelfEventPlugin, GlobalEventPlugin, OptionsEventPlugin, PreventEventPlugin, ResizePlugin, StopEventPlugin, LongtapEventPlugin, DebounceEventPlugin, ThrottleEventPlugin, ]; /** * @deprecated: use {@link provideEventPlugins} */ const NG_EVENT_PLUGINS = PLUGINS.map((useClass) => ({ provide: EVENT_MANAGER_PLUGINS, multi: true, useClass, })); function provideEventPlugins() { return NG_EVENT_PLUGINS; } function shouldCall(predicate) { return (original, _context, desc) => { const value = desc?.value || original; const result = function (...args) { if (!predicate.apply(this, args)) { return; } if (ZonelessPlugin.ngZone) { ZonelessPlugin.ngZone.run(() => value.apply(this, args)); } else { value.apply(this, args); } }; if (!desc) { return result; } desc.value = result; }; } function asCallable(a) { return a; } /** * Generated bundle index. Do not edit. */ export { AbstractEventPlugin, DebounceEventPlugin, GLOBAL_HANDLER, GlobalEventPlugin, LongtapEvent, NG_EVENT_PLUGINS, OptionsEventPlugin, PreventEventPlugin, ResizePlugin, SelfEventPlugin, StopEventPlugin, ThrottleEventPlugin, TimedEventPlugin, ZonelessPlugin, asCallable, provideEventPlugins, shouldCall }; //# sourceMappingURL=taiga-ui-event-plugins.mjs.map