[ADF-4579] Layout - RTL support (#4741)

* single contentAnimation state

* fine tune contentAnimation state based on direction and position

* tets

* fix mock component inputs

* use native direction attribute

* update docs

* pass direction to calculate sidenav layout

* update test

* fix unit test

* remove dialogs directionality workaround

* set document directionality service

* remove context menu direction workaround

* remove unneeded dependencies

* remove direction style workaround

* remove permission icon direction

* remove sidenav layout direction attribute

* update docs

* update sidenav layout direction

* test

* remove document type

* update test dependencies

* clear storage before test

* test

* fix dl gap

* try to fix Uncaught QuotaExceededError

* fix QuotaExceededError

* fix tests

* fix tests
This commit is contained in:
Cilibiu Bogdan
2019-05-29 18:17:13 +03:00
committed by Eugenio Romano
parent f3e90298e6
commit 9cf6f5519c
25 changed files with 386 additions and 221 deletions

View File

@@ -10,7 +10,7 @@
</mat-sidenav>
<div>
<div class="adf-rtl-container-alignment adf-container-full-width" [@contentAnimationLeft]="getContentAnimationStateLeft()" [@contentAnimationRight]="getContentAnimationStateRight()">
<div class="adf-container-full-width" [@contentAnimationLeft]="getContentAnimationState()">
<ng-content select="[app-layout-content]"></ng-content>
</div>
</div>

View File

@@ -9,14 +9,9 @@
overflow: hidden;
}
[dir='rtl'] .adf-rtl-container-alignment {
margin-left: 10px!important;
}
.adf-container-full-width {
width: inherit;
}
/* query for Microsoft IE 11*/
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
.adf-container-full-width {
@@ -24,6 +19,10 @@
}
}
mat-sidenav-content.mat-drawer-content.mat-sidenav-content {
margin: 0!important;
}
.adf-sidenav--hidden {
visibility: hidden !important;
width: 0 !important;

View File

@@ -0,0 +1,210 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LayoutContainerComponent } from './layout-container.component';
import { SimpleChange } from '@angular/core';
describe('LayoutContainerComponent', () => {
let layoutContainerComponent: LayoutContainerComponent;
beforeEach(() => {
layoutContainerComponent = new LayoutContainerComponent();
layoutContainerComponent.sidenavMin = 70;
layoutContainerComponent.sidenavMax = 200;
layoutContainerComponent.mediaQueryList = {
matches: false,
addListener: () => {},
removeListener: () => {}
};
});
describe('OnInit', () => {
describe('Sidenav expanded', () => {
beforeEach(() => {
layoutContainerComponent.expandedSidenav = true;
});
describe('Sidenav [start] position', () => {
beforeEach(() => {
layoutContainerComponent.position = 'start';
});
it('should set `margin-left` equal to sidenavMax when direction is `ltr`', () => {
layoutContainerComponent.direction = 'ltr';
layoutContainerComponent.ngOnInit();
expect(layoutContainerComponent.contentAnimationState).toEqual({
value: 'compact', params: { 'margin-left': layoutContainerComponent.sidenavMax}
});
});
it('should set `margin-right` equal to sidenavMax when direction is `rtl`', () => {
layoutContainerComponent.direction = 'rtl';
layoutContainerComponent.ngOnInit();
expect(layoutContainerComponent.contentAnimationState).toEqual({
value: 'compact', params: { 'margin-right': layoutContainerComponent.sidenavMax}
});
});
});
describe('Sidenav [end] position', () => {
beforeEach(() => {
layoutContainerComponent.position = 'end';
});
it('should set `margin-right` equal to sidenavMax when direction is `ltr`', () => {
layoutContainerComponent.direction = 'ltr';
layoutContainerComponent.ngOnInit();
expect(layoutContainerComponent.contentAnimationState).toEqual({
value: 'compact', params: { 'margin-right': layoutContainerComponent.sidenavMax}
});
});
it('should set `margin-left` equal to sidenavMax when direction is `rtl`', () => {
layoutContainerComponent.direction = 'rtl';
layoutContainerComponent.ngOnInit();
expect(layoutContainerComponent.contentAnimationState).toEqual({
value: 'compact', params: { 'margin-left': layoutContainerComponent.sidenavMax}
});
});
});
});
describe('Sidenav compact', () => {
beforeEach(() => {
layoutContainerComponent.expandedSidenav = false;
});
describe('Sidenav [start] position', () => {
beforeEach(() => {
layoutContainerComponent.position = 'start';
});
it('should set `margin-left` equal to sidenavMin when direction is `ltr`', () => {
layoutContainerComponent.direction = 'ltr';
layoutContainerComponent.ngOnInit();
expect(layoutContainerComponent.contentAnimationState).toEqual({
value: 'expanded', params: { 'margin-left': layoutContainerComponent.sidenavMin}
});
});
it('should set `margin-right` equal to sidenavMin when direction is `rtl`', () => {
layoutContainerComponent.direction = 'rtl';
layoutContainerComponent.ngOnInit();
expect(layoutContainerComponent.contentAnimationState).toEqual({
value: 'expanded', params: { 'margin-right': layoutContainerComponent.sidenavMin}
});
});
});
describe('Sidenav [end] position', () => {
beforeEach(() => {
layoutContainerComponent.position = 'end';
});
it('should set `margin-right` equal to sidenavMin when direction is `ltr`', () => {
layoutContainerComponent.direction = 'ltr';
layoutContainerComponent.ngOnInit();
expect(layoutContainerComponent.contentAnimationState).toEqual({
value: 'expanded', params: { 'margin-right': layoutContainerComponent.sidenavMin}
});
});
it('should set `margin-left` equal to sidenavMin when direction is `rtl`', () => {
layoutContainerComponent.direction = 'rtl';
layoutContainerComponent.ngOnInit();
expect(layoutContainerComponent.contentAnimationState).toEqual({
value: 'expanded', params: { 'margin-left': layoutContainerComponent.sidenavMin}
});
});
});
});
});
describe('OnChange direction', () => {
describe('Sidenav [start] position', () => {
beforeEach(() => {
layoutContainerComponent.position = 'start';
});
it('should set `margin-left` when current direction is `ltr`', () => {
layoutContainerComponent.direction = 'ltr';
layoutContainerComponent.ngOnChanges({ direction: new SimpleChange('', '', false) });
expect(layoutContainerComponent.contentAnimationState.params['margin-left']).not.toBeNull();
});
it('should set `margin-right` when current direction is `rtl`', () => {
layoutContainerComponent.direction = 'rtl';
layoutContainerComponent.ngOnChanges({ direction: new SimpleChange('', '', false) });
expect(layoutContainerComponent.contentAnimationState.params['margin-right']).not.toBeNull();
});
});
describe('Sidenav [end] position', () => {
beforeEach(() => {
layoutContainerComponent.position = 'end';
});
it('should set `margin-right` when current direction is `ltr`', () => {
layoutContainerComponent.direction = 'ltr';
layoutContainerComponent.ngOnChanges({ direction: new SimpleChange('', '', false) });
expect(layoutContainerComponent.contentAnimationState.params['margin-right']).not.toBeNull();
});
it('should set `margin-left` when current direction is `rtl`', () => {
layoutContainerComponent.direction = 'rtl';
layoutContainerComponent.ngOnChanges({ direction: new SimpleChange('', '', false) });
expect(layoutContainerComponent.contentAnimationState.params['margin-left']).not.toBeNull();
});
});
});
describe('toggleMenu()', () => {
it('should switch to sidenav to compact state', () => {
layoutContainerComponent.expandedSidenav = true;
layoutContainerComponent.ngOnInit();
layoutContainerComponent.toggleMenu();
expect(layoutContainerComponent.sidenavAnimationState).toEqual({
value: 'compact', params: { width: layoutContainerComponent.sidenavMin }
});
});
it('should switch to sidenav to expanded state', () => {
layoutContainerComponent.expandedSidenav = false;
layoutContainerComponent.ngOnInit();
layoutContainerComponent.toggleMenu();
expect(layoutContainerComponent.sidenavAnimationState).toEqual({
value: 'expanded', params: { width: layoutContainerComponent.sidenavMax }
});
});
});
});

View File

@@ -15,18 +15,19 @@
* limitations under the License.
*/
import { Component, Input, ViewChild, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
import { Component, Input, ViewChild, OnInit, OnDestroy, ViewEncapsulation, OnChanges } from '@angular/core';
import { MatSidenav } from '@angular/material';
import { sidenavAnimation, contentAnimationLeft, contentAnimationRight } from '../../helpers/animations';
import { sidenavAnimation, contentAnimation } from '../../helpers/animations';
import { Direction } from '@angular/cdk/bidi';
@Component({
selector: 'adf-layout-container',
templateUrl: './layout-container.component.html',
styleUrls: ['./layout-container.component.scss'],
encapsulation: ViewEncapsulation.None,
animations: [sidenavAnimation, contentAnimationLeft, contentAnimationRight]
animations: [sidenavAnimation, contentAnimation]
})
export class LayoutContainerComponent implements OnInit, OnDestroy {
export class LayoutContainerComponent implements OnInit, OnDestroy, OnChanges {
@Input() sidenavMin: number;
@Input() sidenavMax: number;
@@ -39,6 +40,9 @@ export class LayoutContainerComponent implements OnInit, OnDestroy {
/** The side that the drawer is attached to 'start' | 'end' page */
@Input() position = 'start';
/** Layout text orientation 'ltr' | 'rtl' */
@Input() direction: Direction = 'ltr';
@ViewChild(MatSidenav) sidenav: MatSidenav;
sidenavAnimationState: any;
@@ -56,9 +60,7 @@ export class LayoutContainerComponent implements OnInit, OnDestroy {
this.SIDENAV_STATES.EXPANDED = { value: 'expanded', params: { width: this.sidenavMax } };
this.SIDENAV_STATES.COMPACT = { value: 'compact', params: { width: this.sidenavMin } };
this.CONTENT_STATES.MOBILE = { value: 'expanded', params: { margin: 0 } };
this.CONTENT_STATES.EXPANDED = { value: 'expanded', params: { margin: this.sidenavMin } };
this.CONTENT_STATES.COMPACT = { value: 'compact', params: { margin: this.sidenavMax } };
this.CONTENT_STATES.MOBILE = { value: 'expanded' };
this.mediaQueryList.addListener(this.onMediaQueryChange);
@@ -67,10 +69,10 @@ export class LayoutContainerComponent implements OnInit, OnDestroy {
this.contentAnimationState = this.CONTENT_STATES.MOBILE;
} else if (this.expandedSidenav) {
this.sidenavAnimationState = this.SIDENAV_STATES.EXPANDED;
this.contentAnimationState = this.CONTENT_STATES.COMPACT;
this.contentAnimationState = this.toggledContentAnimation;
} else {
this.sidenavAnimationState = this.SIDENAV_STATES.COMPACT;
this.contentAnimationState = this.CONTENT_STATES.EXPANDED;
this.contentAnimationState = this.toggledContentAnimation;
}
}
@@ -78,6 +80,12 @@ export class LayoutContainerComponent implements OnInit, OnDestroy {
this.mediaQueryList.removeListener(this.onMediaQueryChange);
}
ngOnChanges(changes) {
if (changes && changes.direction) {
this.contentAnimationState = this.toggledContentAnimation;
}
}
toggleMenu(): void {
if (this.isMobileScreenSize) {
this.sidenav.toggle();
@@ -87,6 +95,14 @@ export class LayoutContainerComponent implements OnInit, OnDestroy {
}
}
get isMobileScreenSize(): boolean {
return this.mediaQueryList.matches;
}
getContentAnimationState() {
return this.contentAnimationState;
}
private get toggledSidenavAnimation() {
return this.sidenavAnimationState === this.SIDENAV_STATES.EXPANDED
? this.SIDENAV_STATES.COMPACT
@@ -99,35 +115,43 @@ export class LayoutContainerComponent implements OnInit, OnDestroy {
}
if (this.sidenavAnimationState === this.SIDENAV_STATES.EXPANDED) {
return this.CONTENT_STATES.COMPACT;
} else {
return this.CONTENT_STATES.EXPANDED;
}
}
if (this.position === 'start' && this.direction === 'ltr') {
return { value: 'compact', params: { 'margin-left': this.sidenavMax } };
}
get isMobileScreenSize(): boolean {
return this.mediaQueryList.matches;
if (this.position === 'start' && this.direction === 'rtl') {
return { value: 'compact', params: { 'margin-right': this.sidenavMax } };
}
if (this.position === 'end' && this.direction === 'ltr') {
return { value: 'compact', params: { 'margin-right': this.sidenavMax } };
}
if (this.position === 'end' && this.direction === 'rtl') {
return { value: 'compact', params: { 'margin-left': this.sidenavMax } };
}
} else {
if (this.position === 'start' && this.direction === 'ltr') {
return { value: 'expanded', params: { 'margin-left': this.sidenavMin } };
}
if (this.position === 'start' && this.direction === 'rtl') {
return { value: 'expanded', params: { 'margin-right': this.sidenavMin } };
}
if (this.position === 'end' && this.direction === 'ltr') {
return { value: 'expanded', params: { 'margin-right': this.sidenavMin } };
}
if (this.position === 'end' && this.direction === 'rtl') {
return { value: 'expanded', params: { 'margin-left': this.sidenavMin } };
}
}
}
private onMediaQueryChange() {
this.sidenavAnimationState = this.SIDENAV_STATES.EXPANDED;
this.contentAnimationState = this.toggledContentAnimation;
}
getContentAnimationStateLeft() {
if (this.position === 'start') {
return this.contentAnimationState;
} else {
return { value: 'compact', params: { width: this.sidenavMin } };
}
}
getContentAnimationStateRight() {
if (this.position === 'end') {
return this.contentAnimationState;
} else {
return { value: 'compact', params: { width: this.sidenavMin } };
}
}
}

View File

@@ -1,10 +1,11 @@
<div [dir]="direction" class="adf-sidenav-layout-full-space">
<div class="adf-sidenav-layout-full-space">
<ng-container *ngIf="!isHeaderInside">
<ng-container class="adf-sidenav-layout-outer-header"
*ngTemplateOutlet="headerTemplate; context:templateContext"></ng-container>
</ng-container>
<adf-layout-container #container
[direction]="dir"
[position]="position"
[sidenavMin]="sidenavMin"
[sidenavMax]="sidenavMax"

View File

@@ -27,7 +27,10 @@ import { MaterialModule } from '../../../material.module';
import { SidenavLayoutContentDirective } from '../../directives/sidenav-layout-content.directive';
import { SidenavLayoutHeaderDirective } from '../../directives/sidenav-layout-header.directive';
import { SidenavLayoutNavigationDirective } from '../../directives/sidenav-layout-navigation.directive';
import { UserPreferencesService } from '../../../services/user-preferences.service';
import { CommonModule } from '@angular/common';
import { Direction } from '@angular/cdk/bidi';
import { of } from 'rxjs';
@Component({
selector: 'adf-layout-container',
@@ -39,6 +42,7 @@ export class DummyLayoutContainerComponent {
@Input() sidenavMin: number;
@Input() sidenavMax: number;
@Input() position: string;
@Input() direction: Direction;
@Input() mediaQueryList: MediaQueryList;
@Input() hideSidenav: boolean;
@Input() expandedSidenav: boolean;
@@ -67,7 +71,8 @@ describe('SidenavLayoutComponent', () => {
SidenavLayoutNavigationDirective
],
providers: [
MediaMatcher
MediaMatcher,
{ provide: UserPreferencesService, useValue: { select: () => of()} }
]
});
}));

View File

@@ -29,10 +29,13 @@ import {
ViewEncapsulation
} from '@angular/core';
import { MediaMatcher } from '@angular/cdk/layout';
import { UserPreferencesService } from '../../../services/user-preferences.service';
import { SidenavLayoutContentDirective } from '../../directives/sidenav-layout-content.directive';
import { SidenavLayoutHeaderDirective } from '../../directives/sidenav-layout-header.directive';
import { SidenavLayoutNavigationDirective } from '../../directives/sidenav-layout-navigation.directive';
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { Direction } from '@angular/cdk/bidi';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'adf-sidenav-layout',
@@ -45,7 +48,7 @@ export class SidenavLayoutComponent implements OnInit, AfterViewInit, OnDestroy
static STEP_OVER = 600;
/** The direction of the layout. 'ltr' or 'rtl' */
@Input() direction = 'ltr';
dir = 'ltr';
/** The side that the drawer is attached to. Possible values are 'start' and 'end'. */
@Input() position = 'start';
@@ -86,7 +89,9 @@ export class SidenavLayoutComponent implements OnInit, AfterViewInit, OnDestroy
isMenuMinimized: () => this.isMenuMinimized
};
constructor(private mediaMatcher: MediaMatcher) {
private onDestroy$ = new Subject<boolean>();
constructor(private mediaMatcher: MediaMatcher, private userPreferencesService: UserPreferencesService ) {
this.onMediaQueryChange = this.onMediaQueryChange.bind(this);
}
@@ -101,6 +106,13 @@ export class SidenavLayoutComponent implements OnInit, AfterViewInit, OnDestroy
this.mediaQueryList = this.mediaMatcher.matchMedia(`(max-width: ${stepOver}px)`);
this.mediaQueryList.addListener(this.onMediaQueryChange);
this.userPreferencesService
.select('textOrientation')
.pipe(takeUntil(this.onDestroy$))
.subscribe((direction: Direction) => {
this.dir = direction;
});
}
ngAfterViewInit() {
@@ -109,6 +121,8 @@ export class SidenavLayoutComponent implements OnInit, AfterViewInit, OnDestroy
ngOnDestroy(): void {
this.mediaQueryList.removeListener(this.onMediaQueryChange);
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
toggleMenu() {