sass-references/angular-material/material/progress-bar/progress-bar.ts

249 lines
7.8 KiB
TypeScript
Raw Permalink Normal View History

2024-12-06 10:42:08 +08:00
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {
ChangeDetectionStrategy,
Component,
ViewEncapsulation,
ElementRef,
NgZone,
Input,
Output,
EventEmitter,
AfterViewInit,
OnDestroy,
ChangeDetectorRef,
InjectionToken,
inject,
numberAttribute,
ANIMATION_MODULE_TYPE,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {ThemePalette} from '@angular/material/core';
/** Last animation end data. */
export interface ProgressAnimationEnd {
value: number;
}
/** Default `mat-progress-bar` options that can be overridden. */
export interface MatProgressBarDefaultOptions {
/**
* Default theme color of the progress bar. This API is supported in M2 themes only,
* it has no effect in M3 themes.
*
* For information on applying color variants in M3, see
* https://material.angular.io/guide/theming#using-component-color-variants.
*/
color?: ThemePalette;
/** Default mode of the progress bar. */
mode?: ProgressBarMode;
}
/** Injection token to be used to override the default options for `mat-progress-bar`. */
export const MAT_PROGRESS_BAR_DEFAULT_OPTIONS = new InjectionToken<MatProgressBarDefaultOptions>(
'MAT_PROGRESS_BAR_DEFAULT_OPTIONS',
);
/**
* Injection token used to provide the current location to `MatProgressBar`.
* Used to handle server-side rendering and to stub out during unit tests.
* @docs-private
*/
export const MAT_PROGRESS_BAR_LOCATION = new InjectionToken<MatProgressBarLocation>(
'mat-progress-bar-location',
{providedIn: 'root', factory: MAT_PROGRESS_BAR_LOCATION_FACTORY},
);
/**
* Stubbed out location for `MatProgressBar`.
* @docs-private
*/
export interface MatProgressBarLocation {
getPathname: () => string;
}
/** @docs-private */
export function MAT_PROGRESS_BAR_LOCATION_FACTORY(): MatProgressBarLocation {
const _document = inject(DOCUMENT);
const _location = _document ? _document.location : null;
return {
// Note that this needs to be a function, rather than a property, because Angular
// will only resolve it once, but we want the current path on each call.
getPathname: () => (_location ? _location.pathname + _location.search : ''),
};
}
export type ProgressBarMode = 'determinate' | 'indeterminate' | 'buffer' | 'query';
@Component({
selector: 'mat-progress-bar',
exportAs: 'matProgressBar',
host: {
'role': 'progressbar',
'aria-valuemin': '0',
'aria-valuemax': '100',
// set tab index to -1 so screen readers will read the aria-label
// Note: there is a known issue with JAWS that does not read progressbar aria labels on FireFox
'tabindex': '-1',
'[attr.aria-valuenow]': '_isIndeterminate() ? null : value',
'[attr.mode]': 'mode',
'class': 'mat-mdc-progress-bar mdc-linear-progress',
'[class]': '"mat-" + color',
'[class._mat-animation-noopable]': '_isNoopAnimation',
'[class.mdc-linear-progress--animation-ready]': '!_isNoopAnimation',
'[class.mdc-linear-progress--indeterminate]': '_isIndeterminate()',
},
templateUrl: 'progress-bar.html',
styleUrl: 'progress-bar.css',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class MatProgressBar implements AfterViewInit, OnDestroy {
readonly _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
private _ngZone = inject(NgZone);
private _changeDetectorRef = inject(ChangeDetectorRef);
_animationMode? = inject(ANIMATION_MODULE_TYPE, {optional: true});
constructor(...args: unknown[]);
constructor() {
const defaults = inject<MatProgressBarDefaultOptions>(MAT_PROGRESS_BAR_DEFAULT_OPTIONS, {
optional: true,
});
this._isNoopAnimation = this._animationMode === 'NoopAnimations';
if (defaults) {
if (defaults.color) {
this.color = this._defaultColor = defaults.color;
}
this.mode = defaults.mode || this.mode;
}
}
/** Flag that indicates whether NoopAnimations mode is set to true. */
_isNoopAnimation = false;
// TODO: should be typed as `ThemePalette` but internal apps pass in arbitrary strings.
/**
* Theme color of the progress bar. This API is supported in M2 themes only, it
* has no effect in M3 themes.
*
* For information on applying color variants in M3, see
* https://material.angular.io/guide/theming#using-component-color-variants.
*/
@Input()
get color() {
return this._color || this._defaultColor;
}
set color(value: string | null | undefined) {
this._color = value;
}
private _color: string | null | undefined;
private _defaultColor: ThemePalette = 'primary';
/** Value of the progress bar. Defaults to zero. Mirrored to aria-valuenow. */
@Input({transform: numberAttribute})
get value(): number {
return this._value;
}
set value(v: number) {
this._value = clamp(v || 0);
this._changeDetectorRef.markForCheck();
}
private _value = 0;
/** Buffer value of the progress bar. Defaults to zero. */
@Input({transform: numberAttribute})
get bufferValue(): number {
return this._bufferValue || 0;
}
set bufferValue(v: number) {
this._bufferValue = clamp(v || 0);
this._changeDetectorRef.markForCheck();
}
private _bufferValue = 0;
/**
* Event emitted when animation of the primary progress bar completes. This event will not
* be emitted when animations are disabled, nor will it be emitted for modes with continuous
* animations (indeterminate and query).
*/
@Output() readonly animationEnd = new EventEmitter<ProgressAnimationEnd>();
/**
* Mode of the progress bar.
*
* Input must be one of these values: determinate, indeterminate, buffer, query, defaults to
* 'determinate'.
* Mirrored to mode attribute.
*/
@Input()
get mode(): ProgressBarMode {
return this._mode;
}
set mode(value: ProgressBarMode) {
// Note that we don't technically need a getter and a setter here,
// but we use it to match the behavior of the existing mat-progress-bar.
this._mode = value;
this._changeDetectorRef.markForCheck();
}
private _mode: ProgressBarMode = 'determinate';
ngAfterViewInit() {
// Run outside angular so change detection didn't get triggered on every transition end
// instead only on the animation that we care about (primary value bar's transitionend)
this._ngZone.runOutsideAngular(() => {
this._elementRef.nativeElement.addEventListener('transitionend', this._transitionendHandler);
});
}
ngOnDestroy() {
this._elementRef.nativeElement.removeEventListener('transitionend', this._transitionendHandler);
}
/** Gets the transform style that should be applied to the primary bar. */
_getPrimaryBarTransform(): string {
return `scaleX(${this._isIndeterminate() ? 1 : this.value / 100})`;
}
/** Gets the `flex-basis` value that should be applied to the buffer bar. */
_getBufferBarFlexBasis(): string {
return `${this.mode === 'buffer' ? this.bufferValue : 100}%`;
}
/** Returns whether the progress bar is indeterminate. */
_isIndeterminate(): boolean {
return this.mode === 'indeterminate' || this.mode === 'query';
}
/** Event handler for `transitionend` events. */
private _transitionendHandler = (event: TransitionEvent) => {
if (
this.animationEnd.observers.length === 0 ||
!event.target ||
!(event.target as HTMLElement).classList.contains('mdc-linear-progress__primary-bar')
) {
return;
}
if (this.mode === 'determinate' || this.mode === 'buffer') {
this._ngZone.run(() => this.animationEnd.next({value: this.value}));
}
};
}
/** Clamps a value to be between two numbers, by default 0 and 100. */
function clamp(v: number, min = 0, max = 100) {
return Math.max(min, Math.min(max, v));
}