UNPKG

ng-click-outside2

Version:

Angular directive for handling click events outside an element.

319 lines (309 loc) 15.1 kB
import * as i0 from '@angular/core'; import { input, inject, DOCUMENT, Directive, booleanAttribute, output, ElementRef, NgZone, afterNextRender, effect } from '@angular/core'; /** * Optimization for Treeshaking: https://angular.io/guide/lightweight-injection-tokens */ class NgClickOutsideExcludeToken { } /** * Directive to exclude elements from the click-outside */ class NgClickOutsideExcludeDirective extends NgClickOutsideExcludeToken { constructor() { super(...arguments); /** * A comma-separated string of DOM element queries to exclude when clicking outside of the element. * For example: `[exclude]="'button,.btn-primary'"`. */ this.clickOutsideExclude = input('', { ...(ngDevMode ? { debugName: "clickOutsideExclude" } : {}) }); this.document = inject(DOCUMENT); } excludeCheck() { const clickOutsideExclude = this.clickOutsideExclude(); if (clickOutsideExclude) { try { const nodes = Array.from(this.document.querySelectorAll(clickOutsideExclude)); if (nodes) { return nodes; } } catch (err) { console.error('[ng-click-outside] Check your exclude selector syntax.', err); } } return []; } isExclude(target) { const nodesExcluded = this.excludeCheck(); for (let excludedNode of nodesExcluded) { if (excludedNode.contains(target)) { return true; } } return false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideExcludeDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.2", type: NgClickOutsideExcludeDirective, isStandalone: true, selector: "[clickOutsideExclude]", inputs: { clickOutsideExclude: { classPropertyName: "clickOutsideExclude", publicName: "clickOutsideExclude", isSignal: true, isRequired: false, transformFunction: null } }, providers: [ { provide: NgClickOutsideExcludeToken, useExisting: NgClickOutsideExcludeDirective } ], usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideExcludeDirective, decorators: [{ type: Directive, args: [{ selector: '[clickOutsideExclude]', standalone: true, providers: [ { provide: NgClickOutsideExcludeToken, useExisting: NgClickOutsideExcludeDirective } ] }] }], propDecorators: { clickOutsideExclude: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickOutsideExclude", required: false }] }] } }); function arrayAttribute(events) { if (Array.isArray(events)) { return events; } else { return events.split(','); } } /** * Directive to detect clicks outside of the current element * * ```typescript * @Component({ * selector: 'app', * template: ` * <div (clickOutside)="onClickedOutside($event)">Click outside this</div> * ` * }) * export class AppComponent { * onClickedOutside(e: Event) { * console.log('Clicked outside:', e); * } * } * ``` */ class NgClickOutsideDirective { constructor() { /** * Enables directive. */ this.clickOutsideEnabled = input(true, { ...(ngDevMode ? { debugName: "clickOutsideEnabled" } : {}), transform: booleanAttribute }); /** * A comma-separated list of events to cause the trigger. * ### For example, for additional mobile support: * `[clickOutsideEvents]="'click,touchstart'"` */ this.clickOutsideEvents = input(['click'], { ...(ngDevMode ? { debugName: "clickOutsideEvents" } : {}), transform: arrayAttribute }); /** * Outside Click Event */ this.clickOutside = output(); this.excludeDirective = inject(NgClickOutsideExcludeToken, { host: true, optional: true }); this._el = inject(ElementRef); this._ngZone = inject(NgZone); this.document = inject(DOCUMENT); this._initOnClickBody = this._initOnClickBody.bind(this); this._onClickBody = this._onClickBody.bind(this); afterNextRender(() => this._init()); } ngOnDestroy() { this._removeClickOutsideListener(); this.mutationObserver?.disconnect(); } _init() { this._initOnClickBody(); } _initOnClickBody() { this.initListener(); } _emit(ev) { this._ngZone.run(() => this.clickOutside.emit(ev)); } initListener() { this.initMutationObserver(); this._initClickOutsideListener(); } initMutationObserver() { this.mutationObserver = new MutationObserver((mutationList) => { this.lastRemoved = mutationList.flatMap(list => Array.from(list.removedNodes.values())); }); this.mutationObserver.observe(this._el.nativeElement, { childList: true, subtree: true, }); } _initClickOutsideListener() { this._ngZone.runOutsideAngular(() => { this.clickOutsideEvents().forEach(e => this.document.addEventListener(e, this._onClickBody)); }); } _removeClickOutsideListener() { this._ngZone.runOutsideAngular(() => { this.clickOutsideEvents().forEach(e => this.document.removeEventListener(e, this._onClickBody)); }); } _onClickBody(ev) { if (!this.clickOutsideEnabled()) { return; } if (!this._el.nativeElement.contains(ev.target) && !this.excludeDirective?.isExclude(ev.target) && !this.gotRemoved(ev.target)) { this._emit(ev); } this.resetLastRemovedList(); } gotRemoved(target, list = this.lastRemoved) { return list?.some(node => node === target || node.contains(target)) ?? false; } resetLastRemovedList() { this.lastRemoved = undefined; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.2", type: NgClickOutsideDirective, isStandalone: true, selector: "[clickOutside]:not([delayClickOutsideInit]):not([attachOutsideOnClick])", inputs: { clickOutsideEnabled: { classPropertyName: "clickOutsideEnabled", publicName: "clickOutsideEnabled", isSignal: true, isRequired: false, transformFunction: null }, clickOutsideEvents: { classPropertyName: "clickOutsideEvents", publicName: "clickOutsideEvents", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clickOutside: "clickOutside" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideDirective, decorators: [{ type: Directive, args: [{ selector: '[clickOutside]:not([delayClickOutsideInit]):not([attachOutsideOnClick])', standalone: true, }] }], ctorParameters: () => [], propDecorators: { clickOutsideEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickOutsideEnabled", required: false }] }], clickOutsideEvents: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickOutsideEvents", required: false }] }], clickOutside: [{ type: i0.Output, args: ["clickOutside"] }] } }); /** * Directive only starts after a single click and the outside click event handler * will then be removed after a click outside has occurred. */ class NgClickOutsideAttachOutsideDirective extends NgClickOutsideDirective { constructor() { super(); /** * By default, the outside click event handler is automatically attached. * * Explicitely setting this to `true`sets the handler after the element is clicked. The outside click event handler * will then be removed after a click outside has occurred. */ this.attachOutsideOnClick = input.required({ ...(ngDevMode ? { debugName: "attachOutsideOnClick" } : {}), transform: booleanAttribute }); effect(() => { this.attachOutsideOnClick(); this._init(); }); } ngOnDestroy() { super.ngOnDestroy(); this._removeAttachOutsideOnClickListener(); } _init() { if (this.attachOutsideOnClick()) { this._initAttachOutsideOnClickListener(); } else { this._initOnClickBody(); } } _emit(ev) { if (this.attachOutsideOnClick()) { this._removeClickOutsideListener(); } super._emit(ev); } _initAttachOutsideOnClickListener() { this._ngZone.runOutsideAngular(() => { this.clickOutsideEvents().forEach(e => this._el.nativeElement.addEventListener(e, this._initOnClickBody)); }); } _removeAttachOutsideOnClickListener() { this._ngZone.runOutsideAngular(() => { this.clickOutsideEvents().forEach(e => this._el.nativeElement.removeEventListener(e, this._initOnClickBody)); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideAttachOutsideDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.2", type: NgClickOutsideAttachOutsideDirective, isStandalone: true, selector: "[clickOutside][attachOutsideOnClick]", inputs: { attachOutsideOnClick: { classPropertyName: "attachOutsideOnClick", publicName: "attachOutsideOnClick", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideAttachOutsideDirective, decorators: [{ type: Directive, args: [{ selector: '[clickOutside][attachOutsideOnClick]', standalone: true }] }], ctorParameters: () => [], propDecorators: { attachOutsideOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "attachOutsideOnClick", required: true }] }] } }); /** * Click oustside Directive but with an setTimeout on the listener * This may help for items that are conditionally shown ([see issue #13](https://github.com/arkon/ng-click-outside/issues/13)). */ class NgClickOutsideDelayOutsideDirective extends NgClickOutsideDirective { constructor() { super(...arguments); /** * Delays the initialization of the click outside handler. * This may help for items that are conditionally shown ([see issue #13](https://github.com/arkon/ng-click-outside/issues/13)). */ this.delayClickOutsideInit = input.required({ ...(ngDevMode ? { debugName: "delayClickOutsideInit" } : {}), transform: booleanAttribute }); } _initOnClickBody() { if (this.delayClickOutsideInit()) { setTimeout(super.initListener.bind(this)); } else { super.initListener(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideDelayOutsideDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.2", type: NgClickOutsideDelayOutsideDirective, isStandalone: true, selector: "[clickOutside][delayClickOutsideInit]", inputs: { delayClickOutsideInit: { classPropertyName: "delayClickOutsideInit", publicName: "delayClickOutsideInit", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideDelayOutsideDirective, decorators: [{ type: Directive, args: [{ selector: '[clickOutside][delayClickOutsideInit]', standalone: true }] }], propDecorators: { delayClickOutsideInit: [{ type: i0.Input, args: [{ isSignal: true, alias: "delayClickOutsideInit", required: true }] }] } }); /** * emits an event when user clicks outside of applications' window while it's visible. * Especially useful if page contains iframes. */ class NgClickOutsideEmitOnBlurDirective { constructor() { this._ngZone = inject(NgZone); this.document = inject(DOCUMENT); this.blurWindow = output(); this._onWindowBlur = this._onWindowBlur.bind(this); afterNextRender(() => this._initWindowBlurListener()); } ngOnDestroy() { this._removeWindowBlurListener(); } _initWindowBlurListener() { this._ngZone.runOutsideAngular(() => { this.document.defaultView?.addEventListener('blur', this._onWindowBlur); }); } /** * Resolves problem with outside click on iframe * @see https://github.com/arkon/ng-click-outside/issues/32 */ _onWindowBlur(ev) { if (!this.document.hidden) { this._ngZone.run(() => this.blurWindow.emit(ev)); } } _removeWindowBlurListener() { this._ngZone.runOutsideAngular(() => { this.document.defaultView?.removeEventListener('blur', this._onWindowBlur); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideEmitOnBlurDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: NgClickOutsideEmitOnBlurDirective, isStandalone: true, selector: "[clickOutsideEmitOnBlur]", outputs: { blurWindow: "blurWindow" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: NgClickOutsideEmitOnBlurDirective, decorators: [{ type: Directive, args: [{ selector: '[clickOutsideEmitOnBlur]', standalone: true }] }], ctorParameters: () => [], propDecorators: { blurWindow: [{ type: i0.Output, args: ["blurWindow"] }] } }); /* * Public API Surface of ng-click-outside2 */ /** * Generated bundle index. Do not edit. */ export { NgClickOutsideAttachOutsideDirective, NgClickOutsideDelayOutsideDirective, NgClickOutsideDirective, NgClickOutsideEmitOnBlurDirective, NgClickOutsideExcludeDirective, NgClickOutsideExcludeToken }; //# sourceMappingURL=ng-click-outside2.mjs.map