UNPKG

@progress/kendo-angular-buttons

Version:
552 lines (549 loc) 19.7 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, 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&mdash;or multiple classes separated by spaces&mdash; * 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 }] } });