@progress/kendo-angular-buttons
Version:
Buttons Package for Angular
577 lines (568 loc) • 21.3 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, 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']
}] } });