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

feat: show deprecated related information in material/tooltip #1274

Merged
merged 1 commit into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/app/shared/doc-viewer/deprecated-tooltip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {Component} 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:
*
* <div class="docs-api-class-deprecated-marker"
* title="Will be removed in v21.0.0 or later">
* Deprecated
* </div>
*
* It uses `title` attribute to show information regarding
* deprecation and other information regarding deprecation
* 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
* `breaking-change` and `deprecated`.
*/
@Component({
selector: 'deprecated-field',
template: `<div class="deprecated-content"
[matTooltip]="message">
</div>`,
standalone: true,
imports: [MatTooltipModule],
})
export class DeprecatedFieldComponent {
/** Message regarding the deprecation */
message = '';
}
6 changes: 4 additions & 2 deletions src/app/shared/doc-viewer/doc-viewer-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 { }
47 changes: 46 additions & 1 deletion src/app/shared/doc-viewer/doc-viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {DocsAppTestingModule} from '../../testing/testing-module';
import {DocViewer} from './doc-viewer';
import {DocViewerModule} from './doc-viewer-module';
import {ExampleViewer} from '../example-viewer/example-viewer';

import {MatTooltip} from '@angular/material/tooltip';

describe('DocViewer', () => {
let http: HttpTestingController;
Expand Down Expand Up @@ -140,6 +140,36 @@ describe('DocViewer', () => {
expect(console.error).toHaveBeenCalledTimes(1);
});

it('should show tooltip for deprecated symbol', () => {
const fixture = TestBed.createComponent(DocViewerTestComponent);
fixture.componentInstance.documentUrl = `http://material.angular.io/deprecated.html`;
fixture.detectChanges();

const url = fixture.componentInstance.documentUrl;
http.expectOne(url).flush(FAKE_DOCS[url]);

const docViewer = fixture.debugElement.query(By.directive(DocViewer));

expect(docViewer).not.toBeNull();

// we have five deprecated symbols: class, constant, type alias, interface
// and properties.
expect(docViewer.children.length).toBe(5);

// it should have "Deprecated" as its inner text
const deprecatedSymbol = docViewer.children.shift()!;
expect(deprecatedSymbol.nativeElement.innerText).toBe('Deprecated');

// should contain the tooltip component
const tooltipElement = deprecatedSymbol.children.shift()!;
expect(tooltipElement.nativeElement).toBeTruthy();

// should show tooltip on hovering the element
tooltipElement.nativeNode.dispatchEvent(new MouseEvent('hover'));
fixture.detectChanges();
expect(deprecatedSymbol.query(By.directive(MatTooltip))).toBeTruthy();
});

// TODO(mmalerba): Add test that example-viewer is instantiated.
});

Expand Down Expand Up @@ -168,5 +198,20 @@ const FAKE_DOCS: {[key: string]: string} = {
'<div material-docs-example="demo-example"></div>',
'http://material.angular.io/whole-snippet-example.html':
'<div material-docs-example="whole-snippet-example" file="whole-snippet-example.ts"></div>',
'http://material.angular.io/deprecated.html':
`<div class="docs-api-class-deprecated-marker"
deprecated-message="deprecated class">Deprecated</div>

<div class="docs-api-constant-deprecated-marker"
deprecated-message="deprecated constant">Deprecated</div>

<div class="docs-api-interface-deprecated-marker"
deprecated-message="deprecated interface">Deprecated</div>

<div class="docs-api-type-alias-deprecated-marker"
deprecated-message="deprecated type alias">Deprecated</div>

<div class="docs-api-deprecated-marker"
deprecated-message="deprecated">Deprecated</div>`,
/* eslint-enable @typescript-eslint/naming-convention */
};
38 changes: 38 additions & 0 deletions src/app/shared/doc-viewer/doc-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -166,4 +170,38 @@ export class DocViewer implements OnDestroy {
this._clearLiveExamples();
this._documentFetchSubscription?.unsubscribe();
}

_createTooltipsForDeprecated() {
// all of the deprecated symbols end with `deprecated-marker`
// class name on their element.
// for example:
// <div class="docs-api-deprecated-marker">Deprecated</div>,
// these can vary for each deprecated symbols such for class, interface,
// type alias, constants or properties:
// .docs-api-class-interface-marker, docs-api-type-alias-deprecated-marker
// .docs-api-constant-deprecated-marker, .some-more
// so instead of manually writing each deprecated class, we just query
// elements that ends with `deprecated-marker` in their class name.
const deprecatedElements =
this._elementRef.nativeElement.querySelectorAll(`[class$=deprecated-marker]`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the $= needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its shorter for like:

querySelectorAll('.docs-api-deprecated-marker, .docs-api-class-deprecated-marker, .docs-api-class-interface-marker, .some-more')

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. How about adding a small comment illustrating the pattern + using -deprecated-marker as suffix test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be good now, lemme know if it needs rewording cause my english isn't so good.


[...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('deprecated-message');

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;
}

this._portalHosts.push(elementPortalOutlet);
});
}
}
15 changes: 13 additions & 2 deletions src/styles/_api.scss
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,19 @@
.docs-api-interface-deprecated-marker {
display: inline-block;
font-weight: bold;

&[title] {
position: relative;

// We want to set width and height according to our parent
// deprecated marker element because the component that presents
// the tooltip for depcreated message is empty by default and
// empty element can not be able to show up therefore the tooltip
// wont show either. This makes sure that our tooltip component
// is aligned with deprecated marker in position and size.
& .deprecated-content {
position: absolute;
width: 100%;
height: 100%;
top: 0;
border-bottom: 1px dotted grey;
cursor: help;
}
Expand Down