├── src
├── demo
│ ├── app
│ │ ├── examples
│ │ │ ├── tags-backend-example
│ │ │ │ ├── tags-backend-example.component.scss
│ │ │ │ ├── tags-backend-example.component.html
│ │ │ │ └── tags-backend-example.component.ts
│ │ │ ├── tags-custom-example
│ │ │ │ ├── tags-custom-example.component.scss
│ │ │ │ ├── tags-custom-example.component.html
│ │ │ │ └── tags-custom-example.component.ts
│ │ │ ├── tags-default-example
│ │ │ │ ├── tags-default-example.component.scss
│ │ │ │ ├── tags-default-example.component.html
│ │ │ │ └── tags-default-example.component.ts
│ │ │ ├── group-children-example
│ │ │ │ ├── group-children-example.component.scss
│ │ │ │ ├── group-children-example.component.html
│ │ │ │ └── group-children-example.component.ts
│ │ │ ├── group-default-example
│ │ │ │ ├── group-default-example.component.scss
│ │ │ │ ├── group-default-example.component.html
│ │ │ │ └── group-default-example.component.ts
│ │ │ ├── group-function-example
│ │ │ │ ├── group-function-example.component.scss
│ │ │ │ ├── group-function-example.component.html
│ │ │ │ └── group-function-example.component.ts
│ │ │ ├── multi-checkbox-example
│ │ │ │ ├── multi-checkbox-example.component.scss
│ │ │ │ ├── multi-checkbox-example.component.html
│ │ │ │ └── multi-checkbox-example.component.ts
│ │ │ ├── output-events-example
│ │ │ │ ├── output-events-example.component.scss
│ │ │ │ ├── output-events-example.component.html
│ │ │ │ └── output-events-example.component.ts
│ │ │ ├── search-custom-example
│ │ │ │ ├── search-custom-example.component.scss
│ │ │ │ ├── search-custom-example.component.html
│ │ │ │ └── search-custom-example.component.ts
│ │ │ ├── search-default-example
│ │ │ │ ├── search-default-example.component.scss
│ │ │ │ ├── search-default-example.component.html
│ │ │ │ └── search-default-example.component.ts
│ │ │ ├── template-label-example
│ │ │ │ ├── template-label-example.component.scss
│ │ │ │ ├── template-label-example.component.html
│ │ │ │ └── template-label-example.component.ts
│ │ │ ├── virtual-scroll-example
│ │ │ │ ├── virtual-scroll-example.component.scss
│ │ │ │ ├── virtual-scroll-example.component.html
│ │ │ │ └── virtual-scroll-example.component.ts
│ │ │ ├── bindings-custom-example
│ │ │ │ ├── bindings-custom-example.component.scss
│ │ │ │ ├── bindings-custom-example.component.html
│ │ │ │ └── bindings-custom-example.component.ts
│ │ │ ├── bindings-default-example
│ │ │ │ ├── bindings-default-example.component.scss
│ │ │ │ ├── bindings-default-example.component.html
│ │ │ │ └── bindings-default-example.component.ts
│ │ │ ├── bindings-nested-example
│ │ │ │ ├── bindings-nested-example.component.scss
│ │ │ │ ├── bindings-nested-example.component.html
│ │ │ │ └── bindings-nested-example.component.ts
│ │ │ ├── data-source-array-example
│ │ │ │ ├── data-source-array-example.component.scss
│ │ │ │ ├── data-source-array-example.component.html
│ │ │ │ └── data-source-array-example.component.ts
│ │ │ ├── dropdown-position-example
│ │ │ │ ├── dropdown-position-example.component.scss
│ │ │ │ ├── dropdown-position-example.component.html
│ │ │ │ └── dropdown-position-example.component.ts
│ │ │ ├── forms-async-data-example
│ │ │ │ ├── forms-async-data-example.component.scss
│ │ │ │ ├── forms-async-data-example.component.html
│ │ │ │ └── forms-async-data-example.component.ts
│ │ │ ├── group-selectable-example
│ │ │ │ ├── group-selectable-example.component.scss
│ │ │ │ ├── group-selectable-example.component.html
│ │ │ │ └── group-selectable-example.component.ts
│ │ │ ├── search-editable-example
│ │ │ │ ├── search-editable-example.component.scss
│ │ │ │ ├── search-editable-example.component.html
│ │ │ │ └── search-editable-example.component.ts
│ │ │ ├── template-display-example
│ │ │ │ ├── template-display-example.component.scss
│ │ │ │ ├── template-display-example.component.html
│ │ │ │ └── template-display-example.component.ts
│ │ │ ├── template-optgroup-example
│ │ │ │ ├── template-optgroup-example.component.scss
│ │ │ │ ├── template-optgroup-example.component.html
│ │ │ │ └── template-optgroup-example.component.ts
│ │ │ ├── template-option-example
│ │ │ │ ├── template-option-example.component.scss
│ │ │ │ ├── template-option-example.component.html
│ │ │ │ └── template-option-example.component.ts
│ │ │ ├── template-search-example
│ │ │ │ ├── template-search-example.component.scss
│ │ │ │ ├── template-search-example.component.html
│ │ │ │ └── template-search-example.component.ts
│ │ │ ├── data-source-backend-example
│ │ │ │ ├── data-source-backend-example.component.scss
│ │ │ │ ├── data-source-backend-example.component.html
│ │ │ │ └── data-source-backend-example.component.ts
│ │ │ ├── data-source-options-example
│ │ │ │ ├── data-source-options-example.component.scss
│ │ │ │ ├── data-source-options-example.component.html
│ │ │ │ └── data-source-options-example.component.ts
│ │ │ ├── forms-multi-select-example
│ │ │ │ ├── forms-multi-select-example.component.scss
│ │ │ │ ├── forms-multi-select-example.component.html
│ │ │ │ └── forms-multi-select-example.component.ts
│ │ │ ├── forms-single-select-example
│ │ │ │ ├── forms-single-select-example.component.scss
│ │ │ │ ├── forms-single-select-example.component.html
│ │ │ │ └── forms-single-select-example.component.ts
│ │ │ ├── forms-with-options-example
│ │ │ │ ├── forms-with-options-example.component.scss
│ │ │ │ ├── forms-with-options-example.component.ts
│ │ │ │ └── forms-with-options-example.component.html
│ │ │ ├── multi-select-custom-example
│ │ │ │ ├── multi-select-custom-example.component.scss
│ │ │ │ ├── multi-select-custom-example.component.ts
│ │ │ │ └── multi-select-custom-example.component.html
│ │ │ ├── multi-select-hidden-example
│ │ │ │ ├── multi-select-hidden-example.component.scss
│ │ │ │ ├── multi-select-hidden-example.component.html
│ │ │ │ └── multi-select-hidden-example.component.ts
│ │ │ ├── multi-select-limit-example
│ │ │ │ ├── multi-select-limit-example.component.scss
│ │ │ │ ├── multi-select-limit-example.component.html
│ │ │ │ └── multi-select-limit-example.component.ts
│ │ │ ├── search-autocomplete-example
│ │ │ │ ├── search-autocomplete-example.component.scss
│ │ │ │ ├── search-autocomplete-example.component.html
│ │ │ │ └── search-autocomplete-example.component.ts
│ │ │ ├── forms-custom-template-example
│ │ │ │ ├── forms-custom-template-example.component.scss
│ │ │ │ ├── forms-custom-template-example.component.ts
│ │ │ │ └── forms-custom-template-example.component.html
│ │ │ ├── multi-checkbox-group-example
│ │ │ │ ├── multi-checkbox-group-example.component.scss
│ │ │ │ ├── multi-checkbox-group-example.component.html
│ │ │ │ └── multi-checkbox-group-example.component.ts
│ │ │ ├── multi-select-default-example
│ │ │ │ ├── multi-select-default-example.component.scss
│ │ │ │ ├── multi-select-default-example.component.html
│ │ │ │ └── multi-select-default-example.component.ts
│ │ │ ├── multi-select-disabled-example
│ │ │ │ ├── multi-select-disabled-example.component.scss
│ │ │ │ ├── multi-select-disabled-example.component.html
│ │ │ │ └── multi-select-disabled-example.component.ts
│ │ │ ├── multi-select-template-example
│ │ │ │ ├── multi-select-template-example.component.scss
│ │ │ │ ├── multi-select-template-example.component.ts
│ │ │ │ └── multi-select-template-example.component.html
│ │ │ ├── tags-closed-dropdown-example
│ │ │ │ ├── tags-closed-dropdown-example.component.scss
│ │ │ │ ├── tags-closed-dropdown-example.component.html
│ │ │ │ └── tags-closed-dropdown-example.component.ts
│ │ │ ├── template-header-footer-example
│ │ │ │ ├── template-header-footer-example.component.scss
│ │ │ │ ├── template-header-footer-example.component.html
│ │ │ │ └── template-header-footer-example.component.ts
│ │ │ ├── group-selectable-hidden-example
│ │ │ │ ├── group-selectable-hidden-example.component.scss
│ │ │ │ ├── group-selectable-hidden-example.component.html
│ │ │ │ └── group-selectable-hidden-example.component.ts
│ │ │ ├── template-loading-example
│ │ │ │ ├── template-loading-example.component.html
│ │ │ │ ├── template-loading-example.component.ts
│ │ │ │ └── template-loading-example.component.scss
│ │ │ └── append-to-example
│ │ │ │ ├── append-to-example.component.scss
│ │ │ │ ├── append-to-example.component.ts
│ │ │ │ └── append-to-example.component.html
│ │ ├── shared
│ │ │ ├── route-viewer
│ │ │ │ ├── route-viewer.component.html
│ │ │ │ └── route-viewer.component.ts
│ │ │ └── example-viewer
│ │ │ │ ├── example-viewer.component.html
│ │ │ │ ├── stackblitz-button
│ │ │ │ ├── stackblitz-button.component.html
│ │ │ │ ├── stackblitz-button.component.ts
│ │ │ │ └── stackblitz.service.ts
│ │ │ │ └── example-viewer.component.ts
│ │ ├── app.component.html
│ │ ├── layout
│ │ │ ├── sidenav-component.ts
│ │ │ └── header.component.ts
│ │ ├── app.module.ts
│ │ ├── routes.ts
│ │ └── app.component.ts
│ ├── favicon.ico
│ ├── assets
│ │ ├── thor.png
│ │ ├── angular.png
│ │ ├── batman.png
│ │ ├── spidey.png
│ │ └── stackblitz
│ │ │ ├── polyfills.ts
│ │ │ ├── index.html
│ │ │ ├── main.ts
│ │ │ ├── styles.css
│ │ │ ├── app.module.ts
│ │ │ └── angular.json
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── style
│ │ ├── _content.scss
│ │ └── _sidebar.scss
│ ├── tsconfig.app.json
│ ├── main.ts
│ ├── .browserslistrc
│ ├── index.html
│ ├── styles.scss
│ └── polyfills.ts
├── ng-select
│ ├── themes
│ │ └── _mixins.scss
│ ├── ng-package.json
│ ├── lib
│ │ ├── console.service.ts
│ │ ├── id.ts
│ │ ├── ng-select.types.ts
│ │ ├── config.service.ts
│ │ ├── value-utils.ts
│ │ ├── ng-option.component.ts
│ │ ├── ng-select.module.ts
│ │ ├── ng-templates.directive.ts
│ │ ├── ng-dropdown-panel.service.spec.ts
│ │ ├── ng-dropdown-panel.service.ts
│ │ ├── selection-model.ts
│ │ └── ng-select.component.html
│ ├── tsconfig.spec.json
│ ├── tslint.json
│ ├── public-api.ts
│ ├── testing
│ │ ├── mocks.ts
│ │ └── helpers.ts
│ ├── tsconfig.lib.json
│ ├── test.ts
│ ├── karma.conf.js
│ └── package.json
└── ng-option-highlight
│ ├── public-api.ts
│ ├── ng-package.json
│ ├── lib
│ ├── ng-option-highlight.module.ts
│ ├── ng-option-highlight.directive.ts
│ └── ng-option-highlight.directive.spec.ts
│ ├── tslint.json
│ ├── tsconfig.spec.json
│ ├── tsconfig.lib.json
│ ├── test.ts
│ ├── package.json
│ ├── karma.conf.js
│ └── README.md
├── .gitignore
├── release.sh
├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── FUNDING.yml
├── no-response.yml
├── workflows
│ ├── ci.yml
│ ├── stale.yml
│ ├── release.yml
│ └── codeql-analysis.yml
└── config.yml
├── CONTRIBUTING.md
├── tsconfig.json
├── LICENSE
├── tslint.json
├── package.json
└── angular.json
/src/demo/app/examples/tags-backend-example/tags-backend-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-custom-example/tags-custom-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-default-example/tags-default-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-children-example/group-children-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-default-example/group-default-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-function-example/group-function-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-checkbox-example/multi-checkbox-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/output-events-example/output-events-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-custom-example/search-custom-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-default-example/search-default-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-label-example/template-label-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/virtual-scroll-example/virtual-scroll-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/bindings-custom-example/bindings-custom-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/bindings-default-example/bindings-default-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/bindings-nested-example/bindings-nested-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/data-source-array-example/data-source-array-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/dropdown-position-example/dropdown-position-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-async-data-example/forms-async-data-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-selectable-example/group-selectable-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-editable-example/search-editable-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-display-example/template-display-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-optgroup-example/template-optgroup-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-option-example/template-option-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-search-example/template-search-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/data-source-backend-example/data-source-backend-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/data-source-options-example/data-source-options-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-multi-select-example/forms-multi-select-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-single-select-example/forms-single-select-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-with-options-example/forms-with-options-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-custom-example/multi-select-custom-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-hidden-example/multi-select-hidden-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-limit-example/multi-select-limit-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-autocomplete-example/search-autocomplete-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clear/ng-select/master/src/demo/favicon.ico
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-custom-template-example/forms-custom-template-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-checkbox-group-example/multi-checkbox-group-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-default-example/multi-select-default-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-disabled-example/multi-select-disabled-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-template-example/multi-select-template-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-closed-dropdown-example/tags-closed-dropdown-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-header-footer-example/template-header-footer-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-selectable-hidden-example/group-selectable-hidden-example.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/demo/assets/thor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clear/ng-select/master/src/demo/assets/thor.png
--------------------------------------------------------------------------------
/src/demo/assets/angular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clear/ng-select/master/src/demo/assets/angular.png
--------------------------------------------------------------------------------
/src/demo/assets/batman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clear/ng-select/master/src/demo/assets/batman.png
--------------------------------------------------------------------------------
/src/demo/assets/spidey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clear/ng-select/master/src/demo/assets/spidey.png
--------------------------------------------------------------------------------
/src/demo/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/ng-select/themes/_mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin rtl {
2 | @at-root [dir="rtl"] #{&} {
3 | @content
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/demo/assets/stackblitz/polyfills.ts:
--------------------------------------------------------------------------------
1 | import 'core-js/es6/reflect';
2 | import 'core-js/es7/reflect';
3 | import 'zone.js';
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | dist
4 | out-tsc
5 | .idea
6 | *.iml
7 | .DS_Store
8 | travis_rsa
9 | travis_rsa.pub
10 | .vscode/**
11 | dist.tgz
12 |
--------------------------------------------------------------------------------
/src/demo/app/shared/route-viewer/route-viewer.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/demo/style/_content.scss:
--------------------------------------------------------------------------------
1 | .bd-title {
2 | margin-top: .5rem;
3 | margin-bottom: .5rem;
4 | font-weight: 300;
5 | }
6 |
7 | .bd-content {
8 | margin-bottom: 300px;
9 | }
10 |
--------------------------------------------------------------------------------
/src/ng-select/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/ng-select",
4 | "lib": {
5 | "entryFile": "public-api.ts"
6 | }
7 | }
--------------------------------------------------------------------------------
/src/ng-option-highlight/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of ng-option-highlight
3 | */
4 |
5 | export * from './lib/ng-option-highlight.directive';
6 | export * from './lib/ng-option-highlight.module';
7 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/ng-option-highlight",
4 | "lib": {
5 | "entryFile": "public-api.ts"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/ng-select/lib/console.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable({ providedIn: 'root' })
4 | export class ConsoleService {
5 | warn(message: string) {
6 | console.warn(message)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/demo/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/app",
5 | "types": [
6 | "node"
7 | ]
8 | },
9 | "files": [
10 | "main.ts",
11 | "polyfills.ts"
12 | ],
13 | "include": [
14 | "**/*.d.ts"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-closed-dropdown-example/tags-closed-dropdown-example.component.html:
--------------------------------------------------------------------------------
1 |
Tagging without dropdown. Press enter to add item
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/src/demo/assets/stackblitz/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/lib/ng-option-highlight.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { NgOptionHighlightDirective } from './ng-option-highlight.directive';
3 |
4 | @NgModule({
5 | declarations: [NgOptionHighlightDirective],
6 | exports: [NgOptionHighlightDirective]
7 | })
8 | export class NgOptionHighlightModule {}
9 |
--------------------------------------------------------------------------------
/src/ng-select/lib/id.ts:
--------------------------------------------------------------------------------
1 | export function newId() {
2 | // First character is an 'a', it's good practice to tag id to begin with a letter
3 | return 'axxxxxxxxxxx'.replace(/[x]/g, function (_) {
4 | // tslint:disable-next-line:no-bitwise
5 | const val = Math.random() * 16 | 0;
6 | return val.toString(16);
7 | });
8 | }
9 |
--------------------------------------------------------------------------------
/src/ng-select/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": ["jasmine", "node"]
6 | },
7 | "angularCompilerOptions": {
8 | "enableIvy": false
9 | },
10 | "files": ["test.ts"],
11 | "include": ["**/*.spec.ts", "**/*.d.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/src/ng-select/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": {
4 | "directive-selector": [
5 | true,
6 | "attribute",
7 | "lib",
8 | "camelCase"
9 | ],
10 | "component-selector": [
11 | true,
12 | "element",
13 | "lib",
14 | "kebab-case"
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tslint.json",
3 | "rules": {
4 | "directive-selector": [
5 | true,
6 | "attribute",
7 | "lib",
8 | "camelCase"
9 | ],
10 | "component-selector": [
11 | true,
12 | "element",
13 | "lib",
14 | "kebab-case"
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-default-example/tags-default-example.component.html:
--------------------------------------------------------------------------------
1 | If option doesn't exist among items you can add new one using [addTag]="true"
2 |
3 |
6 |
7 |
8 |
9 |
10 | Selected value: {{selectedCompany | json}}
11 |
--------------------------------------------------------------------------------
/src/demo/app/examples/bindings-custom-example/bindings-custom-example.component.html:
--------------------------------------------------------------------------------
1 | You can provide custom property for display and selected value.
2 |
3 |
7 |
8 |
9 |
10 | Selected city ID: {{selectedCityId | json}}
11 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-default-example/search-default-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | By default ng-select will search using label text. You can also use loading input to set
3 | loading state manually if [typeahead] is not used.
4 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/demo/app/examples/bindings-default-example/bindings-default-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | By default ng-select binds to default label property for display, and keeps whole object as selected
3 | value
4 |
5 |
6 |
8 |
9 |
10 |
11 | Selected city object: {{selectedCity | json}}
12 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-hidden-example/multi-select-hidden-example.component.html:
--------------------------------------------------------------------------------
1 | Selected items won't appear among dropdown options
2 |
3 |
11 |
12 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-label-example/template-label-example.component.html:
--------------------------------------------------------------------------------
1 | Custom selected item label using ng-label-tmp
2 |
3 |
4 |
5 |
6 | {{item.name}}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/demo/assets/stackblitz/main.ts:
--------------------------------------------------------------------------------
1 | import './polyfills';
2 |
3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4 | import { AppModule } from './app.module';
5 |
6 | platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
7 | if (window['ngRef']) {
8 | window['ngRef'].destroy();
9 | }
10 | window['ngRef'] = ref;
11 |
12 | }).catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/src/ng-select/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of ng-select
3 | */
4 |
5 | export { DropdownPosition, NgSelectComponent, SELECTION_MODEL_FACTORY } from './lib/ng-select.component';
6 | export { NgSelectModule } from './lib/ng-select.module';
7 | export { NgOption } from './lib/ng-select.types';
8 | export { SelectionModel } from './lib/selection-model';
9 | export { NgSelectConfig } from './lib/config.service';
10 |
--------------------------------------------------------------------------------
/src/demo/app/examples/bindings-nested-example/bindings-nested-example.component.html:
--------------------------------------------------------------------------------
1 | Bind label and value to nested custom property
2 |
3 |
8 |
9 |
10 |
11 | Selected country ID: {{selectedCountryId}}
12 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-search-example/template-search-example.component.html:
--------------------------------------------------------------------------------
1 | Custom search control inside of dropdown
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/demo/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-default-example/tags-default-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'tags-default-example',
5 | templateUrl: './tags-default-example.component.html',
6 | styleUrls: ['./tags-default-example.component.scss']
7 | })
8 | export class TagsDefaultExampleComponent implements OnInit {
9 |
10 | selectedCompany;
11 |
12 | ngOnInit() {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-custom-example/tags-custom-example.component.html:
--------------------------------------------------------------------------------
1 | By providing custom function to [addTag] you can modify result of new tag
2 |
3 |
9 |
10 |
11 |
12 | Selected value: {{selectedCompanies | json}}
13 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | git pull
3 |
4 | echo update changelog
5 | cd ./src/ng-select/
6 | node ../../node_modules/standard-version/bin/cli.js --infile ../../CHANGELOG.md
7 | cd ../..
8 |
9 | echo build lib
10 | yarn run build
11 |
12 | echo push tags
13 | git push --follow-tags origin master
14 |
15 | echo push to npm
16 | cp README.md ./dist/ng-select/
17 | cd ./dist/ng-select/
18 | yarn publish --access=public
19 | cd ./dist/ng-option-highlight/
20 | yarn publish --access=public
21 |
--------------------------------------------------------------------------------
/src/demo/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/src/demo/app/examples/search-custom-example/search-custom-example.component.html:
--------------------------------------------------------------------------------
1 | Use search term and filter on custom fields. Type female to see only females.
2 |
3 |
7 |
8 | {{item.name}}
9 | {{item.gender}}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-loading-example/template-loading-example.component.html:
--------------------------------------------------------------------------------
1 | Custom loading spinner using ng-loadingspinner-tmp
2 |
3 |
4 |
5 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/demo/app/examples/append-to-example/append-to-example.component.scss:
--------------------------------------------------------------------------------
1 | // as we are using ViewEncapsulation.ShadowDom we need to make sure all styles are accessible within this component
2 | // @import "~@ng-select/ng-select/themes/default.theme.css";
3 |
4 | .overflow-box {
5 | padding: 5px;
6 | height: 100px;
7 | border: 1px solid #999;
8 | overflow: hidden;
9 | }
10 |
11 | .scrollable-box {
12 | position: relative;
13 | height: 400px;
14 | overflow: auto;
15 | }
16 |
--------------------------------------------------------------------------------
/src/ng-select/testing/mocks.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, NgZone } from '@angular/core';
2 |
3 | @Injectable()
4 | export class MockNgZone extends NgZone {
5 | constructor() {
6 | super({ enableLongStackTrace: true });
7 | }
8 |
9 | run(fn: Function): any {
10 | return fn();
11 | }
12 |
13 | runOutsideAngular(fn: Function): any {
14 | return fn();
15 | }
16 | }
17 |
18 | @Injectable()
19 | export class MockConsole {
20 | warn() {
21 |
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-closed-dropdown-example/tags-closed-dropdown-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'tags-closed-dropdown-example',
5 | templateUrl: './tags-closed-dropdown-example.component.html',
6 | styleUrls: ['./tags-closed-dropdown-example.component.scss']
7 | })
8 | export class TagsClosedDropdownExampleComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | # Matches multiple files with brace expansion notation
12 | # Set default charset
13 | [*.{js,html,scss}]
14 | charset = utf-8
15 |
16 | # Indentation override for all JS under lib directory
17 | [src/**.{js,html,scss}]
18 | indent_style = space
19 | indent_size = 4
20 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-disabled-example/multi-select-disabled-example.component.html:
--------------------------------------------------------------------------------
1 | Disabled multiple elements
2 |
3 |
9 |
10 |
11 |
12 | Toggle disabled
13 | Toggle model
14 |
15 |
--------------------------------------------------------------------------------
/src/ng-select/lib/ng-select.types.ts:
--------------------------------------------------------------------------------
1 | export interface NgOption {
2 | [name: string]: any;
3 | index?: number;
4 | htmlId?: string;
5 | selected?: boolean;
6 | disabled?: boolean;
7 | marked?: boolean;
8 | label?: string;
9 | value?: string | Object;
10 | parent?: NgOption;
11 | children?: NgOption[];
12 | }
13 |
14 | export enum KeyCode {
15 | Tab = 9,
16 | Enter = 13,
17 | Esc = 27,
18 | Space = 32,
19 | ArrowUp = 38,
20 | ArrowDown = 40,
21 | Backspace = 8
22 | }
23 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-backend-example/tags-backend-example.component.html:
--------------------------------------------------------------------------------
1 | [addTag] also accepts promise if you need to verify new option with backend
2 |
3 |
9 |
10 |
11 | create new : {{search}}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/demo/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/demo/app/examples/dropdown-position-example/dropdown-position-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | By default the dropdown position is set to auto and will be shown above if there is not space placing it at the
3 | bottom.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | You can force position to bottom or top by setting dropdownPosition
12 |
13 |
14 |
17 |
18 |
--------------------------------------------------------------------------------
/src/demo/app/examples/data-source-backend-example/data-source-backend-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | Most common case is showing data from backend
3 | API and with ng-select this is extremely simple since you can bind directly to
4 | observable when using angular | async pipe
5 |
6 |
7 |
12 |
13 |
14 | Selected: {{selectedPersonId}}
15 |
--------------------------------------------------------------------------------
/src/ng-select/lib/config.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable({ providedIn: 'root' })
4 | export class NgSelectConfig {
5 | placeholder: string;
6 | notFoundText = 'No items found';
7 | typeToSearchText = 'Type to search';
8 | addTagText = 'Add item';
9 | loadingText = 'Loading...';
10 | clearAllText = 'Clear all';
11 | disableVirtualScroll = true;
12 | openOnEnter = true;
13 | appendTo: string;
14 | bindValue: string;
15 | bindLabel: string;
16 | appearance = 'underline';
17 | }
18 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-default-example/group-default-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | You can group by item key providing key name as a string to groupBy input
3 |
4 |
5 |
10 |
11 | {{item.country || 'Unnamed group'}}
12 |
13 |
14 |
15 |
16 | Selected: {{selectedAccount | json}}
17 |
18 |
--------------------------------------------------------------------------------
/src/demo/app/examples/data-source-array-example/data-source-array-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | You can also set array of objects as items input
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 | While array of objects is the most common items source, you may want to set simple array of strings, numbers,
14 | booleans
15 |
16 |
17 |
19 |
20 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-editable-example/search-editable-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | By default, ng-select doesn't allow edit the current value for search. You can enable it by setting [editableSearchTerm]
3 | input to true . It's useful when you want to modify part of a long search query
4 |
5 |
6 |
11 |
12 |
13 | Selected: {{selectedPersonId}}
14 |
--------------------------------------------------------------------------------
/src/demo/app/examples/dropdown-position-example/dropdown-position-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'dropdown-position-example',
5 | templateUrl: './dropdown-position-example.component.html',
6 | styleUrls: ['./dropdown-position-example.component.scss']
7 | })
8 | export class DropdownPositionExampleComponent implements OnInit {
9 |
10 | cities = [
11 | { value: 1, label: 'Vilnius' },
12 | { value: 2, label: 'Kaunas' },
13 | { value: 3, label: 'Pavilnys' }
14 | ];
15 |
16 | ngOnInit() {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/demo/app/shared/example-viewer/example-viewer.component.html:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/ng-select/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "target": "es2015",
6 | "declaration": true,
7 | "inlineSources": true,
8 | "types": [],
9 | "lib": [
10 | "dom",
11 | "es2018"
12 | ]
13 | },
14 | "angularCompilerOptions": {
15 | "skipTemplateCodegen": true,
16 | "strictMetadataEmit": true,
17 | "fullTemplateTypeCheck": true,
18 | "strictInjectionParameters": true,
19 | "enableResourceInlining": true,
20 | "enableIvy": false
21 | },
22 | "exclude": [
23 | "test.ts",
24 | "**/*.spec.ts"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "target": "es2015",
6 | "declaration": true,
7 | "inlineSources": true,
8 | "types": [],
9 | "lib": [
10 | "dom",
11 | "es2018"
12 | ]
13 | },
14 | "angularCompilerOptions": {
15 | "skipTemplateCodegen": true,
16 | "strictMetadataEmit": true,
17 | "fullTemplateTypeCheck": true,
18 | "strictInjectionParameters": true,
19 | "enableResourceInlining": true,
20 | "enableIvy": false
21 | },
22 | "exclude": [
23 | "test.ts",
24 | "**/*.spec.ts"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/demo/app/examples/data-source-options-example/data-source-options-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | If you have simple use case, you can omit items array and bind options directly in html using ng-option
3 | component.
4 |
5 |
6 |
7 | Toggle disabled
8 |
9 |
10 |
11 | {{car.name}}
12 | Custom
13 |
14 |
15 | Selected car ID: {{selectedCars | json}}
16 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [varnastadeus]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/src/demo/app/shared/example-viewer/stackblitz-button/stackblitz-button.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 | StackBlitz
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/demo/app/examples/bindings-custom-example/bindings-custom-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'bindings-custom-example',
5 | templateUrl: './bindings-custom-example.component.html',
6 | styleUrls: ['./bindings-custom-example.component.scss']
7 | })
8 | export class BindingsCustomExampleComponent implements OnInit {
9 |
10 | cities = [
11 | { id: 1, name: 'Vilnius' },
12 | { id: 2, name: 'Kaunas' },
13 | { id: 3, name: 'Pavilnys', disabled: true }
14 | ];
15 | selectedCityId: number = null;
16 |
17 | ngOnInit() {
18 | this.selectedCityId = this.cities[0].id;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-selectable-hidden-example/group-selectable-hidden-example.component.html:
--------------------------------------------------------------------------------
1 | Selecting group will hide all of its items
2 |
3 |
12 |
13 | {{item.country || 'Unnamed group'}}
14 |
15 |
16 |
17 |
18 | Selected: {{selectedAccounts | json}}
19 |
--------------------------------------------------------------------------------
/.github/no-response.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-no-response - https://github.com/probot/no-response
2 |
3 | # Number of days of inactivity before an Issue is closed for lack of response
4 | daysUntilClose: 14
5 | # Label requiring a response
6 | responseRequiredLabel: needs info
7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable
8 | closeComment: >
9 | This issue has been automatically closed because there has been no response
10 | to our request for more information from the original author. With only the
11 | description that is currently in the issue, we don't have enough information
12 | to take action. Feel free to create new issue if you have more info to provide.
13 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for your interest in ng-select. All forms of contribution are
4 | welcome, from issue reports to PRs and documentation / write-ups.
5 |
6 |
7 | Please do the following before creating PR.
8 | * If you're planning to add or change a major feature in a PR, please ensure the change is aligned with the project roadmap by opening an issue first, especially if you're going to spend a lot of time on it.
9 | * Test cases for your change are present. Generally we don't accept PR's without a test case.
10 | * All tests are passing and build is not failing.
11 | * In development run `yarn start` to start demo app.
12 | * In development run `yarn test:watch` to start running tests in watch mode.
13 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-default-example/multi-select-default-example.component.html:
--------------------------------------------------------------------------------
1 | Select multiple elements
2 |
3 |
11 |
12 |
13 |
14 | Selected value:
15 |
18 |
Clear model
19 |
Change model
20 |
21 |
--------------------------------------------------------------------------------
/src/demo/app/examples/bindings-default-example/bindings-default-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'bindings-default-example',
5 | templateUrl: './bindings-default-example.component.html',
6 | styleUrls: ['./bindings-default-example.component.scss']
7 | })
8 | export class BindingsDefaultExampleComponent implements OnInit {
9 |
10 | defaultBindingsList = [
11 | { value: 1, label: 'Vilnius' },
12 | { value: 2, label: 'Kaunas' },
13 | { value: 3, label: 'Pavilnys', disabled: true }
14 | ];
15 |
16 | selectedCity = null;
17 |
18 | ngOnInit() {
19 | this.selectedCity = this.defaultBindingsList[0];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/ng-select/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import {
7 | BrowserDynamicTestingModule,
8 | platformBrowserDynamicTesting
9 | } from '@angular/platform-browser-dynamic/testing';
10 |
11 | declare const require: any;
12 |
13 | // First, initialize the Angular testing environment.
14 | getTestBed().initTestEnvironment(
15 | BrowserDynamicTestingModule,
16 | platformBrowserDynamicTesting()
17 | );
18 | // Then we find all the tests.
19 | const context = require.context('./', true, /\.spec\.ts$/);
20 | // And load the modules.
21 | context.keys().map(context);
22 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-children-example/group-children-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | Group by children array. Note that when grouping by already grouped items ng-optgroup-tmp is
3 | required to display correct headers.
4 |
5 |
6 |
12 |
13 | {{item.title}}
14 |
15 |
16 | {{item.title}}
17 |
18 |
19 |
20 |
21 | Selected: {{selectedProjects | json}}
22 |
--------------------------------------------------------------------------------
/src/demo/app/shared/route-viewer/route-viewer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 | import { EXAMPLE_COMPONENTS } from '../../examples/examples';
4 |
5 | @Component({
6 | selector: 'route-viewer',
7 | templateUrl: './route-viewer.component.html'
8 | })
9 | export class RouteViewerComponent implements OnInit {
10 |
11 | examples: string[];
12 |
13 | constructor(
14 | private route: ActivatedRoute) {
15 | }
16 |
17 | ngOnInit() {
18 | this.route.data.subscribe((data: { examples: string }) => {
19 | this.examples = Object.keys(EXAMPLE_COMPONENTS).filter(x => x.startsWith(data.examples));
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-custom-example/tags-custom-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'tags-custom-example',
5 | templateUrl: './tags-custom-example.component.html',
6 | styleUrls: ['./tags-custom-example.component.scss']
7 | })
8 | export class TagsCustomExampleComponent implements OnInit {
9 |
10 | selectedCompanies;
11 | companies: any[] = [];
12 | companiesNames = ['Uber', 'Microsoft', 'Flexigen'];
13 |
14 | ngOnInit() {
15 | this.companiesNames.forEach((c, i) => {
16 | this.companies.push({ id: i, name: c });
17 | });
18 | }
19 |
20 | addTagFn(name) {
21 | return { name: name, tag: true };
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/demo/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-hidden-example/multi-select-hidden-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { DataService } from '../data.service';
4 |
5 | @Component({
6 | selector: 'multi-select-hidden-example',
7 | templateUrl: './multi-select-hidden-example.component.html',
8 | styleUrls: ['./multi-select-hidden-example.component.scss']
9 | })
10 | export class MultiSelectHiddenExampleComponent implements OnInit {
11 |
12 | people$: Observable;
13 | selectedPeople = [];
14 |
15 | constructor(private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.people$ = this.dataService.getPeople();
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-editable-example/search-editable-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Person, DataService } from '../data.service';
3 | import { Observable } from 'rxjs';
4 |
5 | @Component({
6 | selector: 'app-search-editable-example',
7 | templateUrl: './search-editable-example.component.html',
8 | styleUrls: ['./search-editable-example.component.scss']
9 | })
10 | export class SearchEditableExampleComponent implements OnInit {
11 |
12 | people$: Observable;
13 | selectedPersonId = '5a15b13c36e7a7f00cf0d7cb';
14 |
15 | constructor(
16 | private dataService: DataService) {
17 | }
18 |
19 | ngOnInit() {
20 | this.people$ = this.dataService.getPeople();
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import {
7 | BrowserDynamicTestingModule,
8 | platformBrowserDynamicTesting
9 | } from '@angular/platform-browser-dynamic/testing';
10 |
11 | declare const require: any;
12 |
13 | // First, initialize the Angular testing environment.
14 | getTestBed().initTestEnvironment(
15 | BrowserDynamicTestingModule,
16 | platformBrowserDynamicTesting()
17 | );
18 | // Then we find all the tests.
19 | const context = require.context('./', true, /\.spec\.ts$/);
20 | // And load the modules.
21 | context.keys().map(context);
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "downlevelIteration": true,
9 | "module": "es2020",
10 | "moduleResolution": "node",
11 | "experimentalDecorators": true,
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2018",
19 | "dom"
20 | ],
21 | "paths": {
22 | "@ng-select/ng-select": [
23 | "src/ng-select/public-api.ts"
24 | ],
25 | "@ng-select/ng-option-highlight": [
26 | "src/ng-option-highlight/public-api.ts"
27 | ]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/demo/app/examples/append-to-example/append-to-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core';
2 | import { DataService } from '../data.service';
3 |
4 | @Component({
5 | selector: 'append-to-example',
6 | templateUrl: './append-to-example.component.html',
7 | styleUrls: ['./append-to-example.component.scss'],
8 | // encapsulation: ViewEncapsulation.ShadowDom,
9 | })
10 | export class AppendToExampleComponent implements OnInit {
11 |
12 | people: any = [];
13 | selected: any;
14 | selected2: any;
15 | selected3: any;
16 |
17 | constructor(private dataService: DataService) {
18 | }
19 |
20 | ngOnInit() {
21 | this.people = this.dataService.getPeople();
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-optgroup-example/template-optgroup-example.component.html:
--------------------------------------------------------------------------------
1 | Custom label option and optgroup templates
2 |
3 |
4 |
5 |
6 | {{item.name}}
7 |
8 |
9 | City group logo
10 |
11 |
12 | {{item.name}}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-limit-example/multi-select-limit-example.component.html:
--------------------------------------------------------------------------------
1 | Select multiple elements with a limit number of selections (e.g.: 3)
2 |
3 |
4 | Max selection reached
5 |
6 |
7 |
14 |
15 |
16 |
17 | Selected value:
18 |
21 |
Clear model
22 |
23 |
--------------------------------------------------------------------------------
/src/demo/app/examples/data-source-backend-example/data-source-backend-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { DataService, Person } from '../data.service';
3 | import { Observable } from 'rxjs';
4 |
5 | @Component({
6 | selector: 'data-source-backend-example',
7 | templateUrl: './data-source-backend-example.component.html',
8 | styleUrls: ['./data-source-backend-example.component.scss']
9 | })
10 | export class DataSourceBackendExampleComponent implements OnInit {
11 |
12 | people$: Observable;
13 | selectedPersonId = '5a15b13c36e7a7f00cf0d7cb';
14 |
15 | constructor(private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.people$ = this.dataService.getPeople();
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-template-example/multi-select-template-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { DataService } from '../data.service';
4 |
5 | @Component({
6 | selector: 'multi-select-template-example',
7 | templateUrl: './multi-select-template-example.component.html',
8 | styleUrls: ['./multi-select-template-example.component.scss']
9 | })
10 | export class MultiSelectTemplateExampleComponent implements OnInit {
11 |
12 | githubUsers$: Observable;
13 | selectedUsers = ['anjmao'];
14 |
15 | constructor(private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.githubUsers$ = this.dataService.getGithubAccounts('anjm');
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/demo/app/examples/bindings-nested-example/bindings-nested-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'bindings-nested-example',
5 | templateUrl: './bindings-nested-example.component.html',
6 | styleUrls: ['./bindings-nested-example.component.scss']
7 | })
8 | export class BindingsNestedExampleComponent implements OnInit {
9 |
10 | countries = [
11 | { id: 1, nested: { countryId: 'L', name: 'Lithuania' } },
12 | { id: 2, nested: { countryId: 'U', name: 'USA' } },
13 | { id: 3, nested: { countryId: 'A', name: 'Australia' } }
14 | ];
15 |
16 | selectedCountryId: string = null;
17 |
18 | ngOnInit() {
19 | this.selectedCountryId = this.countries[0].nested.countryId;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-custom-example/multi-select-custom-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { DataService } from '../data.service';
4 |
5 | @Component({
6 | selector: 'multi-select-custom-example',
7 | templateUrl: './multi-select-custom-example.component.html',
8 | styleUrls: ['./multi-select-custom-example.component.scss']
9 | })
10 | export class MultiSelectCustomExampleComponent implements OnInit {
11 |
12 | githubUsers$: Observable;
13 | selectedUsers = ['anjmao', 'anjmittu', 'anjmendoza'];
14 |
15 | constructor(private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.githubUsers$ = this.dataService.getGithubAccounts('anjm');
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-autocomplete-example/search-autocomplete-example.component.html:
--------------------------------------------------------------------------------
1 | Use typeahead to subscribe to search term and load async items.
2 | Multi select + Typeahead + Custom items (tags)
3 |
4 |
15 |
16 |
17 |
18 |
19 | Selected persons: {{selectedPersons | json}}
20 |
21 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-header-footer-example/template-header-footer-example.component.html:
--------------------------------------------------------------------------------
1 | Custom header and footer using ng-header-tmp and ng-footer-tmp
2 |
3 |
10 |
11 | Select all
12 | Unselect all
13 |
14 |
15 | Selected count: {{selectedPeople.length}}
16 |
17 |
18 |
19 |
20 |
21 | Selected people: {{selectedPeople}}
22 |
--------------------------------------------------------------------------------
/src/demo/app/examples/data-source-options-example/data-source-options-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'data-source-options-example',
5 | templateUrl: './data-source-options-example.component.html',
6 | styleUrls: ['./data-source-options-example.component.scss']
7 | })
8 | export class DataSourceOptionsExampleComponent implements OnInit {
9 |
10 | selectedCars = [3];
11 | cars = [
12 | { id: 1, name: 'Volvo' },
13 | { id: 2, name: 'Saab', disabled: true },
14 | { id: 3, name: 'Opel' },
15 | { id: 4, name: 'Audi' },
16 | ];
17 |
18 | ngOnInit() {
19 |
20 | }
21 |
22 | toggleDisabled() {
23 | const car: any = this.cars[1];
24 | car.disabled = !car.disabled;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-limit-example/multi-select-limit-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { DataService } from '../data.service';
4 |
5 | @Component({
6 | selector: 'multi-select-limit-example',
7 | templateUrl: './multi-select-limit-example.component.html',
8 | styleUrls: ['./multi-select-limit-example.component.scss']
9 | })
10 | export class MultiSelectLimitExampleComponent implements OnInit {
11 |
12 | people$: Observable;
13 | selectedPeople = [];
14 |
15 | constructor(private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.people$ = this.dataService.getPeople();
20 | }
21 |
22 | clearModel() {
23 | this.selectedPeople = [];
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-template-example/multi-select-template-example.component.html:
--------------------------------------------------------------------------------
1 | Custom template for each selected item using ng-label-tmp
2 |
3 |
9 |
10 |
11 | {{item.login}}
12 | ×
13 |
14 |
15 |
16 | {{item.login}}
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-function-example/group-function-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | Having more complex use case, you can use function expression as groupBy input. Also you can use [groupValue]
3 | to set value of grouped item.
4 |
5 |
6 |
13 |
14 | {{item.name}}
15 | {{item.total}}
16 |
17 |
18 |
19 |
20 | Selected: {{selectedAccounts | json}}
21 |
--------------------------------------------------------------------------------
/src/demo/app/layout/sidenav-component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { appRoutes } from '../routes';
3 |
4 | @Component({
5 | selector: 'layout-sidenav',
6 | template: `
7 |
12 | `
13 | })
14 | export class LayoutSidenavComponent {
15 | routes = [];
16 |
17 | constructor() {
18 | this.routes = appRoutes.filter(route => route.component)
19 | .map(route => ({
20 | title: route.data.title,
21 | url: `/${route.path}`
22 | }));
23 | }
24 | }
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-checkbox-group-example/multi-checkbox-group-example.component.html:
--------------------------------------------------------------------------------
1 | Group selects children
2 |
3 |
13 |
14 | {{item.gender | uppercase}}
15 |
16 |
17 | {{item.name}}
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ng-select/ng-option-highlight",
3 | "version": "0.0.6",
4 | "author": "@ng-select/ng-select",
5 | "license": "MIT",
6 | "repository": "ng-select/ng-select",
7 | "peerDependencies": {
8 | "@angular/common": ">=12.0.0 <13.0.0",
9 | "@angular/core": "^>=12.0.0 <13.0.0"
10 | },
11 | "main": "bundles/ng-select-ng-option-highlight.umd.js",
12 | "module": "fesm2015/ng-select-ng-option-highlight.js",
13 | "es2015": "fesm2015/ng-select-ng-option-highlight.js",
14 | "esm2015": "esm2015/ng-select-ng-option-highlight.js",
15 | "fesm2015": "fesm2015/ng-select-ng-option-highlight.js",
16 | "typings": "ng-select-ng-option-highlight.d.ts",
17 | "metadata": "ng-select-ng-option-highlight.metadata.json",
18 | "sideEffects": false,
19 | "dependencies": {
20 | "tslib": "^2.1.0"
21 | }
22 | }
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-with-options-example/forms-with-options-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FormBuilder, FormGroup } from '@angular/forms';
3 |
4 | @Component({
5 | selector: 'forms-with-options-example',
6 | templateUrl: './forms-with-options-example.component.html',
7 | styleUrls: ['./forms-with-options-example.component.scss']
8 | })
9 | export class FormsWithOptionsExampleComponent implements OnInit {
10 |
11 | basePath;
12 | heroForm: FormGroup;
13 |
14 | constructor(
15 | private fb: FormBuilder) {
16 | }
17 |
18 | ngOnInit() {
19 | this.basePath = window.location.host.includes('localhost') ? '' : '/ng-select';
20 | this.heroForm = this.fb.group({
21 | heroId: 'batman',
22 | agree: null
23 | });
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/demo/app/examples/data-source-array-example/data-source-array-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { DataService, Person } from '../data.service';
3 |
4 | @Component({
5 | selector: 'data-source-array-example',
6 | templateUrl: './data-source-array-example.component.html',
7 | styleUrls: ['./data-source-array-example.component.scss']
8 | })
9 | export class DataSourceArrayExampleComponent implements OnInit {
10 |
11 | people: Person[] = [];
12 | selectedPersonId = '5a15b13c36e7a7f00cf0d7cb';
13 |
14 | selectedSimpleItem = 'Two';
15 | simpleItems = [];
16 |
17 | constructor(private dataService: DataService) {
18 | }
19 |
20 | ngOnInit() {
21 | this.dataService.getPeople().subscribe(items => this.people = items);
22 | this.simpleItems = [true, 'Two', 3];
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-multi-select-example/forms-multi-select-example.component.html:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | push:
6 | paths:
7 | - '**.ts'
8 | - '**.html'
9 | - '**.scss'
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - name: Setup Node
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: '12.x'
21 |
22 | - name: Install dependencies
23 | run: yarn install
24 |
25 | - name: Run lint
26 | run: yarn lint
27 |
28 | - name: Run tests
29 | run: yarn test:ci
30 |
31 | - name: Build
32 | run: yarn build:demo
33 |
34 | - name: Coveralls
35 | uses: coverallsapp/github-action@master
36 | with:
37 | github-token: ${{ secrets.GITHUB_TOKEN }}
38 | path-to-lcov: ./coverage/ng-select/lcov.info
39 |
--------------------------------------------------------------------------------
/src/demo/app/shared/example-viewer/stackblitz-button/stackblitz-button.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { environment } from '../../../../environments/environment';
3 | import { StackblitzService } from './stackblitz.service';
4 |
5 | @Component({
6 | selector: 'stackblitz-button',
7 | templateUrl: './stackblitz-button.component.html',
8 | styles: [`
9 | svg {
10 | height: 18px;
11 | vertical-align: sub;
12 | }
13 | `]
14 | })
15 | export class StackblitzButtonComponent {
16 |
17 | @Input() example: string;
18 |
19 | constructor(
20 | private stackblitz: StackblitzService) {
21 | }
22 |
23 | async openExample() {
24 | if (!environment.production) {
25 | return;
26 | }
27 |
28 | await this.stackblitz.openNewProject(this.example);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-checkbox-example/multi-checkbox-example.component.html:
--------------------------------------------------------------------------------
1 | Select multiple elements using custom templates with checkboxes
2 |
3 |
12 |
13 | {{item.gender | uppercase}}
14 |
15 |
16 | {{item.name}}
17 |
18 |
19 |
20 |
21 | {{selectedPeople | json}}
22 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Mark stale issues and pull requests
2 |
3 | on:
4 | schedule:
5 | - cron: "0 0 * * *"
6 |
7 | jobs:
8 | stale:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/stale@v3
14 | with:
15 | repo-token: ${{ secrets.GITHUB_TOKEN }}
16 | stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions :fireworks:'
17 | stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions :fireworks:'
18 | stale-issue-label: 'stale'
19 | exempt-issue-labels: 'improvement,needs triage,good for contribution,bug'
20 | stale-pr-label: 'stale'
21 |
--------------------------------------------------------------------------------
/src/demo/app/examples/virtual-scroll-example/virtual-scroll-example.component.html:
--------------------------------------------------------------------------------
1 |
2 | In this example we are loading many items but only ~30 of them are rendered in the DOM.
3 | This allows to load as big data as you want.
4 |
5 |
6 |
15 |
16 | Loaded {{photosBuffer.length}} of {{photos.length}}
17 |
18 |
19 | {{index}} {{item.title}}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/demo/assets/stackblitz/styles.css:
--------------------------------------------------------------------------------
1 | @import "~@ng-select/ng-select/themes/default.theme.css";
2 |
3 | body {
4 | font-size: 1rem;
5 | font-weight: 400;
6 | line-height: 1.5;
7 | /* Body position must be relative while using appendTo */
8 | position: relative;
9 |
10 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
11 | }
12 |
13 | .card {
14 | position: relative;
15 | display: -webkit-box;
16 | display: flex;
17 | flex-direction: column;
18 | min-width: 0;
19 | word-wrap: break-word;
20 | background-color: #fff;
21 | background-clip: border-box;
22 | border: 1px solid rgba(0, 0, 0, 0.125);
23 | border-radius: 0.25rem;
24 | }
25 |
26 | .card-body {
27 | flex: 1 1 auto;
28 | padding: 1.25rem;
29 | }
30 |
--------------------------------------------------------------------------------
/src/ng-select/lib/value-utils.ts:
--------------------------------------------------------------------------------
1 | const unescapedHTMLExp = /[&<>"']/g;
2 | const hasUnescapedHTMLExp = RegExp(unescapedHTMLExp.source);
3 | const htmlEscapes = {
4 | '&': '&',
5 | '<': '<',
6 | '>': '>',
7 | '"': '"',
8 | '\'': '''
9 | };
10 |
11 | export function escapeHTML(string: string) {
12 | return (string && hasUnescapedHTMLExp.test(string)) ?
13 | string.replace(unescapedHTMLExp, chr => htmlEscapes[chr]) :
14 | string;
15 | }
16 |
17 | export function isDefined(value: any) {
18 | return value !== undefined && value !== null;
19 | }
20 |
21 | export function isObject(value: any) {
22 | return typeof value === 'object' && isDefined(value);
23 | }
24 |
25 | export function isPromise(value: any) {
26 | return value instanceof Promise;
27 | }
28 |
29 | export function isFunction(value: any) {
30 | return value instanceof Function;
31 | }
32 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-default-example/search-default-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { DataService, Person } from '../data.service';
3 |
4 | @Component({
5 | selector: 'search-default-example',
6 | templateUrl: './search-default-example.component.html',
7 | styleUrls: ['./search-default-example.component.scss']
8 | })
9 | export class SearchDefaultExampleComponent implements OnInit {
10 |
11 | people: Person[] = [];
12 | peopleLoading = false;
13 |
14 | constructor(
15 | private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.loadPeople();
20 | }
21 |
22 | private loadPeople() {
23 | this.peopleLoading = true;
24 | this.dataService.getPeople().subscribe(x => {
25 | this.people = x;
26 | this.peopleLoading = false;
27 | });
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-checkbox-example/multi-checkbox-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { DataService, Person } from '../data.service';
3 | import { map } from 'rxjs/operators';
4 |
5 | @Component({
6 | selector: 'multi-checkbox-example',
7 | templateUrl: './multi-checkbox-example.component.html',
8 | styleUrls: ['./multi-checkbox-example.component.scss']
9 | })
10 | export class MultiCheckboxExampleComponent implements OnInit {
11 |
12 | people: Person[] = [];
13 | selectedPeople = [];
14 |
15 | constructor(private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.dataService.getPeople()
20 | .pipe(map(x => x.filter(y => !y.disabled)))
21 | .subscribe((res) => {
22 | this.people = res;
23 | this.selectedPeople = [this.people[0].id, this.people[1].id];
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-custom-example/multi-select-custom-example.component.html:
--------------------------------------------------------------------------------
1 | Custom template for all selected items using ng-multi-label-tmp
2 |
3 |
10 |
11 |
12 |
{{item.login}}
13 |
×
14 |
15 | 2">
16 | {{items.length - 2}} more...
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-default-example/multi-select-default-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { DataService } from '../data.service';
4 |
5 | @Component({
6 | selector: 'multi-select-default-example',
7 | templateUrl: './multi-select-default-example.component.html',
8 | styleUrls: ['./multi-select-default-example.component.scss']
9 | })
10 | export class MultiSelectDefaultExampleComponent implements OnInit {
11 |
12 | people$: Observable;
13 | selectedPeople = [{ name: 'Karyn Wright' }];
14 |
15 | constructor(private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.people$ = this.dataService.getPeople();
20 | }
21 |
22 | clearModel() {
23 | this.selectedPeople = [];
24 | }
25 |
26 | changeModel() {
27 | this.selectedPeople = [{ name: 'New person' }];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-header-footer-example/template-header-footer-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { DataService } from '../data.service';
3 |
4 | @Component({
5 | selector: 'template-header-footer-example',
6 | templateUrl: './template-header-footer-example.component.html',
7 | styleUrls: ['./template-header-footer-example.component.scss']
8 | })
9 | export class TemplateHeaderFooterExampleComponent implements OnInit {
10 |
11 | people = [];
12 | selectedPeople = [];
13 |
14 | constructor(private dataService: DataService) {
15 | }
16 |
17 | ngOnInit() {
18 | this.dataService.getPeople().subscribe(items => {
19 | this.people = items;
20 | });
21 | }
22 |
23 | selectAll() {
24 | this.selectedPeople = this.people.map(x => x.name);
25 | }
26 |
27 | unselectAll() {
28 | this.selectedPeople = [];
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-checkbox-group-example/multi-checkbox-group-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { DataService, Person } from '../data.service';
3 | import { map } from 'rxjs/operators';
4 |
5 | @Component({
6 | selector: 'multi-checkbox-group-example',
7 | templateUrl: './multi-checkbox-group-example.component.html',
8 | styleUrls: ['./multi-checkbox-group-example.component.scss']
9 | })
10 | export class MultiCheckboxGroupExampleComponent implements OnInit {
11 |
12 | people: Person[] = [];
13 | selectedPeople = [];
14 |
15 | constructor(private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.dataService.getPeople()
20 | .pipe(map(x => x.filter(y => !y.disabled)))
21 | .subscribe((res) => {
22 | this.people = res;
23 | this.selectedPeople = [this.people[0].id];
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ng-select component demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Loading ng-select
14 |
15 |
16 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/demo/app/examples/output-events-example/output-events-example.component.html:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 | 0">
20 |
21 | Clear events
22 |
23 |
24 |
25 |
26 |
27 | {{event.name}} - {{event.value | json}}
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-display-example/template-display-example.component.html:
--------------------------------------------------------------------------------
1 | Custom not found, type to search and loading templates
2 |
10 |
11 |
12 | Start typing...
13 |
14 |
15 |
16 |
17 | No data found for "{{searchTerm}}"
18 |
19 |
20 |
21 |
22 | Fetching data for "{{searchTerm}}"
23 |
24 |
25 |
26 |
27 |
28 |
29 | Selected people: {{selectedPeople}}
30 |
--------------------------------------------------------------------------------
/src/demo/app/examples/tags-backend-example/tags-backend-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'tags-backend-example',
5 | templateUrl: './tags-backend-example.component.html',
6 | styleUrls: ['./tags-backend-example.component.scss']
7 | })
8 | export class TagsBackendExampleComponent implements OnInit {
9 |
10 | selectedCompanies;
11 | companies: any[] = [];
12 | loading = false;
13 | companiesNames = ['Uber', 'Microsoft', 'Flexigen'];
14 |
15 | ngOnInit() {
16 | this.companiesNames.forEach((c, i) => {
17 | this.companies.push({ id: i, name: c });
18 | });
19 | }
20 |
21 | addTagPromise(name) {
22 | return new Promise((resolve) => {
23 | this.loading = true;
24 | // Simulate backend call.
25 | setTimeout(() => {
26 | resolve({ id: 5, name: name, valid: true });
27 | this.loading = false;
28 | }, 1000);
29 | })
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/demo/assets/stackblitz/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Component, NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4 | import { NgSelectModule } from '@ng-select/ng-select';
5 | import { NgOptionHighlightModule } from '@ng-select/ng-option-highlight';
6 | import { CommonModule } from '@angular/common';
7 | import { HttpClientModule } from '@angular/common/http';
8 |
9 | //example-import
10 |
11 | @Component({
12 | selector: 'app-component',
13 | template: '//example-template'
14 | })
15 | export class AppComponent {}
16 |
17 | @NgModule({
18 | imports: [
19 | BrowserModule,
20 | FormsModule,
21 | NgSelectModule,
22 | NgOptionHighlightModule,
23 | CommonModule,
24 | ReactiveFormsModule,
25 | HttpClientModule
26 | ],
27 | declarations: [
28 | AppComponent,
29 | //example-cmp
30 | ],
31 | bootstrap: [AppComponent]
32 | })
33 | export class AppModule {
34 | }
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-children-example/group-children-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'group-children-example',
5 | templateUrl: './group-children-example.component.html',
6 | styleUrls: ['./group-children-example.component.scss']
7 | })
8 | export class GroupChildrenExampleComponent implements OnInit {
9 |
10 | selectedProjects = [];
11 | projects = [
12 | {
13 | id: 'p1',
14 | title: 'Project A',
15 | subprojects: [
16 | { title: 'Subproject 1 of A', id: 's1p1' },
17 | { title: 'Subproject 2 of A', id: 's2p1' },
18 | ]
19 | },
20 | {
21 | id: 'p2',
22 | title: 'Project B',
23 | subprojects: [
24 | { title: 'Subproject 1 of B', id: 's1p2' },
25 | { title: 'Subproject 2 of B', id: 's2p2' },
26 | ]
27 | }
28 | ]
29 |
30 | constructor() {
31 | }
32 |
33 | ngOnInit() {
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | env:
12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
13 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
14 | steps:
15 | - uses: actions/checkout@v1
16 |
17 | - name: Setup Node
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: '12.x'
21 |
22 | - name: Install dependencies
23 | run: npm install
24 |
25 | - name: Build ng-select
26 | run: npm run-script build
27 |
28 | - name: Copy README
29 | run: cp README.md ./dist/ng-select/
30 |
31 | - name: Semantic release
32 | run: npx semantic-release
33 |
34 | - name: build demo
35 | run: npm run-script build:demo
36 |
37 | - name: Deploy demo 🚀
38 | uses: JamesIves/github-pages-deploy-action@releases/v3
39 | with:
40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 | BRANCH: gh-pages
42 | FOLDER: dist/demo
43 |
--------------------------------------------------------------------------------
/.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 | **Reproducbile example**
11 | [Example](https://stackblitz.com) which reproduces described behaviour
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior (if example is not provided):
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-custom-example/search-custom-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { DataService, Person } from '../data.service';
3 |
4 | @Component({
5 | selector: 'search-custom-example',
6 | templateUrl: './search-custom-example.component.html',
7 | styleUrls: ['./search-custom-example.component.scss']
8 | })
9 | export class SearchCustomExampleComponent implements OnInit {
10 |
11 | people: Person[] = [];
12 | peopleLoading = false;
13 |
14 | constructor(
15 | private dataService: DataService) {
16 | }
17 |
18 | ngOnInit() {
19 | this.loadPeople();
20 | }
21 |
22 | private loadPeople() {
23 | this.peopleLoading = true;
24 | this.dataService.getPeople().subscribe(x => {
25 | this.people = x;
26 | this.peopleLoading = false;
27 | });
28 | }
29 |
30 | customSearchFn(term: string, item: Person) {
31 | term = term.toLowerCase();
32 | return item.name.toLowerCase().indexOf(term) > -1 || item.gender.toLowerCase() === term;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) Andžej Maciusovič
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 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-loading-example/template-loading-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'template-loading-example',
5 | templateUrl: './template-loading-example.component.html',
6 | styleUrls: ['./template-loading-example.component.scss']
7 | })
8 | export class TemplateLoadingExampleComponent implements OnInit {
9 |
10 | cities = [
11 | {
12 | id: 1,
13 | name: 'Vilnius',
14 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
15 | },
16 | { id: 2, name: 'Kaunas', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' },
17 | {
18 | id: 3,
19 | name: 'Pavilnys',
20 | avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15'
21 | },
22 | {
23 | id: 4,
24 | name: 'Siauliai',
25 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
26 | },
27 | ];
28 |
29 | ngOnInit() {
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-multi-select-example/forms-multi-select-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FormBuilder, FormGroup } from '@angular/forms';
3 |
4 | @Component({
5 | selector: 'forms-multi-select-example',
6 | templateUrl: './forms-multi-select-example.component.html',
7 | styleUrls: ['./forms-multi-select-example.component.scss']
8 | })
9 | export class FormsMultiSelectExampleComponent implements OnInit {
10 |
11 | heroForm: FormGroup;
12 | isCitiesControlVisible = true;
13 | cities: any[] = [
14 | { id: 1, name: 'Vilnius' },
15 | { id: 2, name: 'Kaunas' },
16 | { id: 3, name: 'Pavilnys (Disabled)', disabled: true },
17 | { id: 4, name: 'Pabradė' },
18 | ];
19 |
20 | constructor(private fb: FormBuilder) {
21 | }
22 |
23 | ngOnInit() {
24 | this.heroForm = this.fb.group({
25 | selectedCitiesIds: []
26 | });
27 | }
28 |
29 | toggleCitiesControl() {
30 | this.isCitiesControlVisible = !this.isCitiesControlVisible;
31 | }
32 |
33 | clearCities() {
34 | this.heroForm.get('selectedCitiesIds').patchValue([]);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-option-example/template-option-example.component.html:
--------------------------------------------------------------------------------
1 | Custom dropdown panel option template using ng-option-tmp
2 |
3 |
4 |
5 | {{item.name}}
6 |
7 |
8 |
{{item.name}}
9 |
Card subtitle
10 |
11 |
12 | Some quick example text to build
13 |
14 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-label-example/template-label-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'template-label-example',
5 | templateUrl: './template-label-example.component.html',
6 | styleUrls: ['./template-label-example.component.scss']
7 | })
8 | export class TemplateLabelExampleComponent implements OnInit {
9 | cities = [
10 | {
11 | id: 1,
12 | name: 'Vilnius',
13 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
14 | },
15 | { id: 2, name: 'Kaunas', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' },
16 | {
17 | id: 3,
18 | name: 'Pavilnys',
19 | avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15'
20 | },
21 | {
22 | id: 4,
23 | name: 'Siauliai',
24 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
25 | },
26 | ];
27 |
28 | selectedCity = this.cities[0].name;
29 |
30 | ngOnInit() {
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-optgroup-example/template-optgroup-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'template-optgroup-example',
5 | templateUrl: './template-optgroup-example.component.html',
6 | styleUrls: ['./template-optgroup-example.component.scss']
7 | })
8 | export class TemplateOptgroupExampleComponent implements OnInit {
9 |
10 | cities = [
11 | {
12 | id: 1,
13 | name: 'Vilnius',
14 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
15 | },
16 | { id: 2, name: 'Kaunas', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' },
17 | {
18 | id: 3,
19 | name: 'Pavilnys',
20 | avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15'
21 | },
22 | {
23 | id: 4,
24 | name: 'Siauliai',
25 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
26 | },
27 | ];
28 |
29 | selectedCity = this.cities[2].name;
30 |
31 | ngOnInit() {
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-search-example/template-search-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'template-search-example',
5 | templateUrl: './template-search-example.component.html',
6 | styleUrls: ['./template-search-example.component.scss']
7 | })
8 | export class TemplateSearchExampleComponent implements OnInit {
9 |
10 | cities = [
11 | {
12 | id: 1,
13 | name: 'Vilnius',
14 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
15 | },
16 | { id: 2, name: 'Kaunas', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' },
17 | {
18 | id: 3,
19 | name: 'Pavilnys',
20 | avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15'
21 | },
22 | {
23 | id: 4,
24 | name: 'Siauliai',
25 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
26 | },
27 | ];
28 |
29 | selectedCity = this.cities[0].name;
30 |
31 | constructor() { }
32 |
33 | ngOnInit() {
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-display-example/template-display-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, OnInit } from '@angular/core';
2 | import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
3 | import { DataService } from '../data.service';
4 |
5 | @Component({
6 | selector: 'template-display-example',
7 | templateUrl: './template-display-example.component.html',
8 | styleUrls: ['./template-display-example.component.scss']
9 | })
10 | export class TemplateDisplayExampleComponent implements OnInit {
11 |
12 | peopleTypeahead = new EventEmitter();
13 | serverSideFilterItems = [];
14 | selectedPeople;
15 |
16 | constructor(private dataService: DataService) {
17 | }
18 |
19 | ngOnInit() {
20 | this.serverSideSearch();
21 | }
22 |
23 | private serverSideSearch() {
24 | this.peopleTypeahead.pipe(
25 | distinctUntilChanged(),
26 | debounceTime(300),
27 | switchMap(term => this.dataService.getPeople(term))
28 | ).subscribe(x => {
29 | this.serverSideFilterItems = x;
30 | }, (err) => {
31 | console.log(err);
32 | this.serverSideFilterItems = [];
33 | });
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-option-example/template-option-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'template-option-example',
5 | templateUrl: './template-option-example.component.html',
6 | styleUrls: ['./template-option-example.component.scss']
7 | })
8 | export class TemplateOptionExampleComponent implements OnInit {
9 |
10 | cities = [
11 | {
12 | id: 1,
13 | name: 'Vilnius',
14 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
15 | },
16 | { id: 2, name: 'Kaunas', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' },
17 | {
18 | id: 3,
19 | name: 'Pavilnys',
20 | avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15'
21 | },
22 | {
23 | id: 4,
24 | name: 'Siauliai',
25 | avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'
26 | },
27 | ];
28 |
29 | selectedCity = this.cities[1].name;
30 |
31 | constructor() {
32 | }
33 |
34 | ngOnInit() {
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/ng-select/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage/ng-select'),
20 | reports: ['lcovonly'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false,
30 | restartOnFileChange: true
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-single-select-example/forms-single-select-example.component.html:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 | Do you wish to continue?
23 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage/ng-option-highlight'),
20 | reports: ['lcovonly'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false,
30 | restartOnFileChange: true
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/src/ng-select/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@clear/ng-select",
3 | "version": "7.1.1",
4 | "description": "Angular ng-select - All in One UI Select, Multiselect and Autocomplete",
5 | "author": "@ng-select/ng-select",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "git@github.com:clear/ng-select.git"
10 | },
11 | "publishConfig": {
12 | "access": "restricted",
13 | "registry": "https://npm.pkg.github.com"
14 | },
15 | "engines": {
16 | "node": ">= 12.14.0",
17 | "npm": ">= 6.0.0"
18 | },
19 | "keywords": [
20 | "angular",
21 | "select",
22 | "ui-select",
23 | "dropdown",
24 | "multiselect",
25 | "autocomplete",
26 | "angular2"
27 | ],
28 | "peerDependencies": {
29 | "@angular/common": ">=12.0.0 <13.0.0",
30 | "@angular/core": ">=12.0.0 <13.0.0",
31 | "@angular/forms": ">=12.0.0 <13.0.0"
32 | },
33 | "dependencies": {
34 | "tslib": "^2.0.0"
35 | },
36 | "main": "bundles/clear-ng-select.umd.js",
37 | "module": "fesm2015/clear-ng-select.js",
38 | "es2015": "fesm2015/clear-ng-select.js",
39 | "esm2015": "esm2015/clear-ng-select.js",
40 | "fesm2015": "fesm2015/clear-ng-select.js",
41 | "typings": "clear-ng-select.d.ts",
42 | "metadata": "clear-ng-select.metadata.json",
43 | "sideEffects": false
44 | }
--------------------------------------------------------------------------------
/src/ng-select/testing/helpers.ts:
--------------------------------------------------------------------------------
1 | import { DebugElement } from '@angular/core';
2 | import { ComponentFixture, tick } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 | import { KeyCode } from '../lib/ng-select.types';
5 |
6 | export class TestsErrorHandler {}
7 |
8 | export function tickAndDetectChanges(fixture: ComponentFixture) {
9 | fixture.detectChanges();
10 | tick();
11 | }
12 |
13 | export function selectOption(fixture, key: KeyCode, index: number) {
14 | triggerKeyDownEvent(getNgSelectElement(fixture), KeyCode.Space); // open
15 | tickAndDetectChanges(fixture); // need to tick and detect changes, since dropdown fully inits after promise is resolved
16 | for (let i = 0; i < index; i++) {
17 | triggerKeyDownEvent(getNgSelectElement(fixture), key);
18 | }
19 | triggerKeyDownEvent(getNgSelectElement(fixture), KeyCode.Enter); // select
20 | }
21 |
22 | export function getNgSelectElement(fixture: ComponentFixture): DebugElement {
23 | return fixture.debugElement.query(By.css('ng-select'));
24 | }
25 |
26 | export function triggerKeyDownEvent(element: DebugElement, which: number, key = ''): void {
27 | element.triggerEventHandler('keydown', {
28 | which: which,
29 | key: key,
30 | preventDefault: () => { },
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-single-select-example/forms-single-select-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
4 |
5 | @Component({
6 | selector: 'forms-single-select-example',
7 | templateUrl: './forms-single-select-example.component.html',
8 | styleUrls: ['./forms-single-select-example.component.scss']
9 | })
10 | export class FormsSingleSelectExampleComponent implements OnInit {
11 |
12 | heroForm: FormGroup;
13 | ages: any[] = [
14 | { value: '<18', label: 'Under 18' },
15 | { value: '18', label: '18' },
16 | { value: '>18', label: 'More than 18' },
17 | ];
18 |
19 | constructor(private fb: FormBuilder, private modalService: NgbModal) {
20 | }
21 |
22 | ngOnInit() {
23 | this.heroForm = this.fb.group({
24 | age: [null, Validators.required],
25 | });
26 | }
27 |
28 | toggleAgeDisable() {
29 | if (this.heroForm.controls.age.disabled) {
30 | this.heroForm.controls.age.enable();
31 | } else {
32 | this.heroForm.controls.age.disable();
33 | }
34 | }
35 |
36 | showConfirm(content) {
37 | this.modalService.open(content);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-selectable-example/group-selectable-example.component.html:
--------------------------------------------------------------------------------
1 | Groups can be made selectable. By default group acts as a separate option.
2 |
9 |
10 | {{item.country || 'Unnamed group'}}
11 |
12 |
13 |
14 |
15 | Selected: {{selectedAccount | json}}
16 |
17 |
18 | Using [selectableGroupAsModel]="false" selecting group will select all of its children but not group itself.
19 |
20 |
21 |
30 |
31 | {{item.country || 'Unnamed group'}}
32 |
33 |
34 |
35 |
36 | Selected: {{selectedAccounts | json}}
37 |
--------------------------------------------------------------------------------
/src/demo/app/examples/multi-select-disabled-example/multi-select-disabled-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { DataService } from '../data.service';
4 |
5 | @Component({
6 | selector: 'multi-select-disabled-example',
7 | templateUrl: './multi-select-disabled-example.component.html',
8 | styleUrls: ['./multi-select-disabled-example.component.scss']
9 | })
10 | export class MultiSelectDisabledExampleComponent implements OnInit {
11 |
12 | people$: Observable;
13 | selectedPeople = [];
14 | disable = true;
15 |
16 | constructor(private dataService: DataService) {
17 | }
18 |
19 | ngOnInit() {
20 | this.people$ = this.dataService.getPeople();
21 | this.setSelectedPeople();
22 | }
23 |
24 | toggleModel() {
25 | if (this.selectedPeople.length > 0) {
26 | this.selectedPeople = [];
27 | } else {
28 | this.setSelectedPeople();
29 | }
30 | }
31 |
32 | setSelectedPeople() {
33 | this.selectedPeople = [
34 | { id: '5a15b13c2340978ec3d2c0ea', name: 'Rochelle Estes', disabled: true },
35 | { id: '5a15b13c663ea0af9ad0dae8', name: 'Mendoza Ruiz' },
36 | { id: '5a15b13c728cd3f43cc0fe8a', name: 'Marquez Nolan', disabled: true }
37 | ];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/demo/app/examples/append-to-example/append-to-example.component.html:
--------------------------------------------------------------------------------
1 | By default dropdown panel is rendered as child element of ng-select, but you can append dropdown to any element using
2 | appendTo .
3 | If you place ng-select inside container with fixed height and hidden overflow then dropdown will not be fully
4 | visible.
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 | It can be fixed by appending dropdown to body or other parent element.
16 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
39 |
--------------------------------------------------------------------------------
/src/demo/app/examples/template-loading-example/template-loading-example.component.scss:
--------------------------------------------------------------------------------
1 | .lds-ellipsis {
2 | display: inline-block;
3 | position: relative;
4 | width: 32px;
5 | height: 32px;
6 | margin-right: 10px;
7 | }
8 |
9 | .lds-ellipsis div {
10 | position: absolute;
11 | top: 14px;
12 | width: 8px;
13 | height: 8px;
14 | border-radius: 50%;
15 | background: #c2c2c2;
16 | animation-timing-function: cubic-bezier(0, 1, 1, 0);
17 | }
18 |
19 | .lds-ellipsis div:nth-child(1) {
20 | left: -9px;
21 | animation: lds-ellipsis1 0.6s infinite;
22 | }
23 |
24 | .lds-ellipsis div:nth-child(2) {
25 | left: -10px;
26 | animation: lds-ellipsis2 0.6s infinite;
27 | }
28 |
29 | .lds-ellipsis div:nth-child(3) {
30 | left: 2px;
31 | animation: lds-ellipsis2 0.6s infinite;
32 | }
33 |
34 | .lds-ellipsis div:nth-child(4) {
35 | left: 24px;
36 | animation: lds-ellipsis3 0.6s infinite;
37 | }
38 |
39 | @keyframes lds-ellipsis1 {
40 | 0% {
41 | transform: scale(0);
42 | }
43 | 100% {
44 | transform: scale(1);
45 | }
46 | }
47 |
48 | @keyframes lds-ellipsis3 {
49 | 0% {
50 | transform: scale(1);
51 | }
52 | 100% {
53 | transform: scale(0);
54 | }
55 | }
56 |
57 | @keyframes lds-ellipsis2 {
58 | 0% {
59 | transform: translate(0, 0);
60 | }
61 | 100% {
62 | transform: translate(19px, 0);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/README.md:
--------------------------------------------------------------------------------
1 | ## Getting started
2 | ### Step 1: Install `ng-option-highlight`:
3 |
4 | #### NPM
5 | ```shell
6 | npm install --save @ng-select/ng-option-highlight
7 | ```
8 | #### YARN
9 | ```shell
10 | yarn add @ng-select/ng-option-highlight
11 | ```
12 | ### Step 2: Import the NgOptionHighlightModule:
13 | ```js
14 | import { NgSelectModule } from '@ng-select/ng-select';
15 | import { NgOptionHighlightModule } from '@ng-select/ng-option-highlight';
16 |
17 | @NgModule({
18 | declarations: [AppComponent],
19 | imports: [
20 | NgSelectModule,
21 | NgOptionHighlightModule
22 | ],
23 | bootstrap: [AppComponent]
24 | })
25 | export class AppModule {}
26 | ```
27 |
28 | ### Step 3: Add directive in your template:
29 |
30 | ```html
31 |
32 | ...
33 |
34 | {{item.title}}
35 |
36 |
37 | ```
38 |
39 |
40 | ## Development
41 |
42 | ### Build
43 |
44 | Run `ng build ng-option-highlight` to build the project. The build artifacts will be stored in the `dist/` directory.
45 |
46 | ### Publishing
47 |
48 | After building your library with `ng build ng-option-highlight`, go to the dist folder `cd dist/ng-option-highlight` and run `npm publish`.
49 |
50 | ### Running unit tests
51 |
52 | Run `ng test ng-option-highlight` to execute the unit tests via [Karma](https://karma-runner.github.io).
53 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-async-data-example/forms-async-data-example.component.html:
--------------------------------------------------------------------------------
1 |
27 |
28 |
--------------------------------------------------------------------------------
/src/demo/app/examples/search-autocomplete-example/search-autocomplete-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { concat, Observable, of, Subject } from 'rxjs';
3 | import { DataService, Person } from '../data.service';
4 | import { catchError, debounceTime, distinctUntilChanged, switchMap, tap } from 'rxjs/operators';
5 |
6 | @Component({
7 | selector: 'search-autocomplete-example',
8 | templateUrl: './search-autocomplete-example.component.html',
9 | styleUrls: ['./search-autocomplete-example.component.scss']
10 | })
11 | export class SearchAutocompleteExampleComponent implements OnInit {
12 |
13 | people$: Observable;
14 | peopleLoading = false;
15 | peopleInput$ = new Subject();
16 | selectedPersons: Person[] = [{ name: 'Karyn Wright' }, { name: 'Other' }];
17 |
18 | constructor(private dataService: DataService) {
19 | }
20 |
21 | ngOnInit() {
22 | this.loadPeople();
23 | }
24 |
25 | trackByFn(item: Person) {
26 | return item.id;
27 | }
28 |
29 | private loadPeople() {
30 | this.people$ = concat(
31 | of([]), // default items
32 | this.peopleInput$.pipe(
33 | distinctUntilChanged(),
34 | tap(() => this.peopleLoading = true),
35 | switchMap(term => this.dataService.getPeople(term).pipe(
36 | catchError(() => of([])), // empty list on error
37 | tap(() => this.peopleLoading = false)
38 | ))
39 | )
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/demo/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @import "~bootstrap/scss/bootstrap";
3 | @import "style/sidebar";
4 | @import "style/content";
5 |
6 | body {
7 | position: relative;
8 | }
9 |
10 | html,
11 | body,
12 | demo-app {
13 | height: 100%;
14 | }
15 |
16 | body {
17 | background-color: #fafafa;
18 | }
19 |
20 | nav.navbar {
21 | @include media-breakpoint-between(xs, sm) {
22 | height: auto;
23 | }
24 | height: 80px;
25 | z-index: 20;
26 | position: relative;
27 | box-shadow: 0 3px 8px 0 rgba(116, 129, 141, 0.1);
28 | border-bottom: 1px solid #d4dadf;
29 | background-color: #FFFFFF;
30 |
31 | a {
32 | color: #242A31;
33 | font-family: Content-font, Roboto, sans-serif;
34 | font-weight: 500;
35 | }
36 | }
37 |
38 | demo-app,
39 | layout-header,
40 | layout-sidenav {
41 | display: block;
42 | }
43 |
44 | .fill {
45 | height: 100%;
46 | }
47 |
48 | .card {
49 | label {
50 | margin-bottom: 5px;
51 | }
52 |
53 | box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12);
54 | border: 0;
55 | border-radius: 4px;
56 | }
57 |
58 | .bd-main {
59 | order: 1;
60 | }
61 |
62 | ng-select.ng-invalid.ng-touched .ng-select-container {
63 | border-color: #dc3545;
64 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 3px #fde6e8;
65 | }
66 |
67 | .btn + .btn {
68 | margin-left: 5px
69 | }
70 |
71 | .card-body .form-group {
72 | margin-bottom: 0;
73 | }
74 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-with-options-example/forms-with-options-example.component.html:
--------------------------------------------------------------------------------
1 |
31 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-async-data-example/forms-async-data-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FormBuilder, FormGroup } from '@angular/forms';
3 | import { NgSelectComponent } from '@ng-select/ng-select'
4 | import { delay } from 'rxjs/operators';
5 | import { DataService } from '../data.service';
6 |
7 | @Component({
8 | selector: 'forms-async-data-example',
9 | templateUrl: './forms-async-data-example.component.html',
10 | styleUrls: ['./forms-async-data-example.component.scss']
11 | })
12 | export class FormsAsyncDataExampleComponent implements OnInit {
13 |
14 | heroForm: FormGroup;
15 | albums = [];
16 | allAlbums = [];
17 |
18 | constructor(
19 | private fb: FormBuilder,
20 | private dataService: DataService) {
21 | }
22 |
23 | ngOnInit() {
24 | this.loadAlbums();
25 | this.heroForm = this.fb.group({
26 | album: '',
27 | });
28 | }
29 |
30 | openSelect(select: NgSelectComponent) {
31 | select.open();
32 | }
33 |
34 | closeSelect(select: NgSelectComponent) {
35 | select.close();
36 | }
37 |
38 | selectAlbumsRange(from, to) {
39 | this.albums = this.allAlbums.slice(from, to);
40 | }
41 |
42 | selectFirstAlbum() {
43 | this.heroForm.get('album').patchValue(this.albums[0].id);
44 | }
45 |
46 | private loadAlbums() {
47 | this.dataService.getAlbums().pipe(delay(500)).subscribe(albums => {
48 | this.allAlbums = albums;
49 | this.albums = [...this.allAlbums];
50 | this.selectFirstAlbum();
51 | });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | # Configuration for request-info - https://github.com/behaviorbot/request-info
2 |
3 | # *OPTIONAL* Comment to reply with
4 | # Can be either a string :
5 | requestInfoReplyComment: >
6 | Hey there! It seems that you didn't give us whole info about this issue. It will help a lot if you would use issue template and provide reproducible demo forking stackblitz example from demo page :tada:
7 |
8 | # Or an array:
9 | # requestInfoReplyComment:
10 | # - Ah no! young blade! That was a trifle short!
11 | # - Tell me more !
12 | # - I am sure you can be more effusive
13 |
14 | # *OPTIONAL* default titles to check against for lack of descriptiveness
15 | # MUST BE ALL LOWERCASE
16 | requestInfoDefaultTitles:
17 | - update readme.md
18 | - updates
19 | - not working
20 |
21 | # *OPTIONAL* Label to be added to Issues and Pull Requests with insufficient information given
22 | requestInfoLabelToAdd: needs info
23 |
24 | # *OPTIONAL* Require Issues to contain more information than what is provided in the issue templates
25 | # Will fail if the issue's body is equal to a provided template
26 | checkIssueTemplate: true
27 |
28 | # *OPTIONAL* Require Pull Requests to contain more information than what is provided in the PR template
29 | # Will fail if the pull request's body is equal to the provided template
30 | checkPullRequestTemplate: true
31 |
32 | # *OPTIONAL* Only warn about insufficient information on these events type
33 | # Keys must be lowercase. Valid values are 'issue' and 'pullRequest'
34 | requestInfoOn:
35 | pullRequest: false
36 | issue: true
37 |
38 | # *OPTIONAL* Add a list of people whose Issues/PRs will not be commented on
39 | # keys must be GitHub usernames
40 | requestInfoUserstoExclude:
41 | - anjmao
42 |
--------------------------------------------------------------------------------
/src/demo/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { HttpClientModule } from '@angular/common/http';
2 | import { NgModule } from '@angular/core';
3 | import { BrowserModule } from '@angular/platform-browser';
4 | import { RouterModule } from '@angular/router';
5 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
6 |
7 | import { AppComponent } from './app.component';
8 | import { DataService } from './examples/data.service';
9 | import { ExamplesModule } from './examples/examples.module';
10 | import { LayoutHeaderComponent } from './layout/header.component';
11 | import { LayoutSidenavComponent } from './layout/sidenav-component';
12 | import { appRoutes } from './routes';
13 | import { ExampleHostDirective, ExampleViewerComponent } from './shared/example-viewer/example-viewer.component';
14 | import { StackblitzButtonComponent } from './shared/example-viewer/stackblitz-button/stackblitz-button.component';
15 | import { RouteViewerComponent } from './shared/route-viewer/route-viewer.component';
16 |
17 | @NgModule({
18 | imports: [
19 | BrowserModule,
20 | HttpClientModule,
21 | ExamplesModule,
22 | NgbModule,
23 | RouterModule.forRoot(
24 | appRoutes,
25 | {
26 | useHash: true,
27 | relativeLinkResolution: 'legacy'
28 | }
29 | )
30 | ],
31 | providers: [
32 | DataService,
33 | ],
34 | declarations: [
35 | ExampleHostDirective,
36 | AppComponent,
37 | LayoutHeaderComponent,
38 | LayoutSidenavComponent,
39 | ExampleViewerComponent,
40 | StackblitzButtonComponent,
41 | RouteViewerComponent,
42 | ],
43 | entryComponents: [],
44 | bootstrap: [AppComponent]
45 | })
46 | export class AppModule {
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/src/demo/app/examples/virtual-scroll-example/virtual-scroll-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 |
4 | @Component({
5 | selector: 'virtual-scroll-example',
6 | templateUrl: './virtual-scroll-example.component.html',
7 | styleUrls: ['./virtual-scroll-example.component.scss']
8 | })
9 | export class VirtualScrollExampleComponent implements OnInit {
10 |
11 | photos = [];
12 | photosBuffer = [];
13 | bufferSize = 50;
14 | numberOfItemsFromEndBeforeFetchingMore = 10;
15 | loading = false;
16 |
17 | constructor(private http: HttpClient) {
18 | }
19 |
20 | ngOnInit() {
21 | this.http.get('https://jsonplaceholder.typicode.com/photos').subscribe(photos => {
22 | this.photos = photos;
23 | this.photosBuffer = this.photos.slice(0, this.bufferSize);
24 | });
25 | }
26 |
27 | onScrollToEnd() {
28 | this.fetchMore();
29 | }
30 |
31 | onScroll({ end }) {
32 | if (this.loading || this.photos.length <= this.photosBuffer.length) {
33 | return;
34 | }
35 |
36 | if (end + this.numberOfItemsFromEndBeforeFetchingMore >= this.photosBuffer.length) {
37 | this.fetchMore();
38 | }
39 | }
40 |
41 | private fetchMore() {
42 | const len = this.photosBuffer.length;
43 | const more = this.photos.slice(len, this.bufferSize + len);
44 | this.loading = true;
45 | // using timeout here to simulate backend API delay
46 | setTimeout(() => {
47 | this.loading = false;
48 | this.photosBuffer = this.photosBuffer.concat(more);
49 | }, 200)
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-custom-template-example/forms-custom-template-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FormBuilder, FormGroup } from '@angular/forms';
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
4 | import { DataService } from '../data.service';
5 |
6 | @Component({
7 | selector: 'forms-custom-template-example',
8 | templateUrl: './forms-custom-template-example.component.html',
9 | styleUrls: ['./forms-custom-template-example.component.scss']
10 | })
11 | export class FormsCustomTemplateExampleComponent implements OnInit {
12 |
13 | heroForm: FormGroup;
14 | photos = [];
15 |
16 | constructor(
17 | private fb: FormBuilder,
18 | private modalService: NgbModal,
19 | private dataService: DataService) {
20 | }
21 |
22 | ngOnInit() {
23 | this.loadPhotos();
24 |
25 | this.heroForm = this.fb.group({
26 | photo: ''
27 | });
28 | }
29 |
30 | selectFirstPhoto() {
31 | this.heroForm.get('photo').patchValue(this.photos[0].thumbnailUrl);
32 | }
33 |
34 | openModal(content) {
35 | this.modalService.open(content);
36 | }
37 |
38 | changePhoto(photo) {
39 | this.heroForm.get('photo').patchValue(photo ? photo.thumbnailUrl : null);
40 | }
41 |
42 | togglePhotoDisabled() {
43 | const photo = this.heroForm.get('photo');
44 | if (photo.disabled) {
45 | photo.enable();
46 | } else {
47 | photo.disable();
48 | }
49 | }
50 |
51 | private loadPhotos() {
52 | this.dataService.getPhotos().subscribe(photos => {
53 | this.photos = photos;
54 | this.selectFirstPhoto();
55 | });
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-default-example/group-default-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'group-default-example',
5 | templateUrl: './group-default-example.component.html',
6 | styleUrls: ['./group-default-example.component.scss']
7 | })
8 | export class GroupDefaultExampleComponent implements OnInit {
9 |
10 | selectedAccount = 'Adam';
11 | accounts = [
12 | { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { state: 'Active' } },
13 | { name: 'Homer', email: 'homer@email.com', age: 47, country: '', child: { state: 'Active' } },
14 | { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } },
15 | { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } },
16 | { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } },
17 | { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { state: 'Active' } },
18 | { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { state: 'Inactive' } },
19 | { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { state: 'Inactive' } },
20 | { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } },
21 | { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { state: 'Inactive' } },
22 | { name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }
23 | ];
24 |
25 | constructor() {
26 | }
27 |
28 | ngOnInit() {
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/demo/app/examples/output-events-example/output-events-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { DataService } from '../data.service';
3 |
4 | interface Event {
5 | name: string;
6 | value: any;
7 | }
8 |
9 | @Component({
10 | selector: 'output-events-example',
11 | templateUrl: './output-events-example.component.html',
12 | styleUrls: ['./output-events-example.component.scss']
13 | })
14 | export class OutputEventsExampleComponent implements OnInit {
15 |
16 | selectedItems: any;
17 | items = [];
18 |
19 | events: Event[] = [];
20 |
21 | constructor(private dataService: DataService) {
22 | this.dataService.getPeople().subscribe(items => {
23 | this.items = items;
24 | });
25 | }
26 |
27 | ngOnInit() {
28 | }
29 |
30 | onChange($event) {
31 | this.events.push({ name: '(change)', value: $event });
32 | }
33 |
34 | onFocus($event: Event) {
35 | this.events.push({ name: '(focus)', value: $event });
36 | }
37 |
38 | onBlur($event: Event) {
39 | this.events.push({ name: '(blur)', value: $event });
40 | }
41 |
42 | onOpen() {
43 | this.events.push({ name: '(open)', value: null });
44 | }
45 |
46 | onClose() {
47 | this.events.push({ name: '(close)', value: null });
48 | }
49 |
50 | onAdd($event) {
51 | this.events.push({ name: '(add)', value: $event });
52 | }
53 |
54 | onRemove($event) {
55 | this.events.push({ name: '(remove)', value: $event });
56 | }
57 |
58 | onClear() {
59 | this.events.push({ name: '(clear)', value: null });
60 | }
61 |
62 | onScrollToEnd($event) {
63 | this.events.push({ name: '(scrollToEnd)', value: $event });
64 | }
65 |
66 | onSearch($event) {
67 | this.events.push({ name: '(search)', value: $event });
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/ng-select/lib/ng-option.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AfterViewChecked,
3 | ChangeDetectionStrategy,
4 | Component,
5 | ElementRef,
6 | Input,
7 | OnChanges,
8 | OnDestroy,
9 | SimpleChanges
10 | } from '@angular/core';
11 | import { Subject } from 'rxjs';
12 |
13 | @Component({
14 | selector: 'ng-option',
15 | changeDetection: ChangeDetectionStrategy.OnPush,
16 | template: ` `
17 | })
18 | export class NgOptionComponent implements OnChanges, AfterViewChecked, OnDestroy {
19 |
20 | @Input() value: any;
21 | @Input()
22 | get disabled() { return this._disabled; }
23 | set disabled(value: any) { this._disabled = this._isDisabled(value) }
24 |
25 | readonly stateChange$ = new Subject<{ value: any, disabled: boolean, label?: string }>();
26 |
27 | private _disabled = false;
28 | private _previousLabel: string;
29 |
30 | constructor(public elementRef: ElementRef) { }
31 |
32 | get label(): string {
33 | return (this.elementRef.nativeElement.textContent || '').trim();
34 | }
35 |
36 | ngOnChanges(changes: SimpleChanges) {
37 | if (changes.disabled) {
38 | this.stateChange$.next({
39 | value: this.value,
40 | disabled: this._disabled
41 | });
42 | }
43 | }
44 |
45 | ngAfterViewChecked() {
46 | if (this.label !== this._previousLabel) {
47 | this._previousLabel = this.label;
48 | this.stateChange$.next({
49 | value: this.value,
50 | disabled: this._disabled,
51 | label: this.elementRef.nativeElement.innerHTML
52 | });
53 | }
54 | }
55 |
56 | ngOnDestroy() {
57 | this.stateChange$.complete();
58 | }
59 |
60 | private _isDisabled(value) {
61 | return value != null && `${value}` !== 'false';
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/demo/app/shared/example-viewer/example-viewer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ComponentFactoryResolver, Directive, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
2 | import { EXAMPLE_COMPONENTS } from '../../examples/examples';
3 |
4 | @Directive({
5 | selector: '[example-host]',
6 | })
7 | export class ExampleHostDirective {
8 | constructor(public viewContainerRef: ViewContainerRef) {
9 | }
10 | }
11 |
12 | @Component({
13 | selector: 'example-viewer',
14 | templateUrl: './example-viewer.component.html',
15 | styles: [`
16 | .card-header {
17 | display: flex;
18 | align-items: center;
19 | justify-content: space-between;
20 | font-weight: 500;
21 | color: rgba(0, 0, 0, 0.54);
22 | }
23 |
24 | a.btn {
25 | color: rgba(0, 0, 0, 0.54);
26 | }
27 |
28 | .card {
29 | margin-bottom: 20px;
30 | }
31 | `]
32 | })
33 | export class ExampleViewerComponent implements OnInit {
34 |
35 | @Input() example: string;
36 |
37 | @ViewChild(ExampleHostDirective, { static: true }) exampleHost: ExampleHostDirective;
38 |
39 | title: string;
40 |
41 | constructor(
42 | private componentFactoryResolver: ComponentFactoryResolver) {
43 | }
44 |
45 | get sourcePath() {
46 | return `https://github.com/ng-select/ng-select/tree/master/src/demo/app/examples/${this.example}`;
47 | }
48 |
49 | ngOnInit() {
50 | this.loadComponent();
51 | }
52 |
53 | private loadComponent() {
54 | const example = EXAMPLE_COMPONENTS[this.example];
55 | this.title = example.title;
56 | const componentFactory = this.componentFactoryResolver.resolveComponentFactory(example.component);
57 |
58 | const viewContainerRef = this.exampleHost.viewContainerRef;
59 | viewContainerRef.clear();
60 | viewContainerRef.createComponent(componentFactory);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/lib/ng-option-highlight.directive.ts:
--------------------------------------------------------------------------------
1 | import * as searchHelper from './search-helper';
2 | import {
3 | AfterViewInit,
4 | Directive,
5 | ElementRef,
6 | Input,
7 | OnChanges,
8 | Renderer2
9 | } from '@angular/core';
10 |
11 | @Directive({
12 | selector: '[ngOptionHighlight]'
13 | })
14 | export class NgOptionHighlightDirective implements OnChanges, AfterViewInit {
15 |
16 | @Input('ngOptionHighlight') term: string;
17 |
18 | private element: HTMLElement;
19 | private label: string;
20 |
21 | constructor(
22 | private elementRef: ElementRef,
23 | private renderer: Renderer2) {
24 | this.element = this.elementRef.nativeElement;
25 | }
26 |
27 | ngOnChanges() {
28 | if (this._canHighlight) {
29 | this._highlightLabel();
30 | }
31 | }
32 |
33 | ngAfterViewInit() {
34 | this.label = this.element.innerHTML;
35 | if (this._canHighlight) {
36 | this._highlightLabel();
37 | }
38 | }
39 |
40 | private _escapeRegExp(str: string): string {
41 | return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
42 | }
43 |
44 | private _highlightLabel() {
45 | const label = this.label;
46 | if (!this.term) {
47 | this._setInnerHtml(label);
48 | return;
49 | }
50 |
51 | const alternationString = this._escapeRegExp(this.term).replace(' ', '|');
52 | const termRegex = new RegExp(alternationString, 'gi');
53 | this._setInnerHtml(label.replace(termRegex, `$& `))
54 | }
55 |
56 | private get _canHighlight() {
57 | return this._isDefined(this.term) && this._isDefined(this.label);
58 | }
59 |
60 | private _setInnerHtml(html) {
61 | this.renderer.setProperty(this.elementRef.nativeElement, 'innerHTML', html);
62 | }
63 |
64 | private _isDefined(value: any) {
65 | return value !== undefined && value !== null;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/demo/app/routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 | import { RouteViewerComponent } from './shared/route-viewer/route-viewer.component';
3 |
4 | export const appRoutes: Routes = [
5 | {
6 | path: '',
7 | redirectTo: '/data-sources',
8 | pathMatch: 'full'
9 | },
10 | { path: 'data-sources', component: RouteViewerComponent, data: { title: 'Data sources', examples: 'data-source' } },
11 | { path: 'forms', component: RouteViewerComponent, data: { title: 'Reactive forms', examples: 'forms' } },
12 | { path: 'bindings', component: RouteViewerComponent, data: { title: 'Data bindings', examples: 'bindings' } },
13 | { path: 'search', component: RouteViewerComponent, data: { title: 'Search and autocomplete', examples: 'search' } },
14 | { path: 'tags', component: RouteViewerComponent, data: { title: 'Tags', examples: 'tags' } },
15 | { path: 'templates', component: RouteViewerComponent, data: { title: 'Templates', examples: 'template' } },
16 | { path: 'multiselect', component: RouteViewerComponent, data: { title: 'Multiselect', examples: 'multi-select' } },
17 | {
18 | path: 'multiselect-checkbox',
19 | component: RouteViewerComponent,
20 | data: { title: 'Multiselect checkbox', examples: 'multi-checkbox' }
21 | },
22 | { path: 'events', component: RouteViewerComponent, data: { title: 'Output events', examples: 'output-events' } },
23 | {
24 | path: 'virtual-scroll',
25 | component: RouteViewerComponent,
26 | data: { title: 'Virtual scroll', examples: 'virtual-scroll' }
27 | },
28 | {
29 | path: 'dropdown-position',
30 | component: RouteViewerComponent,
31 | data: { title: 'Dropdown position', examples: 'dropdown-position' }
32 | },
33 | {
34 | path: 'append-to-element',
35 | component: RouteViewerComponent,
36 | data: { title: 'Append to element', examples: 'append-to' }
37 | },
38 | { path: 'grouping', component: RouteViewerComponent, data: { title: 'Grouping', examples: 'group' } },
39 | ];
40 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "class-name": true,
4 | "comment-format": [
5 | true,
6 | "check-space"
7 | ],
8 | "curly": true,
9 | "eofline": true,
10 | "forin": true,
11 | "indent": [
12 | true,
13 | "spaces"
14 | ],
15 | "label-position": true,
16 | "max-line-length": [
17 | true,
18 | 140
19 | ],
20 | "member-access": false,
21 | "member-ordering": [
22 | true,
23 | "static-before-instance"
24 | ],
25 | "no-arg": true,
26 | "no-bitwise": true,
27 | "no-console": [
28 | true,
29 | "debug",
30 | "info",
31 | "time",
32 | "timeEnd",
33 | "trace"
34 | ],
35 | "no-construct": true,
36 | "no-debugger": true,
37 | "no-duplicate-variable": true,
38 | "no-empty": false,
39 | "no-eval": true,
40 | "no-inferrable-types": [true, "ignore-params"],
41 | "no-shadowed-variable": true,
42 | "no-string-literal": false,
43 | "no-switch-case-fall-through": true,
44 | "no-trailing-whitespace": false,
45 | "no-unused-expression": true,
46 | "no-use-before-declare": false,
47 | "no-var-keyword": true,
48 | "object-literal-sort-keys": false,
49 | "one-line": [
50 | true,
51 | "check-open-brace",
52 | "check-catch",
53 | "check-else",
54 | "check-whitespace"
55 | ],
56 | "quotemark": [
57 | true,
58 | "single"
59 | ],
60 | "radix": true,
61 | "semicolon": [
62 | "always"
63 | ],
64 | "triple-equals": [
65 | true,
66 | "allow-null-check"
67 | ],
68 | "typedef-whitespace": [
69 | true,
70 | {
71 | "call-signature": "nospace",
72 | "index-signature": "nospace",
73 | "parameter": "nospace",
74 | "property-declaration": "nospace",
75 | "variable-declaration": "nospace"
76 | }
77 | ],
78 | "variable-name": false,
79 | "whitespace": [
80 | true,
81 | "check-branch",
82 | "check-decl",
83 | "check-operator",
84 | "check-separator",
85 | "check-type"
86 | ]
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/demo/assets/stackblitz/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "demo": {
7 | "root": "",
8 | "sourceRoot": "",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {},
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/demo",
17 | "index": "index.html",
18 | "main": "main.ts",
19 | "polyfills": "polyfills.ts",
20 | "assets": [
21 | ],
22 | "styles": [
23 | "styles.css"
24 | ],
25 | "scripts": []
26 | },
27 | "configurations": {
28 | "production": {
29 | "fileReplacements": [
30 | {
31 | "replace": "src/environments/environment.ts",
32 | "with": "src/environments/environment.prod.ts"
33 | }
34 | ],
35 | "optimization": true,
36 | "outputHashing": "all",
37 | "sourceMap": false,
38 | "extractCss": true,
39 | "namedChunks": false,
40 | "aot": true,
41 | "extractLicenses": true,
42 | "vendorChunk": false,
43 | "buildOptimizer": true
44 | }
45 | }
46 | },
47 | "serve": {
48 | "builder": "@angular-devkit/build-angular:dev-server",
49 | "options": {
50 | "browserTarget": "demo:build"
51 | },
52 | "configurations": {
53 | "production": {
54 | "browserTarget": "demo:build:production"
55 | }
56 | }
57 | },
58 | "extract-i18n": {
59 | "builder": "@angular-devkit/build-angular:extract-i18n",
60 | "options": {
61 | "browserTarget": "demo:build"
62 | }
63 | }
64 | }
65 | }
66 | },
67 | "defaultProject": "demo"
68 | }
69 |
--------------------------------------------------------------------------------
/src/ng-select/lib/ng-select.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { NgDropdownPanelComponent } from './ng-dropdown-panel.component';
4 | import { NgOptionComponent } from './ng-option.component';
5 | import { NgSelectComponent, SELECTION_MODEL_FACTORY } from './ng-select.component';
6 | import {
7 | NgFooterTemplateDirective,
8 | NgHeaderTemplateDirective,
9 | NgLabelTemplateDirective,
10 | NgLoadingSpinnerTemplateDirective,
11 | NgLoadingTextTemplateDirective,
12 | NgMultiLabelTemplateDirective,
13 | NgNotFoundTemplateDirective,
14 | NgOptgroupTemplateDirective,
15 | NgOptionTemplateDirective,
16 | NgTagTemplateDirective,
17 | NgItemLabelDirective,
18 | NgTypeToSearchTemplateDirective
19 | } from './ng-templates.directive';
20 | import { DefaultSelectionModelFactory } from './selection-model';
21 |
22 | @NgModule({
23 | declarations: [
24 | NgDropdownPanelComponent,
25 | NgOptionComponent,
26 | NgSelectComponent,
27 | NgOptgroupTemplateDirective,
28 | NgOptionTemplateDirective,
29 | NgLabelTemplateDirective,
30 | NgMultiLabelTemplateDirective,
31 | NgHeaderTemplateDirective,
32 | NgFooterTemplateDirective,
33 | NgNotFoundTemplateDirective,
34 | NgTypeToSearchTemplateDirective,
35 | NgLoadingTextTemplateDirective,
36 | NgTagTemplateDirective,
37 | NgLoadingSpinnerTemplateDirective,
38 | NgItemLabelDirective
39 | ],
40 | imports: [
41 | CommonModule
42 | ],
43 | exports: [
44 | NgSelectComponent,
45 | NgOptionComponent,
46 | NgOptgroupTemplateDirective,
47 | NgOptionTemplateDirective,
48 | NgLabelTemplateDirective,
49 | NgMultiLabelTemplateDirective,
50 | NgHeaderTemplateDirective,
51 | NgFooterTemplateDirective,
52 | NgNotFoundTemplateDirective,
53 | NgTypeToSearchTemplateDirective,
54 | NgLoadingTextTemplateDirective,
55 | NgTagTemplateDirective,
56 | NgLoadingSpinnerTemplateDirective
57 | ],
58 | providers: [
59 | { provide: SELECTION_MODEL_FACTORY, useValue: DefaultSelectionModelFactory }
60 | ]
61 | })
62 | export class NgSelectModule {}
63 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-function-example/group-function-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'group-function-example',
5 | templateUrl: './group-function-example.component.html',
6 | styleUrls: ['./group-function-example.component.scss']
7 | })
8 | export class GroupFunctionExampleComponent implements OnInit {
9 |
10 | selectedAccounts = ['Michael'];
11 | accounts = [
12 | { name: 'Jill', email: 'jill@email.com', age: 15, country: undefined, child: { state: 'Active' } },
13 | { name: 'Henry', email: 'henry@email.com', age: 10, country: undefined, child: { state: 'Active' } },
14 | { name: 'Meg', email: 'meg@email.com', age: 7, country: null, child: { state: 'Active' } },
15 | { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { state: 'Active' } },
16 | { name: 'Homer', email: 'homer@email.com', age: 47, country: '', child: { state: 'Active' } },
17 | { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } },
18 | { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } },
19 | { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } },
20 | { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { state: 'Active' } },
21 | { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { state: 'Inactive' } },
22 | { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { state: 'Inactive' } },
23 | { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } },
24 | { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { state: 'Inactive' } },
25 | { name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }
26 | ];
27 |
28 | groupByFn = (item) => item.child.state;
29 |
30 | groupValueFn = (_: string, children: any[]) => ({ name: children[0].child.state, total: children.length });
31 |
32 | constructor() {
33 | }
34 |
35 | ngOnInit() {
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/demo/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component } from '@angular/core';
2 | import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
3 | import { Title } from '@angular/platform-browser';
4 | import { filter, map, mergeMap } from 'rxjs/operators';
5 | import { NgSelectConfig } from '@ng-select/ng-select';
6 |
7 | @Component({
8 | selector: 'demo-app',
9 | templateUrl: './app.component.html',
10 | changeDetection: ChangeDetectionStrategy.Default,
11 | })
12 | export class AppComponent {
13 |
14 | title: string;
15 | version: string = window['ngSelectVersion'];
16 | exampleSourceUrl: string;
17 | dir: 'ltr' | 'rtl' = 'ltr';
18 |
19 | constructor(
20 | private router: Router,
21 | private activatedRoute: ActivatedRoute,
22 | private titleService: Title,
23 | private config: NgSelectConfig
24 | ) {
25 | this.config.placeholder = 'Select item';
26 | // This could be useful if you want to use appendTo in entire application without explicitly defining it. (eg: appendTo = 'body')
27 | this.config.appendTo = null;
28 | // set the bindValue to global config when you use the same bindValue in most of the place.
29 | // You can also override bindValue for the specified template by defining `bindValue` as property
30 | // Eg :
31 | // this.config.bindValue = 'value';
32 | }
33 |
34 | ngOnInit() {
35 | this.setTitle();
36 | }
37 |
38 | private setTitle() {
39 | this.router.events
40 | .pipe(
41 | filter((event) => event instanceof NavigationEnd),
42 | map(() => this.activatedRoute),
43 | map((route) => {
44 | while (route.firstChild) {
45 | route = route.firstChild;
46 | }
47 | return route;
48 | }),
49 | filter((route) => route.outlet === 'primary'),
50 | mergeMap((route) => route.data)
51 | )
52 | .subscribe((event) => {
53 | this.title = event['title'];
54 | this.titleService.setTitle(this.title);
55 | this.exampleSourceUrl = `https://github.com/ng-select/ng-select/tree/master/demo/app/examples/${event['fileName']}`;
56 | });
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/ng-select/lib/ng-templates.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, Input, OnChanges, SimpleChanges, TemplateRef } from '@angular/core';
2 | import { escapeHTML } from './value-utils';
3 |
4 | @Directive({ selector: '[ngItemLabel]' })
5 | export class NgItemLabelDirective implements OnChanges {
6 | @Input() ngItemLabel: string;
7 | @Input() escape = true;
8 |
9 | constructor(private element: ElementRef) { }
10 |
11 | ngOnChanges(changes: SimpleChanges) {
12 | this.element.nativeElement.innerHTML = this.escape ?
13 | escapeHTML(this.ngItemLabel) :
14 | this.ngItemLabel;
15 | }
16 | }
17 |
18 | @Directive({ selector: '[ng-option-tmp]' })
19 | export class NgOptionTemplateDirective {
20 | constructor(public template: TemplateRef) { }
21 | }
22 |
23 | @Directive({ selector: '[ng-optgroup-tmp]' })
24 | export class NgOptgroupTemplateDirective {
25 | constructor(public template: TemplateRef) { }
26 | }
27 |
28 | @Directive({ selector: '[ng-label-tmp]' })
29 | export class NgLabelTemplateDirective {
30 | constructor(public template: TemplateRef) { }
31 | }
32 |
33 | @Directive({ selector: '[ng-multi-label-tmp]' })
34 | export class NgMultiLabelTemplateDirective {
35 | constructor(public template: TemplateRef) { }
36 | }
37 |
38 | @Directive({ selector: '[ng-header-tmp]' })
39 | export class NgHeaderTemplateDirective {
40 | constructor(public template: TemplateRef) { }
41 | }
42 |
43 | @Directive({ selector: '[ng-footer-tmp]' })
44 | export class NgFooterTemplateDirective {
45 | constructor(public template: TemplateRef) { }
46 | }
47 |
48 | @Directive({ selector: '[ng-notfound-tmp]' })
49 | export class NgNotFoundTemplateDirective {
50 | constructor(public template: TemplateRef) { }
51 | }
52 |
53 | @Directive({ selector: '[ng-typetosearch-tmp]' })
54 | export class NgTypeToSearchTemplateDirective {
55 | constructor(public template: TemplateRef) { }
56 | }
57 |
58 | @Directive({ selector: '[ng-loadingtext-tmp]' })
59 | export class NgLoadingTextTemplateDirective {
60 | constructor(public template: TemplateRef) { }
61 | }
62 |
63 | @Directive({ selector: '[ng-tag-tmp]' })
64 | export class NgTagTemplateDirective {
65 | constructor(public template: TemplateRef) { }
66 | }
67 |
68 | @Directive({ selector: '[ng-loadingspinner-tmp]' })
69 | export class NgLoadingSpinnerTemplateDirective {
70 | constructor(public template: TemplateRef) { }
71 | }
72 |
--------------------------------------------------------------------------------
/src/ng-select/lib/ng-dropdown-panel.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { NgDropdownPanelService } from './ng-dropdown-panel.service';
3 |
4 | describe('NgDropdownPanelService', () => {
5 |
6 | let service: NgDropdownPanelService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({
10 | providers: [NgDropdownPanelService]
11 | });
12 |
13 | service = TestBed.inject(NgDropdownPanelService);
14 | });
15 |
16 | describe('calculate items', () => {
17 | it('should calculate items from start', () => {
18 | const itemsLength = 100;
19 | const buffer = 4;
20 |
21 | service.setDimensions(25, 100);
22 | const res = service.calculateItems(0, itemsLength, buffer);
23 |
24 | expect(res).toEqual({
25 | start: 0,
26 | end: 9,
27 | topPadding: 0,
28 | scrollHeight: 2500
29 | })
30 | });
31 |
32 | it('should calculate items when scrolled', () => {
33 | const itemsLength = 100;
34 | const buffer = 4;
35 |
36 | service.setDimensions(25, 100);
37 | const res = service.calculateItems(1250, itemsLength, buffer);
38 |
39 | expect(res).toEqual({
40 | start: 46,
41 | end: 59,
42 | topPadding: 1150,
43 | scrollHeight: 2500
44 | })
45 | });
46 | });
47 |
48 | describe('scroll to', () => {
49 | beforeEach(() => {
50 | service.setDimensions(40, 240);
51 | });
52 |
53 | it('should not scroll if item is in visible area', () => {
54 | expect(service.getScrollTo(0, 40, 0)).toBeNull();
55 | expect(service.getScrollTo(200, 40, 0)).toBeNull();
56 | });
57 |
58 | it('should not scroll if item is inside panel height', () => {
59 | expect(service.getScrollTo(40, 40, 40)).toBeNull();
60 | });
61 |
62 | it('should scroll by item height', () => {
63 | expect(service.getScrollTo(240, 40, 0)).toBe(40);
64 | });
65 |
66 | it('should start from top when reached bottom', () => {
67 | expect(service.getScrollTo(0, 40, 400)).toBe(0);
68 | });
69 |
70 | it('should move to bottom when reached top', () => {
71 | expect(service.getScrollTo(600, 40, 0)).toBe(400);
72 | });
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-selectable-example/group-selectable-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'group-selectable-example',
5 | templateUrl: './group-selectable-example.component.html',
6 | styleUrls: ['./group-selectable-example.component.scss']
7 | })
8 | export class GroupSelectableExampleComponent implements OnInit {
9 |
10 | selectedAccount = [{ country: 'Colombia' }];
11 | selectedAccounts = [{ name: 'Adam' }];
12 | accounts = [
13 | { name: 'Jill', email: 'jill@email.com', age: 15, country: undefined, child: { state: 'Active' } },
14 | { name: 'Henry', email: 'henry@email.com', age: 10, country: undefined, child: { state: 'Active' } },
15 | { name: 'Meg', email: 'meg@email.com', age: 7, country: null, child: { state: 'Active' } },
16 | { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { state: 'Active' } },
17 | { name: 'Homer', email: 'homer@email.com', age: 47, country: '', child: { state: 'Active' } },
18 | { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } },
19 | { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } },
20 | { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } },
21 | { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { state: 'Active' } },
22 | { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { state: 'Inactive' } },
23 | { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { state: 'Inactive' }, disabled: true },
24 | { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } },
25 | { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { state: 'Inactive' } },
26 | { name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }
27 | ];
28 |
29 | ngOnInit() {
30 | }
31 |
32 | compareAccounts = (item, selected) => {
33 | if (selected.country && item.country) {
34 | return item.country === selected.country;
35 | }
36 | if (item.name && selected.name) {
37 | return item.name === selected.name;
38 | }
39 | return false;
40 | };
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/demo/app/examples/group-selectable-hidden-example/group-selectable-hidden-example.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'group-selectable-hidden-example',
5 | templateUrl: './group-selectable-hidden-example.component.html',
6 | styleUrls: ['./group-selectable-hidden-example.component.scss']
7 | })
8 | export class GroupSelectableHiddenExampleComponent implements OnInit {
9 |
10 | accounts = [
11 | { name: 'Jill', email: 'jill@email.com', age: 15, country: undefined, child: { state: 'Active' } },
12 | { name: 'Henry', email: 'henry@email.com', age: 10, country: undefined, child: { state: 'Active' } },
13 | { name: 'Meg', email: 'meg@email.com', age: 7, country: null, child: { state: 'Active' } },
14 | { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { state: 'Active' } },
15 | { name: 'Homer', email: 'homer@email.com', age: 47, country: '', child: { state: 'Active' } },
16 | { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } },
17 | { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } },
18 | { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } },
19 | { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { state: 'Active' } },
20 | { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { state: 'Inactive' } },
21 | { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { state: 'Inactive' } },
22 | { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } },
23 | { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { state: 'Inactive' } },
24 | { name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }
25 | ];
26 |
27 | selectedAccounts = [{ country: 'Argentina' }, { name: 'Samantha' }];
28 |
29 | constructor() {
30 | }
31 |
32 | ngOnInit() {
33 | }
34 |
35 | compareAccounts = (item, selected) => {
36 | if (selected.country && item.country) {
37 | return item.country === selected.country;
38 | }
39 | if (item.name && selected.name) {
40 | return item.name === selected.name;
41 | }
42 | return false;
43 | };
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | # The branches below must be a subset of the branches above
8 | branches: [master]
9 | schedule:
10 | - cron: '0 23 * * 0'
11 |
12 | jobs:
13 | analyze:
14 | name: Analyze
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | # Override automatic language detection by changing the below list
21 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
22 | language: ['javascript']
23 | # Learn more...
24 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
25 |
26 | steps:
27 | - name: Checkout repository
28 | uses: actions/checkout@v2
29 | with:
30 | # We must fetch at least the immediate parents so that if this is
31 | # a pull request then we can checkout the head.
32 | fetch-depth: 2
33 |
34 | # If this run was triggered by a pull request event, then checkout
35 | # the head of the pull request instead of the merge commit.
36 | - run: git checkout HEAD^2
37 | if: ${{ github.event_name == 'pull_request' }}
38 |
39 | # Initializes the CodeQL tools for scanning.
40 | - name: Initialize CodeQL
41 | uses: github/codeql-action/init@v1
42 | with:
43 | languages: ${{ matrix.language }}
44 | # If you wish to specify custom queries, you can do so here or in a config file.
45 | # By default, queries listed here will override any specified in a config file.
46 | # Prefix the list here with "+" to use these queries and those in the config file.
47 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
48 |
49 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
50 | # If this step fails, then you should remove it and run the build manually (see below)
51 | - name: Autobuild
52 | uses: github/codeql-action/autobuild@v1
53 |
54 | # ℹ️ Command-line programs to run using the OS shell.
55 | # 📚 https://git.io/JvXDl
56 |
57 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
58 | # and modify them (or add more) to build your code if your project
59 | # uses a compiled language
60 |
61 | #- run: |
62 | # make bootstrap
63 | # make release
64 |
65 | - name: Perform CodeQL Analysis
66 | uses: github/codeql-action/analyze@v1
67 |
--------------------------------------------------------------------------------
/src/ng-select/lib/ng-dropdown-panel.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | export interface ItemsRangeResult {
3 | scrollHeight: number;
4 | topPadding: number;
5 | start: number;
6 | end: number;
7 | }
8 |
9 | export interface PanelDimensions {
10 | itemHeight: number;
11 | panelHeight: number;
12 | itemsPerViewport: number;
13 | }
14 |
15 | @Injectable()
16 | export class NgDropdownPanelService {
17 |
18 | private _dimensions: PanelDimensions = {
19 | itemHeight: 0,
20 | panelHeight: 0,
21 | itemsPerViewport: 0
22 | };
23 |
24 | get dimensions() {
25 | return this._dimensions;
26 | }
27 |
28 | calculateItems(scrollPos: number, itemsLength: number, buffer: number): ItemsRangeResult {
29 | const d = this._dimensions;
30 | const scrollHeight = d.itemHeight * itemsLength;
31 |
32 | const scrollTop = Math.max(0, scrollPos);
33 | const indexByScrollTop = scrollTop / scrollHeight * itemsLength;
34 | let end = Math.min(itemsLength, Math.ceil(indexByScrollTop) + (d.itemsPerViewport + 1));
35 |
36 | const maxStartEnd = end;
37 | const maxStart = Math.max(0, maxStartEnd - d.itemsPerViewport);
38 | let start = Math.min(maxStart, Math.floor(indexByScrollTop));
39 |
40 | let topPadding = d.itemHeight * Math.ceil(start) - (d.itemHeight * Math.min(start, buffer));
41 | topPadding = !isNaN(topPadding) ? topPadding : 0;
42 | start = !isNaN(start) ? start : -1;
43 | end = !isNaN(end) ? end : -1;
44 | start -= buffer;
45 | start = Math.max(0, start);
46 | end += buffer;
47 | end = Math.min(itemsLength, end);
48 |
49 | return {
50 | topPadding,
51 | scrollHeight,
52 | start,
53 | end
54 | }
55 | }
56 |
57 | setDimensions(itemHeight: number, panelHeight: number) {
58 | const itemsPerViewport = Math.max(1, Math.floor(panelHeight / itemHeight));
59 | this._dimensions = {
60 | itemHeight,
61 | panelHeight,
62 | itemsPerViewport
63 | };
64 | }
65 |
66 | getScrollTo(itemTop: number, itemHeight: number, lastScroll: number) {
67 | const { panelHeight } = this.dimensions;
68 | const itemBottom = itemTop + itemHeight;
69 | const top = lastScroll;
70 | const bottom = top + panelHeight;
71 |
72 | if (panelHeight >= itemBottom && lastScroll === itemTop) {
73 | return null;
74 | }
75 |
76 | if (itemBottom > bottom) {
77 | return top + itemBottom - bottom;
78 | } else if (itemTop <= top) {
79 | return itemTop;
80 | }
81 |
82 | return null;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/demo/style/_sidebar.scss:
--------------------------------------------------------------------------------
1 | .section-nav {
2 | padding-left: 0;
3 | border-left: 1px solid #eee;
4 |
5 | ul {
6 | padding-left: 1rem;
7 |
8 | ul {
9 | display: none;
10 | }
11 | }
12 | }
13 |
14 | .toc-entry {
15 | display: block;
16 |
17 | a {
18 | display: block;
19 | padding: .125rem 1.5rem;
20 | color: #99979c;
21 |
22 | &:hover {
23 | color: $blue;
24 | text-decoration: none;
25 | }
26 | }
27 | }
28 |
29 | .bd-sidebar {
30 | order: 0;
31 | background-color: #fafafa;
32 | height: 100%;
33 | @include media-breakpoint-between(xs, sm) {
34 | height: auto;
35 | }
36 | @include media-breakpoint-up(xl) {
37 | max-width: 320px;
38 | }
39 | // All levels of nav
40 | .nav > li > a {
41 | display: block;
42 | color: rgba(0, 0, 0, .65);
43 | font-weight: 500;
44 | }
45 |
46 | .nav > li > a:hover {
47 | color: rgba(0, 0, 0, .85);
48 | text-decoration: none;
49 | background-color: transparent;
50 | }
51 |
52 | .nav > .active > a,
53 | .nav > .active:hover > a {
54 | font-weight: 500;
55 | color: rgba(0, 0, 0, .85);
56 | background-color: transparent;
57 | }
58 |
59 | .nav > .active > a.active {
60 | border: 0 #343a40 solid;
61 | border-left-width: 4px;
62 | background-color: rgba(248, 248, 248, 0.62);
63 | border-radius: 0;
64 | }
65 | }
66 |
67 | .bd-links {
68 | padding-top: 1rem;
69 | padding-bottom: 1rem;
70 | margin-right: -15px;
71 | margin-left: -15px;
72 | @include media-breakpoint-up(md) {
73 | @supports (position: sticky) {
74 | max-height: calc(100vh - 9rem);
75 | overflow-y: auto;
76 | }
77 | }
78 | // Override collapse behaviors
79 | @include media-breakpoint-up(md) {
80 | display: block !important;
81 | }
82 | }
83 |
84 | .bd-search-docs-toggle {
85 | line-height: 1;
86 | color: $gray-900;
87 | }
88 |
89 | .bd-sidenav {
90 | display: none;
91 | }
92 |
93 | .bd-toc-link {
94 | display: block;
95 | padding: .25rem 1.5rem;
96 | font-weight: 500;
97 | color: rgba(0, 0, 0, .65);
98 |
99 | &:hover {
100 | color: rgba(0, 0, 0, .85);
101 | text-decoration: none;
102 | }
103 | }
104 |
105 | .bd-toc-item {
106 | &.active {
107 | margin-bottom: 1rem;
108 |
109 | &:not(:first-child) {
110 | margin-top: 1rem;
111 | }
112 |
113 | > .bd-toc-link {
114 | color: rgba(0, 0, 0, .85);
115 |
116 | &:hover {
117 | background-color: transparent;
118 | }
119 | }
120 |
121 | > .bd-sidenav {
122 | display: block;
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": "ng-select/ng-select",
3 | "engines": {
4 | "node": ">= 12.14.0",
5 | "npm": ">= 6.0.0"
6 | },
7 | "scripts": {
8 | "build": "ng build ng-select && ng build ng-option-highlight && yarn build:themes && yarn copy-sass",
9 | "build:demo": "ng build demo --prod --baseHref=/ng-select && yarn copy-examples",
10 | "build:themes": "node-sass --output-style compressed src/ng-select/themes/ -o dist/ng-select/themes",
11 | "copy-sass": "mkdir -p dist/ng-select/scss && cp src/ng-select/**/*.scss dist/ng-select/scss",
12 | "copy-examples": "cp -r src/demo/app/examples dist/demo",
13 | "start": "ng serve",
14 | "test:watch": "ng test ng-select --watch",
15 | "test": "ng test ng-select --code-coverage && ng test ng-option-highlight --code-coverage",
16 | "test:ci": "ng test --watch=false --browsers=ChromeHeadless --code-coverage",
17 | "coveralls": "cat ./coverage/ng-select/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
18 | "lint": "ng lint ng-select && ng lint ng-option-highlight",
19 | "publish": "cd dist/ng-select && npm publish && cd ../..",
20 | "postpublish": "cp dist/ng-select/package.json src/ng-select && cp dist/ng-option-highlight/package.json src/ng-option-highlight",
21 | "release": "semantic-release"
22 | },
23 | "release": {
24 | "branch": "master",
25 | "plugins": [
26 | "@semantic-release/commit-analyzer",
27 | "@semantic-release/changelog",
28 | "@semantic-release/github",
29 | [
30 | "@semantic-release/npm",
31 | {
32 | "pkgRoot": "dist/ng-select"
33 | }
34 | ],
35 | "@semantic-release/release-notes-generator"
36 | ]
37 | },
38 | "dependencies": {
39 | "@angular/animations": "~12.0.0",
40 | "@angular/common": "~12.0.0",
41 | "@angular/compiler": "~12.0.0",
42 | "@angular/core": "~12.0.0",
43 | "@angular/forms": "~12.0.0",
44 | "@angular/localize": "^12.0.0",
45 | "@angular/platform-browser": "~12.0.0",
46 | "@angular/platform-browser-dynamic": "~12.0.0",
47 | "@angular/router": "~12.0.0",
48 | "@ng-bootstrap/ng-bootstrap": "^9.0.2",
49 | "bootstrap": "^4.5.0",
50 | "rxjs": "~6.5.5",
51 | "tslib": "^2.2.0",
52 | "zone.js": "~0.11.4"
53 | },
54 | "devDependencies": {
55 | "@angular-devkit/build-angular": "~12.0.0",
56 | "@angular/cli": "~12.0.0",
57 | "@angular/compiler-cli": "~12.0.0",
58 | "@angular/language-service": "~12.0.0",
59 | "@semantic-release/changelog": "^3.0.4",
60 | "@stackblitz/sdk": "^1.3.0",
61 | "@types/jasmine": "~3.6.0",
62 | "@types/jasminewd2": "^2.0.6",
63 | "@types/node": "^12.11.1",
64 | "codelyzer": "^6.0.0",
65 | "coveralls": "^3.0.2",
66 | "gh-pages-travis": "^1.0.4",
67 | "jasmine-core": "~3.6.0",
68 | "jasmine-spec-reporter": "~5.0.0",
69 | "karma": "~6.3.2",
70 | "karma-chrome-launcher": "~3.1.0",
71 | "karma-coverage-istanbul-reporter": "~3.0.2",
72 | "karma-jasmine": "~4.0.0",
73 | "karma-jasmine-html-reporter": "^1.5.0",
74 | "ng-packagr": "^12.0.0",
75 | "node-sass": "^4.12.0",
76 | "semantic-release": "^17.2.3",
77 | "standard-version": "^8.0.1",
78 | "ts-node": "~8.3.0",
79 | "tslint": "~6.1.0",
80 | "typescript": "~4.2.4"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/demo/polyfills.ts:
--------------------------------------------------------------------------------
1 | /***************************************************************************************************
2 | * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
3 | */
4 | import '@angular/localize/init';
5 | /**
6 | * This file includes polyfills needed by Angular and is loaded before the app.
7 | * You can add your own extra polyfills to this file.
8 | *
9 | * This file is divided into 2 sections:
10 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
11 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
12 | * file.
13 | *
14 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
15 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
16 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
17 | *
18 | * Learn more in https://angular.io/guide/browser-support
19 | */
20 |
21 | /***************************************************************************************************
22 | * BROWSER POLYFILLS
23 | */
24 |
25 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
26 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
27 |
28 | /**
29 | * Web Animations `@angular/platform-browser/animations`
30 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
31 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
32 | */
33 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
34 |
35 | /**
36 | * By default, zone.js will patch all possible macroTask and DomEvents
37 | * user can disable parts of macroTask/DomEvents patch by setting following flags
38 | * because those flags need to be set before `zone.js` being loaded, and webpack
39 | * will put import in the top of bundle, so user need to create a separate file
40 | * in this directory (for example: zone-flags.ts), and put the following flags
41 | * into that file, and then add the following code before importing zone.js.
42 | * import './zone-flags.ts';
43 | *
44 | * The flags allowed in zone-flags.ts are listed here.
45 | *
46 | * The following flags will work for all browsers.
47 | *
48 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
49 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
50 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
51 | *
52 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
53 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
54 | *
55 | * (window as any).__Zone_enable_cross_context_check = true;
56 | *
57 | */
58 |
59 | /***************************************************************************************************
60 | * Zone JS is required by default for Angular itself.
61 | */
62 | import 'zone.js'; // Included with Angular CLI.
63 |
64 |
65 | /***************************************************************************************************
66 | * APPLICATION IMPORTS
67 | */
68 |
--------------------------------------------------------------------------------
/src/demo/app/examples/forms-custom-template-example/forms-custom-template-example.component.html:
--------------------------------------------------------------------------------
1 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/ng-select/lib/selection-model.ts:
--------------------------------------------------------------------------------
1 | import { NgOption } from './ng-select.types';
2 |
3 | export type SelectionModelFactory = () => SelectionModel;
4 |
5 | export function DefaultSelectionModelFactory() {
6 | return new DefaultSelectionModel();
7 | }
8 |
9 | export interface SelectionModel {
10 | value: NgOption[];
11 | select(item: NgOption, multiple: boolean, selectableGroupAsModel: boolean);
12 | unselect(item: NgOption, multiple: boolean);
13 | clear(keepDisabled: boolean);
14 | }
15 |
16 | export class DefaultSelectionModel implements SelectionModel {
17 | private _selected: NgOption[] = [];
18 |
19 | get value(): NgOption[] {
20 | return this._selected;
21 | }
22 |
23 | select(item: NgOption, multiple: boolean, groupAsModel: boolean) {
24 | item.selected = true;
25 | if (!item.children || (!multiple && groupAsModel)) {
26 | this._selected.push(item);
27 | }
28 | if (multiple) {
29 | if (item.parent) {
30 | const childrenCount = item.parent.children.length;
31 | const selectedCount = item.parent.children.filter(x => x.selected).length;
32 | item.parent.selected = childrenCount === selectedCount;
33 | } else if (item.children) {
34 | this._setChildrenSelectedState(item.children, true);
35 | this._removeChildren(item);
36 | if (groupAsModel && this._activeChildren(item)) {
37 | this._selected = [...this._selected.filter(x => x.parent !== item), item]
38 | } else {
39 | this._selected = [...this._selected, ...item.children.filter(x => !x.disabled)];
40 | }
41 | }
42 | }
43 | }
44 |
45 | unselect(item: NgOption, multiple: boolean) {
46 | this._selected = this._selected.filter(x => x !== item);
47 | item.selected = false;
48 | if (multiple) {
49 | if (item.parent && item.parent.selected) {
50 | const children = item.parent.children;
51 | this._removeParent(item.parent);
52 | this._removeChildren(item.parent);
53 | this._selected.push(...children.filter(x => x !== item && !x.disabled));
54 | item.parent.selected = false;
55 | } else if (item.children) {
56 | this._setChildrenSelectedState(item.children, false);
57 | this._removeChildren(item);
58 | }
59 | }
60 | }
61 |
62 | clear(keepDisabled: boolean) {
63 | this._selected = keepDisabled ? this._selected.filter(x => x.disabled) : [];
64 | }
65 |
66 | private _setChildrenSelectedState(children: NgOption[], selected: boolean) {
67 | for (const child of children) {
68 | if (child.disabled) {
69 | continue;
70 | }
71 | child.selected = selected;
72 | }
73 | }
74 |
75 | private _removeChildren(parent: NgOption) {
76 | this._selected = [
77 | ...this._selected.filter(x => x.parent !== parent),
78 | ...parent.children.filter(x => x.parent === parent && x.disabled && x.selected)
79 | ];
80 | }
81 |
82 | private _removeParent(parent: NgOption) {
83 | this._selected = this._selected.filter(x => x !== parent)
84 | }
85 |
86 | private _activeChildren(item: NgOption): boolean {
87 | return item.children.every(x => !x.disabled || x.selected);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/demo/app/shared/example-viewer/stackblitz-button/stackblitz.service.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 | import sdk from '@stackblitz/sdk';
4 | import { Project } from '@stackblitz/sdk/typings/interfaces';
5 |
6 | const EXAMPLE_PATH = '/ng-select/examples/';
7 | const TEMPLATE_PATH = '/ng-select/assets/stackblitz/';
8 |
9 | const TEMPLATE_FILES = [
10 | 'index.html',
11 | 'styles.css',
12 | 'polyfills.ts',
13 | 'data.service.ts',
14 | 'main.ts',
15 | 'app.module.ts',
16 | 'angular.json',
17 | ];
18 |
19 | const angularVersion = '>=7.0.0';
20 | const dependencies = {
21 | '@angular/animations': angularVersion,
22 | '@angular/common': angularVersion,
23 | '@angular/compiler': angularVersion,
24 | '@angular/core': angularVersion,
25 | '@angular/forms': angularVersion,
26 | '@angular/http': angularVersion,
27 | '@angular/platform-browser': angularVersion,
28 | '@angular/platform-browser-dynamic': angularVersion,
29 | '@angular/router': angularVersion,
30 | '@ng-select/ng-select': '*',
31 | '@ng-select/ng-option-highlight': '*',
32 | '@ng-bootstrap/ng-bootstrap': '*',
33 | 'core-js': '^2.4.1',
34 | 'rxjs': '>=6.0.0-beta.0 <7.0.0',
35 | 'web-animations-js': '^2.3.1',
36 | 'zone.js': '^0.8.14',
37 | 'hammerjs': '^2.0.8'
38 | };
39 |
40 |
41 | @Injectable({
42 | providedIn: 'root'
43 | })
44 | export class StackblitzService {
45 |
46 | private _exampleName: string;
47 | private _componentName: string;
48 | private _examplePath: string;
49 |
50 | constructor(private _http: HttpClient) {
51 | }
52 |
53 | async openNewProject(example: string) {
54 | this._mapExamplePath(example);
55 |
56 | const exampleFiles = await this.fetchExampleFiles();
57 | const templateFiles = await this.fetchTemplateFiles();
58 |
59 | const project: Project = {
60 | files: {
61 | ...templateFiles,
62 | ...exampleFiles
63 | },
64 | title: 'ng-select example',
65 | description: 'ng-select example',
66 | template: 'angular-cli',
67 | dependencies: dependencies
68 | };
69 |
70 | sdk.openProject(project, { openFile: `src/${this._exampleName}.component.html` })
71 | }
72 |
73 | async fetchTemplateFiles() {
74 | const files: { [path: string]: string } = {};
75 | for (const file of TEMPLATE_FILES) {
76 | let fileResult = await this._readFile(file, TEMPLATE_PATH);
77 | if (file.includes('app.module')) {
78 | fileResult = fileResult.replace('//example-import', this._exampleImport);
79 | fileResult = fileResult.replace('//example-template', this._exampleTemplate);
80 | fileResult = fileResult.replace('//example-cmp', this._componentName);
81 | }
82 | files[file] = fileResult;
83 |
84 | }
85 | return files;
86 | }
87 |
88 | async fetchExampleFiles() {
89 | const exampleFiles = ['html', 'scss', 'ts'].map(ex => (`${this._exampleName}.component.${ex}`));
90 | const files: { [path: string]: string } = {};
91 |
92 | for (const file of exampleFiles) {
93 | const path = `src/${file}`;
94 | files[path] = await this._readFile(file, this._examplePath);
95 | }
96 |
97 | return files;
98 | }
99 |
100 | async _readFile(file: string, path: string) {
101 | return await this._http.get(path + file, { responseType: 'text' }).toPromise();
102 | }
103 |
104 | private _mapExamplePath(example: string) {
105 | const toUpperCase = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
106 |
107 | this._exampleName = example;
108 | this._examplePath = EXAMPLE_PATH + example + '/';
109 | this._componentName = `${this._exampleName.split('-').map(x => toUpperCase(x)).join('')}Component`;
110 | }
111 |
112 | private get _exampleImport() {
113 | return `import { ${this._componentName} } from \'./src/${this._exampleName}.component\'`
114 | }
115 |
116 | private get _exampleTemplate() {
117 | return `<${this._exampleName}>${this._exampleName}>`;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/demo/app/layout/header.component.ts:
--------------------------------------------------------------------------------
1 | import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core';
2 | // @ts-ignore
3 | import antDesignTheme from '../../../ng-select/themes/ant.design.theme.scss';
4 | // @ts-ignore
5 | import defaultTheme from '../../../ng-select/themes/default.theme.scss';
6 | // @ts-ignore
7 | import materialTheme from '../../../ng-select/themes/material.theme.scss';
8 |
9 | type langDir = 'ltr' | 'rtl';
10 |
11 | @Component({
12 | selector: 'layout-header',
13 | template: `
14 |
15 |
16 |
17 | @ng-select/ng-select
18 |
19 |
26 |
27 |
28 |
29 |
30 |
31 |
{{theme}}
33 |
34 | Default theme
35 | Material theme
36 |
37 | Ant Design theme
38 |
39 |
40 |
41 |
42 |
43 |
{{dir}}
45 |
46 | ltr
47 | rtl
48 |
49 |
50 |
51 |
53 |
54 |
62 |
63 |
64 | `
65 | })
66 | export class LayoutHeaderComponent implements AfterViewInit {
67 | theme = 'Default theme';
68 | @Input() version: string;
69 | @Input() dir: langDir;
70 | @Output() dirChange = new EventEmitter();
71 |
72 | private style: HTMLStyleElement;
73 |
74 | ngAfterViewInit() {
75 | setTimeout(() => {
76 | this.style = document.createElement('style');
77 | this.style.type = 'text/css';
78 | this.style.id = 'MyStyleTag'
79 | this.style.innerHTML = defaultTheme;
80 | document.getElementsByTagName('head')[0].appendChild(this.style);
81 | }, 100);
82 | }
83 |
84 | setTheme(theme) {
85 | this.theme = theme;
86 | if (this.theme === 'Default theme') {
87 | this.style.innerHTML = defaultTheme;
88 | } else if (this.theme === 'Material theme') {
89 | this.style.innerHTML = materialTheme;
90 | } else {
91 | this.style.innerHTML = antDesignTheme;
92 | }
93 | }
94 |
95 | changeDirTo(dir: langDir) {
96 | this.dir = dir;
97 | this.dirChange.emit(dir);
98 | }
99 | }
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/ng-option-highlight/lib/ng-option-highlight.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 | import { NgOptionHighlightDirective } from './ng-option-highlight.directive';
4 | import { By } from '@angular/platform-browser';
5 |
6 | @Component({
7 | template: `
8 | My text is highlighted
9 | My text is not highlighted
10 | My text is highlighted
11 | My ťëxť is highlighted text
12 | New label
13 | +My text is) high\\lighted
14 | `
15 | })
16 | class TestComponent {
17 | term: string;
18 | showNew = false;
19 | }
20 |
21 | describe('NgOptionHighlightDirective', () => {
22 |
23 | let fixture: ComponentFixture;
24 |
25 | beforeEach(() => {
26 | fixture = TestBed.configureTestingModule({
27 | declarations: [NgOptionHighlightDirective, TestComponent]
28 | }).createComponent(TestComponent);
29 |
30 | fixture.detectChanges();
31 | });
32 |
33 | it('should have five elements with highlight directive', () => {
34 | const highlightDirectives = fixture.debugElement.queryAll(By.directive(NgOptionHighlightDirective));
35 | expect(highlightDirectives.length).toBe(5);
36 | });
37 |
38 | it('should have one element with highlighted text when term matching', () => {
39 | const span = fixture.debugElement.query(By.css('#test1'));
40 | fixture.componentInstance.term = 'is high';
41 | fixture.detectChanges();
42 | expect(span.nativeElement.querySelectorAll('.highlighted')[0].innerHTML).toBe('is');
43 | expect(span.nativeElement.querySelectorAll('.highlighted')[1].innerHTML).toBe('high');
44 | expect(span.nativeElement.textContent).toBe('My text is highlighted');
45 | });
46 |
47 | it('should have one element with no highlighted text when term not matching', () => {
48 | const span = fixture.debugElement.query(By.css('#test2'));
49 | fixture.componentInstance.term = 'non matching';
50 | fixture.detectChanges();
51 | expect(span.nativeElement.querySelector('.highlighted')).toBeNull();
52 | expect(span.nativeElement.innerHTML).toBe('My text is not highlighted');
53 | });
54 |
55 | it('should have multiple elements with highlighted text when term matching', () => {
56 | const span = fixture.debugElement.query(By.css('#test3'));
57 | fixture.componentInstance.term = 'text highlighted';
58 | fixture.detectChanges();
59 | expect(span.nativeElement.querySelectorAll('.highlighted')[0].innerHTML).toBe('text');
60 | expect(span.nativeElement.querySelectorAll('.highlighted')[1].innerHTML).toBe('highlighted');
61 | expect(span.nativeElement.textContent).toBe('My text is highlighted');
62 | });
63 |
64 | it('Highlights special characters', () => {
65 | const span = fixture.debugElement.query(By.css('#test4'));
66 | fixture.componentInstance.term = 'ťëxť';
67 | fixture.detectChanges();
68 | expect(span.nativeElement.querySelector('.highlighted').innerHTML).toBe('ťëxť');
69 | expect(span.nativeElement.textContent).toBe('My ťëxť is highlighted text');
70 | });
71 |
72 | it('should highlight text when label changed', () => {
73 | fixture.componentInstance.term = 'new';
74 | fixture.detectChanges();
75 | fixture.componentInstance.showNew = true;
76 | fixture.detectChanges();
77 | const span = fixture.debugElement.query(By.css('#test5'));
78 | expect(span.nativeElement.querySelector('.highlighted').innerHTML).toBe('New');
79 | expect(span.nativeElement.textContent).toBe('New label');
80 | });
81 |
82 | it('should highlight text with an special character at the beginning of the term', () => {
83 | const span = fixture.debugElement.query(By.css('#test6'));
84 |
85 | fixture.componentInstance.term = '+My text';
86 | fixture.detectChanges();
87 | expect(span.nativeElement.querySelectorAll('.highlighted')[0].innerHTML).toBe('+My');
88 | expect(span.nativeElement.querySelectorAll('.highlighted')[1].innerHTML).toBe('text');
89 | expect(span.nativeElement.textContent).toBe('+My text is) high\\lighted');
90 | });
91 |
92 | it('should highlight text with an special character at the end of the term', () => {
93 | const span = fixture.debugElement.query(By.css('#test6'));
94 |
95 | fixture.componentInstance.term = 'is)';
96 | fixture.detectChanges();
97 | expect(span.nativeElement.querySelectorAll('.highlighted')[0].innerHTML).toBe('is)');
98 | expect(span.nativeElement.textContent).toBe('+My text is) high\\lighted');
99 | });
100 |
101 | it('should highlight text with an special character in the middle of the term', () => {
102 | const span = fixture.debugElement.query(By.css('#test6'));
103 |
104 | fixture.componentInstance.term = 'high\\l';
105 | fixture.detectChanges();
106 | expect(span.nativeElement.querySelectorAll('.highlighted')[0].innerHTML).toBe('high\\l');
107 | expect(span.nativeElement.textContent).toBe('+My text is) high\\lighted');
108 | });
109 | });
110 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "src",
5 | "projects": {
6 | "ng-select": {
7 | "projectType": "library",
8 | "root": "src/ng-select",
9 | "sourceRoot": "src/ng-select",
10 | "prefix": "lib",
11 | "architect": {
12 | "build": {
13 | "builder": "@angular-devkit/build-angular:ng-packagr",
14 | "options": {
15 | "tsConfig": "src/ng-select/tsconfig.lib.json",
16 | "project": "src/ng-select/ng-package.json"
17 | }
18 | },
19 | "test": {
20 | "builder": "@angular-devkit/build-angular:karma",
21 | "options": {
22 | "main": "src/ng-select/test.ts",
23 | "tsConfig": "src/ng-select/tsconfig.spec.json",
24 | "karmaConfig": "src/ng-select/karma.conf.js",
25 | "watch": false,
26 | "codeCoverageExclude": [
27 | "src/ng-select/testing/*",
28 | "src/ng-select/*.ts",
29 | "src/ng-select/lib/console.service.ts",
30 | "src/ng-select/lib/search-helper.ts",
31 | "src/ng-select/lib/ng-templates.directive.ts"
32 | ]
33 | }
34 | },
35 | "lint": {
36 | "builder": "@angular-devkit/build-angular:tslint",
37 | "options": {
38 | "tsConfig": [
39 | "src/ng-select/tsconfig.lib.json",
40 | "src/ng-select/tsconfig.spec.json"
41 | ],
42 | "exclude": ["**/node_modules/**"]
43 | }
44 | }
45 | }
46 | },
47 | "demo": {
48 | "projectType": "application",
49 | "schematics": {
50 | "@schematics/angular:component": {
51 | "style": "scss",
52 | "skipTests": true
53 | },
54 | "@schematics/angular:class": {
55 | "skipTests": true
56 | },
57 | "@schematics/angular:directive": {
58 | "skipTests": true
59 | },
60 | "@schematics/angular:guard": {
61 | "skipTests": true
62 | },
63 | "@schematics/angular:module": {
64 | "skipTests": true
65 | },
66 | "@schematics/angular:pipe": {
67 | "skipTests": true
68 | },
69 | "@schematics/angular:service": {
70 | "skipTests": true
71 | }
72 | },
73 | "root": "src/demo",
74 | "sourceRoot": "src/demo",
75 | "prefix": "",
76 | "architect": {
77 | "build": {
78 | "builder": "@angular-devkit/build-angular:browser",
79 | "options": {
80 | "outputPath": "dist/demo",
81 | "showCircularDependencies": false,
82 | "index": "src/demo/index.html",
83 | "main": "src/demo/main.ts",
84 | "polyfills": "src/demo/polyfills.ts",
85 | "tsConfig": "src/demo/tsconfig.app.json",
86 | "deployUrl": "/ng-select/",
87 | "assets": [
88 | "src/demo/favicon.ico",
89 | "src/demo/assets"
90 | ],
91 | "styles": [
92 | "src/demo/styles.scss"
93 | ],
94 | "scripts": [],
95 | "vendorChunk": true,
96 | "extractLicenses": false,
97 | "buildOptimizer": false,
98 | "sourceMap": true,
99 | "optimization": false,
100 | "namedChunks": true
101 | },
102 | "configurations": {
103 | "production": {
104 | "fileReplacements": [
105 | {
106 | "replace": "src/demo/environments/environment.ts",
107 | "with": "src/demo/environments/environment.prod.ts"
108 | }
109 | ],
110 | "optimization": true,
111 | "outputHashing": "all",
112 | "sourceMap": false,
113 | "namedChunks": false,
114 | "extractLicenses": true,
115 | "vendorChunk": false,
116 | "buildOptimizer": true,
117 | "budgets": [
118 | {
119 | "type": "initial",
120 | "maximumWarning": "2mb",
121 | "maximumError": "5mb"
122 | },
123 | {
124 | "type": "anyComponentStyle",
125 | "maximumWarning": "6kb"
126 | }
127 | ]
128 | }
129 | }
130 | },
131 | "serve": {
132 | "builder": "@angular-devkit/build-angular:dev-server",
133 | "options": {
134 | "browserTarget": "demo:build",
135 | "deployUrl": "/"
136 | },
137 | "configurations": {
138 | "production": {
139 | "browserTarget": "demo:build:production"
140 | }
141 | }
142 | }
143 | }
144 | },
145 | "ng-option-highlight": {
146 | "projectType": "library",
147 | "root": "src/ng-option-highlight",
148 | "sourceRoot": "src/ng-option-highlight",
149 | "prefix": "lib",
150 | "architect": {
151 | "build": {
152 | "builder": "@angular-devkit/build-angular:ng-packagr",
153 | "options": {
154 | "tsConfig": "src/ng-option-highlight/tsconfig.lib.json",
155 | "project": "src/ng-option-highlight/ng-package.json"
156 | }
157 | },
158 | "test": {
159 | "builder": "@angular-devkit/build-angular:karma",
160 | "options": {
161 | "main": "src/ng-option-highlight/test.ts",
162 | "tsConfig": "src/ng-option-highlight/tsconfig.spec.json",
163 | "karmaConfig": "src/ng-option-highlight/karma.conf.js",
164 | "watch": false
165 | }
166 | },
167 | "lint": {
168 | "builder": "@angular-devkit/build-angular:tslint",
169 | "options": {
170 | "tsConfig": [
171 | "src/ng-option-highlight/tsconfig.lib.json",
172 | "src/ng-option-highlight/tsconfig.spec.json"
173 | ],
174 | "exclude": ["**/node_modules/**"]
175 | }
176 | }
177 | }
178 | }
179 | },
180 | "defaultProject": "demo"
181 | }
182 |
--------------------------------------------------------------------------------
/src/ng-select/lib/ng-select.component.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
{{placeholder}}
9 |
10 |
0">
11 |
12 |
13 | ×
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
0"
25 | [ngTemplateOutlet]="multiLabelTemplate"
26 | [ngTemplateOutletContext]="{ items: selectedValues, clear: clearItem }">
27 |
28 |
29 |
34 |
35 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
60 |
61 |
62 |
63 |
64 | ×
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
92 |
93 |
94 |
104 |
105 |
106 |
107 |
108 |
109 |
112 |
113 |
114 |
115 |
116 |
117 | {{addTagText}} "{{searchTerm}}"
118 |
119 |
120 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | {{notFoundText}}
130 |
131 |
132 |
135 |
136 |
137 |
138 |
139 |
140 | {{typeToSearchText}}
141 |
142 |
143 |
145 |
146 |
147 |
148 |
149 |
150 | {{loadingText}}
151 |
152 |
153 |
156 |
157 |
158 |
159 |
160 |
--------------------------------------------------------------------------------