fix(aio): show embedded ToC (#23944)

On narrow screens (where there is not enough room on the right to show
the floating ToC), an embedded ToC is shown (via an `<aio-toc embedded>`
element in the document). Since ToC was not a custom element, the
component was not instantiated for the embedded element.

This commit fixes it by making `aio-toc` a custom element and loading it
manually for the floating ToC (if necessary).

PR Close #23944
This commit is contained in:
George Kalpakas 2018-05-16 20:24:24 +03:00 committed by Matias Niemelä
parent 431a42a238
commit 6e05ae02a2
9 changed files with 45 additions and 21 deletions

View File

@ -2,8 +2,8 @@
"aio": {
"master": {
"uncompressed": {
"runtime": 2712,
"main": 479729,
"runtime": 2768,
"main": 475857,
"polyfills": 38453,
"prettify": 14913
}

View File

@ -58,7 +58,7 @@
</mat-sidenav-container>
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
<aio-toc></aio-toc>
<aio-lazy-ce selector="aio-toc"></aio-lazy-ce>
</div>
<footer class="no-print">

View File

@ -6,7 +6,7 @@ import { HttpClient } from '@angular/common/http';
import { MatProgressBar, MatSidenav } from '@angular/material';
import { By } from '@angular/platform-browser';
import { timer } from 'rxjs';
import { of, timer } from 'rxjs';
import { first, mapTo } from 'rxjs/operators';
import { AppComponent } from './app.component';
@ -14,6 +14,7 @@ import { AppModule } from './app.module';
import { DocumentService } from 'app/documents/document.service';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { Deployment } from 'app/shared/deployment.service';
import { ElementsLoader } from 'app/custom-elements/elements-loader';
import { GaService } from 'app/shared/ga.service';
import { LocationService } from 'app/shared/location.service';
import { Logger } from 'app/shared/logger.service';
@ -26,7 +27,6 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
import { SearchService } from 'app/search/search.service';
import { SelectComponent } from 'app/shared/select/select.component';
import { TocComponent } from 'app/layout/toc/toc.component';
import { TocItem, TocService } from 'app/shared/toc.service';
const sideBySideBreakPoint = 992;
@ -621,53 +621,53 @@ describe('AppComponent', () => {
});
describe('aio-toc', () => {
let tocDebugElement: DebugElement;
let tocContainer: DebugElement|null;
let tocContainer: HTMLElement|null;
let toc: HTMLElement|null;
const setHasFloatingToc = (hasFloatingToc: boolean) => {
component.hasFloatingToc = hasFloatingToc;
fixture.detectChanges();
tocDebugElement = fixture.debugElement.query(By.directive(TocComponent));
tocContainer = tocDebugElement && tocDebugElement.parent;
tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container');
toc = tocContainer && tocContainer.querySelector('aio-toc');
};
beforeEach(() => setHasFloatingToc(true));
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
expect(tocDebugElement).toBeTruthy();
expect(tocContainer).toBeTruthy();
expect(toc).toBeTruthy();
setHasFloatingToc(false);
expect(tocDebugElement).toBeFalsy();
expect(tocContainer).toBeFalsy();
expect(toc).toBeFalsy();
setHasFloatingToc(true);
expect(tocDebugElement).toBeTruthy();
expect(tocContainer).toBeTruthy();
expect(toc).toBeTruthy();
});
it('should have a non-embedded `<aio-toc>` element', () => {
expect(tocDebugElement.classes['embedded']).toBeFalsy();
expect(toc!.classList.contains('embedded')).toBe(false);
});
it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => {
expect(tocContainer!.styles['max-height']).toBeNull();
expect(tocContainer!.style['max-height']).toBe('');
component.tocMaxHeight = '100';
fixture.detectChanges();
expect(tocContainer!.styles['max-height']).toBe('100px');
expect(tocContainer!.style['max-height']).toBe('100px');
});
it('should restrain scrolling inside the ToC container', () => {
const restrainScrolling = spyOn(component, 'restrainScrolling');
const evt = {};
const evt = new MouseEvent('mousewheel');
expect(restrainScrolling).not.toHaveBeenCalled();
tocContainer!.triggerEventHandler('mousewheel', evt);
tocContainer!.dispatchEvent(evt);
expect(restrainScrolling).toHaveBeenCalledWith(evt);
});
});
@ -1280,6 +1280,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
imports: [ AppModule ],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: ElementsLoader, useClass: TestElementsLoader },
{ provide: GaService, useClass: TestGaService },
{ provide: HttpClient, useClass: TestHttpClient },
{ provide: LocationService, useFactory: () => mockLocationService },
@ -1294,6 +1295,14 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
});
}
class TestElementsLoader {
loadContainingCustomElements = jasmine.createSpy('loadContainingCustomElements')
.and.returnValue(of(undefined));
loadCustomElement = jasmine.createSpy('loadCustomElement')
.and.returnValue(Promise.resolve());
}
class TestGaService {
locationChanged = jasmine.createSpy('locationChanged');
}

View File

@ -32,7 +32,6 @@ import { ScrollService } from 'app/shared/scroll.service';
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { NotificationComponent } from 'app/layout/notification/notification.component';
import { TocComponent } from 'app/layout/toc/toc.component';
import { TocService } from 'app/shared/toc.service';
import { CurrentDateToken, currentDateProvider } from 'app/shared/current-date';
import { WindowToken, windowProvider } from 'app/shared/window';
@ -111,7 +110,6 @@ export const svgIconProviders = [
NavItemComponent,
SearchBoxComponent,
NotificationComponent,
TocComponent,
TopMenuComponent,
],
providers: [
@ -133,7 +131,6 @@ export const svgIconProviders = [
{ provide: CurrentDateToken, useFactory: currentDateProvider },
{ provide: WindowToken, useFactory: windowProvider },
],
entryComponents: [ TocComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }

View File

@ -24,6 +24,10 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
selector: 'aio-resource-list',
loadChildren: './resource/resource-list.module#ResourceListModule'
},
{
selector: 'aio-toc',
loadChildren: './toc/toc.module#TocModule'
},
{
selector: 'code-example',
loadChildren: './code/code-example.module#CodeExampleModule'

View File

@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser';
import { asapScheduler as asap, BehaviorSubject } from 'rxjs';
import { ScrollService } from 'app/shared/scroll.service';
import { TocComponent } from './toc.component';
import { TocItem, TocService } from 'app/shared/toc.service';
import { TocComponent } from './toc.component';
describe('TocComponent', () => {
let tocComponentDe: DebugElement;

View File

@ -0,0 +1,14 @@
import { NgModule, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
import { WithCustomElementComponent } from '../element-registry';
import { TocComponent } from './toc.component';
@NgModule({
imports: [ CommonModule, MatIconModule ],
declarations: [ TocComponent ],
entryComponents: [ TocComponent ],
})
export class TocModule implements WithCustomElementComponent {
customElementComponent: Type<any> = TocComponent;
}