UNPKG

@progress/kendo-angular-navigation

Version:

Kendo UI Navigation for Angular

334 lines (333 loc) 14.1 kB
/**----------------------------------------------------------------------------------------- * 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 }] }] } });