Skip to content

Commit

Permalink
feat: put code for emit on blur into dedicated directive (#55)
Browse files Browse the repository at this point in the history
* feat: put code for emit on blur into dedicated directive

BREAKING CHANGE: emitOnBlur property is now an Directive NgClickOutsideEmitOnBlurDirective
  • Loading branch information
Kr0san89 authored Feb 26, 2024
1 parent 1f4a3bc commit c1b2cf1
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 47 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ testem.log
# System Files
.DS_Store
Thumbs.db
/.nx/
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ If you use Angular <= 12 please use the original package. https://www.npmjs.com/

### Options

| Property name | Type | Default | Description |
| ------------- | ---- | ------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `attachOutsideOnClick` | boolean | `false` | 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. |
| `clickOutsideEnabled` | boolean | `true` | Enables directive. |
| `clickOutsideEvents` | string | `'click'` | A comma-separated list of events to cause the trigger. For example, for additional mobile support: `[clickOutsideEvents]="'click,touchstart'"`. |
| `delayClickOutsideInit` | boolean | `false` | 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)). |
| `emitOnBlur` | boolean | `false` | If enabled, emits an event when user clicks outside of applications' window while it's visible. Especially useful if page contains iframes. |
| `clickOutsideExclude` | string | | A comma-separated string of DOM element queries to exclude when clicking outside of the element. (Import NgClickOutsideExcludeDirective) For example: `[clickOutsideExclude]="'button,.btn-primary'"`. |
| Property name | Type | Default | Description |
| ------------- |---------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `attachOutsideOnClick` | boolean | `false` | 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. |
| `clickOutsideEnabled` | boolean | `true` | Enables directive. |
| `clickOutsideEvents` | string | `'click'` | A comma-separated list of events to cause the trigger. For example, for additional mobile support: `[clickOutsideEvents]="'click,touchstart'"`. |
| `delayClickOutsideInit` | boolean | `false` | 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)). |
| `clickOutsideEmitOnBlur` | - | - | If enabled, emits an `blurWindow` event when user clicks outside of applications' window while it's visible. Especially useful if page contains iframes. (Import `NgClickOutsideEmitOnBlurDirective`) |
| `clickOutsideExclude` | string | | A comma-separated string of DOM element queries to exclude when clicking outside of the element. (Import NgClickOutsideExcludeDirective) For example: `[clickOutsideExclude]="'button,.btn-primary'"`. |

## Example Usage

Expand Down
2 changes: 1 addition & 1 deletion projects/ng-click-outside2/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ng-click-outside2",
"version": "13.0.0",
"version": "14.0.0",
"description": "Angular directive for handling click events outside an element.",
"license": "MIT",
"keywords": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {NgClickOutsideEmitOnBlurDirective} from './ng-click-outside-emit-on-blur.directive';
import {ComponentFixture, TestBed} from "@angular/core/testing";
import {DOCUMENT} from "@angular/common";
import {Component, ViewChild} from "@angular/core";
import {By} from "@angular/platform-browser";


@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'test-click',
standalone: true,
imports: [NgClickOutsideEmitOnBlurDirective],
template: `
<button id="b-1" (blurWindow)="blurWindowButton1 = blurWindowButton1 + 1"
(click)="clickButton1 = clickButton1 + 1" clickOutsideEmitOnBlur
></button>
`
})
class TestClickOutsideComponent {
@ViewChild(NgClickOutsideEmitOnBlurDirective) ngClickOutsideEmitOnBlurDirective?: NgClickOutsideEmitOnBlurDirective
blurWindowButton1 = 0;
clickButton1 = 0;
}


describe('NgClickOutsideEmitOnBlurDirective', () => {
let component: TestClickOutsideComponent;
let fixture: ComponentFixture<TestClickOutsideComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TestClickOutsideComponent],
providers: [{provider: DOCUMENT, useValue: document}]
}).compileComponents();

fixture = TestBed.createComponent(TestClickOutsideComponent);
component = fixture.componentInstance;
fixture.detectChanges();
})

it('should create an instance', () => {
expect(component.ngClickOutsideEmitOnBlurDirective).toBeDefined();
});

it('should emit on window blur', () => {
const button1 = fixture.debugElement.query(By.css('#b-1'));

button1.nativeElement.click();
document.defaultView!.dispatchEvent(new Event('blur'));

expect(component.clickButton1).toBe(1);
expect(component.blurWindowButton1).toBe(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {afterNextRender, Directive, EventEmitter, inject, NgZone, OnDestroy, Output} from '@angular/core';
import {DOCUMENT} from "@angular/common";

/**
* emits an event when user clicks outside of applications' window while it's visible.
* Especially useful if page contains iframes.
*/
@Directive({
selector: '[clickOutsideEmitOnBlur]',
standalone: true
})
export class NgClickOutsideEmitOnBlurDirective implements OnDestroy {
private _ngZone = inject(NgZone);
private document = inject<Document>(DOCUMENT);
@Output() blurWindow: EventEmitter<Event> = new EventEmitter<Event>();

constructor() {
this._onWindowBlur = this._onWindowBlur.bind(this);
afterNextRender(() => this._initWindowBlurListener())
}

ngOnDestroy() {
this._removeWindowBlurListener();
}

private _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
*/
private _onWindowBlur(ev: Event) {
if (!this.document.hidden) {
this._ngZone.run(() => this.blurWindow.emit(ev));
}
}

private _removeWindowBlurListener() {
this._ngZone.runOutsideAngular(() => {
this.document.defaultView?.removeEventListener('blur', this._onWindowBlur);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ describe('NgClickOutsideExcludeDirective', () => {
button3.nativeElement.click();
expect(component.clickButton3).toBe(1);
expect(component.clickButton2).toBe(0);
expect(component.clickOutsideButton2).toBe(0); });

expect(component.clickOutsideButton2).toBe(0);
});
});
36 changes: 0 additions & 36 deletions projects/ng-click-outside2/src/lib/ng-click-outside.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ export class NgClickOutsideDirective implements OnChanges, OnDestroy {
* This may help for items that are conditionally shown ([see issue #13](https://github.com/arkon/ng-click-outside/issues/13)).
*/
@Input() delayClickOutsideInit = false;
/**
* If enabled, emits an event when user clicks outside of applications' window while it's visible.
* Especially useful if page contains iframes.
*/
@Input() emitOnBlur = false;

/**
* A comma-separated list of events to cause the trigger.
Expand All @@ -81,14 +76,12 @@ export class NgClickOutsideDirective implements OnChanges, OnDestroy {
constructor() {
this._initOnClickBody = this._initOnClickBody.bind(this);
this._onClickBody = this._onClickBody.bind(this);
this._onWindowBlur = this._onWindowBlur.bind(this);
afterNextRender(() => this._init())
}

ngOnDestroy() {
this._removeClickOutsideListener();
this._removeAttachOutsideOnClickListener();
this._removeWindowBlurListener();
}

ngOnChanges(changes: SimpleChanges) {
Expand All @@ -107,10 +100,6 @@ export class NgClickOutsideDirective implements OnChanges, OnDestroy {
} else {
this._initOnClickBody();
}

if (this.emitOnBlur) {
this._initWindowBlurListener();
}
}

private _initOnClickBody() {
Expand All @@ -136,18 +125,6 @@ export class NgClickOutsideDirective implements OnChanges, OnDestroy {
}
}

/**
* Resolves problem with outside click on iframe
* @see https://github.com/arkon/ng-click-outside/issues/32
*/
private _onWindowBlur(ev: Event) {
setTimeout(() => {
if (!this.document.hidden) {
this._emit(ev);
}
});
}

private _emit(ev: Event) {
if (!this.clickOutsideEnabled) {
return;
Expand All @@ -156,7 +133,6 @@ export class NgClickOutsideDirective implements OnChanges, OnDestroy {
this._ngZone.run(() => this.clickOutside.emit(ev));
}


private _initClickOutsideListener() {
this._ngZone.runOutsideAngular(() => {
this._events.forEach(e => this.document.addEventListener(e, this._onClickBody));
Expand All @@ -180,16 +156,4 @@ export class NgClickOutsideDirective implements OnChanges, OnDestroy {
this._events.forEach(e => this._el.nativeElement.removeEventListener(e, this._initOnClickBody));
});
}

private _initWindowBlurListener() {
this._ngZone.runOutsideAngular(() => {
this.document.defaultView?.addEventListener('blur', this._onWindowBlur);
});
}

private _removeWindowBlurListener() {
this._ngZone.runOutsideAngular(() => {
this.document.defaultView?.removeEventListener('blur', this._onWindowBlur);
});
}
}

0 comments on commit c1b2cf1

Please sign in to comment.