UNPKG

@progress/kendo-angular-navigation

Version:

Kendo UI Navigation for Angular

521 lines (508 loc) 23.3 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter, HostBinding, Input, NgZone, Output, Renderer2, ViewChild } from '@angular/core'; import { NgIf, NgClass, NgTemplateOutlet } from '@angular/common'; import { validatePackage } from '@progress/kendo-licensing'; import { packageMetadata } from '../package-metadata'; import { Subscription } from 'rxjs'; import { ActionSheetHeaderTemplateDirective, ActionSheetItemTemplateDirective, ActionSheetContentTemplateDirective, ActionSheetFooterTemplateDirective, ActionSheetTemplateDirective } from './models'; import { isDocumentAvailable, isPresent, Keys } from '@progress/kendo-angular-common'; import { getId, getActionSheetItemIndex, getFirstAndLastFocusable, ACTIONSHEET_ITEM_INDEX_ATTRIBUTE } from '../common/util'; import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n'; import { AnimationBuilder } from '@angular/animations'; import { slideDown, slideUp } from './animation/animations'; import { take } from 'rxjs/operators'; import { ActionSheetListComponent } from './list.component'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-l10n"; import * as i2 from "@angular/animations"; const DEFAULT_ANIMATION_CONFIG = { duration: 300 }; /** * Represents the [Kendo UI ActionSheet component for Angular]({% slug overview_actionsheet %}). * Used to display a set of choices related to a task the user initiates. */ export class ActionSheetComponent { element; ngZone; renderer; localizationService; builder; cdr; /** * @hidden */ get hostClass() { return this.expanded; } /** * @hidden */ direction; /** * Specifies the text that is rendered as title. */ title; /** * Specifies the text that is rendered under the title. */ subtitle; /** * The collection of items that will be rendered in the ActionSheet. */ items; /** * The CSS classes that will be rendered on the inner ActionSheet element. * Supports the type of values that are supported by [ngClass](link:site.data.urls.angular['ngclassapi']). */ cssClass; /** * Configures the ActionSheet opening and closing animations ([see example]({% slug animations_actionsheet %})). * By default the animations are turned off. The default animations' duration is `300ms`. * * @default true */ animation = true; /** * Specifies the state of the ActionSheet. * * @default false */ expanded = false; /** * Sets the `aria-labelledby` attribute of the ActionSheet wrapper element. * Use this option when the built-in header element is replaced through the [`ActionSheetHeaderTemplate`]({% slug api_navigation_actionsheetheadertemplatedirective %}) * or [`ActionSheetContentTemplate`]({% slug api_navigation_actionsheetcontenttemplatedirective %}). * */ titleId = getId('k-actionsheet-title'); /** * Fires when the `expanded` property of the component is updated. * Used to provide a two-way binding for the `expanded` property. */ expandedChange = new EventEmitter(); /** * Fires when the ActionSheet is expanded and its animation is complete. */ expand = new EventEmitter(); /** * Fires when the ActionSheet is collapsed and its animation is complete. */ collapse = new EventEmitter(); /** * Fires when an ActionSheet item is clicked. */ itemClick = new EventEmitter(); /** * Fires when the modal overlay is clicked. */ overlayClick = new EventEmitter(); /** * @hidden */ childContainer; /** * @hidden */ actionSheetTemplate; /** * @hidden */ headerTemplate; /** * @hidden */ contentTemplate; /** * @hidden */ itemTemplate; /** * @hidden */ footerTemplate; dynamicRTLSubscription; rtl = false; domSubs = new Subscription(); player; animationEnd = new EventEmitter(); constructor(element, ngZone, renderer, localizationService, builder, cdr) { this.element = element; this.ngZone = ngZone; this.renderer = renderer; this.localizationService = localizationService; this.builder = builder; this.cdr = cdr; validatePackage(packageMetadata); this.dynamicRTLSubscription = this.localizationService.changes.subscribe(({ rtl }) => { this.rtl = rtl; this.direction = this.rtl ? 'rtl' : 'ltr'; }); } ngAfterViewInit() { this.initDomEvents(); this.setCssVariables(); } ngOnChanges(changes) { if (changes['expanded'] && this.expanded) { this.setExpanded(true); } } ngOnDestroy() { this.domSubs.unsubscribe(); if (this.dynamicRTLSubscription) { this.dynamicRTLSubscription.unsubscribe(); } if (this.player) { this.player.destroy(); } } /** * Toggles the visibility of the ActionSheet. * * @param expanded? - Boolean. Specifies if the ActionSheet will be expanded or collapsed. */ toggle(expanded) { const previous = this.expanded; const current = isPresent(expanded) ? expanded : !previous; if (current === previous) { return; } if (current === true) { this.setExpanded(true); } else if (current === false && !this.animation) { this.setExpanded(false); } if (this.animation) { this.animationEnd.pipe(take(1)) .subscribe(() => { this.onAnimationEnd(current); }); this.playAnimation(current); } else { this[current ? 'expand' : 'collapse'].emit(); } } /** * @hidden */ get topGroupItems() { return this.items?.filter(item => !item.group || item.group === 'top'); } /** * @hidden */ get bottomGroupItems() { return this.items?.filter(item => item.group === 'bottom'); } /** * @hidden */ onItemClick(ev) { this.itemClick.emit(ev); } /** * @hidden */ onOverlayClick() { this.overlayClick.emit(); } /** * @hidden */ get shouldRenderSeparator() { return this.topGroupItems?.length > 0 && this.bottomGroupItems?.length > 0; } initDomEvents() { if (!this.element) { return; } this.ngZone.runOutsideAngular(() => { this.domSubs.add(this.renderer.listen(this.element.nativeElement, 'keydown', (ev) => { this.onKeyDown(ev); })); }); } setCssVariables() { if (!this.element || !isDocumentAvailable()) { return; } // The following syntax is used as it does not violate CSP compliance this.element.nativeElement.style.setProperty('--kendo-actionsheet-height', 'auto'); this.element.nativeElement.style.setProperty('--kendo-actionsheet-max-height', 'none'); } onKeyDown(event) { const target = event.target; if (event.keyCode === Keys.Tab) { this.ngZone.run(() => { this.keepFocusWithinComponent(target, event); }); } if (event.keyCode === Keys.Escape) { this.ngZone.run(() => { this.overlayClick.emit(); }); } if (event.keyCode === Keys.Enter) { this.ngZone.run(() => { this.triggerItemClick(target, event); }); } } handleInitialFocus() { const [firstFocusable] = getFirstAndLastFocusable(this.element.nativeElement); if (firstFocusable) { firstFocusable.focus(); } } keepFocusWithinComponent(target, event) { const wrapper = this.element.nativeElement; const [firstFocusable, lastFocusable] = getFirstAndLastFocusable(wrapper); const tabAfterLastFocusable = !event.shiftKey && target === lastFocusable; const shiftTabAfterFirstFocusable = event.shiftKey && target === firstFocusable; if (tabAfterLastFocusable) { event.preventDefault(); firstFocusable.focus(); } if (shiftTabAfterFirstFocusable) { event.preventDefault(); lastFocusable.focus(); } } triggerItemClick(target, event) { const itemIndex = getActionSheetItemIndex(target, ACTIONSHEET_ITEM_INDEX_ATTRIBUTE, this.element.nativeElement); const item = isPresent(itemIndex) ? this.items[itemIndex] : null; if (!item || item.disabled) { return; } this.itemClick.emit({ item, originalEvent: event }); } setExpanded(value) { this.expanded = value; this.expandedChange.emit(value); if (this.expanded) { this.cdr.detectChanges(); this.handleInitialFocus(); } } onAnimationEnd(currentExpanded) { if (currentExpanded) { this.expand.emit(); } else { this.setExpanded(false); this.collapse.emit(); } } playAnimation(expanded) { const duration = typeof this.animation !== 'boolean' && this.animation.duration ? this.animation.duration : DEFAULT_ANIMATION_CONFIG.duration; const contentHeight = getComputedStyle(this.childContainer.nativeElement).height; const animation = expanded ? slideUp(duration, contentHeight) : slideDown(duration, contentHeight); const factory = this.builder.build(animation); this.player = factory.create(this.childContainer.nativeElement); this.player.onDone(() => { if (this.player) { this.animationEnd.emit(); this.player.destroy(); this.player = null; } }); this.player.play(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ActionSheetComponent, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i1.LocalizationService }, { token: i2.AnimationBuilder }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ActionSheetComponent, isStandalone: true, selector: "kendo-actionsheet", inputs: { title: "title", subtitle: "subtitle", items: "items", cssClass: "cssClass", animation: "animation", expanded: "expanded", titleId: "titleId" }, outputs: { expandedChange: "expandedChange", expand: "expand", collapse: "collapse", itemClick: "itemClick", overlayClick: "overlayClick" }, host: { properties: { "class.k-actionsheet-container": "this.hostClass", "attr.dir": "this.direction" } }, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.actionsheet.component' } ], queries: [{ propertyName: "actionSheetTemplate", first: true, predicate: ActionSheetTemplateDirective, descendants: true }, { propertyName: "headerTemplate", first: true, predicate: ActionSheetHeaderTemplateDirective, descendants: true }, { propertyName: "contentTemplate", first: true, predicate: ActionSheetContentTemplateDirective, descendants: true }, { propertyName: "itemTemplate", first: true, predicate: ActionSheetItemTemplateDirective, descendants: true }, { propertyName: "footerTemplate", first: true, predicate: ActionSheetFooterTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "childContainer", first: true, predicate: ["childContainer"], descendants: true }], exportAs: ["kendoActionSheet"], usesOnChanges: true, ngImport: i0, template: ` <ng-container *ngIf="expanded"> <div class="k-overlay" (click)="onOverlayClick()"></div> <div class="k-animation-container k-animation-container-shown"> <div #childContainer class="k-child-animation-container" [style]="'bottom: 0px; width: 100%;'"> <div class="k-actionsheet k-actionsheet-bottom" [ngClass]="cssClass" role="dialog" aria-modal="true" [attr.aria-labelledby]="titleId"> <ng-template *ngIf="actionSheetTemplate; else defaultTemplate" [ngTemplateOutlet]="actionSheetTemplate?.templateRef"> </ng-template> <ng-template #defaultTemplate> <div *ngIf="title || subtitle || headerTemplate" class="k-actionsheet-titlebar"> <ng-template *ngIf="headerTemplate; else defaultHeaderTemplate" [ngTemplateOutlet]="headerTemplate?.templateRef"> </ng-template> <ng-template #defaultHeaderTemplate> <div class="k-actionsheet-titlebar-group k-hbox"> <div class="k-actionsheet-title" [id]="titleId"> <div *ngIf="title" class="k-text-center">{{title}}</div> <div *ngIf="subtitle" class="k-actionsheet-subtitle k-text-center">{{subtitle}}</div> </div> </div> </ng-template> </div> <div *ngIf="items || contentTemplate" class="k-actionsheet-content"> <ng-template *ngIf="contentTemplate; else defaultContentTemplate" [ngTemplateOutlet]="contentTemplate?.templateRef"> </ng-template> <ng-template #defaultContentTemplate> <div *ngIf="topGroupItems" kendoActionSheetList class="k-list-ul" role="group" [groupItems]="topGroupItems" [allItems]="items" [itemTemplate]="itemTemplate?.templateRef" (itemClick)="onItemClick($event)"> </div> <hr *ngIf="shouldRenderSeparator" class="k-hr"/> <div *ngIf="bottomGroupItems" kendoActionSheetList class="k-list-ul" role="group" [groupItems]="bottomGroupItems" [allItems]="items" [itemTemplate]="itemTemplate?.templateRef" (itemClick)="onItemClick($event)"> </div> </ng-template> </div> <div *ngIf="footerTemplate" class="k-actionsheet-footer"> <ng-template [ngTemplateOutlet]="footerTemplate?.templateRef"> </ng-template> </div> </ng-template> </div> </div> </div> </ng-container> `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ActionSheetListComponent, selector: "[kendoActionSheetList]", inputs: ["groupItems", "allItems", "itemTemplate"], outputs: ["itemClick"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ActionSheetComponent, decorators: [{ type: Component, args: [{ exportAs: 'kendoActionSheet', selector: 'kendo-actionsheet', template: ` <ng-container *ngIf="expanded"> <div class="k-overlay" (click)="onOverlayClick()"></div> <div class="k-animation-container k-animation-container-shown"> <div #childContainer class="k-child-animation-container" [style]="'bottom: 0px; width: 100%;'"> <div class="k-actionsheet k-actionsheet-bottom" [ngClass]="cssClass" role="dialog" aria-modal="true" [attr.aria-labelledby]="titleId"> <ng-template *ngIf="actionSheetTemplate; else defaultTemplate" [ngTemplateOutlet]="actionSheetTemplate?.templateRef"> </ng-template> <ng-template #defaultTemplate> <div *ngIf="title || subtitle || headerTemplate" class="k-actionsheet-titlebar"> <ng-template *ngIf="headerTemplate; else defaultHeaderTemplate" [ngTemplateOutlet]="headerTemplate?.templateRef"> </ng-template> <ng-template #defaultHeaderTemplate> <div class="k-actionsheet-titlebar-group k-hbox"> <div class="k-actionsheet-title" [id]="titleId"> <div *ngIf="title" class="k-text-center">{{title}}</div> <div *ngIf="subtitle" class="k-actionsheet-subtitle k-text-center">{{subtitle}}</div> </div> </div> </ng-template> </div> <div *ngIf="items || contentTemplate" class="k-actionsheet-content"> <ng-template *ngIf="contentTemplate; else defaultContentTemplate" [ngTemplateOutlet]="contentTemplate?.templateRef"> </ng-template> <ng-template #defaultContentTemplate> <div *ngIf="topGroupItems" kendoActionSheetList class="k-list-ul" role="group" [groupItems]="topGroupItems" [allItems]="items" [itemTemplate]="itemTemplate?.templateRef" (itemClick)="onItemClick($event)"> </div> <hr *ngIf="shouldRenderSeparator" class="k-hr"/> <div *ngIf="bottomGroupItems" kendoActionSheetList class="k-list-ul" role="group" [groupItems]="bottomGroupItems" [allItems]="items" [itemTemplate]="itemTemplate?.templateRef" (itemClick)="onItemClick($event)"> </div> </ng-template> </div> <div *ngIf="footerTemplate" class="k-actionsheet-footer"> <ng-template [ngTemplateOutlet]="footerTemplate?.templateRef"> </ng-template> </div> </ng-template> </div> </div> </div> </ng-container> `, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.actionsheet.component' } ], standalone: true, imports: [NgIf, NgClass, NgTemplateOutlet, ActionSheetListComponent] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i1.LocalizationService }, { type: i2.AnimationBuilder }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { hostClass: [{ type: HostBinding, args: ['class.k-actionsheet-container'] }], direction: [{ type: HostBinding, args: ['attr.dir'] }], title: [{ type: Input }], subtitle: [{ type: Input }], items: [{ type: Input }], cssClass: [{ type: Input }], animation: [{ type: Input }], expanded: [{ type: Input }], titleId: [{ type: Input }], expandedChange: [{ type: Output }], expand: [{ type: Output }], collapse: [{ type: Output }], itemClick: [{ type: Output }], overlayClick: [{ type: Output }], childContainer: [{ type: ViewChild, args: ['childContainer'] }], actionSheetTemplate: [{ type: ContentChild, args: [ActionSheetTemplateDirective] }], headerTemplate: [{ type: ContentChild, args: [ActionSheetHeaderTemplateDirective] }], contentTemplate: [{ type: ContentChild, args: [ActionSheetContentTemplateDirective] }], itemTemplate: [{ type: ContentChild, args: [ActionSheetItemTemplateDirective] }], footerTemplate: [{ type: ContentChild, args: [ActionSheetFooterTemplateDirective] }] } });