diff --git a/src/components/app/menu-controller.ts b/src/components/app/menu-controller.ts index acb0712150..8e4cf663cd 100644 --- a/src/components/app/menu-controller.ts +++ b/src/components/app/menu-controller.ts @@ -248,7 +248,9 @@ export class MenuController { // there could be more than one menu on the same side // so first try to get the enabled one menu = this._menus.find(m => m.side === menuId && m.enabled); - if (menu) return menu; + if (menu) { + return menu; + } // didn't find a menu side that is enabled // so try to get the first menu side found diff --git a/src/components/app/menu-interface.ts b/src/components/app/menu-interface.ts index e011e05c10..45384961fe 100644 --- a/src/components/app/menu-interface.ts +++ b/src/components/app/menu-interface.ts @@ -1,6 +1,5 @@ import { Animation } from '../../animations/animation'; - -export type Side = 'left' | 'right' | 'start' | 'end'; +import { Side } from '../../util/util'; export interface Menu { setOpen(shouldOpen: boolean, animated: boolean): Promise; @@ -13,6 +12,7 @@ export interface Menu { enabled: boolean; side: Side; id: string; + isRightSide: boolean; isAnimating(): boolean; width(): number; getContentElement(): HTMLElement; diff --git a/src/components/item/item-options.ts b/src/components/item/item-options.ts index 6d7cb85a19..22cc117db7 100644 --- a/src/components/item/item-options.ts +++ b/src/components/item/item-options.ts @@ -1,7 +1,8 @@ import { Directive, ElementRef, EventEmitter, Input, Output, Renderer } from '@angular/core'; import { Platform } from '../../platform/platform'; -import { ITEM_SIDE_FLAG_LEFT, ITEM_SIDE_FLAG_RIGHT, ItemSliding } from './item-sliding'; +import { Side, isRightSide } from '../../util/util'; +import { ItemSliding } from './item-sliding'; /** * @name ItemOptions @@ -33,7 +34,7 @@ export class ItemOptions { * @input {string} The side the option button should be on. Defaults to `"right"`. * If you have multiple `ion-item-options`, a side must be provided for each. */ - @Input() side: string; + @Input() side: Side; /** * @output {event} Emitted when the item has been fully swiped. @@ -49,17 +50,8 @@ export class ItemOptions { /** * @hidden */ - getSides(): number { - switch (this.side) { - case 'left': - return ITEM_SIDE_FLAG_LEFT; - case 'right': - return ITEM_SIDE_FLAG_RIGHT; - case 'start': - return this._plt.isRTL() ? ITEM_SIDE_FLAG_RIGHT : ITEM_SIDE_FLAG_LEFT; - default: // end - return this._plt.isRTL() ? ITEM_SIDE_FLAG_LEFT : ITEM_SIDE_FLAG_RIGHT; - } + isRightSide(): boolean { + return isRightSide(this.side, this._plt.isRTL); } /** diff --git a/src/components/item/item-sliding.ts b/src/components/item/item-sliding.ts index 7ed21c1062..2d25ecb8e2 100644 --- a/src/components/item/item-sliding.ts +++ b/src/components/item/item-sliding.ts @@ -10,10 +10,10 @@ import { ItemOptions } from './item-options'; const SWIPE_MARGIN = 30; const ELASTIC_FACTOR = 0.55; -export const ITEM_SIDE_FLAG_NONE = 0; -export const ITEM_SIDE_FLAG_LEFT = 1 << 0; -export const ITEM_SIDE_FLAG_RIGHT = 1 << 1; -export const ITEM_SIDE_FLAG_BOTH = ITEM_SIDE_FLAG_LEFT | ITEM_SIDE_FLAG_RIGHT; +const ITEM_SIDE_FLAG_NONE = 0; +const ITEM_SIDE_FLAG_LEFT = 1 << 0; +const ITEM_SIDE_FLAG_RIGHT = 1 << 1; +const ITEM_SIDE_FLAG_BOTH = ITEM_SIDE_FLAG_LEFT | ITEM_SIDE_FLAG_RIGHT; const enum SlidingState { @@ -121,6 +121,7 @@ const enum SlidingState { encapsulation: ViewEncapsulation.None }) export class ItemSliding { + private _openAmount: number = 0; private _startX: number = 0; private _optsWidthRightSide: number = 0; @@ -166,7 +167,8 @@ export class ItemSliding { private _dom: DomController, private _renderer: Renderer, private _elementRef: ElementRef, - private _zone: NgZone) { + private _zone: NgZone + ) { list && list.containsSlidingItem(true); _elementRef.nativeElement.$ionComponent = this; this.setElementClass('item-wrapper', true); @@ -180,13 +182,13 @@ export class ItemSliding { this._leftOptions = this._rightOptions = null; for (var item of itemOptions.toArray()) { - var side = item.getSides(); - if (side === ITEM_SIDE_FLAG_LEFT) { - this._leftOptions = item; - } else { + if (item.isRightSide()) { this._rightOptions = item; + sides |= ITEM_SIDE_FLAG_RIGHT; + } else { + this._leftOptions = item; + sides |= ITEM_SIDE_FLAG_LEFT; } - sides |= item.getSides(); } this._optsDirty = true; this._sides = sides; @@ -203,7 +205,7 @@ export class ItemSliding { * @hidden */ getSlidingPercent(): number { - let openAmount = this._openAmount; + const openAmount = this._openAmount; if (openAmount > 0) { return openAmount / this._optsWidthRightSide; } else if (openAmount < 0) { @@ -272,9 +274,9 @@ export class ItemSliding { // Check if the drag didn't clear the buttons mid-point // and we aren't moving fast enough to swipe open - let isResetDirection = (this._openAmount > 0) === !(velocity < 0); - let isMovingFast = Math.abs(velocity) > 0.3; - let isOnCloseZone = Math.abs(this._openAmount) < Math.abs(restingPoint / 2); + const isResetDirection = (this._openAmount > 0) === !(velocity < 0); + const isMovingFast = Math.abs(velocity) > 0.3; + const isOnCloseZone = Math.abs(this._openAmount) < Math.abs(restingPoint / 2); if (swipeShouldReset(isResetDirection, isMovingFast, isOnCloseZone)) { restingPoint = 0; } @@ -330,14 +332,14 @@ export class ItemSliding { } else { if (openAmount > 0) { - let state = (openAmount >= (this._optsWidthRightSide + SWIPE_MARGIN)) + var state = (openAmount >= (this._optsWidthRightSide + SWIPE_MARGIN)) ? SlidingState.Right | SlidingState.SwipeRight : SlidingState.Right; this._setState(state); } else if (openAmount < 0) { - let state = (openAmount <= (-this._optsWidthLeftSide - SWIPE_MARGIN)) + var state = (openAmount <= (-this._optsWidthLeftSide - SWIPE_MARGIN)) ? SlidingState.Left | SlidingState.SwipeLeft : SlidingState.Left; diff --git a/src/components/menu/menu-gestures.ts b/src/components/menu/menu-gestures.ts index 607f1b1d07..65e738e1c4 100644 --- a/src/components/menu/menu-gestures.ts +++ b/src/components/menu/menu-gestures.ts @@ -33,7 +33,7 @@ export class MenuContentGesture extends SlideEdgeGesture { } canStart(ev: any): boolean { - let menu = this.menu; + const menu = this.menu; if (!menu.canSwipe()) { return false; } @@ -57,21 +57,21 @@ export class MenuContentGesture extends SlideEdgeGesture { } onSlide(slide: SlideData, ev: any) { - let z = (this.menu.side === 'right' ? slide.min : slide.max); - let stepValue = (slide.distance / z); + const z = (this.menu.isRightSide ? slide.min : slide.max); + const stepValue = (slide.distance / z); this.menu._swipeProgress(stepValue); } onSlideEnd(slide: SlideData, ev: any) { - let z = (this.menu.side === 'right' ? slide.min : slide.max); - let currentStepValue = (slide.distance / z); - let velocity = slide.velocity; + let z = (this.menu.isRightSide ? slide.min : slide.max); + const currentStepValue = (slide.distance / z); + const velocity = slide.velocity; z = Math.abs(z * 0.5); - let shouldCompleteRight = (velocity >= 0) + const shouldCompleteRight = (velocity >= 0) && (velocity > 0.2 || slide.delta > z); - let shouldCompleteLeft = (velocity <= 0) + const shouldCompleteLeft = (velocity <= 0) && (velocity < -0.2 || slide.delta < -z); console.debug('menu gesture, onSlideEnd', this.menu.side, @@ -88,24 +88,26 @@ export class MenuContentGesture extends SlideEdgeGesture { } getElementStartPos(slide: SlideData, ev: any) { - if (this.menu.side === 'right') { - return this.menu.isOpen ? slide.min : slide.max; + const menu = this.menu; + if (menu.isRightSide) { + return menu.isOpen ? slide.min : slide.max; } // left menu - return this.menu.isOpen ? slide.max : slide.min; + return menu.isOpen ? slide.max : slide.min; } - getSlideBoundaries(): {min: number, max: number} { - if (this.menu.side === 'right') { + getSlideBoundaries(): { min: number, max: number } { + const menu = this.menu; + if (menu.isRightSide) { return { - min: -this.menu.width(), + min: -menu.width(), max: 0 }; } // left menu return { min: 0, - max: this.menu.width() + max: menu.width() }; } } diff --git a/src/components/menu/menu-types.ts b/src/components/menu/menu-types.ts index a3c2d0850f..5543d3c3b2 100644 --- a/src/components/menu/menu-types.ts +++ b/src/components/menu/menu-types.ts @@ -12,6 +12,7 @@ import { Platform } from '../../platform/platform'; * and registers itself with Menu. */ export class MenuType implements IMenuType { + ani: Animation; isOpening: boolean; @@ -67,7 +68,7 @@ export class MenuType implements IMenuType { } destroy() { - this.ani && this.ani.destroy(); + this.ani.destroy(); this.ani = null; } @@ -84,8 +85,8 @@ class MenuRevealType extends MenuType { constructor(menu: Menu, plt: Platform) { super(plt); - let openedX = (menu.width() * (menu.side === 'right' ? -1 : 1)) + 'px'; - let contentOpen = new Animation(plt, menu.getContentElement()); + const openedX = (menu.width() * (menu.isRightSide ? -1 : 1)) + 'px'; + const contentOpen = new Animation(plt, menu.getContentElement()); contentOpen.fromTo('translateX', '0px', openedX); this.ani.add(contentOpen); } @@ -104,24 +105,24 @@ class MenuPushType extends MenuType { super(plt); let contentOpenedX: string, menuClosedX: string, menuOpenedX: string; - - if (menu.side === 'right') { + const width = menu.width(); + if (menu.isRightSide) { // right side - contentOpenedX = -menu.width() + 'px'; - menuClosedX = menu.width() + 'px'; + contentOpenedX = -width + 'px'; + menuClosedX = width + 'px'; menuOpenedX = '0px'; } else { - contentOpenedX = menu.width() + 'px'; + contentOpenedX = width + 'px'; menuOpenedX = '0px'; - menuClosedX = -menu.width() + 'px'; + menuClosedX = -width + 'px'; } - let menuAni = new Animation(plt, menu.getMenuElement()); + const menuAni = new Animation(plt, menu.getMenuElement()); menuAni.fromTo('translateX', menuClosedX, menuOpenedX); this.ani.add(menuAni); - let contentApi = new Animation(plt, menu.getContentElement()); + const contentApi = new Animation(plt, menu.getContentElement()); contentApi.fromTo('translateX', '0px', contentOpenedX); this.ani.add(contentApi); } @@ -140,22 +141,23 @@ class MenuOverlayType extends MenuType { super(plt); let closedX: string, openedX: string; - if (menu.side === 'right') { + const width = menu.width(); + if (menu.isRightSide) { // right side - closedX = 8 + menu.width() + 'px'; + closedX = 8 + width + 'px'; openedX = '0px'; } else { // left side - closedX = -(8 + menu.width()) + 'px'; + closedX = -(8 + width) + 'px'; openedX = '0px'; } - let menuAni = new Animation(plt, menu.getMenuElement()); + const menuAni = new Animation(plt, menu.getMenuElement()); menuAni.fromTo('translateX', closedX, openedX); this.ani.add(menuAni); - let backdropApi = new Animation(plt, menu.getBackdropElement()); + const backdropApi = new Animation(plt, menu.getBackdropElement()); backdropApi.fromTo('opacity', 0.01, 0.35); this.ani.add(backdropApi); } diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index cea2d91c1b..da838dfbd0 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -6,10 +6,10 @@ import { Config } from '../../config/config'; import { Content } from '../content/content'; import { DomController } from '../../platform/dom-controller'; import { GestureController, GESTURE_GO_BACK_SWIPE, BlockerDelegate } from '../../gestures/gesture-controller'; -import { isTrueProperty, assert } from '../../util/util'; +import { isTrueProperty, Side, isRightSide, assert } from '../../util/util'; import { Keyboard } from '../../platform/keyboard'; import { MenuContentGesture } from './menu-gestures'; -import {Menu as MenuInterface, Side} from '../app/menu-interface'; +import { Menu as MenuInterface } from '../app/menu-interface'; import { MenuController } from '../app/menu-controller'; import { MenuType } from './menu-types'; import { Nav } from '../nav/nav'; @@ -206,13 +206,18 @@ export class Menu implements RootNode, MenuInterface, OnInit, OnDestroy { private _events: UIEventManager; private _gestureBlocker: BlockerDelegate; private _isPane: boolean = false; - private _side: Side = 'start'; + private _side: Side; /** * @hidden */ isOpen: boolean = false; + /** + * @hidden + */ + isRightSide: boolean = false; + /** * @hidden */ @@ -262,18 +267,17 @@ export class Menu implements RootNode, MenuInterface, OnInit, OnDestroy { * @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'; + get side(): Side { + return this._side; } set side(val: Side) { - this._side = val; - // Update gesture edge - if (this._gesture) - this._gesture.setEdges(this.side); + this.isRightSide = isRightSide(val, this._plt.isRTL); + if (this.isRightSide) { + this._side = 'right'; + } else { + this._side = 'left'; + } } /** @@ -337,6 +341,7 @@ export class Menu implements RootNode, MenuInterface, OnInit, OnDestroy { this._gestureBlocker = _gestureCtrl.createBlocker({ disable: [GESTURE_GO_BACK_SWIPE] }); + this.side = 'start'; } /** @@ -485,13 +490,11 @@ export class Menu implements RootNode, MenuInterface, OnInit, OnDestroy { } // user has finished dragging the menu + const isRightSide = this.isRightSide; const opening = !this.isOpen; - let shouldComplete = false; - if (opening) { - shouldComplete = (this.side === 'right') ? shouldCompleteLeft : shouldCompleteRight; - } else { - shouldComplete = (this.side === 'right') ? shouldCompleteRight : shouldCompleteLeft; - } + const shouldComplete = (opening) + ? isRightSide ? shouldCompleteLeft : shouldCompleteRight + : isRightSide ? shouldCompleteRight : shouldCompleteLeft; this._getType().setProgressEnd(shouldComplete, stepValue, velocity, (isOpen: boolean) => { console.debug('menu, swipeEnd', this.side); diff --git a/src/components/menu/test/menu.spec.ts b/src/components/menu/test/menu.spec.ts index 21a97c45c6..c90fbf1074 100644 --- a/src/components/menu/test/menu.spec.ts +++ b/src/components/menu/test/menu.spec.ts @@ -1,6 +1,5 @@ import { MenuController } from '../../app/menu-controller'; -import {mockMenu, mockPlatform} from '../../../util/mock-providers'; -import {Side} from '../../app/menu-interface'; +import { mockMenu, mockPlatform } from '../../../util/mock-providers'; describe('MenuController', () => { @@ -217,7 +216,7 @@ describe('MenuController', () => { let someMenu = mockMenu(); menuCtrl._register(someMenu); - ['left', 'right'].forEach((side: Side) => { + ['left', 'right'].forEach((side: any) => { someMenu.side = side; expect(someMenu.side).toEqual(side); @@ -226,27 +225,6 @@ describe('MenuController', () => { }); }); - 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/components/modal/test/basic/pages/page-one/page-one.ts b/src/components/modal/test/basic/pages/page-one/page-one.ts index af474bbbfe..545c68ca9a 100644 --- a/src/components/modal/test/basic/pages/page-one/page-one.ts +++ b/src/components/modal/test/basic/pages/page-one/page-one.ts @@ -16,7 +16,7 @@ export class PageOne { console.log('platforms', plt.platforms()); console.log('mode', config.get('mode')); - console.log('isRTL', plt.isRTL()); + console.log('isRTL', plt.isRTL); console.log('core', plt.is('core')); console.log('cordova', plt.is('cordova')); console.log('mobile', plt.is('mobile')); diff --git a/src/components/split-pane/split-pane.scss b/src/components/split-pane/split-pane.scss index 6e17e1b7e7..0f8f17b9ae 100644 --- a/src/components/split-pane/split-pane.scss +++ b/src/components/split-pane/split-pane.scss @@ -22,7 +22,7 @@ $split-pane-side-max-width: 28% !default; contain: strict; } -.split-pane.split-pane-rtl { +[dir="rtl"] .split-pane { flex-direction: row-reverse; } diff --git a/src/components/split-pane/split-pane.ts b/src/components/split-pane/split-pane.ts index c333047b7a..e9e43188aa 100644 --- a/src/components/split-pane/split-pane.ts +++ b/src/components/split-pane/split-pane.ts @@ -217,9 +217,6 @@ export class SplitPane extends Ion implements RootNode { renderer: Renderer ) { super(config, elementRef, renderer, 'split-pane'); - if (_plt.isRTL()) { - this.setElementClass('split-pane-rtl', true); - } } /** diff --git a/src/gestures/slide-edge-gesture.ts b/src/gestures/slide-edge-gesture.ts index 946842f6fe..3f4c54e66a 100644 --- a/src/gestures/slide-edge-gesture.ts +++ b/src/gestures/slide-edge-gesture.ts @@ -7,7 +7,8 @@ import { Platform } from '../platform/platform'; * @hidden */ export class SlideEdgeGesture extends SlideGesture { - public edges: Array; + + public edges: string[]; public maxEdgeStart: any; private _d: any; @@ -23,31 +24,42 @@ export class SlideEdgeGesture extends SlideGesture { } setEdges(edges: string) { - this.edges = edges.split(' '); + const isRTL = this.plt.isRTL; + this.edges = edges.split(' ').map((value) => { + switch (value) { + case 'start': return isRTL ? 'right' : 'left'; + case 'end': return isRTL ? 'left' : 'right'; + default: value; + } + }); } canStart(ev: any): boolean { - let coord = pointerCoord(ev); + const coord = pointerCoord(ev); this._d = this.getContainerDimensions(); return this.edges.every(edge => this._checkEdge(edge, coord)); } getContainerDimensions() { + const plt = this.plt; return { left: 0, top: 0, - width: this.plt.width(), - height: this.plt.height() + width: plt.width(), + height: plt.height() }; } - _checkEdge(edge: string, pos: any) { + _checkEdge(edge: string, pos: any): boolean { + const data = this._d; + const maxEdgeStart = this.maxEdgeStart; switch (edge) { - case 'left': return pos.x <= this._d.left + this.maxEdgeStart; - case 'right': return pos.x >= this._d.width - this.maxEdgeStart; - case 'top': return pos.y <= this._d.top + this.maxEdgeStart; - case 'bottom': return pos.y >= this._d.height - this.maxEdgeStart; + case 'left': return pos.x <= data.left + maxEdgeStart; + case 'right': return pos.x >= data.width - maxEdgeStart; + case 'top': return pos.y <= data.top + maxEdgeStart; + case 'bottom': return pos.y >= data.height - maxEdgeStart; } + return false; } } diff --git a/src/navigation/nav-controller-base.ts b/src/navigation/nav-controller-base.ts index eced9957db..2b95718258 100644 --- a/src/navigation/nav-controller-base.ts +++ b/src/navigation/nav-controller-base.ts @@ -587,7 +587,7 @@ export class NavControllerBase extends Ion implements NavController { direction: opts.direction, duration: (opts.animate === false ? 0 : opts.duration), easing: opts.easing, - isRTL: this._config.plt.isRTL(), + isRTL: this._config.plt.isRTL, ev: opts.ev, }; diff --git a/src/navigation/swipe-back.ts b/src/navigation/swipe-back.ts index 14deff115b..b4f09cb37c 100644 --- a/src/navigation/swipe-back.ts +++ b/src/navigation/swipe-back.ts @@ -19,7 +19,7 @@ export class SwipeBackGesture extends SlideEdgeGesture { ) { super(plt, plt.doc().body, { direction: 'x', - edge: 'left', + edge: 'start', maxEdgeStart: 75, threshold: 5, zone: false, @@ -30,14 +30,6 @@ 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 { @@ -58,7 +50,7 @@ export class SwipeBackGesture extends SlideEdgeGesture { ev.preventDefault(); ev.stopPropagation(); - let stepValue = (slide.distance / slide.max); + const stepValue = (slide.distance / slide.max); this._nav.swipeBackProgress(stepValue); } diff --git a/src/platform/platform.ts b/src/platform/platform.ts index 9c6b7574c1..c8c8977cd8 100644 --- a/src/platform/platform.ts +++ b/src/platform/platform.ts @@ -30,6 +30,7 @@ import { removeArrayItem } from '../util/util'; * @demo /docs/demos/src/platform/ */ export class Platform { + private _win: Window; private _doc: HTMLDocument; private _versions: {[name: string]: PlatformVersion} = {}; @@ -50,8 +51,6 @@ export class Platform { private _isPortrait: boolean = null; private _uiEvtOpts = false; - static dirChanged: EventEmitter = new EventEmitter(); - /** @hidden */ zone: NgZone; @@ -318,8 +317,8 @@ export class Platform { * @param {boolean} updateDocument */ setDir(dir: string, updateDocument: boolean) { - this._dir = (dir || '').toLowerCase(); - Platform.dirChanged.emit(this._dir); + this._dir = dir = (dir || '').toLowerCase(); + this.isRTL = (dir === 'rtl'); if (updateDocument !== false) { this._doc['documentElement'].setAttribute('dir', dir); @@ -344,9 +343,7 @@ export class Platform { * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir) * @returns {boolean} */ - isRTL(): boolean { - return (this._dir === 'rtl'); - } + isRTL: boolean; /** * Set the app's language and optionally the country code, which will update diff --git a/src/util/util.ts b/src/util/util.ts index 915fefffcf..abb67fb51d 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -128,6 +128,18 @@ export function isCheckedProperty(a: any, b: any): boolean { return (a == b); // tslint:disable-line }; +export type Side = 'left' | 'right' | 'start' | 'end'; + +export function isRightSide(side: Side, isRTL: boolean): boolean { + switch (side) { + case 'right': return true; + case 'left': return false; + case 'end': return !isRTL; + // 'start' by default + default: return isRTL; + } +} + /** @hidden */ export function reorderArray(array: any[], indexes: {from: number, to: number}): any[] {