common-ui/src/lib/listing/scroll-button/scroll-button.component.ts

70 lines
3.0 KiB
TypeScript

import { ChangeDetectionStrategy, Component, HostListener, Input, OnInit } from '@angular/core';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { delay, map, startWith } from 'rxjs/operators';
import { combineLatest, fromEvent, Observable } from 'rxjs';
import { Required, shareDistinctLast } from '../../utils';
const ButtonTypes = {
top: 'top',
bottom: 'bottom',
} as const;
type ButtonType = keyof typeof ButtonTypes;
@Component({
selector: 'iqser-scroll-button',
templateUrl: './scroll-button.component.html',
styleUrls: ['./scroll-button.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ScrollButtonComponent implements OnInit {
readonly buttonType = ButtonTypes;
@Input() @Required() scrollViewport!: CdkVirtualScrollViewport;
@Input() @Required() itemSize!: number;
showScrollUp$?: Observable<boolean>;
showScrollDown$?: Observable<boolean>;
ngOnInit(): void {
const scrollSize = () => this.scrollViewport.getDataLength() * this.itemSize;
const scrollIsNeeded = () => this.scrollViewport.getViewportSize() < scrollSize();
const reachedEnd = (type: ButtonType) => this.scrollViewport.measureScrollOffset(type) < 0.5;
const showScrollUp = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.top);
const showScrollDown = () => scrollIsNeeded() && !reachedEnd(ButtonTypes.bottom);
/** Force an initial emit, so combineLatest works */
const scrolled$ = this.scrollViewport.elementScrolled().pipe(startWith(null));
const resized$ = fromEvent(window, 'resize').pipe(startWith(null));
const rangeChange$ = this.scrollViewport.renderedRangeStream.pipe(startWith(null));
/** Delay so that we can wait for items to be rendered in viewport and get correct values */
const scroll$ = combineLatest([scrolled$, resized$, rangeChange$]).pipe(delay(0), shareDistinctLast());
this.showScrollUp$ = scroll$.pipe(map(showScrollUp));
this.showScrollDown$ = scroll$.pipe(map(showScrollDown));
}
scroll(type: ButtonType): void {
const viewportSize = (this.scrollViewport?.getViewportSize() - this.itemSize) * (type === ButtonTypes.top ? -1 : 1);
const scrollOffset = this.scrollViewport?.measureScrollOffset('top');
this.scrollViewport?.scrollToOffset(scrollOffset + viewportSize, 'smooth');
}
@HostListener('document:keyup', ['$event'])
spaceAndPageDownScroll(event: KeyboardEvent): void {
const target = event.target as EventTarget & { tagName: string };
if (['Space', 'PageDown'].includes(event.code) && target.tagName === 'BODY') {
this.scroll(ButtonTypes.bottom);
} else if (['PageUp'].includes(event.code) && target.tagName === 'BODY') {
this.scroll(ButtonTypes.top);
}
}
get helpModeKey (): string {
const screen = window.location.href.includes('/dossiers/') ? 'documents' : 'dossiers';
return `${screen}`;
}
}