fix(layout): prevent layout scroll disappearing when overlay open

This commit is contained in:
Sergey Andrievskiy 2019-06-26 19:33:05 +03:00
parent 47979ac17b
commit 2144c7a3d9
4 changed files with 157 additions and 2 deletions

View file

@ -1,4 +1,8 @@
import { Component } from '@angular/core';
import { AfterViewInit, Component, Inject, PLATFORM_ID, ViewChild } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { NbLayoutComponent } from '@nebular/theme';
import { WindowModeBlockScrollService } from '../../services/window-mode-block-scroll.service';
@Component({
selector: 'ngx-one-column-layout',
@ -23,5 +27,18 @@ import { Component } from '@angular/core';
</nb-layout>
`,
})
export class OneColumnLayoutComponent {
export class OneColumnLayoutComponent implements AfterViewInit {
@ViewChild(NbLayoutComponent, { static: false }) layout: NbLayoutComponent;
constructor(
@Inject(PLATFORM_ID) private platformId,
private windowModeBlockScrollService: WindowModeBlockScrollService,
) {}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
this.windowModeBlockScrollService.register(this.layout);
}
}
}

View file

@ -0,0 +1,132 @@
import { Inject, Injectable, OnDestroy } from '@angular/core';
import { coerceCssPixelValue } from '@angular/cdk/coercion';
import {
NB_WINDOW,
NbLayoutComponent,
NbLayoutDimensions,
NbLayoutRulerService,
NbLayoutScrollService,
NbViewportRulerAdapter,
} from '@nebular/theme';
import { filter, map, take, takeUntil } from 'rxjs/operators';
import { fromEvent as observableFromEvent, merge, Subject } from 'rxjs';
@Injectable()
export class WindowModeBlockScrollService implements OnDestroy {
private destroy$ = new Subject<void>();
private blockEnabled = false;
private unblock$ = new Subject<void>();
private container: HTMLElement;
private content: HTMLElement;
private previousScrollPosition: { top: number, left: number };
private previousContainerStyles: { overflowY: string };
private previousContentStyles: { left: string, top: string, width: string, overflow: string, position: string };
constructor(
private scrollService: NbLayoutScrollService,
private viewportRuler: NbViewportRulerAdapter,
private layout: NbLayoutRulerService,
@Inject(NB_WINDOW) private window,
) {}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
this.unblock$.complete();
}
register(layout: NbLayoutComponent) {
this.container = layout.scrollableContainerRef.nativeElement;
this.content = this.container.children[0] as HTMLElement;
this.scrollService.onScrollableChange()
.pipe(
filter(() => layout.windowModeValue),
map((scrollable: boolean) => !scrollable),
takeUntil(this.destroy$),
)
.subscribe((shouldBlock: boolean) => {
if (shouldBlock) {
this.blockScroll();
} else {
this.unblockScroll();
}
});
}
blockScroll() {
if (!this.canBeBlocked()) {
return;
}
this.previousScrollPosition = this.viewportRuler.getViewportScrollPosition();
this.backupStyles();
this.container.style.overflowY = 'scroll';
this.content.style.overflow = 'hidden';
this.content.style.position = 'fixed';
this.updateContentSizeAndPosition();
observableFromEvent(this.window, 'resize')
.pipe(
takeUntil(merge(this.destroy$, this.unblock$).pipe(take(1))),
)
.subscribe(() => this.updateContentSizeAndPosition());
this.blockEnabled = true;
}
unblockScroll() {
if (this.blockEnabled) {
this.restoreStyles();
this.scrollService.scrollTo(this.previousScrollPosition.left, this.previousScrollPosition.top);
this.unblock$.next();
this.blockEnabled = false;
}
}
private canBeBlocked(): boolean {
if (this.blockEnabled) {
return false;
}
const { height: containerHeight } = this.viewportRuler.getViewportSize();
return this.content.scrollHeight > containerHeight;
}
private updateContentSizeAndPosition() {
const { top, left } = this.container.getBoundingClientRect();
this.content.style.left = coerceCssPixelValue(-this.previousScrollPosition.left + left);
this.content.style.top = coerceCssPixelValue(-this.previousScrollPosition.top + top);
this.layout.getDimensions()
.pipe(
map(({ clientWidth }: NbLayoutDimensions) => coerceCssPixelValue(clientWidth)),
take(1),
)
.subscribe((clientWidth: string) => this.content.style.width = clientWidth);
}
private backupStyles() {
this.previousContainerStyles = { overflowY: this.container.style.overflowY };
this.previousContentStyles = {
overflow: this.content.style.overflow,
position: this.content.style.position,
left: this.content.style.left,
top: this.content.style.top,
width: this.content.style.width,
};
}
private restoreStyles() {
this.container.style.overflowY = this.previousContainerStyles.overflowY;
this.content.style.overflow = this.previousContentStyles.overflow;
this.content.style.position = this.previousContentStyles.position;
this.content.style.left = this.previousContentStyles.left;
this.content.style.top = this.previousContentStyles.top;
this.content.style.width = this.previousContentStyles.width;
}
}

View file

@ -6,6 +6,7 @@
$nb-themes: nb-register-theme((
font-family-secondary: font-family-primary,
layout-padding-top: 2.25rem,
layout-window-mode-padding-top: 0,
menu-item-icon-margin: 0 0.5rem 0 0,
@ -28,6 +29,7 @@ $nb-themes: nb-register-theme((
$nb-themes: nb-register-theme((
font-family-secondary: font-family-primary,
layout-padding-top: 2.25rem,
layout-window-mode-padding-top: 0,
menu-item-icon-margin: 0 0.5rem 0 0,
@ -50,6 +52,7 @@ $nb-themes: nb-register-theme((
$nb-themes: nb-register-theme((
font-family-secondary: font-family-primary,
layout-padding-top: 2.25rem,
layout-window-mode-padding-top: 0,
menu-item-icon-margin: 0 0.5rem 0 0,
@ -72,6 +75,7 @@ $nb-themes: nb-register-theme((
$nb-themes: nb-register-theme((
font-family-secondary: font-family-primary,
layout-padding-top: 2.25rem,
layout-window-mode-padding-top: 0,
menu-item-icon-margin: 0 0.5rem 0 0,

View file

@ -34,6 +34,7 @@ import {
ThreeColumnsLayoutComponent,
TwoColumnsLayoutComponent,
} from './layouts';
import { WindowModeBlockScrollService } from './services/window-mode-block-scroll.service';
import { DEFAULT_THEME } from './styles/theme.default';
import { COSMIC_THEME } from './styles/theme.cosmic';
import { CORPORATE_THEME } from './styles/theme.corporate';
@ -86,6 +87,7 @@ export class ThemeModule {
},
[ DEFAULT_THEME, COSMIC_THEME, CORPORATE_THEME, DARK_THEME ],
).providers,
WindowModeBlockScrollService,
],
};
}