badge.mjs 14 KB

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