├── .github
└── FUNDING.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── .yo-rc.json
├── LICENSE
├── README.md
├── bs-config.json
├── gulpfile.js
├── package.json
├── src
├── components
│ └── select
│ │ ├── select.component.html
│ │ ├── select.component.scss
│ │ └── select.component.ts
├── enums
│ └── key-code.enum.ts
├── index.ts
├── package.json
├── tsconfig.es5.json
└── tsconfig.spec.json
├── tools
└── gulp
│ └── inline-resources.js
├── tsconfig.json
└── tslint.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [Jaspero]
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node
2 | node_modules/*
3 | npm-debug.log
4 |
5 | # TypeScript
6 | src/*.js
7 | src/*.map
8 | src/*.d.ts
9 |
10 | # JetBrains
11 | .idea
12 | .project
13 | .settings
14 | .idea/*
15 | *.iml
16 |
17 | # VS Code
18 | .vscode/*
19 |
20 | # Windows
21 | Thumbs.db
22 | Desktop.ini
23 |
24 | # Mac
25 | .DS_Store
26 | **/.DS_Store
27 |
28 | # Ngc generated files
29 | **/*.ngfactory.ts
30 |
31 | # Build files
32 | dist/*
33 |
34 | # Playground tmp files
35 | .playground
36 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Node
2 | node_modules/*
3 | npm-debug.log
4 | docs/*
5 | # DO NOT IGNORE TYPESCRIPT FILES FOR NPM
6 | # TypeScript
7 | # *.js
8 | # *.map
9 | # *.d.ts
10 |
11 | # JetBrains
12 | .idea
13 | .project
14 | .settings
15 | .idea/*
16 | *.iml
17 |
18 | # VS Code
19 | .vscode/*
20 |
21 | # Windows
22 | Thumbs.db
23 | Desktop.ini
24 |
25 | # Mac
26 | .DS_Store
27 | **/.DS_Store
28 |
29 | # Ngc generated files
30 | **/*.ngfactory.ts
31 |
32 | # Library files
33 | src/*
34 | build/*
35 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: required
3 | os:
4 | - linux
5 | node_js:
6 | - '8.9.1'
7 | install:
8 | - yarn install
9 | script:
10 | - npm run build
11 |
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-angular2-library": {
3 | "promptValues": {
4 | "gitRepositoryUrl": "https://github.com/Jaspero/ng2-confirmations"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Jaspero co.
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 all
13 | 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 THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/jaspero/ng-select)
2 | [](https://www.npmjs.com/package/@jaspero/ng-select)
3 | # NG2 Select
4 | A select library for Angular, with single and multiple functionality.
5 | ```
6 | npm install --save @jaspero/ng-select
7 | ```
8 | A demo can be found [here](https://jaspero.co/resources/projects/ng-select)
9 |
10 | ## Setup
11 | Import `JasperoSelectModule` in your `@NgModule`:
12 |
13 | ```ts
14 | @NgModule({
15 | imports: [
16 | JasperoSelectModule
17 | ],
18 | declarations: [AppComponent],
19 | bootstrap: [AppComponent]
20 | })
21 | export class AppModule {}
22 | ```
23 |
24 | ## How To Use
25 | To use the library simply add the component in your templates:
26 | ```typescript
27 |
28 | ```
29 |
30 | ## Options
31 |
32 | You can pass the following inputs to the component:
33 |
34 | |Name|Type|Description|Default|
35 | |---|---|---|---|
36 | |options|An array of options for the select dropdown|any[]|[]|
37 | |key|The key from the options object to be used as the name of the option|string|name|
38 | |multi|Should more options be selectable at once|boolean|false|
39 |
40 | This component also support template driven and reactive forms.
41 |
42 |
--------------------------------------------------------------------------------
/bs-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": {
3 | "baseDir": "src",
4 | "routes": {
5 | "/": "playground",
6 | "/node_modules/": "node_modules",
7 | "/dist/": "dist",
8 | "/.playground": ".playground"
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var gulp = require('gulp'),
3 | path = require('path'),
4 | ngc = require('@angular/compiler-cli/src/main').main,
5 | rollup = require('gulp-rollup'),
6 | rename = require('gulp-rename'),
7 | del = require('del'),
8 | runSequence = require('run-sequence'),
9 | inlineResources = require('./tools/gulp/inline-resources');
10 |
11 | const rootFolder = path.join(__dirname);
12 | const srcFolder = path.join(rootFolder, 'src');
13 | const tmpFolder = path.join(rootFolder, '.tmp');
14 | const buildFolder = path.join(rootFolder, 'build');
15 | const distFolder = path.join(rootFolder, 'dist');
16 |
17 | /**
18 | * 1. Delete /dist folder
19 | */
20 | gulp.task('clean:dist', function () {
21 |
22 | // Delete contents but not dist folder to avoid broken npm links
23 | // when dist directory is removed while npm link references it.
24 | return deleteFolders([distFolder + '/**', '!' + distFolder]);
25 | });
26 |
27 | /**
28 | * 2. Clone the /src folder into /.tmp. If an npm link inside /src has been made,
29 | * then it's likely that a node_modules folder exists. Ignore this folder
30 | * when copying to /.tmp.
31 | */
32 | gulp.task('copy:source', function () {
33 | return gulp.src([`${srcFolder}/**/*`, `!${srcFolder}/node_modules`])
34 | .pipe(gulp.dest(tmpFolder));
35 | });
36 |
37 | /**
38 | * 3. Inline template (.html) and style (.css) files into the the component .ts files.
39 | * We do this on the /.tmp folder to avoid editing the original /src files
40 | */
41 | gulp.task('inline-resources', function () {
42 | return Promise.resolve()
43 | .then(() => inlineResources(tmpFolder));
44 | });
45 |
46 |
47 | /**
48 | * 4. Run the Angular compiler, ngc, on the /.tmp folder. This will output all
49 | * compiled modules to the /build folder.
50 | */
51 | gulp.task('ngc', function () {
52 | ngc([ '--project', `${tmpFolder}/tsconfig.es5.json` ]);
53 | return Promise.resolve()
54 | });
55 |
56 | /**
57 | * 5. Run rollup inside the /build folder to generate our Flat ES module and place the
58 | * generated file into the /dist folder
59 | */
60 | gulp.task('rollup:fesm', function () {
61 | return gulp.src(`${buildFolder}/**/*.js`)
62 | // transform the files here.
63 | .pipe(rollup({
64 |
65 | // Bundle's entry point
66 | // See "input" in https://rollupjs.org/#core-functionality
67 | input: `${buildFolder}/index.js`,
68 |
69 | // Allow mixing of hypothetical and actual files. "Actual" files can be files
70 | // accessed by Rollup or produced by plugins further down the chain.
71 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system
72 | // when subdirectories are used in the `src` directory.
73 | allowRealFiles: true,
74 |
75 | // A list of IDs of modules that should remain external to the bundle
76 | // See "external" in https://rollupjs.org/#core-functionality
77 | external: [
78 | '@angular/core',
79 | '@angular/common'
80 | ],
81 |
82 | // Format of generated bundle
83 | // See "format" in https://rollupjs.org/#core-functionality
84 | format: 'es'
85 | }))
86 | .pipe(gulp.dest(distFolder));
87 | });
88 |
89 | /**
90 | * 6. Run rollup inside the /build folder to generate our UMD module and place the
91 | * generated file into the /dist folder
92 | */
93 | gulp.task('rollup:umd', function () {
94 | return gulp.src(`${buildFolder}/**/*.js`)
95 | // transform the files here.
96 | .pipe(rollup({
97 |
98 | // Bundle's entry point
99 | // See "input" in https://rollupjs.org/#core-functionality
100 | input: `${buildFolder}/index.js`,
101 |
102 | // Allow mixing of hypothetical and actual files. "Actual" files can be files
103 | // accessed by Rollup or produced by plugins further down the chain.
104 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system
105 | // when subdirectories are used in the `src` directory.
106 | allowRealFiles: true,
107 |
108 | // A list of IDs of modules that should remain external to the bundle
109 | // See "external" in https://rollupjs.org/#core-functionality
110 | external: [
111 | '@angular/core',
112 | '@angular/common'
113 | ],
114 |
115 | // Format of generated bundle
116 | // See "format" in https://rollupjs.org/#core-functionality
117 | format: 'umd',
118 |
119 | // Export mode to use
120 | // See "exports" in https://rollupjs.org/#danger-zone
121 | exports: 'named',
122 |
123 | // The name to use for the module for UMD/IIFE bundles
124 | // (required for bundles with exports)
125 | // See "name" in https://rollupjs.org/#core-functionality
126 | name: 'ng-select',
127 |
128 | // See "globals" in https://rollupjs.org/#core-functionality
129 | globals: {
130 | typescript: 'ts'
131 | }
132 |
133 | }))
134 | .pipe(rename('ng-select.umd.js'))
135 | .pipe(gulp.dest(distFolder));
136 | });
137 |
138 | /**
139 | * 7. Copy all the files from /build to /dist, except .js files. We ignore all .js from /build
140 | * because with don't need individual modules anymore, just the Flat ES module generated
141 | * on step 5.
142 | */
143 | gulp.task('copy:build', function () {
144 | return gulp.src([`${buildFolder}/**/*`, `!${buildFolder}/**/*.js`])
145 | .pipe(gulp.dest(distFolder));
146 | });
147 |
148 | /**
149 | * 8. Copy package.json from /src to /dist
150 | */
151 | gulp.task('copy:manifest', function () {
152 | return gulp.src([`${srcFolder}/package.json`])
153 | .pipe(gulp.dest(distFolder));
154 | });
155 |
156 | /**
157 | * 9. Copy README.md from / to /dist
158 | */
159 | gulp.task('copy:readme', function () {
160 | return gulp.src([path.join(rootFolder, 'README.MD')])
161 | .pipe(gulp.dest(distFolder));
162 | });
163 |
164 | /**
165 | * 10. Delete /.tmp folder
166 | */
167 | gulp.task('clean:tmp', function () {
168 | return deleteFolders([tmpFolder]);
169 | });
170 |
171 | /**
172 | * 11. Delete /build folder
173 | */
174 | gulp.task('clean:build', function () {
175 | return deleteFolders([buildFolder]);
176 | });
177 |
178 | gulp.task('compile', function () {
179 | runSequence(
180 | 'clean:dist',
181 | 'copy:source',
182 | 'inline-resources',
183 | 'ngc',
184 | 'rollup:fesm',
185 | 'rollup:umd',
186 | 'copy:build',
187 | 'copy:manifest',
188 | 'copy:readme',
189 | 'clean:build',
190 | 'clean:tmp',
191 | function (err) {
192 | if (err) {
193 | console.log('ERROR:', err.message);
194 | deleteFolders([distFolder, tmpFolder, buildFolder]);
195 | } else {
196 | console.log('Compilation finished succesfully');
197 | }
198 | });
199 | });
200 |
201 | /**
202 | * Watch for any change in the /src folder and compile files
203 | */
204 | gulp.task('watch', function () {
205 | gulp.watch(`${srcFolder}/**/*`, ['compile']);
206 | });
207 |
208 | gulp.task('clean', ['clean:dist', 'clean:tmp', 'clean:build']);
209 |
210 | gulp.task('build', ['clean', 'compile']);
211 | gulp.task('build:watch', ['build', 'watch']);
212 | gulp.task('default', ['build:watch']);
213 |
214 | /**
215 | * Deletes the specified folder
216 | */
217 | function deleteFolders(folders) {
218 | return del(folders);
219 | }
220 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jaspero/ng-select",
3 | "version": "0.2.0",
4 | "description": "A select library for Angular, with single and multiple select functionality.",
5 | "scripts": {
6 | "build": "gulp build",
7 | "build:watch": "gulp",
8 | "docs": "npm run docs:build",
9 | "docs:build": "compodoc -p tsconfig.json -n ng-select -d docs --hideGenerator",
10 | "docs:serve": "npm run docs:build -- -s",
11 | "docs:watch": "npm run docs:build -- -s -w",
12 | "lint": "tslint --type-check --project tsconfig.json src/**/*.ts",
13 | "publish": "npm run build && npm publish dist"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/Jaspero/ng-select"
18 | },
19 | "author": {
20 | "name": "Jaspero co.",
21 | "email": "info@jaspero.co"
22 | },
23 | "keywords": [
24 | "angular",
25 | "select"
26 | ],
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/Jaspero/ng-select/issues"
30 | },
31 | "devDependencies": {
32 | "@angular/common": "5.0.0",
33 | "@angular/forms": "5.0.0",
34 | "@angular/compiler": "5.0.0",
35 | "@angular/compiler-cli": "5.0.0",
36 | "@angular/core": "5.0.0",
37 | "@angular/platform-browser": "5.0.0",
38 | "@angular/platform-browser-dynamic": "5.0.0",
39 | "@compodoc/compodoc": "1.0.4",
40 | "@types/jasmine": "2.5.38",
41 | "@types/node": "6.0.60",
42 | "angular-in-memory-web-api": "0.5.1",
43 | "codelyzer": "2.0.0",
44 | "concurrently": "3.4.0",
45 | "core-js": "2.4.1",
46 | "del": "2.2.2",
47 | "gulp": "3.9.1",
48 | "gulp-rename": "1.2.2",
49 | "gulp-rollup": "2.15.0",
50 | "jasmine-core": "2.5.2",
51 | "jasmine-spec-reporter": "3.2.0",
52 | "karma": "1.4.1",
53 | "karma-chrome-launcher": "2.0.0",
54 | "karma-cli": "1.0.1",
55 | "karma-coverage-istanbul-reporter": "0.2.0",
56 | "karma-jasmine": "1.1.0",
57 | "karma-jasmine-html-reporter": "0.2.2",
58 | "lite-server": "2.3.0",
59 | "node-sass": "4.5.2",
60 | "node-sass-tilde-importer": "1.0.0",
61 | "node-watch": "0.5.2",
62 | "protractor": "5.1.0",
63 | "rollup": "0.49.3",
64 | "run-sequence": "1.2.2",
65 | "rxjs": "5.5.2",
66 | "systemjs": "0.20.12",
67 | "ts-node": "2.0.0",
68 | "tslint": "4.5.0",
69 | "typescript": "2.4.2",
70 | "zone.js": "0.8.14"
71 | },
72 | "engines": {
73 | "node": ">=6.0.0"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/components/select/select.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{item[key]}}
15 |
16 |
17 |
18 | {{selected[key]}}
19 |
20 |
21 |
27 |
28 |
41 |
--------------------------------------------------------------------------------
/src/components/select/select.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | .items {
3 | font-size: 14px;
4 | font-family:inherit;
5 | box-sizing: border-box;
6 | border: none;
7 | outline: none;
8 | background: transparent;
9 | width: 100%;
10 | padding: 0 10px;
11 | line-height: 40px;
12 | height: 40px;
13 | position: relative;
14 | box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12);
15 |
16 | &:after {
17 | position: absolute;
18 | top: 50%;
19 | right: 15px;
20 | display: block;
21 | width: 0;
22 | height: 0;
23 | border-color: #808080 transparent transparent transparent;
24 | border-style: solid;
25 | border-width: 5px 5px 0 5px;
26 | content: '';
27 | -webkit-transform: translateY(-50%);
28 | -moz-transform: translateY(-50%);
29 | -ms-transform: translateY(-50%);
30 | -o-transform: translateY(-50%);
31 | transform: translateY(-50%);
32 | }
33 |
34 | .item {
35 | float: left;
36 | margin: 0 3px 3px 0;
37 | cursor: pointer;
38 | display: inline-block;
39 | vertical-align: baseline;
40 | zoom: 1;
41 | }
42 |
43 | &.multi {
44 | .item {
45 | cursor: default;
46 | border-radius: 16px;
47 | display: block;
48 | height: 30px;
49 | line-height: 30px;
50 | margin: 5px 8px 0 0;
51 | padding: 0 12px;
52 | float: left;
53 | box-sizing: border-box;
54 | max-width: 100%;
55 | position: relative;
56 | background: rgb(224,224,224);
57 | color: rgb(66,66,66);
58 | }
59 | }
60 | }
61 |
62 | input[type="text"] {
63 | background: none;
64 | border: none;
65 | outline: none;
66 | float: left;
67 | min-height: 20px;
68 | vertical-align: middle;
69 | position: relative;
70 | top: 50%;
71 | font-family:inherit;
72 | -webkit-transform: translateY(-50%);
73 | -moz-transform: translateY(-50%);
74 | -ms-transform: translateY(-50%);
75 | -o-transform: translateY(-50%);
76 | transform: translateY(-50%);
77 | box-shadow: none;
78 | padding: 0;
79 | display: inline-block;
80 | width: 100%;
81 | }
82 |
83 | .dropdown {
84 | position: absolute;
85 | z-index: 10;
86 | margin: -1px 0 0 0;
87 | background: #ffffff;
88 | border-top: 0 none;
89 | display: none;
90 | width: 100%;
91 | box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12);
92 |
93 | &.active {
94 | display: block;
95 | }
96 |
97 | .dropdown-content {
98 | max-height: 200px;
99 | overflow-x: hidden;
100 | overflow-y: auto;
101 | width: 100%;
102 |
103 | .option {
104 | padding: 8px;
105 | cursor: pointer;
106 |
107 | &.selected {
108 | background-color: #f9f9f9;
109 | }
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/components/select/select.component.ts:
--------------------------------------------------------------------------------
1 | import {DOCUMENT} from '@angular/common';
2 | import {
3 | AfterViewInit,
4 | ChangeDetectionStrategy, ChangeDetectorRef,
5 | Component,
6 | ElementRef,
7 | EventEmitter,
8 | forwardRef,
9 | Inject,
10 | Input, OnChanges,
11 | OnDestroy,
12 | OnInit,
13 | Output,
14 | Renderer2, TemplateRef,
15 | ViewChild
16 | } from '@angular/core';
17 | import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
18 | import {fromEvent} from 'rxjs/observable/fromEvent';
19 | import {filter, skip, takeUntil, withLatestFrom} from 'rxjs/operators';
20 | import {Subject} from 'rxjs/Subject';
21 | import {KeyCode} from '../../enums/key-code.enum';
22 | import {BehaviorSubject} from 'rxjs/BehaviorSubject';
23 |
24 | @Component({
25 | selector: 'jaspero-select',
26 | templateUrl: './select.component.html',
27 | styleUrls: ['./select.component.scss'],
28 | changeDetection: ChangeDetectionStrategy.OnPush,
29 | providers: [{
30 | provide: NG_VALUE_ACCESSOR,
31 | useExisting: forwardRef(() => SelectComponent),
32 | multi: true
33 | }]
34 | })
35 | export class SelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges, OnDestroy {
36 | constructor(
37 | private _renderer: Renderer2,
38 | private _cdr: ChangeDetectorRef,
39 | @Inject(DOCUMENT) private _document: any
40 | ) { }
41 |
42 | @ViewChild('input') inputEl: ElementRef;
43 |
44 | @Input() key = 'name';
45 | @Input() options: any[];
46 | @Input() multiSelect: boolean;
47 | @Input() theTabIndex: number;
48 | @Input() matchFromStart = false;
49 | @Input() returnOnlyKey = false;
50 | @Input() selected: any;
51 | @Input() selectTemplate: TemplateRef;
52 | @Input() dropDownTemplate: TemplateRef;
53 |
54 | @Output() toggled = new EventEmitter();
55 |
56 | propagateChange;
57 | onTouched;
58 |
59 | open$ = new Subject();
60 | destroyed$ = new Subject();
61 |
62 | isMulti$ = new BehaviorSubject(false);
63 | activeIndex$ = new BehaviorSubject(null);
64 | selection$ = new BehaviorSubject(null);
65 |
66 | selectToUse: Function = this.select;
67 | outputToUse: Function = this.output;
68 | removeToUse: Function = this.remove;
69 | mapMethodToUse: Function = this.output;
70 |
71 | blockEmit = false;
72 |
73 | ngOnInit() {
74 | this.open$.pipe(
75 | takeUntil(this.destroyed$)
76 | )
77 | .subscribe(value => {
78 | this.toggled.emit(value);
79 | });
80 |
81 | this.selection$.pipe(
82 | takeUntil(this.destroyed$),
83 | skip(1),
84 | filter(() => !this.blockEmit)
85 | )
86 | .subscribe(value => {
87 | this.propagateChange(this.outputToUse(value));
88 | });
89 | }
90 |
91 | ngAfterViewInit() {
92 | fromEvent(this._document.documentElement, 'click')
93 | .pipe(
94 | takeUntil(this.destroyed$)
95 | )
96 | .subscribe(() => {
97 | this.open$.next(false);
98 | });
99 |
100 | fromEvent(this.inputEl.nativeElement, 'blur')
101 | .pipe(
102 | takeUntil(this.destroyed$)
103 | )
104 | .subscribe(() => {
105 | this.onTouched();
106 | });
107 |
108 | fromEvent(this.inputEl.nativeElement, 'keyup')
109 | .pipe(
110 | takeUntil(this.destroyed$),
111 | withLatestFrom(this.open$, this.activeIndex$)
112 | )
113 | .subscribe(data => {
114 | data[0].stopPropagation();
115 |
116 | switch (data[0].keyCode) {
117 | case KeyCode.UpArrow:
118 | if (!data[1]) {
119 | this.activeIndex$.next(this.options.length - 1);
120 | this.open$.next(true);
121 | } else {
122 | this.activeIndex$.next(data[2] > 0 ? data[2] - 1 : this.options.length - 1);
123 | }
124 |
125 | break;
126 |
127 | case KeyCode.DownArrow:
128 | if (!data[1]) {
129 | this.activeIndex$.next(0);
130 | this.open$.next(true);
131 | } else {
132 | this.activeIndex$.next(data[2] < this.options.length - 1 ? data[2] + 1 : 0);
133 | }
134 |
135 | break;
136 |
137 | case KeyCode.Enter:
138 | if (data[2] === null || !this.options.length) {
139 | return;
140 | }
141 |
142 | this.open$.next(false);
143 | this.selectToUse(data[2]);
144 | this.activeIndex$.next(null);
145 |
146 | break;
147 |
148 | case KeyCode.Escape:
149 | if (data[1]) {
150 | this.activeIndex$.next(null);
151 | this.open$.next(false);
152 | }
153 | break;
154 | }
155 | });
156 | }
157 |
158 | ngOnChanges(change) {
159 |
160 | if (change.multiSelect) {
161 | this.isMulti$.next(change.multiSelect.currentValue);
162 |
163 | if (change.multiSelect.currentValue) {
164 | this.outputToUse = this.returnOnlyKey ? this.outputOnlyKeyMulti : this.output;
165 | this.removeToUse = this.removeMulti;
166 | this.selectToUse = this.selectMulti;
167 | } else {
168 | this.outputToUse = this.returnOnlyKey ? this.outputOnlyKey : this.output;
169 | this.removeToUse = this.remove;
170 | this.selectToUse = this.select;
171 | }
172 | }
173 |
174 | if (change.returnOnlyKey) {
175 | const multiCurrent = this.isMulti$.getValue();
176 |
177 | if (change.returnOnlyKey.currentValue) {
178 |
179 | if (multiCurrent) {
180 | this.outputToUse = this.outputOnlyKeyMulti;
181 | this.mapMethodToUse = this.mapOnlyKeyMulti;
182 | } else {
183 | this.outputToUse = this.outputOnlyKey;
184 | this.mapMethodToUse = this.mapOnlyKey;
185 | }
186 |
187 | } else {
188 | this.outputToUse = this.output;
189 | this.mapMethodToUse = this.output;
190 | }
191 | }
192 |
193 | if (change.options) {
194 |
195 | }
196 |
197 | console.log('the change', change);
198 | }
199 |
200 | ngOnDestroy() {
201 | this.destroyed$.next();
202 | }
203 |
204 | setObservable(name: string, value: any) {
205 | this[name].next(value);
206 | }
207 |
208 | open(event: KeyboardEvent) {
209 | event.stopPropagation();
210 | this.inputEl.nativeElement.focus();
211 | this.open$.next(true);
212 | }
213 |
214 | output(value: any) {
215 | return value;
216 | }
217 |
218 | outputOnlyKey(value: any) {
219 | return value[this.key];
220 | }
221 |
222 | outputOnlyKeyMulti(value: any[]) {
223 | return value.map(val => val[this.key]);
224 | }
225 |
226 | mapOnlyKey(value: any) {
227 | return this.options.find(option => option[this.key] === value);
228 | }
229 |
230 | mapOnlyKeyMulti(value: any) {
231 | return value.map(val => {
232 | return this.options.find(option => option[this.key] === val)
233 | });
234 | }
235 |
236 | select(index: number) {
237 | this.selection$.next(this.options[index]);
238 | }
239 |
240 | selectMulti(value: any) {}
241 |
242 | remove() {
243 | this.selection$.next(null);
244 | }
245 |
246 | removeMulti(value: any) {}
247 |
248 | writeValue(value: any) {
249 | this.blockEmit = true;
250 | this.selection$.next(this.mapMethodToUse(value));
251 | this.blockEmit = false;
252 | }
253 |
254 | registerOnChange(fn) {
255 | this.propagateChange = fn;
256 | }
257 |
258 | setDisabledState(isDisabled) {
259 | this._renderer.setProperty(this.inputEl.nativeElement, 'disabled', isDisabled);
260 | this.open$.next(false);
261 | }
262 |
263 | registerOnTouched(fn) {
264 | this.onTouched = fn;
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/src/enums/key-code.enum.ts:
--------------------------------------------------------------------------------
1 | export enum KeyCode {
2 | Backspace = 8,
3 | Tab = 9,
4 | Enter = 13,
5 | Shift = 16,
6 | Ctrl = 17,
7 | Alt = 18,
8 | PauseBreak = 19,
9 | CapsLock = 20,
10 | Escape = 27,
11 | Space = 32,
12 | PageUp = 33,
13 | PageDown = 34,
14 | End = 35,
15 | Home = 36,
16 |
17 | LeftArrow = 37,
18 | UpArrow = 38,
19 | RightArrow = 39,
20 | DownArrow = 40,
21 |
22 | Insert = 45,
23 | Delete = 46,
24 |
25 | Zero = 48,
26 | ClosedParen = Zero,
27 | One = 49,
28 | ExclamationMark = One,
29 | Two = 50,
30 | AtSign = Two,
31 | Three = 51,
32 | PoundSign = Three,
33 | Hash = PoundSign,
34 | Four = 52,
35 | DollarSign = Four,
36 | Five = 53,
37 | PercentSign = Five,
38 | Six = 54,
39 | Caret = Six,
40 | Hat = Caret,
41 | Seven = 55,
42 | Ampersand = Seven,
43 | Eight = 56,
44 | Star = Eight,
45 | Asterik = Star,
46 | Nine = 57,
47 | OpenParen = Nine,
48 |
49 | A = 65,
50 | B = 66,
51 | C = 67,
52 | D = 68,
53 | E = 69,
54 | F = 70,
55 | G = 71,
56 | H = 72,
57 | I = 73,
58 | J = 74,
59 | K = 75,
60 | L = 76,
61 | M = 77,
62 | N = 78,
63 | O = 79,
64 | P = 80,
65 | Q = 81,
66 | R = 82,
67 | S = 83,
68 | T = 84,
69 | U = 85,
70 | V = 86,
71 | W = 87,
72 | X = 88,
73 | Y = 89,
74 | Z = 90,
75 |
76 | LeftWindowKey = 91,
77 | RightWindowKey = 92,
78 | SelectKey = 93,
79 |
80 | Numpad0 = 96,
81 | Numpad1 = 97,
82 | Numpad2 = 98,
83 | Numpad3 = 99,
84 | Numpad4 = 100,
85 | Numpad5 = 101,
86 | Numpad6 = 102,
87 | Numpad7 = 103,
88 | Numpad8 = 104,
89 | Numpad9 = 105,
90 |
91 | Multiply = 106,
92 | Add = 107,
93 | Subtract = 109,
94 | DecimalPoint = 110,
95 | Divide = 111,
96 |
97 | F1 = 112,
98 | F2 = 113,
99 | F3 = 114,
100 | F4 = 115,
101 | F5 = 116,
102 | F6 = 117,
103 | F7 = 118,
104 | F8 = 119,
105 | F9 = 120,
106 | F10 = 121,
107 | F11 = 122,
108 | F12 = 123,
109 |
110 | NumLock = 144,
111 | ScrollLock = 145,
112 |
113 | SemiColon = 186,
114 | Equals = 187,
115 | Comma = 188,
116 | Dash = 189,
117 | Period = 190,
118 | UnderScore = Dash,
119 | PlusSign = Equals,
120 | ForwardSlash = 191,
121 | Tilde = 192,
122 | GraveAccent = Tilde,
123 |
124 | OpenBracket = 219,
125 | ClosedBracket = 221,
126 | Quote = 222
127 | }
128 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {CommonModule} from '@angular/common';
3 | import {FormsModule} from '@angular/forms';
4 | import {SelectComponent} from './components/select/select.component';
5 |
6 | export * from './components/select/select.component';
7 |
8 | @NgModule({
9 | imports: [CommonModule, FormsModule],
10 | declarations: [SelectComponent],
11 | exports: [SelectComponent]
12 | })
13 | export class JasperoSelectModule {}
14 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jaspero/ng-select",
3 | "version": "0.2.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/Jaspero/ng-select"
7 | },
8 | "author": {
9 | "name": "Jaspero co.",
10 | "email": "info@jaspero.co"
11 | },
12 | "keywords": [
13 | "angular",
14 | "select"
15 | ],
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/Jaspero/ng-select/issues"
19 | },
20 | "main": "ng-select.umd.js",
21 | "module": "ng-select.js",
22 | "jsnext:main": "ng-select.js",
23 | "typings": "ng-select.d.ts",
24 | "peerDependencies": {
25 | "@angular/common": "^5.0.0",
26 | "@angular/forms": "^5.0.0",
27 | "@angular/core": "^5.0.0",
28 | "rxjs": "^5.1.0",
29 | "zone.js": "^0.8.4"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/tsconfig.es5.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "module": "es2015",
5 | "target": "es5",
6 | "baseUrl": ".",
7 | "stripInternal": true,
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "moduleResolution": "node",
11 | "outDir": "../build",
12 | "rootDir": ".",
13 | "lib": [
14 | "es2015",
15 | "dom"
16 | ],
17 | "skipLibCheck": true,
18 | "types": []
19 | },
20 | "angularCompilerOptions": {
21 | "annotateForClosureCompiler": true,
22 | "strictMetadataEmit": true,
23 | "skipTemplateCodegen": true,
24 | "flatModuleOutFile": "ng-select.js",
25 | "flatModuleId": "@jaspero/ng-select"
26 | },
27 | "files": [
28 | "./index.ts"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.es5.json",
3 | "compilerOptions": {
4 | "emitDecoratorMetadata": true,
5 | "experimentalDecorators": true,
6 | "outDir": "../out-tsc/spec",
7 | "module": "commonjs",
8 | "target": "es6",
9 | "baseUrl": "",
10 | "types": [
11 | "jest",
12 | "node"
13 | ]
14 | },
15 | "files": [
16 | "**/*.spec.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/tools/gulp/inline-resources.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // https://github.com/filipesilva/angular-quickstart-lib/blob/master/inline-resources.js
3 | 'use strict';
4 |
5 | const fs = require('fs');
6 | const path = require('path');
7 | const glob = require('glob');
8 | const sass = require('node-sass');
9 | const tildeImporter = require('node-sass-tilde-importer');
10 |
11 | /**
12 | * Simple Promiseify function that takes a Node API and return a version that supports promises.
13 | * We use promises instead of synchronized functions to make the process less I/O bound and
14 | * faster. It also simplifies the code.
15 | */
16 | function promiseify(fn) {
17 | return function () {
18 | const args = [].slice.call(arguments, 0);
19 | return new Promise((resolve, reject) => {
20 | fn.apply(this, args.concat([function (err, value) {
21 | if (err) {
22 | reject(err);
23 | } else {
24 | resolve(value);
25 | }
26 | }]));
27 | });
28 | };
29 | }
30 |
31 | const readFile = promiseify(fs.readFile);
32 | const writeFile = promiseify(fs.writeFile);
33 |
34 | /**
35 | * Inline resources in a tsc/ngc compilation.
36 | * @param projectPath {string} Path to the project.
37 | */
38 | function inlineResources(projectPath) {
39 |
40 | // Match only TypeScript files in projectPath.
41 | const files = glob.sync('**/*.ts', {cwd: projectPath});
42 |
43 | // For each file, inline the templates and styles under it and write the new file.
44 | return Promise.all(files.map(filePath => {
45 | const fullFilePath = path.join(projectPath, filePath);
46 | return readFile(fullFilePath, 'utf-8')
47 | .then(content => inlineResourcesFromString(content, url => {
48 | // Resolve the template url.
49 | return path.join(path.dirname(fullFilePath), url);
50 | }))
51 | .then(content => writeFile(fullFilePath, content))
52 | .catch(err => {
53 | console.error('An error occured: ', err);
54 | });
55 | }));
56 | }
57 |
58 | /**
59 | * Inline resources from a string content.
60 | * @param content {string} The source file's content.
61 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
62 | * @returns {string} The content with resources inlined.
63 | */
64 | function inlineResourcesFromString(content, urlResolver) {
65 | // Curry through the inlining functions.
66 | return [
67 | inlineTemplate,
68 | inlineStyle,
69 | removeModuleId
70 | ].reduce((content, fn) => fn(content, urlResolver), content);
71 | }
72 |
73 | /**
74 | * Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and
75 | * replace with `template: ...` (with the content of the file included).
76 | * @param content {string} The source file's content.
77 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
78 | * @return {string} The content with all templates inlined.
79 | */
80 | function inlineTemplate(content, urlResolver) {
81 | return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function (m, templateUrl) {
82 | const templateFile = urlResolver(templateUrl);
83 | const templateContent = fs.readFileSync(templateFile, 'utf-8');
84 | const shortenedTemplate = templateContent
85 | .replace(/([\n\r]\s*)+/gm, ' ')
86 | .replace(/"/g, '\\"');
87 | return `template: "${shortenedTemplate}"`;
88 | });
89 | }
90 |
91 |
92 | /**
93 | * Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and
94 | * replace with `styles: [...]` (with the content of the file included).
95 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
96 | * @param content {string} The source file's content.
97 | * @return {string} The content with all styles inlined.
98 | */
99 | function inlineStyle(content, urlResolver) {
100 | return content.replace(/styleUrls\s*:\s*(\[[\s\S]*?\])/gm, function (m, styleUrls) {
101 | const urls = eval(styleUrls);
102 | return 'styles: ['
103 | + urls.map(styleUrl => {
104 | const styleFile = urlResolver(styleUrl);
105 | const originContent = fs.readFileSync(styleFile, 'utf-8');
106 | const styleContent = styleFile.endsWith('.scss') ? buildSass(originContent, styleFile) : originContent;
107 | const shortenedStyle = styleContent
108 | .replace(/([\n\r]\s*)+/gm, ' ')
109 | .replace(/"/g, '\\"');
110 | return `"${shortenedStyle}"`;
111 | })
112 | .join(',\n')
113 | + ']';
114 | });
115 | }
116 |
117 | /**
118 | * build sass content to css
119 | * @param content {string} the css content
120 | * @param sourceFile {string} the scss file sourceFile
121 | * @return {string} the generated css, empty string if error occured
122 | */
123 | function buildSass(content, sourceFile) {
124 | try {
125 | const result = sass.renderSync({
126 | data: content,
127 | file: sourceFile,
128 | importer: tildeImporter
129 | });
130 | return result.css.toString()
131 | } catch (e) {
132 | console.error('\x1b[41m');
133 | console.error('at ' + sourceFile + ':' + e.line + ":" + e.column);
134 | console.error(e.formatted);
135 | console.error('\x1b[0m');
136 | return "";
137 | }
138 | }
139 |
140 | /**
141 | * Remove every mention of `moduleId: module.id`.
142 | * @param content {string} The source file's content.
143 | * @returns {string} The content with all moduleId: mentions removed.
144 | */
145 | function removeModuleId(content) {
146 | return content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, '');
147 | }
148 |
149 | module.exports = inlineResources;
150 | module.exports.inlineResourcesFromString = inlineResourcesFromString;
151 |
152 | // Run inlineResources if module is being called directly from the CLI with arguments.
153 | if (require.main === module && process.argv.length > 2) {
154 | console.log('Inlining resources from project:', process.argv[2]);
155 | return inlineResources(process.argv[2]);
156 | }
157 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./src",
4 | "experimentalDecorators": true,
5 | "moduleResolution": "node",
6 | "rootDir": "./src",
7 | "lib": [
8 | "es2015",
9 | "dom"
10 | ],
11 | "skipLibCheck": true,
12 | "types": []
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "rulesDirectory": [
4 | "node_modules/codelyzer"
5 | ],
6 | "rules": {
7 | "class-name": true,
8 | "comment-format": [
9 | true,
10 | "check-space"
11 | ],
12 | "curly": true,
13 | "eofline": true,
14 | "forin": true,
15 | "indent": [
16 | true,
17 | "spaces"
18 | ],
19 | "label-position": true,
20 | "max-line-length": false,
21 | "member-access": false,
22 | "member-ordering": false,
23 | "no-arg": true,
24 | "no-bitwise": true,
25 | "no-console": [
26 | true,
27 | "debug",
28 | "info",
29 | "time",
30 | "timeEnd",
31 | "trace"
32 | ],
33 | "no-construct": true,
34 | "no-debugger": true,
35 | "no-duplicate-variable": true,
36 | "no-empty": false,
37 | "no-eval": true,
38 | "no-inferrable-types": true,
39 | "no-shadowed-variable": true,
40 | "no-string-literal": false,
41 | "no-switch-case-fall-through": true,
42 | "no-trailing-whitespace": true,
43 | "no-unused-expression": true,
44 | "no-unused-variable": true,
45 | "no-use-before-declare": true,
46 | "no-var-keyword": true,
47 | "object-literal-sort-keys": false,
48 | "one-line": [
49 | true,
50 | "check-open-brace",
51 | "check-catch",
52 | "check-else",
53 | "check-whitespace"
54 | ],
55 | "quotemark": [
56 | true,
57 | "single"
58 | ],
59 | "radix": true,
60 | "semicolon": [
61 | "always"
62 | ],
63 | "triple-equals": [
64 | true,
65 | "allow-null-check"
66 | ],
67 | "typedef-whitespace": [
68 | true,
69 | {
70 | "call-signature": "nospace",
71 | "index-signature": "nospace",
72 | "parameter": "nospace",
73 | "property-declaration": "nospace",
74 | "variable-declaration": "nospace"
75 | }
76 | ],
77 | "variable-name": false,
78 | "whitespace": [
79 | true,
80 | "check-branch",
81 | "check-decl",
82 | "check-operator",
83 | "check-separator",
84 | "check-type"
85 | ],
86 | "directive-selector": [true, "attribute", "", "camelCase"],
87 | "component-selector": [true, "element", "", "kebab-case"],
88 | "use-input-property-decorator": true,
89 | "use-output-property-decorator": true,
90 | "use-host-property-decorator": true,
91 | "no-input-rename": true,
92 | "no-output-rename": true,
93 | "use-life-cycle-interface": true,
94 | "use-pipe-transform-interface": true,
95 | "component-class-suffix": true,
96 | "directive-class-suffix": true
97 | }
98 | }
99 |
--------------------------------------------------------------------------------