@angular/material
Version:
Angular Material
312 lines (307 loc) • 22.7 kB
JavaScript
import * as i0 from '@angular/core';
import { Component, ViewEncapsulation, ChangeDetectionStrategy, inject, ApplicationRef, createComponent, EnvironmentInjector, ANIMATION_MODULE_TYPE, booleanAttribute, Directive, Optional, Inject, Input, NgModule } from '@angular/core';
import { MatCommonModule } from '@angular/material/core';
import * as i1 from '@angular/cdk/a11y';
import { InteractivityChecker, A11yModule } from '@angular/cdk/a11y';
import { DOCUMENT } from '@angular/common';
let nextId = 0;
const BADGE_CONTENT_CLASS = 'mat-badge-content';
/** Keeps track of the apps currently containing badges. */
const badgeApps = new Set();
/**
* Component used to load the structural styles of the badge.
* @docs-private
*/
class _MatBadgeStyleLoader {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: _MatBadgeStyleLoader, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.0", type: _MatBadgeStyleLoader, isStandalone: true, selector: "ng-component", ngImport: i0, template: '', isInline: true, styles: [".mat-badge{position:relative}.mat-badge.mat-badge{overflow:visible}.mat-badge-content{position:absolute;text-align:center;display:inline-block;transition:transform 200ms ease-in-out;transform:scale(0.6);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;box-sizing:border-box;pointer-events:none;background-color:var(--mat-badge-background-color);color:var(--mat-badge-text-color);font-family:var(--mat-badge-text-font);font-weight:var(--mat-badge-text-weight);border-radius:var(--mat-badge-container-shape)}.cdk-high-contrast-active .mat-badge-content{outline:solid 1px;border-radius:0}.mat-badge-above .mat-badge-content{bottom:100%}.mat-badge-below .mat-badge-content{top:100%}.mat-badge-before .mat-badge-content{right:100%}[dir=rtl] .mat-badge-before .mat-badge-content{right:auto;left:100%}.mat-badge-after .mat-badge-content{left:100%}[dir=rtl] .mat-badge-after .mat-badge-content{left:auto;right:100%}.mat-badge-disabled .mat-badge-content{background-color:var(--mat-badge-disabled-state-background-color);color:var(--mat-badge-disabled-state-text-color)}.mat-badge-hidden .mat-badge-content{display:none}.ng-animate-disabled .mat-badge-content,.mat-badge-content._mat-animation-noopable{transition:none}.mat-badge-content.mat-badge-active{transform:none}.mat-badge-small .mat-badge-content{width:var(--mat-badge-legacy-small-size-container-size, unset);height:var(--mat-badge-legacy-small-size-container-size, unset);min-width:var(--mat-badge-small-size-container-size, unset);min-height:var(--mat-badge-small-size-container-size, unset);line-height:var(--mat-badge-legacy-small-size-container-size, var(--mat-badge-small-size-container-size));padding:var(--mat-badge-small-size-container-padding);font-size:var(--mat-badge-small-size-text-size);margin:var(--mat-badge-small-size-container-offset)}.mat-badge-small.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-small-size-container-overlap-offset)}.mat-badge-medium .mat-badge-content{width:var(--mat-badge-legacy-container-size, unset);height:var(--mat-badge-legacy-container-size, unset);min-width:var(--mat-badge-container-size, unset);min-height:var(--mat-badge-container-size, unset);line-height:var(--mat-badge-legacy-container-size, var(--mat-badge-container-size));padding:var(--mat-badge-container-padding);font-size:var(--mat-badge-text-size);margin:var(--mat-badge-container-offset)}.mat-badge-medium.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-container-overlap-offset)}.mat-badge-large .mat-badge-content{width:var(--mat-badge-legacy-large-size-container-size, unset);height:var(--mat-badge-legacy-large-size-container-size, unset);min-width:var(--mat-badge-large-size-container-size, unset);min-height:var(--mat-badge-large-size-container-size, unset);line-height:var(--mat-badge-legacy-large-size-container-size, var(--mat-badge-large-size-container-size));padding:var(--mat-badge-large-size-container-padding);font-size:var(--mat-badge-large-size-text-size);margin:var(--mat-badge-large-size-container-offset)}.mat-badge-large.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-large-size-container-overlap-offset)}"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: _MatBadgeStyleLoader, decorators: [{
type: Component,
args: [{ standalone: true, encapsulation: ViewEncapsulation.None, template: '', changeDetection: ChangeDetectionStrategy.OnPush, styles: [".mat-badge{position:relative}.mat-badge.mat-badge{overflow:visible}.mat-badge-content{position:absolute;text-align:center;display:inline-block;transition:transform 200ms ease-in-out;transform:scale(0.6);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;box-sizing:border-box;pointer-events:none;background-color:var(--mat-badge-background-color);color:var(--mat-badge-text-color);font-family:var(--mat-badge-text-font);font-weight:var(--mat-badge-text-weight);border-radius:var(--mat-badge-container-shape)}.cdk-high-contrast-active .mat-badge-content{outline:solid 1px;border-radius:0}.mat-badge-above .mat-badge-content{bottom:100%}.mat-badge-below .mat-badge-content{top:100%}.mat-badge-before .mat-badge-content{right:100%}[dir=rtl] .mat-badge-before .mat-badge-content{right:auto;left:100%}.mat-badge-after .mat-badge-content{left:100%}[dir=rtl] .mat-badge-after .mat-badge-content{left:auto;right:100%}.mat-badge-disabled .mat-badge-content{background-color:var(--mat-badge-disabled-state-background-color);color:var(--mat-badge-disabled-state-text-color)}.mat-badge-hidden .mat-badge-content{display:none}.ng-animate-disabled .mat-badge-content,.mat-badge-content._mat-animation-noopable{transition:none}.mat-badge-content.mat-badge-active{transform:none}.mat-badge-small .mat-badge-content{width:var(--mat-badge-legacy-small-size-container-size, unset);height:var(--mat-badge-legacy-small-size-container-size, unset);min-width:var(--mat-badge-small-size-container-size, unset);min-height:var(--mat-badge-small-size-container-size, unset);line-height:var(--mat-badge-legacy-small-size-container-size, var(--mat-badge-small-size-container-size));padding:var(--mat-badge-small-size-container-padding);font-size:var(--mat-badge-small-size-text-size);margin:var(--mat-badge-small-size-container-offset)}.mat-badge-small.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-small-size-container-overlap-offset)}.mat-badge-medium .mat-badge-content{width:var(--mat-badge-legacy-container-size, unset);height:var(--mat-badge-legacy-container-size, unset);min-width:var(--mat-badge-container-size, unset);min-height:var(--mat-badge-container-size, unset);line-height:var(--mat-badge-legacy-container-size, var(--mat-badge-container-size));padding:var(--mat-badge-container-padding);font-size:var(--mat-badge-text-size);margin:var(--mat-badge-container-offset)}.mat-badge-medium.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-container-overlap-offset)}.mat-badge-large .mat-badge-content{width:var(--mat-badge-legacy-large-size-container-size, unset);height:var(--mat-badge-legacy-large-size-container-size, unset);min-width:var(--mat-badge-large-size-container-size, unset);min-height:var(--mat-badge-large-size-container-size, unset);line-height:var(--mat-badge-legacy-large-size-container-size, var(--mat-badge-large-size-container-size));padding:var(--mat-badge-large-size-container-padding);font-size:var(--mat-badge-large-size-text-size);margin:var(--mat-badge-large-size-container-offset)}.mat-badge-large.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-large-size-container-overlap-offset)}"] }]
}] });
/** Directive to display a text badge. */
class MatBadge {
/** The color of the badge. Can be `primary`, `accent`, or `warn`. */
get color() {
return this._color;
}
set color(value) {
this._setColor(value);
this._color = value;
}
/** The content for the badge */
get content() {
return this._content;
}
set content(newContent) {
this._updateRenderedContent(newContent);
}
/** Message used to describe the decorated element via aria-describedby */
get description() {
return this._description;
}
set description(newDescription) {
this._updateDescription(newDescription);
}
constructor(_ngZone, _elementRef, _ariaDescriber, _renderer, _animationMode) {
this._ngZone = _ngZone;
this._elementRef = _elementRef;
this._ariaDescriber = _ariaDescriber;
this._renderer = _renderer;
this._animationMode = _animationMode;
this._color = 'primary';
/** Whether the badge should overlap its contents or not */
this.overlap = true;
/**
* Position the badge should reside.
* Accepts any combination of 'above'|'below' and 'before'|'after'
*/
this.position = 'above after';
/** Size of the badge. Can be 'small', 'medium', or 'large'. */
this.size = 'medium';
/** Unique id for the badge */
this._id = nextId++;
/** Whether the OnInit lifecycle hook has run yet */
this._isInitialized = false;
/** InteractivityChecker to determine if the badge host is focusable. */
this._interactivityChecker = inject(InteractivityChecker);
this._document = inject(DOCUMENT);
const appRef = inject(ApplicationRef);
if (!badgeApps.has(appRef)) {
badgeApps.add(appRef);
const componentRef = createComponent(_MatBadgeStyleLoader, {
environmentInjector: inject(EnvironmentInjector),
});
appRef.onDestroy(() => {
badgeApps.delete(appRef);
if (badgeApps.size === 0) {
componentRef.destroy();
}
});
}
if (typeof ngDevMode === 'undefined' || ngDevMode) {
const nativeElement = _elementRef.nativeElement;
if (nativeElement.nodeType !== nativeElement.ELEMENT_NODE) {
throw Error('matBadge must be attached to an element node.');
}
const matIconTagName = 'mat-icon';
// Heads-up for developers to avoid putting matBadge on <mat-icon>
// as it is aria-hidden by default docs mention this at:
// https://material.angular.io/components/badge/overview#accessibility
if (nativeElement.tagName.toLowerCase() === matIconTagName &&
nativeElement.getAttribute('aria-hidden') === 'true') {
console.warn(`Detected a matBadge on an "aria-hidden" "<mat-icon>". ` +
`Consider setting aria-hidden="false" in order to surface the information assistive technology.` +
`\n${nativeElement.outerHTML}`);
}
}
}
/** Whether the badge is above the host or not */
isAbove() {
return this.position.indexOf('below') === -1;
}
/** Whether the badge is after the host or not */
isAfter() {
return this.position.indexOf('before') === -1;
}
/**
* Gets the element into which the badge's content is being rendered. Undefined if the element
* hasn't been created (e.g. if the badge doesn't have content).
*/
getBadgeElement() {
return this._badgeElement;
}
ngOnInit() {
// We may have server-side rendered badge that we need to clear.
// We need to do this in ngOnInit because the full content of the component
// on which the badge is attached won't necessarily be in the DOM until this point.
this._clearExistingBadges();
if (this.content && !this._badgeElement) {
this._badgeElement = this._createBadgeElement();
this._updateRenderedContent(this.content);
}
this._isInitialized = true;
}
ngOnDestroy() {
// ViewEngine only: when creating a badge through the Renderer, Angular remembers its index.
// We have to destroy it ourselves, otherwise it'll be retained in memory.
if (this._renderer.destroyNode) {
this._renderer.destroyNode(this._badgeElement);
this._inlineBadgeDescription?.remove();
}
this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this.description);
}
/** Gets whether the badge's host element is interactive. */
_isHostInteractive() {
// Ignore visibility since it requires an expensive style caluclation.
return this._interactivityChecker.isFocusable(this._elementRef.nativeElement, {
ignoreVisibility: true,
});
}
/** Creates the badge element */
_createBadgeElement() {
const badgeElement = this._renderer.createElement('span');
const activeClass = 'mat-badge-active';
badgeElement.setAttribute('id', `mat-badge-content-${this._id}`);
// The badge is aria-hidden because we don't want it to appear in the page's navigation
// flow. Instead, we use the badge to describe the decorated element with aria-describedby.
badgeElement.setAttribute('aria-hidden', 'true');
badgeElement.classList.add(BADGE_CONTENT_CLASS);
if (this._animationMode === 'NoopAnimations') {
badgeElement.classList.add('_mat-animation-noopable');
}
this._elementRef.nativeElement.appendChild(badgeElement);
// animate in after insertion
if (typeof requestAnimationFrame === 'function' && this._animationMode !== 'NoopAnimations') {
this._ngZone.runOutsideAngular(() => {
requestAnimationFrame(() => {
badgeElement.classList.add(activeClass);
});
});
}
else {
badgeElement.classList.add(activeClass);
}
return badgeElement;
}
/** Update the text content of the badge element in the DOM, creating the element if necessary. */
_updateRenderedContent(newContent) {
const newContentNormalized = `${newContent ?? ''}`.trim();
// Don't create the badge element if the directive isn't initialized because we want to
// append the badge element to the *end* of the host element's content for backwards
// compatibility.
if (this._isInitialized && newContentNormalized && !this._badgeElement) {
this._badgeElement = this._createBadgeElement();
}
if (this._badgeElement) {
this._badgeElement.textContent = newContentNormalized;
}
this._content = newContentNormalized;
}
/** Updates the host element's aria description via AriaDescriber. */
_updateDescription(newDescription) {
// Always start by removing the aria-describedby; we will add a new one if necessary.
this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this.description);
// NOTE: We only check whether the host is interactive here, which happens during
// when then badge content changes. It is possible that the host changes
// interactivity status separate from one of these. However, watching the interactivity
// status of the host would require a `MutationObserver`, which is likely more code + overhead
// than it's worth; from usages inside Google, we see that the vats majority of badges either
// never change interactivity, or also set `matBadgeHidden` based on the same condition.
if (!newDescription || this._isHostInteractive()) {
this._removeInlineDescription();
}
this._description = newDescription;
// We don't add `aria-describedby` for non-interactive hosts elements because we
// instead insert the description inline.
if (this._isHostInteractive()) {
this._ariaDescriber.describe(this._elementRef.nativeElement, newDescription);
}
else {
this._updateInlineDescription();
}
}
_updateInlineDescription() {
// Create the inline description element if it doesn't exist
if (!this._inlineBadgeDescription) {
this._inlineBadgeDescription = this._document.createElement('span');
this._inlineBadgeDescription.classList.add('cdk-visually-hidden');
}
this._inlineBadgeDescription.textContent = this.description;
this._badgeElement?.appendChild(this._inlineBadgeDescription);
}
_removeInlineDescription() {
this._inlineBadgeDescription?.remove();
this._inlineBadgeDescription = undefined;
}
/** Adds css theme class given the color to the component host */
_setColor(colorPalette) {
const classList = this._elementRef.nativeElement.classList;
classList.remove(`mat-badge-${this._color}`);
if (colorPalette) {
classList.add(`mat-badge-${colorPalette}`);
}
}
/** Clears any existing badges that might be left over from server-side rendering. */
_clearExistingBadges() {
// Only check direct children of this host element in order to avoid deleting
// any badges that might exist in descendant elements.
const badges = this._elementRef.nativeElement.querySelectorAll(`:scope > .${BADGE_CONTENT_CLASS}`);
for (const badgeElement of Array.from(badges)) {
if (badgeElement !== this._badgeElement) {
badgeElement.remove();
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: MatBadge, deps: [{ token: i0.NgZone }, { token: i0.ElementRef }, { token: i1.AriaDescriber }, { token: i0.Renderer2 }, { token: ANIMATION_MODULE_TYPE, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "18.0.0", type: MatBadge, isStandalone: true, selector: "[matBadge]", inputs: { color: ["matBadgeColor", "color"], overlap: ["matBadgeOverlap", "overlap", booleanAttribute], disabled: ["matBadgeDisabled", "disabled", booleanAttribute], position: ["matBadgePosition", "position"], content: ["matBadge", "content"], description: ["matBadgeDescription", "description"], size: ["matBadgeSize", "size"], hidden: ["matBadgeHidden", "hidden", booleanAttribute] }, host: { properties: { "class.mat-badge-overlap": "overlap", "class.mat-badge-above": "isAbove()", "class.mat-badge-below": "!isAbove()", "class.mat-badge-before": "!isAfter()", "class.mat-badge-after": "isAfter()", "class.mat-badge-small": "size === \"small\"", "class.mat-badge-medium": "size === \"medium\"", "class.mat-badge-large": "size === \"large\"", "class.mat-badge-hidden": "hidden || !content", "class.mat-badge-disabled": "disabled" }, classAttribute: "mat-badge" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: MatBadge, decorators: [{
type: Directive,
args: [{
selector: '[matBadge]',
host: {
'class': 'mat-badge',
'[class.mat-badge-overlap]': 'overlap',
'[class.mat-badge-above]': 'isAbove()',
'[class.mat-badge-below]': '!isAbove()',
'[class.mat-badge-before]': '!isAfter()',
'[class.mat-badge-after]': 'isAfter()',
'[class.mat-badge-small]': 'size === "small"',
'[class.mat-badge-medium]': 'size === "medium"',
'[class.mat-badge-large]': 'size === "large"',
'[class.mat-badge-hidden]': 'hidden || !content',
'[class.mat-badge-disabled]': 'disabled',
},
standalone: true,
}]
}], ctorParameters: () => [{ type: i0.NgZone }, { type: i0.ElementRef }, { type: i1.AriaDescriber }, { type: i0.Renderer2 }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [ANIMATION_MODULE_TYPE]
}] }], propDecorators: { color: [{
type: Input,
args: ['matBadgeColor']
}], overlap: [{
type: Input,
args: [{ alias: 'matBadgeOverlap', transform: booleanAttribute }]
}], disabled: [{
type: Input,
args: [{ alias: 'matBadgeDisabled', transform: booleanAttribute }]
}], position: [{
type: Input,
args: ['matBadgePosition']
}], content: [{
type: Input,
args: ['matBadge']
}], description: [{
type: Input,
args: ['matBadgeDescription']
}], size: [{
type: Input,
args: ['matBadgeSize']
}], hidden: [{
type: Input,
args: [{ alias: 'matBadgeHidden', transform: booleanAttribute }]
}] } });
class MatBadgeModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: MatBadgeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.0.0", ngImport: i0, type: MatBadgeModule, imports: [A11yModule, MatCommonModule, MatBadge, _MatBadgeStyleLoader], exports: [MatBadge, MatCommonModule] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: MatBadgeModule, imports: [A11yModule, MatCommonModule, MatCommonModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: MatBadgeModule, decorators: [{
type: NgModule,
args: [{
// Note: we _shouldn't_ have to import `_MatBadgeStyleLoader`,
// but it seems to be necessary for tests.
imports: [A11yModule, MatCommonModule, MatBadge, _MatBadgeStyleLoader],
exports: [MatBadge, MatCommonModule],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { MatBadge, MatBadgeModule };
//# sourceMappingURL=badge.mjs.map