/*
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostBinding,
HostListener,
Inject,
Input,
OnDestroy,
Optional,
Output,
AfterViewInit,
NgZone,
Renderer2,
} from '@angular/core';
import { Observable, Subject } from 'rxjs';
// Component class scoped counter for aria attributes.
let lastOptionId: number = 0;
import { convertToBoolProperty, NbBooleanInput } from '../helpers';
import { NbFocusableOption } from '../cdk/a11y/focus-key-manager';
import { NbHighlightableOption } from '../cdk/a11y/descendant-key-manager';
import { NB_SELECT_INJECTION_TOKEN } from '../select/select-injection-tokens';
import { NbSelectComponent } from '../select/select.component';
/**
* NbOptionComponent
*
* @styles
*
* option-background-color:
* option-text-color:
* option-text-font-family:
* option-hover-background-color:
* option-hover-text-color:
* option-active-background-color:
* option-active-text-color:
* option-focus-background-color:
* option-focus-text-color:
* option-selected-background-color:
* option-selected-text-color:
* option-selected-hover-background-color:
* option-selected-hover-text-color:
* option-selected-active-background-color:
* option-selected-active-text-color:
* option-selected-focus-background-color:
* option-selected-focus-text-color:
* option-disabled-background-color:
* option-disabled-text-color:
* option-tiny-text-font-size:
* option-tiny-text-font-weight:
* option-tiny-text-line-height:
* option-tiny-padding:
* option-small-text-font-size:
* option-small-text-font-weight:
* option-small-text-line-height:
* option-small-padding:
* option-medium-text-font-size:
* option-medium-text-font-weight:
* option-medium-text-line-height:
* option-medium-padding:
* option-large-text-font-size:
* option-large-text-font-weight:
* option-large-text-line-height:
* option-large-padding:
* option-giant-text-font-size:
* option-giant-text-font-weight:
* option-giant-text-line-height:
* option-giant-padding:
**/
@Component({
selector: 'nb-option',
styleUrls: ['./option.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
`,
})
export class NbOptionComponent implements OnDestroy, AfterViewInit, NbFocusableOption, NbHighlightableOption {
protected disabledByGroup = false;
/**
* Option value that will be fired on selection.
* */
@Input() value: T;
@Input()
get disabled(): boolean {
return this._disabled || this.disabledByGroup;
}
set disabled(value: boolean) {
this._disabled = convertToBoolProperty(value);
}
protected _disabled: boolean = false;
static ngAcceptInputType_disabled: NbBooleanInput;
/**
* Fires value when option selection change.
* */
@Output() selectionChange: EventEmitter> = new EventEmitter();
/**
* Fires when option clicked
*/
protected click$: Subject> = new Subject>();
get click(): Observable> {
return this.click$.asObservable();
}
selected: boolean = false;
protected parent: NbSelectComponent;
protected alive: boolean = true;
/**
* Component scoped id for aria attributes.
* */
@HostBinding('attr.id')
id: string = `nb-option-${lastOptionId++}`;
constructor(@Optional() @Inject(NB_SELECT_INJECTION_TOKEN) parent,
protected elementRef: ElementRef,
protected cd: ChangeDetectorRef,
protected zone: NgZone,
protected renderer: Renderer2) {
this.parent = parent;
}
ngOnDestroy() {
this.alive = false;
}
ngAfterViewInit() {
// TODO: #2254
this.zone.runOutsideAngular(() => setTimeout(() => {
this.renderer.addClass(this.elementRef.nativeElement, 'nb-transition');
}));
}
/**
* Determines should we render checkbox.
* */
get withCheckbox(): boolean {
return this.multiple && this.value != null;
}
get content() {
return this.elementRef.nativeElement.textContent;
}
// TODO: replace with isShowCheckbox property to control this behaviour outside, issues/1965
@HostBinding('class.multiple')
get multiple() {
// We check parent existing because parent can be NbSelectComponent or
// NbAutocomplete and `miltiple` property exists only in NbSelectComponent
return this.parent ? (this.parent as NbSelectComponent).multiple : false;
}
@HostBinding('class.selected')
get selectedClass(): boolean {
return this.selected;
}
@HostBinding('attr.disabled')
get disabledAttribute(): '' | null {
return this.disabled ? '' : null;
}
@HostBinding('tabIndex')
get tabindex() {
return '-1';
}
@HostBinding('class.active')
get activeClass() {
return this._active;
};
protected _active: boolean = false;
@HostListener('click', ['$event'])
@HostListener('keydown.space', ['$event'])
@HostListener('keydown.enter', ['$event'])
onClick(event) {
this.click$.next(this);
// Prevent scroll on space click, etc.
event.preventDefault();
}
select() {
this.setSelection(true);
}
deselect() {
this.setSelection(false);
}
/**
* Sets disabled by group state and marks component for check.
*/
setDisabledByGroupState(disabled: boolean): void {
// Check if the component still alive as the option group defer method call so the component may become destroyed.
if (this.disabledByGroup !== disabled && this.alive) {
this.disabledByGroup = disabled;
this.cd.markForCheck();
}
}
protected setSelection(selected: boolean): void {
/**
* In case of changing options in runtime the reference to the selected option will be kept in select component.
* This may lead to exceptions with detecting changes in destroyed component.
*
* Also Angular can call writeValue on destroyed view (select implements ControlValueAccessor).
* angular/angular#27803
* */
if (this.alive && this.selected !== selected) {
this.selected = selected;
this.selectionChange.emit(this);
this.cd.markForCheck();
}
}
focus(): void {
this.elementRef.nativeElement.focus();
}
getLabel(): string {
return this.content;
}
setActiveStyles(): void {
this._active = true;
this.cd.markForCheck();
}
setInactiveStyles(): void {
this._active = false;
this.cd.markForCheck();
}
}