├── .all-contributorsrc ├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .npmignore ├── .nvmrc ├── .travis.yml ├── .vcmrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bs-config.json ├── config ├── helpers.js ├── karma-require.js ├── karma-test-shim.js ├── karma.conf.js └── webpack.test.js ├── docs ├── app.component.ts ├── app.html ├── app.module.ts ├── bundle.js ├── index.html ├── main.ts └── tsconfig.json ├── index.ts ├── karma.conf.js ├── package.json ├── src ├── focus.directive.spec.ts ├── focus.directive.ts └── focus.module.ts ├── tsconfig.json ├── tslint.json ├── wallaby.js └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "angular2-focus", 3 | "projectOwner": "spirosikmd", 4 | "files": [ 5 | "README.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": false, 9 | "contributors": [ 10 | { 11 | "login": "spirosikmd", 12 | "name": "Spyros Ioakeimidis", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/1057324?v=3", 14 | "profile": "http://www.spyros.io", 15 | "contributions": [ 16 | "question", 17 | "code", 18 | "test" 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.html] 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional REPL history 39 | .node_repl_history 40 | 41 | # Compiled angular 2 files 42 | aot 43 | 44 | # Generated compiled source files 45 | index.js 46 | index.js.map 47 | src/*.js 48 | src/*.js.map 49 | *.d.ts 50 | *.metadata.json 51 | 52 | # Exclude the bundle demo file 53 | !docs/bundle.js 54 | 55 | # Generated build 56 | dist 57 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Node generated files 2 | node_modules 3 | npm-debug.log 4 | 5 | # OS generated files 6 | Thumbs.db 7 | .DS_Store 8 | 9 | # IDE generated files 10 | .idea 11 | 12 | # Demo 13 | docs 14 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: yarn 3 | branches: 4 | only: 5 | - master 6 | notifications: 7 | email: false 8 | script: 9 | - yarn lint 10 | - yarn test 11 | before_install: 12 | - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2 13 | - export PATH=$HOME/.yarn/bin:$PATH 14 | after_success: 15 | - yarn travis-deploy-once "npm run semantic-release" 16 | -------------------------------------------------------------------------------- /.vcmrc: -------------------------------------------------------------------------------- 1 | { 2 | "helpMessage": "\nPlease fix your commit message (and consider using http://npm.im/commitizen)\n", 3 | "types": [ 4 | "feat", 5 | "fix", 6 | "docs", 7 | "style", 8 | "refactor", 9 | "perf", 10 | "test", 11 | "chore", 12 | "revert", 13 | "custom" 14 | ], 15 | "warnOnFail": false, 16 | "autoFix": false 17 | } 18 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 12 | 13 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 14 | 15 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/) 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for being willing to contribute! 4 | 5 | **Working on your first Pull Request?** You can learn how from this *free* series 6 | [How to Contribute to an Open Source Project on GitHub][egghead] 7 | 8 | ## Project setup 9 | 10 | You're going to need [`git`](https://git-scm.com/) to get the project, and [`node`](https://nodejs.org/en/) and 11 | [`yarn`](https://yarnpkg.com/) to install dependencies and run scripts. 12 | 13 | 1. Fork and clone the repo 14 | 2. Run `yarn` to install dependencies 15 | 3. Run `yarn test` for testing 16 | 4. Run `yarn lint` for linting 17 | 5. Run `yarn start` to compile 18 | 6. Run `yarn docs` to test the directive using the example project 19 | 7. Create a branch for your PR 20 | 21 | ## Add yourself as a contributor 22 | 23 | This project follows the [all contributors][all-contributors] specification. To add yourself to the table of 24 | contributors on the README.md, please use the automated script as part of your PR: 25 | 26 | ```console 27 | yarn add-contributor 28 | ``` 29 | 30 | Follow the prompt. If you've already added yourself to the list and are making a new type of contribution, you can run 31 | it again and select the added contribution type. 32 | 33 | ## Committing and Pushing changes 34 | 35 | This project uses [`semantic-release`][semantic-release] to do automatic releases and generate a changelog based on the 36 | commit history. So we follow [a convention][convention] for commit messages. Please follow this convention for your 37 | commit messages. 38 | 39 | You can use `commitizen` to help you to follow [the convention][convention] 40 | 41 | Once you are ready to commit the changes, please use the below commands 42 | 43 | 1. Run `git add ` to stage changed files 44 | 2. Run `yarn commit` to start commitizen to commit those files 45 | 46 | ... and follow the instruction of the interactive prompt. 47 | 48 | ## Help needed 49 | 50 | Please checkout [the issues](https://github.com/spirosikmd/angular2-focus/issues)! Also, please watch the repo 51 | and respond to questions/bug reports/feature requests! Thanks! 52 | 53 | [egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github 54 | [semantic-release]: https://npmjs.com/package/semantic-release 55 | [convention]: https://github.com/conventional-changelog/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md 56 | [all-contributors]: https://github.com/kentcdodds/all-contributors 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular2-focus 2 | 3 | Angular attribute directive that gives focus on an element depending on a given expression. 4 | 5 | [![Build Status][build-badge]][build] 6 | [![version][version-badge]][package] 7 | [![PRs Welcome][prs-badge]](http://makeapullrequest.com) 8 | [![All Contributors][all-contributors-badge]](#contributors) 9 | [![Greenkeeper badge][greenkeeper-badge]](https://greenkeeper.io/) 10 | 11 | ## Install 12 | 13 | `npm install --save angular2-focus` 14 | 15 | ## Example 16 | 17 | ```typescript 18 | import {NgModule, Component} from '@angular/core'; 19 | import {FocusModule} from 'angular2-focus'; 20 | 21 | @Component({ 22 | template: ` 23 | 24 | ` 25 | }) 26 | class AppComponent { } 27 | 28 | @NgModule({ 29 | imports: [FocusModule.forRoot()], 30 | declarations: [AppComponent], 31 | bootstrap: [AppComponent] 32 | }) 33 | export class AppModule { } 34 | ``` 35 | 36 | Check the [docs](docs) for examples using `@angular/forms` and other third party libraries such as 37 | `@ng-bootstrap/ng-bootstrap`, to focus elements when e.g. using a modal. 38 | 39 | ## Contributors 40 | 41 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): 42 | 43 | 44 | | [
Spyros Ioakeimidis](http://www.spyros.io)
💬 [💻](https://github.com/spirosikmd/angular2-focus/commits?author=spirosikmd) [⚠️](https://github.com/spirosikmd/angular2-focus/commits?author=spirosikmd) | 45 | | :---: | 46 | 47 | 48 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. 49 | Contributions of any kind welcome! 50 | 51 | [version-badge]: https://img.shields.io/npm/v/angular2-focus.svg?style=flat-square 52 | [package]: https://www.npmjs.com/package/angular2-focus 53 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 54 | [build-badge]: https://img.shields.io/travis/spirosikmd/angular2-focus.svg?style=flat-square 55 | [build]: https://travis-ci.org/spirosikmd/angular2-focus.svg 56 | [all-contributors-badge]: https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square 57 | [greenkeeper-badge]: https://badges.greenkeeper.io/spirosikmd/angular2-focus.svg 58 | -------------------------------------------------------------------------------- /bs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "./docs/**/*.{html,htm,css,js}" 4 | ], 5 | "server": { 6 | "baseDir": "./docs" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /config/helpers.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const _root = path.resolve(__dirname, '..'); 3 | function root(args) { 4 | args = Array.prototype.slice.call(arguments, 0); 5 | return path.join.apply(path, [_root].concat(args)); 6 | } 7 | exports.root = root; 8 | -------------------------------------------------------------------------------- /config/karma-require.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Ok, this is kinda crazy. We can use the context method on 3 | * require that webpack created in order to tell webpack 4 | * what files we actually want to require or import. 5 | * Below, context will be a function/object with file names as keys. 6 | * Using that regex we are saying look in ../src then find 7 | * any file that ends with spec.ts and get its path. By passing in true 8 | * we say do this recursively 9 | */ 10 | const testContext = require.context('../src', true, /\.spec\.ts/); 11 | 12 | /* 13 | * get all the files, for each file, call the context function 14 | * that will require the file and load it up here. Context will 15 | * loop and require those spec files here 16 | */ 17 | function requireAll(requireContext) { 18 | return requireContext.keys().map(requireContext); 19 | } 20 | 21 | // requires and returns all modules that match 22 | const modules = requireAll(testContext); 23 | -------------------------------------------------------------------------------- /config/karma-test-shim.js: -------------------------------------------------------------------------------- 1 | Error.stackTraceLimit = Infinity; 2 | 3 | require('core-js/es6'); 4 | require('core-js/es7/reflect'); 5 | 6 | require('zone.js/dist/zone'); 7 | require('zone.js/dist/long-stack-trace-zone'); 8 | require('zone.js/dist/proxy'); 9 | require('zone.js/dist/sync-test'); 10 | require('zone.js/dist/jasmine-patch'); 11 | require('zone.js/dist/async-test'); 12 | require('zone.js/dist/fake-async-test'); 13 | 14 | const testing = require('@angular/core/testing'); 15 | const browser = require('@angular/platform-browser-dynamic/testing'); 16 | 17 | testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting()); 18 | 19 | window.__karma__ && require('./karma-require'); 20 | -------------------------------------------------------------------------------- /config/karma.conf.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = require('./webpack.test'); 2 | 3 | module.exports = function(config) { 4 | const _config = { 5 | basePath: '', 6 | 7 | frameworks: ['jasmine'], 8 | 9 | files: [ 10 | {pattern: './config/karma-test-shim.js', watched: false} 11 | ], 12 | 13 | preprocessors: { 14 | './config/karma-test-shim.js': ['webpack', 'sourcemap'] 15 | }, 16 | 17 | webpack: webpackConfig, 18 | 19 | webpackMiddleware: { 20 | stats: 'errors-only' 21 | }, 22 | 23 | webpackServer: { 24 | noInfo: true 25 | }, 26 | 27 | reporters: ['progress'], 28 | port: 9876, 29 | colors: true, 30 | logLevel: config.LOG_INFO, 31 | autoWatch: false, 32 | browsers: ['PhantomJS'], 33 | singleRun: true 34 | }; 35 | 36 | config.set(_config); 37 | }; 38 | -------------------------------------------------------------------------------- /config/webpack.test.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const helpers = require('./helpers'); 3 | 4 | module.exports = { 5 | devtool: 'cheap-module-eval-source-map', 6 | 7 | resolve: { 8 | extensions: ['.ts', '.js'] 9 | }, 10 | 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.ts$/, 15 | loaders: ['awesome-typescript-loader'] 16 | }, 17 | ] 18 | }, 19 | 20 | plugins: [ 21 | new webpack.ContextReplacementPlugin( 22 | // The (\\|\/) piece accounts for path separators in *nix and Windows 23 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, 24 | helpers.root('./src'), // location of your src 25 | {} // a map of your routes 26 | ) 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /docs/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FormControl, FormGroup } from '@angular/forms'; 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; 4 | 5 | @Component({ 6 | selector: 'app', 7 | templateUrl: 'app.html' 8 | }) 9 | export class AppComponent { 10 | isFocused = true; 11 | sins = ['gluttony', 'greed', 'sloth', 'envy', 'wrath', 'pride', 'lust']; 12 | 13 | form = new FormGroup({ 14 | sin: new FormControl(), 15 | name: new FormControl() 16 | }); 17 | 18 | constructor(private modalService: NgbModal) {} 19 | 20 | open(content) { 21 | this.modalService.open(content).result.then( 22 | result => { 23 | console.log(`Closed with: ${result}`); 24 | }, 25 | reason => { 26 | console.log(`Dismissed ${reason}`); 27 | } 28 | ); 29 | } 30 | 31 | toggleFocus() { 32 | this.isFocused = !this.isFocused; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/app.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Using angumar forms

4 |
5 | 10 | 14 | 15 |
16 | 17 |
Form value: {{ form.value | json }}
18 |
19 | 20 |
21 |

Using bootstrap modal

22 | 23 | 24 |
25 |

Modal title

26 | 29 |
30 |
31 | 35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 |
43 |
44 | -------------------------------------------------------------------------------- /docs/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { AppComponent } from './app.component'; 4 | import { FocusModule } from '../index'; 5 | import { ReactiveFormsModule } from '@angular/forms'; 6 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | BrowserModule, 11 | FocusModule.forRoot(), 12 | ReactiveFormsModule, 13 | NgbModule.forRoot() 14 | ], 15 | declarations: [AppComponent], 16 | bootstrap: [AppComponent] 17 | }) 18 | export class AppModule {} 19 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | angular2-focus 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/main.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/es7/reflect'; 2 | import 'zone.js/dist/zone'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { AppModule } from './app.module'; 5 | import { enableProdMode } from '@angular/core'; 6 | enableProdMode(); 7 | const platform = platformBrowserDynamic(); 8 | platform.bootstrapModule(AppModule); 9 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "sourceMap": true, 8 | "inlineSources": true, 9 | "moduleResolution": "node", 10 | "declaration": true, 11 | "lib": ["es2017", "dom"] 12 | }, 13 | "files": [ 14 | "./main.ts", 15 | "./app.component.ts", 16 | "./app.module.ts" 17 | ], 18 | "exclude": [ 19 | "node_modules" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/focus.module'; 2 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./config/karma.conf.js'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-focus", 3 | "version": "0.0.0-development", 4 | "description": "Angular attribute directive that gives focus on an element depending on a given expression", 5 | "main": "dist/bundles/angular2-focus.umd.js", 6 | "module": "dist/angular2-focus.es5.js", 7 | "es2015": "dist/angular2-focus.js", 8 | "typings": "dist/angular2-focus.d.ts", 9 | "scripts": { 10 | "test": "karma start", 11 | "lint": "tslint -c tslint.json 'index.ts src/**/*.ts docs/**/*.ts'", 12 | "start": "ngc", 13 | "build": "ng-packagr -p package.json", 14 | "prepare": "yarn build", 15 | "docs": "concurrently \"watchify docs/main.ts -p [ tsify ] -o docs/bundle.js\" \"lite-server\"", 16 | "semantic-release": "semantic-release", 17 | "commit": "git-cz", 18 | "add-contributor": "all-contributors add", 19 | "generate-contributors": "all-contributors generate", 20 | "precommit": "lint-staged", 21 | "travis-deploy-once": "travis-deploy-once" 22 | }, 23 | "keywords": [ 24 | "angular", 25 | "angular2", 26 | "@angular", 27 | "focus" 28 | ], 29 | "author": "Spyros Ioakeimidis (http://www.spyros.io)", 30 | "license": "MIT", 31 | "devDependencies": { 32 | "@angular/animations": "^6.1.1", 33 | "@angular/common": "^6.1.1", 34 | "@angular/compiler": "^6.1.1", 35 | "@angular/compiler-cli": "^6.1.1", 36 | "@angular/core": "^6.1.1", 37 | "@angular/forms": "^6.1.1", 38 | "@angular/platform-browser": "^6.1.1", 39 | "@angular/platform-browser-dynamic": "^6.1.1", 40 | "@angular/platform-server": "^6.1.1", 41 | "@ng-bootstrap/ng-bootstrap": "^2.2.0", 42 | "@types/core-js": "^2.5.0", 43 | "@types/jasmine": "^2.5.47", 44 | "all-contributors-cli": "^4.3.0", 45 | "awesome-typescript-loader": "^3.1.3", 46 | "browserify": "^14.3.0", 47 | "commitizen": "^2.9.6", 48 | "concurrently": "^3.5.0", 49 | "core-js": "^2.4.1", 50 | "cz-conventional-changelog": "^2.0.0", 51 | "husky": "^0.14.0", 52 | "jasmine-core": "^2.6.1", 53 | "karma": "^1.7.0", 54 | "karma-jasmine": "^1.1.0", 55 | "karma-phantomjs-launcher": "^1.0.4", 56 | "karma-sourcemap-loader": "^0.3.7", 57 | "karma-webpack": "^2.0.3", 58 | "lint-staged": "^7.2.0", 59 | "lite-server": "^2.3.0", 60 | "ng-packagr": "^1.6.0", 61 | "null-loader": "^0.1.1", 62 | "prettier": "^1.4.2", 63 | "rxjs": "^6.2.2", 64 | "semantic-release": "^15.7.1", 65 | "travis-deploy-once": "^5.0.1", 66 | "tsify": "^3.0.1", 67 | "tslint": "^5.0.0", 68 | "typescript": "~2.9.2", 69 | "wallaby-webpack": "^3.9.4", 70 | "watchify": "^3.7.0", 71 | "webpack": "^3.0.0", 72 | "zone.js": "^0.8.26" 73 | }, 74 | "repository": { 75 | "type": "git", 76 | "url": "https://github.com/spirosikmd/angular2-focus" 77 | }, 78 | "config": { 79 | "commitizen": { 80 | "path": "node_modules/cz-conventional-changelog" 81 | } 82 | }, 83 | "lint-staged": { 84 | "*.ts": [ 85 | "prettier --single-quote --write", 86 | "git add" 87 | ] 88 | }, 89 | "ngPackage": { 90 | "lib": { 91 | "entryFile": "index.ts" 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/focus.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { FocusDirective } from './focus.directive'; 2 | import { ElementRef } from '@angular/core'; 3 | 4 | describe('focus', function() { 5 | beforeEach(function() { 6 | this.elementRef = { 7 | nativeElement: jasmine.createSpyObj('nativeElement', ['focus']) 8 | }; 9 | this.directive = new FocusDirective(this.elementRef); 10 | }); 11 | 12 | describe('ngOnInit', function() { 13 | beforeEach(function() { 14 | spyOn(this.directive, 'focusElement'); 15 | }); 16 | 17 | it('does not call focusElement when focus is false', function() { 18 | this.directive.ngOnInit(); 19 | expect(this.directive.focusElement).not.toHaveBeenCalled(); 20 | }); 21 | 22 | it('calls focusElement when focus is true', function() { 23 | this.directive.focus = true; 24 | this.directive.ngOnInit(); 25 | expect(this.directive.focusElement).toHaveBeenCalled(); 26 | }); 27 | }); 28 | 29 | describe('ngOnChanges', function() { 30 | beforeEach(function() { 31 | spyOn(this.directive, 'focusElement'); 32 | }); 33 | 34 | it('calls focusElement when focus current value is true and previous is false', function() { 35 | this.directive.ngOnChanges({ 36 | focus: { 37 | previousValue: false, 38 | currentValue: true 39 | } 40 | }); 41 | expect(this.directive.focusElement).toHaveBeenCalled(); 42 | }); 43 | 44 | it('does not call focusElement when focus current value is false', function() { 45 | this.directive.ngOnChanges({ 46 | focus: { 47 | previousValue: true, 48 | currentValue: false 49 | } 50 | }); 51 | expect(this.directive.focusElement).not.toHaveBeenCalled(); 52 | }); 53 | 54 | it('does not call focusElement when focus current value is same as previous value', function() { 55 | this.directive.ngOnChanges({ 56 | focus: { 57 | previousValue: true, 58 | currentValue: true 59 | } 60 | }); 61 | expect(this.directive.focusElement).not.toHaveBeenCalled(); 62 | }); 63 | }); 64 | 65 | describe('focusElement', function() { 66 | it('calls focus on element ', function() { 67 | this.directive.focusElement(); 68 | expect(this.elementRef.nativeElement.focus).toHaveBeenCalled(); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /src/focus.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, 4 | Input, 5 | SimpleChanges, 6 | OnInit, 7 | OnChanges 8 | } from '@angular/core'; 9 | 10 | @Directive({ 11 | selector: '[focus]' 12 | }) 13 | export class FocusDirective implements OnInit, OnChanges { 14 | @Input() focus: boolean; 15 | private element: HTMLElement; 16 | 17 | constructor($element: ElementRef) { 18 | this.element = $element.nativeElement; 19 | } 20 | 21 | ngOnInit(): void { 22 | if (this.focus) { 23 | this.focusElement(); 24 | } 25 | } 26 | 27 | ngOnChanges(changes: SimpleChanges): void { 28 | const focus = changes.focus; 29 | if ( 30 | focus.currentValue !== focus.previousValue && 31 | focus.currentValue === true 32 | ) { 33 | this.focusElement(); 34 | } 35 | } 36 | 37 | focusElement(): void { 38 | this.element.focus(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/focus.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { FocusDirective } from './focus.directive'; 3 | 4 | @NgModule({ 5 | declarations: [FocusDirective], 6 | exports: [FocusDirective] 7 | }) 8 | export class FocusModule { 9 | static forRoot(): ModuleWithProviders { 10 | return { 11 | ngModule: FocusModule 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "sourceMap": true, 8 | "inlineSources": true, 9 | "moduleResolution": "node", 10 | "declaration": true, 11 | "lib": ["es2017", "dom"] 12 | }, 13 | "files": [ 14 | "index.ts" 15 | ], 16 | "exclude": [ 17 | "node_modules" 18 | ], 19 | "angularCompilerOptions": { 20 | "strictMetadataEmit": true, 21 | "skipTemplateCodegen": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-internal-module": true, 15 | "no-trailing-whitespace": true, 16 | "no-unsafe-finally": true, 17 | "no-var-keyword": true, 18 | "one-line": [ 19 | true, 20 | "check-open-brace", 21 | "check-whitespace" 22 | ], 23 | "quotemark": [ 24 | true, 25 | "single" 26 | ], 27 | "semicolon": [ 28 | true, 29 | "always" 30 | ], 31 | "triple-equals": [ 32 | true, 33 | "allow-null-check" 34 | ], 35 | "typedef-whitespace": [ 36 | true, 37 | { 38 | "call-signature": "nospace", 39 | "index-signature": "nospace", 40 | "parameter": "nospace", 41 | "property-declaration": "nospace", 42 | "variable-declaration": "nospace" 43 | } 44 | ], 45 | "variable-name": [ 46 | true, 47 | "ban-keywords" 48 | ], 49 | "whitespace": [ 50 | true, 51 | "check-branch", 52 | "check-decl", 53 | "check-operator", 54 | "check-separator", 55 | "check-type" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | const wallabyWebpack = require('wallaby-webpack'); 2 | 3 | // if you use the webpack defined variable ENV in any components 4 | const DefinePlugin = require('webpack/lib/DefinePlugin'); 5 | const ENV = process.env.ENV = process.env.NODE_ENV = 'test'; 6 | 7 | const webpackPostprocessor = wallabyWebpack({ 8 | entryPatterns: [ 9 | 'config/karma-test-shim.js', 10 | 'src/**/*spec.js', 11 | ], 12 | 13 | module: { 14 | rules: [ 15 | {test: /\.json$/, loader: 'json-loader'}, 16 | {test: /karma-require/, loader: 'null-loader'}, 17 | ] 18 | }, 19 | plugins: [ 20 | new DefinePlugin({ 21 | 'ENV': JSON.stringify(ENV) 22 | }) 23 | ] 24 | }); 25 | 26 | module.exports = function() { 27 | 28 | return { 29 | files: [ 30 | {pattern: 'config/karma-test-shim.js', load: false}, 31 | {pattern: 'config/karma-require.js', load: false}, 32 | {pattern: 'src/**/*.ts', load: false}, 33 | {pattern: 'src/**/*.json', load: false}, 34 | {pattern: 'src/**/*spec.ts', ignore: true}, 35 | {pattern: 'src/**/*.d.ts', ignore: true} 36 | ], 37 | 38 | tests: [ 39 | {pattern: 'src/**/*spec.ts', load: false}, 40 | ], 41 | 42 | testFramework: 'jasmine', 43 | 44 | postprocessor: webpackPostprocessor, 45 | 46 | setup: function() { 47 | window.__moduleBundler.loadTests(); 48 | }, 49 | 50 | debug: true 51 | }; 52 | }; 53 | --------------------------------------------------------------------------------