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:
parent
431a42a238
commit
6e05ae02a2
|
@ -2,8 +2,8 @@
|
||||||
"aio": {
|
"aio": {
|
||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime": 2712,
|
"runtime": 2768,
|
||||||
"main": 479729,
|
"main": 475857,
|
||||||
"polyfills": 38453,
|
"polyfills": 38453,
|
||||||
"prettify": 14913
|
"prettify": 14913
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
</mat-sidenav-container>
|
</mat-sidenav-container>
|
||||||
|
|
||||||
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
|
<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>
|
</div>
|
||||||
|
|
||||||
<footer class="no-print">
|
<footer class="no-print">
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { HttpClient } from '@angular/common/http';
|
||||||
import { MatProgressBar, MatSidenav } from '@angular/material';
|
import { MatProgressBar, MatSidenav } from '@angular/material';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { timer } from 'rxjs';
|
import { of, timer } from 'rxjs';
|
||||||
import { first, mapTo } from 'rxjs/operators';
|
import { first, mapTo } from 'rxjs/operators';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
@ -14,6 +14,7 @@ import { AppModule } from './app.module';
|
||||||
import { DocumentService } from 'app/documents/document.service';
|
import { DocumentService } from 'app/documents/document.service';
|
||||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||||
import { Deployment } from 'app/shared/deployment.service';
|
import { Deployment } from 'app/shared/deployment.service';
|
||||||
|
import { ElementsLoader } from 'app/custom-elements/elements-loader';
|
||||||
import { GaService } from 'app/shared/ga.service';
|
import { GaService } from 'app/shared/ga.service';
|
||||||
import { LocationService } from 'app/shared/location.service';
|
import { LocationService } from 'app/shared/location.service';
|
||||||
import { Logger } from 'app/shared/logger.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 { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||||
import { SearchService } from 'app/search/search.service';
|
import { SearchService } from 'app/search/search.service';
|
||||||
import { SelectComponent } from 'app/shared/select/select.component';
|
import { SelectComponent } from 'app/shared/select/select.component';
|
||||||
import { TocComponent } from 'app/layout/toc/toc.component';
|
|
||||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||||
|
|
||||||
const sideBySideBreakPoint = 992;
|
const sideBySideBreakPoint = 992;
|
||||||
|
@ -621,53 +621,53 @@ describe('AppComponent', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('aio-toc', () => {
|
describe('aio-toc', () => {
|
||||||
let tocDebugElement: DebugElement;
|
let tocContainer: HTMLElement|null;
|
||||||
let tocContainer: DebugElement|null;
|
let toc: HTMLElement|null;
|
||||||
|
|
||||||
const setHasFloatingToc = (hasFloatingToc: boolean) => {
|
const setHasFloatingToc = (hasFloatingToc: boolean) => {
|
||||||
component.hasFloatingToc = hasFloatingToc;
|
component.hasFloatingToc = hasFloatingToc;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
tocDebugElement = fixture.debugElement.query(By.directive(TocComponent));
|
tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container');
|
||||||
tocContainer = tocDebugElement && tocDebugElement.parent;
|
toc = tocContainer && tocContainer.querySelector('aio-toc');
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => setHasFloatingToc(true));
|
beforeEach(() => setHasFloatingToc(true));
|
||||||
|
|
||||||
|
|
||||||
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
|
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
|
||||||
expect(tocDebugElement).toBeTruthy();
|
|
||||||
expect(tocContainer).toBeTruthy();
|
expect(tocContainer).toBeTruthy();
|
||||||
|
expect(toc).toBeTruthy();
|
||||||
|
|
||||||
setHasFloatingToc(false);
|
setHasFloatingToc(false);
|
||||||
expect(tocDebugElement).toBeFalsy();
|
|
||||||
expect(tocContainer).toBeFalsy();
|
expect(tocContainer).toBeFalsy();
|
||||||
|
expect(toc).toBeFalsy();
|
||||||
|
|
||||||
setHasFloatingToc(true);
|
setHasFloatingToc(true);
|
||||||
expect(tocDebugElement).toBeTruthy();
|
|
||||||
expect(tocContainer).toBeTruthy();
|
expect(tocContainer).toBeTruthy();
|
||||||
|
expect(toc).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have a non-embedded `<aio-toc>` element', () => {
|
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`', () => {
|
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';
|
component.tocMaxHeight = '100';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(tocContainer!.styles['max-height']).toBe('100px');
|
expect(tocContainer!.style['max-height']).toBe('100px');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should restrain scrolling inside the ToC container', () => {
|
it('should restrain scrolling inside the ToC container', () => {
|
||||||
const restrainScrolling = spyOn(component, 'restrainScrolling');
|
const restrainScrolling = spyOn(component, 'restrainScrolling');
|
||||||
const evt = {};
|
const evt = new MouseEvent('mousewheel');
|
||||||
|
|
||||||
expect(restrainScrolling).not.toHaveBeenCalled();
|
expect(restrainScrolling).not.toHaveBeenCalled();
|
||||||
|
|
||||||
tocContainer!.triggerEventHandler('mousewheel', evt);
|
tocContainer!.dispatchEvent(evt);
|
||||||
expect(restrainScrolling).toHaveBeenCalledWith(evt);
|
expect(restrainScrolling).toHaveBeenCalledWith(evt);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1280,6 +1280,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
|
||||||
imports: [ AppModule ],
|
imports: [ AppModule ],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||||
|
{ provide: ElementsLoader, useClass: TestElementsLoader },
|
||||||
{ provide: GaService, useClass: TestGaService },
|
{ provide: GaService, useClass: TestGaService },
|
||||||
{ provide: HttpClient, useClass: TestHttpClient },
|
{ provide: HttpClient, useClass: TestHttpClient },
|
||||||
{ provide: LocationService, useFactory: () => mockLocationService },
|
{ 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 {
|
class TestGaService {
|
||||||
locationChanged = jasmine.createSpy('locationChanged');
|
locationChanged = jasmine.createSpy('locationChanged');
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import { ScrollService } from 'app/shared/scroll.service';
|
||||||
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||||
import { NotificationComponent } from 'app/layout/notification/notification.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 { TocService } from 'app/shared/toc.service';
|
||||||
import { CurrentDateToken, currentDateProvider } from 'app/shared/current-date';
|
import { CurrentDateToken, currentDateProvider } from 'app/shared/current-date';
|
||||||
import { WindowToken, windowProvider } from 'app/shared/window';
|
import { WindowToken, windowProvider } from 'app/shared/window';
|
||||||
|
@ -111,7 +110,6 @@ export const svgIconProviders = [
|
||||||
NavItemComponent,
|
NavItemComponent,
|
||||||
SearchBoxComponent,
|
SearchBoxComponent,
|
||||||
NotificationComponent,
|
NotificationComponent,
|
||||||
TocComponent,
|
|
||||||
TopMenuComponent,
|
TopMenuComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -133,7 +131,6 @@ export const svgIconProviders = [
|
||||||
{ provide: CurrentDateToken, useFactory: currentDateProvider },
|
{ provide: CurrentDateToken, useFactory: currentDateProvider },
|
||||||
{ provide: WindowToken, useFactory: windowProvider },
|
{ provide: WindowToken, useFactory: windowProvider },
|
||||||
],
|
],
|
||||||
entryComponents: [ TocComponent ],
|
|
||||||
bootstrap: [ AppComponent ]
|
bootstrap: [ AppComponent ]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
|
|
|
@ -24,6 +24,10 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
|
||||||
selector: 'aio-resource-list',
|
selector: 'aio-resource-list',
|
||||||
loadChildren: './resource/resource-list.module#ResourceListModule'
|
loadChildren: './resource/resource-list.module#ResourceListModule'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
selector: 'aio-toc',
|
||||||
|
loadChildren: './toc/toc.module#TocModule'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
selector: 'code-example',
|
selector: 'code-example',
|
||||||
loadChildren: './code/code-example.module#CodeExampleModule'
|
loadChildren: './code/code-example.module#CodeExampleModule'
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser';
|
||||||
import { asapScheduler as asap, BehaviorSubject } from 'rxjs';
|
import { asapScheduler as asap, BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { ScrollService } from 'app/shared/scroll.service';
|
import { ScrollService } from 'app/shared/scroll.service';
|
||||||
import { TocComponent } from './toc.component';
|
|
||||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||||
|
import { TocComponent } from './toc.component';
|
||||||
|
|
||||||
describe('TocComponent', () => {
|
describe('TocComponent', () => {
|
||||||
let tocComponentDe: DebugElement;
|
let tocComponentDe: DebugElement;
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue