├── demo
├── public
│ ├── img
│ │ └── favicon.ico
│ └── index.html
├── src
│ ├── polyfills.ts
│ ├── styles
│ │ ├── utils
│ │ │ ├── _variables.scss
│ │ │ └── _core.scss
│ │ └── styles.scss
│ ├── bootstrap.ts
│ └── demo
│ │ └── demo.component.ts
├── tsconfig.json
├── package.json
└── webpack.config.js
├── src
├── index.ts
├── click-outside.module.ts
└── click-outside.directive.ts
├── tsconfig.module.json
├── .editorconfig
├── .gitignore
├── tsconfig.json
├── LICENSE
├── tslint.json
├── package.json
└── README.md
/demo/public/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arkon/ng-click-outside/HEAD/demo/public/img/favicon.ico
--------------------------------------------------------------------------------
/demo/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | // Polyfills
2 | import 'es6-shim';
3 | import 'zone.js/dist/zone';
4 | import 'reflect-metadata';
5 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ClickOutsideDirective } from './click-outside.directive';
2 | export { ClickOutsideModule } from './click-outside.module';
3 |
--------------------------------------------------------------------------------
/tsconfig.module.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "es2015",
5 | "outDir": "lib_esmodule/",
6 | "declaration": false
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 |
7 | [*.{ts,js,css,scss,html}]
8 | charset = utf-8
9 | indent_style = space
10 | indent_size = 2
11 |
--------------------------------------------------------------------------------
/demo/src/styles/utils/_variables.scss:
--------------------------------------------------------------------------------
1 | $fonts: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif;
2 |
3 | $brand: #06d68a;
4 | $light-grey: #f6f6f6;
5 | $grey: #999;
6 | $dark-grey: #545555;
7 | $white: #fff;
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS generated files
2 | .DS_Store
3 | ehthumbs.db
4 | Icon?
5 | Thumbs.db
6 |
7 | # Logs
8 | logs
9 | *.log
10 |
11 | # Node files
12 | node_modules
13 |
14 | # Built files
15 | lib_commonjs/
16 | lib_esmodule/
17 |
18 | # Built demo files
19 | demo/dist
20 |
--------------------------------------------------------------------------------
/src/click-outside.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { ClickOutsideDirective } from './click-outside.directive';
4 |
5 | @NgModule({
6 | declarations: [ClickOutsideDirective],
7 | exports: [ClickOutsideDirective]
8 | })
9 | export class ClickOutsideModule {}
10 |
--------------------------------------------------------------------------------
/demo/src/styles/utils/_core.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | html,
8 | body {
9 | height: 100%;
10 | width: 100%;
11 | }
12 |
13 | body {
14 | background: $light-grey;
15 | color: $dark-grey;
16 | font: 400 1em/1.25 $fonts;
17 | }
18 |
19 | ::selection {
20 | background-color: $brand;
21 | color: $white;
22 | }
23 |
24 | a {
25 | color: $brand;
26 | text-decoration: none;
27 |
28 | &:hover {
29 | border-bottom: 1px dotted $brand;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "removeComments": true,
10 | "lib": ["dom", "es5", "es2015.core", "es2015.collection", "es2015.iterable", "es2015.promise"]
11 | },
12 | "exclude": [
13 | "node_modules"
14 | ],
15 | "angularCompilerOptions": {
16 | "preserveWhitespaces": false
17 | },
18 | "compileOnSave": false,
19 | "buildOnSave": false
20 | }
21 |
--------------------------------------------------------------------------------
/demo/src/bootstrap.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, enableProdMode } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4 | import { ClickOutsideModule } from 'ng-click-outside';
5 |
6 | import { DemoComponent } from './demo/demo.component';
7 |
8 | import './styles/styles.scss';
9 |
10 | enableProdMode();
11 |
12 | @NgModule({
13 | declarations: [DemoComponent],
14 | imports: [BrowserModule, ClickOutsideModule],
15 | bootstrap: [DemoComponent],
16 | })
17 | class DemoAppModule {}
18 |
19 | platformBrowserDynamic().bootstrapModule(DemoAppModule);
20 |
--------------------------------------------------------------------------------
/demo/src/styles/styles.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | @import 'utils/variables';
4 | @import 'utils/core';
5 |
6 | body {
7 | text-align: center;
8 | }
9 |
10 | header {
11 | background: $white;
12 | box-shadow: 0 0.15em 2em rgba($grey, 0.25);
13 | margin-bottom: 2em;
14 | padding: 1em;
15 | }
16 |
17 | demo {
18 | border: 3px solid $brand;
19 | display: block;
20 | margin: 2em auto;
21 | max-width: 800px;
22 | -webkit-user-select: none;
23 | -moz-user-select: none;
24 | user-select: none;
25 |
26 | > div {
27 | padding: 2em;
28 | }
29 | }
30 |
31 | a {
32 | padding: 0.5em 1em;
33 |
34 | + a {
35 | border-left: 1px solid $grey;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ng-click-outside demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ng-click-outside demo
16 |
17 |
18 |
19 | Loading demo...
20 |
21 |
22 | NPM
23 | GitHub
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "noUnusedParameters": true,
7 | "noUnusedLocals": true,
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "declaration": true,
11 | "removeComments": true,
12 | "stripInternal": true,
13 | "outDir": "lib_commonjs/",
14 | "rootDir": "src",
15 | "lib": ["dom", "es5", "es2015.core", "es2015.collection", "es2015.iterable", "es2015.promise"]
16 | },
17 | "include": [
18 | "src/**/*"
19 | ],
20 | "compileOnSave": false,
21 | "buildOnSave": false,
22 | "angularCompilerOptions": {
23 | "decoratorsAs": "static fields",
24 | "skipTemplateCodegen": true,
25 | "preserveWhitespaces": false,
26 | // https://angular.io/guide/creating-libraries#transitioning-libraries-to-partial-ivy-format
27 | "compilationMode": "partial"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Eugene Cheung
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "class-name": true,
4 | "comment-format": [true, "check-space"],
5 | "curly": true,
6 | "eofline": true,
7 | "indent": [true, "spaces"],
8 | "interface-name": [true, "always-prefix"],
9 | "jsdoc-format": true,
10 | "max-line-length": [true, 120],
11 | "member-ordering": [true, {"order": "statics-first"}],
12 | "new-parens": true,
13 | "no-angle-bracket-type-assertion": true,
14 | "no-arg": true,
15 | "no-construct": true,
16 | "no-debugger": true,
17 | "no-duplicate-variable": true,
18 | "no-empty": true,
19 | "no-eval": true,
20 | "no-invalid-this": true,
21 | "no-mergeable-namespace": true,
22 | "no-reference": true,
23 | "no-shadowed-variable": true,
24 | "no-trailing-whitespace": true,
25 | "no-unused-expression": true,
26 | "no-var-keyword": true,
27 | "one-line": [
28 | true,
29 | "check-open-brace",
30 | "check-catch",
31 | "check-else",
32 | "check-whitespace"
33 | ],
34 | "quotemark": [true, "single", "jsx-double"],
35 | "radix": true,
36 | "semicolon": [true, "always"],
37 | "triple-equals": [
38 | true,
39 | "allow-null-check",
40 | "allow-undefined-check"
41 | ],
42 | "use-isnan": true,
43 | "variable-name": false,
44 | "whitespace": [
45 | true,
46 | "check-decl",
47 | "check-operator",
48 | "check-separator",
49 | "check-type"
50 | ]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/demo/src/demo/demo.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'demo',
5 | template: `
6 |
12 |
Clicked inside: {{countInside}}
13 |
Clicked outside: {{countOutside}}
14 |
15 |
19 |
20 |
24 |
25 | `
26 | })
27 | export class DemoComponent {
28 | private countInside: number = 0;
29 | private countOutside: number = 0;
30 |
31 | private attachOutsideOnClick = false;
32 | private enabled = true;
33 |
34 | private _toggleAttachOutsideOnClick() {
35 | this.attachOutsideOnClick = !this.attachOutsideOnClick;
36 | }
37 |
38 | private _toggleEnabled() {
39 | this.enabled = !this.enabled;
40 | }
41 |
42 | private onClick(e: Event) {
43 | console.info('Clicked inside:', e);
44 | this.countInside++;
45 | }
46 |
47 | private onClickedOutside(e: Event) {
48 | console.info('Clicked outside:', e);
49 | this.countOutside++;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-click-outside-demo",
3 | "version": "0.0.0",
4 | "description": "Demo of ng-click-outside.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/arkon/ng-click-outside.git"
8 | },
9 | "author": "Eugene Cheung",
10 | "license": "MIT",
11 | "scripts": {
12 | "install-lib": "npm i -f ng-click-outside",
13 | "start": "webpack-dev-server --progress --colors",
14 | "build": "webpack -p --progress --colors",
15 | "deploy": "npm run build && gh-pages -d dist"
16 | },
17 | "engines": {
18 | "node": ">=10"
19 | },
20 | "dependencies": {
21 | "@angular/common": "^12.1.4",
22 | "@angular/compiler": "^12.1.4",
23 | "@angular/compiler-cli": "^12.1.4",
24 | "@angular/core": "^12.1.4",
25 | "@angular/platform-browser": "^12.1.4",
26 | "@angular/platform-browser-dynamic": "^12.1.4",
27 | "es6-shim": "^0.35.5",
28 | "ng-click-outside": "file:..",
29 | "reflect-metadata": "^0.1.13",
30 | "rxjs": "^6.6.6",
31 | "zone.js": "~0.11.4"
32 | },
33 | "devDependencies": {
34 | "copy-webpack-plugin": "^5.1.1",
35 | "css-loader": "^3.4.2",
36 | "extract-text-webpack-plugin": "^4.0.0-beta.0",
37 | "gh-pages": "^2.2.0",
38 | "html-webpack-plugin": "^3.2.0",
39 | "node-sass": "^5.0.0",
40 | "sass-loader": "^10.1.1",
41 | "style-loader": "^1.1.3",
42 | "ts-loader": "^6.2.1",
43 | "typescript": "4.3.5",
44 | "webpack": "^4.44.1",
45 | "webpack-cli": "^3.3.12",
46 | "webpack-dev-server": "^3.11.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-click-outside",
3 | "version": "9.0.1",
4 | "description": "[DEPRECATED] Angular directive for handling click events outside an element.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/arkon/ng-click-outside.git"
8 | },
9 | "homepage": "http://echeung.me/ng-click-outside/",
10 | "bugs": {
11 | "url": "https://github.com/arkon/ng-click-outside/issues"
12 | },
13 | "files": [
14 | "lib_commonjs/*",
15 | "lib_esmodule/*",
16 | "README.md",
17 | "LICENSE"
18 | ],
19 | "author": "Eugene Cheung",
20 | "license": "MIT",
21 | "keywords": [
22 | "angular",
23 | "ng",
24 | "click",
25 | "event",
26 | "outside",
27 | "handler"
28 | ],
29 | "main": "./lib_commonjs/index.js",
30 | "module": "./lib_esmodule/index.js",
31 | "jsnext:main": "./lib_esmodule/index.js",
32 | "typings": "./lib_commonjs/index.d.ts",
33 | "scripts": {
34 | "lint": "tslint --project tsconfig.json src/**/*.ts",
35 | "build:commonjs": "rimraf lib_commonjs && ngc -p tsconfig.json",
36 | "build:esmodule": "rimraf lib_esmodule && ngc -p tsconfig.module.json",
37 | "build": "npm run lint && npm run build:commonjs && npm run build:esmodule",
38 | "prepare": "npm run build"
39 | },
40 | "peerDependencies": {
41 | "@angular/common": ">=12.0.0",
42 | "@angular/core": ">=12.0.0"
43 | },
44 | "devDependencies": {
45 | "@angular/common": "^12.1.4",
46 | "@angular/compiler": "^12.1.4",
47 | "@angular/compiler-cli": "^12.1.4",
48 | "@angular/core": "^12.1.4",
49 | "@angular/platform-browser": "^12.1.4",
50 | "rimraf": "^3.0.2",
51 | "rxjs": "^6.6.6",
52 | "tslint": "~6.1.3",
53 | "typescript": "4.3.5",
54 | "zone.js": "~0.11.4"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/demo/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const CopyWebpackPlugin = require('copy-webpack-plugin');
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 |
7 | const root = function (args) {
8 | return path.join.apply(path, [__dirname].concat(...arguments));
9 | };
10 |
11 | module.exports = {
12 | entry: [
13 | root('src/polyfills.ts'),
14 | root('src/bootstrap.ts')
15 | ],
16 |
17 | output: {
18 | path: root('dist'),
19 | filename: 'js/[name].js'
20 | },
21 |
22 | resolve: {
23 | modules: [path.join(__dirname, 'node_modules')],
24 | extensions: ['.js', '.ts', '.scss', '.html']
25 | },
26 |
27 | module: {
28 | rules: [
29 | {
30 | test: /\.ts$/,
31 | exclude: [/node_modules\//],
32 | loader: 'ts-loader'
33 | },
34 | {
35 | test: /\.scss$/,
36 | loader: ExtractTextPlugin.extract({
37 | fallback: 'style-loader',
38 | use: [
39 | {
40 | loader: 'css-loader',
41 | options: {
42 | 'sourceMap': true,
43 | 'importLoaders': 1
44 | }
45 | },
46 | 'sass-loader'
47 | ]
48 | })
49 | }
50 | ]
51 | },
52 |
53 | plugins: [
54 | new HtmlWebpackPlugin({
55 | template: root('public/index.html'),
56 | inject: true
57 | }),
58 |
59 | new ExtractTextPlugin({
60 | filename: 'css/[name].css'
61 | }),
62 |
63 | new CopyWebpackPlugin([{
64 | from: root('public')
65 | }]),
66 |
67 | new webpack.ContextReplacementPlugin(
68 | /angular(\\|\/)core(\\|\/)@angular/,
69 | root('src')
70 | )
71 | ],
72 |
73 | devServer: {
74 | contentBase: root('public'),
75 | stats: { chunkModules: false },
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ng-click-outside
2 |
3 | ⚠️ **This package is deprecated and not maintained.** ⚠️
4 |
5 | ---
6 |
7 | [](https://nodei.co/npm/ng-click-outside/)
8 |
9 | **[Demo](https://echeung.me/ng-click-outside/)**
10 |
11 | *Formerly called [ng2-click-outside](https://github.com/arkon/ng2-click-outside)*
12 |
13 | Angular directive for handling click events outside an element. Useful for things like reacting to clicking
14 | outside of a dropdown menu or modal dialog.
15 |
16 | Like binding to a regular `click` event in a template, you can do something like this:
17 |
18 | ```HTML
19 | My element
20 | ```
21 |
22 |
23 | ## Installation
24 |
25 | ```shell
26 | npm install --save ng-click-outside
27 | ```
28 |
29 |
30 | ## Usage
31 |
32 | Add `ClickOutsideModule` to your list of module imports:
33 |
34 | ```typescript
35 | import { ClickOutsideModule } from 'ng-click-outside';
36 |
37 | @NgModule({
38 | declarations: [AppComponent],
39 | imports: [BrowserModule, ClickOutsideModule],
40 | bootstrap: [AppComponent]
41 | })
42 | class AppModule {}
43 | ```
44 |
45 | You can then use the directive in your templates:
46 |
47 | ```typescript
48 | @Component({
49 | selector: 'app',
50 | template: `
51 | Click outside this
52 | `
53 | })
54 | export class AppComponent {
55 | onClickedOutside(e: Event) {
56 | console.log('Clicked outside:', e);
57 | }
58 | }
59 | ```
60 |
61 | ### Options
62 |
63 | | Property name | Type | Default | Description |
64 | | ------------- | ---- | ------- | ----------- |
65 | | `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. |
66 | | `clickOutsideEnabled` | boolean | `true` | Enables directive. |
67 | | `clickOutsideEvents` | string | `'click'` | A comma-separated list of events to cause the trigger. For example, for additional mobile support: `[clickOutsideEvents]="'click,touchstart'"`. |
68 | | `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)). |
69 | | `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. |
70 | | `exclude` | string | | A comma-separated string of DOM element queries to exclude when clicking outside of the element. For example: `[exclude]="'button,.btn-primary'"`. |
71 | | `excludeBeforeClick` | boolean | `false` | By default, `clickOutside` registers excluded DOM elements on init. This property refreshes the list before the `clickOutside` event is triggered. This is useful for ensuring that excluded elements added to the DOM after init are excluded (e.g. ng2-bootstrap popover: this allows for clicking inside the `.popover-content` area if specified in `exclude`). |
72 |
--------------------------------------------------------------------------------
/src/click-outside.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | ElementRef,
4 | EventEmitter,
5 | Inject,
6 | Input,
7 | OnChanges,
8 | OnDestroy,
9 | OnInit,
10 | Output,
11 | PLATFORM_ID,
12 | SimpleChanges,
13 | NgZone,
14 | } from '@angular/core';
15 | import { isPlatformBrowser } from '@angular/common';
16 |
17 | @Directive({ selector: '[clickOutside]' })
18 | export class ClickOutsideDirective implements OnInit, OnChanges, OnDestroy {
19 |
20 | @Input() clickOutsideEnabled: boolean = true;
21 |
22 | @Input() attachOutsideOnClick: boolean = false;
23 | @Input() delayClickOutsideInit: boolean = false;
24 | @Input() emitOnBlur: boolean = false;
25 |
26 | @Input() exclude: string = '';
27 | @Input() excludeBeforeClick: boolean = false;
28 |
29 | @Input() clickOutsideEvents: string = '';
30 |
31 | @Output() clickOutside: EventEmitter = new EventEmitter();
32 |
33 | private _nodesExcluded: Array = [];
34 | private _events: Array = ['click'];
35 |
36 | constructor(
37 | private _el: ElementRef,
38 | private _ngZone: NgZone,
39 | @Inject(PLATFORM_ID) private platformId: Object) {
40 | this._initOnClickBody = this._initOnClickBody.bind(this);
41 | this._onClickBody = this._onClickBody.bind(this);
42 | this._onWindowBlur = this._onWindowBlur.bind(this);
43 | }
44 |
45 | ngOnInit() {
46 | if (!isPlatformBrowser(this.platformId)) { return; }
47 |
48 | this._init();
49 | }
50 |
51 | ngOnDestroy() {
52 | if (!isPlatformBrowser(this.platformId)) { return; }
53 |
54 | this._removeClickOutsideListener();
55 | this._removeAttachOutsideOnClickListener();
56 | this._removeWindowBlurListener();
57 | }
58 |
59 | ngOnChanges(changes: SimpleChanges) {
60 | if (!isPlatformBrowser(this.platformId)) { return; }
61 |
62 | if (changes['attachOutsideOnClick'] || changes['exclude'] || changes['emitOnBlur']) {
63 | this._init();
64 | }
65 | }
66 |
67 | private _init() {
68 | if (this.clickOutsideEvents !== '') {
69 | this._events = this.clickOutsideEvents.split(',').map(e => e.trim());
70 | }
71 |
72 | this._excludeCheck();
73 |
74 | if (this.attachOutsideOnClick) {
75 | this._initAttachOutsideOnClickListener();
76 | } else {
77 | this._initOnClickBody();
78 | }
79 |
80 | if (this.emitOnBlur) {
81 | this._initWindowBlurListener();
82 | }
83 | }
84 |
85 | private _initOnClickBody() {
86 | if (this.delayClickOutsideInit) {
87 | setTimeout(this._initClickOutsideListener.bind(this));
88 | } else {
89 | this._initClickOutsideListener();
90 | }
91 | }
92 |
93 | private _excludeCheck() {
94 | if (this.exclude) {
95 | try {
96 | const nodes = Array.from(document.querySelectorAll(this.exclude)) as Array;
97 | if (nodes) {
98 | this._nodesExcluded = nodes;
99 | }
100 | } catch (err) {
101 | console.error('[ng-click-outside] Check your exclude selector syntax.', err);
102 | }
103 | }
104 | }
105 |
106 | private _onClickBody(ev: Event) {
107 | if (!this.clickOutsideEnabled) { return; }
108 |
109 | if (this.excludeBeforeClick) {
110 | this._excludeCheck();
111 | }
112 |
113 | if (!this._el.nativeElement.contains(ev.target) && !this._shouldExclude(ev.target)) {
114 | this._emit(ev);
115 |
116 | if (this.attachOutsideOnClick) {
117 | this._removeClickOutsideListener();
118 | }
119 | }
120 | }
121 |
122 | /**
123 | * Resolves problem with outside click on iframe
124 | * @see https://github.com/arkon/ng-click-outside/issues/32
125 | */
126 | private _onWindowBlur(ev: Event) {
127 | setTimeout(() => {
128 | if (!document.hidden) {
129 | this._emit(ev);
130 | }
131 | });
132 | }
133 |
134 | private _emit(ev: Event) {
135 | if (!this.clickOutsideEnabled) { return; }
136 |
137 | this._ngZone.run(() => this.clickOutside.emit(ev));
138 | }
139 |
140 | private _shouldExclude(target): boolean {
141 | for (let excludedNode of this._nodesExcluded) {
142 | if (excludedNode.contains(target)) {
143 | return true;
144 | }
145 | }
146 |
147 | return false;
148 | }
149 |
150 | private _initClickOutsideListener() {
151 | this._ngZone.runOutsideAngular(() => {
152 | this._events.forEach(e => document.addEventListener(e, this._onClickBody));
153 | });
154 | }
155 |
156 | private _removeClickOutsideListener() {
157 | this._ngZone.runOutsideAngular(() => {
158 | this._events.forEach(e => document.removeEventListener(e, this._onClickBody));
159 | });
160 | }
161 |
162 | private _initAttachOutsideOnClickListener() {
163 | this._ngZone.runOutsideAngular(() => {
164 | this._events.forEach(e => this._el.nativeElement.addEventListener(e, this._initOnClickBody));
165 | });
166 | }
167 |
168 | private _removeAttachOutsideOnClickListener() {
169 | this._ngZone.runOutsideAngular(() => {
170 | this._events.forEach(e => this._el.nativeElement.removeEventListener(e, this._initOnClickBody));
171 | });
172 | }
173 |
174 | private _initWindowBlurListener() {
175 | this._ngZone.runOutsideAngular(() => {
176 | window.addEventListener('blur', this._onWindowBlur);
177 | });
178 | }
179 |
180 | private _removeWindowBlurListener() {
181 | this._ngZone.runOutsideAngular(() => {
182 | window.removeEventListener('blur', this._onWindowBlur);
183 | });
184 | }
185 |
186 | }
187 |
--------------------------------------------------------------------------------