diff --git a/lib/core/i18n/en.json b/lib/core/i18n/en.json
index c06f967872..cb8264ad59 100644
--- a/lib/core/i18n/en.json
+++ b/lib/core/i18n/en.json
@@ -353,7 +353,8 @@
"ROTATE_LEFT": "Rotate left",
"ROTATE_RIGHT": "Rotate right",
"RESET": "Reset",
- "THUMBNAILS": "Document thumbnails"
+ "THUMBNAILS": "Document thumbnails",
+ "THUMBNAILS_PANLEL_CLOSE": "Close thumbnails panel"
},
"PAGE_LABEL": {
"SHOWING": "Showing",
diff --git a/lib/core/viewer/components/pdf-viewer-thumb.component.html b/lib/core/viewer/components/pdf-viewer-thumb.component.html
index b355eabce3..a61d175504 100644
--- a/lib/core/viewer/components/pdf-viewer-thumb.component.html
+++ b/lib/core/viewer/components/pdf-viewer-thumb.component.html
@@ -1,4 +1,5 @@
+ title="{{ 'ADF_VIEWER.SIDEBAR.THUMBNAILS.PAGE' | translate: { pageNum: page.id } }}"
+ [attr.aria-label]="'ADF_VIEWER.SIDEBAR.THUMBNAILS.PAGE' | translate: { pageNum: page.id }">
diff --git a/lib/core/viewer/components/pdf-viewer-thumb.component.spec.ts b/lib/core/viewer/components/pdf-viewer-thumb.component.spec.ts
index 4845f43089..8fdfc52284 100644
--- a/lib/core/viewer/components/pdf-viewer-thumb.component.spec.ts
+++ b/lib/core/viewer/components/pdf-viewer-thumb.component.spec.ts
@@ -66,4 +66,12 @@ describe('PdfThumbComponent', () => {
done();
});
});
+
+ it('should focus element', () => {
+ component.page = page;
+ fixture.detectChanges();
+ component.focus();
+
+ expect(fixture.debugElement.nativeElement.id).toBe(document.activeElement.id);
+ });
});
diff --git a/lib/core/viewer/components/pdf-viewer-thumb.component.ts b/lib/core/viewer/components/pdf-viewer-thumb.component.ts
index 903cc32717..bbf70f8753 100644
--- a/lib/core/viewer/components/pdf-viewer-thumb.component.ts
+++ b/lib/core/viewer/components/pdf-viewer-thumb.component.ts
@@ -15,28 +15,34 @@
* limitations under the License.
*/
-import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
+import { Component, ElementRef, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
+import { FocusableOption } from '@angular/cdk/a11y';
@Component({
selector: 'adf-pdf-thumb',
templateUrl: './pdf-viewer-thumb.component.html',
- encapsulation: ViewEncapsulation.None
+ encapsulation: ViewEncapsulation.None,
+ host: { tabindex: '0'}
})
-export class PdfThumbComponent implements OnInit {
+export class PdfThumbComponent implements OnInit, FocusableOption {
@Input()
page: any = null;
image$: Promise;
- constructor(private sanitizer: DomSanitizer) {
+ constructor(private sanitizer: DomSanitizer, private element: ElementRef) {
}
ngOnInit() {
this.image$ = this.page.getPage().then((page) => this.getThumb(page));
}
+ focus() {
+ this.element.nativeElement.focus();
+ }
+
private getThumb(page): Promise {
const viewport = page.getViewport({ scale: 1 });
diff --git a/lib/core/viewer/components/pdf-viewer-thumbnails.component.html b/lib/core/viewer/components/pdf-viewer-thumbnails.component.html
index c9e1baa0a9..9290dc87bd 100644
--- a/lib/core/viewer/components/pdf-viewer-thumbnails.component.html
+++ b/lib/core/viewer/components/pdf-viewer-thumbnails.component.html
@@ -4,6 +4,7 @@
[style.transform]="'translate(-50%, ' + translateY + 'px)'">
diff --git a/lib/core/viewer/components/pdf-viewer-thumbnails.component.scss b/lib/core/viewer/components/pdf-viewer-thumbnails.component.scss
index f64ec6a39a..b4d4b30c33 100644
--- a/lib/core/viewer/components/pdf-viewer-thumbnails.component.scss
+++ b/lib/core/viewer/components/pdf-viewer-thumbnails.component.scss
@@ -28,9 +28,5 @@
&__thumb:hover {
box-shadow: 0 0 5px 0 $black-87-opacity;
}
-
- &__thumb--selected:not(:hover) {
- box-shadow: 0 0 5px 0 $black-87-opacity;
- }
}
}
diff --git a/lib/core/viewer/components/pdf-viewer-thumbnails.component.spec.ts b/lib/core/viewer/components/pdf-viewer-thumbnails.component.spec.ts
index 71e05c051b..ddcb869fa1 100644
--- a/lib/core/viewer/components/pdf-viewer-thumbnails.component.spec.ts
+++ b/lib/core/viewer/components/pdf-viewer-thumbnails.component.spec.ts
@@ -21,6 +21,7 @@ import { PdfThumbListComponent } from './pdf-viewer-thumbnails.component';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
+import { DOWN_ARROW, UP_ARROW, ESCAPE } from '@angular/cdk/keycodes';
declare const pdfjsViewer: any;
@@ -41,7 +42,7 @@ describe('PdfThumbListComponent', () => {
set currentPageNumber(pageNum) {
this._currentPageNumber = pageNum;
/* cspell:disable-next-line */
- this.eventBus.dispatch('pagechange', { pageNumber: pageNum });
+ this.eventBus.dispatch('pagechanging', { pageNumber: pageNum });
},
get currentPageNumber() {
return this._currentPageNumber;
@@ -125,7 +126,7 @@ describe('PdfThumbListComponent', () => {
expect(renderedIds).toContain(12);
/* cspell:disable-next-line */
- viewerMock.eventBus.dispatch('pagechange', { pageNumber: 12 });
+ viewerMock.eventBus.dispatch('pagechanging', { pageNumber: 12 });
const newRenderedIds = component.renderItems.map((item) => item.id);
@@ -139,10 +140,17 @@ describe('PdfThumbListComponent', () => {
expect(component.renderItems[component.renderItems.length - 1].id).toBe(6);
expect(fixture.debugElement.nativeElement.scrollTop).toBe(0);
- viewerMock.currentPageNumber = 6;
+ component.pdfViewer.eventBus.dispatch('pagechanging', { pageNumber: 6 });
expect(component.scrollInto).not.toHaveBeenCalled();
- expect(fixture.debugElement.nativeElement.scrollTop).toBe(129);
+ expect(fixture.debugElement.nativeElement.scrollTop).toBe(0);
+ });
+
+ it('should set active current page on onPageChange event', () => {
+ fixture.detectChanges();
+ component.pdfViewer.eventBus.dispatch('pagechanging', { pageNumber: 6 });
+
+ expect(document.activeElement.id).toBe('6');
});
it('should return current viewed page as selected', () => {
@@ -161,4 +169,58 @@ describe('PdfThumbListComponent', () => {
expect(viewerMock.currentPageNumber).toBe(12);
});
+
+ describe('Keyboard events', () => {
+ it('should select next page in the list on DOWN_ARROW event', () => {
+ const event = new KeyboardEvent('keydown', {'keyCode': DOWN_ARROW} as KeyboardEventInit);
+ fixture.detectChanges();
+ component.goTo(1);
+ expect(document.activeElement.id).toBe('1');
+
+ fixture.debugElement.nativeElement.dispatchEvent(event);
+ expect(document.activeElement.id).toBe('2');
+ });
+
+ it('should select previous page in the list on UP_ARROW event', () => {
+ const event = new KeyboardEvent('keydown', {'keyCode': UP_ARROW} as KeyboardEventInit);
+ fixture.detectChanges();
+ component.goTo(2);
+ expect(document.activeElement.id).toBe('2');
+
+ fixture.debugElement.nativeElement.dispatchEvent(event);
+ expect(document.activeElement.id).toBe('1');
+ });
+
+ it('should not select previous page if it is the first page', () => {
+ const event = new KeyboardEvent('keydown', {'keyCode': UP_ARROW} as KeyboardEventInit);
+ fixture.detectChanges();
+ component.goTo(1);
+ expect(document.activeElement.id).toBe('1');
+
+ fixture.debugElement.nativeElement.dispatchEvent(event);
+ expect(document.activeElement.id).toBe('1');
+ });
+
+ it('should not select next item if it is the last page', () => {
+ const event = new KeyboardEvent('keydown', {'keyCode': DOWN_ARROW} as KeyboardEventInit);
+ fixture.detectChanges();
+ component.scrollInto(16);
+ fixture.detectChanges();
+
+ component.pdfViewer.eventBus.dispatch('pagechanging', { pageNumber: 16 });
+ expect(document.activeElement.id).toBe('16');
+
+ fixture.debugElement.nativeElement.dispatchEvent(event);
+ expect(document.activeElement.id).toBe('16');
+ });
+
+ it('should emit on ESCAPE event', () => {
+ const event = new KeyboardEvent('keydown', {'keyCode': ESCAPE} as KeyboardEventInit);
+ spyOn(component.close, 'emit');
+ fixture.detectChanges();
+
+ fixture.debugElement.nativeElement.dispatchEvent(event);
+ expect(component.close.emit).toHaveBeenCalled();
+ });
+ });
});
diff --git a/lib/core/viewer/components/pdf-viewer-thumbnails.component.ts b/lib/core/viewer/components/pdf-viewer-thumbnails.component.ts
index 51f3fa8719..4442d01862 100644
--- a/lib/core/viewer/components/pdf-viewer-thumbnails.component.ts
+++ b/lib/core/viewer/components/pdf-viewer-thumbnails.component.ts
@@ -17,19 +17,27 @@
import {
Component, Input, ContentChild, TemplateRef, HostListener, OnInit,
- AfterViewInit, ElementRef, OnDestroy, ViewEncapsulation
+ AfterViewInit, ElementRef, OnDestroy, ViewEncapsulation, EventEmitter, Output, Inject, ViewChildren, QueryList
} from '@angular/core';
+import { ESCAPE, UP_ARROW, DOWN_ARROW } from '@angular/cdk/keycodes';
+import { DOCUMENT } from '@angular/common';
+import { FocusKeyManager } from '@angular/cdk/a11y';
+import { PdfThumbComponent } from './pdf-viewer-thumb.component';
+import { delay } from 'rxjs/operators';
@Component({
selector: 'adf-pdf-thumbnails',
templateUrl: './pdf-viewer-thumbnails.component.html',
styleUrls: ['./pdf-viewer-thumbnails.component.scss'],
- host: { 'class': 'adf-pdf-thumbnails' },
+ host: { class: 'adf-pdf-thumbnails' },
encapsulation: ViewEncapsulation.None
})
export class PdfThumbListComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() pdfViewer: any;
+ @Output()
+ close: EventEmitter = new EventEmitter();
+
virtualHeight: number = 0;
translateY: number = 0;
renderItems = [];
@@ -39,56 +47,96 @@ export class PdfThumbListComponent implements OnInit, AfterViewInit, OnDestroy {
private items = [];
private margin: number = 15;
private itemHeight: number = 114 + this.margin;
+ private previouslyFocusedElement: HTMLElement | null = null;
+ private keyManager: FocusKeyManager;
@ContentChild(TemplateRef)
template: any;
+ @ViewChildren(PdfThumbComponent)
+ thumbsList: QueryList;
+
+ @HostListener('keydown', ['$event'])
+ onKeydown(event: KeyboardEvent): void {
+ const keyCode = event.keyCode;
+
+ if (keyCode === UP_ARROW && this.canSelectPreviousItem()) {
+ this.pdfViewer.currentPageNumber -= 1;
+ }
+
+ if (keyCode === DOWN_ARROW && this.canSelectNextItem()) {
+ this.pdfViewer.currentPageNumber += 1;
+ }
+
+ if (keyCode === ESCAPE) {
+ this.close.emit();
+ }
+
+ this.keyManager.setFocusOrigin('keyboard');
+ event.preventDefault();
+ }
+
@HostListener('window:resize')
onResize() {
this.calculateItems();
}
- constructor(private element: ElementRef) {
+ constructor(private element: ElementRef, @Inject(DOCUMENT) private document: any) {
this.calculateItems = this.calculateItems.bind(this);
this.onPageChange = this.onPageChange.bind(this);
}
ngOnInit() {
/* cspell:disable-next-line */
- this.pdfViewer.eventBus.on('pagechange', this.onPageChange);
+ this.pdfViewer.eventBus.on('pagechanging', this.onPageChange);
this.element.nativeElement.addEventListener('scroll', this.calculateItems, true);
this.setHeight(this.pdfViewer.currentPageNumber);
this.items = this.getPages();
this.calculateItems();
+ this.previouslyFocusedElement = this.document.activeElement as HTMLElement;
}
ngAfterViewInit() {
- setTimeout(() => this.scrollInto(this.pdfViewer.currentPageNumber), 0);
+ this.keyManager = new FocusKeyManager(this.thumbsList);
+
+ this.thumbsList.changes
+ .pipe(delay(0))
+ .subscribe(() => this.keyManager.setActiveItem(this.getPageIndex(this.pdfViewer.currentPageNumber)));
+
+ setTimeout(() => {
+ this.scrollInto(this.pdfViewer.currentPageNumber);
+ this.keyManager.setActiveItem(this.getPageIndex(this.pdfViewer.currentPageNumber));
+ }, 0);
}
ngOnDestroy() {
this.element.nativeElement.removeEventListener('scroll', this.calculateItems, true);
/* cspell:disable-next-line */
- this.pdfViewer.eventBus.off('pagechange', this.onPageChange);
+ this.pdfViewer.eventBus.on('pagechanging', this.onPageChange);
+
+ if (this.previouslyFocusedElement) {
+ this.previouslyFocusedElement.focus();
+ this.previouslyFocusedElement = null;
+ }
}
trackByFn(_: number, item: any): number {
return item.id;
}
- isSelected(pageNum: number) {
- return this.pdfViewer.currentPageNumber === pageNum;
+ isSelected(pageNumber: number) {
+ return this.pdfViewer.currentPageNumber === pageNumber;
}
- goTo(pageNum: number) {
- this.pdfViewer.currentPageNumber = pageNum;
+ goTo(pageNumber: number) {
+ this.pdfViewer.currentPageNumber = pageNumber;
}
- scrollInto(item: any) {
+ scrollInto(pageNumber: number) {
if (this.items.length) {
- const index: number = this.items.findIndex((element) => element.id === item);
+ const index: number = this.items.findIndex((element) => element.id === pageNumber);
if (index < 0 || index >= this.items.length) {
return;
@@ -164,5 +212,20 @@ export class PdfThumbListComponent implements OnInit, AfterViewInit, OnDestroy {
if (index >= this.renderItems.length - 1) {
this.element.nativeElement.scrollTop += this.itemHeight;
}
+
+ this.keyManager.setActiveItem(this.getPageIndex(event.pageNumber));
+ }
+
+ private getPageIndex(pageNumber: number): number {
+ const thumbsListArray = this.thumbsList.toArray();
+ return thumbsListArray.findIndex(el => el.page.id === pageNumber);
+ }
+
+ private canSelectNextItem(): boolean {
+ return this.pdfViewer.currentPageNumber !== this.pdfViewer.pagesCount;
+ }
+
+ private canSelectPreviousItem(): boolean {
+ return this.pdfViewer.currentPageNumber !== 1;
}
}
diff --git a/lib/core/viewer/components/pdf-viewer.component.html b/lib/core/viewer/components/pdf-viewer.component.html
index ebadf89fb3..a68ea05312 100644
--- a/lib/core/viewer/components/pdf-viewer.component.html
+++ b/lib/core/viewer/components/pdf-viewer.component.html
@@ -3,7 +3,12 @@
-
@@ -13,7 +18,7 @@
-
+
diff --git a/lib/core/viewer/components/pdf-viewer.component.ts b/lib/core/viewer/components/pdf-viewer.component.ts
index c730e60a32..1a4de3ea1d 100644
--- a/lib/core/viewer/components/pdf-viewer.component.ts
+++ b/lib/core/viewer/components/pdf-viewer.component.ts
@@ -100,6 +100,8 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
return Math.round(this.currentScale * 100) + '%';
}
+ private eventBus = new pdfjsViewer.EventBus();
+
constructor(
private dialog: MatDialog,
private renderingQueueServices: RenderingQueueServices,
@@ -200,15 +202,16 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
this.pdfViewer = new pdfjsViewer.PDFViewer({
container: container,
viewer: viewer,
- renderingQueue: this.renderingQueueServices
+ renderingQueue: this.renderingQueueServices,
+ eventBus: this.eventBus
});
// cspell: disable-next
- this.pdfViewer.eventBus.on('pagechanging', this.onPageChange);
+ this.eventBus.on('pagechanging', this.onPageChange);
// cspell: disable-next
- this.pdfViewer.eventBus.on('pagesloaded', this.onPagesLoaded);
+ this.eventBus.on('pagesloaded', this.onPagesLoaded);
// cspell: disable-next
- this.pdfViewer.eventBus.on('textlayerrendered', this.onPageRendered);
+ this.eventBus.on('textlayerrendered', this.onPageRendered);
this.renderingQueueServices.setViewer(this.pdfViewer);
this.pdfViewer.setDocument(pdfDocument);
@@ -219,11 +222,11 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
ngOnDestroy() {
if (this.pdfViewer) {
// cspell: disable-next
- this.pdfViewer.eventBus.off('pagechanging');
+ this.eventBus.off('pagechanging');
// cspell: disable-next
- this.pdfViewer.eventBus.off('pagesloaded');
+ this.eventBus.off('pagesloaded');
// cspell: disable-next
- this.pdfViewer.eventBus.off('textlayerrendered');
+ this.eventBus.off('textlayerrendered');
}
if (this.loadingTask) {