UNPKG

@progress/kendo-angular-buttons

Version:
577 lines (568 loc) 21.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 { Component, HostBinding, Input, Output, EventEmitter, ElementRef, Renderer2, NgZone, isDevMode } from '@angular/core'; import { Subscription } from 'rxjs'; import { isDocumentAvailable, Keys } from '@progress/kendo-angular-common'; import { LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n'; import { validatePackage } from '@progress/kendo-licensing'; import { packageMetadata } from '../package-metadata'; import { closest, getStylingClasses, getThemeColorClasses, isObjectEmpty } from '../util'; import { moreVerticalIcon, xCircleIcon } from '@progress/kendo-svg-icons'; import { IconWrapperComponent } from '@progress/kendo-angular-icons'; import { NgIf, NgClass, NgStyle } from '@angular/common'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-l10n"; const DEFAULT_SIZE = 'medium'; const DEFAULT_ROUNDED = 'medium'; const DEFAULT_THEME_COLOR = 'base'; const DEFAULT_FILL_MODE = 'solid'; /** * Displays a Chip that represents an input, attribute or an action. */ export class ChipComponent { element; renderer; ngZone; localizationService; /** * Sets the label text of the Chip. */ label; /** * Defines the name for an existing icon in a Kendo UI theme. * The icon is rendered inside the Chip by a `span.k-icon` element. */ icon; /** * Defines an [`SVGIcon`](slug:api_icons_svgicon) icon to be rendered inside the Chip using * a [`KendoSVGIcon`](slug:api_icons_svgiconcomponent) component. */ svgIcon; /** * Defines a CSS class — or multiple classes separated by spaces — * which are applied to a span element. * Allows the usage of custom icons. */ iconClass; /** * Use these settings to render an avatar within the Chip. */ avatarSettings; /** * Specifies the selected state of the Chip. * @default false */ selected = false; /** * Specifies if the Chip will be removable or not. * If the property is set to `true`, the Chip renders a remove icon. * @default false */ removable = false; /** * Specifies a custom remove font icon class that will be rendered when the Chip is removable. * [see example]({% slug icons %}) */ removeIcon; /** * Specifies a custom remove SVG icon that will be rendered when the Chip is removable. */ removeSvgIcon; /** * @hidden * * Determines whether the Chip has a menu. */ hasMenu = false; /** * @hidden * * Specifies a custom menu font icon class that will be rendered when the Chip has menu. */ menuIcon; /** * @hidden * * Specifies a custom menu SVG icon that will be rendered when the Chip has menu. */ menuSvgIcon; /** * If set to `true`, the Chip will be disabled. * @default false */ disabled = false; /** * The size property specifies the padding of the Chip * ([see example]({% slug appearance_chip %}#toc-size)). * * The possible values are: * * `small` * * `medium` (default) * * `large` * * `none` */ set size(size) { const newSize = size ? size : DEFAULT_SIZE; !this.sizeIsSet && (this.sizeIsSet = true); this.handleClasses(newSize, 'size'); this._size = newSize; } get size() { return this._size; } /** * The rounded property specifies the border radius of the Chip * ([see example](slug:appearance_chip#toc-roundness)). * * The possible values are: * * `small` * * `medium` (default) * * `large` * * `full` * * `none` */ set rounded(rounded) { const newRounded = rounded ? rounded : DEFAULT_ROUNDED; this.handleClasses(newRounded, 'rounded'); this._rounded = newRounded; } get rounded() { return this._rounded; } /** * The fillMode property specifies the background and border styles of the Chip * ([see example](slug:appearance_chip#toc-fill-mode)). * * The possible values are: * * `solid` (default) * * `outline` * * `none` */ set fillMode(fillMode) { const newFillMode = fillMode ? fillMode : DEFAULT_FILL_MODE; this.handleClasses(newFillMode, 'fillMode'); this._fillMode = newFillMode; } get fillMode() { return this._fillMode; } /** * The Chip allows you to specify predefined theme colors. * The theme color will be applied as a background and border color while also amending the text color accordingly * ([see example](slug:appearance_chip#toc-theme-colors)). * * The possible values are: * * `base` (default) * * `info` * * `success` * * `warning` * * `error` * * `none` */ set themeColor(themeColor) { const newThemeColor = themeColor ? themeColor : DEFAULT_THEME_COLOR; this.handleThemeColor(newThemeColor); this._themeColor = newThemeColor; } get themeColor() { return this._themeColor; } /** * Fires each time the user clicks the remove icon of the Chip. */ remove = new EventEmitter(); /** * @hidden * * Fires each time the user clicks the menu icon of the Chip. */ menuToggle = new EventEmitter(); /** * Fires each time the user clicks the content of the Chip. */ contentClick = new EventEmitter(); tabIndex = 0; hostClass = true; get hasIconClass() { return Boolean(this.icon || this.iconClass || (this.avatarSettings && !isObjectEmpty(this.avatarSettings))); } get disabledClass() { return this.disabled; } get selectedClass() { return this.selected; } get focusedClass() { return this.focused; } /** * @hidden */ direction; /** * @hidden */ defaultRemoveIcon = xCircleIcon; /** * @hidden */ defaultMenuIcon = moreVerticalIcon; /** * @hidden */ sizeIsSet = false; _size = 'medium'; _rounded = 'medium'; _fillMode = 'solid'; _themeColor = 'base'; focused = false; subs = new Subscription(); constructor(element, renderer, ngZone, localizationService) { this.element = element; this.renderer = renderer; this.ngZone = ngZone; this.localizationService = localizationService; validatePackage(packageMetadata); this.direction = localizationService.rtl ? 'rtl' : 'ltr'; } ngOnInit() { this.subs.add(this.localizationService.changes .subscribe(({ rtl }) => this.direction = rtl ? 'rtl' : 'ltr')); this.renderer.setAttribute(this.element.nativeElement, 'role', 'button'); } ngOnDestroy() { this.subs.unsubscribe(); } ngOnChanges(changes) { if (changes && changes['selected']) { const hasAriaSelected = this.element.nativeElement.hasAttribute('aria-selected'); if (!hasAriaSelected) { this.renderer.setAttribute(this.element.nativeElement, 'aria-pressed', `${this.selected}`); } } } ngAfterViewInit() { const chip = this.element.nativeElement; const stylingOptions = ['size', 'rounded', 'fillMode']; stylingOptions.forEach(input => { this.handleClasses(this[input], input); }); this.attachElementEventHandlers(chip); } /** * @hidden */ get kendoIconClass() { this.verifyIconSettings([this.iconClass]); return `k-i-${this.icon}`; } /** * @hidden */ get customIconClass() { this.verifyIconSettings([this.icon]); return this.iconClass; } /** * @hidden */ get removeIconClass() { return this.removeIcon ? this.removeIcon : 'k-i-x-circle'; } /** * Focuses the Chip component. */ focus() { if (isDocumentAvailable()) { this.element.nativeElement.focus(); } } /** * Blurs the Chip component. */ blur() { if (isDocumentAvailable()) { this.element.nativeElement.blur(); } } /** * @hidden */ onRemoveClick(e) { if (this.removable) { this.remove.emit({ sender: this, originalEvent: e }); } } /** * @hidden */ onMenuClick(e) { if (this.hasMenu) { this.menuToggle.emit({ sender: this, originalEvent: e }); } } attachElementEventHandlers(chip) { this.ngZone.runOutsideAngular(() => { this.subs.add(this.renderer.listen(chip, 'focus', () => { this.renderer.addClass(chip, 'k-focus'); })); this.subs.add(this.renderer.listen(chip, 'blur', () => { this.renderer.removeClass(chip, 'k-focus'); })); this.subs.add(this.renderer.listen(chip, 'click', (e) => { const isActionClicked = closest(e.target, '.k-chip-action'); if (!isActionClicked) { this.ngZone.run(() => { this.contentClick.emit({ sender: this, originalEvent: e }); }); } })); this.subs.add(this.renderer.listen(chip, 'keydown', this.keyDownHandler.bind(this))); }); } /** * @hidden */ verifyIconSettings(iconsToCheck) { if (isDevMode()) { if (iconsToCheck.filter(icon => icon !== null && icon !== undefined).length > 0) { this.renderer.removeClass(this.element.nativeElement, 'k-chip-has-icon'); throw new Error('Invalid configuration: Having multiple icons is not supported. Only a single icon on a chip can be displayed.'); } } } handleClasses(value, input) { const elem = this.element.nativeElement; const classes = getStylingClasses('chip', input, this[input], value); if (input === 'fillMode') { this.handleThemeColor(this.themeColor, this[input], value); } if (classes.toRemove) { this.renderer.removeClass(elem, classes.toRemove); } if (classes.toAdd) { this.renderer.addClass(elem, classes.toAdd); } } handleThemeColor(value, prevFillMode, fillMode) { const elem = this.element.nativeElement; const removeFillMode = prevFillMode ? prevFillMode : this.fillMode; const addFillMode = fillMode ? fillMode : this.fillMode; const themeColorClass = getThemeColorClasses('chip', removeFillMode, addFillMode, this.themeColor, value); this.renderer.removeClass(elem, themeColorClass.toRemove); if (addFillMode !== 'none' && fillMode !== 'none') { if (themeColorClass.toAdd) { this.renderer.addClass(elem, themeColorClass.toAdd); } } } keyDownHandler(e) { const isEnterOrSpace = e.keyCode === Keys.Enter || e.keyCode === Keys.Space; const isDeleteOrBackspace = e.keyCode === Keys.Delete || e.keyCode === Keys.Backspace; if (this.disabled) { return; } if (isEnterOrSpace) { this.ngZone.run(() => { this.contentClick.emit({ sender: this, originalEvent: e }); }); } else if (isDeleteOrBackspace && this.removable) { this.ngZone.run(() => { this.remove.emit({ sender: this, originalEvent: e }); }); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChipComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ChipComponent, isStandalone: true, selector: "kendo-chip", inputs: { label: "label", icon: "icon", svgIcon: "svgIcon", iconClass: "iconClass", avatarSettings: "avatarSettings", selected: "selected", removable: "removable", removeIcon: "removeIcon", removeSvgIcon: "removeSvgIcon", hasMenu: "hasMenu", menuIcon: "menuIcon", menuSvgIcon: "menuSvgIcon", disabled: "disabled", size: "size", rounded: "rounded", fillMode: "fillMode", themeColor: "themeColor" }, outputs: { remove: "remove", menuToggle: "menuToggle", contentClick: "contentClick" }, host: { properties: { "attr.tabindex": "this.tabIndex", "class.k-chip": "this.hostClass", "class.k-chip-has-icon": "this.hasIconClass", "attr.aria-disabled": "this.disabledClass", "class.k-disabled": "this.disabledClass", "class.k-selected": "this.selectedClass", "class.k-focus": "this.focusedClass", "attr.dir": "this.direction" } }, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.chip' } ], usesOnChanges: true, ngImport: i0, template: ` <kendo-icon-wrapper *ngIf="icon || svgIcon" size="small" innerCssClass="k-chip-icon" [name]="icon" [svgIcon]="svgIcon"></kendo-icon-wrapper> <kendo-icon-wrapper *ngIf="iconClass" size="small" innerCssClass="k-chip-icon" [customFontClass]="customIconClass"></kendo-icon-wrapper> <span *ngIf="avatarSettings" class="k-chip-avatar k-avatar k-avatar-sm k-avatar-solid k-avatar-solid-primary k-rounded-full" [ngStyle]="avatarSettings.cssStyle"> <ng-container *ngIf="avatarSettings?.imageSrc"> <span class="k-avatar-image"> <img src="{{ avatarSettings.imageSrc }}" [ngStyle]="avatarSettings.imageCssStyle" [attr.alt]="avatarSettings.imageAltText" /> </span> </ng-container> <ng-container *ngIf="avatarSettings?.initials"> <span class="k-avatar-text" [ngStyle]="avatarSettings.initialsCssStyle">{{ avatarSettings.initials.substring(0, 2) }}</span> </ng-container> </span> <span class="k-chip-content"> <span class="k-chip-label" *ngIf="label"> {{ label }} </span> <ng-content *ngIf="!label"></ng-content> </span> <span class="k-chip-actions" *ngIf="hasMenu || removable"> <span class="k-chip-action k-chip-more-action" *ngIf="hasMenu" (click)="onMenuClick($event)"> <kendo-icon-wrapper name="more-vertical" size="small" [svgIcon]="defaultMenuIcon || menuSvgIcon" [customFontClass]="menuIcon"></kendo-icon-wrapper> </span> <span class="k-chip-action k-chip-remove-action" *ngIf="removable" (click)="onRemoveClick($event)"> <kendo-icon-wrapper name="x-circle" size="small" [svgIcon]="removeSvgIcon || defaultRemoveIcon" [customFontClass]="removeIcon"></kendo-icon-wrapper> </span> </span> `, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChipComponent, decorators: [{ type: Component, args: [{ selector: 'kendo-chip', template: ` <kendo-icon-wrapper *ngIf="icon || svgIcon" size="small" innerCssClass="k-chip-icon" [name]="icon" [svgIcon]="svgIcon"></kendo-icon-wrapper> <kendo-icon-wrapper *ngIf="iconClass" size="small" innerCssClass="k-chip-icon" [customFontClass]="customIconClass"></kendo-icon-wrapper> <span *ngIf="avatarSettings" class="k-chip-avatar k-avatar k-avatar-sm k-avatar-solid k-avatar-solid-primary k-rounded-full" [ngStyle]="avatarSettings.cssStyle"> <ng-container *ngIf="avatarSettings?.imageSrc"> <span class="k-avatar-image"> <img src="{{ avatarSettings.imageSrc }}" [ngStyle]="avatarSettings.imageCssStyle" [attr.alt]="avatarSettings.imageAltText" /> </span> </ng-container> <ng-container *ngIf="avatarSettings?.initials"> <span class="k-avatar-text" [ngStyle]="avatarSettings.initialsCssStyle">{{ avatarSettings.initials.substring(0, 2) }}</span> </ng-container> </span> <span class="k-chip-content"> <span class="k-chip-label" *ngIf="label"> {{ label }} </span> <ng-content *ngIf="!label"></ng-content> </span> <span class="k-chip-actions" *ngIf="hasMenu || removable"> <span class="k-chip-action k-chip-more-action" *ngIf="hasMenu" (click)="onMenuClick($event)"> <kendo-icon-wrapper name="more-vertical" size="small" [svgIcon]="defaultMenuIcon || menuSvgIcon" [customFontClass]="menuIcon"></kendo-icon-wrapper> </span> <span class="k-chip-action k-chip-remove-action" *ngIf="removable" (click)="onRemoveClick($event)"> <kendo-icon-wrapper name="x-circle" size="small" [svgIcon]="removeSvgIcon || defaultRemoveIcon" [customFontClass]="removeIcon"></kendo-icon-wrapper> </span> </span> `, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.chip' } ], standalone: true, imports: [NgIf, NgStyle, IconWrapperComponent, NgClass] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: i1.LocalizationService }]; }, propDecorators: { label: [{ type: Input }], icon: [{ type: Input }], svgIcon: [{ type: Input }], iconClass: [{ type: Input }], avatarSettings: [{ type: Input }], selected: [{ type: Input }], removable: [{ type: Input }], removeIcon: [{ type: Input }], removeSvgIcon: [{ type: Input }], hasMenu: [{ type: Input }], menuIcon: [{ type: Input }], menuSvgIcon: [{ type: Input }], disabled: [{ type: Input }], size: [{ type: Input }], rounded: [{ type: Input }], fillMode: [{ type: Input }], themeColor: [{ type: Input }], remove: [{ type: Output }], menuToggle: [{ type: Output }], contentClick: [{ type: Output }], tabIndex: [{ type: HostBinding, args: ['attr.tabindex'] }], hostClass: [{ type: HostBinding, args: ['class.k-chip'] }], hasIconClass: [{ type: HostBinding, args: ['class.k-chip-has-icon'] }], disabledClass: [{ type: HostBinding, args: ['attr.aria-disabled'] }, { type: HostBinding, args: ['class.k-disabled'] }], selectedClass: [{ type: HostBinding, args: ['class.k-selected'] }], focusedClass: [{ type: HostBinding, args: ['class.k-focus'] }], direction: [{ type: HostBinding, args: ['attr.dir'] }] } });