Skip to content

Commit

Permalink
fix(layout): scroll block in with scroll mode (#1805)
Browse files Browse the repository at this point in the history
  • Loading branch information
yggg committed Jul 12, 2019
1 parent a0efb6b commit 8958fc9
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 54 deletions.
88 changes: 46 additions & 42 deletions src/framework/theme/components/layout/_layout.component.theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
@mixin window-mode($padding-top) {
padding-top: $padding-top;

nb-layout-header.fixed {
top: $padding-top;
}

nb-layout-header.fixed ~ .layout-container nb-sidebar .main-container-fixed {
height: calc(100vh - #{$padding-top} - #{nb-theme(header-height)});
top: calc(#{$padding-top} + #{nb-theme(header-height)});
}

nb-sidebar.fixed {
left: calc((100vw - #{nb-theme(layout-window-mode-max-width)}) / 2);
}
Expand Down Expand Up @@ -48,12 +57,7 @@
}
}

nb-layout.overlay-scroll-block .scrollable-container {
overflow: hidden;
}

.layout {
// TODO: check this prop name
min-width: nb-theme(layout-window-mode-min-width);
}

Expand Down Expand Up @@ -118,43 +122,6 @@
line-height: nb-theme(layout-text-line-height);
min-height: nb-theme(layout-min-height);

nb-layout-header {
color: nb-theme(header-text-color);
font-family: nb-theme(header-text-font-family);
font-size: nb-theme(header-text-font-size);
font-weight: nb-theme(header-text-font-weight);
line-height: nb-theme(header-text-line-height);

nav {
background: nb-theme(header-background-color);
color: nb-theme(header-text-color);
box-shadow: nb-theme(header-shadow);
height: nb-theme(header-height);
padding: nb-theme(header-padding);

a {
color: nb-theme(header-text-color);

@include hover-focus-active {
color: nb-theme(header-text-color);
}
}
}

& ~ .layout-container {
min-height: calc(#{nb-theme(layout-min-height)} - #{nb-theme(header-height)});
}

&.fixed ~ .layout-container {
padding-top: nb-theme(header-height);
min-height: nb-theme(layout-min-height);
}

&.fixed ~ .layout-container > nb-sidebar > .main-container {
height: calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)});
}
}

.layout-container {

nb-sidebar {
Expand Down Expand Up @@ -209,6 +176,43 @@
}
}

nb-layout-header {
color: nb-theme(header-text-color);
font-family: nb-theme(header-text-font-family);
font-size: nb-theme(header-text-font-size);
font-weight: nb-theme(header-text-font-weight);
line-height: nb-theme(header-text-line-height);

nav {
background: nb-theme(header-background-color);
color: nb-theme(header-text-color);
box-shadow: nb-theme(header-shadow);
height: nb-theme(header-height);
padding: nb-theme(header-padding);

a {
color: nb-theme(header-text-color);

@include hover-focus-active {
color: nb-theme(header-text-color);
}
}
}

& ~ .layout-container {
min-height: calc(#{nb-theme(layout-min-height)} - #{nb-theme(header-height)});
}

&.fixed ~ .layout-container {
padding-top: nb-theme(header-height);
min-height: nb-theme(layout-min-height);
}

&.fixed ~ .layout-container nb-sidebar .main-container {
height: calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)});
}
}

nb-layout.with-subheader {
nb-sidebar .main-container {
box-shadow: none; // so that we don't have a shadow over the header in this mode
Expand Down
136 changes: 136 additions & 0 deletions src/framework/theme/components/layout/layout.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Component } from '@angular/core';
import { TestBed, ComponentFixture, flush, fakeAsync } from '@angular/core/testing';
import {
NbLayoutComponent,
NbLayoutScrollService,
NbLayoutModule,
NbThemeModule,
NbLayoutDirectionService,
NbLayoutDirection,
} from '@nebular/theme';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';

@Component({
template: `
<nb-layout withScroll>
<nb-layout-column>
<div [style.height]="contentHeight" style="background: lightcoral;"></div>
</nb-layout-column>
</nb-layout>
`,
})
export class LayoutWithScrollModeComponent {
contentHeight: string = '200vh';
}

describe('NbLayoutComponent', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ RouterTestingModule.withRoutes([]), NbThemeModule.forRoot(), NbLayoutModule ],
declarations: [ LayoutWithScrollModeComponent ],
});
});

describe('withScroll mode - scroll block', () => {
let fixture: ComponentFixture<LayoutWithScrollModeComponent>;
let layoutComponent: NbLayoutComponent;
let scrollService: NbLayoutScrollService;

beforeEach(() => {
fixture = TestBed.createComponent(LayoutWithScrollModeComponent);
fixture.detectChanges();

layoutComponent = fixture.debugElement.query(By.directive(NbLayoutComponent)).componentInstance;
scrollService = TestBed.get(NbLayoutScrollService);
});

it('should hide overflow when scroll blocked', fakeAsync(() => {
scrollService.scrollable(false);
flush();
fixture.detectChanges();

expect(layoutComponent.scrollableContainerRef.nativeElement.style.overflow).toEqual('hidden');
}));


// Comment this specs until global (theme) styles included into unit test build.
// Currently scrollable container and layout has same width so no padding added and specs fail.
// it('should add right padding to layout container in LTR mode when blocking scroll', fakeAsync(() => {
// scrollService.scrollable(false);
// flush();
// fixture.detectChanges();
//
// expect(layoutComponent.layoutContainerRef.nativeElement.style.paddingRight).not.toEqual('');
// }));
//
// it('should add left padding to layout container in RTL mode when blocking scroll', fakeAsync(() => {
// const layoutDirectionService: NbLayoutDirectionService = TestBed.get(NbLayoutDirectionService);
// layoutDirectionService.setDirection(NbLayoutDirection.RTL);
// flush();
// fixture.detectChanges();
//
// scrollService.scrollable(false);
// flush();
// fixture.detectChanges();
//
// expect(layoutComponent.layoutContainerRef.nativeElement.style.paddingLeft).not.toEqual('');
// }));
// it('should not change layout padding if content is not scrollable', fakeAsync(() => {
// fixture.componentInstance.contentHeight = '1px';
// fixture.detectChanges();
//
// layoutComponent.layoutContainerRef.nativeElement.style.paddingLeft = '1px';
//
// scrollService.scrollable(false);
// flush();
// fixture.detectChanges();
//
// expect(layoutComponent.layoutContainerRef.nativeElement.style.paddingLeft).toEqual('1px');
// }));

it('should restore previous overflow value when enabling scroll', fakeAsync(() => {
layoutComponent.scrollableContainerRef.nativeElement.style.overflow = 'auto';

scrollService.scrollable(false);
flush();
fixture.detectChanges();
scrollService.scrollable(true);
flush();
fixture.detectChanges();

expect(layoutComponent.scrollableContainerRef.nativeElement.style.overflow).toEqual('auto');
}));

it('should restore previous padding left value when enabling scroll in LTR mode', fakeAsync(() => {
layoutComponent.layoutContainerRef.nativeElement.style.paddingLeft = '1px';

scrollService.scrollable(false);
flush();
fixture.detectChanges();
scrollService.scrollable(true);
flush();
fixture.detectChanges();

expect(layoutComponent.layoutContainerRef.nativeElement.style.paddingLeft).toEqual('1px');
}));

it('should restore previous padding right value when enabling scroll in RTL mode', fakeAsync(() => {
const layoutDirectionService: NbLayoutDirectionService = TestBed.get(NbLayoutDirectionService);
layoutDirectionService.setDirection(NbLayoutDirection.RTL);
flush();
fixture.detectChanges();
layoutComponent.layoutContainerRef.nativeElement.style.paddingRight = '1px';

scrollService.scrollable(false);
flush();
fixture.detectChanges();
scrollService.scrollable(true);
flush();
fixture.detectChanges();

expect(layoutComponent.layoutContainerRef.nativeElement.style.paddingRight).toEqual('1px');
}));
});
});
79 changes: 67 additions & 12 deletions src/framework/theme/components/layout/layout.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ import { NbOverlayContainerAdapter } from '../cdk/adapter/overlay-container-adap
styleUrls: ['./layout.component.scss'],
template: `
<div class="scrollable-container" #scrollableContainer (scroll)="onScroll($event)">
<div class="layout">
<div class="layout" #layoutContainer>
<ng-content select="nb-layout-header:not([subheader])"></ng-content>
<div class="layout-container">
<ng-content select="nb-sidebar"></ng-content>
Expand All @@ -147,13 +147,17 @@ import { NbOverlayContainerAdapter } from '../cdk/adapter/overlay-container-adap
})
export class NbLayoutComponent implements AfterViewInit, OnDestroy {

protected scrollBlockClass = 'nb-global-scrollblock';
protected isScrollBlocked = false;
protected scrollableContainerOverflowOldValue: string;
protected layoutPaddingOldValue: { left: string; right: string };

centerValue: boolean = false;
restoreScrollTopValue: boolean = true;

@HostBinding('class.window-mode') windowModeValue: boolean = false;
@HostBinding('class.with-scroll') withScrollValue: boolean = false;
@HostBinding('class.with-subheader') withSubheader: boolean = false;
@HostBinding('class.overlay-scroll-block') overlayScrollBlock: boolean = false;

/**
* Defines whether the layout columns will be centered after some width
Expand Down Expand Up @@ -207,7 +211,12 @@ export class NbLayoutComponent implements AfterViewInit, OnDestroy {

// TODO remove as of 5.0.0
@ViewChild('layoutTopDynamicArea', { read: ViewContainerRef, static: false }) veryTopRef: ViewContainerRef;
@ViewChild('scrollableContainer', { read: ElementRef, static: false }) scrollableContainerRef: ElementRef;

@ViewChild('scrollableContainer', { read: ElementRef, static: false })
scrollableContainerRef: ElementRef<HTMLElement>;

@ViewChild('layoutContainer', { read: ElementRef, static: false })
layoutContainerRef: ElementRef<HTMLElement>;

protected afterViewInit$ = new BehaviorSubject(null);

Expand Down Expand Up @@ -300,20 +309,15 @@ export class NbLayoutComponent implements AfterViewInit, OnDestroy {
filter(() => this.withScrollValue),
)
.subscribe((scrollable: boolean) => {
const root = this.document.documentElement;
const scrollBlockClass = 'nb-global-scrollblock';

this.overlayScrollBlock = !scrollable;

/**
* In case when Nebular Layout custom scroll `withScroll` mode is enabled
* we need to disable default CDK scroll blocker (@link NbBlockScrollStrategyAdapter) on HTML element
* so that it won't add additional positioning.
*/
if (!scrollable) {
this.renderer.addClass(root, scrollBlockClass);
if (scrollable) {
this.enableScroll();
} else {
this.renderer.removeClass(root, scrollBlockClass);
this.blockScroll();
}
});

Expand Down Expand Up @@ -443,6 +447,57 @@ export class NbLayoutComponent implements AfterViewInit, OnDestroy {
this.window.scrollTo(x, y);
}
}

// TODO: Extract into block scroll strategy
protected blockScroll() {
if (this.isScrollBlocked) {
return;
}

this.isScrollBlocked = true;

this.renderer.addClass(this.document.documentElement, this.scrollBlockClass);

const scrollableContainerElement = this.scrollableContainerRef.nativeElement;
const layoutElement = this.layoutContainerRef.nativeElement;

const layoutWithScrollWidth = layoutElement.clientWidth;
this.scrollableContainerOverflowOldValue = scrollableContainerElement.style.overflow;
scrollableContainerElement.style.overflow = 'hidden';
const layoutWithoutScrollWidth = layoutElement.clientWidth;
const scrollWidth = layoutWithoutScrollWidth - layoutWithScrollWidth;

if (!scrollWidth) {
return;
}

this.layoutPaddingOldValue = {
left: layoutElement.style.paddingLeft,
right: layoutElement.style.paddingRight,
};

if (this.layoutDirectionService.isLtr()) {
layoutElement.style.paddingRight = `${scrollWidth}px`;
} else {
layoutElement.style.paddingLeft = `${scrollWidth}px`;
}
}

private enableScroll() {
if (this.isScrollBlocked) {
this.isScrollBlocked = false;

this.renderer.removeClass(this.document.documentElement, this.scrollBlockClass);
this.scrollableContainerRef.nativeElement.style.overflow = this.scrollableContainerOverflowOldValue;

if (this.layoutPaddingOldValue) {
const layoutElement = this.layoutContainerRef.nativeElement;
layoutElement.style.paddingLeft = this.layoutPaddingOldValue.left;
layoutElement.style.paddingRight = this.layoutPaddingOldValue.right;
this.layoutPaddingOldValue = null;
}
}
}
}

/**
Expand Down Expand Up @@ -477,7 +532,7 @@ export class NbLayoutColumnComponent {
}

/**
* Make columnt first in the layout.
* Make column first in the layout.
* @param {boolean} val
*/
@Input()
Expand Down

0 comments on commit 8958fc9

Please sign in to comment.