@progress/kendo-angular-navigation
Version:
Kendo UI Navigation for Angular
334 lines (333 loc) • 14.1 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Component, HostBinding, Input, Output, EventEmitter, ElementRef, Renderer2, ContentChild, NgZone, ChangeDetectorRef } from '@angular/core';
import { Keys } from '@progress/kendo-angular-common';
import { LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n';
import { validatePackage } from '@progress/kendo-licensing';
import { Subscription } from 'rxjs';
import { BottomNavigationSelectEvent } from './events/select-event';
import { BottomNavigationItemTemplateDirective } from './templates/item-template.directive';
import { closestItem, itemIndex } from '../common/dom-queries';
import { BOTTOMNAVIGATION_ITEM_INDEX, colors } from './constants';
import { packageMetadata } from '../package-metadata';
import { BottomNavigationItemComponent } from './bottomnavigation-item.component';
import { NgIf, NgFor, NgClass, NgStyle } from '@angular/common';
import * as i0 from "@angular/core";
import * as i1 from "@progress/kendo-angular-l10n";
/**
* Represents the [Kendo UI BottomNavigation component for Angular]({% slug overview_bottomnavigation %}).
*
* Use the BottomNavigation component to let users quickly switch between primary views in your app.
*
* @example
* ```typescript
* @Component({
* selector: 'my-app',
* template: `
* <kendo-bottomnavigation [items]="items"></kendo-bottomnavigation>
* `
* })
* class AppComponent {
* public items: Array<any> = [
* { text: 'Inbox', icon: 'envelop', selected: true },
* { text: 'Calendar', icon: 'calendar'},
* { text: 'Profile', icon: 'user'}
* ];
* }
* ```
*/
export class BottomNavigationComponent {
localization;
hostElement;
ngZone;
changeDetector;
renderer;
/**
* Provides the collection of items rendered in the BottomNavigation ([see example]({% slug items_bottomnavigation %})).
*/
items;
/**
* Shows a top border on the BottomNavigation ([see example]({% slug appearance_bottomnavigation %})).
*
* @default false
*/
border = false;
/**
* Disables the entire BottomNavigation.
*
* @default false
*/
disabled = false;
/**
* Sets the fill style of the BottomNavigation ([see example]({% slug appearance_bottomnavigation %})).
*
* @default 'flat'
*/
set fill(fill) {
this.renderer.removeClass(this._nativeHostElement, `k-bottom-nav-${this.fill}`);
this.renderer.removeClass(this._nativeHostElement, `k-bottom-nav-${this.fill}-${this.themeColor}`);
this._fill = fill === 'solid' ? 'solid' : 'flat';
this.renderer.addClass(this._nativeHostElement, `k-bottom-nav-${this._fill}`);
this.renderer.addClass(this._nativeHostElement, `k-bottom-nav-${this._fill}-${this.themeColor}`);
}
get fill() {
return this._fill;
}
/**
* Controls how the icon and text label are positioned in the BottomNavigation items.
*
* @default 'vertical'
*/
set itemFlow(itemFlow) {
this.renderer.removeClass(this._nativeHostElement, `k-bottom-nav-item-flow-${this.itemFlow}`);
this._itemFlow = itemFlow === 'horizontal' ? 'horizontal' : 'vertical';
this.renderer.addClass(this._nativeHostElement, `k-bottom-nav-item-flow-${this._itemFlow}`);
}
get itemFlow() {
return this._itemFlow;
}
/**
* Sets the position and behavior of the BottomNavigation when the page is scrollable ([see example]({% slug positioning_bottomnavigation %})).
*
* @default 'fixed'
*/
set positionMode(positionMode) {
this.renderer.removeClass(this._nativeHostElement, `k-pos-${this.positionMode}`);
this._positionMode = positionMode === 'sticky' ? 'sticky' : 'fixed';
this.renderer.addClass(this._nativeHostElement, `k-pos-${this._positionMode}`);
}
get positionMode() {
return this._positionMode;
}
/**
* Sets the theme color of the BottomNavigation ([see example]({% slug appearance_bottomnavigation %})).
*
* @default 'primary'
*/
set themeColor(themeColor) {
const newColor = colors.find(color => color === themeColor);
if (newColor) {
this.renderer.removeClass(this._nativeHostElement, `k-bottom-nav-${this.fill}-${this._themeColor}`);
this._themeColor = themeColor;
this.renderer.addClass(this._nativeHostElement, `k-bottom-nav-${this.fill}-${this._themeColor}`);
}
}
get themeColor() {
return this._themeColor;
}
/**
* Fires when a user selects an item. This event is preventable.
*/
select = new EventEmitter();
/**
* @hidden
*/
hostClass = true;
/**
* @hidden
*/
get borderClass() {
return this.border;
}
/**
* @hidden
*/
get disabledClass() {
return this.disabled;
}
/**
* @hidden
*/
role = 'navigation';
/**
* @hidden
*/
direction;
/**
* @hidden
*/
itemTemplate;
/**
* @hidden
*/
selectedIdx;
_fill = 'flat';
_itemFlow = 'vertical';
_positionMode = 'fixed';
_themeColor = 'primary';
_nativeHostElement;
dynamicRTLSubscription;
subscriptions = new Subscription();
rtl = false;
constructor(localization, hostElement, ngZone, changeDetector, renderer) {
this.localization = localization;
this.hostElement = hostElement;
this.ngZone = ngZone;
this.changeDetector = changeDetector;
this.renderer = renderer;
validatePackage(packageMetadata);
this._nativeHostElement = this.hostElement.nativeElement;
this.dynamicRTLSubscription = this.localization.changes.subscribe(({ rtl }) => {
this.rtl = rtl;
this.direction = this.rtl ? 'rtl' : 'ltr';
});
}
/**
* @hidden
*/
ngOnInit() {
this.initDomEvents();
}
/**
* @hidden
*/
ngAfterViewInit() {
this.applyClasses();
}
/**
* @hidden
*/
ngOnDestroy() {
if (this.dynamicRTLSubscription) {
this.dynamicRTLSubscription.unsubscribe();
}
this.subscriptions.unsubscribe();
}
/**
* @hidden
*/
selectItem(idx, args) {
const eventArgs = new BottomNavigationSelectEvent({ ...args });
this.select.emit(eventArgs);
if (!eventArgs.isDefaultPrevented()) {
this.selectedIdx = idx;
}
}
applyClasses() {
this.renderer.addClass(this._nativeHostElement, `k-bottom-nav-${this.fill}`);
this.renderer.addClass(this._nativeHostElement, `k-bottom-nav-item-flow-${this.itemFlow}`);
this.renderer.addClass(this._nativeHostElement, `k-pos-${this.positionMode}`);
this.renderer.addClass(this._nativeHostElement, `k-bottom-nav-${this.fill}-${this.themeColor}`);
}
initDomEvents() {
if (!this.hostElement) {
return;
}
this.ngZone.runOutsideAngular(() => {
this.subscriptions.add(this.renderer.listen(this._nativeHostElement, 'click', this.clickHandler.bind(this)));
this.subscriptions.add(this.renderer.listen(this._nativeHostElement, 'keydown', this.keyDownHandler.bind(this)));
});
}
clickHandler(e) {
const itemIdx = this.getBottomNavigationItemIndex(e.target);
const item = this.items[itemIdx];
if (!item) {
return;
}
if (item.disabled) {
e.preventDefault();
return;
}
const args = {
index: itemIdx,
item: item,
originalEvent: e,
sender: this
};
this.ngZone.run(() => {
this.selectItem(itemIdx, args);
this.changeDetector.markForCheck();
});
}
keyDownHandler(e) {
const isEnterOrSpace = e.code === Keys.Enter || e.code === Keys.NumpadEnter || e.code === Keys.Space;
if (!isEnterOrSpace) {
return;
}
this.clickHandler(e);
}
getBottomNavigationItemIndex(target) {
const item = closestItem(target, BOTTOMNAVIGATION_ITEM_INDEX, this._nativeHostElement);
if (item) {
return itemIndex(item, BOTTOMNAVIGATION_ITEM_INDEX);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: BottomNavigationComponent, deps: [{ token: i1.LocalizationService }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: BottomNavigationComponent, isStandalone: true, selector: "kendo-bottomnavigation", inputs: { items: "items", border: "border", disabled: "disabled", fill: "fill", itemFlow: "itemFlow", positionMode: "positionMode", themeColor: "themeColor" }, outputs: { select: "select" }, host: { properties: { "class.k-bottom-nav": "this.hostClass", "class.k-bottom-nav-border": "this.borderClass", "class.k-disabled": "this.disabledClass", "attr.role": "this.role", "attr.dir": "this.direction" } }, providers: [
LocalizationService,
{
provide: L10N_PREFIX,
useValue: 'kendo.bottomnavigation'
}
], queries: [{ propertyName: "itemTemplate", first: true, predicate: BottomNavigationItemTemplateDirective, descendants: true }], exportAs: ["kendoBottomNavigation"], ngImport: i0, template: "\n <ng-container *ngIf=\"items\">\n <span kendoBottomNavigationItem\n *ngFor=\"let item of items; let idx=index\"\n role=\"link\"\n class=\"k-bottom-nav-item\"\n [disabledComponent]=\"disabled\"\n [item]=\"item\"\n [index]=\"idx\"\n [selectedIdx]=\"selectedIdx\"\n [itemTemplate]=\"itemTemplate\"\n [attr.data-kendo-bottomnavigation-index]=\"idx\"\n [ngClass]=\"item.cssClass\"\n [ngStyle]=\"item.cssStyle\"\n [orientation]=\"itemFlow\">\n </span>\n </ng-container>\n ", isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: BottomNavigationItemComponent, selector: "[kendoBottomNavigationItem]", inputs: ["itemTemplate", "item", "index", "disabledComponent", "selectedIdx", "orientation"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: BottomNavigationComponent, decorators: [{
type: Component,
args: [{
exportAs: 'kendoBottomNavigation',
selector: 'kendo-bottomnavigation',
providers: [
LocalizationService,
{
provide: L10N_PREFIX,
useValue: 'kendo.bottomnavigation'
}
],
template: `
<ng-container *ngIf="items">
<span kendoBottomNavigationItem
*ngFor="let item of items; let idx=index"
role="link"
class="k-bottom-nav-item"
[disabledComponent]="disabled"
[item]="item"
[index]="idx"
[selectedIdx]="selectedIdx"
[itemTemplate]="itemTemplate"
[attr.${BOTTOMNAVIGATION_ITEM_INDEX}]="idx"
[ngClass]="item.cssClass"
[ngStyle]="item.cssStyle"
[orientation]="itemFlow">
</span>
</ng-container>
`,
standalone: true,
imports: [NgIf, NgFor, BottomNavigationItemComponent, NgClass, NgStyle]
}]
}], ctorParameters: function () { return [{ type: i1.LocalizationService }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }]; }, propDecorators: { items: [{
type: Input
}], border: [{
type: Input
}], disabled: [{
type: Input
}], fill: [{
type: Input
}], itemFlow: [{
type: Input
}], positionMode: [{
type: Input
}], themeColor: [{
type: Input
}], select: [{
type: Output
}], hostClass: [{
type: HostBinding,
args: ['class.k-bottom-nav']
}], borderClass: [{
type: HostBinding,
args: ['class.k-bottom-nav-border']
}], disabledClass: [{
type: HostBinding,
args: ['class.k-disabled']
}], role: [{
type: HostBinding,
args: ['attr.role']
}], direction: [{
type: HostBinding,
args: ['attr.dir']
}], itemTemplate: [{
type: ContentChild,
args: [BottomNavigationItemTemplateDirective, { static: false }]
}] } });