Skip to content
This repository was archived by the owner on Dec 18, 2024. It is now read-only.

Commit 7886880

Browse files
committed
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 angular/components#29839
1 parent 6a6db1c commit 7886880

File tree

5 files changed

+137
-5
lines changed

5 files changed

+137
-5
lines changed
Lines changed: 36 additions & 0 deletions
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+
}

src/app/shared/doc-viewer/doc-viewer-module.ts

Lines changed: 4 additions & 2 deletions
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 { }

src/app/shared/doc-viewer/doc-viewer.spec.ts

Lines changed: 46 additions & 1 deletion
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;
@@ -140,6 +140,36 @@ describe('DocViewer', () => {
140140
expect(console.error).toHaveBeenCalledTimes(1);
141141
});
142142

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

@@ -168,5 +198,20 @@ const FAKE_DOCS: {[key: string]: string} = {
168198
'<div material-docs-example="demo-example"></div>',
169199
'http://material.angular.io/whole-snippet-example.html':
170200
'<div material-docs-example="whole-snippet-example" file="whole-snippet-example.ts"></div>',
201+
'http://material.angular.io/deprecated.html':
202+
`<div class="docs-api-class-deprecated-marker"
203+
deprecated-message="deprecated class">Deprecated</div>
204+
205+
<div class="docs-api-constant-deprecated-marker"
206+
deprecated-message="deprecated constant">Deprecated</div>
207+
208+
<div class="docs-api-interface-deprecated-marker"
209+
deprecated-message="deprecated interface">Deprecated</div>
210+
211+
<div class="docs-api-type-alias-deprecated-marker"
212+
deprecated-message="deprecated type alias">Deprecated</div>
213+
214+
<div class="docs-api-deprecated-marker"
215+
deprecated-message="deprecated">Deprecated</div>`,
171216
/* eslint-enable @typescript-eslint/naming-convention */
172217
};

src/app/shared/doc-viewer/doc-viewer.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {Observable, Subscription} from 'rxjs';
2121
import {shareReplay, take, tap} from 'rxjs/operators';
2222
import {ExampleViewer} from '../example-viewer/example-viewer';
2323
import {HeaderLink} from './header-link';
24+
import {DeprecatedFieldComponent} from './deprecated-tooltip';
2425

2526
@Injectable({providedIn: 'root'})
2627
class DocFetcher {
@@ -121,6 +122,9 @@ export class DocViewer implements OnDestroy {
121122
this._loadComponents('material-docs-example', ExampleViewer);
122123
this._loadComponents('header-link', HeaderLink);
123124

125+
// Create tooltips for the deprecated fields
126+
this._createTooltipsForDeprecated();
127+
124128
// Resolving and creating components dynamically in Angular happens synchronously, but since
125129
// we want to emit the output if the components are actually rendered completely, we wait
126130
// until the Angular zone becomes stable.
@@ -166,4 +170,38 @@ export class DocViewer implements OnDestroy {
166170
this._clearLiveExamples();
167171
this._documentFetchSubscription?.unsubscribe();
168172
}
173+
174+
_createTooltipsForDeprecated() {
175+
// all of the deprecated symbols end with `deprecated-marker`
176+
// class name on their element.
177+
// for example:
178+
// <div class="docs-api-deprecated-marker">Deprecated</div>,
179+
// these can vary for each deprecated symbols such for class, interface,
180+
// type alias, constants or properties:
181+
// .docs-api-class-interface-marker, docs-api-type-alias-deprecated-marker
182+
// .docs-api-constant-deprecated-marker, .some-more
183+
// so instead of manually writing each deprecated class, we just query
184+
// elements that ends with `deprecated-marker` in their class name.
185+
const deprecatedElements =
186+
this._elementRef.nativeElement.querySelectorAll(`[class$=deprecated-marker]`);
187+
188+
[...deprecatedElements].forEach((element: Element) => {
189+
// the deprecation message, it will include alternative to deprecated item
190+
// and breaking change if there is one included.
191+
const deprecationTitle = element.getAttribute('deprecated-message');
192+
193+
const elementPortalOutlet = new DomPortalOutlet(
194+
element, this._componentFactoryResolver, this._appRef, this._injector);
195+
196+
const tooltipPortal = new ComponentPortal(DeprecatedFieldComponent, this._viewContainerRef);
197+
const tooltipOutlet = elementPortalOutlet.attach(tooltipPortal);
198+
199+
200+
if (deprecationTitle) {
201+
tooltipOutlet.instance.message = deprecationTitle;
202+
}
203+
204+
this._portalHosts.push(elementPortalOutlet);
205+
});
206+
}
169207
}

src/styles/_api.scss

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,19 @@
100100
.docs-api-interface-deprecated-marker {
101101
display: inline-block;
102102
font-weight: bold;
103-
104-
&[title] {
103+
position: relative;
104+
105+
// We want to set width and height according to our parent
106+
// deprecated marker element because the component that presents
107+
// the tooltip for depcreated message is empty by default and
108+
// empty element can not be able to show up therefore the tooltip
109+
// wont show either. This makes sure that our tooltip component
110+
// is aligned with deprecated marker in position and size.
111+
& .deprecated-content {
112+
position: absolute;
113+
width: 100%;
114+
height: 100%;
115+
top: 0;
105116
border-bottom: 1px dotted grey;
106117
cursor: help;
107118
}

0 commit comments

Comments
 (0)