UNPKG

@progress/kendo-angular-layout

Version:

Kendo UI for Angular Layout Package - a collection of components to create professional application layoyts

1,324 lines (1,308 loc) 557 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import * as i0 from '@angular/core'; import { Injectable, Directive, Optional, isDevMode, Component, SkipSelf, Host, Input, ViewChild, HostBinding, ViewChildren, ContentChildren, EventEmitter, Output, ContentChild, HostListener, Inject, QueryList, NgZone, forwardRef, ElementRef, ViewEncapsulation, TemplateRef, NgModule } from '@angular/core'; import * as i1 from '@progress/kendo-angular-l10n'; import { LocalizationService, L10N_PREFIX, ComponentMessages } from '@progress/kendo-angular-l10n'; import * as i1$1 from '@progress/kendo-angular-common'; import { Keys, shouldShowValidationUI, WatermarkOverlayComponent, isDocumentAvailable, anyChanged, isObjectPresent, removeHTMLAttributes, parseAttributes, setHTMLAttributes, DraggableDirective, PreventableEvent as PreventableEvent$1, guid, ResizeSensorComponent, hasObservers, isPresent as isPresent$1, focusableSelector, isChanged } from '@progress/kendo-angular-common'; import { validatePackage } from '@progress/kendo-licensing'; import * as i1$2 from '@angular/animations'; import { trigger, state, style, transition, animate, AUTO_STYLE } from '@angular/animations'; import { Subject, BehaviorSubject, Subscription, of } from 'rxjs'; import { chevronUpIcon, chevronDownIcon, caretAltLeftIcon, caretAltRightIcon, caretAltUpIcon, caretAltDownIcon, xIcon, checkCircleIcon, exclamationCircleIcon, chevronRightIcon } from '@progress/kendo-svg-icons'; import { IconWrapperComponent } from '@progress/kendo-angular-icons'; import { NgIf, NgTemplateOutlet, NgFor, NgStyle, NgClass } from '@angular/common'; import { delay, takeUntil, map, tap, filter, switchMap, take } from 'rxjs/operators'; import { ProgressBarComponent } from '@progress/kendo-angular-progressbar'; import { Draggable } from '@progress/kendo-draggable'; import { ButtonComponent } from '@progress/kendo-angular-buttons'; import { DatePipe } from '@progress/kendo-angular-intl'; /** * @hidden */ const packageMetadata = { name: '@progress/kendo-angular-layout', productName: 'Kendo UI for Angular', productCode: 'KENDOUIANGULAR', productCodes: ['KENDOUIANGULAR'], publishDate: 1749540075, version: '19.1.1', licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/?utm_medium=product&utm_source=kendoangular&utm_campaign=kendo-ui-angular-purchase-license-keys-warning' }; /** * Represents the expand modes of the PanelBar. * By default, the expand mode is set to `multiple`. */ var PanelBarExpandMode; (function (PanelBarExpandMode) { /** * Allows you to expand only one item at a time. * When you expand an item, the item that was previously expanded is coll. */ PanelBarExpandMode[PanelBarExpandMode["Single"] = 0] = "Single"; /** * Allows you to expand only one item at a time and requires you to set the `height` property. * The expanded area occupies the entire height of the PanelBar. */ PanelBarExpandMode[PanelBarExpandMode["Full"] = 1] = "Full"; /** * The default mode of the PanelBar. * Allows you to expand more than one item at a time. Items can also be toggled. */ PanelBarExpandMode[PanelBarExpandMode["Multiple"] = 2] = "Multiple"; /** * By default, the expand mode is set to `multiple`. */ PanelBarExpandMode[PanelBarExpandMode["Default"] = 2] = "Default"; })(PanelBarExpandMode || (PanelBarExpandMode = {})); /** * @hidden */ let nextPanelbarId = 0; /** * @hidden */ class PanelBarService { children$; keepContent$; parent$; pbId; animate; expandMode; itemClick; childSource; keepContentSource; parentSource; onKeepContent(keepContent) { this.keepContentSource.next(keepContent); } onSelect(event) { this.childSource.next(event); } onFocus() { this.parentSource.next(true); } onBlur() { this.parentSource.next(false); } constructor() { this.parentSource = new Subject(); this.keepContentSource = new BehaviorSubject(false); this.childSource = new Subject(); this.itemClick = new Subject(); this.parent$ = this.parentSource.asObservable(); this.children$ = this.childSource.asObservable(); this.keepContent$ = this.keepContentSource.asObservable(); this.pbId = nextPanelbarId++; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarService, decorators: [{ type: Injectable }], ctorParameters: function () { return []; } }); /** * Represents the content template of the declaratively initialized PanelBar items. * The content can be expanded or collapsed through the item. */ class PanelBarContentDirective { templateRef; constructor(templateRef) { this.templateRef = templateRef; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarContentDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: PanelBarContentDirective, isStandalone: true, selector: "[kendoPanelBarContent]", ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarContentDirective, decorators: [{ type: Directive, args: [{ selector: "[kendoPanelBarContent]", standalone: true }] }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; } }); /** * Represents the template directive of the PanelBar which helps to customize the item title * ([more information and example]({% slug templates_panelbar %}#toc-customizing-the-appearance-of-the-title)). * * > The `kendoPanelBarItemTitle` directive overrides the PanelBarItem [title]({% slug api_layout_panelbaritemcomponent %}#toc-title) option. * * @example * ```ts-preview * * _@Component({ * selector: 'my-app', * template: ` * <kendo-panelbar> * <kendo-panelbar-item [expanded]="true"> * <ng-template kendoPanelBarItemTitle> * Item Title * </ng-template> * </kendo-panelbar-item> * </kendo-panelbar> * ` * }) * * class AppComponent {} * * ``` */ class PanelBarItemTitleDirective { templateRef; constructor(templateRef) { this.templateRef = templateRef; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarItemTitleDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: PanelBarItemTitleDirective, isStandalone: true, selector: "[kendoPanelBarItemTitle]", ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarItemTitleDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoPanelBarItemTitle]', standalone: true }] }], ctorParameters: function () { return [{ type: i0.TemplateRef, decorators: [{ type: Optional }] }]; } }); let nextId$1 = 0; const SIZES = { small: 'sm', medium: 'md', large: 'lg' }; const ROUNDNESS = { small: 'sm', medium: 'md', large: 'lg', full: 'full' }; const SHAPE_TO_ROUNDED = { rounded: 'large', circle: 'full' }; /** * @hidden */ const parsePanelBarItems = (data) => { return data.map((item) => { if (!isPresent(item.id)) { item.id = `default-${nextId$1++}`; } if (item.children) { item.children = parsePanelBarItems(item.children); } return item; }); }; /** * @hidden */ const isPresent = (value) => value !== null && value !== undefined; /** * @hidden */ const isHorizontalArrowKey = keyCode => keyCode === Keys.ArrowLeft || keyCode === Keys.ArrowRight; /** * @hidden */ const isVerticalArrowKey = keyCode => keyCode === Keys.ArrowUp || keyCode === Keys.ArrowDown; /** * @hidden */ const isArrowKey = keyCode => isHorizontalArrowKey(keyCode) || isVerticalArrowKey(keyCode); /** * @hidden */ const isNavigationKey = keyCode => keyCode === Keys.PageUp || keyCode === Keys.PageDown || keyCode === Keys.Home || keyCode === Keys.End; /** * @hidden * * Returns the styling classes to be added and removed */ const getStylingClasses = (componentType, stylingOption, previousValue, newValue) => { switch (stylingOption) { case 'size': return { toRemove: `k-${componentType}-${SIZES[previousValue]}`, toAdd: newValue !== 'none' ? `k-${componentType}-${SIZES[newValue]}` : '' }; case 'rounded': return { toRemove: `k-rounded-${ROUNDNESS[previousValue]}`, toAdd: newValue !== 'none' ? `k-rounded-${ROUNDNESS[newValue]}` : '' }; default: break; } }; /** * @hidden */ const mapShapeToRounded = (shape) => SHAPE_TO_ROUNDED[shape] || 'none'; /** * @hidden */ const isNumber = (value) => typeof value === 'number' && isFinite(value); const focusableRegex = /^(?:a|input|select|option|textarea|button|object)$/i; const toClassList = (classNames) => String(classNames).trim().split(' '); /** * @hidden */ const isFocusable = (element) => { if (element.tagName) { const tagName = element.tagName.toLowerCase(); const tabIndex = element.getAttribute('tabindex'); const skipTab = tabIndex === '-1'; let focusable = tabIndex !== null && !skipTab; if (focusableRegex.test(tagName)) { focusable = !element.disabled && !skipTab; } return focusable; } return false; }; /** * @hidden */ const hasClass = (element, className) => Boolean(toClassList(element.className).find((name) => name === className)); /** * @hidden */ const closestInScope = (target, predicate, scope, targetAttr) => { while (target && target !== scope && !predicate(target, targetAttr)) { target = target.parentNode; } if (target !== scope) { return target; } }; /** * @hidden */ const itemIndex = (item, indexAttr) => +item.getAttribute(indexAttr); const hasItemIndex = (item, indexAttr) => isPresent(item.getAttribute(indexAttr)); /** * @hidden */ const closestItem = (target, targetAttr, scope) => closestInScope(target, hasItemIndex, scope, targetAttr); /* eslint-disable @typescript-eslint/no-explicit-any */ /** * @hidden */ let nextId = 0; /** * Represents the items of the PanelBar. */ class PanelBarItemComponent { parent; eventService; element; renderer; /** * Sets the title of the PanelBar item ([see example]({% slug items_panelbar %}#toc-titles)). */ title; /** * Allows the component to set the `"id"` property to each item. * Used to set the `id` attributes of the nested elements and to enable the WAI-ARIA support. */ id = `default-${nextId++}`; /** * Defines the icon that will be rendered next to the title ([see example]({% slug items_panelbar %}#toc-title-icons)). */ icon = ''; /** * Defines the icon that will be rendered next to the title by using a custom CSS class * ([see example]({% slug items_panelbar %}#toc-title-icons)). */ iconClass = ''; /** * Defines an SVGIcon to be rendered. * The input can take either an [existing Kendo SVG icon](slug:svgicon_list) or a custom one. */ set svgIcon(icon) { if (isDevMode() && icon && this.icon && this.iconClass) { throw new Error('Setting both icon/svgIcon and iconClass options at the same time is not supported.'); } this._svgIcon = icon; } get svgIcon() { return this._svgIcon; } /** * Defines the location of the image that will be displayed next to the title * ([see example]({% slug items_panelbar %}#toc-title-images)). */ imageUrl = ''; /** * When set to `true`, disables a PanelBar item ([see example]({% slug items_panelbar %}#toc-disabled-state)). */ disabled = false; /** * When set to `true`, expands the PanelBar item ([see example]({% slug items_panelbar %}#toc-expanded-state)). */ set expanded(value) { const activeState = this.animate ? "active" : "activeWithoutAnimation"; this.state = value ? activeState : "inactive"; if (!this.keepContent) { this.toggleExpandedChildAnimations(value); } this._expanded = value; } get expanded() { return this._expanded; } /** * Sets the selected state of a PanelBar item ([see example]({% slug items_panelbar %}#toc-selected-state)). */ selected = false; /** * Sets the content of the PanelBar item. * By design, it is used when the * [items]({% slug api_layout_panelbarcomponent %}#toc-items) * property of the PanelBar is set. */ content; /** * @hidden */ items; /** * @hidden */ template; header; contentWrapper; contentHeight; contentOverflow; keepContent = false; childrenItems; hasChildItems = false; hasItems = false; hasContent = false; state = "inactive"; get animate() { return this.eventService.animate; } role = "treeitem"; titleAttribute = null; // eslint-disable-line kItemClass = true; get kStateExpandedClass() { return !this.disabled && this.expanded && (this.hasChildItems || this.hasContent); } get itemId() { return 'k-panelbar-' + this.eventService.pbId + '-item-' + this.id; } get ariaExpanded() { return (this.hasChildItems || this.hasContent) ? !this.disabled && this.expanded : null; } get ariaSelected() { return !this.disabled && this.selected; } get ariaDisabled() { return this.disabled ? true : null; } get headerClass() { return this.parent ? null : true; } /** * @hidden */ get titleTemplate() { return this.titleTemplates.length > 0 ? this.titleTemplates.toArray()[0].templateRef : undefined; } viewChildItems; contentItems; //ContentChild does not support descendants property, so we use ContentChildren for contentTemplate instead contentTemplate; titleTemplates; focused = false; wrapperFocused = false; subscriptions = new Subscription(() => { }); _expanded = false; level; _svgIcon; constructor(parent, eventService, element, renderer) { this.parent = parent; this.eventService = eventService; this.element = element; this.renderer = renderer; this.subscriptions.add(eventService.parent$.subscribe(focused => this.onWrapperFocusChange(focused))); this.subscriptions.add(eventService.keepContent$.subscribe(keepContent => this.keepContent = keepContent)); this.wrapperFocused = parent ? parent.focused : false; this.level = this.parent ? this.parent.level + 1 : 0; } /** * @hidden */ headerHeight() { return this.element.nativeElement.offsetHeight - (this.contentWrapper ? this.contentWrapper.nativeElement.offsetHeight : 0); } ngOnInit() { this.addLevelClass(); } ngAfterContentChecked() { this.hasItems = this.items && this.items.filter(item => !item.hidden).length > 0; this.hasChildItems = this.contentItems.filter(item => item !== this).length > 0 || this.hasItems; this.hasContent = (this.contentTemplate !== undefined && this.contentTemplate.length > 0) || this.content !== undefined; this.validateConfiguration(); } ngAfterViewChecked() { if (this.items) { this.childrenItems = this.viewChildItems.toArray(); } else { this.childrenItems = this.contentItems.filter(item => item !== this); } } ngOnDestroy() { this.subscriptions.unsubscribe(); } /** * @hidden */ onItemAction() { if (!this.disabled) { this.eventService.onSelect(this); } } /** * @hidden */ onItemClick(e) { if (!isFocusable(e.target)) { this.eventService.itemClick.next({ item: this.serialize(), originalEvent: e }); this.onItemAction(); } } /** * @hidden */ get iconClasses() { if (this.icon) { return `${this.icon}`; } } /** * @hidden */ get customIconClasses() { if (this.iconClass) { return `${this.iconClass}`; } } /** * @hidden */ get dirInnerCssClasses() { const dirClass = this.expanded ? 'k-panelbar-collapse' : 'k-panelbar-expand'; return `k-panelbar-toggle ${dirClass}`; } /** * @hidden */ get expanderSVGIcon() { return this.expanded ? chevronUpIcon : chevronDownIcon; } /** * @hidden */ serialize() { return { content: this.content, disabled: this.disabled, expanded: this.expanded, focused: this.focused, icon: this.icon, iconClass: this.iconClass, svgIcon: this.svgIcon, id: this.id, imageUrl: this.imageUrl, selected: this.selected, title: this.title, children: this.items }; } /** * @hidden */ subTreeViewItems() { let subTree = []; this.viewChildItems.forEach(item => { subTree = subTree.concat(item.subTreeViewItems()); subTree.push(item); }); return subTree; } /** * @hidden */ validateConfiguration() { if (isDevMode()) { if (this.content && (this.contentTemplate !== undefined && this.contentTemplate.length > 0)) { throw new Error("Invalid configuration: mixed template components and component property."); } } } /** * @hidden */ toggleAnimationState(value) { if (!this.animate) { return; } this.state = value && this.eventService.expandMode !== PanelBarExpandMode.Single ? 'active' : 'activeWithoutAnimation'; } /** * @hidden */ toggleExpandedChildAnimations(value) { if (this.childrenItems) { this.childrenItems.forEach(child => { if (child.expanded) { child.toggleAnimationState(value); child.toggleExpandedChildAnimations(value); } }); } } /** * @hidden */ addLevelClass() { if (this.level >= 0) { this.renderer.addClass(this.element.nativeElement, `k-level-${this.level}`); } } onWrapperFocusChange(focused) { this.wrapperFocused = focused; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarItemComponent, deps: [{ token: PanelBarItemComponent, host: true, optional: true, skipSelf: true }, { token: PanelBarService }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: PanelBarItemComponent, isStandalone: true, selector: "kendo-panelbar-item", inputs: { title: "title", id: "id", icon: "icon", iconClass: "iconClass", svgIcon: "svgIcon", imageUrl: "imageUrl", disabled: "disabled", expanded: "expanded", selected: "selected", content: "content", items: "items", template: "template" }, host: { properties: { "attr.role": "this.role", "attr.title": "this.titleAttribute", "class.k-panelbar-item": "this.kItemClass", "class.k-expanded": "this.kStateExpandedClass", "id": "this.itemId", "attr.aria-expanded": "this.ariaExpanded", "attr.aria-selected": "this.ariaSelected", "attr.aria-disabled": "this.ariaDisabled", "class.k-panelbar-header": "this.headerClass" } }, queries: [{ propertyName: "contentItems", predicate: PanelBarItemComponent }, { propertyName: "contentTemplate", predicate: PanelBarContentDirective }, { propertyName: "titleTemplates", predicate: PanelBarItemTitleDirective }], viewQueries: [{ propertyName: "header", first: true, predicate: ["header"], descendants: true }, { propertyName: "contentWrapper", first: true, predicate: ["contentWrapper"], descendants: true }, { propertyName: "viewChildItems", predicate: PanelBarItemComponent, descendants: true }], exportAs: ["kendoPanelbarItem"], ngImport: i0, template: ` <span #header [class.k-link]="true" [class.k-selected]="!disabled && selected" [class.k-focus]="focused && wrapperFocused" [class.k-disabled]="disabled" (click)="onItemClick($event)"> <kendo-icon-wrapper *ngIf="icon || iconClass || svgIcon" [name]="iconClasses" [customFontClass]="customIconClasses" [svgIcon]="svgIcon" innerCssClass="k-panelbar-item-icon" > </kendo-icon-wrapper> <img *ngIf="imageUrl" class="k-image k-panelbar-item-icon" [src]="imageUrl" alt=""> <ng-container *ngIf="!titleTemplate"><span class="k-panelbar-item-text">{{title}}</span></ng-container> <ng-template *ngIf="titleTemplate" [ngTemplateOutlet]="titleTemplate" [ngTemplateOutletContext]="{ item: { title: title, id: id, icon: icon, iconClass: iconClass, svgIcon: svgIcon, imageUrl: imageUrl, selected: selected, expanded: expanded, disabled: disabled, focused: focused, content: content } }"></ng-template> <kendo-icon-wrapper *ngIf="hasChildItems || hasContent" [name]="expanded ? 'chevron-up' : 'chevron-down'" [svgIcon]="expanderSVGIcon" [innerCssClass]="dirInnerCssClasses" > </kendo-icon-wrapper> </span> <div #contentWrapper *ngIf="keepContent || (!disabled && expanded && (hasChildItems || hasContent))" [@toggle]="state" [attr.role]="'group'" [attr.aria-hidden]="!disabled && !expanded" > <div *ngIf="hasChildItems && !items?.length" [style.overflow]="contentOverflow" [style.height]="contentHeight" class="k-panelbar-group" > <ng-content select="kendo-panelbar-item"></ng-content> </div> <div *ngIf="hasContent && !content" [style.overflow]="contentOverflow" [style.height]="contentHeight" class="k-panelbar-content"> <ng-template [ngTemplateOutlet]="contentTemplate.first.templateRef" [ngTemplateOutletContext]="{ $implicit: { title: title, id: id, icon: icon, imageUrl: imageUrl, disabled: disabled, content: content } }"> </ng-template> </div> <div *ngIf="hasItems" [style.overflow]="contentOverflow" [style.height]="contentHeight" class="k-panelbar-group"> <ng-container *ngFor="let item of items"> <kendo-panelbar-item *ngIf="!item.hidden" [title]="item.title" [id]="item.id" [icon]="item.icon" [iconClass]="item.iconClass" [svgIcon]="item.svgIcon" [imageUrl]="item.imageUrl" [selected]="!!item.selected" [expanded]="!!item.expanded" [disabled]="!!item.disabled" [template]="template" [items]="item.children" [content]="item.content"> </kendo-panelbar-item> </ng-container> </div> <div *ngIf="content" [style.overflow]="contentOverflow" [style.height]="contentHeight" class="k-panelbar-content"> <ng-template [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ $implicit: { title: title, id: id, icon: icon, imageUrl: imageUrl, disabled: disabled, content: content } }"> </ng-template> <ng-template [ngIf]="!template">{{content}}</ng-template> </div> </div>`, isInline: true, dependencies: [{ kind: "component", type: PanelBarItemComponent, selector: "kendo-panelbar-item", inputs: ["title", "id", "icon", "iconClass", "svgIcon", "imageUrl", "disabled", "expanded", "selected", "content", "items", "template"], exportAs: ["kendoPanelbarItem"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], animations: [ trigger('toggle', [ state('inactive', style({ display: 'none' })), transition('* => active', [ style({ overflow: 'hidden', display: 'block', height: 0 }), animate(200, style({ height: AUTO_STYLE })) ]), transition('active => *', [ style({ overflow: 'hidden', height: AUTO_STYLE }), animate(200, style({ height: 0, display: 'none' })) ]) ]) ] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarItemComponent, decorators: [{ type: Component, args: [{ animations: [ trigger('toggle', [ state('inactive', style({ display: 'none' })), transition('* => active', [ style({ overflow: 'hidden', display: 'block', height: 0 }), animate(200, style({ height: AUTO_STYLE })) ]), transition('active => *', [ style({ overflow: 'hidden', height: AUTO_STYLE }), animate(200, style({ height: 0, display: 'none' })) ]) ]) ], exportAs: 'kendoPanelbarItem', selector: "kendo-panelbar-item", template: ` <span #header [class.k-link]="true" [class.k-selected]="!disabled && selected" [class.k-focus]="focused && wrapperFocused" [class.k-disabled]="disabled" (click)="onItemClick($event)"> <kendo-icon-wrapper *ngIf="icon || iconClass || svgIcon" [name]="iconClasses" [customFontClass]="customIconClasses" [svgIcon]="svgIcon" innerCssClass="k-panelbar-item-icon" > </kendo-icon-wrapper> <img *ngIf="imageUrl" class="k-image k-panelbar-item-icon" [src]="imageUrl" alt=""> <ng-container *ngIf="!titleTemplate"><span class="k-panelbar-item-text">{{title}}</span></ng-container> <ng-template *ngIf="titleTemplate" [ngTemplateOutlet]="titleTemplate" [ngTemplateOutletContext]="{ item: { title: title, id: id, icon: icon, iconClass: iconClass, svgIcon: svgIcon, imageUrl: imageUrl, selected: selected, expanded: expanded, disabled: disabled, focused: focused, content: content } }"></ng-template> <kendo-icon-wrapper *ngIf="hasChildItems || hasContent" [name]="expanded ? 'chevron-up' : 'chevron-down'" [svgIcon]="expanderSVGIcon" [innerCssClass]="dirInnerCssClasses" > </kendo-icon-wrapper> </span> <div #contentWrapper *ngIf="keepContent || (!disabled && expanded && (hasChildItems || hasContent))" [@toggle]="state" [attr.role]="'group'" [attr.aria-hidden]="!disabled && !expanded" > <div *ngIf="hasChildItems && !items?.length" [style.overflow]="contentOverflow" [style.height]="contentHeight" class="k-panelbar-group" > <ng-content select="kendo-panelbar-item"></ng-content> </div> <div *ngIf="hasContent && !content" [style.overflow]="contentOverflow" [style.height]="contentHeight" class="k-panelbar-content"> <ng-template [ngTemplateOutlet]="contentTemplate.first.templateRef" [ngTemplateOutletContext]="{ $implicit: { title: title, id: id, icon: icon, imageUrl: imageUrl, disabled: disabled, content: content } }"> </ng-template> </div> <div *ngIf="hasItems" [style.overflow]="contentOverflow" [style.height]="contentHeight" class="k-panelbar-group"> <ng-container *ngFor="let item of items"> <kendo-panelbar-item *ngIf="!item.hidden" [title]="item.title" [id]="item.id" [icon]="item.icon" [iconClass]="item.iconClass" [svgIcon]="item.svgIcon" [imageUrl]="item.imageUrl" [selected]="!!item.selected" [expanded]="!!item.expanded" [disabled]="!!item.disabled" [template]="template" [items]="item.children" [content]="item.content"> </kendo-panelbar-item> </ng-container> </div> <div *ngIf="content" [style.overflow]="contentOverflow" [style.height]="contentHeight" class="k-panelbar-content"> <ng-template [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ $implicit: { title: title, id: id, icon: icon, imageUrl: imageUrl, disabled: disabled, content: content } }"> </ng-template> <ng-template [ngIf]="!template">{{content}}</ng-template> </div> </div>`, standalone: true, imports: [NgIf, IconWrapperComponent, NgTemplateOutlet, NgFor] }] }], ctorParameters: function () { return [{ type: PanelBarItemComponent, decorators: [{ type: SkipSelf }, { type: Host }, { type: Optional }] }, { type: PanelBarService }, { type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { title: [{ type: Input }], id: [{ type: Input }], icon: [{ type: Input }], iconClass: [{ type: Input }], svgIcon: [{ type: Input }], imageUrl: [{ type: Input }], disabled: [{ type: Input }], expanded: [{ type: Input }], selected: [{ type: Input }], content: [{ type: Input }], items: [{ type: Input }], template: [{ type: Input }], header: [{ type: ViewChild, args: ['header', { static: false }] }], contentWrapper: [{ type: ViewChild, args: ['contentWrapper', { static: false }] }], role: [{ type: HostBinding, args: ['attr.role'] }], titleAttribute: [{ type: HostBinding, args: ['attr.title'] }], kItemClass: [{ type: HostBinding, args: ['class.k-panelbar-item'] }], kStateExpandedClass: [{ type: HostBinding, args: ['class.k-expanded'] }], itemId: [{ type: HostBinding, args: ['id'] }], ariaExpanded: [{ type: HostBinding, args: ['attr.aria-expanded'] }], ariaSelected: [{ type: HostBinding, args: ['attr.aria-selected'] }], ariaDisabled: [{ type: HostBinding, args: ['attr.aria-disabled'] }], headerClass: [{ type: HostBinding, args: ['class.k-panelbar-header'] }], viewChildItems: [{ type: ViewChildren, args: [PanelBarItemComponent] }], contentItems: [{ type: ContentChildren, args: [PanelBarItemComponent] }], contentTemplate: [{ type: ContentChildren, args: [PanelBarContentDirective, { descendants: false }] }], titleTemplates: [{ type: ContentChildren, args: [PanelBarItemTitleDirective, { descendants: false }] }] } }); /** * Represents the template directive of the PanelBar which helps to customize the item content. */ class PanelBarItemTemplateDirective { templateRef; constructor(templateRef) { this.templateRef = templateRef; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarItemTemplateDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: PanelBarItemTemplateDirective, isStandalone: true, selector: "[kendoPanelBarItemTemplate]", ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PanelBarItemTemplateDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoPanelBarItemTemplate]', standalone: true }] }], ctorParameters: function () { return [{ type: i0.TemplateRef, decorators: [{ type: Optional }] }]; } }); /** * @hidden */ class PreventableEvent { prevented = false; /** * Prevents the default action for a specified event. * In this way, the source component suppresses * the built-in behavior that follows the event. */ preventDefault() { this.prevented = true; } /** * Returns `true` if the event was prevented * by any of its subscribers. * * @returns `true` if the default action was prevented. * Otherwise, returns `false`. */ isDefaultPrevented() { return this.prevented; } /** * @hidden */ constructor(args) { Object.assign(this, args); } } /** * Arguments for the `collapse` event of the PanelBar. */ class PanelBarCollapseEvent extends PreventableEvent { /** * The item that will be collapsed. */ item; } /** * Arguments for the `expand` event of the PanelBar. */ class PanelBarExpandEvent extends PreventableEvent { /** * The item that will be expanded. */ item; } /** * Arguments for the `select` event of the PanelBar. */ class PanelBarSelectEvent extends PreventableEvent { /** * The item that will be selected. */ item; } /** * Arguments for the `stateChange` event of the PanelBar. */ class PanelBarStateChangeEvent { /** * A collection of all modified items. */ items; } /** * Arguments for the `itemClick` event of the PanelBar. */ class PanelBarItemClickEvent { /** * The clicked item. */ item; /** * The DOM event that triggered the `itemClick` event. */ originalEvent; } /** * Represents the [Kendo UI PanelBar component for Angular]({% slug overview_panelbar %}). */ // TODO: add styles as input prop class PanelBarComponent { localization; /** * Sets the expand mode of the PanelBar through the `PanelBarExpandMode` enum ([see example]({% slug expandmodes_panelbar %})). * * The available modes are: * - `"single"`&mdash;Expands only one item at a time. Expanding an item collapses the item that was previously expanded. * - `"multiple"`&mdash;The default mode of the PanelBar. * Expands more than one item at a time. Items can also be toggled. * - `"full"`&mdash;Expands only one item at a time. * The expanded area occupies the entire height of the PanelBar. Requires you to set the `height` property. */ expandMode = PanelBarExpandMode.Default; /** * Allows the PanelBar to modify the selected state of the items. */ selectable = true; /** * Sets the animate state of the PanelBar ([see example]({% slug animations_panelbar %})). */ animate = true; /** * Sets the height of the component when the `"full"` expand mode is used. * This option is ignored in the `"multiple"` and `"single"` expand modes. */ height = '400px'; /** * When set to `true`, the PanelBar renders the content of all items and they are persisted in the DOM * ([see example]({% slug templates_panelbar %}#toc-collections)). * By default, this option is set to `false`. */ get keepItemContent() { return this._keepItemContent; } set keepItemContent(keepItemContent) { this._keepItemContent = keepItemContent; this.eventService.onKeepContent(keepItemContent); } /** * Sets the items of the PanelBar as an array of `PanelBarItemModel` instances * ([see example]({% slug items_panelbar %})). */ set items(data) { if (data) { this._items = parsePanelBarItems(data); } } get items() { return this._items; } /** * Fires each time the user interacts with a PanelBar item * ([see example](slug:routing_panelbar#using-router-service)). * The event data contains a collection of all items that are modified. */ stateChange = new EventEmitter(); /** * Fires when an item is about to be selected. * ([see example]({% slug events_panelbar %})) * This event is preventable. If you cancel it, the item will not be selected. */ select = new EventEmitter(); /** * Fires when an item is about to be expanded. * ([see example]({% slug events_panelbar %})) * This event is preventable. If you cancel it, the item will remain collapsed. */ expand = new EventEmitter(); /** * Fires when an item is about to be collapsed. * ([see example]({% slug events_panelbar %})) * This event is preventable. If you cancel it, the item will remain expanded. */ collapse = new EventEmitter(); /** * Fires when the user clicks an item ([see example]({% slug events_panelbar %})). */ itemClick = new EventEmitter(); hostClasses = true; tabIndex = 0; role = 'tree'; activeDescendant = ''; get hostHeight() { return this.expandMode === PanelBarExpandMode.Full ? this.height : 'auto'; } get overflow() { return this.expandMode === PanelBarExpandMode.Full ? 'hidden' : 'visible'; } get dir() { return this.localization.rtl ? 'rtl' : 'ltr'; } template; contentItems; contentChildItems; viewChildItems; /** * @hidden */ showLicenseWatermark = false; allItems; childrenItems; isViewInit = true; focused = false; _items; _keepItemContent = false; elementRef; eventService; keyBindings; subs = new Subscription(); constructor(elementRef, eventService, localization) { this.localization = localization; const isValid = validatePackage(packageMetadata); this.showLicenseWatermark = shouldShowValidationUI(isValid); /* eslint-disable-line*/ this.keyBindings = this.computedKeys; this.elementRef = elementRef; this.eventService = eventService; this.subs.add(this.eventService.children$.subscribe(event => this.onItemAction(event))); this.subs.add(this.eventService.itemClick.subscribe(ev => this.itemClick.emit(ev))); } /** * @hidden */ invertKeys(original, inverted) { return this.localization.rtl ? inverted : original; } get computedKeys() { return { [Keys.Space]: () => this.selectFocusedItem(), [Keys.Enter]: () => this.selectFocusedItem(), [Keys.ArrowUp]: () => this.focusPreviousItem(), [this.invertKeys(Keys.ArrowLeft, Keys.ArrowRight)]: () => this.collapseItem(), [Keys.ArrowDown]: () => this.focusNextItem(), [this.invertKeys(Keys.ArrowRight, Keys.ArrowLeft)]: () => this.expandItem(), [Keys.End]: () => this.focusLastItem(), [Keys.Home]: () => this.focusFirstItem() }; } ngOnDestroy() { this.subs.unsubscribe(); } ngOnInit() { this.subs.add(this.localization.changes.subscribe(() => this.keyBindings = this.computedKeys)); this.eventService.animate = this.animate; this.eventService.expandMode = this.expandMode; } ngAfterViewChecked() { if (this.items) { this.childrenItems = this.viewChildItems.toArray(); this.allItems = this.viewItems; } else { this.childrenItems = this.contentChildItems.toArray(); this.allItems = this.contentItems.toArray(); } if (this.isViewInit && this.childrenItems.length) { this.isViewInit = false; setTimeout(() => this.updateChildrenHeight()); } this.validateConfiguration(); } ngOnChanges(changes) { if (changes['height'] || changes['expandMode'] || changes['items']) { // eslint-disable-line if (this.childrenItems) { setTimeout(this.updateChildrenHeight); } } if (changes['animate']) { this.eventService.animate = this.animate; } if (changes['expandMode']) { this.eventService.expandMode = this.expandMode; } } get templateRef() { return this.template ? this.template.templateRef : undefined; } /** * @hidden */ onComponentClick(event) { const itemClicked = this.visibleItems().some((item) => { return item.header.nativeElement.contains(event.target); }); if (!isFocusable(event.target) && !this.focused && itemClicked) { this.elementRef.nativeElement.focus(); } } /** * @hidden */ onComponentFocus() { this.eventService.onFocus(); this.focused = true; if (this.allItems.length > 0) { const visibleItems = this.visibleItems(); const focusedItems = visibleItems.filter(item => item.focused); if (!focusedItems.length && visibleItems.length > 0) { visibleItems[0].focused = true; this.activeDescendant = visibleItems[0].itemId; } } } /** * @hidden */ onComponentBlur() { this.eventService.onBlur(); this.focused = false; this.activeDescendant = ''; } /** * @hidden */ onComponentKeyDown(event) { if (event.target === this.elementRef.nativeElement) { if (event.keyCode === Keys.Space || event.keyCode === Keys.ArrowUp || event.keyCode === Keys.ArrowDown || event.keyCode === Keys.ArrowLeft || event.keyCode === Keys.ArrowRight || event.keyCode === Keys.Home || event.keyCode === Keys.End || event.keyCode === Keys.PageUp || event.keyCode === Keys.PageDown) { event.preventDefault(); } const handler = this.keyBindings[event.keyCode]; //TODO: check if next item is disabled and skip operation? if (handler) { handler(); } } } /** * @hidden */ emitEvent(event, item) { let eventArgs; switch (event) { case 'select': eventArgs = new PanelBarSelectEvent(); break; case 'collapse': eventArgs = new PanelBarCollapseEvent(); break; default: eventArgs = new PanelBarExpandEvent(); break; } eventArgs.item = item.serialize(); this[event].emit(eventArgs); return eventArgs; } get viewItems() { let treeItems = []; this.viewChildItems.toArray().forEach(item => { treeItems.push(item); treeItems = treeItems.concat(item.subTreeViewItems()); }); return treeItems; } validateConfiguration() { if (isDevMode()) { if (this.items && (this.contentItems && this.contentItems.length > 0)) { throw new Error('Invalid configuration: mixed template components and items property.'); } } } updateChildrenHeight = () => { let child