@progress/kendo-angular-buttons
Version:
Buttons Package for Angular
552 lines (549 loc) • 19.7 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, ElementRef, EventEmitter, HostBinding, HostListener, Input, Renderer2, Output, Optional, NgZone, isDevMode } from '@angular/core';
import { KendoButtonService } from './button.service';
import { isDocumentAvailable, isChanged, hasObservers, isSafari, isFirefox } from '@progress/kendo-angular-common';
import { caretAltDownIcon } from '@progress/kendo-svg-icons';
import { LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n';
import { Subscription } from 'rxjs';
import { validatePackage } from '@progress/kendo-licensing';
import { packageMetadata } from '../package-metadata';
import { getStylingClasses, getThemeColorClasses } from '../util';
import { IconWrapperComponent } from '@progress/kendo-angular-icons';
import { NgIf, NgClass } from '@angular/common';
import * as i0 from "@angular/core";
import * as i1 from "./button.service";
import * as i2 from "@progress/kendo-angular-l10n";
const DEFAULT_ROUNDED = 'medium';
const DEFAULT_SIZE = 'medium';
const DEFAULT_THEME_COLOR = 'base';
const DEFAULT_FILL_MODE = 'solid';
/**
* Represents the Kendo UI Button component for Angular.
*
* As of package v17, the `span[kendoButton]` and `kendo-button` selectors are removed.
* Please use the `button[kendoButton]` selector only.
*/
export class ButtonComponent {
renderer;
service;
ngZone;
/**
* @hidden
* @default false
* required for DropDownButton arrow icon.
*/
arrowIcon = false;
/**
* Provides visual styling that indicates if the Button is active.
*
* @default false
*/
toggleable = false;
/**
* Backwards-compatible alias
*
* @hidden
*/
get togglable() {
return this.toggleable;
}
/**
* @hidden
*/
set togglable(value) {
this.toggleable = value;
}
/**
* Sets the selected state of the Button.
*
* @default false
*/
get selected() {
return this._selected || false;
}
set selected(value) {
this._selected = value;
}
/**
* @hidden
*/
set tabIndex(index) {
this.element.tabIndex = index;
}
get tabIndex() {
return this.element.tabIndex;
}
/**
* Defines a URL which is used for an `img` element inside the Button.
* The URL can be relative or absolute. If relative, it is evaluated with relation to the web page URL.
*/
imageUrl;
/**
* Defines a CSS class—or multiple classes separated by spaces—
* which are applied to a `span` element inside the Button. Allows the usage of custom icons.
*/
set iconClass(value) {
if (isDevMode() && value && (this.icon || this.svgIcon)) {
throw new Error('Setting both icon/svgIcon and iconClass options at the same time is not supported.');
}
this._iconClass = value;
}
get iconClass() {
return this._iconClass;
}
/**
* Defines the name for an existing font icon in the Kendo UI theme.
*/
set icon(name) {
if (isDevMode() && name && this.iconClass) {
throw new Error('Setting both icon/svgIcon and iconClass options at the same time is not supported.');
}
this._icon = name;
}
get icon() {
return this._icon;
}
/**
* If set to `true`, it disables the Button.
*
* @default false
*/
set disabled(disabled) {
//Required, because in FF focused buttons are not blurred on disabled
if (disabled && isDocumentAvailable() && isFirefox(navigator.userAgent)) {
this.blur();
}
this.isDisabled = disabled;
this.renderer.setProperty(this.element, 'disabled', disabled);
}
get disabled() {
return this.isDisabled;
}
/**
* The size property specifies the padding of the Button
* ([see example]({% slug appearance_button %}#toc-size)).
*
* @default 'medium'
*/
set size(size) {
const newSize = size ? size : DEFAULT_SIZE;
this.handleClasses(newSize, 'size');
this._size = newSize;
}
get size() {
return this._size;
}
/**
* The rounded property specifies the border radius of the Button
* ([see example](slug:appearance_button#toc-roundness)).
*
* @default 'medium'
*/
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 Button
* ([see example](slug:appearance_button#toc-fill-mode)).
*
* @default 'solid'
*/
set fillMode(fillMode) {
const newFillMode = fillMode ? fillMode : DEFAULT_FILL_MODE;
this.handleClasses(newFillMode, 'fillMode');
this._fillMode = newFillMode;
}
get fillMode() {
return this._fillMode;
}
/**
* The Button 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_button#toc-theme-colors)).
*
* @default 'base'
*/
set themeColor(themeColor) {
const newThemeColor = themeColor ? themeColor : DEFAULT_THEME_COLOR;
this.handleThemeColor(newThemeColor);
this._themeColor = newThemeColor;
}
get themeColor() {
return this._themeColor;
}
/**
* Defines an SVGIcon to be rendered within the button.
*/
set svgIcon(icon) {
if (isDevMode() && 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;
}
/**
* Fires each time the selected state of a toggleable button is changed.
*
* The event argument is the new selected state (boolean).
*/
selectedChange = new EventEmitter();
/**
* Fires each time the user clicks the button.
*/
click = new EventEmitter();
element;
isDisabled = false;
caretAltDownIcon = caretAltDownIcon;
_size = DEFAULT_SIZE;
_rounded = DEFAULT_ROUNDED;
_fillMode = DEFAULT_FILL_MODE;
_themeColor = DEFAULT_THEME_COLOR;
_focused = false;
direction;
_selected;
subs = new Subscription();
_iconClass;
_icon;
_svgIcon;
set isFocused(isFocused) {
this.toggleClass('k-focus', isFocused);
this._focused = isFocused;
}
get isFocused() {
return this._focused;
}
get classButton() {
return true;
}
get isToggleable() {
return this.toggleable;
}
get iconButtonClass() {
const hasIcon = this.icon || this.iconClass || this.imageUrl || this.svgIcon;
return hasIcon && !this.hasText;
}
get classDisabled() {
return this.isDisabled;
}
get classActive() {
return this.selected;
}
get getDirection() {
return this.direction;
}
/**
* @hidden
*/
onFocus() {
this.isFocused = true;
}
/**
* @hidden
*/
onBlur() {
this.isFocused = false;
}
/**
* @hidden
*/
set primary(value) {
this.themeColor = value ? 'primary' : 'base';
}
/**
* @hidden
*/
set look(value) {
switch (value) {
case 'default':
this.fillMode = 'solid';
break;
default:
this.fillMode = value;
break;
}
}
/**
* Alias for ElementRef.nativeElement to workaround
* ViewChild() selectors that used to return the host element before v11.
*
* @hidden
*/
get nativeElement() {
return this.element;
}
constructor(element, renderer, service, localization, ngZone) {
this.renderer = renderer;
this.service = service;
this.ngZone = ngZone;
validatePackage(packageMetadata);
this.direction = localization.rtl ? 'rtl' : 'ltr';
this.subs.add(localization.changes.subscribe(({ rtl }) => (this.direction = rtl ? 'rtl' : 'ltr')));
this.element = element.nativeElement;
}
ngOnInit() {
if (!this.element.hasAttribute('role') && this.togglable) {
this.toggleAriaPressed(this.toggleable);
}
this.ngZone.runOutsideAngular(() => {
this.subs.add(this.renderer.listen(this.element, 'click', this._onButtonClick.bind(this)));
this.subs.add(this.renderer.listen(this.element, 'mousedown', (event) => {
const isBrowserSafari = isDocumentAvailable() && isSafari(navigator.userAgent);
if (!this.isDisabled && isBrowserSafari) {
event.preventDefault();
this.element.focus();
}
}));
});
}
ngOnChanges(change) {
if (isChanged('togglable', change) || isChanged('toggleable', change)) {
this.toggleAriaPressed(this.toggleable);
}
}
ngAfterViewInit() {
const stylingOptions = ['size', 'rounded', 'fillMode'];
stylingOptions.forEach(input => {
this.handleClasses(this[input], input);
});
}
ngOnDestroy() {
this.subs.unsubscribe();
}
/**
* @hidden
*/
get hasText() {
return isDocumentAvailable() && this.element.textContent.trim().length > 0;
}
/**
* Focuses the Button component.
*/
focus() {
if (isDocumentAvailable()) {
this.element.focus();
this.isFocused = true;
}
}
/**
* Blurs the Button component.
*/
blur() {
if (isDocumentAvailable()) {
this.element.blur();
this.isFocused = false;
}
}
/**
* @hidden
*/
setAttribute(attribute, value) {
this.renderer.setAttribute(this.element, attribute, value);
}
/**
* @hidden
*/
removeAttribute(attribute) {
this.renderer.removeAttribute(this.element, attribute);
}
/**
* @hidden
*
* Internal setter that triggers selectedChange
*/
setSelected(value) {
const changed = this.selected !== value;
this.selected = value;
this.setAttribute('aria-pressed', this.selected.toString());
this.toggleClass('k-selected', this.selected);
if (changed && hasObservers(this.selectedChange)) {
this.ngZone.run(() => {
this.selectedChange.emit(value);
});
}
}
toggleAriaPressed(shouldSet) {
if (!isDocumentAvailable()) {
return;
}
if (shouldSet) {
this.setAttribute('aria-pressed', this.selected.toString());
}
else {
this.removeAttribute('aria-pressed');
}
}
toggleClass(className, add) {
if (add) {
this.renderer.addClass(this.element, className);
}
else {
this.renderer.removeClass(this.element, className);
}
}
_onButtonClick() {
if (!this.disabled && this.service) {
this.ngZone.run(() => {
this.service.click(this);
});
}
if (this.togglable && !this.service) {
this.setSelected(!this.selected);
}
}
handleClasses(value, input) {
const elem = this.element;
const classes = getStylingClasses('button', 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;
const removeFillMode = prevFillMode ? prevFillMode : this.fillMode;
const addFillMode = fillMode ? fillMode : this.fillMode;
const themeColorClass = getThemeColorClasses('button', 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);
}
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ButtonComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i1.KendoButtonService, optional: true }, { token: i2.LocalizationService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ButtonComponent, isStandalone: true, selector: "button[kendoButton]", inputs: { arrowIcon: "arrowIcon", toggleable: "toggleable", togglable: "togglable", selected: "selected", tabIndex: "tabIndex", imageUrl: "imageUrl", iconClass: "iconClass", icon: "icon", disabled: "disabled", size: "size", rounded: "rounded", fillMode: "fillMode", themeColor: "themeColor", svgIcon: "svgIcon", primary: "primary", look: "look" }, outputs: { selectedChange: "selectedChange", click: "click" }, host: { listeners: { "focus": "onFocus()", "blur": "onBlur()" }, properties: { "class.k-button": "this.classButton", "class.k-toggle-button": "this.isToggleable", "class.k-icon-button": "this.iconButtonClass", "class.k-disabled": "this.classDisabled", "class.k-selected": "this.classActive", "attr.dir": "this.getDirection" } }, providers: [
LocalizationService,
{
provide: L10N_PREFIX,
useValue: 'kendo.button'
}
], exportAs: ["kendoButton"], usesOnChanges: true, ngImport: i0, template: `
<kendo-icon-wrapper
*ngIf="icon || svgIcon"
innerCssClass="k-button-icon"
[name]="icon"
[svgIcon]="svgIcon"></kendo-icon-wrapper>
<span *ngIf="imageUrl" class="k-button-icon k-icon">
<img [src]="imageUrl" class="k-image" role="presentation" />
</span>
<span *ngIf="iconClass" class="k-button-icon" [ngClass]="iconClass"></span>
<span class="k-button-text"><ng-content></ng-content></span>
<span *ngIf="$any(arrowIcon).iconClass" class="k-button-icon" [ngClass]="$any(arrowIcon).iconClass"></span>
<span *ngIf="arrowIcon && !$any(arrowIcon).iconClass" class="k-button-arrow">
<kendo-icon-wrapper
[name]="$any(arrowIcon).icon || 'caret-alt-down'"
[svgIcon]="$any(arrowIcon).svgIcon || caretAltDownIcon"></kendo-icon-wrapper>
</span>
`, isInline: true, dependencies: [{ 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: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ButtonComponent, decorators: [{
type: Component,
args: [{
exportAs: 'kendoButton',
providers: [
LocalizationService,
{
provide: L10N_PREFIX,
useValue: 'kendo.button'
}
],
selector: 'button[kendoButton]',
template: `
<kendo-icon-wrapper
*ngIf="icon || svgIcon"
innerCssClass="k-button-icon"
[name]="icon"
[svgIcon]="svgIcon"></kendo-icon-wrapper>
<span *ngIf="imageUrl" class="k-button-icon k-icon">
<img [src]="imageUrl" class="k-image" role="presentation" />
</span>
<span *ngIf="iconClass" class="k-button-icon" [ngClass]="iconClass"></span>
<span class="k-button-text"><ng-content></ng-content></span>
<span *ngIf="$any(arrowIcon).iconClass" class="k-button-icon" [ngClass]="$any(arrowIcon).iconClass"></span>
<span *ngIf="arrowIcon && !$any(arrowIcon).iconClass" class="k-button-arrow">
<kendo-icon-wrapper
[name]="$any(arrowIcon).icon || 'caret-alt-down'"
[svgIcon]="$any(arrowIcon).svgIcon || caretAltDownIcon"></kendo-icon-wrapper>
</span>
`,
standalone: true,
imports: [NgIf, IconWrapperComponent, NgClass]
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i1.KendoButtonService, decorators: [{
type: Optional
}] }, { type: i2.LocalizationService }, { type: i0.NgZone }]; }, propDecorators: { arrowIcon: [{
type: Input
}], toggleable: [{
type: Input
}], togglable: [{
type: Input
}], selected: [{
type: Input
}], tabIndex: [{
type: Input
}], imageUrl: [{
type: Input
}], iconClass: [{
type: Input
}], icon: [{
type: Input
}], disabled: [{
type: Input
}], size: [{
type: Input
}], rounded: [{
type: Input
}], fillMode: [{
type: Input
}], themeColor: [{
type: Input
}], svgIcon: [{
type: Input
}], selectedChange: [{
type: Output
}], click: [{
type: Output
}], classButton: [{
type: HostBinding,
args: ['class.k-button']
}], isToggleable: [{
type: HostBinding,
args: ['class.k-toggle-button']
}], iconButtonClass: [{
type: HostBinding,
args: ['class.k-icon-button']
}], classDisabled: [{
type: HostBinding,
args: ['class.k-disabled']
}], classActive: [{
type: HostBinding,
args: ['class.k-selected']
}], getDirection: [{
type: HostBinding,
args: ['attr.dir']
}], onFocus: [{
type: HostListener,
args: ['focus']
}], onBlur: [{
type: HostListener,
args: ['blur']
}], primary: [{
type: Input
}], look: [{
type: Input
}] } });