diff --git a/src/components/app/menu-interface.ts b/src/components/app/menu-interface.ts index ed2d6bdde2..e011e05c10 100644 --- a/src/components/app/menu-interface.ts +++ b/src/components/app/menu-interface.ts @@ -1,5 +1,7 @@ import { Animation } from '../../animations/animation'; +export type Side = 'left' | 'right' | 'start' | 'end'; + export interface Menu { setOpen(shouldOpen: boolean, animated: boolean): Promise; open(): Promise; @@ -9,7 +11,7 @@ export interface Menu { swipeEnable(shouldEnable: boolean): Menu; isOpen: boolean; enabled: boolean; - side: string; + side: Side; id: string; isAnimating(): boolean; width(): number; diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index 70fa7f2f82..cea2d91c1b 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, EventEmitter, forwardRef, Input, NgZone, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; +import { OnInit, OnDestroy, ChangeDetectionStrategy, Component, ContentChild, ElementRef, EventEmitter, forwardRef, Input, NgZone, Output, Renderer, ViewChild, ViewEncapsulation } from '@angular/core'; import { App } from '../app/app'; import { Backdrop } from '../backdrop/backdrop'; @@ -9,7 +9,7 @@ import { GestureController, GESTURE_GO_BACK_SWIPE, BlockerDelegate } from '../.. import { isTrueProperty, assert } from '../../util/util'; import { Keyboard } from '../../platform/keyboard'; import { MenuContentGesture } from './menu-gestures'; -import { Menu as MenuInterface } from '../app/menu-interface'; +import {Menu as MenuInterface, Side} from '../app/menu-interface'; import { MenuController } from '../app/menu-controller'; import { MenuType } from './menu-types'; import { Nav } from '../nav/nav'; @@ -193,7 +193,7 @@ import { RootNode } from '../split-pane/split-pane'; encapsulation: ViewEncapsulation.None, providers: [{provide: RootNode, useExisting: forwardRef(() => Menu) }] }) -export class Menu implements RootNode, MenuInterface { +export class Menu implements RootNode, MenuInterface, OnInit, OnDestroy { private _cntEle: HTMLElement; private _gesture: MenuContentGesture; @@ -206,6 +206,7 @@ export class Menu implements RootNode, MenuInterface { private _events: UIEventManager; private _gestureBlocker: BlockerDelegate; private _isPane: boolean = false; + private _side: Side = 'start'; /** * @hidden @@ -237,11 +238,6 @@ export class Menu implements RootNode, MenuInterface { */ @Input() id: string; - /** - * @input {string} Which side of the view the menu should be placed. Default `"left"`. - */ - @Input() side: string; - /** * @input {string} The display type of the menu. Default varies based on the mode, * see the `menuType` in the [config](../../config/Config). Available options: @@ -262,6 +258,24 @@ export class Menu implements RootNode, MenuInterface { this.enable(isEnabled); } + /** + * @input {string} Which side of the view the menu should be placed. Default `"left"`. + */ + @Input() + get side() { + if (this._side === 'right' || (this._side === 'start' && this._plt.isRTL()) || (this._side === 'end' && !this._plt.isRTL())) { + return 'right'; + } + return 'left'; + } + + set side(val: Side) { + this._side = val; + // Update gesture edge + if (this._gesture) + this._gesture.setEdges(this.side); + } + /** * @input {boolean} If true, swiping the menu is enabled. Default `true`. */ @@ -339,11 +353,7 @@ export class Menu implements RootNode, MenuInterface { return console.error('Menu: must have a [content] element to listen for drag events on. Example:\n\n\n\n'); } - // normalize the "side" - if (this.side !== 'left' && this.side !== 'right') { - this.side = 'left'; - } - this.setElementAttribute('side', this.side); + this.setElementAttribute('side', this._side); // normalize the "type" if (!this.type) { diff --git a/src/components/menu/test/menu.spec.ts b/src/components/menu/test/menu.spec.ts index 5828903bf7..21a97c45c6 100644 --- a/src/components/menu/test/menu.spec.ts +++ b/src/components/menu/test/menu.spec.ts @@ -1,6 +1,6 @@ import { MenuController } from '../../app/menu-controller'; -import { mockMenu } from '../../../util/mock-providers'; - +import {mockMenu, mockPlatform} from '../../../util/mock-providers'; +import {Side} from '../../app/menu-interface'; describe('MenuController', () => { @@ -116,6 +116,30 @@ describe('MenuController', () => { expect(menu).toEqual(someMenu); }); + it('should get the only left menu on start ltr', () => { + let someMenu = mockMenu(); + someMenu.side = 'start'; + menuCtrl._register(someMenu); + + let menu = menuCtrl.get('left'); + expect(menu).toEqual(someMenu); + }); + + it('should get the only left menu on end rtl', () => { + let platform = mockPlatform(); + platform.setDir('rtl', true); + expect(platform.dir()).toEqual('rtl'); + + let someMenu = mockMenu(platform); + someMenu.side = 'end'; + menuCtrl._register(someMenu); + + expect(someMenu.side).toEqual('left'); + + let menu = menuCtrl.get('left'); + expect(menu).toEqual(someMenu); + }); + it('should get the enabled left menu', () => { let someMenu1 = mockMenu(); someMenu1.side = 'left'; @@ -155,6 +179,30 @@ describe('MenuController', () => { expect(menu).toEqual(someMenu); }); + it('should get the only right menu on end ltr', () => { + let someMenu = mockMenu(); + someMenu.side = 'end'; + menuCtrl._register(someMenu); + + let menu = menuCtrl.get('right'); + expect(menu).toEqual(someMenu); + }); + + it('should get the only right menu on start rtl', () => { + let platform = mockPlatform(); + platform.setDir('rtl', true); + expect(platform.dir()).toEqual('rtl'); + + let someMenu = mockMenu(platform); + someMenu.side = 'start'; + menuCtrl._register(someMenu); + + expect(someMenu.side).toEqual('right'); + + let menu = menuCtrl.get('right'); + expect(menu).toEqual(someMenu); + }); + it('should get the menu by left with id', () => { let someMenu = mockMenu(); someMenu.id = 'myMenu'; @@ -165,6 +213,40 @@ describe('MenuController', () => { expect(menu).toEqual(someMenu); }); + it('should switch menu side in runtime', () => { + let someMenu = mockMenu(); + menuCtrl._register(someMenu); + + ['left', 'right'].forEach((side: Side) => { + someMenu.side = side; + expect(someMenu.side).toEqual(side); + + let menu = menuCtrl.get(side); + expect(menu).toEqual(someMenu); + }); + }); + + it('should switch menu side in runtime by direction', () => { + let platform = mockPlatform(); + platform.setDir('ltr', true); + expect(platform.dir()).toEqual('ltr'); + + let someMenu = mockMenu(platform); + menuCtrl._register(someMenu); + + expect(someMenu.side).toEqual('left'); + + let menu = menuCtrl.get('left'); + expect(menu).toEqual(someMenu); + + platform.setDir('rtl', true); + + expect(someMenu.side).toEqual('right'); + + let menu2 = menuCtrl.get('right'); + expect(menu2).toEqual(someMenu); + }); + }); describe('enable()', () => { diff --git a/src/gestures/slide-edge-gesture.ts b/src/gestures/slide-edge-gesture.ts index 5737e46c7b..946842f6fe 100644 --- a/src/gestures/slide-edge-gesture.ts +++ b/src/gestures/slide-edge-gesture.ts @@ -18,10 +18,14 @@ export class SlideEdgeGesture extends SlideGesture { }); super(plt, element, opts); // Can check corners through use of eg 'left top' - this.edges = opts.edge.split(' '); + this.setEdges(opts.edge); this.maxEdgeStart = opts.maxEdgeStart; } + setEdges(edges: string) { + this.edges = edges.split(' '); + } + canStart(ev: any): boolean { let coord = pointerCoord(ev); this._d = this.getContainerDimensions(); diff --git a/src/navigation/swipe-back.ts b/src/navigation/swipe-back.ts index 8ffea77b37..14deff115b 100644 --- a/src/navigation/swipe-back.ts +++ b/src/navigation/swipe-back.ts @@ -30,6 +30,14 @@ export class SwipeBackGesture extends SlideEdgeGesture { disableScroll: true }) }); + + this.setSide(plt.dir()); + + Platform.dirChanged.subscribe(this.setSide.bind(this)); + } + + private setSide(dir: string) { + this.setEdges(dir === 'ltr' ? 'left' : 'right'); } canStart(ev: any): boolean { diff --git a/src/platform/platform.ts b/src/platform/platform.ts index dbde094e3f..9c6b7574c1 100644 --- a/src/platform/platform.ts +++ b/src/platform/platform.ts @@ -50,6 +50,8 @@ export class Platform { private _isPortrait: boolean = null; private _uiEvtOpts = false; + static dirChanged: EventEmitter = new EventEmitter(); + /** @hidden */ zone: NgZone; @@ -313,9 +315,12 @@ export class Platform { * direction needs to be dynamically changed per user/session. * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir) * @param {string} dir Examples: `rtl`, `ltr` + * @param {boolean} updateDocument */ setDir(dir: string, updateDocument: boolean) { this._dir = (dir || '').toLowerCase(); + Platform.dirChanged.emit(this._dir); + if (updateDocument !== false) { this._doc['documentElement'].setAttribute('dir', dir); } diff --git a/src/util/mock-providers.ts b/src/util/mock-providers.ts index 09de7ea9fc..62320cde56 100644 --- a/src/util/mock-providers.ts +++ b/src/util/mock-providers.ts @@ -543,13 +543,14 @@ export function mockTabs(app?: App): Tabs { return new Tabs(null, null, app, config, elementRef, platform, renderer, linker); } -export function mockMenu(): Menu { +export function mockMenu(platform: MockPlatform = null): Menu { let app = mockApp(); let gestureCtrl = new GestureController(app); let dom = mockDomController(); let elementRef = mockElementRef(); let renderer = mockRenderer(); - return new Menu(null, elementRef, null, null, renderer, null, null, gestureCtrl, dom, app); + let plt = platform === null ? mockPlatform() : platform; + return new Menu(null, elementRef, null, plt, renderer, null, null, gestureCtrl, dom, app); } export function mockDeepLinkConfig(links?: any[]): DeepLinkConfig {