Skip to content

Commit 9804943

Browse files
naaajiicrisbeto
authored andcommittedDec 3, 2024
feat: show deprecated related information in material/tooltip
previously we weren't showing any other information other than `breaking-change` for deprecated fields, this commit adds a component that protrays information regarding deprecation in tooltips rather than `title` attribute closes #29839
1 parent 65f1ec6 commit 9804943

File tree

5 files changed

+137
-5
lines changed

5 files changed

+137
-5
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {Component} from '@angular/core';
2+
import {MatTooltipModule} from '@angular/material/tooltip';
3+
4+
/**
5+
* This component is responsible for showing the
6+
* deprecated fields throughout API from material repo,
7+
*
8+
* When deprecated docs content is generated like:
9+
*
10+
* <div class="docs-api-class-deprecated-marker"
11+
* title="Will be removed in v21.0.0 or later">
12+
* Deprecated
13+
* </div>
14+
*
15+
* It uses `title` attribute to show information regarding
16+
* deprecation and other information regarding deprecation
17+
* isnt shown either.
18+
*
19+
* We are gonna use this component to show deprecation
20+
* information using the `material/tooltip`, the information
21+
* would contain when the field is being deprecated and what
22+
* are the alternatives to it which both are extracted from
23+
* `breaking-change` and `deprecated`.
24+
*/
25+
@Component({
26+
selector: 'deprecated-field',
27+
template: `<div class="deprecated-content"
28+
[matTooltip]="message">
29+
</div>`,
30+
standalone: true,
31+
imports: [MatTooltipModule],
32+
})
33+
export class DeprecatedFieldComponent {
34+
/** Message regarding the deprecation */
35+
message = '';
36+
}

‎material.angular.io/src/app/shared/doc-viewer/doc-viewer-module.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {PortalModule} from '@angular/cdk/portal';
99
import {NgModule} from '@angular/core';
1010
import {HeaderLink} from './header-link';
1111
import {CodeSnippet} from '../example-viewer/code-snippet';
12+
import {DeprecatedFieldComponent} from './deprecated-tooltip';
1213

1314

1415
// ExampleViewer is included in the DocViewerModule because they have a circular dependency.
@@ -23,8 +24,9 @@ import {CodeSnippet} from '../example-viewer/code-snippet';
2324
DocViewer,
2425
ExampleViewer,
2526
HeaderLink,
26-
CodeSnippet
27+
CodeSnippet,
28+
DeprecatedFieldComponent
2729
],
28-
exports: [DocViewer, ExampleViewer, HeaderLink]
30+
exports: [DocViewer, ExampleViewer, HeaderLink, DeprecatedFieldComponent]
2931
})
3032
export class DocViewerModule { }

‎material.angular.io/src/app/shared/doc-viewer/doc-viewer.spec.ts

+46-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {DocsAppTestingModule} from '../../testing/testing-module';
66
import {DocViewer} from './doc-viewer';
77
import {DocViewerModule} from './doc-viewer-module';
88
import {ExampleViewer} from '../example-viewer/example-viewer';
9-
9+
import {MatTooltip} from '@angular/material/tooltip';
1010

1111
describe('DocViewer', () => {
1212
let http: HttpTestingController;
@@ -149,6 +149,36 @@ describe('DocViewer', () => {
149149
expect(console.error).toHaveBeenCalledTimes(1);
150150
});
151151

152+
it('should show tooltip for deprecated symbol', () => {
153+
const fixture = TestBed.createComponent(DocViewerTestComponent);
154+
fixture.componentInstance.documentUrl = `http://material.angular.io/deprecated.html`;
155+
fixture.detectChanges();
156+
157+
const url = fixture.componentInstance.documentUrl;
158+
http.expectOne(url).flush(FAKE_DOCS[url]);
159+
160+
const docViewer = fixture.debugElement.query(By.directive(DocViewer));
161+
162+
expect(docViewer).not.toBeNull();
163+
164+
// we have five deprecated symbols: class, constant, type alias, interface
165+
// and properties.
166+
expect(docViewer.children.length).toBe(5);
167+
168+
// it should have "Deprecated" as its inner text
169+
const deprecatedSymbol = docViewer.children.shift()!;
170+
expect(deprecatedSymbol.nativeElement.innerText).toBe('Deprecated');
171+
172+
// should contain the tooltip component
173+
const tooltipElement = deprecatedSymbol.children.shift()!;
174+
expect(tooltipElement.nativeElement).toBeTruthy();
175+
176+
// should show tooltip on hovering the element
177+
tooltipElement.nativeNode.dispatchEvent(new MouseEvent('hover'));
178+
fixture.detectChanges();
179+
expect(deprecatedSymbol.query(By.directive(MatTooltip))).toBeTruthy();
180+
});
181+
152182
// TODO(mmalerba): Add test that example-viewer is instantiated.
153183
});
154184

@@ -177,6 +207,21 @@ const FAKE_DOCS: {[key: string]: string} = {
177207
'<div material-docs-example="demo-example"></div>',
178208
'http://material.angular.io/whole-snippet-example.html':
179209
'<div material-docs-example="whole-snippet-example" file="whole-snippet-example.ts"></div>',
210+
'http://material.angular.io/deprecated.html':
211+
`<div class="docs-api-class-deprecated-marker"
212+
deprecated-message="deprecated class">Deprecated</div>
213+
214+
<div class="docs-api-constant-deprecated-marker"
215+
deprecated-message="deprecated constant">Deprecated</div>
216+
217+
<div class="docs-api-interface-deprecated-marker"
218+
deprecated-message="deprecated interface">Deprecated</div>
219+
220+
<div class="docs-api-type-alias-deprecated-marker"
221+
deprecated-message="deprecated type alias">Deprecated</div>
222+
223+
<div class="docs-api-deprecated-marker"
224+
deprecated-message="deprecated">Deprecated</div>`,
180225
/* eslint-enable @typescript-eslint/naming-convention */
181226
};
182227

‎material.angular.io/src/app/shared/doc-viewer/doc-viewer.ts

+38
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {Observable, Subscription} from 'rxjs';
2727
import {shareReplay, take, tap} from 'rxjs/operators';
2828
import {ExampleViewer} from '../example-viewer/example-viewer';
2929
import {HeaderLink} from './header-link';
30+
import {DeprecatedFieldComponent} from './deprecated-tooltip';
3031

3132
@Injectable({providedIn: 'root'})
3233
class DocFetcher {
@@ -144,6 +145,9 @@ export class DocViewer implements OnDestroy {
144145
this._loadComponents('material-docs-example', ExampleViewer);
145146
this._loadComponents('header-link', HeaderLink);
146147

148+
// Create tooltips for the deprecated fields
149+
this._createTooltipsForDeprecated();
150+
147151
// Resolving and creating components dynamically in Angular happens synchronously, but since
148152
// we want to emit the output if the components are actually rendered completely, we wait
149153
// until the Angular zone becomes stable.
@@ -189,4 +193,38 @@ export class DocViewer implements OnDestroy {
189193
this._clearLiveExamples();
190194
this._documentFetchSubscription?.unsubscribe();
191195
}
196+
197+
_createTooltipsForDeprecated() {
198+
// all of the deprecated symbols end with `deprecated-marker`
199+
// class name on their element.
200+
// for example:
201+
// <div class="docs-api-deprecated-marker">Deprecated</div>,
202+
// these can vary for each deprecated symbols such for class, interface,
203+
// type alias, constants or properties:
204+
// .docs-api-class-interface-marker, docs-api-type-alias-deprecated-marker
205+
// .docs-api-constant-deprecated-marker, .some-more
206+
// so instead of manually writing each deprecated class, we just query
207+
// elements that ends with `deprecated-marker` in their class name.
208+
const deprecatedElements =
209+
this._elementRef.nativeElement.querySelectorAll(`[class$=deprecated-marker]`);
210+
211+
[...deprecatedElements].forEach((element: Element) => {
212+
// the deprecation message, it will include alternative to deprecated item
213+
// and breaking change if there is one included.
214+
const deprecationTitle = element.getAttribute('deprecated-message');
215+
216+
const elementPortalOutlet = new DomPortalOutlet(
217+
element, this._componentFactoryResolver, this._appRef, this._injector);
218+
219+
const tooltipPortal = new ComponentPortal(DeprecatedFieldComponent, this._viewContainerRef);
220+
const tooltipOutlet = elementPortalOutlet.attach(tooltipPortal);
221+
222+
223+
if (deprecationTitle) {
224+
tooltipOutlet.instance.message = deprecationTitle;
225+
}
226+
227+
this._portalHosts.push(elementPortalOutlet);
228+
});
229+
}
192230
}

‎material.angular.io/src/styles/_api.scss

+13-2
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,19 @@
131131
.docs-api-interface-deprecated-marker {
132132
display: inline-block;
133133
font-weight: bold;
134-
135-
&[title] {
134+
position: relative;
135+
136+
// We want to set width and height according to our parent
137+
// deprecated marker element because the component that presents
138+
// the tooltip for depcreated message is empty by default and
139+
// empty element can not be able to show up therefore the tooltip
140+
// wont show either. This makes sure that our tooltip component
141+
// is aligned with deprecated marker in position and size.
142+
& .deprecated-content {
143+
position: absolute;
144+
width: 100%;
145+
height: 100%;
146+
top: 0;
136147
border-bottom: 1px dotted grey;
137148
cursor: help;
138149
}

0 commit comments

Comments
 (0)
Please sign in to comment.