badge.mjs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import { AriaDescriber, _IdGenerator, InteractivityChecker, A11yModule } from '@angular/cdk/a11y';
  2. import { DOCUMENT } from '@angular/common';
  3. import * as i0 from '@angular/core';
  4. import { Component, ViewEncapsulation, ChangeDetectionStrategy, inject, NgZone, ElementRef, Renderer2, ANIMATION_MODULE_TYPE, booleanAttribute, Directive, Input, NgModule } from '@angular/core';
  5. import { _CdkPrivateStyleLoader, _VisuallyHiddenLoader } from '@angular/cdk/private';
  6. import { M as MatCommonModule } from './common-module-WayjW0Pb.mjs';
  7. import '@angular/cdk/bidi';
  8. const BADGE_CONTENT_CLASS = 'mat-badge-content';
  9. /**
  10. * Component used to load the structural styles of the badge.
  11. * @docs-private
  12. */
  13. class _MatBadgeStyleLoader {
  14. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: _MatBadgeStyleLoader, deps: [], target: i0.ɵɵFactoryTarget.Component });
  15. static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", 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, var(--mat-sys-error));color:var(--mat-badge-text-color, var(--mat-sys-on-error));font-family:var(--mat-badge-text-font, var(--mat-sys-label-small-font));font-weight:var(--mat-badge-text-weight, var(--mat-sys-label-small-weight));border-radius:var(--mat-badge-container-shape, var(--mat-sys-corner-full))}.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%}@media(forced-colors: active){.mat-badge-content{outline:solid 1px;border-radius:0}}.mat-badge-disabled .mat-badge-content{background-color:var(--mat-badge-disabled-state-background-color, color-mix(in srgb, var(--mat-sys-error) 38%, transparent));color:var(--mat-badge-disabled-state-text-color, var(--mat-sys-on-error))}.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, 6px);min-height:var(--mat-badge-small-size-container-size, 6px);line-height:var(--mat-badge-small-size-line-height, 6px);padding:var(--mat-badge-small-size-container-padding, 0);font-size:var(--mat-badge-small-size-text-size, 0);margin:var(--mat-badge-small-size-container-offset, -6px 0)}.mat-badge-small.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-small-size-container-overlap-offset, -6px)}.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, 16px);min-height:var(--mat-badge-container-size, 16px);line-height:var(--mat-badge-line-height, 16px);padding:var(--mat-badge-container-padding, 0 4px);font-size:var(--mat-badge-text-size, var(--mat-sys-label-small-size));margin:var(--mat-badge-container-offset, -12px 0)}.mat-badge-medium.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-container-overlap-offset, -12px)}.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, 16px);min-height:var(--mat-badge-large-size-container-size, 16px);line-height:var(--mat-badge-large-size-line-height, 16px);padding:var(--mat-badge-large-size-container-padding, 0 4px);font-size:var(--mat-badge-large-size-text-size, var(--mat-sys-label-small-size));margin:var(--mat-badge-large-size-container-offset, -12px 0)}.mat-badge-large.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-large-size-container-overlap-offset, -12px)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
  16. }
  17. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: _MatBadgeStyleLoader, decorators: [{
  18. type: Component,
  19. args: [{ 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, var(--mat-sys-error));color:var(--mat-badge-text-color, var(--mat-sys-on-error));font-family:var(--mat-badge-text-font, var(--mat-sys-label-small-font));font-weight:var(--mat-badge-text-weight, var(--mat-sys-label-small-weight));border-radius:var(--mat-badge-container-shape, var(--mat-sys-corner-full))}.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%}@media(forced-colors: active){.mat-badge-content{outline:solid 1px;border-radius:0}}.mat-badge-disabled .mat-badge-content{background-color:var(--mat-badge-disabled-state-background-color, color-mix(in srgb, var(--mat-sys-error) 38%, transparent));color:var(--mat-badge-disabled-state-text-color, var(--mat-sys-on-error))}.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, 6px);min-height:var(--mat-badge-small-size-container-size, 6px);line-height:var(--mat-badge-small-size-line-height, 6px);padding:var(--mat-badge-small-size-container-padding, 0);font-size:var(--mat-badge-small-size-text-size, 0);margin:var(--mat-badge-small-size-container-offset, -6px 0)}.mat-badge-small.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-small-size-container-overlap-offset, -6px)}.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, 16px);min-height:var(--mat-badge-container-size, 16px);line-height:var(--mat-badge-line-height, 16px);padding:var(--mat-badge-container-padding, 0 4px);font-size:var(--mat-badge-text-size, var(--mat-sys-label-small-size));margin:var(--mat-badge-container-offset, -12px 0)}.mat-badge-medium.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-container-overlap-offset, -12px)}.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, 16px);min-height:var(--mat-badge-large-size-container-size, 16px);line-height:var(--mat-badge-large-size-line-height, 16px);padding:var(--mat-badge-large-size-container-padding, 0 4px);font-size:var(--mat-badge-large-size-text-size, var(--mat-sys-label-small-size));margin:var(--mat-badge-large-size-container-offset, -12px 0)}.mat-badge-large.mat-badge-overlap .mat-badge-content{margin:var(--mat-badge-large-size-container-overlap-offset, -12px)}\n"] }]
  20. }] });
  21. /** Directive to display a text badge. */
  22. class MatBadge {
  23. _ngZone = inject(NgZone);
  24. _elementRef = inject(ElementRef);
  25. _ariaDescriber = inject(AriaDescriber);
  26. _renderer = inject(Renderer2);
  27. _animationMode = inject(ANIMATION_MODULE_TYPE, { optional: true });
  28. _idGenerator = inject(_IdGenerator);
  29. /**
  30. * Theme color of the badge. This API is supported in M2 themes only, it
  31. * has no effect in M3 themes. For color customization in M3, see https://material.angular.dev/components/badge/styling.
  32. *
  33. * For information on applying color variants in M3, see
  34. * https://material.angular.dev/guide/material-2-theming#optional-add-backwards-compatibility-styles-for-color-variants
  35. */
  36. get color() {
  37. return this._color;
  38. }
  39. set color(value) {
  40. this._setColor(value);
  41. this._color = value;
  42. }
  43. _color = 'primary';
  44. /** Whether the badge should overlap its contents or not */
  45. overlap = true;
  46. /** Whether the badge is disabled. */
  47. disabled;
  48. /**
  49. * Position the badge should reside.
  50. * Accepts any combination of 'above'|'below' and 'before'|'after'
  51. */
  52. position = 'above after';
  53. /** The content for the badge */
  54. get content() {
  55. return this._content;
  56. }
  57. set content(newContent) {
  58. this._updateRenderedContent(newContent);
  59. }
  60. _content;
  61. /** Message used to describe the decorated element via aria-describedby */
  62. get description() {
  63. return this._description;
  64. }
  65. set description(newDescription) {
  66. this._updateDescription(newDescription);
  67. }
  68. _description;
  69. /** Size of the badge. Can be 'small', 'medium', or 'large'. */
  70. size = 'medium';
  71. /** Whether the badge is hidden. */
  72. hidden;
  73. /** Visible badge element. */
  74. _badgeElement;
  75. /** Inline badge description. Used when the badge is applied to non-interactive host elements. */
  76. _inlineBadgeDescription;
  77. /** Whether the OnInit lifecycle hook has run yet */
  78. _isInitialized = false;
  79. /** InteractivityChecker to determine if the badge host is focusable. */
  80. _interactivityChecker = inject(InteractivityChecker);
  81. _document = inject(DOCUMENT);
  82. constructor() {
  83. const styleLoader = inject(_CdkPrivateStyleLoader);
  84. styleLoader.load(_MatBadgeStyleLoader);
  85. styleLoader.load(_VisuallyHiddenLoader);
  86. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  87. const nativeElement = this._elementRef.nativeElement;
  88. if (nativeElement.nodeType !== nativeElement.ELEMENT_NODE) {
  89. throw Error('matBadge must be attached to an element node.');
  90. }
  91. // Heads-up for developers to avoid putting matBadge on <mat-icon>
  92. // as it is aria-hidden by default docs mention this at:
  93. // https://material.angular.dev/components/badge/overview#accessibility
  94. if (nativeElement.tagName.toLowerCase() === 'mat-icon' &&
  95. nativeElement.getAttribute('aria-hidden') === 'true') {
  96. console.warn(`Detected a matBadge on an "aria-hidden" "<mat-icon>". ` +
  97. `Consider setting aria-hidden="false" in order to surface the information assistive technology.` +
  98. `\n${nativeElement.outerHTML}`);
  99. }
  100. }
  101. }
  102. /** Whether the badge is above the host or not */
  103. isAbove() {
  104. return this.position.indexOf('below') === -1;
  105. }
  106. /** Whether the badge is after the host or not */
  107. isAfter() {
  108. return this.position.indexOf('before') === -1;
  109. }
  110. /**
  111. * Gets the element into which the badge's content is being rendered. Undefined if the element
  112. * hasn't been created (e.g. if the badge doesn't have content).
  113. */
  114. getBadgeElement() {
  115. return this._badgeElement;
  116. }
  117. ngOnInit() {
  118. // We may have server-side rendered badge that we need to clear.
  119. // We need to do this in ngOnInit because the full content of the component
  120. // on which the badge is attached won't necessarily be in the DOM until this point.
  121. this._clearExistingBadges();
  122. if (this.content && !this._badgeElement) {
  123. this._badgeElement = this._createBadgeElement();
  124. this._updateRenderedContent(this.content);
  125. }
  126. this._isInitialized = true;
  127. }
  128. ngOnDestroy() {
  129. // ViewEngine only: when creating a badge through the Renderer, Angular remembers its index.
  130. // We have to destroy it ourselves, otherwise it'll be retained in memory.
  131. if (this._renderer.destroyNode) {
  132. this._renderer.destroyNode(this._badgeElement);
  133. this._inlineBadgeDescription?.remove();
  134. }
  135. this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this.description);
  136. }
  137. /** Gets whether the badge's host element is interactive. */
  138. _isHostInteractive() {
  139. // Ignore visibility since it requires an expensive style caluclation.
  140. return this._interactivityChecker.isFocusable(this._elementRef.nativeElement, {
  141. ignoreVisibility: true,
  142. });
  143. }
  144. /** Creates the badge element */
  145. _createBadgeElement() {
  146. const badgeElement = this._renderer.createElement('span');
  147. const activeClass = 'mat-badge-active';
  148. badgeElement.setAttribute('id', this._idGenerator.getId('mat-badge-content-'));
  149. // The badge is aria-hidden because we don't want it to appear in the page's navigation
  150. // flow. Instead, we use the badge to describe the decorated element with aria-describedby.
  151. badgeElement.setAttribute('aria-hidden', 'true');
  152. badgeElement.classList.add(BADGE_CONTENT_CLASS);
  153. if (this._animationMode === 'NoopAnimations') {
  154. badgeElement.classList.add('_mat-animation-noopable');
  155. }
  156. this._elementRef.nativeElement.appendChild(badgeElement);
  157. // animate in after insertion
  158. if (typeof requestAnimationFrame === 'function' && this._animationMode !== 'NoopAnimations') {
  159. this._ngZone.runOutsideAngular(() => {
  160. requestAnimationFrame(() => {
  161. badgeElement.classList.add(activeClass);
  162. });
  163. });
  164. }
  165. else {
  166. badgeElement.classList.add(activeClass);
  167. }
  168. return badgeElement;
  169. }
  170. /** Update the text content of the badge element in the DOM, creating the element if necessary. */
  171. _updateRenderedContent(newContent) {
  172. const newContentNormalized = `${newContent ?? ''}`.trim();
  173. // Don't create the badge element if the directive isn't initialized because we want to
  174. // append the badge element to the *end* of the host element's content for backwards
  175. // compatibility.
  176. if (this._isInitialized && newContentNormalized && !this._badgeElement) {
  177. this._badgeElement = this._createBadgeElement();
  178. }
  179. if (this._badgeElement) {
  180. this._badgeElement.textContent = newContentNormalized;
  181. }
  182. this._content = newContentNormalized;
  183. }
  184. /** Updates the host element's aria description via AriaDescriber. */
  185. _updateDescription(newDescription) {
  186. // Always start by removing the aria-describedby; we will add a new one if necessary.
  187. this._ariaDescriber.removeDescription(this._elementRef.nativeElement, this.description);
  188. // NOTE: We only check whether the host is interactive here, which happens during
  189. // when then badge content changes. It is possible that the host changes
  190. // interactivity status separate from one of these. However, watching the interactivity
  191. // status of the host would require a `MutationObserver`, which is likely more code + overhead
  192. // than it's worth; from usages inside Google, we see that the vats majority of badges either
  193. // never change interactivity, or also set `matBadgeHidden` based on the same condition.
  194. if (!newDescription || this._isHostInteractive()) {
  195. this._removeInlineDescription();
  196. }
  197. this._description = newDescription;
  198. // We don't add `aria-describedby` for non-interactive hosts elements because we
  199. // instead insert the description inline.
  200. if (this._isHostInteractive()) {
  201. this._ariaDescriber.describe(this._elementRef.nativeElement, newDescription);
  202. }
  203. else {
  204. this._updateInlineDescription();
  205. }
  206. }
  207. _updateInlineDescription() {
  208. // Create the inline description element if it doesn't exist
  209. if (!this._inlineBadgeDescription) {
  210. this._inlineBadgeDescription = this._document.createElement('span');
  211. this._inlineBadgeDescription.classList.add('cdk-visually-hidden');
  212. }
  213. this._inlineBadgeDescription.textContent = this.description;
  214. this._badgeElement?.appendChild(this._inlineBadgeDescription);
  215. }
  216. _removeInlineDescription() {
  217. this._inlineBadgeDescription?.remove();
  218. this._inlineBadgeDescription = undefined;
  219. }
  220. /** Adds css theme class given the color to the component host */
  221. _setColor(colorPalette) {
  222. const classList = this._elementRef.nativeElement.classList;
  223. classList.remove(`mat-badge-${this._color}`);
  224. if (colorPalette) {
  225. classList.add(`mat-badge-${colorPalette}`);
  226. }
  227. }
  228. /** Clears any existing badges that might be left over from server-side rendering. */
  229. _clearExistingBadges() {
  230. // Only check direct children of this host element in order to avoid deleting
  231. // any badges that might exist in descendant elements.
  232. const badges = this._elementRef.nativeElement.querySelectorAll(`:scope > .${BADGE_CONTENT_CLASS}`);
  233. for (const badgeElement of Array.from(badges)) {
  234. if (badgeElement !== this._badgeElement) {
  235. badgeElement.remove();
  236. }
  237. }
  238. }
  239. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatBadge, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  240. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.6", 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 });
  241. }
  242. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatBadge, decorators: [{
  243. type: Directive,
  244. args: [{
  245. selector: '[matBadge]',
  246. host: {
  247. 'class': 'mat-badge',
  248. '[class.mat-badge-overlap]': 'overlap',
  249. '[class.mat-badge-above]': 'isAbove()',
  250. '[class.mat-badge-below]': '!isAbove()',
  251. '[class.mat-badge-before]': '!isAfter()',
  252. '[class.mat-badge-after]': 'isAfter()',
  253. '[class.mat-badge-small]': 'size === "small"',
  254. '[class.mat-badge-medium]': 'size === "medium"',
  255. '[class.mat-badge-large]': 'size === "large"',
  256. '[class.mat-badge-hidden]': 'hidden || !content',
  257. '[class.mat-badge-disabled]': 'disabled',
  258. },
  259. }]
  260. }], ctorParameters: () => [], propDecorators: { color: [{
  261. type: Input,
  262. args: ['matBadgeColor']
  263. }], overlap: [{
  264. type: Input,
  265. args: [{ alias: 'matBadgeOverlap', transform: booleanAttribute }]
  266. }], disabled: [{
  267. type: Input,
  268. args: [{ alias: 'matBadgeDisabled', transform: booleanAttribute }]
  269. }], position: [{
  270. type: Input,
  271. args: ['matBadgePosition']
  272. }], content: [{
  273. type: Input,
  274. args: ['matBadge']
  275. }], description: [{
  276. type: Input,
  277. args: ['matBadgeDescription']
  278. }], size: [{
  279. type: Input,
  280. args: ['matBadgeSize']
  281. }], hidden: [{
  282. type: Input,
  283. args: [{ alias: 'matBadgeHidden', transform: booleanAttribute }]
  284. }] } });
  285. class MatBadgeModule {
  286. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatBadgeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  287. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: MatBadgeModule, imports: [A11yModule, MatCommonModule, MatBadge, _MatBadgeStyleLoader], exports: [MatBadge, MatCommonModule] });
  288. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatBadgeModule, imports: [A11yModule, MatCommonModule, MatCommonModule] });
  289. }
  290. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatBadgeModule, decorators: [{
  291. type: NgModule,
  292. args: [{
  293. // Note: we _shouldn't_ have to import `_MatBadgeStyleLoader`,
  294. // but it seems to be necessary for tests.
  295. imports: [A11yModule, MatCommonModule, MatBadge, _MatBadgeStyleLoader],
  296. exports: [MatBadge, MatCommonModule],
  297. }]
  298. }] });
  299. export { MatBadge, MatBadgeModule };
  300. //# sourceMappingURL=badge.mjs.map