UNPKG

@progress/kendo-angular-progressbar

Version:

Kendo UI Angular component starter template

1,274 lines (1,264 loc) 64.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 * as i1 from '@progress/kendo-angular-l10n'; import { ComponentMessages, LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n'; import * as i0 from '@angular/core'; import { isDevMode, Component, HostBinding, Input, Directive, forwardRef, EventEmitter, Output, ViewChild, ContentChild, NgModule } from '@angular/core'; import { validatePackage } from '@progress/kendo-licensing'; import { hasObservers, isDocumentAvailable, isChanged, ResizeSensorComponent, ResizeBatchService } from '@progress/kendo-angular-common'; import { NgStyle, NgClass, NgIf, NgFor, NgTemplateOutlet } from '@angular/common'; import { Subscription } from 'rxjs'; import { take } from 'rxjs/operators'; /** * @hidden */ const packageMetadata = { name: '@progress/kendo-angular-progressbar', productName: 'Kendo UI for Angular', productCode: 'KENDOUIANGULAR', productCodes: ['KENDOUIANGULAR'], publishDate: 1743579485, version: '18.4.0', licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/' }; /** * @hidden */ const MIN_MAX_ERROR_MESSAGE = `The max value should be greater than the min.`; /** * @hidden */ const LABEL_DECIMALS = 3; /** * @hidden */ const MIN_RATIO = 0.0001; /** * @hidden */ const formatValue = (value, min, max, label) => { const defaultFormattedValue = truncateNumber(value); if (typeof label !== 'boolean') { if (typeof label.format === 'string') { switch (label.format) { case 'value': return defaultFormattedValue; case 'percent': return `${Math.floor(calculatePercentage(value, min, max))}%`; default: return defaultFormattedValue; } } else if (typeof label.format === 'function') { return label.format(value); } else { return defaultFormattedValue; } } return defaultFormattedValue; }; /** * @hidden */ const validateRange = (min, max) => { if (isDevMode && min > max) { throw new Error(MIN_MAX_ERROR_MESSAGE); } }; /** * @hidden */ const adjustValueToRange = (min, max, value) => Math.max(Math.min(value, max), min); /** * @hidden */ const calculatePercentage = (value, min, max) => { const decimalValue = Math.abs((value - min) / (max - min)); return decimalValue * 100; }; /** * @hidden */ const truncateNumber = (value) => { const numberParts = value.toString().split('.'); return numberParts.length === 1 ? `${numberParts[0]}` : `${numberParts[0]}.${numberParts[1].substr(0, LABEL_DECIMALS)}`; }; /** * @hidden */ const calculateRatio = (min, max, value) => Math.max((value - min) / (max - min), MIN_RATIO); /** * @hidden */ const extractValueFromChanges = (changes, type, value) => changes[type] && changes[type].currentValue !== undefined ? changes[type].currentValue : value; /** * @hidden */ const runAnimation = (changes, animation, previousValue, displayValue) => animation && typeof requestAnimationFrame !== 'undefined' && changes['value'] && previousValue !== displayValue; /** * @hidden */ const stopCurrentAnimation = (changes) => { const isAnimationChanged = Boolean(changes['animation']); const hasAnimation = isAnimationChanged && changes['animation'].currentValue; return isAnimationChanged && !hasAnimation; }; /** * @hidden */ const setProgressBarStyles = (props, renderer) => { props.forEach(prop => { renderer[prop.method](prop.el, prop.attr, `${prop.attrValue}`); }); }; /** * @hidden */ const removeProgressBarStyles = (props, renderer) => { props.forEach(prop => { renderer[prop.method](prop.el, prop.attr); }); }; /** * @hidden */ const hasElementSize = (element) => { return !!(element.style.width && element.style.height); }; /** * @hidden */ class ProgressBarBase { elem; renderer; localization; hostClasses = true; get isHorizontal() { return this.orientation === 'horizontal'; } get isVertical() { return this.orientation === 'vertical'; } get disabledClass() { return this.disabled; } get reverseClass() { return this.reverse; } get indeterminateClass() { return this.indeterminate; } get dirAttribute() { return this.direction; } roleAttribute = 'progressbar'; get ariaMinAttribute() { return String(this.min); } get ariaMaxAttribute() { return String(this.max); } get ariaValueAttribute() { return this.indeterminate ? undefined : String(this.displayValue); } /** * The maximum value of the ProgressBar. * Defaults to `100`. */ max = 100; /** * The minimum value of the ProgressBar. * Defaults to `0`. */ min = 0; /** * The value of the ProgressBar. * Has to be between `min` and `max`. * By default, the value is equal to the `min` value. */ /** * The value of the ProgressBar. * Has to be between `min` and `max`. * Defaults to `0`. */ value = 0; /** * @hidden */ get isCompleted() { return this.value === this.max; } /** * @hidden */ get statusWidth() { return this.orientation === 'horizontal' ? this._progressRatio * 100 : 100; } /** * @hidden */ get statusHeight() { return this.orientation === 'vertical' ? this._progressRatio * 100 : 100; } /** * @hidden */ get statusWrapperWidth() { return this.orientation === 'horizontal' ? 100 / this._progressRatio : 100; } /** * @hidden */ get statusWrapperHeight() { return this.orientation === 'vertical' ? 100 / this._progressRatio : 100; } get _progressRatio() { return calculateRatio(this.min, this.max, this.displayValue); } /** * Defines the orientation of the ProgressBar * ([see example]({% slug progressbar_orientation %})). * Defaults to `horizontal`. */ orientation = 'horizontal'; /** * If set to `true`, the ProgressBar will be disabled * ([see example]({% slug progressbar_disabled %})). * It will still allow you to change its value. * Defaults to `false`. */ disabled = false; /** * If set to `true`, the ProgressBar will be reversed * ([see example]({% slug progressbar_direction %})). * Defaults to `false`. */ reverse = false; /** * Sets the `indeterminate` state of the ProgressBar. * Defaults to `false`. */ indeterminate = false; direction; localizationChangeSubscription; displayValue = 0; previousValue = 0; /** * @hidden */ constructor(elem, renderer, localization) { this.elem = elem; this.renderer = renderer; this.localization = localization; validatePackage(packageMetadata); this.localizationChangeSubscription = localization.changes.subscribe(({ rtl }) => { this.direction = rtl ? 'rtl' : 'ltr'; }); } ngAfterViewInit() { const elem = this.elem.nativeElement; const label = this.localization.get('progressBarLabel'); this.renderer.setAttribute(elem, 'aria-label', label); } ngOnChanges(changes) { const min = extractValueFromChanges(changes, 'min', this.min); const max = extractValueFromChanges(changes, 'max', this.max); const value = extractValueFromChanges(changes, 'value', this.value); if (changes['min'] || changes['max'] || changes['value']) { if (changes['min'] || changes['max']) { validateRange(min, max); } if (changes['value']) { if (value == null || Number.isNaN(value)) { this.value = min; } const previousValue = this.displayValue; this.displayValue = adjustValueToRange(this.min, this.max, value); this.previousValue = previousValue; } this.min = min; this.max = max; this.displayValue = adjustValueToRange(this.min, this.max, value); } } ngOnDestroy() { if (this.localizationChangeSubscription) { this.localizationChangeSubscription.unsubscribe(); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ProgressBarBase, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ProgressBarBase, selector: "ng-component", inputs: { max: "max", min: "min", value: "value", orientation: "orientation", disabled: "disabled", reverse: "reverse", indeterminate: "indeterminate" }, host: { properties: { "class.k-progressbar": "this.hostClasses", "class.k-progressbar-horizontal": "this.isHorizontal", "class.k-progressbar-vertical": "this.isVertical", "class.k-disabled": "this.disabledClass", "class.k-progressbar-reverse": "this.reverseClass", "class.k-progressbar-indeterminate": "this.indeterminateClass", "attr.dir": "this.dirAttribute", "attr.role": "this.roleAttribute", "attr.aria-valuemin": "this.ariaMinAttribute", "attr.aria-valuemax": "this.ariaMaxAttribute", "attr.aria-valuenow": "this.ariaValueAttribute" } }, usesOnChanges: true, ngImport: i0, template: '', isInline: true }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ProgressBarBase, decorators: [{ type: Component, args: [{ template: '' }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i1.LocalizationService }]; }, propDecorators: { hostClasses: [{ type: HostBinding, args: ['class.k-progressbar'] }], isHorizontal: [{ type: HostBinding, args: ['class.k-progressbar-horizontal'] }], isVertical: [{ type: HostBinding, args: ['class.k-progressbar-vertical'] }], disabledClass: [{ type: HostBinding, args: ['class.k-disabled'] }], reverseClass: [{ type: HostBinding, args: ['class.k-progressbar-reverse'] }], indeterminateClass: [{ type: HostBinding, args: ['class.k-progressbar-indeterminate'] }], dirAttribute: [{ type: HostBinding, args: ['attr.dir'] }], roleAttribute: [{ type: HostBinding, args: ['attr.role'] }], ariaMinAttribute: [{ type: HostBinding, args: ['attr.aria-valuemin'] }], ariaMaxAttribute: [{ type: HostBinding, args: ['attr.aria-valuemax'] }], ariaValueAttribute: [{ type: HostBinding, args: ['attr.aria-valuenow'] }], max: [{ type: Input }], min: [{ type: Input }], value: [{ type: Input }], orientation: [{ type: Input }], disabled: [{ type: Input }], reverse: [{ type: Input }], indeterminate: [{ type: Input }] } }); /** * @hidden */ class ProgressBarMessages extends ComponentMessages { /** * The aria-label attribute for the ProgressBar component. */ progressBarLabel; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ProgressBarMessages, deps: null, target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ProgressBarMessages, inputs: { progressBarLabel: "progressBarLabel" }, usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ProgressBarMessages, decorators: [{ type: Directive, args: [{}] }], propDecorators: { progressBarLabel: [{ type: Input }] } }); /** * @hidden */ class LocalizedProgressBarMessagesDirective extends ProgressBarMessages { service; constructor(service) { super(); this.service = service; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: LocalizedProgressBarMessagesDirective, deps: [{ token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: LocalizedProgressBarMessagesDirective, isStandalone: true, selector: "[kendoProgressBarLocalizedMessages]", providers: [ { provide: ProgressBarMessages, useExisting: forwardRef(() => LocalizedProgressBarMessagesDirective) } ], usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: LocalizedProgressBarMessagesDirective, decorators: [{ type: Directive, args: [{ providers: [ { provide: ProgressBarMessages, useExisting: forwardRef(() => LocalizedProgressBarMessagesDirective) } ], selector: `[kendoProgressBarLocalizedMessages]`, standalone: true }] }], ctorParameters: function () { return [{ type: i1.LocalizationService }]; } }); /** * Represents the [Kendo UI ProgressBar component for Angular]({% slug overview_progressbar %}). * * @example * ```ts-preview * _@Component({ * selector: 'my-app', * template: ` * <kendo-progressbar [value]="value"> * </kendo-progressbar> * ` * }) * class AppComponent { * public value = 50; * } * ``` */ class ProgressBarComponent extends ProgressBarBase { localization; elem; renderer; zone; /** * Determines whether the status label will be visible. * Defaults to `true`&mdash;the label will be visible and displayed with the default * `LabelSettings` having its position set to `end` and its format set to `value`. */ label = true; /** * The CSS styles that will be rendered on the inner element which represents the full portion of the progress bar * ([see example]({% slug progressbar_appearance %})). * Supports the type of values that are supported by [`ngStyle`](link:site.data.urls.angular['ngstyleapi']). */ progressCssStyle; /** * The CSS classes that will be rendered on the inner element which represents the full portion of the progress bar * ([see example]({% slug progressbar_appearance %})). * Supports the type of values that are supported by [`ngClass`](link:site.data.urls.angular['ngclassapi']). */ // eslint-disable-next-line @typescript-eslint/no-explicit-any progressCssClass; /** * The CSS styles that will be rendered on the inner element which represents the empty portion of the progress bar * ([see example]({% slug progressbar_appearance %})). * Supports the type of values that are supported by [`ngStyle`](link:site.data.urls.angular['ngstyleapi']). */ emptyCssStyle; /** * The CSS classes that will be rendered on the inner element which represents the empty portion of the progress bar * ([see example]({% slug progressbar_appearance %})). * Supports the type of values that are supported by [`ngClass`](link:site.data.urls.angular['ngclassapi']). */ // eslint-disable-next-line @typescript-eslint/no-explicit-any emptyCssClass; /** * The animation configuration of the ProgressBar. * Defaults to `false`. */ animation = false; /** * Fires when the animation which indicates the latest value change is completed. */ animationEnd = new EventEmitter(); /** * @hidden */ get showLabel() { if (typeof this.label === 'boolean') { return this.label; } else { if (this.label && !this.label.hasOwnProperty('visible')) { this.label.visible = true; } return this.label.visible; } } /** * @hidden */ get labelPosition() { if (typeof this.label === 'boolean') { return 'end'; } else { if (this.label && !this.label.hasOwnProperty('position')) { this.label.position = 'end'; } return this.label.position; } } /** * @hidden */ get isPositionStart() { return this.labelPosition === 'start'; } /** * @hidden */ get isPositionCenter() { return this.labelPosition === 'center'; } /** * @hidden */ get isPositionEnd() { return this.labelPosition === 'end'; } /** * @hidden */ get formattedLabelValue() { return formatValue(this.displayValue, this.min, this.max, this.label); } progressStatusElement; progressStatusWrapperElement; animationFrame; cancelCurrentAnimation; isAnimationInProgress; /** * @hidden */ constructor(localization, elem, renderer, zone) { super(elem, renderer, localization); this.localization = localization; this.elem = elem; this.renderer = renderer; this.zone = zone; } /** * @hidden */ ngOnChanges(changes) { super.ngOnChanges(changes); if (this.isAnimationInProgress && stopCurrentAnimation(changes)) { this.cancelCurrentAnimation = true; } if (runAnimation(changes, this.animation, this.previousValue, this.displayValue) && !changes['value'].firstChange) { this.startAnimation(this.previousValue); } } /** * @hidden */ ngOnDestroy() { if (this.animationFrame) { cancelAnimationFrame(this.animationFrame); } } /** * @hidden */ startAnimation(previousValue) { this.isAnimationInProgress = true; const element = this.progressStatusElement.nativeElement; const wrapperElement = this.progressStatusWrapperElement.nativeElement; const animationOptions = this.getAnimationOptions(previousValue); this.zone.runOutsideAngular(() => { if (this.animationFrame) { cancelAnimationFrame(this.animationFrame); } const animate = () => { const elapsed = new Date().getTime() - animationOptions.startTime; const position = Math.min(elapsed / animationOptions.duration, 1); const size = animationOptions.startSize + animationOptions.deltaSize * position; const wrapperSize = (100 / size) * 100; this.renderValueChange(element, wrapperElement, animationOptions.property, size, wrapperSize); if (position < 1) { if (this.cancelCurrentAnimation) { this.resetProgress(element, wrapperElement, animationOptions.property); return; } this.animationFrame = requestAnimationFrame(animate); } else { this.stopAnimation(previousValue); } }; animate(); }); } /** * @hidden */ get animationDuration() { if (typeof this.animation === 'boolean') { return 400; } else { if (this.animation && !this.animation.hasOwnProperty('duration')) { this.animation.duration = 400; } return this.animation.duration; } } stopAnimation(value) { if (hasObservers(this.animationEnd)) { this.zone.run(() => { this.animationEnd.emit({ from: value, to: this.displayValue }); }); } this.zone.run(() => { this.isAnimationInProgress = false; }); } getAnimationOptions(value) { const isHorizontal = this.orientation === 'horizontal'; const previousRatio = calculateRatio(this.min, this.max, value); const previousStatusWidth = isHorizontal ? previousRatio * 100 : 100; const previousStatusHeight = !isHorizontal ? previousRatio * 100 : 100; const property = isHorizontal ? 'width' : 'height'; const startTime = new Date().getTime(); const startSize = isHorizontal ? previousStatusWidth : previousStatusHeight; const deltaSize = isHorizontal ? this.statusWidth - previousStatusWidth : this.statusHeight - previousStatusHeight; const duration = this.animationDuration * Math.abs((deltaSize / 100)); return { property, startTime, startSize, deltaSize, duration }; } renderValueChange(element, wrapperElement, property, size, wrapperSize) { this.renderer.setStyle(element, property, size + '%'); this.renderer.setStyle(wrapperElement, property, wrapperSize + '%'); } resetProgress(element, wrapperElement, property) { const size = calculateRatio(this.min, this.max, this.value); const newSize = size * 100; const newWrapperSize = 100 / size; this.renderValueChange(element, wrapperElement, property, newSize, newWrapperSize); this.zone.run(() => { this.cancelCurrentAnimation = false; this.isAnimationInProgress = false; }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ProgressBarComponent, deps: [{ token: i1.LocalizationService }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ProgressBarComponent, isStandalone: true, selector: "kendo-progressbar", inputs: { label: "label", progressCssStyle: "progressCssStyle", progressCssClass: "progressCssClass", emptyCssStyle: "emptyCssStyle", emptyCssClass: "emptyCssClass", animation: "animation" }, outputs: { animationEnd: "animationEnd" }, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.progressbar' } ], viewQueries: [{ propertyName: "progressStatusElement", first: true, predicate: ["progressStatus"], descendants: true }, { propertyName: "progressStatusWrapperElement", first: true, predicate: ["progressStatusWrap"], descendants: true }], exportAs: ["kendoProgressBar"], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: ` <ng-container kendoProgressBarLocalizedMessages i18n-progressBarLabel="kendo.progressbar.progressBarLabel|The aria-label attribute for the ProgressBar component." progressBarLabel="Progressbar" > </ng-container> <span class="k-progress-status-wrap" [class.k-progress-start]="isPositionStart" [class.k-progress-center]="isPositionCenter" [class.k-progress-end]="isPositionEnd" [ngStyle]="emptyCssStyle" [ngClass]="emptyCssClass"> <span *ngIf="showLabel" class="k-progress-status">{{formattedLabelValue}}</span> </span> <div #progressStatus class="k-selected k-progressbar-value" [class.k-complete]="isCompleted" [ngStyle]="progressCssStyle" [ngClass]="progressCssClass" [style.width.%]="statusWidth" [style.height.%]="statusHeight" > <span #progressStatusWrap class="k-progress-status-wrap" [style.width.%]="statusWrapperWidth" [style.height.%]="statusWrapperHeight" [class.k-progress-start]="isPositionStart" [class.k-progress-center]="isPositionCenter" [class.k-progress-end]="isPositionEnd" > <span *ngIf="showLabel" class="k-progress-status">{{formattedLabelValue}}</span> </span> </div> `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedProgressBarMessagesDirective, selector: "[kendoProgressBarLocalizedMessages]" }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ProgressBarComponent, decorators: [{ type: Component, args: [{ exportAs: 'kendoProgressBar', selector: 'kendo-progressbar', template: ` <ng-container kendoProgressBarLocalizedMessages i18n-progressBarLabel="kendo.progressbar.progressBarLabel|The aria-label attribute for the ProgressBar component." progressBarLabel="Progressbar" > </ng-container> <span class="k-progress-status-wrap" [class.k-progress-start]="isPositionStart" [class.k-progress-center]="isPositionCenter" [class.k-progress-end]="isPositionEnd" [ngStyle]="emptyCssStyle" [ngClass]="emptyCssClass"> <span *ngIf="showLabel" class="k-progress-status">{{formattedLabelValue}}</span> </span> <div #progressStatus class="k-selected k-progressbar-value" [class.k-complete]="isCompleted" [ngStyle]="progressCssStyle" [ngClass]="progressCssClass" [style.width.%]="statusWidth" [style.height.%]="statusHeight" > <span #progressStatusWrap class="k-progress-status-wrap" [style.width.%]="statusWrapperWidth" [style.height.%]="statusWrapperHeight" [class.k-progress-start]="isPositionStart" [class.k-progress-center]="isPositionCenter" [class.k-progress-end]="isPositionEnd" > <span *ngIf="showLabel" class="k-progress-status">{{formattedLabelValue}}</span> </span> </div> `, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.progressbar' } ], standalone: true, imports: [LocalizedProgressBarMessagesDirective, NgStyle, NgClass, NgIf] }] }], ctorParameters: function () { return [{ type: i1.LocalizationService }, { type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }]; }, propDecorators: { label: [{ type: Input }], progressCssStyle: [{ type: Input }], progressCssClass: [{ type: Input }], emptyCssStyle: [{ type: Input }], emptyCssClass: [{ type: Input }], animation: [{ type: Input }], animationEnd: [{ type: Output }], progressStatusElement: [{ type: ViewChild, args: ['progressStatus', { static: false }] }], progressStatusWrapperElement: [{ type: ViewChild, args: ['progressStatusWrap', { static: false }] }] } }); /** * Represents the [Kendo UI ChunkProgressBar component for Angular]({% slug overview_chunkprogressbar %}). * * @example * ```ts-preview * _@Component({ * selector: 'my-app', * template: ` * <kendo-chunkprogressbar [value]="value"> * </kendo-chunkprogressbar> * ` * }) * class AppComponent { * public value = 40; * } * ``` */ class ChunkProgressBarComponent extends ProgressBarBase { localization; elem; renderer; chunkClass = true; /** * Sets the number of chunks into which the ChunkProgressBar will be split. * Defaults to `5`. */ chunkCount = 5; /** * @hidden */ get chunks() { const count = this.chunkCount; const chunks = Array(count).fill(false); const completedChunks = Math.floor(this._progressRatio * count); for (let i = 0; i < completedChunks; i++) { chunks[i] = true; } return chunks; } /** * The CSS styles that will be rendered on the full chunk elements ([see example]({% slug chunkprogressbar_appearance %})). * Supports the type of values that are supported by [`ngStyle`](link:site.data.urls.angular['ngstyleapi']). */ // eslint-disable-next-line @typescript-eslint/no-explicit-any progressCssStyle; /** * The CSS classes that will be rendered on the full chunk elements ([see example]({% slug chunkprogressbar_appearance %})). * Supports the type of values that are supported by [`ngClass`](link:site.data.urls.angular['ngclassapi']). */ progressCssClass; /** * The CSS styles that will be rendered on the empty chunk elements ([see example]({% slug chunkprogressbar_appearance %})). * Supports the type of values that are supported by [`ngStyle`](link:site.data.urls.angular['ngstyleapi']). */ emptyCssStyle; /** * The CSS classes that will be rendered on the empty chunk elements ([see example]({% slug chunkprogressbar_appearance %})). * Supports the type of values that are supported by [`ngClass`](link:site.data.urls.angular['ngclassapi']). */ // eslint-disable-next-line @typescript-eslint/no-explicit-any emptyCssClass; /** * @hidden */ get chunkSizePercentage() { return 100 / this.chunkCount; } /** * @hidden */ get orientationStyles() { if (this.orientation === 'horizontal') { this._orientationStyles.width = `${this.chunkSizePercentage}%`; this._orientationStyles.height = null; } else { this._orientationStyles.height = `${this.chunkSizePercentage}%`; this._orientationStyles.width = null; } return this._orientationStyles; } _orientationStyles = { width: `${this.chunkSizePercentage}%`, height: null }; /** * @hidden */ constructor(localization, elem, renderer) { super(elem, renderer, localization); this.localization = localization; this.elem = elem; this.renderer = renderer; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChunkProgressBarComponent, deps: [{ token: i1.LocalizationService }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ChunkProgressBarComponent, isStandalone: true, selector: "kendo-chunkprogressbar", inputs: { chunkCount: "chunkCount", progressCssStyle: "progressCssStyle", progressCssClass: "progressCssClass", emptyCssStyle: "emptyCssStyle", emptyCssClass: "emptyCssClass" }, host: { properties: { "class.k-chunk-progressbar": "this.chunkClass" } }, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.chunkprogressbar' } ], exportAs: ["kendoChunkProgressBar"], usesInheritance: true, ngImport: i0, template: ` <ng-container kendoProgressBarLocalizedMessages i18n-progressBarLabel="kendo.chunkprogressbar.progressBarLabel|The aria-label attribute for the ChunkProgressBar component." progressBarLabel="Chunk progressbar" > </ng-container> <ul class="k-reset k-progressbar-chunks"> <li class="k-progressbar-chunk" *ngFor="let chunk of chunks; let i = index;" [class.k-first]="i === 0" [class.k-last]="i === chunkCount - 1" [class.k-selected]="chunk" [ngClass]="chunk ? progressCssClass : emptyCssClass" [ngStyle]="chunk ? progressCssStyle : emptyCssStyle" [style.width]="orientationStyles.width" [style.height]="orientationStyles.height" > </li> </ul> `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedProgressBarMessagesDirective, selector: "[kendoProgressBarLocalizedMessages]" }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChunkProgressBarComponent, decorators: [{ type: Component, args: [{ exportAs: 'kendoChunkProgressBar', selector: 'kendo-chunkprogressbar', template: ` <ng-container kendoProgressBarLocalizedMessages i18n-progressBarLabel="kendo.chunkprogressbar.progressBarLabel|The aria-label attribute for the ChunkProgressBar component." progressBarLabel="Chunk progressbar" > </ng-container> <ul class="k-reset k-progressbar-chunks"> <li class="k-progressbar-chunk" *ngFor="let chunk of chunks; let i = index;" [class.k-first]="i === 0" [class.k-last]="i === chunkCount - 1" [class.k-selected]="chunk" [ngClass]="chunk ? progressCssClass : emptyCssClass" [ngStyle]="chunk ? progressCssStyle : emptyCssStyle" [style.width]="orientationStyles.width" [style.height]="orientationStyles.height" > </li> </ul> `, providers: [ LocalizationService, { provide: L10N_PREFIX, useValue: 'kendo.chunkprogressbar' } ], standalone: true, imports: [LocalizedProgressBarMessagesDirective, NgFor, NgClass, NgStyle] }] }], ctorParameters: function () { return [{ type: i1.LocalizationService }, { type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { chunkClass: [{ type: HostBinding, args: ['class.k-chunk-progressbar'] }], chunkCount: [{ type: Input }], progressCssStyle: [{ type: Input }], progressCssClass: [{ type: Input }], emptyCssStyle: [{ type: Input }], emptyCssClass: [{ type: Input }] } }); /** * A directive that selects a template within the `<kendo-circularprogessbar>` component which will be used for the center template. * ([see example]({% slug center_template_circularprogressbar %})). */ class CircularProgressbarCenterTemplateDirective { templateRef; constructor(templateRef) { this.templateRef = templateRef; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CircularProgressbarCenterTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: CircularProgressbarCenterTemplateDirective, isStandalone: true, selector: "[kendoCircularProgressbarCenterTemplate]", ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CircularProgressbarCenterTemplateDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoCircularProgressbarCenterTemplate]', standalone: true }] }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; } }); const DEFAULT_SURFACE_SIZE = 200; /** * Represents the [Kendo UI Circular ProgressBar component for Angular]({% slug overview_circularprogressbar %}). * * @example * ```ts-preview * _@Component({ * selector: 'my-app', * template: ` * <kendo-circularprogressbar [value]="value"></kendo-circularprogressbar> * ` * }) * class AppComponent { * public value: number = 50; * } * ``` */ class CircularProgressBarComponent { renderer; cdr; localization; element; zone; hostClasses = true; get ariaMinAttribute() { return String(this.min); } get ariaMaxAttribute() { return String(this.max); } get ariaValueAttribute() { return this.indeterminate ? undefined : String(this.value); } roleAttribute = 'progressbar'; /** * Sets the default value of the Circular Progressbar between `min` and `max`. * * @default 0 */ set value(value) { if (value > this.max) { this.handleErrors('value > max'); } if (value < this.min) { this.handleErrors('value < min'); } this.previousValue = this.value; this._value = value; } get value() { return this._value; } /** * The maximum value which the Circular Progressbar can accept. * * @default 100 */ set max(max) { if (max < this.min) { this.handleErrors('max < min'); } this._max = max; } get max() { return this._max; } /** * The minimum value which the Circular Progressbar can accept. * * @default 0 */ set min(min) { if (min > this.max) { this.handleErrors('max < min'); } this._min = min; } get min() { return this._min; } /** * Indicates whether an animation will be played on value changes. * * @default false */ animation = false; /** * The opacity of the value arc. * @default 1 */ opacity = 1; /** * Puts the Circular ProgressBar in indeterminate state. * @default false */ set indeterminate(indeterminate) { this._indeterminate = indeterminate; } get indeterminate() { return this._indeterminate; } /** * Configures the pointer color. Could be set to a single color string or customized per progress stages. */ progressColor; /** * Fires when the animation which indicates the latest value change is completed. */ animationEnd = new EventEmitter(); progress; scale; labelElement; surface; centerTemplate; centerTemplateContext = {}; _indeterminate = false; _max = 100; _min = 0; _value = 0; previousValue = 0; internalValue = 0; rtl; subscriptions = new Subscription(); constructor(renderer, cdr, localization, element, zone) { this.renderer = renderer; this.cdr = cdr; this.localization = localization; this.element = element; this.zone = zone; validatePackage(packageMetadata); this.subscriptions.add(this.localization.changes.subscribe(this.rtlChange.bind(this))); } ngAfterViewInit() { if (!isDocumentAvailable()) { return; } const elem = this.element.nativeElement; const ariaLabel = this.localization.get('progressBarLabel'); this.renderer.setAttribute(elem, 'aria-label', ariaLabel); this.initProgressArc(); } ngOnChanges(changes) { const skipFirstChange = true; if (isChanged('value', changes, skipFirstChange) && this.progress) { if (this.animation) { this.progressbarAnimation(); } else { const value = this.value - this.min; this.internalValue = changes['value'].currentValue; this.calculateProgress(value); } } if (changes['opacity'] && this.progress) { setProgressBarStyles([{ method: 'setAttribute', el: this.progress.nativeElement, attr: 'opacity', attrValue: this.opacity.toString() }], this.renderer); } if (changes['indeterminate'] && !changes['indeterminate'].firstChange) { this.indeterminateState(); } } ngOnDestroy() { this.subscriptions.unsubscribe(); } /** * @hidden */ onResize() { this.setStyles(); const value = this.animation ? this.internalValue : this.value; this.calculateProgress(value); this.updateCenterTemplate(value); } initProgressArc() { this.setStyles(); if (this.indeterminate) { this.indeterminateState(); } else { if (!this.animation) { const value = this.value - this.min; this.calculateProgress(value); } else { this.progressbarAnimation(); } } } calculateProgress(value) { if (this.progressColor) { this.updateProgressColor(value); } // needed when we have *ngIf inside the template to render different content depending on some condition this.zone.onStable.pipe(take(1)).subscribe(() => { this.updateCenterTemplate(value + this.min); }); const progressArc = this.progress.nativeElement; const radius = this.progress.nativeElement.r.baseVal.value; const circumference = Math.PI * (radius * 2); const dir = this.rtl ? circumference * -1 : circumference; const strokeDashOffest = circumference - dir * (value / (this.max - this.min)); const progressCalculations = [ { method: 'setStyle', el: progressArc, attr: 'strokeDasharray', attrValue: circumference.toString() }, { method: 'setStyle', el: progressArc, attr: 'strokeDashoffset', attrValue: strokeDashOffest.toString() } ]; setProgressBarStyles(progressCalculations, this.renderer); } progressbarAnimation() { const forwardProgress = { isOngoing: this.internalValue > this.value - this.min, isPositive: this.value >= this.previousValue }; const backwardProgress = { isOngoing: this.internalValue < this.value - this.min, isNegative: this.value <= this.previousValue }; if (forwardProgress.isOngoing && forwardProgress.isPositive || backwardProgress.isOngoing && backwardProgress.isNegative) { return; } this.calculateProgress(this.internalValue); const from = this.internalValue; if (hasObservers(this.animationEnd)) { this.animationEnd.emit({ from: from, to: this.internalValue }); } // eslint-disable-next-line no-unused-expressions forwardProgress.isPositive ? this.internalValue += 1 : this.internalValue -= 1; requestAnimationFrame(this.progressbarAnimation.bind(this)); } setStyles() { const progressArc = this.progress.nativeElement; const scale = this.scale.nativeElement; const surface = this.surface.nativeElement; const element = this.element.nativeElement; let elWidth = element.getBoundingClientRect().width; if (!hasElementSize(element)) { const surfaceSize = [ { method: 'setStyle', el: surface, attr: 'width', attrValue: `${DEFAULT_SURFACE_SIZE}px` }, { method: 'setStyle', el: surface, attr: 'height', attrValue: `${DEFAULT_SURFACE_SIZE}px` } ]; elWidth = DEFAULT_SURFACE_SIZE; setProgressBarStyles(surfaceSize, this.renderer); } const attributesArray = [ { method: 'setAttribute', el: progressArc, attr: 'r', attrValue: String((elWidth / 2) - 10) }, { method: 'setAttribute', el: progressArc, attr: 'cx', attrValue: String((elWidth / 2)) }, { method: 'setAttribute', el: progressArc, attr: 'cy', attrValue: String((elWidth / 2)) }, { method: 'setAttribute', el: progressArc, attr: 'opacity', attrValue: String(this.opacity) }, { method: 'setAttribute', el: scale, attr: 'r', attrValue: String((elWidth / 2) - 10) }, { method: 'setAttribute', el: scale, attr: 'cx', attrValue: String(elWidth / 2) }, { method: 'setAttribute', el: scale, attr: 'cy', attrValue: String(elWidth / 2) } ]; setProgressBarStyles(attributesArray, this.renderer); } indeterminateState() { const progressArc = this.progress.nativeElement; if (this.indeterminate) { // the indeterminate state wont work as the `k-circular-progressbar-arc` has a transform: rotate(-90deg) which is // interfering with the svg animation as the animateTransform brings its own transform: rotate() // This will be like this until the themes release a new version, bringing a new class `k-circular-progressbar-indeterminate-arc` // containing only the necassery CSS styles and we will switch between them when the state of the progressbar is switched. this.calculateProgress(this.value - this.min); const rotate = this.rtl ? { from: 360, to: 0 } : { from: 0, to: 360 }; let color; if (!this.progressColor) { color = getComputedStyle(progressArc).stroke; } const indeterminateStyles = [ { method: 'setStyle', el: progressArc, attr: 'transform-origin', attrValue: 'center' }, { method: 'setStyle', el: progressArc, attr: 'fill', attrValue: 'none' }, { method: 'setStyle', el: progressArc, attr: 'stroke-linecap', attrValue: 'round' }, { method: 'setStyle', el: progressArc, attr: 'stroke', attrValue: color ? color : this.currentColor } ]; setProgressBarStyles(indeterminateStyles, this.renderer); this.renderer.removeClass(progressArc, 'k-circular-progressbar-arc'); progressArc.innerHTML = `<animateTransform attributeName="transform" type="rotate" from="${rotate.from} 0 0" to="${rotate.to} 0 0" dur="1s" repeatCount="indefinite" />`; } else { this.renderer.addClass(progressArc, 'k-circular-progressbar-arc'); const removeIndeterminateStyles = [ { method: 'removeStyle', el: progressArc, attr: 'transform-origin' }, { method: 'removeStyle', el: progressArc, attr: 'fill' }, { method: 'removeStyle', el: progressArc, attr: 'stroke-linecap' } ]; removeProgressBarStyles(removeIndeterminateStyles, this.renderer); progressArc.innerHTML = ''; if (this.animation) { this.progressbarAnimation(); } } } updateCenterTemplate(value) { if (!this.centerTemplate) { return; } this.centerTemplateContext.value = value; this.centerTemplateContext.color = this.currentColor; this.cdr.detectChanges(); this.positionLabel(); } positionLabel() { const labelEl = this.labelElement.nativeElement; const element = this.element.nativeElement; const surface = this.surface.nativeElement; let elWidth; let elHeight; if (!hasElementSize(element)) { const surfaceSize = surface.getBoundingClientRect(); elWidth = surfaceSize.width; elHeight = surfaceSize.height; } else { const elementSize = element.getBoundingClientRect(); elWidth = elementSize.width; elHeight = elementSize.height; } const left = (elWidth / 2) - (labelEl.offsetWidth / 2); const top = (elHeight / 2) - (labelEl.offsetHeight / 2); const labelCalculations = [ { method: 'setStyle', el: labelEl, attr: 'left', attrValue: `${left}px` }, { method: 'setStyle', el: labelEl, attr: 'top', attrValue: `${top}px` } ]; setProgressBa