mirror of
https://github.com/akveo/ngx-admin.git
synced 2025-12-17 07:50:12 +01:00
fix(layout): prevent layout scroll disappearing when overlay open
This commit is contained in:
parent
47979ac17b
commit
2144c7a3d9
4 changed files with 157 additions and 2 deletions
|
|
@ -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({
|
@Component({
|
||||||
selector: 'ngx-one-column-layout',
|
selector: 'ngx-one-column-layout',
|
||||||
|
|
@ -23,5 +27,18 @@ import { Component } from '@angular/core';
|
||||||
</nb-layout>
|
</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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
132
src/app/@theme/services/window-mode-block-scroll.service.ts
Normal file
132
src/app/@theme/services/window-mode-block-scroll.service.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
$nb-themes: nb-register-theme((
|
$nb-themes: nb-register-theme((
|
||||||
font-family-secondary: font-family-primary,
|
font-family-secondary: font-family-primary,
|
||||||
layout-padding-top: 2.25rem,
|
layout-padding-top: 2.25rem,
|
||||||
|
layout-window-mode-padding-top: 0,
|
||||||
|
|
||||||
menu-item-icon-margin: 0 0.5rem 0 0,
|
menu-item-icon-margin: 0 0.5rem 0 0,
|
||||||
|
|
||||||
|
|
@ -28,6 +29,7 @@ $nb-themes: nb-register-theme((
|
||||||
$nb-themes: nb-register-theme((
|
$nb-themes: nb-register-theme((
|
||||||
font-family-secondary: font-family-primary,
|
font-family-secondary: font-family-primary,
|
||||||
layout-padding-top: 2.25rem,
|
layout-padding-top: 2.25rem,
|
||||||
|
layout-window-mode-padding-top: 0,
|
||||||
|
|
||||||
menu-item-icon-margin: 0 0.5rem 0 0,
|
menu-item-icon-margin: 0 0.5rem 0 0,
|
||||||
|
|
||||||
|
|
@ -50,6 +52,7 @@ $nb-themes: nb-register-theme((
|
||||||
$nb-themes: nb-register-theme((
|
$nb-themes: nb-register-theme((
|
||||||
font-family-secondary: font-family-primary,
|
font-family-secondary: font-family-primary,
|
||||||
layout-padding-top: 2.25rem,
|
layout-padding-top: 2.25rem,
|
||||||
|
layout-window-mode-padding-top: 0,
|
||||||
|
|
||||||
menu-item-icon-margin: 0 0.5rem 0 0,
|
menu-item-icon-margin: 0 0.5rem 0 0,
|
||||||
|
|
||||||
|
|
@ -72,6 +75,7 @@ $nb-themes: nb-register-theme((
|
||||||
$nb-themes: nb-register-theme((
|
$nb-themes: nb-register-theme((
|
||||||
font-family-secondary: font-family-primary,
|
font-family-secondary: font-family-primary,
|
||||||
layout-padding-top: 2.25rem,
|
layout-padding-top: 2.25rem,
|
||||||
|
layout-window-mode-padding-top: 0,
|
||||||
|
|
||||||
menu-item-icon-margin: 0 0.5rem 0 0,
|
menu-item-icon-margin: 0 0.5rem 0 0,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import {
|
||||||
ThreeColumnsLayoutComponent,
|
ThreeColumnsLayoutComponent,
|
||||||
TwoColumnsLayoutComponent,
|
TwoColumnsLayoutComponent,
|
||||||
} from './layouts';
|
} from './layouts';
|
||||||
|
import { WindowModeBlockScrollService } from './services/window-mode-block-scroll.service';
|
||||||
import { DEFAULT_THEME } from './styles/theme.default';
|
import { DEFAULT_THEME } from './styles/theme.default';
|
||||||
import { COSMIC_THEME } from './styles/theme.cosmic';
|
import { COSMIC_THEME } from './styles/theme.cosmic';
|
||||||
import { CORPORATE_THEME } from './styles/theme.corporate';
|
import { CORPORATE_THEME } from './styles/theme.corporate';
|
||||||
|
|
@ -86,6 +87,7 @@ export class ThemeModule {
|
||||||
},
|
},
|
||||||
[ DEFAULT_THEME, COSMIC_THEME, CORPORATE_THEME, DARK_THEME ],
|
[ DEFAULT_THEME, COSMIC_THEME, CORPORATE_THEME, DARK_THEME ],
|
||||||
).providers,
|
).providers,
|
||||||
|
WindowModeBlockScrollService,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue