diff --git a/src/app/shared/doc-viewer/deprecated-tooltip.ts b/src/app/shared/doc-viewer/deprecated-tooltip.ts new file mode 100644 index 00000000..489d6c46 --- /dev/null +++ b/src/app/shared/doc-viewer/deprecated-tooltip.ts @@ -0,0 +1,45 @@ +import {Component, Input} from '@angular/core'; +import {MatTooltipModule} from '@angular/material/tooltip'; + +/** + * This component is responsible for showing the + * deprecated fields throughout API from material repo, + * + * When deprecated docs content is generated like: + * + *
+ * Deprecated + *
+ * + * It uses `title` attribute to show information regarding + * depreciation and other information regarding depreciation + * isnt shown either. + * + * We are gonna use this component to show deprecation + * information using the `material/tooltip`, the information + * would contain when the field is being deprecated and what + * are the alternatives to it which both are extracted from + * `deprecated` and `breaking-change`. + */ +@Component({ + selector: 'deprecated-field', + template: `
+ {{ htmlContent }} +
`, + standalone: true, + imports: [MatTooltipModule], +}) +export class DeprecatedFieldComponent { + /** Message regarding the deprecation */ + @Input() message!: string; + /** Inner content for our element, it could be either + * `Deprecated` which is shown mostly in most of the case + * but we can not show class names with just `Deprecated` label + * so otherwise the value is going to be name of whatever + * is being deprecated, so we can show the deprecation alternative + * description on hover */ + @Input() htmlContent!: string; +} diff --git a/src/app/shared/doc-viewer/doc-viewer-module.ts b/src/app/shared/doc-viewer/doc-viewer-module.ts index 9c3f51c0..36f5a9ad 100644 --- a/src/app/shared/doc-viewer/doc-viewer-module.ts +++ b/src/app/shared/doc-viewer/doc-viewer-module.ts @@ -9,6 +9,7 @@ import {PortalModule} from '@angular/cdk/portal'; import {NgModule} from '@angular/core'; import {HeaderLink} from './header-link'; import {CodeSnippet} from '../example-viewer/code-snippet'; +import {DeprecatedFieldComponent} from './deprecated-tooltip'; // ExampleViewer is included in the DocViewerModule because they have a circular dependency. @@ -23,8 +24,9 @@ import {CodeSnippet} from '../example-viewer/code-snippet'; DocViewer, ExampleViewer, HeaderLink, - CodeSnippet + CodeSnippet, + DeprecatedFieldComponent ], - exports: [DocViewer, ExampleViewer, HeaderLink] + exports: [DocViewer, ExampleViewer, HeaderLink, DeprecatedFieldComponent] }) export class DocViewerModule { } diff --git a/src/app/shared/doc-viewer/doc-viewer.ts b/src/app/shared/doc-viewer/doc-viewer.ts index 8690fad8..3dafc082 100644 --- a/src/app/shared/doc-viewer/doc-viewer.ts +++ b/src/app/shared/doc-viewer/doc-viewer.ts @@ -21,6 +21,7 @@ import {Observable, Subscription} from 'rxjs'; import {shareReplay, take, tap} from 'rxjs/operators'; import {ExampleViewer} from '../example-viewer/example-viewer'; import {HeaderLink} from './header-link'; +import {DeprecatedFieldComponent} from './deprecated-tooltip'; @Injectable({providedIn: 'root'}) class DocFetcher { @@ -121,6 +122,9 @@ export class DocViewer implements OnDestroy { this._loadComponents('material-docs-example', ExampleViewer); this._loadComponents('header-link', HeaderLink); + // Create tooltips for the deprecated fields + this._createTooltipsForDeprecated(); + // Resolving and creating components dynamically in Angular happens synchronously, but since // we want to emit the output if the components are actually rendered completely, we wait // until the Angular zone becomes stable. @@ -166,4 +170,37 @@ export class DocViewer implements OnDestroy { this._clearLiveExamples(); this._documentFetchSubscription?.unsubscribe(); } + + _createTooltipsForDeprecated() { + // all of the deprecated markers end with `deprecated-marker` + // in their class name + const deprecatedElements = + this._elementRef.nativeElement.querySelectorAll(`[class$=deprecated-marker]`); + + [...deprecatedElements].forEach((element: Element) => { + // the deprecation message, it will include alternative to deprecated item + // and breaking change if there is one included. + const deprecationTitle = element.getAttribute('depreciation-title'); + // In case of a class being deprecated we can not just show `Deprecated` + // text, this attribute contains the text we need to show tooltip against. + const innerText = element.getAttribute('inner-text'); + + const elementPortalOutlet = new DomPortalOutlet( + element, this._componentFactoryResolver, this._appRef, this._injector); + + const tooltipPortal = new ComponentPortal(DeprecatedFieldComponent, this._viewContainerRef); + const tooltipOutlet = elementPortalOutlet.attach(tooltipPortal); + + + if (deprecationTitle) { + tooltipOutlet.instance.message = deprecationTitle; + } + + if (innerText) { + tooltipOutlet.instance.htmlContent = innerText; + } + + this._portalHosts.push(elementPortalOutlet); + }); + } } diff --git a/src/styles/_api.scss b/src/styles/_api.scss index 0fa8b7e7..a088ce3f 100644 --- a/src/styles/_api.scss +++ b/src/styles/_api.scss @@ -101,12 +101,13 @@ display: inline-block; font-weight: bold; - &[title] { + & .deprecated-content { border-bottom: 1px dotted grey; cursor: help; } } -.docs-api-deprecated-marker + .docs-api-property-name { +.docs-api-deprecated-marker + .docs-api-property-name, +.docs-api-class-deprecated-marker { text-decoration: line-through; }