├── 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 | [![NPM](https://nodei.co/npm/ng-click-outside.png?compact=true)](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 | --------------------------------------------------------------------------------